P220 ThaiOCR by Porsuk

P220-ThaiOCR-9B

P220-ThaiOCR-9B is a 9B vision-language model for Thai documents: printed and handwritten text, tables, forms, receipts, government PDFs and scans. It reads pages into plain text, Markdown or HTML tables, and answers questions about a document (key information, classification, charts, diagrams), without per-template rules.

BF16 weights.

Top open model on ThaiOCRBench: P220-ThaiOCR-9B scores higher on ThaiOCRBench than every open model tested, including Qwen3.6-35B-A3B and OpenThai 2.0 27B, and above GPT-6 Luna. Also close to GPT-6.1 Sol on the transcription tasks.

  • Developed by: Porsuk Rungarunsrisuk
  • Base model: Qwen/Qwen3.5-9B (Apache-2.0), fine-tuned with LoRA and merged.
  • Languages: Thai, English.
  • Thinking: off. The model was trained and evaluated without thinking, and the included chat template turns thinking off by default.

Variants

Repo Format
PorsukR/P220-ThaiOCR-9B BF16 (transformers / vLLM)
PorsukR/P220-ThaiOCR-9B-FP8 FP8 (vLLM)
PorsukR/P220-ThaiOCR-9B-W4A16 INT4 weight-only (vLLM)
PorsukR/P220-ThaiOCR-9B-NVFP4 NVFP4 (vLLM, Blackwell)
PorsukR/P220-ThaiOCR-9B-GGUF GGUF BF16 / Q8_0 / Q4_K_M / Q3_K_M / Q2_K (llama.cpp, Ollama)

Which file should I use?

Hardware Use
NVIDIA Blackwell GPU PorsukR/P220-ThaiOCR-9B-NVFP4
NVIDIA Ada / Hopper GPU PorsukR/P220-ThaiOCR-9B-FP8
NVIDIA Ampere GPU PorsukR/P220-ThaiOCR-9B-W4A16
CPU, Mac, or Ollama PorsukR/P220-ThaiOCR-9B-GGUF Q4_K_M (Q3_K_M / Q2_K for less memory)

Every quantized P220 file was made from the BF16 weights with Thai OCR quality as the goal: the 4-bit, 3-bit and 2-bit GGUF files use an imatrix built from Thai OCR text, W4A16 / NVFP4 are calibrated on the same text, and FP8 and GGUF Q8_0 need no calibration. Every released file was tested on the full held-out sets (see Quantized variants).

High accuracy for its size: even the smallest file, GGUF Q2_K (3.9 GB), is more accurate (lower CER) than P110-ThaiOCR-4B at BF16 and most other models tested, including GPT-6 Luna.

Usage

Recommended prompt for plain transcription (Thai):

เธญเนˆเธฒเธ™เธ‚เน‰เธญเธ„เธงเธฒเธกเธ—เธฑเน‰เธ‡เธซเธกเธ”เนƒเธ™เธ เธฒเธžเธ™เธตเน‰เนเธšเธšเธ„เธฃเธšเธ–เน‰เธงเธ™เนเธฅเธฐเธ•เธฃเธ‡เธ•เธฒเธกเธ•เน‰เธ™เธ‰เธšเธฑเธš เธฃเธงเธกเธ–เธถเธ‡เธ•เธฑเธงเน€เธฅเธ‚ เธ•เธฒเธฃเธฒเธ‡ เธซเธฑเธงเธ‚เน‰เธญ เนเธฅเธฐเน€เธ™เธทเน‰เธญเธซเธฒเธ—เธธเธเธชเนˆเธงเธ™ เธซเน‰เธฒเธกเธชเธฃเธธเธ› เธซเน‰เธฒเธกเธ‚เน‰เธฒเธกเธชเนˆเธงเธ™เนƒเธ” เธซเน‰เธฒเธกเน€เธžเธดเนˆเธกเธ„เธณเธญเธ˜เธดเธšเธฒเธข

vllm serve PorsukR/P220-ThaiOCR-9B --max-model-len 32768
from openai import OpenAI
import base64

client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
img = base64.b64encode(open("page.png", "rb").read()).decode()
resp = client.chat.completions.create(
    model="PorsukR/P220-ThaiOCR-9B",
    messages=[{"role": "user", "content": [
        {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img}"}},
        {"type": "text", "text": "เธญเนˆเธฒเธ™เธ‚เน‰เธญเธ„เธงเธฒเธกเธ—เธฑเน‰เธ‡เธซเธกเธ”เนƒเธ™เธ เธฒเธžเธ™เธตเน‰เนเธšเธšเธ„เธฃเธšเธ–เน‰เธงเธ™เนเธฅเธฐเธ•เธฃเธ‡เธ•เธฒเธกเธ•เน‰เธ™เธ‰เธšเธฑเธš เธฃเธงเธกเธ–เธถเธ‡เธ•เธฑเธงเน€เธฅเธ‚ เธ•เธฒเธฃเธฒเธ‡ เธซเธฑเธงเธ‚เน‰เธญ เนเธฅเธฐเน€เธ™เธทเน‰เธญเธซเธฒเธ—เธธเธเธชเนˆเธงเธ™ เธซเน‰เธฒเธกเธชเธฃเธธเธ› เธซเน‰เธฒเธกเธ‚เน‰เธฒเธกเธชเนˆเธงเธ™เนƒเธ” เธซเน‰เธฒเธกเน€เธžเธดเนˆเธกเธ„เธณเธญเธ˜เธดเธšเธฒเธข"},
    ]}],
    temperature=0,
    max_tokens=4096,  # stops a rare runaway repeat; raise it for very dense pages
    extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
print(resp.choices[0].message.content)

Tested with vLLM 0.29.0 and transformers 5.17.0.

Evaluation

All open models were run on the same hardware with the same scoring code. Qwen3.5-9B is the base model P220 was fine-tuned from. Models with two runs are averaged. Bold marks the best score in each row.

GPT-6.1 Sol and GPT-6 Luna are closed models, called through their API with the same images, prompts and scoring code. GPT-6.1 Sol was the newest top-tier model available for comparison when these evaluations were run (October 2026). GPT-6.1 Sol was run with thinking set to low; every other model, P220 included, was run with thinking off. On ThaiOCRBench, 15 of the 2,808 items were filtered by the Luna API and returned no answer; an item with no answer scores 0.

Prompts: on ThaiOCRBench every model gets the benchmark's own prompt for each task. For CER, the text crops (ocr_th, iapp, openthaigpt) use the plain OCR prompt from the Usage section, and the PDF pages use a page-to-JSON prompt (text blocks, headings, tables) that falls back to the plain OCR prompt if the JSON is invalid. Typhoon OCR 7B is trained for its own fixed prompt, so its CER rows use its official prompt and sampling settings instead.

ThaiOCRBench (13 tasks, higher is better)

ThaiOCRBench, all 2,808 items.

ThaiOCRBench, all 13 tasks

P220 has the highest 13-task average of the open models in this comparison (0.674, up from 0.572 for P110-ThaiOCR-4B; the best other open model is Qwen3.6-35B-A3B at 0.589, and P220's base Qwen3.5-9B scores 0.462). GPT-6.1 Sol is higher overall (0.738). On the 5 transcription tasks, P220 scores above GPT-6.1 Sol on text recognition, table parsing and document parsing, and below it on full-page OCR and handwriting (5-task average 0.635 vs 0.652).

Task P220-ThaiOCR-9B P110-ThaiOCR-4B GPT-6.1 Sol (API) GPT-6 Luna (API) Qwen3.5-9B (base) Typhoon OCR 7B Typhoon2 Vision 7B Qwen-SEA-LION-v4-8B-VL Gemma-SEA-LION-v4-27B-VL Qwen3.6-35B-A3B Qwen3.8-27B OpenThai 2.0 27B
Chart parsing 0.806 0.757 0.786 0.774 0.758 0.001 0.245 0.744 0.775 0.739 0.745 0.725
Cognition VQA 0.754 0.686 0.783 0.400 0.566 0.486 0.521 0.502 0.410 0.655 0.534 0.559
Diagram VQA 0.591 0.476 0.749 0.417 0.538 0.358 0.277 0.403 0.280 0.590 0.548 0.504
Document classification 0.903 0.773 0.975 0.823 0.921 0.346 0.815 0.865 0.851 0.935 0.893 0.957
Document parsing 0.508 0.409 0.502 0.472 0.451 0.054 0.230 0.486 0.376 0.422 0.530 0.260
Fine-grained text recognition 0.566 0.459 0.791 0.101 0.044 0.091 0.051 0.045 0.076 0.548 0.409 0.477
Full-page OCR 0.791 0.734 0.896 0.656 0.793 0.644 0.494 0.703 0.528 0.801 0.718 0.730
Handwritten content extraction 0.475 0.395 0.523 0.134 0.135 0.205 0.146 0.134 0.180 0.255 0.148 0.119
Infographics 0.825 0.755 0.809 0.391 0.645 0.496 0.564 0.558 0.451 0.699 0.592 0.603
Key information extraction 0.443 0.217 0.575 0.425 0.314 0.000 0.039 0.369 0.417 0.367 0.369 0.422
Key information mapping 0.704 0.464 0.860 0.674 0.521 0.000 0.004 0.700 0.608 0.561 0.478 0.663
Table parsing 0.657 0.585 0.646 0.638 0.111 0.182 0.258 0.576 0.321 0.653 0.630 0.401
Text recognition 0.745 0.723 0.692 0.143 0.208 0.371 0.302 0.143 0.290 0.432 0.182 0.144
Average, all 13 tasks 0.674 0.572 0.738 0.465 0.462 0.249 0.303 0.479 0.428 0.589 0.521 0.505
Average, 5 transcription tasks 0.635 0.569 0.652 0.409 0.340 0.291 0.286 0.409 0.339 0.513 0.442 0.331

Typhoon OCR 7B with its own official prompt scores 0.476 on the 5 transcription tasks (it is not built for the other 8).

ThaiOCRBench, 5 transcription tasks

Character error rate (CER %, lower is better)

Character error rate (CER)

Test set P220-ThaiOCR-9B P110-ThaiOCR-4B GPT-6.1 Sol (API) GPT-6 Luna (API) Qwen3.5-9B (base) Typhoon OCR 7B Typhoon2 Vision 7B Qwen-SEA-LION-v4-8B-VL Gemma-SEA-LION-v4-27B-VL Qwen3.6-35B-A3B Qwen3.8-27B OpenThai 2.0 27B
Thai printed text โ€” mekpro/ocr_th (held-out split, 400) 1.35 1.97 0.31 5.06 3.73 8.99 35.26 7.15 9.69 3.12 10.21 1.15
Thai handwriting โ€” iapp/thai_handwriting_dataset (held-out split, 1,355) 24.43 35.70 9.76 89.78 43.41 23.50 57.38 59.95 86.05 40.93 65.79 17.87
openthaigpt/thai-ocr-evaluation (104 crops) 4.75 6.72 3.88 30.29 10.65 3.88 67.74 12.32 23.95 7.63 12.27 5.22
Thai government PDFs โ€” set A (in-house, 20 pages) 2.74 3.49 12.23 26.58 4.78 8.41 25.68 12.95 27.18 3.71 4.85 11.85
Thai government PDFs โ€” set B (in-house, 66 pages) 5.92 7.36 7.93 33.88 7.43 9.73 24.82 12.69 29.30 7.32 9.38 21.28
Scanned Thai government documents (in-house, 86 pages) 5.13 6.09 6.80 13.84 11.29 9.23 47.90 13.10 27.72 5.84 8.23 13.44

GPT-6.1 Sol is the most accurate on the text crops (printed, handwriting, openthaigpt); P220 has the lowest error on the three sets of full government PDF pages. On those PDF sets GPT-6.1 Sol returned no usable answer for 2 pages in each set, and OpenThai 2.0 27B for 1 page of set B; a page with no answer scores 100%.

How CER is measured

CER is the character edit distance (Levenshtein) between the model output and the reference text, divided by the length of the reference, averaged over items. Each item is capped at 100%, so one runaway output cannot dominate the average. Before comparing, both texts go through the same normalization for every model: Unicode NFC, a detached sara am (เน + เธฒ) joined into เธณ, curly quotes made straight, markdown table rules and |*# markup removed, dotted or underlined fill-in lines (โ€ฆโ€ฆ, ___) removed, and all whitespace removed (Thai does not separate words with spaces, and models differ in how they space and break lines).

The held-out splits of mekpro/ocr_th and iapp/thai_handwriting_dataset are 10% of each subset, chosen with a fixed seed (42) before training and never trained on. The training data was also checked for overlap with ThaiOCRBench. The three PDF sets are in-house pages of Thai government documents with verified reference text.

Quantized variants

Variant Format For CER % handwriting / printed
BF16 safetensors vLLM, transformers 24.59 / 1.39
FP8 compressed-tensors FP8 dynamic vLLM, Ada / Hopper and newer 24.56 / 1.59
W4A16 compressed-tensors INT4 (GPTQ) vLLM, Ampere and newer 25.60 / 1.37
NVFP4 compressed-tensors NVFP4 (MLP) + FP8 (attention) vLLM, Blackwell 25.41 / 1.28
Q8_0 GGUF llama.cpp, Ollama 25.14 / 1.34
Q4_K_M GGUF llama.cpp, Ollama 24.81 / 1.29
Q3_K_M GGUF llama.cpp, Ollama, low memory 27.39 / 1.46
Q2_K GGUF llama.cpp, Ollama, lowest memory 29.63 / 1.56

The full held-out sets from the CER table (iapp handwriting, 1,355 items; ocr_th printed text, 400 items), run on each released file, lower is better. The BF16 row is the released merged weights; the CER table above comes from the evaluation run before merging, so its numbers differ slightly. GGUF files were run in llama.cpp with --image-min-tokens 64 and the F16 vision encoder.

Training data (summary)

An expanded and improved version of the P110-ThaiOCR-4B training data, built with newer and more varied synthetic document techniques, together with pages from publicly available Thai documents with verified text and public Thai OCR datasets (mekpro/ocr_th, iapp/thai_handwriting_dataset, training splits only). No benchmark test data was used for training.

Limitations

  • Upside-down or heavily rotated pages are not detected; rotate pages upright first.
  • Very long or dense pages can occasionally loop; set a max_tokens limit.
  • Rare stray non-Thai glyphs inside Thai words can appear.

License and attribution

Developed by Porsuk Rungarunsrisuk. Released under Apache-2.0 (see LICENSE and NOTICE). Built on Qwen3.5-9B by the Qwen team (Alibaba Cloud); this project is not affiliated with or endorsed by the Qwen team.

Citation

@misc{rungarunsrisuk2026p220thaiocr,
  title  = {P220-ThaiOCR-9B: a 9B vision-language model for Thai document OCR},
  author = {Rungarunsrisuk, Porsuk},
  year   = {2026},
  url    = {https://huggingface.co/PorsukR/P220-ThaiOCR-9B}
}
Downloads last month
12
Safetensors
Model size
10B params
Tensor type
BF16
ยท
F32
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for PorsukR/P220-ThaiOCR-9B

Finetuned
Qwen/Qwen3.5-9B
Finetuned
(1225)
this model
Quantizations
5 models

Collection including PorsukR/P220-ThaiOCR-9B