快速修复
# 1. 兜底:梯度裁剪(PyTorch 标准 API,max_norm=1.0 是常见起点)
torch.nn.utils.clip_grad_norm_(model.parameters, max_norm=1.0)
# 2. 从最后一个健康 checkpoint 恢复 + 学习率降到原来的 1/3~1/10
# (恢复前用异常 batch 和普通 batch 各重放一次,确认根因)
机制实测:学习率过大 → loss 在约 100 步内从 0.05 级爆炸到 inf(不恢复);同样学习率加梯度裁剪 max_norm=1.0 → 全程有限稳定;注入单步尖峰后从健康 checkpoint 降学习率恢复 → 正常收敛。
你会遇到什么
训练(大模型微调/从头训练)跑到一半,loss 曲线突然跳出尖峰:可能单步跳高后自己回落,可能连续多个 spike,也可能 spike 之后永远回不到原来的水平,甚至直接变 NaN / inf。网上最常见的建议是"降学习率",但现象不等于原因——先分清是数据、超参还是链路问题,再动手。
本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。
为什么会这样
一次参数更新 = 学习率 × 梯度。loss 尖峰本质是更新量失控,但同一现象背后有三类常见根因:
- 数据:异常 batch(损坏样本、序列过长、label 非法)——通常表现为单步 spike 后恢复;
- 超参/链路:学习率过大、缺梯度裁剪、AMP 精度问题——通常表现为连续 spike 或永久不恢复;
- 目标/实现:loss mask、label、多卡归约错误——通常表现为"loss 平滑但验证不改善"或只有多卡异常。
按尖峰类型分流(原文诊断表 + 机制实测):
| 现象 | 先看什么 | 最小验证 | 确认后才处理 |
|---|---|---|---|
| 单步 spike 后恢复 | batch 上下文(token/序列长度/来源/label)、grad norm | 重放相同 batch | 修复损坏数据;合法难样本不丢弃 |
| 连续多个 spike | grad norm、clip 系数、相对更新量、学习率 | 对齐这些指标 | 调整已确认的超参/结构问题 |
| spike 后永久不恢复 | 相对更新量、optimizer state | 健康 checkpoint 对照 | 修复根因后从健康 checkpoint 恢复 |
| 第一步 NaN | 输入、activation、loss | 单 batch FP32 重放 | 修复第一个非有限运算或精度配置 |
训练循环里,参数更新的本质是 参数 += 学习率 × 梯度,loss 尖峰就是一次(或连续几次)更新量过大。更新量由两个因子相乘:学习率(超参,恒定或按 schedule 变化)和梯度(由当前 batch 的损失反向传播算出)。
两个因子各自爆炸都会让乘积失控:学习率过大 = 每次更新都太大,即使梯度正常也会发散;梯度异常 = batch 里有损坏样本/序列过长/label 非法时,这一批算出的梯度方向或量级异常,单步更新就巨大。
这就是“现象 ≠ 原因”的结构:尖峰是结果,要从“哪个因子失控”反推根因。两个排障工具对应两个因子:重放相同 batch(如果同一批数据稳定复现尖峰,说明是数据因子;不能复现,说明是随机性/并行/状态因子);看 grad norm(裁剪前梯度范数持续抬升 = 梯度因子风险;grad norm 正常但更新仍大 = 学习率因子)。
梯度裁剪兜底的机制也由此而来:它直接把梯度范数限制在阈值内,等于给“梯度因子”上了保险丝,即使数据或计算偶发异常,单步更新量也被钳制在安全范围。
一步步来
1. 先分类:什么类型的 spike — 必做
单步后回落 / 连续多个 / 永久不恢复 / 直接 NaN。类型决定排查方向(见上表)。
2. 重放相同 batch — 必做
- 能稳定复现 → 数据问题优先(损坏样本、label、mask);
- 不能复现 → 检查随机算子、并行执行、前一状态差异。
3. 看 grad norm,而不是只看 loss — 必做
裁剪前 grad norm 持续抬升 → 更新链路风险;用相对更新量(参数更新幅度/参数幅值)判断训练是否还在有效学习。
4. 按根因选处理手段 — 必做
- 数据损坏 → 修数据;合法难样本 → 不丢弃;
- 更新量失控 → 梯度裁剪 / 降学习率(一次只改一个变量,做单变量对照);
- 已 NaN / 不恢复 → 从健康 checkpoint 恢复,不要只修模型参数后继续用可疑 optimizer state。
5. 防再犯:pilot run — 必做
正式训练前小规模试跑:覆盖完整 warmup、峰值学习率一段、代表性数据、至少一次 validation、固定随机种子。监控分三档:每步(loss/梯度是否有限)、窗口(趋势偏离)、周期(验证指标)。
怎么确认修好了
loss 恢复下降趋势且不再出尖峰;grad norm 回到健康区间;从健康 checkpoint 恢复后训练能持续推进;validation 指标同步改善。
其它环境的表现
| 环境 | 说明 |
|---|---|
| 单卡正常、多卡异常 | 先查归约与控制流(有效 token 数、loss 归约、各 rank 是否一致 backward),不先调超参 |
| FP16/AMP | 先 unscale 再统计和裁剪梯度(GradScaler 判断思路见原文) |
| 框架差异 | 梯度裁剪各框架都有对应实现(PyTorch clip_grad_norm_ / TF clip_by_global_norm 等),机制相同 |
| 未实测 | 本条目为等价最小机制复现(numpy 2 层 MLP),未在完整 LLM 训练框架复现;LLaMA-Factory 微调相关见 combo-local-llm-finetune |
症状
loss 曲线突然尖峰:单步 spike 后恢复、连续 spike、spike 后永久不恢复,或直接 NaN/inf;grad norm 同步异常抬升。
原因
更新量 = 学习率 × 梯度,尖峰本质是更新量失控。根因分三类:数据(异常 batch)、超参/链路(学习率过大、缺梯度裁剪、AMP 精度)、目标/实现(mask、label、多卡归约错误)。现象 ≠ 原因,先证据后处理。
解法
- 按尖峰类型定位(单步/连续/不恢复/NaN);
- 重放相同 batch + 看 grad norm / 相对更新量;
- 按根因处理:修数据 / 梯度裁剪 / 降学习率 / 从健康 checkpoint 恢复(一次只改一个变量);
- 训练前跑 pilot run + 分档监控防再犯。
验证证据
- 本机实测(2026-08-30,Python 3.14 + numpy 2.5.2,venv+清华镜像):2 层 MLP 回归 y=sin(x) 机制复现——lr=0.01 收敛(loss 0.065→0.026);lr=1.0 发散(25 步 loss 5.4e51,98 步 inf,不恢复);lr=1.0+clip=1.0 全程有限(gradnorm 裁剪后=1.0);注入单步尖峰(loss 0.048→0.224)后从健康 checkpoint 降 lr 恢复收敛(0.028)。证据:实测复现日志(2026-08-30)。
- 原文:石头开会《模型训练稳定性:从训练链路到排障手册》(2026-08-28,博客园)。
- 未实测:完整 LLM 训练框架(PyTorch/LLaMA-Factory)上的实况复现(环境重,标注"机制已复现、环境未复现",SM-076)。
适用工具 (1)
本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。
python · 外部工具成套方案 (组合包)
这个问题包含在下列成套安装方案中。
来源与证据
← 大模型本地部署 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡