Pipeline Initialization and Dependency Loading#
DocumentConverter initialization unfolds in three distinct phases with very different cost profiles:
| Phase | When | What happens |
|---|---|---|
| Import time | from docling.document_converter import DocumentConverter | All backend and pipeline modules are loaded; heavy ML framework code (torch, transformers, ONNX) is transitively imported |
| Constructor | DocumentConverter(...) | Format→options map is built; pipeline cache is created empty — no pipeline or model is instantiated |
| First use | First converter.convert(...) call, or explicit initialize_pipeline(format) | The pipeline class is instantiated; model weights are downloaded and loaded into memory |
The critical distinction: dependency code (PyTorch kernels, transformer registries) loads at import time, but model weights are not read from disk until first use.
Import Time: What Loads When You Import DocumentConverter#
document_converter.py opens with a large block of eager top-level imports — every format backend (DoclingParseDocumentBackend, MsWordDocumentBackend, HTMLDocumentBackend, etc.) and every pipeline class (StandardPdfPipeline, SimplePipeline, AsrPipeline, VideoPipeline) are imported unconditionally .
Importing StandardPdfPipeline in turn pulls in additional model modules at module load :
CodeFormulaVlmModel(line 59–61)HeadingHierarchyModel(line 62–64)LayoutPostprocessingModel(line 65–67)ReadingOrderModel(line 76–79)
ReadingOrderModel unconditionally imports from docling_ibm_models, and older versions of granite_vision.py (chart extraction) had bare top-level import torch and from transformers import AutoModelForImageTextToText, AutoProcessor — both caused ModuleNotFoundError on slim installs that only include the ONNX runtime extra .
Recent Fixes#
A series of merged PRs has progressively addressed these eager-import issues:
- PR #3837 (merged July 22, 2026): Moved chart extraction (
granite_vision.py) torch/transformers imports into_load_model(), with a conditional lazy import inbase_pipeline.pygated ondo_chart_extraction. - PR #3751 (merged July 6, 2026): Wrapped optional format-backend imports (LaTeX, MS Office, XML formats) in module-level
try/except ImportErrorguards, with actionable install hints raised on backend instantiation . - PR #3702 (merged June 26, 2026): Same pattern for email and Markdown backends .
- PR #3860 (merged July 23, 2026): Moved
scipyimport inside the function that uses it invideo_frame_sampling.py. - PR #3792 (open as of July 2026): Broader ONNX-only support — wraps engine registrations in
suppress(ImportError), adds NumPy fallback for RT-DETR layout, and adds a torch-free geometric reading-order fallback .
Established Guard Patterns#
Three patterns appear across these fixes:
try/except ImportErrorat module level — sets an_X_AVAILABLEflag; the backend/model__init__checks the flag and raises with an install hint.- Conditional imports gated on
do_*flags — e.g., chart extraction models are only imported whendo_chart_extraction=TrueinConvertPipeline.__init__. TYPE_CHECKINGguards — used inhf_vision_base.pyto importtransformerstypes only during static analysis, not at runtime.
The VLM engine factory already follows a clean lazy pattern: all backend-specific imports (TransformersVlmEngine, MLX, API variants) live inside their respective branches of create_vlm_engine(), so unused backends impose no import cost .
Constructor: Cheap by Design#
DocumentConverter.__init__ does three things:
- Stores
allowed_formats(defaults to all formats). - Normalizes
format_options(applies a deprecation shim forInputFormat.IMAGE) and merges with defaults to buildself.format_to_options. - Initializes
self.initialized_pipelinesas an emptydictkeyed by(pipeline_class, options_hash).
No pipeline class is instantiated. No model weights are touched. The constructor cost is purely Python object creation and dict lookups.
First Use: Pipeline and Model Weight Loading#
The first call to converter.convert(...) (or convert_all) reaches _execute_pipeline, which calls _get_pipeline. On a cache miss, _get_pipeline instantiates the pipeline class:
self.initialized_pipelines[cache_key] = pipeline_class(pipeline_options=pipeline_options)
For PDF/image documents this is StandardPdfPipeline.__init__, which immediately calls _init_models() . That method loads all heavy models in sequence :
| Model attribute | Factory/class |
|---|---|
ocr_model | get_ocr_factory().create_instance(...) |
layout_model | get_layout_factory().create_instance(...) |
table_model | get_table_structure_factory().create_instance(...) |
reading_order_model | ReadingOrderModel(...) → docling_ibm_models |
heading_hierarchy_model | HeadingHierarchyModel(...) |
assemble_model | PageAssembleModel(...) |
enrichment_pipe | CodeFormulaVlmModel(...) (VLM engine created only if enabled) |
The three model factories (OcrFactory, LayoutFactory, TableStructureFactory) are themselves process-level @lru_cache singletons in docling/models/factories/__init__.py , so they are created only once per process. The model instances they return are owned by the pipeline object.
Weights download automatically on first use from HuggingFace (or ModelScope for RapidOCR artifacts) unless artifacts_path in PipelineOptions points to a pre-downloaded local directory .
Pre-warming#
To avoid latency on the first real document, call initialize_pipeline(format) explicitly at application startup:
converter = DocumentConverter()
converter.initialize_pipeline(InputFormat.PDF) # loads models now
This calls _get_pipeline immediately, forcing the cache-miss path and model weight loading before any documents are submitted.