LLM 学习日志 #2 集合通信拓扑、原语和实现

这篇博客主要是重温一下之前学过的集合通信的基本模式,包括拓扑、原语和实现。说是“重温”是因为这部分在之前学习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     每条链路带宽(双向)  每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