从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 35 分钟 页内实验 ×3
C10 并行:一张卡不够怎么办 第 1 课

一张卡太慢,8 张卡怎么一起训同一个模型?

数据并行
每张卡拿一份完整模型、各算 1/N 的数据,每步把梯度加起来——这就是数据并行。把梯度全部汇总到一张卡,那张卡的流量随卡数线性增长;ring all-reduce 让每张卡只收发约 2 倍梯度,分桶再把通信藏到反向后面。但每张卡仍要放下完整的模型状态。
❓

本课一问:一张卡太慢。给你 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 张卡,不到两个月 。

从已有出发 · 训练一步摊到多张卡 已学本课还没学
取一批
前向
loss
反向
梯度同步 DP
优化器更新
每课多切一维:
数字口径:构建时由锚点 Qwen3.8-27B 与 DeepSeek-V3 的官方 config 按公式现算;卡数按 8 张计。本章没有我们自己的多卡实测。

上面这条是 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 得到的梯度 。每张卡再用这同一份梯度做同样的更新,N 份模型就一直逐位相同。

剩下的问题只有一个:N 份梯度怎么加起来,再发回每一张卡?最容易想到的是找一张卡当「汇总员」:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案全部发给 0 号卡求和,再由 0 号卡发回
数字口径:每块数据是固定种子生成的整数测试输入,不是真实梯度;通信量与时间由公式现算(推演),参数量取自官方 config 与项目导出。
🎯

试三件事:① 在朴素做法里点到结束,看「已收到」一列:0 号卡的数是别人的 N − 1 倍,把 N 拖到 8 再看一次;② 切到 ring all-reduce,一步一步走,盯住蓝框——每一步每张卡只发一块、只收一块;走完之后看核对行,每张卡的结果与直接逐块求和的最大差是 0;③ 在下方「真实规模」里把卡数从 8 换到 1,024:朴素做法的 0 号卡涨了一百多倍,ring 的每卡流量几乎不动。

改进:ring all-reduce 为什么只要约 2 倍梯度

把 ring 的流量逐项拆开:

ring all-reduce:每张卡每步发出(也收到)的字节
× ÷ ×
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
N = 8 时系数是 14/8 = 1.75,每卡 97.2 GB;N = 64 时是 1.97,109.4 GB;N 再大也不超过 2G。复现 R4 调研的数:Qwen3-8B 在 8 卡上每卡 28.7 GB。

这个结论可以严格证明:对「每张卡最后都要拿到全部之和」这件事,每张卡至少要发出 (N−1)/N·G(否则别人拿不全它的贡献),也至少要收到 (N−1)/N·G;ring 的两段各正好是这个下限,所以在带宽上是最优的 。

出处要说准:ring all-reduce 不是深度学习发明的。它来自高性能计算(Patarasuk 与 Yuan,2009);百度 2017 年把它引入深度学习训练,Horovod(2018)让它普及 。

再改进:别等反向做完才同步

ring 把「发多少」降到了下限,可「什么时候发」还没讲究:如果等整个反向做完才开始 all-reduce,通信期间卡什么都不算。反向是从 lm_head 往回一层一层算的,最后一层的梯度最先算好——它没必要等第 0 层。PyTorch DDP 的做法是把梯度分成若干个桶(默认 25 MiB),攒满一桶就立刻发起这个桶的 all-reduce,和后面几层的反向同时进行 :

🎯

试三件事:① 先切到「反向做完再一次同步」,看红色那段有多长,再切回「分桶」,看它缩了多少;② 默认每卡每步 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 达到带宽下限
③ 实验上为什么最后选了它?它是所有并行里最简单、通信最少的一维;分桶重叠让通信大部分藏到反向里(PyTorch DDP);它是 Llama 3、DeepSeek-V3 等所有大规模训练的最外层维度

演进地图:你在这里

并行的演进:每切一维,都是被上一代的某个真数字逼出来的
  1. 起点 一张卡 C9 的结论
    解决了训练一步的全部东西都在一张卡上,不需要任何通信。
    卡在锚点的模型状态 16 字节 × 27,781,427,952 = 444.5 GB,是 80 GB 的 5.6 倍;就算放得下也太慢:DeepSeek-V3 预训练用了 2,664K H800 GPU 小时,一张卡约 304 年。
  2. 第一代 数据并行 + ring all-reduce ring 来自 HPC(2009),2017–2018 进入深度学习 你在这里
    解决了每卡一份完整模型、算 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。
  3. 第一代 · 工程 分桶,边反向边同步 PyTorch DDP(Li et al. 2020)
    解决了反向逐层产出梯度,攒满一桶(默认 25 MiB)就先发,通信藏到后面几层的反向里。
    卡在DP 的通信量每步固定:锚点 8 卡每卡 97.2 GB,按 IB 50 GB/s 要 1.94 s;每卡 4,096 个 token 的反向只有约 1.05 s(推演)——token 太少就藏不住。
  4. 第二代 ZeRO-1 / 2 / 3 · FSDP Rajbhandari 2019;PyTorch FSDP 2023
    解决了不再每卡存一份:8 卡上 444.5 → 152.8 → 104.2 → 55.6 GB(依次切优化器状态、梯度、参数)。
    卡在切参数后每步多一次 all-gather,通信是 DP 的 1.5 倍;激活不切:s = 4,096 的选择性重计算要 45.6 GB,放不进剩下的 24.4 GB。
  5. 第三代 张量并行(层内切矩阵) Megatron-LM(Shoeybi 2019)
    解决了第一个矩阵按列切、第二个按行切,一个 MLP 只要一次 all-reduce;t = 8 配序列并行,激活 45.6 → 5.7 GB。
    卡在每层前向 2 次、反向 2 次同步:每 4,096 个 token 每卡要发 18.8 GB,每层都要等——只能放在机内。ZeRO 原文实测:跨两台 DGX-2,每卡约 5 TFLOPS,不到峰值 5%。
  6. 解决了按层切,跨机只传段与段之间的激活;切 micro-batch 填满流水线;1F1B 把在途激活从 m 份压到 p 份;interleaved 把气泡除以 v。
    卡在首尾必然空等:p = 8、m = 32 时气泡 (p−1)/m = 21.9%(占总时间 17.9%),interleaved v = 4 也还有 5.5%。
  7. 2023–2024 Zero Bubble · DualPipe Qi 2023;DeepSeek-V3 2024
    解决了反向拆成「对输入」B 与「对权重」W,W 拿去填空泡(F = B = W 时 ZB-H1 的气泡是 1F1B 的 1/3);DualPipe 从两头喂 micro-batch,并把 all-to-all 藏到另一批计算后面。
    卡在DualPipe 要存两份参数;调度更复杂;通信只是被藏住了,量一点没少。
  8. 岔路 · 长序列 序列并行 / 上下文并行 2021–2023:SP、Ring Attention、Ulysses
    解决了128K 的序列按序列切给多张卡;zig-zag 头尾配对,把因果负载 10 : 26 : 42 : 58 配平成每卡一样多。
    卡在每个全注意力层都要在卡之间传 K、V:锚点 s = 131,072、8 卡时每层 0.5 GB;Ulysses 按头切,KV 只有 4 个头。
  9. 岔路 · MoE 专家并行 + all-to-all GShard 2020 → DeepSeek-V3 2024
    解决了DeepSeek-V3 的 654B 路由专家参数分到 64 张卡,每卡每层 4 个;每个 token 最多去 4 个节点。
    卡在跨节点 IB 50 GB/s 只有 NVLink 160 GB/s 的约 1/3,跨节点 EP 的计算与通信约 1 : 1;热门专家所在的卡成为掉队者(→ C11)。
  10. 2021 起 组合:3D / 4D / 5D 并行 Narayanan 2021;Llama 3;DeepSeek-V3
    解决了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。
    卡在没有万能配置:每种组合都要先算清每卡显存、每一维的通信走哪条链路、气泡多大(实验课)。
数字口径:锚点与 DeepSeek-V3 的数字由 anchor-model.json / frontier-models.json 的官方 config 在构建时按公式现算(GB = 10⁹ 字节,卡按 80 GB 计);「分桶」一行的时间是推演(有效算力 400 TFLOPS、IB 50 GB/s);5 TFLOPS 出自 ZeRO 原文 §1;GPU 小时、并行配置、带宽出自 DeepSeek-V3 报告 §1、§3.1–3.2(本地 PDF);Llama 3 的配置出自其报告表 4。本章没有我们自己的多卡实测。

一句话

📌

数据并行让每张卡拿一份完整模型、算 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 的两个动作。

你可能会问