Skip to content

Commit e606917

Browse files
committed
docs: example deployment Dockerfile; record the pdfium-text-path finding
Adds examples/Dockerfile — a 3-stage build (export models with torch → compile the CLI → slim Python-free runtime) that bakes the binary, native libs, and exported models (including the KV-cached TableFormer decoder) into one image, with the DOCLING_* env vars preset. README gains a "Deploy in a container" section and the crate docs point to it. Also records the outcome of roadmap item 6 (drop pdfium's text path): the parser is already the sole prose source, but moving word/code cells off pdfium too was trialled and reverted — it regresses vs the docling groundtruth (2305 34→46, code_and_formula 2→4 ws-normalized; 10/91 snapshots drift) because TableFormer matches predicted cell boxes against the page's word cells by overlap, and the parser's font-advance loose boxes differ from the ink boxes pdfium reports. Fully retiring pdfium's text path needs a docling-parse-faithful word/cell grouping in the parser, not the legacy gap-heuristic on parser glyphs. No functional code change here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QYCj8stK1TzKcy6crtTfha
1 parent 9d3cf08 commit e606917

4 files changed

Lines changed: 150 additions & 1 deletion

File tree

PDF_PARSER_NOTES.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,19 @@ numbering and footnote reading order.)
163163
4. ~~Korean quote normalization~~ — DONE (normal_4pages 74→54).
164164
5. **Now: 6/14 strict, 7/14 whitespace-normalized.** Blocker B (amt) needs a
165165
font-metrics layer for strict 7/14.
166-
6. Long term: drop pdfium's text path (keep it for rasterisation).
166+
6. **Investigated — drop pdfium's text path (keep it for rasterisation).** The
167+
parser is already the sole *prose* source (item 2). Moving the *word* and
168+
*code* cells off pdfium too — by running the parser's glyph stream through the
169+
same `words_from_glyphs` / `lines_from_glyphs` grouping — was trialled and
170+
**reverted**: it regresses vs the docling groundtruth (`2305` 34→46, `code_and_formula`
171+
2→4 ws-normalized; 10 of 91 snapshots drift). TableFormer matches predicted cell
172+
boxes against the page's word cells by overlap, and the parser's font-advance
173+
*loose* boxes differ from the *ink* boxes pdfium reports — which the table
174+
matching was validated against — so the cell text shifts. Fully retiring
175+
pdfium's text path needs a docling-parse-faithful word/cell grouping in the
176+
parser (a `dp_lines` equivalent for words, not the legacy gap-heuristic on
177+
parser glyphs) — a dedicated effort. Until then pdfium stays for word/code
178+
cells (alongside page rasterization + link annotations).
167179

168180
## Tooling (under `scripts/`)
169181

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ The comparison scripts install the latest published Python `docling` from PyPI
188188
into `.venv-compare` automatically on first run. See
189189
[`COMPARING.md`](./COMPARING.md).
190190

191+
## Deploy in a container
192+
193+
For a real-world service, bake the binary, native libs, and models into one image
194+
so the runtime needs no Python. [`examples/Dockerfile`](./examples/Dockerfile) is a
195+
3-stage build that does exactly this — a `models` stage exports the layout +
196+
**TableFormer** (KV-cached decoder) ONNX with torch and fetches the OCR model +
197+
pdfium, a `builder` stage compiles the CLI, and a slim `runtime` stage carries just
198+
the binary, `libonnxruntime`, pdfium, and the models, with the `DOCLING_*` env vars
199+
preset:
200+
201+
```bash
202+
docker build -f examples/Dockerfile -t fleischwolf .
203+
docker run --rm -v "$PWD:/data" fleischwolf /data/input.pdf # Markdown to stdout
204+
docker run --rm -v "$PWD:/data" fleischwolf /data/input.pdf --to json
205+
```
206+
207+
The image converts PDFs/images fully offline; the model export (torch +
208+
`docling-ibm-models`) happens only at build time, never at runtime.
209+
191210
## Performance
192211

193212
`scripts/performance.sh` runs the **largest fixture of each supported type** through

crates/fleischwolf/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
//! println!("{}", result.document.export_to_markdown());
1313
//! ```
1414
//!
15+
//! For the PDF/image ML pipeline (pdfium + layout/TableFormer/OCR ONNX), reuse a
16+
//! [`Pipeline`] across documents to amortize model loading, instead of the
17+
//! per-call [`DocumentConverter`]. Deploying as a service: `examples/Dockerfile`
18+
//! is a 3-stage build that bakes the binary, native libs, and exported models
19+
//! (including the KV-cached TableFormer decoder) into a slim, Python-free runtime
20+
//! image — see the "Deploy in a container" section of the README.
21+
//!
1522
//! See `MIGRATION.md` for the architecture, the Python → Rust mapping, and the
1623
//! phased plan. Phase 0 ships the converter plumbing plus Markdown and CSV
1724
//! backends; PDF/DOCX/HTML and the ML pipeline land in later phases.

examples/Dockerfile

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# syntax=docker/dockerfile:1
2+
#
3+
# Example deployment image for fleischwolf (the Rust docling port) with the full
4+
# PDF ML pipeline — pdfium rasterization + RT-DETR layout + TableFormer table
5+
# structure (KV-cached decoder) + PP-OCRv3. Builds a slim runtime with the
6+
# `fleischwolf` CLI, the native libs, and all models baked in, so a container can
7+
# convert PDFs/images offline with no Python at runtime.
8+
#
9+
# Build from the repository root:
10+
# docker build -f examples/Dockerfile -t fleischwolf .
11+
# Convert a document (Markdown to stdout):
12+
# docker run --rm -v "$PWD:/data" fleischwolf /data/input.pdf
13+
#
14+
# Stages:
15+
# models — export the layout + TableFormer ONNX (torch, one-time) and fetch
16+
# the OCR model/dict + pdfium
17+
# builder — compile the CLI (the `ort` crate downloads ONNX Runtime at build)
18+
# runtime — slim image with just the binary, native libs and models
19+
20+
# ----------------------------------------------------------------------------
21+
# Stage 1: ML models + native libs (Python only here, never at runtime)
22+
# ----------------------------------------------------------------------------
23+
FROM python:3.12-slim AS models
24+
25+
RUN apt-get update \
26+
&& apt-get install -y --no-install-recommends curl ca-certificates tar \
27+
&& rm -rf /var/lib/apt/lists/*
28+
29+
# torch (CPU) + the docling model packages used by the export scripts.
30+
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \
31+
&& pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu \
32+
"transformers>=4.45,<5" "onnx>=1.16,<2" \
33+
docling-ibm-models onnxscript onnxruntime huggingface_hub
34+
35+
WORKDIR /build
36+
COPY scripts/export_layout.py scripts/export_tableformer.py ./scripts/
37+
38+
RUN mkdir -p /opt/models /opt/pdfium \
39+
# RT-DETR layout detector -> models/layout_heron.onnx
40+
&& python scripts/export_layout.py /opt/models/layout_heron.onnx \
41+
# TableFormer encoder/decoder/bbox. The decoder is the KV-cached step
42+
# (self-attention state cache + precomputed cross-attention K/V) — byte-exact
43+
# vs docling and ~2-4x faster on table-dense pages. The artifacts dir
44+
# auto-resolves (downloads docling-project/docling-models if absent).
45+
&& python scripts/export_tableformer.py /opt/models/tableformer \
46+
# PP-OCRv3 recognition model + dictionary (for scanned pages)
47+
&& curl -sSL -o /opt/models/ocr_rec.onnx \
48+
"https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/ch_PP-OCRv3_rec_infer.onnx" \
49+
&& curl -sSL -o /opt/models/ppocr_keys_v1.txt \
50+
"https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/ppocr_keys_v1.txt" \
51+
# pdfium prebuilt (page rasterization only)
52+
&& curl -sSL -o /tmp/pdfium.tgz \
53+
"https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-x64.tgz" \
54+
&& tar xzf /tmp/pdfium.tgz -C /opt/pdfium \
55+
&& rm /tmp/pdfium.tgz
56+
57+
# ----------------------------------------------------------------------------
58+
# Stage 2: Rust build
59+
# ----------------------------------------------------------------------------
60+
FROM rust:1-slim-trixie AS builder
61+
62+
RUN apt-get update \
63+
&& apt-get install -y --no-install-recommends \
64+
build-essential cmake perl pkg-config ca-certificates \
65+
&& rm -rf /var/lib/apt/lists/*
66+
67+
WORKDIR /app
68+
69+
# Warm the dependency cache on the manifests first (fast rebuilds on src edits).
70+
COPY Cargo.toml Cargo.lock ./
71+
COPY crates ./crates
72+
RUN cargo build --release -p fleischwolf-cli --locked
73+
74+
# Collect the binary + the ONNX Runtime shared lib `ort` downloaded during build.
75+
RUN mkdir -p /artifacts \
76+
&& cp target/release/fleischwolf /artifacts/ \
77+
&& find target -name 'libonnxruntime*.so*' -exec cp -av {} /artifacts/ \; || true
78+
79+
# ----------------------------------------------------------------------------
80+
# Stage 3: runtime (no Python, no toolchain)
81+
# ----------------------------------------------------------------------------
82+
FROM debian:trixie-slim AS runtime
83+
84+
RUN apt-get update \
85+
&& apt-get install -y --no-install-recommends ca-certificates libstdc++6 libgomp1 \
86+
&& rm -rf /var/lib/apt/lists/*
87+
88+
COPY --from=builder /artifacts/ /tmp/artifacts/
89+
RUN cp /tmp/artifacts/fleischwolf /usr/local/bin/ \
90+
&& (cp /tmp/artifacts/libonnxruntime*.so* /usr/local/lib/ 2>/dev/null || true) \
91+
&& rm -rf /tmp/artifacts \
92+
&& ldconfig
93+
94+
COPY --from=models /opt/models /opt/models
95+
COPY --from=models /opt/pdfium /opt/pdfium
96+
97+
# The pipeline finds its native lib + models via these env vars (the TableFormer
98+
# graphs default to models/tableformer/*; set them explicitly so the binary works
99+
# from any working directory).
100+
ENV PDFIUM_DYNAMIC_LIB_PATH=/opt/pdfium/lib \
101+
DOCLING_LAYOUT_ONNX=/opt/models/layout_heron.onnx \
102+
DOCLING_OCR_REC_ONNX=/opt/models/ocr_rec.onnx \
103+
DOCLING_OCR_DICT=/opt/models/ppocr_keys_v1.txt \
104+
DOCLING_TABLEFORMER_ENCODER=/opt/models/tableformer/encoder.onnx \
105+
DOCLING_TABLEFORMER_DECODER=/opt/models/tableformer/decoder.onnx \
106+
DOCLING_TABLEFORMER_BBOX=/opt/models/tableformer/bbox.onnx \
107+
LD_LIBRARY_PATH=/usr/local/lib:/opt/pdfium/lib
108+
109+
# Default: read a file arg and print Markdown to stdout, e.g.
110+
# docker run --rm -v "$PWD:/data" fleischwolf /data/input.pdf --to json
111+
ENTRYPOINT ["fleischwolf"]

0 commit comments

Comments
 (0)