想象一下,你是一座大型博物馆的馆长,手里有一批珍贵展品要摆到好几个分馆里。每件展品除了本体,还配着一本厚重的历史档案册(记录它的修复记录)和一叠玻璃上的批注贴纸(检查员的修改意见)。博物馆总有放不下东西的时候——展馆的库房(GPU 显存)永远不够用。怎么办呢?最聪明的办法不是加建库房,而是把档案册、批注、甚至展品本体分别放到不同的分馆去,谁要用谁去调阅(等用完了再还回去)。这就是 ZeRO(零冗余优化器)的核心思想:与其每个 GPU 都背着一整套“模型状态的库存”,不如把库存拆开,分配给各卡分工保管。 ZeRO 把要切的“库存”分成三层:优化器状态(档案册)、梯度(批注贴纸)、模型参数(展品本体)。Stage 1 只切档案册,Stage 2 把批注也切了,Stage 3 连展品都拆开分藏。听起来“切得越多越省”,但天下没有免费的午餐——省显存的同时,各分馆之间“调阅”展品的通信次数也变多了。这篇文章就用仓库分馆的比喻,把每个阶段省了多少、贵了多少、什么时候该选哪一档,讲得明明白白。读完你就能对着自己的模型大小和 GPU 集群,直接拍板选 Stage 1、2 还是 3。
对 AI 从业者来说,ZeRO 选型是分布式训练中最常见的“性价比决策”:选低了 OOM 崩溃,选高了白付通信成本。掌握这套决策逻辑,你就能在算力有限时优雅地塞进更大的模型,这是大模型工程师的基本功。
可视化演示
展示ZeRO-1/2/3各阶段显存分区节省、通信开销递增机制、模型规模与集群匹配的选型决策树,切换模式掌握分布式训练优化选型核心。
本节配有交互动效(进入视口后自动加载,约 3KB)。
ZeRO三阶段显存分区节省 ZeRO-1分区优化器状态(占显存最大),4x节省;ZeRO-2额外分区梯度,8x节省;ZeRO-3再分区参数,N_dx节省 分区策略将完整副本切分到各GPU,每卡仅存1/N的对应部分,按需All-Gather恢复 Z1: optimizer | Z2: +gradients | Z3: +parameters ZeRO通过逐步分区三类显存组件实现显存的指数级节省
显存三兄弟
先回到博物馆的库房。一件展品要想顺利展览,得有三样东西:展品本体(模型参数)、检查员写下的批注贴纸(梯度,反向传播算出来的修改方向)、以及一本厚厚的修复档案册(优化器状态,比如 Adam 优化器要记录的一阶动量和二阶动量)。每个 GPU 在训练时,都要把这“三兄弟”在显存里腾出位置。混合精度训练下,每个参数平均要吃掉 16 字节:参数本身存成 FP16 半精度副本占 2 字节,梯度也是 FP16 占 2 字节,剩下的 12 字节全是优化器状态的——4 字节 FP32 主副本、4 字节动量、4 字节方差。
“三兄弟”的体型差距一目了然:优化器账本最胖,占了总显存的四分之三。所以 ZeRO 的第一刀,就砍向这个最肥的账本;第二刀砍向第二重的批注;最后才轮到展品本体。这个“由胖到瘦”的切割顺序,决定了三个阶段的名字和演进关系。
所以当你听说“7B 模型要 112GB 模型状态显存”时,别惊讶——就是 \(16 \times 7 = 112\)(GB)。库房不够用时,馆长自然盯上最占地方的档案柜:12 字节的优化器账本。
- 认识总账单\(K_{\text{all}}\) 是单卡完整存放模型状态所需的总显存,它是参数量的 16 倍。
- 参数副本训练时模型参数以 FP16 半精度保存一份用于前向传播,每个参数 2 字节。
- 梯度批注反向传播算出的梯度同样以 FP16 保存,每个参数再花 2 字节。
- 优化器账本Adam 等优化器要为每个参数维护 FP32 主权重、一阶动量、二阶动量,三者各 4 字节,共 12 字节。
阶段一存账本
Stage 1 的策略,就是把每个展品最厚的“档案册”从中央库房拆散,平均分到 N 个分馆。每个分馆只保管全馆档案的 \(1/N\),查资料时找对应的分馆要就行。这样一来,原本每卡要背着 16 字节/参数的完整负担,现在优化器状态那一大坨(12 字节)被切成了 N 份,每卡只需拿 \(12/N\) 字节。
公式也随之清爽:每参数显存开销从 16 字节降到了 \(4 + 12/N\) 字节。假设你有 8 张卡,那就是 \(4+1.5=5.5\) 字节/参数——比原来省了约三分之二。而且 Stage 1 的通信模式跟普通数据并行(DDP)处于同一量级,因为梯度还是要全网同步,只是同步完各卡各自更新自己手头那部分账本。用大白话说,馆长只是把档案挪了位置,展览流程基本没变,却一下子腾出了大量库房。
Stage 1 是“性价比最高的一刀”:改动最小、通信几乎不变、显存立刻瘦身。如果你只是刚开始接触分布式训练,模型在单卡上勉强能跑、只是显存吃紧,Stage 1 往往是最稳妥的入门选择。
- 固定的 4 字节参数副本和梯度是前向反向的“必需品”,每个 GPU 仍要完整持有,共 4 字节。
- 账本被切分12 字节的优化器状态被切成 N 份,每卡只留总数的 \(1/N\)。
- 相加得结果两者相加就是 Stage 1 的每卡显存公式。
阶段二分批注
Stage 1 省下的空间可能还不够。馆长发现,检查员的“批注贴纸”也挺占地方的——2 字节的梯度虽然在体型上不如账本,但叠加起来依然可观。于是 Stage 2 决定:账本和批注全都分馆存放。现在每个 GPU 只需要保管 \(12/N\) 的优化器状态和 \(2/N\) 的梯度,参数本体仍然每卡留一份完整的。
每参数显存开销进一步降到 \(2 + 14/N\) 字节。8 卡场景下是 \(2+1.75=3.75\) 字节/参数,比 Stage 1 的 5.5 字节又少了近三分之一。为什么 Stage 2 能这么省?因为它把“谁算的梯度谁自己留着更新”,不再把全量梯度广播给所有人。梯度同步的方式也从“所有人拿到完整批注”变为“每人拿属于自己那部分批注”,通信量依然和 DDP 同一量级。
Stage 2 是分布式训练里最常用的“默认档位”:8 卡训练 7B 模型时,每卡模型状态仅 26GB 左右(\(3.75 \times 7\)),配上激活值和大 batch,80GB 的 A100 也游刃有余。它就像把批注贴纸也分馆之后,各分馆终于能同时并行处理自己的那部分检查工作,效率不降、空间大省。
- 参数仍要全量前向计算需要完整模型参数,所以每卡保留 2 字节的参数副本。
- 梯度加入切分14 代表“12 字节优化器状态 + 2 字节梯度”打包切分。
- 合起来最后得到 Stage 2 的每卡显存。
阶段三搬展品
到了 Stage 3,馆长的决心最大:连展品本体(模型参数)也拆开,每个分馆只存 \(1/N\)。这下每参数显存直接降到 \(16/N\) 字节,8 卡时只有 2 字节——理论上显存占用和参数量直接解耦了,想训练多大的模型,只要卡够多、网络够快,就能硬撑下来。
代价是什么?前向传播时要算某一层,得先把分布在各分馆的这层参数“调阅”过来(通信原语叫 all-gather,即把分散的参数片段聚合完整);反向传播时为了算梯度,又得再调阅一次参数;算完梯度后还要把梯度“回收”到各自负责的分馆(通信原语叫 reduce-scatter,即把梯度按归属切片再汇总)。一来一回,通信量比 Stage 2 多出约 50%。在博物馆的比喻里,相当于每次办展览都要把展品从各分馆押运到中央展厅,展览结束再运回去——运一次两次没事,天天运,运输队(网络带宽)就吃不消了。
Stage 3 是“极限省显存”模式,专为大模型而生。当你的单卡连一层模型都塞不下时,Stage 3 几乎是唯一出路;但如果你的模型只有 1B 且卡很多,用 Stage 3 就是杀鸡用牛刀——省下的显存有限,多出来的通信成本却实实在在。
- 三兄弟全切16 字节的完整状态被 N 张卡均分,每卡只留 \(16/N\)。
- 前向调阅每层前向计算前先用 all-gather 聚合该层完整参数。
- 反向回收反向算完梯度后用 reduce-scatter 把各卡负责的梯度切片聚合回去。
通信成本账
回到馆长视角,做决策前得算清楚“运输账”。普通 DDP 每次迭代要把全量梯度做一个 all-reduce(全网求和并广播),通信量约 \(2\psi\)(梯度 2 字节 × 参数量)。Stage 1 和 Stage 2 的通信量和 DDP 保持同一量级,因为梯度数据量没有膨胀,只是换了个聚合方式。Stage 3 就不同了:前向 all-gather 参数、反向再 all-gather 一次参数、随后 reduce-scatter 梯度,合计约 \(3\psi\),是 Stage 2 的 1.5 倍。
这 50% 的增量,换来了显存从“线性下降”变成“反比下降”的质变。用数据说话:8 卡时,Stage 1 每参数需 5.5 字节,Stage 2 需 3.75 字节,Stage 3 只要 2 字节。如果模型是 7B,三者每卡占用分别是 38.5GB、26.25GB、14GB——Stage 3 硬生生把模型状态压到了 DDP 的八分之一。
所以选型时,网络带宽是隐藏的裁判。如果你的集群是千兆以太网,节点间带宽只有 1Gbps 级别,跑 Stage 3 很可能会让训练变成“等快递”——GPU 算完了,参数还没运到。而如果集群有 NVLink 或 InfiniBand 高速互联,Stage 3 的额外通信成本就会被大幅稀释,变成划算的买卖。
- 基准通信量普通 DDP 同步梯度一次 all-reduce 的通信量为 \(2\psi\)。
- 前两阶段持平Stage 1/2 只是把 all-reduce 拆成 reduce-scatter + 局部更新,总量不变。
- 第三阶段倍增多出的 \(1\psi\) 来自前向和反向各一次的参数 all-gather。
选型决策表
现在把决策逻辑收拢成一张三步走的表。第一步看模型体量:1B 以下的小模型,单卡显存够用就别上 ZeRO,或者用 Stage 1 求稳,省下的显存留给更大 batch。第二步看中间地带:1B~13B 的模型配 8 卡,Stage 2 是最甜的点,显存占用适中、通信几乎不涨,这也是绝大多数开源大模型微调任务的默认配置。第三步看大模型:超过 30B 的模型,单卡连参数都放不下,直接 Stage 3(必要时再加 CPU offload),别无选择。
第三步之外还有一道“网速裁决”:同样 13B 模型,在 4 张 A100 且 NVLink 互联的机器上,Stage 2 可以凑合;但如果卡数少、网络又差,硬上 Stage 3 可能比 Stage 2 更慢。可以用一个朴素公式估算每卡模型状态显存:选一个阶段,算出 \(K\) 值,再乘以模型参数量(单位十亿),得到大致 GB 数。这个数字加上激活显存,必须小于单卡显存,同时留出 20% 余量。
回到博物馆的故事:如果你的展品只有一幅画(小模型),一个分馆就够摆,何必大动干戈全城分馆?如果展品是一整套兵马俑(大模型),那就必须把每个陶俑分到不同分馆,哪怕运输队要跑断腿。ZeRO 的阶段选择,本质就是一场“库存空间 vs 运输成本”的博弈——把每一样东西都切开分掉看起来很美,但只有当你真正放不下时,才值得付出那 50% 的额外运费。
- 分子是总需求GoalBytes 指模型状态 + 激活 + 临时缓冲的总显存需求。
- 分母是可用空间GPU 显存打八折,再减去激活显存,得到可用于模型状态的空间。
- 向上取整需要的卡数就是总需求除以单卡可用空间后向上取整。
AI 应用场景
大模型训练平台的基础设施选型
阶段:模型训练与集群资源规划
解决的问题:大模型训练工程师在申请 GPU 资源时,往往不确定该用多少张卡、开哪个 ZeRO 阶段。传统做法是靠经验猜,结果要么显存溢出(OOM)导致训练中断,要么开了过高的阶段,白付通信成本。用本文的估算方法,输入模型参数量和卡数,几分钟内就能算出各阶段每卡显存,直接对接集群调度配置。
具体案例:某团队要在 8×A100-80G 集群上训练 7B 对话模型。估算 Stage 2 每卡模型状态 26GB,加激活约 45GB,余量充足;若改用 Stage 3 反而会把单步耗时从 1.2 秒拉到 1.8 秒(通信开销增加 50%),于是果断选择 Stage 2,训练吞吐提升 30%。
低带宽环境下的微调降级策略
阶段:全参数微调(Full Fine-tuning)
解决的问题:很多企业只有千兆以太网连接的多机集群,盲目按“大模型=Stage 3”的惯例配置,结果跨节点通信成为瓶颈,训练速度惨不忍睹。需要根据实际带宽做阶段降级。
具体案例:某公司在 4 机 × 4 卡、万兆以太网环境下微调 13B 模型。直接上 Stage 3,跨机通信占每步耗时的 70%;改用 Stage 2 + 梯度累积后,虽然每卡显存从 26GB 升到 49GB,但吞吐反而提升了 2 倍——因为通信量降为原来的三分之二。
超大规模模型的极限压榨
阶段:预训练(Pretraining)
解决的问题:训练 65B 以上模型时,单卡完全放不下模型状态,必须用 Stage 3 把参数、梯度、优化器状态全部切分,并配合 CPU Offload 把优化器状态卸载到内存。
具体案例:某高校实验室用 32×A100-40G 训练 65B 模型,Stage 3 下每卡模型状态仅 \(16 \times 65 / 32 = 32.5\)GB,恰好塞进 40G 显存,再配合激活检查点(activation checkpointing)成功跑通预训练。
Python 演示
# -*- coding: utf-8 -*-"""ZeRO 阶段选型小助手: 估算模型状态显存并推荐阶段"""G = 1024 ** 3 # 1GB 字节数 def model_state_gb(params_bn, gpus, stage): """计算每卡模型状态显存(GB); stage 取 0/1/2/3, 0 表示 DDP 基线""" psi = params_bn * 1e9 # 参数个数 if stage == 0: return 16.0 * psi / G if stage == 1: return (4.0 + 12.0 / gpus) * psi / G if stage == 2: return (2.0 + 14.0 / gpus) * psi / G return 16.0 * psi / gpus / G def suggest(params_bn, gpus, gpu_mem, fast_net=True, activation_gb=8.0): """给出选型建议: 返回(推荐阶段, 各阶段显存列表)""" ests = [model_state_gb(params_bn, gpus, s) for s in range(4)] ok = [s for s in range(1, 4) if ests[s] + activation_gb < gpu_mem * 0.8] if not ok: return None, ests # 所有阶段都放不下 # 默认选最低满足的阶段; 网络差时强制不用 Stage 3 if not fast_net and 3 in ok and len(ok) > 1: ok.remove(3) return ok[0], ests if __name__ == "__main__": for params_bn, gpus, mem in [(7, 8, 80), (13, 8, 80), (65, 32, 40)]: rec, ests = suggest(params_bn, gpus, mem, fast_net=True) line = " | ".join(["%.1fGB" % v for v in ests]) print("params=%dG gpus=%d mem=%dG -> DDP/S1/S2/S3: %s -> 推荐 Stage %s" % (params_bn, gpus, mem, line, rec if rec else "无"))
def model_state_gb(params_bn, gpus, stage)核心估算函数,按 16 字节/参数的规则计算不同阶段的每卡模型状态显存。psi = params_bn * 1e9把“十亿参数”换算成具体参数个数,代入公式时保持单位一致。if stage == 0: return 16.0 * psi / GDDP 基线:每卡保存完整模型状态,16 字节 × 参数量再换算成 GB。return (4.0 + 12.0 / gpus) * psi / GStage 1 公式:4 字节固定 + 12 字节优化器状态均分。return (2.0 + 14.0 / gpus) * psi / GStage 2 公式:参数 2 字节固定,优化器+梯度共 14 字节均分。return 16.0 * psi / gpus / GStage 3 公式:全部状态均分到 N 张卡。ests = [model_state_gb(...) ]一次算出 DDP、S1、S2、S3 四档显存,方便横向对比。if ests[s] + activation_gb < gpu_mem * 0.8判断该阶段是否能装下:模型状态 + 激活值必须小于单卡显存的 80%。if not fast_net and 3 in ok and len(ok) > 1网络差时把 Stage 3 从候选里剔除,避免通信瓶颈拖慢训练。return ok[0], ests返回最低能满足的 Stage,遵循“够用就好”的选型原则。
常见误区
误区很多人觉得 Stage 3 显存省得最多,肯定是性能最好的,不管什么模型都直接配 `--zero_stage=3`。结果小模型训练不仅没变快,反而每步都慢了不少。
正解ZeRO 是用通信换显存。Stage 3 的通信量是 Stage 2 的 1.5 倍,如果模型状态本来就能放下,高阶段只会增加额外开销。正确的思路是“够用就好”:用 Stage 1/2 能装下时,就别上 Stage 3;只有单卡放不下完整参数时才考虑它。
为何常见:因为显存节省的数字很直观,而通信开销是看不见摸不着的;新手容易只盯着一项指标做决策。
误区觉得买 80G 的 A100 就能解决一切,ZeRO 是给没钱买大显存的人用的土办法。结果用 80G 卡跑 13B 全参微调照样 OOM。
正解显卡显存和价格不呈线性,80G 卡的价格可能是 40G 卡的两倍多,但省下的显存有限。ZeRO 是通过算法把显存需求按卡数均摊,8 张 40G 卡跑 Stage 3 能承载的模型,大概率比 1 张 80G 卡大得多。选型应该先算 ZeRO 账,再决定要不要升级硬件。
为何常见:人们对“加钱换配置”有路径依赖,忽略了分布式训练里“多个臭皮匠顶个诸葛亮”的规模效应。
误区以为 Stage 3 下每一层都在各卡上独立计算,不需要看到完整模型参数,因此完全不用担心显存里有大块参数。
正解Stage 3 只是“长期存储”切分,但“短期计算”仍需完整参数——前向传播每一层之前必须执行 all-gather 把该层参数聚合到当前卡上,算完再释放。所以运行期显存峰值依然会短暂出现完整的一层参数,只是不长期占用。
为何常见:把“存储”和“计算”两个概念混淆了,以为切分存储就等于切分计算。
误区认为反正都是多卡训练,通信无非是同步一下梯度,Stage 1/2/3 差距不大。
正解Stage 1/2 的通信量与 DDP 同量级,但 Stage 3 因为前后向都要重新聚合参数,通信量约为 Stage 2 的 1.5 倍。在千兆以太网环境,这 50% 的增量可能让训练速度直接腰斩。
为何常见:直觉上“参数切分”只是存储变化,没意识到前向计算需要完整参数这一硬约束,自然忽略聚合参数的通信成本。
误区常常听到 Stage 3 + Offload、ZeRO-Offload 等名词,误以为 Offload 是 Stage 4,是比 Stage 3 更强力的切分方案。
正解Offload 是另一个维度的技术:把优化器状态或参数从 GPU 显存搬到 CPU 内存(甚至 NVMe 硬盘),与 ZeRO 的阶段切分相互正交。ZeRO-Offload 基于 Stage 2,而 Stage 3 + Offload 则是两套技术叠加。选型时要把“切分维度”和“卸载维度”分开考虑。
为何常见:技术名词叠加让新手误以为存在线性升级关系,实际上它们是两个独立的设计轴。
面试题
Q1. ZeRO Stage 1 主要切分了哪部分冗余?
- A.优化器状态← 正确答案
- B.梯度张量
- C.模型参数
- D.中间激活
答案:A。Stage 1 只切分优化器状态(12 字节/参数的大头);梯度要等到 Stage 2 才切,模型参数要到 Stage 3 才切。就像先搬最沉的档案柜,而不是先动展品。
Q2. 混合精度训练下,每个参数平均消耗约 16 字节模型状态显存,其中占比最大的是?
- A.约 4 字节的参数副本
- B.约 8 字节的梯度总和
- C.约 12 字节的优化器状态← 正确答案
- D.约 16 字节的激活缓存
答案:C。FP32 主权重 4 字节 + Adam 动量 4 字节 + 方差 4 字节,共 12 字节;参数和梯度各 2 字节。优化器状态最肥,所以 ZeRO 先切它。
Q3. Stage 2 相比 Stage 1 额外切分了什么?
- A.前向用的全部层参数
- B.反向算出的梯度← 正确答案
- C.学习率与动量表
- D.中间激活缓存
答案:B。Stage 1 只切优化器状态,Stage 2 把反向传播算出的梯度也按卡切分(14 字节均分)。参数要等到 Stage 3 才切,学习率调度器不参与 ZeRO 切分。
Q4. Stage 3 训练时,前向和反向传播中可能频繁出现哪些通信操作?
- A.all-gather 聚合层参数← 正确答案
- B.reduce-scatter 汇总梯度切片← 正确答案
- C.point-to-point 权重单点交换
- D.broadcast 广播全部参数
答案:AB。Stage 3 前向每层前 all-gather 参数,反向算完梯度后 reduce-scatter 梯度切片。它不采用单点交换,也不广播全部参数,而是分散聚合。
Q5. Stage 3 的通信量约为 Stage 2 的多少倍?
- A.0.5 倍
- B.1 倍
- C.1.5 倍← 正确答案
- D.3 倍
答案:C。Stage 2 通信量约 \(2\psi\),Stage 3 因前后向各聚合一次参数并 reduce-scatter 梯度,合计约 \(3\psi\),即 1.5 倍。这 50% 的增量是选型时的重要权衡。
Q6. 8 张 A100-80G 训练 13B 模型,最稳妥的 ZeRO 配置是?
- A.Stage 1 即可
- B.Stage 2 勉强可用
- C.Stage 3 更稳妥← 正确答案
- D.无需 ZeRO 直接跑
答案:C。Stage 2 下每卡模型状态约 49GB,加激活和缓冲逼近 80G 上限;Stage 3 每卡约 26GB,余量充足。单卡放不下完整状态时优先考虑 Stage 3,同时注意网络带宽。
Q7. 公式 \(4 + 12/N\)(字节/参数)对应哪个阶段的每卡显存?
- A.Stage 1← 正确答案
- B.Stage 2
- C.Stage 3
- D.DDP 基线
答案:A。4 字节是固定的参数+梯度,12/N 是切分后的优化器状态。Stage 2 应是 \(2 + 14/N\),Stage 3 是 \(16/N\)。
Q8. 选择 ZeRO 阶段时,以下哪些因素应该纳入考虑?
- A.模型参数量与 GPU 数量← 正确答案
- B.集群网络带宽← 正确答案
- C.优化器类型← 正确答案
- D.训练框架的版本号
答案:ABC。参数量和卡数决定每卡显存,带宽决定 Stage 3 的通信代价是否可承受,优化器类型影响状态字节数(如 SGD 无二阶动量只有 8 字节)。框架版本号不影响 ZeRO 的数学原理。
Q9. Stage 3 在 N 张卡下,每参数模型状态显存为?
- A.16/N 字节← 正确答案
- B.4+12/N 字节
- C.2+14/N 字节
- D.恒为 16 字节
答案:A。Stage 3 把参数、梯度、优化器状态全部切分,16 字节总账按 N 平均分。因此卡越多单卡压力越小,这也是它能训练超大规模模型的原因。
Q10. 关于 ZeRO 的说法,哪些是正确的?
- A.小模型多卡时升级阶段收益递减← 正确答案
- B.Stage 3 通信量约为 Stage 2 的 1.5 倍← 正确答案
- C.Stage 3 运行期仍需临时聚合参数← 正确答案
- D.阶段越高级训练速度一定越快
答案:ABC。小模型本身占显存小,切分收益有限且通信成本突出;Stage 3 通信更高且运行时仍需 all-gather 参数。D 把通信成本忽略了,高阶段省显存不代表训练更快。