Instructions to use botnick/Netra-2B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use botnick/Netra-2B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="botnick/Netra-2B") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("botnick/Netra-2B") model = AutoModelForMultimodalLM.from_pretrained("botnick/Netra-2B", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use botnick/Netra-2B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "botnick/Netra-2B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "botnick/Netra-2B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/botnick/Netra-2B
- SGLang
How to use botnick/Netra-2B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "botnick/Netra-2B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "botnick/Netra-2B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "botnick/Netra-2B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "botnick/Netra-2B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use botnick/Netra-2B with Docker Model Runner:
docker model run hf.co/botnick/Netra-2B
- Netra-2B v0.1
- Quick start
- Results
- สรุปภาษาไทย (Thai Executive Summary)
- Intended use & limits
- Details
- Modes and output formats
- Loop guard
- vLLM (offline batch)
- Key mapping with your own keys
- Release gate
- Netra-Bench-Real
- ThaiOCRBench
- Hold-out sets (never used for selection)
- Thai receipt key mapping (thairocr)
- Accounting KIE on synthetic documents
- Trust gate
- Training
- Known limitations
- Licence and attribution
- Citation
- Quick start
Netra-2B v0.1
Status: released — v0.1 (2026-10-22). Use it with the loop guard; the release gate is passed only with the guard on. Update 2026-10-22: rounds 6, 7 and 8 were trained and tested against v0.1; none beat it, so v0.1 stays the release (Rounds). The trust gate now checks every field and cross-reads two independent OCR passes (Trust gate).
Netra (เนตร) reads Thai business and accounting documents. Give it a photo or scan of a page and it returns the text as Markdown, or the accounting fields as one JSON object. It is built for developers who process Thai tax invoices, receipts, transfer slips, 50 ทวิ withholding-tax certificates and billing notes. Netra is a 2B vision-language model, fine-tuned from typhoon-ai/typhoon-ocr1.5-2b, and released under Apache-2.0.
Highlights
- Real documents: reading 0.709 vs 0.666 for the base, extraction 0.575 vs 0.406 (Netra-Bench-Real, 147 real Thai finance documents).
- Receipt key mapping: key accuracy 52.8% vs 10.4% for the base, valid JSON 97.3% vs 41.6% (NECTEC thairocr, 1,091 receipts).
- Accounting extraction: field accuracy 90.8% and valid JSON 100% on 120 held-out synthetic documents (accounting KIE).
- Ahead of the larger 3B sibling on real documents: 0.709 / 0.575 vs 0.669 / 0.474 for typhoon-ocr1.5-3b-qat (reading / extraction, same benchmark and settings).
Quick start
Define the two prompts, then run one page. Tested with transformers 5.17.0.
OCR_PROMPT and KIE_PROMPT (copy these exactly)
OCR_PROMPT = """Extract all text from the image.
Instructions:
- Only return the clean Markdown.
- Do not include any explanation or extra text.
- You must include all information on the page.
Formatting Rules:
- Tables: Render tables using <table>...</table> in clean HTML format.
- Equations: Render equations using LaTeX syntax with inline ($...$) and block ($$...$$).
- Images/Charts/Diagrams: Wrap any clearly defined visual areas (e.g. charts, diagrams, pictures) in:
<figure>
Describe the image's main elements (people, objects, text), note any contextual clues (place, event, culture), mention visible text and its meaning, provide deeper analysis when relevant (especially for financial charts, graphs, or documents), comment on style or architecture if relevant, then give a concise overall summary. Describe in Thai.
</figure>
- Page Numbers: Wrap page numbers in <page_number>...</page_number> (e.g., <page_number>14</page_number>).
- Checkboxes: Use ☐ for unchecked and ☑ for checked boxes."""
KIE_PROMPT = """Extract the accounting data from this Thai document image.
Return only one JSON object, no explanation, with these keys (null when the document has no such field):
doc_type, doc_title, doc_no, book_no, date, time, due_date, seller, buyer, payer, payee, items, subtotal, discount,
vat_base, vat_rate, vat, vat_included, wht_rate, wht, total, net_paid, amount_in_words, currency, ref_doc_no, ref_date, payment, extra.
Parties are {name, tax_id, branch, address, phone}; items are [{desc, qty, unit, unit_price, amount}].
Dates YYYY-MM-DD (convert พ.ศ. to ค.ศ.). Tax id = 13 digits, no dashes. Branch = 5 digits, head office "00000".
Write all digits as Arabic numerals. Copy names exactly as printed. Money printed blank or "-" is 0. Numbers must be JSON numbers."""
import torch
from PIL import Image
from transformers import AutoModelForImageTextToText, AutoProcessor
from loop_guard import degenerate # loop_guard.py from this repo
model_id = "botnick/Netra-2B"
model = AutoModelForImageTextToText.from_pretrained(model_id, dtype=torch.bfloat16, device_map="auto")
processor = AutoProcessor.from_pretrained(model_id)
def load(path, max_side=1400):
img = Image.open(path).convert("RGB")
if max(img.size) > max_side:
k = max_side / max(img.size)
img = img.resize((round(img.width * k), round(img.height * k)), Image.LANCZOS)
return img
def run(img, prompt, max_new_tokens, guard=False):
messages = [{"role": "user", "content": [{"type": "image", "image": img}, {"type": "text", "text": prompt}]}]
inputs = processor.apply_chat_template(messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt").to(model.device)
def gen(**kw):
out = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=False, **kw)
return processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0]
text = gen(repetition_penalty=1.05)
if guard and degenerate(text): # loop guard: retry once with stronger anti-repetition
text = gen(repetition_penalty=1.25, no_repeat_ngram_size=16)
return text
page = load("invoice.png")
markdown = run(page, OCR_PROMPT, 4096, guard=True) # page reading
kie_raw = run(page, KIE_PROMPT, 2048) # accounting JSON
Recommended settings: greedy decoding, repetition_penalty=1.05, long side of the image at most 1400 px,
max_new_tokens 4096 for reading and 2048 for extraction, and the loop guard on reading output. The shipped
generation_config.json keeps the base's sampling defaults (do_sample=true, temperature=0.7), so pass
do_sample=False and repetition_penalty=1.05 explicitly, as above.
For batch inference, see vLLM (offline batch). To ask for your own keys, see key mapping.
Results
Netra improves on its base on every benchmark in the chart. The largest gains are in structured output: asked for specific keys, it returns valid JSON with the right keys far more often. The real-document gains are smaller and measured on a small set (34 extraction documents), and the synthetic KIE comparison is not a fair head-to-head because the base never saw that schema. Sources and caveats for each number are in Details.
สรุปภาษาไทย (Thai Executive Summary)
ภาพรวมโมเดล: เนตร (Netra-2B) พัฒนาต่อยอดจาก Typhoon OCR 1.5 2B โดย fine-tune ด้วยเทคนิค LoRA สำหรับเอกสารทางบัญชีและธุรกิจไทยโดยเฉพาะ (เช่น ใบกำกับภาษีเต็มรูป, ใบกำกับภาษีอย่างย่อ, ใบลดหนี้/ใบเพิ่มหนี้, หนังสือรับรองการหักภาษี ณ ที่จ่าย ตามมาตรา 50 ทวิ, สลิปโอนเงิน และใบวางบิล) ตลอดจนเอกสารทางราชการ พร้อมผสานน้ำหนักโมเดล (merge) กลับเข้าสู่โมเดลฐานอย่างเต็มรูปแบบ (1.0 full strength)
รูปแบบผลลัพธ์: แปลงเนื้อหาบนหน้าเอกสารเป็น Markdown (แสดงโครงสร้างตารางด้วย HTML) และสกัดข้อมูลสำคัญทางบัญชี (Key Information Extraction: KIE) ให้อยู่ในรูป JSON ตามโครงสร้างข้อมูล (schema) 28 คีย์มาตรฐานเดียว
ประสิทธิภาพบนเอกสารจริง (Netra-Bench-Real): ประเมินผลกับเอกสารการเงินจริง 147 ฉบับ (ชุดทดสอบที่ไม่เคยใช้ในการเทรน): ความแม่นยำในการอ่านเอกสารอยู่ที่ 0.7091 (โมเดลฐาน 0.6659) และความแม่นยำในการสกัดข้อมูลอยู่ที่ 0.575 (โมเดลฐาน 0.4058) ซึ่งมีประสิทธิภาพเหนือกว่าโมเดลขนาดใหญ่กว่าอย่าง typhoon-ocr1.5-3b-qat (0.6691 / 0.4738)
ชุดทดสอบแบบ Hold-out (802 รายการ): ไม่ด้อยกว่าโมเดลฐานในทุกชุดข้อมูล โดยมีคะแนนชนะ 5 ชุด และเสมอ 2 ชุด (ไม่มีชุดใดแพ้)
ข้อกำหนดสำคัญในการประมวลผล (Loop Guard): สำหรับงานแปลงเอกสารทั้งหน้า (Full-page OCR) จำเป็นต้องเปิดใช้งานระบบป้องกันข้อความวนซ้ำ (loop guard) จึงจะทำผลงานได้เหนือกว่าโมเดลฐานครบทั้ง 3 ภารกิจ หากไม่เปิดใช้ งานอ่านทั้งหน้าจะผิดพลาดมากกว่าโมเดลฐาน (CER 0.386 เทียบกับ 0.359)
ข้อควรระวังในการนำไปใช้: ข้อมูลทางบัญชีที่ใช้เทรนเป็นชุดข้อมูลสังเคราะห์ (synthetic data) ทั้งหมด จึงห้ามนำผลลัพธ์ไปบันทึกบัญชีโดยอัตโนมัติ เว้นแต่จะผ่านการตรวจสอบยืนยัน (Trust Gate) เทียบกับผลอ่าน OCR หรือมีผู้ตรวจทาน และไม่อนุญาตให้นำไปประมวลผลบัตรประจำตัวประชาชนหรือเอกสารส่วนบุคคล
อัปเดต 22 ต.ค. 2569: เทรนรอบ 6, 7, 8 แล้ว ไม่มีรอบไหนชนะ v0.1 จึงยังใช้ v0.1 · ตัวตรวจ (Trust Gate) ตรวจครบทุกช่อง และเทียบกับผลอ่านจากโมเดลอ่านตัวที่สองที่เป็นอิสระ: ผิดแต่ผ่าน 0–2 ฉบับจาก 117–120 ฉบับ แต่ผ่านอัตโนมัติได้เพียง 7–15% ที่เหลือส่งคนตรวจ
Intended use & limits
Use it for:
- Transcribing Thai (and Thai/English) business documents to Markdown for search, archiving and downstream parsing.
- Extracting accounting fields from Thai tax invoices, abbreviated tax invoices, credit/debit notes, 50 ทวิ withholding-tax certificates, bank transfer slips and billing notes into JSON.
- The extraction stage of a pipeline in which every JSON answer passes a deterministic check and failing documents go to a person.
Know its limits:
- The accounting training data is synthetic. No real Thai tax invoice, receipt, slip, 50 ทวิ or billing note was used for training. Expect lower accuracy on real layouts than the synthetic numbers suggest.
- Do not book entries automatically unless each answer passes the trust gate with an OCR cross-read, or a person reviews it. The model can produce plausible but wrong tax IDs, dates and names.
- Real phone photos of VAT-inclusive receipts are still error-prone. The model may recompute VAT on top of a total that already includes it.
- No ID cards or personal documents (ID cards, passports, house registrations, medical records). The model was not trained or evaluated on them, and processing them raises PDPA obligations this card does not address.
- Not for general visual question answering or for languages other than Thai and English.
The full list is in Known limitations.
Details
Modes and output formats
Netra-2B is typhoon-ai/typhoon-ocr1.5-2b (Qwen3-VL-2B architecture) with a LoRA adapter (training round 5) trained on Thai accounting and government documents, merged into the base at full strength. One set of weights does three jobs:
| Mode | Input | Output |
|---|---|---|
| Page reading | page image + OCR_PROMPT (the Typhoon OCR prompt) |
Markdown, tables as one-line HTML, <figure> descriptions |
| Accounting extraction (KIE) | page image + KIE_PROMPT |
one JSON object, fixed 28-key schema |
| Key mapping | page image + a prompt naming the keys you want | JSON with exactly the asked keys |
Reading output:
| Element | Format |
|---|---|
| Text | Markdown; title as # …; reading order left column, then right column, top to bottom |
| Tables | HTML on one line: <table><tr><td>…</td></tr></table>; no <th>, no <thead>/<tbody>; colspan/rowspan kept |
| Pictures, logos, stamps, charts | <figure>…</figure> with a Thai description |
| Page numbers | <page_number>…</page_number> |
| Checkboxes | ☐ / ☑ |
| Equations | LaTeX, $…$ and $$…$$ |
| Digits in reading mode | as printed (Thai digits stay Thai digits; a dashed tax ID stays dashed) |
KIE JSON schema
Every object has all 28 keys. A field the document does not have is null.
| Key | Type | Notes |
|---|---|---|
doc_type |
string | tax_invoice, abbreviated_tax_invoice, credit_note, debit_note, wht_certificate, transfer_slip, billing_note; also invoice, quotation, receipt, payment_voucher |
doc_title |
string | printed title, plus the English subtitle when printed |
doc_no |
string | document number; for a transfer slip, the transaction reference |
book_no |
string | เล่มที่ (receipt, 50 ทวิ) |
date, due_date |
YYYY-MM-DD |
Gregorian, converted from พ.ศ. |
time |
HH:MM[:SS] |
POS and transfer slips |
seller, buyer, payer, payee |
party | {name, tax_id, branch, address, phone}; tax_id 13 digits, no dashes; branch 5 digits, head office "00000", null when none is printed; name without the branch suffix |
items |
list | {desc, qty, unit, unit_price, amount}; billing note: {doc_no, date, due_date, desc, amount}; [] when none |
subtotal, discount, vat_base, vat, wht, total, net_paid |
number | discount 0 when none; total includes VAT, before WHT; net_paid = total − WHT |
vat_rate, wht_rate |
number | percent; wht_rate null when rates are mixed |
vat_included |
bool | line prices already include VAT |
amount_in_words |
string | จำนวนเงินตัวอักษรภาษาไทยโดยไม่ต้องใส่วงเล็บ (50 ทวิ: จำนวนภาษีที่หักและนำส่งไว้) |
currency |
string | "THB" |
ref_doc_no, ref_date |
string, date | original tax invoice referenced by a credit/debit note |
payment |
object | method and details (cash / transfer / cheque / card / QR / PromptPay fields) |
extra |
object | ข้อมูลเฉพาะตามประเภทเอกสาร เช่น 50 ทวิ: form, income[], funds; ใบลดหนี้/ใบเพิ่มหนี้: adjustment, difference |
Normalisation: all digits Arabic. Numbers are JSON numbers (int when whole, else at most 2 decimals). Money printed
blank or - is 0. Masked account numbers and names on slips stay masked as printed.
Shape of an answer (fictitious values):
{"doc_type": "tax_invoice", "doc_title": "ใบกำกับภาษี/ใบเสร็จรับเงิน TAX INVOICE/RECEIPT", "doc_no": "INV-0001",
"book_no": null, "date": "2026-01-15", "time": null, "due_date": "2026-02-14",
"seller": {"name": "บริษัท ตัวอย่าง จำกัด", "tax_id": "0000000000001", "branch": "00000", "address": "…", "phone": null},
"buyer": {"name": "ห้างหุ้นส่วนจำกัด ทดสอบ", "tax_id": "0000000000001", "branch": "00000", "address": "…", "phone": null},
"payer": null, "payee": null,
"items": [{"desc": "ค่าบริการ", "qty": 1, "unit": null, "unit_price": 1000, "amount": 1000}],
"subtotal": 1000, "discount": 0, "vat_base": 1000, "vat_rate": 7, "vat": 70, "vat_included": false,
"wht_rate": null, "wht": null, "total": 1070, "net_paid": null,
"amount_in_words": "หนึ่งพันเจ็ดสิบบาทถ้วน", "currency": "THB", "ref_doc_no": null, "ref_date": null,
"payment": null, "extra": {"po_no": null, "salesperson": null, "copy": null, "note": null}}
Loop guard
On a small share of pages the model repeats one line or phrase, often until the token limit. Without the guard,
degenerate() flags 9 of the 120 round-5 ThaiOCRBench outputs. The guard detects the loop and generates once more
with stronger anti-repetition. degenerate() ships in loop_guard.py and needs only the standard
library:
from loop_guard import degenerate # True if a non-trivial line repeats ≥ 8× or a 15–80 char span repeats ≥ 6× in a row
text = generate(page, do_sample=False, repetition_penalty=1.05)
if degenerate(text):
text = generate(page, do_sample=False, repetition_penalty=1.25, no_repeat_ngram_size=16)
What was measured:
- The guard was measured on reading output with transformers
generateonly (see ThaiOCRBench). There the first pass usedrepetition_penalty=1.1, not the 1.05 recommended here. - It was not measured on extraction output or with vLLM. vLLM's
SamplingParamshas nono_repeat_ngram_size, so with vLLM, re-run the pages thatdegenerate()flags through transformers with the retry settings above. - The retry also breaks legitimately repeated table cells. On ThaiOCRBench tables it costs 0.013 CER compared with no guard (still better than the base).
vLLM (offline batch)
This is the configuration used for Netra-Bench-Real and the hold-out sets below (vLLM 0.31.0, no loop guard).
from transformers import AutoProcessor
from vllm import LLM, SamplingParams
model_id = "botnick/Netra-2B"
processor = AutoProcessor.from_pretrained(model_id)
llm = LLM(model=model_id, max_model_len=8192, max_num_seqs=32, seed=0,
limit_mm_per_prompt={"image": {"count": 1, "width": 1400, "height": 1400}, "video": 0})
def chat(prompt):
return processor.apply_chat_template(
[{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": prompt}]}],
tokenize=False, add_generation_prompt=True)
read = SamplingParams(temperature=0.0, max_tokens=4096, repetition_penalty=1.05)
extract = SamplingParams(temperature=0.0, max_tokens=2048, repetition_penalty=1.05)
pages = [load(p) for p in ["a.png", "b.png"]] # load() from the quick start
reqs = [{"prompt": chat(OCR_PROMPT), "multi_modal_data": {"image": im}} for im in pages] + \
[{"prompt": chat(KIE_PROMPT), "multi_modal_data": {"image": im}} for im in pages]
outs = llm.generate(reqs, [read] * len(pages) + [extract] * len(pages))
An OpenAI-compatible server (vllm serve botnick/Netra-2B --max-model-len 8192) accepts the same prompts. Send the
image as an image_url content part and set temperature=0, repetition_penalty=1.05. The server path has not been
benchmarked for this card.
Key mapping with your own keys
Name the keys you want. The answer uses exactly those keys, in that order, with null when a key is absent. The
prompt decides whether values are copied verbatim or normalised. An example in the style of the training prompts:
ดึงข้อมูลต่อไปนี้จากเอกสาร ตอบเป็น JSON อย่างเดียว:
เลขที่ใบกำกับภาษี
วันที่ออกเอกสาร
ยอดรวมทั้งสิ้น
Seller Tax ID
(ตัวเลขเป็นตัวเลข, วันที่แบบ YYYY-MM-DD, ไม่มีให้ใส่ null)
Ask for "values exactly as printed" instead and the answer keeps the page's own strings (Thai digits, commas and dashes).
Release gate
The release rule was written down before the round-5 results were read: the model must lose to the base on no hold-out set and no ThaiOCRBench task. v0.1 passes only with the loop guard. Without it, ThaiOCRBench Full-page OCR is worse than the base (CER 0.386 vs 0.359). The guard is therefore part of the recommended inference.
Netra-Bench-Real
147 real Thai documents selected from the ThaiOCRBench test split (typhoon-ai/ThaiOCRBench, CC BY-SA 4.0, human-annotated). Selection: category in Finance, Retail, Food & Beverage, Utilities or Insurance, and the question or answer contains a money/tax keyword (ใบเสร็จ, ใบกำกับ, receipt, invoice, ภาษี, VAT, ยอด, บาท, ชำระ, สลิป, โอน).
- Test only. No ThaiOCRBench image appears in any training source (checked by image path against all 17 round-5 training files; content-level near-duplicates were not checked).
- Category mix: Finance 59, Food & Beverage 29, Retail 24, Insurance 21, Utilities 14.
- Reading (113 docs; Full-page OCR, Document parsing, Table parsing, Text recognition, Handwritten,
Fine-grained): the model gets
OCR_PROMPT. Score = 1 − min(1, Levenshtein / reference length) after removing<figure>blocks and tags, markdown symbols and extra whitespace on both sides. Table parsing scores only the<table>…</table>span. Higher is better. - Extraction (34 docs; Key information extraction, Key information mapping): the model gets the benchmark's own question. Score = share of reference values found verbatim (whitespace-normalised) in the output. Higher is better.
- Engine: vLLM 0.31.0, greedy,
repetition_penalty=1.05, settings as in the quick start, no loop guard. Same 147 document IDs for every model.
| Model | Merge strength | Reading (113) | Extraction (34) |
|---|---|---|---|
| typhoon-ocr1.5-2b (base) | — | 0.6659 | 0.4058 |
| typhoon-ocr1.5-3b-qat (larger sibling, reference) | — | 0.6691 | 0.4738 |
| round 3 (rejected) | 1.0 | 0.5729 | 0.4881 |
| round 4 | 1.0 | 0.6415 | 0.506 |
| round 4 (previous candidate, not shipped) | 0.78 | 0.6663 | 0.483 |
| round 5 | 0.70 | 0.6819 | 0.5345 |
| round 5 | 0.78 | 0.6841 | 0.5422 |
| round 5 | 0.85 | 0.6798 | 0.533 |
| round 5 — Netra-2B v0.1 (released) | 1.0 | 0.7091 | 0.575 |
| round 6 (not released) | 0.85 | 0.6945 | 0.5324 |
| round 7 (not released) | 0.85 | 0.6751 | 0.5763 |
| round 8 (not released) | 0.85 | 0.6795 | 0.5652 |
Other models, same 147 documents, harness and prompts (reference only):
| Model | Reading (113) | Extraction (34) | Note |
|---|---|---|---|
| datalab-to/chandra-ocr-2 | 0.6998 | 0.0282 | page reader; does not answer extraction questions as JSON, so its extraction score is not a fair comparison |
| Qwen/Qwen3-VL-8B-Instruct | 0.5412 | 0.6577 | 4× larger general model; best extraction measured here |
| google/gemma-4-12B-it (w4a16 quantised) | 0.3828 | 0.6663 | quantised build; extraction strong, reading weak |
| Pathumma-llm-vision 3.0.0 | 0.5474 | 0.3626 |
Netra v0.1 has the best reading score of every model measured. On extraction the larger general models (8B, 12B) are ahead; closing that gap is the current work.
How to read this table:
- This benchmark is the selection set. The rule, fixed before the round-5 results were read: among the merge strengths whose reading score is at least the base's, take the best extraction. Full strength won on both columns. Because v0.1 was selected on this set, its numbers here are somewhat optimistic. The hold-out sets and ThaiOCRBench (below) are the independent checks.
- Paired difference, v0.1 − base, computed per document: reading +0.0432 (standard error 0.0205), extraction +0.1692 (standard error 0.0957). The reading gain is about two standard errors; the extraction gain is within noise at n = 34. Per-model standard error of the mean is about 0.03 for reading and 0.07 for extraction.
- Per document, v0.1 vs base: 62 better, 54 worse, 31 equal.
- The typhoon-ocr1.5-3b-qat row was run with the same harness, prompts and vLLM settings.
- Round 1–3 reports quote HF-
generatenumbers for the base (0.657 / 0.434). Compare numbers within one engine only.
Per task (mean score, same runs):
| Task | n | Base | r4 @0.78 (previous) | r5 (v0.1) |
|---|---|---|---|---|
| Table parsing | 42 | 0.650 | 0.656 | 0.716 |
| Document parsing | 39 | 0.728 | 0.699 | 0.756 |
| Key information extraction | 17 | 0.516 | 0.486 | 0.424 |
| Key information mapping | 17 | 0.296 | 0.480 | 0.726 |
| Text recognition | 14 | 0.582 | 0.583 | 0.588 |
| Full-page OCR | 10 | 0.823 | 0.895 | 0.896 |
| Handwritten content extraction | 6 | 0.531 | 0.566 | 0.563 |
| Fine-grained text recognition | 2 | 0.000 | 0.000 | 0.000 |
Tasks with n ≤ 10 move by large steps per document; do not read trends into them. Key information extraction (free-form questions about menus, receipts and similar) is below the base (0.424 vs 0.516).
Throughput (vLLM 0.31.0, one RTX 5090, gpu_memory_utilization=0.6, max_num_seqs=32): 147 documents generated in
70.9 s. Not a controlled latency benchmark.
ThaiOCRBench
40 documents per task from the ThaiOCRBench test split (the same 120 documents for every row). Metric: CER after
tag/whitespace normalisation (table: table span only); lower is better. 22 of these 120 documents are also in
Netra-Bench-Real. Engine: transformers generate (Unsloth 2026.10.7, transformers 5.17.0), greedy,
repetition_penalty=1.1, max_new_tokens=4096; loop-guard retry with repetition_penalty=1.25,
no_repeat_ngram_size=16. Any higher mean CER than the base counts as a loss under the release gate.
| Task (n = 40) | Base | r4 @0.78 (previous) | r5, no guard | r5 + loop guard (recommended) | Pass? (r5 + guard) |
|---|---|---|---|---|---|
| Full-page OCR CER | 0.359 | 0.345 | 0.386 | 0.340 | yes (better) |
| Document parsing CER | 0.218 | 0.204 | 0.193 | 0.172 | yes (better) |
| Table parsing CER | 0.308 | 0.270 | 0.280 | 0.293 | yes (better) |
| Outputs with whole-output CER ≥ 0.9 (of 120) | 21 | 22 | 23 | 19 | — |
- Without the guard, round 5 fails the rule (Full-page OCR 0.386 vs 0.359). On Full-page OCR, 34 of 40 pages
tie the base. 6 pages score CER ≈ 1.0: 4 repetition loops until the token limit and 2 diagram-heavy pages
transcribed entirely inside one
<figure>(which the metric strips). The guard fixes loops, not the<figure>case. - The guard was added after the no-guard result was seen, then re-tested on the same 120 pages under the same rule. The Full-page margin over the base (−0.019) is small at n = 40. The guard regenerated 10 of 120 outputs.
Hold-out sets (never used for selection)
802 rows from seven sets that were never used to choose a model. An image-path leak check of the round-5 training mix
against all 802 images found 0 overlap. Scoring is the same as Netra-Bench-Real reading/extraction per row (vLLM,
repetition_penalty=1.05, no loop guard). Difference = v0.1 − base per row, with a paired bootstrap 95% CI (2,000
resamples). "win" and "loss" are used only when the CI excludes 0.
| Set | n | Base | v0.1 | Δ | 95% CI | Verdict |
|---|---|---|---|---|---|---|
| Thai government documents (real) | 34 | 0.570 | 0.580 | +0.011 | [−0.056, +0.074] | tie |
| FinTabNet tables (real) | 150 | 0.727 | 0.814 | +0.086 | [+0.066, +0.108] | win |
| PubTabNet tables (real) | 150 | 0.572 | 0.787 | +0.215 | [+0.178, +0.257] | win |
| DocAtlas Thai pages (real) | 7 | 0.631 | 0.526 | −0.104 | [−0.283, +0.024] | tie |
| WildReceipt key mapping (real) | 61 | 0.347 | 0.728 | +0.381 | [+0.273, +0.493] | win |
| Key mapping (synthetic + CORD/FATURA) | 300 | 0.272 | 0.959 | +0.688 | [+0.650, +0.727] | win |
| General Thai pages (rendered) | 100 | 0.976 | 0.980 | +0.004 | [+0.002, +0.005] | win |
- No set is a loss. The round-4 FinTabNet loss (−0.050, dot-leader loops) became a win after round 5 collapsed dot-leader runs in the training targets.
- Round 5 trained on other pages of the same Thai government collection and of FinTabNet, PubTabNet, WildReceipt and the key-mapping generator. Those hold-out rows are therefore in-distribution for this round (different images; the government pages come from different source PDFs). DocAtlas (n = 7) has a negative point estimate; it is too small to call.
Thai receipt key mapping (thairocr)
An extra, report-only check, not used to choose or release the model. It uses 1,091 receipt images from the test split of NECTEC thairocr (photorealistic rendered receipts, not scans of real ones), never used in round-5 training. Each prompt names the keys to return (key mapping). vLLM, no loop guard. Key accuracy = correct keys / asked keys, pooled over all rows. Row score = the same per row, averaged (95% CI). No thairocr image is included in this repository.
| Model | Key accuracy | Row score [95% CI] | JSON valid | Every key right | Hit token limit |
|---|---|---|---|---|---|
| typhoon-ocr1.5-2b (base) | 0.104 | 0.105 [0.094, 0.117] | 0.416 | 0.002 | 412 of 1,091 |
| Netra-2B v0.1 | 0.528 | 0.525 [0.508, 0.542] | 0.973 | 0.075 | 1 of 1,091 |
Accounting KIE on synthetic documents
120 held-out documents from our synthetic set (synth2/val, fixed seed): transfer slip 24, tax invoice 21,
abbreviated tax invoice 21, credit/debit note 19, 50 ทวิ certificate 18, billing note 17. Prompt KIE_PROMPT;
transformers generate, greedy, max_new_tokens=2048, no repetition penalty, no loop guard.
Metrics:
- Field accuracy over 15 scored fields: doc_type, doc_no, date, seller/buyer name, tax_id and branch, subtotal, discount, vat, wht, total, net_paid, ref_doc_no. Strings are whitespace-normalised; numbers must match within 0.005.
- Item F1 on (desc, amount).
- JSON-valid rate.
- Doc perfect = every scored field and every item right.
Caveat: the base model never saw this schema. Its score measures schema compliance, not reading ability, so this is not a fair head-to-head. The fine-tuned rows are in-distribution (same generator as the training data).
| Model | JSON valid | Field accuracy | Item F1 | Doc perfect |
|---|---|---|---|---|
| typhoon-ocr1.5-2b (base) | 0.75 | 0.0368 | 0.3634 | 0.0 |
| round 2 (rejected for release) | 0.975 | 0.8962 | 0.9662 | 0.483 |
| round 4 @0.78 (previous candidate) | 0.942 | 0.8149 | 0.9482 | 0.25 |
| Netra-2B v0.1 (round 5) | 1.0 | 0.9082 | 0.9208 | 0.542 |
v0.1 field accuracy by type: tax invoice 0.971, abbreviated tax invoice 0.960, credit/debit note 0.934, transfer slip 0.933, billing note 0.918, 50 ทวิ certificate 0.623.
Trust gate
The trust gate auto-accepts a KIE answer only when every deterministic check passes. Otherwise it names the fields a
person must check. Checks: required and forbidden fields per doc_type, item sums, the VAT chain, WHT = total ×
rate, net = total − WHT, tax-ID checksum, Thai baht words, branch suffix rules, and (when an OCR pass is supplied)
that dates, names, tax IDs and branches in the JSON are printed in the OCR text.
Replay on v0.1's KIE outputs above (120 synthetic documents):
| Mode | Accepted | Correct among accepted | Wrong but accepted | Perfect docs rejected | Share of perfect docs accepted |
|---|---|---|---|---|---|
| Checks without OCR cross-read | 87 / 120 (72.5%) | 64.4% | 31 | 9 | 86.2% |
| With OCR cross-read, gold OCR text | 49 / 120 (40.8%) | 95.9% | 2 | 18 | 72.3% |
- Without the cross-read the gate is not sufficient. About 1 in 3 accepted answers is wrong (31 of 87), mostly a wrong month in a date or a one-letter slip in a name. Arithmetic and checksum checks cannot see these.
- Caveat (first replay): the cross-read used the synthetic ground-truth text in place of a real OCR pass. That is an upper bound:
a real OCR pass (for example this model in reading mode) makes its own errors, some shared with the KIE answer.
The cross-read with a real OCR pass has not been measured. The 2 wrong answers it accepted were 50 ทวิ certificates
with a wrong
subtotal.
Production replay (2026-10-22)
The gate was then extended and replayed the way it runs in practice, on Netra's own OCR pass instead of the ground truth:
- Every field scored. A wrong answer counts if any field is wrong, not only the 15 core fields.
- Field placement checks: item descriptions must be whole table cells with their amount on the same row; party names must not be cut short; a branch must sit in its own party's block; payment fields (bank, account, PromptPay, reference) and item quantities and prices must be printed; labelled extras (copy mark, cashier, credit days, due date) must not be dropped.
- Second reader: every OCR check must also pass against an independent reader (typhoon-ocr1.5-3b-qat). Netra's own OCR shares its misreadings: 9 of 10 wrong answers accepted in the first own-OCR replay were the same one-letter Thai name slip in both its OCR and its JSON.
- Token confidence: a free-text value (name, item, address, bank, extras) is sent to a person when any of its tokens has probability below 0.85. The misread names had 0.45–0.84.
| Set | Pipeline | Accepted | Wrong but accepted |
|---|---|---|---|
| synthetic accounting, 120 docs | own OCR + confidence | 18 (15.0%) | 1 (copy mark dropped) |
| synthetic accounting, 120 docs | + second reader | 16 (13.3%) | 1 |
| phone-photo invoices, 117 docs | own OCR + confidence | 17 (14.5%) | 2 (card mask, salesperson dropped) |
| phone-photo invoices, 117 docs | + second reader | 8 (6.8%) | 0 |
- Correct documents rejected by the checks themselves: 0 of 7,200 ground-truth documents.
- The gate is close to zero wrong answers, but it accepts only 7–15% of documents; the rest go to a person. Raising that share needs a more accurate model, not a looser gate.
- 36 adversarial mutations of correct answers (invented fields, shifted rows, cut names) are all rejected; they are regression tests.
Training
Rounds
All rounds: LoRA on typhoon-ocr1.5-2b, scored on test sets never used in training.
| Round | Start | Data (samples) | lr, epochs | Real reading / extraction | Verdict |
|---|---|---|---|---|---|
| r1 | base | 6,072 (synthetic forms + Thai-Synth-Receipts) | 1e-4, 2 | 0.624 / 0.394 (HF; base 0.657 / 0.434) | Not released: full-page OCR CER 13.8% → 26.3% (n = 10), stopped emitting <figure> |
| r2 | base | 42,074 | 5e-5, 1 | 0.606 / 0.273 (HF) | Rejected: answered in its own schema instead of the asked keys; template hallucination; early stop |
| r3 | r2 adapter | 25,778 (+ 8,000 key-mapping rows) | 5e-5, 1 | 0.590 / 0.446 (HF); 0.5729 / 0.4881 (vLLM) | Rejected: reading below base; tables regressed |
| r4 | base (fresh LoRA) | 37,593 (+ table normalisation, self-distillation, real tables) | 5e-5, 1 | 0.6415 / 0.506 (vLLM) at strength 1.0; 0.6663 / 0.483 at 0.78 | Previous candidate (0.78); replaced: hold-out loss on FinTabNet −0.050 |
| r5 — v0.1 (released) | base (fresh LoRA) | 38,630 | 5e-5, 2 | 0.7091 / 0.575 (vLLM; base 0.6659 / 0.4058) at strength 1.0 | No hold-out loss; ThaiOCRBench better on 3/3 with the loop guard (Full-page worse without it) |
| r6 | r5 adapter | 53,196 (+ phone-photo tax invoices, + 10,000 thairocr key-mapping rows) | 5e-5, 1 | 0.6945 / 0.5324 at 0.85 | Not released: below v0.1 on both |
| r7 | r5 adapter | 44,931 (r6 without thairocr; 2,070 crawled government pages replace 300) | lower lr, 1 | 0.6751 / 0.5763 at 0.85 | Not released: reading below v0.1 |
| r8 | base (fresh LoRA) | 44,931 (r7 data) | 5e-5, 2 | 0.6385 / 0.5385 at 1.0; 0.6795 / 0.5652 at 0.85 | Not released: hold-out ties v0.1 on all 7 sets, real reading lower |
Round-8 root cause: the loss is concentrated in long tables. Round 8 runs into the 4,096-token limit (a repetition loop) on 13 of the 147 pages against 7 for v0.1, and a looped page scores near 0. Rounds 7 and 8 share the larger crawled government set, so round 9 returns to the round-5 mix and adds only the phone-photo invoices, to test that cause in isolation. The comparison is being re-run with the loop guard on, as Netra is used in practice.
Round-5 changes, from the round-4 root-cause analysis:
- Dot-leader runs (". . . .", up to thousands of dots in 860 of 2,850 FinTabNet targets) collapsed to
...in every OCR target. Round 4 had learned to emit them until the token limit. <figure>pages no longer upsampled. Round 4 used ×4, which pushed figure pages to about a quarter of OCR targets and made the model describe figures at length while dropping page text.- 4,427 more self-distilled pages (base model outputs, quality-filtered), on top of the 3,959 from round 4. Sources with real gold labels (government pages, FinTabNet, PubTabNet) keep their own labels.
- 300 real Thai government pages added (see below).
- Thai-Synth-Receipts: one OCR target per image (68 contradicting duplicates dropped).
- 2 epochs instead of 1.
Round-5 data mix
Rows as loaded at train start: after per-source caps, after removal of all evaluation and hold-out images, and after replacing a page's own label with the base model's output where the page was self-distilled. A row that has both a reading and a KIE label becomes two samples: 38,630 training samples, 200 validation.
| Source | Rows | Task | Images | Licence |
|---|---|---|---|---|
| In-house synthetic forms (invoice, quotation, receipt, payment voucher) | 1,476 | reading | synthetic | in-house |
| In-house synthetic Thai accounting documents (6 types) | 2,439 | reading + KIE | synthetic | in-house |
| KIE labels for the synthetic forms | 1,383 | KIE | synthetic | in-house |
| Thai-Synth-Receipts | 3,000 | reading | synthetic | MIT |
| Thai Wikipedia text rendered as articles/reports/posters | 8 own label (+1,394 pages in self-distillation) | reading | synthetic render | CC BY-SA 4.0 (text) |
| SmartWhatt thai_doc_parsing (Thai government publications) | 39 own label (+1,201 pages in self-distillation) | reading | real | ODC / CC BY / CC BY-SA (per document) |
| Thai government documents (procurement notices, budget reports, agency financial statements, blank official forms; 13 agencies) | 300 | reading | real | not copyrighted (Copyright Act B.E. 2537, section 7) |
| iapp Thai handwriting | 2,101 | reading | real | Apache-2.0 |
| CORD v2 (receipts) | 760 | partial KIE (annotated keys only) | real | CC BY 4.0 |
| FATURA2 (invoices) | 1,481 | partial KIE (annotated keys only) | template-generated | CC BY 4.0 |
| Key-mapping rows built from the synthetic sets, CORD and FATURA | 8,000 | key mapping | as source | as source |
| Self-distillation (base outputs on train-split images of the sources in this table) | 8,386 | reading | as source | base output (Apache-2.0); images as source |
| FinTabNet.c (table crops → HTML) | 2,850 | tables | real | CDLA-Permissive-2.0 |
| PubTabNet (table crops → HTML) | 2,850 | tables | real | CDLA-Permissive-1.0 |
| DocAtlas Instruct, Thai rows | 31 own label (+537 pages in self-distillation) | reading | real | CC BY 4.0 |
| WildReceipt | 1,168 | key mapping | real | Apache-2.0 |
Every filled-in Thai accounting document in training (tax invoices, receipts, 50 ทวิ, slips, billing notes) is synthetic. The real Thai pages are government publications and Thai handwriting. The government pages (new in round 5) were issued by ministries, departments, state agencies and local administrations, which section 7 of the Thai Copyright Act B.E. 2537 excludes from copyright. Pages showing private individuals' ID numbers or home addresses were filtered out. No customer documents were used.
Hyperparameters
| Setting | Value |
|---|---|
| Method | LoRA on vision and language layers, attention + MLP |
| Rank / alpha / dropout | 16 / 16 / 0 |
| Trainable parameters | 23,724,032 |
| Learning rate | 5e-5, cosine, warmup ratio 0.05 |
| Epochs / steps | 2 / 9,658 |
| Batch | 2 × 4 gradient accumulation = 8 |
| Optimiser | AdamW 8-bit, weight decay 0.01 |
| Precision | bf16 |
| Image size | long side ≤ 1400 px |
<figure> upsampling |
none |
| Seed | 3407 |
| Mean train loss / final eval loss | 0.0953 / 0.1692 |
| Hardware / time | 1 × RTX 5090, 17,550 s (part of it shared with another GPU job) |
| Software | Unsloth 2026.10.7, transformers 5.17.0, TRL 1.15.0, PEFT 0.21.2 |
| Merge | LoRA merged into the base at full strength (1.0), bf16. Strengths 0.85 / 0.78 / 0.70 were evaluated and not selected |
Known limitations
From the per-round root-cause analyses. Items marked not re-measured have not been checked on v0.1.
- Repetition loops. Without the loop guard a few pages loop until the token limit (ThaiOCRBench: 9 of 120
outputs flagged). This is why Full-page OCR fails the release rule without the guard. The guard is measured with
transformers only; with vLLM, flagged pages must be re-run (see Loop guard). Diagram-heavy pages can
still be transcribed entirely inside one
<figure>block. - Template hallucination. Round 2 transcribed a restaurant menu with an invented shop address and
TAX ID:line. Cause: most accounting training targets are synthetic documents with shared header layouts. A plausible fabricated tax ID is the most damaging error for accounting use. Not re-measured; a hallucination probe set is planned. Always cross-check extracted tax IDs against an independent OCR pass. - The trust gate needs an OCR cross-read. Without it, the gate accepted 72.5% of v0.1's synthetic KIE answers, and only 64.4% of those were fully correct (wrong months in dates, one-letter name slips). Use the gate only in cross-read mode with a real OCR pass. That mode has so far been measured only with gold text (an upper bound).
- Early stop on long pages. Round 2 returned 30–190 characters where the reference was 600–2,000. Short synthetic targets dominate the length distribution. Rounds 4–5 added distilled long pages; output/reference length ratio not re-measured.
- Tables. With the loop guard, ThaiOCRBench table CER is 0.293 (base 0.308, no guard 0.280). The retry's
no_repeat_ngram_size=16also breaks legitimately repeated cells. - Accounting data is synthetic. No real Thai tax invoice, receipt, slip, 50 ทวิ or billing note was used for training. Synthetic layouts are low-entropy (round 1 reached train loss 0.0177), so accuracy on unseen real layouts is lower than the synthetic KIE numbers suggest. In one real phone photo of a VAT-inclusive tax invoice, an earlier round (r4) read every key field correctly in reading mode. In KIE mode it returned invalid JSON, recomputed VAT on top of the printed VAT-inclusive total and invented payment fields. This card has no measured result for v0.1 on real photos of accounting documents. 50 ทวิ certificates are the weakest synthetic type (field accuracy 0.623).
- Small real test set. 113 reading and 34 extraction documents; standard error ≈ 0.03 on reading means and ≈ 0.07 on extraction means. Differences under ~0.05 (reading) are not distinguishable from noise, and the extraction gain over the base is not statistically significant.
- Merge strength chosen on the test set. See Netra-Bench-Real. The hold-out sets and ThaiOCRBench are the independent checks.
- Schema imposition / generic extraction. Round 2 answered free-form questions in its own 28-key schema. Key-mapping data fixed most of it (mapping 0.296 → 0.726). Generic non-accounting extraction (food menus, a crypto-exchange receipt) is still below the base (Key information extraction 0.424 vs 0.516) and lower than round 4 (0.486).
- Fine-grained text recognition scores 0.000 for every model on the 2 documents in the set.
- Dates. KIE converts พ.ศ. to ค.ศ.; two-digit พ.ศ. years are read as 25yy. Synthetic training dates span 2025–2027.
Licence and attribution
The model weights are released under Apache-2.0, the licence of the base model. Training data carries the terms below. Attribution is required for CC BY, ODC and CDLA sources.
| Data | Licence | Attribution |
|---|---|---|
| typhoon-ai/typhoon-ocr1.5-2b (base) | Apache-2.0 | Nonesung et al., "Typhoon OCR: Open Vision-Language Model For Thai Document Extraction", arXiv:2601.14722 (2026) |
| Thai-Synth-Receipts | MIT | DuckerMaster/Thai-Synth-Receipts on Hugging Face |
| iapp Thai handwriting | Apache-2.0 | iapp/thai_handwriting_dataset on Hugging Face |
| SmartWhatt thai_doc_parsing | Open Data Common / CC BY / CC BY-SA, per document | SmartWhatt/thai_doc_parsing; per-document title, publisher, licence and URL in ATTRIBUTION.md |
| Thai government documents (round 5) | Not copyrighted: Copyright Act B.E. 2537, section 7 | Issuing agencies listed in ATTRIBUTION.md |
| Thai Wikipedia | CC BY-SA 4.0 | Thai Wikipedia contributors; article URLs recorded per training page |
| CORD v2 | CC BY 4.0 | Park et al., "CORD: A Consolidated Receipt Dataset for Post-OCR Parsing", NeurIPS 2019 Document Intelligence Workshop; naver-clova-ix/cord-v2 |
| FATURA2 | CC BY 4.0 | Limam, Dhiaf, Kessentini, "FATURA: A Multi-Layout Invoice Image Dataset for Document Analysis and Understanding", arXiv:2311.11856 (2023); Zenodo record 10371464 |
| FinTabNet.c | CDLA-Permissive-2.0 (original FinTabNet: CDLA-Permissive-1.0) | Smock, Pesala, Abraham, "Aligning benchmark datasets for table structure recognition", ICDAR 2023, arXiv:2303.00716; IBM FinTabNet, Zheng et al., WACV 2021 |
| PubTabNet | CDLA-Permissive-1.0 | Zhong, ShafieiBavani, Jimeno Yepes, "Image-based table recognition: data, model, and evaluation", ECCV 2020, arXiv:1911.10683; images from the PubMed Central Open Access Subset |
| DocAtlas Instruct (Thai rows) | CC BY 4.0 | Heakl et al., "DocAtlas: Multilingual Document Understanding Across 80+ Languages", arXiv:2605.12623 (2026) |
| WildReceipt | Apache-2.0 | Sun et al., "Spatial Dual-Modality Graph Reasoning for Key Information Extraction", arXiv:2103.14470 (2021); OpenMMLab MMOCR |
| ThaiOCRBench (evaluation only) | CC BY-SA 4.0 | Nonesung et al., "ThaiOCRBench: A Task-Diverse Benchmark for Vision-Language Understanding in Thai", arXiv:2511.04479 (2025) |
Modifications to third-party data: table crops and HTML targets rebuilt from annotations (FinTabNet.c, PubTabNet); markdown normalised to the output conventions above (DocAtlas); key-mapping prompts generated from box labels (WildReceipt); CORD/FATURA labels converted to the KIE schema with unannotated keys removed from the target.
Licence decision for this release: the weights are Apache-2.0. CC BY-SA material (Thai Wikipedia text rendered to pages; 36 page images from 15 CC BY-SA SmartWhatt documents) was a training input only. No training data, page image or label is redistributed with the model, and no ThaiOCRBench image is included in this repository.
Citation
@misc{netra2026,
title = {Netra-2B: a Thai accounting-document reader fine-tuned from Typhoon OCR 1.5},
author = {botnick},
year = {2026},
note = {Version 0.1},
url = {https://huggingface.co/botnick/Netra-2B}
}
Please also cite the base model:
@misc{nonesung2026typhoonocropenvisionlanguage,
title = {Typhoon OCR: Open Vision-Language Model For Thai Document Extraction},
author = {Surapon Nonesung and Natapong Nitarach and Teetouch Jaknamon and Pittawat Taveekitworachai and Kunat Pipatanakul},
year = {2026},
eprint = {2601.14722},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2601.14722}
}
- Downloads last month
- 13
Model tree for botnick/Netra-2B
Base model
Qwen/Qwen3-VL-2B-Instruct



