讲解稿 · 第 06 章 第 05 节 · 分布式深度学习¶
口播时长:约 18 分钟|建议语速:中速,公式处停两拍|字数:约 3900 字
承 Ch06 §04 强化学习 → Ch06 §05 分布式深度学习 → 第 07 章 前沿专题
导语(约 80 秒)¶
凌晨三点的数据中心。一万张 A100 整齐码在机柜里,散热风扇以 6000 RPM 旋转,整层楼功耗顶得上一座中型工厂的用电量。运维盯着监控屏:显存占用 78.4%,网络吞吐 1.4 Tbps,梯度同步延迟 11 ms。屏幕上跑的是 GPT 类的训练任务,正在吃第 1.4 万亿 token。训练日志里每个 step 耗时 0.83 秒——这已经是被精心切分到 1024 块 GPU 上同时跑出来的结果。
为什么非得这么夸张?单卡 A100 有 80 GB 显存,理论算力 312 TFLOPS。看着挺强,但你一算账就明白了:一个 70 亿参数的模型,参数 28 GB,梯度 28 GB,Adam 优化器还要 56 GB——光这三项就要 112 GB,单卡连装都装不下,更别提训练时的激活值。这就是分布式深度学习存在的意义:把模型、数据、计算摊到多张卡上协同完成,靠通信原语把它们合成一台"逻辑上的超级 GPU"。
今天这一节我们就拆开这个"超级 GPU"的黑箱。从显存账本算起,到混合精度、数据并行、模型并行、流水线并行、MoE 缩放,一直到环形 all-reduce 的通信数学。一路走完,你就能看懂上面那块监控屏上每一行数字在说什么。
复习昨日 & 今日路线(约 45 秒)¶
Ch06 走过了四个分水岭:§01 经典机器学习给了我们线性回归、决策树、朴素贝叶斯;§02 梯度机器学习把 MSE 损失和梯度下降连成一条线;§03 深度学习把 MLP 卷成 CNN、Transformer;§04 强化学习让 agent 在环境里学着最大化累计奖励。
到这里,我们讨论的"模型"还都是装在一块 GPU 上的。但现实是——任何前沿模型都不可能装在单卡里。Ch06 §05 解决的是"装不下"和"跑太慢"两个问题:当模型容量突破单卡显存墙、当数据量大到单卡要跑几十年,我们怎么把计算摊开,又怎么把结果收拢。
下一节 Ch07 联邦学习与隐私计算,是分布式的另一面:不只是把数据并行处理,还要在数据不出本地的前提下协作。路线是:显存墙算账 → 混合精度 → 数据并行 → 模型并行 → 通信原语 → 缩放律 → MoE → 容错。
核心概念一 · 显存账本:参数、梯度、优化器状态、激活值(约 2.5 分钟)¶
直觉。 训练时 GPU 不是只存模型权重。它是个仓库,得同时留住四样东西:刚出炉的权重、刚算出来的梯度、优化器要用的状态、历史激活——任何一样丢了,反向传播就续不上。
定义。 设模型参数量为 \(P\),用 Adam 优化器、FP32(32 位浮点)权重: - 参数: 存 \(P\) 个 FP32 数字,占 \(4P\) 字节。 - 梯度: 反向传播算出来,大小与参数相同,占 \(4P\) 字节。 - 优化器状态: Adam 维护一阶动量 \(m\) 和二阶动量 \(v\),各占 \(4P\) 字节。合计 \(8P\) 字节。 - 激活值: 前向时存下来供反向使用的中间张量。Transformer 一层激活大致为 \(B \cdot n \cdot (3d + n + 4d)\) 个 BF16 数字(\(B\) 批大小,\(n\) 序列长度,\(d\) 隐藏维度)。
例子。 70 亿参数模型,FP32 Adam:
这只算了前三项。激活值还没算,单张 80 GB 的 A100 就已经装不下了。Pyodide 实测(GPT-2 规模,\(d=n=1024, B=8, L=24\)):
10 亿参数的 100 倍就是 800 GB——这就是为什么必须切分到多张卡上。
反例。 有人会问:"我用 FP16 不就省一半了吗?"——参数和激活确实能省一半(2 字节 vs 4 字节),但 Adam 的 \(m, v\) 通常保留 FP32 拷贝以保数值稳定(参考 Ch03 §05 优化器动量)。所以显存大头往往是优化器状态,不是参数本身。
一句话总结。 训练显存的固定开销是"参数 + 梯度 + 优化器状态",三块加起来 ≈ 16 字节/参数(FP32 Adam);激活值随批大小和序列长度线性增长,常常是最致命的那一块。
暂停 10 秒。30 GB 还是 30 GB,那个 7B 模型怎么都装不进单卡——下一节混合精度就是来解决这个问题的。
核心概念二 · 混合精度:BF16 让显存砍半(约 2 分钟)¶
直觉。 32 位浮点存得太奢侈了。神经网络里大部分数字的精度需求没那么高,把 16 位用好就能省一半显存。
定义。 - FP16: 1 位符号 + 5 位指数 + 10 位尾数。精度高但范围窄(约 \(6 \times 10^{-5}\) 到 \(6.5 \times 10^4\)),容易溢出/下溢。 - BF16 (Brain Float 16): 1 位符号 + 8 位指数 + 7 位尾数。指数范围和 FP32 一样,几乎不溢出;精度低一些但训练 Transformer 完全够用。 - 损失缩放 (loss scaling): FP16 训练时把损失乘一个大因子 \(S\)(比如 1024)放大梯度,反向后再除以 \(S\) 还原,缓解下溢。BF16 通常不需要。
例子。 70 亿参数模型,BF16 训练: - 参数 BF16: 14 GB(FP32 的一半) - 梯度 BF16: 14 GB - 优化器状态 FP32: 56 GB(仍保留 FP32 母本) - 激活 BF16: 视配置
反例。 "BF16 损失缩放"——很多新手会问"BF16 不是不溢出吗,为什么还看到 loss scaling 代码?"答案是 BF16 在累积大量梯度时仍可能下溢(特别是 batch 内累加),很多框架在 BF16 路径上仍保留可选的损失缩放,但默认关掉。
一句话总结。 BF16 是 Transformer 训练的默认选择,FP16 配损失缩放是次优但仍可用的备选;混合精度的关键不是把参数降精度,而是用低精度跑前向/反向、用高精度保优化器母本。
核心概念三 · 数据并行:复制模型、切分 batch(约 2.5 分钟)¶
直觉。 如果模型能装进单卡,但数据太多——把模型复制 \(N\) 份发到 \(N\) 张卡上,每张卡只跑 \(1/N\) 的 batch。跑完各自算出来梯度,求个平均广播回去。每张卡拿着同样的平均梯度更新本地副本。
定义。 数据并行 (Data Parallelism, DP) 的核心操作: - 复制: 把同一份模型参数 \(W\) 复制到 \(N\) 张 GPU。 - 切分: 批大小 \(B\) 切成 \(N\) 个 mini-batch,每张卡拿到 \(B/N\)。 - 梯度平均: 用 all-reduce 把 \(N\) 张卡上的梯度取平均。 - 更新: 每张卡本地更新参数。
数学上等价于单卡跑有效批大小 \(N \cdot B\)。
例子。 Pyodide 实测:64 个样本切 4 份,每份 16 条,单卡与数据并行的 100 步后权重差 < 1e-5 量级——证明数学等价。
反例。 异步数据并行让每张卡独立更新共享参数服务器,不等别人。优点是消灭了 straggler,缺点是引入"陈旧梯度 (stale gradient)":卡 A 算梯度时用的是 \(t-2\) 步的权重,到了 \(t\) 步才把梯度送上去,参数已经走过两步。陈旧梯度会注入噪声,经验上收敛要慢 1.2-1.5×。现代生产环境几乎都用同步 SGD + 高效通信。
一句话总结。 数据并行 = 模型不动,切数据;梯度同步后数学上等价于单卡用 \(N\) 倍大 batch 训练。
暂停 10 秒。这一步你可能会问——既然数学上等价,为什么不直接用大 batch 训练?因为大 batch 需要大显存,单卡装不下那么多激活。数据并行是把同一份模型复制 \(N\) 份,靠通信换显存。
核心概念四 · 张量并行与流水线并行:把模型切开放(约 3 分钟)¶
直觉。 模型装不进单卡怎么办?把模型本身切开。两种主流切法:张量并行沿矩阵拆,流水线并行沿层拆。
定义。 - 张量并行 (Tensor Parallelism, TP): 单个矩阵乘 \(Y = XW\) 太大?把 \(W\) 按列切成 \([W_1, W_2]\),两张卡并行算 \(Y_1 = XW_1\) 和 \(Y_2 = XW_2\),最后拼接。每层都得通信(一般是节点内 NVLink,900 GB/s 带宽),所以切点选在 attention 头之间或 FFN 内部。 - 流水线并行 (Pipeline Parallelism, PP): 把 \(L\) 层分成 \(K\) 段,第 1-4 层放卡 0,第 5-8 层放卡 1,依次类推。数据像流水线一样"流"过去。朴素做法的"气泡 (bubble)"会让大部分 GPU 闲置;微批 (micro-batching) 把 mini-batch 切成更小的 micro-batch 流过流水线,填满气泡。
例子。 GPT-3 175B 那种尺寸: - 单卡装不下,单纯数据并行也无济于事。 - 节点内 8 卡用 TP 切一层矩阵(NVLink 互联,带宽足够)。 - 节点间用 PP 串联 8 段(IB 网络,200 Gbps)。 - 多个节点组之间再叠数据并行。
这就是 3D 并行(TP × PP × DP),GPT-4、Llama-3 全部这么训。
反例。 把张量并行切点放在 ReLU 之后——每张卡算完 ReLU 再通信,但 ReLU 是非线性、不交换的,分到不同卡后无法合并。正确做法是在 ReLU 之前切 \(W\),每张卡独立算完 \(XW_i\) 再 all-reduce。
一句话总结。 张量并行切权重,要求高速互联;流水线并行切层数,要求填气泡;3D 混合并行 = TP(节点内)+ PP(节点间)+ DP(多副本),这是 100B+ 模型的标配。
核心概念五 · 环形 all-reduce:通信的数学(约 2.5 分钟)¶
直觉。 数据并行最关键的通信操作是 all-reduce:每张卡各持一份梯度,求和/平均后所有卡都拿到相同结果。朴素的"主从"做法——所有卡把数据送到卡 0,求和再广播——通信量 \(O(N)\) 且卡 0 是瓶颈。环形 all-reduce 把这个常数砍到了几乎不随 \(N\) 增长。
定义。 环形 all-reduce 把 \(N\) 张 GPU 排成环,每张把自己的数据切成 \(N\) 块: - scatter-reduce 阶段: \(N-1\) 步,每张卡向下一邻居发一块,从上一邻居收一块,累加部分和。 - all-gather 阶段: 再 \(N-1\) 步,把完整和传播给所有卡。
每张卡总传输量 = 数据大小的 \(2(N-1)/N\) 倍。
例子。 Pyodide 实测: - \(N=8\): 传输因子 \(2 \times 7 / 8 = 1.750\) 倍 - \(N=64\): \(2 \times 63 / 64 = 1.969\) 倍 - \(N \to \infty\): 趋近 \(2\) 倍
带宽最优——无论堆多少卡,每张卡要发的总数据量基本是数据大小的 2 倍。这就是为什么大模型训练都默认用 ring all-reduce。
反例。 "传 1 GB 数据要 1 GB 流量"——这是单卡思维。在 \(N\) 卡 ring all-reduce 下,每张卡收 1 GB、发 1 GB,所以总网络流量 ≈ 2 GB(每张卡都收一份完整的 1 GB)。这是带宽下界。
一句话总结。 环形 all-reduce 是带宽最优的梯度平均方案,每张卡的传输量不随 GPU 数量增长,趋近数据大小的 2 倍。
暂停 10 秒。\(1.75\times\) vs \(1.97\times\)——当 \(N\) 足够大时,每张卡都要传将近 2 倍数据。听起来多,但这已经是下界:所有卡都得各拿一份完整结果。
核心概念六 · 缩放律与 Chinchilla:训练多少 token 才对(约 2 分钟)¶
直觉。 给定一块算力预算,是训一个 70 亿参数的小模型看 1400 亿 token,还是训一个 700 亿参数的大模型看 14 亿 token?直觉似乎两者都行——但 Chinchilla 告诉我们:应该训小模型,喂它更多数据。
定义。 Kaplan 等人 (2020) 的缩放律 (Scaling Laws) 指出,损失按幂律下降:
其中 \(N\) 参数量、\(D\) 数据量、\(C\) 算力预算。Chinchilla 缩放律 (Hoffmann 等, 2022) 进一步指出,给定算力,最优配比约为每个参数配 20 个 token。
例子。 7B 参数模型按 Chinchilla 应该看 \(7 \times 10^9 \times 20 = 1.4 \times 10^{11}\) = 1400 亿 token。但 Llama-1 65B 模型用了 3000 亿 token——按 Chinchilla 这意味着 65B 太大了,应该换成 200B 模型看 4000 亿 token。
反例。 "缩放律在所有规模都成立"——错。Chinchilla 是在 100M-10B 参数范围内拟合的经验规律,扩展到 100B+ 仍有争议;MoE 模型打破了"参数 = FLOPs"的等价关系,缩放律需要重写。
一句话总结。 Chinchilla 算力最优 ≈ 20 token/参数;早期大模型普遍训练不足,缩放律告诉我们"小而多次"通常优于"大而少次"。
一个走完全程的例子 · GPT-2 规模的训练账本(约 2.5 分钟)¶
把今天学的东西串起来。假设要训一个 GPT-2 规模模型:\(d=1024, n=1024, B=8, L=24\)。
第 1 步:算 FLOPs。 Pyodide 实测一层前向 240.5 GFLOPs(QKV 51.5 + Attn 34.4 + Out 17.2 + FFN 137.4),前向 + 反向 ≈ 3 倍:
一张 A100 跑 312 TFLOPS BF16,单步耗时 17.32 / 312 ≈ 0.056 秒。
第 2 步:算显存。 参数量 302.3M,参数 FP32 1.21 GB,Adam 状态 2.42 GB,24 层激活 3.22 GB——总计 8.06 GB,单张 24 GB 显卡能装下。
第 3 步:扩到 64 卡数据并行。 有效 batch 8 × 64 = 512,环形 all-reduce 通信因子 1.969,单卡需传输的梯度数据 ≈ 1.21 GB × 1.969 ≈ 2.38 GB。100 Gbps IB 网络下耗时约 0.19 秒——单步 0.056 秒 + 通信 0.19 秒 = 0.25 秒。
第 4 步:扩到 GPT-3 175B。 模型大了 580 倍,单卡装不下,必须叠加 3D 并行(TP=8 节点内 + PP=8 节点间 + DP=16 副本)。参数被切成 64 份优化器状态,每张卡只存 175B / 64 ≈ 2.73B 参数的部分,FP32 Adam 显存 ≈ 2.73B × 16 = 43.7 GB——单张 80 GB A100 装得下。
第 5 步:Chinchilla 算账。 175B 参数按 20 token/param 需要 3.5T token 训练数据。即使 1 万张 A100 全速跑,单 epoch 也要 ≈ 175 天。这就是为什么前沿训练是"算力工程",不只拼算法。
易错点回头看(约 80 秒)¶
第一条:很多人以为"数据并行越多越快",其实通信会成为瓶颈。 \(N\) 张卡理论上能省 \(N\) 倍时间,但 all-reduce 的同步延迟是 \(\Omega(\log N)\)(树形)/ \(O(1)\)(环形),梯度数据本身要 1.969× 单卡大小的传输。当通信耗时超过计算耗时,再多卡反而变慢。
第二条:很多人以为"BF16 训练和 FP32 等价",其实对小梯度仍有影响。 BF16 的 7 位尾数能区分的最小相对差是 \(2^{-7} \approx 0.78\%\)。当梯度数量级很小时(接近 0),BF16 会把它们量化到同一个最小表示,损失有效信息。学习率小的时候这种现象更明显。
第三条:很多人以为"MoE 总参数大就更强",其实单 token FLOPs 才决定算力。 Mixtral 8x7B 总参 470 亿,但每个 token 只激活 top-2 专家,单 token 算力 ≈ 130 亿参数的稠密模型——是"用小算力拿大模型效果"。
与下节的钩子(约 30 秒)¶
今天我们学的是"算力堆叠"——把多张 GPU 拼成一台超级机器,靠数据/张量/流水线并行把模型训完。但所有卡都假设数据可以自由搬运到中心机房。下一节 Ch07 联邦学习要解决完全相反的问题:数据不能离开本地(医院病历、银行交易、手机输入法),多个客户端怎么协作训练一个全局模型而不暴露原始数据?梯度会被噪声掩盖以保护隐私,参数服务器会做安全聚合——这些是分布式深度学习的"另一面"。
今天学到的环形 all-reduce、3D 并行、混合精度——明天会原样出现在联邦学习的客户端通信里。Ch06 §05 就到这里。