4. ZeRO Stage 1

在混合精度训练中,显存占用的大头往往不是模型参数本身,而是优化器状态。以 Adam 为例,每个可训练参数需要维护一阶动量与二阶动量两份 FP32 副本,再加上 FP32 主权重,合计约 12 字节/参数。当模型达到十亿甚至百亿参数时,优化器状态会迅速成为显存瓶颈。ZeRO Stage 1 的核心思路非常直接:把优化器状态按数据并行进程切分,每个 GPU 只保存 1/N 的优化器状态,从而把这一部分显存降到原来的 1/N。

4.1 核心原理:分片、收集与释放

ZeRO Stage 1 只对优化器状态做分片,模型参数和梯度仍然在每个数据并行进程上完整保留。训练一步的流程可以概括为“前向反向照常、优化器状态分片更新、参数广播同步”三个阶段:

  • 前向与反向:每个 rank 用完整参数计算梯度,梯度通过 AllReduce 在数据并行组内求平均,得到一致的完整梯度。
  • 优化器更新:每个 rank 只负责自己分片内的那部分参数,用本地保存的优化器状态执行 Adam 更新,得到更新后的参数分片。
  • 参数收集:通过 AllGather 把各 rank 更新后的参数分片收集起来,拼成完整参数,供下一轮前向使用。

关键点在于:优化器状态是“常驻分片”,而完整参数只在更新后短暂地通过 AllGather 重建。这样既保证了数学等价性,又显著降低了优化器状态的显存占用。

ZeRO Stage 1 单步训练数据流 Rank 0 完整参数 P 优化器状态 1/4 Rank 1 完整参数 P 优化器状态 1/4 Rank 2 完整参数 P 优化器状态 1/4 Rank 3 完整参数 P 优化器状态 1/4 AllReduce 梯度 → 每个 rank 得到一致的完整梯度 G 各 rank 用本地 1/4 优化器状态更新对应参数分片 AllGather 参数分片 → 重建完整参数 P,进入下一轮前向

4.2 配置参数:一行开关背后的细节

在 DeepSpeed 中启用 ZeRO Stage 1 只需要在配置文件中设置 zero_optimization.stage = 1。但要让训练真正稳定高效,还需要关注几个配套参数:

参数 作用 Stage 1 建议
stage 选择 ZeRO 优化阶段 设为 1,仅分片优化器状态
allgather_partitions 是否用 AllGather 收集参数分片 true,默认即可
allgather_bucket_size AllGather 的桶大小 5e8 起步,显存紧张可调小
reduce_scatter 是否用 ReduceScatter 替代 AllReduce Stage 1 通常保持 false
overlap_comm 通信与计算重叠 true,可提升吞吐
contiguous_gradients 梯度连续内存布局 true,减少碎片
提示:Stage 1 的显存收益主要来自优化器状态。若模型参数本身已经放不下,Stage 1 无法解决,需要升级到 Stage 2 或 Stage 3。

4.3 实战演练:从零跑通 Stage 1

下面以 HuggingFace Transformers 模型为例,展示一个最小可运行的 DeepSpeed Stage 1 配置与启动命令。

{
  "train_batch_size": 32,
  "gradient_accumulation_steps": 1,
  "fp16": {
    "enabled": true
  },
  "zero_optimization": {
    "stage": 1,
    "allgather_partitions": true,
    "allgather_bucket_size": 5e8,
    "overlap_comm": true,
    "contiguous_gradients": true,
    "reduce_scatter": false
  },
  "optimizer": {
    "type": "Adam",
    "params": {
      "lr": 2e-5,
      "betas": [0.9, 0.999],
      "eps": 1e-8,
      "weight_decay": 0.01
    }
  }
}

启动命令如下,注意 --num_gpus 要与实际数据并行规模一致:

deepspeed --num_gpus=4 train.py \
  --deepspeed ds_zero_stage1.json \
  --model_name_or_path bert-large-uncased \
  --per_device_train_batch_size 8 \
  --output_dir ./output

训练启动后,日志中会出现类似下面的显存统计,可以直观看到优化器状态被分片:

[Rank 0] Zero Stage 1: optimizer state partitioned
[Rank 0] Before: optimizer state 12.0 GB per GPU
[Rank 0] After : optimizer state  3.0 GB per GPU (1/4)
[Rank 0] AllGather bucket size: 500000000

4.4 误区与复盘

  • 误区一:Stage 1 能省参数显存。不能。Stage 1 只分片优化器状态,模型参数和梯度仍然完整复制,参数显存不变。
  • 误区二:分片后不需要通信。仍然需要 AllReduce 梯度、AllGather 参数,通信量比纯数据并行略高,但换来显存下降。
  • 误区三:Stage 1 一定比 Stage 2 快。不一定。Stage 2 进一步分片梯度,显存更省,但通信模式不同;应根据显存与带宽实测选择。
  • 复盘要点:先确认瓶颈是优化器状态而非激活值;再检查 allgather_bucket_size 是否过大导致显存峰值;最后用 overlap_comm 观察吞吐变化。
一句话总结:ZeRO Stage 1 用“优化器状态分片 + 参数 AllGather 重建”的方式,把 Adam 的 12 字节/参数开销降到约 1/N,是数据并行训练中性价比最高、改动最小的显存优化手段。