这篇博客主要是重温一下之前学过的集合通信的基本模式,包括拓扑、原语和实现。说是“重温”是因为这部分在之前学习NCCL的时候其实就都已经粗略的学习过了,但之后没有用到过,因此掌握不熟练、印象不深。现在趁着Pico-vLLM的开发机会,把这部分知识彻底的掌握起来。
集合通信的原语
点对点(P2P)
Broadcast
Broadcast意味着一对多,一个GPU的数据广播给所有人。
GPU 0: [A B C D] → GPU 0: [A B C D]
GPU 1: [ ] GPU 1: [A B C D]
GPU 2: [ ] GPU 2: [A B C D]
GPU 3: [ ] GPU 3: [A B C D]
Reduce
Reduce意味着多对一,所有GPU的数据归约到一个GPU。要注意的是它不是拼接而是运算,其最终得到的数据的数据量和原本每个GPU自己的数据量是相同的。Reduce本身并不是一个具体的操作,而是一系列具有特殊性质的规约算子的集合,其满足的性质是为可结合(associative)且通常可交换(commutative)的二元运算。Reduce的常见算子包括了:
- Sum(求和)
- Prod(乘积)
- Max / Min(极值)
- Avg(平均)
- BAND / BOR / BXOR(按位运算)
- MinLoc / MaxLoc(极值+极值所在位置)
LLM训练中99%的Reduce操作都是求和,其他算子出现的极少。NCCL甚至不支持MinLoc / MaxLoc操作。
GPU 0: [A₀] → GPU 0: [A₀+A₁+A₂+A₃]
GPU 1: [A₁] GPU 1: [ ]
GPU 2: [A₂] GPU 2: [ ]
GPU 3: [A₃] GPU 3: [ ]
All-Reduce
All-Reduce意味着将所有GPU的数据归约,结果每个GPU都有一份完整拷贝。从逻辑上可以理解为先完成一个Reduce再完成一个Broadcast——实际上的操作在概念上类似但具体实现并不相同,同样是分解为两个阶段,但具体分解成的是Reduce-Scatter和All-Gather。
为什么不是一个Reduce+一个Broadcast?实际上,在早期的参数服务器模式下,确实就是如此。但它有一个问题:可扩展性。Reduce 阶段所有数据汇聚到一个节点(CPU),Broadcast 阶段再从这一个节点(GPU)发出去。这个节点(GPU)的带宽成为整个操作的瓶颈,其他 GPU 在等待时空闲。Reduce-Scatter+All-Gather模式实际上是通过一个线性的偏移把每一个节点(GPU)变成了一个$1/N$的参数服务器,每个只负责和自己编号相同的第$K$块的数据的Reduce和Broadcast。在集合通信的视角下,这恰好就是Reduce-Scatter+All-Gather的模式。这样讲,可能在概念上是最容易理解的。
GPU 0: [A₀] → GPU 0: [A₀+A₁+A₂+A₃]
GPU 1: [A₁] GPU 1: [A₀+A₁+A₂+A₃]
GPU 2: [A₂] GPU 2: [A₀+A₁+A₂+A₃]
GPU 3: [A₃] GPU 3: [A₀+A₁+A₂+A₃]
Reduce-Scatter
Reduce-Scatter操作意味着归约后,结果被切分,每个 GPU 只拿到一部分。但这种简化的描述方式实际上不完全准确,因为理解它在过程中的副产物,比理解它的开始状态和结束状态更有助于理解它是如何运作的。以最经典的ring为例子讲解,一张图会更容易帮助读者理解到底发生了什么:
每步:每个 GPU 向下一个 GPU 发送一块数据,同时从上一个 GPU 接收一块并求和。
规则:第 1 步发送自己编号对应的块,之后每步发送刚更新过的块。
初始状态:
GPU 0: [A₀ B₀ C₀ D₀]
GPU 1: [A₁ B₁ C₁ D₁]
GPU 2: [A₂ B₂ C₂ D₂]
GPU 3: [A₃ B₃ C₃ D₃]
第 1 步: GPU 0 ──A₀──→ GPU 1
GPU 1 ──B₁──→ GPU 2
GPU 2 ──C₂──→ GPU 3
GPU 3 ──D₃──→ GPU 0
GPU 0: [A₀ B₀ C₀ D₀+D₃ ]
GPU 1: [A₁+A₀ B₁ C₁ D₁ ]
GPU 2: [A₂ B₂+B₁ C₂ D₂ ]
GPU 3: [A₃ B₃ C₃+C₂ D₃ ]
第 2 步: GPU 0 ──D₀₃──→ GPU 1 (发送刚更新的 D 块)
GPU 1 ──A₀₁──→ GPU 2 (发送刚更新的 A 块)
GPU 2 ──B₁₂──→ GPU 3 (发送刚更新的 B 块)
GPU 3 ──C₂₃──→ GPU 0 (发送刚更新的 C 块)
GPU 0: [A₀ B₀ C₀+C₂₃ D₀₃ ]
GPU 1: [A₀₁ B₁ C₁ D₁+D₀₃ ]
GPU 2: [A₂+A₀₁ B₁₂ C₂ D₂ ]
GPU 3: [A₃ B₃+B₁₂ C₂₃ D₃ ]
第 3 步: GPU 0 ──C₀₂₃──→ GPU 1 (发送刚更新的 C 块)
GPU 1 ──D₀₁₃──→ GPU 2 (发送刚更新的 D 块)
GPU 2 ──A₀₁₂──→ GPU 3 (发送刚更新的 A 块)
GPU 3 ──B₁₂₃──→ GPU 0 (发送刚更新的 B 块)
GPU 0: [A₀ B₀₁₂₃=B_all C₀₂₃ D₀₃ ]
GPU 1: [A₀₁ B₁ C₀₁₂₃=C_all D₀₁₃ ]
GPU 2: [A₀₁₂ B₁₂ C₂ D₀₁₂₃=D_all]
GPU 3: [A₀₁₂₃=A_all B₁₂₃ C₂₃ D₃ ]
规约完成。
因此,它的最终结果就是下图。但需要注意的是,它并不是一个没有副产物的操作。如果是原位操作的话,它实际上会修改每个GPU没有得到最终规约结果的相应数据位置,使得它不再是原本值,而是一个和自身rank决定的偏移量、数据偏移量都相关的部分累加和,也可以和某种意义上的前缀和相类比。
GPU 0: [A₀ B₀ C₀ D₀] → GPU 0: [A₀+A₁+A₂+A₃]
GPU 1: [A₁ B₁ C₁ D₁] GPU 1: [B₀+B₁+B₂+B₃]
GPU 2: [A₂ B₂ C₂ D₂] GPU 2: [C₀+C₁+C₂+C₃]
GPU 3: [A₃ B₃ C₃ D₃] GPU 3: [D₀+D₁+D₂+D₃]
All-Gather
每个GPU有一部分数据,收集后每个GPU都有完整数据。Ring All-Gather中没有任何归约(求和)操作,每个GPU拿到的块直接原样转发给下一个人就行。每步每个GPU把自己最近收到的完整块沿环传递,N-1步之后每个GPU就收集齐了所有块。
GPU 0: [A] → GPU 0: [A B C D]
GPU 1: [B] GPU 1: [A B C D]
GPU 2: [C] GPU 2: [A B C D]
GPU 3: [D] GPU 3: [A B C D]
All-to-All
每个 GPU 向每个其他 GPU 发送不同的数据。由于其$O(n^2)$的通信连接数(有时候还有通信量)和复杂度,其被称为是“集群性能的试金石和考验”。它几乎总是可能引起拥塞。另一方面,大部分运行在集群上的应用都会尽量避免产生频繁的All-to-All通信,因为它很可能严重拉低相较于规整的集合通信模式下的集群性能。对All-to-All的通信优化算法和工程实践思路以处理其产生的拥塞控制为主。此外,就像上一篇的博客中对于MoE的介绍一样,许多All-to-All是动态的而非静态的,从而进一步加剧了这个问题。
GPU 0: [A₀ A₁ A₂ A₃] → GPU 0: [A₀ B₀ C₀ D₀]
GPU 1: [B₀ B₁ B₂ B₃] GPU 1: [A₁ B₁ C₁ D₁]
GPU 2: [C₀ C₁ C₂ C₃] GPU 2: [A₂ B₂ C₂ D₂]
GPU 3: [D₀ D₁ D₂ D₃] GPU 3: [A₃ B₃ C₃ D₃]
All-to-All-v
每个 GPU 向每个其他 GPU 发送不同的数据,但收发的总数据量不一定每个都相同。
集合通信的常见拓扑
Ring(环形)
最经典的集合通信的拓扑模式。
优点是带宽最优——每个 GPU 的发送和接收带宽被充分利用,总通信量和 GPU 数无关(都是 2×(N-1)/N × 数据量)。
缺点是延迟随 GPU 数线性增长——步数是 2(N-1),GPU 越多步数越多。
Tree(树形)
优点是延迟最优——只需 O(log N) 步。
缺点是带宽利用率不如 Ring——叶节点只参与部分通信,根节点成为瓶颈。
Double Binary Tree
两棵互补的二叉树同时工作,结合了Tree的低延迟和更好的带宽利用率。NCCL在某些场景下会用这种策略。但其复杂且难以理解,而且对集群的节点数量有更严格的要求。
Recursive Halving-Doubling
递归地把GPU分成两半,先在小组内归约,再逐步扩大。兼顾延迟和带宽。
集合通信的常见实现
XCCL:NCCL、HCCL、ACCL...许多初创的CCL通信库实际上都是直接魔改的NCCL。
NCCL 的 kernel 是沿三个维度预编译的模板实例:
算法(Algorithm):决定数据流动的拓扑模式
Ring: 环形,带宽最优
Tree: 树形(双二叉树),延迟最优
CollnetDirect / CollnetChain: 利用 SHARP 等网内计算
协议(Protocol):决定数据传输的同步和打包方式
Simple: 大块传输,高带宽,适合大消息
LL: Low Latency,8 字节粒度,用 flag 做同步,适合小消息
LL128: 128 字节粒度,利用 NVLink 的原子操作,延迟和带宽的折中
归约操作 × 数据类型:
Sum/Prod/Max/Min × FP16/BF16/FP32/FP64/INT8/...
这些维度的组合在编译时就被实例化成了大量的 kernel 变体。例如 Ring + Simple + Sum + FP16 是一个 kernel,Tree + LL128 + Max + FP32 是另一个 kernel。这也是为什么 NCCL 的编译时间很长。
Cost Model:运行时选择
NCCL 的 cost model 是默认调优决策的核心。这个模型以时间为指标评估集合操作的开销,用于选择正确的协议和算法。cost model 考虑多种因素,包括数据量、GPU 架构、拓扑结构、网络和算法属性。
MSCCL:微软用的一种DSL,基于NCCL自定义通信算法。
MPI:太经典不讲。
硬件互联的数感建立
这一节列举常见网络和网卡设备的所有规格和速度,以帮助读者,包括写博客的我自己,建立对于网络的数字感知。
一、GPU 节点内互联:NVLink
代际 首发GPU 每条链路带宽(双向) 每GPU链路数 每GPU总带宽(双向)
NVLink 1.0 P100 40 GB/s 4 160 GB/s
NVLink 2.0 V100 50 GB/s 6 300 GB/s
NVLink 3.0 A100 50 GB/s 12 600 GB/s
NVLink 4.0 H100 100 GB/s (注1) 18 900 GB/s
NVLink 5.0 B200 100 GB/s 18 1,800 GB/s
NVLink 6.0 Rubin 200 GB/s 18 3,600 GB/s
注1: H100 的 NVLink 4.0 每条链路实际是 50 GB/s 双向,但 NVIDIA 在产品规格中
按 sub-link 口径标注为 900 GB/s(18 sub-links × 50 GB/s)。
B200 的 NVLink 5.0 SerDes 速率翻倍,同样 18 条但每条 100 GB/s,所以总带宽 1.8 TB/s。
二、GPU 节点内互联:NVSwitch
代际 配套GPU 每芯片端口数 每芯片交换带宽 单节点GPU数 节点总NVLink带宽
NVSwitch 1.0 V100 18 900 GB/s 8 (DGX-2) ~2.4 TB/s
NVSwitch 2.0 A100 36 (注2) ~3.2 TB/s 8 (DGX A100) ~4.8 TB/s
NVSwitch 3.0 H100 64 25.6 Tbps 8 (DGX H100) ~3.6 TB/s
NVSwitch 4.0 B200 72 NVLink5 14.4 TB/s 72 (NVL72) ~130 TB/s (机柜级)
注2: DGX A100 有 6 片 NVSwitch 2.0,每个 A100 的 12 条 NVLink 分别连到 6 片 NVSwitch
(每片 2 条),实现 8 GPU 全连接。
NVSwitch 3.0 (Hopper) 开始引入 SHARP(Scalable Hierarchical Aggregation and Reduction Protocol),支持在交换芯片内部直接做归约计算(如 All-Reduce 的 sum),数据不需要绕回 GPU。
NVSwitch 4.0 (Blackwell) 把 NVLink 域从 8 GPU 扩展到了 72 GPU(整个机柜),是一个质变——以前跨节点才需要 InfiniBand,现在机柜内全部走 NVLink。
三、CPU-GPU 互联:PCIe
代际 发布年份 每通道速率 x16 单向带宽 x16 双向带宽
PCIe 3.0 2010 8 GT/s ~16 GB/s ~32 GB/s
PCIe 4.0 2017 16 GT/s ~32 GB/s ~64 GB/s
PCIe 5.0 2019 32 GT/s ~64 GB/s ~128 GB/s
PCIe 6.0 2022 64 GT/s ~128 GB/s ~256 GB/s
PCIe 7.0 2025(规范) 128 GT/s ~256 GB/s ~512 GB/s
对比:H100 的 NVLink 总带宽 900 GB/s,而 PCIe 5.0 x16 双向才 128 GB/s,相差 7 倍。这就是为什么 TP 必须走 NVLink 而不能走 PCIe。
四、节点间互联:InfiniBand
代际 发布年份 每通道速率 4x 链路带宽 单/双向 典型网卡 典型交换机
SDR 2001 2.5 Gbps 10 Gbps 单向 - -
DDR 2005 5 Gbps 20 Gbps 单向 - -
QDR 2007 10 Gbps 40 Gbps 单向 ConnectX-2/3 -
FDR 2011 14 Gbps 56 Gbps 单向 ConnectX-3 SwitchX
EDR 2014 25 Gbps 100 Gbps 单向 ConnectX-4/5 SwitchIB-2
HDR 2019 50 Gbps 200 Gbps 单向 ConnectX-6 Quantum
NDR 2022 100 Gbps 400 Gbps 单向 ConnectX-7 Quantum-2
XDR 2025 200 Gbps 800 Gbps 单向 ConnectX-8 Quantum-X800
GDR ~2028 400 Gbps 1.6 Tbps 单向 (规划中) (规划中)
换算成更直观的单位(双向):
EDR: ~25 GB/s 双向
HDR: ~50 GB/s 双向
NDR: ~100 GB/s 双向 ← 当前主流 AI 集群配置
XDR: ~200 GB/s 双向 ← 2025 年开始部署
一台服务器通常配备多块网卡。例如 DGX H100 配 8 张 ConnectX-7 NDR 400G 网卡,每 GPU 一张,节点间总带宽 = 8 × 100 GB/s = 800 GB/s 双向。即便如此,仍然只有 NVLink (900 GB/s) 的约 89%。
五、节点间互联:以太网(RoCE)
速率 双向带宽 延迟(典型) 备注
10 GbE ~2.5 GB/s ~10-50 μs 传统数据中心
25 GbE ~6.25 GB/s ~5-20 μs
40 GbE ~10 GB/s ~5-20 μs
100 GbE ~25 GB/s ~2-10 μs RoCEv2 常见配置
200 GbE ~50 GB/s ~2-5 μs
400 GbE ~100 GB/s ~1-3 μs 正在部署
800 GbE ~200 GB/s ~1-2 μs 2025 年开始
以太网 + RoCE(RDMA over Converged Ethernet)是 InfiniBand 的主要替代方案。带宽可以做到类似,但延迟通常高于 InfiniBand,且需要额外的拥塞控制(PFC/ECN)来模拟 IB 的无损特性。
NVIDIA 的 Spectrum-X 平台就是基于以太网的 AI 网络方案,面向不想用 InfiniBand 的客户。
六、带宽层级
带宽 (GB/s, 双向) 技术 用途
─────────────────────────────────────────────────────────
8,000 HBM3e (B200) GPU 内部显存带宽
3,600 NVLink 6.0 (Rubin) 机柜内 GPU-GPU (未来)
1,800 NVLink 5.0 (B200) 机柜内 GPU-GPU
900 NVLink 4.0 (H100) 节点内 GPU-GPU
800 8×NDR IB (DGX H100) 节点间总带宽
600 NVLink 3.0 (A100) 节点内 GPU-GPU
200 XDR IB (单卡) 节点间单卡带宽
128 PCIe 5.0 x16 CPU-GPU
100 NDR IB (单卡) 节点间单卡带宽
50 HDR IB (单卡) 节点间单卡带宽
25 EDR IB (单卡) 节点间单卡带宽 / 100GbE
数量级关系:
HBM 带宽 > NVLink > 多卡 IB 总带宽 > 单卡 IB >> PCIe
七、延迟层级
带宽之外,延迟同样重要:
操作 典型延迟
GPU 内部 SRAM (共享内存) 访问 ~几十 ns
GPU HBM 访问 ~100-300 ns
NVLink GPU-GPU ~1-2 μs
PCIe GPU-CPU ~2-5 μs
InfiniBand RDMA (节点间) ~1-3 μs
RoCE (节点间) ~2-10 μs
TCP/IP 以太网 (节点间) ~10-100 μs