量化基础:从FP32到INT8,精度损失从哪来?

各位同学,欢迎来到《嵌入式推理精度与速度平衡术》的第二讲。

上一章我们聊了为什么要在嵌入式设备上做推理优化。今天,咱们直接切入核心——量化。说白了,就是把模型里那些又大又沉的FP32参数,换成更轻便的INT8。但问题来了:精度损失到底从哪冒出来的?

1. 为什么非要从FP32换成INT8?

先问个问题:你手头的嵌入式芯片,跑一个ResNet-50,FP32推理需要多少算力?大概7.7 GFLOPs。换成INT8呢?直接降到原来的1/4左右。速度翻倍,功耗减半,这买卖谁不做?

但代价呢?精度。我见过太多项目,一上来就无脑量化,结果模型直接崩了。嗯,这里要注意:量化不是万能药,它是有代价的。

核心矛盾:FP32有约4.3×10^9个离散值,INT8只有256个。用256个值去近似40亿个值,精度损失几乎是必然的。

2. 量化映射:把大象塞进冰箱

量化的本质,就是做一个映射。把FP32的数值范围,映射到INT8的0~255(或者-128~127)。

最常见的映射方式叫均匀量化。公式很简单:

q = round((r - min) / scale)
scale = (max - min) / 255

其中r是原始FP32值,q是量化后的INT8值。scale是缩放因子,min是截断下限。

举个例子:假设你的权重范围是[-1.0, 1.0],那么scale = 2.0/255 ≈ 0.00784。0.0映射到128,1.0映射到255,-1.0映射到0。

听起来很简单对吧?但坑就在这。

3. 精度损失的三大元凶

我在项目中遇到过无数次精度下降的问题。总结下来,罪魁祸首就三个:

3.1 截断误差

这是最直接的损失来源。你想想看,FP32的数值范围可能是[-10.0, 10.0],但大部分权重集中在[-1.0, 1.0]。如果你把整个范围都映射到INT8,那大部分有效信息都被压缩到几个离散值里了。

我曾经做过一个语音识别模型,量化后准确率从95%掉到82%。查了半天,发现是权重中有几个异常大的值,把整个量化范围撑大了。解决办法?截断。把范围限制在[-3.0, 3.0],超出部分直接截掉。准确率回到了93%。

避坑指南:我曾经因为没做截断优化,导致一个目标检测模型的mAP掉了15个点。后来用KL散度找到最优截断阈值,才把损失控制在2%以内。

3.2 舍入误差

量化公式里有个round操作。0.3量化后是0,0.7量化后也是0?不对,0.7量化后是1。但0.3和0.7之间的差异,在反量化后就完全丢失了。

这种误差在单次计算中很小,但经过几十层网络累积,就会变得不可忽视。我习惯用随机舍入来缓解这个问题——不是简单的四舍五入,而是以概率决定向上还是向下舍入。比如0.3有30%的概率变成1,70%的概率变成0。这样在统计意义上,期望值更接近原始值。

3.3 梯度误差(训练后量化)

如果你做的是训练后量化(PTQ),那梯度误差其实不直接存在。但如果你做量化感知训练(QAT),梯度在反向传播时会被量化操作截断,导致梯度不准确。

说白了,就是量化操作不可导。怎么办?用直通估计器(STE)——前向传播时做量化,反向传播时假装量化不存在,直接把梯度传过去。这招虽然粗糙,但实践中效果不错。

4. 量化误差的数学分析

咱们来点硬核的。量化误差可以分解为两部分:

误差类型 来源 典型大小 影响
截断误差 超出量化范围的值被截断 可高达50% 严重,需优化截断阈值
舍入误差 浮点值无法精确映射到整数 约0.5个量化步长 中等,可通过随机舍入缓解
累积误差 多层网络误差叠加 随层数增长 严重,需逐层校准

我个人习惯用信噪比(SNR)来量化误差。对于均匀量化,SNR ≈ 6.02N + 4.77 dB,其中N是量化位数。INT8的理论SNR约53dB,FP32约192dB。差了将近140dB,这就是精度损失的数学根源。

5. 实战:如何量化一个卷积层

光说不练假把式。咱们看一个实际例子:

# 假设输入x是FP32,范围[-2.0, 2.0]
# 权重w是FP32,范围[-0.5, 0.5]

# 1. 计算量化参数
x_scale = (2.0 - (-2.0)) / 255 = 0.0157
x_zero_point = 128  # 0映射到128

w_scale = (0.5 - (-0.5)) / 255 = 0.00392
w_zero_point = 128

# 2. 量化
x_int = round(x / x_scale + x_zero_point)
w_int = round(w / w_scale + w_zero_point)

# 3. INT8卷积
y_int = conv2d(x_int, w_int)  # 输出是INT32

# 4. 反量化
y_fp32 = (y_int - y_zero_point) * y_scale

注意:卷积的输入和输出量化参数可能不同。我建议在每一层都单独校准量化参数,而不是全局统一。

小技巧:如果你用PyTorch,可以直接用torch.quantization.QuantStub和DeQuantStub。但别完全依赖自动量化,手动校准往往效果更好。

6. 量化后的精度恢复策略

精度损失了怎么办?别慌,我有三板斧:

  1. 逐层校准:用一小批校准数据,逐层调整量化参数。我习惯用100-500张图片做校准。
  2. 量化感知训练:在训练过程中模拟量化,让模型自己适应。虽然训练时间翻倍,但精度损失可以控制在1%以内。
  3. 混合精度量化:敏感层用FP16或INT16,不敏感层用INT8。我做过一个实验,只把前两层和最后一层保留FP16,精度就恢复了90%。

最后说一句:量化不是终点,是起点。真正的高手,是在精度和速度之间找到那个微妙的平衡点。下一章,咱们聊聊如何选择量化方案——是选训练后量化,还是量化感知训练?

记住:没有最好的量化方案,只有最适合你场景的方案。