From 459c286f619f0d9a37743c1c6a8cbf38dae35965 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:51:26 +0000 Subject: [PATCH 1/5] [Docs] Mock all imports for docs Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- docs/mkdocs/hooks/generate_argparse.py | 59 ++++++++++++++++++++------ requirements/docs.txt | 9 ---- vllm/utils/cache.py | 4 +- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/docs/mkdocs/hooks/generate_argparse.py b/docs/mkdocs/hooks/generate_argparse.py index ea89108f01fc..05aaec55bec0 100644 --- a/docs/mkdocs/hooks/generate_argparse.py +++ b/docs/mkdocs/hooks/generate_argparse.py @@ -3,10 +3,11 @@ import importlib import logging import sys +import traceback from argparse import SUPPRESS, HelpFormatter from pathlib import Path from typing import Literal -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from pydantic_core import core_schema @@ -16,7 +17,30 @@ ARGPARSE_DOC_DIR = ROOT_DIR / "docs/argparse" sys.path.insert(0, str(ROOT_DIR)) + + +# Mock custom op code +class MockCustomOp: + @staticmethod + def register(name): + def decorator(cls): + return cls + + return decorator + + +noop = lambda *a, **k: None sys.modules["vllm._C"] = MagicMock() +sys.modules["vllm.model_executor.custom_op"] = MagicMock(CustomOp=MockCustomOp) +sys.modules["vllm.utils.torch_utils"] = MagicMock(direct_register_custom_op=noop) + +# Mock any version checks by reading from compiled CI requirements +with open(ROOT_DIR / "requirements/test.txt") as f: + VERSIONS = dict(line.split("==") for line in f.readlines() if "==" in line) +importlib.metadata.version = lambda name: VERSIONS.get(name) or "0.0.0" + +# Make torch.nn.Parameter safe to inherit from +sys.modules["torch.nn"] = MagicMock(Parameter=object) class PydanticMagicMock(MagicMock): @@ -31,20 +55,17 @@ def __get_pydantic_core_schema__(self, source_type, handler): return core_schema.any_schema() -def auto_mock(module, attr, max_mocks=50): +def auto_mock(module, attr, max_mocks=100): """Function that automatically mocks missing modules during imports.""" logger.info("Importing %s from %s", attr, module) for _ in range(max_mocks): try: # First treat attr as an attr, then as a submodule - with patch("importlib.metadata.version", return_value="0.0.0"): - return getattr( - importlib.import_module(module), - attr, - importlib.import_module(f"{module}.{attr}"), - ) - except importlib.metadata.PackageNotFoundError as e: - raise e + return getattr( + importlib.import_module(module), + attr, + importlib.import_module(f"{module}.{attr}"), + ) except ModuleNotFoundError as e: logger.info("Mocking %s for argparse doc generation", e.name) sys.modules[e.name] = PydanticMagicMock(name=e.name) @@ -139,10 +160,18 @@ def create_parser(add_cli_args, **kwargs) -> FlexibleArgumentParser: Returns: FlexibleArgumentParser: A parser with markdown formatting for the class. """ - parser = FlexibleArgumentParser(add_json_tip=False) - parser.formatter_class = MarkdownFormatter - with patch("vllm.config.DeviceConfig.__post_init__"): + try: + parser = FlexibleArgumentParser(add_json_tip=False) + parser.formatter_class = MarkdownFormatter _parser = add_cli_args(parser, **kwargs) + except ModuleNotFoundError as e: + # Auto-mock runtime imports + if tb_list := traceback.extract_tb(e.__traceback__): + path = Path(tb_list[-1].filename).relative_to(ROOT_DIR) + auto_mock(module=".".join(path.parent.parts), attr=path.stem) + return create_parser(add_cli_args, **kwargs) + else: + raise e # add_cli_args might be in-place so return parser if _parser is None return _parser or parser @@ -184,3 +213,7 @@ def on_startup(command: Literal["build", "gh-deploy", "serve"], dirty: bool): with open(doc_path, "w", encoding="utf-8") as f: f.write(super(type(parser), parser).format_help()) logger.info("Argparse generated: %s", doc_path.relative_to(ROOT_DIR)) + + +if __name__ == "__main__": + on_startup("build", False) diff --git a/requirements/docs.txt b/requirements/docs.txt index 00c314874016..ed7ddfc325d6 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -9,12 +9,3 @@ mkdocs-git-revision-date-localized-plugin mkdocs-minify-plugin regex ruff - -# Required for argparse hook only --f https://download.pytorch.org/whl/cpu -cachetools -cloudpickle -py-cpuinfo -msgspec -pydantic -torch diff --git a/vllm/utils/cache.py b/vllm/utils/cache.py index d5e08caa8a1e..4338983f9060 100644 --- a/vllm/utils/cache.py +++ b/vllm/utils/cache.py @@ -3,7 +3,7 @@ from collections import UserDict from collections.abc import Callable, Hashable, Iterator, KeysView, Mapping from types import MappingProxyType -from typing import Generic, NamedTuple, TypeVar, cast, overload +from typing import NamedTuple, TypeVar, cast, overload import cachetools @@ -48,7 +48,7 @@ def __sub__(self, other: "CacheInfo"): ) -class LRUCache(cachetools.LRUCache[_K, _V], Generic[_K, _V]): +class LRUCache(cachetools.LRUCache[_K, _V]): def __init__(self, capacity: float, getsizeof: Callable[[_V], float] | None = None): super().__init__(capacity, getsizeof) From 1b377205b2bde091a8d5a9f1c6dd07e2244a4cb6 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:57:32 +0000 Subject: [PATCH 2/5] `noop` the device check Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- docs/mkdocs/hooks/generate_argparse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/mkdocs/hooks/generate_argparse.py b/docs/mkdocs/hooks/generate_argparse.py index 05aaec55bec0..2caa865466ce 100644 --- a/docs/mkdocs/hooks/generate_argparse.py +++ b/docs/mkdocs/hooks/generate_argparse.py @@ -33,6 +33,7 @@ def decorator(cls): sys.modules["vllm._C"] = MagicMock() sys.modules["vllm.model_executor.custom_op"] = MagicMock(CustomOp=MockCustomOp) sys.modules["vllm.utils.torch_utils"] = MagicMock(direct_register_custom_op=noop) +sys.modules["vllm.config.DeviceConfig.__post_init__"] = noop # Mock any version checks by reading from compiled CI requirements with open(ROOT_DIR / "requirements/test.txt") as f: From 424153e71665e178acf6cb563272870169ebf750 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:58:47 +0000 Subject: [PATCH 3/5] Add pydantic back because it's imported in a hook Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- requirements/docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/docs.txt b/requirements/docs.txt index ed7ddfc325d6..0fd6dbe22c51 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -9,3 +9,4 @@ mkdocs-git-revision-date-localized-plugin mkdocs-minify-plugin regex ruff +pydantic From c561221776a55c020b8fab73f6566e5d3b4ce160 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:00:27 +0000 Subject: [PATCH 4/5] Mock `DeviceConfig.__post_init__` the same way we used to Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- docs/mkdocs/hooks/generate_argparse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/mkdocs/hooks/generate_argparse.py b/docs/mkdocs/hooks/generate_argparse.py index 2caa865466ce..846bc553b906 100644 --- a/docs/mkdocs/hooks/generate_argparse.py +++ b/docs/mkdocs/hooks/generate_argparse.py @@ -7,7 +7,7 @@ from argparse import SUPPRESS, HelpFormatter from pathlib import Path from typing import Literal -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from pydantic_core import core_schema @@ -33,7 +33,6 @@ def decorator(cls): sys.modules["vllm._C"] = MagicMock() sys.modules["vllm.model_executor.custom_op"] = MagicMock(CustomOp=MockCustomOp) sys.modules["vllm.utils.torch_utils"] = MagicMock(direct_register_custom_op=noop) -sys.modules["vllm.config.DeviceConfig.__post_init__"] = noop # Mock any version checks by reading from compiled CI requirements with open(ROOT_DIR / "requirements/test.txt") as f: @@ -164,7 +163,8 @@ def create_parser(add_cli_args, **kwargs) -> FlexibleArgumentParser: try: parser = FlexibleArgumentParser(add_json_tip=False) parser.formatter_class = MarkdownFormatter - _parser = add_cli_args(parser, **kwargs) + with patch("vllm.config.DeviceConfig.__post_init__"): + _parser = add_cli_args(parser, **kwargs) except ModuleNotFoundError as e: # Auto-mock runtime imports if tb_list := traceback.extract_tb(e.__traceback__): From c77f1d290f5f83679519a49f3c5c29b7c97c9c74 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:12:36 +0000 Subject: [PATCH 5/5] Strip newlines when getting versions Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- docs/mkdocs/hooks/generate_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs/hooks/generate_argparse.py b/docs/mkdocs/hooks/generate_argparse.py index 846bc553b906..ce1c5c53cf35 100644 --- a/docs/mkdocs/hooks/generate_argparse.py +++ b/docs/mkdocs/hooks/generate_argparse.py @@ -36,7 +36,7 @@ def decorator(cls): # Mock any version checks by reading from compiled CI requirements with open(ROOT_DIR / "requirements/test.txt") as f: - VERSIONS = dict(line.split("==") for line in f.readlines() if "==" in line) + VERSIONS = dict(line.strip().split("==") for line in f if "==" in line) importlib.metadata.version = lambda name: VERSIONS.get(name) or "0.0.0" # Make torch.nn.Parameter safe to inherit from