vLLM Explained

Platform plugins and current_platform

IntermediatevLLM conceptsabout 10 min read + 4 min deep dives

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

In one sentence

Each vLLM process makes one Platform object on first use. Built-in and out-of-tree detectors decide its class, and an out-of-tree platform wins.

The idea#

vLLM runs on NVIDIA and AMD GPUs, on CPUs, on Intel XPUs and on other hardware. Much of its code must ask: which device do I run on, which attention backend can I use, which communicator do I use for collectives? vLLM puts all these answers into one object, the platform. Code reads it as current_platform. Its class is a subclass of Platform, for example CudaPlatform.

vLLM must choose the class before it can use it. A platform detector is a small function that checks for one kind of hardware. It returns the dotted name of a Platform subclass when the hardware is there, and None when it is not. vLLM has five built-in detectors. A hardware vendor adds a sixth one as a plugin in the group vllm.platform_plugins. When exactly one out-of-tree detector answers, it wins over the built-in ones.

The choice happens late, on the first read of current_platform, not when Python imports vllm.platforms. The reason: an out-of-tree platform must import the Platform base class from vLLM before vLLM chooses a platform. Each process of vllm serve makes this choice again, because each process has its own Python objects.

An everyday comparison. Think of a universal remote control. The first time that you press a button, it tests which television is in the room. Then it uses the codes for that television for as long as it runs. A vendor can add a code card for a new television, and a code card always wins over the built-in codes. The comparison stops here. If two code cards answer, vLLM stops with an error. And each process of vLLM is a separate remote control that tests the room again.

The picture#

Figure: the first read of current_platform runs the detection once per process (panel 1). The out-of-tree count is checked first (panel 2). Three example machines (panel 3).
Open full size ↗

Read panel 1 from left to right: this is the code path of one process. Panel 2 is the decision after all detectors have run. Note the order: the code looks at the out-of-tree detectors first, so the built-in count does not matter when one out-of-tree detector is active.

In the code#

Step 1: current_platform is made on first use#

vllm/platforms/__init__.py has no variable named current_platform. It has a module-level __getattr__ function. Since Python 3.7 (PEP 562), Python calls such a function when code reads a name that the module does not have:

_current_platform = None
_init_trace: str = ""

if TYPE_CHECKING:
    current_platform: 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
    elif name in globals():
        return globals()[name]
    else:
        raise AttributeError(f"No attribute named '{name}' exists in {__name__}.")
The comment (lines 306-315) gives the two reasons for the lazy load. Lines 317-321 run once per process: detect, import the class, make one object.
  1. Line 296. _current_platform starts as None in each process.
  2. Lines 299-300. The name exists only for type checkers. At run time it is not a module attribute, so the import from vllm.platforms import current_platform calls __getattr__.
  3. Line 318. The detection returns a dotted class name, for example "vllm.platforms.cuda.CudaPlatform".
  4. Line 319. resolve_obj_by_qualname imports the module and gets the class (vllm/utils/import_utils.py:119-123). The () makes the one object.
  5. Lines 320-321. _init_trace keeps the stack of the first read. A test prints it when a platform resolves too early (tests/plugins_tests/test_platform_plugins.py:28-34).

The comment gives the reason for the late choice. An out-of-tree platform needs from vllm.platforms import Platform to define its subclass. If the import of vllm.platforms chose the platform, the plugin could not load first. A test checks that import vllm alone does not resolve the platform (tests/plugins_tests/test_platform_plugins.py:37-48).

Step 2: resolve_current_platform_cls_qualname, part 1#

The first part handles the CPU target and runs every detector:

def resolve_current_platform_cls_qualname() -> str:
    # An explicit CPU target is authoritative. Native CPU-only CI jobs reuse
    # an accelerator wheel and can run on accelerator hosts, so probing every
    # plugin would otherwise activate both CPU and the host accelerator.
    if envs.VLLM_TARGET_DEVICE == "cpu":
        cpu_platform_cls_qualname = cpu_platform_plugin()
        assert cpu_platform_cls_qualname is not None
        logger.debug("Explicitly selected CPU platform.")
        return cpu_platform_cls_qualname

    platform_plugins = load_plugins_by_group(PLATFORM_PLUGINS_GROUP)

    activated_plugins = []

    for name, func in chain(builtin_platform_plugins.items(), platform_plugins.items()):
        try:
            assert callable(func)
            platform_cls_qualname = func()
            if platform_cls_qualname is not None:
                activated_plugins.append(name)
        except Exception:
            logger.debug(
                "Platform plugin %s failed during detection.",
                name,
                exc_info=True,
            )
  1. Lines 244-248. If VLLM_TARGET_DEVICE is cpu, the function returns the CPU class at once. It does not load the plugin group and does not run any other detector. The comment says why: CPU-only test jobs run on hosts with accelerators. The default value is cuda (vllm/envs.py:622).
  2. Line 250. load_plugins_by_group imports the detectors of vllm.platform_plugins. VLLM_PLUGINS filters them, as for the other groups (vllm/plugins/__init__.py:63-64).
  3. Line 254. The loop runs the five built-in detectors first, then the out-of-tree ones.
  4. Lines 257-259. A detector is active when it returns a name, not None.
  5. Lines 260-265. An exception in a detector is logged at DEBUG level, and the detector counts as not active.

Step 3: resolve_current_platform_cls_qualname, part 2#

    activated_builtin_plugins = list(
        set(activated_plugins) & set(builtin_platform_plugins.keys())
    )
    activated_oot_plugins = list(set(activated_plugins) & set(platform_plugins.keys()))

    if len(activated_oot_plugins) >= 2:
        raise RuntimeError(
            "Only one platform plugin can be activated, but got: "
            f"{activated_oot_plugins}"
        )
    elif len(activated_oot_plugins) == 1:
        platform_cls_qualname = platform_plugins[activated_oot_plugins[0]]()
        logger.info("Platform plugin %s is activated", activated_oot_plugins[0])
    elif len(activated_builtin_plugins) >= 2:
        raise RuntimeError(
            "Only one platform plugin can be activated, but got: "
            f"{activated_builtin_plugins}"
        )
    elif len(activated_builtin_plugins) == 1:
        platform_cls_qualname = builtin_platform_plugins[activated_builtin_plugins[0]]()
        logger.debug(
            "Automatically detected platform %s.", activated_builtin_plugins[0]
        )
    else:
        platform_cls_qualname = "vllm.platforms.interface.UnspecifiedPlatform"
        logger.debug("No platform detected, vLLM is running on UnspecifiedPlatform")
    return platform_cls_qualname
  1. Lines 267-270. The active names are split into built-in and out-of-tree. The split uses sets, so the order of the names in the error text can vary.
  2. Lines 272-276. Two or more active out-of-tree detectors: RuntimeError.
  3. Lines 277-279. Exactly one: vLLM calls the detector again to get the name, and logs "Platform plugin ... is activated" at INFO level. The built-in detectors do not matter here.
  4. Lines 280-284. No out-of-tree detector and two or more built-in ones: RuntimeError.
  5. Lines 285-289. Exactly one built-in detector: it is called again, with a DEBUG log.
  6. Lines 290-292. No active detector: UnspecifiedPlatform, with a DEBUG log only.

UnspecifiedPlatform has device_type = "" (vllm/platforms/interface.py:1493-1495). Most methods of the base class raise NotImplementedError or return a default, so start-up fails later with a less clear error.

Step 4: the five built-in detectors#

The dict builtin_platform_plugins maps a name to each detector (vllm/platforms/__init__.py:231-237). Each detector checks for its hardware without starting a device context:

NameIt returns a class whenClassLines
tpuVLLM_TPU_USING_PATHWAYS is true, or import libtpu workstpu_inference.platforms.tpu_platform.TpuPlatform, or vllm.platforms.tpu.TpuPlatformvllm/platforms/__init__.py:33-54
cudaNVML counts at least one GPU and the vLLM version does not contain "cpu"; or the host is a Jetsonvllm.platforms.cuda.CudaPlatformvllm/platforms/__init__.py:57-105
rocmamdsmi finds at least one GPU; under WSL also through PyTorchvllm.platforms.rocm.RocmPlatformvllm/platforms/__init__.py:108-140
xputorch.xpu.is_available()vllm.platforms.xpu.XPUPlatformvllm/platforms/__init__.py:143-162
cpuVLLM_TARGET_DEVICE=cpu, a vLLM version with "cpu", or macOSZenCpuPlatform on AMD Zen with zentorch, else CpuPlatformvllm/platforms/__init__.py:174-228

Even the built-in TPU platform lives in another package. vllm/platforms/tpu.py only imports TpuPlatform from tpu_inference, and logs an error if that package is missing (vllm/platforms/tpu.py:9-20).

Step 5: the platform comes before the general plugins#

In every process of vllm serve, the platform resolves before the first general plugin runs. A module-level from vllm.platforms import current_platform in the import chain triggers __getattr__ early, for example in vllm/engine/arg_utils.py (vllm/engine/arg_utils.py:122) and in vllm/config/compilation.py (vllm/config/compilation.py:22). The page How vLLM finds and loads plugins traces the chain for the front end, the engine core and the workers. So a general plugin cannot change which platform vLLM detects. A platform package that needs general setup publishes a second entry point (Step 6).

Step 6: what a platform plugin package looks like#

vLLM's test platform is the smallest complete example. Its setup.py publishes two entry points:

setup(
    name="vllm_add_dummy_platform",
    version="0.1",
    packages=["vllm_add_dummy_platform"],
    entry_points={
        "vllm.platform_plugins": [
            "dummy_platform_plugin = vllm_add_dummy_platform:dummy_platform_plugin"  # noqa
        ],
        "vllm.general_plugins": [
            "dummy_custom_ops = vllm_add_dummy_platform:register_ops"
        ],
    },
)
One detector in vllm.platform_plugins, and one function in vllm.general_plugins that registers custom ops in each process.

The detector returns the dotted name of the class. This test detector always answers, because it runs only in a test environment:

def dummy_platform_plugin() -> str | None:
    # Real platform plugins often import vLLM modules while registering.
    from vllm.utils.torch_utils import direct_register_custom_op  # noqa: F401

    return "vllm_add_dummy_platform.dummy_platform.DummyPlatform"


def register_ops():
    import vllm_add_dummy_platform.dummy_custom_ops  # noqa

The class sets the identity fields and overrides two hooks. _enum = PlatformEnum.OOT makes is_out_of_tree() true (vllm/platforms/interface.py:218-219):

class DummyPlatform(Platform):
    _enum = PlatformEnum.OOT
    device_name = "DummyDevice"
    device_type: str = "privateuseone"
    dispatch_key: str = "PrivateUse1"

    @classmethod
    def check_and_update_config(cls, vllm_config: VllmConfig) -> None:
        vllm_config.compilation_config.custom_ops = ["all"]

    @classmethod
    def get_attn_backend_cls(
        cls,
        selected_backend: "AttentionBackendEnum",
        attn_selector_config: "AttentionSelectorConfig",
        num_heads: int | None = None,
    ) -> str:
        return "vllm_add_dummy_platform.dummy_attention_backend.DummyAttentionBackend"  # noqa E501

A real detector checks for its device and returns None when the device is missing. The design doc says the same (docs/design/plugin_system.md:50). The page The Platform interface explains which methods a real platform must override, for example check_and_update_config, which must set worker_cls.

Step 7: out-of-tree platforms named in the repository#

  • tpu_inference. The TPU detector returns a class from the external tpu_inference package when JAX_PLATFORMS contains "proxy" (Pathways) (vllm/platforms/__init__.py:36-39, vllm/envs.py:1594-1596). Without Pathways, vllm/platforms/tpu.py re-exports the class of that package (vllm/platforms/tpu.py:9-14).
  • Other hardware plugins. The docs list "Google TPUs, Intel Gaudi, IBM Spyre, Huawei Ascend, Rebellions NPU, Apple Silicon, MetaX GPU, and more" as hardware plugins (docs/README.md:64). The code of these plugins is not in this repository, so this site does not describe them.

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

The detection algorithm as numbered steps

For one process, the first read of current_platform does this (vllm/platforms/__init__.py:240-322):

  1. If _current_platform is not None, return it. Stop.
  2. If envs.VLLM_TARGET_DEVICE == "cpu" (the value is lower-cased, vllm/envs.py:622), run cpu_platform_plugin(). It must return a name (an assert). Go to step 8.
  3. Load vllm.platform_plugins through load_plugins_by_group: filter by VLLM_PLUGINS, import each object, log and skip an import error (vllm/plugins/__init__.py:36-74).
  4. For each built-in name, then each plugin name: assert that the object is callable, and call it. Mark it active if the result is not None. Catch any exception, log it at DEBUG level and go on.
  5. Count the active out-of-tree names. Two or more: raise RuntimeError. One: call it again and use the result.
  6. Else count the active built-in names. Two or more: raise RuntimeError. One: call it again and use the result.
  7. Else use "vllm.platforms.interface.UnspecifiedPlatform".
  8. Import the class from the dotted name, call it with no arguments, keep the object in _current_platform, and save the stack in _init_trace.

Worked example. A host has 8 NVIDIA GPUs and the packages acme_hw (a made-up platform plugin) and vLLM. VLLM_PLUGINS is not set. Step 4 calls six detectors: tpu (None, no libtpu), cuda (active, NVML counts 8), rocm (None), xpu (None), cpu (None), acme_hw (active). Step 5 finds one out-of-tree name, calls acme_hw a second time and logs "Platform plugin acme_hw is activated". The CUDA answer is ignored. With VLLM_PLUGINS=acme_admin, step 3 filters out acme_hw, and the process uses CudaPlatform with no warning.

Deep dive

Edge cases: detectors run more than once, and can fail

  • Every detector runs, even when an out-of-tree platform wins. The loop calls all of them before the decision (line 254). So the CUDA detector calls NVML on a host where your plugin wins. A detector must be cheap and must not start a device context.
  • The winner runs twice. Line 278 (out-of-tree) or line 286 (built-in) calls the winning detector again, outside the try block. A detector must return the same answer each time, and an exception in the second call is not caught.
  • Detectors can have side effects. The XPU detector sets XPUPlatform.dist_backend = "xccl" when XCCL is available, even when XPU is not the result (vllm/platforms/__init__.py:149-154).
  • The CUDA detector raises some errors. It raises again any exception whose class name does not contain "nvml" (vllm/platforms/__init__.py:85-89). The loop catches it (line 260), so CUDA is then "not active" with only a DEBUG log. One such error: the vLLM package metadata is missing, so vllm_version_matches_substr raises PackageNotFoundError after a warning (vllm/platforms/__init__.py:18-30).
  • Jetson. When NVML fails with an NVML error, the CUDA detector checks two Tegra files and still answers CUDA on a Jetson (vllm/platforms/__init__.py:91-101).
  • Only cpu is a short-circuit. VLLM_TARGET_DEVICE=rocm does not skip detection: the function checks only for "cpu" (line 244).
Deep dive

One object per process, and how to replace it

_current_platform is a module global, so each Python process has its own (vllm/platforms/__init__.py:296). With the spawn start method that vllm serve uses, each child runs the detection again (vllm/entrypoints/common/startup.py:147-149). A child inherits the environment variables of its parent, so it normally gets the same answer. (Derived.)

The module also defines a function named __setattr__ (vllm/platforms/__init__.py:329-336). Python does not call a module-level __setattr__: PEP 562 adds only __getattr__ and __dir__ for modules. So vllm.platforms.current_platform = x makes a normal module attribute. Later reads find that attribute and do not call __getattr__. _current_platform keeps the old object, and modules that imported the old object keep it. vLLM does such an assignment once: for vllm bench, when the platform is UnspecifiedPlatform (vllm/entrypoints/cli/main.py:80-83). (Derived from Python semantics; the code has no comment about it.)

The methods of Platform are mostly class methods. An attribute that the class does not have goes to Platform.__getattr__, which forwards to torch.<device_type> (vllm/platforms/interface.py:1300-1319). The page The Platform interface explains this fallback.

Deep dive

Environment variables and logs

VariableDefaultRead atEffect
VLLM_TARGET_DEVICEcuda (vllm/envs.py:622)vllm/platforms/__init__.py:244, vllm/platforms/__init__.py:176cpu: CPU platform, no detection, no platform plugins.
VLLM_PLUGINSnot set (load all)vllm/plugins/__init__.py:40A list that does not name your platform plugin hides it.
VLLM_TPU_USING_PATHWAYStrue when JAX_PLATFORMS contains "proxy" (vllm/envs.py:1594-1596)vllm/platforms/__init__.py:37The TPU detector answers with the tpu_inference class.
VLLM_LOGGING_LEVELINFOvllm/envs.py:860Set DEBUG to see each detector's result and the reason when a detector fails.

At the default INFO level, the log shows "Platform plugin ... is activated" only for an out-of-tree winner (line 279). A built-in winner and UnspecifiedPlatform are logged at DEBUG level (lines 287-292).

What can go wrong#

Why it matters. The platform decides the attention backend, the communicator, the worker class and many defaults. A wrong platform gives errors that look like model or kernel bugs. Check the platform first: look for the "activated" line, or log at DEBUG level.

Check yourself#

  1. 1
    When does vLLM choose the platform of a process?
    easy
    Show answer

    On the first read of vllm.platforms.current_platform in that process. The module __getattr__ runs the detection when _current_platform is still None (vllm/platforms/__init__.py:303-319).

  2. 2
    Why is the choice lazy, and not made at import vllm.platforms?
    easy
    Show answer

    An out-of-tree platform plugin needs from vllm.platforms import Platform to define its class. The platform can only be chosen after the plugins load. The comment at vllm/platforms/__init__.py:306-315 gives this reason.

  3. 3
    A host has an NVIDIA GPU and two platform plugin packages. Both detectors return a class name. What happens?
    medium
    Show answer

    Start-up stops with RuntimeError: two or more active out-of-tree detectors are not allowed (vllm/platforms/__init__.py:272-276). The active CUDA detector does not change this, because the out-of-tree count is checked first.

  4. 4
    You set VLLM_TARGET_DEVICE=cpu on a GPU host that has your platform plugin. Is your detector called?
    medium
    Show answer

    No. The function returns the CPU class before it loads vllm.platform_plugins (vllm/platforms/__init__.py:244-250).

  5. 5
    Your detector opens a device handle each time that it runs, and it is the only active out-of-tree detector. With vllm serve and TP = 2, how many times does it run? Ignore the model registry subprocess.
    hard
    Show answer

    Eight times. In each process it runs twice: once in the loop (line 257) and once more to get the name (line 278) (vllm/platforms/__init__.py:254-279). Four processes detect: the front end, the engine core and the 2 workers. Each is a spawned process with its own _current_platform (vllm/entrypoints/common/startup.py:147-149). So 2 × 4 = 8 calls. (Derived from the lines above.)