onnx-demo — the same two models, through ONNX Runtime Web

db_resnet50 + crnn_vgg16_bn, the OnnxTR exports, run by onnxruntime-web on WebGPU or WebAssembly. Everything around the two forward passes — the resize, the normalisation, the DB postprocessor, the crop geometry, the CTC decode and the document builder — is doctr-demo's own code, vendored byte-for-byte. So any difference between the two sites on the same machine is the inference engine, and nothing else.

1 · models

base—
db_resnet50100.9 MB
crnn_vgg16_bn63.2 MB
164 MB, and not from this origin. Cloudflare Pages caps one asset at 25 MiB, so the two .onnx files are served from R2 and fetched cross-origin with CORS. They are cached in the page's own Cache API bucket (onnx-demo-models-v1) afterwards, so a reload is instant. Append ?models=/models/ to run against a local copy.
not loaded

2 · execution path

onnxruntime-web1.29.0
ORT build—
EP requested—
EP that started—
db_resnet50 nodes—
crnn nodes—
WebGPU adapter—
wasm threads—
crossOriginIsolated—
SharedArrayBuffer—
hardwareConcurrency—


“EP that started” is not a label, it is a measurement. ORT falls back from webgpu to wasm silently when it is given a list of providers, so this page gives it exactly one: a session that exists is proof that provider initialised. The two node rows below it are the other half — inside a webgpu session ORT still assigns any op it has no GPU kernel for to the CPU, and those counts are scraped from ORT's own graph-partitioner log.

3 · image

Drop a PNG or JPEG here, or click to pick one.
For a like-for-like comparison against doctr-demo, use doctr-native/test/fixtures/sample.png — the page both projects' 660-word parity numbers were measured on. PDF is not supported, for the same reason it is not on doctr-demo: rasterising one in a page would mean a JS PDF parser.

stages

    progress

    idle
    The tab stops repainting during detection. One 1024×1024 forward pass through db_resnet50 is a single dependency chain and ORT runs it on this thread — same as doctr-demo. Recognition yields between batches, so the bar moves there.

    4 · benchmark — the standard measurement

    PARITY, NOT ACCURACY. The two reference files are Python doctr 1.0.1's own output on these two images, not the true text of the pages. On the scan doctr itself reads Orgnal for “Original”, Tor for “for”, and the GSTIN as 06AAHCM7396M:2P where the page reads “06AAHCM7396M1ZP”. A score of 407/407 means REPRODUCED DOCTR EXACTLY. It does not mean the page was read correctly.
    Keep this tab in front for the whole run. A browser throttles a window it thinks nobody is looking at, by up to an order of magnitude, with no error anywhere — and occlusion by another application's window does not fire visibilitychange. A run that was ever hidden is marked THROTTLED, not averaged in. The page refuses to start if the tab is not visible at click time.

    verifying the fixtures…

    press quick — a pre-flight pass measures this machine before committing it to anything

    4 blocks per click: wasm/clean, wasm/scan, webgpu/clean, webgpu/scan. quick recognises the first 64 tiles of each block; full recognises every tile and is the only mode comparable with the numbers already recorded for this project. full is not the mode for a slow machine — on a Core M it is minutes per block, and a fanless machine will thermally throttle inside a run that long, which the receipt's noise-floor line shows as a rising detect rather than hides.

    log