OCR pipeline: đừng đá tất cả bằng GPU
Một playbook giúp team builder quyết định khi nào dùng OCR model nặng, khi nào đi đường nhanh, và khi nào nên bỏ qua luôn.
Bụi Wire9 giờ tối, một bạn lead nhắn mình: “Có nên đưa OCR bằng vision-language model vào production không? File khách gửi toàn PDF scan, bảng biểu, chữ bé li ti.”
Câu hỏi nghe như chọn model, nhưng thật ra là chọn chiến thuật triển khai. Nếu bạn bê nguyên một model 3B parameter lên GPU rồi bắt mọi tài liệu chạy qua cùng một luồng, hệ thống sẽ giống đội bóng chỉ biết lên bóng bằng một cánh: lúc gặp sân đẹp thì mượt, gặp mặt sân xấu là tự vấp.
Luận điểm của mình: OCR pipeline tốt không bắt đầu bằng model mạnh nhất, mà bắt đầu bằng routing rõ ràng giữa tài liệu dễ, tài liệu khó, và tài liệu không đáng xử lý bằng AI.

Sơ đồ tóm tắt ý chính của bài viết.
Mục tiêu: ra quyết định, không sưu tầm model
OCR — optical character recognition, nhận dạng chữ từ ảnh hoặc PDF — từng khá thẳng: đưa ảnh vào, lấy text ra. Nhưng với PDF nhiều trang, layout dày, bảng, footnote, chữ nhỏ, câu chuyện đổi khác. Các model kiểu vision-language model — model vừa nhìn ảnh vừa sinh text — có thể đọc tài liệu theo ngữ cảnh tốt hơn OCR truyền thống, nhưng đổi lại bạn phải trả bằng GPU, latency, memory và công sức kiểm soát output.
Baidu’s Unlimited-OCR trong tutorial gốc đi theo hướng khá thực dụng: load model 3B từ Hugging Face, tự chọn bfloat16 hoặc float16 tùy GPU, xử lý ảnh đơn, rồi mở rộng sang PDF nhiều trang bằng PyMuPDF và infer_multi().
Nhưng với team đang build thật, câu hỏi không phải “chạy được không?”. Câu hỏi nên là:
Tài liệu nào xứng đáng vào luồng OCR nặng, tài liệu nào nên đi luồng nhẹ, và khi nào phải dừng trước khi đốt GPU vô ích?
Nói thẳng ra thì, bạn không cần một tiền đạo ghi bàn từ mọi vị trí. Bạn cần đội hình biết chuyền bóng đúng người.
Ba lựa chọn triển khai nên đặt cạnh nhau
Đừng bắt đầu bằng “dùng Unlimited-OCR hay không”. Hãy bắt đầu bằng ba lane xử lý:
| Lane | Dùng khi nào | Cái được | Cái mất |
|---|---|---|---|
| Base OCR lane | Ảnh/PDF sạch, chữ rõ, layout đơn giản | Nhanh hơn, ít bước hơn | Dễ hụt chữ nhỏ hoặc layout dày |
| Tiled OCR lane | Trang dày, bảng nhiều, chữ nhỏ, scan độ phân giải cao | Giữ được chi tiết tốt hơn | Chậm hơn, tốn GPU hơn |
| Reject / manual lane | File mờ, lệch nặng, scan lỗi, tài liệu rủi ro cao | Không tạo output giả tự tin | Cần quy trình fallback |
Trong Unlimited-OCR, “Base mode” dùng một view ảnh 1024 pixel, tắt crop để giảm độ phức tạp. Hợp với trang sạch, tài liệu in rõ, layout không quá đông. Còn “Gundam mode” dùng tiled inference — chia ảnh thành nhiều ô nhỏ để model nhìn kỹ hơn — cộng với global view của cả trang. Nó giống đội hình có thêm hậu vệ biên hỗ trợ khi đối thủ ép sân: hiệu quả hơn trong tình huống khó, nhưng không miễn phí.
Quyết định quan trọng: đừng cho mọi request chạy Gundam mode chỉ vì nó có vẻ “xịn” hơn. Với tài liệu dễ, bạn đang tự kéo hệ thống vào hiệp phụ không cần thiết.
Checklist trước khi viết dòng code đầu tiên
Trước khi dựng pipeline, mình sẽ khóa 6 biến này trong một file config, không hardcode rải rác:
- Loại input: ảnh đơn, PDF scan, PDF text-native, hay hỗn hợp.
- Độ phân giải và kích thước trang: ảnh quá lớn cần resize/crop có kiểm soát.
- Mật độ layout: có bảng, cột, footnote, header/footer lặp lại không.
- Yêu cầu output: plain text, Markdown, JSON theo schema, hay giữ cấu trúc bảng.
- Ngưỡng routing: khi nào Base đủ, khi nào chuyển tiled, khi nào reject.
- Cách đo chất lượng: không chỉ “nhìn có vẻ đúng”, mà cần sample có ground truth hoặc review checklist.
Một cấu hình tối thiểu có thể trông như thế này:
ocr:
default_lane: base
lanes:
base:
image_size: 1024
crop_mode: false
max_new_tokens: 4096
tiled:
crop_mode: true
tile_size: 768
max_new_tokens: 8192
routing:
use_tiled_if:
min_text_size_small: true
has_dense_tables: true
scan_quality: medium_or_better
reject_if:
scan_quality: poor
page_rotation_unknown: true
contains_sensitive_fields: true
Đây không phải config “chuẩn vũ trụ”. Nó là cách ép team nói rõ điều kiện quyết định. Khi production có lỗi, bạn còn biết thẻ vàng nằm ở lane nào.
Một buổi dựng prototype: đủ để biết có nên đi tiếp
Bạn có thể làm một bản kiểm chứng trong một buổi chiều, miễn đừng nhầm prototype với production.
Bước 1: Chuẩn bị môi trường GPU và dtype
bfloat16 và float16 là kiểu số giảm độ chính xác để tiết kiệm VRAM, thường dùng khi chạy model lớn trên GPU. bfloat16 ổn hơn trên phần cứng hỗ trợ tốt; float16 phổ biến hơn nhưng có thể nhạy với một số phép tính.
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.bfloat16 if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else torch.float16
print(device, dtype)
Nếu máy không có CUDA GPU, đừng cố benchmark. Bạn chỉ đang đo sự kiên nhẫn của bản thân.
Bước 2: Tạo bộ tài liệu test có chủ đích
Đừng chỉ dùng một ảnh đẹp. Hãy tạo ít nhất ba nhóm:
- Trang sạch, chữ rõ.
- Trang nhiều bảng và footnote.
- PDF nhiều trang có header/footer lặp lại.
Ví dụ cụ thể: giả sử team bạn làm hệ thống đọc hồ sơ vay vốn. Một trang là thông tin cá nhân, một trang là bảng thu nhập, một trang là điều khoản scan hơi mờ. Nếu chỉ test trang đầu, pipeline sẽ tự tin quá sớm.
Bước 3: Chạy Base trước, tiled sau
Pseudo-code nên giữ routing tách khỏi inference:
def choose_lane(page_meta):
if page_meta["scan_quality"] == "poor":
return "reject"
if page_meta["has_dense_tables"] or page_meta["small_text"]:
return "tiled"
return "base"
for page in pages:
lane = choose_lane(page.meta)
if lane == "reject":
save_for_manual_review(page)
elif lane == "base":
run_base_ocr(page.image)
else:
run_tiled_ocr(page.image)
Điểm cần giữ: routing là logic sản phẩm, không phải chi tiết model. Sau này bạn đổi Unlimited-OCR sang model khác, lane vẫn còn giá trị.
Bước 4: Với PDF nhiều trang, kiểm soát ngữ cảnh
PyMuPDF giúp render từng trang PDF thành ảnh. Với multi-page parsing, các hàm kiểu infer_multi() hữu ích vì có thể xử lý xuyên trang, nhất là khi tài liệu có bảng kéo dài hoặc mục lục liên quan nhiều trang.
Nhưng nhớ thuật ngữ này: long-context generation — sinh output dài trong vùng ngữ cảnh lớn. Nó giúp model giữ nhiều thông tin hơn, nhưng cũng mở cửa cho lỗi lặp, trôi format, hoặc “đọc nhầm trang 3 sang trang 4”. Vì vậy cần đặt max_new_tokens, repetition control, và schema output rõ.
Bẫy triển khai: output đẹp chưa chắc dùng được
Có ba lỗi mình thấy team builder dễ dính.
Một là thiếu schema. Nếu downstream cần JSON mà bạn để model trả Markdown tự do, bạn sẽ viết parser kiểu chữa cháy. Nên yêu cầu format ngay từ prompt và validate sau khi sinh.
required_keys = {"pages", "tables", "warnings"}
result = parse_json(model_output)
missing = required_keys - set(result.keys())
if missing:
raise ValueError(f"Missing keys: {missing}")
Hai là benchmark nhầm mục tiêu. Với OCR pipeline, latency trung bình chưa đủ. Bạn cần nhìn theo nhóm tài liệu: clean page, dense table, multi-page PDF, rejected scan. Nếu có điều kiện, hãy mượn tư duy Pareto frontier — đường biên tradeoff giữa throughput và latency — từ benchmark serving LLM: cấu hình nào vừa nhanh vừa đủ tốt, cấu hình nào chỉ đẹp trên một trục nhưng làm hỏng trục còn lại.
Ba là không có lane từ chối. AI system tốt không phải lúc nào cũng trả lời. Với giấy tờ pháp lý, tài chính, y tế, output sai nhưng trông gọn gàng còn nguy hiểm hơn báo “không đọc được”. Đừng ngại cho pipeline quyền giơ cờ dừng trận.
Nếu là mình, mình sẽ chọn thế này
Với team nhỏ đang xử lý tài liệu thật, mình sẽ không triển khai Unlimited-OCR như một endpoint duy nhất. Mình sẽ đi theo quyết định sau:
- Dùng Base lane cho tài liệu sạch, cần tốc độ, ít layout phức tạp.
- Dùng tiled lane cho trang scan dày, bảng biểu, chữ nhỏ, hoặc khi mất chi tiết gây thiệt hại nghiệp vụ.
- Bỏ qua hoặc review thủ công nếu input quá xấu, rủi ro cao, hoặc không có cách kiểm chứng output.
- Chỉ dùng multi-page inference khi có quan hệ xuyên trang thật sự; nếu từng trang độc lập, xử lý page-by-page sẽ dễ debug hơn.
- Đổi quyết định khi log cho thấy một lane đang ăn quá nhiều GPU, lỗi tập trung ở một loại tài liệu, hoặc downstream phải sửa tay quá nhiều.
Sau bài này, điểm bạn nên nghĩ khác là: OCR không phải một bước “extract text”, mà là một hệ thống phân luồng rủi ro. Model mạnh chỉ là một cầu thủ trong đội hình; thắng hay không nằm ở cách bạn xếp người, thay người, và biết lúc nào nên đá an toàn.
---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng
Nguồn tham khảo
- How to Build an End-to-End OCR Pipeline with Baidu’s Unlimited-OCR for High-Resolution Images and Multi-Page PDF Parsing - MarkTechPost
- Validating Distributed LLM Serving Benchmarks with NVIDIA srt-slurm, SLURM Recipes, Parameter Sweeps, and Pareto Analysis - MarkTechPost
- What Is HyDE? How to Improve RAG with Hypothetical Documents
- How to Build an AI Feature With Gemini: A Practical Guide to Prompt Engineering for Developers
- Product Experiment Counterfactual Methods for Estimating the Effects of AI Prompt Engineering