AI服务器GPU拓扑感知调度:NUMA与NVLink绑定实战
多卡训练时GPU利用率上不去、跨卡通信慢,很多时候不是GPU本身的问题,而是进程被调度到了错误的CPU和GPU组合上。
AI服务器GPU拓扑感知调度要做的,就是先看清物理连接关系,再让进程落在同一NUMA节点和高速互联范围内。
先读懂拓扑:GPU挂在哪颗CPU、哪条NVLink上
在动手绑定前,先用两条命令确认硬件拓扑。
登录服务器后执行:
nvidia-smi topo -m
lscpu | grep -E "NUMA|Socket|Core"
nvidia-smi topo -m 输出的矩阵里,NV# 表示GPU之间走NVLink,SYS 表示跨NUMA节点走PCIe和UPI,NODE 表示同一NUMA节点。
比如某张卡与GPU0显示 NV12,说明它们之间有12条NVLink链路,应尽量让通信频繁的进程落在这一组卡上。
再看 lscpu 输出的NUMA节点分布:每个NUMA节点对应一组CPU核心,GPU的PCIe总线通常挂在其一节点下。
用以下命令查看GPU的PCI总线与NUMA归属:
nvidia-smi --query-gpu=index,pci.bus_id --format=csv
cat /sys/bus/pci/devices/0000:3b:00.0/numa_node
把总线ID替换成你实际的GPU总线地址。
返回值如 0 表示该GPU位于NUMA节点0。
记录每张卡的NUMA节点,后面绑定进程时按这个对应关系来。
手动绑定:用taskset把进程锁在对应CPU核上
如果不用集群调度器,最简单的方式是 taskset。
假设GPU3挂在NUMA节点1,节点1的CPU核心范围是16-31,启动训练进程时绑定:
taskset -c 16-31 python train.py --gpus 3
同时用 CUDA_VISIBLE_DEVICES=3 限定进程只看到GPU3,避免框架自行选择其他卡:
CUDA_VISIBLE_DEVICES=3 taskset -c 16-31 python train.py
多进程训练(如PyTorch DDP)时,每个进程分别绑定到自己GPU所在NUMA节点的核心范围,不要把所有进程绑到同一组核上,否则会互相抢CPU。
集群环境:让Slurm按拓扑自动分配
如果使用Slurm管理GPU节点,可以在节点配置中启用拓扑感知。
在 slurm.conf 中确认包含:
SelectType=select/cons_tres
SelectTypeParameters=CR_Core_Memory
TaskPlugin=task/affinity,task/cgroup
提交作业时用 --gpus-per-task 和 --ntasks-per-node 让Slurm按GPU与CPU的亲和关系分配资源。
提交后检查作业实际绑定:
scontrol show job | grep -i "cpu\|gpu"
如果发现进程仍跨NUMA节点,检查节点是否启用了 task/affinity 插件,以及GPU是否被正确识别到GRES中。
常见坑:绑定后性能反而下降
- 绑错核心范围:不同型号服务器NUMA节点对应的核心编号不同,不能照搬示例,必须用
lscpu实际确认。 - 忽略超线程:
lscpu中同一物理核的两个逻辑CPU都绑给进程可能引发争抢,建议只绑物理核。 - 只绑GPU不绑CPU:数据加载线程仍可能被调度到远端NUMA节点,造成内存访问延迟。
- 容器内绑定失效:Docker默认不继承taskset设置,需加
--cpuset-cpus参数,例如docker run --cpuset-cpus="16-31" --gpus '"device=3"' ...。
验证绑定是否生效
进程启动后,用以下命令确认CPU亲和性:
taskset -cp
输出应只包含目标核心范围。
再查看GPU进程与拓扑:
nvidia-smi topo -m
nvidia-smi --query-compute-apps=pid,gpu_bus_id --format=csv
对比进程绑定的CPU与GPU所在NUMA节点是否一致。
如果一致,说明拓扑感知调度生效。
可以进一步用 numastat -p 查看进程内存是否主要落在本地NUMA节点,Numa_hit 比例高说明本地访问正常。
拓扑感知调度不是一次性配置,换卡、换主板或升级驱动后都应重新核对拓扑。
建议把上述查询命令写成巡检脚本,在每次调整训练任务前跑一遍,避免凭经验绑定。
相关问题
问题1:nvidia-smi topo -m显示SYS,是否必须避免跨节点?
不一定。跨NUMA节点通信并非不可用,只是延迟更高。如果任务对通信不敏感,或GPU数量不足以在单节点内分配,可以接受SYS。但对AllReduce频繁的训练任务,优先选择NVLink和同NUMA组合。
问题2:容器里看不到GPU的NUMA信息怎么办?
容器默认只挂载部分设备节点。启动时加 --privileged 或显式挂载 /sys/bus/pci/devices 相关路径,也可以直接在宿主机上用 nvidia-smi topo -m 查看后,把绑定参数传给容器。
问题3:Slurm没有按拓扑分配,如何排查?
先确认 slurm.conf 中 TaskPlugin 包含 task/affinity,再检查节点GRES配置是否把GPU与NUMA节点关联。用 slurmd -C 查看节点上报的拓扑信息,若GPU未出现在GRES中,需要重启slurmd并检查 gres.conf。