ZeRO-Offload:把优化器状态卸载到 CPU 内存,用时间换空间的极致操作

我第一次算训练 7.5B 模型的显存时,对着数字发了好半天呆:模型参数是 FP16,15GB;但加上 Adam 优化器之后,要求 120GB。差了 8 倍。到底谁才是显存大户?答案是优化器状态。

AI technology illustration

后来看到 ZeRO-Offload 论文,我的第一反应是:这不会是用 CPU 硬扛大模型训练吧?肯定慢得没法用。看完才发现,它比我想象的聪明——它只是把最不该留在 GPU 的那部分内存,搬到了 CPU 那边去。

等等,为什么优化器状态比模型参数还大?

混合精度训练里,真正引导模型收敛的是 FP32 副本,FP16 模型只是给前向和反向读的“小抄”。Adam 要维护一阶动量、二阶动量,还有 FP32 Master weight。所以一个参数,背后跟着一串“行李”:

数据 精度 每参数占用 7.5B 模型总量
模型参数(FP16) FP16 2 B 15 GB
梯度 FP16 2 B 15 GB
Master weight FP32 4 B 30 GB
Momentum(一阶动量) FP32 4 B 30 GB
Variance(二阶动量) FP32 4 B 30 GB

所以一个参数总共 16 字节。其中参数和梯度只占 4 字节,剩下 12 字节全是优化器状态。你不妨把这个比例记在心里:Adam 的优化器状态是模型参数的 6 倍。这就是为什么 7.5B 的模型参数只有 15GB,训练起来却要 120GB 显存。

ZeRO-Offload 的卸载决定:什么该留下,什么该搬走

ZeRO 系列之前的思路是:状态还在 GPU 上,只是切碎,分给多张卡。ZeRO-Offload 换了一个维度:把状态放到 GPU 以外。但并不是全盘搬走——它有明确的取舍。

留下 GPU 的是:FP16 模型参数和激活值。这两个东西前向反向每一层都要用,搬走代价太大。

搬去 CPU 的是:FP32 Master weight、Momentum、Variance,还有梯度。它们只在优化器更新这一个环节需要被读取。于是整个训练循环变成这样:

  1. GPU 前向计算,保存激活值;反向计算逐层算出梯度。
  2. 某一层的梯度一算完,就切成小块发给 CPU,不用等整轮反向结束。
  3. CPU 收到梯度小块后,立刻执行 Adam 更新:用本地的 Master weight 和动量状态,产出新的 FP16 参数,传回 GPU。
  4. 与此同时,GPU 继续算下一层梯度,两边像流水线一样并行。

这个流程的关键不是把 Adam 放到 CPU 上跑,而是把通信和计算重叠起来。反向传播本身就是逐层释放依赖的,梯度可以一边生成一边传,不用停下来。

在 DeepSpeed 配置里,打开 ZeRO-Offload 只需要加一个字段:

实际配置长这样
{
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {
      "device": "cpu"
    }
  }
}

官方文档说得很清楚:优化器状态被卸载到 CPU,计算也在 CPU 上完成。转发、反向、梯度计算仍在 GPU。

时间换空间的账,怎么算才划算?

要判断这套方案值不值,先看看数据在不同位置的移动速度:

数据通道 带宽量级 特点
GPU 显存(HBM) 约 1 TB/s 极快,但容量小
CPU 内存(DDR4/DDR5) 约 50 GB/s 便宜,容量大
CPU-GPU 之间(PCIe 4.0 x16) 约 32 GB/s 整机最窄的咽喉
NVMe SSD 约 2-4 GB/s 容量更大,但更慢

可以看到,PCIe 带宽比显存带宽低了两个数量级。如果每一步都等全部梯度传完再更新,7.5B 模型光传梯度就是 15GB,接近 0.5 秒——训练速度直接崩掉。ZeRO-Offload 的解法是让 CPU 和 GPU 同时干活:GPU 算反向,CPU 算更新,PCIe 上只有小块数据来回流动。这样,只要 GPU 一个反向步的计算时间大于“CPU 更新时间 + 传输时间”,速度损失就能被藏起来。

这其实就是时间换空间的本质:你多等了数据在 PCIe 上跑的几百微秒,换回了可能让 OOM 的几十 GB 显存。对于动不动上百亿参数的模型,这笔买卖很划算。

我最早对 ZeRO-Offload 的理解是“拿 CPU 做分布式训练”,后来才发现自己把顺序想反了。它不是把 GPU 的工作搬给 CPU,而是让 CPU 去做它最不该做的活——Adam 更新——同时让 GPU 专心做它最擅长的前向和反向。异构计算的精髓是各干各的,不是谁顶替谁。

效果和边界:它不是免费的

ZeRO-Offload 论文的实验里,一块 V100 能训练 130 亿参数的模型,比当时的最好结果大了 10 倍。这个结果很有冲击力,也直接带火了 CPU offload 的思路。

但它的边界同样明显:

  • 只对“状态大户”优化器划算。Adam、AdamW、LAMB 这类优化器状态大,卸载收益高;SGD 只有一份动量甚至没有,搬走反而浪费。
  • PCIe 始终是上限。如果你的 GPU 极快而 CPU 更新太慢,通信隐藏不了,速度必然掉。
  • CPU 内存带宽会在多卡场景先耗光。每块 GPU 都往 CPU 搬状态,主机内存的读写带宽会先于显存成为瓶颈。
  • 它不解决激活值占显存的问题。ZeRO-Offload 只管优化器状态和梯度,激活值仍然留在 GPU 上,超大模型还得配合激活检查点。

有人容易把 ZeRO-Offload 和 ZeRO-3 混在一起,其实它们走的是两条路。ZeRO-3 用多张 GPU 分担所有状态,靠网络通信换显存;ZeRO-Offload 用 CPU 内存和 CPU 算力扩容,靠 PCIe 换显存。两者可以组合,但出发点不同。

一个常见误解

“把状态放 CPU 上,训练不会慢到没法看吗?”

不一定。ZeRO-Offload 之所以能成立,是因为 CPU 和 PCIe 在反向传播期间一直处于忙碌状态。如果 GPU 的计算时间足够长,卸载开销几乎被隐藏。真正需要担心的不是“慢”,而是你的 CPU 内存带宽撑不撑得住。所以它在 10B 级模型上表现优秀,到 100B 级就得继续往 NVMe 分层卸载。

看到这里你应该明白了:ZeRO-Offload 的价值不是“把优化器状态放到 CPU”这个动作本身,而是它迫使你重新思考训练循环里到底哪些状态是频繁访问的,哪些只需要偶尔访问一次。这种“远近分层”的思路,后来在 ZeRO-Infinity 里被推到极致——CPU 放不下的就往 NVMe 放。

直到今天,训练更大的模型依然绕不开这个命题:显存永远不够,计算永远可以等一等。ZeRO-Offload 用一次极其克制的卸载,换来十亿参数的容量跃迁。它不是一个终极方案,但它是理解 AI 训练内存分层的第一课。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/320.html

(0)
上一篇 2026年8月28日
下一篇 2026年8月28日

相关推荐