Platform plugins and current_platform
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#
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__}.")- Line 296.
_current_platformstarts asNonein each process. - 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_platformcalls__getattr__. - Line 318. The detection returns a dotted class name, for example
"vllm.platforms.cuda.CudaPlatform". - Line 319.
resolve_obj_by_qualnameimports the module and gets the class (vllm/utils/import_utils.py:119-123). The()makes the one object. - Lines 320-321.
_init_tracekeeps 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,
)- Lines 244-248. If
VLLM_TARGET_DEVICEiscpu, 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 iscuda(vllm/envs.py:622). - Line 250.
load_plugins_by_groupimports the detectors ofvllm.platform_plugins.VLLM_PLUGINSfilters them, as for the other groups (vllm/plugins/__init__.py:63-64). - Line 254. The loop runs the five built-in detectors first, then the out-of-tree ones.
- Lines 257-259. A detector is active when it returns a name, not
None. - 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- 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.
- Lines 272-276. Two or more active out-of-tree detectors:
RuntimeError. - 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.
- Lines 280-284. No out-of-tree detector and two or more built-in ones:
RuntimeError. - Lines 285-289. Exactly one built-in detector: it is called again, with a DEBUG log.
- 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:
| Name | It returns a class when | Class | Lines |
|---|---|---|---|
tpu | VLLM_TPU_USING_PATHWAYS is true, or import libtpu works | tpu_inference.platforms.tpu_platform.TpuPlatform, or vllm.platforms.tpu.TpuPlatform | vllm/platforms/__init__.py:33-54 |
cuda | NVML counts at least one GPU and the vLLM version does not contain "cpu"; or the host is a Jetson | vllm.platforms.cuda.CudaPlatform | vllm/platforms/__init__.py:57-105 |
rocm | amdsmi finds at least one GPU; under WSL also through PyTorch | vllm.platforms.rocm.RocmPlatform | vllm/platforms/__init__.py:108-140 |
xpu | torch.xpu.is_available() | vllm.platforms.xpu.XPUPlatform | vllm/platforms/__init__.py:143-162 |
cpu | VLLM_TARGET_DEVICE=cpu, a vLLM version with "cpu", or macOS | ZenCpuPlatform on AMD Zen with zentorch, else CpuPlatform | vllm/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"
],
},
)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 # noqaThe 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 E501A 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_inferencepackage whenJAX_PLATFORMScontains "proxy" (Pathways) (vllm/platforms/__init__.py:36-39,vllm/envs.py:1594-1596). Without Pathways,vllm/platforms/tpu.pyre-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 diveThe detection algorithm as numbered steps
For one process, the first read of current_platform does this (vllm/platforms/__init__.py:240-322):
- If
_current_platformis notNone, return it. Stop. - If
envs.VLLM_TARGET_DEVICE == "cpu"(the value is lower-cased,vllm/envs.py:622), runcpu_platform_plugin(). It must return a name (anassert). Go to step 8. - Load
vllm.platform_pluginsthroughload_plugins_by_group: filter byVLLM_PLUGINS, import each object, log and skip an import error (vllm/plugins/__init__.py:36-74). - 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. - Count the active out-of-tree names. Two or more: raise
RuntimeError. One: call it again and use the result. - Else count the active built-in names. Two or more: raise
RuntimeError. One: call it again and use the result. - Else use
"vllm.platforms.interface.UnspecifiedPlatform". - 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 diveEdge 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
tryblock. 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, sovllm_version_matches_substrraisesPackageNotFoundErrorafter 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
cpuis a short-circuit.VLLM_TARGET_DEVICE=rocmdoes not skip detection: the function checks only for"cpu"(line 244).
Deep diveOne 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 diveEnvironment variables and logs
| Variable | Default | Read at | Effect |
|---|---|---|---|
VLLM_TARGET_DEVICE | cuda (vllm/envs.py:622) | vllm/platforms/__init__.py:244, vllm/platforms/__init__.py:176 | cpu: CPU platform, no detection, no platform plugins. |
VLLM_PLUGINS | not set (load all) | vllm/plugins/__init__.py:40 | A list that does not name your platform plugin hides it. |
VLLM_TPU_USING_PATHWAYS | true when JAX_PLATFORMS contains "proxy" (vllm/envs.py:1594-1596) | vllm/platforms/__init__.py:37 | The TPU detector answers with the tpu_inference class. |
VLLM_LOGGING_LEVEL | INFO | vllm/envs.py:860 | Set 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#
- Two platform plugins are installed. If both detectors answer, start-up stops with "Only one platform plugin can be activated" (
vllm/platforms/__init__.py:272-276). Uninstall one, or make its detector returnNone. - Your detector raises. vLLM logs it only at DEBUG level and uses another platform (
vllm/platforms/__init__.py:260-265). Run withVLLM_LOGGING_LEVEL=DEBUGto see the traceback. - The platform resolves too early. If code reads
current_platformduringimport vllm, the detection runs the platform plugins against a half-imported vLLM. A test checks this (tests/plugins_tests/test_platform_plugins.py:37-48). Another test prints_init_traceto find the first read (tests/plugins_tests/test_platform_plugins.py:28-34). - No platform is found.
UnspecifiedPlatformhas an emptydevice_type(vllm/platforms/interface.py:1493-1495). The first device call then fails far from the cause. - libtpu without tpu_inference. The TPU detector answers
vllm.platforms.tpu.TpuPlatform, but that module defines the name only whentpu_inferenceimports (vllm/platforms/tpu.py:9-20). The import of the class then fails. (Derived.)
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#
- 1When does vLLM choose the platform of a process?easy
Show answer
On the first read of
vllm.platforms.current_platformin that process. The module__getattr__runs the detection when_current_platformis stillNone(vllm/platforms/__init__.py:303-319). - 2Why is the choice lazy, and not made ateasy
import vllm.platforms?Show answer
An out-of-tree platform plugin needs
from vllm.platforms import Platformto define its class. The platform can only be chosen after the plugins load. The comment atvllm/platforms/__init__.py:306-315gives this reason. - 3A 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. - 4You setmedium
VLLM_TARGET_DEVICE=cpuon a GPU host that has your platform plugin. Is your detector called?Show answer
No. The function returns the CPU class before it loads
vllm.platform_plugins(vllm/platforms/__init__.py:244-250). - 5Your detector opens a device handle each time that it runs, and it is the only active out-of-tree detector. Withhard
vllm serveand TP = 2, how many times does it run? Ignore the model registry subprocess.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.)