预训练全流程
第 9 课:预训练全流程
上一课我们备好了数据。这一课把这些 token 真正跑起来——做一次预训练要走哪些阶段、学习率怎么设、模型怎么切到几千张卡上、跑崩了怎么办、最后能跑出多少算力利用率。
② 解释学习率 warmup + cosine 的必要性,并算出衰减比例
③ 会估训练显存、会选并行策略、会处理 loss spike、会算 MFU
上一课我们把语料备好了。现在问:这些 token 能不能一口气训完?
因为不同训练阶段的最优设置并不相同,所以必须分阶段。可以把这类比成盖楼:打地基、砌主体、精装修三道工序用的材料和手法都不一样,硬要一次做完只会两头不讨好。
| 阶段 | 上下文长度 | token 预算 | 关键设置 |
|---|---|---|---|
| ① 初始预训练 | 8K | 绝大部分(15.6T 减去后面两段) | 高学习率、稳定为主 |
| ② 长上下文扩展 | 8K → 128K(6 个子阶段) | 约 800B | 逐步拉长序列,配 RoPE(给每个 token 注入位置信息的位置编码)频率调整 |
| ③ 退火 | 同② | 约 40M | 学习率降到 0,上采样高质量数据 |
| 若强行合并 | 会发生什么 |
|---|---|
| 一开始就用 128K 上下文 | 注意力成本按序列长度平方增长,早期算力被大量浪费在长序列上 |
| 全程用同一套数据配比 | 无法在末期"精准补课",推理与代码能力上不去 |
| 末尾不做退火 | 学习率停在半空,最后一段数据的影响被稀释 |
| 阶段 | token 量 | 占比 | 学习率 | 对最终权重的影响 |
|---|---|---|---|---|
| 初始预训练 | 约 14.8T | ≈ 94.9% | 高 | 决定能力底座 |
| 长上下文扩展 | 约 800B | ≈ 5.1% | 逐步降低 | 决定窗口能开多大 |
| 退火 | 约 40M | ≈ 0.0003%(量级示意) | 衰减到 0 | 决定"最后一公里"的风格与专项能力 |
预训练不是"一条曲线跑到底",而是三个目标不同的阶段串联:初始预训练打底座(便宜、量大)、长上下文扩展补窗口(贵、针对性强)、退火做精修(量极小、影响极大)。不理解分阶段,就会误以为"多喂数据就能变强"——实际上末期喂什么,往往比总量更重要。
上一张卡片我们看到预训练分三阶段。每个阶段都要配一条学习率曲线,而这条曲线的形状是先升后降——为什么?
因为训练初期参数是随机初始化的,gradient(梯度)方向噪声极大,所以要用 warmup 把步长从小放大;因为训练末期已经接近最优解,所以要把步长收敛到接近 0。可以把这类比成开车:起步时缓慢加油(warmup),到站前必须减速(decay)——上来就地板油或者到站不刹车都会出事。
更早、更自然的做法是一步到位就用峰值学习率——但那恰好落在训练最脆弱的时刻:初始权重随机,早期 gradient(梯度)既大又乱,一次更新就能把参数推入「logits 饱和、范数爆掉」的区域,训练在最初几百步内直接 NaN 或发散(Goyal et al., arXiv:1706.02677 的 warmup 方案正是为此提出)。warmup 只用 8,000 步(占总步数 0.67%)就把这段危险期护了过去。
| 模型 | 峰值学习率 | 相对 405B | 说明 |
|---|---|---|---|
| 8B | 3 × 10⁻⁴ | 3.75× | 小模型可用更大步长 |
| 70B | 1.5 × 10⁻⁴ | 1.875× | —— |
| 405B | 8 × 10⁻⁵ | 1× | 越大越保守 |
| 参数 | 数值 | 占总步数比例 |
|---|---|---|
| 总训练步数 | 1,200,000 | 100% |
| warmup 步数 | 8,000 | ≈ 0.67% |
| 峰值学习率 | 8 × 10⁻⁵ | —— |
| 最终学习率 | 8 × 10⁻⁷ | 峰值的 1% |
| 项目 | 计算 | 结果 |
|---|---|---|
| 学习率降幅 | 8e-7 ÷ 8e-5 | 1%(降了 99%) |
| 对照:线性衰减(同样端点) | 中点同为 50% 峰值 | cosine 在前 1/4 段更高、后 1/4 段更低 |
| 若不衰减(恒定 8e-5) | 末期步长过大 | 损失在最优解附近来回震荡,收敛不到底 |
学习率曲线是"先探索、后收敛"的量化表达:warmup 用不到 1% 的步数换取训练不崩,cosine 把学习率从峰值压到 1% 换取收敛到最优解附近。三个数(峰值、warmup 步数、衰减终点)就决定了整条曲线——而峰值随模型变大而减小,是"规模越大越保守"的直接体现。
上一张卡片把学习率定好了。但还有一个更硬的问题:405B 参数的模型,一张卡装得下吗?
因为权重、梯度和优化器状态加起来有好几 TB,所以必须切分。可以把这类比成搬一张超大沙发进小公寓:要么拆成几块分别搬(张量并行),要么先把客厅、卧室分开搬(流水线并行),要么雇几组人各搬各的家具(数据并行)。
| 项目 | 每参数字节 | 405B 总计 |
|---|---|---|
| fp16/BF16 权重 | 2 | 405e9 × 2 = 810 GB |
| fp16/BF16 梯度 | 2 | 405e9 × 2 = 810 GB |
| Adam 优化器状态(fp32 主权重 + 一阶 + 二阶矩) | 12 | 405e9 × 12 = 4,860 GB |
| 合计 | 16 | ≈ 6,480 GB(6.48 TB) |
| 项目 | 计算 | 结果 |
|---|---|---|
| 单卡显存 | —— | 80 GB |
| 仅装训练状态所需卡数 | 6,480 ÷ 80 | ≥ 81 张 |
| 加上激活与缓冲(经验上再留 50% 余量) | 6,480 × 1.5 ÷ 80 | ≈ 122 张起 |
| 策略 | 切什么 | 解决什么问题 | 代价 |
|---|---|---|---|
| DP 数据并行 | 数据 | 提高吞吐 | 每卡都要存完整模型状态 |
| TP 张量并行 | 矩阵乘 | 单层显存与算力分摊 | 通信量最大(每层都要同步) |
| PP 流水线并行 | 网络层 | 按层分摊模型 | 有空泡(流水线等待造成的空闲),需微批填充(把大批切成小块塞满流水线) |
| CP 上下文并行 | 序列 | 支撑超长上下文 | attention(注意力)通信增加 |
并行策略不是"为了快",而是"为了装得下":405B 的训练状态就要 6.48 TB,单卡 80GB 至少需要 81 张卡,实际配置往往上千张。选哪种并行的核心判据是通信开销——通信量最大的张量并行放在机内高速互联,通信量小的数据并行放到跨机器。
上一张卡片把模型切分开了。但显存仍然紧张,而且训练还有个更隐蔽的风险:某一步梯度爆炸——为什么这两个问题要一起解决?
因为降低精度既能省显存又能加速,所以用 BF16 混合精度;因为低精度下梯度更容易溢出,所以必须配梯度裁剪兜底。可以把这类比成开车既换轻量轮胎(省油),又加装限速器(防超速失控)——前者提高效率,后者保证不出事。
更早的做法是全程 fp32——但代价是双份:权重与梯度都要按 4 字节存(BF16 只需 2 字节),矩阵乘吞吐也低得多(H100 上 FP32 CUDA Core 峰值 67 TFLOPS、BF16 张量核 dense 989 TFLOPS,跨口径差约 15 倍;同为张量核口径时 TF32 约 495 TFLOPS,差距要小得多。来源:NVIDIA H100 官方规格)。不做裁剪也一样会翻车:正常批次梯度范数约 0.3,异常批次可达 50(均为示意值),不裁剪时后者的更新幅度是前者的约 167 倍;而把全局学习率调小来兜底,又会拖慢所有正常批次——裁剪只对异常批次收手,正是为了不动正常学习。
| 格式 | 指数位 | 尾数位 | 能表示的最大值 | 是否需要 loss scaling(损失缩放:把损失放大以避免小梯度下溢) |
|---|---|---|---|---|
| FP16 | 5 | 10 | 约 6.5 × 10⁴ | 需要(容易溢出) |
| BF16 | 8 | 7 | 约 3.4 × 10³⁸ | 不需要(指数范围与 fp32 一致) |
| FP32 | 8 | 23 | 约 3.4 × 10³⁸ | 不需要 |
| 方案 | 权重 | 梯度 | 优化器状态 | 合计(字节/参数) |
|---|---|---|---|---|
| 全程 fp32(Adam) | 4 | 4 | 4 + 4 = 8 | 16 |
| BF16 混合精度 | 2 | 2 | fp32 主权重 4 + 4 + 4 = 12 | 16 |
| 情形 | 梯度范数 | 若裁剪到阈值 1.0 | 后果对比 |
|---|---|---|---|
| 正常批次 | 0.3(示意) | 不触发(0.3 < 1.0) | 正常更新 |
| 异常批次 | 50(示意) | 缩放到 1.0(缩小 50 倍) | 避免一步走出有效区域 |
| 若裁剪到 10 | 50 | 缩放到 10(仅缩 5 倍) | 可能仍不够,模型仍有被推飞的风险 |
低精度与梯度裁剪是一对:BF16 换的是更快的矩阵乘,而不是更小的显存(权重梯度各减半、但要多存 fp32 主权重,合计仍是 16 字节/参数),梯度裁剪则给单步更新设上界,防止个别异常批次把参数推飞。两者合起来,才让"几千张卡连续跑几个月"成为可能——任何一步坏掉,整轮训练的时间成本都极高。
上一张卡片讲了精度与裁剪。但即便这些都做对,训练仍可能突然崩一下——loss 在几步之内飙升。该怎么处理?
因为尖峰往往是"坏批次 × 当前参数"的偶发组合——具体表现为某一批数据算出的 logits 异常,交叉熵损失随之跳高——所以处理思路不是改超参,而是回滚 + 跳过。可以把这类比成走山路遇到塌方:不用重修整条路(从头重训),退回到塌方前的位置(回滚检查点)、绕开那一段(跳过坏批次)就够了。
| 项目 | 数值 | 说明 |
|---|---|---|
| 尖峰次数 | 约 20 次 | 540B 训练全程 |
| 回滚步数 | 约 100 步 | 从尖峰前最近的检查点重启 |
| 跳过批次 | 约 200~500 批 | 绕开导致尖峰的数据段 |
| 是否启用梯度裁剪 | 已启用却仍发生 | 说明裁剪不是万能的 |
| 方案 | 要重算的量 | 相对成本 |
|---|---|---|
| 从头重训 | 全部 1,200,000 步 | 1×(完整预训练成本) |
| 回滚 100 步 + 跳过 500 批 | 约 100 步的重复 + 跳过的数据 | 约 0.008% |
| 不回滚、继续训 | 0 | 但参数可能已进入坏区域,后续 loss 长期偏高 |
| 现象 | 判断 | 动作 |
|---|---|---|
| loss 飙升后几步内自行回落 | 偶发波动 | 不必回滚,继续观察 |
| 飙升后持续在高位不降 | 参数已坏 | 回滚检查点 + 跳过该数据段 |
| 同一数据段反复引发尖峰 | 数据本身有问题 | 回滚并永久剔除该数据 |
Loss spike 是大规模训练的常态而非异常——PaLM 540B 全程遇到约 20 次,且梯度裁剪已启用。标准处置是"回滚约 100 步 + 跳过约 200~500 批",成本仅约 0.008% 的一轮训练,远低于从头重训。所以工程上的关键不是"如何避免尖峰",而是"多快能发现、多近能回滚"——这直接决定了检查点策略。
前面五张卡片讲了阶段、学习率、并行、精度、故障处理。最后要回答一个问题:这套方案跑下来,到底用上了多少算力?
因为"跑了多少天"不能反映效率(卡多、模型小、效率低也能耗掉同样时间),所以需要一个归一化指标——MFU。可以把这类比成评价一辆货车的运输效率:不看它跑了多久,而看"载重 × 里程 ÷ 理论最大载重里程"——MFU 就是这个比值。
| 符号 | 含义 | 来源 |
|---|---|---|
| 分子 | 实际达到的模型 FLOPs/s | 由 6ND ÷ 训练时长 ÷ 卡数 反推;厂商或论文通常直接报告 MFU |
| 分母 | 硬件理论峰值 FLOPs/s | 厂商规格(需注明精度口径) |
| MFU | 分子 ÷ 分母 | 越高说明算力浪费越少 |
| 模型 | 报告的 MFU | 硬件与口径 |
|---|---|---|
| PaLM 540B | 46.2% | 6,144 TPU v4 |
| Llama 3 405B | 约 38%~43% | H100,BF16 口径(区间随并行与批配置而异,非同一配置) |
| 损耗项 | 大致影响 | 能否消除 |
|---|---|---|
| 通信等待(TP/PP/DP 同步) | 主要损耗 | 部分(重叠通信与计算) |
| 流水线空泡 | 随 PP 级数增大 | 部分(增加微批、交错调度) |
| 重算(gradient checkpointing) | 多算约 30% 前向 | 不计入 MFU 分子,但真实耗时增加 |
| 激活与缓冲的访存 | 次要 | 部分 |
| 故障回滚(loss spike 等) | 少量 | 不可完全消除 |
MFU 是衡量"这次预训练跑得好不好"的归一化读尺:PaLM 540B 是 46.2%,Llama 3 405B 论文自报约 38%~43%(BF16 口径)。剩下的算力损耗主要来自通信、流水线空泡与故障回滚。所以"能不能训成"之外,"能不能把 MFU 做上去"才是工程能力的直接体现——同一笔预算,MFU 差 10 个百分点就是十天级别的差距。