模型量化基础(上):FP32 到 INT8 的思维转变
各位同学,今天我们来聊聊嵌入式 AI 里一个绕不开的话题——模型量化。说实话,我刚开始接触 STM32 上的 AI 部署时,觉得量化就是个「压缩工具」,把模型变小就完事了。后来在几个量产项目里踩了坑才发现,量化其实是一场精度与速度的博弈,理解它的底层逻辑,比会调工具重要得多。
什么是量化?从 FP32 到 INT8 的「降维打击」
量化,说白了就是把模型里那些 32 位浮点数(FP32)的权重和激活值,用 8 位整数(INT8)来表示。你想想看,一个参数从 4 字节变成 1 字节,模型体积直接缩到四分之一,推理速度也能翻倍——这对资源受限的 MCU 来说,简直是救命稻草。
核心收益:
- 模型体积:FP32 → INT8,存储减少 75%
- 推理速度:STM32 的 SIMD 指令对 INT8 有硬件加速,通常快 2-4 倍
- 内存带宽:每次访存少读 3 个字节,DDR 压力骤降
但代价呢?精度损失。我记得在做一个手势识别项目时,FP32 模型准确率 97%,量化后掉到了 93%。虽然还能用,但如果你做的是医疗设备或安全关键系统,这 4% 的差距可能就是「能用」和「不能用」的分界线。
对称量化 vs 非对称量化:两种思路,两种场景
量化的核心公式其实很简单:
量化值 = round( (浮点值 - 零点) / 缩放因子 )
这里的 缩放因子(scale) 和 零点(zero-point) 就是量化参数。根据零点的取值方式,量化分为两种:
| 对比项 | 对称量化 | 非对称量化 |
|---|---|---|
| 零点取值 | 固定为 0 | 根据数据分布动态计算 |
| 表示范围 | [-127, 127](INT8) | [0, 255](INT8) |
| 适用场景 | 权重(通常对称分布) | 激活值(通常非对称,如 ReLU 后全正数) |
| 计算复杂度 | 低(无需零点偏移) | 稍高(需要零点补偿) |
| 精度损失 | 数据分布偏斜时较大 | 能更好适配实际分布 |
我个人习惯:权重用对称量化,激活值用非对称量化。为什么?因为权重经过训练后通常围绕 0 对称分布,对称量化不会浪费表示范围;而激活值经过 ReLU 后全是非负数,用非对称量化能把整个 INT8 的 256 个等级都利用起来。
实战小技巧:在 STM32Cube.AI 里,你可以通过校准数据集来观察每层的激活值分布。如果某层激活值集中在很小的正数区间(比如 0~0.1),非对称量化能显著减少精度损失。我曾经在一个语音唤醒模型里,就因为把全连接层的激活量化从对称改成非对称,准确率回升了 2.3%。
量化参数:scale 和 zero-point 到底怎么算?
这两个参数是量化的「翻译官」。我们来看具体计算:
// 假设浮点范围 [min, max],量化范围 [qmin, qmax]
scale = (max - min) / (qmax - qmin)
zero_point = round(qmin - min / scale)
// 对称量化时,min = -max,zero_point = 0
// 非对称量化时,min 和 max 来自实际数据统计
举个例子:某层激活值在 [0.0, 6.0] 之间,我们用非对称量化到 [0, 255]:
scale = (6.0 - 0.0) / (255 - 0) = 0.02353
zero_point = round(0 - 0.0 / 0.02353) = 0
// 浮点值 3.14 量化后:
q = round(3.14 / 0.02353 + 0) = 133
反量化回去:
float_value = (133 - 0) * 0.02353 = 3.129
你看,3.14 变成了 3.129,误差 0.011——这就是量化精度的代价。
避坑指南:我曾经在部署一个目标检测模型时,发现某一层的激活值范围是 [-0.01, 12.5],但校准数据集采样不足,只统计到了 [0.5, 10.0]。结果量化后,小于 0.5 的值全部被截断成 0,模型直接「瞎了」。所以校准数据集一定要覆盖真实场景的极端值,别偷懒。
量化流程:从浮点到整数的「三步走」
下面这张图展示了量化的完整流程,我建议你把它存下来,每次做量化前对照一遍:
量化误差的来源与应对
量化不是无损的,误差主要来自三个方面:
- 截断误差:浮点值超出量化范围时被强行截断。比如 INT8 最大表示 127,但某个权重是 128.5,量化后变成 127,信息丢失。
- 舍入误差:round() 操作本身带来的精度损失。0.5 到底舍还是入?不同硬件实现可能不同。
- 粒度误差:每层共用一套 scale/zero-point,无法精细到每个通道。Per-channel 量化能缓解,但计算更复杂。
我的经验:在 STM32Cube.AI 里,你可以开启「混合精度量化」——对敏感层(比如第一层和最后一层)保留 FP16 或 FP32,中间层用 INT8。我在一个工业缺陷检测项目里,只把输入层和输出层保留 FP16,整体精度损失从 5% 降到了 0.8%,而推理速度只慢了 12%。这买卖划算。
量化参数在 STM32Cube.AI 中的实际配置
在 STM32Cube.AI 的配置界面里,量化参数不是让你手算的——工具会自动完成。但你需要理解几个关键选项:
| 配置项 | 说明 | 我的建议 |
|---|---|---|
| Quantization Mode | 对称 / 非对称 / 自动选择 | 新手选「自动」,熟悉后手动指定 |
| Calibration Dataset | 用于统计激活值分布的样本集 | 至少 100 张代表性图片,覆盖极端情况 |
| Per-Channel Quantization | 每个输出通道独立计算 scale | 精度敏感场景开启,但会增加模型体积 |
| Quantization Fallback | 精度损失过大时自动回退到 FP16 | 建议开启,防止模型「崩掉」 |
嗯,这里要注意:校准数据集的质量直接决定量化效果。我见过有人随便拿 10 张图做校准,结果模型部署后准确率暴跌 15%。后来换成 500 张覆盖各种光照条件的图,精度只掉了 1.2%。
小结:量化不是终点,是起点
量化参数 scale 和 zero-point 看似简单,但它们决定了你的模型在 MCU 上能不能跑、跑多快、准不准。对称量化省计算但牺牲精度,非对称量化保精度但多一步零点补偿——没有银弹,只有权衡。