AllReduce 让一组进程各持一份数据,归约(求和/取最大等)后使所有进程拿到相同结果——它是数据并行训练中梯度同步的通信核心,Ring AllReduce 以带宽最优的方式实现它。

AllReduce 集合通信

语义:Reduce 与 Broadcast 的合一

AllReduce = Reduce + Broadcast: 个进程各持等长向量,先按归约算子(SUM、MAX 等)合成一份全局结果,再让每个进程都持有它。与其他集合操作对照(API 层面见 torch.distributed 通信原语):

操作语义结果落在谁手里
Broadcast根节点数据发往所有进程全体
Reduce归约到根节点仅根
AllReduceReduce + Broadcast 合一全体
ReduceScatter归约后按分片散布每进程一个分片
AllGather各进程数据拼接后分发全体

关键分解:AllReduce = ReduceScatter + AllGather。Ring AllReduce 的两阶段正源于此。

朴素实现与主节点瓶颈

最直接的做法:选 GPU0 当主节点,先 Reduce 再 Broadcast。以 3 GPU、SUM 为例(本地值分别为 ):

  1. GPU1、GPU2 把数据发给 GPU0,GPU0 求和得
  2. GPU0 广播 ,三卡全部持有

问题不在步数而在带宽:设每卡数据量 ,主节点需接收 、再发送 ,全部压在一张网卡上; 越大主节点越是热点,其余链路却大量闲置。通信量随 线性增长,加卡无法摊薄。

Ring AllReduce

两阶段流程

张卡排成环,数据切成 个分片,每步每卡只沿环发送一个分片():

Scatter-Reduce(N−1 步):沿环发送分片并逐份累加
  GPU0 ─▶ GPU1 ─▶ GPU2 ─▶ … ─▶ GPU0
  结束时:每卡恰好完整归约出一个分片
 
AllGather(N−1 步):沿环转发已完整的分片
  GPU0 ─▶ GPU1 ─▶ GPU2 ─▶ … ─▶ GPU0
  结束时:每卡持有完整归约结果

任一时刻每卡只发一份、收一份, 条链路全速并行,不存在主节点。

通信量推导

  • Scatter-Reduce: 步 × 每步每卡发送 ,每卡共发送
  • AllGather:同样 步,再发送

合计每卡发送量:

直觉:结果人人要有一份,每卡至少得”送出自己的 、收回别人的 “,理论下界即 ——Ring 恰好达到该下界,故称带宽最优(bandwidth-optimal);且发送量随 增大趋近常数 。

维度朴素主节点Ring
单卡最大发送量(root 独担)(均摊)
瓶颈root 网卡带宽无
随 变化线性恶化趋近
采用者—NCCL、DDP、DeepSpeed

带宽最优与延迟的取舍

总时间近似:

( 为每步启动延迟, 为链路带宽)。大 时带宽项主导,Ring 占优;小 时 步的延迟项主导。NCCL 因此内置 Ring、Tree(双二叉树,关键路径 )等多套实现,按消息大小与实际拓扑(NVLink、PCIe、InfiniBand)自动选择或混合,使用者无需感知。

DDP 梯度同步中的地位

数据并行(见 分布式训练总览)的核心通信需求只有一条:每步 backward 结束后各副本梯度求平均。DDP 的 Reducer 把梯度组织成桶(bucket,默认 25 MB),按参数逆序分桶使先就绪的梯度先通信——桶一满即异步发起 all-reduce,与剩余反向计算重叠;归约结果除以 world_size 得平均梯度,随后各进程执行相同的 torch.optim 更新,参数保持一致。

import torch
import torch.distributed as dist
 
g = torch.ones(4, device="cuda") * dist.get_rank()
dist.all_reduce(g, op=dist.ReduceOp.SUM)  # 所有进程得到同一份求和结果
g /= dist.get_world_size()                # DDP 语义:取平均

进程组由 torchrun 拉起。更大规模的 Megatron/DeepSpeed 训练里,梯度与参数分片的底层通信原语仍然是 AllReduce 及其组成部分 ReduceScatter/AllGather。

应用场景

场景AllReduce 的角色
DDP 训练梯度求平均,保持副本一致
混合并行的数据并行维度ZeRO/Megatron 梯度同步
多卡推理/特征汇总各卡输出(logits、embedding)
全局统计跨进程聚合 loss、accuracy