Files
wehub-resource-sync 9f97f3abbe
CI - Python Bindings / sdist (push) Failing after 1s
CI - Python Bindings / Build x86_64-unknown-linux-musl (push) Failing after 1s
CI / fmt (push) Failing after 1s
E2E Output Validation / compare-outputs (push) Failing after 1s
Sync Docs to Developer Hub / sync-docs (push) Failing after 1s
CI - Python Bindings / Build x86_64-unknown-linux-gnu (push) Failing after 1s
CI - WASM Bindings / Build WASM (push) Failing after 0s
CI / clippy (push) Failing after 1s
CI / build-and-test (ubuntu-latest) (push) Failing after 0s
CI - WASM Bindings / Edge runtime PDF parse test (push) Has been skipped
CI - WASM Bindings / Browser PDF parse test (push) Has been skipped
Deploy Demo to GitHub Pages / deploy (push) Failing after 1s
CI / build-docker-image (push) Failing after 3s
CI - Node Bindings / Build darwin-x64 (push) Has been cancelled
CI - Node Bindings / Test win32-x64-msvc (push) Has been cancelled
CI - Node Bindings / Build linux-arm64-gnu (push) Has been cancelled
CI - Node Bindings / Build linux-x64-gnu (push) Has been cancelled
CI - Node Bindings / Build linux-x64-musl (push) Has been cancelled
CI - Node Bindings / Build win32-arm64-msvc (push) Has been cancelled
CI - Node Bindings / Build win32-x64-msvc (push) Has been cancelled
CI - Node Bindings / Test darwin-arm64 (push) Has been cancelled
CI - Node Bindings / Test darwin-x64 (push) Has been cancelled
CI - Node Bindings / Test linux-x64-gnu (push) Has been cancelled
CI - Node Bindings / Test linux-x64-musl (push) Has been cancelled
CI - Node Bindings / Test win32-arm64-msvc (push) Has been cancelled
CI - Python Bindings / Build aarch64-pc-windows-msvc (push) Has been cancelled
CI - Python Bindings / Build x86_64-pc-windows-msvc (push) Has been cancelled
CI - Python Bindings / Build x86_64-apple-darwin (push) Has been cancelled
CI - Python Bindings / Build aarch64-apple-darwin (push) Has been cancelled
CI - Python Bindings / Build aarch64-unknown-linux-gnu (push) Has been cancelled
CI - Node Bindings / Build darwin-arm64 (push) Has been cancelled
CI - Python Bindings / Test x86_64-apple-darwin (push) Has been cancelled
CI - Python Bindings / Test aarch64-apple-darwin (push) Has been cancelled
CI - Python Bindings / Test x86_64-unknown-linux-gnu (push) Has been cancelled
CI - Python Bindings / Test x86_64-unknown-linux-musl (push) Has been cancelled
CI - Python Bindings / Test aarch64-pc-windows-msvc (push) Has been cancelled
CI - Python Bindings / Test x86_64-pc-windows-msvc (push) Has been cancelled
CI / build-and-test (macos-26-intel) (push) Has been cancelled
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (windows-11-arm) (push) Has been cancelled
CI / build-and-test (windows-latest) (push) Has been cancelled
E2E Output Validation / upload-dataset (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:44 +08:00
..

Surya OCR Service

A FastAPI server wrapping Surya OCR 2 to conform to the LiteParse OCR API specification (see ../../OCR_API_SPEC.md).

Surya 2 is a multilingual OCR foundation model with strong accuracy across many languages — a single model handles all languages with no per-language setup.

Build and Run

# install and run (in one command)
uv run server.py

The first run downloads Surya model weights from Hugging Face and may take a few minutes; weights are cached afterward.

Inference backend (required)

Surya 2 is a VLM-backed model: text recognition runs through a separate inference backend, which you must provide. Surya does not bundle it.

  • CPU / local (llama.cpp): install the llama-server binary so Surya's llamacpp backend can spawn it:
  • GPU (vllm): set SURYA_INFERENCE_BACKEND=vllm (requires a CUDA GPU).
  • External server: point SURYA_INFERENCE_URL at an already-running Surya inference server to attach without spawning one locally.

Without a backend, startup succeeds and GET /health works, but POST /ocr returns a 500 with "llama-server binary not found".

Docker (GPU, single image)

The provided Dockerfile builds one image that runs both the API and the model server on a GPU. It is based on the official CUDA llama.cpp image (ghcr.io/ggml-org/llama.cpp:server-cuda), which ships a qwen35-capable llama-server (Surya 2's custom GGUF architecture) with libggml-cuda and the CUDA runtime. Surya spawns llama-server from PATH inside the same container, so no sidecar or separate inference service is needed.

# Build
docker build -t suryaocr-liteparse:cuda .

# Run (requires the NVIDIA container runtime)
docker run --rm --gpus all -p 8830:8830 \
  -v "$HOME/.cache/suryaocr-models:/models" \
  suryaocr-liteparse:cuda
  • --gpus all exposes the host GPUs; all model layers are offloaded (LLAMA_CPP_NGL=99).
  • The -v …:/models mount caches weights (HF_HOME=/models). The first POST /ocr downloads the GGUF weights from Hugging Face and spawns llama-server (a few minutes); subsequent requests reuse the cache and the running server.

The image bakes in the backend configuration: SURYA_INFERENCE_BACKEND=llamacpp, LLAMA_CPP_NGL=99, and SURYA_GUIDED_LAYOUT=false. The last one is required on the upstream llama-server build: Surya's layout step is the only request that sends a grammar, and its regex pattern breaks llama.cpp's json-schema→GBNF converter (failed to parse grammar), which otherwise yields empty results. With guided layout off, layout runs as free generation (Surya parses the output itself) and the rest of the pipeline never uses a grammar.

To use a CUDA GPU via vLLM instead of bundled llama.cpp, set SURYA_INFERENCE_BACKEND=vllm (see the backend section above).

Verify it is up:

curl http://localhost:8830/health
curl -X POST -F "file=@image.png" http://localhost:8830/ocr

Usage

The service exposes:

  • POST /ocr — Perform OCR on an uploaded image
  • GET /health — Health check

Parameters

  • file — Image file (multipart/form-data)
  • language — Language code (accepted for API compatibility; ignored, since Surya 2 is multilingual)

Example

curl -X POST -F "file=@image.png" http://localhost:8830/ocr

Response Format

{
  "results": [
    {
      "text": "recognized text",
      "bbox": [x1, y1, x2, y2],
      "confidence": 0.95,
      "polygon": [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
    }
  ]
}

Results are block-level (one entry per detected layout block), with each block's HTML stripped to plain text. This conforms to the LiteParse OCR API spec.

Supported Languages

Surya 2 is a single multilingual model — no language parameter is required (the language field is accepted but ignored).

Per Surya's own benchmark, it scores an 87.2% overall pass rate across 91 languages, with 38 of the 91 languages scoring ≥ 90% and 76 scoring ≥ 80%, covering text accuracy, layout, tables, math, and reading order. It has strong performance across both Latin and non-Latin scripts.

Device / GPU

There are two device knobs, for two different parts of the pipeline:

  • LLAMA_CPP_NGL controls how many layers of the VLM (the model that reads text) the llamacpp backend offloads to the GPU. 99 offloads everything; 0 keeps it on CPU. The Docker image sets 99.

  • TORCH_DEVICE controls the device for Surya's helper torch models (e.g. layout). Surya auto-detects, or you can force it:

    TORCH_DEVICE=cuda uv run server.py   # GPU
    TORCH_DEVICE=cpu  uv run server.py   # CPU
    

For a turnkey GPU deployment, prefer the Docker image above — it wires both up.

Use with LiteParse

lit parse document.pdf --ocr-server-url http://localhost:8830/ocr

Or in code:

import { LiteParse } from 'liteparse';

const parser = new LiteParse({
  ocrServerUrl: 'http://localhost:8830/ocr',
});

const result = await parser.parse('document.pdf');

Testing

uv run pytest test_server.py

Tests mock the Surya predictor, so they run without downloading any models.

Notes

  • First request/startup may be slow while models download.
  • Default port is 8830 (easyocr 8828, paddleocr 8829).
  • Output is block-granular; Surya 2 has no per-line text API.