Add --mock_deps: build docs without installing heavy dependencies - #801
Conversation
doc-builder must import the documented library (docstrings are built dynamically, e.g. transformers' add_start_docstrings decorators), which historically forced installing the full dependency tree - torch, GPU wheels, custom containers - just to build documentation. --mock_deps torch,... installs a sys.meta_path finder providing, for each mocked package and its submodules: - importable module trees that pass the find_spec + importlib.metadata availability checks HF libraries gate exports on (naive sys.modules mocks fail these and route to dummy objects) - mocks usable as base classes via PEP 560 __mro_entries__, which substitutes a plain-`type` base so real subclasses keep their real __init__ signature and docstring under inspect - pass-through decorator behavior (calling a mock with a single function/class returns it unchanged), so decorated docstrings survive - dotted-path repr and short __name__/__qualname__, so mocked defaults and annotations render like the real objects Also: get_type_name now prefers __qualname__ for non-typing objects, which renders mocked annotations like real classes and fixes real builds rendering builtin-function annotations as `<built-in function callable>` (now `callable`). Fidelity, verified by building accelerate docs in a venv WITHOUT torch or deepspeed installed (--mock_deps torch,deepspeed) against a real- torch reference build: 46/48 pages byte-identical (modulo the known nondeterministic 0x addresses); the remaining 2 differ only in typing paths rendered from the public import path (torch.nn.Module) instead of the internal one (torch.nn.modules.module.Module). The documented library itself stays really installed (inspect reads its real source); packages that are actually installed are never mocked. `doc-builder preview` supports the flag too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validated by building all 723 transformers en doc pages with --mock_deps torch,tensorflow,flax,jax,torchvision,torchaudio,timm and only light real deps installed (tokenizers, sentencepiece, ...): zero conversion errors, full en mdx build in ~9s. Spot-check against production pages built with real deps: bert.md and llama.md are byte-identical (similarity 1.0000); trainer.md at 0.9984 with residual diffs attributable to dependency version skew (older local accelerate). Gaps found and fixed by the validation: - PEP 604 unions in runtime-evaluated annotations (`int | torch.Tensor`) now build *real* typing.Union objects (mocks pass typing._type_check because they are callable), so tools unwrapping Optional/Union via typing.get_origin/get_args — like transformers' auto_docstring — treat them exactly like real annotations. - mock modules are a ModuleType subclass carrying the same operator surface: `from torch import Tensor` on a mocked package materializes `torch.Tensor` as a module via _handle_fromlist, which then appears in unions/calls/base classes. - mock instances expose `__module__` (the mocked path) so `__module__.__qualname__` renderers show `torch.Tensor`, not doc_builder.mock_imports. - mock base classes gained class-level attribute fallback via a metaclass (real subclasses may rely on inherited attributes like nn.Module.forward) deriving from ABCMeta to stay combinable with real ABC bases; it defines no __call__, keeping subclass signatures intact. - find_object_in_package: when a submodule is importable but not bound as an attribute of its (lazy) parent, resolve it from sys.modules and heal the binding — fixes transformers' fusion_mapping-style pages, independent of mocking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a validation round against the full transformers docs, per the plan in the PR description: Setup: venv with only light real deps ( Result: all 723 en pages convert with zero errors; the full en mdx build takes ~9 seconds on a laptop (vs minutes in the GPU container). Fidelity spot-check against production pages (built with real deps, fetched from the doc bucket): The validation surfaced and fixed five gaps (see commit message): real |
Each library gets a src/doc_builder/mock_deps/<library>.txt listing the heavy dependencies to mock when building its docs; build/preview apply it automatically, so workflows no longer need --mock_deps (kept as an additive extra). Registry validated by building the docs of 17 HF libraries end to end with only light real deps installed. Also hardens the mocks for cases those builds surfaced: mocked module __version__ compares against tuples like torch's TorchVersion, len() of a mock returns 0, and class-attribute fallback on mocked bases is restricted to a whitelist (forward/call) so hasattr probes on subclasses stay accurate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Added a per-library mock-deps registry ( Validated by building the docs of every doc-builder-based library end to end locally, installing only the library itself (
Fidelity checks: transformers Only exception: The validation surfaced three mock hardening fixes, all included: tuple-comparable |
Registry keys are the doc-builder library names, so namespace subpackages get dotted files (optimum.intel.txt, optimum.neuron.txt, ...) and optimum-onnx shares optimum.txt since its docs build under the library name "optimum". MockFinder now matches registered names by prefix instead of top-level root, so a registered name can itself be a dotted submodule (mocking optimum.onnxruntime while the real optimum stays importable). Validated end to end: optimum (16 pages), optimum-onnx (23), optimum.intel (9), optimum.executorch (5), optimum.neuron (53, with the workflow's notebook-to-mdx pre-steps), optimum.tpu (19), optimum.habana (21, needs its version-pinned real deps plus a habana-torch-plugin distribution visible to `pip list` because of an import-time subprocess version check). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Extended the registry to the optimum family — the special composite case where How that family builds today, for context:
Registry consequences, all included here:
Validation (only the library installed
Caveats found along the way:
With this, every library from moon-landing's docs index that documents a Python package validates against the registry — 24/25 builds green (setfit remains the lone blocked case). |
The mock-deps registry now records both sides of a light doc build: bare lines are mocked, real:<spec> lines are installed for real, and nodeps:<spec> lines are installed with --no-deps (their own dependency trees pull in heavy packages). The new `doc-builder light-install <library>` command installs the real deps via uv, pinning bare names to the documented library's own declared version ranges so registry files never chase upstream pins (e.g. transformers' tokenizers range); `--check` reports whether a registry entry exists (exit 3 when not). The shared build workflows install the package with --no-deps and light-install when the package has a registry entry, falling back to the old full [dev] install otherwise. The custom_container input is removed (the light path makes prebuilt-heavy-deps images unnecessary) and every workflow now uses uv exclusively: setup-uv replaces the pip bootstrap, and hf sync runs through uvx. The accelerate integration test exercises the light path end to end. Mock hardening the validation surfaced: issubclass()/isinstance() against a mock answers False instead of raising (scipy>=1.17 probes `issubclass(cls, torch.Tensor)` whenever torch is in sys.modules). Every registry file was validated in a fresh venv containing only doc-builder, the registry's real deps, and the library --no-deps: all 25 builds (18 libraries + optimum family) pass on first iteration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Went with option 2 and overhauled the workflows: registry-driven light installs, Registry format — each
Workflows (
Validation — every registry file was re-proven in a fresh venv containing only doc-builder + the registry's real deps + the library The fresh-venv sweep surfaced one more mock hardening (included): |
What
doc-builder imports the documented library to introspect docstrings with
inspect— necessary because libraries like transformers construct docstrings dynamically (decorators, templating), which rules out static analysis. The cost: building docs requires installing the library's full dependency tree — torch, GPU wheels, and in transformers' case a dedicated container image.This adds
--mock_deps torch,deepspeed,...: the listed packages are mocked at import time, so only the documented library itself (plus its light dependencies) needs to be installed.How it works
A
sys.meta_pathfinder (Sphinx'sautodoc_mock_importsidea, extended for HF-library realities) provides for each mocked package:importlib.util.find_spec(pkg)+importlib.metadata.metadata(pkg)and route to dummy objects when they fail — naivesys.modulesmocks don't pass these. The finder serves both module specs and fake distribution metadata.__mro_entries__, substituting a plain-typebase — so a realclass Model(nn.Module)keeps its real__init__signature and docstring underinspect(a metaclass-based mock silently degrades every subclass signature to*args, **kwargs).@torch.no_grad()-style decorations don't swallow docstrings.repr(repr(torch.float32) == "torch.float32") and short__name__/__qualname__, so mocked defaults and annotations render like the real objects.Packages that are actually installed are never mocked.
doc-builder previewsupports the flag too.Fidelity verification
Built accelerate docs in a venv without torch or deepspeed installed and compared all 48 generated mdx pages against a real-torch reference build:
<object object at 0x…>addresses).torch.nn.Module,torch.Generator) instead of the internal one the real class exposes (torch.nn.modules.module.Module,torch._C.Generator) — arguably an improvement.Bonus fix while aligning rendering:
get_type_namenow prefers__qualname__for non-typing objects, so builtin-function annotations render ascallableinstead of<built-in function callable>(which also removes a source of nondeterministic0x…addresses in real builds).Adoption path
No workflow changes required: repos can pass
--mock_deps ...today via the shared workflows' existingadditional_argsinput. For transformers this opens the door to dropping the GPU container for doc builds (pip install transformers --no-deps+ light deps +--mock_deps torch,tensorflow,flax,...) — that migration should be validated per-library with the same mdx-diff methodology used here.Also includes
tests/test_mock_imports.pycovering availability checks, subclass signature preservation, decorator pass-through, and rendering.🤖 Generated with Claude Code