跳转到内容

集合通信原语与代价模型

多卡协同的所有通信,几乎都能归结为六个集合通信原语。它们不是随便定义的——每一个都对应一种真实的并行需求,且都有成熟的最优算法。

这一章讲清楚这六个原语和它们的代价。第十章讲并行策略时会反复用到:每种并行方式的本质区别,就是它用哪个原语、通信多少、多久一次。

朴素的做法是点对点:每张卡把数据发给需要它的卡。pp 张卡做一次全局求和,如果都发给 0 号卡再广播回来,0 号卡要收发 2(p−1)2(p-1) 份数据——它成了瓶颈,且时间随 pp 线性增长。

集合通信的价值在于利用所有链路。同样的全局求和,用环形算法可以让每张卡的收发量与 pp 几乎无关。这是从"能跑"到"能扩展"的关键差别。

设有 pp 个参与者(rank),每个持有大小为 SS 字节的数据。

一个 rank 的数据发给所有 rank。

前: [A] [ ] [ ] [ ]
后: [A] [A] [A] [A]

用途:分发模型初始权重、分发配置。

所有 rank 的数据按元素做运算(求和、求最大值等),结果给一个 rank。

前: [A] [B] [C] [D]
后: [A+B+C+D] [ ] [ ] [ ]

用途:收集统计量到主 rank。

归约后所有 rank 都拿到结果。这是训练中最重要的原语。

前: [A] [B] [C] [D]
后: [A+B+C+D] × 4

用途:数据并行的梯度同步、张量并行的部分和合并。

每个 rank 的数据拼接起来,所有 rank 都拿到完整拼接结果。

前: [A] [B] [C] [D]
后: [ABCD] × 4

用途:ZeRO-3 在计算前收集完整参数分片、序列并行合并激活。

先按元素归约,再把结果切成 pp 份,每个 rank 拿一份。

前: [A1A2A3A4] [B1B2B3B4] [C1C2C3C4] [D1D2D3D4]
后: [Σ1] [Σ2] [Σ3] [Σ4]

用途:ZeRO 的梯度分片归约。关键恒等式:

All-Reduce=Reduce-Scatter+All-Gather\text{All-Reduce} = \text{Reduce-Scatter} + \text{All-Gather}

这个分解不只是理论——高效的 all-reduce 实现就是这么做的。

每个 rank 把自己的数据切成 pp 份,第 ii 份发给 rank ii;同时从每个 rank 收一份。相当于分布式转置。

前: [A1A2A3A4] [B1B2B3B4] [C1C2C3C4] [D1D2D3D4]
后: [A1B1C1D1] [A2B2C2D2] [A3B3C3D3] [A4B4C4D4]

用途:MoE 的 token 路由、序列并行与张量并行之间的维度切换。它对网络的要求与 all-reduce 完全不同——需要真正的全互联带宽,而不只是环形带宽。

假设每个 rank 持有 SS 字节,用最优算法,每个 rank 的收发量:

原语每 rank 通信量随 pp 增长?
BroadcastSS否
ReduceSS否
All-Reduce2p−1pS≈2S2\frac{p-1}{p} S \approx 2S否(趋于 2S2S)
All-Gatherp−1pS≈S\frac{p-1}{p} S \approx S否
Reduce-Scatterp−1pS≈S\frac{p-1}{p} S \approx S否
All-to-Allp−1pS≈S\frac{p-1}{p} S \approx S否(但连接数随 pp 增长)

最重要的一个事实:all-reduce 的通信量约为 2S2S,与参与的卡数无关。这就是数据并行能扩展到成千上万张卡的根本原因——加卡不增加每卡的通信量。

(注意"通信量不变"不等于"时间不变",下面讲延迟项。)

标准的 α-β 模型把一次通信的时间拆成两部分:

T=α+nBT = \alpha + \frac{n}{B}
  • α\alpha:延迟(固定开销),与数据量无关。跨节点典型值 2–5 μs。
  • n/Bn/B:传输时间,数据量除以带宽。

对集合通信,要考虑算法的步数:

算法延迟项带宽项适合
Ring all-reduce2(p−1)α2(p-1)\alpha2p−1pSB2\frac{p-1}{p}\frac{S}{B}大消息
Tree all-reduce2log⁡2(p)⋅α2\log_2(p) \cdot \alpha2SB2\frac{S}{B}小消息

关键区别在延迟项:Ring 的步数随 pp 线性增长,Tree 只随 log⁡p\log p 增长。

于是有一条清晰的分界:

  • 大消息(带宽项主导)→ 用 Ring,带宽利用率最优。
  • 小消息(延迟项主导)→ 用 Tree,步数少。

通信库会根据消息大小自动选择,但选择阈值有时需要手动调整。

1024 张卡做数据并行,模型 7B 参数,梯度用 BF16(14 GB)。跨节点带宽 50 GB/s,延迟 3 μs。

Ring all-reduce:

T≈2×1023×3 μs⏟≈6.1 ms+2×10231024×14 GB50 GB/s⏟≈560 ms≈566 msT \approx \underbrace{2 \times 1023 \times 3\ \mu s}_{\approx 6.1\ ms} + \underbrace{2 \times \frac{1023}{1024} \times \frac{14\ \text{GB}}{50\ \text{GB/s}}}_{\approx 560\ ms} \approx 566\ \text{ms}

带宽项完全主导。如果一个训练步的计算时间是 500 ms,那么通信占了一半以上——必须与计算重叠,否则扩展性归零。

这个算例解释了两件事:为什么梯度通信必须与反向传播重叠(边算边发),以及为什么梯度压缩、低精度通信是有价值的优化方向。

上面的模型假设每条链路带宽相同,实际不是。回顾第三章的阶梯:节点内互连比跨节点高一个数量级。

于是同一个 all-reduce,参与者的物理位置会让性能差好几倍:

8 卡 all-reduce相对速度
同一台机器内的 8 张卡1× (最快)
分布在 2 台机器(各 4 张)~0.3×
分布在 8 台机器(各 1 张)~0.1×

这直接推出并行策略的映射规则:通信最频繁的并行维度放在带宽最高的层级上。第十章会把这条规则用足。

现代通信库支持分层算法:先在节点内归约(走高速互连),再跨节点归约(只有一份数据出机器),最后节点内广播。这能把跨节点通信量降低到 1/81/8。

集合通信是同步点:所有参与者都到达才能继续。这带来两个后果。

一、最慢的决定整体。 如果 1023 张卡都到了,1 张卡慢 100 ms,所有卡都等 100 ms。这就是 straggler 问题——在同步训练中,性能由最差的节点决定,而不是平均值。

二、抖动会累积。 每一步的等待时间是所有卡延迟的最大值。即使单卡的延迟波动很小,取 1024 个样本的最大值也会显著大于平均值。这解释了为什么大规模训练的实际速度常常低于按平均性能的预测。

提前给个索引,第十章展开:

并行方式主要原语通信量频率
数据并行All-Reduce参数量每步一次
数据并行 + ZeROReduce-Scatter + All-Gather参数量每步(分散在各层)
张量并行All-Reduce激活大小每层两次
流水线并行点对点 send/recv边界激活每 stage 边界
专家并行All-to-All路由的 token每 MoE 层两次
上下文并行点对点 或 All-to-AllKV 分块注意力内部

看这张表就能理解为什么张量并行只能在节点内做:它的原语是 all-reduce(较贵),频率是每层两次(极高)。跨节点的带宽撑不住这个频率。

建立带宽基线。 集群搭好后跑标准的集合通信基准测试,记录各种消息大小下的实际带宽。之后每次怀疑网络有问题,与基线对比即可。这是最有用的一条运维实践。

关注小消息性能。 很多问题出在小消息上——延迟项主导时,拓扑和算法选择的影响被放大。

通信要和计算重叠。 现代框架默认会做,但重叠是否真正生效需要用时间线验证。看到通信和计算在时间线上是串行的,就说明重叠失效了。

警惕单点故障。 一条降速的链路会拖慢整个集合通信,而且很难从上层发现——表现只是"训练变慢了"。

  • 六个原语:broadcast、reduce、all-reduce、all-gather、reduce-scatter、all-to-all。
  • All-Reduce = Reduce-Scatter + All-Gather,通信量约 2S2S,与卡数无关——这是数据并行能扩展的根本原因。
  • 代价模型 T=α+n/BT = \alpha + n/B:大消息用 Ring(带宽最优),小消息用 Tree(延迟项只有 log⁡p\log p)。
  • 拓扑决定实际带宽,同样 8 卡 all-reduce 跨 8 机比同机慢约十倍;分层算法能把跨节点通信降到 1/8。
  • 集合通信是同步点:最慢的节点决定整体,且抖动在取最大值时被放大。
  • 并行策略的本质区别就是"用哪个原语、多少量、多久一次"。