Prhub

#26385 Introduce CpuDeviceMixin and CpuSRTPlatform

原始 PR 作者 zijiexia 合并时间 2026-06-18 08:41 文件变更 4 提交数 13 评论 14 代码增减 +313 / -2

执行摘要

为 CPU 引擎引入平台抽象层

镜像 PR #24096(添加 CudaDeviceMixin/CudaSRTPlatform 和 RocmDeviceMixin/RocmSRTPlatform)进行 CPU 平台抽象。此前 CPU 主机只能回退到抽象基类 SRTPlatform,其 [Active] 方法抛出 NotImplementedError。通过引入 CpuDeviceMixin 和 CpuSRTPlatform,使三个主要内置设备都有具体的平台类返回,避免 CPU 落在抽象基类上。

该 PR 是精心设计的预备基础设施,值得精读。关键设计决策(架构分支、内存查询契约、empty_cache 实现权衡、分布式后端选择等)有清晰的文档注释。对于计划为其他设备添加平台类的开发者,参考 CpuDeviceMixinCpuSRTPlatform 的实现模式和测试方法非常有价值。

讨论亮点
  • 性能优化建议:gemini-code-assist[bot] 建议缓存 psutil.Process() 实例以避免每次调用 get_current_memory_usage 的开销;建议在 empty_cache 中通过 ctypes 调用 malloc_trim 释放堆内存回 OS。开发者 zijiexia 回应指出 CPU 部署指南预加载 tcmalloc/TBB malloc,malloc_trim 无效,且 gc.collect() 已在基类中定义为 no-op,当前实现已比原基类更主动。alexnails 同意添加注释说明潜在问题。
  • 架构标识设计:alexnails 要求将 CPU 架构作为一等属性体现。zijiexia 新增了 cpu_arch cached_property,从 get_cpu_architecture() 解析,用于 get_device_name() 输出架构标签。
  • get_device 的 NUMA 感知:alexnails 指出 CPU 上可以有多个 rank,建议后续支持 NUMA 感知的 rank 放置。zijiexia 添加了 TODO 注释。
  • empty_cache 的 allocator 局限:讨论确认当前 gc.collect() 在 tcmalloc/TBB malloc 下不会将内存返还 OS,但该问题属于低频清理路径,优先级低。

实现拆解

  1. 新建 CPU 平台实现:文件 python/sglang/srt/platforms/cpu.py,包含 CpuDeviceMixin(DeviceMixin)CpuSRTPlatform(CpuDeviceMixin, SRTPlatform)。CpuDeviceMixin 实现了 DeviceMixin 定义的全部 12 个方法,包括设备标识、内存查询(psutil)、empty_cache(触发 gc.collect())、synchronize(调用 torch.cpu.synchronize())等。CpuSRTPlatform 覆写了功能开关(supports_fp8 等返回 False)和 is_pin_memory_available(返回 False)。
  2. 更新平台发现逻辑:修改 python/sglang/srt/platforms/__init__.py,新增 _is_cpu_available() 检查环境变量 SGLANG_USE_CPU_ENGINE。在 _resolve_platform() 的自动发现分支中,当无插件激活时,首先检查 CPU 是否启用(优先生效),再检查 CUDA / ROCm,最后回退到抽象基类。该顺序允许开发者在 GPU 主机上显式测试 CPU 路径。
  3. 增加单元测试:在 test/registered/unit/platforms/test_platform_interface.py 中添加 TestCpuDeviceMixin 类(14 个测试),覆盖平台身份、设备返回、内存查询(psutil mock)、empty_cache 调用 gc.collectsynchronize 调用、分布式后端 gloo、架构分支(ARM/x86)、能力标志等。同时新增 3 个平台解析器测试(CPU 启用/未启用/同时有 CUDA 时的优先级)。
  4. 同步文档:更新 docs_new/docs/hardware-platforms/plugin.mdx 中的平台发现流程图,加入 CPU 回退路径,并同步已有 CUDA/ROCm 回退路径以反映实际 _resolve_platform() 逻辑。
文件 模块 状态 重要度
python/sglang/srt/platforms/cpu.py 平台层 added 8.87
python/sglang/srt/platforms/__init__.py 平台层 modified 6.7
test/registered/unit/platforms/test_platform_interface.py 平台测试 modified 7.09
docs_new/docs/hardware-platforms/plugin.mdx 文档 modified 2.38

关键符号

CpuDeviceMixin.get_device_total_memory CpuDeviceMixin.get_current_memory_usage CpuDeviceMixin.get_device CpuDeviceMixin.set_device CpuDeviceMixin.get_device_name CpuDeviceMixin.get_device_uuid CpuDeviceMixin.empty_cache CpuDeviceMixin.synchronize CpuDeviceMixin.cpu_arch CpuSRTPlatform.is_pin_memory_available _is_cpu_available _resolve_platform

关键源码片段

python/sglang/srt/platforms/cpu.py core-logic

核心新增文件:定义 CpuDeviceMixin 和 CpuSRTPlatform,实现 CPU 设备操作和平台类

import gc
import platform as _platform
from functools import cached_property
from typing import Optionalimport psutil
import torchfrom sglang.srt.platforms.device_mixin import (
    CpuArchEnum,
    DeviceCapability,
    DeviceMixin,
    PlatformEnum,
)
from sglang.srt.platforms.interface import SRTPlatform
​
​
class CpuDeviceMixin(DeviceMixin):
    """CPU 设备操作混合类,实现 DeviceMixin 抽象接口。"""
    _enum: PlatformEnum = PlatformEnum.CPU
    device_name: str = "cpu"
    device_type: str = "cpu"
​
    @cached_property
    def cpu_arch(self) -> CpuArchEnum:
        """主机 CPU 架构(X86 / ARM / UNSPECIFIED),进程内只解析一次。"""
        return self.get_cpu_architecture()
​
    def get_device_total_memory(self, device_id: int = 0) -> int:
        # 返回整机物理内存总量(字节),与 PSUTIL 一致
        return int(psutil.virtual_memory().total)
​
    def get_current_memory_usage(
        self, device: Optional["torch.device"] = None
    ) -> float:
        """整机已用内存(total - available),而非进程 RSS。
        该方法遵循 [Active] 契约:free = total - used 应当正确反映
        系统可用内存(psutil.available)。返回浮点字节数。
        """
        vm = psutil.virtual_memory()
        return float(vm.total - vm.available)
​
    def get_device(self, local_rank: int) -> "torch.device":
        # CPU 只有一个设备,忽略 local_rank;rank 隔离通过 numactl / OpenMP 绑定实现
        # TODO(zijiexia): 后续可支持 NUMA 感知的 rank 放置
        return torch.device("cpu")
​
    def set_device(self, device: "torch.device") -> None:
        # CPU 上为显式无操作;避免使用 torch.set_default_device("cpu") 改变全局默认张量设备
        torch.cpu.set_device(device)
​
    def get_device_name(self, device_id: int = 0) -> str:
        # 返回基于架构的简短描述,避免调用 platform.processor()(可能产生子进程)
        if self.cpu_arch == CpuArchEnum.ARM:
            return "cpu (aarch64)"
        if self.cpu_arch == CpuArchEnum.X86:
            return "cpu (x86_64)"
        return "cpu"
​
    def get_device_uuid(self, device_id: int = 0) -> str:
        # CPU 无设备 UUID,以平台架构字符串作为稳定主机标识
        return _platform.machine()
​
    def get_device_capability(self, device_id: int = 0) -> Optional[DeviceCapability]:
        return None
​
    def empty_cache(self) -> None:
        # CPU 上无 torch.cpu.empty_cache(),通过 GC 回收引用循环内存。
        # 注意:gc.collect() 的暂停时间随堆大小增长,且释放的内存在 tcmalloc /
        # TBB malloc 下不会返还给 OS,需后续通过 allocator 感知调用改进
        gc.collect()
​
    def synchronize(self) -> None:
        # CPU 无异步流,调用 torch.cpu.synchronize() 保持与 CudaDeviceMixin 的对称性
        torch.cpu.synchronize()

评论区精华

empty_cache 实现与 malloc_trim 讨论 性能

gemini-code-assist[bot] 建议调用 malloc_trim 释放内存回 OS。zijiexia 回应 CPU 部署通常预加载 tcmalloc/TBB malloc,malloc_trim 无效,且当前 gc.collect 已比基类 pass 更主动。alexnails 要求添加注释说明潜在问题。

结论:保留 gc.collect(),添加注释说明在 tcmalloc/TBB malloc 下 RSS 不降,未来可通过 allocator 感知改进。 · 已解决

get_device 的 NUMA 感知 设计

alexnails 指出 CPU 上可以有多个 rank,应支持 NUMA 感知的设备返回。zijiexia 解释当前所有 rank 共享单个 CPU 设备,隔离通过 OpenMP/numactl 绑定,并添加 TODO。

结论:添加 TODO 注释,后续可支持 NUMA 感知的 rank 放置。 · 已解决

CPU 架构作为一等属性 设计

alexnails 要求将 CPUArchEnum 在类中显式表示。zijiexia 新增 cached_property `cpu_arch`,从 `get_cpu_architecture()` 解析,用于 `get_device_name` 的架构分支。

结论:新增 `cpu_arch` 属性,避免重复调用 platform.machine()。 · 已解决

风险与影响

  • psutil 调用性能风险get_device_total_memoryget_current_memory_usage 每次调用都执行系统级查询(psutil.virtual_memory()),若被热路径频繁调用可能引入额外开销。当前 PR 未迁移任何运行时调用,风险仅存在于未来启用后。
  • gc.collect() 暂停:在空缓存、空闲休眠时调用 gc.collect() 可能因堆大小引入毫秒级暂停,但出现在清理路径,可接受。未使用 malloc_trim 可能导致 RSS 不降,但符合 CPU 指导(预加载 tcmalloc)。
  • 环境变量竞争SGLANG_USE_CPU_ENGINE=1 在 GPU 主机上会覆盖 CUDA/ROCm 回退,若用户无意设置可能导致异常行为。但该变量为显式 opt-in,风险可控。
  • 平台发现顺序改变:CPU 回退现在优先于 CUDA/ROCm,若未来有同等优先级的其他平台(如 XPU/HPU),可能需要调整排序逻辑。当前仅 CPU 使用环境变量 opt-in,无实际冲突。
  • 用户影响:对使用 CPU 引擎的用户(设置 SGLANG_USE_CPU_ENGINE=1)透明,现在 current_platform 返回 CpuSRTPlatform 而非抽象基类,empty_cache()pass 提升为 gc.collect(),内存查询从报错变为返回真实值。对其他用户无影响。
  • 系统影响:新增 cpu.py__init__.py 中的平台发现逻辑,测试套件扩展。无运行时性能影响,所有变更均为静态平台类定义和测试。
  • 团队影响:为后续迁移分布式后端(get_torch_distributed_backend_strgloo)、内存管理(is_pin_memory_available → False)等基础设施提供基础。其他设备(XPU/HPU/NPU/MUSA)可参考此模式添加自己的平台类。
psutil 调用可能引入热路径开销 gc.collect() 暂停时间随堆增长 环境变量意外设置导致 GPU 主机使用 CPU 路径 平台发现顺序变更可能影响未来多平台排序

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论