为何全Float32模型使用Gradient Scaling可采用更大Learning Rate?
Gradient Scaling原本用于混合精度训练(部分模型权重为float16、部分为float32),核心目的是缓解float16存储时的小梯度下溢问题。但实验中发现一个反常现象:在全float32权重的模型中使用Gradient Scaling时,能采用远高于无缩放场景的学习率(LR)实现收敛,两者LR差异极大(例如1.5e-4 vs 1.5e-8)。
带缩放的更新代码:
loss_scaler = torch.cuda.amp.GradScaler() loss_scaler.scale(loss).backward() loss_scaler.unscale_(optimizer) loss_scaler.step(optimizer) loss_scaler.update()
无缩放的更新代码:
loss.backward() optimizer.step()
核心疑问:按道理缩放后的梯度最终会被还原为原始梯度,两者训练时的LR应该一致才对?为何会出现如此巨大的LR差异?ChatGPT给出的解释是“即使float32也可能发生下溢,缩放可使这些下溢梯度参与权重更新,提升训练稳定性,因此可采用更大LR”,但对此解释存疑。
你观察到的现象本质上和梯度缩放的实际执行逻辑以及float32的数值特性有关,并非简单的“梯度还原”就能概括:
梯度缩放并非完全无损的“缩放-还原”
虽然unscale_()会将梯度缩放回原始量级,但反向传播的梯度计算是基于缩放后的loss完成的。float32的动态范围有限(约1e-45到1e38),如果原始梯度极小(远小于1e-38),无缩放场景下这些梯度会直接下溢为0,完全无法参与权重更新;而通过梯度缩放,小梯度会被放大到float32可表示的范围,在反向传播中保留有效数值,后续再被unscale_()还原——此时梯度已不是“全0”状态,而是携带了有效信息。有效梯度占比提升带来学习率容忍度变化
无缩放场景中,大量小梯度被下溢为0,实际参与权重更新的有效梯度占比极低,模型只能依赖极小的学习率缓慢迭代;开启梯度缩放后,原本丢失的小梯度被保留,模型的有效梯度信号密度大幅提升,此时可以用更大的学习率驱动更快收敛——这就是两者LR差异巨大的核心原因。GradScaler的动态调整机制增强稳定性
PyTorch的GradScaler会通过update()动态调整缩放因子:如果训练中出现梯度溢出,会自动降低缩放因子避免NaN/Inf出现。这种动态调整进一步提升了训练稳定性,让模型能承受更大的学习率而不发散。
若要验证上述结论,可开展两组对比实验:
- 打印无缩放场景下的梯度分布,统计梯度为0的比例;
- 开启梯度缩放后,打印
unscale_()前后的梯度数值,对比无缩放时的原始梯度,观察是否有原本为0的梯度变为非0的有效数值。
内容的提问来源于stack exchange,提问作者Zhenshun Liu

