1295 lines
46 KiB
Python
1295 lines
46 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
import contextlib
|
|
import enum
|
|
import functools
|
|
import os
|
|
import platform
|
|
import sys
|
|
from datetime import timedelta
|
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
|
|
import torch
|
|
|
|
from vllm.logger import init_logger
|
|
from vllm.v1.attention.backends.registry import AttentionBackendEnum
|
|
|
|
if TYPE_CHECKING:
|
|
from torch.distributed import PrefixStore, ProcessGroup
|
|
|
|
from vllm.config import VllmConfig
|
|
from vllm.config.kernel import IrOpPriorityConfig
|
|
from vllm.inputs import EngineInput
|
|
from vllm.pooling_params import PoolingParams
|
|
from vllm.sampling_params import SamplingParams
|
|
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
|
from vllm.v1.attention.backend import AttentionBackend
|
|
from vllm.v1.attention.selector import AttentionSelectorConfig
|
|
else:
|
|
FlexibleArgumentParser = object
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
_assigned_physical_gpu_ids: list[int] | None = None
|
|
|
|
|
|
def set_assigned_physical_gpu_ids(ids: list[int]) -> None:
|
|
"""Set the physical GPU IDs assigned to this worker process.
|
|
Called during worker init so that device_id_to_physical_device_id()
|
|
can map local_rank to the correct physical device without relying
|
|
on CUDA_VISIBLE_DEVICES.
|
|
|
|
Idempotent: a second call with the same value is a no-op.
|
|
Raises RuntimeError if called again with a different value.
|
|
|
|
This is expected to run during single-threaded worker initialization."""
|
|
global _assigned_physical_gpu_ids
|
|
if _assigned_physical_gpu_ids is not None:
|
|
if _assigned_physical_gpu_ids != ids:
|
|
raise RuntimeError(
|
|
f"set_assigned_physical_gpu_ids called with conflicting values: "
|
|
f"existing={_assigned_physical_gpu_ids}, new={ids}"
|
|
)
|
|
return
|
|
_assigned_physical_gpu_ids = ids
|
|
|
|
|
|
def get_assigned_physical_gpu_ids() -> list[int] | None:
|
|
return _assigned_physical_gpu_ids
|
|
|
|
|
|
@functools.cache
|
|
def in_wsl() -> bool:
|
|
# Reference: https://github.com/microsoft/WSL/issues/4071
|
|
return "microsoft" in " ".join(platform.uname()).lower()
|
|
|
|
|
|
class PlatformEnum(enum.Enum):
|
|
"""Enumeration of supported hardware platforms."""
|
|
|
|
CUDA = enum.auto()
|
|
ROCM = enum.auto()
|
|
TPU = enum.auto()
|
|
XPU = enum.auto()
|
|
CPU = enum.auto()
|
|
OOT = enum.auto()
|
|
UNSPECIFIED = enum.auto()
|
|
|
|
|
|
class CpuArchEnum(enum.Enum):
|
|
X86 = enum.auto()
|
|
ARM = enum.auto()
|
|
POWERPC = enum.auto()
|
|
S390X = enum.auto()
|
|
RISCV = enum.auto()
|
|
OTHER = enum.auto()
|
|
UNKNOWN = enum.auto()
|
|
|
|
|
|
class DeviceCapability(NamedTuple):
|
|
major: int
|
|
minor: int
|
|
|
|
def __lt__(self, other: Any) -> bool:
|
|
if not isinstance(other, DeviceCapability):
|
|
return NotImplemented
|
|
return (self.major, self.minor) < (other.major, other.minor)
|
|
|
|
def __le__(self, other: Any) -> bool:
|
|
if not isinstance(other, DeviceCapability):
|
|
return NotImplemented
|
|
return (self.major, self.minor) <= (other.major, other.minor)
|
|
|
|
def __eq__(self, other: Any) -> bool:
|
|
if not isinstance(other, DeviceCapability):
|
|
return NotImplemented
|
|
return (self.major, self.minor) == (other.major, other.minor)
|
|
|
|
def __ge__(self, other: Any) -> bool:
|
|
if not isinstance(other, DeviceCapability):
|
|
return NotImplemented
|
|
return (self.major, self.minor) >= (other.major, other.minor)
|
|
|
|
def __gt__(self, other: Any) -> bool:
|
|
if not isinstance(other, DeviceCapability):
|
|
return NotImplemented
|
|
return (self.major, self.minor) > (other.major, other.minor)
|
|
|
|
def __hash__(self) -> int:
|
|
return hash((self.major, self.minor))
|
|
|
|
def as_version_str(self) -> str:
|
|
return f"{self.major}.{self.minor}"
|
|
|
|
def to_int(self) -> int:
|
|
"""
|
|
Express device capability as an integer `<major><minor>`.
|
|
|
|
It is assumed that the minor version is always a single digit.
|
|
"""
|
|
assert 0 <= self.minor < 10
|
|
return self.major * 10 + self.minor
|
|
|
|
|
|
class Platform:
|
|
_enum: PlatformEnum
|
|
device_name: str
|
|
device_type: str
|
|
|
|
# available dispatch keys:
|
|
# check https://github.com/pytorch/pytorch/blob/313dac6c1ca0fa0cde32477509cce32089f8532a/torchgen/model.py#L134 # noqa
|
|
# use "CPU" as a fallback for platforms not registered in PyTorch
|
|
dispatch_key: str = "CPU"
|
|
|
|
# available ray device keys:
|
|
# https://github.com/ray-project/ray/blob/10ba5adadcc49c60af2c358a33bb943fb491a171/python/ray/_private/ray_constants.py#L438 # noqa
|
|
# empty string means the device does not support ray
|
|
ray_device_key: str = ""
|
|
|
|
# platform-agnostic way to specify the device control environment variable,
|
|
# .e.g. CUDA_VISIBLE_DEVICES for CUDA.
|
|
# hint: search for "get_visible_accelerator_ids_env_var" in
|
|
# https://github.com/ray-project/ray/tree/master/python/ray/_private/accelerators # noqa
|
|
device_control_env_var: str = "VLLM_DEVICE_CONTROL_ENV_VAR_PLACEHOLDER"
|
|
|
|
# environment variables that need to be set to 1 to prevent ray from
|
|
# setting the visible devices e.g.
|
|
# RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES
|
|
ray_noset_device_env_vars: list[str] = []
|
|
|
|
# The torch.compile backend for compiling simple and
|
|
# standalone functions. The default value is "inductor" to keep
|
|
# the same behavior as PyTorch.
|
|
# NOTE: for the forward part of the model, vLLM has another separate
|
|
# compilation strategy.
|
|
simple_compile_backend: str = "inductor"
|
|
|
|
# The backend used for distributed communication.
|
|
dist_backend: str = ""
|
|
|
|
supported_quantization: list[str] = []
|
|
|
|
additional_env_vars: list[str] = []
|
|
|
|
_global_graph_pool: Any | None = None
|
|
|
|
@property
|
|
def pass_key(self) -> str:
|
|
"""Inductor config key for the PassManager custom pass"""
|
|
return "post_grad_custom_post_pass"
|
|
|
|
@property
|
|
def supported_dtypes(self) -> list[torch.dtype]:
|
|
"""Returns the supported dtypes for the current platform."""
|
|
# Be careful with the order of the dtypes. The first dtype will
|
|
# be used as the default dtype fallback for the current platform,
|
|
# when encountering unsupported dtypes in "auto" dtype.
|
|
return [torch.bfloat16, torch.float16, torch.float32]
|
|
|
|
def is_cuda(self) -> bool:
|
|
return self._enum == PlatformEnum.CUDA
|
|
|
|
def is_rocm(self) -> bool:
|
|
return self._enum == PlatformEnum.ROCM
|
|
|
|
def is_tpu(self) -> bool:
|
|
return self._enum == PlatformEnum.TPU
|
|
|
|
def is_xpu(self) -> bool:
|
|
return self._enum == PlatformEnum.XPU
|
|
|
|
def is_cpu(self) -> bool:
|
|
return self._enum == PlatformEnum.CPU
|
|
|
|
def uses_host_device_handling(self) -> bool:
|
|
"""Whether vLLM should leave DeviceConfig.device unset."""
|
|
return self.is_tpu()
|
|
|
|
def is_zen_cpu(self) -> bool:
|
|
return False
|
|
|
|
def is_out_of_tree(self) -> bool:
|
|
return self._enum == PlatformEnum.OOT
|
|
|
|
def is_unspecified(self) -> bool:
|
|
return self._enum == PlatformEnum.UNSPECIFIED
|
|
|
|
def get_max_output_tokens(self, prompt_len: int) -> int:
|
|
return sys.maxsize
|
|
|
|
def is_cuda_alike(self) -> bool:
|
|
"""Stateless version of [torch.cuda.is_available][]."""
|
|
return self._enum in (PlatformEnum.CUDA, PlatformEnum.ROCM)
|
|
|
|
def is_sleep_mode_available(self) -> bool:
|
|
# TODO: Actually only mi3xx has the sleep mode support now
|
|
# for ROCm, but currently we don't have a way to detect the
|
|
# exact GPU model statelessly here. So we return True for
|
|
# all ROCm platforms for now.
|
|
return self._enum in (PlatformEnum.CUDA, PlatformEnum.ROCM, PlatformEnum.XPU)
|
|
|
|
def is_cumem_allocator_available(self) -> bool:
|
|
try:
|
|
from vllm.device_allocator.cumem import cumem_available
|
|
except ImportError:
|
|
return False
|
|
|
|
return cumem_available
|
|
|
|
@classmethod
|
|
def get_pass_manager_cls(cls) -> str:
|
|
"""
|
|
Get the pass manager class for this platform.
|
|
It will be registered as a custom pass under the current_platform.pass_key.
|
|
"""
|
|
return "vllm.compilation.passes.pass_manager.PostGradPassManager"
|
|
|
|
@classmethod
|
|
def get_compile_backend(cls) -> str:
|
|
"""
|
|
Get the custom compile backend for current platform.
|
|
"""
|
|
return cls.simple_compile_backend
|
|
|
|
@classmethod
|
|
def import_ir_kernels(cls) -> None:
|
|
"""
|
|
The default implementation imports ``vllm.kernels``, which registers
|
|
the built-in IR op implementations. Out-of-tree (OOT) platforms should
|
|
override this method to import their own kernel modules.
|
|
"""
|
|
import vllm.kernels # noqa: F401
|
|
|
|
@classmethod
|
|
def device_control_id_to_physical_device_id(cls, device_id: str) -> int:
|
|
"""Map one device-control env entry to an integer physical device ID."""
|
|
try:
|
|
return int(device_id)
|
|
except ValueError as e:
|
|
raise ValueError(
|
|
f"Non-integer device ID {device_id!r} is not supported by "
|
|
f"{cls.device_name}."
|
|
) from e
|
|
|
|
# GPU device IDs can refer to three distinct namespaces:
|
|
# - logical: vLLM-local IDs such as local ranks. These index
|
|
# assigned_physical_gpu_ids when it is set.
|
|
# - visible: torch/CUDA ordinals in the current process after applying
|
|
# the device-control env var, e.g. CUDA_VISIBLE_DEVICES.
|
|
# - physical: global GPU IDs used by topology and management APIs such as
|
|
# NVML, which are not remapped by CUDA_VISIBLE_DEVICES.
|
|
# Keep conversions explicit. In particular, torch device indices are
|
|
# visible IDs, not vLLM logical IDs.
|
|
|
|
@classmethod
|
|
def device_id_to_physical_device_id(cls, device_id: int):
|
|
"""Map a vLLM-local logical device ID to a physical device ID.
|
|
|
|
The input is a logical local ID (e.g. a local rank), NOT a visible
|
|
device ordinal; for the latter use
|
|
visible_device_id_to_physical_device_id(). The two coincide only
|
|
when no logical-to-physical mapping is in effect.
|
|
"""
|
|
if _assigned_physical_gpu_ids is not None:
|
|
if device_id >= len(_assigned_physical_gpu_ids):
|
|
raise IndexError(
|
|
f"device_id {device_id} is out of range for "
|
|
f"assigned_physical_gpu_ids {_assigned_physical_gpu_ids} "
|
|
f"({len(_assigned_physical_gpu_ids)} devices assigned)"
|
|
)
|
|
return _assigned_physical_gpu_ids[device_id]
|
|
# Treat empty device control env var as unset. This is a valid
|
|
# configuration in Ray setups where the engine is launched in
|
|
# a CPU-only placement group located on a GPU node.
|
|
if (
|
|
cls.device_control_env_var in os.environ
|
|
and os.environ[cls.device_control_env_var] != ""
|
|
):
|
|
device_ids = os.environ[cls.device_control_env_var].split(",")
|
|
physical_device_id = device_ids[device_id]
|
|
return cls.device_control_id_to_physical_device_id(physical_device_id)
|
|
else:
|
|
return device_id
|
|
|
|
@classmethod
|
|
def logical_device_id_to_visible_device_id(cls, device_id: int) -> int:
|
|
"""Map a vLLM-local logical device ID to the current process's
|
|
visible accelerator ordinal.
|
|
|
|
vLLM internals use logical local IDs. Physical IDs are used only
|
|
at platform/topology boundaries. This helper performs the final
|
|
translation needed by APIs such as ``torch.device("cuda:N")``.
|
|
"""
|
|
physical_device_id = cls.device_id_to_physical_device_id(device_id)
|
|
device_control_env = os.environ.get(cls.device_control_env_var, "")
|
|
if not device_control_env:
|
|
return physical_device_id
|
|
|
|
visible_physical_device_ids = [
|
|
cls.device_control_id_to_physical_device_id(physical_id)
|
|
for physical_id in device_control_env.split(",")
|
|
]
|
|
if physical_device_id not in visible_physical_device_ids:
|
|
raise RuntimeError(
|
|
f"Physical device {physical_device_id} for logical device "
|
|
f"{device_id} is not visible in {cls.device_control_env_var}="
|
|
f"{device_control_env}"
|
|
)
|
|
return visible_physical_device_ids.index(physical_device_id)
|
|
|
|
@classmethod
|
|
def visible_device_id_to_physical_device_id(cls, device_id: int) -> int:
|
|
"""Map a visible accelerator ordinal (e.g. ``torch.device.index``)
|
|
to a physical device ID.
|
|
|
|
This is the inverse of the env-var translation performed by
|
|
logical_device_id_to_visible_device_id() and is independent of any
|
|
logical-to-physical mapping set via set_assigned_physical_gpu_ids().
|
|
"""
|
|
device_control_env = os.environ.get(cls.device_control_env_var, "")
|
|
if not device_control_env:
|
|
return device_id
|
|
visible_device_ids = device_control_env.split(",")
|
|
if device_id >= len(visible_device_ids):
|
|
raise IndexError(
|
|
f"visible device ordinal {device_id} is out of range for "
|
|
f"{cls.device_control_env_var}={device_control_env}"
|
|
)
|
|
return cls.device_control_id_to_physical_device_id(
|
|
visible_device_ids[device_id]
|
|
)
|
|
|
|
@classmethod
|
|
def import_kernels(cls) -> None:
|
|
"""Import any platform-specific C kernels."""
|
|
try:
|
|
import vllm._C # noqa: F401
|
|
except ImportError as e:
|
|
logger.warning_once("Failed to import from vllm._C: %r", e)
|
|
with contextlib.suppress(ImportError):
|
|
import vllm._moe_C_stable_libtorch # noqa: F401
|
|
|
|
@classmethod
|
|
def get_attn_backend_cls(
|
|
cls,
|
|
selected_backend: "AttentionBackendEnum",
|
|
attn_selector_config: "AttentionSelectorConfig",
|
|
num_heads: int | None = None,
|
|
) -> str:
|
|
"""Get the attention backend class of a device."""
|
|
return ""
|
|
|
|
@classmethod
|
|
def get_supported_vit_attn_backends(cls) -> list["AttentionBackendEnum"]:
|
|
return [
|
|
AttentionBackendEnum.TORCH_SDPA,
|
|
]
|
|
|
|
@classmethod
|
|
def get_vit_attn_backend(
|
|
cls,
|
|
head_size: int,
|
|
dtype: torch.dtype,
|
|
backend: "AttentionBackendEnum | None" = None,
|
|
) -> "AttentionBackendEnum":
|
|
"""
|
|
Get the vision attention backend class of a device.
|
|
|
|
NOTE: ViT Attention should be checked and override in the platform-specific
|
|
implementation. we should not override this in any other places, like
|
|
the model_executor/models/<model_name>.py.
|
|
|
|
We check if the backend is None or not:
|
|
1. If not, check if the backend is supported by the platform.
|
|
2. If None, continue to the default selection logic.
|
|
"""
|
|
if backend is not None:
|
|
assert backend in cls.get_supported_vit_attn_backends(), (
|
|
f"Backend {backend} is not supported for vit attention"
|
|
f"Supported backends are: {cls.get_supported_vit_attn_backends()}"
|
|
)
|
|
logger.info_once(f"Using backend {backend} for vit attention")
|
|
return backend
|
|
|
|
logger.info_once(
|
|
f"Using default backend {AttentionBackendEnum.TORCH_SDPA} for vit attention"
|
|
)
|
|
return AttentionBackendEnum.TORCH_SDPA
|
|
|
|
@classmethod
|
|
def get_device_capability(
|
|
cls,
|
|
device_id: int = 0,
|
|
) -> DeviceCapability | None:
|
|
"""Stateless version of [torch.cuda.get_device_capability][].
|
|
|
|
Args:
|
|
device_id: Device index in the visible device namespace, matching
|
|
the argument accepted by torch.cuda.
|
|
"""
|
|
return None
|
|
|
|
@classmethod
|
|
def has_device_capability(
|
|
cls,
|
|
capability: tuple[int, int] | int,
|
|
device_id: int = 0,
|
|
) -> bool:
|
|
"""
|
|
Test whether this platform is compatible with a device capability.
|
|
|
|
The `capability` argument can either be:
|
|
|
|
- A tuple `(major, minor)`.
|
|
- An integer `<major><minor>`. (See
|
|
[`DeviceCapability.to_int`][vllm.platforms.interface.DeviceCapability.to_int])
|
|
"""
|
|
current_capability = cls.get_device_capability(device_id=device_id)
|
|
if current_capability is None:
|
|
return False
|
|
|
|
if isinstance(capability, tuple):
|
|
return current_capability >= capability
|
|
|
|
return current_capability.to_int() >= capability
|
|
|
|
@classmethod
|
|
def is_device_capability(
|
|
cls,
|
|
capability: tuple[int, int] | int,
|
|
device_id: int = 0,
|
|
) -> bool:
|
|
"""
|
|
Test whether this platform has exactly the specified device capability.
|
|
|
|
The `capability` argument can either be:
|
|
|
|
- A tuple `(major, minor)`.
|
|
- An integer `<major><minor>`. (See
|
|
[`DeviceCapability.to_int`][vllm.platforms.interface.DeviceCapability.to_int])
|
|
"""
|
|
current_capability = cls.get_device_capability(device_id=device_id)
|
|
if current_capability is None:
|
|
return False
|
|
|
|
if isinstance(capability, tuple):
|
|
return current_capability == capability
|
|
|
|
return current_capability.to_int() == capability
|
|
|
|
@classmethod
|
|
def is_device_capability_family(
|
|
cls,
|
|
capability: int,
|
|
device_id: int = 0,
|
|
) -> bool:
|
|
"""
|
|
Returns True if the device capability is any <major>.x.
|
|
Mirrors CUDA 13 'family' architecture semantics (e.g. 10.x, 11.x, 12.x).
|
|
"""
|
|
current_capability = cls.get_device_capability(device_id=device_id)
|
|
if current_capability is None:
|
|
return False
|
|
return (current_capability.to_int() // 10) == (capability // 10)
|
|
|
|
@classmethod
|
|
def get_device_name(cls, device_id: int = 0) -> str:
|
|
"""Get the name of a device."""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def get_device_uuid(cls, device_id: int = 0) -> str:
|
|
"""Get the uuid of a device, e.g. the PCI bus ID."""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def get_device_total_memory(cls, device_id: int = 0) -> int:
|
|
"""Get the total memory of a device in bytes."""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def get_all_gpu_pci_bus_ids(cls) -> dict[int, str]:
|
|
"""Return a mapping of device index to PCI bus ID string.
|
|
|
|
Used by ``VLLM_GPU_NIC_PCIE_MAPPING`` for RDMA NIC selection.
|
|
Subclasses should override with platform-specific discovery
|
|
(e.g. pynvml for CUDA).
|
|
"""
|
|
raise NotImplementedError(
|
|
"VLLM_GPU_NIC_PCIE_MAPPING is not supported on the "
|
|
f"current platform ({cls.device_name})"
|
|
)
|
|
|
|
@classmethod
|
|
def inference_mode(cls):
|
|
"""A device-specific wrapper of `torch.inference_mode`.
|
|
|
|
This wrapper is recommended because some hardware backends such as TPU
|
|
do not support `torch.inference_mode`. In such a case, they will fall
|
|
back to `torch.no_grad` by overriding this method.
|
|
"""
|
|
return torch.inference_mode(mode=True)
|
|
|
|
@classmethod
|
|
def set_device(cls, device: torch.device) -> None:
|
|
"""
|
|
Set the device for the current platform.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def manual_seed_all(cls, seed: int) -> None:
|
|
"""Set RNG seed across all devices for the current platform."""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def pre_register_and_update(
|
|
cls, parser: FlexibleArgumentParser | None = None
|
|
) -> None:
|
|
"""
|
|
Do some pre-registration or update action for the current platform.
|
|
|
|
This function is called before global VllmConfig is initialized or cli
|
|
arguments are parsed. It's used for out-of-tree platforms to register or
|
|
update the configuration.
|
|
|
|
For example, the out-of-tree quantization config can be imported and
|
|
registered here dynamically.
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def apply_config_platform_defaults(cls, vllm_config: "VllmConfig") -> None:
|
|
"""
|
|
Apply the platform-specific default values to the config.
|
|
|
|
This function is called during the initialization of global VllmConfig, after
|
|
parsing cli arguments.
|
|
It can modify the defaults of the config according to the platform. For example,
|
|
it can enable custom_ops based on the enabled features.
|
|
|
|
The config is passed by reference, so it can be modified in place.
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def check_and_update_config(cls, vllm_config: "VllmConfig") -> None:
|
|
"""
|
|
Check and update the configuration for the current platform.
|
|
|
|
It can raise an exception if the configuration is not compatible with
|
|
the current platform, or it can update the configuration to make it
|
|
compatible with the current platform.
|
|
|
|
The config is passed by reference, so it can be modified in place.
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def _find_non_ssm_backend(
|
|
cls, vllm_config: "VllmConfig"
|
|
) -> "type[AttentionBackend] | None":
|
|
"""Find the first non-SSM attention backend from model layers."""
|
|
from vllm.config.vllm import get_layers_from_vllm_config
|
|
from vllm.model_executor.layers.attention_layer_base import (
|
|
AttentionLayerBase,
|
|
)
|
|
|
|
attn_layers = get_layers_from_vllm_config(
|
|
vllm_config,
|
|
AttentionLayerBase, # type: ignore[type-abstract]
|
|
)
|
|
for layer in attn_layers.values():
|
|
b = layer.get_attn_backend()
|
|
if not b.is_ssm():
|
|
return b
|
|
return None
|
|
|
|
@classmethod
|
|
def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
|
|
"""
|
|
Ensure block_size is compatible with the attention backend.
|
|
For hybrid models, also aligns block_size with mamba page sizes.
|
|
"""
|
|
from vllm.config.cache import CacheConfig
|
|
from vllm.config.vllm import set_current_vllm_config
|
|
|
|
cache_config = vllm_config.cache_config
|
|
model_config = vllm_config.model_config
|
|
|
|
# model_config may be None during testing.
|
|
if not model_config:
|
|
return
|
|
|
|
backend_cls = cls._find_non_ssm_backend(vllm_config)
|
|
if backend_cls is None:
|
|
return
|
|
|
|
# Phase 1: Pick block size from backend (skip if user set --block-size)
|
|
if not cache_config.user_specified_block_size:
|
|
with set_current_vllm_config(vllm_config):
|
|
preferred = backend_cls.get_preferred_block_size(
|
|
CacheConfig.DEFAULT_BLOCK_SIZE
|
|
)
|
|
if preferred != CacheConfig.DEFAULT_BLOCK_SIZE:
|
|
logger.info(
|
|
"Setting kv cache block size to %d for %s backend.",
|
|
preferred,
|
|
backend_cls.get_name(),
|
|
)
|
|
cache_config.block_size = preferred
|
|
|
|
# Phase 2: Align block/mamba sizes for hybrid models
|
|
# (may override user settings).
|
|
if model_config.is_hybrid:
|
|
cls._align_hybrid_block_size(vllm_config, backend_cls)
|
|
|
|
# Phase 3: Align block/page sizes when multiple KV dtypes share the
|
|
# block pool (e.g. nvfp4 primary + unquantized skip layers).
|
|
# May override the user's --block-size.
|
|
if cache_config.kv_cache_dtype_skip_layers:
|
|
cls._align_heterogeneous_kv_block_size(vllm_config, backend_cls)
|
|
|
|
@classmethod
|
|
def _align_heterogeneous_kv_block_size(
|
|
cls,
|
|
vllm_config: "VllmConfig",
|
|
backend_cls: "type[AttentionBackend]",
|
|
) -> None:
|
|
"""Align block size when several KV dtypes share one block pool.
|
|
|
|
A quantized primary (e.g. nvfp4) shares the block pool with one or more
|
|
higher-precision "padded specs" (skip layers today; the first/last-N
|
|
sibling in the future). A padded spec's per-token page is larger than
|
|
the primary's *and not an integer multiple of it*, so the trivial
|
|
``unify_kv_cache_spec_page_size`` cannot reconcile them. We do it here
|
|
instead, before the specs are built:
|
|
|
|
1. Bump the primary ``block_size`` (kernel-aligned) until the primary
|
|
page is large enough to cover the largest padded-spec page.
|
|
2. Record that shared page in each padded spec's ``*_page_size_padded``
|
|
hint, so it pads up to the shared page.
|
|
|
|
``unify`` then sees equal pages and stays trivial.
|
|
|
|
To add a padded-spec type: append its per-token page to ``padded_pages``
|
|
and set its ``*_page_size_padded`` hint below.
|
|
"""
|
|
from vllm.config.vllm import set_current_vllm_config
|
|
from vllm.utils.math_utils import cdiv
|
|
from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE
|
|
from vllm.v1.attention.backend import MultipleOf
|
|
from vllm.v1.kv_cache_interface import FullAttentionSpec, get_kv_quant_mode
|
|
|
|
cache_config = vllm_config.cache_config
|
|
model_config = vllm_config.model_config
|
|
parallel_config = vllm_config.parallel_config
|
|
if not model_config:
|
|
return
|
|
|
|
def per_token_page_bytes(dtype: "torch.dtype", cache_dtype: str) -> int:
|
|
"""Bytes one token occupies in one layer, for the given dtype."""
|
|
return FullAttentionSpec(
|
|
block_size=1,
|
|
num_kv_heads=model_config.get_num_kv_heads(parallel_config),
|
|
head_size=model_config.get_head_size(),
|
|
dtype=dtype,
|
|
kv_quant_mode=get_kv_quant_mode(cache_dtype),
|
|
).page_size_bytes
|
|
|
|
primary_dtype = (
|
|
STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
|
|
if cache_config.cache_dtype != "auto"
|
|
else model_config.dtype
|
|
)
|
|
primary_page = per_token_page_bytes(primary_dtype, cache_config.cache_dtype)
|
|
|
|
# Per-token page of every higher-precision padded spec sharing the pool.
|
|
padded_pages: list[int] = []
|
|
if cache_config.kv_cache_dtype_skip_layers:
|
|
padded_pages.append(per_token_page_bytes(model_config.dtype, "auto"))
|
|
# To add the first/last-N sibling:
|
|
# padded_pages.append(per_token_page_bytes(<sibling_dtype>, "auto"))
|
|
if not padded_pages:
|
|
return
|
|
|
|
largest_padded_page = max(padded_pages)
|
|
assert largest_padded_page >= primary_page, (
|
|
f"padded-spec per-token page ({largest_padded_page}B) < primary "
|
|
f"({primary_page}B); a higher-precision padded spec must not be "
|
|
"smaller than the quantized primary."
|
|
)
|
|
if largest_padded_page == primary_page:
|
|
# Pages already match per token; ``unify`` reconciles the differing
|
|
# block sizes by integer scaling, so no bump or padding is needed.
|
|
return
|
|
|
|
# Smallest block the kernel supports, and the granularity the primary
|
|
# block is rounded up to (never below the already-chosen block_size).
|
|
with set_current_vllm_config(vllm_config):
|
|
supported = backend_cls.get_supported_kernel_block_sizes()
|
|
smallest_kernel_block = min(
|
|
s.base if isinstance(s, MultipleOf) else s for s in supported
|
|
)
|
|
block_alignment = max(smallest_kernel_block, cache_config.block_size)
|
|
|
|
# Bytes one padded-spec page spans at its own smallest kernel block;
|
|
# also cover any mamba page a hybrid model already padded.
|
|
required_page = max(
|
|
largest_padded_page * smallest_kernel_block,
|
|
cache_config.mamba_page_size_padded or 0,
|
|
)
|
|
|
|
# Smallest kernel-aligned primary block whose page covers required_page.
|
|
primary_block_size = block_alignment * cdiv(
|
|
required_page, block_alignment * primary_page
|
|
)
|
|
if cache_config.block_size < primary_block_size:
|
|
cache_config.block_size = primary_block_size
|
|
logger.info(
|
|
"Setting attention block size to %d tokens so the quantized "
|
|
"primary KV page covers the higher-precision padded-spec page.",
|
|
primary_block_size,
|
|
)
|
|
|
|
# The shared page that every padded spec (and mamba) pads up to.
|
|
shared_page = cache_config.block_size * primary_page
|
|
if cache_config.kv_cache_dtype_skip_layers:
|
|
cache_config.skip_page_size_padded = shared_page
|
|
# To add the first/last-N sibling:
|
|
# cache_config.sibling_page_size_padded = shared_page
|
|
if cache_config.mamba_page_size_padded is not None:
|
|
cache_config.mamba_page_size_padded = shared_page
|
|
|
|
@classmethod
|
|
def _align_hybrid_block_size(
|
|
cls,
|
|
vllm_config: "VllmConfig",
|
|
backend_cls: "type[AttentionBackend]",
|
|
) -> None:
|
|
"""
|
|
For hybrid attention/mamba models, ensure that the attention page
|
|
size is >= the mamba page size, and pad the mamba page size to match.
|
|
"""
|
|
from math import lcm
|
|
|
|
from vllm.config.vllm import set_current_vllm_config
|
|
from vllm.model_executor.models import ModelRegistry
|
|
from vllm.utils.math_utils import cdiv
|
|
from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE
|
|
from vllm.v1.attention.backend import MultipleOf
|
|
from vllm.v1.kv_cache_interface import (
|
|
FullAttentionSpec,
|
|
MambaSpec,
|
|
MLAAttentionSpec,
|
|
get_kv_quant_mode,
|
|
)
|
|
|
|
cache_config = vllm_config.cache_config
|
|
model_config = vllm_config.model_config
|
|
parallel_config = vllm_config.parallel_config
|
|
|
|
if cache_config.cache_dtype == "auto":
|
|
kv_cache_dtype = model_config.dtype
|
|
else:
|
|
kv_cache_dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
|
|
|
|
kv_quant_mode = get_kv_quant_mode(cache_config.cache_dtype)
|
|
|
|
# Compute attention page size for 1 token
|
|
if model_config.use_mla:
|
|
attn_page_size_1_token = MLAAttentionSpec(
|
|
block_size=1,
|
|
num_kv_heads=model_config.get_num_kv_heads(parallel_config),
|
|
head_size=model_config.get_head_size(),
|
|
dtype=kv_cache_dtype,
|
|
kv_quant_mode=kv_quant_mode,
|
|
).page_size_bytes
|
|
elif cache_config.cache_dtype.startswith("turboquant_"):
|
|
# TQ has a packed K|V layout; the standard FullAttentionSpec
|
|
# formula over-sizes it and trips unify_kv_cache_spec_page_size
|
|
# when all attention layers are TQ. With mixed skip+TQ the skip
|
|
# layers still use the standard layout — take max so mamba
|
|
# padding covers the largest actual page.
|
|
from vllm.model_executor.layers.quantization.turboquant.config import (
|
|
TurboQuantConfig,
|
|
)
|
|
from vllm.v1.kv_cache_interface import TQFullAttentionSpec
|
|
|
|
tq_cfg = TurboQuantConfig.from_cache_dtype(
|
|
cache_config.cache_dtype, model_config.get_head_size()
|
|
)
|
|
tq_page = TQFullAttentionSpec(
|
|
block_size=1,
|
|
num_kv_heads=model_config.get_num_kv_heads(parallel_config),
|
|
head_size=model_config.get_head_size(),
|
|
head_size_v=model_config.get_head_size(),
|
|
dtype=kv_cache_dtype,
|
|
kv_quant_mode=kv_quant_mode,
|
|
tq_slot_size=tq_cfg.slot_size_aligned,
|
|
).page_size_bytes
|
|
if cache_config.kv_cache_dtype_skip_layers:
|
|
skip_page = FullAttentionSpec(
|
|
block_size=1,
|
|
num_kv_heads=model_config.get_num_kv_heads(parallel_config),
|
|
head_size=model_config.get_head_size(),
|
|
dtype=model_config.dtype,
|
|
).page_size_bytes
|
|
# lcm, not max: skip_page is often not a multiple of
|
|
# tq_page, so max would leave per-layer page sizes
|
|
# un-unifiable downstream.
|
|
attn_page_size_1_token = lcm(tq_page, skip_page)
|
|
else:
|
|
attn_page_size_1_token = tq_page
|
|
else:
|
|
attn_page_size_1_token = FullAttentionSpec(
|
|
block_size=1,
|
|
num_kv_heads=model_config.get_num_kv_heads(parallel_config),
|
|
head_size=model_config.get_head_size(),
|
|
dtype=kv_cache_dtype,
|
|
kv_quant_mode=kv_quant_mode,
|
|
).page_size_bytes
|
|
|
|
# Compute mamba page size
|
|
model_cls, _ = ModelRegistry.resolve_model_cls(
|
|
model_config.architecture,
|
|
model_config=model_config,
|
|
)
|
|
mamba_page_size = MambaSpec(
|
|
shapes=model_cls.get_mamba_state_shape_from_config(vllm_config),
|
|
dtypes=model_cls.get_mamba_state_dtype_from_config(vllm_config),
|
|
block_size=-1,
|
|
).page_size_bytes
|
|
|
|
if mamba_page_size == 0:
|
|
return
|
|
|
|
# mamba_block_size here should either be user specified value or None
|
|
mamba_block_size = (
|
|
cache_config.mamba_block_size
|
|
if cache_config.user_specified_mamba_block_size
|
|
else None
|
|
)
|
|
|
|
# Get kernel block alignment from the backend's supported sizes
|
|
with set_current_vllm_config(vllm_config):
|
|
kernel_block_alignment_size = max(
|
|
min(
|
|
s.base if isinstance(s, MultipleOf) else s
|
|
for s in backend_cls.get_supported_kernel_block_sizes()
|
|
),
|
|
cache_config.block_size,
|
|
)
|
|
|
|
if cache_config.mamba_cache_mode == "all":
|
|
# With prefix caching, align to mamba chunk size for kernel perf
|
|
# TODO(tdoublep): this constraint can be relaxed fairly
|
|
# easily by changing the way we layout chunks in the
|
|
# mamba2 kernels.
|
|
base_chunk_size = mamba_block_size or model_config.get_mamba_chunk_size()
|
|
assert base_chunk_size is not None
|
|
attn_tokens_per_mamba_state = cdiv(mamba_page_size, attn_page_size_1_token)
|
|
chunk_size = lcm(base_chunk_size, kernel_block_alignment_size)
|
|
attn_block_size = chunk_size * cdiv(attn_tokens_per_mamba_state, chunk_size)
|
|
cache_config.mamba_block_size = attn_block_size
|
|
else:
|
|
# Without prefix caching, use minimum block size that satisfies
|
|
# both backend alignment and mamba page size compatibility
|
|
attn_block_size = kernel_block_alignment_size * cdiv(
|
|
mamba_page_size,
|
|
kernel_block_alignment_size * attn_page_size_1_token,
|
|
)
|
|
|
|
if cache_config.block_size < attn_block_size:
|
|
cache_config.block_size = attn_block_size
|
|
logger.info(
|
|
"Setting attention block size to %d tokens "
|
|
"to ensure that attention page size is >= mamba page size.",
|
|
attn_block_size,
|
|
)
|
|
|
|
if cache_config.mamba_cache_mode == "align":
|
|
cache_config.mamba_block_size = cache_config.block_size
|
|
|
|
# Pad mamba page size to exactly match attention page size
|
|
attn_page_size = cache_config.block_size * attn_page_size_1_token
|
|
assert attn_page_size >= mamba_page_size
|
|
|
|
if attn_page_size == mamba_page_size:
|
|
return
|
|
|
|
if (
|
|
cache_config.mamba_page_size_padded is None
|
|
or cache_config.mamba_page_size_padded != attn_page_size
|
|
):
|
|
cache_config.mamba_page_size_padded = attn_page_size
|
|
mamba_padding_pct = (
|
|
100 * (attn_page_size - mamba_page_size) / mamba_page_size
|
|
)
|
|
logger.info(
|
|
"Padding mamba page size by %.2f%% to ensure "
|
|
"that mamba page size and attention page size are "
|
|
"exactly equal.",
|
|
mamba_padding_pct,
|
|
)
|
|
|
|
@classmethod
|
|
def register_custom_kv_cache_specs(cls, vllm_config: "VllmConfig") -> None:
|
|
"""
|
|
Register custom KVCacheSpec class on current platform.
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def verify_model_arch(cls, model_arch: str) -> None:
|
|
"""
|
|
Verify whether the current platform supports the specified model
|
|
architecture.
|
|
|
|
- This will raise an Error or Warning based on the model support on
|
|
the current platform.
|
|
- By default all models are considered supported.
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def verify_quantization(cls, quant: str) -> None:
|
|
"""
|
|
Verify whether the quantization is supported by the current platform.
|
|
"""
|
|
if cls.supported_quantization and quant not in cls.supported_quantization:
|
|
raise ValueError(
|
|
f"{quant} quantization is currently not supported in {cls.device_name}."
|
|
)
|
|
|
|
@classmethod
|
|
def get_cpu_architecture(cls) -> CpuArchEnum:
|
|
"""
|
|
Determine the CPU architecture of the current system.
|
|
Returns CpuArchEnum indicating the architecture type.
|
|
"""
|
|
machine = platform.machine().lower()
|
|
|
|
if machine in ("x86_64", "amd64", "i386", "i686"):
|
|
return CpuArchEnum.X86
|
|
elif machine.startswith("arm") or machine.startswith("aarch"):
|
|
return CpuArchEnum.ARM
|
|
elif machine.startswith("ppc"):
|
|
return CpuArchEnum.POWERPC
|
|
elif machine == "s390x":
|
|
return CpuArchEnum.S390X
|
|
elif machine.startswith("riscv"):
|
|
return CpuArchEnum.RISCV
|
|
|
|
return CpuArchEnum.OTHER if machine else CpuArchEnum.UNKNOWN
|
|
|
|
@classmethod
|
|
def is_pin_memory_available(cls) -> bool:
|
|
"""Checks whether pin memory is available on the current platform."""
|
|
if in_wsl():
|
|
# https://docs.nvidia.com/cuda/wsl-user-guide/index.html#known-limitations-for-linux-cuda-applications
|
|
# Pinned memory support under WSL depends on the vendor and driver
|
|
# version. Conservative default: return False. Platform subclasses
|
|
# that can verify support (e.g. CudaPlatformBase) override this.
|
|
logger.warning_once(
|
|
"Using 'pin_memory=False' as WSL is detected. "
|
|
"This may slow down performance."
|
|
)
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def get_current_memory_usage(
|
|
cls, device: torch.types.Device | None = None
|
|
) -> float:
|
|
"""
|
|
Return the memory usage in bytes.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def get_punica_wrapper(cls) -> str:
|
|
"""
|
|
Return the punica wrapper for current platform.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def get_infinity_values(cls, dtype: torch.dtype) -> tuple[float, float]:
|
|
"""
|
|
Return the platform specific values for (-inf, inf)
|
|
"""
|
|
return float("-inf"), float("inf")
|
|
|
|
@classmethod
|
|
def can_update_inplace(cls) -> bool:
|
|
"""
|
|
Checks if the platform allows inplace memory updates
|
|
"""
|
|
return True
|
|
|
|
@classmethod
|
|
def get_lora_vocab_padding_size(cls) -> int:
|
|
"""
|
|
Returns how much padding the LoRA logits need for kernels
|
|
"""
|
|
return 256
|
|
|
|
@classmethod
|
|
def get_device_communicator_cls(cls) -> str:
|
|
"""
|
|
Get device specific communicator class for distributed communication.
|
|
"""
|
|
return "vllm.distributed.device_communicators.base_device_communicator.DeviceCommunicatorBase" # noqa
|
|
|
|
@classmethod
|
|
def is_integrated_gpu(cls, device_id: int = 0) -> bool:
|
|
"""
|
|
Returns whether the GPU is an integrated (UMA) device that shares
|
|
system memory with the CPU.
|
|
|
|
On UMA systems (e.g. NVIDIA GH200, DGX Spark, Jetson Orin),
|
|
cudaMemGetInfo may underreport free memory because it does not
|
|
account for reclaimable OS memory (page cache, buffers).
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def supports_mx(cls) -> bool:
|
|
"""
|
|
Returns whether the current platform supports MX types.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def supports_fp8(cls) -> bool:
|
|
"""
|
|
Returns whether the current platform supports FP8 types.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def is_fp8_fnuz(cls) -> bool:
|
|
"""
|
|
Returns whether the preferred FP8 type is FNUZ on the current platform.
|
|
|
|
There are two representations of FP8, OCP FP8 and FNUZ FP8.
|
|
The OCP specification can be found at https://tinyurl.com/b7jvwpft.
|
|
The FNUZ specification can be found at https://tinyurl.com/5n6hwwu5.
|
|
|
|
AMD's MI300 and MI325 have native hardware support for FNUZ. All other
|
|
hardware has converged on the OCP FP8 standard.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def fp8_dtype(cls) -> torch.dtype:
|
|
"""
|
|
Returns the preferred FP8 type on the current platform.
|
|
|
|
See the documentation for is_fp8_fnuz for details.
|
|
"""
|
|
return torch.float8_e4m3fn
|
|
|
|
@classmethod
|
|
def use_all_gather(cls) -> bool:
|
|
"""
|
|
Whether to use allgather in LogitsProcessor to gather the logits.
|
|
"""
|
|
return True
|
|
|
|
@classmethod
|
|
def use_custom_allreduce(cls) -> bool:
|
|
"""
|
|
Returns if custom allreduce is supported on the current platform
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def opaque_attention_op(cls) -> bool:
|
|
"""
|
|
Returns True if we register attention as one giant opaque custom op
|
|
on the current platform
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def validate_request(
|
|
cls,
|
|
processed_inputs: "EngineInput",
|
|
params: "SamplingParams | PoolingParams",
|
|
) -> None:
|
|
"""Raises if this request is unsupported on this platform"""
|
|
|
|
def __getattr__(self, key: str):
|
|
# Pickle checks dunder methods like __getstate__. If we return None
|
|
# for them, pickle treats it like a real value and tries to call it.
|
|
if key.startswith("__") and key.endswith("__"):
|
|
raise AttributeError(key)
|
|
|
|
device = getattr(torch, self.device_type, None)
|
|
if device is not None and hasattr(device, key):
|
|
attr = getattr(device, key)
|
|
# NOTE: `hasattr(device, key)=True` can only avoid AttributeError,
|
|
# but the value of this attr could be `None`.
|
|
if attr is not None:
|
|
return attr
|
|
|
|
logger.warning_once(
|
|
"Current platform %s does not have '%s' attribute.",
|
|
self.device_type,
|
|
key,
|
|
)
|
|
return None
|
|
|
|
def get_global_graph_pool(self) -> Any:
|
|
"""
|
|
Return the global graph pool for this platform.
|
|
"""
|
|
cls = self.__class__
|
|
if cls._global_graph_pool is None:
|
|
cls._global_graph_pool = self.graph_pool_handle()
|
|
return cls._global_graph_pool
|
|
|
|
@classmethod
|
|
def get_static_graph_wrapper_cls(cls) -> str:
|
|
"""
|
|
Get static graph wrapper class for static graph.
|
|
"""
|
|
return "vllm.compilation.base_static_graph.AbstractStaticGraphWrapper"
|
|
|
|
@classmethod
|
|
def stateless_init_device_torch_dist_pg(
|
|
cls,
|
|
backend: str,
|
|
prefix_store: "PrefixStore",
|
|
group_rank: int,
|
|
group_size: int,
|
|
timeout: timedelta,
|
|
) -> "ProcessGroup":
|
|
"""
|
|
Init platform-specific torch distributed process group.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def check_if_supports_dtype(cls, dtype: torch.dtype):
|
|
"""
|
|
Check if the dtype is supported by the current platform.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def support_hybrid_kv_cache(cls) -> bool:
|
|
"""
|
|
Returns if the hybrid kv cache is supported by the current platform.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def support_static_graph_mode(cls) -> bool:
|
|
"""
|
|
Returns if the graph mode is supported by the current platform.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def support_deep_gemm(cls) -> bool:
|
|
"""
|
|
Returns if DeepGEMM is supported by the current platform.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def use_custom_op_collectives(cls) -> bool:
|
|
"""
|
|
Whether this platform should use torch.ops.vllm.* custom ops for collectives.
|
|
|
|
Returns False by default - platforms must explicitly opt-in.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def use_sync_weight_loader(cls) -> bool:
|
|
"""
|
|
Returns if the current platform needs to sync weight loader.
|
|
"""
|
|
return False
|
|
|
|
@classmethod
|
|
def make_synced_weight_loader(cls, original_weight_loader):
|
|
"""
|
|
Wrap the original weight loader to make it synced.
|
|
"""
|
|
if not cls.use_sync_weight_loader():
|
|
return original_weight_loader
|
|
|
|
def _synced_weight_loader(param, *args, **kwargs):
|
|
out = original_weight_loader(param, *args, **kwargs)
|
|
if param.device != torch.device("cpu"):
|
|
torch._sync(param)
|
|
return out
|
|
|
|
return _synced_weight_loader
|
|
|
|
@classmethod
|
|
def get_nixl_supported_devices(cls) -> dict[str, tuple[str, ...]]:
|
|
"""
|
|
Returns a mapping from device_type to a tuple of supported
|
|
kv_buffer_device for nixl.
|
|
"""
|
|
return {}
|
|
|
|
@classmethod
|
|
def get_nixl_memory_type(cls) -> str | None:
|
|
"""
|
|
Returns the nixl memory type for the current platform.
|
|
"""
|
|
return None
|
|
|
|
@classmethod
|
|
def check_max_model_len(cls, max_model_len: int) -> int:
|
|
"""
|
|
Check max_model_len for the current platform.
|
|
"""
|
|
return max_model_len
|
|
|
|
@classmethod
|
|
def set_additional_forward_context(cls, *args, **kwargs) -> dict[str, Any]:
|
|
"""
|
|
Set some additional forward context for the current platform if needs.
|
|
"""
|
|
return {}
|
|
|
|
@classmethod
|
|
def num_compute_units(cls, device_id: int = 0) -> int:
|
|
"""
|
|
Get the number of compute units for the current platform.
|
|
(NVIDIA SM / AMD CU / Intel EU)
|
|
"""
|
|
raise NotImplementedError(
|
|
"num_compute_units is not implemented for the current platform."
|
|
)
|
|
|
|
@classmethod
|
|
def get_default_ir_op_priority(
|
|
cls, vllm_config: "VllmConfig"
|
|
) -> "IrOpPriorityConfig":
|
|
"""Get the default IR op priority for the current platform."""
|
|
from vllm.config.kernel import IrOpPriorityConfig
|
|
|
|
# Native always used by default. Platforms can override this behavior.
|
|
return IrOpPriorityConfig.with_default(["native"])
|
|
|
|
@classmethod
|
|
def is_arch_support_pdl(cls) -> bool:
|
|
"""
|
|
Does the current platform support PDL (Programmatic Dependent Launch)?
|
|
"""
|
|
return False
|
|
|
|
|
|
class UnspecifiedPlatform(Platform):
|
|
_enum = PlatformEnum.UNSPECIFIED
|
|
device_type = ""
|