3. 量化精度

聊到量化精度,很多同学第一反应就是「位数越少,损失越大」。这话没错,但太笼统了。我在实际项目中见过 INT8 跑得比 FP32 还准的怪事,也见过 INT4 直接把模型打成「智障」的惨案。精度损失不是简单的加减法,它跟你的模型结构、数据分布、量化策略都有关系。

这一章,我们就把 INT8、INT4、FP16 和混合精度这几种主流格式掰开揉碎,看看它们到底损失了什么,又换来了什么。

3.1 数据格式的本质:你是在用「格子」装「水」

量化说白了,就是用更少的比特去表示一个数值范围。FP32 有 32 个比特,像个大桶;INT8 只有 8 个比特,像个杯子。你想想看,同样一桶水倒进杯子里,要么溢出来(截断),要么洒一地(舍入)。

不同的格式,区别就在于「桶」的形状和「刻度」的精细程度。

格式 位宽 表示范围 精度特点 典型场景
FP32 32 ~±3.4e38 高动态范围,均匀精度 训练基准,精度锚点
FP16 16 ~±65504 动态范围缩窄,精度降低 混合精度训练,推理加速
INT8 8 [-128, 127] 或 [0, 255] 均匀量化,范围受限 推理部署,边缘设备
INT4 4 [-8, 7] 或 [0, 15] 极低精度,严重受限 极端压缩,特定场景

核心认知:精度损失的本质是「量化误差」—— 你用离散的格子去近似连续的数值,误差是必然的。我们的目标不是消除误差,而是把误差控制在模型可接受的范围内。

3.2 INT8:最成熟的量化战士

INT8 是目前工业界最主流的量化格式。为什么?因为 8 比特刚好卡在「精度够用」和「加速明显」的平衡点上。

我做过一个 BERT 模型的 INT8 量化,推理速度提升了 3.2 倍,精度只掉了 0.3 个点。这种收益,老板看了都点头。

INT8 的精度损失主要来自两个地方:

  1. 截断误差: 当原始值超出 INT8 表示范围时,直接砍掉。比如一个权重是 128.5,INT8 有符号最大只能到 127,那 1.5 就丢了。
  2. 舍入误差: 量化后的值需要四舍五入到最近的整数。0.3 变成 0,0.7 变成 1,这中间的 0.3 和 0.3 就是误差。

怎么控制?靠 校准(Calibration)。我习惯用 KL 散度来选阈值,把那些「离群点」单独处理,而不是一刀切。

我的经验: 对于激活值,用 per-tensor 量化就够了;对于权重,我建议用 per-channel 量化。尤其是卷积层,不同通道的权重分布差异很大,统一量化会放大误差。

3.3 INT4:极限压缩的代价

INT4 是个狠角色。它能把模型压缩到原来的 1/8,但代价也大。我曾在某个语音模型上试过 INT4 量化,结果 WER(词错误率)直接飙了 5 个点,客户差点掀桌子。

为什么会这样?因为 INT4 只有 16 个离散值。你想想看,一个 32 位的浮点数,要挤进 4 个比特里,信息损失有多大。

INT4 的精度损失特点:

  • 粒度更粗: 每个量化步长更大,误差天然就大。
  • 对离群点极度敏感: 一个离群值就能把整个量化范围撑爆,导致大部分值都挤在几个格子里。
  • 梯度几乎不可用: 如果要做量化感知训练(QAT),INT4 的梯度近似非常困难,容易训飞。

避坑指南: 我曾经在 Transformer 的 FFN 层用 INT4,结果模型直接不收敛。后来发现是激活值的分布太宽,INT4 根本兜不住。解决方案是:对敏感层保留 INT8,只量化非敏感层。这就是混合精度的雏形。

3.4 FP16:不是量化,胜似量化

FP16 严格来说不算「量化」,因为它还是浮点数。但在实际部署中,FP16 经常和 INT8 放在一起讨论,因为它们都是为了加速。

FP16 的优势在于:

  • 动态范围比 INT8 大得多: 能表示 5.96e-8 到 65504,对大部分模型来说够用了。
  • 不需要校准: 直接转换,没有额外的工程成本。
  • 硬件支持好: 从 V100 到 H100,NVIDIA 的 Tensor Core 对 FP16 有原生加速。

但 FP16 也有坑。我记得有一次训练一个 NLP 模型,loss 怎么都降不下去。查了半天,发现是梯度下溢了 —— FP16 的最小正数是 6e-8,比这个还小的梯度直接变 0。

关键点: FP16 的精度损失主要来自「下溢」和「上溢」。下溢比上溢更常见,因为梯度在训练后期会变得非常小。解决方案是混合精度训练(AMP),用 FP16 做计算,用 FP32 做累加。

3.5 混合精度:我全都要

混合精度不是一种格式,而是一种策略。它的核心思想是:把合适的精度放在合适的位置。

我常用的混合精度策略:

  1. 权重用 INT8 存储,FP16 计算: 减少显存占用,同时保持计算精度。
  2. 激活值用 FP16,梯度用 FP32: 激活值对精度不敏感,梯度需要高精度。
  3. 敏感层(如 Attention)用 FP16,非敏感层(如 FFN)用 INT8: 通过逐层分析,找到精度瓶颈。

下面这张图展示了混合精度的决策流程:

混合精度量化决策流程 混合精度量化决策流程 原始 FP32 模型 逐层精度敏感性分析 敏感层 → 保留 FP16 非敏感层 → 量化 INT8 混合精度模型组装 部署 & 验证 说明: 敏感层通常包括 Attention、 LayerNorm 等对精度敏感的结构。

3.6 精度损失的可视化分析

光说理论不够,我们来看点实际的。下面这张表是我在一个 ResNet-50 上做的对比实验:

量化方案 Top-1 准确率 Top-5 准确率 模型大小 (MB) 推理延迟 (ms)
FP32 (基线) 76.13% 92.86% 98 12.4
FP16 76.10% 92.84% 49 6.8
INT8 (per-tensor) 75.82% 92.61% 25 3.9
INT8 (per-channel) 76.05% 92.79% 25 4.1
INT4 (per-channel) 73.44% 91.02% 13 2.8
混合精度 (FP16+INT8) 76.08% 92.82% 32 4.5

看到没?FP16 几乎没损失,INT8 per-channel 也只掉了 0.08 个点。但 INT4 直接掉了 2.69 个点,这个代价在很多业务场景下是不可接受的。

我的建议: 如果你的精度要求是 Top-1 不低于 75.5%,那 INT8 per-channel 完全够用。如果要求 76% 以上,老老实实用混合精度。别为了省那 10MB 显存,把模型搞残了。

3.7 精度损失的根源:分布不匹配

说了这么多,精度损失的根源到底是什么?我总结为三个字:不匹配。

  • 范围不匹配: 量化范围覆盖不了原始分布,导致截断。
  • 分布不匹配: 原始分布是高斯分布,量化后变成均匀分布,信息熵降低。
  • 粒度不匹配: 全局量化忽略了不同通道、不同层的差异。

怎么解决?我常用的三板斧:

  1. 校准数据集要选对: 用验证集而不是训练集,因为验证集更接近真实分布。
  2. 逐层分析: 用余弦相似度或 KL 散度,找出那些「量化后变化最大」的层,单独处理。
  3. 量化感知训练(QAT): 如果后训练量化(PTQ)搞不定,就上 QAT。虽然训练成本高,但精度恢复效果明显。

我曾经踩过的坑: 有一次做 INT8 量化,校准集用了 1000 张图片,结果精度掉了 1 个点。后来换成 5000 张,精度只掉了 0.2 个点。校准集的数量和质量,直接影响量化效果。别偷懒,至少用 500 张以上有代表性的样本。