The same db_resnet50 + crnn_vgg16_bn engine the CLI runs, loaded straight
out of src/ with no edits, no bundler and no npm packages. An import map remaps its
node:* imports onto shims; the weights arrive over fetch into a virtual filesystem.
1 · weights
int8 changes the output — it is not just faster.
Measured on the project's sample page (README, “The int8 trade”):
int8 detection emits 666 boxes instead of 660 and keeps only 564/660 box geometries
bit-exact; int8 recognition keeps every box and costs one character in one word,
but moves confidences — 290 of 660 words move by more than 1e-3.
Pick fp32 if you are comparing against the Node build.
not loaded
2 · execution path
f32 GEMM kernel
—
int8 GEMM kernel
—
compute backend
—
GPU self-check
—
recognition tier
—
threads
—
weights in workers
—
SharedArrayBuffer
—
crossOriginIsolated
—
hardwareConcurrency
—
The GPU tier runs vgg16_bn_r and the CRNN head on WebGPU and is used only
after selfCheck() has run the 64 fixture crops through BOTH paths on this machine and found every one of
260,096 logits bit-identical and all 64 strings equal. It is fp32-only and it replaces the worker pool, so
the worker count below does not apply to it. Detection stays on the CPU either way.
3 · image
Drop a PNG or JPEG here, or click to pick one.
PDF is not supported in the browser.
The Node build rasterises PDFs with pdftoppm; a page has no subprocesses,
and a JS PDF parser would be an npm dependency this project does not have.
Run pdftoppm -r 144 -png in.pdf page first and drop the PNGs.
stages
progress
idle
Single-threaded on the main thread.
Detection is one dependency chain of ~53 convolutions and runs there regardless —
the tab will stop repainting for the whole of it — but with no worker pool,
recognition (about 90% of a page) runs there too. See the reason above.
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. Three independent detectors run for the whole
of every block (a visibility latch, a dedicated-worker timer watchdog, and a
requestAnimationFrame counter); a block any of them trips is marked
THROTTLED and printed, never silently dropped or averaged in.
The run refuses to start at all if this tab is not visible when you click.
This forces fp32 weights — 164.8 MB if they are not already resident.
int8 detection emits 666 boxes on the clean page instead of 660: a genuinely different
engine, not a faster one, and it would read as an engine discrepancy rather than as a
precision change. If the fp32 blobs are not reachable (they are over the Cloudflare Pages
25 MiB per-asset cap and must come from R2 via the doctr-weights-base meta tag)
the benchmark refuses with the reason instead of quietly measuring int8.
fixtures — verified before the clock starts
not verified yet
4 blocks: cpu/clean, cpu/scan, gpu/clean, gpu/scan. quick recognises the first 64 tiles
per block; full recognises every tile (669 on clean, 411 on scan). Detection is a fixed
1024x1024 forward in every block regardless of image size, so it costs the same four times and
cannot be reduced. An estimate for THIS machine appears here after the pre-flight pass.
full is not the low-end mode — on a fanless low-end laptop it is projected at 9–10 minutes,
and such a machine will thermally throttle inside a run that long, which the receipt's
noise-floor line shows as a rising detect rather than hides.