2、模型量化基础:从FP32到INT8,量化原理与精度损失分析

好,咱们开始聊量化。说实话,量化这个话题在模型部署里太重要了。我刚开始做部署那会儿,模型跑在GPU上动不动就显存爆了,后来发现量化能直接把模型体积砍到原来的四分之一,推理速度还能翻倍。嗯,今天咱们就把量化的底裤扒干净。

2.1 为什么要量化?FP32有什么问题?

先看一个简单的数字。一个FP32的浮点数,占4个字节。一个INT8整数,只占1个字节。你想想看,一个模型几亿个参数,光存参数就得几个GB。我有个项目,模型1.2GB,手机端根本跑不动。量化到INT8后,直接降到300MB,速度还快了3倍。

FP32的问题说白了就两点:

  • 存储开销大:每个参数32位,显存和内存都扛不住
  • 计算速度慢:浮点运算比整数运算慢得多,尤其在移动端和边缘设备上

但量化不是免费的午餐。精度会掉,关键看你怎么平衡。我个人习惯是,先跑一遍FP32的baseline,再量化,对比精度差异。如果掉点超过1%,就得想办法了。

2.2 量化原理:映射与缩放

量化的核心思想很简单:用更少的比特数表示原来的数值范围

比如一个FP32的权重范围是[-1.0, 1.0],我们要把它映射到INT8的范围[-128, 127]。怎么做?需要一个缩放因子(scale)和零点(zero point)。

公式长这样:

量化值 = round(原始值 / scale) + zero_point

反过来,反量化:

原始值 ≈ (量化值 - zero_point) * scale

其中scale = (原始最大值 - 原始最小值) / (量化最大值 - 量化最小值)。

举个例子:

原始范围: [-1.0, 1.0]
量化范围: [-128, 127]
scale = (1.0 - (-1.0)) / (127 - (-128)) = 2.0 / 255 ≈ 0.00784
zero_point = round(-128 - (-1.0 / 0.00784)) ≈ 0

原始值 0.5 → 量化值 = round(0.5 / 0.00784) + 0 ≈ 64
量化值 64 → 原始值 ≈ (64 - 0) * 0.00784 ≈ 0.5018

你看,0.5变成了0.5018,这就是量化误差。误差不可避免,但我们可以控制。

关键点:量化不是简单的截断,而是带缩放和偏移的映射。scale和zero_point的选择直接影响精度。

2.3 对称量化 vs 非对称量化

这里有个选择:对称还是非对称?

类型 特点 适用场景
对称量化 zero_point = 0,量化范围对称 权重(通常分布对称)
非对称量化 zero_point ≠ 0,可偏移 激活值(分布可能偏斜)

我在项目中遇到过一个问题:用对称量化量化激活值,结果精度掉了2%。后来换成非对称量化,精度只掉了0.3%。为什么?因为激活值经过ReLU后全是非负数,对称量化浪费了一半的表示范围。

我的建议:权重用对称量化,激活值用非对称量化。这是业界最常用的组合。

2.4 量化粒度:逐层 vs 逐通道

量化粒度也是个坑。你想想看,一个卷积层有多个输出通道,每个通道的数值范围可能差很多。如果整个层用一个scale,那范围大的通道会压缩小通道的精度。

  • 逐层量化:整个层共用一个scale和zero_point。简单,但精度损失大。
  • 逐通道量化:每个通道有自己的scale和zero_point。精度高,但计算复杂一点。

我曾经在一个图像分类模型上试过,逐层量化精度掉了1.5%,逐通道量化只掉了0.2%。所以,能逐通道就别逐层,除非你实在受限于硬件支持。

2.5 精度损失分析:到底丢了多少?

量化精度损失主要来自三个方面:

  1. 截断误差:超出量化范围的数值被截断
  2. 舍入误差:浮点到整数的四舍五入
  3. 累积误差:多层量化误差叠加

怎么评估?我一般用两个指标:

  • 相对误差:|量化值 - 原始值| / |原始值|
  • 余弦相似度:量化前后输出向量的相似度

举个例子,一个ResNet-50模型:

FP32 Top-1准确率: 76.1%
INT8逐层量化: 74.5% (掉1.6%)
INT8逐通道量化: 75.8% (掉0.3%)

你看,逐通道量化几乎无损。但要注意,小模型更容易掉精度。我做过一个MobileNet的量化,逐通道也掉了0.8%。

避坑指南:我曾经在量化一个BERT模型时,直接用了默认的逐层量化,结果下游任务F1分数掉了5个点。后来改成逐通道+混合精度(部分层保留FP16),才把损失控制在0.5%以内。所以,别盲目量化,先分析每层的敏感度

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

这两个是主流方案,区别很大:

方法 流程 精度 成本
PTQ 训练完再量化,不需要重新训练 一般,可能掉1-2% 低,几分钟搞定
QAT 训练过程中模拟量化,让模型适应 高,几乎不掉点 高,需要重新训练

我个人习惯是:先试PTQ。如果精度满足要求,那就直接用。如果掉点太多,再上QAT。毕竟QAT要重新训练,时间成本高。

QAT的原理是在前向传播时插入伪量化节点(fake quant),模拟量化误差,让模型学会补偿。代码大概长这样:

# 伪量化操作
def fake_quantize(x, scale, zero_point, qmin, qmax):
    x_q = torch.round(x / scale + zero_point)
    x_q = torch.clamp(x_q, qmin, qmax)
    x_deq = (x_q - zero_point) * scale
    return x_deq

注意,反向传播时用的是直通估计器(STE),把量化操作的梯度近似为1,这样才能训练。

2.7 实战建议:量化流程总结

好,说了这么多,给个我常用的量化流程:

  1. 跑FP32 baseline:记录准确率、延迟、显存占用
  2. PTQ快速尝试:用校准集计算scale和zero_point,评估精度
  3. 分析敏感层:逐层量化,找出掉点严重的层
  4. 混合精度:敏感层保留FP16或FP32,其他层用INT8
  5. 如果还不行:上QAT,重新训练几个epoch
  6. 验证:在测试集上对比FP32和量化后的结果

小技巧:校准集最好用训练集的子集,1000张图片左右就够了。别用测试集做校准,那叫数据泄露。

嗯,量化这块内容不少,但核心就这些。说白了,就是用精度换速度和存储,关键是怎么把精度损失降到最低。下一章咱们聊具体的量化工具和框架,比如TensorRT、ONNX Runtime这些,到时候手把手教你实操。