集合通信原语与代价模型
多卡协同的所有通信,几乎都能归结为六个集合通信原语。它们不是随便定义的——每一个都对应一种真实的并行需求,且都有成熟的最优算法。
这一章讲清楚这六个原语和它们的代价。第十章讲并行策略时会反复用到:每种并行方式的本质区别,就是它用哪个原语、通信多少、多久一次。
为什么需要"集合"通信
Section titled “为什么需要"集合"通信”朴素的做法是点对点:每张卡把数据发给需要它的卡。 张卡做一次全局求和,如果都发给 0 号卡再广播回来,0 号卡要收发 份数据——它成了瓶颈,且时间随 线性增长。
集合通信的价值在于利用所有链路。同样的全局求和,用环形算法可以让每张卡的收发量与 几乎无关。这是从"能跑"到"能扩展"的关键差别。
设有 个参与者(rank),每个持有大小为 字节的数据。
1. Broadcast(广播)
Section titled “1. Broadcast(广播)”一个 rank 的数据发给所有 rank。
前: [A] [ ] [ ] [ ]后: [A] [A] [A] [A]用途:分发模型初始权重、分发配置。
2. Reduce(归约)
Section titled “2. Reduce(归约)”所有 rank 的数据按元素做运算(求和、求最大值等),结果给一个 rank。
前: [A] [B] [C] [D]后: [A+B+C+D] [ ] [ ] [ ]用途:收集统计量到主 rank。
3. All-Reduce(全归约)
Section titled “3. All-Reduce(全归约)”归约后所有 rank 都拿到结果。这是训练中最重要的原语。
前: [A] [B] [C] [D]后: [A+B+C+D] × 4用途:数据并行的梯度同步、张量并行的部分和合并。
4. All-Gather(全收集)
Section titled “4. All-Gather(全收集)”每个 rank 的数据拼接起来,所有 rank 都拿到完整拼接结果。
前: [A] [B] [C] [D]后: [ABCD] × 4用途:ZeRO-3 在计算前收集完整参数分片、序列并行合并激活。
5. Reduce-Scatter(归约散射)
Section titled “5. Reduce-Scatter(归约散射)”先按元素归约,再把结果切成 份,每个 rank 拿一份。
前: [A1A2A3A4] [B1B2B3B4] [C1C2C3C4] [D1D2D3D4]后: [Σ1] [Σ2] [Σ3] [Σ4]用途:ZeRO 的梯度分片归约。关键恒等式:
这个分解不只是理论——高效的 all-reduce 实现就是这么做的。
6. All-to-All(全交换)
Section titled “6. All-to-All(全交换)”每个 rank 把自己的数据切成 份,第 份发给 rank ;同时从每个 rank 收一份。相当于分布式转置。
前: [A1A2A3A4] [B1B2B3B4] [C1C2C3C4] [D1D2D3D4]后: [A1B1C1D1] [A2B2C2D2] [A3B3C3D3] [A4B4C4D4]用途:MoE 的 token 路由、序列并行与张量并行之间的维度切换。它对网络的要求与 all-reduce 完全不同——需要真正的全互联带宽,而不只是环形带宽。
假设每个 rank 持有 字节,用最优算法,每个 rank 的收发量:
| 原语 | 每 rank 通信量 | 随 增长? |
|---|---|---|
| Broadcast | 否 | |
| Reduce | 否 | |
| All-Reduce | 否(趋于 ) | |
| All-Gather | 否 | |
| Reduce-Scatter | 否 | |
| All-to-All | 否(但连接数随 增长) |
最重要的一个事实:all-reduce 的通信量约为 ,与参与的卡数无关。这就是数据并行能扩展到成千上万张卡的根本原因——加卡不增加每卡的通信量。
(注意"通信量不变"不等于"时间不变",下面讲延迟项。)
标准的 α-β 模型把一次通信的时间拆成两部分:
- :延迟(固定开销),与数据量无关。跨节点典型值 2–5 μs。
- :传输时间,数据量除以带宽。
对集合通信,要考虑算法的步数:
| 算法 | 延迟项 | 带宽项 | 适合 |
|---|---|---|---|
| Ring all-reduce | 大消息 | ||
| Tree all-reduce | 小消息 |
关键区别在延迟项:Ring 的步数随 线性增长,Tree 只随 增长。
于是有一条清晰的分界:
- 大消息(带宽项主导)→ 用 Ring,带宽利用率最优。
- 小消息(延迟项主导)→ 用 Tree,步数少。
通信库会根据消息大小自动选择,但选择阈值有时需要手动调整。
1024 张卡做数据并行,模型 7B 参数,梯度用 BF16(14 GB)。跨节点带宽 50 GB/s,延迟 3 μs。
Ring all-reduce:
带宽项完全主导。如果一个训练步的计算时间是 500 ms,那么通信占了一半以上——必须与计算重叠,否则扩展性归零。
这个算例解释了两件事:为什么梯度通信必须与反向传播重叠(边算边发),以及为什么梯度压缩、低精度通信是有价值的优化方向。
拓扑决定实际带宽
Section titled “拓扑决定实际带宽”上面的模型假设每条链路带宽相同,实际不是。回顾第三章的阶梯:节点内互连比跨节点高一个数量级。
于是同一个 all-reduce,参与者的物理位置会让性能差好几倍:
| 8 卡 all-reduce | 相对速度 |
|---|---|
| 同一台机器内的 8 张卡 | 1× (最快) |
| 分布在 2 台机器(各 4 张) | ~0.3× |
| 分布在 8 台机器(各 1 张) | ~0.1× |
这直接推出并行策略的映射规则:通信最频繁的并行维度放在带宽最高的层级上。第十章会把这条规则用足。
现代通信库支持分层算法:先在节点内归约(走高速互连),再跨节点归约(只有一份数据出机器),最后节点内广播。这能把跨节点通信量降低到 。
同步的隐藏代价
Section titled “同步的隐藏代价”集合通信是同步点:所有参与者都到达才能继续。这带来两个后果。
一、最慢的决定整体。 如果 1023 张卡都到了,1 张卡慢 100 ms,所有卡都等 100 ms。这就是 straggler 问题——在同步训练中,性能由最差的节点决定,而不是平均值。
二、抖动会累积。 每一步的等待时间是所有卡延迟的最大值。即使单卡的延迟波动很小,取 1024 个样本的最大值也会显著大于平均值。这解释了为什么大规模训练的实际速度常常低于按平均性能的预测。
与并行策略的对应
Section titled “与并行策略的对应”提前给个索引,第十章展开:
| 并行方式 | 主要原语 | 通信量 | 频率 |
|---|---|---|---|
| 数据并行 | All-Reduce | 参数量 | 每步一次 |
| 数据并行 + ZeRO | Reduce-Scatter + All-Gather | 参数量 | 每步(分散在各层) |
| 张量并行 | All-Reduce | 激活大小 | 每层两次 |
| 流水线并行 | 点对点 send/recv | 边界激活 | 每 stage 边界 |
| 专家并行 | All-to-All | 路由的 token | 每 MoE 层两次 |
| 上下文并行 | 点对点 或 All-to-All | KV 分块 | 注意力内部 |
看这张表就能理解为什么张量并行只能在节点内做:它的原语是 all-reduce(较贵),频率是每层两次(极高)。跨节点的带宽撑不住这个频率。
建立带宽基线。 集群搭好后跑标准的集合通信基准测试,记录各种消息大小下的实际带宽。之后每次怀疑网络有问题,与基线对比即可。这是最有用的一条运维实践。
关注小消息性能。 很多问题出在小消息上——延迟项主导时,拓扑和算法选择的影响被放大。
通信要和计算重叠。 现代框架默认会做,但重叠是否真正生效需要用时间线验证。看到通信和计算在时间线上是串行的,就说明重叠失效了。
警惕单点故障。 一条降速的链路会拖慢整个集合通信,而且很难从上层发现——表现只是"训练变慢了"。
- 六个原语:broadcast、reduce、all-reduce、all-gather、reduce-scatter、all-to-all。
- All-Reduce = Reduce-Scatter + All-Gather,通信量约 ,与卡数无关——这是数据并行能扩展的根本原因。
- 代价模型 :大消息用 Ring(带宽最优),小消息用 Tree(延迟项只有 )。
- 拓扑决定实际带宽,同样 8 卡 all-reduce 跨 8 机比同机慢约十倍;分层算法能把跨节点通信降到 1/8。
- 集合通信是同步点:最慢的节点决定整体,且抖动在取最大值时被放大。
- 并行策略的本质区别就是"用哪个原语、多少量、多久一次"。
- 数值精度与格式 — 上一章
- 效率度量 — 下一章
- 并行的五个维度 — 这些原语的用武之地
- NCCL 与集合通信算法 — 第三卷的详细展开