main
py 770 lines 30.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import glob
5 import os
6 import platform
7 import subprocess
8 import sys
9 from typing import TYPE_CHECKING
10
11 import torch
12
13 from vllm import envs
14 from vllm.logger import init_logger
15 from vllm.utils.cpu_resource_utils import (
16 DEVICE_CONTROL_ENV_VAR,
17 get_memory_node_info,
18 get_visible_memory_node,
19 )
20 from vllm.utils.mem_constants import GiB_bytes
21 from vllm.v1.attention.backends.registry import AttentionBackendEnum
22
23 from .interface import CpuArchEnum, Platform, PlatformEnum
24
25 logger = init_logger(__name__)
26
27 if TYPE_CHECKING:
28 from vllm.config import VllmConfig
29 from vllm.v1.attention.selector import AttentionSelectorConfig
30 else:
31 VllmConfig = None
32
33
34 def _cpu_mamba_backend(vllm_config: VllmConfig) -> str:
35 """Return the CPU state backend selected by the model configuration."""
36 model_config = vllm_config.model_config
37 if model_config is None:
38 return "none"
39
40 hf_config = getattr(model_config, "hf_text_config", None)
41 model_type = str(getattr(hf_config, "model_type", "")).lower()
42 architecture = str(getattr(model_config, "architecture", "")).lower()
43 layer_types = getattr(hf_config, "layer_types", None)
44 has_linear_attention = isinstance(layer_types, (list, tuple)) and (
45 "linear_attention" in layer_types
46 )
47
48 try:
49 has_inner_state = bool(model_config.has_inner_state)
50 except (AttributeError, RuntimeError):
51 has_inner_state = False
52
53 if not (has_inner_state or has_linear_attention):
54 return "none"
55
56 fallback_backend = model_type or architecture or "unknown"
57 if not has_linear_attention:
58 return fallback_backend
59
60 try:
61 model_cls, _ = model_config.registry.resolve_model_cls(
62 model_config.architecture,
63 model_config=model_config,
64 )
65 except Exception:
66 return fallback_backend
67
68 try:
69 state_dtypes = model_cls.get_mamba_state_dtype_from_config(vllm_config)
70 except Exception:
71 return fallback_backend
72
73 if not isinstance(state_dtypes, tuple) or len(state_dtypes) != 2:
74 return fallback_backend
75
76 if vllm_config.cache_config.mamba_ssm_cache_dtype == "float16":
77 cache_dtype = torch.float16
78 elif vllm_config.cache_config.mamba_ssm_cache_dtype == "bfloat16":
79 cache_dtype = torch.bfloat16
80 else:
81 return fallback_backend
82
83 if state_dtypes[1] == cache_dtype:
84 return "gdn"
85
86 return fallback_backend
87
88
89 def get_max_threads(pid=0):
90 if hasattr(os, "sched_getaffinity"):
91 return len(os.sched_getaffinity(pid))
92 elif platform.system() == "Darwin":
93 return os.cpu_count()
94 else:
95 raise NotImplementedError("Unsupported OS")
96
97
98 class CpuPlatform(Platform):
99 _enum = PlatformEnum.CPU
100 device_name: str = "cpu"
101 device_type: str = "cpu"
102 dispatch_key: str = "CPU"
103 dist_backend: str = "gloo"
104 device_control_env_var = DEVICE_CONTROL_ENV_VAR
105
106 @property
107 def supported_dtypes(self) -> list[torch.dtype]:
108 if self.get_cpu_architecture() == CpuArchEnum.POWERPC:
109 return [torch.bfloat16, torch.float32, torch.float16]
110 elif self.get_cpu_architecture() == CpuArchEnum.ARM and sys.platform.startswith(
111 "darwin"
112 ):
113 # sysctl exits non-zero when the OID is absent, so check_output would
114 # raise instead of letting the fp16/fp32 fallback below run.
115 bf16 = (
116 subprocess.run(
117 ["sysctl", "-n", "hw.optional.arm.FEAT_BF16"], capture_output=True
118 ).stdout.strip()
119 == b"1"
120 )
121 if bf16:
122 return [torch.bfloat16, torch.float16, torch.float32]
123 return [torch.float16, torch.float32]
124 elif self.get_cpu_architecture() == CpuArchEnum.RISCV:
125 return [torch.bfloat16, torch.float16, torch.float32]
126 # x86/aarch64 CPU has supported both bf16 and fp16 natively.
127 return [torch.bfloat16, torch.float16, torch.float32]
128
129 @classmethod
130 def check_runner_kv_caches_multi_layer(cls) -> None:
131 pass
132
133 @classmethod
134 def get_device_name(cls, device_id: int = 0) -> str:
135 return "cpu"
136
137 @classmethod
138 def get_attn_backend_cls(
139 cls,
140 selected_backend: "AttentionBackendEnum",
141 attn_selector_config: "AttentionSelectorConfig",
142 num_heads: int | None = None,
143 ) -> str:
144 if attn_selector_config.use_sparse:
145 raise NotImplementedError("Sparse Attention is not supported on CPU.")
146 if attn_selector_config.use_mla:
147 amx_available = (
148 cls.get_cpu_architecture() == CpuArchEnum.X86
149 and torch.cpu._is_amx_tile_supported()
150 )
151 if amx_available and selected_backend != AttentionBackendEnum.CPU_MLA:
152 # Prefer AMX when available, unless CPU_MLA was explicitly requested.
153 if (
154 selected_backend
155 and selected_backend != AttentionBackendEnum.AMX_MLA
156 ):
157 logger.info("Cannot use %s backend on CPU.", selected_backend)
158 logger.info_once("Using %s backend.", AttentionBackendEnum.AMX_MLA.name)
159 return AttentionBackendEnum.AMX_MLA.get_path()
160 # Reference MLA implementation on CPU. Performance is not the
161 # goal here; the backend simply wires the CPU decode kernel
162 # (`mla_decode_kvcache`) and an SDPA-based prefill together with
163 # the shared MLA scaffolding so that DeepSeek-style models can
164 # execute on CPU.
165 if selected_backend and selected_backend not in (
166 AttentionBackendEnum.CPU_MLA,
167 AttentionBackendEnum.AMX_MLA,
168 ):
169 logger.info("Cannot use %s backend on CPU.", selected_backend)
170 logger.info_once("Using %s backend.", AttentionBackendEnum.CPU_MLA.name)
171 return AttentionBackendEnum.CPU_MLA.get_path()
172 if selected_backend and selected_backend != AttentionBackendEnum.CPU_ATTN:
173 logger.info("Cannot use %s backend on CPU.", selected_backend)
174 return AttentionBackendEnum.CPU_ATTN.get_path()
175
176 @classmethod
177 def get_device_total_memory(cls, device_id: int = 0) -> int:
178 meminfo = get_memory_node_info(device_id)
179
180 return meminfo.total_memory
181
182 @classmethod
183 def set_device(cls, device: torch.device) -> None:
184 """Set the device for the current platform."""
185 torch.cpu.set_device(device)
186
187 @classmethod
188 def manual_seed_all(cls, seed: int) -> None:
189 pass
190
191 @classmethod
192 def inference_mode(cls):
193 return torch.no_grad()
194
195 @classmethod
196 def check_and_update_config(cls, vllm_config: VllmConfig) -> None:
197 model_config = vllm_config.model_config
198
199 if model_config is not None:
200 model_config.disable_cascade_attn = True
201
202 # Import lazily: vllm.triton_utils imports vllm.platforms.current_platform,
203 # which is still being resolved while this platform class is loading.
204 from vllm.triton_utils import HAS_TRITON
205
206 if cls.get_cpu_architecture() == CpuArchEnum.X86 and not HAS_TRITON:
207 logger.warning_once(
208 "Triton is not installed. triton-cpu is expected on x86 CPUs."
209 )
210
211 cache_config = vllm_config.cache_config
212
213 is_deepseek_v4 = (
214 model_config is not None
215 and getattr(model_config.hf_config, "model_type", None) == "deepseek_v4"
216 )
217
218 # The CPU MLA decode kernel only compiles with block_size=16 today
219 # (see csrc/cpu/mla_decode.cpp). If the model uses MLA we override
220 # the default block size regardless of user preference to avoid a
221 # runtime kernel dispatch failure. AMX MLA has no such constraint
222 # (same AMX-available condition as get_attn_backend_cls), so it's
223 # excluded from this override. DeepSeek-V4 is also excluded here and
224 # handled in its own branch below: its sparse-MLA and indexer
225 # backends declare block_size=256 as their only supported kernel
226 # block size (DeepseekV4SparseMLABackend/DeepseekV4IndexerBackend),
227 # so it needs the same override-regardless-of-preference treatment
228 # as CPU MLA, just with a different value.
229 cpu_mla_enabled = (
230 not is_deepseek_v4
231 and model_config is not None
232 and getattr(model_config, "use_mla", False)
233 )
234 amx_mla_enabled = (
235 cpu_mla_enabled
236 and cls.get_cpu_architecture() == CpuArchEnum.X86
237 and torch.cpu._is_amx_tile_supported()
238 and vllm_config.attention_config.backend != AttentionBackendEnum.CPU_MLA
239 )
240 reference_cpu_mla_enabled = cpu_mla_enabled and not amx_mla_enabled
241 # DeepSeek-V4's CPU attention/indexer kernels
242 # (csrc/cpu/sgl-kernels/{flash_mla,store_cache,compressor,
243 # paged_mqa_logits,topk}.cpp) are AMX-kernel-backed and built on the
244 # same paged/position-indexed conventions as the GPU/XPU backends
245 # (block_table/slot_mapping addressing throughout, chunk metadata
246 # that already carries per-token causal offsets for partial/extend
247 # continuation) -- so they support chunked prefill and prefix
248 # caching the same way. `amx_mla_enabled` above deliberately
249 # excludes DeepSeek-V4 (it has its own block-size requirements,
250 # unrelated to chunked-prefill support), so it can't be reused here.
251 amx_mla_or_dsv4_enabled = amx_mla_enabled or (
252 is_deepseek_v4
253 and cls.get_cpu_architecture() == CpuArchEnum.X86
254 and torch.cpu._is_amx_tile_supported()
255 )
256 if reference_cpu_mla_enabled:
257 if cache_config.user_specified_block_size and cache_config.block_size != 16:
258 logger.warning(
259 "CPU MLA backend requires block_size=16, overriding "
260 "user-specified block_size=%s.",
261 cache_config.block_size,
262 )
263 cache_config.block_size = 16
264 elif is_deepseek_v4:
265 if (
266 cache_config.user_specified_block_size
267 and cache_config.block_size != 256
268 ):
269 logger.warning(
270 "DeepSeek-V4 CPU backend requires block_size=256, "
271 "overriding user-specified block_size=%s.",
272 cache_config.block_size,
273 )
274 cache_config.block_size = 256
275 elif not cache_config.user_specified_block_size:
276 cache_config.block_size = 128
277
278 if not reference_cpu_mla_enabled and cache_config.block_size % 32 != 0:
279 logger.warning(
280 "CPU backend prefers block_size is multiples of 32, "
281 "otherwise the performance is not optimized."
282 )
283
284 # Accelerated GDN uses AMX tiles or AVX-512BF16 VDPBF16PS.
285 if (
286 torch.cpu._is_avx512_bf16_supported()
287 and cache_config.mamba_ssm_cache_dtype != "float32"
288 ):
289 mamba_backend = _cpu_mamba_backend(vllm_config)
290 if (
291 cache_config.mamba_ssm_cache_dtype in ("float16", "bfloat16")
292 and mamba_backend == "gdn"
293 ):
294 logger.info(
295 "Using %s SSM state storage for the CPU accelerated GDN backend.",
296 cache_config.mamba_ssm_cache_dtype,
297 )
298 else:
299 cache_config.mamba_ssm_cache_dtype = "float32"
300 logger.warning(
301 "Reset SSM cache type to float32 for accelerated GDN mamba "
302 "backend '%s'.",
303 mamba_backend,
304 )
305
306 # Lagecy setting
307 env_key = "VLLM_CPU_KVCACHE_SPACE"
308 if env_key in os.environ and os.environ[env_key] != "":
309 kv_cache_space = int(os.environ[env_key])
310 cache_config.kv_cache_memory_bytes = kv_cache_space * GiB_bytes
311
312 scheduler_config = vllm_config.scheduler_config
313 # async scheduling is not required on CPU
314 scheduler_config.async_scheduling = False
315
316 parallel_config = vllm_config.parallel_config
317 if (
318 os.environ.get("VLLM_ENABLE_V1_MULTIPROCESSING", "1") == "1"
319 and parallel_config.distributed_executor_backend == "uni"
320 ):
321 # OMP requires the MP executor to function correctly, UniProc
322 # is not supported as it is not possible to set the OMP
323 # environment correctly
324 parallel_config.distributed_executor_backend = "mp"
325
326 if parallel_config.worker_cls == "auto":
327 parallel_config.worker_cls = "vllm.v1.worker.cpu_worker.CPUWorker"
328 # Disable DBO
329 if parallel_config.enable_dbo:
330 logger.warning_once("Dual-Batch Overlap is not supported on CPU, disabled.")
331 parallel_config.enable_dbo = False
332
333 # Note: workaround for v1 gpu_model_runner
334 from vllm.config import CompilationMode
335
336 vllm_config.compilation_config.cudagraph_capture_sizes = []
337
338 compilation_config = vllm_config.compilation_config
339 if vllm_config.compilation_config.mode == CompilationMode.VLLM_COMPILE:
340 # Note: vLLM V1 is using PIECEWISE level compilation, which will
341 # take time to compile kernels just-in-time with the inductor
342 # backend. For CPU CI tests, most of them are executed fast and
343 # compilations consume too much time, even with torch compile
344 # cache. So use VLLM_CPU_CI_ENV to indicate the CI environment,
345 # and just execute model with dynamo + eager mode to save time.
346 # VLLM_CPU_CI_ENV is only used as an internal variable.
347 backend = "eager" if envs.VLLM_CPU_CI_ENV else "inductor"
348
349 compilation_config.mode = CompilationMode.DYNAMO_TRACE_ONCE
350 compilation_config.backend = backend
351 compilation_config.inductor_compile_config.update(
352 {
353 "dce": True,
354 "size_asserts": False,
355 "nan_asserts": False,
356 "epilogue_fusion": True,
357 "cpp.dynamic_threads": True,
358 }
359 )
360 compilation_config.ir_enable_torch_wrap = False
361
362 if vllm_config.lora_config is not None:
363 compilation_config.mode = CompilationMode.NONE
364
365 if (
366 cls.get_cpu_architecture() == CpuArchEnum.ARM
367 and "+gelu" not in compilation_config.custom_ops
368 and "-gelu" not in compilation_config.custom_ops
369 ):
370 compilation_config.custom_ops.append("+gelu")
371 if (
372 cls.get_cpu_architecture() == CpuArchEnum.ARM
373 and "+gelu_tanh" not in compilation_config.custom_ops
374 and "-gelu_tanh" not in compilation_config.custom_ops
375 ):
376 compilation_config.custom_ops.append("+gelu_tanh")
377 if (
378 cls.get_cpu_architecture() == CpuArchEnum.ARM
379 and "+gelu_and_mul" not in compilation_config.custom_ops
380 and "-gelu_and_mul" not in compilation_config.custom_ops
381 ):
382 compilation_config.custom_ops.append("+gelu_and_mul")
383
384 vllm_config.profiler_config.torch_profiler_dump_cuda_time_total = False
385
386 assert vllm_config.device_config.device_type == "cpu"
387
388 #
389 # Environment variables for CPU executor
390 #
391
392 os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
393
394 # Note: to avoid the error 'nthreads cannot be larger than environment
395 # variable "NUMEXPR_MAX_THREADS" (64)'.
396 os.environ["NUMEXPR_MAX_THREADS"] = str(get_max_threads())
397
398 # Disable torch async compiling which won't work with daemonic processes
399 os.environ["TORCHINDUCTOR_COMPILE_THREADS"] = "1"
400
401 # Disable multi-stream for shared experts as no Stream on CPU
402 os.environ["VLLM_DISABLE_SHARED_EXPERTS_STREAM"] = "1"
403
404 # Avoid inductor generates num_thread() and breaks the thread binding
405 os.environ["TORCHINDUCTOR_CPP_DYNAMIC_THREADS"] = "1"
406
407 # NIXL's Mamba descriptors require DS conv state storage. Select it
408 # before cache shapes are created, while preserving an explicit layout.
409 conv_state_layout_env = "VLLM_SSM_CONV_STATE_LAYOUT"
410 if conv_state_layout_env not in os.environ:
411 kv_transfer_config = vllm_config.kv_transfer_config
412 uses_nixl = kv_transfer_config is not None and any(
413 kv_transfer_config.has_connector(name)
414 for name in (
415 "NixlConnector",
416 "NixlPullConnector",
417 "NixlPushConnector",
418 )
419 )
420 if uses_nixl:
421 os.environ[conv_state_layout_env] = "DS"
422 elif torch.cpu._is_avx512_bf16_supported():
423 os.environ[conv_state_layout_env] = "SD"
424
425 ld_preload_str = os.getenv("LD_PRELOAD", "")
426 cpu_architecture = Platform.get_cpu_architecture()
427
428 if (
429 platform.system() == "Linux"
430 and cpu_architecture
431 in (
432 CpuArchEnum.ARM,
433 CpuArchEnum.POWERPC,
434 CpuArchEnum.X86,
435 CpuArchEnum.S390X,
436 )
437 and not (
438 "libomp" in ld_preload_str
439 or "libgomp" in ld_preload_str
440 or "libiomp" in ld_preload_str
441 )
442 ):
443 # We need to LD_PRELOAD PyTorch's libgomp, otherwise only
444 # one core will be properly utilized when we thread-bind
445 # See: https://github.com/vllm-project/vllm/issues/27369
446 # TODO: Remove once:
447 # https://github.com/pytorch/pytorch/issues/166087 is fixed
448
449 # We need to find the location of PyTorch's libgomp
450 torch_pkg = os.path.dirname(torch.__file__)
451 site_root = os.path.dirname(torch_pkg)
452 # Search both torch.libs and torch/lib - See:
453 # https://github.com/vllm-project/vllm/issues/30470
454 torch_libs_paths = [
455 os.path.join(site_root, "torch.libs"),
456 os.path.join(torch_pkg, "lib"),
457 ]
458 pytorch_libgomp_so_candidates = []
459 for torch_libs in torch_libs_paths:
460 pytorch_libgomp_so_candidates.extend(
461 glob.glob(os.path.join(torch_libs, "libgomp*.so*"))
462 )
463 if pytorch_libgomp_so_candidates:
464 pytorch_libgomp_so = pytorch_libgomp_so_candidates[0]
465 if ld_preload_str:
466 ld_preload_str += ":"
467 ld_preload_str += pytorch_libgomp_so
468 os.environ["LD_PRELOAD"] = ld_preload_str
469
470 # LD_PRELOAD libtcmalloc, bundled under vllm/libs to reduce
471 # memory allocation overhead
472 if (
473 platform.system() == "Linux"
474 and cpu_architecture
475 in (CpuArchEnum.ARM, CpuArchEnum.X86, CpuArchEnum.S390X)
476 and "libtcmalloc" not in ld_preload_str
477 ):
478 vllm_pkg = os.path.dirname(os.path.dirname(__file__))
479 tcmalloc_so = None
480 for pattern in ("libtcmalloc_minimal*.so*", "libtcmalloc.so*"):
481 tcmalloc_so_candidates = glob.glob(
482 os.path.join(vllm_pkg, "libs", pattern)
483 )
484 if tcmalloc_so_candidates:
485 tcmalloc_so = tcmalloc_so_candidates[0]
486 break
487
488 if tcmalloc_so is not None:
489 if ld_preload_str:
490 ld_preload_str = f"{tcmalloc_so}:{ld_preload_str}"
491 else:
492 ld_preload_str = tcmalloc_so
493 os.environ["LD_PRELOAD"] = ld_preload_str
494
495 os.environ["LOCAL_WORLD_SIZE"] = str(
496 vllm_config.parallel_config.tensor_parallel_size
497 )
498
499 if (
500 model_config is not None
501 and model_config.use_mla
502 and not amx_mla_or_dsv4_enabled
503 ):
504 logger.info_once(
505 "MLA is enabled on a non-GPU platform; forcing chunked "
506 "prefill and prefix caching to be disabled."
507 )
508 vllm_config.scheduler_config.enable_chunked_prefill = False
509 vllm_config.cache_config.enable_prefix_caching = False
510 vllm_config.scheduler_config.max_num_batched_tokens = max(
511 vllm_config.model_config.max_model_len,
512 vllm_config.scheduler_config.DEFAULT_MAX_NUM_BATCHED_TOKENS,
513 )
514
515 @classmethod
516 def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
517 model_config = vllm_config.model_config
518 if model_config is None or not model_config.is_hybrid:
519 return
520
521 # reconcile attention and mamba page sizes
522 backend_classes = cls._find_non_ssm_backends(vllm_config)
523 if not backend_classes:
524 return
525
526 cls._align_hybrid_block_size(vllm_config, backend_classes[0])
527
528 @classmethod
529 def discover_numa_topology(cls) -> list[list[int]]:
530 """Discover NUMA topology and keep the last physical core of each numa
531 into one core group list for nixl start_kv_load()
532 """
533 SYS_NODE = "/sys/devices/system/node"
534 SYS_CPU = "/sys/devices/system/cpu"
535
536 if not (os.path.exists(SYS_NODE) and os.path.exists(SYS_CPU)):
537 return []
538
539 use_highest_sibling = cls.get_cpu_architecture() == CpuArchEnum.X86
540 core_rsv_for_kv = []
541 for node in os.listdir(SYS_NODE):
542 if not node.startswith("node") or not node[4:].isdigit():
543 continue
544 node_path = f"{SYS_NODE}/{node}"
545
546 seen_phys = set()
547 for cpu in os.listdir(node_path):
548 if not cpu.startswith("cpu") or not cpu[3:].isdigit():
549 continue
550
551 cpu_id = int(cpu[3:])
552 # thread_siblings based on cpu_id
553 path = f"{SYS_CPU}/cpu{cpu_id}/topology/thread_siblings_list"
554
555 if os.path.exists(path):
556 try:
557 with open(path) as f:
558 s = f.read()
559 cpus: list[int] = []
560 for part in s.strip().split(","):
561 if "-" in part:
562 a, b = map(int, part.split("-"))
563 cpus.extend(range(a, b + 1))
564 else:
565 cpus.append(int(part))
566 siblings = cpus if cpus else [cpu_id]
567 except (OSError, ValueError):
568 siblings = [cpu_id]
569 else:
570 siblings = [cpu_id]
571
572 phys = max(siblings) if use_highest_sibling else min(siblings)
573
574 if phys not in seen_phys:
575 seen_phys.add(phys)
576
577 if len(seen_phys) > 0:
578 core_rsv_for_kv.append(list(seen_phys))
579
580 return core_rsv_for_kv
581
582 @classmethod
583 def is_pin_memory_available(cls) -> bool:
584 return False
585
586 @classmethod
587 def get_punica_wrapper(cls) -> str:
588 return "vllm.lora.punica_wrapper.punica_cpu.PunicaWrapperCPU"
589
590 @classmethod
591 def get_device_communicator_cls(cls) -> str:
592 """Get device specific communicator class for distributed communication."""
593 return "vllm.distributed.device_communicators.cpu_communicator.CpuCommunicator" # noqa
594
595 @classmethod
596 def supports_structured_output(cls) -> bool:
597 return True
598
599 @classmethod
600 def opaque_attention_op(cls) -> bool:
601 return True
602
603 @classmethod
604 def support_hybrid_kv_cache(cls) -> bool:
605 return True
606
607 @classmethod
608 def num_compute_units(cls, device_id: int = 0) -> int:
609 return torch.get_num_threads()
610
611 @classmethod
612 def import_kernels(cls) -> None:
613 if Platform.get_cpu_architecture() in (CpuArchEnum.X86,):
614 # Note: The lib name is _C_AVX2/AVX512, but the module name is _C.
615 # This will cause a exception "dynamic module does define
616 # module export function". But the library is imported
617 # successfully. So ignore the exception for now, until we find
618 # a solution.
619 ignored_msg = "dynamic module does not define module export function"
620 if torch.cpu._is_avx512_supported():
621 if torch.cpu._is_avx512_bf16_supported():
622 try:
623 import vllm._C # noqa: F401
624 except ImportError as e:
625 logger.warning_once(
626 "Failed to import from vllm._C: %s", repr(e)
627 )
628 else:
629 try:
630 import vllm._C_AVX512 # noqa: F401
631 except ImportError as e:
632 if ignored_msg not in e.msg:
633 logger.warning_once(
634 "Failed to import from vllm._C_AVX512: %s", repr(e)
635 )
636 else:
637 try:
638 import vllm._C_AVX2 # noqa: F401
639 except ImportError as e:
640 if ignored_msg not in e.msg:
641 logger.warning_once(
642 "Failed to import from vllm._C_AVX2: %s", repr(e)
643 )
644 else:
645 try:
646 import vllm._C # noqa: F401
647 except ImportError as e:
648 logger.warning_once("Failed to import from vllm._C: %s", repr(e))
649
650 @classmethod
651 def register_triton_kernel_overrides(cls) -> None:
652 """Fallback C++ implementations for core Triton kernels.
653
654 Used when the Triton-CPU backend is unavailable. When Triton-CPU is
655 present, the Triton kernels can run natively and the overrides
656 would only shadow them, so registration is skipped.
657 """
658 from vllm.triton_utils import HAS_TRITON
659
660 if HAS_TRITON:
661 return
662
663 from vllm.triton_utils.dispatcher import register_kernels
664 from vllm.utils import cpu_triton_utils as cpu_tl
665
666 register_kernels(
667 {
668 "vllm.v1.worker.block_table.ComputeSlotMappingKernel.kernel": (
669 cpu_tl._compute_slot_mapping_kernel_impl
670 ),
671 "vllm.v1.spec_decode.utils.eagle_step_slot_mapping_metadata_kernel": (
672 cpu_tl._eagle_step_slot_mapping_metadata_kernel_impl
673 ),
674 "vllm.v1.spec_decode.utils.eagle_prepare_inputs_padded_kernel": (
675 cpu_tl._eagle_prepare_inputs_padded_kernel_impl
676 ),
677 "vllm.v1.spec_decode.utils.eagle_prepare_next_token_padded_kernel": (
678 cpu_tl._eagle_prepare_next_token_padded_kernel_impl
679 ),
680 "vllm.v1.spec_decode.utils.copy_and_expand_eagle_inputs_kernel": (
681 cpu_tl._copy_and_expand_eagle_inputs_kernel_impl
682 ),
683 "vllm.v1.spec_decode.utils.copy_and_expand_dflash_inputs_kernel": (
684 cpu_tl._copy_and_expand_dflash_inputs_kernel_impl
685 ),
686 "vllm.v1.sample.rejection_sampler.rejection_greedy_sample_kernel": (
687 cpu_tl._rejection_greedy_sample_kernel_impl
688 ),
689 "vllm.v1.sample.rejection_sampler.rejection_random_sample_kernel": (
690 cpu_tl._rejection_random_sample_kernel_impl
691 ),
692 "vllm.v1.sample.rejection_sampler.expand_kernel": (
693 cpu_tl._expand_kernel_impl
694 ),
695 "vllm.v1.sample.rejection_sampler.sample_recovered_tokens_kernel": (
696 cpu_tl._sample_recovered_tokens_kernel_impl
697 ),
698 "vllm.v1.worker.mamba_utils.batch_memcpy_kernel": (
699 cpu_tl._batch_memcpy_impl
700 ),
701 }
702 )
703
704 @classmethod
705 def pack_kv_cache(
706 cls,
707 kv_cache: torch.Tensor,
708 indices: torch.Tensor,
709 ) -> None:
710 """Rewrite the kv cache shape for the current platform."""
711 # Import lazily: cpu_attn pulls in _custom_ops, which needs a fully
712 # initialized vllm.platforms (avoid circular import while CpuPlatform loads).
713 from vllm._custom_ops import cpu_attn_reshape_and_cache
714 from vllm.v1.attention.backends.cpu_attn import _get_attn_isa
715
716 # MLA uses a single latent cache of shape [N, block_size, head_size],
717 # so the classic key/value split does not apply. The MLA backend
718 # writes the cache itself via `concat_and_cache_mla` inside
719 # `do_kv_cache_update`, so there is nothing to pack here.
720 if kv_cache.dim() == 3:
721 return
722
723 num_blocks, num_kv_heads, block_size, fused_head_size = kv_cache.shape
724 head_size = fused_head_size // 2
725
726 # Fused path used by heterogeneous NIXL CPU_ATTN post-processing.
727 blocks_to_update = kv_cache.index_select(0, indices)
728 key = blocks_to_update[..., :head_size]
729 value = blocks_to_update[..., head_size:]
730
731 key_cache, value_cache = kv_cache.view(
732 num_blocks, num_kv_heads, block_size * 2, head_size
733 ).chunk(2, dim=2)
734
735 dtype = key.dtype
736 # For CPU_ATTN, the shape is [N, num_kv_heads, block_size, head_size]
737 key = key.permute(0, 2, 1, 3).flatten(0, 1)
738 value = value.permute(0, 2, 1, 3).flatten(0, 1)
739
740 isa = _get_attn_isa(dtype, block_size, head_size)
741 block_offsets = torch.arange(block_size, device="cpu", dtype=torch.long)
742 num_blocks = indices.numel()
743 slot_mapping = (
744 block_offsets.reshape(1, block_size)
745 + indices.reshape(num_blocks, 1) * block_size
746 ).flatten()
747 if key_cache.dtype == torch.uint8:
748 raise NotImplementedError(
749 "FP8 KV cache is not yet supported with KV transfer on CPU"
750 )
751 cpu_attn_reshape_and_cache(
752 key,
753 value,
754 key_cache,
755 value_cache,
756 slot_mapping,
757 isa,
758 )
759
760 @classmethod
761 def get_current_memory_usage(
762 cls, device: torch.types.Device | None = None
763 ) -> float:
764 allowed_mem_node_list = get_visible_memory_node()
765 mem_status_list = [get_memory_node_info(i) for i in allowed_mem_node_list]
766 memory_usage = 0
767 for s in mem_status_list:
768 memory_usage += s.total_memory - s.available_memory
769
770 return memory_usage