Files
vllm-omni/vllm_omni/platforms/cpu/platform.py
T
tzhouamandClaude Fable 5.1 35e43b9983 [Edge][WP3] Add CPU OmniPlatform (AR + generation stages on vLLM's CPU backend)
* 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>
2026-09-09 11:02:14 +08:00

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]