3. 模型量化原理

量化这个话题,说白了就是给模型「减减肥、降降温」。我在NPU部署一线摸爬滚打这些年,遇到最多的性能瓶颈就是模型太大、跑不动。而量化,往往是性价比最高的解决方案。

今天咱们就聊聊,怎么把FP32的模型变成INT8,以及这背后到底发生了什么。

为什么非要量化?

先看一组数据。一个FP32的权重参数占4个字节,INT8只占1个字节。你想想看,模型大小直接缩到四分之一。内存带宽压力小了,计算单元也能一次处理更多数据。

我曾在某款边缘NPU上做过测试:一个ResNet-50模型,FP32推理延迟是120ms,量化到INT8后直接降到35ms。速度提升了3倍多,而精度损失不到0.5%。

当然,不是所有模型都这么友好。但大部分场景下,量化是值得尝试的。

FP32到INT8的转换原理

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

FP32的数值范围大约是 ±3.4×10³⁸,而INT8只有 -128 到 127(有符号)或 0 到 255(无符号)。我们需要找到一个映射关系,把FP32的值「塞」进INT8的桶里。

这个映射关系由两个参数决定:缩放因子(scale)和零点(zero point)。

公式长这样:

INT8值 = round(FP32值 / scale) + zero_point

反过来,反量化就是:

FP32值 ≈ (INT8值 - zero_point) × scale

scale决定了每个INT8步长代表多大的FP32范围。zero_point则负责对齐零点。

对称量化 vs 非对称量化

这里有个关键选择:要不要让FP32的0和INT8的0对齐?

对称量化

对称量化强制zero_point = 0。也就是说,FP32的0映射到INT8的0。

好处是计算简单,硬件实现起来省资源。很多NPU对对称量化有专门的加速指令。

坏处呢?如果数据分布偏向正数或负数,对称量化会浪费一部分表示范围。比如ReLU的输出全是非负数,用对称量化的话,负数那半边就白白浪费了。

我在项目中遇到过这种情况:一个MobileNet模型,用对称量化后精度掉了2%。换成非对称量化,精度损失只有0.3%。

非对称量化

非对称量化允许zero_point不为0。这样就能灵活地适配数据的实际分布。

公式变成:

scale = (max_val - min_val) / 255
zero_point = round(-min_val / scale)

非对称量化能更充分地利用INT8的表示范围。但代价是计算稍微复杂一点,硬件实现需要多处理一个偏移量。

对比项 对称量化 非对称量化
zero_point 固定为0 可调整
计算复杂度 低 中
硬件友好度 高 中
精度表现 数据对称时好 通用性好
典型应用 权重、部分激活 激活值、偏置

我个人习惯:权重用对称量化,激活值用非对称量化。这样既保证了计算效率,又兼顾了精度。

量化感知训练(QAT)与训练后量化(PTQ)

搞清楚了怎么量化,下一个问题就是:什么时候做量化?

训练后量化(PTQ)

PTQ是最直接的方法。模型训练完了,拿一批校准数据跑一遍,统计出每层激活值的min/max,然后直接量化。

优点很明显:不需要重新训练,省时省力。

缺点呢?精度损失可能比较大。尤其是对量化敏感的小模型,或者有异常值的数据分布。

我曾经在一个语义分割模型上试PTQ,mIoU直接从72%掉到65%。嗯,这种场景下PTQ就不太够用了。

量化感知训练(QAT)

QAT的思路是在训练过程中模拟量化效果。前向传播时插入伪量化节点(fake quantize),把权重和激活值先量化再反量化,让模型自己去适应量化带来的误差。

反向传播时,使用直通估计器(STE)来近似量化函数的梯度。说白了就是:前向走量化,反向走浮点。

代码实现大致长这样:

# 伪代码示例:QAT中的伪量化操作
def fake_quantize(x, scale, zero_point, bit_width=8):
    # 量化
    x_int = torch.round(x / scale) + zero_point
    x_int = torch.clamp(x_int, 0, 2**bit_width - 1)
    # 反量化(模拟量化误差)
    x_fp = (x_int - zero_point) * scale
    return x_fp

QAT的精度通常比PTQ好很多。还是刚才那个语义分割模型,用QAT后mIoU只掉了1.2%。

但代价是训练时间变长,而且需要修改训练代码。

核心结论:

  • 大模型、对精度不敏感的场景 → 用PTQ,省事
  • 小模型、精度要求高的场景 → 用QAT,值得
  • 实在拿不准 → 先试PTQ,精度不够再上QAT

量化误差的来源

量化不是无损的。误差主要来自三个方面:

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

我见过一个案例:某模型在FP32下精度95%,PTQ后掉到88%。排查发现是某一层的激活值分布特别宽,scale设得太大,导致大部分值都被量化到同一个INT8值上。后来改用逐通道量化(per-channel quantization),精度回到了93%。

避坑指南:

我曾经在部署一个YOLOv5模型时,发现量化后检测框全偏了。排查了半天,原来是某些层的激活值有极端异常值,导致scale被拉得特别大。解决办法是先用校准数据统计出合理的min/max,或者用百分位法(比如取99.9%分位)来截断异常值。

注意事项:

不是所有算子都支持INT8计算。比如某些NPU的Softmax、LayerNorm还是跑在FP32上。这意味着你的模型里会有混合精度的情况——部分层INT8,部分层FP32。这中间的格式转换也会带来额外的开销。

知识体系总览

下面这张图把量化相关的核心概念串起来了。建议你保存下来,做项目时对照着看。

模型量化知识体系 模型量化 量化方法 对称量化 非对称量化 量化时机 PTQ QAT 量化粒度 逐层量化 逐通道量化 关键决策点 精度要求高 → QAT + 非对称 + 逐通道 速度优先 → PTQ + 对称 + 逐层

量化这条路,说深不深,说浅不浅。关键是要理解每个选择背后的 trade-off。我个人建议,刚开始做量化时,先用 PTQ 跑通流程,感受一下精度损失的程度。如果精度达标,那就皆大欢喜。如果不达标,再上 QAT 也不迟。

记住一点:量化不是银弹。有些模型天生对量化不敏感,比如某些轻量级 Transformer。遇到这种情况,可能需要结合剪枝、蒸馏等其他手段一起上。