2. 量化精度与性能权衡:INT4/INT8/FP16 量化对比与误差分析

量化,说白了就是用更少的比特数去表示模型的权重和激活值。很多刚入行的朋友觉得量化就是简单地把 float32 转成 int8,跑起来就完事了。嗯,要是真这么简单,我就不用花一整章来讲它了。

我个人的习惯是,在动手量化之前,先想清楚一个问题:你到底要省什么? 省内存?省带宽?还是省计算时间?不同的目标,对应的量化策略完全不同。这一章,我们就来把这笔账算清楚。

2.1 三种主流量化格式:INT8、INT4、FP16

先看一张对比表,心里有个底:

格式 位宽 动态范围 精度特点 典型场景
FP16 16-bit ±65,504 接近 FP32,几乎无精度损失 GPU/部分 MCU 加速器
INT8 8-bit [-128, 127] 精度损失可控,推理加速明显 MCU 主力量化格式
INT4 4-bit [-8, 7] 精度损失大,需特殊校准 极端内存受限场景

你想想看,FP16 其实不算严格意义上的「量化」,它只是把 float 的指数和尾数砍了一半。在支持 FP16 的硬件上,它几乎是零成本的精度保留方案。但大多数 MCU 没有 FPU,或者 FPU 只支持 FP32,这时候 FP16 反而要软件模拟,得不偿失。

INT8 才是 MCU 上的主力。我做过一个项目,把 MobileNetV2 从 FP32 量化到 INT8,模型体积从 14MB 直接降到 3.5MB,推理速度提升了 3 倍,而 Top-1 准确率只掉了 0.7%。这个 trade-off,绝大多数应用都能接受。

INT4 就比较激进了。我曾经在一个只有 256KB Flash 的 Cortex-M4 上跑关键词唤醒模型,FP32 根本塞不下,INT8 勉强能放但没空间放中间缓存。最后硬着头皮上了 INT4,配合混合精度,才把模型塞进去。代价是精度掉了 2.3%,但至少能跑了。

核心结论: 对于大多数 MCU 项目,INT8 是性价比最高的选择。FP16 只在硬件原生支持时考虑,INT4 是最后的手段。

2.2 量化误差从哪来?

量化误差不是玄学,它有三个主要来源。我习惯把它们叫做「三把刀」:

  1. 截断误差: 数值超出量化范围,直接砍掉。比如 INT8 最大只能表示 127,权重里有个 128.3,对不起,截断成 127。
  2. 舍入误差: 数值在量化步长之间,四舍五入带来的偏差。比如步长是 0.5,实际值 1.2 量化成 1.0,误差 0.2。
  3. 异常值放大: 激活值里偶尔出现几个特别大的值,为了覆盖它们,量化步长被拉大,导致大部分正常值的精度反而变差。

为什么会这样?我举个例子你就明白了。假设你有一组激活值,99% 都在 [-1, 1] 之间,但有 1% 的值跑到了 10。如果用 INT8 量化,为了覆盖那个 10,步长必须设成 10/127 ≈ 0.0787。结果 [-1, 1] 区间只用了不到 26 个量化等级,精度损失惨重。这就是异常值在作祟。

避坑指南: 我曾经在量化一个语音模型时,发现某个通道的激活值偶尔会飙到 50 以上。排查了半天,发现是输入信号里有一个高频噪声。加了一个简单的低通滤波器后,异常值消失,量化精度直接回升了 1.5%。所以,量化前先看看你的数据分布,很有必要。

2.3 精度损失评估方法

量化完怎么知道模型还行不行?不能光靠感觉。我一般用下面这套流程:

2.3.1 逐层误差分析

不要只看最终准确率。我习惯把每一层的输出都 dump 出来,对比 FP32 和量化后的余弦相似度。如果某一层的相似度突然掉到 0.95 以下,那这一层就是瓶颈。

# 伪代码:逐层余弦相似度检查
for layer in model.layers:
    fp32_out = layer.forward(fp32_input)
    int8_out = layer.forward(int8_input)
    cos_sim = cosine_similarity(fp32_out.flatten(), int8_out.flatten())
    print(f"Layer {layer.name}: cos_sim = {cos_sim:.4f}")
    if cos_sim < 0.95:
        print("  ⚠️ 需要关注!")

2.3.2 最终指标对比

对于分类任务,看 Top-1/Top-5 准确率。对于回归任务,看 MSE 或 MAE。但要注意,量化后的精度波动在 ±0.5% 以内通常是可以接受的。如果掉了 1% 以上,就要回去找原因了。

评估指标 适用场景 可接受下降范围
Top-1 准确率 分类 ≤ 0.5%
MSE 回归 ≤ 1%
余弦相似度 逐层分析 ≥ 0.95
PSNR 图像/音频 ≥ 30dB

2.3.3 校准数据集的重要性

量化需要「校准数据集」来确定缩放因子。很多人随便拿几张图就跑,结果上线后精度崩了。我建议:校准集至少要覆盖真实场景中 80% 的数据分布。比如你做的是户外摄像头的人脸检测,校准集里就不能全是室内自拍。

警告: 校准集数量太少(比如 < 100 张),会导致量化参数过拟合到校准集上,在真实数据上精度反而更差。我一般至少用 500-1000 张图片做校准。

2.4 量化精度与性能的权衡决策树

下面这张图是我自己总结的决策流程,每次做量化项目我都会过一遍:

量化精度与性能权衡决策树 模型量化需求 内存极度受限? 内存相对充裕? 尝试 INT4 混合精度 尝试 INT8 量化 精度达标?→ 部署 精度达标?→ 部署 精度不达标?→ 回退 INT8 精度不达标?→ 回退 FP16 INT8 部署 FP16 部署 说明:每一步都要做精度评估,不达标就回退到更宽容的量化格式

2.5 实战中的量化策略选择

最后,我分享几个实战中的经验法则:

  • 先跑 INT8 校准,看精度。 如果掉点 < 0.5%,直接上 INT8,别折腾。
  • 如果 INT8 掉点 > 1%, 先检查是不是异常值问题。用逐层余弦相似度定位问题层,考虑对该层使用 FP16 混合精度。
  • 如果 Flash 实在不够, 才考虑 INT4。但要做好精度下降 2-5% 的心理准备。
  • FP16 在 MCU 上要谨慎。 除非你的芯片有硬件 FP16 支持,否则软件模拟 FP16 比 INT8 慢得多。
我的习惯: 每次量化完,我都会在开发板上跑一遍完整的推理,用示波器量一下实际功耗和推理时间。有时候理论分析和实际表现差距很大,比如某些 MCU 的 SIMD 指令对 INT8 有特殊优化,跑起来比预期快一倍。所以,纸上谈兵不如动手跑一跑。