Configuration
Configure TurboOCR via environment variables passed to docker run -e KEY=VALUE. Every setting is read once at startup — restart the container to apply a change. Per-request behaviour (layout, PDF mode, render DPI) is controlled with query parameters; see the API Reference.
Quick reference
Section titled “Quick reference”| Variable | Default | Description | When to change |
|---|---|---|---|
OCR_MODEL | tiny | The recognizer selector. PP-OCRv6 tiers tiny / small / medium (Latin + Chinese + Japanese), or a PP-OCRv5 script model arabic / eslav / korean / thai / greek. Unknown value is fatal and prints the valid list. | Higher accuracy (small/medium) or a non-Latin/Chinese/Japanese script. |
TABLE_BACKEND | unset | slanext loads SLANet-Plus (table → HTML); the encoder auto-resolves from the baked bundle. (vlm routes to a VL endpoint.) Required at startup for ?tables=1 to work. | You parse tables. |
FORMULA_BACKEND | unset | ppformulanet_s (English/Latin, default engine), ppformulanet_plus_m (Chinese-capable, GPU only), or auto (GPU only: runs -S everywhere, re-runs CJK crops on plus-M — English output stays byte-identical to -S) loads formula → LaTeX. (vlm routes to a VL endpoint.) Required at startup for ?formulas=1 to work. | You parse formulas. |
PIPELINE_POOL_SIZE | auto | Concurrent GPU pipelines. Auto-sized from total VRAM at startup. | Cap to share a GPU with other workloads, or pin for capacity planning. |
TURBO_OCR_CUDA_GRAPHS | 1 | Bakes CUDA graphs for the recognition batch shapes at warmup (default on since v3.1.0): +10–16% throughput and lower p50 at identical accuracy, ~0.5 GiB extra VRAM per pipeline. | Set 0 on VRAM-constrained cards. |
DISABLE_LAYOUT | 0 | Set to 1 to skip loading PP-DocLayoutV3 entirely. Requests with ?layout=1 then fail. | You will never use layout detection. |
LAYOUT_MERGE_MODE | all | How nested layout boxes are reconciled: all (keep everything) / outer (keep containers) / inner (keep innermost). | Forms collapse under outer — leave at all. |
LAYOUT_KEEP_NESTED_CHILDREN | 0 | Set to 1 to keep child boxes nested inside a parent region. | You need every nested region surfaced. |
ENABLE_PDF_MODE | ocr | Default mode for /ocr/pdf when no ?mode= is supplied: ocr / geometric / auto / auto_verified. | You serve trusted PDFs and want a faster default. |
DISABLE_ANGLE_CLS | 0 | Set to 1 to skip the angle classifier (~0.4 ms saved per image). | You only OCR upright text. |
DET_LIMIT_TYPE | min | Detection resize policy: min grows the shorter side to DET_LIMIT_SIDE_LEN; max shrinks the longer side. | Switch to max to downscale large scans for throughput. |
DET_LIMIT_SIDE_LEN | 64 (min) | Target side length for the resize policy. Under max it defaults to DET_MAX_SIDE_LIMIT instead (since v3.1.0 — a bare max policy keeps native resolution rather than thumbnailing to 64 px). | Tune detection input resolution. |
DET_MAX_SIDE_LIMIT | 1280 | Caps the longer resized side. (PaddleOCR’s 4000 OOMs the pre-allocated pool.) | Very small text on high-resolution scans. |
REQUEST_TIMEOUT_MS | 60000 | Per-request inference deadline (ms). Overrun → 504 INFERENCE_TIMEOUT and the slot is freed. 0 = unbounded (pre-v3 behaviour). | Tighter SLAs (lower) or long single jobs (raise). |
PIPELINE_HARD_KILL_MS | 600000 | Watchdog hard-kill margin (ms). If a worker stays wedged mid-CUDA this long after a deadline trips, the process exits so an orchestrator restarts it. | Rarely — only inert when REQUEST_TIMEOUT_MS=0. |
PORT | 8080 | HTTP port the Drogon binary listens on. nginx fronts it on 8000 and reverse-proxies to this port. | Port collision inside the container. |
GRPC_PORT | 50051 | gRPC port. | Port collision in your environment. |
PDF_DAEMONS | 16 | Number of PDFium daemon processes kept warm for PDF rendering. | Heavy PDF workload — raise to keep more renderers ready, or lower to save RAM. |
PDF_WORKERS | 4 | Render-worker concurrency per PDF request. | Mostly large multi-page PDFs — raise to render pages in parallel. |
HTTP_THREADS | max(pool * 32, 128) | Drogon work-pool threads that run blocking inference dispatch. | You see thread starvation under high concurrency. Rarely needed. |
MAX_PDF_PAGES | 2000 | Reject PDFs with more pages than this before rendering. | Tighter cap for hosted or multi-tenant deployments. |
MAX_BATCH_IMAGES | 1024 | Max images per /ocr/batch and gRPC RecognizeBatch; over the limit → 400 BATCH_TOO_LARGE. | Larger client-side batches. |
MAX_BODY_MB | 100 | Per-request body cap in megabytes (validated 1..102400 at boot — out-of-range crashes the server with a clear message). Applies to both the Drogon HTTP server and the docker nginx front. | Larger uploads (raise) or stricter hosted limits (lower). |
LOG_LEVEL | info | One of debug, info, warn, error. | Quieter prod, louder debugging. |
LOG_FORMAT | json | json (one structured object per line) or text (human-readable). | Local dev, where text is easier to tail. |
Model selection
OCR_MODEL is the single recognizer selector. Every model is baked into the image at build time (SHA256-verified from pinned PaddleOCR releases) — no runtime download, no network dependency at container start. Switching is a restart with a different value.
The three PP-OCRv6 tiers cover Latin + Chinese + Japanese and trade accuracy for speed, not language coverage:
tiny(default) — maximum throughput; the fast tier.small— a middle ground.medium— highest accuracy; the tier that powers the FUNSD 92% / CORD 93% word-F1 numbers.
Other scripts use retained PP-OCRv5 recognizers, selected with the same variable:
arabic,eslav(East Slavic — Russian, Ukrainian, …),korean,thai,greek.
Tables & formulas
Both stages are strict opt-in and run locally in C++ — no Python, no sidecar. A stage loads only when its backend env var is set at startup, and runs only when a request passes ?tables=1 / ?formulas=1 (which also auto-enable ?layout=1).
TABLE_BACKEND=slanext— SLANet-Plus (TRT FP16 CNN encoder + hand-written C++ GRU decoder), tables → HTML with cell quads. The encoder auto-resolves from the baked bundle.FORMULA_BACKEND=ppformulanet_s— PP-FormulaNet-S, formulas → LaTeX. English/Latin, the default engine; in-process pure-C++ (ORT-CUDA-13 on the GPU build, ORT-CPU on the CPU build).FORMULA_BACKEND=ppformulanet_plus_m— the Chinese-capable formula engine (GPU only).FORMULA_BACKEND=auto— composite (GPU only): every crop runs -S first, and only crops with CJK context re-run on plus-M. English pages keep -S speed and byte-identical output; Chinese pages get plus-M accuracy.- Either backend can be set to
vlmto route that stage to a VL endpoint instead of the local model.
Asking for a stage the server was not started with is a hard 400 TABLE_BACKEND_DISABLED / 400 FORMULA_BACKEND_DISABLED — never a silent empty result. A configured-but-failed stage fails at boot rather than serving empties.
Pipeline pool
Each pipeline holds its own CUDA stream and serves one request at a time. PIPELINE_POOL_SIZE is auto-sized from total VRAM at startup. Set it explicitly to:
- Cap pool size when sharing the GPU with another process.
- Force a higher count when auto-detection is conservative.
- Pin to a known value across deployments for capacity planning.
HTTP_THREADS defaults to max(pool * 32, 128). The multiplier covers PDF requests that hold multiple pipeline slots concurrently; there is rarely a reason to override it.
Layout detection
Layout detection (PP-DocLayoutV3, RT-DETR-L, 25 region classes) is loaded at startup but only runs when a request includes ?layout=1 (or implicitly via ?tables=1 / ?formulas=1). Requests without it have zero layout overhead.
Set DISABLE_LAYOUT=1 to skip loading the model entirely. Do this only if you are certain the deployment will never need layout — once disabled, requests with ?layout=1 return an error.
Nested-box reconciliation
Section titled “Nested-box reconciliation”LAYOUT_MERGE_MODE (default all) controls how the detector’s nested boxes are reconciled:
all(default) — keep every box, so formulas/tables/titles the model nests inside a larger region are never dropped.outer— keep the outer container regions and drop boxes nested inside them. Collapses forms, where every field sits inside an outer frame — useallorinnerthere.inner— keep the innermost boxes and drop the pure containers.
The old large / small / union names are accepted as deprecated aliases of outer / inner / all. Set LAYOUT_KEEP_NESTED_CHILDREN=1 to additionally surface child boxes nested inside a parent region.
PDF default mode
ENABLE_PDF_MODE sets the fallback for /ocr/pdf when a request omits ?mode=. The four modes:
ocr— render every page and run the full OCR pipeline. Baseline speed; immune to text-layer manipulation. The safe default.geometric— extract PDFium’s text layer only, no rasterisation. ~10× faster thanocr, but trusts whatever the PDF author embedded.auto— per-page: text layer if available, OCR otherwise. Fastest for mixed PDFs of trusted origin.auto_verified— full OCR plus a cross-check against the text layer; native text is accepted only if it passes a heuristic (character count, replacement-char ratio, no rotation). Slightly slower thanocr.
Angle classifier
The angle classifier handles text rotated by 90°, 180°, or 270° before recognition. It costs ~0.4 ms per image. Set DISABLE_ANGLE_CLS=1 if you only OCR upright scans (e.g. document scanners that auto-orient) and want the savings. Rotated text will then be misread.
Detection input size
Three knobs shape what the detection model sees:
DET_LIMIT_TYPE(defaultmin) — resize policy.mingrows the shorter side up toDET_LIMIT_SIDE_LEN;maxshrinks the longer side to it.DET_LIMIT_SIDE_LEN(default64) — the target side length for that policy.DET_MAX_SIDE_LIMIT(default1280) — caps the longer resized side. Official PaddleOCR uses4000, but that OOMs the pre-allocated pool;1280runs the vast majority of documents at native resolution. Raise it for very small text on high-resolution scans.
DET_MAX_SIDE is still honored as a single-knob override of the TensorRT engine’s optimization-profile MAX side; changing it invalidates the cached engine and forces a one-time rebuild.
Request lifecycle
REQUEST_TIMEOUT_MS (default 60000) is the per-request inference deadline. On overrun, a single-image / batch / gRPC request returns 504 INFERENCE_TIMEOUT and frees its GPU slot; PDF jobs bound their per-page join by the same value, scaled by page count. Set 0 to disable it (unbounded wait — the pre-v3 behaviour).
PIPELINE_HARD_KILL_MS (default 600000) is the dispatcher watchdog’s hard-kill margin. If a worker stays wedged mid-CUDA this long after a deadline trips and a recycle is requested, the process exits so an orchestrator can restart it. It is inert when REQUEST_TIMEOUT_MS=0 (the watchdog only scans once a deadline is set).
Input-size limits
The server rejects oversized inputs before doing real work, with a 400:
MAX_BATCH_IMAGES(default1024) — max images per/ocr/batchand gRPCRecognizeBatch→400 BATCH_TOO_LARGE.MAX_PDF_PAGE_PIXELS_MP(default40) — max rendered megapixels per PDF page (decompression-bomb guard) →400 PIXELS_TOO_LARGE.MAX_IMAGE_PIXELS_MP(default128) — total image area cap on image routes →400 DIMENSIONS_TOO_LARGE.MAX_IMAGE_DIM(default16384) — per-side pixel cap on decode routes.
Ports and split mode
PORT (default 8080) is the port the Drogon binary listens on. nginx in the container fronts it on 8000 and reverse-proxies with keep-alive — this absorbs connection storms. GRPC_PORT (default 50051) is served directly by the binary and must differ from PORT (fatal otherwise).
Change PORT / GRPC_PORT only when you have a port collision in your environment.
PDF pipeline tuning
PDF requests fan out across two pools before they ever touch the GPU:
PDF_DAEMONS(default16) — long-lived PDFium daemon processes kept warm. Each daemon is single-threaded and handles one PDF at a time. Raise for high concurrent PDF traffic; lower to save RAM.PDF_WORKERS(default4) — render-worker concurrency within a single PDF request. Raise for mostly-large multi-page PDFs (renders pages in parallel); leave as is for many small PDFs.MAX_PDF_PAGES(default2000) — hard cap on pages per request. Requests with more pages are rejected before any rendering happens.HTTP_THREADS(defaultmax(pool * 32, 128)) — Drogon’s blocking-work pool. PDFs hold a slot for the full render-and-OCR cycle, so this multiplier is intentional. Override only if you see thread starvation under load.
Request body cap
MAX_BODY_MB (default 100, range 1..102400) caps the request body the server will accept. Both the Drogon HTTP server and the docker nginx reverse proxy in front of it read the same value at startup, so the cap is enforced consistently and an over-sized upload is rejected at the edge with a 413 instead of being read into memory.
The value is validated at boot — anything outside [1, 102400] (or non-numeric, leading zero, etc.) crashes the server with a clear error message rather than failing 90 s later with a confusing nginx parse error.
Logging
LOG_FORMAT=json emits one structured object per line — ready to ship to any log pipeline (Loki, Elasticsearch, CloudWatch). LOG_FORMAT=text is friendlier for terminal tailing during local development.
LOG_LEVEL=debug includes per-request timing breakdowns; info is the right default for production. warn and error are quieter still — appropriate for high-volume hosted deployments where every log line costs money.
Example: full-pipeline production deployment
Section titled “Example: full-pipeline production deployment”A typical structured-parsing setup — the accurate medium recognizer, the table and Chinese-capable formula backends, a pinned pool, and JSON logs:
docker run --gpus all -p 8000:8000 -p 50051:50051 \ -v trt-cache:/home/ocr/.cache/turbo-ocr \ -e OCR_MODEL=medium \ -e TABLE_BACKEND=slanext \ -e FORMULA_BACKEND=ppformulanet_plus_m \ -e PIPELINE_POOL_SIZE=3 \ -e LOG_LEVEL=info \ -e LOG_FORMAT=json \ ghcr.io/aiptimizer/turboocr:v3.1.0For deployment topology, GPU and driver requirements, and Docker tag selection, see the Deployment guide.