Fix model info cache for package models (#46567)

Signed-off-by: soaringk <k3vin.zhang@gmail.com>

soaringk committed Jun 29, 2026 at 17:17 UTC ab132ee98ba14c5d99977b1f83c2d5517c0a1e79
2 files changed +37 -2
tests/models/test_registry.py
+17
@@ -20,6 +20,7 @@ from vllm.model_executor.models.registry import (
20 _SPECULATIVE_DECODING_MODELS,
21 _TEXT_GENERATION_MODELS,
22 ModelRegistry,
23 + _LazyRegisteredModel,
24 )
25 from vllm.platforms import current_platform
26
@@ -127,6 +128,22 @@ def test_registry_is_pp(model_arch, is_pp, init_cuda):
128 )
129
130
131 +def test_lazy_modelinfo_package_hash_includes_submodules(tmp_path):
132 + package_dir = tmp_path / "model_package"
133 + package_dir.mkdir()
134 + init_file = package_dir / "__init__.py"
135 + init_file.write_text("from .model import Model\n", encoding="utf-8")
136 + model_file = package_dir / "model.py"
137 + model_file.write_text("class Model: pass\n", encoding="utf-8")
138 +
139 + first_hash = _LazyRegisteredModel._get_modelinfo_module_hash(init_file)
140 +
141 + model_file.write_text("class Model:\n supports_pp = True\n", encoding="utf-8")
142 + second_hash = _LazyRegisteredModel._get_modelinfo_module_hash(init_file)
143 +
144 + assert first_hash != second_hash
145 +
146 +
147 def test_hf_registry_coverage():
148 untested_archs = (
149 ModelRegistry.get_supported_archs() - HF_EXAMPLE_MODELS.get_supported_archs()
vllm/model_executor/models/registry.py
+20 -2
@@ -849,6 +849,25 @@ class _LazyRegisteredModel(_BaseRegisteredModel):
849 cls_name = f"{self.module_name}-{self.class_name}".replace(".", "-")
850 return f"{cls_name}.json"
851
852 + @staticmethod
853 + def _get_modelinfo_module_hash(model_path: Path) -> str:
854 + if model_path.name == "__init__.py":
855 + # Package entry points often re-export classes implemented in
856 + # submodules, so include the package contents in the cache key.
857 + module_paths = sorted(model_path.parent.rglob("*.py"))
858 + root_path = model_path.parent
859 + else:
860 + module_paths = [model_path]
861 + root_path = model_path.parent
862 +
863 + hasher = safe_hash(b"", usedforsecurity=False)
864 + for path in module_paths:
865 + hasher.update(path.relative_to(root_path).as_posix().encode("utf-8"))
866 + hasher.update(b"\0")
867 + hasher.update(path.read_bytes())
868 + hasher.update(b"\0")
869 + return hasher.hexdigest()
870 +
871 def _load_modelinfo_from_cache(self, module_hash: str) -> _ModelInfo | None:
872 try:
873 try:
@@ -915,8 +934,7 @@ class _LazyRegisteredModel(_BaseRegisteredModel):
934 module_hash = None
935
936 if model_path is not None and model_path.exists():
918 - with open(model_path, "rb") as f:
919 - module_hash = safe_hash(f.read(), usedforsecurity=False).hexdigest()
937 + module_hash = self._get_modelinfo_module_hash(model_path)
938
939 mi = self._load_modelinfo_from_cache(module_hash)
940 if mi is not None: