CPU 绑定#
概述#
CPU 绑定是针对 ARM 服务器上 vLLM 工作进程的 Ascend 原生主机端优化。从 vllm-ascend v0.18.0rc1 开始,通过 enable_cpu_binding=True 默认启用。
该功能不会改变模型执行逻辑或数值结果。它仅在主机环境允许时,控制工作进程、关键运行时线程、内存页和 NPU IRQ 的 CPU 放置。通过将主工作线程、ACL 线程和释放线程保持在专用 CPU 范围内,有助于减少繁忙主机上因调度器抢占导致的上下文切换开销。
为什么需要 CPU 绑定?#
在多插槽 ARM 系统上,Linux 调度器可能将工作线程放置在远离工作进程所驱动 NPU 的 CPU 上。这会增加跨 NUMA 流量、增加线程抢占并引入延迟抖动。因此,Ascend 后端拥有自己的 CPU 分配策略,旨在减少跨 NUMA 流量、减少线程抢占并提高延迟稳定性,而不是依赖上游 GPU NUMA 绑定标志。
这也是上游 NUMA 标志在 Ascend 上被适配的原因:
--numa-bind被转换为additional_config={"enable_cpu_binding": true}。--numa-bind-nodes和--numa-bind-cpus被忽略,因为 Ascend 根据 NPU 拓扑或全局逻辑 NPU ID 计算 CPU 池。
工作原理#
分配器根据运行时主机状态推导其计划:
输入 |
来源 |
用途 |
|---|---|---|
允许的 CPU |
|
唯一有资格绑定的 CPU。容器 cpuset 会被尊重。 |
逻辑 NPU 映射 |
|
将卡/芯片 ID 映射到全局逻辑 NPU ID,并提供 |
运行中的 NPU |
|
识别此工作进程使用的逻辑 NPU。 |
拓扑亲和性 |
|
为 |
CPU NUMA 映射 |
|
用于将单 NUMA 亲和性池扩展到下一个 NUMA 节点。 |
策略选择#
绑定策略根据 Ascend 设备类型选择:
设备类型 |
策略 |
原因 |
|---|---|---|
A3 |
|
A3 使用 HCCS 卡间互联。每个 NPU 与所有 NUMA 节点的距离几乎相等,因此没有强烈的 NPU 到 NUMA 亲和性信号。基于全局逻辑 NPU ID 的分片提供了确定性的、不重叠的 CPU 池,以及工作进程间的 CPU/NUMA 隔离。 |
A2、Atlas 300 推理产品及其他非 A3 设备类型 |
|
A2 和 Atlas 300 推理产品通过 |
如果选择了 topo_affinity 但拓扑亲和性不可用,分配器将回退到 global_slice。
CPU 池构建#
global_slice#
global_slice 专为 A3 设计。由于 A3 的 HCCS 互联使得每个 NPU 到每个 NUMA 节点的距离几乎相同,拓扑亲和性不是一个有用的放置信号。因此,分配器根据全局逻辑 NPU ID 对排序后的 allowed_cpus 列表进行分区。
按以下顺序确定
total_npus:来自
npu-smi info -m的total_logic_npus拓扑亲和性条目数量
运行中 NPU 数量
计算:
base = len(allowed_cpus) // total_npusextra = len(allowed_cpus) % total_npus
每个逻辑 NPU 获得一个确定性的分片:
NPU ID
< extra获得base + 1个 CPU。其余 NPU ID 获得
base个 CPU。
只有运行中的 NPU 会被实例化到
npu_cpu_pool中。
这是关键特性:两个独立的工作进程,即使具有相同的 cpuset 但不同的可见 NPU ID,仍然获得不重叠的 CPU 池,因为两个进程都针对相同的全局 NPU ID 空间进行分片。使用 NUMA 对齐的 cpuset,这也提供了工作进程间的 CPU/NUMA 隔离,因此一个工作进程不会与另一个工作进程共享相同的 CPU 或 NUMA 分片。
global_slice 要求 base >= 5,因为每个 NPU 池保留:
2 个 CPU 用于 SQ/CQ IRQ 绑定
至少 1 个 CPU 用于主工作线程
1 个 CPU 用于 ACL 线程
1 个 CPU 用于释放线程
topo_affinity#
topo_affinity 专为 A2、Atlas 300 推理产品及其他非 A3 设备类型设计。A2 和 Atlas 300 推理产品暴露了有意义的 NPU 到 CPU 亲和性信息,因此分配器在可用时从 NPU 拓扑亲和性开始,然后避免共享亲和性组的重叠。
从所有逻辑 NPU 构建候选 NPU:
始终包含运行中的 NPU
仅当非运行中 NPU 的亲和性与该进程的允许 cpuset 重叠时才包含它们
对于每个候选 NPU,将拓扑亲和性与
allowed_cpus取交集。如果某个候选 NPU 的交集为空,则此 rank 的绑定失败。
如果亲和性 CPU 都在一个 NUMA 节点上,则使用来自下一个 NUMA 节点的 CPU 扩展池,受
allowed_cpus约束。将具有相同扩展池的 NPU 分组,并在该组内均匀分割每个共享池。
在最终的
npu_cpu_pool中仅保留运行中的 NPU。
非运行候选步骤是有意设计的。它防止两个独立的单卡工作进程在它们可见的NPU共享相同拓扑亲和性时选择相同的CPU范围。
角色拆分#
构建CPU池后,分配器按角色进行拆分:
角色 |
CPU |
|---|---|
SQ/CQ中断 |
|
主工作进程及子线程 |
|
ACL线程 |
|
释放线程 |
|
如果最终池中的CPU少于5个,则此rank的绑定失败,工作进程将从调用者处记录警告。
条件性主机调优#
应用CPU亲和性后,当环境支持时,CPU绑定还可以执行两个主机端调优步骤:
内存迁移使用
migratepages将工作进程的现有页面移动到选定的NUMA节点。这使工作进程更接近其读取的内存,并减少远程NUMA内存读取延迟。当
/proc/irq可写且IRQ文件可解析时,IRQ绑定将NPU中断处理放置在为相应NPU保留的CPU上。
这些是CPU绑定的条件性部分,而非独立的功能开关。如果缺少主机前提条件,该步骤将被跳过,而CPU线程绑定仍会继续。缺少migratepages仍可能使页面留在远程NUMA节点上,因此与完整的CPU绑定设置相比,延迟或吞吐量可能会下降。
示例#
具有640个CPU和16个NPU的A3推理服务器#
输入:
allowed_cpus = [0..639]total_logic_npus = 16running_npu_list = [0..15]
计算:
base = 640 // 16 = 40extra = 0驱动逻辑NPU
i的工作进程i接收CPU切片[i * 40 .. i * 40 + 39]。
全局切片视图:
CPU range: 0 639
|-- worker0/NPU0 --|-- worker1/NPU1 --| ... |-- worker15/NPU15 --|
| 0-39 | 40-79 | ... | 600-639 |
每个工作进程切片内的角色拆分:
40-CPU worker slice
| IRQ CPUs | main worker process and subthreads | ACL thread | release thread |
| c0-c1 | c2-c37 | c38 | c39 |
具体示例:
工作进程 |
逻辑NPU |
CPU池 |
中断CPU |
主CPU |
ACL CPU |
释放CPU |
|---|---|---|---|---|---|---|
0 |
0 |
0-39 |
0-1 |
2-37 |
38 |
39 |
1 |
1 |
40-79 |
40-41 |
42-77 |
78 |
79 |
... |
... |
... |
... |
... |
... |
... |
15 |
15 |
600-639 |
600-601 |
602-637 |
638 |
639 |
即使不同工作进程共享相同的cpuset,此布局也保持确定性,因为切分是基于全局逻辑NPU ID的。
日志#
分配器记录所选模式和分配计划:
[cpu_bind_mode] mode=topo_affinity rank=0 visible_npus=[0]
The CPU allocation plan is as follows:
NPU0: main=[...] acl=[...] release=[...]
限制#
CPU绑定仅在ARM上运行。在x86_64上跳过。
每个最终 NPU 池必须至少有 5 个 CPU。
global_slice是确定性的,当 cpuset 与 NUMA 对齐时提供 CPU/NUMA 隔离,但当 CPU 编号或 cpuset 布局跨越 NUMA 边界时,无法保证 NUMA 本地池。topo_affinity依赖于npu-smi info -t topo的可用输出。IRQ 绑定需要可写的
/proc/irq和可解析的 PCI/IRQ 信息。内存迁移需要
migratepages;否则仅跳过内存迁移。CPU 亲和性仍然生效,但性能可能下降,因为现有页面不会移动到目标 NUMA 节点,可能通过更高延迟的远程 NUMA 访问读取。如果绑定流程中发生异常,
NPUWorker记录警告并跳过该 rank 的 CPU 绑定。
参考#
实现:
vllm_ascend/cpu_binding.pyWorker 集成:
vllm_ascend/worker/worker.py配置:
vllm_ascend/ascend_config.py和docs/source/user_guide/configuration/additional_config.md测试:
tests/ut/device_allocator/test_cpu_binding.py