4. 模型量化入门:从FP32到INT8,为什么量化是裁剪的第一步?

做嵌入式AI,尤其是Cortex-M系列,你很快会撞上一堵墙——内存墙。

模型在PC上跑得飞起,一放到MCU上,Flash不够,RAM爆炸。这时候你第一个想到的可能是剪枝、蒸馏,但我个人的经验是:先做量化。为什么?因为量化带来的收益最直接、最稳定,而且几乎不损失太多精度。

说白了,量化就是把模型里那些“娇贵”的FP32小数,换成“皮实”的INT8整数。你想想看,一个参数从4字节变成1字节,体积直接缩到四分之一。这还不算完,整数运算在Cortex-M上比浮点快得多,很多M4/M7内核甚至没有硬件浮点单元,跑FP32全靠软件模拟,那速度……嗯,我当年调一个语音识别模型,用FP32推理一次要2秒,量化到INT8后直接降到200毫秒,效果立竿见影。

4.1 为什么FP32在MCU上“水土不服”?

先看一组数据,你就明白了:

数据类型 位宽 表示范围 精度 Cortex-M硬件支持
FP32 32 bit ±3.4×10³⁸ ~7位小数 仅M7/M55有FPU,其余需软模拟
INT8 8 bit -128 ~ 127 整数 所有Cortex-M原生支持
INT16 16 bit -32768 ~ 32767 整数 所有Cortex-M原生支持

看到没?FP32的表示范围大得离谱,但MCU根本不需要。一个神经网络权重,大部分值都在[-1, 1]之间,你用32位去存它,纯粹是浪费。我曾经在一个项目里,模型权重分布集中在[-0.5, 0.5],FP32的高位全是0,白白占着Flash。

更关键的是,Cortex-M的Flash和RAM都是按KB算的。一个100KB的FP32模型,量化后只有25KB。这25KB的差距,往往决定了你的芯片能不能装下这个模型。

核心结论: 量化是裁剪的第一步,因为它直接解决了“模型装不装得下”这个生死问题。剪枝和蒸馏是后续优化,但量化是基础。

4.2 量化原理:从浮点到整数的“映射”

量化不是简单的四舍五入。它背后有一套数学映射关系。

我们用一个公式就能说清楚:

// 量化公式:浮点 → 整数
q = round(r / scale) + zero_point

// 反量化公式:整数 → 浮点
r = (q - zero_point) * scale

这里:

  • r:原始FP32值
  • q:量化后的INT8值
  • scale:缩放因子,决定每个整数步长代表多少浮点数
  • zero_point:零点偏移,保证浮点0能精确映射到整数

举个例子。假设你有一组权重,范围在[-1.0, 1.0]。INT8的范围是[-128, 127]。那么:

scale = (1.0 - (-1.0)) / (127 - (-128)) = 2.0 / 255 ≈ 0.00784
zero_point = round(-(-1.0) / 0.00784) + (-128) = round(127.55) - 128 = 128 - 128 = 0

所以浮点0.5量化后就是:q = round(0.5 / 0.00784) + 0 = round(63.78) = 64。

反推回来:r = (64 - 0) * 0.00784 = 0.50176,误差只有0.00176,完全可以接受。

我的习惯: 量化时一定要检查zero_point是否在[0, 255]范围内。如果超出,说明你的scale算错了,或者数据分布太极端。我曾经踩过这个坑,模型推理结果全是乱的,排查了半天才发现是zero_point溢出了。

4.3 两种主流量化方式:对称 vs 非对称

实际工程中,我们主要用两种量化策略:

特性 对称量化 非对称量化
zero_point 固定为0 根据数据分布计算
表示范围 [-127, 127](牺牲一个值) [-128, 127](全利用)
适用场景 权重(通常对称分布) 激活值(通常非对称,如ReLU后全正)
计算复杂度 低(无需零点偏移) 中等(需要处理零点)
精度损失 对对称分布友好 对非对称分布更精确

我个人在Cortex-M上做量化时,权重用对称,激活用非对称。为什么?因为权重经过训练后,正负值分布通常比较对称,用对称量化可以省掉zero_point的计算,推理时少一次减法操作。而激活值经过ReLU后全是非负数,用非对称量化能把[0, 6]这个范围映射到[0, 255],精度更高。

避坑指南: 我曾经在一个项目中,所有层都用了对称量化,结果精度掉了5%。后来发现是某几层激活值分布严重偏斜,对称量化浪费了大量整数表示范围。换成非对称量化后,精度损失降到了0.5%以内。所以,不要一刀切,要根据每层的数据分布选择量化方式。

4.4 量化后的推理:整数运算才是王道

量化不只是为了省内存。更重要的是,INT8的乘加运算在Cortex-M上比FP32快一个数量级。

看一个典型的卷积层计算:

// FP32卷积(无硬件FPU时)
for (int i = 0; i < N; i++) {
    sum += a_fp32[i] * b_fp32[i];  // 每条指令都要调用软浮点库
}

// INT8卷积(原生支持)
for (int i = 0; i < N; i++) {
    sum += a_int8[i] * b_int8[i];  // 单条指令完成,32位累加器防溢出
}

在Cortex-M4上,一次INT8乘法只需要1个时钟周期,而FP32软模拟需要几十甚至上百个周期。你想想看,一个百万参数的模型,这个差距有多大。

但这里有个坑:累加器溢出。INT8乘法结果最大是127×127=16129,如果累加很多次,16位累加器会溢出。所以Cortex-M的SMLABB指令用32位累加器来存结果,安全得多。

量化推理的关键步骤:
  1. 输入数据量化:将FP32输入转为INT8
  2. 整数卷积/全连接:用INT8权重和INT8输入做乘加
  3. 累加结果反量化:将INT32累加结果转回FP32
  4. 激活函数:在FP32域做ReLU等操作
  5. 输出再量化:将激活结果量化回INT8,传给下一层

嗯,这里要注意,不是所有层都适合量化。比如Batch Normalization层,它涉及除法和开方,量化后精度损失很大。我通常的做法是:把BN层的参数融合到卷积层里,再做量化。这样既保留了BN的效果,又避免了量化误差。

4.5 量化精度损失:如何评估与补救?

量化不是无损的。你可能会问:损失多少算正常?

根据我的经验:

  • 分类任务:Top-1准确率下降<1%算优秀,<3%可接受
  • 检测任务:mAP下降<2%算正常
  • 语音/信号处理:信噪比下降<1dB算良好

如果精度损失过大,可以试试这些方法:

  1. 逐通道量化:每个输出通道单独算scale,而不是整个层共用一个。我试过,能挽回1-2%的精度。
  2. 量化感知训练(QAT):在训练时模拟量化误差,让模型自己适应。效果最好,但需要重新训练。
  3. 混合精度:敏感层用INT16或FP32,不敏感层用INT8。适合关键任务。
我的建议: 先做简单的后训练量化(PTQ),如果精度达标就直接用。不达标再上QAT。不要一上来就搞复杂的,嵌入式开发讲究“够用就好”。

4.6 量化在Cortex-M上的落地流程

最后,我画了一张流程图,帮你理清整个量化落地的步骤:

Cortex-M模型量化落地流程 Cortex-M 模型量化落地流程 训练好的FP32模型 数据分布分析 选择量化策略 对称量化(权重) 非对称量化(激活) 生成INT8模型 Cortex-M部署验证 提示:部署后需用真实数据验证精度,若损失过大则回退到QAT或混合精度方案

这个流程我用了很多次,每次都能在1-2天内完成从FP32到INT8的转换。关键是要先跑通,再优化。不要一开始就追求极致精度,先让模型在MCU上跑起来,看看内存和速度是否达标,再回头调精度。

量化带来的收益总结:
  • 模型体积:减少75%(FP32→INT8)
  • 推理速度:提升2-4倍(取决于内核和算子优化)
  • 内存带宽:降低75%,减少总线瓶颈
  • 功耗:降低50%以上(整数运算比浮点省电)

所以,为什么量化是裁剪的第一步?因为它用最小的改动,换来了最大的收益。你不需要改模型结构,不需要重新训练,只需要做一次数学变换,就能让模型在Cortex-M上跑起来。这就是我为什么每次做嵌入式AI,第一件事就是量化。