3、量化核心概念:什么是量化?对称量化 vs 非对称量化

好,咱们今天聊聊量化的核心概念。说实话,很多刚接触模型部署的兄弟,一听到「量化」两个字就觉得头大。其实没那么玄乎。

量化,说白了就是「降精度」。

你想想看,我们训练模型的时候,权重和激活值通常都是 FP32(32位浮点数)。一个数占 4 个字节。但到了 STM32 这种单片机上,Flash 和 RAM 都金贵得很。我能不能用 8 位整数(INT8)甚至 4 位整数(INT4)来近似表示这些数?

能。这就是量化。

用 INT8 代替 FP32,模型体积直接缩到四分之一。内存带宽也降到四分之一。推理速度还能翻倍——因为 Cortex-M 内核有 SIMD 指令,一次能处理多个 8 位数据。

我在项目中遇到过最极端的情况:一个 2MB 的模型,硬塞进 512KB Flash 的 STM32F4 里。不做量化?门都没有。

量化的数学本质

量化的核心公式其实就一行:

q = round(r / scale) + zero_point

其中:

  • r 是原始的浮点数(FP32)
  • q 是量化后的整数(INT8/INT4)
  • scale 是缩放因子(步长)
  • zero_point 是零点偏移

反量化就是反过来:

r = (q - zero_point) * scale

嗯,这里要注意:round 操作会带来精度损失。你想想看,原本 FP32 能表示 2^32 种数值,INT8 只能表示 256 种。这中间的取舍,就是量化的艺术。

对称量化 vs 非对称量化

这两种方式,区别就在 zero_point 上。

对称量化

对称量化,zero_point 固定为 0。公式简化成:

q = round(r / scale)

这意味着量化后的整数范围是对称的。比如 INT8 就是 [-128, 127]。浮点数的 0.0 精确映射到整数的 0。

优点:

  • 计算简单,没有偏移量,硬件实现快
  • 卷积、全连接层的累加器不需要处理偏移

缺点:

  • 如果数据分布不对称(比如全是正数),会浪费一半的量化范围

我个人的习惯: 对于权重,我几乎只用对称量化。因为训练好的权重通常分布接近正态,正负对称。我在 STM32N6 上部署 MobileNetV2 时,对称量化权重的精度损失不到 0.5%。

非对称量化

非对称量化,zero_point 可以不为 0。它把浮点数的最小值映射到整数的最小值,最大值映射到整数的最大值。

举个例子:假设激活值范围是 [0.0, 6.0],用 INT8 量化(范围 [0, 255]):

scale = (6.0 - 0.0) / (255 - 0) = 0.02353
zero_point = 0 - round(0.0 / 0.02353) = 0

如果范围是 [-3.0, 5.0]:

scale = (5.0 - (-3.0)) / (255 - 0) = 0.03137
zero_point = 0 - round(-3.0 / 0.03137) = 96

优点:

  • 充分利用量化范围,精度损失更小
  • 特别适合 ReLU 后的激活值(全是非负数)

缺点:

  • 计算时需要处理 zero_point 偏移,稍微复杂一点
  • 硬件加速器可能不支持(比如某些 NPU 只支持对称量化)

避坑指南: 我曾经在 STM32MP1 上踩过一个坑——激活值用对称量化,结果精度掉了 3%。后来发现激活值全是正数,对称量化把 [-128, 0] 那半边全浪费了。换成非对称量化,精度损失降到 0.8%。

什么时候选哪种?

场景 推荐方案 原因
权重量化 对称量化 权重分布通常对称,计算简单
激活值量化(ReLU后) 非对称量化 全是非负数,充分利用范围
激活值量化(tanh/sigmoid后) 对称量化 输出范围对称 [-1, 1] 或 [0, 1]
硬件不支持偏移 对称量化 兼容性优先
精度要求极高 非对称量化 更精细的粒度

实际部署中的选择

在 STM32CubeAI 里,默认的量化策略是:

  • 权重: 对称量化(per-channel)
  • 激活值: 非对称量化(per-tensor)

为什么这么选?

权重用 per-channel 对称量化,每个输出通道有自己的 scale。这样能处理不同通道的权重范围差异。激活值用 per-tensor 非对称量化,因为激活值范围通常比较稳定,而且非对称能更好地匹配 ReLU 的输出。

我记得有一次调试一个语音识别模型,激活值范围特别大(从 -10 到 20)。用对称量化,scale 被拉得很大,小数值的精度全丢了。换成非对称量化后,精度直接回升了 2%。

注意: 非对称量化在 STM32 上做 INT8 矩阵乘法时,需要额外处理 zero_point。具体来说,卷积计算时要先减去 zero_point 再做乘加。这会增加一点点计算量,但通常可以忽略不计。

量化参数的计算

实际部署时,量化参数(scale 和 zero_point)怎么算?

有两种方式:

  1. 校准(Calibration): 用一小批数据跑一遍模型,统计每层的激活值范围。然后根据范围计算 scale 和 zero_point。
  2. 训练时量化(QAT): 在训练过程中模拟量化误差,让模型自己适应低精度。精度通常比校准高,但需要重新训练。

对于 STM32 部署,我个人建议先用校准。如果精度不够,再考虑 QAT。因为校准只需要几分钟,而 QAT 可能要跑好几个小时。

好了,总结一下:

  • 量化就是 FP32 → INT8/INT4 的映射
  • 对称量化:zero_point=0,适合权重和对称分布的数据
  • 非对称量化:zero_point≠0,适合非对称分布的数据(如 ReLU 输出)
  • STM32CubeAI 默认用权重对称 + 激活非对称的组合

下一章咱们聊聊「量化感知训练(QAT)」和「训练后量化(PTQ)」的具体实现。到时候我会拿一个实际的 STM32 项目来演示。