Skip to content

publish models

publish models #18

# Export the RT-DETR layout model + TableFormer to ONNX, re-host pdfium + the
# OCR model, and publish everything as GitHub Release assets, so
# `scripts/install/download_dependencies.sh` can fetch the whole PDF/image ML stack
# from a single host (this repo's releases) with zero configuration.
#
# The layout model and TableFormer are PyTorch→ONNX *format conversions* of
# docling-project's own models (Apache-2.0 / CDLA-Permissive-2.0 — see
# docs/MODELS_NOTICE.md); no weights are retrained here. The `*_int8` assets are
# post-training quantizations of those same exports (scripts/install/quantize_models.py
# — a numeric re-encoding of the weights, no retraining; conformance-validated
# in PDF_PERFORMANCE.md). pdfium and the OCR model are re-hosted third-party
# binaries, unmodified, from their own public releases — mirrored here only so
# `download_dependencies.sh` needs one host instead of three.
#
# Trigger: manual only (workflow_dispatch) — it installs torch and exports two
# models, several GB and 10-20 minutes, not something to run on every push.
# Re-running with the same tag re-uploads (clobbers) matching asset names, so
# it's safe to retry after fixing a failed export.
#
# gh workflow run publish-models.yml # tag = models-v1
# gh workflow run publish-models.yml -f tag=models-v2 # a future re-export
name: publish models
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish/update (bump only when the export itself changes)."
required: false
default: "models-v1"
permissions:
contents: write # create the release + upload assets
jobs:
publish-models:
name: export + publish
runs-on: ubuntu-latest
timeout-minutes: 60
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.11"
# --- layout model (required) --------------------------------------
- name: Install layout export deps
run: pip install --upgrade pip && pip install torch transformers onnx
- name: Export layout model
run: python scripts/install/export_layout.py models/layout_heron.onnx
# --- TableFormer (optional — a failure here doesn't block layout) ---
- name: Install TableFormer export deps
continue-on-error: true
id: tf-deps
run: pip install docling_ibm_models onnxscript onnxruntime huggingface_hub opencv-python-headless
- name: Export TableFormer
if: steps.tf-deps.outcome == 'success'
continue-on-error: true
id: tf-export
run: python scripts/install/export_tableformer.py models/tableformer
# --- INT8 quantization (optional — fp32 assets publish regardless) ---
# Conv-only static INT8 of the layout model (calibrated on the repo's PDF
# corpus, checked out above) + dynamic INT8 of the TableFormer decoder.
# quantize_models.py SELF-VALIDATES the layout quant (fp32-vs-int8
# agreement gate over the calibration corpus; zero tolerance for lost
# tables) and deletes its output on failure — so a bad quant simply
# isn't staged and the release stays fp32-only for that model
# (download_dependencies.sh treats every int8 asset as optional). The
# quant itself is deterministic given the fp32 input; what drifts
# between runs is the *export* upstream of it, which is exactly what
# the gate protects against. Deps pinned to the validated set. Two
# separate steps so a TableFormer export failure doesn't block the
# layout int8.
- name: Install quantization deps
continue-on-error: true
id: quant-deps
run: pip install onnx==1.22.0 onnxruntime==1.27.0 sympy==1.14.0 pypdfium2==5.12.0 pillow==12.3.0 numpy==2.4.6
- name: Quantize layout model (static INT8, conv-only)
if: steps.quant-deps.outcome == 'success'
continue-on-error: true
run: python scripts/install/quantize_models.py layout
- name: Quantize TableFormer decoder (dynamic INT8)
if: steps.quant-deps.outcome == 'success' && steps.tf-export.outcome == 'success'
continue-on-error: true
run: python scripts/install/quantize_models.py tableformer-decoder
# --- CodeFormula VLM (optional — the enrichment models are opt-in) ---
# CodeFormulaV2 (Idefics3/SmolVLM-class, Apache-2.0) exported to the
# three-graph ONNX set the Rust `--enrich-code`/`--enrich-formula`
# passes run; the export script verifies the graphs argmax-identical to
# transformers.generate before writing them.
- name: Install CodeFormula export deps
continue-on-error: true
id: cf-deps
run: pip install "transformers>=4.46" onnxruntime pillow huggingface_hub
- name: Export CodeFormula
if: steps.cf-deps.outcome == 'success'
continue-on-error: true
id: cf-export
run: python scripts/install/export_code_formula.py models/code_formula
- name: Quantize CodeFormula decoder (dynamic INT8)
if: steps.cf-export.outcome == 'success' && steps.quant-deps.outcome == 'success'
continue-on-error: true
run: python scripts/install/quantize_models.py code-formula-decoder
# --- pdfium + OCR + chunk tokenizer (re-hosted third-party assets) ---
# The tokenizer is all-MiniLM-L6-v2's tokenizer.json (Apache-2.0), the
# default docling's HybridChunker counts tokens with; re-hosted so the
# hybrid chunker works out of the box after download_dependencies.sh.
- name: Fetch pdfium + OCR + chunk tokenizer assets
run: |
mkdir -p third-party
curl -fsSL -o /tmp/pdfium.tgz \
"https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-x64.tgz"
tar -xzf /tmp/pdfium.tgz -C third-party
curl -fsSL -o third-party/ocr_rec.onnx \
"https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/ch_PP-OCRv3_rec_infer.onnx"
curl -fsSL -o third-party/ppocr_keys_v1.txt \
"https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/ppocr_keys_v1.txt"
curl -fsSL -o third-party/chunk_tokenizer.json \
"https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer.json"
# DocumentFigureClassifier-v2.5 (Apache-2.0) ships its ONNX graph
# upstream — re-hosted as-is for --enrich-picture-classes.
curl -fsSL -o third-party/picture_classifier.onnx \
"https://huggingface.co/docling-project/DocumentFigureClassifier-v2.5/resolve/main/model.onnx"
# --- stage + publish -------------------------------------------------
# GitHub release assets can't contain "/", so tableformer/*.onnx is
# flattened to bare encoder.onnx / decoder.onnx / bbox.onnx (no naming
# collision with anything else in this release; download_dependencies.sh
# downloads these exact names). Each file is staged only if the export
# produced it, including the optional `.onnx.data` external-weights
# sidecar some exports need above ONNX's ~2GB protobuf limit (currently
# just the TableFormer decoder, but checked for every file since that's
# export-size dependent, not fixed).
- name: Stage release assets
run: |
mkdir -p release-assets
stage() { # <source-path> <asset-name>
if [ -f "$1" ]; then cp "$1" "release-assets/$2"; fi
if [ -f "$1.data" ]; then cp "$1.data" "release-assets/$2.data"; fi
}
stage third-party/lib/libpdfium.so libpdfium.so
stage third-party/ocr_rec.onnx ocr_rec.onnx
stage third-party/ppocr_keys_v1.txt ppocr_keys_v1.txt
stage third-party/chunk_tokenizer.json chunk_tokenizer.json
stage models/layout_heron.onnx layout_heron.onnx
stage models/layout_heron_int8.onnx layout_heron_int8.onnx
stage models/tableformer/encoder.onnx encoder.onnx
stage models/tableformer/decoder.onnx decoder.onnx
stage models/tableformer/decoder_int8.onnx decoder_int8.onnx
stage models/tableformer/decoder_kv.onnx decoder_kv.onnx
stage models/tableformer/decoder_kv_int8.onnx decoder_kv_int8.onnx
stage models/tableformer/bbox.onnx bbox.onnx
stage third-party/picture_classifier.onnx picture_classifier.onnx
# CodeFormula graphs flatten with a cf_ prefix (release assets are
# one flat namespace).
stage models/code_formula/vision.onnx cf_vision.onnx
stage models/code_formula/embed.onnx cf_embed.onnx
stage models/code_formula/decoder_kv.onnx cf_decoder_kv.onnx
stage models/code_formula/decoder_kv_int8.onnx cf_decoder_kv_int8.onnx
stage models/code_formula/tokenizer.json cf_tokenizer.json
echo "staged:"
ls -la release-assets/
- name: Publish release
run: |
if gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then
gh release upload "$TAG" release-assets/* --repo "${{ github.repository }}" --clobber
else
gh release create "$TAG" release-assets/* \
--repo "${{ github.repository }}" \
--title "docling.rs ML models ($TAG)" \
--notes-file docs/MODELS_NOTICE.md
fi
# --- prune stale assets ------------------------------------------------
# --clobber only overwrites assets whose names match; a re-run that
# renames or drops an export would leave the old asset behind forever.
# Keep the release exactly equal to what this run staged: delete every
# published asset that has no counterpart in release-assets/.
- name: Prune stale release assets
run: |
gh release view "$TAG" --repo "${{ github.repository }}" \
--json assets --jq '.assets[].name' |
while IFS= read -r asset; do
if [ ! -f "release-assets/$asset" ]; then
echo "pruning stale asset: $asset"
gh release delete-asset "$TAG" "$asset" --repo "${{ github.repository }}" --yes
fi
done