Hilde · Decision models · answer key v2

Page images

763 questions with underlined or numbered words

The Page images benchmark contains the 763 questions of the Text tab's 2,198 that point at marked words on the printed page: 229 at underlined words and 543 at numbered words (9 at both). Each model answered them in up to four ways:

The picture alone (B)

Share of the 763 questions answered right from a picture of the question alone · higher is better

  1. Liquid d1-ultra50.1%
  2. vLLM-SR d348.5%
  3. Drex Max v146.8%
  4. SubQ Snap 1.145.7%
  5. vLLM-SR d3-ultra45.5%
  6. Drex Max 1.145.1%
  7. Jev 1.14.043.3%
  8. OneJev 27B41.3%
  9. GPT-6 Luna (Decisions API)32.6%
  10. Liquid d1-2B (open weights)23.3%

Decision models: worst 23.3%best 50.1% (higher is better) The same model with the text (A)

The text plus the picture (C)

Accuracy with the picture after the text, and with the text alone · higher is better

  1. Liquid d1-ultra72.1% · 73.5%
  2. Drex Max v166.1% · 64.1%
  3. Drex Max 1.165.5% · 64.1%
  4. vLLM-SR d364.9% · 63.2%
  5. vLLM-SR d3-ultra64.5% · 63.0%
  6. SubQ Snap 1.162.9% · 58.1%
  7. OneJev 27B58.7% · 54.0%
  8. Jev 1.14.058.6% · 54.3%
  9. GPT-6 Luna (Decisions API)56.6% · 55.8%
  10. Liquid d1-2B (open weights)26.9% · 29.2%

Text plus picture (C) Text (A)

The best OCR route (D)

Accuracy with one OCR model's reading of the picture as text, the best of the four · higher is better

  1. Liquid d1-ultra67.1%
  2. Fastino GLiDE v1.261.3%
  3. vLLM-SR d360.2%
  4. vLLM-SR d3-ultra58.7%
  5. Perplexity Decider 27B v1.258.2%
  6. SubQ Snap 1.154.3%
  7. Liquid d1.152.3%
  8. OneJev 27B50.6%
  9. GPT-6 Luna (Decisions API)50.3%
  10. Jev 1.14.046.4%

Decision models: worst 46.4%best 67.1% (higher is better) The same model with the text (A) The best route is Surya OCR 2 for every model.

Marks the OCR models kept

Share of the underlined and of the numbered words still marked in the text the models received, on the human-verified text · higher is better

  1. Surya OCR 290.2% · 77.5%
  2. Baidu Unlimited-OCR-231.1% · 55.5%
  3. dots.mocr15.9% · 54.0%
  4. Chandra OCR 20.0% · 73.7%

Underlined words Numbered words

Info The tables, what we found and how the benchmark was run

About this test

Accuracy: the share of the 763 questions answered right (a refusal or an answer outside the options counts as wrong), with its 95% interval under it in the tables. This tab is ranked on its own; its numbers do not enter the Text tab's ranking.

Download: vision.json (every number on this page, and each answer's pick and option probabilities; no question text, page image or OCR text).

Decision models

Image models

Ranked by B · image only, highest first.

ModelA · text onlyaccuracy, higher is betterB · image onlyaccuracy, higher is betterC · text plus imageaccuracy, higher is betterBest OCR routeaccuracy, higher is better
Liquid d1-ultra1 question per request73.5%70.3% to 76.5%50.1%46.5% to 53.6%72.1%68.8% to 75.2%67.1%via Surya OCR 263.7% to 70.3%
vLLM-SR d31 question per request63.2%59.7% to 66.5%48.5%45.0% to 52.0%64.9%61.4% to 68.2%60.2%via Surya OCR 256.6% to 63.6%
Drex Max v11 question per request64.1%60.6% to 67.4%46.8%43.3% to 50.3%66.1%62.6% to 69.3%Not available: not run
SubQ Snap 1.11 question per request58.1%54.5% to 61.5%45.7%42.2% to 49.3%62.9%59.4% to 66.3%54.3%via Surya OCR 250.7% to 57.8%
vLLM-SR d3-ultratransformers, rev 52ba9e7 · 1 question per request63.0%59.6% to 66.4%45.5%42.0% to 49.0%64.5%61.0% to 67.8%58.7%via Surya OCR 255.2% to 62.2%
Drex Max 1.11 question per request64.1%60.6% to 67.4%45.1%41.6% to 48.6%65.5%62.1% to 68.8%Not available: not run
Jev 1.14.01 question per request54.3%50.7% to 57.8%43.3%39.8% to 46.8%58.6%55.1% to 62.0%46.4%via Surya OCR 242.9% to 49.9%
OneJev 27B1 question per request54.0%50.4% to 57.5%41.3%37.8% to 44.8%58.7%55.2% to 62.2%50.6%via Surya OCR 247.0% to 54.1%
GPT-6 Luna (Decisions API)Decisions API, beta · 1 question per request55.8%52.3% to 59.3%32.6%29.4% to 36.0%56.6%53.1% to 60.1%50.3%via Surya OCR 246.8% to 53.9%
Liquid d1-2B (open weights)1 question per request29.2%26.1% to 32.6%23.3%20.5% to 26.5%26.9%23.8% to 30.1%Not available: not run

Text-only models

These models take no image input, so B and C do not apply. Ranked by their best OCR route, highest first.

ModelA · text onlyaccuracy, higher is betterB · image onlyaccuracy, higher is betterC · text plus imageaccuracy, higher is betterBest OCR routeaccuracy, higher is better
Fastino GLiDE v1.2local · 1 question per request64.7%61.3% to 68.1%No image inputNo image input61.3%via Surya OCR 257.8% to 64.7%
Perplexity Decider 27B v1.2local · 1 question per request62.3%58.8% to 65.6%No image inputNo image input58.2%via Surya OCR 254.7% to 61.6%
Liquid d1.11 question per request56.2%52.7% to 59.7%No image inputNo image input52.3%via Surya OCR 248.7% to 55.8%

Not run

  • Perplexity Decider 27B v1.1: its published text runs went through Perplexity's API, and no API key was available. By decision, its released checkpoint was not run on our GPU instead.

Other models: chat and general models

No chat or general model has been run on the page images yet.

How well the OCR models read the pages

The error rates and counts use only human-verified question text: the crops of the questions whose text was checked by hand (status 'verified' in the bank), 375 of 763 questions, 378 crops with their passages. Each OCR model's text is compared with that text. Character error rate: edits over the reference's characters, summed over the 378 crops. Turkish letter error rate: edits of the same alignment whose reference or OCR character is one of ı İ ş Ş ğ Ğ ç Ç ö Ö ü Ü, over the reference's count of those letters. Kept: an underlined word still marked, or a numbered word's numeral still there, in the text the models received.

OCR modelCharacter error ratelower is betterTurkish letter error ratelower is betterUnderlined words kepthigher is betterNumbered words kepthigher is better
Chandra OCR 2datalab-to/chandra-ocr-2 at af93b474.69%1.44%0 of 1,5581,412 in its raw output931 of 1,264
dots.mocrdots-studio/dots.mocr at e539fbb4.28%0.71%247 of 1,558682 of 1,264
Surya OCR 2datalab-to/surya-ocr-2 at 3b3d4cd5.09%1.12%1,406 of 1,5581,407 in its raw output980 of 1,264
Baidu Unlimited-OCR-2baidu/Unlimited-OCR-2 at 04647c714.94%3.21%485 of 1,558702 of 1,264

What we found

All 10 image models were less accurate with the picture alone (B) than with the text (A): by 5.9 points (Liquid d1-2B (open weights)) to 23.5 points (Liquid d1-ultra), each difference significant in the exact McNemar test.

With the picture added to the text (C), 8 of the 10 were more accurate than with the text alone, by 0.8 to 4.8 points; the gains of Jev 1.14.0, OneJev 27B and SubQ Snap 1.1 (4.3 to 4.8 points) are significant, the others are not. Liquid d1-ultra and Liquid d1-2B (open weights) were less accurate with the picture (1.4 and 2.4 points), not significantly.

Through an OCR model (D), each of the 10 models with an OCR route was less accurate than with the text: its best route was 3.0 to 7.9 points below condition A. Surya OCR 2 gave the best route for all 10. It kept the most of the marks these questions point at (1,406 of 1,558 underlined words and 980 of 1,264 numbered words), although dots.mocr read the text with fewer errors (character error rate 4.28% against 5.09%). For the 7 image models with both, every OCR route was more accurate than the picture alone (B): the best route by 3.1 to 17.7 points, significant for 6 of the 7 (not for Jev 1.14.0).

Liquid d1-ultra was the most accurate model in every condition: 73.5% with the text, 50.1% with the picture alone, 72.1% with both and 67.1% through Surya OCR 2.

Each cell is one pass over the 763 questions. Each test compares two conditions of the same model on the same questions, one comparison at a time, without an adjustment for the number of comparisons; the OCR routes have no paired test against the text (A).

How the benchmark was run

Conditions

  • A: clean text: the published text-track run (the same questions, prompts and options), graded against the bank as it stands now
  • B: the crop(s), passage first, as the whole state; instructions 'Which option is correct?'; the question's own option letters as the choices, each described by its letter only
  • C: condition A's text and options, with the crop(s) after the text; A's instructions ('Which option is the correct answer to this Turkish multiple-choice question?')
  • D: one OCR model's output for the crop(s) (passage first, joined by a blank line) as the state, as its official export gives it, with layout tags removed (Unlimited-OCR-2's block boxes and page marks) and markup kept; instructions 'Which option is correct?'; option letters only, as B

Each cell is one run. It is one pass over the 763 questions, not an average of repeated runs (6 passes were resumed after an interruption; see Models and cost).

Pictures of the questions

The bank's per-question boxes (questions.bbox_json, passages.bbox_json, from its extraction run), on pages rendered from the source PDF with PyMuPDF at zoom 2.5 (180 dpi, 1600 x 1990 px), padded 4 px; on pages that print an answer key, each crop stops 6 px above the top line of the key box; crops under 280 px high are padded with white below, and crops over 1568 px on a side are scaled down to 1568 (TypeSafe's image limits; every model gets the same files). 767 images for 763 questions; 129 crops stop above an answer key box, 3 were padded and 1 scaled. Questions per topic: 1: 73, 2: 86, 3: 3, 4: 2, 5: 54, 6: 1, 7: 47, 8: 19, 9: 18, 10: 37, 11: 53, 12: 31, 13: 46, 14: 20, 15: 23, 16: 94, 17: 23, 18: 68, 19: 32, 20: 33.

We sent every crop at its own size: at most 1568 pixels a side (one crop is 1568; the rest are at most 1169). The input tokens each API reported match its documented billing grid at those sent sizes, on all 763 questions: TypeSafe's Jev, 2 tokens per 28 by 28 pixel cell plus a fixed 354 to 361 tokens (375 to 380 with two crops); Liquid's d1-ultra, 44 plus 1.5 tokens per 32 by 32 pixel patch; OpenAI's GPT-6 Luna through the Decisions API, 130 plus 1 token per 32 by 32 pixel patch with the default detail setting. A downscale before counting, to 1024 pixels or less, would have changed these counts. This shows the size at which each API counted and billed an image, not what the model saw inside it. TypeSafe documents that it rejects images outside 280 to 1568 pixels a side instead of resizing them; that is a rule about accepted inputs, not a guarantee about every internal tensor. Liquid's and OpenAI's image dimensions after tokenization are not observable to us.

Checked by eye for leaks: 20 crops, with 0 leaks found. The first detector looked only for pink or dark lines and missed the blue key boxes of some units, whose keys are printed in plain sight (page 60 shows '1-C 2-C ...'); it was replaced by a colour-blind test (any coloured or dark row across 85% of the width) before any model saw a crop; all 368 crops on key pages now stop 6 px above the box's top line (1796 or 1797 px), and no rendered page without a recorded key has such a box.

We also searched every OCR output, 3,068 in all (4 OCR models × 767 crops), for answer-key text: number-letter pairs as a key prints them (“1‑C 2‑C”) and key wording. None had key text. Six Baidu Unlimited-OCR-2 outputs each had one number-letter pair; in each, the number is the question’s own number and the letter is not its answer.

OCR models

  • Chandra OCR 2 (datalab-to/chandra-ocr-2 at af93b47): chandra-ocr 0.2.0 official vLLM path (chandra.model.InferenceManager(method='vllm'), the code behind the `chandra <input> <out> --method vllm` CLI) against a vLLM 0.32.1rc1.dev613+g8977b13a1 OpenAI-compatible server (local nightly venv on 1 GPU, started with the same arguments as chandra's `chandra_vllm` launcher: --dtype bfloat16 --max-model-len 18000 --max-num-seqs 64 --max-num-batched-tokens 8192 --enable-prefix-caching --mm-processor-kwargs {min_pixels: 3136, max_pixels: 6291456}; the launcher itself would run the vllm/vllm-openai:v0.17.0 docker image, which was not used). Each crop was loaded with chandra.input.load_image (upscaled so the shorter side is >= 1536 px), resized by chandra's scale_to_fit, sent as PNG with the default 'ocr_layout' prompt (PROMPT_MAPPING['ocr_layout']); all 767 requests in one generate() call with 64 client threads. Decoding: temperature 0, top_p 0.1, max_tokens 12384 (chandra defaults); chandra's built-in retry re-generates an output with a detected repeat-token loop at temperature min(0.2*(n+1), 0.8), top_p 0.95 (up to 6 retries). The model emits layout HTML (<div data-bbox data-label> blocks); the stored text is chandra's official markdown export (BatchOutputItem.markdown = chandra.output.parse_markdown with the CLI defaults include_images=True, include_headers_footers=False, i.e. Page-Header/Page-Footer blocks dropped). dtype bfloat16. In this run chandra's client logged 39 repeat-token retry events (the retried pages' final text therefore comes from a sampled, non-greedy re-generation; chandra does not report which pages).
  • dots.mocr (dots-studio/dots.mocr at e539fbb): dots_mocr parser (github.com/rednote-hilab/dots.mocr @ 23f3e5612fb8, installed editable) official vLLM path: DotsMOCRParser.parse_file(<png>, prompt_mode='prompt_layout_all_en', fitz_preprocess=True), i.e. `python3 dots_mocr/parser.py <png>` with the CLI defaults (prompt_layout_all_en, top_p 1.0, max_completion_tokens 16384, dpi 200 fitz re-render of the image, no min/max_pixels override) except temperature 0.0 instead of the CLI's 0.1 for greedy decoding; requests go to a vLLM 0.32.1rc1.dev613+g8977b13a1 OpenAI-compatible server (local nightly venv, native DotsOCRForCausalLM implementation, launched as in the model card: --gpu-memory-utilization 0.9 --chat-template-content-format string --served-model-name model --trust-remote-code; model dtype bfloat16 from config). 64 images in flight. The parser's request function was replaced by an identical copy that also records finish_reason (to detect token-limit hits). The model returns layout JSON (bbox, category, text); the stored text is the parser's own markdown export (<name>.md = format_transformer.layoutjson2md(..., no_page_hf=False): cells in reading order joined by blank lines, Formula as LaTeX, Table as HTML, Picture cells as an inline base64 PNG image link); when the JSON fails to parse, the parser's OutputCleaner fallback text is stored (noted in failures).
  • Surya OCR 2 (datalab-to/surya-ocr-2 at 3b3d4cd): surya-ocr 0.22.1 official full-page OCR path (the code behind `surya_ocr <folder>`: surya.input.load.load_from_folder, then RecognitionPredictor(SuryaInferenceManager())(images, full_page=True)) against a vLLM 0.20.1 OpenAI-compatible server (vllm==0.20.1 from PyPI in an isolated venv on 1 GPU, the version of surya's vllm/vllm-openai:v0.20.1 docker image, started with the same arguments surya's VllmBackend gives that image for VLLM_GPU_TYPE=h100: --dtype bfloat16 --max-model-len 18000 --max-num-seqs 104 --max-num-batched-tokens 16384 --enable-prefix-caching --mm-processor-kwargs {min_pixels: 3136, max_pixels: 6291456} --speculative-config {method: mtp, num_speculative_tokens: 2}; docker itself was not used). The server venv pins transformers 5.17.0 (surya requires >=5.12.1): transformers >=5.18 loads this checkpoint's char-level tokenizer as the byte-level Qwen3_5Tokenizer, which corrupted every prompt and turned non-ASCII output letters (ı ğ ç ö ü ...) into U+FFFD/control bytes in a first run with vLLM nightly + transformers 5.20 (discarded). Attached via SURYA_INFERENCE_BACKEND=vllm, SURYA_INFERENCE_URL; client concurrency 96 (surya default for that sizing). Each crop is sent once as PNG (after surya's scale_to_fit) with the default full-page prompt HIGH_ACCURACY_BBOX_PROMPT; decoding: temperature 0, top_p 0.1, max_tokens 12288 (surya defaults; SURYA_FULLPAGE_REGEN off), surya's client retries a request that ends in a repeat-token loop at temperature min(0.2*(n+1), 0.8), top_p 0.95 (max 3 retries), and a page whose full-page HTML fails to parse or loops falls back to surya's layout + per-block OCR. The model emits HTML layout blocks (<div data-bbox data-label>); surya parses them into PageOCRResult.blocks in reading order with per-block inner HTML. Surya has no markdown/text export of its own (that lives in marker), so the stored text is the non-skipped blocks' `html` (surya's official per-block output, math in <math>, tables as <table>) joined in reading order with blank lines; bounding boxes and labels are dropped. dtype bfloat16. In this run: 101 client-side retry requests (non-greedy), 101 of them full-page; 2 pages used the layout + per-block fallback (12 layout/block requests).
  • Baidu Unlimited-OCR-2 (baidu/Unlimited-OCR-2 at 04647c7): Model card's Transformers path (trust_remote_code; the snapshot's modeling_unlimitedocr.py), README 'Single-page inference': AutoModel.from_pretrained(..., trust_remote_code=True, use_safetensors=True, torch_dtype=torch.bfloat16).eval().cuda(), then model.infer(tokenizer, image_file=<png>, output_path=<dir>, save_results=True) per crop with all decoding defaults: prompt built automatically as '<PAGE><image>Multi page parsing.' for one page, crop_mode=None -> gundam dynamic tiling for a single page (base_size 1024, image_size 640, max_crops 16), greedy argmax (temperature 0), PageCountGuard on with max_page_tokens=20000, max_length 131072, no n-gram blocking, DASH sparse attention (dash_topm 16). eval_mode=True only disables the console token streamer; return_dict=True only returns token ids/finish reason. Batch size 1 per model copy (the path has no batching); 8 worker processes, each with its own model copy, shared GPU 2 and took the crops round-robin; seconds = first worker start (after all models loaded) to last worker end. The model emits markdown with grounding refs (<|ref|>..<|/ref|><|det|>[[boxes]]<|/det|>) and a <PAGE> marker; the stored text is the model's own save_results output result.md (its _uocr_save_results: non-image refs/boxes removed, image refs replaced by ![](images/page_0_<i>.jpg) links, '<PAGE>' line kept). torch 2.13.0+cu130, transformers 4.52.1, dtype bfloat16.

Models and cost

Every request went through the benchmark's runner (the runs record its commit: 9162663 for runs 1 to 28, on the published main; 0fbd97b for runs 29 to 55, merged into the published main in fff7c20d; 61f3824 for runs 56 to 59, merged into the published main in 4908dd9d; 737862e for runs 60 and 61, merged into the published main in 80380b72; 0ad2797 for runs 62 to 67, merged into the published main in c458470b), one question per request; the images went in each API's own format. Self-hosted models ran on 1 × RTX PRO 6000 Blackwell (96 GB); BF16; 1 × RTX PRO 6000 Blackwell (96 GB); BF16 (the published text run used a DGX Spark); 1 × RTX PRO 6000 Blackwell (96 GB); BF16; d1_server.py with the model card's system_one (transformers 5.22.1, torch 2.15.0); 1 × RTX PRO 6000 Blackwell (96 GB); BF16; drex_max_server.py with Nace's drex_max at e206b27 (transformers 5.17.0, torch 2.15.0, flash-linear-attention 0.6.0; causal_conv1d not installed, so its reference PyTorch path); 1 × RTX PRO 6000 Blackwell (96 GB); BF16; vLLM 0.32.1rc1.dev613+g8977b13a1 with --max-num-seqs 64 --gpu-memory-utilization 0.78 (another process held 18 GB of the GPU). The API image runs (B and C) cost $0.40 and the API runs of condition D $0.29, at the prices in results.json.

Engine check: we asked vLLM-SR d3's condition B again through d3_server.py on transformers, the server d3-ultra ran on (run 55): 365 of 763 right (47.8%), against 370 (48.5%) through vLLM (run 41), the run in the table. Exact McNemar test: 10 questions right only on transformers and 15 only on vLLM, p = 0.42; 47 of 763 picks differ.

Each cell holds one stored answer per question: 45,780 in all, from 66 runs. Six cells were finished by a second run after an interruption (Jev 1.14.0 B, runs 1 and 6; Liquid d1-ultra B, runs 2 and 5; GPT-6 Luna B, runs 3 and 4; GPT-6 Luna D · dots.mocr, runs 17 and 27; OneJev 27B B, runs 28 and 35; Liquid d1.1 D · dots.mocr, runs 16 and 26); these are continuations, not repeats. 45,764 answers name an option and 16 are refusals. The runs’ logs record 143 client retries: 5 in run 4 and 2 in run 9, both GPT-6 Luna after HTTP 503 errors, and 2 in run 34, d3-ultra after HTTP 500, and 54 in run 58 and 80 in run 59, both Drex Max 1.1 after HTTP 429 errors. The logs do not cover every run, and an attempt that failed and was asked again is not stored, so 45,923 is a lower bound on the requests sent. Tokens and costs come from the stored responses only: not every attempt and not an invoice. The usage and cost of attempts that were not kept are unknown, not zero.

Liquid d1-2B (open weights) answered conditions B and C four more times with the same inputs and runner (passes 2 to 5, in vision-repeats.json); every pass gave the same picks and option probabilities as the table's runs 60 and 61. The table shows the first pass, chosen as the cell before the runs.