2、量化基础:从FP32到Int8的数值表示、量化误差来源与基本量化公式

TinyML 设备(如 Cortex-M 系列微控制器)通常没有 FPU(浮点运算单元)或仅有单精度 FPU,且 SRAM 往往只有几十到几百 KB。若直接运行 FP32 模型,不仅推理速度慢,内存也常常放不下。将权重和激活从 FP32 转换为 Int8,是让模型在 MCU 上落地的最关键一步。本节将深入拆解数值表示差异、量化误差的来源,以及对称/非对称量化的核心公式,为后续的时钟频率与功耗优化打下数学基础。

FP32 数值域 符号位1 + 指数8 + 尾数23 范围: ±3.4×10³⁸ 精度: ~7位有效数字 权重示例: 0.173828125 量化 Q = round(r/S) + Z Int8 数值域 有符号: -128 ~ 127 无符号: 0 ~ 255 精度: 256个离散级别 量化后: 22 (scale=0.0078125) 量化误差三大来源 ① 舍入误差 round() 取整导致 最大偏差 = S/2 均匀分布 ±0.5LSB ② 截断误差 超出范围被截断 |r| > α 时发生 误差可达 |r|-α ③ 精度损失 动态范围压缩 小数值分辨率下降 相对误差增大 核心公式 对称量化: S = max|r| / 127 q = round(r / S) 非对称量化: S = (rmax-rmin)/255 Z = round(-rmin/S) q = round(r/S) + Z 反量化: r ≈ (q - Z) × S Int8 乘法累加 速度提升 2-4 倍

2.1 FP32 与 Int8 的数值表示差异

FP32(单精度浮点数)采用 IEEE 754 标准,由 1 位符号位、8 位指数位和 23 位尾数位组成。这种表示法能够覆盖从 ±1.4×10⁻⁴⁵ 到 ±3.4×10³⁸ 的极大动态范围,并且在小数值附近保持高精度。例如,0.1 在 FP32 中可以被精确表示为 0x3DCCCCCD,误差约为 1e-8 量级。

相比之下,Int8 只有 8 个比特位,总共能表示 256 个离散值。有符号 Int8 的范围是 -128 ~ 127,无符号 Int8 的范围是 0 ~ 255。Int8 没有指数位,因此无法直接表示小数,也无法覆盖大动态范围。但它的优势在于:单次乘法累加运算只需 1 个时钟周期(在 Cortex-M4/M7 上),而 FP32 乘法需要 3-15 个周期(取决于是否启用 FPU)。对于卷积层这种计算密集型操作,Int8 的吞吐量通常是 FP32 的 2-4 倍。

从信息论角度看,FP32 到 Int8 的转换本质上是将连续值域映射到离散值域。这个过程必然引入信息损失,但通过合理选择量化参数,可以将精度损失控制在 1%-3% 以内(对于大多数分类和检测任务)。关键是要理解:量化不是简单地取整,而是需要先确定缩放因子(Scale)和零点(Zero Point)。

核心认知:FP32 的优势在于动态范围和精度,Int8 的优势在于速度和内存占用。TinyML 场景下,我们牺牲少量精度换取 4 倍内存压缩和 2-4 倍推理加速,这在电池供电的嵌入式设备上是值得的。

2.2 量化误差的来源分析

量化误差主要来自三个方面,理解这些来源有助于我们在实际部署时采取针对性措施。

2.2.1 舍入误差(Rounding Error)

当浮点数值落在两个相邻量化级别之间时,round() 操作会将其归一到最近的整数。这个误差的最大值为 S/2(S 为缩放因子),且呈均匀分布。例如,若 S=0.1,则 0.05 可能被量化为 0 或 1,误差为 0.05。对于权重而言,这种随机误差通常会被卷积层的累加操作部分抵消,因为正负误差会相互平均。

2.2.2 截断误差(Clipping Error)

当浮点数值超出量化范围时(例如大于 127×S 或小于 -128×S),该值会被截断到边界值。这种误差是系统性偏差,不会相互抵消,会导致输出特征图整体偏移。截断误差的大小取决于数据分布与量化范围的匹配程度。如果权重中存在明显的离群值(Outlier),截断误差会显著增大。

2.2.3 精度损失(Precision Loss)

即使没有舍入和截断,Int8 的 256 个离散级别也限制了最小分辨率。对于数值范围在 [-1, 1] 的权重,量化步长约为 0.0078,这意味着小于该步长的权重变化无法被表示。这种精度损失在深层网络中会逐层累积,尤其是当激活值经过 ReLU 后分布变得偏斜时。

误差类型 产生原因 误差特性 缓解策略
舍入误差 round() 取整 随机、均匀分布,最大 S/2 使用随机舍入或增加量化级别
截断误差 超出量化范围 系统性偏差,不可抵消 选择合适的 α 值,或使用非对称量化
精度损失 离散级别有限 小数值分辨率下降 对权重做缩放预处理,或使用混合精度

2.3 基本量化公式与两种量化方案

量化过程的核心是找到从浮点实数 r 到整数 q 的映射关系。最通用的形式为:

q = round(r / S) + Z

其中 S(Scale)是缩放因子,Z(Zero Point)是零点偏移。反量化公式为:

r ≈ (q - Z) × S

根据 Z 是否为零,量化方案分为两种:

2.3.1 对称量化(Symmetric Quantization)

对称量化假设浮点数据的分布关于原点对称,因此 Z = 0。此时公式简化为:

S = max(|r_min|, |r_max|) / 127
q = round(r / S)

这种方案实现简单,不需要存储零点,且 Int8 乘法累加时无需额外处理偏移。但缺点是:如果数据分布不对称(例如 ReLU 后的激活值全为正),会浪费一半的量化范围。对称量化常用于权重的量化,因为训练后的权重通常近似对称分布。

2.3.2 非对称量化(Asymmetric Quantization)

非对称量化允许零点偏移,能够充分利用 256 个量化级别。公式为:

S = (r_max - r_min) / 255
Z = round(-r_min / S)
q = round(r / S) + Z

对于 ReLU 后的激活值(范围 [0, 6]),非对称量化可以将整个范围映射到 [0, 255],分辨率提升约一倍。代价是推理时需要额外处理零点偏移,计算量略有增加。实际部署中,激活值通常使用非对称量化,权重使用对称量化,这是 TensorFlow Lite 和 PyTorch 的默认策略。

经验法则:如果数据的值最大值远大于均值(即存在离群值),优先使用对称量化并配合裁剪(Clipping);如果数据分布偏斜(如 ReLU 输出),使用非对称量化。对于 TinyML 模型,建议先尝试对称量化,若精度下降超过 2%,再切换为非对称量化。

2.4 实战案例:一个 3×3 卷积层的量化过程

假设我们有一个训练好的 3×3 卷积层,其权重矩阵如下(FP32):

权重 W = [[0.15, -0.32, 0.08],
          [0.21, 0.05, -0.18],
          [-0.12, 0.27, -0.04]]

输入激活值(单通道)为:

输入 X = [[0.5, 1.2, 0.3],
           [0.8, 0.6, 1.5],
           [0.2, 0.9, 0.4]]

步骤 1:确定量化参数

对于权重,采用对称量化。计算 max|W| = 0.32,则 S_w = 0.32 / 127 ≈ 0.00252。量化后的权重为:

W_q = round(W / 0.00252) = [[60, -127, 32],
                              [83, 20, -71],
                              [-48, 107, -16]]

对于激活值,采用非对称量化。输入范围 [0.2, 1.5],则 S_x = (1.5-0.2)/255 ≈ 0.0051,Z = round(-0.2/0.0051) ≈ -39。量化后的输入为:

X_q = round(X/0.0051) + (-39) = [[59, 196, 20],
                                   [118, 79, 255],
                                   [0, 137, 39]]

步骤 2:Int8 卷积计算

在 MCU 上执行 Int8 乘累加(MAC)操作。以输出位置 (0,0) 为例:

acc = 60×59 + (-127)×196 + 32×20
    + 83×118 + 20×79 + (-71)×255
    + (-48)×0 + 107×137 + (-16)×39
    = 3540 - 24892 + 640 + 9794 + 1580 - 18105 + 0 + 14659 - 624
    = -13408

步骤 3:反量化得到 FP32 输出

输出缩放因子 S_out = S_w × S_x = 0.00252 × 0.0051 ≈ 1.285e-5。反量化后:

y = acc × S_out = -13408 × 1.285e-5 ≈ -0.1723

而 FP32 直接计算的结果为 -0.1718,误差仅为 0.3%。这个误差在后续层的累加中会被进一步平均化,最终对分类结果的影响通常小于 1%。

风险提示:上述计算中,中间累加器 acc 的值可能超出 Int8 范围(-128~127)。因此,实际硬件中累加器必须使用 Int32 或更高精度。Cortex-M4 的 SMLAD 指令支持 Int8×Int8+Int32 的累加操作,这正是 Int8 推理能提速的关键硬件基础。

2.5 常见误区与总结复盘

在实际项目中,工程师常犯以下错误:

  • 误区一:直接对 FP32 模型做 round() 取整。没有先计算 Scale 和 Zero Point,导致量化范围严重不匹配,精度暴跌。
  • 误区二:忽略激活值的分布特征。对所有层使用相同的量化参数,没有针对每层单独统计 min/max。正确的做法是使用校准数据集(Calibration Dataset)统计每层激活值的实际范围。
  • 误区三:认为量化后模型一定变慢。实际上,如果 MCU 没有 FPU,Int8 推理可能比 FP32 快 5-10 倍;即使有 FPU,Int8 也能减少内存带宽压力,从而降低功耗。
  • 误区四:忽视 BatchNorm 的融合。BatchNorm 层在推理时可以融合到卷积层中,但量化时需要特别注意:必须先融合再量化,否则会引入额外的缩放误差。