上一章《硬件与算力账本》算清了单卡的算力与带宽,却默认你有一张足够大的显卡。这一章先面对现实:
一个 70B 的模型,光是训练需要的状态就有 1.1 TB,
而一张主流训练卡也只有 80 GB——怎么把一个装不下的东西切开塞进去。
切开之后还有一个更土的问题:这些卡插在哪儿?
同一份数据,在机箱里传和在机器之间传差 18 倍,
这个数字决定了哪些切法只能待在机内、哪些必须跨机。
还有,一万六千张卡的系统,平均每三个小时就会坏一次。
很多人以为「模型多大就需要多大显存」。这个直觉错了,而且错得很远—— 训练一个模型,需要的显存大约是参数量的 16 倍。
| 占什么 | 每个参数占几字节 | 7B 模型 | 70B 模型 |
|---|---|---|---|
| 模型权重(BF16) | 2 | 14 GB | 140 GB |
| 梯度(BF16) | 2 | 14 GB | 140 GB |
| 优化器:参数主副本(FP32) | 4 | 28 GB | 280 GB |
| 优化器:一阶动量 m(FP32) | 4 | 28 GB | 280 GB |
| 优化器:二阶动量 v(FP32) | 4 | 28 GB | 280 GB |
| 合计(不含 |
16 | 112 GB | 1120 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》那章就是这个口径)。
「分布式训练」不是一种方法,而是四种思路。它们的区别只看一件事: 到底把什么切开了。
四种并行的本质区别
互动 · 四种方式到底省不省显存(每张卡要存多少)
| 并行方式 | 切什么 | 省显存吗 | 通信 |
|---|---|---|---|
| 数据并行 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 的出发点。
上一节说四种并行「切什么」不同。但不管怎么切,都绕不开同一道算术:
互动 · 卡越多,每卡存得越少,但要传的越多
互动 · 每个符号管图上的哪一块
把模型规模拖到 70B,看每卡显存那条线整个抬起来;再把卡数往右拖,
它会按 16P/N 往下掉——但橙色那条通信线几乎不降。
两条线的间距,就是这一章所有工程复杂度的来源:显存可以除以 N,通信不能。
下面按真实的
互动 · 显存账本分解(真实计算)
上面这个计算器,就是这一章最该带走的东西——「一张卡装不下」的全部解法,都在它这几个滑块里。试这几个组合,理解 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 张卡,每张负责 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):前向时不保存中间激活值,
反向要用到哪一层,就从前一个检查点把它重算一遍——用多算一次前向,换掉那份激活值占的显存。
还有一件事得说明:空转的那几张卡不能顺手去算下一批。
它们要等前面传来的梯度,而梯度得等整条流水线的反向都排到自己——这个空转是依赖关系造出来的,不是卡偷懒。
分布式训练最反直觉的一点:卡越多,你越可能被「传数据」卡住,而不是被「算数据」卡住。 而这一切从第九张卡开始:八张卡以内,通信走在机箱里的专用总线上; 从第九张开始,数据必须离开这台机器、走网络——带宽掉到十八分之一。
| 你在哪见过 | 背后那件事 |
|---|---|
| 「张量并行只在单机内有效」 | 它一步要通信一百多次,走网络会被延迟拖死 |
| 「加卡反而变慢了」 | 通信量涨得比算力快,加的那几张卡全在等数据 |
| 「训到一半挂了,从上一个存档重来」 | 规模一大,故障从「意外」变成「日常」 |
| 「今天卡全满了,排到下周三」 | 集群有调度器,它不是你想用就能用的 |
互动 · 同一份数据,走这三种线各要多久
默认那 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 | 每层 4 次,32 层 = 128 次 | 基本不能藏,是延迟受限的 | |
| 流水线 PP | 只有段边界,几次 | 能,通信量最小 |
这张表解释了为什么张量并行只能待在同一台机器里:它单次通信量看着不大(33.6 MB), 但一步要通信 128 次。通信的代价主要不是「传多少」,而是「每次都要等一个来回的延迟」。 128 次延迟叠加起来,如果走普通以太网,通信时间会远远超过计算时间。 这就是 TP 必须走 NVLink(同一台机器内的高速总线)的原因——它不是带宽问题,是延迟问题。 TP 像同桌对稿:每次只对几个字,但一天要对几百次,所以必须坐隔壁桌(机内)——换成隔壁楼就慢得没法用。
汇总梯度最常用的算法叫 ring all-reduce:把卡排成一个环, 每张卡只跟左右两个邻居说话。听着很绕,但结论干净得让人意外—— 无论多少张卡,每张卡收发的总量都趋近于 2 倍的 S(S 是要汇总的那份数据的大小)。
先把直觉铺开。每张卡手里有一份 14 GB 的梯度,目标是让所有卡都拿到 完整的、七十亿个参数各自的平均值。做法是绕环走两圈: 第一圈走 n−1 步,每步把自己手上那份加到邻居手上,一圈走完,每张卡手里正好攒着 「所有卡加起来」的一段;第二圈再走 n−1 步把各段广播出去,最后除以 n 就是平均值。 下面拖「卡数 n」,两圈还是两圈,看每张卡要发的量怎么变。
互动 · 卡数从 2 拖到 1024,每张卡要发的量怎么变
| 卡数 n | 2(n−1)/n | 每张卡要发多少 |
|---|---|---|
| 2 | 1.000 | 1.00 × S |
| 8 | 1.750 | 1.75 × S |
| 64 | 1.969 | 1.97 × S |
| 1024 | 1.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 毫秒,那加卡之后每张卡有三分之二的时间在等网线(这是通信藏不住的情形)。 这就是「加卡反而变慢」的全部原因,它跟算法无关,跟这一步算术有关。
| 技术 | 提出 | 做了什么 | 今天用在哪 |
|---|---|---|---|
| DDP | 2018 | 标准数据并行,梯度 all-reduce | 小模型、单机多卡的基本盘 |
| Megatron 张量并行 | 2019 | 把注意力头和 FFN 按列/行切到不同卡上,第一次让 83 亿参数的模型装得下 | 所有大模型训练的标配 |
| ZeRO-1/2/3 | 2019 | 逐级分片:优化器状态 → 梯度 → 参数 | 主流选择。ZeRO-1 的性价比最高 |
| 流水线并行 | 2019-21 | 层间切分 + 微批次填流水线(1F1B 调度) | 跨机器扩展的主要手段 |
| FSDP | 2021 | PyTorch 原生版的 ZeRO-3,把分片做进了框架 | PyTorch 用户最常用的方案 |
| 3D 并行 | 2021+ | TP 在机内 + PP 跨机 + DP 在最外层,三者同时用 | 训练千亿以上模型的唯一现实方案 |
| 专家并行 EP | 2020+ | MoE 的不同专家放在不同卡上 | MoE 模型(见阶段 5 那一章) |
典型配方:三种规模的模型怎么切
| 模型规模 | 卡数 | 典型配置 |
|---|---|---|
| ≤ 7B | 1-8 | DDP(数据并行)就够 |
| 7B - 70B | 8-128 | FSDP / ZeRO-3,单机内可能再加 TP=8 |
| 70B - 500B | 128-2048 | TP=8(机内)× PP=4~16(跨机)× DP(最外层)+ ZeRO-1 |
| > 500B | 数千-数万 | 3D 并行 + 专家并行 + ZeRO-1;训练框架本身成了核心竞争力 |
互动 · 把一次训练切成小块,每块一张卡
最后换一个角度:这些卡是谁的?集群不是一台你能随便用的电脑, 它下面这三种形态,决定了你在跟谁谈、要操心什么。
| 形态 | 谁在用 | 你要操心的那件事 |
|---|---|---|
| 自己建 | 大公司、国家级实验室 | 从机房供电、散热,到网络拓扑、调度器,全是你的。换来的是极限规模和可控性 |
| 租公有云(按卡租) | 绝大多数团队 | 硬件别人管。你要操心的是账:同一个任务,不同实例类型的价格能差好几倍 |
| 买托管服务(按 token 或按训练算力) | 不养基础设施的团队 | 连调度都不用管。代价是你能调的东西最少,也更难做非标准的事 |
前面那些切法都有账单。这一节把账单摊开——每条代价都配一个「什么时候真的会痛」, 因为同一件事在小规模下无所谓,到了某个量级就变成事故。
| 代价 | 具体是什么 | 什么时候真的会痛 |
|---|---|---|
| 算得快不如传得快 | 卡越多,单卡分到的计算越少,通信量不减。超过某个规模后再加卡反而更慢——这个转折点叫「通信墙」 | 一步的计算时间短于通信时间时:加卡纯粹是在给交换机打工 |
| 流水线气泡浪费算力 | 4 段流水线、8 个微批次 → 27% 的时间在空转;压到 5% 以下要约 57 个微批次,而这么多微批次同时驻留又吃显存 | 段数多、微批次少的配置:省下的显存,又被多出来的微批次吃回去 |
| TP 卡在单机内 | 受延迟限制,TP 度一般就是 8(一台机器 8 张卡)。想再扩,只能靠 PP 和 DP | 想把单层切到两台机器上时:这是硬约束,不是软件问题 |
| 节点故障是常态 | Llama 3 在 16384 张 H100 上训练 54 天,记录到 419 次中断——平均每三小时一次;单卡六年才坏一次,一万六千张放在一起就变成三小时一次 | 上千张卡跑几周时。没做容错重启(定期存档、失败后从最近状态恢复)的话,几天的工作可能白跑 |
| 调参空间指数爆炸,数值还会变 | TP 几、PP 几、ZeRO 到几、微批次多少、重计算开不开——这些参数互相耦合;不同的切分又改变浮点求和顺序 | 换一个并行配置就得重调一片;同一模型在不同配置下,loss 曲线会有可见差异 |
| 集群不是你的,数据也喂不上 | 要排队申请「多少张卡、跑几天」(常见的是 Slurm,集群的排队与分配系统),分到的卡还不一定挨在一起;几千张卡同时读会把存储打爆 | 「明天出结果」取决于排多久;GPU 算得比你喂得快时,卡会空着不干活 |
选型时先问一句:我的瓶颈是「一张卡装不下」,还是「一张卡跑太久」? 装不下,才轮到这一章的内容;跑太久,先回去看 batch 和显存。
互动 · 上千张卡跑几周:检查点多久存一次才划算
这一节把前面所有切法归到一个动作上: 显存不够,就把它切碎,让每张卡只存一小块。 而切碎是要付钱的——碎块之间必须不停地互相通话。 规模再往上,通信换成三条差 18 倍的线;一千张卡在一起,平均每三小时就坏一次。
回到第 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 倍的通路和随时会坏的节点。 所以:能单卡跑就别分布式。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。