混合精度训练显存优化实战 - 第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 倍。但代价就是精度和范围不够用。

那怎么办?

混合精度训练的思路是:各取所长。

混合精度的核心策略

  1. 权重用 FP32 存储:保证精度不丢失
  2. 前向和反向用 FP16 计算:享受速度优势
  3. Loss 缩放(Loss Scaling):防止梯度下溢出
  4. 主权重更新用 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 范围小 · 精度低 · 速度快 混合精度 取长补短 · 又快又稳 上溢出 / 下溢出 精度不足 / 梯度消失 Loss Scaling + FP32主权重 解决方案:混合精度训练 FP16计算 + FP32存储 + Loss Scaling + 动态缩放

这张图把本章的核心逻辑串起来了。FP32 和 FP16 各有优劣,混合精度就是那个“聪明的中间人”。