Skip to main content

05 - 第一次完整预训练与复盘

前面四篇产出的是代码,这一篇产出的是一次真实的训练记录

需要说明这一篇的性质:它现在是模板,不是记录。 表格里的数字要等真开卡跑完才能填。之所以先写出来,是因为该记什么必须在开跑之前就定好——训练跑起来之后再想「早知道该记一下这个」,就得重跑。

前置:01 到 04 篇的验收清单全部通过。

一、开跑前的最后检查

前面每篇都有验收清单,这里只列跳过了就会烧掉真金白银的那几条。

检查项出自跳过的后果
train.bin 随机位置 decode 回文本通顺01 篇六节数据是乱的,17 小时全废
val.bin 的文档不在 train.bin01 篇 5.7验证 loss 偏低,误判模型变好
参数量对得上 502,193,66402 篇 8.1模型结构写错
初始 loss 在 10.37 附近02 篇 8.3偏低说明因果掩码失效,模型在抄答案
因果性测试通过02 篇 8.4同上,且这是唯一能主动发现它的手段
杀掉进程能从 checkpoint 恢复且曲线接得上03 篇十节实例被回收时无法续跑
checkpoint 路径指向持久化云盘03 篇 8.3实例回收后 checkpoint 一起没
多卡 loss 曲线与单卡重合04 篇六节种子没错开,四张卡在算同样的数据

最后两条最容易漏,也最贵。 前者的代价是整轮训练白跑,后者的代价是花了四倍的钱得到单卡的效果。

开跑前要过的八条,按「跳过的代价」分组跳过 = 整轮 17 小时白跑train.bin 随机位置 decode 通顺01 篇六节 · 数据是乱的就全废因果性测试通过02 篇 8.4 · 唯一能主动发现它的手段checkpoint 路径指向持久化云盘03 篇 8.3 · 实例回收后一起没跳过 = 钱花了,效果打折多卡 loss 曲线与单卡重合04 篇六节 · 种子没错开 = 四卡在算同一批数据代价是花四倍的钱买到单卡的效果杀掉进程能从 checkpoint 恢复03 篇十节 · 曲线要接得上,不能跳这两条最容易漏:跑完之前完全看不出异常跳过 = 结论错,但还能补救val.bin 的文档不在 train.bin 里01 篇 5.7 · 验证 loss 会系统性偏低参数量对得上 502,193,66402 篇 8.1 · 对不上就是结构写错了初始 loss 落在 10.37 附近02 篇 8.3 · 偏低 = 因果掩码失效这八条的共同点:全部能在开跑前几分钟内验完,而其中任何一条失败,都要等 17 小时之后才看得见。中途发现要改超参怎么办 —— 学习率偏高可以从最近的 checkpoint 恢复、调低继续;数据或模型结构的问题,从头重跑是唯一选择。
三组的排序不是按重要性,是按「发现得越晚越贵」。左边一组的失败模式是训练本身毫无意义,中间一组是训练有意义但账单翻倍,右边一组是训练有效但你对它的判断是错的。第三类最容易被当成小事,可它会让你在后面所有决策上都偏一点。

二、跑起来之后盯什么

2.1 六个指标

指标记录频率健康的样子
训练 loss每 10 步从 10.37 持续下降
验证 loss每 200 步跟随训练 loss,不明显背离
grad norm每 10 步稳定小幅波动,不长期贴着裁剪阈值
学习率每 10 步与 03 篇 5.3 打印的曲线一致
tokens/s每 10 步稳定,用来估剩余时间
MFU每 10 步30% 以上;低于 30% 查瓶颈

另外每次存 checkpoint 时记一下峰值显存已消耗卡时。前者用来判断还能不能加大 batch,后者用来对成本。

2.2 日志要落盘,不要只打屏

torchrun 的输出在实例被回收时一起消失。把日志重定向到持久化云盘上,和 checkpoint 放一起:

torchrun --standalone --nproc_per_node=4 train_ddp.py --mode ddp \
2>&1 | tee /mnt/persistent/logs/run_$(date +%Y%m%d_%H%M).log

复盘时要回看的是曲线,不是最后一屏。

六个指标 · 记录频率 · 健康的样子训练 loss每 10 步从 10.37 一路下降开头陡、后面缓验证 loss每 200 步跟随训练 loss不明显背离就健康grad norm每 10 步稳定小幅波动别长期贴着阈值 1.0学习率每 10 步warmup 后 cosine 降与 03 篇 5.3 打印的一致tokens/s每 10 步平稳拿它估剩余时间MFU每 10 步30% 以上低于 30% 就查瓶颈另外每次存 checkpoint 时记一下峰值显存和已消耗卡时:前者用来判断还能不能加大 batch,后者用来对成本。日志必须重定向到持久化云盘 —— torchrun 的输出在实例被回收时一起消失。复盘时要回看的是整条曲线,不是最后一屏。
六条曲线里,前两条讲「学得怎么样」,后四条讲「跑得怎么样」。真正会被忽略的是第三条:grad norm 不影响任何结果指标,却是唯一能在 loss 出问题之前给出信号的量。记录频率也值得留意——验证 loss 每 200 步一次不是省事,是因为它每次都要跑一遍验证集,太频繁会明显拖慢训练。

三、loss 曲线的四种形状

绝大多数训练问题在 loss 曲线上有特征,而且前两百步就能看出来。开跑之后先盯着前两百步,不对就立刻停,别等 17 小时。

① 健康10.37开头陡、后面缓符合预期,继续跑② 中途发散10.37先降后炸,最后变 NaNloss 忘了除 grad_accum / 学习率太高 / 没做裁剪③ 完全不降10.37几百步过去几乎是水平线学习率被设成 0 / 忘了 step() / warmup 写成了总步数④ 降得异常快10.37几十步就掉到很低并压平因果掩码失效,模型在抄答案 —— 最危险四种形状对应四类完全不同的原因,而且前两百步就能分辨。最需要警惕的是第四种 —— 它看起来是「训练特别顺利」,实际多半是模型能看到答案。曲线越好看越要先怀疑因果掩码。
把四条曲线并排放,是为了训练一种反射:开跑后先盯前两百步,把实际曲线往这四张里对,不对就立刻停。等 17 小时跑完再看,第②③④种的代价都是整轮重来。

对应的诊断:

形状最可能的原因先查什么
① 健康正常继续,但仍要看 grad norm
② 中途发散loss 忘了除 grad_accum;学习率太高;没做梯度裁剪03 篇 3.3、5.4、第六节
③ 完全不降学习率被设成 0;忘了 optimizer.step();warmup 步数写成了总步数03 篇五节
④ 降得异常快因果掩码失效,模型在抄答案02 篇 8.4 因果性测试

第四种最危险,因为它伪装成好消息。02 篇 8.3 说过:初始 loss 明显低于 10.37 是同一个问题的早期信号。

四、翻车现场与处置

按「你会先看到什么」组织,而不是按原因分类。

你看到的现象大概率原因处置
启动就 OOM,只有卡 0 显存满忘了 torch.cuda.set_device(local_rank)04 篇 2.2
训练卡死,四张卡利用率 100% 但不动某些 rank 少调了一次 collectiveTORCH_NCCL_BLOCKING_WAIT=1 让它报错,见 04 篇 5.3
MFU 只有百分之十几数据加载跟不上检查 pin_memory;调大 micro_batch
屏幕刷四份重复日志没判断 rank == 004 篇 2.2
恢复 checkpoint 后 loss 跳一下只存了权重,没存优化器状态03 篇 8.1
恢复后学习率不对step 没存进 checkpoint同上
存 checkpoint 时进程被杀,旧的也没了没做原子写03 篇 8.2

中途发现要改超参怎么办。 如果只是学习率偏高,可以从最近的 checkpoint 恢复、调低学习率继续,不必从头。但如果是数据或模型结构的问题,从头重跑是唯一选择——这也是为什么第一节那张检查表要在开跑前全过一遍。

按「你会先看到什么」排,而不是按原因排 —— 出事的时候你手上只有现象训练生命周期启动那几秒只有卡 0 显存满就 OOM忘了 set_device · 04 篇 2.2屏幕刷四份重复日志没判断 rank == 0前两百步初始 loss 不是 10.3702 篇 8.3loss �曲线四形状上一节 · 不对就立刻停中途长跑四卡 100% 但不动collective 序列不一致MFU 只有十几数据加载跟不上存盘那几十秒进程被杀,旧的也没了没做原子写 · 03 篇 8.2这是唯一一个「一次失败毁掉两份」的环节恢复之后loss 跳一下只存了权重,没存优化器状态学习率对不上step 没进 checkpoint · 03 篇 8.1前两格的故障几分钟内就会暴露,后三格要等几小时甚至等到实例真被回收才现形 —— 而它们恰好都是可以在开跑前用一次演练验掉的。
这张表的组织方式本身是个方法:排障时你不知道原因,只知道屏幕上出现了什么。把经验按「现象」而不是按「知识点」归档,下次才查得到。第五节最后那条「每次翻车记现象、原因、损失的卡时」,攒的就是这张表。

五、跑完要填的表

5.1 基本信息

开跑时间待填
硬件待填(型号、卡数、NVLink 还是 PCIe)
并行方式待填(DDP / FSDP)
实际总步数待填
实际总耗时待填

5.2 性能实测

对照 03 篇 9.3 那张「耗时与 MFU 对应关系」表填:

指标估算值实测值
tokens/s163,399待填
MFU39.5%待填
单步耗时6.42 s待填
峰值显存8 GB 静态 + 激活待填
4 卡相对单卡加速比3.7~3.9×(04 篇 4.2 预期)待填

估算与实测的偏差本身就是复盘内容。 差得多说明账算错了或有瓶颈,这比数字本身更值得记。

5.3 效果

指标
初始 loss待填(应 ≈ 10.3735)
最终训练 loss待填
最终验证 loss待填
对应困惑度待填(08 篇 4.1,PPL = exp(loss))

5.4 成本

按 00 篇 3.4 的式子 Cost=p×n×t\text{Cost} = p \times n \times t 填:

卡时金额
数据准备(CPU,不算 GPU)待填
冒烟测试(TinyStories)待填待填
多卡基准测试(04 篇 4.1)待填待填
正式预训练待填待填
翻车重跑待填待填
合计待填待填

00 篇按 150~200 卡时做的预算,实际花了多少、超没超、超在哪一项,是这张表要回答的。

00 篇按 150 到 200 卡时做的预算,钱花在哪几处正式预训练 · 68 卡时 · 4 卡 × 17 h冒烟测试 + 多卡基准 + 翻车重跑(全部待填)068150200 卡时数据准备CPU,不占 GPU0 卡时冒烟测试TinyStories 走通全链路待填多卡基准测试04 篇 4.1 的四组配置待填正式预训练4 卡 × 17 h68 卡时(估)翻车重跑预算里 2 到 3 倍余量待填五项要分开记。合并成一个总数,就答不了「超没超、超在哪一项」这个唯一值得复盘的问题 —— 而 68 卡时只占预算的三分之一,说明预算里绝大部分本来就是留给翻车的。
条形图里琥珀色那段比红色那段还长,这个比例才是这张图想说的事:一次顺利的训练只占预算的三分之一,剩下的都是给失败留的。把余量当成必然支出而不是保险,估算才不会一次次超。

5.5 翻车记录

每次翻车记三行:现象、原因、损失的卡时。这是整篇复盘里对下一次最有用的部分——第四节那张表就是这么积累出来的。

六、验收

  • 训练跑完,最终 checkpoint 在持久化存储上
  • 完整日志已落盘,能画出 loss 曲线
  • 5.1 到 5.5 五张表填完
  • 估算与实测的偏差已解释
  • 用 08 篇的方法算出困惑度
  • 拿 base 模型生成一段文本,确认它在说人话(哪怕内容是错的)

最后一条是最直观的验收。0.5B 训 10B token 的 base 模型不会回答问题(06 篇 0.2 节讲了为什么),但它续写出来的文本应该语法通顺、词能搭上。如果还是乱码,说明前面某处有问题,别急着进 06 篇。