为什么需要 All-Reduce?
在分布式深度学习训练中,每个 GPU 独立前向传播和反向传播计算出自己的梯度。但这些梯度是局部的——为了让所有 GPU 上的模型参数保持一致,需要把所有人的梯度求和(或求平均),再把结果分发回每个 GPU。
这就是 All-Reduce 操作:N 个 GPU,每个人有自己的数据,最终每个人都拿到所有人的归约结果(比如总和或均值)。
传统方案的问题
| 方案 | 每 GPU 通信量 | 瓶颈 |
|---|---|---|
| Parameter Server(参数服务器) | 2 × data | 中心节点带宽随 N 线性增长 |
| 全互联 All-to-All | 大 | GPU 越多越难实施 |
Ring All-Reduce 的核心洞察:把 GPU 围成一个环,只和邻居通信,去中心化,把带宽压力均匀分摊到每条链路上。
设定:4 GPU 围成一圈
环:GPU0 → GPU1 → GPU2 → GPU3 → GPU0(顺时针)。
每个 GPU 上有自己的梯度数组,被均匀切成 N = 4 块(chunk):
GPU0: [a0, a1, a2, a3]
GPU1: [b0, b1, b2, b3]
GPU2: [c0, c1, c2, c3]
GPU3: [d0, d1, d2, d3]
目标:让所有 GPU 都得到:
[a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3]
Phase 1: Scatter-Reduce(分散-归约,N-1 = 3 轮)
核心规则:每个 chunk group j 从 GPU j 出发,沿环传播 N-1 站。每到一站和本地数据累加,最终落在 GPU (j-1) mod N 上。
Round 1.1 — 发送自己的起始块
每个 GPU 发出自己的 “起始块”(chunk 索引 = GPU 编号)给下一个邻居:
| 发送方 | 内容 | → | 接收方 | 操作 |
|---|---|---|---|---|
| GPU0 | a0 | → | GPU1 | b0 += a0 |
| GPU1 | b1 | → | GPU2 | c1 += b1 |
| GPU2 | c2 | → | GPU3 | d2 += c2 |
| GPU3 | d3 | → | GPU0 | a3 += d3 |
结果:
GPU0: [a0, a1, a2, a3+d3 ]
GPU1: [b0+a0, b1, b2, b3 ]
GPU2: [c0, c1+b1, c2, c3 ]
GPU3: [d0, d1, d2+c2, d3 ]
Round 1.2 — 接力转发
每个 GPU 把上一轮收到的部分和发给下一个邻居:
| 发送方 | 内容 | → | 接收方 | 操作 |
|---|---|---|---|---|
| GPU0 | a3+d3 | → | GPU1 | b3 += a3+d3 |
| GPU1 | b0+a0 | → | GPU2 | c0 += b0+a0 |
| GPU2 | c1+b1 | → | GPU3 | d1 += c1+b1 |
| GPU3 | d2+c2 | → | GPU0 | a2 += d2+c2 |
结果:
GPU0: [a0, a1, a2+d2+c2, a3+d3 ]
GPU1: [b0+a0, b1, b2, b3+a3+d3 ]
GPU2: [c0+b0+a0, c1+b1, c2, c3 ]
GPU3: [d0, d1+c1+b1, d2+c2, d3 ]
此时走过的路径:
- Chunk 0 从 GPU0 出发,经过 1→2,累计了 a0 + b0 + c0,落在 GPU2
- Chunk 1 从 GPU1 出发,经过 2→3,累计了 b1 + c1 + d1,落在 GPU3
- Chunk 2 从 GPU2 出发,经过 3→0,累计了 c2 + d2 + a2,落在 GPU0
- Chunk 3 从 GPU3 出发,经过 0→1,累计了 d3 + a3 + b3,落在 GPU1
Round 1.3 — Scatter-Reduce 最后一站
| 发送方 | 内容 | → | 接收方 | 操作 |
|---|---|---|---|---|
| GPU0 | a2+d2+c2 | → | GPU1 | b2 += a2+d2+c2 |
| GPU1 | b3+a3+d3 | → | GPU2 | c3 += b3+a3+d3 |
| GPU2 | c0+b0+a0 | → | GPU3 | d0 += c0+b0+a0 |
| GPU3 | d1+c1+b1 | → | GPU0 | a1 += d1+c1+b1 |
Scatter-Reduce 结束。 此时每个 GPU 恰好持有一个完整归约的 chunk(加粗):
GPU0: [a0, 🔴 a1+b1+c1+d1, a2+d2+c2, a3+d3 ]
GPU1: [b0+a0, b1, 🔴 a2+b2+c2+d2, b3+a3+d3 ]
GPU2: [c0+b0+a0, c1+b1, c2, 🔴 a3+b3+c3+d3]
GPU3: [🔴 a0+b0+c0+d0, d1+c1+b1, d2+c2, d3 ]
验证——每个完整块都是四个 GPU 的对应位置之和:
- GPU3 的 chunk 0 = a0 + b0 + c0 + d0 ✓
- GPU0 的 chunk 1 = a1 + b1 + c1 + d1 ✓
- GPU1 的 chunk 2 = a2 + b2 + c2 + d2 ✓
- GPU2 的 chunk 3 = a3 + b3 + c3 + d3 ✓
Phase 2: All-Gather(全收集,N-1 = 3 轮)
核心规则:把 Phase 1 中分散在各 GPU 的完整 chunk 沿环广播。收到的不再累加,直接覆盖对应位置。
Round 2.1
每个 GPU 发出自己持有的完整 chunk:
| 发送方 | 发送内容 | → | 接收方 | 覆盖 |
|---|---|---|---|---|
| GPU3 | chunk 0: a0+b0+c0+d0 | → | GPU0 | chunk 0 → 完整 |
| GPU0 | chunk 1: a1+b1+c1+d1 | → | GPU1 | chunk 1 → 完整 |
| GPU1 | chunk 2: a2+b2+c2+d2 | → | GPU2 | chunk 2 → 完整 |
| GPU2 | chunk 3: a3+b3+c3+d3 | → | GPU3 | chunk 3 → 完整 |
GPU0: [a0+b0+c0+d0, a1+b1+c1+d1, ., . ]
GPU1: [., a1+b1+c1+d1, a2+b2+c2+d2, . ]
GPU2: [., ., a2+b2+c2+d2, a3+b3+c3+d3 ]
GPU3: [a0+b0+c0+d0, ., ., a3+b3+c3+d3 ]
现在每个 GPU 有 2 个完整 chunk。
Round 2.2
转发上一轮刚收到的完整 chunk:
| 发送方 | 发送内容 | → | 接收方 | 覆盖 |
|---|---|---|---|---|
| GPU0 | chunk 0: a0+b0+c0+d0 | → | GPU1 | chunk 0 → 完整 |
| GPU1 | chunk 1: a1+b1+c1+d1 | → | GPU2 | chunk 1 → 完整 |
| GPU2 | chunk 2: a2+b2+c2+d2 | → | GPU3 | chunk 2 → 完整 |
| GPU3 | chunk 3: a3+b3+c3+d3 | → | GPU0 | chunk 3 → 完整 |
GPU0: [a0+b0+c0+d0, a1+b1+c1+d1, ., a3+b3+c3+d3 ]
GPU1: [a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, . ]
GPU2: [., a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3 ]
GPU3: [a0+b0+c0+d0, ., a2+b2+c2+d2, a3+b3+c3+d3 ]
Round 2.3 — 最后一圈
| 发送方 | 发送内容 | → | 接收方 | 覆盖 |
|---|---|---|---|---|
| GPU0 | chunk 3: a3+b3+c3+d3 | → | GPU1 | chunk 3 → 完整 |
| GPU1 | chunk 0: a0+b0+c0+d0 | → | GPU2 | chunk 0 → 完整 |
| GPU2 | chunk 1: a1+b1+c1+d1 | → | GPU3 | chunk 1 → 完整 |
| GPU3 | chunk 2: a2+b2+c2+d2 | → | GPU0 | chunk 2 → 完整 |
All-Gather 完成。 每个 GPU 都拥有完整的归约结果:
GPU0: [a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3]
GPU1: [a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3]
GPU2: [a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3]
GPU3: [a0+b0+c0+d0, a1+b1+c1+d1, a2+b2+c2+d2, a3+b3+c3+d3]
四个 GPU 的结果完全一致 🎉
总结
Scatter-Reduce (3 轮) All-Gather (3 轮)
每个 chunk j 从 GPU j 出发,走 N-1 站, 完整 chunk 沿环广播,收到即覆盖
每到一站做累加,最后落在 GPU j-1
GPU0 ─── a0 ──→ GPU1 GPU0 ── chunk0 ──→ GPU1
↑ ↓ ↑ ↓
GPU3 ←── d3 ─── GPU2 GPU3 ←─ chunk2 ── GPU2
(每轮传播一个块) (每轮传播一个完整块)
总轮数 2 × (N - 1) = 6 轮
总通信量 每 GPU 发送约 2 × data_size
瓶颈 无中心节点,带宽均匀分摊到每条链路
为什么 Ring All-Reduce 在分布式训练中被广泛采用?
- 带宽最优:每 GPU 的通信量与 GPU 总数 N 几乎无关(≈ 2× data_size),这在数百 GPU 的大规模训练中至关重要
- 去中心化:无单点瓶颈,无需昂贵的交换机或专用拓扑
- 实现简洁:NCCL 和 Horovod 都内置了高效的 ring all-reduce 实现
- 传输大数据最优:Ring 的延迟随 N 线性增长,但带宽恒定——恰好匹配深度学习梯度的大数据块特征
其他 All-Reduce 方案对比
| 方案 | 适用场景 | 特点 |
|---|---|---|
| Ring All-Reduce | 大数据块(梯度) | 带宽最优,延迟 O(N) |
| Tree All-Reduce | 小数据块 | 延迟 O(log N),但中间节点有带宽瓶颈 |
| Recursive Halving-Doubling | 计算资源充裕 | 延迟 O(log N),需要更多中间内存 |
| 全互联 | GPU 数量极少 | 只需 1 轮,但扩展性最差 |
参考资料
- Baidu SVAIL 博客: Bringing HPC Techniques to Deep Learning
- Uber Horovod: Meet Horovod: Uber’s Open Source Distributed Deep Learning Framework
- NCCL 文档: NVIDIA Collective Communications Library

358

被折叠的 条评论
为什么被折叠?



