每张卡都放不下怎么办?
本课一问:数据并行解决了「慢」,可每张卡仍要放下完整的 444.5 GB。8 张卡上存着 8 份一模一样的东西——能不能每张卡只存一份的 1/8?
这一课先不碰:激活怎么切(第 3 课的序列并行、岔路 1);把状态挪到 CPU 内存或硬盘(只在 Dig 里提一句);层内怎么切矩阵(第 3 课)。
从已有出发
切什么每张卡一份完整模型,各算全局 batch 的 1/N;反向之后把 N 份梯度加起来,每张卡用同一份梯度更新。
多出的通信all-reduce 梯度
有多大锚点 BF16 梯度 55.6 GB;8 卡 ring all-reduce 每卡每步发 97.2 GB。
切什么优化器状态、梯度、参数按 N 份切开,每张卡只存、只更新自己那份;ZeRO-3 在用到一层之前把这一层的参数拼回来。
多出的通信reduce-scatter 梯度 + all-gather 参数
有多大8 卡 ZeRO-3:每卡模型状态 55.6 GB(原来 444.5 GB);通信每卡每步 145.9 GB,是 DP 的 1.5 倍。
切什么一层里的矩阵乘拆给 t 张卡:第一个矩阵按列切、第二个按行切,非线性各算各的,最后一次 all-reduce。
多出的通信每层 all-reduce(前向 2 次、反向 2 次)
有多大t = 8、每 4,096 个 token:每卡发 18.8 GB,每层都要等它做完;所以只放在一台机的 8 张卡里。
切什么把 64 层按顺序切成 p 段放在不同的卡上,每段做完把激活交给下一段,反向时再把梯度传回来。
多出的通信段与段之间 send / recv 激活
有多大锚点每段 8 层(p = 8);每个 micro-batch(4,096 个 token)在段间传 0.0 GB 激活;代价是气泡:p = 8、m = 32 时 21.9%。
切什么一条很长的序列切给几张卡,每张卡只存自己那段 token 的激活;注意力需要全部 K、V,就让 K、V 块沿环传一圈。
多出的通信注意力里沿环传 K、V
有多大s = 131,072、8 卡:每张卡每一步传出一块 K、V 0.1 GB,每个全注意力层传 7 块。
切什么MoE 的专家分到不同卡上,token 用 all-to-all 发到持有对应专家的卡,算完再 all-to-all 收回来。
多出的通信FFN 前后各一次 all-to-all
有多大DeepSeek-V3:256 个路由专家 ÷ 64 张卡 = 每卡每层 4 个;每个 token 最多去 4 个节点。
C9 第 1 课的账,这一课原样搬过来:训练时一个参数要 16 字节——16 位权重 2、16 位梯度 2,再加 FP32 主权重、Adam 的 m、v 各 4(ZeRO 论文把后三项合称「优化器状态」,K = 12)。上一课的数据并行让 8 张卡各存一整份。这一课讨论的是「一张卡上的账」,和句子内容无关,所以贯穿例句暂时不出场。
先自己设计:照数据并行的样子,每卡一份
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
每一行是一张卡。左边三条是「所有权条」:权重、梯度、优化器状态各切成 N 份,实色的那份归这张卡负责,斜纹是它替别的卡存的副本。从「纯数据并行」往右逐级切,看斜纹一类一类消失。
改进:ZeRO 的三级切分
关键的观察来自上一课:数据并行里,8 张卡用同一份梯度做同一个更新,每张卡都把全部参数更新了一遍——同一件事做了 8 次。ZeRO(Rajbhandari 等,2019)的做法是分工:卡 i 只负责第 i 份参数的更新,于是只需要存第 i 份的优化器状态;更新完,再把新参数发给大家。逐项看每张卡要存多少:
ZeRO 原文图 1 用 7.5B 参数、64 张卡给出了 120 / 31.4 / 16.6 / 1.9 GB 四档 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条纯记账算术;表 1 把最后一档写作 1.88。前提是混合精度 + Adam 的 16 字节口径。 出处ZeRO 原文图 1、表 1(需联网) ↗需联网 ,同一个公式代入锚点就是上面的四个数。它不压缩任何一个数:每个数的位数和 C9 一模一样,省下的全是副本。
试三件事:① 在改进一侧从「纯数据并行」逐级点到 ZeRO-3,读每卡合计怎么越过 80 GB 线;② 把卡数 N 从 8 拖到 64、1,024:ZeRO-1、ZeRO-2 很快降不动了(被没切的 2Ψ、4Ψ 卡住),只有 ZeRO-3 一直按 1/N 往下走;③ 在 ZeRO-3 下把「激活」切到全量重计算、选择性重计算,看剩下的 24.4 GB 够不够——激活这一段不随 N 变。
通信:前两级白送,切参数多 50%
ZeRO 切得这么狠,通信会不会爆?回到上一课的 ring all-reduce:它本来就是两半,前一半 reduce-scatter 让每张卡拿到一块的全和,后一半 all-gather 把块发给所有人。
- ZeRO-1、ZeRO-2:梯度做 reduce-scatter——卡 i 恰好只拿到它负责的第 i 份梯度之和;更新第 i 份参数;再 all-gather 把新参数发给大家。两半加起来正是一次 all-reduce,通信量与数据并行完全相同(2Ψ 个数)。
- ZeRO-3:参数也只存 1/N,于是前向每一层之前要 all-gather 一次、反向再 all-gather 一次,加上梯度的 reduce-scatter,一共 3Ψ 个数,是数据并行的 1.5 倍 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条原文:切到梯度时通信「与数据并行的基线完全相同」为 2Ψ;切参数时为 3Ψ,「是基线的 1.5 倍」。 出处ZeRO 原文 §7.2(需联网) ↗需联网 。锚点 8 卡每卡每步从 97.2 GB 变成 145.9 GB。
PyTorch 的 FSDP 就是 ZeRO-3 这一级的工程实现 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条工程论文,报告的是他们在自己集群上的实测。 出处Zhao et al. 2023(PyTorch FSDP,需联网) ↗需联网 :把模型按层(或若干层)包成单元,算到哪个单元就 all-gather 哪个单元的参数,算完释放,并提前预取下一个单元,让拼参数的通信和上一个单元的计算重叠。所以 FSDP 的显存峰值 ≈ 本卡的分片 + 正在用的那一两个单元的完整参数:锚点最大的一层(全注意力层 + FFN)约 3.72 亿个参数,16 位下 0.7 GB。
常见误解之一:「ZeRO 是把数压缩了,所以会损失精度。」
ZeRO 不改变任何一个数的格式:FP32 主权重仍是 FP32,16 位权重仍是 16 位。它省的只是「N 张卡各存一份」的冗余,结果与数据并行逐位相同(不计通信中加法顺序带来的舍入)。
常见误解之二:「ZeRO-3 的通信是数据并行的 3 倍。」
是 1.5 倍:数据并行本来就要 2Ψ(reduce-scatter + all-gather),ZeRO-3 多一次参数 all-gather,变成 3Ψ。
常见误解之三:「ZeRO 把激活也切了。」
没有。每张卡处理自己那份样本,激活就是自己那份,和 N 无关。s = 4,096 时,C9 的选择性重计算要 45.6 GB(C9 写作 42.5 GiB),ZeRO-3 之后剩下的 24.4 GB 放不下;全量重计算只要 2.7 GB,但多花约一次前向的算力。
🔍 DeepSeek-V3 为什么只用 ZeRO-1?
DeepSeek-V3 的报告写明它用的是「ZeRO-1 数据并行」,同时有 16 路流水线并行和跨 8 个节点的 64 路专家并行 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条报告只写了配置,没有解释为什么选 ZeRO-1 而不是 ZeRO-3。 出处DeepSeek-V3 报告 §3.2(本地 PDF) ↗ 。报告没有解释原因;能按记账推出来的是:参数已经被流水线(按层)和专家并行(按专家)切得很碎,每张卡剩下的权重和梯度本来就不多,占大头的优化器状态用 ZeRO-1 再切一刀,就够了——而不切参数,就省掉了 ZeRO-3 那 50% 的额外通信。
🔍 还放不下,能不能挪到 CPU 内存?
可以,代价是走 PCIe。ZeRO 的后续工作 ZeRO-Offload(arXiv 2101.06840)把优化器状态和更新挪到 CPU,ZeRO-Infinity(arXiv 2104.07857)再往 NVMe 硬盘挪。DeepSeek-V3 也把一小块东西放在 CPU:参数的指数滑动平均(EMA)存在 CPU 内存里、每步之后异步更新(报告 §3.2.3)。本章的主线不展开 offload:它换的是带宽更低的链路,原理和本课一样——不是少存,而是换个地方存。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 数据并行让每张卡都存一整份模型状态;ZeRO 让 N 张卡分工,每张卡只存、只更新 1/N |
| ② 数学上为什么可能有效? | 更新是逐参数的:第 i 份参数的更新只需要第 i 份的梯度与优化器状态,所以可以切开 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条AdamW 的更新对每个参数独立(逐元素),这是 C5 的结论;权重衰减、裁剪里的全局范数需要一次额外的同步,不改变结论。 出处待补 ;每卡显存与通信都是记账算术 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条16 字节口径下严格成立。 出处ZeRO 原文图 1、§7.2 ↗需联网 |
| ③ 实验上为什么最后选了它? | ZeRO 原文在 400 张 GPU 上训了超过 100B 参数的模型、吞吐 15 PFLOPs(摘要自报);FSDP 进了 PyTorch;DeepSeek-V3 用 ZeRO-1,Llama 3 的 DP 维就是 FSDP 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条都是官方自报的配置。Llama 3 的原话是其 4D 顺序里的 DP 指 FSDP。 出处DeepSeek-V3 §3.2;Llama 3 图 5 说明 ↗ |
演进地图:你在这里
- 解决了训练一步的全部东西都在一张卡上,不需要任何通信。卡在锚点的模型状态 16 字节 × 27,781,427,952 = 444.5 GB,是 80 GB 的 5.6 倍;就算放得下也太慢:DeepSeek-V3 预训练用了 2,664K H800 GPU 小时,一张卡约 304 年。
- 解决了每卡一份完整模型、算 1/N 的数据,每步把梯度加起来。ring 让每卡只收发 2(N−1)/N 倍梯度:锚点 8 卡 97.2 GB、64 卡 109.4 GB(汇总到 0 号卡的朴素做法:388.9 GB → 3,500 GB)。卡在只解决「慢」:每张卡仍要放下完整的 444.5 GB。
- 解决了反向逐层产出梯度,攒满一桶(默认 25 MiB)就先发,通信藏到后面几层的反向里。卡在DP 的通信量每步固定:锚点 8 卡每卡 97.2 GB,按 IB 50 GB/s 要 1.94 s;每卡 4,096 个 token 的反向只有约 1.05 s(推演)——token 太少就藏不住。
- 解决了不再每卡存一份:8 卡上 444.5 → 152.8 → 104.2 → 55.6 GB(依次切优化器状态、梯度、参数)。卡在切参数后每步多一次 all-gather,通信是 DP 的 1.5 倍;激活不切:s = 4,096 的选择性重计算要 45.6 GB,放不进剩下的 24.4 GB。
- 解决了第一个矩阵按列切、第二个按行切,一个 MLP 只要一次 all-reduce;t = 8 配序列并行,激活 45.6 → 5.7 GB。卡在每层前向 2 次、反向 2 次同步:每 4,096 个 token 每卡要发 18.8 GB,每层都要等——只能放在机内。ZeRO 原文实测:跨两台 DGX-2,每卡约 5 TFLOPS,不到峰值 5%。
- 解决了按层切,跨机只传段与段之间的激活;切 micro-batch 填满流水线;1F1B 把在途激活从 m 份压到 p 份;interleaved 把气泡除以 v。卡在首尾必然空等:p = 8、m = 32 时气泡 (p−1)/m = 21.9%(占总时间 17.9%),interleaved v = 4 也还有 5.5%。
- 解决了反向拆成「对输入」B 与「对权重」W,W 拿去填空泡(F = B = W 时 ZB-H1 的气泡是 1F1B 的 1/3);DualPipe 从两头喂 micro-batch,并把 all-to-all 藏到另一批计算后面。卡在DualPipe 要存两份参数;调度更复杂;通信只是被藏住了,量一点没少。
- 解决了128K 的序列按序列切给多张卡;zig-zag 头尾配对,把因果负载 10 : 26 : 42 : 58 配平成每卡一样多。卡在每个全注意力层都要在卡之间传 K、V:锚点 s = 131,072、8 卡时每层 0.5 GB;Ulysses 按头切,KV 只有 4 个头。
- 解决了DeepSeek-V3 的 654B 路由专家参数分到 64 张卡,每卡每层 4 个;每个 token 最多去 4 个节点。卡在跨节点 IB 50 GB/s 只有 NVLink 160 GB/s 的约 1/3,跨节点 EP 的计算与通信约 1 : 1;热门专家所在的卡成为掉队者(→ C11)。
- 解决了Llama 3 405B:[TP, CP, PP, DP] = 8 × 1 × 16 × 128,16,384 张 H100,BF16 MFU 41%;DeepSeek-V3:PP 16 × EP 64 × ZeRO-1 DP,不用 TP。卡在没有万能配置:每种组合都要先算清每卡显存、每一维的通信走哪条链路、气泡多大(实验课)。
一句话
ZeRO 不压缩任何数,只是让 N 张卡不再各存一份一样的优化器状态、梯度和参数:锚点在 8 卡上从 444.5 GB 降到 152.8 → 104.2 → 55.6 GB;前两级通信和数据并行一样,切参数多 50%。
先停在这里:记住「优化器状态占 12/16,最先切」「前两级通信白送,ZeRO-3 多 50%」「ZeRO 不切激活」三句,就可以进下一课。
再深一层:展开两个 Dig(DeepSeek-V3 为什么只用 ZeRO-1、offload),再去读 ZeRO 原文里切激活、临时缓冲与显存碎片的 ZeRO-R 部分。
下一个缺口
ZeRO-3 之后,每张卡仍在一个人算完整的一层、存自己那份激活——一层的矩阵乘能不能拆开算? → 第 3 课:一层的矩阵乘,能不能拆到几张卡上一起算?
回扣先记下:C9 第 5 课的激活公式在下一课被切成 1/t;「ZeRO 不切激活」这条限制,在第 3 课(序列并行)和岔路 1(长序列)兑现。
你可能会问
- 「ZeRO 和模型并行是一回事吗?」 不是。ZeRO 仍是数据并行:每张卡算的是完整的一层(只是参数用前临时拼回来),各卡处理不同的样本。模型并行(第 3、4 课)是把一层或一段层永久放在某张卡上,各卡处理同一批样本的不同部分。
- 「ZeRO-3 的 all-gather 会不会让一步慢很多?」 会多 50% 的通信,但和上一课一样可以预取、与计算重叠;每卡 token 多时藏得住,token 少时露在外面。DeepSeek-V3 这种已经切得很碎的配置选了 ZeRO-1。
- 「我们的小模型上能测 ZeRO 吗?」 单卡上 N = 1,ZeRO 没有东西可切。能做的是计算题:27,781,427,952 换成我们的 3,688,704,每一级都按上面的公式算——16 字节 × 3,688,704 = 56.3 MiB,一张卡绰绰有余。