vLLM Explained

How vLLM finds and loads plugins

IntermediatevLLM conceptsabout 14 min read + 8 min deep dives

Code version: vLLM @ 3e44c0d · 2026-10-22

In one sentence

vLLM finds plugins as Python entry points in named groups. Each vLLM process loads only the groups that it needs, when it needs them.

The idea#

A plugin is code in another Python package that changes vLLM. For example, a plugin can add a model architecture, a hardware platform or a new HTTP route. vLLM does not import your package by its name. Your package publishes an entry point in its metadata, under a plugin group such as vllm.general_plugins. vLLM asks Python which installed packages publish a name in that group, and then imports the object that the name points to. The page Python entry points explains entry points in general.

vllm serve runs as several processes. The front end runs the HTTP server. The EngineCore process runs the scheduler. Each worker process owns one GPU. With tensor parallelism, one model runs on several GPUs, so there are several workers (Parallelism basics).

Python objects live in one process. When a plugin registers a model in the front end, the registry in a worker does not change. So each process must load the plugins itself. But not every process needs every group. Only the front end needs HTTP routes, and only the workers run the model. So vLLM loads each group in the processes that use it, at the moment that it needs it.

An everyday comparison. Think of a company with three offices: sales, planning and the factory. Each office has its own copy of the rule book. When a new rule comes, each office must add it to its own copy. The sales office reads the rules for customers. The factory does not need them. The comparison stops here. In vLLM, each process reads the same central list: the installed entry points. No process sends the rules to the next one.

Three rules control the loading:

  1. One allowlist for five groups. The environment variable VLLM_PLUGINS lists the plugin names to load. If it is not set, vLLM loads every plugin that it finds, but not the endpoint plugins. The group vllm.logits_processors does not use this list (Step 7).
  2. Once per process. General plugins run at most once in each process. A module-level flag stops the second load.
  3. A broken import is skipped. A broken plugin function stops start-up. vLLM logs and skips an entry point that fails to import. It does not catch an error that a general plugin function raises.

The picture#

Figure: the groups that each process of vllm serve loads (panel 1), what one load does (panel 2), and two special cases (panel 3).
Open full size ↗

Read panel 1 column by column. Green groups load in every process. Blue groups load only in the front end. The violet group loads in the front end and in the workers. Panel 2 shows the shared loader. Panel 3 shows that a new Python process always loads the general plugins again, and a second call in the same process does nothing.

In the code#

Step 1: the plugin groups#

Five group names are constants in vllm/plugins/__init__.py. Their comments say where each group loads:

# Default plugins group will be loaded in all processes(process0, engine core
# process and worker processes)
DEFAULT_PLUGINS_GROUP = "vllm.general_plugins"
# IO processor plugins group will be loaded in process0 only
IO_PROCESSOR_PLUGINS_GROUP = "vllm.io_processor_plugins"
# Platform plugins group will be loaded in all processes when
# `vllm.platforms.current_platform` is called and the value not initialized,
PLATFORM_PLUGINS_GROUP = "vllm.platform_plugins"
# Stat logger plugins group will be loaded in process0 only when serve vLLM with
# async mode.
STAT_LOGGER_PLUGINS_GROUP = "vllm.stat_logger_plugins"
# Endpoint plugins group is loaded in the API server front end process only.
# Each entry point resolves to a factory returning an `EndpointPlugin`
# (see `vllm/plugins/endpoint_plugins/interface.py`).
ENDPOINT_PLUGINS_GROUP = "vllm.endpoint_plugins"

# make sure one process only loads plugins once
plugins_loaded = False
The five groups, and the flag plugins_loaded for the once-per-process rule.

A sixth group, vllm.logits_processors, is defined in the sampler code (vllm/v1/sample/logits_processor/__init__.py:48). The Model Runner V2 loader defines the same name again (vllm/v1/worker/gpu/sample/logits_processor/loader.py:31-34). vLLM itself publishes two general plugins, the two LoRA resolvers:

[project.entry-points."vllm.general_plugins"]
lora_filesystem_resolver = "vllm.plugins.lora_resolvers.filesystem_resolver:register_filesystem_resolver"
lora_hf_hub_resolver = "vllm.plugins.lora_resolvers.hf_hub_resolver:register_hf_hub_resolver"

The table shows what the object behind each entry point must be, and who loads the group.

GroupThe entry point givesLoaded byProcess
vllm.general_pluginsa function with no arguments; vLLM calls itload_general_plugins() (vllm/plugins/__init__.py:77-90)every process
vllm.platform_pluginsa function that returns a platform class name, or Noneresolve_current_platform_cls_qualname() (vllm/platforms/__init__.py:250)every process, at the first use of current_platform (not when VLLM_TARGET_DEVICE=cpu, Step 6)
vllm.stat_logger_pluginsa subclass of StatLoggerBaseload_stat_logger_plugin_factories() (vllm/v1/metrics/loggers.py:84-98), called in AsyncLLM.__init__ (vllm/v1/engine/async_llm.py:155-156)front end
vllm.endpoint_pluginsa factory that returns an EndpointPluginload_endpoint_plugins() (vllm/plugins/__init__.py:93-159), called by attach_endpoint_plugins() (vllm/plugins/endpoint_plugins/interface.py:108), which build_app calls (vllm/entrypoints/launchers/app.py:51)front end (API server)
vllm.io_processor_pluginsa function that returns an IO processor class name, or Noneget_io_processor() (vllm/plugins/io_processors/__init__.py:59-71)front end, pooling models only
vllm.logits_processorsa LogitsProcessor subclass_load_v2_logitsprocs_plugins() (vllm/v1/worker/gpu/sample/logits_processor/loader.py:37-74), with Model Runner V2front end (validation) and workers

Step 2: one loader for five groups#

Five groups use one function, load_plugins_by_group(). It finds the entry points of one group, filters them by name, and imports them:

def load_plugins_by_group(group: str) -> dict[str, Callable[[], Any]]:
    """Load plugins registered under the given entry point group."""
    from importlib.metadata import entry_points

    allowed_plugins = envs.VLLM_PLUGINS

    discovered_plugins = entry_points(group=group)
    if len(discovered_plugins) == 0:
        logger.debug("No plugins for group %s found.", group)
        return {}

    # Check if the only discovered plugin is the default one
    is_default_group = group == DEFAULT_PLUGINS_GROUP
    # Use INFO for non-default groups and DEBUG for the default group
    log_level = logger.debug if is_default_group else logger.info

    log_level("Available plugins for group %s:", group)
    for plugin in discovered_plugins:
        log_level("- %s -> %s", plugin.name, plugin.value)

    if allowed_plugins is None:
        log_level(
            "All plugins in this group will be loaded. "
            "Set `VLLM_PLUGINS` to control which plugins to load."
        )

    plugins = dict[str, Callable[[], Any]]()
    for plugin in discovered_plugins:
        if allowed_plugins is None or plugin.name in allowed_plugins:
            if allowed_plugins is not None:
                log_level("Loading plugin %s", plugin.name)

            try:
                func = plugin.load()
                plugins[plugin.name] = func
            except Exception:
                logger.exception("Failed to load plugin %s", plugin.name)

    return plugins
Line 42 asks Python for the entry points. Line 64 applies the allowlist. Line 69 imports the object. Lines 71-72 log an import error and continue.

The function returns a dict from plugin name to the loaded object (vllm/plugins/__init__.py:62). It does not call the objects. The caller decides what to do: load_general_plugins() calls each function, and the platform code calls each detector function (vllm/platforms/__init__.py:254-257).

Step 3: the allowlist VLLM_PLUGINS#

    # a list of plugin names to load, separated by commas.
    # if this is not set, it means all plugins will be loaded
    # if this is set to an empty string, no plugins will be loaded
    "VLLM_PLUGINS": lambda: (
        None
        if "VLLM_PLUGINS" not in os.environ
        else os.environ["VLLM_PLUGINS"].split(",")
    ),
Not set: None, which means "load all". Set: a list of names, split at commas.

Line 64 of the loader compares plugin.name with the list. This is the name on the left side of the entry point, for example lora_filesystem_resolver in pyproject.toml. It is not the name of the Python package.

Step 4: general plugins, once per process#

def load_general_plugins():
    """WARNING: plugins can be loaded for multiple times in different
    processes. They should be designed in a way that they can be loaded
    multiple times without causing issues.
    """
    global plugins_loaded
    if plugins_loaded:
        return
    plugins_loaded = True

    plugins = load_plugins_by_group(group=DEFAULT_PLUGINS_GROUP)
    # general plugins, we only need to execute the loaded functions
    for func in plugins.values():
        func()

The module-level flag plugins_loaded makes a second call in the same process return at once (lines 83-84). The function sets the flag before it calls any plugin function (line 85). Then it calls each function, with no try block (lines 89-90). The docstring warns that a plugin can load several times, in different processes (lines 78-80). So a plugin function must be safe to run again. vLLM's own test plugin checks the registry before it registers a model:

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)

Step 5: which process loads what, and when#

Five call sites run load_general_plugins(), in four kinds of process: the front end (two calls), the engine core, the worker and the model registry subprocess.

Front end. The CLI builds the parsers of all subcommands before it reads your flags (vllm/entrypoints/cli/main.py:100-110). The serve parser calls make_arg_parser (vllm/entrypoints/cli/serve.py:172), which calls AsyncEngineArgs.add_cli_args (vllm/entrypoints/common/cli_args.py:466). That function loads the general plugins first:

    def add_cli_args(
        parser: FlexibleArgumentParser, async_args_only: bool = False
    ) -> FlexibleArgumentParser:
        # Initialize plugin to update the parser, for example, The plugin may
        # add a new kind of quantization method to --quantization argument or
        # a new device to --device argument.
        load_general_plugins()
The comment gives the reason: a plugin can add a new choice to --quantization or --device before the parser reads the flags.

Later, EngineArgs.__post_init__ calls load_general_plugins() again (vllm/engine/arg_utils.py:921-924). In the front end of vllm serve, this second call returns at once because of the flag. It is still useful: the offline LLM class makes an EngineArgs object (vllm/entrypoints/llm.py:295) and does not build a CLI parser.

Engine core process. EngineCore.__init__ loads the general plugins before it makes the executor and the scheduler:

        # plugins need to be loaded at the engine/scheduler level too
        from vllm.plugins import load_general_plugins

        load_general_plugins()

The executor comes later, at vllm/v1/engine/core.py:147. So the plugins are ready before any worker starts.

Worker. Each worker loads them in WorkerWrapperBase.init_worker, before it makes the worker class:

    def init_worker(self, all_kwargs: list[dict[str, Any]]) -> None:
        """Here we inject some common logic before initializing the worker.
        Arguments are passed to the worker class constructor.
        """
        kwargs = all_kwargs[self.rpc_rank]

        vllm_config: VllmConfig | None = kwargs.get("vllm_config")
        assert vllm_config is not None, (
            "vllm_config is required to initialize the worker"
        )
        self.vllm_config = vllm_config

        vllm_config.enable_trace_function_call_for_thread()

        from vllm.plugins import load_general_plugins

        load_general_plugins()

        # Let the platform replace core Triton kernels (e.g. CPU fallback
        # implementations) before any worker or model code can launch them.
        from vllm.platforms import current_platform

        current_platform.register_triton_kernel_overrides()
After the plugins, the platform can replace Triton kernels (line 290). Then the code resolves worker_cls, which a platform plugin may set.

A worker process calls init_worker from WorkerProc.__init__ (vllm/v1/executor/multiproc_executor.py:672). With one GPU, the default executor is uni (vllm/config/parallel.py:1099-1100). It calls init_worker inside the engine core process (vllm/v1/executor/uniproc_executor.py:68). There, the flag is already set, so nothing loads twice.

Model registry subprocess. Every built-in model is a lazy registry entry (vllm/model_executor/models/registry.py:1565-1573). An out-of-tree model is lazy too when you register it as a string "module:Class" (vllm/model_executor/models/registry.py:1187-1193). To read the capabilities of a lazy model without an import in the main process, vLLM may run a new Python process:

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))
The subprocess loads the general plugins first. Then it runs the function that the parent sent.

This happens only on a cache miss (vllm/model_executor/models/registry.py:1086-1109). A miss is a missing cache file, or a hash that does not match the model's source file. vLLM also skips the cache when it cannot find the source file. The command is python -m vllm.model_executor.models.registry (vllm/model_executor/models/registry.py:823). The page Out-of-tree models and the model registry explains this cache.

Step 6: platform plugins load on first use#

Platform plugins do not use load_general_plugins(). They load the first time that code in a process reads vllm.platforms.current_platform:

def __getattr__(name: str):
    if name == "current_platform":
        # lazy init current_platform.
        # 1. out-of-tree platform plugins need `from vllm.platforms import
        #    Platform` so that they can inherit `Platform` class. Therefore,
        #    we cannot resolve `current_platform` during the import of
        #    `vllm.platforms`.
        # 2. when users use out-of-tree platform plugins, they might run
        #    `import vllm`, some vllm internal code might access
        #    `current_platform` during the import, and we need to make sure
        #    `current_platform` is only resolved after the plugins are loaded
        #    (we have tests for this, if any developer violate this, they will
        #    see the test failures).
        global _current_platform
        if _current_platform is None:
            platform_cls_qualname = resolve_current_platform_cls_qualname()
            _current_platform = resolve_obj_by_qualname(platform_cls_qualname)()
            global _init_trace
            _init_trace = "".join(traceback.format_stack())
        return _current_platform
The comment (lines 306-315) gives two reasons for the lazy load. One reason: an out-of-tree platform must import Platform before vLLM chooses the platform.

resolve_current_platform_cls_qualname() then loads the group vllm.platform_plugins (vllm/platforms/__init__.py:250). It calls each detector function, and one activated out-of-tree platform wins over the built-in platforms (vllm/platforms/__init__.py:277-279). If VLLM_TARGET_DEVICE is cpu, the function returns the CPU platform before it loads this group (vllm/platforms/__init__.py:244-248). The default value is cuda (vllm/envs.py:622). The page Platform plugins and current_platform explains the detection order.

Step 7: groups that only the front end loads#

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 dive

load_plugins_by_group(), line by line

def load_plugins_by_group(group: str) -> dict[str, Callable[[], Any]]:
    """Load plugins registered under the given entry point group."""
    from importlib.metadata import entry_points

    allowed_plugins = envs.VLLM_PLUGINS

    discovered_plugins = entry_points(group=group)
    if len(discovered_plugins) == 0:
        logger.debug("No plugins for group %s found.", group)
        return {}

    # Check if the only discovered plugin is the default one
    is_default_group = group == DEFAULT_PLUGINS_GROUP
    # Use INFO for non-default groups and DEBUG for the default group
    log_level = logger.debug if is_default_group else logger.info

    log_level("Available plugins for group %s:", group)
    for plugin in discovered_plugins:
        log_level("- %s -> %s", plugin.name, plugin.value)

    if allowed_plugins is None:
        log_level(
            "All plugins in this group will be loaded. "
            "Set `VLLM_PLUGINS` to control which plugins to load."
        )

    plugins = dict[str, Callable[[], Any]]()
    for plugin in discovered_plugins:
        if allowed_plugins is None or plugin.name in allowed_plugins:
            if allowed_plugins is not None:
                log_level("Loading plugin %s", plugin.name)

            try:
                func = plugin.load()
                plugins[plugin.name] = func
            except Exception:
                logger.exception("Failed to load plugin %s", plugin.name)

    return plugins
  1. Line 38. The import of importlib.metadata.entry_points is inside the function, so it runs only on a call.
  2. Line 40. envs.VLLM_PLUGINS reads the environment variable on each call (vllm/envs.py:2297-2298). After enable_envs_cache(), the value is cached (vllm/envs.py:2308-2326). The engine core and the workers call it at the end of their start-up (vllm/v1/engine/core.py:243, vllm/v1/executor/multiproc_executor.py:710).
  3. Lines 42-45. entry_points(group=group) returns the entry points of all installed packages in that group. If there are none, the function returns {} with a debug log.
  4. Lines 48-60. The log level is DEBUG for vllm.general_plugins and INFO for the other groups. The default log level is INFO (vllm/envs.py:860). So by default, the log lists the platform, stat logger and IO processor plugins that it finds, but not the general plugins.
  5. Lines 63-64. An entry point passes when VLLM_PLUGINS is None, or when its name is in the list. The check is an exact string match.
  6. Lines 68-72. plugin.load() imports the module and gets the attribute, for example vllm_add_dummy_model:register. Any Exception is logged with its traceback, and the loop continues with the next entry point.
  7. Line 74. The result is a dict[str, Callable] from the entry-point name to the object. The order is the order of entry_points().

The function has no guard. Each call reads the metadata and imports the objects again. Python caches imported modules, so a second load() of the same module does not run the module code again. The guard of load_general_plugins() is about the plugin functions, which have side effects.

Deep dive

VLLM_PLUGINS: values and edge cases

The value comes from os.environ["VLLM_PLUGINS"].split(",") (vllm/envs.py:1163-1167). The comment above it says: "if this is set to an empty string, no plugins will be loaded" (vllm/envs.py:1160-1162).

ValueParsedEffect in load_plugins_by_group()
not setNoneLoads every plugin in the group. Endpoint plugins are the exception: none load (vllm/plugins/__init__.py:122-131).
""[""]No name matches, so no plugin loads. A test checks this for endpoint plugins (tests/plugins_tests/test_endpoint_plugins.py:107-115).
register_dummy_model["register_dummy_model"]Only this name loads, in every group that uses the list.
a, b (with a space)["a", " b"]The code does not strip spaces, so " b" does not match a plugin named b. (Derived from lines 1166 and 64; there is no test for it.)

Extra rules in single plugins.

  • The built-in Hugging Face Hub LoRA resolver registers itself only when VLLM_LORA_RESOLVER_HF_REPO_LIST is set and VLLM_PLUGINS names lora_hf_hub_resolver. When VLLM_LORA_RESOLVER_HF_REPO_LIST is set but the name is missing, it logs a warning, because the resolver downloads files (vllm/plugins/lora_resolvers/hf_hub_resolver.py:129-145).
  • An endpoint plugin also needs a matching task. If its required_tasks does not meet the tasks of the server, vLLM skips it with an INFO log (vllm/plugins/__init__.py:143-154).
  • vllm.logits_processors ignores VLLM_PLUGINS (previous section).
Deep dive

Error handling: what is logged and what stops start-up

Each group handles errors in its own way. "Logged" means that start-up continues without the plugin.

GroupThe import (load()) failsThe plugin object fails or is wrong
vllm.general_pluginslogged, skipped (vllm/plugins/__init__.py:71-72)not caught by the loader: the exception ends the process (vllm/plugins/__init__.py:89-90)
vllm.platform_pluginslogged, skipped (same lines)an error in the detector is logged at DEBUG, and the plugin counts as not active (vllm/platforms/__init__.py:255-265). Two active out-of-tree platforms raise RuntimeError (vllm/platforms/__init__.py:272-276).
vllm.stat_logger_pluginslogged, skippednot a StatLoggerBase subclass: TypeError (vllm/v1/metrics/loggers.py:88-94)
vllm.endpoint_pluginslogged, skippedthe factory raises: logged, skipped (vllm/plugins/__init__.py:137-141)
vllm.io_processor_pluginslogged, skippedthe function raises: warning, skipped. No usable plugin, or not the requested one: ValueError (vllm/plugins/io_processors/__init__.py:64-84)
vllm.logits_processorsRuntimeError (vllm/v1/worker/gpu/sample/logits_processor/loader.py:62-66)not a V2 LogitsProcessor: ValueError (vllm/v1/worker/gpu/sample/logits_processor/loader.py:67-72)

The guard and a failed plugin. load_general_plugins() sets plugins_loaded = True before it calls the functions (vllm/plugins/__init__.py:85). If a function raises and some code catches the exception, a later call in the same process returns at once. The plugin functions after the failed one then never run in that process. (Derived from lines 82-90.) In the normal start-up, the process logs the exception and ends. The engine core process logs "EngineCore failed to start." and raises the exception again (vllm/v1/engine/core.py:1570-1576). A worker process logs "WorkerProc failed to start." (vllm/v1/executor/multiproc_executor.py:950-957).

The platform detector runs twice. When exactly one out-of-tree platform is active, line 278 calls its function again to get the class name, outside the try block (vllm/platforms/__init__.py:277-279). So a detector must give the same result on each call.

Deep dive

fork, spawn and the once-per-process guard

plugins_loaded is a module global (vllm/plugins/__init__.py:33). What a child process sees depends on how the parent starts it:

  • spawn: the child is a new Python interpreter. It imports vllm.plugins again, so the flag is False and the registries are empty. The child loads the plugins itself.
  • fork: the child gets a copy of the parent's memory. If the parent made them, the copy has the flag, the filled registries and the platform object. Then a call to load_general_plugins() in the child returns at once. (Derived from the fork semantics of the operating system and lines 82-85.)

Which method does vllm serve use?

  1. The CLI calls cli_env_setup() (vllm/entrypoints/cli/main.py:67-68). It sets VLLM_WORKER_MULTIPROC_METHOD=spawn if you did not set the variable (vllm/entrypoints/common/startup.py:147-149).
  2. The default of the variable is fork (vllm/envs.py:938-940). This matters when you use vLLM as a library.
  3. get_mp_context() calls _maybe_force_spawn() (vllm/utils/system_utils.py:185-200). That function forces spawn, for example when CUDA is already initialized or under WSL (vllm/utils/system_utils.py:145-162).
  4. Both the engine core processes (vllm/v1/engine/utils.py:256) and the worker processes (vllm/v1/executor/multiproc_executor.py:723) use this context.

So with vllm serve and the default settings, each engine core and each worker is a spawned child, and it loads the general plugins again. fork applies only when you set VLLM_WORKER_MULTIPROC_METHOD=fork yourself, or when you use vLLM as a library. On a machine with 1 front end, 1 engine core and 4 workers (TP=4), a general plugin function runs 6 times. It runs once more in each model registry subprocess, which runs only on a cache miss (Step 5). (Derived from the call sites in Step 5.)

The model registry subprocess is always a new interpreter. It starts with subprocess.run and sys.executable, not with multiprocessing (vllm/model_executor/models/registry.py:1591-1593).

Deep dive

Platform plugins and general plugins: which loads first?

In every process of vllm serve, the platform resolves before the first general plugin runs. The cause is a module-level from vllm.platforms import current_platform. The name is not a real module attribute, so this import calls the module __getattr__ (PEP 562), and that resolves the platform (vllm/platforms/__init__.py:299-319). (Derived from the imports below.)

So a general plugin cannot change which platform vLLM detects. A platform package that also needs general setup publishes two entry points. vLLM's test platform does this: one in vllm.platform_plugins and one in vllm.general_plugins that registers custom ops (tests/plugins/vllm_add_dummy_platform/setup.py:10-17).

What can go wrong#

  • A plugin is missing, with no error. If the import of a plugin fails, vLLM writes "Failed to load plugin ..." with a traceback, and continues (vllm/plugins/__init__.py:71-72). Look for this line in the log of each process.
  • Start-up stops in one process. An exception in a general plugin function stops the process that runs it (vllm/plugins/__init__.py:89-90). vLLM's own filesystem LoRA resolver raises ValueError when VLLM_LORA_RESOLVER_CACHE_DIR is set to a path that is not a folder (vllm/plugins/lora_resolvers/filesystem_resolver.py:51-57).
  • Side effects run many times. A plugin function runs in each process (previous deep dive). Write it so that it can run again: check before you register, as the test plugin does.
  • A second registration replaces the first. ModelRegistry.register_model overwrites an architecture that is already registered, with only a DEBUG log (vllm/model_executor/models/registry.py:1179-1185).
  • CUDA starts too early. If a plugin imports model code that touches CUDA, the front end can initialize CUDA. Register models as "module:Class" strings. The docstring names the error that this avoids: "Cannot re-initialize CUDA in forked subprocess" (vllm/model_executor/models/registry.py:1167-1174).
  • The docs say "every process". The design doc says that every process created by vLLM needs to load the plugin (docs/design/plugin_system.md:7). In the code, this is true for general and platform plugins only. Three groups load only in the front end (Step 7).

Why it matters. Every extension of vLLM starts with this loading step. To debug a plugin, you must know which process loads it and when. A model plugin that loads in the front end but fails in a worker gives an error only in the worker log. An allowlist that is too short gives no error at all.

Check yourself#

  1. 1
    Which environment variable selects the plugins that vLLM loads, and what happens when it is not set?
    easy
    Show answer

    VLLM_PLUGINS. When it is not set, its value is None and vLLM loads every plugin that it finds (vllm/envs.py:1163-1167, vllm/plugins/__init__.py:63-64). Endpoint plugins are the exception: they load only when VLLM_PLUGINS is set and names them.

  2. 2
    A package publishes an entry point in vllm.stat_logger_plugins. In which process does vLLM load it?
    easy
    Show answer

    Only in the front end, in AsyncLLM.__init__ (vllm/v1/engine/async_llm.py:156). The engine core and the workers do not load this group.

  3. 3
    The front end calls load_general_plugins() in add_cli_args and again in EngineArgs.__post_init__. How often do the plugin functions run in the front end of vllm serve?
    medium
    Show answer

    Once. The first call sets plugins_loaded = True, and the second call returns at once (vllm/plugins/__init__.py:82-85). The call in __post_init__ exists for code paths that do not build a CLI parser, such as the offline LLM class.

  4. 4
    A general plugin has a bug: its module raises ImportError. A second general plugin raises KeyError inside its function. What happens to each?
    medium
    Show answer

    The first one is skipped with a log line "Failed to load plugin ...", because load() is inside try (vllm/plugins/__init__.py:68-72). The second one stops start-up of the process, because func() is not inside try (vllm/plugins/__init__.py:89-90).

  5. 5
    You run vllm serve with TP=2 and the default settings. How many times does a general plugin function run, and why not only once? Ignore the model registry subprocess.
    hard
    Show answer

    Four times: once in the front end, once in the engine core process, and once in each of the 2 worker processes. The CLI sets the start method to spawn (vllm/entrypoints/common/startup.py:147-149). So each child is a new interpreter with plugins_loaded = False, and it loads again in EngineCore.__init__ (vllm/v1/engine/core.py:132) or init_worker (vllm/v1/worker/worker_base.py:284). With one GPU and no DP, the default executor is uni (vllm/config/parallel.py:1099-1100). It runs the worker inside the engine core process, so the count is two.