快速上手NCCL
#
# NCCL——NVIDIA Collective Communication Library(英伟达集合通信库)
传奇牢ccl,在英伟达显卡横行的时代成为了一众网络工作者持续优化的对象,为的就是在通信和计算的(巴巴)博弈中,取得胜利;
以上是作者自己的废话,请勿当真()咳咳咳,书接上回!上次说到啊,一块GPU它放不下那么大的模型,要么就是用什么奇技淫巧放下了模型之后呢效率太低(详细可见大模型并行那些事:dp/pp/tp/ep/cp)所以,我们就需要——并行!那么并行了之后,几个机子就需要通信!而NCCL能够自动识别服务器内部的拓扑结构(比如 NVLink)以及服务器之间的网络(比如 InfiniBand 或 RoCE),并选择最优路径进行数据传输,总之就是在可能的范围内将通信性能压榨到极致!
不过今天这篇帖子不是来讲解它的源码的——因为博主自己也不是很懂其实x
主要是来讲解一下怎么使用nccl,从而在一个未接触的集群上快速测试其网络连通性
这边先给出nccl源码 (opens new window)和它的小跟班nccl-test套件 (opens new window)
在一个陌生的集群上进行 NCCL 测试,是排查网络瓶颈、验证 RDMA/RoCE 配置是否生效的标准动作。
为了确保测试结果准确,建议按照以下 4 个阶段 循序渐进地进行 👇
# 第一阶段:环境准备与编译
在测试前,你需要获取 NVIDIA 官方提供的测试工具 nccl-tests。
- 确认环境:确保已安装
CUDA、NCCL库以及MPI(用于跨机启动)。- 什么是
CUDA呢?这里指的是一整套开发环境,NVIDIA CUDA Toolkit,包括:- nvcc (CUDA Compiler):这是最关键的。make 命令会调用它将 C++ 代码编译成能在 GPU 上运行的二进制文件。
- CUDA Runtime Library (libcudart.so):程序运行时管理 GPU 内存申请、流(Stream)同步的基础库。
- Header Files (.h):位于 include/ 目录,定义了 GPU 编程的接口。
- nvidia-smi:虽然不参与编译,但它是你检查 GPU 状态、驱动版本、显存占用的必备工具
- MPI 是跨机通信的标准协议,在 nccl-tests 中,MPI 并不负责搬运 GPU 数据(那是 NCCL 的活),它负责多机任务的启动和同步;那这话啥意思呢?简单来说,你可以理解为你如果想在多机跑的话,那你肯定需要在每台机子上都启动一个或者多个进程对吧,你当然可以手动的在每个服务器上输入命令行开启进程,但是如果能在某一个机子上给别的机子发命令然后一起执行相同的指令是不是就——泰裤辣!(很省事主要是x)不过确实,由于可以手动起机子,所以这个不是完全必须的;但是一般想要自动化跑的话那就是必须的了;
- 后续会介绍怎么使用mpirun
- 什么是
- 下载与编译NCCL库:注:编译完成后,
git clone https://github.com/NVIDIA/nccl-tests.git cd nccl-tests # 编译(需指定 CUDA 路径,通常在 /usr/local/cuda) make CUDA_HOME=/usr/local/cuda NCCL_HOME=/usr/local/nccl MPI=11
2
3
4build/目录下会生成all_reduce_perf等可执行文件。
其实博主认为,使用docker镜像 (opens new window)是比较省力的,为什么呢?
- 配置CUDA的时候一般需要手动编译,而手动编译最头疼的就是版本匹配,比如说CUDA 12.1 要匹配特定版本的 NCCL,而 OpenMPI 又可能和系统库冲突
- 镜像优势:NVIDIA 官方提供的 PyTorch 或 CUDA 镜像(如 nvcr.io/nvidia/pytorch)已经预装并测试好了最匹配的 CUDA、NCCL 和 OpenMPI。你不需要去查表看哪个版本兼容哪个,直接“开箱即用”
- 保证集群内“环境绝对一致“,这个相比都不需要多说,同一个镜像开的容器里环境都是完全一样的了;因此不会出现因为环境不一致出现的bug;
- 避免污染宿主机环境:经常配环境的朋友都知道,配环境最怕出现依赖问题了;这个机子能跑,那个机子跑不了是常有的事情x 很多时候我们现在装的东西可能会破坏之前环境的某个模块,然后越改越烦x docker完美解决这个问题!因为所有的编译工具(nvcc, mpicc)和临时库都留在镜像里。测试完直接删掉容器,宿主机依然干净如初
在使用docker的时候,职责划分如下表:
| 组件 | 宿主机 (Host) 是否需要? | 容器 (Container) 是否需要? | 为什么? |
|---|---|---|---|
| NVIDIA Driver (驱动) | 必须安装 | 不需要 | 驱动是硬件的“翻译官”,必须直接运行在内核态。 |
| NVIDIA Container Toolkit | 必须安装 | 不需要 | 这是 Docker 调用 GPU 的“桥梁”(即 nvidia-docker2)。 |
| CUDA Toolkit (nvcc等) | 不需要 | 必须包含 | 容器内自带了编译和运行所需的库,不依赖宿主机的 CUDA。 |
| NCCL 库/源码 | 不需要 | 必须包含 | 容器内自备,确保环境一致性。 |
| MPI (mpirun) | 可选 | 必须包含 | 见下文详细分析。 |
- cuda只需要驱动CUDA Driver不需要toolkit是因为Docker 启动时通过 --gpus all 参数,会将宿主机的 NVIDIA Driver 库文件(如 libcuda.so)映射到容器内部
- 虽然不需要 CUDA Toolkit,但你必须在宿主机安装 NVIDIA Container Toolkit。没有它,Docker 容器就看不见显卡
- mpirun最好还是都装了(后续俺再说)
# 第二阶段:单机性能测试 (Baseline)
在跑跨机网络前,先确定单机内的 GPU 通信(NVLink)是否达标。如果单机都不行,跨机一定有问题
- 运行单机 AllReduce:
cd nccl-test # 假设单机有 8 块 GPU ./build/all_reduce_perf -b 8M -e 1G -f 2 -g 81
2
3
| 参数 | 全称 | 详细含义 | 建议设置 |
|---|---|---|---|
-b | Begin size | 测试数据量的起始大小(例如 8M)。 | 从较小值开始,观察小包延迟。 |
-e | End size | 测试数据量的结束大小(例如 1G)。 | 建议设大一点,直到带宽达到饱和。 |
-f | Step factor | 步进倍数。每次测试后,数据量翻多少倍。 | 常用 2,即 8M -> 16M -> 32M... |
-g | Number of GPUs | 本次测试使用的 GPU 数量。 | 单机 8 卡机通常直接设为 8。 |
-n | Iterations | 每个数据量重复测试的次数(默认 20)。 | 增加次数可以获得更稳定的平均值。 |
-w | Warmup iters | 预热次数。排除刚启动时的波动。 | 默认 5 次,通常足够。 |
因为网络带宽在不同数据量下的表现不同。小包(如 8KB)测试的是延迟(Latency),大包(如 1GB)测试的是吞吐量(Throughput)。
- 观察指标:
- BusBw (总线带宽):对于 A100/H100 这种带 NVLink 的机器,单机内 BusBw 应接近 300GB/s - 450GB/s
- PCIe指的是 通用总线。它是计算机的“主干道”,连接 CPU、显卡、硬盘、网卡等所有外设,正因为它的通用性,所以其传输性能就相对较低
- NVLink是 NVIDIA 为了解决多卡通信瓶颈专门研发的技术,它让GPU间的通信不需要经过CPU就可以完成,大大减少了延迟;
- 如果带宽只有几十 GB/s,说明 NVLink 没起作用,走的是 PCIe
- BusBw (总线带宽):对于 A100/H100 这种带 NVLink 的机器,单机内 BusBw 应接近 300GB/s - 450GB/s
如果你发现带宽不对劲,可以往以下几个方向排查(具体的命令行问ai)
- 物理连接(NVLink)是否存在,版本是否正确,硬件拓扑是否正常
- 软件驱动是否正常工作,比如对于 NVIDIA SXM 系统(如 8 卡 A100/H100 训练服务器),必须启动 nvidia-fabricmanager 服务,并且确保 Fabric Manager 的版本与驱动版本完全一致
- 检查屏蔽参数: 检查你的脚本或 .bashrc 中是否设置了: export NCCL_P2P_DISABLE=1 或 export NCCL_NVLS_DISABLE=1
- Docker 参数: 确保使用了 --gpus all,有些旧版本的容器运行时可能无法正确透传 NVLink 设备节点(如 /dev/nvidia-uvm)
# 第三阶段:跨机连通性与配置验证
这是最关键的一步,用于检查多机之间是否能“握手”成功。
# 设置必要的环境变量(以 RoCE 网络为例):
export NCCL_DEBUG=INFO # 开启详细日志,必选
export NCCL_IB_GID_INDEX=3 # RoCE 通常需要指定 GID (通常是 3)
export NCCL_IB_HCA=mlx5_0,mlx5_1 # 指定使用的网卡名
export NCCL_SOCKET_IFNAME=eth0 # 指定控制面使用的网卡(通常是业务网)
export NCCL_NET_GDR_LEVEL=2 # GPUDirect RDMA 级别。0:关闭; 1:系统级; 2:P2P; 3:PHB; 4:PBM; 5:本地。通常设为 2 以获得最佳性能。
export NCCL_IB_DISABLE=0 # 是否禁用 InfiniBand/RoCE。如果想强制走 TCP 业务网测试,设为 1
#下面是一些不那么常用的
export NCCL_IB_QPS_PER_CONNECTION=4 # 每个连接使用的 QP(Queue Pairs)数量。在多卡高带宽环境下,增加此值可以提升并发性能。
export NCCL_MAX_NCHANNELS=16 # 设置最大通道数。通道越多,并行度越高,但 CPU 开销也会增加
export NCCL_CROSS_NIC=1 # 是否允许跨网卡传输。如果你的机器有两块网卡,开启它可以让数据在不同网卡间均衡。
export NCCL_IB_TC=106 # 设置 RoCE 的流量类别(Traffic Class),用于实现 PFC (Priority Flow Control) 丢包保护。106是给流量打上队列为3的标记
export NCCL_ALGO=RING # 设置nccl算子的算法实现
#调试用的参数
export NCCL_DEBUG_SUBSYS=INIT,GRAPH,ENV #配合 DEBUG=INFO 使用,只显示特定子系统的日志,减少干扰。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里着重说一下怎么选择NCCL_SOCKET_IFNAME和NCCL_IB_HCA
# NCCL_IB_HCA
对于 NCCL_IB_HCA,它是指定数据面(RDMA/IB)的网卡,在 RoCE 或 InfiniBand 环境下,它直接决定了 GPU 数据通过哪块物理网卡流出;
- 通过命令行
ibstat或者ibv_devinfo -v,然后看命令行的输出- 看状态 (State):必须是 PORT_ACTIVE。如果是 DOWN,说明网线没插或没配置好
- 看驱动 (HCA):你会看到类似 mlx5_0, mlx5_1 这样的名字,我们最后就会选用这样的东西来
- 看拓扑(进阶):执行
nvidia-smi topo -m- 寻找 NIC 列。你会发现某些 GPU 距离某个 mlx5_x 最近(处于同一个 PCIe Switch 下)
- 实例
- 单网卡:
export NCCL_IB_HCA=mlx5_0 - 双网卡(想全用):
export NCCL_IB_HCA=mlx5_0,mlx5_1
- 单网卡:
那么如何判断处于同一子网呢?
- 理论判断:在两台机器上分别执行
ip addr show,观察你的高速网卡的ip,一般是x.x.x.x的形式,x表示0-255的数字,然后如果掩码是 /24,那么前三段必须完全一样(都是 192.168.3.x)才算同一个子网(24表示前24位,一个数字是8位因此是前三个数字); - 实际测试:使用
ping命令验证两台机器间的网络连通性:如果能够正常ping通(没有丢包,延迟合理),说明两台机器在同一子网内且网络连接正常# 在机器A上ping机器B的高速网卡IP ping 192.168.3.10 # 在机器B上ping机器A的高速网卡IP ping 192.168.3.111
2
3
4
5 - 网络工具验证:使用
ip route或netstat -rn查看路由表,确认两台机器是否在相同的网络段:查看输出中的目标网络和子网掩码,确认两台机器的IP是否落在同一网络段内ip route show # 或 netstat -rn1
2
3
这里来说一下上面的判断原理,为什么不是同一个子网还可以ping呢?
- 如果在同一个子网,说明他们通过交换机连接或者直接用网线对连,是靠 MAC地址来通信的,不需要经过路由器即可到达对方;可以使用
arp -a查看MAC地址(我草mac居然也可以使用这个命令不愧是UNIX!);这个叫两层连通——数据链路层 - 不在同一个子网,那就不能靠MAC地址在数据链路层找到对方,就需要上三层——网络层找到对方,这个时候通过路由器或者三层交换机进行连接,可以通过
traceroute [目标IP]来判断到达对方是否需要经过交换机,如果中间出现了一个或多个网关,那么需要三层连通
# NCCL_SOCKET_IFNAME
对于 NCCL_SOCKET_IFNAME,它指定的是控制面(TCP)网卡;这是 握手与协调 通道。在多机训练开始前,各节点需要通过 TCP 互相交换信息(如 IP、端口、GPU 数量等)
- 通过在容器内或者宿主机内执行
ip addr show- 看 IP 地址:找到那个能让两台机器互相 ping 通的 IP 所在的网卡名。
- 排除虚拟网卡:绝对不要选 lo (127.0.0.1), docker0, br-xxxx
- 网卡分类(只对于博主见过的),在 Linux 系统(尤其是高性能服务器)中,网卡命名的差异通常反映了它们在硬件拓扑、物理位置以及功能角色上的不同
网卡分类
- eno0 / eno1:板载网卡,这些一般是主板芯片组自带
- 带宽性能:通常较低,多为 1Gbps 或 10Gbps
- 管理面:用于 SSH 远程登录、安装系统、PXE 启动。
- 业务面:处理简单的 API 请求或小流量数据。
- enp... / enpf...:PCIe 插槽网卡 (Ethernet PCI)
- 这类网卡是通过 PCIe 插槽插入服务器的独立网卡(如 NVIDIA/Mellanox ConnectX 系列)
- 带宽性能:通常极高,25G / 100G / 200G / 400G
- 数据面:专门跑 RDMA / RoCE,负责分布式训练中的模型权重同步(NCCL 通讯)
- 命名含义:enp1s0f0np0:表示第 1 号总线、第 0 号设备、第 0 个物理功能、第 0 个网络端口
- p 代表 bus/port(PCIe 总线位置)
- f 代表 function(多功能网卡,常见于支持虚拟化 SR-IOV 的网卡)
可以用ethtool eno0 | grep Speed来查看网卡速率
# 双机简单测试
使用 mpirun 启动两台机器,每台出 1 块 GPU 进行测试:
mpirun -np 2 -H host1:1,host2:1 \
-x NCCL_DEBUG=INFO \
./build/all_reduce_perf -b 8M -e 128M -f 2 -g 1
2
3
# 排查日志
- 看到
NCCL INFO NET/IB : Using [0]mlx5_0:1/RoCE说明 RDMA 已启用。 - 看到
NCCL INFO NET/Socket说明 RDMA 没跑通,回退到了慢速的 TCP 模式。
# 第四阶段:全集群压力与性能测试
当连通性没问题后,进行全规模测试以评估网络性能。
- 全规模 AllReduce 测试:
# 假设 2 台机器,每台 8 块 GPU mpirun -np 16 -H host1:8,host2:8 \ -x NCCL_DEBUG=VERSION \ ./build/all_reduce_perf -b 1M -e 4G -f 2 -g 11
2
3
4 - 性能评估标准:
- 100G 网卡:跨机 BusBw 理论上限约 10-11 GB/s。
- 200G (IB/RoCE) 网卡:跨机 BusBw 应在 20-22 GB/s 左右。
- 400G 网卡:应在 40-45 GB/s 左右。
# 💡 性能调优建议 (Cheat Sheet)
| 现象 | 可能原因 | 解决建议 |
|---|---|---|
| 带宽极低 (1GB/s以下) | 走的是 TCP 而非 RDMA | 检查 NCCL_IB_DISABLE=0,确认驱动支持。 |
| 带宽只有预期的一半 | 没开多队列或网卡映射不对 | 设置 NCCL_IB_HCA 明确指定所有高速网卡。 |
| 测试中途卡死 (Timeout) | 网络丢包或防火墙拦截 | 检查 PFC/ECN 配置,关闭系统防火墙。 |
| 报错 GID Index | RoCE 配置不匹配 | 通过 ibv_devinfo -v 查看 RoCE v2 对应的 Index。 |
在陌生集群上,一定要先开 NCCL_DEBUG=INFO。它会告诉你 NCCL 最终选择了哪个网卡、哪个协议。如果看到 NET/Socket 字样,哪怕测试跑通了,性能也是不及格的!