feat: add PaddleOCR-VL 1.6 OCR server - #405
Conversation
Adds ocr/paddleocrvl/, a FastAPI server wrapping the PaddleOCR-VL 1.6 document parsing VLM (0.9B) behind the LiteParse OCR API on port 8831. - Layout-aware block extraction (tables, formulas, charts) across 109 languages; polygon output for rotated-text recovery - Runs locally on CPU by default; PADDLEOCR_VL_ENGINE=transformers for the PyTorch GPU path, PADDLEOCR_VL_SERVER_URL to attach a vLLM/SGLang genai server - Mocked pytest suite (no model download) wired into ocr_servers.yml CI - Docs: comparison table, server entry, spec reference list Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9c273ca2a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| with self._lock: | ||
| predictions = list(self.pipeline.predict(np.asarray(image))) |
There was a problem hiding this comment.
Avoid queuing concurrent pages behind the model lock
When LiteParse processes a multi-page scanned document with the documented CPU default, it sends up to num_workers pages concurrently (ocr_merge.rs:502-545, defaulting to CPU cores minus one in config.rs:247-251), but this lock serializes their tens-of-seconds inferences. The HTTP client times each request out after 60 seconds (http_simple.rs:143-152), so later queued pages can time out before acquiring the lock; their retries then add more work behind the still-running synchronous handlers. Configure LiteParse to use one worker for this server, reject busy requests so retries can back off, or otherwise align queueing with the client timeout.
Useful? React with 👍 / 👎.
What
Adds
ocr/paddleocrvl/— a FastAPI server wrapping PaddleOCR-VL 1.6 (Apache 2.0, 0.9B params) behind the LiteParse OCR API on port 8831, following the structure of the recentocr/suryaocr/contribution:server.py— pipeline wrapper +POST /ocr/GET /healthtest_server.py— 12 pytest cases against a mocked pipeline (no model download in CI)Dockerfile+.dockerignore— CPU image; GPU serving documented via the official genai vLLM containerREADME.md— setup, backends, API, limitationsocr/README.md,OCR_API_SPEC.md, and theocr_servers.ymlCI matrixWhy
PaddleOCR-VL 1.6 pairs a small layout detector with an ERNIE-based VLM and currently tops OmniDocBench v1.6 (96.33). It reads tables, formulas, and charts across 109 languages — a document class the existing servers (line-level OCR) handle poorly. In my benchmarks below it posted the best accuracy of every engine I tested on modern structured documents, in every category.
Design notes
uv run server.py, nothing else to install.PADDLEOCR_VL_ENGINE=transformersruns both models through PyTorch/CUDA (this is also the only practical GPU route on Windows, where vLLM/SGLang can't run natively and the Paddle GPU wheels currently ship a broken cuDNN pin).PADDLEOCR_VL_SERVER_URLattaches VL recognition to a running vLLM/SGLang genai server for throughput serving.bboxcomes from the parser,confidencefrom the layout detector's region score (the VLM exposes no token confidence), and the optionalpolygonfield is populated from the detector's 4-point polygons so LiteParse can recover rotated text. Table HTML is flattened to plain text with stdlib only; the VLM's LaTeX escapes (\%,\$) are unescaped.Benchmarks
Run locally (RTX 5090,
PADDLEOCR_VL_ENGINE=transformers, all engines via their/ocrHTTP endpoints; CER/WER via jiwer on reading-order concatenation). Two datasets:Real-world PDFs — 110 pages rendered at 200 dpi from 22 public documents (arXiv papers, IRS forms, Berkshire shareholder letters, NIST publications), scored against the PDFs' native text layer:
Per-category WER (paddleocrvl vs next-best): academic 0.275 vs 0.320, business 0.013 vs 0.084, forms 0.491 vs 0.528, technical 0.160 vs 0.185 — best in all four categories.
FUNSD (50 scanned 1990s forms, test split):
Honest limitations
Testing
uv run pytest— 12/12 pass (mocked pipeline; verified in a fresh Python 3.12 env, same as the CI job this PR adds)POST /ocrverified against synthetic pages and both benchmark datasets above (160 pages, zero HTTP or empty-result failures)🤖 Generated with Claude Code