refactor(layout): run all layout inference through the object-detection factory path - #3914
Merged
Conversation
`LayoutObjectDetectionModel` becomes the only code that runs layout
inference and `LayoutObjectDetectionOptions` becomes the default layout
options. `docling_ibm_models.layoutmodel.LayoutPredictor` is no longer
imported anywhere under `docling/`.
Two layout implementations had drifted apart: the legacy `LayoutModel`
via `docling-ibm-models`, and `LayoutObjectDetectionModel` via
`AutoModelForObjectDetection`. Both ran the same weights at the same
score threshold, but with different postprocessing defaults, different
bounding-box handling and different debug hooks - and only the legacy one
was reachable by default, while only the new one supported pluggable
inference engines. Keeping both meant every layout change had to be made
twice.
Object-detection model fixes:
- Run inference as a single `predict_batch` call instead of one call per
page, so `page_batch_size` / `layout_batch_size` reach the GPU again,
and `TimeRecorder("layout")` covers the batch rather than each page.
- Clamp bounding boxes to the page after page-space scaling.
`LayoutPostprocessor` compares cluster area against page area, so an
overshooting box can cross the 0.90 full-page threshold and stop
adopting its children.
- Drop detections whose label id is absent from the model's own
`id2label` instead of silently relabelling them as TEXT.
- Restore XPU support in the transformers engine.
- Emit `settings.debug.visualize_raw_layout` output, which previously
existed only on the legacy path.
Defaults and plumbing:
- Register presets for heron-101 and the three egret variants, so the
documented higher-accuracy recommendations are actually selectable.
- Give `engine_options` a default so `LayoutObjectDetectionOptions()`
constructs without arguments.
- Promote `create_orphan_clusters` to `BaseLayoutOptions` with a single
`True` default. The two option classes disagreeing on it was the drift
that made the paths produce different documents, and it forced
`isinstance` branching in three pipelines.
- Prefetch every engine variant of the layout model, including the ONNX
repository, which air-gapped ONNX deployments never received.
`LayoutOptions` and `LayoutModel` are deprecated but keep working:
`LayoutModel` is now a shim that warns, translates the options and
delegates to `LayoutObjectDetectionModel`. `DOCLING_LAYOUT_V2` is no
longer supported and falls back to Heron with a warning.
The only intended output change is a sub-pixel bounding-box shift on
pages whose size is not an integer number of points: the engine returns
boxes in image-pixel space, and the legacy path never scaled them back
to page space.
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Contributor
|
✅ DCO Check Passed Thanks @cau-git, all your commits are properly signed off. 🎉 |
Contributor
Merge Protections🟢 All 2 merge protections satisfied — ready to merge. Show 2 satisfied protections🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
🟢 Require two reviewer for test updatesWhen test data is updated, we require two reviewers
|
cau-git
marked this pull request as draft
July 30, 2026 12:18
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
…emove-drift-legacy-layout
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
cau-git
marked this pull request as ready for review
July 31, 2026 08:33
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Member
Author
|
Note: Generated outputs of this PR was also checked against 500 doclaynet test set samples, with no regression in any metrics. |
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
nikos-livathinos
approved these changes
Jul 31, 2026
Anai-Guo
added a commit
to Anai-Guo/docling
that referenced
this pull request
Aug 9, 2026
torch.compile() is lazy: the backend only builds a kernel on the first forward pass, so on a machine without a working C++ compiler the failure surfaces in predict_batch() rather than in initialize(). Nothing catches it there, so it propagates as a ConversionError and the whole conversion fails. Since docling-project#3914 made LayoutObjectDetectionOptions the default and compile_torch_models defaults to True, this is on the default PDF path, so a plain convert() raises on any environment without a compiler -- Windows without MSVC, but also slim Linux images without g++. Compilation is an optimization, so keep a handle on the uncompiled model and degrade to it with a warning instead of failing the conversion. The swap is permanent, so later batches do not retry compilation. A missing handle, and errors that are not TorchDynamoException, still propagate unchanged. Signed-off-by: Tai An <antai12232931@outlook.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Layout detection had two implementations that had drifted apart:
LayoutModel, backed bydocling_ibm_models.layoutmodel.LayoutPredictor— the default, but locked to a single inference runtime.LayoutObjectDetectionModel, backed byAutoModelForObjectDetection— supports the pluggable engine system (transformers / ONNX Runtime / KServe v2) and presets, but was not the default and had accumulated its own defects.Both run the same weights (
docling-layout-heron) at the same 0.3 score threshold, so this is a defaults-and-plumbing change, not an accuracy change. But they disagreed on postprocessing defaults, bounding-box handling and debug hooks, which meant every layout change had to be made twice and the two paths could produce different documents from the same PDF.This collapses them onto one.
LayoutObjectDetectionModelbecomes the only code that runs layout inference, and nothing underdocling/importsdocling_ibm_models.layoutmodelany more. The dependency itself stays — it is still required by the reading-order and table-structure models.What changed
Object-detection model fixes
predict_layoutlooped page-by-page callingengine.predict(), which wraps each image inpredict_batch([x]). The GPU batch was pinned to 1 regardless ofpage_batch_size/layout_batch_size. Now onepredict_batchcall per page batch. This also fixesTimeRecorder("layout"), which was opened once per page instead of once per batch.LayoutPostprocessorcomparescluster.bbox.area() / page_areaagainst a 0.90 full-page threshold and usesintersection_over_selffor containment; an overshooting box inflates only the numerator, so a picture covering ~88% of a page could cross the threshold and stop adopting its text children.TEXT._resolve_deviceonly offered CPU/CUDA/MPS).settings.debug.visualize_raw_layoutnow fires on this path; it previously existed only on the legacy model.Defaults and plumbing
layout_heron_101,layout_egret_medium,layout_egret_large,layout_egret_xlarge. Onlylayout_heron_defaultexisted, so the higher-accuracy models the docs recommend were not actually selectable.engine_optionshas a default, soLayoutObjectDetectionOptions()constructs without arguments.create_orphan_clustersmoved up toBaseLayoutOptionswith oneTruedefault. The two option classes declaring it with different defaults was the drift that made the paths produce different documents, and it forced this in three pipelines:TableCropsLayoutOptions, the third implementer: it setsrequires_layout_postprocessing = False, so the postprocessor never reads the flag.docling-tools models downloadnow fetches every engine variant of the layout model, includingdocling-layout-heron-onnx. Air-gapped deployments selecting the ONNX engine previously failed at conversion time because that repository was never prefetched.Deprecations — nothing stops working
LayoutOptionsandLayoutModelemitDeprecationWarning.LayoutModelis now a ~50-line shim that warns, translates the options and delegates; it inheritspredict_layout.LayoutOptionsstill constructs, still selects any of the five supported models, and still resolves throughLayoutFactory.DOCLING_LAYOUT_V2(docling-layout-old) is no longer supported. The symbol stays, but selecting it warns and falls back to Heron. It is the only model in the catalog with a class that is not aDocItemLabel(backgroundat index 0), and retiring it means no label-map special-casing anywhere.artifacts_path/<model_path>direct-pointing fallback is gone. It already emitted aDeprecationWarning, and the standardartifacts_path/<repo--folder>convention is unaffected.layout_model_specs.py,LayoutModelConfigand theDOCLING_LAYOUT_*constants now exist solely to serveLayoutOptions. They carry comments recording that they can be deleted in the same release that removes it.Behaviour changes to be aware of
torch.compileis now on for the layout model by default (settings.inference.compile_torch_models).LayoutPredictornever compiled. Steady-state inference is faster, but there is a per-process cold-start cost of roughly 15s that one-shot CLI and serverless users will notice. Opt out withsettings.inference.compile_torch_models = False.Verification
test_e2e_conversioncorpus already is a cross-revision parity gate, since it converts with whatever the default is and compares against ground truth generated under the old default. Across the wholeml_pdf_modelsuite, the only assertion that moved was the sub-pixel scaling case above, confirmed arithmetically rather than by inspection (388.67487 × 595.32/595 = 388.88387).torch.compileon/off gives byte-identical boxes, so the shift is not numerical noise.tests/test_layout_migration.pycovers: the shim contract for all five model specs; option round-tripping; theDOCLING_LAYOUT_V2fallback; factory dispatch for both option types; label maps for every supported repository (config-only, no weights, no inference); single-batched-call and page-skipping behaviour; picture-internal text survival; and a grep-level assertion thatdocling_ibm_models.layoutmodelis not imported underdocling/.LayoutOptionspath — all three succeed and produce identical output.Checklist: