混合精度训练显存优化实战 - 第2节
2. FP16 vs FP32
咱们直接切入正题。FP32(单精度浮点数)和 FP16(半精度浮点数),说白了就是两种不同的数字“容器”。
FP32 用了 32 个比特位来存一个数,FP16 只用了 16 个。你想想看,容量差了一倍,能装的东西自然不一样。
FP16 的“内存地图”
FP16 的 16 个比特是这样分配的:
- 1 位符号位:决定正负
- 5 位指数位:决定范围
- 10 位尾数位:决定精度
而 FP32 呢?1 位符号位,8 位指数位,23 位尾数位。
我刚开始接触这个的时候,总觉得差几个比特能有多大区别?直到我在项目里亲眼看到 loss 直接变成 NaN,才明白这玩意儿有多敏感。
表示范围对比
| 类型 | 最小值(正数) | 最大值 | 最小精度 |
|---|---|---|---|
| FP32 | 约 1.4 × 10⁻⁴⁵ | 约 3.4 × 10³⁸ | 约 1.2 × 10⁻⁷ |
| FP16 | 约 5.96 × 10⁻⁸ | 约 65504 | 约 9.8 × 10⁻⁴ |
看到没?FP16 的最大值才 65504。这在深度学习里意味着什么?
举个例子。你的梯度如果稍微大一点,比如某个权重更新量是 70000,FP16 直接给你溢出成无穷大。嗯,这就是经典的“梯度爆炸”在 FP16 下的表现。
精度问题:你丢掉的不是一点点
FP16 的尾数只有 10 位,精度大约是 0.00098。也就是说,两个数相差小于 0.001 的时候,FP16 就分不清谁是谁了。
我遇到过最坑的一次,是训练一个 NLP 模型。模型收敛到后期,loss 在 0.01 附近震荡。FP16 下,loss 直接卡住不动了——因为 loss 的变化量小于 FP16 的精度,被“吞”掉了。
核心结论:FP16 的精度损失,在模型训练后期尤其致命。梯度太小,直接变成 0,参数再也不更新了。
FP16 的数值溢出问题
数值溢出分两种:上溢出和下溢出。
上溢出(Overflow)
就是数值太大,超过了 65504。常见于:
- 激活函数输出值过大(比如 ReLU 后接大权重)
- 梯度累积过程中数值膨胀
- Batch Normalization 的统计量计算
上溢出的结果就是 NaN。一旦出现 NaN,整个训练就崩了。我记得有一次跑 ResNet-50,FP16 下训练到第 3 个 epoch 就 NaN 了,排查了半天才发现是某个卷积层的权重初始化太大了。
下溢出(Underflow)
数值太小,小于 5.96 × 10⁻⁸,直接变成 0。
这个更隐蔽。你训练了半天,loss 看起来在下降,但参数其实根本没更新。因为梯度被下溢出了。
我做过一个实验:用纯 FP16 训练一个 50 层的网络,前向传播没问题,反向传播的梯度有 60% 以上都变成了 0。模型根本学不动。
避坑指南:我曾经在训练 GAN 时,生成器的梯度特别小,FP16 下几乎全被下溢出。后来换成混合精度,才让生成器正常更新。
为什么需要混合精度而非纯 FP16?
看到这里,你可能会问:FP16 问题这么多,为什么还要用它?
答案很简单:快和省。
FP16 的显存占用是 FP32 的一半,计算速度在支持 Tensor Core 的 GPU 上能快 2-4 倍。但代价就是精度和范围不够用。
那怎么办?
混合精度训练的思路是:各取所长。
混合精度的核心策略
- 权重用 FP32 存储:保证精度不丢失
- 前向和反向用 FP16 计算:享受速度优势
- Loss 缩放(Loss Scaling):防止梯度下溢出
- 主权重更新用 FP32:避免累积误差
说白了,就是“计算用半精度,存储用全精度”。
我个人的习惯是:所有可训练参数都保留一份 FP32 的副本。每次更新时,先把 FP32 的权重转成 FP16 做前向,算完梯度后再转回 FP32 做更新。
小技巧:Loss Scaling 的初始值可以设成 1024。如果训练过程中出现 NaN,自动减半;如果连续 100 步都没问题,可以加倍。PyTorch 的 GradScaler 就是这么干的。
纯 FP16 为什么不行?
我直接给你列几个致命问题:
- 梯度消失:小梯度被下溢出,深层网络根本学不动
- 精度不足:模型收敛到后期,loss 震荡无法继续优化
- 数值不稳定:BatchNorm 的统计量在 FP16 下容易漂移
- 权重更新误差:多次小更新累积后,误差会越来越大
你想想看,一个 100 层的网络,每层都损失一点精度,累积到最后结果能靠谱吗?
混合精度的实际效果
我在实际项目中测试过:
| 方案 | 显存占用 | 训练速度 | 模型精度 |
|---|---|---|---|
| 纯 FP32 | 100% | 1x | 基准 |
| 纯 FP16 | 约 55% | 约 2.5x | 下降 0.5-2% |
| 混合精度 | 约 60% | 约 2.2x | 几乎无损失 |
看到了吧?混合精度在几乎不损失精度的情况下,显存省了 40%,速度快了一倍多。这才是我们想要的。
一句话总结:纯 FP16 是“省着用但容易翻车”,混合精度是“聪明地用,又快又稳”。
知识体系总览
下面这张图,帮你理清本章的核心逻辑:
这张图把本章的核心逻辑串起来了。FP32 和 FP16 各有优劣,混合精度就是那个“聪明的中间人”。