3、量化技术入门:FP32 到 INT8 的转换与精度损失分析
各位做嵌入式AI的朋友,咱们今天聊聊量化。说实话,我刚接触这玩意儿的时候,也觉得挺玄乎的——好好的FP32不用,非得折腾成INT8,这不是自找麻烦吗?
后来在项目里被内存逼急了,才真正体会到量化的价值。你想想看,一个模型从FP32压到INT8,体积直接缩到四分之一,推理速度还能翻倍。这种好事,谁不想要?
但代价呢?精度损失。今天我就把这块掰开揉碎了讲清楚。
3.1 为什么需要量化?
先说说我的亲身经历。去年做个工业视觉项目,STM32H743上跑一个分类模型。模型不大,但RAM只有512KB,FP32的权重一加载,内存直接爆了。
后来做了INT8量化,模型从2.3MB降到580KB,推理时间从120ms降到45ms。精度从98.2%掉到97.6%,完全可以接受。
所以量化的核心价值就两点:
- 减少存储:INT8只有FP32的1/4大小
- 加速计算:整数运算比浮点快得多,尤其在有硬件加速的MCU上
关键数据对比:
| 数据类型 | 占用空间 | 典型推理速度 | 适用场景 |
|---|---|---|---|
| FP32 | 4字节 | 1x(基准) | PC端、服务器 |
| FP16 | 2字节 | 1.5-2x | 高端MCU、GPU |
| INT8 | 1字节 | 2-4x | 嵌入式、边缘设备 |
3.2 量化到底在做什么?
说白了,量化就是把一个连续的值域映射到离散的整数上。FP32的取值范围是大约±3.4×10³⁸,而INT8只有-128到127这256个整数。
怎么映射?核心公式就一个:
q = round(r / S) + Z
其中:
- r:原始的FP32浮点数
- q:量化后的INT8整数
- S:缩放因子(scale),决定步长
- Z:零点偏移(zero point),让0能精确表示
反量化就是逆过程:
r = (q - Z) × S
嗯,这里要注意。S和Z怎么选?这直接决定了精度损失的大小。
3.3 两种主流量化方式
我在项目中试过好几种量化策略,最常用的就两种:
3.3.1 对称量化
对称量化把FP32的范围对称地映射到INT8的[-127, 127]区间。Z固定为0,公式简化成:
q = round(r / S)
优点:计算简单,硬件实现方便。
缺点:如果数据分布不对称(比如全是正数),浪费了一半的表示范围。
我的经验:对称量化适合权重。因为训练好的权重通常接近正态分布,正负对称。我在STM32CubeAI里默认就用对称量化,效果一直不错。
3.3.2 非对称量化
非对称量化允许Z不为0,可以覆盖任意范围的数据。公式就是前面那个完整的:
q = round(r / S) + Z
优点:充分利用INT8的256个值,精度损失更小。
缺点:计算时多一个偏移量,稍微复杂点。
避坑指南:我曾经在激活值量化上吃过亏。ReLU的输出全是非负数,用对称量化的话,一半的整数范围就浪费了。后来换成非对称量化,精度直接提升了0.8%。
3.4 精度损失从哪来?
量化不是无损的。损失主要来自三个方面:
- 舍入误差:round操作会丢失小数部分。比如3.7量化成4,0.3的误差就没了。
- 截断误差:超出INT8范围的值会被截断。比如FP32的200.0,INT8最大127,直接截成127。
- 量化噪声:多次量化累积的误差,在深层网络中会被放大。
为什么会这样?你想想看,FP32有约23位精度,INT8只有8位。相当于用一把毫米尺去量纳米级的物体,误差在所难免。
3.5 如何评估精度损失?
我一般用三个指标来评估:
| 指标 | 含义 | 可接受范围 |
|---|---|---|
| Top-1准确率下降 | 分类任务中首选的正确率变化 | < 1% |
| 均方误差(MSE) | 量化前后输出的差异 | < 0.01 |
| 余弦相似度 | 特征向量的方向一致性 | > 0.99 |
实用建议:别只看准确率。我在一个目标检测项目里,准确率只掉了0.3%,但边界框偏移了5个像素。所以一定要结合具体任务来评估。
3.6 STM32CubeAI中的量化实践
在STM32CubeAI里做量化,流程其实挺简单的:
- 用STM32CubeMX导入你的模型(.h5或.tflite格式)
- 选择量化选项:一般选"INT8 with calibration"
- 提供校准数据集(100-500张典型图片就够了)
- 工具会自动计算S和Z,生成量化后的模型
代码层面,你不需要手动写量化逻辑。CubeAI生成的代码里已经包含了量化推理的完整实现:
// CubeAI生成的量化推理代码示例
// 输入已经是INT8格式
ai_i8* input_data = ...;
ai_i8* output_data = ...;
// 前向推理
ai_network_run(network, input_data, output_data);
// 反量化输出(如果需要浮点结果)
float scale = network->output_scale;
int zero_point = network->output_zero_point;
float result = (output_data[0] - zero_point) * scale;
个人习惯:我一般会在PC上用TensorFlow Lite做一次预量化,确认精度没问题后,再导入CubeAI。这样能省不少调试时间。
3.7 常见问题与解决方案
最后分享几个我踩过的坑:
- 校准数据太少:少于50张图片,S和Z算不准。我建议至少用200张。
- 数据分布差异大:校准集和实际场景差异大,量化效果差。尽量用真实场景的数据做校准。
- 某些层特别敏感:比如第一层和最后一层。可以尝试对这些层保留FP16或FP32。
嗯,量化这东西,说白了就是个权衡。你要速度,就得接受一点精度损失。但好在大部分嵌入式场景下,INT8的精度完全够用。
下一章我会讲如何在STM32CubeAI里做量化感知训练,从源头减少精度损失。到时候咱们再细聊。