2、量化基础:什么是量化、为什么需要量化、对称与非对称量化、定点与浮点表示
在进入 TFLite Micro 的算子调试之前,必须先建立对量化本身的清晰认知。量化不是简单的“把 float 变成 int”,而是一种有损的信息重编码过程,它直接决定了模型在嵌入式设备上的精度、内存占用和推理速度。本章从量化定义出发,逐步拆解对称/非对称量化的数学本质,并厘清定点与浮点表示在硬件执行层面的差异,为后续逐算子调试打下理论基础。
2.1 什么是量化:从连续到离散的映射
量化(Quantization)本质上是将一个连续取值(或大范围离散取值)的实数集合,映射到一个有限大小的整数集合的过程。在深度学习推理场景中,最常见的操作是把 float32 的权重和激活值转换为 int8(-128~127)或 uint8(0~255)。这个映射不是简单的四舍五入,而是通过两个关键参数——Scale(缩放因子)和 Zero Point(零点)——建立线性变换关系。
数学上,量化过程可以表示为:
real_value = (int_value - zero_point) × scale
其中 scale 是一个浮点数,表示每个整数单位对应的实数范围;zero_point 是一个整数,表示实数 0 对应的量化整数值。反量化(Dequantization)则是上述公式的逆运算。TFLite Micro 在算子执行时,通常采用“整型计算 + 浮点 scale 缩放”的混合策略,即中间累加过程使用 int32 或 int64 以避免溢出,最终输出前再乘以 scale 并转换为目标整数类型。
2.2 为什么需要量化:嵌入式部署的硬约束
TFLite Micro 面向的是 Cortex-M 系列 MCU、ESP32、RP2040 等资源受限设备。这些平台通常只有几十到几百 KB 的 RAM,主频在几十到几百 MHz 之间,且多数不具备硬件浮点单元(FPU)或仅有单精度 FPU。在这种环境下,float32 模型几乎无法直接运行,原因有三:
- 内存瓶颈:一个 300KB 的 float32 模型,转换为 int8 后仅需 75KB,这直接决定了模型能否放入片上 Flash 或 RAM。
- 算力瓶颈:整数乘法(MUL)和加法(ADD)指令在 MCU 上通常只需 1~2 个时钟周期,而浮点运算在没有 FPU 的情况下需要调用软件库,耗时可达几十甚至上百个周期。
- 功耗与带宽:更少的数据搬运意味着更低的功耗,对于电池供电的 IoT 设备至关重要。
以 Cortex-M4 为例,其 DSP 指令集支持单周期 MAC(乘累加)操作,但仅限整数。量化后的模型可以充分利用这些指令,实现接近实时推理。而 float32 模型即使能运行,也往往因为速度过慢而失去实用价值。
| 对比维度 | float32 模型 | int8 量化模型 |
|---|---|---|
| 模型大小(300KB 示例) | 300KB | 75KB |
| RAM 占用(激活值) | 高(4 字节/元素) | 低(1 字节/元素) |
| 乘法指令周期(无 FPU) | 20~100+ 周期(软件模拟) | 1~2 周期(硬件整数 MAC) |
| 典型精度损失 | 基准 | 1%~3%(分类任务) |
2.3 对称量化与非对称量化:zero_point 的取舍
对称量化(Symmetric Quantization)强制 zero_point = 0,即实数 0 精确映射到整数 0。此时量化公式简化为 real_value = int_value × scale。这种方式的优点是计算简单,不需要额外的零点减法操作,适合权重(权重通常关于 0 对称分布)。但缺点是如果激活值分布偏向正区间(例如 ReLU 后的输出),对称量化会浪费一半的整数表示范围。
非对称量化(Asymmetric Quantization)允许 zero_point 不为 0,可以更精细地覆盖任意实数分布。例如,ReLU 输出的激活值范围是 [0, 6.0],非对称量化可以将 [0, 6.0] 完整映射到 [0, 255],而对称量化只能映射 [-3.0, 3.0] 或 [-6.0, 6.0],导致精度下降。TFLite Micro 的默认方案是:权重使用对称量化,激活值使用非对称量化,这是精度与计算复杂度的折中。
2.4 定点与浮点表示:硬件视角的本质差异
定点数(Fixed-point)用整数存储,但约定小数点的位置固定。例如 Q7.8 格式表示 7 位整数位、8 位小数位,实际值 = 整数值 / 256。定点运算完全基于整数指令,速度快、功耗低,但动态范围有限。浮点数(Floating-point)则通过符号位、指数位和尾数位表示,动态范围大,但需要专用硬件单元(FPU)或软件库支持。
在 TFLite Micro 中,量化推理的核心是“整型计算 + 浮点 scale 缩放”的混合模式。具体来说:
- 卷积或全连接层的乘累加操作使用 int8 输入和 int8 权重,累加器使用 int32 以避免中间溢出。
- 偏置(Bias)通常以 int32 存储,其 scale 等于输入 scale × 权重 scale。
- 最终输出前,将 int32 累加结果乘以输出 scale 的倒数,并加上 zero_point,再截断到 int8 范围。
这种设计的精妙之处在于:乘累加过程完全避免浮点运算,只有最后的缩放和偏移使用浮点乘法。但这也带来了调试难点——scale 的精度直接决定了量化误差的大小。如果 scale 计算不准确(例如使用 float32 存储但精度不足),会导致输出值系统性偏移。
// TFLite Micro 量化卷积核心伪代码
int32_t acc = 0;
for (int i = 0; i < input_channels; i++) {
acc += input_data[i] * weight_data[i]; // int8 × int8 → int32
}
acc += bias_data[out_channel]; // int32 偏置
// 量化缩放:乘以 scale 并加上 zero_point
float real_value = static_cast<float>(acc) * output_scale;
int8_t output = static_cast<int8_t>(real_value + output_zero_point);
// 实际实现中会使用定点乘法近似,避免浮点除法
2.5 案例拆解:一个 Conv2D 算子的量化参数分析
假设我们有一个训练好的 float32 模型,其中第一个 Conv2D 层的权重范围是 [-0.5, 0.5],激活值(ReLU 后)范围是 [0.0, 5.0]。使用 TFLite Converter 进行 int8 量化后,我们得到以下参数:
- 权重:对称量化,scale = 0.5 / 127 ≈ 0.003937,zero_point = 0
- 激活输入:非对称量化,scale = 5.0 / 255 ≈ 0.019608,zero_point = 0
- 激活输出:非对称量化,scale = 5.0 / 255 ≈ 0.019608,zero_point = 0
- 偏置:int32,scale = 0.003937 × 0.019608 ≈ 0.0000772
在调试过程中,我们发现在 MCU 上运行的结果与 PC 端 float32 推理结果相比,最大误差达到 0.15。通过打印中间激活值,发现第 3 个通道的输出整体偏大。进一步检查发现,该通道的权重分布并非关于 0 对称,实际范围是 [-0.1, 0.6],对称量化导致正半轴精度不足。
解决方案:对该层改用非对称量化(通过配置 TFLite Converter 的 experimental_new_quantizer 或手动指定量化方案),将权重范围完整映射到 [-128, 127],zero_point 设为 -21(对应 -0.1 的量化值)。重新部署后,最大误差降至 0.03,满足应用需求。
2.6 常见误区与总结复盘
误区一:认为量化就是简单的“除以最大值再乘以 255”。实际上,正确的量化必须考虑 zero_point 的偏移,否则对于非对称分布的数据会产生系统性误差。
误区二:忽略 scale 的精度。在 TFLite Micro 中,scale 通常以 float32 存储,但在某些极端情况下(如 scale 非常小或非常大),float32 的精度可能不足。此时应考虑使用 double 或调整量化范围。
误区三:认为 int8 量化后所有算子都支持。实际上,TFLite Micro 中部分算子(如某些高级激活函数)可能只支持 float32 或需要特殊的量化实现。在模型转换时,应检查算子兼容性。
误区四:混淆定点数和整数。定点数虽然用整数存储,但语义上包含小数部分。在调试时,不能简单地将 int8 值当作整数看待,必须结合 scale 和 zero_point 还原为实数。
总结本章,量化是 TFLite Micro 部署的基石。理解 scale 和 zero_point 的数学含义,掌握对称/非对称量化的适用场景,明确定点与浮点在硬件执行上的差异,是后续逐算子调试的前提。在实际调试中,建议先打印各张量的 scale 和 zero_point,确认量化参数是否符合预期,再深入分析算子内部的整型计算逻辑。