数据并行有个隐蔽的浪费:每张卡都存着完整的模型状态。对一个参数量 $\Psi$ 的模型,AdamW 训练下每卡显存固定占用是
$$ \underbrace{2\Psi}_{\text{权重 bf16}} + \underbrace{2\Psi}_{\text{梯度 bf16}} + \underbrace{4\Psi}_{m} + \underbrace{4\Psi}_{v} + \underbrace{4\Psi}_{\text{master fp32}} = 16\Psi\ \text{字节}. $$
7B 模型约 112 GB——还没算激活,单卡 80 GB 的 A100 已经不够。但这些副本在做的事高度冗余:梯度 all-reduce 之后每卡的梯度一样,optimizer.step 之后每卡的权重一样。N 张卡存 N 份相同的东西,这就是 ZeRO(Zero Redundancy Optimizer,Rajbhandari et al. 2020)要消灭的冗余。
三个递进阶段
ZeRO 分三个优化阶段,每个阶段多分片一样东西:
Stage 1:分片优化器状态。第 $i$ 张卡只存 $\frac{1}{N}$ 的 $(m, v)$ 和 master weights。流程上,backward 照常算完整梯度,然后各卡 reduce-scatter 到自己负责的那 $1/N$ 参数分片上(把对应分片的梯度平均职责做完),本地更新自己那份参数,最后 all-gather 把更新后的权重广播回所有卡。显存从 $16\Psi$ 降到 $16\Psi - 12\Psi\cdot\frac{N-1}{N} \approx 4\Psi + \frac{12\Psi}{N}$。通信量与朴素 DP 完全打平——all-reduce 本来就等于 reduce-scatter + all-gather,ZeRO-1 只是把它拆开用对了地方。
Stage 2:再分片梯度。backward 算出的梯度也按参数分片归属存储,每卡只保留自己那 $1/N$ 的梯度,其余分片的梯度 reduce-scatter 到对应卡后立即释放。显存再省 $2\Psi\cdot\frac{N-1}{N}$,到 $\approx 2\Psi + \frac{14\Psi}{N}$。通信量与 Stage 1 相同(梯度反正要流动,只是落地位置变了)。
Stage 3:再分片权重。前向时按需 all-gather 权重分片,用完释放——这是唯一增加通信的阶段:forward 多一次参数 all-gather($\Psi$),backward 再 all-gather 一次($\Psi$)外加梯度 reduce-scatter($\Psi$)。总计通信从 $2\Psi$ 涨到约 $3\Psi$,1.5 倍换显存从 $O(\Psi)$ 降到 $O(\Psi/N)$:64 卡训练 7B 模型,单卡模型状态从 112 GB 压到约 2 GB 权重 + 2 GB 余量。
通信账的精确计算
以每卡每步的字节数计(忽略 $\frac{N-1}{N}$ 系数):
- 朴素 DP:all-reduce 梯度 $2\Psi$。
- ZeRO-1/2:reduce-scatter 梯度 $\Psi$ + all-gather 权重 $\Psi$ = $2\Psi$。打平。
- ZeRO-3:前向 all-gather 权重 $\Psi$ + 反向 all-gather 权重 $\Psi$ + reduce-scatter 梯度 $\Psi$ = $3\Psi$。
ZeRO-3 的额外 $\Psi$ 不是白花的:它买到的不仅是显存。权重按分片流式加载的结构天然支持超大模型——参数量超过 aggregate 显存时,分片 + offload 到 CPU/NVMe 的通路是现成的(DeepSpeed 的 ZeRO-Infinity)。对超大模型训练,ZeRO-3 实际上是唯一能起跑的方案(不想切模型的前提下)。
工程上还有两个 ZeRO-3 的配套机制:预取(按层提前 all-gather 下一块权重,把通信藏进计算)和分片粒度(分片按参数分组而非逐个参数,避免元数据爆炸)。不做预取的朴素 ZeRO-3 在跨机会被通信拖死——ZeRO-3 的 1.5 倍通信是理论下限,不做流水化时实测可能到 2 倍以上。
与模型并行的关系、以及 FSDP
ZeRO 和上一篇的 TP/PP 正交:ZeRO 切的是"副本冗余",TP/PP 切的是"模型本身"。实践中两者常叠加——TP 切到单机 8 卡,跨机组之间用 ZeRO-3 替代朴素 DP,兼得低延迟单机通信和跨机显存扩展。
PyTorch 生态里 ZeRO 的对应物是 FSDP(Fully Sharded Data Parallel),语义上等于 ZeRO-3,默认分片粒度是"自动 wrap 的模块块",配合 prefetch 策略和 SHARD_GRAD_OP(对应 ZeRO-2)档位。DeepSpeed 的 ZeRO 则更细:三档独立开关、CPU offload、以及 ZeRO++ 用量化通信把跨机 all-gather 再压一刀。选型的经验法则:单机内或中小规模 FSDP 足够顺;需要 offload 或极致调优再进 DeepSpeed。
小结
ZeRO 的洞见朴素到近乎平凡:数据并行副本的梯度同步协议里,藏着"谁真正需要持有哪份状态"的全部信息——reduce-scatter 天然把分片归属分好了。三个递进阶段分别消灭优化器状态、梯度、权重的冗余,前两档通信零成本,第三档用 1.5 倍通信把单卡模型显存压到 $O(\Psi/N)$。它不改变模型切分、不引入 TP 的延迟敏感通信、不制造 PP 的气泡,是大模型分布式训练里性价比最高的一刀。