Skip to content

feat(ocr): add Baidu PaddleOCR as a selectable OCR provider - #45

Open
bamit99 wants to merge 1 commit into
OpenAgriNet:mainfrom
bamit99:feat/paddleocr-provider
Open

bamit99 wants to merge 1 commit into
OpenAgriNet:mainfrom
bamit99:feat/paddleocr-provider

Conversation

@bamit99

@bamit99 bamit99 commented Nov 6, 2026

Copy link
Copy Markdown

What

Adds Baidu PaddleOCR (PP-OCR) as a selectable OCR provider alongside the existing chandra option. The user picks the model at deploy time via OCR_PROVIDER, and the weights are downloaded on the fly.

Why two lanes

chandra (existing) paddleocr (new)
Model ~5B-param vision-language PP-OCRv6, 34.5M-param det+rec
Hardware remote vLLM server, GPU host CPU, in-process, in this image
Languages 90+ 50, one checkpoint
Weights separate server downloaded on first use into PADDLE_PDX_CACHE_HOME

chandra stays the high-accuracy lane. PaddleOCR is the lane that works on a self-hosted box with no GPU and no external inference server.

Selecting the model

OCR_PROVIDER=paddleocr (aliases paddle, baidu), then:

Variable Default Purpose
PADDLEOCR_MODEL medium PP-OCRv6 tier (medium / small / tiny), or an explicit <det>,<rec> pair
PADDLEOCR_DET_MODEL / PADDLEOCR_REC_MODEL '' Explicit model names; win over PADDLEOCR_MODEL
PADDLEOCR_OCR_VERSION PP-OCRv6 PP-OCRv6 / PP-OCRv5 / PP-OCRv4
PADDLEOCR_LANG '' Checkpoint language hint (en, ch, dev, ...)
PADDLEOCR_DEVICE cpu cpu or gpu / gpu:0
PADDLEOCR_MIN_SCORE 0 Drop recognised lines below this confidence
PADDLEOCR_IMAGE_DPI 192 Page render DPI

The three PP-OCRv6 tiers are all verified working — each resolves to its own checkpoint and OCRs correctly.

On-the-fly model download

The weights are not baked into the image. PaddleX downloads the selected checkpoint on first use into PADDLE_PDX_CACHE_HOME, which docker-compose.yml points at the mounted HF_HOME volume — so the download happens once and survives a container rebuild.

Verified from a completely empty cache: checkpoint downloaded on demand (133M) and the text came back correctly.

Packaging notes

  • Dockerfile — installs libgomp1 (paddle core links against it and refuses to import without it: "Can not import paddle core") and libgl1 / libglib2.0-0 (opencv-contrib loads them at import time).
  • pyproject.toml — paddleocr / paddlepaddle are gated to linux/amd64 by marker. paddlepaddle publishes no aarch64 wheels, so the arm64 image cannot carry this lane; the marker keeps uv sync resolving on both architectures. The arm64 image keeps working with chandra / mistral unchanged.

Known workaround

PaddlePaddle 3.3 crashes on CPU in the PIR -> oneDNN conversion:

NotImplementedError: (Unimplemented) ConvertPirAttribute2RuntimeAttribute not support
[pir::ArrayAttribute<pir::DoubleAttribute>]

The FLAGS_* env vars do not fix it (PaddleX picks run_mode=mkldnn independently), so the pipeline is constructed with enable_mkldnn=False. Exposed as PADDLEOCR_ENABLE_MKLDNN, default false.

Verification

  • uv sync --frozen (the exact CI command) resolves — uv.lock regenerated.
  • 726 passed — full suite, including 14 new unit tests.
  • ruff check . — clean.
  • Image built and a real PDF OCR'd end-to-end from an empty cache:
    Invoice Number 4471
    Total Amount USD 250.00
    PaddleOCR end to end test
    
  • All three tiers (medium / small / tiny) verified.

Files

  • pipeline/ocr/paddleocr_local.py — new provider (one class per model, per the provider-plugin convention in CONVENTIONS.md)
  • pipeline/ocr/base.py — the OcrConfig contract gains the PaddleOCR knobs
  • pipeline/ocr/service.py — provider registered + env-var mapping
  • pipeline/ocr/__init__.py — export
  • Dockerfile, docker-compose.yml, pyproject.toml, uv.lock — packaging
  • .env.example, ENV.md — operator-facing docs
  • tests/test_ocr_paddleocr.py — 14 new unit tests

Note: I have read access to this repo but not push access, so this is from a fork. Happy to rebase onto main if the branch has drifted.

Adds PP-OCR (Baidu PaddleOCR) as a second OCR lane alongside the existing
chandra provider, selectable at deploy time with OCR_PROVIDER=paddleocr.

Why
- chandra is the high-accuracy lane but needs a remote vLLM server on a GPU
  host. PaddleOCR is the opposite trade: PP-OCRv6 is a 34.5M-parameter
  detection+recognition pair that runs on CPU in-process, reads 50 languages
  from one checkpoint, and needs no external inference server.

What
- pipeline/ocr/paddleocr_local.py: new provider, one class per model per the
  repo provider-plugin convention.
- Model choice is a deploy-time setting, not a code change:
  PADDLEOCR_MODEL selects a PP-OCRv6 tier (medium | small | tiny) or an
  explicit "<det>,<rec>" pair; PADDLEOCR_DET_MODEL/PADDLEOCR_REC_MODEL
  override it; PADDLEOCR_OCR_VERSION/LANG/DEVICE/MIN_SCORE/IMAGE_DPI cover the
  rest.
- Weights are NOT baked into the image. PaddleX downloads the selected
  checkpoint on first use into PADDLE_PDX_CACHE_HOME, which docker-compose
  points at the mounted HF_HOME volume so it survives a rebuild.
- Dockerfile: install libgomp1 (paddle core links against it and refuses to
  import without it) and libgl1/libglib2.0-0 (opencv-contrib loads them at
  import time).
- pyproject.toml: paddleocr/paddlepaddle gated to linux/amd64 by marker.
  paddlepaddle publishes no aarch64 wheels, so the arm64 image cannot carry
  this lane; the marker keeps uv sync resolving on both architectures.

Verified
- uv sync --frozen (the CI command) resolves; 726 tests pass; ruff clean.
- Built the image and ran a real PDF through it from an empty cache: the
  checkpoint downloaded on demand (133M) and the text came back correctly.
- All three PP-OCRv6 tiers (medium/small/tiny) resolve to their own
  checkpoints and OCR correctly.

Known workaround
- PaddlePaddle 3.3 crashes on CPU in the PIR -> oneDNN conversion
  (ConvertPirAttribute2RuntimeAttribute not support). The FLAGS_* env vars do
  not fix it because PaddleX picks run_mode=mkldnn independently, so the
  pipeline is constructed with enable_mkldnn=False. Exposed as
  PADDLEOCR_ENABLE_MKLDNN, default false.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant