3. 模型量化原理:从FP32到INT4,一场精度与速度的博弈

量化,说白了就是把模型从「高精度浮点」搬到「低精度整数」的过程。你想想看,FP32 的模型跑在 NPU 上,就像开着一辆满载的卡车——稳是稳,但跑不快。而 INT8 甚至 INT4,就像把货卸下来换成了小跑车,速度上去了,但得小心别把东西颠碎了。

我在项目中遇到过不少团队,一上来就追求 INT4 量化,结果精度掉得一塌糊涂。后来我总结了一个原则:能用 INT8 就别碰 INT4,除非你实在被带宽逼急了。下面我们一步步拆解这些量化方案。

3.1 量化本质:把连续值映射到离散值

量化的数学本质很简单:

Q = round( (x - zero_point) / scale )

其中 x 是原始浮点值,scale 是缩放因子,zero_point 是零点偏移。反量化就是逆过程:

x_approx = Q * scale + zero_point

嗯,这里要注意:round 操作会引入信息损失。你想想看,FP32 有约 4.2e9 个可表示的值,INT8 只有 256 个。这就像把整个图书馆的书塞进一个小书架——总得扔掉一些。

核心观点: 量化不是魔法,它是在精度和效率之间做 trade-off。我个人习惯先评估模型对量化的敏感度,再决定用哪种方案。

3.2 FP32、FP16、INT8、INT4 量化原理

3.2.1 FP32 → FP16:最温和的量化

FP16 其实不算严格意义上的量化,它只是把浮点数的位宽砍了一半。FP32 有 8 位指数 + 23 位尾数,FP16 是 5 位指数 + 10 位尾数。好处是:

  • 显存/带宽减半
  • 计算吞吐翻倍(很多 NPU 对 FP16 有专门的加速单元)
  • 精度损失极小,通常 < 0.1%

我曾经在一个语音识别模型上试过,FP16 直接跑,WER(词错误率)只涨了 0.05%。这种方案我建议作为量化的「保底选项」——如果 INT8 搞不定,至少 FP16 能让你先跑起来。

3.2.2 FP32 → INT8:最主流的量化方案

INT8 是目前工业界的主力。为什么?因为大部分 NPU 对 INT8 的计算效率最高,功耗也最低。

INT8 量化需要确定两个参数:scale 和 zero_point。这里就引出了对称量化和非对称量化的区别。

我的经验: 对于权重,我通常用对称量化;对于激活值,我倾向于非对称量化。因为权重的分布通常关于 0 对称,而激活值(比如 ReLU 的输出)全是非负的。

3.2.3 INT4 量化:极限压缩

INT4 只有 16 个可表示的值。说实话,这玩意儿挺难伺候的。我在一个 NLP 模型上试过 INT4 量化,精度直接掉了 5 个点,后来用了混合精度(敏感层保留 INT8)才勉强拉回来。

INT4 的适用场景:

  • 对带宽极度敏感的端侧模型
  • 模型本身冗余度很高(比如大语言模型)
  • 可以接受一定精度损失的场景

3.3 对称量化 vs 非对称量化

这两种方案的区别,说白了就是零点要不要偏移。

特性 对称量化 非对称量化
zero_point 固定为 0 可偏移
表示范围 [-127, 127](INT8) [0, 255](INT8)
计算复杂度 低(不需要零点修正) 高(需要零点修正)
适用场景 权重、分布对称的激活值 分布不对称的激活值(如 ReLU)

我个人习惯:权重用对称,激活用非对称。为什么?因为权重的分布通常比较规整,对称量化省去了零点修正的计算开销。而激活值(尤其是经过 ReLU 的)全是正数,非对称量化能更好地利用 INT8 的 256 个 bin。

避坑指南: 我曾经在一个模型上用了对称量化处理 ReLU 的输出,结果精度掉了 2%。后来换成非对称量化,精度直接回来了。原因是 ReLU 的输出全是非负的,对称量化把一半的 bin 浪费在了负数区域。

3.4 量化感知训练(QAT) vs 训练后量化(PTQ)

这是量化领域最经典的选择题。我直接说结论:能 PTQ 就别 QAT,除非 PTQ 搞不定。

3.4.1 训练后量化(PTQ)

PTQ 就是模型训练完了,直接拿权重和激活值的统计信息来算 scale 和 zero_point。流程很简单:

  1. 收集校准数据(几百到几千张图片/样本)
  2. 跑一遍推理,记录每层的激活值分布
  3. 根据分布计算量化参数
  4. 把模型转成 INT8

PTQ 的优点:

  • 不需要重新训练,省时省力
  • 对大部分模型效果不错
  • 工具链成熟(TensorRT、ONNX Runtime 都支持)

缺点:

  • 对分布敏感的模型(比如小模型、稀疏模型)效果差
  • 无法补偿量化带来的误差
我的建议: PTQ 是量化的第一步。我通常先用 PTQ 跑一遍,如果精度损失在 1% 以内,就直接用了。如果超过 1%,再考虑 QAT。

3.4.2 量化感知训练(QAT)

QAT 是在训练过程中模拟量化操作,让模型学会「适应」量化误差。核心思想是:在 forward 时插入 fake quantization 节点,backward 时用 STE(Straight-Through Estimator)近似梯度。

# 伪代码示例:QAT 中的 fake quantization
def fake_quantize(x, scale, zero_point):
    # 量化
    q = torch.round(x / scale + zero_point)
    # 反量化(模拟量化后的精度损失)
    x_hat = (q - zero_point) * scale
    # STE:梯度直接通过
    return x_hat.detach() + x - x.detach()

QAT 的流程:

  1. 加载预训练模型
  2. 插入 fake quantization 节点
  3. 用较小的学习率 fine-tune 几个 epoch
  4. 导出 INT8 模型

QAT 的优点:

  • 精度通常比 PTQ 高 1-3%
  • 对敏感模型特别有效

缺点:

  • 需要重新训练,耗时
  • 调参复杂(学习率、量化起始 epoch 都要调)
避坑指南: 我曾经在一个目标检测模型上做 QAT,结果精度反而比 PTQ 还差。后来发现是学习率设太大了,模型在量化模拟中「过拟合」到了校准集上。建议 QAT 的学习率设为原训练学习率的 1/10 到 1/100。

3.5 知识体系总览

下面这张图总结了量化方案的选择逻辑:

模型量化方案选择流程图 原始 FP32 模型 带宽/延迟 是否敏感? 否 FP16 量化 是 精度要求 是否严格? 否 是 INT8 PTQ 精度达标? 是 部署 INT8 否 INT8 QAT INT4 量化 起始 决策 成功部署 需要调优

3.6 实战建议

最后,我总结几条实战经验:

  1. 先做 PTQ:90% 的模型 PTQ 就够了,别一上来就 QAT 浪费时间。
  2. 校准集要选对:校准集要覆盖真实场景的分布。我曾经用 ImageNet 的校准集量化了一个医疗模型,结果精度掉了 5%,换成医疗数据后就好了。
  3. 混合精度是王道:如果 INT8 搞不定,别硬上 INT4。试试敏感层保留 FP16,其他层用 INT8。我在多个项目上验证过,混合精度比纯 INT4 效果好得多。
  4. 量化后的模型要验证:不要只看 top-1 accuracy,还要看具体场景的指标。比如检测模型要看 mAP,语音模型要看 WER。
一句话总结: 量化不是越低位越好,而是在精度、速度、带宽之间找到最适合你的那个平衡点。FP16 保底,INT8 主力,INT4 留给真正需要极限压缩的场景。

好了,这一章的内容就到这里。量化是个实践性很强的工作,建议你拿一个实际模型跑一遍 PTQ 和 QAT,感受一下精度的变化。纸上得来终觉浅,绝知此事要躬行。