深蓝黑背景封面。左侧是一条大模型训练 loss 曲线:横轴为训练步数、纵轴为 loss,青色折线整体稳步下降,在中段突然向上冲出一根紫色尖刺并被圆圈标注「Loss 突刺」;曲线下方并排放置三张成因卡片,分别标注「脏数据 batch」「学习率过大」「梯度爆炸 / 溢出」;再下方一行恢复流程条,写着「恢复动作:回滚到突刺前 checkpoint → 跳过可疑数据 → 以更小学习率重启」。右侧大标题两行「训练曲线突然冒出一根刺」「Loss 突刺是怎么回事」,副标题「大模型训练的数值稳定性事故:成因、定位与恢复」,底部小注「先回滚再排查:数据、学习率、梯度三条线分开查」。左上角标注「面试经典 · 训练稳定性」,右上角圆角胶囊标注「Day 08 · 12-05」。 图 1:本文封面,loss 突刺的三条成因线与恢复流程。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料整理,与任何公司的当前招聘无关。loss 突刺(loss spike)指的是训练过程中 loss 曲线在平稳下降时突然向上冲出一根尖峰。这道题考的从来不是名词解释,而是你把一次训练事故当成系统工程问题来处理的能力:成因分几层、先查什么、怎么恢复。

一句话结论:突刺是数值稳定性事故,成因集中在数据、学习率、梯度数值三条线;处理原则是先回滚到突刺前的 checkpoint 稳住现场,再分头排查。

突刺为什么值得单独当回事?

它不只是曲线上难看。轻的时候,模型要花大量训练步才能爬回突刺前的水平,等于白烧一段算力;重的时候 loss 再也降不下来,整个训练跑废。这不是理论担忧:2022 年发布的 GLM-130B 技术报告(arXiv:2210.02414)把 loss 突刺与发散列为训练百亿规模模型时最突出的工程挑战,并提到同期的 OPT-175B 靠人工跳过数据段、调整超参数硬扛,BLOOM-176B 用了 embedding norm 技术压住不稳定。也就是说,一线大模型团队都在和同一根刺搏斗。

成因分三层,排查也按这三层走。

数据层:某个 batch 混入脏样本——乱码、超长文本、标注错误——瞬时算出一个离谱的损失和梯度。特征是突刺往往单步出现、随后自己回落。优化层:学习率峰值过高或 warmup 太短,模型在损失曲面的陡峭区域迈出过大的一步,轻则震荡重则弹飞。数值层:梯度爆炸或混合精度上溢。GLM-130B 的报告里有一个很值的记住的观察:训练崩溃之前,梯度范数(grad norm)往往提前几步就飙出尖峰,而且异常梯度经常来自嵌入层——他们最终用嵌入层梯度收缩(系数取 0.1)抹掉了绝大多数突刺。这给了排查一个关键抓手:grad norm 是比 loss 更早的报警器。

为什么模型越大越容易中招?小规模训练里梯度噪声会被平均掉,而出问题的条件——超大 batch 下梯度估计趋于平稳、部分参数长期得不到有效更新、混合精度数值范围紧张——在百亿规模上会同时出现,任何一层数值失控都会被后续的连乘放大成整根尖刺。GLM-130B 的最终训练跑也只靠继续收缩嵌入层梯度就压住了后期三次发散,说明这类事故大多数时候可恢复,前提是你有高频 checkpoint 和正确的排查顺序。

用最小实验看懂「谁先有信号」。

一段玩具代码(Node.js 22.15.0,node spike.js):让参数沿梯度稳定下降,第 6 步注入一个 50 倍梯度的脏 batch,对比不做梯度裁剪和把梯度范数裁到 1 的两种跑法:

let w = 1.0, lr = 0.1;
const target = () => 0.5 * w * w;        // 玩具损失,最优点在 w=0
function run(clip) {
  w = 1.0;
  const log = [];
  for (let step = 1; step <= 12; step++) {
    const dirty = step === 6;            // 第 6 步混入异常 batch
    const g = w * (dirty ? 50 : 1);
    const gn = Math.abs(g);
    const gg = clip && gn > 1 ? g / gn : g;  // 梯度范数裁剪到 1
    w -= lr * gg;
    if (step >= 5 && step <= 8)
      log.push(`步${step} loss=${target().toFixed(3)} |g|=${gn.toFixed(1)}`);
  }
  return log.join('  ');
}
console.log('不裁剪:', run(false));
console.log('裁剪  :', run(true));

实测输出:

不裁剪: 步5 loss=0.174 |g|=0.7  步6 loss=2.789 |g|=29.5  步7 loss=2.259 |g|=2.4  步8 loss=1.830 |g|=2.1
裁剪  : 步5 loss=0.174 |g|=0.7  步6 loss=0.120 |g|=29.5  步7 loss=0.097 |g|=0.5  步8 loss=0.079 |g|=0.4

不裁剪时第 6 步 loss 从 0.174 冲到 2.789——一根教科书式的刺;裁剪后同一根 29.5 的梯度尖峰被压平,loss 继续平滑下降。注意两种跑法第 6 步的 |g| 都是 29.5:监控 grad norm 能提前看到事故,裁剪则让事故不演变成灾难。

深蓝黑背景概念图。顶部一行小字「概念图 · Loss 突刺」与主标题「一根刺背后的三层成因:先稳住,再分头查」。左上是一张放大的训练曲线面板:青色 loss 折线下降中段被一根紫色尖刺打断,尖刺旁标注「这一步发生了什么?」,曲线下注「监控三件套:loss 曲线、梯度范数 grad norm、学习率曲线一起看」。右上是「定位三问」面板,依次列出「突刺是单步还是持续?」「对应 batch 数据是否异常?」「同一时刻 grad norm 是否飙升?」。中部是三张竖排成因卡片:数据层(脏样本、乱码、标注错误,处理:跳过该 batch 并清洗数据源)、优化层(学习率峰值过大、warmup 太短,处理:降低峰值、延长预热)、数值层(梯度爆炸、FP16 上溢,处理:梯度裁剪、检查 loss scale)。底部是一条四步恢复流程带箭头串联:回滚到突刺前的 checkpoint、跳过可疑数据段、以更小学习率重启、观察后续曲线确认收敛。 图 2:三层成因、定位三问与四步恢复流程。

面试现场的答题结构。

先给定义和危害,再把成因拆成数据、学习率、梯度数值三层各举一例,然后讲定位三问:突刺是单步还是持续、对应 batch 数据是否异常、同一时刻 grad norm 是否飙升——三问分别指向三条成因线。最后给恢复流程:回滚到突刺前的 checkpoint,跳过可疑数据段,以更小学习率重启并观察曲线确认收敛;长期手段是梯度裁剪、合理 warmup、数据清洗,混合精度场景还要检查数值范围。如果追问「怎么提前防」,就落到 GLM-130B 那条经验上:盯 grad norm 这个先行指标。你在训练里遇到过 loss 突刺吗?最后定位到的是数据问题还是数值问题?欢迎聊聊。