第15章:预训练
训练一个 70B 参数的大语言模型,需要多少资源?粗略估算:仅模型权重本身,以 BF16 格式存储就需要 140GB 显存,而一张顶级 A100 显卡只有 80GB。这还没算梯度、优化器状态……单卡训练从根本上行不通。
本章从这个硬件约束出发,逐步拆解大模型预训练背后的核心技术:如何把计算分散到成百上千张 GPU 上、如何用低精度计算省内存而不失准度、如何让训练过程稳定不崩溃,以及 Scaling Law 告诉我们模型到底该多大、数据该多多。
15.1 分布式训练
单卡的天花板
训练 LLM 需要存储以下几类数据:
| 类型 | 70B 模型(BF16) | 70B 模型(Adam 优化器) |
|---|---|---|
| 模型参数 | ~140 GB | ~140 GB |
| 梯度 | ~140 GB | ~140 GB |
| 优化器状态(Adam 的 m, v) | - | ~560 GB(FP32 × 2) |
| 合计 | ~280 GB | ~980 GB |
A100 80GB 根本装不下。唯一出路是分布式训练——把计算和内存拆分到多张 GPU 上。有三种正交的拆分维度。
数据并行(Data Parallelism,DDP)
最直觉的做法:每张 GPU 保存完整的模型副本,但每次处理不同的数据子集。
GPU 0: 模型副本 → batch_0 → 梯度_0
GPU 1: 模型副本 → batch_1 → 梯度_1
GPU 2: 模型副本 → batch_2 → 梯度_2
↓ AllReduce(梯度求和/平均)
统一更新参数
每个 step 结束时,所有 GPU 通过 AllReduce 操作同步梯度,然后各自用相同的梯度更新参数,保持权重一致。
优点:实现简单,通信量只有梯度(而非激活值)。缺点:每张 GPU 仍需完整模型——对 70B 模型无效。
张量并行(Tensor Parallelism,TP)
对于大矩阵乘法 ,可以把权重矩阵 沿某个维度切分到多张 GPU 上并行计算。
以列切分为例(Megatron-LM 的做法):
GPU 0 计算 ,GPU 1 计算 ,最后 AllGather 合并结果。对于 Transformer 的 MLP 层(),第一个矩阵列切分,第二个矩阵行切分,可以实现通信最小化。
:::info 为什么 TP 需要高带宽互联 张量并行在每层前向/反向都需要 GPU 间通信。如果 GPU 之间只有 PCIe(~32 GB/s),通信会成为瓶颈。NVLink(~600 GB/s)是 TP 实用化的关键硬件前提。 :::
流水线并行(Pipeline Parallelism,PP)
把模型的不同层放在不同 GPU 上,数据像流水线一样流过。
GPU 0: 第 0-9 层 → micro_batch_1 → micro_batch_2 → ...
GPU 1: 第 10-19 层 → micro_batch_1 → micro_batch_2 → ...
GPU 2: 第 20-29 层 → micro_batch_1 → ...
GPU 3: 第 30-39 层
挑战是流水线气泡(bubble):GPU 0 处理完第一个 micro-batch 后需要等待反向传播信号,这段时间是空闲的。GPipe、PipeDream 等方案通过增大 micro-batch 数量来提高 GPU 利用率。
3D 并行:三种方式的组合
实际训练超大模型时,三种并行方式同时使用:
- DP(数据并行):最外层,多路完全相同的"3D 并行组"处理不同数据
- PP(流水线并行):中间层,不同 GPU 负责不同层
- TP(张量并行):最内层,同一层内的矩阵切分
以训练 GPT-3(175B)为例,可以配置 64 路 DP × 8 路 PP × 8 路 TP,共使用 4096 张 GPU。
ZeRO 优化(DeepSpeed)
ZeRO(Zero Redundancy Optimizer)是 DeepSpeed 提出的另一条路:在数据并行的基础上,消除每张 GPU 上的冗余状态。
ZeRO 分三个阶段递进:
| 阶段 | 切分内容 | 每 GPU 显存节省(以 7.5B 模型为例) |
|---|---|---|
| ZeRO-1 | 优化器状态(m, v) | ~4× |
| ZeRO-2 | 优化器状态 + 梯度 | ~8× |
| ZeRO-3 | 优化器状态 + 梯度 + 参数 | ~64×(取决于 GPU 数量) |
ZeRO-3 下,每张 GPU 只保存 的参数,需要时通过 AllGather 从其他 GPU 取回。代价是通信量增加,但对于显存严重不足的场景是救命稻草。
:::tip ZeRO vs 张量并行 ZeRO 在通信模式上属于数据并行的扩展(跨 DP 组切分),实现相对简单,无需修改模型代码。张量并行需要修改矩阵乘法的计算逻辑,但通信更规整,通常用于同一节点内的 GPU(依赖 NVLink)。实践中常见 ZeRO-3 + TP 的组合。 :::
15.2 混合精度训练(FP16 / BF16)
为什么要用低精度
解决了内存的分布问题,还要解决内存的总量问题。把数据从 FP32(4字节)改为 FP16/BF16(2字节),可以直接减半显存,还能让现代 GPU 的 Tensor Core 以更高吞吐量计算(A100 的 BF16 Tensor Core 算力是 FP32 的 8 倍)。
FP16 vs BF16:微妙但关键的区别
两种格式都使用 16 位,但位分配不同:
| 格式 | 符号位 | 指数位 | 尾数位 | 表示范围 | 精度 |
|---|---|---|---|---|---|
| FP32 | 1 | 8 | 23 | 高 | |
| FP16 | 1 | 5 | 10 | 中 | |
| BF16 | 1 | 8 | 7 | 低 |
BF16 与 FP32 的指数位数完全相同,表示范围一致,只是精度(尾数位)更低。这一点至关重要:LLM 训练中,梯度的数值范围跨度极大,FP16 的有限范围容易导致数值溢出(overflow)或下溢(underflow),而 BF16 保持了与 FP32 相同的动态范围,训练稳定性接近 FP32。
这就是为什么现代 LLM 训练几乎全面转向 BF16。
混合精度的关键:主权重保留 FP32
完全用 BF16 训练会有问题:某些小梯度在 BF16 精度下无法被准确累加,长期累积会导致误差。混合精度训练的解法是:
前向传播:BF16(省内存,快)
反向传播:BF16(省内存,快)
梯度累积到 FP32 主权重:FP32(精确更新)
将更新后的 FP32 主权重复制回 BF16 工作副本
这样每个参数实际上存了两份:FP32 主权重(用于精确优化)+ BF16 工作副本(用于计算)。额外的显存开销约 50%(相比纯 BF16),但远低于纯 FP32。
梯度缩放(Gradient Scaling)
如果仍使用 FP16(而非 BF16),动态范围问题更严重。解法是梯度缩放:
其中 是一个大的缩放因子(如 )。反向传播计算出的梯度自然也被缩放了 倍,避免了 FP16 下溢。在参数更新前,再把梯度除以 恢复原值。
动态缩放因子会根据是否出现 inf/nan 自动调整:没有溢出则翻倍,出现溢出则减半,跳过本步更新。
15.3 学习率调度与训练稳定性
分布式和低精度解决了硬件约束,但训练过程本身也可能"失控"——梯度爆炸、loss 突然飙升……这需要一套调度与保护机制。
Warmup:让模型平稳起步
训练初期,模型参数随机初始化,梯度方向混乱。如果直接用目标学习率(如 )训练,参数会剧烈震荡。
解法是 Warmup:从极小的学习率(如 )线性增加到目标值,持续若干步(通常 1000–4000 步):
Warmup 让优化器的动量估计(Adam 中的 )有时间积累,参数逐渐找到有意义的方向后再加速。
Cosine 退火:让学习率优雅落地
Warmup 之后,学习率按余弦函数从最大值降到最小值(通常为最大值的 10%):
Cosine 调度的优点是平滑、无需手动设定何时降学习率。训练末期学习率极小,模型在已收敛的区域做精细调整。
梯度裁剪(Gradient Clipping)
即使使用了 Warmup 和 Cosine 调度,偶发的梯度爆炸仍可能让训练崩溃。梯度裁剪将全局梯度范数(L2 norm)限制在阈值 (通常为 1.0)以内:
实现极其简单(PyTorch 的 clip_grad_norm_),但对训练稳定性有显著保护作用。监控每步的梯度范数也是发现训练问题的重要手段。
Loss Spike:检测与处理
大模型训练过程中,Loss 偶尔会突然飙升(loss spike),这是业界普遍观测到的现象,确切原因至今未完全搞清楚(可能与特定 batch 数据、数值不稳定性有关)。
:::warning Loss Spike 的处理策略
- 检测:监控每步 loss,设定阈值(如超过前 100 步均值的 4 倍)自动报警
- 回滚:保存频繁的检查点(checkpoint),spike 发生后回滚到 spike 前的检查点
- 跳过:有时只需跳过问题 batch 重新采样即可恢复
- 降学习率:从检查点以更低学习率重新训练该段
LLaMA、PaLM 等模型的技术报告均记录了 loss spike 的发生,这是大规模训练中的"正常意外"。 :::
15.4 Scaling Law
有了上述工程基础,终极问题来了:模型多大才够?数据多少才够?
Kaplan Scaling Law(2020)
OpenAI 的 Kaplan 等人发现,模型性能(测试 loss)与三个因素之间存在幂律(power-law)关系:
其中 是模型参数量, 是训练数据量(token 数), 是总计算量(FLOPs)。三个系数大约都在 0.05–0.10 之间,意味着 loss 随规模缓慢但稳定地降低。
Kaplan 的结论偏向于优先扩大模型:给定固定算力,把大部分 FLOP 用于更大的模型,而不是用更多数据训练更小的模型。这一结论推动了 GPT-3(175B)等大模型的诞生。
Chinchilla Scaling Law(2022)
DeepMind 的 Hoffmann 等人重新用更严格的实验方法拟合,得出了不同结论:
给定算力预算 ,最优的模型大小 和训练数据量 应满足:
换句话说,模型大小和数据量应该等比例增加。以 10× 算力为例:模型大 ~3×,数据也多 ~3×,而不是模型大 10× 数据不变。
他们用同等算力训练了 Chinchilla(70B),数据量是 GPT-3 的 4 倍以上,结果在几乎所有基准上超过了 Gopher(280B)。
:::info Chinchilla 定律的核心公式
DeepMind 拟合的最优配比约为:
即每个参数对应约 20 个 token 的训练数据。例如一个 7B 模型,最优数据量约为 140B token。
:::
实践含义:之前的大模型都"欠训练"了
按 Chinchilla 的标准回看:
- GPT-3(175B)仅训练了 300B token,理想应训练 3.5T token
- Gopher(280B)也严重欠训练
这一发现改变了整个行业的训练策略。
当前趋势:训练更长而非更大
Chinchilla 之后,业界开始重新平衡"模型大小 vs 数据量":
| 模型 | 参数量 | 训练数据 | Chinchilla 最优数据 |
|---|---|---|---|
| GPT-3 | 175B | 300B token | ~3.5T token |
| Chinchilla | 70B | 1.4T token | ~1.4T token(匹配) |
| LLaMA-1 | 65B | 1.4T token | ~1.3T token(接近) |
| LLaMA-2 | 70B | 2T token | ~1.4T token(超过) |
| LLaMA-3 | 8B | 15T token | ~160B token(远超) |
LLaMA-3 的 8B 模型训练了 15T token,远超 Chinchilla 最优——为什么?因为推理成本比训练成本更重要:一个小而训练充分的模型,推理时更快更便宜,实际部署价值更高。"过度训练"的小模型反而成了主流。
:::tip Scaling Law 的局限 Scaling Law 描述的是 loss 的趋势,但 loss 下降并非与所有能力线性相关——某些能力(如逻辑推理、代码生成)会在某个规模阈值后涌现(emergence),即量变引发质变。这也是为什么单靠 Scaling Law 无法完全预测模型能力边界。 :::
本章小结
| 技术 | 解决的问题 | 关键要点 |
|---|---|---|
| 数据并行(DDP) | 加速训练,模型仍需完整 | AllReduce 同步梯度 |
| 张量并行(TP) | 单层矩阵太大放不下一卡 | 需要高带宽 NVLink |
| 流水线并行(PP) | 模型层数太多放不下一卡 | 需处理流水线气泡 |
| ZeRO-3 | 优化器状态显存占用巨大 | 3D 并行的显存补充 |
| BF16 混合精度 | 显存不足,计算慢 | 主权重 FP32,计算 BF16 |
| Warmup + Cosine | 训练初期不稳定,末期震荡 | 标配调度策略 |
| 梯度裁剪 | 梯度爆炸 | clip_grad_norm_ ≤ 1.0 |
| Chinchilla Law | 如何分配算力预算 | 模型大小与数据量等比扩展 |
预训练完成后,模型具备了语言理解和生成的基础能力,但它还不知道如何"听从指令"——它只会续写文本,不会回答问题。下一章将介绍如何通过指令微调(Instruction Tuning)和 RLHF 把一个预训练基座模型变成一个真正有用的对话助手。