Skip to content

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.

VariableDefaultDescriptionWhen to change
OCR_MODELtinyThe 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_BACKENDunsetslanext 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_BACKENDunsetppformulanet_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_SIZEautoConcurrent 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_GRAPHS1Bakes 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_LAYOUT0Set to 1 to skip loading PP-DocLayoutV3 entirely. Requests with ?layout=1 then fail.You will never use layout detection.
LAYOUT_MERGE_MODEallHow nested layout boxes are reconciled: all (keep everything) / outer (keep containers) / inner (keep innermost).Forms collapse under outer — leave at all.
LAYOUT_KEEP_NESTED_CHILDREN0Set to 1 to keep child boxes nested inside a parent region.You need every nested region surfaced.
ENABLE_PDF_MODEocrDefault 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_CLS0Set to 1 to skip the angle classifier (~0.4 ms saved per image).You only OCR upright text.
DET_LIMIT_TYPEminDetection 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_LEN64 (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_LIMIT1280Caps the longer resized side. (PaddleOCR’s 4000 OOMs the pre-allocated pool.)Very small text on high-resolution scans.
REQUEST_TIMEOUT_MS60000Per-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_MS600000Watchdog 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.
PORT8080HTTP port the Drogon binary listens on. nginx fronts it on 8000 and reverse-proxies to this port.Port collision inside the container.
GRPC_PORT50051gRPC port.Port collision in your environment.
PDF_DAEMONS16Number of PDFium daemon processes kept warm for PDF rendering.Heavy PDF workload — raise to keep more renderers ready, or lower to save RAM.
PDF_WORKERS4Render-worker concurrency per PDF request.Mostly large multi-page PDFs — raise to render pages in parallel.
HTTP_THREADSmax(pool * 32, 128)Drogon work-pool threads that run blocking inference dispatch.You see thread starvation under high concurrency. Rarely needed.
MAX_PDF_PAGES2000Reject PDFs with more pages than this before rendering.Tighter cap for hosted or multi-tenant deployments.
MAX_BATCH_IMAGES1024Max images per /ocr/batch and gRPC RecognizeBatch; over the limit → 400 BATCH_TOO_LARGE.Larger client-side batches.
MAX_BODY_MB100Per-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_LEVELinfoOne of debug, info, warn, error.Quieter prod, louder debugging.
LOG_FORMATjsonjson (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 vlm to 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.

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 — use all or inner there.
  • 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 than ocr, 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 than ocr.

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 (default min) — resize policy. min grows the shorter side up to DET_LIMIT_SIDE_LEN; max shrinks the longer side to it.
  • DET_LIMIT_SIDE_LEN (default 64) — the target side length for that policy.
  • DET_MAX_SIDE_LIMIT (default 1280) — caps the longer resized side. Official PaddleOCR uses 4000, but that OOMs the pre-allocated pool; 1280 runs 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 (default 1024) — max images per /ocr/batch and gRPC RecognizeBatch400 BATCH_TOO_LARGE.
  • MAX_PDF_PAGE_PIXELS_MP (default 40) — max rendered megapixels per PDF page (decompression-bomb guard) → 400 PIXELS_TOO_LARGE.
  • MAX_IMAGE_PIXELS_MP (default 128) — total image area cap on image routes → 400 DIMENSIONS_TOO_LARGE.
  • MAX_IMAGE_DIM (default 16384) — 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 (default 16) — 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 (default 4) — 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 (default 2000) — hard cap on pages per request. Requests with more pages are rejected before any rendering happens.
  • HTTP_THREADS (default max(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:

Terminal window
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.0

For deployment topology, GPU and driver requirements, and Docker tag selection, see the Deployment guide.