Loss Spike 排查:训练发散问题定位
摘要:训练跑到一半 loss 突然暴涨或跳成 NaN,是千卡训练最贵的问题——每一次发散都可能意味着数小时算力白费。本文给出四层排查框架(数据 / 数值 / 通信 / 硬件),按序定位,每层都有明确判据与处置。
1. 先给现象分类:不同 spike 不同根因
| 现象 | 特征 | 最可能方向 |
|---|---|---|
| 瞬间 NaN/Inf | loss 一步内变成 NaN,之后全是 NaN | 数值层:梯度溢出、坏样本、优化器状态异常 |
| 单点尖刺后恢复 | 单个 step 暴涨,随后回到正常 | 数据层:异常 batch;通信层:瞬时重传 |
| 尖刺后不回 | 暴涨后持续高位或继续涨 | 学习率/数据分布突变、检查点损坏恢复 |
| 缓慢爬升 | 无尖刺,loss 趋势向上 | 数据顺序变化(RNG 未恢复)、精度问题、LR 策略 |
第一步永远是记录:训练日志里必须有 step / loss / lr / grad_norm / gpu 温度 / 网络重传 的可视化,否则任何排查都是盲猜。
2. 四层排查框架(按序执行)
2.1 第一层:数据层——是不是喂进去的东西坏了
排查动作:
1. 定位 spike 对应的 global step → 找到该 step 的 batch(data_loader 状态)
2. 检查该 batch:是否有损坏样本(损坏图片/超长文本/空样本)
3. 检查数据 shuffle 是否在恢复检查点后"换序"(RNG 未恢复)
4. 检查数据加载是否有错位(分布式下各 rank batch 不一致)- 分布式数据错位是隐蔽杀手:某个 rank 的数据文件损坏/读取失败,该 rank 梯度变成垃圾,AllReduce 后全集群污染。
- 验证:
grad_norm突变 + 单 rank 显存异常波动 → 在 dataloader 里加torch.distributed.barrier()与采样 seed 校验。
2.2 第二层:数值层——梯度是不是爆了
排查动作:
1. 开启 grad clip(先保命):max_grad_norm 1.0 起步
2. 检查 grad_norm 曲线:spike 前 grad_norm 是否先爆
3. torch.autograd.detect_anomaly() 定位 NaN 产生算子
4. 检查 LR 是否过大(spike 后降 LR 0.3× 测试恢复)
5. 检查 FP16 Loss Scaling:scale 崩到极小 → 梯度下溢python
# 定位 NaN 源头(debug 用,慢,只跑少量步)
with torch.autograd.detect_anomaly():
loss.backward()
# 生产:记录 grad_norm 与 NaN 标记
grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
if not torch.isfinite(grad_norm):
logger.error(f"NaN grad at step {step}")- BF16 通常不会下溢,若 BF16 训练出 NaN,优先怀疑数据/算子实现,而非精度本身。
- Inf 输入:
tokenizer特殊 token 溢出、embedding 层初始化异常也会产出 Inf。
2.3 第三层:通信层——是不是同步出了问题
排查动作:
1. NCCL_DEBUG=INFO 重跑,观察 spike 时刻是否有重传/超时(TME 报错)
2. 交换机侧:ethtool -S 看 pause 帧/丢包在 spike 时刻是否同步上涨
3. 确认通信算子与并行策略匹配(PP 的 send/recv 是否错位)
4. 检查是否混用了不同 NCCL 版本的镜像- 通信层异常的特征是"全场同时变差":所有 rank 的 loss 同时跳,且伴随网络计数上涨。
- 与数据层区分:数据错位是"个别 rank 异常",通信异常是"全体同步异常"。
2.4 第四层:硬件层——是不是机器坏了
排查动作:
1. nvidia-smi -q -d ECC:spike 时刻前后 ECC 错误计数是否增长
2. dmesg / nvidia-smi -q -d XID:Xid 79/45/63 等错误
3. 温度/功耗曲线:是否有节点过热降频(吞吐掉但 loss 不该跳)
4. IB 链路:ibstatus 速率协商、SM lid 变化(链路闪断)- 硬件故障的典型特征:spike 与特定节点强相关(换掉该节点的 rank 映射后不再出现)。
- 处置:先隔离节点,不要边查边训——故障节点可能继续污染全局梯度。
3. 快速处置矩阵
| 现象 | 保命动作 | 根因确认 | 恢复 |
|---|---|---|---|
| 瞬间 NaN | 停训练,留现场 | detect_anomaly + grad_norm 曲线 | 修根因后从最近完好 checkpoint 恢复 |
| 单点尖刺 | 记录,继续观察 50 步 | 是否复现 + 网络计数 | 不复发继续;复发进数据层排查 |
| 尖刺后不回 | 降 LR 0.3× 试跑 | 是否改善 | 改善=LR 问题;无改善=数据/检查点 |
| 恢复后爬升 | 校验 RNG 恢复 | 对比未中断 loss 曲线 | 修正 dataloader/RNG 恢复逻辑 |
4. 生产案例:千卡训练反复 NaN 的真凶
现象:千卡集群 LLM 训练,每 2~3 天出现一次全局 NaN,重启后训练正常,约 1 天后再现。
排查过程:
| 步骤 | 动作 | 发现 |
|---|---|---|
| 1 | 定位 NaN 前 grad_norm | 前 10 步 grad_norm 缓慢爬升后爆表 |
| 2 | detect_anomaly | 指向某个 attention 层 matmul 输出 Inf |
| 3 | 数据层检查 | 该 batch 含一个超长文本样本(token 数异常) |
| 4 | 复现验证 | 单独喂该样本,单卡必现 NaN |
| 5 | 修复 | 数据管线加长度过滤 + max_length 截断 + 采样去重 |
根因:上游清洗管道漏过一个损坏的超长样本,attention 计算量随序列长度平方增长,超出数值范围。数据层问题伪装成数值层故障,是最高频的误判——所以四层框架必须从数据层开始查。
5. 防复发清单
- [ ] 训练日志含 step/loss/lr/grad_norm/温度/网络计数,且可视化
- [ ] 数据管线有长度过滤、空样本过滤、去重校验
- [ ] 分布式采样 RNG seed 校验,恢复检查点后数据顺序一致
- [ ] grad clip 开启(1.0 起步),grad_norm 监控告警
- [ ] ECC/Xid 监控接入告警,故障节点自动隔离
- [ ] 所有恢复都从"最近完好 checkpoint"(含校验和)出发
- [ ] 定期用少量步做"健康跑"(无通信、无数据变化的纯计算基准)作为对照组
延伸阅读:混合精度训练与断点续训(精度与恢复的稳定性基础)、NCCL 网络调优实战(通信层深度排查)。