* vllm_omni/platforms/cpu/platform.py: CPUOmniPlatform(OmniPlatform, CpuPlatform) selected when VLLM_TARGET_DEVICE=cpu or a +cpu vLLM wheel is installed (builtin plugin "cpu"; explicit CPU target short-circuits accelerator probes like vLLM does). NUMA-based memory queries, one logical device, no graph wrapper (sentinel type for isinstance checks), inductor opt-in via VLLM_OMNI_CPU_INDUCTOR=1, diffusion stages unsupported. * vllm_omni/platforms/cpu/worker: CPUARWorker / CPUGenerationWorker compose vLLM's CPUWorker with the Omni mixin (XPU pattern); CPUARModelRunner / CPUGenerationModelRunner compose the Omni runners with CPUModelRunner (no graphs, no async omni output; sleep/wake return error ACKs). * patch.py skips the CuMem patch on CPU-only torch; Qwen3-TTS speaker encoder and prompt-embeds dtype follow the model dtype (float32/float16 on CPU). * vllm_omni/deploy/qwen3_tts.yaml: `platforms: cpu:` overlay (eager, no async scheduling, absolute kv_cache_memory_bytes, disjoint OMP thread ranges). * requirements/cpu.txt (+psutil, intel-openmp), docs/getting_started/installation/cpu.md. Measured (this x86 Xeon 8480C host, vllm 0.28.0+cpu, bf16, 12+4 threads): Qwen3-TTS 0.6B CustomVoice end-to-end on CPU, 12 prompts: TTFA p50 292 ms, RTF ~1.7 (25-frame chunk ~4.6 s), init 185 s. Result JSON + init timeline committed under benchmarks/tts/edge_results/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
"""CPU OmniPlatform: AR / generation stages on vLLM's CPU backend.
|
|
|
|
Composes vLLM's ``CpuPlatform`` (device attributes, ``check_and_update_config``
|
|
forcing: no async scheduling, no CUDA graphs, ``CPU_ATTN``, thread binding,
|
|
LD_PRELOAD) with the Omni interface, exactly as ``CudaOmniPlatform`` composes
|
|
``CudaPlatformBase``. Diffusion stages are not supported on CPU.
|
|
|
|
Activate with ``VLLM_TARGET_DEVICE=cpu`` (or a ``+cpu`` vLLM wheel). Deploy
|
|
YAMLs can pin CPU values under ``platforms: cpu:``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
import torch
|
|
from vllm.logger import init_logger
|
|
from vllm.platforms.cpu import CpuPlatform
|
|
|
|
from vllm_omni.platforms.interface import OmniPlatform, OmniPlatformEnum
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def _numa_memory(device: torch.device | None = None) -> tuple[int, int]:
|
|
"""(available, total) bytes of the NUMA node this process is allowed on, else host memory."""
|
|
try:
|
|
from vllm.utils.cpu_resource_utils import get_allowed_cpu_list, get_memory_node_info
|
|
|
|
node = get_allowed_cpu_list()[0].numa_node
|
|
info = get_memory_node_info(node)
|
|
return int(info.available_memory), int(info.total_memory)
|
|
except Exception:
|
|
try:
|
|
import psutil
|
|
|
|
vm = psutil.virtual_memory()
|
|
return int(vm.available), int(vm.total)
|
|
except Exception:
|
|
return 0, 0
|
|
|
|
|
|
class _NoGraphWrapper:
|
|
"""Sentinel graph-wrapper type for the CPU platform (never instantiated)."""
|
|
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover - guard
|
|
raise NotImplementedError("CUDA graphs are not available on the CPU OmniPlatform")
|
|
|
|
|
|
class CPUOmniPlatform(OmniPlatform, CpuPlatform):
|
|
"""CPU implementation of OmniPlatform (AR + generation stages only)."""
|
|
|
|
_omni_enum = OmniPlatformEnum.CPU
|
|
|
|
# ----------------------------------------------------------- workers
|
|
@classmethod
|
|
def get_omni_ar_worker_cls(cls) -> str:
|
|
return "vllm_omni.platforms.cpu.worker.cpu_ar_worker.CPUARWorker"
|
|
|
|
@classmethod
|
|
def get_omni_generation_worker_cls(cls) -> str:
|
|
return "vllm_omni.platforms.cpu.worker.cpu_generation_worker.CPUGenerationWorker"
|
|
|
|
@classmethod
|
|
def get_default_stage_config_path(cls) -> str:
|
|
return "vllm_omni/deploy"
|
|
|
|
# ---------------------------------------------------------- diffusion
|
|
@classmethod
|
|
def get_diffusion_worker_cls(cls) -> str:
|
|
raise NotImplementedError("Diffusion stages are not supported on the CPU OmniPlatform")
|
|
|
|
@classmethod
|
|
def get_diffusion_model_runner_cls(cls) -> str:
|
|
raise NotImplementedError("Diffusion stages are not supported on the CPU OmniPlatform")
|
|
|
|
@classmethod
|
|
def get_diffusion_attn_backend_cls(cls, *args: Any, **kwargs: Any) -> str:
|
|
raise NotImplementedError("Diffusion attention backends are not supported on the CPU OmniPlatform")
|
|
|
|
@classmethod
|
|
def supports_diffusion_dense_flash_attention(cls) -> bool:
|
|
return False
|
|
|
|
@classmethod
|
|
def has_flash_attn_package(cls) -> bool:
|
|
return False
|
|
|
|
# ------------------------------------------------------------ compile
|
|
@classmethod
|
|
def supports_torch_inductor(cls) -> bool:
|
|
# The code predictor's torch.compile costs a long C++ codegen on first
|
|
# use; keep it off unless explicitly requested (measured variant).
|
|
return os.environ.get("VLLM_OMNI_CPU_INDUCTOR", "0") == "1"
|
|
|
|
@classmethod
|
|
def supports_talker_mtp_graph_capture(cls) -> bool:
|
|
return False
|
|
|
|
@classmethod
|
|
def get_graph_wrapper_cls(cls) -> type:
|
|
# Runners use this in ``isinstance`` checks on every decode step
|
|
# (``_talker_mtp_forward``); on CPU nothing is ever wrapped, so return a
|
|
# sentinel class that no object is an instance of.
|
|
return _NoGraphWrapper
|
|
|
|
# ------------------------------------------------------------- device
|
|
@classmethod
|
|
def get_torch_device(cls, local_rank: int | None = None) -> torch.device:
|
|
return torch.device("cpu")
|
|
|
|
@classmethod
|
|
def get_device_count(cls) -> int:
|
|
# One logical device: stage device locks / admission iterate range(count).
|
|
return 1
|
|
|
|
@classmethod
|
|
def get_device_version(cls) -> str | None:
|
|
return None
|
|
|
|
@classmethod
|
|
def synchronize(cls) -> None:
|
|
return None
|
|
|
|
@classmethod
|
|
def record_device_event(cls):
|
|
return None
|
|
|
|
@classmethod
|
|
def get_free_memory(cls, device: torch.device | None = None) -> int:
|
|
return _numa_memory(device)[0]
|
|
|
|
@classmethod
|
|
def get_device_memory(cls, device: torch.device | None = None) -> tuple[int, int]:
|
|
return _numa_memory(device)
|
|
|
|
@classmethod
|
|
def get_device_total_memory(cls, device_id: int = 0) -> int:
|
|
try:
|
|
return int(CpuPlatform.get_device_total_memory(device_id))
|
|
except Exception:
|
|
return _numa_memory()[1]
|
|
|
|
@classmethod
|
|
def supports_cpu_offload(cls) -> bool:
|
|
return False
|
|
|
|
@classmethod
|
|
def is_fully_connected(cls, physical_device_ids: list[int] | None = None) -> bool:
|
|
return True
|
|
|
|
@classmethod
|
|
def get_default_ir_op_priority(cls, *args: Any, **kwargs: Any):
|
|
parent = getattr(CpuPlatform, "get_default_ir_op_priority", None)
|
|
if parent is not None:
|
|
return parent(*args, **kwargs)
|
|
return super().get_default_ir_op_priority(*args, **kwargs) # type: ignore[misc]
|