Image-Text-to-Text
Transformers
Safetensors
nemotron_parse
text-generation
VLM
OCR
Parse
conversational
custom_code
Eval Results
Instructions to use nvidia/Nemotron-3.5-Parse with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use nvidia/Nemotron-3.5-Parse with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="nvidia/Nemotron-3.5-Parse", trust_remote_code=True) 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 AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("nvidia/Nemotron-3.5-Parse", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use nvidia/Nemotron-3.5-Parse with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "nvidia/Nemotron-3.5-Parse" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "nvidia/Nemotron-3.5-Parse", "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/nvidia/Nemotron-3.5-Parse
- SGLang
How to use nvidia/Nemotron-3.5-Parse 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 "nvidia/Nemotron-3.5-Parse" \ --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": "nvidia/Nemotron-3.5-Parse", "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 "nvidia/Nemotron-3.5-Parse" \ --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": "nvidia/Nemotron-3.5-Parse", "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 nvidia/Nemotron-3.5-Parse with Docker Model Runner:
docker model run hf.co/nvidia/Nemotron-3.5-Parse
|
Download docs/postprocessing.md from nvidia/Nemotron-3.5-Parse: direct link, hf CLI and curl.
- Browser
- Download file 23.8 kB
-
https://huggingface.co/nvidia/Nemotron-3.5-Parse/resolve/main/docs/postprocessing.md
- Command line
-
hf download hf://nvidia/Nemotron-3.5-Parse/docs/postprocessing.md
-
curl -L -o postprocessing.md https://huggingface.co/nvidia/Nemotron-3.5-Parse/resolve/main/docs/postprocessing.md
23.8 kB
| # Document postprocessing and multipass parsing | |
| The public helpers operate on model predictions and ordinary page images. They do | |
| not load benchmark labels, scorer code, category IDs, reference answers, or | |
| page-specific rules. They are adapted from the general conversion and crop-merge | |
| components of the bundled ParseBench pipeline. The pinned reproduction bundle | |
| remains unchanged so its recorded hashes and results stay reproducible. | |
| ## Installation | |
| For vLLM, use the model card's repository-source installation: | |
| ```bash | |
| hf download nvidia/Nemotron-3.5-Parse --local-dir nemotron-parse | |
| cd nemotron-parse | |
| python -m pip install '.[vllm,client]' | |
| ``` | |
| This installs the plugin, public `nemotron_parse` Python package, compatibility | |
| modules, and `nemotron-parse` command. No prebuilt wheel is required. | |
| The repository source installs the vLLM plugin, parsing helpers and CLI together. | |
| Archived release artifacts and their digests are listed in the | |
| [build evidence](../reference/package/README.md). | |
| Use version 3.5.0.post5 or later for the restored merge default and post7 or later | |
| for the corrected third-party notices. | |
| For Transformers, keep the model card's inference dependencies. To download the | |
| helpers without installing the package: | |
| ```bash | |
| python -m pip install 'beautifulsoup4==4.13.3' 'Pillow==12.2.0' 'numpy==2.2.6' 'httpx==0.28.1' | |
| hf download nvidia/Nemotron-3.5-Parse \ | |
| --include 'postprocessing.py' 'latex2html.py' 'nemotron_parse/**' --local-dir . | |
| ``` | |
| Run Python in that directory, or put it on your Python import path. Basic text/table | |
| postprocessing only needs Beautiful Soup; the image wrappers also need Pillow and | |
| NumPy. The HTTP backend needs httpx. Downloaded source and installed packages | |
| provide the same public API. | |
| ## Render an existing prediction | |
| ```python | |
| from nemotron_parse import postprocess_document, PostprocessOptions | |
| options = PostprocessOptions( | |
| infer_headers=True, | |
| render_charts=True, | |
| split_chart_value_percent=True, | |
| relocate_chart_captions=True, | |
| ) | |
| document = postprocess_document(raw_model_output, image_size=(width, height), options=options) | |
| print(document['markdown']) | |
| print(document['blocks']) | |
| ``` | |
| `raw_out` contains the cleaned model serialization. Each block has its original | |
| class, normalized `bbox`, literal `text`, and rendered `content`. Supplying | |
| `image_size` adds clipped, ordered `bbox_pixels`. `markdown` renders the complete | |
| page in model order, with section headings and HTML tables for span preservation. | |
| Raw per-block text remains available independently of presentation changes. | |
| Malformed table markup that cannot be converted is preserved rather than passed | |
| through a lossy row splitter; document `warnings` identify these blocks. | |
| Default cleanup preserves empty cells, spans, line breaks, nested inline styles, | |
| and escaped literals. It removes exact repeated blocks at identical coordinates, | |
| repairs empty incomplete serialization prefixes, removes internal continuation | |
| markers, and normalizes existing bullets and soft hyphens in textual roles. | |
| Code/math roles and marked code/math text are protected from soft-hyphen cleanup. | |
| Table conversion is applied to Table/Chart blocks; literal LaTeX in prose, code, | |
| formulas, or Picture blocks retains its source representation. | |
| Header inference uses predicted span topology and existing bold styling, never an | |
| English vocabulary list. It can still make mistakes; disable `infer_headers` to | |
| use only explicit predicted headers. Unknown classes and page numbers are retained. | |
| Chart titles, subtitles, and explicit panel labels can be rendered with their | |
| associated tables. Two guarded presentation choices are enabled by default and can be disabled: | |
| - `split_chart_value_percent=True` splits consistently predicted numeric pairs | |
| such as `32 (16.6%)` into Value and Percent subcolumns. It requires at least two | |
| matching nonempty cells, adds those column labels, and does not calculate values. | |
| - `relocate_chart_captions=True` associates a plain caption immediately above a | |
| chart when geometry and text-preservation checks pass. Notes below remain | |
| standalone. Set `relocate_chart_captions=False` to retain the standalone caption in its source order. | |
| `normalize_text=False`, `deduplicate=False`, and `render_charts=False` disable the | |
| corresponding cleanup/rendering choices. The existing root imports continue to | |
| work: | |
| ```python | |
| from postprocessing import extract_classes_bboxes, postprocess_text | |
| from latex2html import convert_html_table_to_json_hierarchical | |
| ``` | |
| `postprocess_text(..., cls='Table', table_format=...)` supports LaTeX, HTML, | |
| Markdown, record JSON, hierarchical JSON, and CSV. HTML retains merged-cell | |
| structure. Flat exports expand spans and use neutral column names for headerless | |
| tables without consuming the first data row as a header. Duplicate column names | |
| are disambiguated without overwriting values. Hierarchical JSON retains the | |
| existing `headers`, `columns`, and flat `data` fields and additionally exposes | |
| `body_cells` with explicit text, span, and header-role metadata. | |
| ## Parse a page with a running vLLM server | |
| Start the server using the model card. Then: | |
| ```python | |
| from nemotron_parse.inference import DocumentParser | |
| with DocumentParser(base_url='http://localhost:8000/v1', model='nemotron-parse') as parser: | |
| single = parser.parse('page.png') | |
| double = parser.parse_double('page.png') | |
| triple = parser.parse_triple('page.png', triple_regions='union') | |
| documents = parser.parse_many(['one.png', 'two.png'], mode='double', concurrency=128) | |
| ``` | |
| | Mode | Work performed | | |
| | --- | --- | | |
| | Single | Full-page parse and document rendering | | |
| | Double | Full-page parse, then Table/Chart crops from its predicted regions | | |
| | Triple | Full-page parse, separate detection, then Table/Chart crop parsing | | |
| A pass denotes a stage, not one request: each retained crop needs its own model | |
| call. `triple_regions='union'` retains detector regions first and adds parser | |
| regions after overlap filtering; `'detector'` uses only the separate detector. | |
| The region budget defaults to 80 per page and can be changed with `max_regions`. | |
| Crops use 4% plus 12-pixel margins and JPEG quality 95, configurable margins, and | |
| content-normalized coordinates mapped back to the original image. Tiny crops are | |
| skipped. Crops are made in memory and no temporary image files remain. | |
| The default `merge_policy="alternatives"` preserves differing overlapping | |
| Table/Chart predictions from the full-page and crop passes. Near-identical blocks | |
| are deduplicated using geometry and text. Guarded table refinement checks native | |
| grid validity and compatible column counts; the original block order is preserved | |
| and crop alternatives are inserted by their predicted geometry. This policy | |
| matches the reference approach using only model predictions, without labels, | |
| benchmark categories or scorer feedback. | |
| A returned document can contain overlapping or conflicting Table/Chart hypotheses. | |
| Use `DocumentParser(merge_policy="guarded")` (CLI: `--merge-policy guarded`) when | |
| one selected prediction per overlapping same-class region is preferable. This | |
| option applies conservative structural replacement and rejects overlapping | |
| same-class additions. It can discard a correct alternative, reducing recall. | |
| Raw stage responses remain available with either policy. | |
| Returned results include `stages` with original raw responses and usage, and | |
| `inference` with request counts, regions, sampling, and crop/rendering settings. | |
| Empty, failed, filtered, or token-limited generations raise `GenerationError`; | |
| its `stage` and `response` expose the failed response. Successful stage responses | |
| from the same page are only returned when parsing completes. No automatic retry | |
| policy changes the request settings. | |
| Sampling is configurable per stage: | |
| ```python | |
| from nemotron_parse.inference import DocumentParser, GenerationSettings | |
| with DocumentParser( | |
| fullpage=GenerationSettings(temperature=0, top_k=1, repetition_penalty=1.1), | |
| detection=GenerationSettings(temperature=0, top_k=1, max_tokens=4096), | |
| crop=GenerationSettings(temperature=0, top_k=1, repetition_penalty=1.0), | |
| ) as parser: | |
| document = parser.parse_double('page.png') | |
| ``` | |
| Defaults are temperature 0.2 / repetition penalty 1.05 for full-page and detection, | |
| and 0.1 / 1.0 for crops, top-k 50, top-p 1.0, seed 0. Token caps are 8,744 for | |
| parsing and 4,096 for detection. These are practical starting settings rather than | |
| a claim of optimal sampling for every document type. | |
| ## Use an already loaded Transformers model | |
| Continue from the model card's Transformers setup: | |
| ```python | |
| from nemotron_parse.inference import DocumentParser, TransformersBackend | |
| backend = TransformersBackend(model, processor, generation_config=generation_config) | |
| with DocumentParser(backend=backend) as parser: | |
| document = parser.parse_triple(image) | |
| ``` | |
| This uses the same image/crop orchestration without a server or plugin. The adapter | |
| serializes calls to the shared model, uses inference mode, applies stage settings, | |
| and restores the CPU/current-GPU RNG after generation. Model weights and processor | |
| remain caller-owned. Reaching the token cap is conservatively reported as | |
| truncation, including when generation forces an EOS at the cap. | |
| Other inference implementations can supply a callable | |
| `backend(image, *, prompt, stage, settings)` returning | |
| `{'raw_out': text, 'finish_reason': 'stop', 'usage': {...}}`. A string return is also | |
| accepted, but then the custom backend must detect truncation itself. Custom | |
| backends used with `parse_many` should support concurrent calls or use concurrency 1. | |
| ## Command line | |
| ```bash | |
| nemotron-parse --image page.png --mode double --output parsed-page | |
| nemotron-parse --image-dir pages --mode triple --triple-regions union --output parsed-pages | |
| ``` | |
| The download-only equivalent is `python -m nemotron_parse.inference` with the same | |
| arguments. The default outputs are compact `documents.jsonl.gz` and `metrics.json`, containing | |
| blocks, raw stages, settings and timing. Add `--write-page-files` for individual | |
| Markdown and JSON exports. Use a new/empty output directory. Files are written only | |
| after all pages complete. The API accepts PIL images and image paths; the CLI | |
| accepts image files. Render PDF pages to images before calling it. | |
| ## Validation and scope | |
| Run the focused CPU regression suite from the downloaded repository: | |
| ```bash | |
| python -m pip install pytest | |
| python -m pytest -q tests/test_postprocessing.py tests/test_inference.py tests/test_batch.py | |
| ``` | |
| The 86 focused tests passed from both source and the installed wheel. They cover | |
| table structure, text preservation, chart defaults and opt-outs, crop geometry, | |
| merge guards, HTTP handling, explicit truncation reporting, batch ordering, | |
| independent-stage overlap, early crop submission, a shared concurrency bound, | |
| and staged/pipelined region and output equivalence with deterministic responses. | |
| The Transformers adapter is checked with a small CPU model stub. The real-model | |
| H100 runs and official full-page scoring are recorded below and in | |
| [the validation receipt](../reference/validation/postprocessing.json). The pinned reproduction | |
| bundle and its recorded headline result remain unchanged. | |
| ## Prepared batches and timing | |
| `parser.prepare(images, workers=8)` returns a reusable in-memory batch containing | |
| decoded RGB pixels and encoded image requests. Existing RGB JPEG/PNG/WebP payloads | |
| are retained without another lossy encode. Other inputs are encoded as PNG. | |
| The preparation time is reported separately, and cached images can occupy several | |
| GB for thousands of document pages. | |
| `parser.parse_prepared(batch, mode="double", concurrency=128, postprocess_workers=8)` | |
| returns a `BatchResult` with `documents` and `metrics`. The default | |
| `schedule="pipelined"` overlaps independent full-page and detection requests, | |
| submits detector crops as soon as their regions are ready, and adds parser-only | |
| regions in the original union order. Double-pass crops similarly start as each | |
| page finishes its full-page request. Ready crops take the next available HTTP | |
| slot, and new pages fill remaining slots. All stages share one bounded | |
| `concurrency` window, including requests waiting inside vLLM. Crop preparation | |
| uses separate CPU threads; finished pages merge and render while other pages | |
| continue inference. Large batches use spawned rendering processes. | |
| Run standalone scripts under a main guard, or choose `postprocess_workers=1`. | |
| HTTP `parse_many` uses this same pipelined batch engine and returns only the | |
| documents. Its default request window is 128; a custom backend retains the | |
| four-worker default. Pass `concurrency=1` for a backend that requires serial calls. | |
| Use `schedule="staged"` for stage-barrier comparisons. A custom Transformers | |
| backend still serializes its shared model calls. | |
| Metrics include separately reported initial preparation, pipeline elapsed time, | |
| request counts, token-limit counts, and processed-page and fully completed-page | |
| throughput. Pipeline time excludes | |
| initial preparation and output-file writing; model-dependent crop preparation is | |
| included. A benchmark should also report serialization and warmup/server-startup | |
| exclusions explicitly. The same prepared batch can be reused for different modes; | |
| each call generates its own responses and provides an independent pipeline timing. | |
| Pipelined stage spans overlap and must not be added together. | |
| `request_service_seconds` sums concurrent request latency, including server queue | |
| wait; it is not GPU compute time. `peak_inflight_requests` records the client | |
| window, while actual server running/waiting requests require vLLM metrics. | |
| Strict generation handling remains the default. Set `allow_partial=True` (CLI: | |
| `--allow-partial`) only when partial results are useful: capped raw responses are | |
| preserved, affected pages have `complete=False` and `generation_warnings`, and | |
| truncated crops are excluded from refinement. HTTP/transport errors and empty | |
| responses still raise. A successful benchmark execution does not imply that every | |
| page completed within its token limit; inspect both coverage and throughput. | |
| The CLI supports `--no-split-chart-value-percent` and | |
| `--no-relocate-chart-captions` to disable the default presentation rules. | |
| ## Measured prepared-batch performance | |
| The following matched scheduler comparison used the earlier **guarded** merge | |
| policy in package 3.5.0.post4. The restored default and its fresh results are in | |
| [accuracy restoration](#accuracy-restoration-with-the-faster-scheduler). | |
| Measured on all 2,078 unique ParseBench pages with one H100 80GB, vLLM 0.26.0, | |
| `--distributed-executor-backend uni`, tensor parallelism 1, one-token MTP, FA3, | |
| `max_num_seqs=192`, `max_num_batched_tokens=16384`, and `max_model_len=16384`. | |
| Both chart presentation rules were enabled. Each run generated all of its own | |
| responses. The same-server comparison used the same prepared images and | |
| per-mode concurrency limit for both schedulers. | |
| | Mode | Concurrency | Staged pages/s | Pipelined pages/s | Pipelined elapsed | Fully completed pages | | |
| | --- | ---: | ---: | ---: | ---: | ---: | | |
| | Single | 128 | 14.56 | 14.91 | 139.4 s | 2047/2078 | | |
| | Double | 256 | 11.16 | 11.87 | 175.0 s | 2038/2078 | | |
| | Triple | 128 | 7.31 | 10.90 | 190.6 s | 2039/2078 | | |
| Initial decoding/encoding of the full batch took 9.7 seconds and was excluded | |
| from these rates. Timed work includes all model calls, predicted-region crop | |
| preparation, merge and rendering; startup, warmup and output serialization are | |
| excluded. The receipt separately reports serialization-inclusive rates. | |
| These diagnostic runs explicitly enabled `allow_partial=True`. Token-limited | |
| responses remain visible and affected pages count as incomplete. The table reports | |
| processed-page throughput; fully completed-page throughput and cap counts are in | |
| [the benchmark receipt](../reference/validation/batch_benchmarks.json). Strict failure handling remains | |
| the API default. A successful scoring run does not make truncated generations complete. | |
| Concurrency 128 and 256 were compared on a 256-page pilot; the selected settings | |
| above were then checked on the full batch. The earlier full single-pass sweep reached | |
| 15.09 pages/s at 256. These are measured serving profiles, not a throughput ceiling. | |
| Sampling is nonzero: changed batching can change output lengths and crop counts. | |
| The receipt includes request and completion-token totals for each run. | |
| Stages share one bounded client request window. In the pipelined runner, crops | |
| receive available slots while independent full-page/detection requests continue. | |
| Stage spans overlap; adding them does not produce elapsed time. Queue averages | |
| include pipeline ramp-up and final drain, so mean running-request counts need not | |
| equal the configured limit. | |
| ### Quality and the reference output contract | |
| | Previous guarded merge, pipelined | Official overall (%) | | |
| | --- | ---: | | |
| | Single | 72.79 | | |
| | Double | 73.28 | | |
| | Triple | 73.48 | | |
| Each full-run output shown here was freshly scored on all 2,078 pages with the | |
| pinned official scorer | |
| `9af4102e26b51f234ab5f037e19fec05c0696ec6`, with all 241 Python source hashes | |
| verified and zero official scoring errors. Capped generations are included in scoring. | |
| The pinned reference merge can retain an original Table/Chart block and an | |
| overlapping crop alternative when their text differs. The chart evaluator searches | |
| rendered tables for the best matching data. The previous guarded policy uses | |
| replacement and rejects overlapping same-class crop additions; raw alternatives | |
| remain in `stages.crops` for inspection. | |
| A controlled replay of identical baseline triple responses scored 73.44 | |
| with the previous guarded merge and 75.19 with reference-style merging of | |
| the same completed crops. The latter inserted 1,660 overlapping alternative blocks. | |
| This identified the output contract as the cause of the score gap. Package | |
| 3.5.0.post5 restores alternatives as the default, keeping the faster scheduler. | |
| Use the separately pinned reproduction pipeline for the exact headline 75.20 | |
| contract. The receipt includes category metrics and the merge-only replay checks. | |
| ## Accuracy restoration with the faster scheduler | |
| Package 3.5.0.post5 defaults to `merge_policy="alternatives"`. The scheduler, | |
| model settings, region selection and chart presentation defaults are unchanged. | |
| The merge retains differing full-page/crop hypotheses as the reference does; | |
| it adds no model calls. `merge_policy="guarded"` remains available explicitly. | |
| Fresh model inference on all 2,078 pages with one H100, the same uni/FA3/MTP server | |
| profile, and prepared inputs produced: | |
| | Pipeline | Concurrency | Pages/s | Elapsed | Official overall (%) | Fully completed pages | | |
| | --- | ---: | ---: | ---: | ---: | ---: | | |
| | Double | 256 | 11.64 | 178.5 s | 74.92 | 2043/2078 | | |
| | Triple | 128 | 10.61 | 195.8 s | 75.20 | 2038/2078 | | |
| Timing includes crop preparation and merge/rendering. Preparation, startup, warmup | |
| and serialization are excluded; the receipt also reports serialization-inclusive | |
| and fully completed-page rates. These diagnostic runs explicitly used | |
| `allow_partial=True`; token-limit cases remain marked incomplete. | |
| The same saved responses from the earlier staged and pipelined runs were also | |
| rescored with the restored merge. This isolates the merge from generation: | |
| | Saved responses | Guarded overall (%) | Alternatives overall (%) | | |
| | --- | ---: | ---: | | |
| | Double Staged | 73.25 | 74.88 | | |
| | Double Pipelined | 73.28 | 74.96 | | |
| | Triple Staged | 73.51 | 75.23 | | |
| | Triple Pipelined | 73.48 | 75.23 | | |
| All six outputs were freshly scored on 2,078 unique pages with the pinned official | |
| scorer and all 241 source hashes verified, with zero official scoring errors. | |
| Batching at nonzero temperature can change individual responses; this does not | |
| establish bit-identical generations or a guarantee for every document. | |
| The reference-compatible score includes alternative tables/charts. The evaluator | |
| can match the best alternative, so score recovery does not prove that a single | |
| chosen transcription is equally accurate. The alternatives are present in the | |
| actual returned document and its raw stages. Use the guarded policy for a more | |
| compact document when that tradeoff fits the application. | |
| See [the restoration receipt](../reference/validation/accuracy_restoration.json) for category | |
| scores, generation coverage, exact settings, request/token counts, hashes and | |
| controlled replay results. | |
| ## Inference throughput with prepared pages | |
| For prediction throughput, use `result.metrics["inference_pages_per_second"]` | |
| (package 3.5.0.post6 or later). Its wall clock runs from the first model request | |
| start to the last model response across all passes. It includes server queueing, | |
| image encoding inside the server, generation, HTTP transfer and any gaps waiting | |
| for predicted-region selection, crop encoding or dispatch. It excludes initial | |
| page preparation, startup/warmup, final merge/rendering after inference, and writes. | |
| `pipeline_seconds` and `processed_pages_per_second` retain their earlier meanings, | |
| including merge/rendering, for applications that need complete documents. | |
| Concurrent rendering can still compete for CPU during inference; that observed | |
| contention remains in the wall time. We do not subtract overlapping CPU time or | |
| sum concurrent request latencies to claim a higher rate. | |
| The fastest observed profiles compatible with the restored output quality are: | |
| | Pipeline | Concurrency | Schedule | Prediction pages/s | Inference wall time | Fully completed pages | | |
| | --- | ---: | --- | ---: | ---: | ---: | | |
| | Single | 256 | Staged | **15.42** | 134.78 s | 2045/2078 | | |
| | Double | 256 | Pipelined | **11.65** | 178.35 s | 2043/2078 | | |
| | Triple | 128 | Pipelined | **10.62** | 195.70 s | 2038/2078 | | |
| These use 2,078 unique prepared ParseBench page images on one H100 80GB and the | |
| same uni/FA3/MTP serving profile. The single-pass measurement predates the merge | |
| restoration; it has no crops and is unaffected by that merge change. Its saved | |
| stage clock includes executor setup/teardown and is slightly conservative. | |
| Double/triple times are reconstructed exactly from saved stage start/end spans, | |
| including gaps between calls. These are existing measured runs, not a new GPU | |
| run of the timing-only post6 release. See the [timing receipt](../reference/validation/inference_benchmarks.json). | |
| The earlier guarded-merge runs reached 11.88/10.91 prediction pages/s for | |
| double/triple, respectively, but had the lower scores documented above. Use the | |
| restored profiles when comparing speed while preserving the reference output | |
| contract. The restored double/triple scores are 74.92/75.20. | |
| This is realistic batch prediction throughput when pages are prepared ahead of | |
| time and enough work is queued. It is not PDF ingestion throughput or single-page | |
| latency, and is not a measured hardware ceiling. Document complexity, generation | |
| length, crop count, CPU capacity, network placement and batching affect speed. | |
| The numerator includes token-limited pages; these diagnostic runs explicitly | |
| allow partial output. Use `complete_inference_pages_per_second` for the number | |
| of fully completed pages divided by the same wall time. Strict errors remain the | |
| default outside these diagnostic runs. | |
| ```python | |
| prepared = parser.prepare(page_images, workers=8) # outside inference clock | |
| result = parser.parse_prepared( | |
| prepared, mode="triple", concurrency=128, postprocess_workers=8, | |
| ) | |
| print(result.metrics["inference_seconds"]) | |
| print(result.metrics["inference_pages_per_second"]) | |
| ``` | |
| For double pass, use `mode="double", concurrency=256`. The measured fastest | |
| single-pass profile uses `mode="single", concurrency=256, schedule="staged"`; | |
| a single-pass pipelined run at concurrency 128 measured 14.92 prediction pages/s. | |
| A pipelined single-pass run at concurrency 256 has not been measured here. | |