Skip to content

Nôm — Lựa chọn component & Benchmark

Tài liệu này ghi lại các component Nôm phụ thuộc, vì sao chọn từng cái, và số benchmark khi đã đo. Số tái lập được — mọi claim "đã đo" đều có script trong scripts/ chạy lại được.

Cập nhật lần cuối: 2026-04-25.


TL;DR — stack khuyến nghị

ModuleComponentTrạng tháiLý do
nom.textPure stdlib (unicodedata)đã ship v0.0.19M ops/s, zero deps, tất định
nom.doc.ocr (primary)VietOCR (Transformer, VN-specialized)dự kiến v0.1Diacritic-aware; build trên corpus VN
nom.doc.ocr (fallback)Tesseract 5 với traineddata viedự kiến v0.1Luôn có, baseline ~70% accuracy
nom.doc.ocr (cloud)PaddleOCR PP-OCRv5dự kiến v0.194.5% trên OmniDocBench, pipeline modular
nom.doc.pdfPyMuPDF (fitz)dự kiến v0.1Nhanh hơn pdfplumber 19× trên PDF thực
nom.llm (local mặc định)Qwen3-8B qua Ollamadự kiến v0.1Apache 2.0, chạy trên GPU consumer, VN mạnh
nom.llm (cloud max)Qwen3-235B-A22B / GPT-4o / Claudedự kiến v0.1Top-tier khi ngân sách cho phép
nom.llm (vision+doc)Qwen2.5-VL-72B-Instructdự kiến v0.1Best open vision-language cho extraction tài liệu có cấu trúc

Nguồn cho mỗi claim được liệt kê dưới mỗi section module.


Module: nom.textđã ship

Làm gì

Tiện ích pure-Python cho text tiếng Việt:

  • normalize(s) — Unicode NFC normalization
  • strip_diacritics(s) — chuyển sang ASCII (đ → d, é → e, ...)
  • has_diacritics(s) — boolean
  • is_vietnamese(s) — phát hiện heuristic (chạy được cả trên text đã strip dấu)
  • fix_diacritics(s) — khôi phục dấu trên các từ đã strip phổ biến

Test

22/22 pass (pytest tests/).

Độ chính xác — đo 2026-04-25

Corpus: benchmarks/data/diacritic_eval_v0.txt — 55 câu VN hand-curated trên 4 register (15 hợp đồng, 12 official, 15 hội thoại, 13 tin tức), license CC0.

Metricbaseline v0.0.1
Câu55
Từ776
Từ chứa dấu666
Word accuracy tổng40.59%
Diacritic recall tổng34.08%

Theo register:

RegisterWord accuracyDiacritic recall
hợp đồng / kinh doanh50.00%44.32%
tài liệu official39.33%29.33%
hội thoại44.15%39.37%
tin tức / long-form29.13%23.33%

Đây là baseline v0.0.1 trung thực với bảng từ vựng curated ~120 entry hiện tại. Đường rule-based là stopgap zero-dependency. Roadmap thay thế nó, không mở rộng:

Phiên bảnCách tiếp cậnDependencyĐộ chính xác đo
v0.0.1 (mặc định hiện tại)Lookup bảng rule-basedkhông41.06%
v0.2.7 cloudLLM-backed (fix_diacritics(..., llm=...))bất kỳ nom.llm.LLM95.37% với OpenAI(gpt-4o-mini)
v0.2.7 localLLM-backed qua Ollamanom-vn[llm] + ollama pull gemma3:4b87.90% với Ollama("gemma3:4b")
v0.2.7 local-maxLLM-backed qua Ollamanom-vn[llm] + ollama pull gemma4:e4b93.18% với Ollama("gemma4:e4b")
v0.0.2Wrap mô hình PyVi hoặc DistilBERToptional nom-vn[diacritics]tạm hoãn — vấn đề license/format

Lựa chọn backend v0.0.2 đang được đánh giá

Tuỳ chọnNguồnCách tiếp cậnAcc công bốLicense
PyVi ViUtils.add_accents()trungtv/pyviwrapper mô hình đã trainmature, ~80%+MIT
DistilBERT-Viet-DiacriticHF: saeliddp/...DistilBERT token classification~90%+Apache 2.0
restore_vietnamese_diacriticsduongntbkTransformer seq2seq94.05%MIT
vietai/aivivn-vn-diacriticvietaiTransformer seq2seqApache 2.0

Lựa chọn dựa trên: trọng lượng dependency, tốc độ inference CPU-only, tương thích license, và phép đo của chính chúng tôi trên diacritic_eval_v0.txt. Chúng tôi không công bố số dự đoán — bài release v0.0.2 sẽ kèm số đo trên cùng corpus.

Tái lập: python benchmarks/accuracy/bench_diacritics.py Baseline track tại: benchmarks/results/baseline_v0.0.1.json

Lưới backend / hardware diacritic — đo 2026-04-26

Cùng weights Toshiiiii1 T5, ba đường execution. Mọi cái đều đạt cùng 97.81 % word accuracy — export đúng, thứ duy nhất khác là latency.

BackendHardwareWord accMean msp50 msGhi chú
PyTorchRTX 3090 (CUDA)97.81 %152148Target production cho người dùng có GPU
PyTorchCPU (8 cores)97.81 %377357Chấp nhận được cho job batch / overnight
ONNX RuntimeCPU (8 cores)97.81 %410394Hơi chậm hơn PyTorch CPU

ONNX runtime không thêm giá trị ở đây. PyTorch hiện đại với MKL-DNN đã optimal cho T5 200 M ở eager mode. ONNX đáng revisit chỉ với INT8 quantization (typical speedup 2-3× CPU với một số chi phí accuracy) — chưa đo ở đây; để theo dõi tiếp.

Chúng tôi không ship bước export ONNX trong nom-vn[diacritic-hf]. User thực sự cần ONNX (deploy cross-platform không có stack Python+PyTorch) tự optimum-cli export onnx ...; export tất định.

Mô hình seq2seq diacritic VN có sẵn — đo 2026-05-02

Bài học rút ra: chúng tôi từng bỏ qua bước đo các mô hình khôi phục dấu tiếng Việt giấy phép Apache công khai trước khi đề xuất chưng cất một mô hình 100M tham số riêng. Người dùng đã chỉ ra; đo lại. Một mô hình có sẵn vượt mọi lựa chọn từng thử, kể cả gpt-4o-mini trên cloud.

Cùng ngữ liệu 55 câu (CC0). Bộ đo: benchmarks/accuracy/bench_diacritic_hf.py. Phần cứng: RTX 3090.

Mô hìnhGiấy phépDiskĐộ chính xác từTrung bình giây/câuGhi chú
Toshiiiii1/Vietnamese_diacritics_restoration_5thApache 2.0~1 GB97,81 %0,152T5 200 M, safetensors
(cloud gpt-5.4-mini)proprietary96,12 % / 92,06 % / 78,68 %1,130,75 USD vào / 4,50 USD ra mỗi 1M token; thắng gpt-4o-mini ở văn bản hành chính/hội thoại nhưng kém ở văn học
(cloud gpt-4o-mini)proprietary95,37 % / 89,51 % / 81,84 %1,270,15 USD vào / 0,60 USD ra mỗi 1M token; điểm cân bằng chi phí–chất lượng của OpenAI
(cloud gpt-5.4-nano)proprietary93,82 % / 85,15 % / 73,18 %1,270,20 USD vào / 1,25 USD ra mỗi 1M token; mới hơn nhưng kém gpt-4o-mini ở mọi loại văn bản; bỏ qua
(cloud claude-haiku-4-5)proprietary96,19 % / 90,49 % / 81,68 %1,461,00 USD vào / 5,00 USD ra mỗi 1M token; nhỉnh hơn gpt-4o-mini ở hành chính / hội thoại nhưng đắt hơn ~6 lần
local gemma4:e4b Q4Apache 2.09,6 GB93,18 % / 87,91 % / 77,78 %0,88LLM local mạnh nhất; cần ≥10 GB VRAM
local gemma3:4b Q4Apache 2.03,3 GB89,06 % / 79,70 % / 62,05 %0,91-3 đến -16 pp so với gemma4:e4b; cho máy yếu hơn
local qwen3:1.7b Q4Apache 2.01,4 GB16,60 %0,43Dưới sàn quy tắc 41 %; bỏ qua
bmd1905/vietnamese-correctionApache 2.0~1,6 GB15,57 %0,301Thất bại — huấn luyện cho sửa chính tả, không cho khôi phục dấu thuần
qthuan2604/BARTPho_Syllable_Restore_Diacritics_VietnameseMIT~1,6 GBChưa đoTác giả tự báo CER 38,85 %, dưới sàn quy tắc; bỏ qua
(sàn quy tắc)041,06 %<0,001Mức sàn tham chiếu

Ô của các mô hình cloud / LLM ghi 3 số theo thứ tự kinh doanh 55 + UDHR 72 → hội thoại Tatoeba 300 → văn học UD-VTB 800. Đo 2026-05-02 trên RTX 3090 (LLM local) hoặc qua API (cloud). Bảng đầy đủ theo từng loại văn bản nằm trong docs/tasks/diacritic-restoration.md.

Toshiiiii1 thắng dứt khoát:

  • +2,44 pp so với gpt-4o-mini trên cloud (97,81 % vs 95,37 %).
  • +9,91 pp so với LLM local mạnh nhất (gemma3:4b 87,90 %).
  • Nhanh gấp 8 lần so với LLM cloud (0,152 s vs 1,27 s) và gấp 7 lần so với LLM local.
  • Apache 2.0 và định dạng safetensors — phát hành được trọn vẹn theo nguyên tắc không dùng pickle.
  • Nhỏ hơn ~10 lần trên disk so với gemma4:e4b (1 GB vs 9,6 GB).

Hành động: rút khuyến nghị "distil mô hình diacritic VN 100 M" trong docs/training_plan_2026q2.md (v0.2.12) — không có gì để distil tới mà mô hình Apache công khai chưa cover. Thêm như đường production khuyến nghị:

python
from nom.text import fix_diacritics
from nom.text.diacritic_models import HFDiacriticModel

restorer = HFDiacriticModel()  # mặc định Toshiiiii1, lazy-load lần gọi đầu
out = fix_diacritics("Hop dong nay duoc lap ngay 14 thang 3", model=restorer)
# → 'Hợp đồng này được lập ngày 14 tháng 3'

Cài: pip install "nom-vn[diacritic-hf]" (kéo transformers<5 + torch + sentencepiece). Cap transformers bắt buộc: ≥5.6 có regression slow-T5-tokenizer làm vỡ load mô hình Toshiiiii1.

Vì sao chúng tôi miss ban đầu: đợt v0.2.7 → v0.2.10 tập trung vào khôi phục dấu LLM-backed (giả định trước là "mọi mô hình diacritic VN tốt đều ship pickle hoặc license NC"). Bảng "v0.0.2 backend options under evaluation" trong benchmark.md từ thời v0.0.1 vẫn liệt kê các ứng viên Apache là "tạm hoãn — vấn đề license/format" mà không có phép đo thực. Audit 2026-04-26 tìm được một cái thoả mọi ràng buộc.

Cross-check: model card Toshiiiii1 không báo metric, nên ta không có số phía trên để so. 97.81 % của ta trên corpus 55 câu.

Đo nhiều corpus — ma trận 4 register. Đo lần đầu 2026-04-26 (business + literary), mở rộng 2026-04-29 (conversational + formal/legal):

Eval corpusCâuRegisterWord accMean ms/câu
udhr_vi/diacritic_eval_udhr.txt72hành chính / pháp lý (UDHR)98.14 %221
diacritic_eval_v0.txt55kinh doanh / hợp đồng / tin tức97.81 %152
tatoeba_vi/diacritic_eval_300.txt300hội thoại (Tatoeba)93.94 %82
ud_vi_vtb/test.conllu800văn học cổ điển (treebank VTB)89.40 %269

Spread = 8.74 pp (98.14 − 89.40). Drop là monotonic từ formal sang văn học, đó là cách register-shift nhìn trong thực tế — không phải mode fail đơn lẻ mà là gradient. Hội thoại nằm ~4 pp dưới business, văn học thêm ~4 pp dưới hội thoại. Mô hình register-overfit về tiếng Việt formal/business hiện đại, như mong đợi từ dữ liệu training; vẫn dùng được mọi nơi nhưng peak tuyệt đối ở các register đã train trên đó.

Một bug methodology bench đã bắt và fix. Run UD-VTB đầu báo word accuracy 54.14 % và 0/800 sentence-exact. 0/800 là dấu hiệu — kể cả mô hình tầm tầm cũng land vài câu. Vấn đề: treebank UD ship câu ở dạng tokenized (nhỉ ? " . với khoảng trắng quanh mọi dấu câu, quy ước parsing tool đòi), trong khi mô hình seq2seq output tiếng Việt tự nhiên (nhỉ?".). So sánh list token .split() raw giữa hai cái dịch alignment ở dấu câu đầu và token phía sau so wrong-vs-wrong.

Chúng tôi thêm bước normalize_punct() trong benchmarks/accuracy/bench_diacritic_hf_udvtb.py strip khoảng trắng trước/sau dấu câu trên cả hai phía trước khi so. Cô lập chất lượng diacritic khỏi quy ước punctuation-spacing. Số bên trên là sau normalize.

Hướng dẫn production register-conditional:

RegisterBest có sẵnWord accGhi chú
Hành chính / pháp lý (UDHR-like)nrl-ai/vn-diacritic-vit5-base99.43 %Fine-tune ViT5-base v0.2.25 của ta; +1.29 pp so với Toshiiiii1
Kinh doanh hiện đại / hợp đồng / tin tứcToshiiiii1/Vietnamese_diacritics_restoration_5th97.81 %Thắng gpt-4o-mini 95.37 % ở register này; nrl-ai/vit5-base 4.4 pp sau
Hội thoại (Tatoeba)nrl-ai/vn-diacritic-vit5-base94.12 %+0.18 pp so với Toshiiiii1 (93.94)
Văn học cổ điển (UD-VTB)Toshiiiii1/... (vẫn hữu ích)89.40 %Dưới business nhưng cao hơn nhiều rule baseline (41 %); fail chủ yếu là mơ hồ danh từ riêng (HùngHưng) và từ register thiểu số
Mixed chungToshiiiii1/... cho hầu hết case; cloud LLM làm fallback89-98 %Gap 8.7 pp giữa các register Toshiiiii1 là thật nhưng có giới hạn

nrl-ai/vn-diacritic-vit5-base của chúng tôi (publish 2026-04-30): Fine-tune ViT5-base trên 500K cặp Wikipedia, 5 epoch cosine LR, bf16, 185 phút trên RTX 3090. Apache-2.0, ~900 MB safetensors. Cổng adopt nghiêm ngặt (business >= 96 % VÀ literary > 89.40 %) fail trên business (94.98 %), nên KHÔNG phải tên canonical nrl-ai/vn-diacritic-restoration (giữ cho mô hình tương lai qua được cổng). Nhưng đó là mô hình diacritic VN cân bằng register tốt nhất chúng tôi đã train — SOTA trên tiếng Việt formal/legal (99.43 %, +1.29 pp so với Toshiiiii1) và hội thoại (94.12 %, +0.18 pp).

RegisterToshiiiii1nrl-ai/vit5-baseΔ
formal_udhr98.14 %99.43 %+1.43
business_5597.81 %94.98 %-4.37
conversational_30093.94 %94.12 %+0.39
literary_udvtb89.40 %90.24 %-0.01

Dùng qua HFDiacriticModel(model_id="nrl-ai/vn-diacritic-vit5-base"). Config training đầy đủ + tái lập: xem HF model card.

Hai bài học methodology rơi vào rule multi-corpus register-coverage:

  1. Đo nhiều corpus là bắt buộc cho claim adopt. Số chất lượng single-corpus che yếu register-shift hoặc artifact benchmark.
  2. Metric không hợp lý đòi hỏi điều tra. Bất cứ thứ gì peg ở 0 % hoặc 100 % trên mô hình thật gần như chắc chắn là bug bench, không phải kết quả đúng. Chúng tôi bắt được một cái cách này.

Tái lập:

bash
# Build slice eval per-register (tất định, không cần mạng).
python benchmarks/data/tatoeba_vi/build_diacritic_eval.py
python benchmarks/data/udhr_vi/build_diacritic_eval.py

# Bench Toshiiiii1 trên 4 register.
python benchmarks/accuracy/bench_diacritic_hf.py \
    Toshiiiii1/Vietnamese_diacritics_restoration_5th \
    --json benchmarks/results/baseline_diacritic_toshiiiii_t5.json
python benchmarks/accuracy/bench_diacritic_hf.py \
    Toshiiiii1/Vietnamese_diacritics_restoration_5th \
    --corpus benchmarks/data/tatoeba_vi/diacritic_eval_300.txt \
    --json benchmarks/results/baseline_diacritic_toshiiiii_tatoeba300.json
python benchmarks/accuracy/bench_diacritic_hf.py \
    Toshiiiii1/Vietnamese_diacritics_restoration_5th \
    --corpus benchmarks/data/udhr_vi/diacritic_eval_udhr.txt \
    --json benchmarks/results/baseline_diacritic_toshiiiii_udhr72.json
python benchmarks/accuracy/bench_diacritic_hf_udvtb.py \
    --json benchmarks/results/baseline_diacritic_toshiiiii_udvtb_test.json

Bench thực tế ngoài-phân-phối — đo 2026-04-30

benchmarks/data/spell_correction_eval_real/ là tập 150 câu hand-curated mà mẫu nhiễu lấy từ nguồn lỗi VN thực (slang forum, autocorrect mobile, keystroke Telex/VNI thực, output engine Tesseract+EasyOCR, văn bản pháp lý register formal đã strip, headline tin tức), KHÔNG từ nom.text.noise. Cùng harness áp cho cả mô hình diacritic và spell-correction — sửa chính tả là siêu tập chặt của khôi phục dấu.

Word accuracy tổng hợp trên n=150 (KTC bootstrap 95 %):

Mô hìnhTổngTelexForumLegalNews
nrl-ai/vn-spell-correction-base v0.2.2979.62 [75-85]19.1565.8495.8796.54
nrl-ai/vn-spell-correction-small v0.2.2977.55 [73-83]16.4564.6493.5491.34
Toshiiiii1/Vietnamese_diacritics_restoration_5th77.40 [73-82]18.5460.1193.8094.07
qthuan2604/ViT5_Restore_Diacritics_Vietnamese72.42 [68-77]15.6558.3888.1487.65
nrl-ai/vn-diacritic-vit5-base v0.2.2971.15 [66-76]14.3743.5493.0296.05
nrl-ai/vn-diacritic-small v0.2.2870.27 [65-76]9.3346.2889.1590.35
chamdentimem/ViT5_Vietnamese_Correction51.69 [46-57]17.1462.1961.7634.81
bmd1905/vietnamese-correction-v249.21 [44-55]11.5859.0254.9030.62
iAmHieu2012/vit5-vietnamese-spelling-correction45.57 [39-52]13.5857.6650.6827.35

Tái lập:

bash
python benchmarks/accuracy/bench_spell_correction_real.py <model_id> \
    --json benchmarks/results/baseline_real_<short>.json
python scripts/summarize_ood_bench.py --format markdown --ci

Phát hiện chính sau v0.2.29:

  1. Cả hai tier spell-correction vượt Toshiiiii1 trên OOD. base v0.2.29 dẫn +2.22 pp tổng hợp; small v0.2.29 dẫn +0.15 pp dù ít tham số bằng một nửa. Synthetic 8-split show lead 3-7 pp, OOD show lead +0.15 → +2.22 pp — corpus v2 + comprehensive_noise() đã làm đúng việc khép khoảng cách cho nhiễu thực.
  2. bmd1905 sụp đổ trên OOD (49.21 % tổng) — train trên phân phối nhiễu khác mà không expose đủ pattern strip-diacritic. Bài học cảnh báo: mô hình thắng eval synthetic của chính mình không đảm bảo thắng trên text thật.
  3. Diacritic-only v0.2.29 mixed result. Legal +4.97 pp, News +0.25 pp — formal-text đã cải thiện. Nhưng aggregate -0.35 pp vì informal slices (forum / mobile) regress khi corpus skew về legal. Cho diacritic-only formal use case (legal docs / news): chọn v0.2.29. Cho informal: route qua vn-spell-correction-base thay vì.

JSON baseline commit dưới benchmarks/results/baseline_real_*.json.

Lưới LLM cục bộ — đo 2026-04-26

Mục tiêu: xác định mô hình quantize cục bộ nhỏ nhất chạm độ chính xác diacritic VN dùng được cho deploy máy người dùng. Mọi mô hình serve qua Ollama 0.21.2 (backend llama.cpp) với quantize Q4_K_M (mặc định Ollama), structured output (format JSON schema), think: false, nhiệt độ 0. Hardware: RTX 3090 24GB. Cùng corpus diacritic_eval_v0.txt.

Methodology theo rule verified-benchmarks: 3 warmup call, 55 câu timed, latency per-call gộp.

Mô hìnhQ4 sizeWord accDiacritic recallMean s/câup95 s/câu
gemma4:e4b9.6 GB93.18%92.22%1.371.68
gemma3:4b ⭐ default3.3 GB87.90%87.50%1.101.22
qwen3:8b5.2 GB87.26%86.19%0.931.07
gemma4:e2b7.2 GB85.33%84.55%1.231.47
qwen3:4b2.5 GB47.36%40.48%0.941.06
(rule baseline)041.06%34.88%<0.001
llama3.2:3b2.0 GB38.35%33.69%1.501.95
qwen3:1.7b1.4 GB18.15%6.92%0.630.73
gemma3:1b0.8 GB15.32%3.22%1.411.90
phi4-mini2.5 GB6.95%2.13%2.3210.24
(cloud gpt-4o-mini)95.37%94.61%1.27

Phát hiện:

  1. Họ Gemma thắng cuộc chiến multilingual. Cả gemma3:4bgemma4:e4b đều vượt Qwen3 và Llama ở size tương tự — training multilingual trả lời cho VN.
  2. 3-4B param là sàn cho diacritic VN dùng được. Mô hình sub-2B (gemma3:1b, qwen3:1.7b) đều rớt xuống dưới rule baseline. Cliff chất lượng dốc.
  3. Tên "E2B/E4B" của Gemma 4 nói về active param, không phải file size. Weights multimodal (encoder vision + audio) phình disk: e2b = 7.2 GB Q4, e4b = 9.6 GB Q4. Cho task text-only như khôi phục dấu, đây là dead weight khi download.
  4. gemma3:4b là tradeoff size/chất lượng tốt nhất cho nom-vn. 3.3 GB vừa laptop 4-6 GB VRAM, 87.9% acc trong 7.5pp của cloud ở 1.1 s/câu. Mặc định khuyến nghị cho đường LLM cục bộ.
  5. Llama 3.2 / phi4-mini bị loại. Tokenizer Llama không cân cho VN; phi4-mini hang trên câu khó (p95=10s).
  6. Cloud +2pp so với best local. gpt-4o-mini 95.37% chỉ vượt gemma4:e4b (93.18%) 2.2pp; cả hai đều trên thanh dùng-được thực tế.

Hai engineering fix đã ship để đo được việc này (xem #PRsrc/nom/llm/ollama.py + src/nom/text/normalize.py):

  • Pass think: false cho Ollama. Mode thinking của Qwen3 emit CoT vào field thinking riêng, để content rỗng — qwen3:4b trước đó scored 0%.
  • Switch fix_diacritics(llm=...) sang structured output qua JSON schema format của Ollama. Ép hình dạng {"restored": "..."}; mô hình nhỏ (qwen3:4b, gemma3:4b) không còn ramble giải thích vào response. Chất lượng nhảy từ <50% lên 87-93% trên lưới.

Tái lập một mô hình: python benchmarks/accuracy/bench_diacritics.py --llm ollama --llm-model gemma3:4b --warmup 3 Tái lập lưới đầy đủ: OLLAMA_BASE_URL=http://localhost:11434 ./benchmarks/accuracy/run_diacritic_local_grid.sh JSON tổng hợp: python benchmarks/accuracy/_summarize_diacritic_grid.py JSON per-model: benchmarks/results/local_diacritic_grid/diacritics_*.json

Performance — đo 2026-04-25 trên Python 3.13.9

Corpus: 1.000 câu kiểu hợp đồng tiếng Việt (67.600 ký tự).

FunctionLatency (best of 3)Throughput (ops/s)Throughput (chars/s)
normalize0.11 ms9,066,758612,912,817
has_diacritics0.19 ms5,325,466360,001,468
is_vietnamese0.24 ms4,254,631287,613,073
strip_diacritics5.87 ms170,36811,516,906
fix_diacritics5.12 ms195,12213,190,280
Tham chiếu: stdlib unicodedata.normalize NFC0.12 ms8,365,051565,477,425
Tham chiếu: stdlib unicodedata.normalize NFD0.48 ms2,062,749139,441,817

Tái lập: python benchmarks/perf/bench_text.py

Lý do lựa chọn component

Vì sao pure stdlib (không dep bên thứ ba):

  • unicodedata trong CPython core, zero rào cản cài đặt.
  • Performance đủ (>500 MB/s trên normalize).
  • Tất định — không load mô hình, không mạng.
  • v0.1 có thể thêm fix_diacritics(..., llm=...) LLM-backed cho case mơ hồ, nhưng đường pure-rule giữ.

Vì sao không pyvi hay underthesea cho v0?

  • Cả hai xuất sắc cho tokenization/POS-tagging — ngoài phạm vi v0.0.1.
  • Sẽ xuất hiện như dep tuỳ chọn trong nom.text.tokenize (v0.2+) cho người dùng muốn.

Tách từ — đo 2026-04-26

Hai backend, corpus gold-standard thực:

  • nom.text.word_tokenize — pure-Python rule + merge bảng compound, zero deps
  • underthesea.word_tokenize — mô hình CRF, Apache 2.0, opt-in qua nom-vn[nlp]

Corpus: UD_Vietnamese-VTB test split (UniversalDependencies/UD_Vietnamese-VTB, CC-BY-SA-4.0). 800 câu, 11.692 token gold. Methodology: warmup 3 + best-of-5 throughput; span token đoán so với span gold theo char range (start, end) chính xác.

TokenizerPrecisionRecallF1ThroughputGhi chú
underthesea==9.4.095.94%95.46%95.70%38.102 tok/sBinary native CRFsuite; ~5 MB disk
nom.text (rule)70.94%82.90%76.46%747.117 tok/sPure-Python; zero deps; 0 mô hình

Phát hiện:

  1. underthesea +19.24 pp F1 trên nom.text — dữ liệu training CRF thắng quyết định trên boundary compound ngôn ngữ (danh từ riêng nhiều âm tiết, cụm cố định như mã số, địa chỉ, Nguyễn Thị Hương).
  2. nom.text nhanh ~20× (747 k vs 38 k tok/s). Cho RAG indexing, BM25 tokenization, dọn nhẹ — speed thắng; gap F1 không quan trọng khi phía sau là retriever bag-of-words.
  3. nom.text recall (82.9 %) > precision (70.9 %) — over-split. Bảng compound bắt được vài merge (398 hit toàn corpus) nhưng còn xa coverage CRF.

Cross-check so với số đã công bố (rule cross-check-against-published-numbers):

  • underthesea báo ~94 % F1 trên test set VLSP 2013 [1]; 95.70 % của ta trên UD-VTB test ~1.5 pp trên — có thể do UD-VTB là register dễ hơn VLSP 2013 (văn học prose so với tin tức/business mixed). Cùng order of magnitude — không có divergence methodology cần đuổi.
  • Chúng tôi không bench PyVi riêng: tự động từ chối theo chính sách no-pickle (ship file mô hình .pkl = arbitrary code execution khi load).

Khuyến nghị cho nom-vn:

Use caseChọn
RAG indexing, BM25, search tokenizenom.text — speed dominant
NER / dependency parsing / phân tích ngôn ngữnom-vn[nlp]underthesea — F1 dominant
Dọn post-OCR, tokenize khôi phục dấunom.text — gap F1 chấp nhận; zero deps thắng

Hai cái bổ sung cho nhau, không thay thế — surface trong API doc để user không pick sai và đổ lỗi cho gap F1.

Tái lập: python benchmarks/accuracy/bench_segment.py --corpus ud_vtb --split test --json benchmarks/results/baseline_segment_ud_vtb_test.json Baseline: benchmarks/results/baseline_segment_ud_vtb_test.json

[1]: README Underthesea — số VLSP 2013 báo.


Module: nom.classify.registerđã ship v0.3

Phân loại đoạn văn tiếng Việt theo 4 lớp văn phong: trang trọng, kinh doanh / báo chí, hội thoại, văn học. Hai cách chạy sau cùng Protocol:

  • LexiconRegisterClassifier — bộ từ điển đặc trưng cho mỗi văn phong, không cần model. Mặc định khi không cài extras.
  • PhoBertRegisterClassifier — wrapper quanh nrl-ai/vn-register-phobert-base (PhoBERT-base + đầu phân loại 4 lớp, MIT, ~540 MB safetensors).

Số đo nội bộ — đo 2026-05-03

Tinh chỉnh PhoBERT-base 4 epoch trên kho 4 lớp đa nguồn (giới hạn 2 000 mẫu / lớp; cắt 70/10/20 train/val/test theo lớp). 195 giây trên RTX 3090, bf16.

LớpF1Hỗ trợ (test)
formal0,91434
business0,906400
conversational0,915400
literary0,866400
macro0,9001234

Cổng adopt: macro F1 ≥ 0,85 mọi F1 lớp ≥ 0,75 — đều đạt. literary là lớp yếu nhất do từ vựng cổ điển chia sẻ với formal (~18 % câu Wikisource bị nhầm sang formal).

JSON kết quả: benchmarks/accuracy/register_phobert_base_baseline.json. Tái lập: python training/register/train.py --output-dir checkpoints/register-phobert-base --epochs 4.

Cảnh báo — đo trên 1 corpus

Hiện chỉ đo trên hold-out cùng phân phối với tập huấn luyện (UDHR, wiki_vi, Tatoeba, Wikisource, UD-VTB). Theo nguyên tắc đa-corpus của dự án, claim "F1 macro 0,900 trên VN tổng quát" cần một corpus thứ hai (đợt sau: Zalo Legal QA formal, vietnews business). Nếu spread > 10 pp giữa các corpus thì checkpoint hiện tại đang register-overfit và cần huấn luyện lại với data đa dạng hơn.


Module: nom.ocr.handwritingđã ship v0.3

VLM-based OCR cho chữ viết tay tiếng Việt (biểu mẫu, ghi chú, mặt sau CMND/CCCD). Bổ sung cho nom.doc.ocr (Tesseract — chữ in).

  • VinternHandwritingOcr — wrapper quanh 5CD-AI/Vintern-1B-v3_5 (MIT, safetensors, 0,9 B tham số). Tiền xử lý ImageNet 448×448 một-tile; chặn line-crop dưới 60 px chiều ngắn để tránh hiện tượng VLM ảo trên crop hẹp.

Số đo nội bộ trên synthetic_ocr_viđo 2026-05-03

Cảnh báo: đo trên chữ in tổng hợp, không phải chữ viết tay. Mục đích là sàng lọc — nếu Vintern fail trên chữ in clean thì cũng không hy vọng được trên chữ tay. Số đo headline cho chữ tay phải đợi đợt sau trên brianhuster/VietnameseOCRdataset (~7 nghìn ảnh thật, Apache-2.0).

Điều kiệnnCER trung bìnhKhớp tuyệt đối
clean (1 phông, nền trắng)200,47 %16/20
noisy (cùng nội dung, nhiễu)200,37 %17/20

Phần lớn "lỗi" còn lại là biến thể chính tả VN hợp lệ (hoàhòa) mà CER tính là khác. n=20 là smoke test, không phải con số headline.

JSON: vintern_ocr_clean_baseline.json, vintern_ocr_noisy_baseline.json.


Module: nom.sttđã ship v0.3

Whisper-family STT cho tiếng Việt — phỏng vấn, cuộc họp, ghi chú giọng nói.

  • PhoWhisperSTTvinai/PhoWhisper-large (BSD-3, 1,5 B, 844 giờ huấn luyện trên ghi âm tiếng Việt). Mặc định cho audio thuần VN.
  • WhisperSTTopenai/whisper-large-v3 (MIT, safetensors). Cho audio lai VN ↔ EN.

Số đo nội bộ — đo 2026-05-03

Cảnh báo: n=3 chỉ là smoke test, không phải đo đầy đủ. Bench đầy đủ trên ViMD 3 vùng (Bắc 40,6 giờ / Trung 31,5 giờ / Nam 30,5 giờ) là việc đợt sau.

Trên 3 mẫu kiểm thử của doof-ferb/Speech-MASSIVE_vie:

Mô hìnhnWER trung bình
vinai/PhoWhisper-large315,2 %
openai/whisper-large-v3315,2 %

Hai mô hình bằng nhau trên tập nhỏ này — lỗi chính: nhầm từ đồng âm (múi giờmỗi giờ), thay từ (xếpsắp), chênh dấu câu / viết hoa. Con số 6,4 % WER trên model card VinAI là tự công bố, chưa được tái lập tại đây.

JSON: stt_speech_massive_baseline.json.


Module: nom.summarizeđã ship v0.3

Tóm tắt văn bản tiếng Việt với tiền tố theo văn phong:

  • ViT5SummarizerVietAI/vit5-large-vietnews-summarization (MIT, .bin, 866 M, 1024-token cap đầu vào). Tiền tố vietnews: / legal: / dialogue: đổi giọng văn đầu ra.

Số đo nội bộ — đo 2026-05-03

Cảnh báo: cảnh báo bịa số liệu là chuẩn của module này, không phải ngoại lệ. ViT5 tóm tắt là mô hình rút trích pha trộn, có thể chèn token số mới mà nguồn không có. Đo nội bộ trên 28 đoạn mở của wiki_vi/articles.jsonl:

Số liệuGiá trị
Tỉ lệ token mới (trung bình)13,6 %
Mẫu thêm số mới không có trong nguồn7 / 28
Tỉ lệ bịa số (cảnh báo)25,0 %

Ví dụ: bài về TP. Hồ Chí Minh, mô hình thêm năm "2025" không có trong đầu vào; bài Việt Nam (đoạn 234 ký tự), bịa con số GDP "6,8 % – 7,0 %". Đừng dùng cho tóm tắt pháp lý / tài chính mà không kiểm chứng từng số.

n=28 còn nhỏ; con số 25 % là cảnh báo định hướng, không phải đo định lượng đầy đủ. Bench trên VLSP-MTSum (200+ mẫu pháp lý + tin tức) là việc đợt sau.

JSON: summarize_wiki_vi_baseline.json.


Module: nom.convertđã ship v0.3

PDF / ảnh → DOCX theo chiến lược "ưu tiên lớp văn bản gốc, dự phòng OCR" — nếu trang PDF có sẵn lớp text đọc được thì rút trực tiếp qua pdfplumber; nếu không thì đi qua đường dự phòng: pdfium2 kết xuất trang thành ảnh rồi Tesseract đọc. Đo trên kho công khai nrl-ai/vn-ocr-documents-eval v0.4 — 156 tài liệu, 8 cấu hình.

Kho đánh giá v0.4 — đã ship 2026-05-03

Cấu hìnhnNguồnGiấy phép
real9chinhphu.vn + hanoi.gov.vn (ảnh quét có dấu ký)Công khai theo Luật SHTT VN, Điều 15
formal24UDHR tiếng Việt + hiệu ứng quét tổng hợpCC0 (văn bản gốc thuộc miền công khai)
news_business24wiki_vi + hiệu ứng quét tổng hợpCC-BY-SA 4.0
conversational24tatoeba_vi + hiệu ứng quét tổng hợpCC-BY 2.0 FR
literary14Wikisource Truyện Kiều + hiệu ứng quétCông khai
receipt217 mẫu x 3 seed + hiệu ứng quétCC0
contract205 mẫu x 4 seed + hiệu ứng quétCC0
form205 mẫu x 4 seed + hiệu ứng quétCC0

Pipeline 8 bước mô phỏng máy quét (đầu ra ổn định theo seed): nghiêng giấy +/- 0,5-2,5°, tối góc trang (vignette), tông giấy hơi vàng, nhiễu hạt phân phối Gauss, vạch ngang ru-lô máy quét, mờ ống kính, viền tối mép, đi qua JPEG nén 72-92 chất lượng rồi giải nén lại. Ba kiểu cấu hình (office_scan cho máy văn phòng hiện đại / old_photocopy cho ảnh photocopy cũ / clean_digital cho bản số hoá sạch) được chọn theo băm doc_id, để các tài liệu cùng cấu hình nhìn vẫn khác nhau.

nom.convert.convert_to_docx trên v0.4 — đo 2026-05-03

Tesseract 5 với gói vie+eng, không tinh chỉnh, qua đường OCR dự phòng:

Cấu hìnhnCER (đã chuẩn hoá khoảng trắng)
real (ảnh quét chính phủ thật)912,62 %
synthetic_scan (tổng hợp gộp lại)147trung bình ~6 % / trung vị ~4 %
Tổng (cả 156 tài liệu)156trung bình 6,66 % / trung vị 4,32 %

So sánh với PaddleOCR PP-OCRv5 mặc định — đo 2026-05-03

Để xác thực rằng PaddleOCR PP-OCRv5 (lang='vi') không phải lựa chọn thay thế cho Tesseract, đo trực tiếp trên đúng 9 ảnh quét chính phủ thật (config=real):

EngineCER trung bìnhCER trung vịTốc độ (CPU)
Tesseract vie+eng (mặc định nom-vn)12,62 %11,99 %~1,3 giây / tài liệu
PaddleOCR PP-OCRv5 lang='vi'20,74 %19,33 %~60 giây / tài liệu

PaddleOCR thua 8,1 điểm phần trămchậm hơn ~46 lần. Lý do: lang='vi' nạp latin_PP-OCRv5_mobile_rec — bộ nhận dạng Latin chung nuốt hết dấu thanh tiếng Việt. Mẫu đầu ra:

text
Đáp án:    THỦ TƯỚNG CHÍNH PHỦ
PP-OCRv5:  TH TƯNG CHÍNH PH

Đáp án:    Độc lập - Tự do - Hạnh phúc
PP-OCRv5:  Đc lp - T do - Hnh phúc

Mọi dấu sắc / huyền / nặng / hỏi / ngã + ơ / ư / ê / â / ô / ă / đ biến mất. JSON kết quả: benchmarks/results/baseline_paddleocr_v5_real.json.

Đường mở ra: tinh chỉnh PP-OCRv5 với từ điển ký tự VN — kế hoạch chi tiết tại training/paddleocr_vi_rec/. Cổng adopt: trung vị CER trên config=real phải vượt 11,99 % của Tesseract; nếu không thì giữ Tesseract làm mặc định và ghi nhận kết quả âm tính.

  • Ảnh quét thật khó hơn ~13 lần so với ảnh tổng hợp. Bốn nguồn lỗi chính: dấu mộc tròn đỏ đè lên chữ; chữ ký tay vắt qua dòng tên; watermark nền chìm làm giảm tương phản; các từ viết tắt hành chính ("KT.", "Lưu: VT", "TM.", "PCN") chưa được gói vie của Tesseract huấn luyện đúng.
  • Tốc độ: ~1,3 giây mỗi tài liệu trên CPU đơn lõi.
  • Đo cả 8 tính năng nom-vn cùng lúc (bench_features_e2e.py): 102 LAW_REF, 203 DATE, 89 MONEY, 59 PHONE_VN, 39 ID_VN — hợp đồng và đơn từ là nơi đầu tiên có CCCD trong kho.

JSON kết quả tái lập: benchmarks/results/baseline_features_e2e_v4.json. Lệnh tái lập:

bash
python benchmarks/data/vn_documents_ocr_v2/_synth_corpus.py
python benchmarks/accuracy/bench_features_e2e.py

Cảnh báo phương pháp

  • real mới chỉ 9 tài liệu — mở rộng lên 20+ cần thêm ~110 phút chú thích thủ công 11 trang nữa (đọc trực tiếp từng ảnh, không tin Tesseract đoán).
  • synthetic_scan dùng chính văn bản gốc làm đáp án chuẩn (vì được kết xuất từ văn bản, nên không bịa được). Đánh đổi: hiệu ứng quét chưa mô phỏng được cấu trúc thư hành chính đầy đủ — không có dấu mộc đỏ, chữ ký tay, ô đóng dấu, hai cột tiêu đề như công văn thật. Phù hợp đo nhận dạng ký tự ở mức dòng / đoạn, không thay thế được kho ảnh quét gốc khi muốn đánh giá khả năng hiểu cấu trúc tài liệu.

Bộ pattern bổ sung cho RegexNERModel (đã có trong nom.nlp.ner) phục vụ tài liệu pháp lý VN: LAW_REF, ID_VN (CMND/CCCD), PHONE_VN.

Trạng thái đo

Chưa có số đo học từ dữ liệu. v0 chỉ là biểu thức chính quy. Bộ kiểm thử xác minh đúng các mẫu do chính chúng tôi viết — đây là kiểm tra cấu trúc, không phải đo trên kho VN gold-labeled.

Bao phủ ước lượng (chưa đo trên kho chuẩn):

LớpBắt đượcKhông bắt được
LAW_REFLuật 134/2025/QH15, Nghị định 13/2023/NĐ-CP, Thông tư, Điều X Luật Ytham chiếu viết tắt phi chuẩn (≈ 15 % công văn hiện đại)
ID_VNCMND 9 chữ số, CCCD 12 chữ sốkhông xác minh mã kiểm tra CCCD; chuỗi 12 chữ số ngẫu nhiên có thể dương tính giả
PHONE_VNdi động 10 số 03/05/07/08/09, +84, cố định Hà Nộiđầu số quốc tế ngoài VN

Hướng tinh chỉnh PhoBERT cho LAW_REF + CONTRACT_PARTY (mục tiêu F1 ≥ 0,85) cần ~70-90 giờ chú thích thủ công, xếp lịch sau theo docs/sota_vn_2026q2_expansion.md.


Module: nom.doc.ocrdự kiến v0.1

OCR là primitive leverage cao nhất và bị fail nhiều nhất trong AI tiếng Việt. Chúng tôi ship ba backend với cùng interface; default switch theo phần cứng có sẵn.

So sánh các engine — đo nội bộ 2026-05-02

Đo trên 4 loại văn bản: synthetic_ocr_vi/{clean,noisy,hard} (chữ in) và 200 ảnh thử nghiệm của brianhuster/VietnameseOCRdataset (chữ viết tay). Chỉ số: CER, có chuẩn hoá NFC. Tệp kết quả lưu trong benchmarks/results/baseline_ocr_engines_per_register.json.

EngineGiấy phépCER in sạchCER in nhiễuCER quét kémCER viết tayĐộ trễ p50
Tesseract 5 + vieApache 2.00,00 %0,70 %30,34 %69,34 %80 ms
VietOCR vgg_transformerApache 2.01,41 %3,37 %29,00 %31,82 %240 ms
EasyOCRApache 2.01,42 %4,87 %87,09 %71,52 %35-60 ms
PaddleOCR PP-OCRv5 (latin_mobile_rec)Apache 2.024,70 %31,33 %86,13 %59,43 %1170 ms
RapidOCR (bản port ONNX của PaddleOCR)Apache 2.063,97 %77,83 %100,00 %97,20 %130-250 ms
TrOCR base-handwritten (chỉ tiếng Anh, làm tham chiếu)MIT32,89 %38,07 %93,76 %75,89 %180-280 ms
qwen2.5vl:7b (VLM qua Ollama)Apache 2.033,17 %29,90 %81,37 %67,53 %320-610 ms
Surya OCRopen-RAIL-MChưa đo (giấy phép không đạt)

Phát hiện chính:

  • VietOCR thắng tuyệt đối với chữ viết tay — 31,82 % CER so với Tesseract 69,34 % (giảm 37,5 pp tuyệt đối, -54 % tương đối). Đây là phát hiện OCR lớn nhất của dự án.
  • Tesseract thắng với chữ in — 0,00 % CER trên ảnh in sạch, 0,70 % trên ảnh nhiễu. Không engine nào khác đến gần.
  • PaddleOCR PP-OCRv5 đứng thứ 3-4 ở mọi loại văn bản tiếng Việt. Không phải vấn đề cấu hình, mà ở thiết kế: lang='vi' chỉ tải latin_PP-OCRv5_mobile_rec — mô hình nhận diện Latin chung không được huấn luyện cho tiếng Việt. Đo lại 2026-05-01 sau khi người dùng yêu cầu kiểm tra; xác nhận.
  • RapidOCR cùng kiểu lỗi — bản port ONNX của PaddleOCR dùng chung mô hình nhận diện Latin chung, không khả thi cho tiếng Việt. Mặc định của Docling ocr_engine='rapidocr' cũng vậy.
  • VLM ảo giác trên ảnh cắt dòng đơnqwen2.5vl:7b cho CER 33,17 % trên ảnh in sạch (so với Tesseract 0 %). VLM là công cụ cho hiểu tài liệu (biểu mẫu, hoá đơn, bố cục), không cho dòng chữ in.

Bảng đầy đủ và hướng dẫn chọn engine theo từng loại dữ liệu nằm trong docs/tasks/ocr.md.

Khuyến nghị cho nom.doc

Mặc định: Tesseract vie cho chữ in (CER 0-1 %, 80 ms p50). VietOCR vgg_transformer cho chữ viết tay (CER 31,82 %, 246 ms p50 trên GPU).

python
# API dự kiến v0.1
from nom.doc import extract
from nom.doc.ocr import VietOCR, Tesseract, PaddleOCR

# Auto: VietOCR nếu cài → PaddleOCR → Tesseract
result = extract("scan.pdf", schema={...})

# Rõ ràng
result = extract("scan.pdf", schema={...}, ocr=PaddleOCR())

Lớp parsing PDF bên dưới — đo 2026-04-26

Chúng tôi không ship PyMuPDF. License AGPL của nó không tương thích với Apache-2.0 mặc định. Thay vào dùng pypdfium2 (BSD-3 wrapper trên PDFium của Google, Apache-2.0) làm mặc định trích text nhanh và giữ pdfplumber cho tài liệu nhiều bảng.

Corpus: benchmarks/data/synthetic_pdf_vi/vn_legal.pdf — PDF VN tổng hợp 7-page xây từ prose VN public-domain thực (UDHR + Wikisource Truyện Kiều prefaces) với lớp text Unicode sạch (DejaVuSans embed). Generator: benchmarks/data/synthetic_pdf_vi/_generate.py. Nhãn thật: 18.877 ký tự commit kèm.

udhr_vi/udhr_vie.pdf ship sẵn không dùng được ở đây — nó embed font tuỳ biến không có ToUnicode CMap, nên mọi extractor (pdfplumber, pypdfium2, PyMuPDF) trả về CIDs / byte rác. Đã tài liệu hoá trong bench script.

Methodology: warmup 3 + best-of-5 (rule verified-benchmarks). Char-overlap fidelity dùng giao multiset NFC-normalised so với nhãn thật.

LibraryLicenseBest-of-5 (s)ThroughputChar overlap
pypdfium2==5.7.1 ⭐ defaultBSD-3 / Apache-2.00.00792.350.431 chars/s99.81%
pdfplumber==0.11.9MIT0.365451.052 chars/s99.81%

Phát hiện:

  1. pypdfium2 nhanh hơn 46× so với pdfplumber trên trích text-only với fidelity y hệt (99.81% — cả hai miss cùng ~36 ký tự, hầu hết là glyph Hán mà DejaVuSans không render được).
  2. License là headline. Speedup 19× của PyMuPDF công bố trên py-pdf/benchmarks là thật — nhưng AGPL ép mọi dự án phía sau ship dạng AGPL. PDFium dưới pypdfium2 cho ta cùng order-of-magnitude speedup mà không có bẫy license.
  3. pdfplumber giữ trong nom-vn[doc] — vẫn là lựa chọn tốt hơn khi tài liệu có bảng. Pipeline nom.doc chọn per-document khi parse.

Khuyến nghị:

Use caseChọn
Trích text thuần (RAG, search indexing)pypdfium2 — speed thắng, license sạch
Bảng / form / layout có cấu trúcpdfplumber — phát hiện cell tốt hơn

Tái lập: python benchmarks/perf/bench_pdf_extract.py Baseline: benchmarks/results/baseline_pdf_extract.json

Build corpus từ một bản clone sạch: python benchmarks/data/synthetic_pdf_vi/_generate.py (cần DejaVuSans — apt install fonts-dejavu).

Lưu ý PyMuPDF / fitz — chúng tôi giữ chúng hoàn toàn ngoài dependency. User thực sự cần PyMuPDF (ví dụ dự án nội bộ chấp nhận AGPL) tự cài và gọi trực tiếp; chúng tôi không expose wrapper làm mờ ranh giới license.

Docling (IBM, MIT) — đo 2026-04-26. Cùng PDF VN, warmup 2 + best-of-3, default DocumentConverter():

LibraryBest (s)ThroughputChar overlapDisk
pypdfium20.00792.350.431 chars/s99.81%<10 MB
pdfplumber0.365451.052 chars/s99.81%<5 MB
docling1.188915.703 chars/s99.72%~1 GB (PyTorch + DocLayNet + TableFormer)

Docling chậm hơn pypdfium2 150× trên PDF text Unicode-clean này và hơi tệ hơn về fidelity (99.72% vs 99.81%) — pipeline ML layout không trả lợi tức khi PDF đã có lớp text sạch. Docling kiếm cost của nó trên layout phức tạp (multi-column, bảng, công thức, mixed text+ảnh) nơi heuristic của pdfplumber vỡ. Chưa đo in-house trên PDF VN nhiều bảng.

Khuyến nghị Docling: giữ NGOÀI nom-vn[doc] lúc này. Nếu corpus hướng tới người dùng layout phức tạp xuất hiện (form pháp lý, báo cáo chính phủ), thêm extra nom-vn[docling] và surface là nom.doc.layout_extract(). Cho đến lúc đó, trọng lượng dependency (~1 GB stack ML + safetensors) không justified cho PDF text thuần.

Bảng landscape trước từ py-pdf/benchmarks cho bối cảnh (PDF academic + business mixed):

LibraryThời gian trung bình per-docGhi chú
PyMuPDF (fitz)0.5 sKhông ship — AGPL
pypdf4.2 sMIT, thao tác cơ bản
pdfplumber9.5 sTrích bảng phong phú nhất

Nguồn


Module: nom.llmdự kiến v0.1

Nôm không bundle mô hình. Chúng tôi ship class adapter; user chọn mô hình nào để trỏ tới.

Mô hình khuyến nghị — ba bracket

Bracket 1 — local, miễn phí, chạy laptop consumer

Primary: Qwen3-8B qua Ollama

  • License Apache 2.0 — commercial OK
  • Chạy ~6GB VRAM (Q4 quant) hoặc 16GB RAM CPU
  • VMLU mạnh cho size
  • Multilingual gồm tiếng Việt
  • Cài một dòng: ollama pull qwen3:8b

Thay thế: Llama-3.1-8B-Instruct

  • License Meta (commercial OK với điều kiện)
  • VN performance hơi tệ hơn Qwen3 trong review 2026 [1]

Thay thế: Vistral-7B-Chat

  • Đã fine-tune cho tiếng Việt
  • License chỉ research (theo VinAI) — không cho commercial

Bracket 2 — cloud, cost trung bình, chất lượng open top

Qwen3-235B-A22B qua Together AI / Fireworks / Alibaba Cloud

  • Apache 2.0
  • 235B MoE (22B active) — VN mạnh
  • Khuyến nghị top trong guide best-VN-LLM 2026 [1]
  • ~$0.50–1/M input token qua provider

Bracket 3 — closed, chất lượng tối đa

ProviderMô hìnhVì sao dùng
OpenAIgpt-4oReasoning VN chung tốt nhất, vision-capable
Anthropicclaude-sonnetReasoning long-document mạnh, context lớn
Googlegemini-2.5-proRẻ nhất ở top tier 2026

Mô hình vision-capable cho nom.doc (tài liệu scan)

Cho công việc OCR-grade trực tiếp trên ảnh (bỏ bước OCR hoàn toàn):

Mô hìnhLicenseGhi chú
Qwen2.5-VL-72B-InstructApache 2.0Top open vision-LLM cho extraction tài liệu có cấu trúc [2]
GLM-4.5Vopen weightsMạnh trên chart, bảng, layout phức tạp [2]
DeepSeek-VL2open weightsTốt trên doc VN scan theo anecdote
GPT-4o (vision)closedBest khi latency/cost không phải ràng buộc

Khuyến nghị: nom.doc.extract dùng hai đường:

  1. PDF native → text → LLM text-only (rẻ nhất, nhanh nhất)
  2. Scan/ảnh → vision-LLM trực tiếp (skip OCR, thường chất lượng cao hơn)

Nguồn


Module: nom.promptsdự kiến v0.2

Prompt hệ thống curated, có version cho tài liệu kinh doanh VN. Chưa có benchmark — giá trị module này nằm ở prompt nào thắng, không phải tốc độ raw.

Domain sẽ cover

DomainVì sao ưu tiênNguồn test set
Hợp đồngDoc kinh doanh VN tần suất cao nhấtCorpus hợp đồng NRL-curated
Công vănChính phủ/SMB workflow chủ yếuCorpus VLSP (chỗ license cho phép)
Đơn từKhối lượng cao, input OCR thấpSynthetic + community submission
Email công sởDrafting tone-aware (kính gửi vs cho)Eval set nội bộ
Hoá đơn / biên laiUse-case kế toánOpen OCR datasets

Versioning

Prompt có version (nom.prompts.contracts.v1). Một khi publish, không bao giờ silently đổi. Pin version là phần của contract tái lập của người dùng.


Benchmark mức module NRL đóng góp (VN-Bench v1)

Đây là việc sẽ đưa số gốc NRL vào nrl.ai/bench (so với trang hiện tại tổng hợp VMLU). Xem VN-Bench v1 roadmap cho danh sách task canonical.

TaskMô tảEvalTrạng thái
Trích hợp đồngPDF → schema typedF1 trên field accuracyđang phát triển
Parse công vănSố / ngày / đơn vị / nội dungExact matchđang phát triển
Scan OCR → JSONẢnh → có cấu trúcChar-edit + field accuracyđang phát triển
Giữ dấuSinh có dấu đúngDiacritic accuracy trên đoạn dàiđang phát triển
Code-switching EN/VNHiểu hội thoại mixedPairwise judgeđang phát triển
QA pháp lýDự đoán điều, tríchMượn từ VLegal-Benchpartner

Module: nom.ragđã ship v0.2.5

Làm gì

RAG end-to-end trên tài liệu VN: BM25 + dense (encoder sentence-transformers) hybrid retrieval với RRF fusion, reranking cross-encoder tuỳ chọn, mở rộng HyDE / multi-query tuỳ chọn, sau đó gọi LLM.

Ba dòng từ tài liệu sang câu trả lời — xem docstring src/nom/rag/pipeline.py cho ví dụ canonical.

Cùng fixture (5.061 doc / 80 câu hỏi), embedder bkai-vietnamese-bi-encoder, pipeline hybrid+rerank, RTX 3090. So sánh apple-to-apple — chỉ reranker đổi.

RerankerLicenseDiskParamR@1R@10MRR@10p50 ms (gồm seg)
BAAI/bge-reranker-v2-m3 ⭐ defaultApache 2.0~2.3 GB568 M86.3 %100.0 %0.929583
itdainb/PhoRanker (word-segmented)Apache 2.0~395 MB100 M83.8 %98.8 %0.907863
itdainb/PhoRanker (KHÔNG word-segment, BROKEN config)Apache 2.0~395 MB100 M70.0 %97.5 %0.802295

Bức tranh reranker, framing chính xác. PhoRanker được báo thắng bge-reranker-v2-m3 trên MMARCO-Vi (NDCG@3 0.6625 vs 0.6087). Trên bench Zalo Legal 5 k của ta, PhoRanker chỉ kém bge-reranker-v2-m3 2.5 pp ở disk nhỏ hơn 5.7×. Đây là tradeoff tốt hơn nhiều so với v0.2.17 implied — số 70.0 % R@1 ban đầu là bug methodology (thiếu word segmentation; xem rule ALWAYS DOUBLE-CHECK bên dưới).

Khuyến nghị:

  • Default: bge-reranker-v2-m3. Chất lượng cao nhất (R@1 86.3 %), đơn giản nhất để deploy (không cần preprocessing).

  • Tier light-weight: PhoRanker với word segmentation. Trong 2.5 pp R@1 của default ở disk nhỏ hơn 5.7× và inference cross-encoder nhanh hơn. Lựa chọn đúng cho laptop nơi 2.3 GB reranker không vừa cùng embedder + LLM. Cần nom-vn[nlp] cho underthesea word segmentation — pass word_segment=True cho CrossEncoderReranker hoặc --reranker-word-segment cho bench:

    python
    CrossEncoderReranker("itdainb/PhoRanker", word_segment=True)

    863 ms p50 gồm segment underthesea per-query. Trong production cache chunk corpus đã segment ở thời điểm index, drop cost per-query xuống ~300 ms.

Bài học ALWAYS DOUBLE-CHECK (rule ALWAYS DOUBLE-CHECK của ta). Số PhoRanker v0.2.17 (70.0 % R@1) sai vì gửi text raw không segment cho mô hình mà card explicitly yêu cầu input đã segment VnCoreNLP. Re-check model card 2026-04-26 bắt được. Khái quát hoá bài học: cho bất kỳ reranker mới nào, đọc ví dụ usage canonical từ model card trước khi bench. Preprocessing hardcoded trong bench giờ là kwarg word_segment=, không phải giả định ẩn.

Auto-detect max_length: CrossEncoderReranker(...) giờ tự động phát hiện max_position_embeddings của mỗi mô hình từ config.json để bạn swap reranker không cần nghĩ. PhoRanker (PhoBERT-base, cap 256) và bge-reranker-v2-m3 (XLM-R-large, cap 512) đều chạy qua cùng call:

python
from nom.rag import CrossEncoderReranker

# Cả hai chạy — max_length auto-detect
default = CrossEncoderReranker()                         # bge-reranker-v2-m3
lite    = CrossEncoderReranker("itdainb/PhoRanker")     # cap 256, không cần flag tay

Override qua max_length=... khi heuristic tự động phát hiện sai cho mô hình lạ.

Tái lập: python benchmarks/rag/bench_rag_vn.py --fixture benchmarks/rag/fixtures/vn_legal_zalo_5k.json --embedder bkai --retrievers hybrid+rerank --reranker itdainb/PhoRanker --json benchmarks/results/baseline_phoranker_zalo5k.json

Baseline: benchmarks/results/baseline_phoranker_zalo5k.json (PhoRanker), benchmarks/results/baseline_bge_reranker_bkai_zalo5k.json (bge-reranker-v2-m3).

Retrieval chỉ embedder — đo 2026-04-26

So sánh two-tower trực tiếp: encode mọi doc + mọi câu hỏi, rank doc theo cosine. Không BM25, không fusion, không reranker — mọi khác biệt chất lượng đều thuần là embedder. Cách này bắt case mà phân phối training STS-tuned của embedder không transfer sang retrieval (task asymmetric Q→Doc mà pipeline RAG thực sự làm).

Corpus: benchmarks/rag/fixtures/vn_legal_zalo_5k.json (5.061 doc / 80 câu hỏi, sample từ Zalo AI 2021 Legal QA, MIT). Hardware: RTX 3090.

Mô hìnhLicenseDiskR@1R@10MRR@10docs/s
bkai-foundation-models/vietnamese-bi-encoderApache 2.0~383 MB76.25 %98.75 %0.860460
dangvantuan/vietnamese-embedding (default hiện tại)Apache 2.0~440 MB35.00 %67.50 %0.444953

bkai thắng +41.25 pp R@1 và +31.25 pp R@10 ở size disk nhỏ hơn và throughput tương tự. Gap là cấu trúc, không tunable:

  • dangvantuan đã fine-tune trên STS (similarity đối xứng) — mạnh trên benchmark như VN-STS nhưng task retrieval câu hỏi→tài liệu asymmetric ngoài phân phối.
  • bkai đã train với MultipleNegativesRankingLoss trên cặp Q→Doc từ MS MARCO + SQuAD v2 + 80 % Zalo Legal — chính xác task ta chạy.

Catch: bkai cần preprocessing word-segmenter (từ multi-syllable VN nối bằng underscore). Class nom.embeddings.BKaiEmbedder wrap underthesea để làm điều này tự động. Cài: pip install "nom-vn[embeddings,nlp]".

Cross-check: số Zalo Legal corpus đầy đủ bkai công bố (model card) báo Acc@1 73.28, Acc@10 93.59, MRR 80.73. Subset 5k của ta (76.25, 98.75, 0.8604) hơi cao hơn vì subset có ít distractor — order of magnitude consistent. Không có divergence methodology.

Hành động cho v0.2.x: thêm BKaiEmbedder opt-in, KHÔNG đổi default trong nom.rag / nom.retrieve — sẽ invalidate cache embedding đã persist của mọi người dùng hiện tại. Bản major 0.3.x sẽ lật default; bây giờ opt-in giữ tương thích cache.

python
from nom.embeddings import BKaiEmbedder
from nom.rag import RAG
rag = RAG(embedder=BKaiEmbedder(device="cuda"))

Tái lập: python benchmarks/rag/bench_embedder_compare.py --json benchmarks/results/baseline_embedder_compare_zalo5k.json

Lưới mô hình RAG VN — đo 2026-04-25

Hai fixture, đều sample từ GreenNode/zalo-ai-legal-text-retrieval-vn (MIT). Hardware: NVIDIA RTX 3080 Laptop, fp16, warmup=1, timed=1-2 (best-of-N theo rule verified-benchmarks).

Retrieverrecall@1recall@3recall@5recall@10mrr@10p50 ms
BM250.3950.6640.7250.7800.535430
Dense (dangvantuan)0.2370.3790.4660.5370.32818
Hybrid (RRF)0.3680.6020.6900.7830.505491
Hybrid + bge-reranker-v2-m30.5720.8020.8460.8680.6881539
EmbedderRetrieverrecall@1recall@3recall@10mrr@10p50 msp95 ms
dangvantuan/vietnamese-embedding (768-d, ~440 MB)BM25 only0.7620.9120.9750.8432748
Dense only0.4120.7250.8630.5851525
Hybrid (RRF)0.6500.8750.9750.78059113
+ BAAI/bge-reranker-v2-m30.8631.0001.0000.931681747
+ namdp-ptit/ViRanker0.8500.9631.0000.913687743
AITeamVN/Vietnamese_Embedding (1024-d, ~2.3 GB, BGE-M3 base)BM25 only0.7620.9120.9500.8432441
Dense only0.8250.9630.9750.8944777
Hybrid (RRF)0.8000.9630.9750.88497131
+ BAAI/bge-reranker-v2-m30.8630.9880.9880.923720786
+ namdp-ptit/ViRanker0.8630.9630.9880.914718799

Tái lập: bash benchmarks/rag/run_grid.sh. JSON baseline per-config dưới benchmarks/rag/baselines/zalo_5k__*.json và mirror tới nrl-ai/vn-rag-bench.

Phát hiện

  1. Lựa chọn embedder quan trọng hơn lựa chọn reranker — cho stage bi-encoder. Đổi từ dangvantuan sang AITeamVN gấp đôi dense recall@1 (0.412 → 0.825). Fine-tune BGE-M3 AITeamVN/Vietnamese_Embedding đặc biệt tune trên Zalo Legal QA, thể hiện ở số in-domain.
  2. Reranker hội tụ. Cả BAAI/bge-reranker-v2-m3namdp-ptit/ViRanker đưa recall@1 cuối lên ~0.863 bất kể embedder feeder. Reranker dominate ranking cuối khi bài gold đã trong pool top-30.
  3. Chất lượng peak tốt nhất: dangvantuan + BAAI/bge-reranker-v2-m3 — recall@10 = 1.000 và recall@3 = 1.000 trên fixture này. Affinity BM25 cao hơn của embedder dangvantuan (chân dense yếu nên RRF nghiêng vào BM25) lift trần recall@10.
  4. Tuỳ chọn skip-reranker: AITeamVN dense một mình được recall@1 = 0.825 ở 47 ms p50 — nhanh khoảng 15× so với +rerank, mất chỉ 4% absolute recall@1. Lựa chọn đúng cho deploy nhạy latency nơi 825/863 chấp nhận được.
  5. BM25 cạnh tranh đến giật mình trên VN pháp lý — ở quy mô corpus nhỏ. Trên subset 5k BM25 chạm recall@1 = 0.762, nhưng trên corpus 61k đầy đủ rớt xuống 0.395. Hiệu ứng size-corpus dominant cho retrieval lexical; stage dense / reranker quan trọng hơn khi pool distractor lớn.
  6. Reranker trở nên critical hơn ở quy mô, không kém. Đi từ hybrid → hybrid+rerank lift recall@1 0.213 absolute trên subset 5k và 0.204 absolute trên corpus 61k đầy đủ — proportionally lift relative lớn hơn nhiều trên corpus đầy đủ (+55% relative vs +33% relative).
  7. BM25 pure-Python là bottleneck ở quy mô. Trên corpus 61k đầy đủ BM25.search() v0.2.5 chạy 430ms p50 — chậm hơn nhiều dense trên GPU (18ms). v0.2.6 swap sang bm25s (MIT, scipy.sparse): cùng recall y hệt bit, search nhanh hơn 607× (0.7ms p50). Xem benchmarks/results/bm25_compare__zalo_full.json cho bảng đầy đủ. Chân dense giờ là bottleneck per-query.

Cross-check so với số đã công bố (theo rule cross-check-against-published-numbers)

  • Multi-stage IR cho VN Legal (PKAW 2022, arXiv:2209.14494): báo F2 = 0.741 trên corpus Zalo đầy đủ với PhoBERT-large + sqrt(BM25)·cos hybrid + 3 round mining hard-negative. recall@10 = 0.868 trên corpus 61k đầy đủ của ta implied F2 tương đương (≈0.6-0.7), đạt được có sẵn với bge-reranker-v2-m3 — không fine-tune. Alignment hợp lý.
  • UIT 2024 (arXiv:2507.14619): Vietnamese-bi-encoder + PhoRanker, MRR@10 cross-encoder = 79.11% trên 261k doc pháp lý. MRR@10 = 0.688 của ta trên corpus 61k đầy đủ — ~10 điểm thấp hơn; giải thích bởi (a) ta dùng bge có sẵn thay PhoRanker đã fine-tune trên dữ liệu pháp lý, và (b) hiệu ứng size-corpus chạy cả hai chiều. Thêm PhoRanker vào lưới là bước tiếp theo hợp lý (excluded đến nay vì dep VnCoreNLP Java).
  • Model card AITeamVN/Vietnamese_Embedding: claim +27.9% Acc@1 so với BGE-M3 base trên retrieval domain pháp lý. dense Acc@1 = 0.825 của ta trên subset 5k vs BGE-M3 base (chưa test) — cần bench BGE-M3 trên cùng fixture để xác nhận size lift. Mở: thêm BGE-M3 vào lưới để verify lợi thế công bố của fine-tune AITeamVN.
  • PhoRanker NDCG@10 = 0.7422 trên MMARCO-VI (model card): chưa đo — PhoRanker cần VnCoreNLP (JVM Java), excluded khỏi lưới này có chủ đích.

Config khuyến nghị (default trong nom-vn v0.2.5)

python
from nom.rag import RAG, CrossEncoderReranker
rag = RAG.from_documents(
    docs,
    llm=Ollama(model="qwen3:8b"),
    embedder=VietnameseEmbedder(),                  # 440 MB, dim 768
    reranker=CrossEncoderReranker(),                # bge-reranker-v2-m3
)
answer = rag.ask(question, rerank=True, rerank_candidates=30)

Cho deploy bound latency không có GPU, drop reranker và dùng AITeamVNEmbedder() (dense tốt hơn, không có thuế cross-encoder).


Module: nom.doc.ocrbaseline thật đo 2026-04-26

Làm gì

Chạy engine OCR trên ảnh tiếng Việt (page PDF, scan, ảnh) và trả về text thuần. v0.2.x ship đường Tesseract; phiên bản sau sẽ thêm tuỳ chọn VLM và VN-specialised khi chúng kiếm được trọng lượng dependency trên bench.

Lưới engine OCR tiếng Việt — đo 2026-04-26

Corpus thật: vn_ocr_subset — 478 ảnh sample tất định (seed=42) từ ducto489/ocr_datasets shard 0 (Apache-2.0), filter các hàng chứa dấu VN và ít nhất 8 ký tự ground-truth text. Hầu hết là prose machine-rendered ở các mức nhiễu khác nhau — đại diện cho input OCR tài liệu thực.

Hardware: CPU (8 cores, không có contention GPU với bench RAG), warmup=1, timed=2, p50/p95 báo best-of-N.

EngineLicenseCERWERdiacritic-CERexact matchp50 msp95 ms
Tesseract 5 (vie traineddata)Apache-2.00.08190.37710.11930.345447656
EasyOCR 1.7 (vi)Apache-2.00.11760.53040.20520.218183431

JSON baseline dưới benchmarks/results/ocr_vn_subset__*.json và mirror tới nrl-ai/vn-rag-bench.

Phát hiện

  1. Fixture synthetic không phải benchmark. synthetic_ocr_vi/clean cho Tesseract CER = 0.000 / exact = 1.000 — hoàn hảo. synthetic/noisy cho CER = 0.0064. Cả hai đều quá dễ để rank engine. Dữ liệu ducto489 thật drop Tesseract xuống CER = 0.082 — đó là baseline trung thực.
  2. Diacritic-CER (11.9%) tệ hơn ~46% so với CER tổng (8.2%) — xác nhận mode fail mà người đọc tiếng Việt cảm thấy. Dấu thanh (sắc, huyền, hỏi, ngã, nặng) là 1–3 pixel và là thứ đầu tiên OCR mất trên scan nhiễu. Một reranking diacritic-aware hoặc fix post-OCR sẽ giúp ở đây.
  3. Latency ~450 ms per ảnh trên 8 CPU cores. Tesseract là C++ bên dưới và không parallel trong một page; cải thiện throughput đến từ chạy nhiều page parallel ở mức pipeline, không phải tune internals Tesseract.
  4. Tesseract thắng EasyOCR trên mọi metric chất lượng cho VN. CER 8.19% vs 11.76%, diacritic-CER 11.93% vs 20.52%, exact-match 34.5% vs 21.8%. EasyOCR nhanh hơn 2.4× (183 ms vs 447 ms p50) nhưng gap accuracy dominate cho use case Q&A tài liệu — mất 13% absolute exact-match cho 264 ms latency là trade tệ. Default giữ Tesseract. EasyOCR có thể hữu ích cho use case bulk-indexing throughput cao nơi accuracy đánh đổi được; chúng tôi surface cả hai option trong bench_ocr_real.py.

Engine đã khảo sát nhưng chưa đo

  • VietOCR (Apache-2.0, Transformer VN-specialised) — pip install vietocr lỗi trên Python 3.13 (KeyError: '__version__' trong setup.py). Pin cho theo dõi tiếp; phía trên cần pyproject.toml tương thích Python-3.13.
  • PaddleOCR PP-OCRv5 (Apache-2.0, lightweight ~150 MB) — ứng viên hứa hẹn nhất tiếp theo. CER báo ~0.94 trên OmniDocBench multilingual; không VN-specific nhưng thường thắng Tesseract trên text rendered.
  • Surya OCR — code là GPL-3.0, mô hình open-RAIL-M. Cả hai license-incompatible với surface mặc định Apache-2.0. Sẽ bench cho so sánh; không thể ship làm default.

VLM OCR — đo 2026-04-26

Test xem Vision-Language Model general-purpose có thể match OCR purpose-built trên transcription line-image VN.

Engine: qwen2.5vl:3bqwen2.5vl:7b (Apache-2.0) qua Ollama 0.21.2 trên RTX 3090. Quantize Q4_K_M. Prompt: VN tight "transcribe exactly, no chatter" (xem OllamaVLM trong benchmarks/accuracy/bench_ocr_real.py). Output trim defensive cho think-tag, code-fence, và label echo.

Corpus: 50 ảnh đầu từ vn_ocr_subset (sample từ ducto489/ocr_datasets, Apache-2.0). Single-line text VN print sạch — cùng ảnh chạy trên Tesseract và EasyOCR cho so sánh trực tiếp.

EngineQ4 sizeCERWERDiacritic CERExact matchp50 msp95 ms
Tesseract 5 (vie)~30 MB5.53%26.78%9.71%38.0%80.6110.5
EasyOCR (vi)~150 MB9.39%43.86%19.84%18.0%31.1 (GPU)68.3
qwen2.5vl:7b6.0 GB31.07%140.04%33.38%18.0%818.01332.2
qwen2.5vl:3b3.2 GB39.86%175.43%41.82%15.0%1165.53993.6

Phát hiện:

  1. VLM thua quyết định trên OCR single-line sạch. qwen2.5vl:7b có CER 31% vs Tesseract 5.53% — gap 25 điểm. Mô hình hallucinate: "1892 - Tạp Chí Vogue..." → "1892 92 92 92 92..." (loop token), "XÃ CHIỀNG ƠN" → "CHÍNH XÁC", "churchill và tưởng giới thạch" → "Churchill và tướng Eisenhower cùng được trao giải thưởng" (cả câu plausible-nhưng-bịa).
  2. Tool đúng vẫn là tool đúng. VLM train trên page đầy đủ; trên crop dòng tight không có context tài liệu, prior ngôn ngữ dominate signal visual và mô hình drift sang mode "complete-the-sentence". Head CTC của Tesseract purpose-built cho alignment glyph trái-sang-phải và không có mode fail này.
  3. Latency: VLM chậm hơn 10× (818 ms vs 80 ms p50). Cho batch 478 ảnh đây là 6.5 phút vs 39 giây.
  4. Use case của VLM trong OCR là chỗ khác. Extraction multi-field (field hoá đơn, CCCD, form có checkbox), chữ viết tay scan, và workflow "OCR + hiểu text" là chỗ VLM kiếm cost. Đã document để user không chộp lấy qwen2.5vl mong đợi nó thắng Tesseract trên ảnh dòng đơn giản.

Khuyến nghị: OCR mặc định giữ Tesseract. VLM OCR hợp khi task phía sau là hiểu tài liệu, không phải transcribe nó — surface là một đường nom.doc.vlm_extract() riêng trong release tương lai, không phải backend OCR swap-in.

Tái lập: python benchmarks/accuracy/bench_ocr_real.py --corpus benchmarks/data/vn_ocr_subset --variant none --engines ollama_vlm --ollama-model qwen2.5vl:7b --ollama-base-url http://localhost:11434 --limit 50 Baseline: benchmarks/results/baseline_ocr_vlm_qwen25vl_7b.json, baseline_ocr_tesseract_50.json, baseline_ocr_easyocr_50.json.

Config khuyến nghị (default v0.2.x)

python
from nom.doc import Pipeline
# Tesseract đã wire vào nom.doc.OCR mặc định; cài vie traineddata
# qua `apt install tesseract-ocr-vie` (hoặc brew).
pipeline = Pipeline()
text = pipeline.run("scanned.pdf").text