4、单机多卡测试:使用 NCCL Tests 测试单机内 GPU 间 P2P 带宽(NVLink/NVSwitch),理解拓扑对带宽的影响

好,咱们进入实战环节。

单机多卡测试,说白了就是看看你机器里那几张 GPU 互相通信到底有多快。我见过不少团队,模型跑不起来就怪代码,结果一查,是 GPU 之间带宽没跑满。嗯,这节咱们就用 NCCL Tests 这把尺子,把带宽量明白。

4.1 为什么单机内 P2P 带宽这么重要?

你想想看,大模型训练时,数据并行、张量并行、流水线并行……哪个不得频繁地在 GPU 之间搬数据?

  • 数据并行:每张卡算完梯度,得 AllReduce 一下。这步慢了,整卡都在等。
  • 张量并行:每层计算拆到多张卡上,前向反向都得来回传激活值和梯度。
  • 流水线并行:虽然通信量小点,但 stage 之间也得传数据。

所以,P2P 带宽就是多卡训练的命脉。带宽不够,算力再强也白搭。

核心观点:单机内 GPU 间 P2P 带宽,直接决定了多卡扩展效率的上限。

4.2 NCCL Tests 是什么?

NCCL Tests 是 NVIDIA 官方提供的一套微基准测试工具。它专门用来测试 NCCL(NVIDIA Collective Communications Library)的各种集合通信操作性能。

我个人习惯用它来验证三件事:

  1. 带宽是否达标:比如 A100 配 NVLink 3.0,理论单向 600 GB/s,实际能跑多少?
  2. 拓扑是否合理:同一台机器里,卡 0 和卡 1 快,卡 0 和卡 7 慢,为什么?
  3. 驱动/NCCL 版本是否正常:有时候升级了驱动,带宽反而掉了,一测便知。

4.3 编译与运行 NCCL Tests

先把它编译出来。过程很简单:

# 下载源码
git clone https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests

# 编译(假设你已经装好了 CUDA 和 NCCL)
make

# 编译完成后,会生成一堆可执行文件,比如 all_reduce_perf, sendrecv_perf 等

小提示:如果你用的是容器,记得把 NCCL 库挂载进去。我遇到过有人编译好了,跑的时候报错找不到 libnccl.so,其实就是环境变量没配好。

编译完,咱们先跑个最简单的 点对点(P2P)带宽测试:

# 测试 GPU 0 到 GPU 1 的双向带宽
./sendrecv_perf -b 8 -e 128M -f 2 -g 0 -g 1

参数说明:

  • -b 8:起始消息大小 8 字节
  • -e 128M:结束消息大小 128 MB
  • -f 2:步进因子,每次翻倍
  • -g 0 -g 1:指定 GPU 0 和 GPU 1

输出大概长这样:

#                                                      out-of-place                       in-place
#       size         count      type   redop    root     time   algbw   busbw
#        (B)    (elements)                       (us)  (GB/s)  (GB/s)
         8             2     float     none      -1    12.34    0.00    0.00
        16             4     float     none      -1    12.45    0.00    0.00
        ...
     134217728    33554432     float     none      -1   210.45  637.89  637.89

注意看最后一行,busbw 就是实际的双向带宽。对于 A100 配 NVLink 3.0,单条链路双向 600 GB/s,这里跑到 637 GB/s 说明正常(因为还有 NVSwitch 的加成)。

4.4 理解拓扑对带宽的影响

为什么我要强调拓扑?

我举个例子。在 DGX A100 里,8 张 GPU 通过 6 个 NVSwitch 全互联。理论上任意两张卡之间带宽都一样。但如果你用的是普通服务器,比如 4 张卡插在 PCIe 上,那卡 0 和卡 1 可能走同一个 PCIe Root Complex,带宽高;卡 0 和卡 3 可能要走 QPI 跨 CPU,带宽直接砍半。

咱们用 nvidia-smi topo -m 看看拓扑:

nvidia-smi topo -m

输出示例:

        GPU0    GPU1    GPU2    GPU3    GPU4    GPU5    GPU6    GPU7    mlx5_0
GPU0     X      NV1     NV1     NV2     NV2     SYS     SYS     SYS     NODE
GPU1    NV1      X      NV1     NV2     NV2     SYS     SYS     SYS     NODE
GPU2    NV1     NV1      X      NV2     NV2     SYS     SYS     SYS     NODE
GPU3    NV2     NV2     NV2      X      NV1     SYS     SYS     SYS     NODE
GPU4    NV2     NV2     NV2     NV1      X      NV1     SYS     SYS     NODE
GPU5    SYS     SYS     SYS     SYS     NV1      X      NV1     NV2     NODE
GPU6    SYS     SYS     SYS     SYS     NV1     NV1      X      NV2     NODE
GPU7    SYS     SYS     SYS     SYS     NV2     NV2     NV2      X      NODE

这里 NV1、NV2 表示通过 NVLink 连接,SYS 表示要通过系统总线(PCIe/QPI)。你看 GPU0 和 GPU1 是 NV1,带宽高;GPU0 和 GPU5 是 SYS,带宽低。

避坑指南:我曾经遇到过一台机器,跑 AllReduce 总是比预期慢 30%。查了半天,发现是两张卡虽然物理上插在同一个 CPU 的 PCIe 槽上,但 BIOS 里把 PCIe 拆分成了 x8 模式。带宽直接减半。所以,测完拓扑后,别忘了用 nvidia-smi -q -d PCI 确认一下链路宽度。

4.5 全互联带宽测试:AllReduce

点对点测完了,咱们再测一下集合通信。最常用的就是 AllReduce:

# 测试 8 卡 AllReduce 带宽
mpirun -np 8 ./all_reduce_perf -b 8 -e 128M -f 2 -g 8

这里 -g 8 表示使用 8 张 GPU。输出会显示不同消息大小下的带宽。重点关注大消息(比如 128MB)的带宽,因为大模型训练时梯度同步都是大块数据。

我一般会对比两个值:

  • 理论峰值:比如 8 卡 A100,NVLink 全互联,理论 AllReduce 带宽约 600 GB/s * (8-1)/8 ≈ 525 GB/s(算法开销)。
  • 实测值:如果实测只有 400 GB/s,那就要排查了——是 NCCL 算法没选对?还是拓扑有瓶颈?

4.6 用 SVG 图理解拓扑与带宽的关系

下面这张图,是我自己总结的「单机多卡带宽测试决策流程」。你照着走一遍,基本不会漏掉关键点。

单机多卡带宽测试决策流程 1. 检查拓扑 2. 编译 NCCL Tests 3. 跑 P2P 测试 4. 跑 AllReduce 测试 5. 对比理论值 nvidia-smi topo -m make sendrecv_perf all_reduce_perf 是否达标? 注意: 检查 PCIe 链路宽度 确认 NVLink 是否启用

4.7 常见问题与排查思路

跑完测试,如果发现带宽不对,别慌。我按优先级列一下排查步骤:

  1. 检查 NCCL 版本:nccl-tests --version。太老的版本可能有 bug。
  2. 检查环境变量:NCCL_DEBUG=INFO 打开调试日志,看它选了哪个算法(Ring 还是 Tree)。Ring 适合小消息,Tree 适合大消息。
  3. 检查 GPU 频率:nvidia-smi -q -d CLOCK。如果 GPU 降频了,带宽也会掉。
  4. 检查 NUMA 绑定:用 numactl --cpunodebind=0 --membind=0 把进程绑到和 GPU 同一个 NUMA 节点上。跨 NUMA 访问会损失带宽。

一句话总结:单机多卡测试,先看拓扑,再测 P2P,最后跑 AllReduce。每一步都对照理论值,偏差超过 10% 就要深挖。

好了,这节的内容就到这。你按这个流程走一遍,基本能把单机内的 GPU 带宽摸得清清楚楚。