Ring AllReduce Walkthrough
本页以一个具体数值示例,逐轮展示 Ring AllReduce 的 Reduce-Scatter 和 All-Gather 两个阶段。
设定
3 个 GPU 排成环形:GPU 0 → GPU 1 → GPU 2 → GPU 0
每个 GPU 持有一个 6 元素向量,均分为 3 个 chunk(每 chunk 2 个元素):
| Chunk 0 | Chunk 1 | Chunk 2 | |
|---|---|---|---|
| GPU 0 | [1, 2] | [3, 4] | [5, 6] |
| GPU 1 | [7, 8] | [9, 10] | [11, 12] |
| GPU 2 | [13, 14] | [15, 16] | [17, 18] |
目标:每个 GPU 都得到全部元素之和 [21, 24, 27, 30, 33, 36]。
阶段一:Reduce-Scatter( 轮)
每轮中,每个 GPU 向右邻居发送一个 chunk,同时从左邻居接收一个 chunk 并累加到本地。
Round 1
每个 GPU 发送自己”负责”的 chunk,接收左邻居的 chunk 并累加:
| GPU | 发送 → 右邻居 | 从左邻居接收 | 累加到本地 chunk |
|---|---|---|---|
| GPU 0 | Chunk 0 [1, 2] → GPU 1 | Chunk 2 [17, 18] from GPU 2 | Chunk 2: [5+17, 6+18] = [22, 24] |
| GPU 1 | Chunk 1 [9, 10] → GPU 2 | Chunk 0 [1, 2] from GPU 0 | Chunk 0: [7+1, 8+2] = [8, 10] |
| GPU 2 | Chunk 2 [17, 18] → GPU 0 | Chunk 1 [9, 10] from GPU 1 | Chunk 1: [15+9, 16+10] = [24, 26] |
Round 1 后的状态(粗体为累加后的 chunk):
| Chunk 0 | Chunk 1 | Chunk 2 | |
|---|---|---|---|
| GPU 0 | [1, 2] | [3, 4] | [22, 24] |
| GPU 1 | [8, 10] | [9, 10] | [11, 12] |
| GPU 2 | [13, 14] | [24, 26] | [17, 18] |
Round 2
每个 GPU 发送上一轮刚累加过的 chunk:
| GPU | 发送 → 右邻居 | 从左邻居接收 | 累加到本地 chunk |
|---|---|---|---|
| GPU 0 | Chunk 2 [22, 24] → GPU 1 | Chunk 1 [24, 26] from GPU 2 | Chunk 1: [3+24, 4+26] = [27, 30] ✅ |
| GPU 1 | Chunk 0 [8, 10] → GPU 2 | Chunk 2 [22, 24] from GPU 0 | Chunk 2: [11+22, 12+24] = [33, 36] ✅ |
| GPU 2 | Chunk 1 [24, 26] → GPU 0 | Chunk 0 [8, 10] from GPU 1 | Chunk 0: [13+8, 14+10] = [21, 24] ✅ |
Reduce-Scatter 结果
每个 GPU 恰好持有一个完整归约的 chunk:
| GPU | 负责的 Chunk | 值 |
|---|---|---|
| GPU 0 | Chunk 1 | [27, 30] |
| GPU 1 | Chunk 2 | [33, 36] |
| GPU 2 | Chunk 0 | [21, 24] |
验证:21+24+27+30+33+36 = 1+2+...+18 = 171 ✓
阶段二:All-Gather( 轮)
现在每个 GPU 持有完整结果的 ,需要通过 All-Gather 让所有 GPU 都拥有完整结果。操作与 Reduce-Scatter 类似,但接收后直接覆盖而非累加。
Round 1
| GPU | 发送 → 右邻居 | 从左邻居接收 → 覆盖本地 |
|---|---|---|
| GPU 0 | [27, 30] → GPU 1 | [21, 24] from GPU 2 → 覆盖 Chunk 0 |
| GPU 1 | [33, 36] → GPU 2 | [27, 30] from GPU 0 → 覆盖 Chunk 1 |
| GPU 2 | [21, 24] → GPU 0 | [33, 36] from GPU 1 → 覆盖 Chunk 2 |
Round 1 后(✅ 表示已拥有完整归约值):
| Chunk 0 | Chunk 1 | Chunk 2 | |
|---|---|---|---|
| GPU 0 | ✅ [21, 24] | ✅ [27, 30] | — |
| GPU 1 | — | ✅ [27, 30] | ✅ [33, 36] |
| GPU 2 | ✅ [21, 24] | — | ✅ [33, 36] |
Round 2
| GPU | 发送 → 右邻居 | 从左邻居接收 → 覆盖本地 |
|---|---|---|
| GPU 0 | [21, 24] → GPU 1 | [33, 36] from GPU 2 → 覆盖 Chunk 2 |
| GPU 1 | [27, 30] → GPU 2 | [21, 24] from GPU 0 → 覆盖 Chunk 0 |
| GPU 2 | [33, 36] → GPU 0 | [27, 30] from GPU 1 → 覆盖 Chunk 1 |
最终结果
| Chunk 0 | Chunk 1 | Chunk 2 | 拼接 | |
|---|---|---|---|---|
| GPU 0 | [21, 24] | [27, 30] | [33, 36] | ✅ [21, 24, 27, 30, 33, 36] |
| GPU 1 | [21, 24] | [27, 30] | [33, 36] | ✅ [21, 24, 27, 30, 33, 36] |
| GPU 2 | [21, 24] | [27, 30] | [33, 36] | ✅ [21, 24, 27, 30, 33, 36] |
所有 GPU 持有相同的完整归约结果。
通信量统计
整个过程中,每个 GPU:
- Reduce-Scatter:发送 2 个 chunk × 2 元素 = 4 个元素
- All-Gather:发送 2 个 chunk × 2 元素 = 4 个元素
- 总计:8 个元素
公式验证: ✓
Comments