pytorch的gradscaler通过“放大损失→fp16反向传播→unscale还原→fp32更新”三步链式操作,解决fp16梯度下溢(如1e-10被截断为0)问题;漏掉scaler.unscale_()或未确保optimizer使用fp32参数将导致更新失效。

PyTorch 的 GradScaler 是专为解决 FP16 下溢出设计的核心机制,但必须配合手动 unscale 和 FP32 参数更新才能生效;单纯开启 AMP 不会自动规避下溢。
为什么 loss.backward() 后梯度会变成 0?
FP16 最小正数约 5.96e-8,而训练后期梯度常低于 1e-10。这类值在 FP16 中直接被截断为 0.,导致参数无法更新。
- 典型现象:
print(grad_fp16)输出tensor([0.], dtype=torch.float16),但同位置的 FP32 梯度非零 - 不是模型“不学了”,是数值被硬件精度硬性归零
-
GradScaler不改变前向计算,只干预反向传播起点——loss 的缩放
GradScaler 如何工作?关键三步不能跳
它不“智能修复”,而是靠“放大→计算→还原”链式操作保梯度有效位:
- 前向后、
loss.backward()前:调用scaler.scale(loss).backward()→ loss 被乘以当前scale(默认2**16),使梯度整体上移进 FP16 可表区间 - 反向后、
optimizer.step()前:必须插入scaler.unscale_(optimizer)→ 将 FP16 梯度转成 FP32 并除以scale,否则step()会拿被缩放过的小梯度直接更新(仍为 0) -
scaler.step(optimizer)内部会检查梯度是否含inf/nan,若存在则跳过本次更新并自动衰减scale
容易被忽略的致命细节
很多崩溃不是因为没开 GradScaler,而是漏掉中间环节或误用类型:
- 未调用
scaler.unscale_(optimizer)就直接optimizer.step()→ 梯度仍是缩放后的 FP16,极大概率全 0 或溢出 - 在
autocast区域内对 loss 做了额外处理(如loss.mean().item())→ 提前触发 FP16 到 CPU 的转换,丢失缩放上下文 - optimizer 使用了
torch.optim.SGD等不支持 FP16 参数的版本 → 必须确保optimizer.param_groups[0]["params"]指向的是 FP32 参数副本(AMP 默认维护) -
scaler.step()后忘记scaler.update()→scale不会自适应调整,下溢风险随训练轮次累积
最隐蔽的问题往往出现在「以为自己用了 FP32 更新,其实 optimizer 里存的是 FP16 参数」——务必确认 model.parameters() 和 optimizer.param_groups[0]["params"] 的 dtype 不同,且后者是 FP32。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











