Out-of-tree models and the model registry
Code version: vLLM @ 3e44c0d · 2026-10-22
In one sentence
vLLM looks up the architecture name from config.json in ModelRegistry; a plugin adds its own class there with register_model.
The idea#
A Hugging Face checkpoint names its model class in config.json, for example "architectures": ["LlamaForCausalLM"] (Hugging Face checkpoints). vLLM does not use the Transformers class. It looks up the name in the model registry, a table from architecture name to a vLLM model class. At this commit the built-in table has 406 names.
An out-of-tree package adds a name to the table, or replaces one, with ModelRegistry.register_model(). It calls this function from a general plugin, so the call runs in every process (How vLLM finds and loads plugins). The package can give the class itself, or a string "module:Class". The string makes a lazy entry: vLLM imports the module only in a process that needs the class.
The front end must know what the model can do before any worker exists: is it generative, does it support pipeline parallelism or LoRA? So the registry inspects the class and keeps the answers in a model info record. For a lazy entry, the inspection runs in a separate Python process, and the result goes into a JSON cache on disk. The page Model capability protocols (SupportsPP, SupportsLoRA, ...) explains the answers.
An everyday comparison. Think of a library catalogue. Each card has a title and a shelf position, not the book itself. You fetch the book only when you want to read it. A catalogue clerk writes a summary of each book on the card once, and reuses it until the book changes. The comparison stops here. A vLLM "card" can also hold the book itself (the eager form). And a new card with the same title replaces the old one.
The picture#
Read panel 2 as the order of the code: the first step that returns a class wins. Panel 3 happens in the front end of vllm serve. The worker in the bottom right imports the class only to build the model.
In the code#
Step 1: the built-in tables#
registry.py holds ten tables, one per kind of model. Each value is a pair (module, class). A short module name means a file in vllm/model_executor/models/:
_TEXT_GENERATION_MODELS = {
# [Decoder-only]
"AfmoeForCausalLM": ("afmoe", "AfmoeForCausalLM"),
"AliceAIForCausalLM": ("alice_ai", "AliceAIForCausalLM"),
"ApertusForCausalLM": ("apertus", "ApertusForCausalLM"),
"AXK1ForCausalLM": ("AXK1", "AXK1ForCausalLM"),
"BailingMoeForCausalLM": ("bailing_moe", "BailingMoeForCausalLM"),
"BailingMoeV2ForCausalLM": ("bailing_moe", "BailingMoeV2ForCausalLM"),
"BailingMoeV2_5ForCausalLM": ("bailing_moe_linear", "BailingMoeV25ForCausalLM"),
"BailingMoeV3ForCausalLM": ("bailing_moe_v3", "BailingMoeV3ForCausalLM"),
"BeamForCausalLM": ("vllm.models.beam", "BeamForCausalLM"),The ten tables merge into _VLLM_MODELS (vllm/model_executor/models/registry.py:806-817). At the end of the file, every entry becomes a lazy entry:
def _resolve_module_name(mod_relname: str) -> str:
# Allow registry entries to point at fully-qualified module paths (e.g.
# ``vllm.models.deepseek_v4``) for models that live outside the legacy
# ``vllm.model_executor.models`` flat layout.
if mod_relname.startswith("vllm."):
return mod_relname
return f"vllm.model_executor.models.{mod_relname}"
ModelRegistry = _ModelRegistry(
{
model_arch: _LazyRegisteredModel(
module_name=_resolve_module_name(mod_relname),
class_name=cls_name,
)
for model_arch, (mod_relname, cls_name) in _VLLM_MODELS.items()
}
)vllm. stays as it is. Other names get the prefix vllm.model_executor.models..| Table | Lines | Entries |
|---|---|---|
_TEXT_GENERATION_MODELS | vllm/model_executor/models/registry.py:73-215 | 121 |
_EMBEDDING_MODELS | vllm/model_executor/models/registry.py:217-268 | 38 (33 written, plus 5 copied from the text table, lines 232-237) |
_LATE_INTERACTION_MODELS | vllm/model_executor/models/registry.py:270-287 | 13 |
_REWARD_MODELS | vllm/model_executor/models/registry.py:289-293 | 3 |
_TOKEN_CLASSIFICATION_MODELS | vllm/model_executor/models/registry.py:295-316 | 8 |
_SEQUENCE_CLASSIFICATION_MODELS | vllm/model_executor/models/registry.py:318-346 | 11 |
_MULTIMODAL_MODELS | vllm/model_executor/models/registry.py:348-645 | 126 |
_SPECULATIVE_DECODING_MODELS | vllm/model_executor/models/registry.py:647-732 | 66 |
_TRANSFORMERS_SUPPORTED_MODELS | vllm/model_executor/models/registry.py:734-769 | 23 |
_TRANSFORMERS_BACKEND_MODELS | vllm/model_executor/models/registry.py:771-804 | 10 |
_VLLM_MODELS (all) | vllm/model_executor/models/registry.py:806-817 | 406 names (the sum is 419; 13 names occur in two tables with the same value) |
The counts come from an evaluation of the dict literals at this commit (derived). Two more tables are not in the registry. One lists 50 removed architectures, each with the last vLLM version that had it (vllm/model_executor/models/registry.py:825-879). The other lists 5 architectures that moved to plugins, with the plugin URL (vllm/model_executor/models/registry.py:881-887). They only improve the error text (Step 4).
The second model tree. 36 of the 406 entries point to modules in vllm/models/, for example ("vllm.models.beam", "BeamForCausalLM") (vllm/model_executor/models/registry.py:83). This tree has one package per model family, with sub-packages per vendor. The package __init__.py picks the implementation for current_platform, for example in vllm/models/deepseek_v4 (vllm/models/deepseek_v4/__init__.py:14-48). For the registry, these are normal lazy entries. The only difference: the cache hash covers every file of the package (Step 5). This site does not explain the packages further (What vLLM is today).
Step 2: register_model(), eager or lazy#
def register_model(
self,
model_arch: str,
model_cls: type[nn.Module] | str,
) -> None:
"""Register an external model to be used in vLLM.
`model_cls` can be either:
- A [`torch.nn.Module`][] class directly referencing the model.
- A string in the format `<module>:<class>` which can be used to
lazily import the model. This is useful to avoid initializing CUDA
when importing the model and thus the related error
`RuntimeError: Cannot re-initialize CUDA in forked subprocess`.
"""
if not isinstance(model_arch, str):
msg = f"`model_arch` should be a string, not a {type(model_arch)}"
raise TypeError(msg)
if model_arch in self.models:
logger.debug(
"Model architecture %s is already registered, and will be "
"overwritten by the new model class %s.",
model_arch,
model_cls,
)
if isinstance(model_cls, str):
split_str = model_cls.split(":")
if len(split_str) != 2:
msg = "Expected a string in the format `<module>:<class>`"
raise ValueError(msg)
model = _LazyRegisteredModel(*split_str)
elif isinstance(model_cls, type) and issubclass(model_cls, nn.Module):
model = _RegisteredModel.from_model_cls(model_cls)
else:
msg = (
"`model_cls` should be a string or PyTorch model class, "
f"not a {type(model_cls)}"
)
raise TypeError(msg)
self.models[model_arch] = model- Lines 1175-1177. The architecture name must be a
str, elseTypeError. - Lines 1179-1185. A name that is already in the table is overwritten. vLLM logs this only at DEBUG level. So a plugin can also replace a built-in model, for example
LlamaForCausalLM. - Lines 1187-1193. A string must contain exactly one colon.
"pkg.mod:Cls"becomes_LazyRegisteredModel("pkg.mod", "Cls"). Nothing is imported now."pkg.mod.Cls"(dots only) raisesValueError. - Lines 1194-1195. A subclass of
nn.Modulebecomes a_RegisteredModel. Its constructor computes the model info at once, in this process (vllm/model_executor/models/registry.py:974-979). - Lines 1196-1201. Anything else (an object, a function) raises
TypeError.
Why lazy. The docstring gives the reason: a string avoids an import that starts CUDA, and so the error "Cannot re-initialize CUDA in forked subprocess" (lines 1170-1173). In vllm serve the CLI already uses spawn (vllm/entrypoints/common/startup.py:147-149), and get_mp_context switches to spawn when CUDA is already started (vllm/utils/system_utils.py:145-162). The lazy form still has a clear gain: a process that does not build the model does not import its module. A test checks that the inspection of a lazy entry does not start CUDA (tests/models/test_registry.py:150-158).
Step 3: a model plugin package#
vLLM's test package vllm_add_dummy_model is the smallest complete example. Its setup.py publishes one general plugin:
setup(
name="vllm_add_dummy_model",
version="0.1",
packages=["vllm_add_dummy_model"],
entry_points={
"vllm.general_plugins": ["register_dummy_model = vllm_add_dummy_model:register"]
},
)The function registers four architectures, two in each form:
from vllm import ModelRegistry
def register():
# Test directly passing the model
from .my_opt import MyOPTForCausalLM, MyOPTRawInputTokensForCausalLM
if "MyOPTForCausalLM" not in ModelRegistry.get_supported_archs():
ModelRegistry.register_model("MyOPTForCausalLM", MyOPTForCausalLM)
if "MyOPTRawInputTokensForCausalLM" not in ModelRegistry.get_supported_archs():
ModelRegistry.register_model(
"MyOPTRawInputTokensForCausalLM", MyOPTRawInputTokensForCausalLM
)
# Test passing lazy model
if "MyGemma2Embedding" not in ModelRegistry.get_supported_archs():
ModelRegistry.register_model(
"MyGemma2Embedding",
"vllm_add_dummy_model.my_gemma_embedding:MyGemma2Embedding",
)
if "MyLlava" not in ModelRegistry.get_supported_archs():
ModelRegistry.register_model("MyLlava", "vllm_add_dummy_model.my_llava:MyLlava")- Line 4.
from vllm import ModelRegistryis a lazy package attribute (vllm/__init__.py:26). - Line 9. The eager form needs the class, so the function imports
my_optinside the function, not at module level. A failed import here crashes start-up, because vLLM does not catch errors of a plugin function (vllm/plugins/__init__.py:89-90). - Lines 11, 14, 20, 26. Each call checks
get_supported_archs()first. The plugin function runs in several processes and must be safe to run again. - Lines 20-24. The lazy form: only a string.
The model class itself can be small. MyOPTForCausalLM subclasses the in-tree OPT model and changes only compute_logits, so token 0 always has the highest logit (tests/plugins/vllm_add_dummy_model/vllm_add_dummy_model/my_opt.py:11-18). A test sets VLLM_PLUGINS="": the plugin does not load, and vLLM raises "are not supported for now" (tests/plugins_tests/test_oot_registration_offline.py:13-22).
Step 4: from config.json to a class#
The front end inspects the architecture while it makes ModelConfig (vllm/config/model.py:703-706, vllm/config/model.py:741-744). The log line "Resolved architecture: ..." shows the result at INFO level. inspect_model_cls tries these steps in order:
def inspect_model_cls(
self,
architectures: str | list[str],
model_config: ModelConfig,
) -> tuple[_ModelInfo, 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_info = self._try_inspect_model_cls(arch)
if model_info is not None:
return (model_info, arch)
elif model_config.model_impl == "terratorch":
model_info = self._try_inspect_model_cls("Terratorch")
assert model_info is not None
return (model_info, "Terratorch")
# 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_info = self._try_inspect_model_cls(arch)
if model_info is not None:
return (model_info, arch)
for arch in architectures:
normalized_arch = self._normalize_arch(arch, model_config)
model_info = self._try_inspect_model_cls(normalized_arch)
if model_info is not None:
return (model_info, 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_info = self._try_inspect_model_cls(arch)
if model_info is not None:
return (model_info, arch)
return self._raise_for_unsupported(architectures)- Lines 1362-1367. With
--model-impl transformers, the Transformers backend class comes first. See model implementation. - Lines 1368-1371. With
--model-impl terratorch, the answer is always"Terratorch". - Lines 1374-1383. If no name of the list is registered,
model_implisautoand no conversion is set, try the Transformers backend. - Lines 1385-1389. Try each name of
architecturesin order._normalize_archkeeps a registered name. Otherwise it maps a name with a known suffix to a registered base name, for exampleMyForSequenceClassificationtoMyForCausalLM(vllm/model_executor/models/registry.py:1325-1349). The first name that inspects wins. - Lines 1391-1400. The Transformers fallback again, now without the conversion condition.
- Line 1402. No answer:
_raise_for_unsupportedraisesValueError.
resolve_model_cls has the same order, but it imports the class (vllm/model_executor/models/registry.py:1404-1456). The worker calls it when it builds the model (vllm/model_executor/model_loader/utils.py:305-313). If the result is a Transformers backend class and model_impl is auto, vLLM warns "has no vLLM implementation, falling back to Transformers implementation" (vllm/model_executor/model_loader/utils.py:315-322).
The four messages of _raise_for_unsupported tell you the cause (vllm/model_executor/models/registry.py:1205-1236):
- "failed to be inspected": the name is registered, but the import or inspection failed. The traceback is in the log above it.
- "was supported in vLLM until v...": the architecture was removed.
- "is not supported in-tree anymore. Please install the plugin at ...": the architecture moved to a plugin.
- "are not supported for now": the name is unknown. Your plugin did not load, or did not register this name.
Step 5: the inspection subprocess and its cache#
A lazy entry inspects its class without an import in the calling process:
def inspect_model_cls(self) -> _ModelInfo:
# Modules registered with a non-default location (e.g. the
# hardware-isolated ``vllm.models.<name>`` layout) live outside
# ``vllm/model_executor/models``. Resolve the module spec directly
# so the file-hash cache stays warm for them.
model_path: Path | None = None
if self.module_name.startswith("vllm.model_executor.models."):
model_path = Path(__file__).parent / f"{self.module_name.split('.')[-1]}.py"
if model_path is None or not model_path.exists():
try:
spec = importlib.util.find_spec(self.module_name)
except (ImportError, ValueError):
spec = None
model_path = Path(spec.origin) if spec is not None and spec.origin else None
module_hash = None
if model_path is not None and model_path.exists():
module_hash = self._get_modelinfo_module_hash(model_path)
mi = self._load_modelinfo_from_cache(module_hash)
if mi is not None:
logger.debug(
"Loaded model info for class %s.%s from cache",
self.module_name,
self.class_name,
)
return mi
else:
logger.debug(
"Cache model info for class %s.%s miss. Loading model instead.",
self.module_name,
self.class_name,
)
# Performed in another process to avoid initializing CUDA
mi = _run_in_subprocess(
lambda: _ModelInfo.from_model_cls(self.load_model_cls())
)
logger.debug(
"Loaded model info for class %s.%s", self.module_name, self.class_name
)
# save cache file
if module_hash is not None:
self._save_modelinfo_to_cache(mi, module_hash)
return mi- Lines 1078-1085. Find the source file: directly for
vllm.model_executor.models.*, else withimportlib.util.find_spec.find_specimports the parent package, not the module itself (Python rule). - Lines 1088-1091. Hash the source and read the cache file. The hash is md5 (sha256 where md5 is not available) of the relative path and the bytes of the file (
vllm/model_executor/models/registry.py:1004-1020,vllm/utils/hashing.py:118-121). For a package__init__.py, it covers every.pyfile of the package (lines 1005-1009). - Lines 1106-1109. On a miss, run
_ModelInfo.from_model_cls(self.load_model_cls())in a subprocess. The comment says why: "to avoid initializing CUDA". - Lines 1114-1116. Save the result, only if a source file was found.
The cache directory is $VLLM_CACHE_ROOT/modelinfos (vllm/model_executor/models/registry.py:996-997). VLLM_CACHE_ROOT defaults to ~/.cache/vllm, or $XDG_CACHE_HOME/vllm (vllm/envs.py:713-718). The subprocess is python -m vllm.model_executor.models.registry (vllm/model_executor/models/registry.py:823). It loads the general plugins first, so your plugin's packages are ready:
def _run_in_subprocess(fn: Callable[[], _T]) -> _T:
# NOTE: We use a temporary directory instead of a temporary file to avoid
# issues like https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file
with tempfile.TemporaryDirectory() as tempdir:
output_filepath = os.path.join(tempdir, "registry_output.tmp")
# `cloudpickle` allows pickling lambda functions directly
import cloudpickle
input_bytes = cloudpickle.dumps((fn, output_filepath))
# cannot use `sys.executable __file__` here because the script
# contains relative imports
returned = subprocess.run(
_SUBPROCESS_COMMAND, input=input_bytes, capture_output=True
)
# check if the subprocess is successful
try:
returned.check_returncode()
except Exception as e:
# wrap raised exception to provide more information
raise RuntimeError(
f"Error raised in subprocess:\n{returned.stderr.decode()}"
) from e
with open(output_filepath, "rb") as f:
return pickle.load(f)
def _run() -> None:
# Setup plugins
from vllm.plugins import load_general_plugins
load_general_plugins()
fn, output_file = pickle.loads(sys.stdin.buffer.read())
result = fn()
with open(output_file, "wb") as f:
f.write(pickle.dumps(result))This runs only on a cache miss, and also for built-in models, because they are lazy too. The page How vLLM finds and loads plugins counts the extra plugin run that a miss causes.
Step 6: what the class must look like#
The worker builds the model with initialize_model. It checks the constructor signature by name:
signatures = inspect.signature(model_class.__init__)
all_params = [param.name for param in signatures.parameters.values()]
if "vllm_config" in all_params and "prefix" in all_params:
# new-style model class
with set_current_vllm_config(vllm_config, check_compile=True, prefix=prefix):
model = model_class(vllm_config=vllm_config, prefix=prefix)
record_metadata_for_reloading(model)
return model
msg = (
"vLLM model class should accept `vllm_config` and `prefix` as "
"input arguments. Possibly you have an old-style model class"
" registered from out of tree and it is used for new vLLM version. "
"Check https://docs.vllm.ai/en/latest/design/arch_overview.html "
"for the design and update the model class accordingly."
)
warnings.warn(msg, DeprecationWarning, stacklevel=2)A class without both vllm_config and prefix gets a DeprecationWarning, and vLLM guesses the old arguments (lines 114-136 of the same file). The registry also checks the structure when it inspects the class (Model capability protocols (SupportsPP, SupportsLoRA, ...)):
__init__accepts the keywordvllm_config, the class hasembed_input_ids, andforwardacceptsinput_idsandpositions(vllm/model_executor/models/interfaces_base.py:108-115).- A generative model also has
compute_logits(vllm/model_executor/models/interfaces_base.py:140-147). - The default loader calls
model.load_weights(weights)with an iterator of (name, tensor) pairs (vllm/model_executor/model_loader/default_loader.py:503). The registry does not check this method.
The page How a vLLM model file is written (llama.py) explains how to write these methods, with llama.py as the example.
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 diveWorked example: Llama on a fresh host, then with a plugin
A fresh host runs vllm serve meta-llama/Llama-3.1-8B-Instruct. config.json says "architectures": ["LlamaForCausalLM"]. (The model is an example.)
- The front end builds
ModelConfig.registry.is_text_generation_modelcallsinspect_model_cls(["LlamaForCausalLM"], ...)(vllm/config/model.py:705). model_implisautoand the name is registered, so the first fallback is skipped. Step 4 of the lookup finds the lazy entry("vllm.model_executor.models.llama", "LlamaForCausalLM").- The source path is
vllm/model_executor/models/llama.py(line 1079). The cache file is~/.cache/vllm/modelinfos/vllm-model_executor-models-llama-LlamaForCausalLM.json: module and class joined by "-", dots replaced by "-" (vllm/model_executor/models/registry.py:999-1001). - The file does not exist: DEBUG log "not found", then a subprocess. It loads the general plugins, imports
llama.py, computes the 24 fields and returns them. The parent writes{"hash": "...", "modelinfo": {...}}(vllm/model_executor/models/registry.py:1054-1070). - The next start of the server finds the file with the same hash: no subprocess.
Now the user installs a plugin that calls register_model("LlamaForCausalLM", "acme.llama:FastLlama") (a made-up package). The front end loads general plugins while it builds the CLI parser, before ModelConfig exists (vllm/engine/arg_utils.py:3243). So the lookup finds the new lazy entry. Its cache file is acme-llama-FastLlama.json, with the hash of acme/llama.py. Each worker loads the same plugin in init_worker and imports acme.llama in resolve_model_cls.
Deep diveCaches and how often each step runs
- Per process, per entry.
_try_inspect_model_clsand_try_load_model_clsare module functions withlru_cache(maxsize=128), keyed by (name, entry) (vllm/model_executor/models/registry.py:1125-1150).ModelConfigasks the registry several times, but the subprocess runs at most once per entry and process. - Per model in the worker.
get_model_architecturekeeps a dict keyed by a hash of the model name, convert type, runner type,trust_remote_code,model_impland architectures (vllm/model_executor/model_loader/utils.py:340-356). - On disk. One JSON file per (module, class). The key is the source hash only. A change in a file that the model module imports does not change the hash (derived from lines 1004-1020). Delete
$VLLM_CACHE_ROOT/modelinfosafter you edit an imported mixin. - Eager entries. No disk cache and no subprocess. The process computes the info at registration (line 1195). The front end, the engine core and every worker import the module, because each one runs the general plugin.
The method inspect_model_cls of a lazy entry has the @logtime decorator: "Registry inspect model class: Elapsed time ..." at DEBUG level (vllm/model_executor/models/registry.py:1071, vllm/logging_utils/log_time.py:17-26).
Deep diveThe Transformers backend fallback in detail
_try_resolve_transformers decides if the Transformers backend can run a name (vllm/model_executor/models/registry.py:1250-1323):
- A name from
_TRANSFORMERS_BACKEND_MODELS, such asTransformersForCausalLM, is returned as it is (lines 1255-1256). - Else it loads the
auto_mapclasses of a custom checkpoint, config class first (lines 1270-1280). - It looks for the name in the
transformerspackage, then inauto_map["AutoModel*"](lines 1282-1307). With nothing found:Noneforauto,ValueErrorfortransformers. - The class must be backend-compatible, or able to set its attention implementation (lines 1309-1321).
- It returns the name of the backend class from
ModelConfig._get_transformers_backend_cls():Transformers+MultiModal(nested config) +MoE+ a suffix by task (vllm/config/model.py:1129-1157).
The names in _TRANSFORMERS_SUPPORTED_MODELS, such as OlmoForCausalLM, are normal registry names. They map to a backend class directly, so the fallback is not needed for them (vllm/model_executor/models/registry.py:734-752).
--model-impl | Effect on the lookup |
|---|---|
auto (default, vllm/config/model.py:398) | vLLM class first; Transformers backend only if no name is registered. |
vllm | Registry only; no fallback. |
transformers | Transformers backend first; then the registry. |
terratorch | Always the Terratorch entry. |
Deep divemodel_class_overrides: a development hook
--model-class-overrides maps an architecture name to a "module:Class" target, the same format as register_model. The docstring says it is "for development and debugging purposes only" (vllm/config/model.py:351-357).
@property
def registry(self):
self._maybe_register_model_class_overrides()
return me_models.ModelRegistry
def _maybe_register_model_class_overrides(self) -> None:
# Apply ``model_class_overrides`` here because this property is the
# single chokepoint through which every model-class inspect/resolve
# passes, in both the engine front-end and every worker process. The
# guard is process-local (see ``_REGISTERED_MODEL_CLASS_OVERRIDES``), so
# each worker re-registers into its own ModelRegistry exactly once
# rather than trusting a pickled-in instance flag.
if not self.model_class_overrides:
return
pending = [
(arch, target)
for arch, target in self.model_class_overrides.items()
if (arch, target) not in _REGISTERED_MODEL_CLASS_OVERRIDES
]
if not pending:
return
logger.warning_once(
"Applying model_class_overrides %s. This is intended for "
"development/debugging.",
str(self.model_class_overrides),
)
for arch, target in pending:
me_models.ModelRegistry.register_model(arch, target)
_REGISTERED_MODEL_CLASS_OVERRIDES.add((arch, target))Every access to ModelConfig.registry registers the pending overrides first (line 1167). A module-level set makes it once per process (line 84 of the file), so a worker registers again in its own registry. vLLM logs a WARNING once. No plugin package is needed, but the target module must be importable in every process.
Deep diveEnvironment variables, flags and logs
| Name | Default | Read at | Effect |
|---|---|---|---|
VLLM_CACHE_ROOT | ~/.cache/vllm or $XDG_CACHE_HOME/vllm | vllm/model_executor/models/registry.py:997 | Root of the modelinfos cache. |
VLLM_PLUGINS | not set (all) | vllm/plugins/__init__.py:63-64 | A list that does not name your model plugin hides the model. |
--model-impl | auto | vllm/engine/arg_utils.py:1057 | Lookup order (deep dive above). |
--model-class-overrides | {} | vllm/engine/arg_utils.py:1029-1031 | Development hook. |
VLLM_LOGGING_LEVEL=DEBUG | INFO | vllm/envs.py:860 | Shows cache hits, misses and overwrites. |
What can go wrong#
- "failed to be inspected". The subprocess failed, for example because the module raises on import. vLLM wraps the child's stderr in a
RuntimeError(vllm/model_executor/models/registry.py:1596-1602), logs it, and then raises theValueError(vllm/model_executor/models/registry.py:1141-1150,vllm/model_executor/models/registry.py:1208-1212). Read the log above the error. - Two plugins register the same name. The last call wins, with only a DEBUG log (
vllm/model_executor/models/registry.py:1179-1185). The order of the plugin calls is the order ofentry_points(). - A stale cache after an edit of an imported file. See the deep dive on caches. The flags can be wrong until the model file changes or you delete the cache.
- An old-style constructor. A class with
__init__(self, config, ...)works only through argument guessing, with aDeprecationWarning(vllm/model_executor/model_loader/utils.py:105-117). - The class is not generative. If the class has no
embed_input_idsor nocompute_logits,ModelConfigraises "This model does not support--runner generate" (vllm/config/model.py:727-728).
Why it matters. A model plugin is the usual way to serve a new architecture without a fork of vLLM. The registry decides the class in each process, and the cached model info decides which features start-up allows. Most model-plugin failures show up as one of the four registry messages.
Check yourself#
- 1What is the difference betweeneasy
register_model("A", MyModel)andregister_model("A", "pkg.mod:MyModel")?Show answer
The class form makes a
_RegisteredModeland computes the model info at once, in the calling process. The string form makes a_LazyRegisteredModel; vLLM imports the module only when a process needs the class (vllm/model_executor/models/registry.py:1187-1195). - 2Your plugin registersmedium
"LlamaForCausalLM". What happens to the built-in Llama class?Show answer
The plugin's entry replaces it.
register_modeloverwrites an existing name and logs only at DEBUG level (vllm/model_executor/models/registry.py:1179-1185). The general plugins load beforeModelConfiginspects the model, so the lookup finds the new entry. - 3The server starts with the message "Model architectures ['MyArch'] are not supported for now". Name a likely cause.medium
Show answer
The name is not in the registry. The plugin did not load (for example,
VLLM_PLUGINSdoes not name it), or it does not register this name. A registered name whose inspection fails gives "failed to be inspected" instead (vllm/model_executor/models/registry.py:1205-1236). - 4hard
MyModelinmy_pkg/model.pyinheritsMyBasefrommy_pkg/mixins.py. You addSupportsPPtoMyBaseand change onlymixins.py. The registration is"my_pkg.model:MyModel". Why can--pipeline-parallel-size 2still fail?Show answer
The cache key is the hash of
my_pkg/model.pyonly (vllm/model_executor/models/registry.py:1004-1020). The hash did not change, so the front end reads the oldsupports_pp=falsefrommodelinfos(vllm/model_executor/models/registry.py:1088-1098).ModelConfigthen raisesNotImplementedError(vllm/config/model.py:1569-1576). Delete the cache file, or changemodel.pytoo. (Derived from the hash code.)