第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 量化误差的来源
量化不是无损的。误差主要来自三个方面:
- 舍入误差:round操作会引入±0.5个步长的误差
- 截断误差:超出表示范围的值被截断,丢失信息
- 表示精度不足: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的位置放错了。
嗯,今天就到这里。有问题欢迎交流。