model_executor/models/registry.py: the model registry
Code version: vLLM @ 3e44c0d · 2026-10-22
Purpose#
vllm/model_executor/models/registry.py maps an architecture name from config.json to a model class, and tells the rest of vLLM what that class supports, without an import of the model in the front end.
| File | vllm/model_executor/models/registry.py, 1,623 lines of Python. About 820 lines are data tables (lines 73-887). |
|---|---|
| Main objects | ModelRegistry, the one _ModelRegistry object (vllm/model_executor/models/registry.py:1565); classes _ModelInfo, _RegisteredModel, _LazyRegisteredModel, _ModelRegistry. |
| Runs in | Every process that imports it. In vllm serve: the front end inspects (in ModelConfig); each worker resolves and imports the class; plugins call register_model everywhere. The file is also a script: python -m vllm.model_executor.models.registry is the inspection subprocess. |
| Called by | ModelConfig through its registry property (vllm/config/model.py:1165-1168), the model loader (vllm/model_executor/model_loader/utils.py:310), the speculative config for draft models (vllm/config/speculative.py:1779), some platform and KV-planning code, and general plugins through from vllm import ModelRegistry (vllm/__init__.py:26). |
| Calls | The detector functions of interfaces.py and interfaces_base.py (vllm/model_executor/models/registry.py:47-69), importlib, subprocess with cloudpickle, transformers (Transformers backend checks), safe_hash, current_platform.verify_model_arch. |
| Not in this file | The model classes, the capability protocols (interfaces.py, page Model capability protocols (SupportsPP, SupportsLoRA, ...)), the conversion adapters for --convert (adapters.py), and the plugin loader. |
The page Out-of-tree models and the model registry explains the registry from the user's side, with a plugin example. This page reads the file section by section.
Where it sits#
Map of the file#
| Lines | Section |
|---|---|
vllm/model_executor/models/registry.py:1-69 | Docstring (update tests/models/registry.py with each new model), imports. Line 26 imports vllm.config, so the inspection subprocess resolves current_platform early. |
vllm/model_executor/models/registry.py:73-804 | Ten tables of built-in architectures (summary below). |
vllm/model_executor/models/registry.py:806-817 | _VLLM_MODELS: all ten merged. |
vllm/model_executor/models/registry.py:819-823 | _SUBPROCESS_COMMAND. |
vllm/model_executor/models/registry.py:825-887 | _PREVIOUSLY_SUPPORTED_MODELS (50) and _OOT_SUPPORTED_MODELS (5): only for error texts. |
vllm/model_executor/models/registry.py:890-954 | _ModelInfo and from_model_cls. |
vllm/model_executor/models/registry.py:957-986 | _BaseRegisteredModel and the eager _RegisteredModel. |
vllm/model_executor/models/registry.py:989-1122 | The lazy _LazyRegisteredModel with its disk cache. |
vllm/model_executor/models/registry.py:1125-1150 | Module-level _try_load_model_cls / _try_inspect_model_cls with lru_cache. |
vllm/model_executor/models/registry.py:1152-1553 | _ModelRegistry: register, look up, error texts, Transformers fallback, twelve one-field helpers. |
vllm/model_executor/models/registry.py:1556-1573 | _resolve_module_name and the ModelRegistry object. |
vllm/model_executor/models/registry.py:1575-1623 | _run_in_subprocess, _run and the __main__ block. |
Who calls it#
- Front end, config time.
ModelConfig.__post_init__asksis_text_generation_modelandis_pooling_model, then callsinspect_model_clsand stores the record (vllm/config/model.py:703-744). PP and HC-SP checks callis_pp_supported_modelandis_hc_sp_supported_model(vllm/config/model.py:1552,vllm/config/model.py:1570). - Worker, model build.
_get_model_architecturecallsresolve_model_cls(vllm/model_executor/model_loader/utils.py:305-313). - Other readers. The CPU platform (
vllm/platforms/cpu.py:66), the Mamba page-size code ofPlatform(vllm/platforms/interface.py:1043) and model-owned KV cache grouping (vllm/v1/core/kv_cache_planning.py:1677). - Plugins.
register_modelandget_supported_archs(tests/plugins/vllm_add_dummy_model/vllm_add_dummy_model/__init__.py:11-12).
What it calls#
- 20 detector and getter functions to build
_ModelInfo(vllm/model_executor/models/registry.py:917-954). importlib.import_moduleandimportlib.util.find_spec(vllm/model_executor/models/registry.py:1082,vllm/model_executor/models/registry.py:1121).subprocess.run(_SUBPROCESS_COMMAND, ...)(vllm/model_executor/models/registry.py:1591-1593).current_platform.verify_model_archbefore each class import (vllm/model_executor/models/registry.py:1132); the base method accepts all models (vllm/platforms/interface.py:1142-1150).try_get_class_from_dynamic_moduleand thetransformerspackage, for the Transformers backend (vllm/model_executor/models/registry.py:1270-1282).
The tables, summarized#
| Table | Lines | Entries | Examples |
|---|---|---|---|
_TEXT_GENERATION_MODELS | vllm/model_executor/models/registry.py:73-215 | 121 | LlamaForCausalLM, Qwen3MoeForCausalLM |
_EMBEDDING_MODELS | vllm/model_executor/models/registry.py:217-268 | 38 | BertModel; a comprehension copies all LlamaForCausalLM-class names (lines 232-237) |
_LATE_INTERACTION_MODELS | vllm/model_executor/models/registry.py:270-287 | 13 | HF_ColBERT |
_REWARD_MODELS | vllm/model_executor/models/registry.py:289-293 | 3 | Qwen2ForRewardModel |
_TOKEN_CLASSIFICATION_MODELS | vllm/model_executor/models/registry.py:295-316 | 8 | BertForTokenClassification |
_SEQUENCE_CLASSIFICATION_MODELS | vllm/model_executor/models/registry.py:318-346 | 11 | cross-encoders |
_MULTIMODAL_MODELS | vllm/model_executor/models/registry.py:348-645 | 126 | Qwen2VLForConditionalGeneration |
_SPECULATIVE_DECODING_MODELS | vllm/model_executor/models/registry.py:647-732 | 66 | EagleLlamaForCausalLM, MTP heads |
_TRANSFORMERS_SUPPORTED_MODELS | vllm/model_executor/models/registry.py:734-769 | 23 | names served by the Transformers backend |
_TRANSFORMERS_BACKEND_MODELS | vllm/model_executor/models/registry.py:771-804 | 10 | TransformersForCausalLM |
The merged dict has 406 unique names. The counts come from an evaluation of the literals at this commit (derived); 13 names occur in two tables with the same value, so the 10 tables hold 419 entries in total; the later table wins in the merge. 36 entries point to vllm.models.* packages (What vLLM is today).
Key classes and functions#
| Name | Line | What it does |
|---|---|---|
_ModelInfo | vllm/model_executor/models/registry.py:890-915 | Frozen record of 24 capability fields. |
_ModelInfo.from_model_cls | vllm/model_executor/models/registry.py:917-954 | Runs the detectors on a class. |
_RegisteredModel | vllm/model_executor/models/registry.py:967-986 | Eager entry: the class and its record. |
_LazyRegisteredModel | vllm/model_executor/models/registry.py:988-1122 | Lazy entry: module and class names; cached inspection. |
_try_load_model_cls, _try_inspect_model_cls | vllm/model_executor/models/registry.py:1125-1150 | Cached wrappers that log an exception and return None. |
_ModelRegistry.register_model | vllm/model_executor/models/registry.py:1160-1203 | Adds or replaces an entry. |
_ModelRegistry._raise_for_unsupported | vllm/model_executor/models/registry.py:1205-1236 | Four ValueError texts. |
_ModelRegistry._try_resolve_transformers | vllm/model_executor/models/registry.py:1250-1323 | Name of a Transformers backend class, or None. |
_ModelRegistry._normalize_arch | vllm/model_executor/models/registry.py:1325-1349 | Maps a converted name to its base name. |
_ModelRegistry.inspect_model_cls | vllm/model_executor/models/registry.py:1351-1402 | Lookup order → (record, name). |
_ModelRegistry.resolve_model_cls | vllm/model_executor/models/registry.py:1404-1456 | Same order → (class, name). |
is_text_generation_model ... is_transcription_only_model | vllm/model_executor/models/registry.py:1458-1553 | Twelve one-field helpers over inspect_model_cls (one of them is model_has_inner_state). |
ModelRegistry | vllm/model_executor/models/registry.py:1565-1573 | The object, filled with lazy entries. |
_run_in_subprocess, _run | vllm/model_executor/models/registry.py:1578-1619 | Parent and child of the inspection subprocess. |
Walk through the main methods#
_LazyRegisteredModel: the disk cache#
@staticmethod
def _get_cache_dir() -> Path:
return Path(envs.VLLM_CACHE_ROOT) / "modelinfos"
def _get_cache_filename(self) -> str:
cls_name = f"{self.module_name}-{self.class_name}".replace(".", "-")
return f"{cls_name}.json"
@staticmethod
def _get_modelinfo_module_hash(model_path: Path) -> str:
if model_path.name == "__init__.py":
# Package entry points often re-export classes implemented in
# submodules, so include the package contents in the cache key.
module_paths = sorted(model_path.parent.rglob("*.py"))
root_path = model_path.parent
else:
module_paths = [model_path]
root_path = model_path.parent
hasher = safe_hash(b"", usedforsecurity=False)
for path in module_paths:
hasher.update(path.relative_to(root_path).as_posix().encode("utf-8"))
hasher.update(b"\0")
hasher.update(path.read_bytes())
hasher.update(b"\0")
return hasher.hexdigest()
def _load_modelinfo_from_cache(self, module_hash: str) -> _ModelInfo | None:
try:
try:
modelinfo_path = self._get_cache_dir() / self._get_cache_filename()
with open(modelinfo_path, encoding="utf-8") as file:
mi_dict = json.load(file)
except FileNotFoundError:
logger.debug(
"Cached model info file for class %s.%s not found",
self.module_name,
self.class_name,
)
return None
if mi_dict["hash"] != module_hash:
logger.debug(
"Cached model info file for class %s.%s is stale",
self.module_name,
self.class_name,
)
return None
# file not changed, use cached _ModelInfo properties
return _ModelInfo(**mi_dict["modelinfo"])
except Exception:
logger.debug(
"Cached model info for class %s.%s error. ",
self.module_name,
self.class_name,
)
return None- Line 997. The directory is
$VLLM_CACHE_ROOT/modelinfos. - Lines 1000-1001. The file name is
module-Class.jsonwith every dot replaced by "-". - Lines 1005-1009. For a package
__init__.py, all.pyfiles below the package are hashed, sorted; else only the one file. - Lines 1014-1020. The hash takes the relative path, a zero byte, the file bytes and a zero byte, for each file.
safe_hashis md5, or sha256 where md5 is not allowed (vllm/utils/hashing.py:118-121). - Lines 1036-1042. A different hash means "stale": DEBUG log,
None. - Line 1045. A hit rebuilds
_ModelInfofrom the JSON dict. Any other error also returnsNonewith a DEBUG log (lines 1046-1052).
The save writes {"hash": ..., "modelinfo": asdict(mi)} through atomic_writer, and only logs an exception on failure (vllm/model_executor/models/registry.py:1054-1069). The method that uses both is inspect_model_cls (vllm/model_executor/models/registry.py:1071-1118), walked on Out-of-tree models and the model registry.
_try_inspect_model_cls: the error filter#
@lru_cache(maxsize=128)
def _try_load_model_cls(
model_arch: str,
model: _BaseRegisteredModel,
) -> type[nn.Module] | None:
from vllm.platforms import current_platform
current_platform.verify_model_arch(model_arch)
try:
return model.load_model_cls()
except Exception:
logger.exception("Error in loading model architecture '%s'", model_arch)
return None
@lru_cache(maxsize=128)
def _try_inspect_model_cls(
model_arch: str,
model: _BaseRegisteredModel,
) -> _ModelInfo | None:
try:
return model.inspect_model_cls()
except Exception:
logger.exception("Error in inspecting model architecture '%s'", model_arch)
return None
- Lines 1125, 1140.
lru_cache(maxsize=128)keyed by (name, entry). Entries are frozen dataclasses, so they are hashable and compare by value. A new registration with another target is a new key; the same module and class strings give an equal key. - Line 1132. Before an import, the platform may reject the architecture.
- Lines 1135-1137, 1147-1149. Any exception is logged with its traceback ("Error in loading/inspecting model architecture"), and the result is
None. The caller then tries the next step of the lookup.
resolve_model_cls: the worker's lookup#
def resolve_model_cls(
self,
architectures: str | list[str],
model_config: ModelConfig,
) -> tuple[type[nn.Module], str]:
if isinstance(architectures, str):
architectures = [architectures]
if not architectures:
raise ValueError("No model architectures are specified")
# Require transformers impl
if model_config.model_impl == "transformers":
arch = self._try_resolve_transformers(architectures[0], model_config)
if arch is not None:
model_cls = self._try_load_model_cls(arch)
if model_cls is not None:
return (model_cls, arch)
elif model_config.model_impl == "terratorch":
arch = "Terratorch"
model_cls = self._try_load_model_cls(arch)
if model_cls is not None:
return (model_cls, arch)
# Fallback to transformers impl (after resolving convert_type)
if (
all(arch not in self.models for arch in architectures)
and model_config.model_impl == "auto"
and getattr(model_config, "convert_type", "none") == "none"
):
arch = self._try_resolve_transformers(architectures[0], model_config)
if arch is not None:
model_cls = self._try_load_model_cls(arch)
if model_cls is not None:
return (model_cls, arch)
for arch in architectures:
normalized_arch = self._normalize_arch(arch, model_config)
model_cls = self._try_load_model_cls(normalized_arch)
if model_cls is not None:
return (model_cls, arch)
# Fallback to transformers impl (before resolving runner_type)
if (
all(arch not in self.models for arch in architectures)
and model_config.model_impl == "auto"
):
arch = self._try_resolve_transformers(architectures[0], model_config)
if arch is not None:
model_cls = self._try_load_model_cls(arch)
if model_cls is not None:
return (model_cls, arch)
return self._raise_for_unsupported(architectures)- Lines 1414-1425.
transformersorterratorchimplementation first. - Lines 1427-1437. Transformers fallback when no name is registered and no conversion is set.
- Lines 1439-1443. Each name, normalized; the first class that imports wins. The returned name is the original one, not the normalized one.
- Lines 1445-1454. The fallback again, without the conversion condition.
- Line 1456.
_raise_for_unsupported.
inspect_model_cls (lines 1351-1402) is the same with _try_inspect_model_cls, except one detail: for terratorch it asserts that inspection worked (line 1370).
_raise_for_unsupported: four messages#
def _raise_for_unsupported(self, architectures: list[str]):
all_supported_archs = self.get_supported_archs()
if any(arch in all_supported_archs for arch in architectures):
raise ValueError(
f"Model architectures {architectures} failed "
"to be inspected. Please check the logs for more details."
)
for arch in architectures:
if arch in _PREVIOUSLY_SUPPORTED_MODELS:
previous_version = _PREVIOUSLY_SUPPORTED_MODELS[arch]
raise ValueError(
f"Model architecture {arch} was supported in vLLM until "
f"v{previous_version}, and is not supported anymore. "
"Please use an older version of vLLM if you want to "
"use this model architecture."
)
if arch in _OOT_SUPPORTED_MODELS:
plugin_url = _OOT_SUPPORTED_MODELS[arch]
raise ValueError(
f"Model architecture {arch} is not supported in-tree anymore. "
f"Please install the plugin at {plugin_url} if you want to "
"use this model architecture."
)
raise ValueError(
f"Model architectures {architectures} are not supported for now. "
f"Supported architectures: {all_supported_archs}"
)The order matters: a registered name that failed always gets "failed to be inspected" (lines 1208-1212), even when it is also in a removed-models table. The last message lists all supported names, so it can be long (lines 1233-1236).
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveData flow: types in and out
| Value | Type | Where |
|---|---|---|
| table value | tuple[str, str]: (module, class); a module without vllm. is relative to vllm.model_executor.models | vllm/model_executor/models/registry.py:1556-1562 |
_ModelRegistry.models | dict[str, _BaseRegisteredModel] | vllm/model_executor/models/registry.py:1154-1155 |
get_supported_archs() | a live view of the dict keys (Set[str]) | vllm/model_executor/models/registry.py:1157-1158 |
inspect_model_cls argument | str | list[str]; empty → ValueError | vllm/model_executor/models/registry.py:1356-1359 |
inspect_model_cls result | (_ModelInfo, str) | vllm/model_executor/models/registry.py:1355 |
| subprocess stdin | cloudpickle of (fn, output_path) | vllm/model_executor/models/registry.py:1587 |
| subprocess output | pickle of _ModelInfo in registry_output.tmp of a temporary directory | vllm/model_executor/models/registry.py:1581-1582, vllm/model_executor/models/registry.py:1618-1619 |
Deep diveProcesses: who runs what
- Every process builds
ModelRegistryat import, with 406 lazy entries and no model import (vllm/model_executor/models/registry.py:1565-1573). - Every process that loads general plugins runs the plugins'
register_modelcalls. The registry is a module global, so each process has its own copy. - The front end inspects. On a cache miss it starts one extra Python process per lazy entry; that child loads the general plugins too (
vllm/model_executor/models/registry.py:1608-1612). - Each worker imports the chosen class through
resolve_model_cls.
The child gets its command from _SUBPROCESS_COMMAND, a module list that the comment allows other packagers to change (vllm/model_executor/models/registry.py:819-823). The comment at lines 1589-1590 says why it uses -m: the module uses relative imports.
Deep diveA worked example: the cache file of an out-of-tree package
A plugin registers register_model("MyLlava", "vllm_add_dummy_model.my_llava:MyLlava") (tests/plugins/vllm_add_dummy_model/vllm_add_dummy_model/__init__.py:26-27).
- The module name does not start with
vllm.model_executor.models., sofind_spec("vllm_add_dummy_model.my_llava")finds the file (lines 1080-1085). This imports the packagevllm_add_dummy_model, but notmy_llava(Python rule). - The file is
my_llava.py, not__init__.py, so the hash covers one file, with the relative path"my_llava.py". - The cache file is
$VLLM_CACHE_ROOT/modelinfos/vllm_add_dummy_model-my_llava-MyLlava.json(dots replaced; underscores stay). - On a miss, the child imports
my_llavaand returns the record.supports_multimodalis then true ifMyLlavainherits the multimodal protocol. (The class content is not shown here.)
Deep diveError paths: what raises, what only logs
| Situation | Result | Where |
|---|---|---|
register_model with a non-string name | TypeError | vllm/model_executor/models/registry.py:1175-1177 |
| A string without exactly one colon | ValueError | vllm/model_executor/models/registry.py:1187-1191 |
Neither string nor nn.Module class | TypeError | vllm/model_executor/models/registry.py:1196-1201 |
| Name already registered | overwrite, DEBUG log | vllm/model_executor/models/registry.py:1179-1185 |
| Cache file missing, stale or broken | DEBUG log; subprocess | vllm/model_executor/models/registry.py:1028-1052 |
| Cache write fails | logger.exception; start-up goes on | vllm/model_executor/models/registry.py:1068-1069 |
| Subprocess exits with an error | RuntimeError with the child's stderr, caught and logged by the wrapper | vllm/model_executor/models/registry.py:1596-1602, vllm/model_executor/models/registry.py:1147-1149 |
| No step finds a class | ValueError (four texts) | vllm/model_executor/models/registry.py:1205-1236 |
--model-impl transformers with no Transformers class | ValueError "Cannot find model module" | vllm/model_executor/models/registry.py:1297-1307 |
Deep diveEnvironment variables
| Variable | Default | Read at | Effect |
|---|---|---|---|
VLLM_CACHE_ROOT | ~/.cache/vllm, or $XDG_CACHE_HOME/vllm (vllm/envs.py:713-718) | vllm/model_executor/models/registry.py:997 | Root of the modelinfos cache. |
VLLM_PLUGINS | not set | in the child, through load_general_plugins (vllm/model_executor/models/registry.py:1612) | The child sees the same plugin filter as its parent (it inherits the environment). |
Related concepts#
- Out-of-tree models and the model registry: how to ship a model plugin, and the lookup order with an example.
- Model capability protocols (SupportsPP, SupportsLoRA, ...): what each
_ModelInfofield means and who reads it. - How vLLM finds and loads plugins: why the subprocess loads general plugins, and the extra plugin run on a miss.
- How a vLLM model file is written (llama.py): how a model class is written.
Check yourself#
- 1How many names does the built-in registry hold at this commit, and what kind of entry is each?easy
Show answer
406 names in
_VLLM_MODELS(derived count). Each becomes a_LazyRegisteredModel(vllm/model_executor/models/registry.py:1565-1573). - 2Why doesmedium
_try_inspect_model_clsreturnNoneinstead of raising?Show answer
So that the lookup can try the next step (another name, or the Transformers fallback). The exception is logged with its traceback (
vllm/model_executor/models/registry.py:1141-1150); if all steps fail,_raise_for_unsupportedraises "failed to be inspected" for a registered name. - 3What is the cache file name formedium
vllm.models.deepseek_v4:DeepseekV4ForCausalLM, and what does its hash cover?Show answer
vllm-models-deepseek_v4-DeepseekV4ForCausalLM.json(vllm/model_executor/models/registry.py:999-1001). The source is the package__init__.py, so the hash covers every.pyfile undervllm/models/deepseek_v4/(vllm/model_executor/models/registry.py:1005-1009). - 4Config sayshard
["MyForSequenceClassification"], onlyMyForCausalLMis registered, and the user passes--convert classify. Which class do the workers build?Show answer
The Transformers fallback before the loop is skipped because a conversion is set (
vllm/model_executor/models/registry.py:1427-1437). In the loop,_normalize_archmatches the suffixForSequenceClassificationand findsMyForCausalLM(vllm/model_executor/models/registry.py:1335-1347). The loader then wraps it withas_seq_cls_model(vllm/model_executor/model_loader/utils.py:331-333).