vLLM Explained

plugins/__init__.py: the plugin loaders

IntermediateCode mapabout 13 min read + 7 min deep dives

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

Purpose#

vllm/plugins/__init__.py finds the plugins that other Python packages publish as entry points, filters them with VLLM_PLUGINS, and imports them for the code that asks.

Filevllm/plugins/__init__.py, 159 lines of Python. Five group-name constants, one flag and three functions.
Main functionsload_plugins_by_group (vllm/plugins/__init__.py:36), load_general_plugins (vllm/plugins/__init__.py:77), load_endpoint_plugins (vllm/plugins/__init__.py:93). There is no class.
Runs inEvery process of vllm serve: the API server (the front end), the EngineCore process, each worker process, and the model registry subprocess (only when it starts). With one GPU, the worker runs inside the engine core process. The code runs on the CPU, in the thread of the caller.
Called byNine call sites in vllm/: the engine arguments, EngineCore, the worker wrapper, the model registry, the platform detection, the stat loggers, the IO processor loader and the endpoint plugin wiring (see below).
Callsimportlib.metadata.entry_points, envs.VLLM_PLUGINS, EntryPoint.load(), and the loaded plugin functions or factories.
Not in this fileThe platform detection rules (vllm/platforms/__init__.py), the stat logger type check (vllm/v1/metrics/loggers.py), the IO processor choice (vllm/plugins/io_processors/__init__.py), the route wiring of endpoint plugins (vllm/plugins/endpoint_plugins/interface.py), and the group vllm.logits_processors.

The file only finds, filters and imports. Each caller decides what to do with the loaded objects. The page How vLLM finds and loads plugins explains the same code from the view of the processes. This page reads the file line by line.

Where it sits#

Figure: who calls each of the three functions, and what each one calls (panel 1); how each function treats VLLM_PLUGINS and errors (panel 2).
Open full size ↗

Read panel 1 row by row. Green is the path of the general plugins, blue is the shared loader, and pink is the path of the endpoint plugins. Grey boxes are code outside the plugin loaders. Panel 2 is a short form of the walk-through below.

Who calls it#

load_general_plugins() has five callers in vllm/:

load_plugins_by_group() has three callers outside this file. Each one imports the group constant from this file:

load_endpoint_plugins() has one caller: attach_endpoint_plugins() (vllm/plugins/endpoint_plugins/interface.py:106-108). build_app calls that function after the core routes (vllm/entrypoints/launchers/app.py:45-51).

Tests also call the loaders. For example, a test resets the flag plugins_loaded by hand, so that load_general_plugins() runs again (tests/v1/kv_connector/unit/test_scheduler_kv_connector_override.py:113-119).

What it calls#

The subfolders of vllm/plugins/#

Three subfolders hold plugin code that vLLM itself ships. The page Plugins in the API server explains how to write such plugins.

Key classes and functions#

NameLineWhat it does
DEFAULT_PLUGINS_GROUPvllm/plugins/__init__.py:18"vllm.general_plugins". The comment says: loaded in all processes.
IO_PROCESSOR_PLUGINS_GROUPvllm/plugins/__init__.py:20"vllm.io_processor_plugins". The comment says: loaded in "process0" only, which is the front end.
PLATFORM_PLUGINS_GROUPvllm/plugins/__init__.py:23"vllm.platform_plugins". Loaded in all processes, on the first read of current_platform. Not loaded with VLLM_TARGET_DEVICE=cpu.
STAT_LOGGER_PLUGINS_GROUPvllm/plugins/__init__.py:26"vllm.stat_logger_plugins". Loaded in the front end, with the async engine.
ENDPOINT_PLUGINS_GROUPvllm/plugins/__init__.py:30"vllm.endpoint_plugins". Loaded in the API server only. Each entry point gives a factory that returns an EndpointPlugin.
plugins_loadedvllm/plugins/__init__.py:33Module-level flag. It makes sure that one process loads the general plugins only once.
load_plugins_by_group(group)vllm/plugins/__init__.py:36-74Finds the entry points of one group, filters them by name, imports each one. Returns dict[str, Callable].
load_general_plugins()vllm/plugins/__init__.py:77-90Loads vllm.general_plugins once per process and calls each function.
load_endpoint_plugins(supported_tasks)vllm/plugins/__init__.py:93-159Loads endpoint plugins only when VLLM_PLUGINS is set. Calls each factory and keeps the plugins whose tasks match.

Walk through the main methods#

load_plugins_by_group: find, filter, import#

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 entry_points is inside the function, so it runs only on a call.
  2. Line 40. envs.VLLM_PLUGINS is None when the variable is not set, else the value split at commas (vllm/envs.py:1163-1167).
  3. Lines 42-45. entry_points(group=group) returns the entry points of all installed packages in the group. No entry points: a DEBUG log and an empty dict.
  4. Lines 48-50. The log level is DEBUG for vllm.general_plugins and INFO for every other group. The comment on line 47 does not match the code: the check is only for the group name.
  5. Lines 52-60. The function logs each found plugin as name -> value. When the allowlist is not set, it also logs that it loads all plugins.
  6. Lines 63-66. A plugin passes when there is no allowlist, or when its entry-point name is in the list. The test is an exact string match. With an allowlist, line 66 logs "Loading plugin" for each name that passes. A name that does not pass gets no such line.
  7. Lines 68-72. plugin.load() imports the module and gets the attribute. An exception is logged with its traceback, and the loop continues.
  8. Line 74. The result maps the entry-point name to the loaded object. The function does not call the objects.

load_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()
  1. Lines 78-80. The docstring warns that plugins can load many times, in different processes. So a plugin function must be safe to run again.
  2. Lines 82-85. The guard. The first call sets the module global; each later call in the same process returns at once.
  3. Line 87. The shared loader gets the functions of vllm.general_plugins, filtered by VLLM_PLUGINS.
  4. Lines 89-90. Each function runs with no arguments and no try block. An exception goes up to the caller, for example EngineCore.__init__.

load_endpoint_plugins: opt-in only#

The docstring gives the reason for a stricter rule: endpoint plugins add HTTP routes, so a plugin loads only when VLLM_PLUGINS names it (vllm/plugins/__init__.py:98-103).

    from importlib.metadata import entry_points

    if envs.VLLM_PLUGINS is None:
        discovered = entry_points(group=ENDPOINT_PLUGINS_GROUP)
        if discovered:
            logger.warning(
                "Found endpoint plugin(s) %s but VLLM_PLUGINS is not set. "
                "Endpoint plugins add HTTP routes and must be explicitly "
                "allowlisted via VLLM_PLUGINS to be loaded.",
                [p.name for p in discovered],
            )
        return []

    factories = load_plugins_by_group(ENDPOINT_PLUGINS_GROUP)

    endpoint_plugins: list[EndpointPlugin] = []
    for name, factory in factories.items():
        try:
            plugin = factory()
        except Exception:
            logger.exception("Failed to instantiate endpoint plugin %s", name)
            continue

        required_tasks = plugin.required_tasks
        if required_tasks is not None and (
            supported_tasks is None or not set(required_tasks) & set(supported_tasks)
        ):
            logger.info(
                "Skipping endpoint plugin %s: requires one of tasks %s, "
                "server supports %s",
                name,
                required_tasks,
                supported_tasks,
            )
            continue

        logger.info("Loaded endpoint plugin %s", name)
        endpoint_plugins.append(plugin)

    return endpoint_plugins
  1. Lines 122-131. VLLM_PLUGINS not set: the function still lists the group. If it finds plugins, it logs a WARNING with their names. Then it returns an empty list. So an endpoint plugin never loads by accident.
  2. Line 133. VLLM_PLUGINS set: the shared loader imports the named entry points. An empty string gives [""], which matches no name (vllm/plugins/__init__.py:105-109).
  3. Lines 136-141. Each factory runs with no arguments. If it raises, the function logs the error and skips the plugin.
  4. Lines 143-154. The task gate. required_tasks = None means "always eligible". Otherwise, the plugin needs at least one task in common with supported_tasks. If the caller gives supported_tasks = None, every plugin with tasks is skipped. A skip writes an INFO log.
  5. Lines 156-159. The function logs each loaded plugin and returns them in the order of discovery.

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

Data flow: types in and out

ValueTypeWhere
groupstr, one of the five constantsargument of load_plugins_by_group (vllm/plugins/__init__.py:36)
envs.VLLM_PLUGINSlist[str] | Nonedeclared at vllm/envs.py:116, parsed at vllm/envs.py:1163-1167
discovered_pluginsimportlib.metadata.EntryPoints; each item has .name, .value and .load()vllm/plugins/__init__.py:42
return of load_plugins_by_groupdict[str, Callable[[], Any]]: entry-point name to the loaded objectvllm/plugins/__init__.py:62, vllm/plugins/__init__.py:74
supported_taskstuple[SupportedTask, ...] | None; a task is for example "generate"vllm/plugins/__init__.py:94, vllm/tasks.py:7, vllm/tasks.py:43
return of load_endpoint_pluginslist[EndpointPlugin], in discovery ordervllm/plugins/__init__.py:95, vllm/plugins/__init__.py:159

The "callable" in the type hint of line 36 is only true for some groups. The table shows what the object behind each entry point must be, from the code of each caller:

GroupThe loaded objectWhat the caller does with it
vllm.general_pluginsa function with no argumentscalls it (vllm/plugins/__init__.py:89-90)
vllm.platform_pluginsa function that returns a class name or Nonecalls it to detect the platform (vllm/platforms/__init__.py:254-259)
vllm.stat_logger_pluginsa subclass of StatLoggerBase (a class, not a function)checks the type and keeps the class (vllm/v1/metrics/loggers.py:87-96)
vllm.io_processor_pluginsa function that returns a class name or Nonecalls it, then resolves the requested class (vllm/plugins/io_processors/__init__.py:63-88)
vllm.endpoint_pluginsa factory with no arguments (a class or a function)calls it to get an EndpointPlugin (vllm/plugins/__init__.py:138, vllm/plugins/endpoint_plugins/interface.py:53-56)
Deep dive

Processes and threads

The file has no thread and no lock. Each function runs in the thread of its caller. plugins_loaded is a global of the module, so each Python process has its own copy (vllm/plugins/__init__.py:33).

ProcessFunctions that run thereCall site
API server (front end)load_general_plugins (the second call returns at once), platform loading, stat loggers, endpoint plugins, IO processors (pooling only)vllm/engine/arg_utils.py:3243, vllm/engine/arg_utils.py:924, vllm/platforms/__init__.py:250, vllm/v1/engine/async_llm.py:156, vllm/entrypoints/launchers/app.py:51, vllm/entrypoints/pooling/pooling/io_processor.py:51
Engine core processload_general_plugins, platform loadingvllm/v1/engine/core.py:132, vllm/platforms/__init__.py:250
Worker processload_general_plugins, platform loadingvllm/v1/worker/worker_base.py:284, vllm/platforms/__init__.py:250
Model registry subprocessload_general_plugins, platform loading (through the module-level import of vllm.config)vllm/model_executor/models/registry.py:1612, vllm/model_executor/models/registry.py:26

With one GPU, the executor is uni (vllm/config/parallel.py:1099-1100), and init_worker runs inside the engine core process (vllm/v1/executor/uniproc_executor.py:68). There, the flag is already True, so line 284 of worker_base.py does nothing. How spawn and fork change the flag in a child process is on How vLLM finds and loads plugins.

The platform comes first. In the processes of vllm serve, the platform plugins load before the general plugins. A module-level from vllm.platforms import current_platform calls the module __getattr__ and resolves the platform at import time (vllm/platforms/__init__.py:303-319). The package vllm.config has such an import: it imports vllm.config.compilation (vllm/config/__init__.py:7), which has the import (vllm/config/compilation.py:22). Each process imports vllm.config or vllm.platforms before it calls load_general_plugins():

So a general plugin cannot change which platform vLLM detects. (Derived from the imports above.)

envs.VLLM_PLUGINS is read again on each access until enable_envs_cache() runs. After that, the value is cached (vllm/envs.py:2291-2298, vllm/envs.py:2308-2326). Only the engine core and the multiproc workers call enable_envs_cache() (vllm/v1/engine/core.py:243, vllm/v1/executor/multiproc_executor.py:710). The front end never calls it. The docstring says that the cache starts "after service initialization" (vllm/envs.py:2313-2315). So change VLLM_PLUGINS only before you start vLLM.

Deep dive

A worked example: three plugins and two settings of VLLM_PLUGINS

Assume this machine has vLLM and two more packages. Package acme_hw publishes acme = acme_hw:detect in vllm.platform_plugins. Package acme_routes publishes acme_admin = acme_routes:make in vllm.endpoint_plugins, with required_tasks = None. vLLM publishes lora_filesystem_resolver and lora_hf_hub_resolver in vllm.general_plugins (pyproject.toml:45-47). The names acme... are examples, not real packages.

Case 1: VLLM_PLUGINS is not set.

  1. General plugins: both vLLM functions run (line 64 passes every name). The filesystem resolver registers only if VLLM_LORA_RESOLVER_CACHE_DIR is set (vllm/plugins/lora_resolvers/filesystem_resolver.py:51-59). The Hugging Face resolver checks the allowlist itself: VLLM_PLUGINS is None, so it does not register. If VLLM_LORA_RESOLVER_HF_REPO_LIST is set, it logs a warning (vllm/plugins/lora_resolvers/hf_hub_resolver.py:131-142).
  2. Platform plugins: acme loads, and the platform code calls detect() (vllm/platforms/__init__.py:254-259).
  3. Endpoint plugins: none load. The log shows a WARNING with ['acme_admin'] (vllm/plugins/__init__.py:122-131).

Case 2: VLLM_PLUGINS=acme,acme_admin. The parsed value is ["acme", "acme_admin"].

  1. General plugins: line 64 rejects both vLLM names, so load_plugins_by_group returns {}, and no LoRA resolver registers. No error and no warning appears. The only log lines for this group are at DEBUG level (lines 50-54), and the default log level is INFO (vllm/envs.py:860).
  2. Platform plugins: acme loads, as before.
  3. Endpoint plugins: acme_admin loads, make() runs, and with required_tasks = None the plugin is kept. The log shows "Loaded endpoint plugin acme_admin" (vllm/plugins/__init__.py:156).

To keep the filesystem LoRA resolver in case 2, add its name: VLLM_PLUGINS=acme,acme_admin,lora_filesystem_resolver. (Derived from lines 63-64 and vllm/envs.py:1166. The code does not strip spaces, so do not put a space after a comma.)

Deep dive

Error paths: what raises, what only logs

SituationResultWhere
No entry points in the groupDEBUG log, empty resultvllm/plugins/__init__.py:43-45
The import of a plugin fails (load() raises)ERROR log with traceback ("Failed to load plugin ..."); the plugin is skippedvllm/plugins/__init__.py:68-72
A general plugin function raisesnot caught here; the caller gets the exception, and start-up of that process stops. The engine core process logs "EngineCore failed to start." and raises again (vllm/v1/engine/core.py:1570-1576). A worker process logs "WorkerProc failed to start." (vllm/v1/executor/multiproc_executor.py:950-957).vllm/plugins/__init__.py:89-90
Endpoint plugins exist, VLLM_PLUGINS not setWARNING log; no plugin loadsvllm/plugins/__init__.py:122-131
An endpoint factory raisesERROR log with traceback; the plugin is skippedvllm/plugins/__init__.py:137-141
Endpoint tasks do not matchINFO log; the plugin is skippedvllm/plugins/__init__.py:143-154
A stat logger plugin is not a StatLoggerBase subclassTypeError (in the caller)vllm/v1/metrics/loggers.py:88-94
Two out-of-tree platform plugins are activeRuntimeError (in the caller)vllm/platforms/__init__.py:272-276
The model needs an IO processor that is not installedValueError (in the caller)vllm/plugins/io_processors/__init__.py:73-84
VLLM_LORA_RESOLVER_CACHE_DIR is set, but it is not a folderValueError from the general plugin, so start-up stopsvllm/plugins/lora_resolvers/filesystem_resolver.py:52-57
Deep dive

Environment variables

VariableDefaultRead atEffect
VLLM_PLUGINSnot set, which gives None (vllm/envs.py:1163-1167)vllm/plugins/__init__.py:40, vllm/plugins/__init__.py:122One comma-separated allowlist for every group of this file. It does not filter vllm.logits_processors, which does not use this file. Not set: load all, except endpoint plugins. "": load none. The comment above the definition gives the first and the last rule, but not the endpoint exception (vllm/envs.py:1160-1162).
VLLM_LORA_RESOLVER_CACHE_DIRNone (vllm/envs.py:1171-1172)vllm/plugins/lora_resolvers/filesystem_resolver.py:51Folder in which the filesystem resolver looks for LoRA adapters.
VLLM_LORA_RESOLVER_HF_REPO_LISTNone (vllm/envs.py:1178-1179)vllm/plugins/lora_resolvers/hf_hub_resolver.py:131Comma-separated Hugging Face repositories for the Hub resolver. Works only when VLLM_PLUGINS names lora_hf_hub_resolver.

The tests show the edge cases of VLLM_PLUGINS for endpoint plugins: not set gives no plugin, and an empty string gives no plugin (tests/plugins_tests/test_endpoint_plugins.py:101-115).

Check yourself#

  1. 1
    Which function in this file calls the plugin functions, and which function only imports them?
    easy
    Show answer

    load_general_plugins() calls each function (vllm/plugins/__init__.py:89-90). load_plugins_by_group() only imports the objects and returns a dict (vllm/plugins/__init__.py:68-74). The caller decides what to do with them.

  2. 2
    You install an endpoint plugin and do not set VLLM_PLUGINS. Does the route appear?
    easy
    Show answer

    No. load_endpoint_plugins() returns an empty list and logs a warning with the plugin names (vllm/plugins/__init__.py:122-131). Endpoint plugins load only when VLLM_PLUGINS names them.

  3. 3
    A general plugin function raises KeyError. A test catches the exception and calls load_general_plugins() again in the same process. Do the other general plugins run?
    medium
    Show answer

    No. Line 85 sets plugins_loaded = True before the loop, so the second call returns at once (vllm/plugins/__init__.py:83-85). The functions after the failed one never run in that process. To load again, code must reset the flag by hand, as a test does (tests/v1/kv_connector/unit/test_scheduler_kv_connector_override.py:113-119).

  4. 4
    An endpoint plugin has required_tasks = ("embed",). The server supports only ("generate",). What happens, and at which log level?
    medium
    Show answer

    The factory runs, but the sets {"embed"} and {"generate"} have nothing in common. So the function skips the plugin with an INFO log (vllm/plugins/__init__.py:143-154). A test checks the same rule with the two tasks swapped (tests/plugins_tests/test_endpoint_plugins.py:129-140).

  5. 5
    You set VLLM_PLUGINS=acme,acme_admin to enable your platform and endpoint plugins, and VLLM_LORA_RESOLVER_CACHE_DIR=/loras. Requests for an unknown adapter in /loras now fail. Why, and what is the fix?
    hard
    Show answer

    VLLM_PLUGINS is one allowlist for every group that goes through load_plugins_by_group(), the general plugins too. The name lora_filesystem_resolver is not in it, so line 64 skips vLLM's own general plugin, and register_filesystem_resolver never runs (vllm/plugins/__init__.py:63-64, pyproject.toml:46). No resolver registers, and nothing logs an error. Add the name: VLLM_PLUGINS=acme,acme_admin,lora_filesystem_resolver. This is the worked example in the deep dive.