From 392d316716530bba6772da9463e6c88c0879127a Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Wed, 6 May 2026 14:06:52 +0200 Subject: [PATCH] docs(#254): flesh out design doc + sync README image sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Design doc 254: status Draft → Implemented; fill §4 (context), §5 (per-layer impact), §6 (alternatives), §7 (env vars / build-args), §8 (risks mapped to audit dims), §9 (testing + measured sizes), §10 (rollout), §11 (deferred follow-ups), §12 (PR + Docling tools link). - README image-variants table: latest-local 1.9 GB → 3.2 GB (baked, with the BAKE_MODELS=false escape hatch documented), latest-remote 270 MB → 585 MB (real measure on the new build). --- README.md | 4 +- .../254-optim-taille-image-latest-local.md | 270 +++++++++++------- 2 files changed, 162 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 342a4c9..c52b5a3 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,8 @@ Open [http://localhost:3000](http://localhost:3000), upload a PDF, and get resul | Variant | Image tag | Size | Description | |---------|-----------|------|-------------| -| **local** | `latest-local` | ~1.9 GB | Full — runs Docling in-process, CPU-only | -| **remote** | `latest-remote` | ~270 MB | Lightweight — delegates to an external [Docling Serve](https://github.com/DS4SD/docling-serve) instance | +| **local** | `latest-local` | ~3.2 GB | Full — runs Docling in-process, CPU-only, model checkpoints baked in (instant first run). Pass `--build-arg BAKE_MODELS=false` to drop to ~1.9 GB and let Docling download models on first request. | +| **remote** | `latest-remote` | ~585 MB | Lightweight — delegates to an external [Docling Serve](https://github.com/DS4SD/docling-serve) instance | For remote mode: diff --git a/docs/design/254-optim-taille-image-latest-local.md b/docs/design/254-optim-taille-image-latest-local.md index 241559b..29a42d2 100644 --- a/docs/design/254-optim-taille-image-latest-local.md +++ b/docs/design/254-optim-taille-image-latest-local.md @@ -30,11 +30,11 @@ the linked issue. Everything else is on the author. - **Title on issue:** [ENHANCEMENT] Optim taille image latest-local (sortir reasoning, multi-stage, dockerignore) - **Author:** Pier-Jean Malandrino - **Date:** 2026-05-06 -- **Status:** Draft +- **Status:** Implemented - **Target milestone:** 0.6.0 — Doc-centric ingest -- **Impacted layers:** · | shared | app> · · -- **Audit dimensions likely touched:** -- **ADR spawned?:** *(write an ADR when choosing a library, moving a boundary, or deciding **not** to do something — see `docs/architecture/adr-guide.md`)* +- **Impacted layers:** infra/CI (Dockerfile, requirements split, compose) · docs (README, this doc) +- **Audit dimensions likely touched:** CI/Build · Performance · Decoupling · Documentation +- **ADR spawned?:** no *(no load-bearing library/boundary change — Docker layout choice is locally scoped)* --- @@ -82,36 +82,35 @@ for you, badly. ## 4. Context & constraints - + +- **No SQLite or API contract change** — additive build-only. +- **No Pydantic DTO change.** +- **Backwards-compatible runtime behaviour** — the same `CONVERSION_ENGINE` toggle drives the same code paths; missing reasoning deps were already handled by `deps_present()`. +- **CI / GHCR push pipeline must keep working** — both `latest-local` and `latest-remote` tags continue to be built, with the same target names. +- **Performance budget** — image size budget per the issue is −30 %; achieved −48 % (baked) / −69 % (slim) on local and −90 % on remote (see §9 / §10). ## 5. Proposed design @@ -166,18 +165,64 @@ valuable than pseudocode. ### 5.1 Domain +Untouched. + ### 5.2 Persistence +Untouched. + ### 5.3 Infra adapters +Untouched at the Python level. The `LocalConverter`, `ServeConverter`, and `DoclingAgentReasoningRunner` adapters keep their current contracts. The only infra change is at the **deployment** layer: + +- `requirements.txt` no longer carries `docling-agent==0.1.0` and `mellea==0.4.2`. They move to a new `requirements-reasoning.txt` (opt-in). +- `Dockerfile` is rewritten as a multi-stage build: + +``` + python:3.12-slim + │ + ┌─────────────┴─────────────┐ + ▼ ▼ +builder-remote builder-local + │ pip install │ pip install torch torchvision (--index-url cpu) + │ -r requirements.txt │ pip install -r requirements-local.txt + │ │ if WITH_REASONING: pip install -r requirements-reasoning.txt + │ │ + │ (/opt/venv) │ (/opt/venv) + └────────────┬──────────────┘ + ▼ + runtime-base (poppler + appuser + HF_HOME, no pip, no source) + │ + ┌────────────┴────────────┐ + ▼ ▼ +remote (final) local (final) +COPY venv-remote apt: libgl1 + libglib2.0-0 +COPY . COPY venv-local + if BAKE_MODELS: docling-tools models download + COPY . +``` + +- Source (`COPY . /app`) is now COPYed only in the **final** stages — a code-only edit reuses every pip-install layer. +- Two new build-args: `WITH_REASONING` (default `false`) and `BAKE_MODELS` (default `true`). Both opt-out for `local-reasoning` / slim variants respectively. + ### 5.4 Services +Untouched. + ### 5.5 API +Untouched. + ### 5.6 Frontend — feature module +Untouched. + ### 5.7 Cross-cutting (feature flags, i18n, shared types) +- `/api/health` — no schema change. `reasoningAvailable` continues to reflect `infra/docling_agent_reasoning.deps_present()`, so it correctly reports `false` on the standard `latest-local` image and `true` on a `local-reasoning` image. +- `i18n` — no string change. +- `shared/types.ts` — no type change. + ## 6. Alternatives considered -### Alternative A — +### Alternative A — Dedicated `local-reasoning` Dockerfile target (3rd stage) -- **Summary:** -- **Why not:** +- **Summary:** Add a third final target `FROM local AS local-reasoning` that runs `pip install -r requirements-reasoning.txt`. CI publishes `latest-local` and `latest-local-reasoning` separately. +- **Why not:** Doubles the CI surface for very little benefit, and the duplication of intent (build-arg vs target) confuses operators. A single `--build-arg WITH_REASONING=true` is enough — operators tag the resulting image as `local-reasoning` themselves if they need the distinction. -### Alternative B — +### Alternative B — Bake reasoning deps into the standard image, do nothing -- **Summary:** -- **Why not:** +- **Summary:** Keep `docling-agent` + `mellea` in `requirements.txt`, accept the 5+ GB image as the cost of "everything works out of the box". +- **Why not:** The standard `latest-remote` image (which delegates to Docling Serve and never reasons locally) was carrying ~5 GB of unrelated CUDA + LLM SDK weights. That alone disqualifies the do-nothing path. + +### Alternative C — Bake the Docling models into a separate image and mount as a sidecar volume + +- **Summary:** Build a tiny "models-only" image, mount it as a read-only volume on the backend container. +- **Why not:** Adds a deployment moving piece (multi-image orchestration) for a property — instant cold start — that a single `BAKE_MODELS=true` build-arg already gives, at the cost of +1.3 GB the operator can opt out of. ## 7. API & data contract - -## 8. Risks & mitigations +**Additive only at the deployment level. Two operational expectations change** — both intentional: + +1. Anyone running the standard `latest-local` image with `REASONING_ENABLED=true` will see `reasoningAvailable=false` from the API and the Reasoning sidebar entry will hide. To restore: rebuild with `--build-arg WITH_REASONING=true`. (Already documented in README.) +2. The previously-added `hf_cache` named volume in compose is removed. Models are now baked at build time; a leftover `hf_cache` volume from a prior `up` is a no-op (orphan), safe to `docker volume rm`. +1. `docker compose up --build` → confirm the backend container starts, `/api/health` returns 200, `reasoningAvailable=false`. +2. Upload a small PDF and run an analysis → first conversion completes without a multi-minute model download (validates `BAKE_MODELS=true`). +3. Rebuild with `WITH_REASONING=true docker compose up --build` and `REASONING_ENABLED=true`, with Ollama reachable → `reasoningAvailable=true`, `POST /api/documents/:id/reasoning` works. +4. Rebuild with `--build-arg BAKE_MODELS=false` → image is lighter; first conversion downloads models on demand. + +### Performance / load — image size measurements + +Measured on arm64, cold Docker cache: + +| Variant | Before | After | Δ | +|----------------------------------------|----------:|----------:|-------:| +| `latest-local` (models baked, default) | 6.09 GB | 3.19 GB | −48 % | +| `latest-local` (`BAKE_MODELS=false`) | 6.09 GB | 1.89 GB | −69 % | +| `latest-remote` | 5.85 GB | 585 MB | −90 % | + +Build durations (cold cache): `after-remote` ≈ 49 s, `after-local` ≈ 1 m 46 s. Warm rebuild after a Python-only edit: sub-second (pip layers reused). ## 10. Rollout & observability - +Pure-revert. Re-deploying the previous tag (`v0.5.x`) restores the prior image. No data migration or env flip is involved. The `hf_cache` named volume (added then removed in the same release branch) is a no-op orphan after rollback — safe to ignore or `docker volume rm hf_cache`. ## 11. Open questions - +Resolved during implementation. Two follow-ups deferred to dedicated issues: -- ... -- ... +- Drop `docling-core[chunking]` extra from `requirements.txt` to push `latest-remote` from 585 MB toward the historical ~270 MB. Needs verification that the `infra/local_chunker.py` path is local-only (it appears to be, but a check is warranted). +- Pin `torch` to a CPU build (`torch==X.Y.Z+cpu`) and add a CI guard that fails if `nvidia-*` packages appear in the venv — concrete safeguard against the regression mode that produced the original 5.6 GB bloat. ## 12. References @@ -347,12 +395,14 @@ Links to everything a future reader would want. --> - **Issue:** https://github.com/scub-france/Docling-Studio/issues/254 -- **Related PRs / commits:** -- **ADRs:** +- **Related PRs / commits:** https://github.com/scub-france/Docling-Studio/pull/255 +- **ADRs:** none planned - **Project docs:** - Architecture: `docs/architecture.md` - Coding standards: `docs/architecture/coding-standards.md` - ADR guide / template: `docs/architecture/adr-guide.md`, `docs/architecture/adr-template.md` - Audit master: `docs/audit/master.md` - E2E conventions: `e2e/CONVENTIONS.md` -- **External:** +- **External:** + - Upstream `_rag_loop` public-API replacement: https://github.com/docling-project/docling-agent/issues/26 + - Docling models tooling: `docling-tools models download` (CLI shipped by `docling`)