简介:NCCL性能与正确性测试工具包内含NVIDIA集体通信库的官方测试套件源码,面向CUDA开发者、高性能计算工程师及AI训练框架调优人员,用于在单机多卡及多节点环境中验证集合通信操作的性能与正确性,尤其适合需要定量评估GPU通信瓶颈的中高级开发者。压缩包共15个文件,其中7个cu源文件覆盖all_reduce、broadcast、alltoall、reduce_scatter等典型算子测试逻辑,2个头文件提供公共接口与NCCL兼容声明,2个Makefile支撑灵活构建,2个Markdown文档与1个txt说明构成完整使用指引,整体仅27KB。目前已有4609人学习下载。读者可依照README说明,通过make并指定CUDA_HOME/NCCL_HOME完成本地编译,开启MPI选项后即可支持跨节点多进程扩展测试;测试运行还支持多线程及每线程多CUDA设备。借此可快速获得一套可编译的基准测试框架,在自有集群上逐项测量各算子的吞吐与延迟,为NCCL调优提供量化依据与正确性校验方案。
1. nccl-tests 到底在测什么:一张报告定位多卡通信瓶颈
nccl-tests 是 NVIDIA 官方发布的 NCCL 性能测试工具,它直接调用 NCCL 库把集合通信的带宽、延迟、正确性量化成一张表。多卡训练跑不满、带宽上不去,很多人第一反应是调 batch size、改学习率,折腾一圈发现瓶颈根本不在模型侧。只要在目标机型上跑一遍这套测试,问题到底出在 GPU 间的 PCIe/NVLink,还是多机间的网卡,几分钟就能看明白。这份资源是完整源码包,编译配置、测试脚本和常用参数都齐,适合做大模型训练、推理加速和多机多卡调优的工程师。
2. 编译安装与首跑:让 nccl-tests 在四卡机上出第一份报告
NCCL 是 NVIDIA 的集合通信库,它对 GPU 之间的数据搬运做了大量底层优化,包括 NVLink 直连、PCIe 直通、共享内存以及跨节点的 RDMA 路径选择。nccl-tests 不是库,而是一组独立客户端程序,通过调用 NCCL API 来测量 each 原语的实际吞吐。训练场景里最常见的 all-reduce(梯度同步),就是它的默认测试重点。
2.1 依赖与源码包结构
编译前先确认三样东西:CUDA Toolkit、NCCL 库及其头文件、gcc 和 make。这里最容易踩的坑是 NCCL 只装了运行库、没有 dev 包,导致编译时找不到 nccl.h。源码包解压后能看到 Makefile 和 src 目录,核心测试源码全部是.c文件,每个原语一个文件,结构非常直白。编译产物默认输出到 build/ 目录。
Makefile 在设计上允许你覆盖默认路径,这是整个编译过程唯一需要手动介入的地方。CUDA 装得比较规范的情况下/usr/local/cuda可以直接被找到;NCCL 如果是从官网单独下载的,通常解压到/usr/local/nccl,这时候必须显式指给 Makefile。
2.2 编译命令与常见路径修正
export CUDA_HOME=/usr/local/cuda export NCCL_HOME=/usr/local/nccl make CUDA_HOME=/usr/local/cuda NCCL_HOME=/usr/local/nccl -j$(nproc)逻辑说明:CUDA_HOME用来定位 nvcc 编译器和 CUDA runtime 头文件,NCCL_HOME用来定位nccl.h以及libnccl.so。两个变量缺一个都会在 include 或 link 阶段直接报错。如果你的 NCCL 是随 CUDA 一起安装的,那么把NCCL_HOME指向/usr/local/cuda也可以,因为 CUDA 安装目录里同样包含了 NCCL 的头文件和库文件,但独立安装时建议分开指定,避免版本混乱。
编译完成后检查 build/ 目录,正常情况下会出现六个可执行文件:all_reduce_perf、all_gather_perf、broadcast_perf、reduce_perf、reduce_scatter_perf、sendrecv_perf。其中all_reduce_perf和all_gather_perf使用频率最高,前者对应训练时的梯度同步,后者对应张量并行里的激活聚合。
提示:编译脚本默认只在单机单进程模式下工作。多机测试时编译本身不需要额外开关,启动环节用 mpirun 即可,但各节点需要预先装好 MPI 环境,并打通 SSH 免密登录。
2.3 首跑命令:单机四卡 all-reduce 全流程
cd /path/to/nccl-tests ./build/all_reduce_perf -b 8 -e 8 -f 2 -g 4 -c 1 -n 100 -w 25参数说明表格:
| 参数 | 取值 | 含义 |
|---|---|---|
| -b | 8 | 起始数据量,单位 MiB,这里从 8 MiB 开始测 |
| -e | 8 | 结束数据量,与起始一致表示只测单一档位 |
| -f | 2 | 每档之间数据量翻倍,多档位测试时用 |
| -g | 4 | 每节点参与测试的 GPU 数量 |
| -c | 1 | 开启结果校验,生产环境建议始终为 1 |
| -n | 100 | 每档位迭代次数 |
| -w | 25 | 先跑 25 轮预热,让 GPU 提升到稳态频率 |
逻辑说明:all_reduce_perf -b 8 -e 8 -f 2 -g 4的含义是,在四个 GPU 上对 8 MiB 数据反复做 all-reduce,跑 100 次取平均。预热轮次的必要性在于,GPU 初始频率和显存频率都偏低,直接计时会得到一个明显劣于真实水平的数字。8 MiB 这个档位对训练场景有特殊意义,很多模型的梯度张量在通信前被切块后,单块大小就落在几百 KiB 到几十 MiB 区间。
如果想一次看到完整曲线,我会这样跑:
./build/all_reduce_perf -b 8 -e 512 -f 2 -g 4 -c 1 -n 50 -w 10这条命令从 8 MiB 一直测到 512 MiB,共 7 个档位。小尺寸档位反映的是延迟敏感区,大尺寸档位反映带宽饱和区。通过对比不同尺寸下的带宽变化,能大致判断通信链路是否存在异常拐点。注意输出里的单位是 GiB/s(按 1024^3 计算),跟网卡宣传的 Gb/s(按 1000^3 计算)完全不是一回事,换算时别搞混。
3. 看懂输出:algbw 与 busbw 的换算才是测试关键
第一次跑完,终端会打出一张多列表格,很多人只扫一眼 size 和 algbw 就下结论,这是不够的。真正该盯的是 busbw,它直接反映底层链路被实际压榨出了多少性能。
3.1 输出字段逐项拆解
一个典型输出长这样(字段顺序可能随版本略有差异):
| 字段 | 含义 |
|---|---|
| nthreads | 每进程参与的线程数量 |
| dtype | 数据类型,默认 float |
| size | 单次通信的数据量(字节) |
| count | 元素个数,size 除以 dtype 大小 |
| time | 单次操作平均耗时(微秒) |
| algbw | 算法带宽,即 size / time |
| busbw | 总线带宽,经过数据放大修正后的值 |
其中 count 和 size 的关系是size = count × sizeof(dtype),默认 dtype 为 float 时每个元素占 4 字节。如果你在 512 MiB 档位看到 count 为 134217728,那正是 512 × 1024 × 1024 除以 4 得来的。time 是多次迭代的平均值,数值越小越好。
3.2 为什么 busbw 更值得关注:ring 算法的流量放大
algbw 计算的是「数据量除以耗时」,但集合通信在底层存在数据搬运放大。以最经典的 ring all-reduce 为例,每个 GPU 不仅要发送自己那部分数据,还要接收并转发其他人的数据,整个过程下来,单卡实际收发总字节数是单卡数据量的2 × (n-1) / n倍。NCCL 会根据拓扑和消息大小自动选择 ring、tree 或 split-tree 算法,不同算法放大系数略有差异,但核心逻辑一致:总线上的实际流量永远大于算法层面的数据量。
n = 4 algbw = 5.0 # 一个实测示例值,单位 GiB/s factor = 2 * (n - 1) / n busbw = algbw * factor print(f"{n}卡放大系数: {factor:.2f}, busbw: {busbw:.2f} GiB/s")逻辑说明:这段代码把换算关系展示得很直接。四卡场景放大系数是 1.5,八卡是 1.75,十六卡是 1.875。放大系数随卡数增加而增加,意味着你拿八卡的 algbw 去和四卡的 algbw 对比,会错误地得出「八卡更慢」的结论,实际上总线带宽可能是一样的。
| 卡数 | 放大系数 |
|---|---|
| 2 | 1.0 |
| 4 | 1.5 |
| 8 | 1.75 |
| 16 | 1.875 |
所以比较不同卡数、不同拓扑下的通信性能,一律用 busbw。这也是 nccl-tests 官方文档里强调的要点:busbw 才是衡量底层硬件互连能力的口径。
3.3 如何判断数字算不算正常
实测参考值大致是这样:单机八卡 A100,NVLink 双向聚合标称 600 GB/s 左右,all-reduce 大尺寸档位 busbw 通常能到 350 GiB/s 以上;小尺寸 8 MiB 因为延迟占比高,往往只有 100~200 GiB/s。如果是 PCIe Gen4 x16 直连的卡,busbw 大概在 20~30 GiB/s 封顶。多机场景 100 Gbps InfiniBand 的标称是 12.5 GB/s,实际跑出 10~11 GB/s 就是健康的。
需要强调的是,这些数字只是经验区间,不是硬性标准。异构机型、NVLink 拓扑、CPU 绑定策略都会影响最终数值。我在交付测试报告时,习惯先在同一套硬件上跑一遍基线存档,再去做环境变量或拓扑调整,这样每一次改动都能看到真实增量。
4. 多机压测与网卡选型:从单机到集群的完整测试路径
单机测试只能验证 GPU 之间的问题。多卡训练真正容易翻车的,是跨节点通信:IB 没走对、网卡选错、防火墙拦截、NCCL fallback 到 TCP,这些坑在单机测试里根本暴露不出来。多机测试的完整路径,应该是先验证单机基线,再叠加网络层验证。
4.1 用 hostfile 跑跨节点 all-reduce
多机测试前,先确认每台节点上的 NCCL 和 CUDA 版本一致,最好连 nccl-tests 二进制也保持同一次编译的产物。启动方式用 mpirun,hostfile 是启动的关键文件。文件内容按行写入节点名和槽位数,比如四节点、每节点四卡:
node01 slots=4node02 slots=4node03 slots=4node04 slots=4
然后执行:
mpirun --hostfile hostfile -np 16 \ -x NCCL_SOCKET_IFNAME=eth0 \ -x NCCL_DEBUG=INFO \ ./build/all_reduce_perf -b 8 -e 8 -f 2 -g 4 -c 1 -n 50 -w 10逻辑说明:-np 16是总进程数,对应四节点 × 每节点四卡。-g 4告诉 nccl-tests 每个节点上同时参与通信的 GPU 数量。这里的关键是-g的取值必须与 hostfile 中每节点的槽位数一致,否则 NCCL 无法正确判断节点边界,会导致跨节点算法选择错误。NCCL_SOCKET_IFNAME指定对外通信的物理网卡,多网卡机器上不设这个,NCCL 可能选到管理口,性能和连通性都会出问题。
参数说明:先把NCCL_DEBUG设为 INFO,是为了看清 NCCL 内部到底选择了哪条通信路径。日志量大也无妨,第一轮测试就是为了排查,等一切正常后可以降回 WARN。
4.2 确认通信链路:IB 还是 TCP,一眼分辨
NCCL_DEBUG=INFO 跑完后,日志里会出现关键连接信息,典型片段长这样:
[0] NCCL INFO NET/IB : Connected [0] - ib0/1 [0] NCCL INFO Using network IB如果看到Using network IB,说明跨节点流量走的是 InfiniBand,理论带宽上限高很多;如果看到NET/Socket或Using network Socket,说明走了 TCP/IP 协议栈,大尺寸通信性能会明显受限。有些场景下 IB 链路建立失败,NCCL 会自动静默回退到 TCP,这时候你看到的数字会奇低无比。
日志解读:关键搜索词是Using network、NET/IB、NET/Socket。观察到NET/IB出现但随后又有NET/Socket回退记录,优先检查 IB 设备的 GID 配置、子网管理器是否正常、以及端口是否被防火墙拦截。
4.3 多网卡环境变量组合与排查思路
常用环境变量我按优先级整理成一个组合,多网卡场景基本一次到位:
| 环境变量 | 作用 |
|---|---|
| NCCL_SOCKET_IFNAME | 指定 socket 通信使用的网卡,多个网卡用逗号分隔 |
| NCCL_IB_HCA | 指定 IB 设备,比如mlx5_0:1,格式是设备名:端口 |
| NCCL_IB_DISABLE | 设为 1 强制走 TCP,用于对比验证 IB 的收益 |
| NCCL_P2P_DISABLE | 设为 1 关闭 GPU 间 P2P,排查 NVLink 问题时用 |
| NCCL_DEBUG | INFO 看全量日志,WARN 只看警告,VERSION 只看版本 |
我常用的验证思路是三层递进:先单机多卡跑一遍,确认本机 NVLink/PCIe 没问题;再双节点各一张卡对测,确认网络基本通路;最后上全量节点跑完整带宽。这样哪一层出问题都很容易定位。如果跨节点带宽远低于预期,先加NCCL_DEBUG=INFO看链路类型,再用NCCL_IB_DISABLE=1强制走 TCP 复跑一次对照,两组数字差异不明显,问题大概率不在网络层,而在拓扑感知或 CPU 绑核上。
5. 避坑清单:几十次压测里记下的高频翻车点
压测做多了,翻车点基本集中在版本匹配、路径指定、链路选择和校验开关上。下面五条是我认为出现频率最高的,每条都按现象、原因、解决三层记录。
5.1 编译报错:找不到 nccl.h
现象:make 过程中提示nccl.h: No such file or directory,或者 link 阶段报cannot find -lnccl。
原因:最常见的是 NCCL 只装了 runtime 包,没有装 dev 开发包;另一种是把NCCL_HOME指到了错误目录,头文件不在预期的 include/ 子目录下。
解决:先用find / -name nccl.h 2>/dev/null定位头文件真实位置,再把NCCL_HOME指向其上层目录。例如头文件在/usr/include/nccl.h,那么export NCCL_HOME=/usr即可。如果头文件能找到但 lib 找不到,说明库路径没进LD_LIBRARY_PATH,这种情况我会直接export LD_LIBRARY_PATH=$NCCL_HOME/lib:$LD_LIBRARY_PATH再重新 make。
5.2 单机结果远低于 NVLink 标称
现象:八卡 A100 实测大尺寸 busbw 只有 200 GiB/s 上下,跟 NVLink 标称的 600 GB/s 差距很大。
原因:标称值是多条 NVLink 双向聚合的理论峰值,ring all-reduce 本身还有流量放大,还要扣掉协议开销和动态功耗限制。更隐蔽的原因是 CPU 绑核不对,进程被调度到离 GPU 较远的 NUMA 节点,跨 QPI/UPI 的访存延迟把通信拖慢。
解决:先跑nvidia-smi topo -m看 GPU 拓扑,再把进程用taskset绑到 GPU 所在 NUMA 节点。测试前用nvidia-smi -pm 1打开持久模式,把无关负载清理干净。另外对比数字时必须同尺寸、同迭代次数,否则没有意义。
5.3 多机带宽比单机还慢
现象:四节点 IB 集群,跨节点 all-reduce 只有 1~2 GB/s,比单机 NVLink 慢了一个数量级。
原因:链路走了 TCP 而不是 IB;或者NCCL_SOCKET_IFNAME指到了管理网口,业务网络根本没被启用;也可能是 IB 设备名写错,NCCL 自动 fallback 到 socket。
解决:用NCCL_DEBUG=INFO确认日志里的Using network到底选了什么。确实是 IB 环境就显式设NCCL_IB_HCA,不要依赖自动探测。RoCEv2 环境还要额外检查交换机的 PFC/ECN 配置,普通交换机跑 RoCE 丢包率一上去,性能会剧烈抖动。
5.4 校验开关忘开,小尺寸测不出的问题
现象:8 MiB 档位数据一切正常,但到 64 MiB 以上结果异常,有时甚至直接报错,而之前的测试报告里没有任何异常提示。
原因:nccl-tests 默认-c 0不做数据校验,只要操作没崩就能出结果。某些内存对齐错误或算法路径 bug 只在数据量变大时触发,不校验就完全黑匣子。
解决:任何正式对比或交付前,先跑一轮-c 1的完整档位测试,确认所有 size 全部通过。如果 error 字段出现非零值,先查 NCCL 版本和驱动版本匹配情况,再查超频与降频设置,不要继续用这套环境跑基准。
5.5 版本与调优开关导致数字不可比
现象:同一套硬件,半个月前后两次测试结果差了 30%,但没人动过拓扑和参数。
原因:NCCL 版本升级了、nccl-tests 版本不同、或者环境变量被 profile 脚本悄悄注入了调优参数。NCCL 内部有自动调优器,会根据拓扑和消息大小自动选择算法和轮数,这会让输出结果随版本漂移。
解决:测试报告里强制记录 nccl-tests 版本、NCCL 版本、驱动版本、CUDA 版本和完整命令行参数。做对比时锁定同一套版本,必要时加-z 1关闭自动调优,让算法选择固定下来。从那以后我每次出报告都先跑nvidia-smi和nccl-tests --version存档,避免版本漂移带来的玄学差异。
6. 进阶用法:用 nccl-tests 给 llama.cpp 多卡推理做通信预检
6.1 匹配 llama.cpp 的实际通信尺寸
llama.cpp 的 ggml-cuda 在张量并行模式下,跨卡同步的数据块通常落在几 MiB 到几十 MiB 区间,和训练场景动辄几百 MiB 的梯度同步不一样。这时候照搬大尺寸测试结论意义不大,正确做法是跑一个贴合实际负载的小尺寸集:
./build/all_reduce_perf -b 1 -e 64 -f 2 -g 2 -c 1 -n 200 -w 50逻辑说明:-b 1 -e 64 -f 2覆盖 1 MiB 到 64 MiB 七个档位,-n 200提高迭代次数以压低随机噪声,-g 2对应双卡张量并行。如果这个区间内 busbw 能接近链路标称的六成以上,通信基本不是瓶颈;如果数值始终上不去,再去排查拓扑和网络参数才有意义。
6.2 建立基线文件并做 diff
调参时最怕凭记忆对比。每次测试都把 stdout 落盘,形成可追溯的基线:
./build/all_reduce_perf -b 8 -e 64 -f 2 -g 2 -c 1 -n 200 -w 50 \ | tee logs/nccl_$(date +%Y%m%d_%H%M%S).log逻辑说明:tee把结果同时输出到屏幕和日志文件,文件名带时间戳方便归档。下次调整环境变量后,用一个简单的 diff 或直接比较 busbw 列,就能看出改动带来的实际收益,而不是靠印象下结论。
6.3 最后一公里
经验是,多卡推理性能不佳时,先用 nccl-tests 做一分半钟的快速预检,能省下大量排查时间。真正棘手的问题往往不是通信库本身,而是路径配置、版本匹配和网卡选型这些外围因素,而 nccl-tests 恰好能把它们一次性暴露出来。从那以后我每次调多卡推理,都强制先跑一遍这份测试,把单机、多机两份基线存好,再动手改任何环境变量。希望帮到你。
本文还有配套的精品资源,点击获取