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 精度损失从哪来?

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

  1. 舍入误差:round操作会丢失小数部分。比如3.7量化成4,0.3的误差就没了。
  2. 截断误差:超出INT8范围的值会被截断。比如FP32的200.0,INT8最大127,直接截成127。
  3. 量化噪声:多次量化累积的误差,在深层网络中会被放大。

为什么会这样?你想想看,FP32有约23位精度,INT8只有8位。相当于用一把毫米尺去量纳米级的物体,误差在所难免。

3.5 如何评估精度损失?

我一般用三个指标来评估:

指标 含义 可接受范围
Top-1准确率下降 分类任务中首选的正确率变化 < 1%
均方误差(MSE) 量化前后输出的差异 < 0.01
余弦相似度 特征向量的方向一致性 > 0.99

实用建议:别只看准确率。我在一个目标检测项目里,准确率只掉了0.3%,但边界框偏移了5个像素。所以一定要结合具体任务来评估。

3.6 STM32CubeAI中的量化实践

在STM32CubeAI里做量化,流程其实挺简单的:

  1. 用STM32CubeMX导入你的模型(.h5或.tflite格式)
  2. 选择量化选项:一般选"INT8 with calibration"
  3. 提供校准数据集(100-500张典型图片就够了)
  4. 工具会自动计算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里做量化感知训练,从源头减少精度损失。到时候咱们再细聊。