1. 课程导入与NCCL定位

当你把单卡训练脚本改成 torchrun --nproc_per_node=8 之后,往往会遇到一个反直觉的现象:8 张 GPU 的算力叠加起来,训练速度却只提升了 4~5 倍,甚至更少。算力并没有消失,它只是被"等待"消耗掉了——等待梯度从别的卡上传过来,等待参数从别的卡上同步回来。这部分被消耗的时间,就是多 GPU 通信开销。NCCL(NVIDIA Collective Communications Library)正是为压缩这部分开销而生的库。

一、多 GPU 训练中通信到底发生在哪里

以最典型的数据并行(Data Parallel, DP)为例,一次迭代的完整链路是:每张 GPU 各自读取一个 mini-batch 分片,独立完成前向传播和反向传播,得到本地的梯度张量;随后所有 GPU 必须把各自的梯度做一次"求和再平均",让每张卡拿到完全一致的全局梯度;最后每张卡用同一份梯度更新自己持有的那份参数副本,从而保证下一轮迭代开始时,所有卡上的模型参数仍然完全相同。

这个"求和再平均"的动作,就是 AllReduce。它是数据并行训练中通信量最大、最频繁的集合通信操作。假设模型有 P 个可训练参数,使用 FP16 梯度,那么一次 AllReduce 需要搬运的数据量约为 2P 字节(每张卡发送 P 字节、接收 P 字节)。一个 7B 参数的模型,单次 AllReduce 的通信量就是约 14 GB。如果每步训练耗时 300 ms,而 AllReduce 占掉 120 ms,那么通信占比高达 40%,GPU 计算单元有近一半时间在空转。

除了数据并行,还有另外两类通信模式同样常见:流水线并行(Pipeline Parallel, PP)在相邻 stage 之间传递激活值和梯度,属于点对点通信;张量并行(Tensor Parallel, TP)在每一层内部把矩阵切分到多张卡上,前向和反向都需要 AllReduce 或 AllGather 来拼接结果。三类并行方式叠加时,通信模式会同时包含集合通信和点对点通信,拓扑也从一个节点内的 NVLink 扩展到跨节点的 InfiniBand 或 RoCE 网络。

数据并行训练一步迭代中的通信位置 GPU 0 分片 batch 0 本地梯度 g₀ GPU 1 分片 batch 1 本地梯度 g₁ GPU 2 分片 batch 2 本地梯度 g₂ GPU 3 分片 batch 3 本地梯度 g₃ 各卡独立完成前向 + 反向,得到本地梯度 AllReduce:g = (g₀ + g₁ + g₂ + g₃) / 4 NCCL 在此处选择 Ring / Tree 算法,决定走 NVLink 还是 IB/RoCE 通信量 ≈ 2P 字节(P 为参数字节数) 参数更新 W ← W − lr·g 参数更新 W ← W − lr·g 参数更新 W ← W − lr·g 参数更新 W ← W − lr·g 所有卡使用同一份全局梯度 g,保证参数副本始终一致 通信开销 = AllReduce 耗时;NCCL 的目标就是让这段耗时尽可能小

二、NCCL 的定位:它解决什么问题,不解决什么问题

NCCL 是一个面向多 GPU、多节点环境的集合通信库。它向上提供 AllReduce、Broadcast、Reduce、AllGather、ReduceScatter、AlltoAll 等标准集合通信原语,向下屏蔽了 GPU 之间复杂的物理连接差异——同一节点内可能走 PCIe 或 NVLink,跨节点可能走 InfiniBand、RoCE 或以太网。NCCL 会根据当前拓扑自动选择路径和算法,让上层框架(PyTorch DDP、Megatron-LM、DeepSpeed、TensorFlow 等)不必关心"数据到底怎么从 A 卡到 B 卡"。

需要明确的是,NCCL 不负责模型切分策略,不负责梯度累积逻辑,也不负责启动进程。它只做一件事:把一批数据从一组 GPU 高效地搬到另一组 GPU。因此调优 NCCL 的本质,是让"搬运"这件事在给定硬件拓扑下尽可能接近链路带宽上限,并尽可能与计算重叠。

NCCL 的调优空间主要落在几个维度上:算法选择(Ring 还是 Tree)、通道数(NCCL_NCHANNELS)、协议(LL、LL128、Simple)、跨节点传输方式(IB、RoCE、Socket)、以及是否启用 P2P 和 NVLink。这些参数并非越大越好,而是要与实际的 GPU 数量、拓扑结构、消息大小匹配。例如小消息(几 KB 到几百 KB)通常受益于 Tree 算法和 LL128 协议,大消息(几十 MB 以上)则更适合 Ring 算法和 Simple 协议。

三、本课程的学习路径

课程按"先建立认知、再动手调优、最后排障复盘"的顺序展开,共 30 章,大致分为四个阶段。

  • 基础认知阶段(第 1~6 章):理解多 GPU 通信在训练中的位置、NCCL 的适用场景、集合通信原语语义、以及 GPU 间物理拓扑(PCIe、NVLink、NVSwitch)对带宽的影响。这一阶段的目标是能看懂 nvidia-smi topo -m 的输出,并判断两台机器之间的通信路径。
  • 环境与配置阶段(第 7~14 章):掌握 NCCL 的安装方式、环境变量体系、日志级别(NCCL_DEBUG=INFO)、以及如何用 nccl-tests 中的 all_reduce_perf 做基线带宽测试。这一阶段会给出可复现的测试命令和结果判读方法。
  • 调优实战阶段(第 15~24 章):针对 Ring/Tree 算法选择、通道数、协议、IB/RoCE 参数、P2P 与 NVLink 开关、以及通信与计算重叠(如梯度分桶、bucket_cap_mb)逐项展开,每项都给出参数含义、调整方向、观察指标和典型收益区间。
  • 排障与复盘阶段(第 25~30 章):覆盖常见故障(NCCL 超时、IB 链路降速、拓扑识别错误、多网卡选路异常)的定位路径,以及如何用 NCCL 日志和 nccl-tests 结果反推瓶颈位置。
学习本课程前,建议先确认三件事:一是机器上已安装与 CUDA 版本匹配的 NCCL;二是能运行 nccl-tests 的 all_reduce_perf;三是知道当前节点内 GPU 之间是 NVLink 还是 PCIe 连接。这三点决定了后续所有调优的起点。

四、一个可观察的起点:先测出你的通信基线

在开始调参之前,先建立基线。以下命令在 8 卡节点上运行一次 AllReduce 带宽测试,消息大小从 8 字节扫到 128 MB:

# 编译 nccl-tests 后,在 8 卡节点上执行
./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8

# 关键输出列:
#   size      : 消息大小(字节)
#   time      : 单次 AllReduce 耗时(us)
#   algbw     : 算法带宽 = size / time
#   busbw     : 总线带宽 = algbw × 2×(n-1)/n,n 为 GPU 数

判读方法:对于 8 卡 NVLink 全互联节点,大消息(≥ 64 MB)的 busbw 通常应接近 NVLink 单向带宽的 70%~85%;如果明显偏低,说明算法、通道数或拓扑识别存在问题。对于跨节点场景,busbw 应接近 IB 端口线速的 70% 左右。把这次测得的数值记下来,它就是后续每一章调优效果的对照基准。