阶段 6 · 让模型跑起来

分布式训练与集群:一张卡放不下
一千张卡怎么连

上一章《硬件与算力账本》算清了单卡的算力与带宽,却默认你有一张足够大的显卡。这一章先面对现实: 一个 70B 的模型,光是训练需要的状态就有 1.1 TB, 而一张主流训练卡也只有 80 GB——怎么把一个装不下的东西切开塞进去。
切开之后还有一个更土的问题:这些卡插在哪儿? 同一份数据,在机箱里传和在机器之间传差 18 倍, 这个数字决定了哪些切法只能待在机内、哪些必须跨机。 还有,一万六千张卡的系统,平均每三个小时就会坏一次。

1

显存到底被什么吃掉了

很多人以为「模型多大就需要多大显存」。这个直觉错了,而且错得很远—— 训练一个模型,需要的显存大约是参数量的 16 倍。

占什么每个参数占几字节7B 模型70B 模型
模型权重(BF16)214 GB140 GB
梯度(BF16)214 GB140 GB
优化器:参数主副本(FP32)428 GB280 GB
优化器:一阶动量 m(FP32)428 GB280 GB
优化器:二阶动量 v(FP32)428 GB280 GB
合计(不含激活值) 16 112 GB1120 GB

表里的 BF16 是 16 位浮点(占 2 字节),比 FP32 省一半;激活值是前向传播时每层算出的中间结果,反向传播要用它来算梯度,这里先不计——它的量随 batch 和序列长度走,和参数量是两笔账。

互动 · 每个参数那 16 个字节,到底花在哪

💡 最反直觉的一条

优化器状态是权重的 6 倍、是权重+梯度的 3 倍(占全部训练状态的 12/16 = 75%)。7B 的模型权重只占 14 GB,但 Adam 的三个 FP32 状态占了 84 GB。
原因是:Adam 要给每个参数存一阶动量、二阶动量和一份 FP32 主副本, 每个都是 4 字节。而权重本身只需要 2 字节的 BF16。
这解释了为什么「省显存」的第一刀总是砍向优化器状态——那里有最多的肉。
口径注:FP32 主副本可省;不算它,每参数 12 字节、70B 只要 840 GB(《LoRA》那章就是这个口径)。

2

四种并行,切的东西完全不同

「分布式训练」不是一种方法,而是四种思路。它们的区别只看一件事: 到底把什么切开了。

四种并行的本质区别

互动 · 四种方式到底省不省显存(每张卡要存多少)

并行方式切什么省显存吗通信
数据并行 DP 切数据。每张卡都存一份完整模型 完全不省 每步一次梯度 all-reduce
张量并行 TP 切单层内部的矩阵(按行或按列拆开) 省。÷ TP 度 通信量极大,每层都要通信
流水线并行 PP 切不同的层,每张卡负责一段 省。÷ PP 度 通信很少,只有段边界要传
ZeRO / FSDP 切优化器状态、梯度、参数,本质是给数据并行加上分片 省。可分片到 ÷ N 每步多次 all-gather / reduce-scatter

三个通信动作:all-reduce 把各卡的数据汇总成一份再发回每张卡(梯度求平均就是它);all-gather 把各卡的分片拼回完整的一份;reduce-scatter 汇总后不广播全文,每张卡只留自己那一份。ZeRO 是给数据并行加的分片方案,FSDP 是 PyTorch 里的实现——下面第 3 节会看到它的三个阶段。

🎯 类比 · 一条工厂流水线

把训练想成一条流水线:DP 是每条线各做一整件、只换原料;TP 是把其中一道最费的工序拆成几瓣让几个人同时做(手递手交接最快);PP 是每人守一道工序、中间用传送带接力;ZeRO 是工具和备件不各存一套,共享一个仓库、要用时去取。

这个类比管到「谁负责哪一段」为止:真实系统里交接不是零成本的,手递手比隔一堵墙递慢得多——这正是这一章后半段那 18 倍带宽差在讲的事。

很多人说「数据并行就是把模型复制到每张卡上」。这句话只说对了一半—— 真正被复制的是模型和优化器状态,而且是冗余地复制: 8 张卡各存一份完全相同的 112 GB。显存一点没省,只是把 batch 放大了 8 倍。 想省显存,必须让每张卡存的东西不一样——这就是 ZeRO 的出发点。

M

数学 · 切开之后每卡要存多少

上一节说四种并行「切什么」不同。但不管怎么切,都绕不开同一道算术: 混合精度 + Adam 的训练状态是参数量的 16 倍;切成 N 份,每张卡就只存 16P/N。 这条式子把这一章的收益和代价钉在了同一个坐标里。

互动 · 卡越多,每卡存得越少,但要传的越多

每卡显存
—
每步通信量
—
装得下 80GB 吗
—

互动 · 每个符号管图上的哪一块

🎬 自己验一遍

把模型规模拖到 70B,看每卡显存那条线整个抬起来;再把卡数往右拖, 它会按 16P/N 往下掉——但橙色那条通信线几乎不降。
两条线的间距,就是这一章所有工程复杂度的来源:显存可以除以 N,通信不能。

3

亲手配一次:你的卡放得下吗

下面按真实的混合精度 + Adam 账本计算。拖动参数,看每张卡各要存多少。

互动 · 显存账本分解(真实计算)

★ 这一章的核心图

上面这个计算器,就是这一章最该带走的东西——「一张卡装不下」的全部解法,都在它这几个滑块里。试这几个组合,理解 ZeRO 三个阶段在做什么:
先看基准:7B / 8 卡 / 不做 ZeRO → 每卡 112 GB。80 GB 的卡放不下。
ZeRO-1:只把优化器状态分片 → 每卡 38.5 GB。放得下了。这是性价比最高的一步。
ZeRO-2:再分片梯度 → 26.3 GB。
ZeRO-3:连参数也分片 → 14.0 GB。省得最多,但每层前向都要把参数 gather 回来, 通信最频繁。
既然 ZeRO-1 就够用,为什么还有人上 ZeRO-3?当 38.5 GB 还是塞不下你想训的模型时—— ZeRO-3 拿更频繁的通信,去换更小的常驻显存。

4

流水线并行的代价:气泡

流水线并行的想法很自然:4 张卡,每张负责 1/4 的层,像工厂流水线一样接力。 但接力需要时间填满——第一张卡开始干活时,后面三张只能干等着。

填的办法是把一个 batch 切成 m 份,一份一份依次送进流水线,每一份叫一个微批次(micro-batch)。 每张卡一次做一份、做完就往下传,后面几张才不用干等。 下面的模拟用的 1F1B 调度就是这件事的常用排法:前向一步、反向一步交替着走, 让一张卡刚腾出手就接上下一份。

互动 · 真实的 1F1B 调度模拟

人话版 气泡占比 = (p − 1) / (m + p − 1)

p = 流水线段数,m = 微批次数量

上面那张图里每一个方块都是按依赖关系调度出来的,气泡占比也是数出来的

👆 先悬停,再拖那个滑块

把鼠标停在下面公式里有下划线的符号上——上面「流水线段数 p」和「微批次数 m」两个滑块会分别亮起来。 p 变大,气泡跟着变大;m 变大,气泡被填掉、变小。

互动 · 气泡占比 (p−1)/(m+p−1) 到底长什么样

💡 一个反直觉的发现

这个公式和每段算多久无关。实测:把反向传播设成前向的 2 倍(更接近真实情况), 气泡占比一模一样,一分不差。
原因是在流水线调度里,用了几格和每格多长是两件事——气泡是「格子数量」的问题, 不是「每格多快」的问题。
所以想让气泡变小,唯一有效的办法是增加微批次数量, 而不是去优化单步速度。

⚠️ 但微批次不能无限加

增加微批次会降低气泡,但代价是同时驻留的激活值变多—— 每个微批次在流水线上都要占显存,直到它的反向传播做完。
所以流水线并行里有个真实的矛盾:气泡要小就得多塞微批次,塞多了显存又爆。 实践中的解法是重计算(activation checkpointing):前向时不保存中间激活值, 反向要用到哪一层,就从前一个检查点把它重算一遍——用多算一次前向,换掉那份激活值占的显存。
还有一件事得说明:空转的那几张卡不能顺手去算下一批。 它们要等前面传来的梯度,而梯度得等整条流水线的反向都排到自己——这个空转是依赖关系造出来的,不是卡偷懒。

5

卡和卡之间:差 18 倍的三种线

分布式训练最反直觉的一点:卡越多,你越可能被「传数据」卡住,而不是被「算数据」卡住。 而这一切从第九张卡开始:八张卡以内,通信走在机箱里的专用总线上; 从第九张开始,数据必须离开这台机器、走网络——带宽掉到十八分之一。

你在哪见过背后那件事
「张量并行只在单机内有效」它一步要通信一百多次,走网络会被延迟拖死
「加卡反而变慢了」通信量涨得比算力快,加的那几张卡全在等数据
「训到一半挂了,从上一个存档重来」规模一大,故障从「意外」变成「日常」
「今天卡全满了,排到下周三」集群有调度器,它不是你想用就能用的

互动 · 同一份数据,走这三种线各要多久

14 GB

默认那 14 GB 不是随便挑的:一个 70 亿参数模型的梯度,用半精度存就是 14 GB。 每走一步,这份梯度都要在卡之间绕环汇总一遍,每张卡实际收发的量约是它的两倍。 在机箱里,这一步花 31 毫秒,几乎可以忽略;走网络,它变成 560 毫秒—— 如果前向反向只要 300 毫秒、这一步又没藏住,训练速度就能少掉一半(第 6 节算的就是藏不住的情形)。

三种连接的带宽:900 GB/s、50 GB/s、20 GB/s

这个台阶不是工程没做好,是物理距离决定的:同一块板子上,信号走几厘米; 出了机箱,信号要走几十米,还要经过好几次光电转换。 所以「机内并行」和「机间并行」说的不是两种算法, 是同一种算法跑在台阶的两侧——上面那种可以一步传一百次,下面那种不能。 一台训练服务器就是下面的样子:

零件它干什么关键数字
八张计算卡干活。插在同一块主板上,共享机箱的供电和散热单卡显存 80 GB(H100)
NVSwitch(交换芯片)让这八张卡两两之间都能直接说话,不用绕道(卡间这条高速总线叫 NVLink)每卡 900 GB/s
网卡(每台若干张)把数据送出这台机器。它才是真正的瓶颈(机间网络叫 InfiniBand)一块 400 Gb/s = 50 GB/s
本地盘装操作系统、缓存数据。不存训练数据,那是机架外的事几 GB/s

一台机器 → 一个机柜 → 整个集群

一千台机器不会「每两台之间拉一根线」。真实的集群长成一棵树: 机器接在机柜的交换机上,机柜交换机再接上层的交换机。 树形的好处是便宜、好扩展。代价是带宽会被收敛: 一台机器上的一块网卡只有 50 GB/s,如果它下面挂着几十台机器, 大家同时想往外传,就得分这一条线。

接法画出来是什么样谁的带宽会被别人分掉
完全互联每两台之间一根线谁都分不到谁。但 n 台机器要 n(n−1)/2 根线,几百台就拉不起了
树形(叶脊 / 胖树)机器 → 机柜交换机 → 上层交换机看收敛比。同一机柜里的几台机器,会抢同一条上联

这件事有一个很实际的后果:在集群里,两台机器的通信速度取决于它们离得多近。 同一个机柜里的两台,可能跑满;跨好几个交换机层的两台,可能慢好几倍。 调度器分给你的卡是不是挨在一起,会直接影响训练速度——第 8 节会回到这件事。

⚠️ 「网络不是瓶颈」这句话在集群里是错的

单机上你习惯的那套判断(算力才是瓶颈)在这里会失效。
《硬件与算力账本》讲的是一张卡内部的算力墙和带宽墙; 这一段讲的是卡与卡之间的第三堵墙。它比前两堵都矮,也因此最容易先撞上。

下面用 7B 模型、32 层、隐藏维度 4096、单条 4096 长度的样本,真实估算四种切法各要传多少。

互动 · 四种方式的通信量对比

互动 · 四种方式一步要传多少、传几次(对数尺度)

互动 · 看两件事:一步传多少 vs 一步传几次

方式每次通信传多少一步通信几次能藏起来吗
DP(DDP) 梯度 all-reduce ≈ 28 GB 1 次 计算 ≥ 通信时能藏住(边反向边通信)
ZeRO-3 / FSDP 参数 + 梯度 ≈ 42 GB 每层都有,几十次 部分能藏,但比 DP 难
张量并行 TP 激活值 ≈ 33.6 MB 每层 4 次,32 层 = 128 次 基本不能藏,是延迟受限的
流水线 PP 激活值 ≈ 33.6 MB 只有段边界,几次 能,通信量最小

这张表解释了为什么张量并行只能待在同一台机器里:它单次通信量看着不大(33.6 MB), 但一步要通信 128 次。通信的代价主要不是「传多少」,而是「每次都要等一个来回的延迟」。 128 次延迟叠加起来,如果走普通以太网,通信时间会远远超过计算时间。 这就是 TP 必须走 NVLink(同一台机器内的高速总线)的原因——它不是带宽问题,是延迟问题。 TP 像同桌对稿:每次只对几个字,但一天要对几百次,所以必须坐隔壁桌(机内)——换成隔壁楼就慢得没法用。

6

一百张卡一起传一轮

汇总梯度最常用的算法叫 ring all-reduce:把卡排成一个环, 每张卡只跟左右两个邻居说话。听着很绕,但结论干净得让人意外—— 无论多少张卡,每张卡收发的总量都趋近于 2 倍的 S(S 是要汇总的那份数据的大小)。

先把直觉铺开。每张卡手里有一份 14 GB 的梯度,目标是让所有卡都拿到 完整的、七十亿个参数各自的平均值。做法是绕环走两圈: 第一圈走 n−1 步,每步把自己手上那份加到邻居手上,一圈走完,每张卡手里正好攒着 「所有卡加起来」的一段;第二圈再走 n−1 步把各段广播出去,最后除以 n 就是平均值。 下面拖「卡数 n」,两圈还是两圈,看每张卡要发的量怎么变。

互动 · 卡数从 2 拖到 1024,每张卡要发的量怎么变

每张卡收发
—
全集群合计
—
机间 50 GB/s 下每卡要传
—

卡数 n2(n−1)/n每张卡要发多少
21.0001.00 × S
81.7501.75 × S
641.9691.97 × S
10241.998几乎正好 2 × S

无论多少张卡,每张卡收发的总量都趋近于 2 倍的 S。 从 8 张加到 1024 张,单卡要发的数据只从 1.75 倍涨到 2 倍—— 涨了 14%,而卡数涨了 127 倍。

这是 ring all-reduce 被普遍使用的原因:它的通信量几乎不随卡数增长。 代价是它需要绕 n−1 步,延迟随卡数线性增长。 所以卡特别多的时候,另一类算法(树形)会赢——它绕的步数是 log 级的, 但带宽利用率没那么满。到底用哪个,是通信库替你算的。

机间带宽只有 50 GB/s。传 2 × 14 GB = 28 GB 要花 560 毫秒。 如果你的模型一步只算 300 毫秒,那加卡之后每张卡有三分之二的时间在等网线(这是通信藏不住的情形)。 这就是「加卡反而变慢」的全部原因,它跟算法无关,跟这一步算术有关。

7

真实的训练配方

技术提出做了什么今天用在哪
DDP2018 标准数据并行,梯度 all-reduce 小模型、单机多卡的基本盘
Megatron 张量并行2019 把注意力头和 FFN 按列/行切到不同卡上,第一次让 83 亿参数的模型装得下 所有大模型训练的标配
ZeRO-1/2/32019 逐级分片:优化器状态 → 梯度 → 参数 主流选择。ZeRO-1 的性价比最高
流水线并行2019-21 层间切分 + 微批次填流水线(1F1B 调度) 跨机器扩展的主要手段
FSDP2021 PyTorch 原生版的 ZeRO-3,把分片做进了框架 PyTorch 用户最常用的方案
3D 并行2021+ TP 在机内 + PP 跨机 + DP 在最外层,三者同时用 训练千亿以上模型的唯一现实方案
专家并行 EP2020+ MoE 的不同专家放在不同卡上 MoE 模型(见阶段 5 那一章)

典型配方:三种规模的模型怎么切

模型规模卡数典型配置
≤ 7B1-8 DDP(数据并行)就够
7B - 70B8-128 FSDP / ZeRO-3,单机内可能再加 TP=8
70B - 500B128-2048 TP=8(机内)× PP=4~16(跨机)× DP(最外层)+ ZeRO-1
> 500B数千-数万 3D 并行 + 专家并行 + ZeRO-1;训练框架本身成了核心竞争力

互动 · 把一次训练切成小块,每块一张卡

最后换一个角度:这些卡是谁的?集群不是一台你能随便用的电脑, 它下面这三种形态,决定了你在跟谁谈、要操心什么。

形态谁在用你要操心的那件事
自己建大公司、国家级实验室从机房供电、散热,到网络拓扑、调度器,全是你的。换来的是极限规模和可控性
租公有云(按卡租)绝大多数团队硬件别人管。你要操心的是账:同一个任务,不同实例类型的价格能差好几倍
买托管服务(按 token 或按训练算力)不养基础设施的团队连调度都不用管。代价是你能调的东西最少,也更难做非标准的事
8

什么时候真的会痛

前面那些切法都有账单。这一节把账单摊开——每条代价都配一个「什么时候真的会痛」, 因为同一件事在小规模下无所谓,到了某个量级就变成事故。

代价具体是什么什么时候真的会痛
算得快不如传得快 卡越多,单卡分到的计算越少,通信量不减。超过某个规模后再加卡反而更慢——这个转折点叫「通信墙」 一步的计算时间短于通信时间时:加卡纯粹是在给交换机打工
流水线气泡浪费算力 4 段流水线、8 个微批次 → 27% 的时间在空转;压到 5% 以下要约 57 个微批次,而这么多微批次同时驻留又吃显存 段数多、微批次少的配置:省下的显存,又被多出来的微批次吃回去
TP 卡在单机内 受延迟限制,TP 度一般就是 8(一台机器 8 张卡)。想再扩,只能靠 PP 和 DP 想把单层切到两台机器上时:这是硬约束,不是软件问题
节点故障是常态 Llama 3 在 16384 张 H100 上训练 54 天,记录到 419 次中断——平均每三小时一次;单卡六年才坏一次,一万六千张放在一起就变成三小时一次 上千张卡跑几周时。没做容错重启(定期存档、失败后从最近状态恢复)的话,几天的工作可能白跑
调参空间指数爆炸,数值还会变 TP 几、PP 几、ZeRO 到几、微批次多少、重计算开不开——这些参数互相耦合;不同的切分又改变浮点求和顺序 换一个并行配置就得重调一片;同一模型在不同配置下,loss 曲线会有可见差异
集群不是你的,数据也喂不上 要排队申请「多少张卡、跑几天」(常见的是 Slurm,集群的排队与分配系统),分到的卡还不一定挨在一起;几千张卡同时读会把存储打爆 「明天出结果」取决于排多久;GPU 算得比你喂得快时,卡会空着不干活

选型时先问一句:我的瓶颈是「一张卡装不下」,还是「一张卡跑太久」? 装不下,才轮到这一章的内容;跑太久,先回去看 batch 和显存。

互动 · 上千张卡跑几周:检查点多久存一次才划算

9

小结

这一节把前面所有切法归到一个动作上: 显存不够,就把它切碎,让每张卡只存一小块。 而切碎是要付钱的——碎块之间必须不停地互相通话。 规模再往上,通信换成三条差 18 倍的线;一千张卡在一起,平均每三小时就坏一次。

它对应哪条线 ③ 规模会赢——这一章全部的工程复杂度,都是「规模会赢」这句话的账单。 正因为规模真的会赢,你才非要把模型做大到一张卡装不下, 于是有了这一章的所有切法;也正因为它要堆到上千张卡, 才有了机内/机间那个 18 倍的台阶和「三小时坏一次」这两笔账
一句话 这一段的做法是在用通信换显存—— 把一块装不下的显存需求切成很多块,代价是让这些块算每一步时不停地互相传数据。 四种并行只是这个交易里「切什么」的四种选择;而它们传数据的三条通路差 18 倍, 这个台阶把所有并行策略挤成了现在的形状
它牺牲了什么 牺牲了效率与简单性:加卡的边际收益会被通信吃掉, 过了某个规模再加卡反而更慢(通信墙)。还有可靠性——它从「默认」变成「要自己挣」: 上千张卡跑几周,节点故障是必然,存档、重启、续跑必须是训练程序的一部分。 最后是排队:集群不是你一个人的,分到哪几张卡会直接影响速度。(回扣暗线 B)
🎬 自己验一遍

回到第 3 节那张「显存账本分解」。固定 7B / 8 卡,把「ZeRO 阶段」从 0 挨个切到 3—— 看「每卡合计」怎么从 112 GB(装不下)掉到 14 GB(装得下)。 再把「总卡数」从 8 拖回 1:分片没了,数字立刻涨回去。 这就是「用通信换显存」:每张卡存的东西变少了, 缺的那部分只能随时问邻居要。

再回到第 4 节,把「流水线段数 p」从 2 拖到 8: 切得更细,可「气泡占比」一路涨上去。 最后去第 5 节,把「要传的数据量」从 14 GB 拖到 40 GB—— 机内那条只到 44 毫秒,机间那条涨到 800 毫秒。 这三个滑块合起来,就是这一章全部的内容:省下的显存,要用通信和空转付回去。

它在暗线里站在哪

六条暗线不占任何一章,但每一章都要回答它们。

暗线这一章的回答
B 什么被牺牲了 换来的是能训一个单卡绝对装不下的模型(70B 光训练状态就有 1.1 TB)。 牺牲的是效率(通信墙、气泡)、可复现性(不同切分改变浮点求和顺序) 和确定性(上千卡跑几周,硬件出问题是必然)。这三样都是不可逆的。
C 参数账本 这一章的账精确到字节:每个参数 16 字节(权重 2 + 梯度 2 + 优化器状态 12), 70B 全量状态 = 70 × 10⁹ × 16 ≈ 1.12 TB,一张 80 GB 的卡要 14 张才刚够存「状态」。 传一遍也一样具体:14 GB 的梯度绕环一圈,每张卡收发 2 × S ≈ 28 GB(第 6 节那个滑块)。
F 违背了哪个直觉 「卡越多越快」是错的:超过通信墙,加卡反而更慢——同样一个模型,128 卡可能比 64 卡还慢。 「分布式是为了更快」也错了:它首先是为了装得下,快是次要的副产品。

它给后面留了什么:下一章《混合精度与显存账本》接着算每个参数占几字节。

一句话带走分布式训练与集群

显存不够就切开——DP 切数据、TP 切矩阵、PP 切层、ZeRO 切状态,切开的代价是卡间那三条差 18 倍的通路和随时会坏的节点。 所以:能单卡跑就别分布式。

10

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式