第4章 模型量化原理:FP32到INT8的映射

量化这个话题,说实话我入行时也觉得挺玄乎的。

FP32好好的,为什么要转成INT8?精度会不会掉?

嗯,咱们今天就把这件事彻底讲透。

4.1 为什么需要量化?

先说说背景。FP32的模型,一个参数占4个字节。你想想看,一个ResNet-50大概25MB,放到手机上还行。但BEV模型呢?动辄几百MB甚至上GB。

嵌入式设备上,内存就那么点,带宽也有限。FP32推理一次,光数据传输就够呛。

INT8就不一样了,每个参数只占1个字节。体积直接缩到1/4,推理速度也能快2-4倍。我在项目中遇到过,一个原本跑不动的BEV模型,量化后居然在Jetson上实时跑了——虽然精度掉了1.5%,但客户能接受。

核心思想:用更少的比特数,近似表示原来的浮点数。说白了,就是「压缩+近似」。

4.2 FP32到INT8的映射原理

量化本质上是做一次线性变换。

FP32的取值范围是大约[-3.4e38, 3.4e38],INT8的范围是[-128, 127](有符号)或[0, 255](无符号)。

我们要把FP32的值映射到INT8的离散格点上。公式很简单:

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

反过来,反量化:

F = Q * scale + zero_point

这里有两个关键参数:

  • scale(缩放因子):决定每个INT8步长对应多少FP32值
  • zero_point(零点):FP32的0值对应哪个INT8值

我刚开始做量化时,总觉得zero_point是多余的。后来发现,没有它,ReLU后的全正数就没法精确表示0了。你想想看,如果0被映射成-128,那反量化回来就不是0了,误差会累积。

4.3 对称量化 vs 非对称量化

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

特性 对称量化 非对称量化
zero_point 固定为0 可调节
映射范围 [-127, 127] 映射到 [-max, max] [0, 255] 映射到 [min, max]
适用场景 权重(通常对称分布) 激活值(通常非对称,如ReLU后全正)
计算复杂度 低(无需处理zero_point偏移) 稍高(需要处理偏移)
精度损失 对非对称分布数据损失较大 能更好适配数据分布

对称量化:zero_point=0,INT8的[-127, 127]直接映射到FP32的[-max, max]。计算简单,硬件友好。我习惯对权重用对称量化,因为权重分布通常以0为中心。

非对称量化:zero_point可以是非零值,INT8的[0, 255]映射到FP32的[min, max]。能更精细地表示数据。激活值我通常用非对称量化,尤其是ReLU之后全是正数,用对称量化会浪费一半的表示范围。

我的经验:权重用对称量化,激活用非对称量化。这是业界最常用的组合。我曾经试过全对称量化,结果激活值精度掉了3%以上,后来改成非对称就好了。

4.4 量化参数的计算

scale和zero_point怎么算?

假设我们有一组FP32的权重,范围是[-2.0, 1.5]:

# 对称量化
max_abs = max(|-2.0|, |1.5|) = 2.0
scale = 2.0 / 127 ≈ 0.01575
zero_point = 0

# 非对称量化
min_val = -2.0
max_val = 1.5
scale = (1.5 - (-2.0)) / 255 ≈ 0.01373
zero_point = round(-(-2.0) / 0.01373) ≈ 146
# 注意:zero_point需要截断到[0, 255]

嗯,这里有个坑。zero_point计算出来可能超出范围,需要做clip。我遇到过zero_point算出来是-3的情况,直接导致量化结果全错。

注意:zero_point必须落在INT8的表示范围内。对于无符号INT8,范围是[0, 255];有符号INT8,范围是[-128, 127]。超出范围的值必须截断。

4.5 量化误差的来源

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

  1. 舍入误差:round操作会引入±0.5个步长的误差
  2. 截断误差:超出表示范围的值被截断,丢失信息
  3. 表示精度不足:INT8只有256个离散值,无法精确表示所有FP32值

我做过一个实验:一个FP32的BEV模型,量化后精度掉了2.3%。分析发现,主要是截断误差导致的——某些层的激活值范围特别大,被截断后信息丢失严重。后来我用了per-channel量化(每通道单独算scale),误差降到了0.8%。

4.6 量化策略的选择

实际部署时,你有很多选择:

  • per-tensor vs per-channel:整个张量共享一组参数 vs 每个通道独立参数。per-channel精度更高,但计算更复杂。
  • 权重量化 vs 激活量化:权重可以离线量化,激活需要在线量化(因为依赖输入数据)。
  • 训练后量化 vs 量化感知训练:PTQ简单快速,QAT精度更高但需要重新训练。

我个人习惯:先用PTQ试试,如果精度损失在1%以内,就直接用。如果超过1%,再考虑QAT。毕竟重新训练BEV模型,成本太高了。

总结一下:量化就是把FP32映射到INT8,核心是scale和zero_point。对称量化适合权重,非对称量化适合激活。误差不可避免,但可以通过per-channel、QAT等手段控制。

下一章,咱们聊聊量化感知训练的具体实现。到时候我会分享一个我在实际项目中踩过的坑——QAT训练时精度反而下降了,后来发现是fake_quant的位置放错了。

嗯,今天就到这里。有问题欢迎交流。