一张卡太慢,8 张卡怎么一起训同一个模型?
本课一问:一张卡太慢。给你 8 张卡,怎么让它们一起训同一个模型,而不是各训各的?
这一课先不碰:每张卡放不下怎么办(第 2 课);把一层拆开算(第 3 课);机器之间的网络拓扑细节(只用两个带宽数字)。
从已有出发
C9 结束时,训练锚点 Qwen3.8-27B 的一步在一张卡上是这样一本账:27,781,427,952 个参数 × 16 字节 = 444.5 GB 的模型状态,再加激活。放不下是一回事;就算放得下,一张卡也算不完。C6 第 1 课的算力账 C ≈ 6ND 说明了为什么:DeepSeek-V3 的报告写明,它的预训练用了 2,664K 个 H800 GPU 小时,一张卡要 304 年;实际用 2,048 张卡,不到两个月 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报的 GPU 小时;「不到两个月」也是报告原话。304 年是 2,664,000 ÷ 8,760 小时/年 的换算。 出处DeepSeek-V3 报告 §1 表 1(本地 PDF) ↗ 。
切什么每张卡一份完整模型,各算全局 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 个节点。
上面这条是 C5 讲过的训练一步:取一批 → 前向 → loss → 反向 → 更新。这一课在「反向」和「更新」之间插入一环:梯度同步。贯穿例句也换了个身份:一步的全局 batch 有 8 条序列,「小明把苹果给了小王」是分给 0 号卡的那一条,另外 7 张卡各拿另外 7 条。
先自己设计:每张卡一份模型,各算一部分数据
最直接的分工:每张卡放一份完整的模型,全局 batch 切成 N 份,每张卡只算自己那份的前向和反向。这叫数据并行(data parallelism,DP)。
它为什么能得到「同一个模型」?因为 loss 是整个 batch 的平均。把 B 条样本分成 N 份、每份 B/N 条:
L = (1/B)·Σ全部样本 ℓ = (1/N)·Σi Li,其中 Li 是第 i 张卡那份样本的平均 loss。
求导是线性的,所以 ∇L = (1/N)·Σi ∇Li:每张卡算出自己的梯度,N 份加起来除以 N,就是一张大卡算整个 batch 得到的梯度 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条只用到「求导是线性的」;前提是各卡样本数(按 token 平均时是 token 数)相同,否则要按数量加权。 出处待补 。每张卡再用这同一份梯度做同样的更新,N 份模型就一直逐位相同。
剩下的问题只有一个:N 份梯度怎么加起来,再发回每一张卡?最容易想到的是找一张卡当「汇总员」:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
每张卡只和环上的下一张卡说话:前 N − 1 步边传边加(reduce-scatter),每张卡攒齐一块的全和;后 N − 1 步把攒齐的块沿环传一圈(all-gather)。每张卡的流量都一样,而且不随卡数增长。
试三件事:① 在朴素做法里点到结束,看「已收到」一列:0 号卡的数是别人的 N − 1 倍,把 N 拖到 8 再看一次;② 切到 ring all-reduce,一步一步走,盯住蓝框——每一步每张卡只发一块、只收一块;走完之后看核对行,每张卡的结果与直接逐块求和的最大差是 0;③ 在下方「真实规模」里把卡数从 8 换到 1,024:朴素做法的 0 号卡涨了一百多倍,ring 的每卡流量几乎不动。
改进:ring all-reduce 为什么只要约 2 倍梯度
把 ring 的流量逐项拆开:
这个结论可以严格证明:对「每张卡最后都要拿到全部之和」这件事,每张卡至少要发出 (N−1)/N·G(否则别人拿不全它的贡献),也至少要收到 (N−1)/N·G;ring 的两段各正好是这个下限,所以在带宽上是最优的 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条两段各 N−1 步、每卡传 2(N−1)K/N;带宽最优性由 Patarasuk 与 Yuan 2009 年证明。只算带宽,不算每一步的延迟:卡数很多时延迟项会变大,这时会换成树形或分层的算法。 出处Gibiansky 2017(百度);Patarasuk & Yuan 2009(需联网) ↗需联网 。
出处要说准:ring all-reduce 不是深度学习发明的。它来自高性能计算(Patarasuk 与 Yuan,2009);百度 2017 年把它引入深度学习训练,Horovod(2018)让它普及 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条Horovod 论文明确写了算法来自 2009 年的论文、借鉴了百度 2017 年的文章。 出处Horovod §3(需联网) ↗需联网 。
再改进:别等反向做完才同步
ring 把「发多少」降到了下限,可「什么时候发」还没讲究:如果等整个反向做完才开始 all-reduce,通信期间卡什么都不算。反向是从 lm_head 往回一层一层算的,最后一层的梯度最先算好——它没必要等第 0 层。PyTorch DDP 的做法是把梯度分成若干个桶(默认 25 MiB),攒满一桶就立刻发起这个桶的 all-reduce,和后面几层的反向同时进行 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条论文 §5.2 写默认桶大小 25 MB;现行 PyTorch 文档写作 bucket_cap_mb 缺省时用 25 MiB。 出处Li et al. 2020(PyTorch DDP)§3.2.3、§5.2(需联网) ↗需联网 :
试三件事:① 先切到「反向做完再一次同步」,看红色那段有多长,再切回「分桶」,看它缩了多少;② 默认每卡每步 4,096 个 token 时,「通信 ÷ 反向」大于 1——分桶也藏不住,把 token 数调到 16,384,读「藏到反向后面的」比例;③ 分桶之后还剩一小段红色露在最后:那是 embedding(以及纯文本一步里不参与计算的 MTP 与视觉塔)的梯度,它们在反向的最后才就绪,谁也藏不住。
这张图说明了数据并行的一条基本规律:通信量每步固定(由参数量决定,和每卡算多少 token 无关),计算量随每卡的 token 数增长。所以数据并行要跑得快,每张卡每步得分到足够多的 token;卡越多、全局 batch 不变时,每卡的 token 就越少,通信越难藏住。全局 batch 又不能无限大(C5 岔路讲过 batch 与学习率的关系),这是数据并行扩展的天花板之一。
常见误解之一:「数据并行 = 每张卡各训一个模型,最后平均一下。」
不是。每一步都要把梯度加起来,所有卡用同一份梯度更新,8 张卡上的参数任何时刻都逐位相同。「各训各的、最后平均权重」是另一回事(联邦学习、模型融合里有),它的结果和单卡大 batch 不等价。
常见误解之二:「卡数翻倍,每张卡的通信也翻倍。」
ring 下每张卡发 2(N−1)/N 倍梯度:8 卡 97.2 GB,64 卡 109.4 GB,1,024 卡也不超过 111.1 GB。翻倍的是「汇总到一张卡」的朴素做法。
常见误解之三:「数据并行能让放不下的模型放进卡里。」
不能。每张卡放的是一整份模型:锚点仍是每卡 444.5 GB。数据并行只解决「慢」,不解决「放不下」——这是下一课的事。
🔍 按 token 平均时,各卡的样本不一样长怎么办?
上面的证明要求每张卡那份样本的「权重」相同。LLM 的 loss 通常按 token 平均:如果 0 号卡的 8 条序列一共 30,000 个有效 token、1 号卡只有 20,000 个(其余是 padding),各卡先各自按 token 平均、再把 N 份梯度简单平均,就等于给 1 号卡的 token 加了 1.5 倍的权重。正确做法是按 token 数加权,或者先对 loss 求和、同步 token 总数后再除。这是数据并行里真实的工程坑,和数学上的等价性不矛盾:等价的前提就是「同一种平均」。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 一张卡算不完:让 N 张卡各算 1/N 的数据,得到和一张大卡完全相同的一步更新 |
| ② 数学上为什么可能有效? | 平均梯度 = 全局 batch 的梯度(求导是线性的) 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条前提:各卡用同一种平均、样本数相同或按数量加权。 出处待补 ;ring 的流量 2(N−1)/N·G 达到带宽下限 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条只算带宽,不含延迟。 出处Gibiansky 2017;Patarasuk & Yuan 2009 ↗需联网 |
| ③ 实验上为什么最后选了它? | 它是所有并行里最简单、通信最少的一维;分桶重叠让通信大部分藏到反向里(PyTorch DDP);它是 Llama 3、DeepSeek-V3 等所有大规模训练的最外层维度 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条重叠带来的加速取决于模型、带宽与每卡 batch,论文给的是他们的实测。 出处PyTorch DDP 论文(需联网) ↗需联网 |
演进地图:你在这里
- 解决了训练一步的全部东西都在一张卡上,不需要任何通信。卡在锚点的模型状态 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。卡在没有万能配置:每种组合都要先算清每卡显存、每一维的通信走哪条链路、气泡多大(实验课)。
一句话
数据并行让每张卡拿一份完整模型、算 1/N 的数据,每步用 all-reduce 把梯度加起来;ring 让每张卡只收发约 2 倍梯度、与卡数几乎无关,分桶把通信藏到反向后面——但它只解决「慢」,每张卡仍要放下 16 字节/参数。
先停在这里:记住「平均梯度 = 大 batch 梯度」「ring 每卡约 2 倍梯度」「通信每步固定、计算随 token 增长」三句,就可以进下一课。
再深一层:展开上面的 Dig(不等长样本怎么平均),再在步进器里把模式换成「只看 reduce-scatter」和「只看 all-gather」——下一课的 ZeRO 正好各用一半。
下一个缺口
8 张卡存了 8 份一模一样的 444.5 GB——能不能每张卡只存 1/8? → 第 2 课:每张卡都放不下怎么办?
回扣先记下:C9 第 1 课的「16 字节/参数」在下一课被切开;ring all-reduce 的两半(reduce-scatter、all-gather)正是 ZeRO 的两个动作。
你可能会问
- 「我们自己的小模型要不要数据并行?」 不需要为了显存:它只有 3,688,704 个参数,模型状态 56.3 MiB。想训得更快时可以用,但一台 Mac 只有一块 GPU,项目里用不上。
- 「梯度累积和数据并行有什么区别?」 数学上一样:梯度累积是在一张卡上串行算 N 个小 batch、把梯度加起来再更新;数据并行是 N 张卡同时算。前者省卡不省时间,后者省时间要通信。
- 「为什么叫 all-reduce?」 reduce 是「把很多份合成一份」(这里是求和),all 是「每个人都拿到结果」。只让一张卡拿到结果的叫 reduce,每人只拿一块结果的叫 reduce-scatter。