执行摘要
- 一句话:修复 RISC-V 平台检测与 lscpu 解析问题
- 推荐动作:值得精读的部分:
_get_cpu_list() 中 lscpu 输出的正则处理策略,以及如何优雅地处理不完全的拓扑信息。建议关注 _synthesize_cpu_list 的引入位置,它为后续可能出现的其他弱拓扑平台提供了复用基础。
功能与动机
RISC-V 架构在之前无法正确识别,平台解析为 UnspecifiedPlatform,且 lscpu 输出格式与 x86/ARM 不同,导致 CPU 资源初始化失败,阻碍 RISC-V 设备使用 vLLM。
实现拆解
- 提取合成 CPU 列表函数 (
vllm/utils/cpu_resource_utils.py):将原来 macOS 专用的合成逻辑抽取为 _synthesize_cpu_list(),返回每个逻辑 CPU 独占 core 且 NUMA node 为 0 的拓扑。该函数同时用于 lscpu 输出完全无效时的回退。
- 增强 lscpu 正则处理:将裸
- 字段分两步处理:先映射 "node": - 为 "node": 0(保持非 NUMA 系统行为),再将 "cpu"/"core": - 引用为字符串,使 JSON 解析时转为 -1,随后被过滤器剔除,触发回退。同时恢复了 --online 标志(之前被误删)。
- 非 NUMA meminfo 回退 (
get_memory_node_info):当 /sys/devices/system/node/node0/meminfo 不存在时(常见于 RISC-V 开发板),改用 psutil.virtual_memory() 返回系统级内存信息,避免 RuntimeError。
- 修正 setup.py 版本后缀:
get_vllm_version() 中 CPU 分支改用本地变量 VLLM_TARGET_DEVICE(已被自动检测修改),而非直接读取环境变量,确保 CPU-only 主机(如 RISC-V)正确得到 +cpu 后缀。
- 删除多余异常捕获 (
vllm/platforms/__init__.py):移除了 cpu_platform_plugin() 中的 except Exception 子句(仅一行),简化代码。
关键文件:
vllm/utils/cpu_resource_utils.py(模块 资源工具;类别 source;类型 core-logic;符号 _synthesize_cpu_list, _get_cpu_list, get_memory_node_info): 核心变更文件,包含 lscpu 解析增强、合成 CPU 函数提取、非 NUMA meminfo 回退,对 RISC-V 平台初始化至关重要。
setup.py(模块 构建脚本;类别 source;类型 core-logic): 修正版本后缀逻辑,确保 CPU-only 主机(如 RISC-V)正确获得 +cpu 后缀,是平台检测的关键一环。
vllm/platforms/__init__.py(模块 平台检测;类别 source;类型 core-logic): 删除了多余的异常捕获,简化代码(但在最终方案中 RISC-V 检测通过 setup.py 实现,此文件改动较小)。
关键符号:_synthesize_cpu_list, _get_cpu_list, get_memory_node_info, get_vllm_version
关键源码片段
vllm/utils/cpu_resource_utils.py
核心变更文件,包含 lscpu 解析增强、合成 CPU 函数提取、非 NUMA meminfo 回退,对 RISC-V 平台初始化至关重要。
# 从 _get_cpu_list 中提取出的合成函数,用于 macOS 和 lscpu 无效时回退
def _synthesize_cpu_list() -> list[LogicalCPUInfo]:
"""Synthesize a flat CPU list: each logical CPU is its own core on
NUMA node 0. Used when lscpu output is unavailable or unparsable
(e.g. macOS, RISC-V)."""
cpu_count = os.cpu_count()
assert cpu_count
# 每个逻辑 CPU 单独一个 core,NUMA 节点统一为 0(单节点)
return [LogicalCPUInfo(i, i, 0) for i in range(cpu_count)]
def _get_cpu_list() -> list[LogicalCPUInfo]:
if platform.system() == "Darwin":
# macOS 直接合成
return _synthesize_cpu_list()
lscpu_output = subprocess.check_output(
"lscpu --json --extended=CPU,CORE,NODE --online", shell=True, text=True
)
# 第一遍:将裸的 "node": - 替换为 "node": 0,保持非 NUMA 系统行为
lscpu_output = re.sub(r'"node":\s*-\s*(,|\n|\})', r'"node": 0\1', lscpu_output)
# 第二遍:将 "cpu" 或 "core" 字段的裸 - 引用为字符串 "-",
# 这样 JSON 解析后会变成 -1,后续过滤掉,触发合成回退
lscpu_output = re.sub(
r'("(?:cpu|core)":\s*)-\s*(,|\n|\})',
r'\1"-"\2',
lscpu_output,
)
logical_cpu_list: list[LogicalCPUInfo] = json.loads(
lscpu_output, object_hook=LogicalCPUInfo.json_decoder
)["cpus"]
# 过滤掉含有 -1 的无效条目(id, physical_core, numa_node 任一为 -1 则丢弃)
logical_cpu_list = [
x for x in logical_cpu_list if -1 not in (x.id, x.physical_core, x.numa_node)
]
# 如果没有任何有效条目(例如 RISC-V),则回退到合成拓扑
if not logical_cpu_list:
logical_cpu_list = _synthesize_cpu_list()
return logical_cpu_list
setup.py
修正版本后缀逻辑,确保 CPU-only 主机(如 RISC-V)正确获得 +cpu 后缀,是平台检测的关键一环。
elif _is_cpu():
# 使用本地变量 VLLM_TARGET_DEVICE(可能已被前面的 auto-detect 修改),
# 而不是直接读环境变量 envs.VLLM_TARGET_DEVICE,
# 这样 CPU-only 主机(包括 RISC-V)仍然能获取 +cpu 后缀
if VLLM_TARGET_DEVICE == "cpu":
version += f"{sep}cpu"
评论区精华
风险与影响
- 风险:
- 回归风险:正则修改可能影响其他架构的 lscpu 输出解析,但已通过分步处理和单元测试验证(作者提及对两个样本进行了测试)。
- 性能影响:非 NUMA 系统的 meminfo 回退使用 psutil,增加一次系统调用,但仅在 meminfo 文件缺失时才触发,影响极小。
- 兼容性:修改了
_get_cpu_list() 的返回路径,所有 Linux 平台均受影响。但合成回退只在无有效条目时触发,理论上不会改变现有行为。不过,如果某些平台的 lscpu 输出中包含部分有效条目,原有逻辑可能不再适用(但现有过滤逻辑未变)。
- 影响:
- 用户影响:RISC-V 设备用户现在可以正常使用 CPU 后端,不再因平台检测失败或初始化错误而无法启动。非 NUMA 系统(如部分 ARM 开发板)也受益于 meminfo 回退。
- 系统影响:代码变更集中在 CPU 资源工具函数,不影响 GPU 或其他加速器后端。
- 团队影响:低。该 PR 由外部贡献者提交,审核过程简洁,没有引发大量讨论。
- 风险标记:lscpu 解析正则回归风险, 非 NUMA 系统 meminfo 回退路径, 合成拓扑可能丢失核心信息
关联脉络
- PR #39781 [Platform] Fix non-NUMA node handling in lscpu parsing: 本 PR 中
"node": - 映射为 0 的逻辑继承自 #39781,并进一步增强。
- PR #40161 [Platform] Add --online flag to lscpu command: 本 PR 早期版本误删了 #40161 添加的
--online 标志,后在 review 中恢复。
参与讨论