第四章:算法量化基础——浮点与定点运算对比、量化误差分析、ARM平台下的量化工具

各位同学,今天我们来聊聊量化。说实话,我刚入行那会儿,觉得量化就是个“压缩”操作,把模型变小就完事了。后来在ARM Cortex-M7上跑一个手势识别模型,浮点版本跑得比乌龟还慢,我才意识到——量化不是简单的精度换速度,而是一场精度、速度、内存的三角博弈

4.1 浮点与定点:两种世界观

先问个问题:为什么ARM芯片里,定点运算比浮点快那么多?

原因很简单。浮点数(比如float32)用1位符号位、8位指数、23位尾数来表示一个数。你想想看,每次做加法,CPU得先对齐指数,再对尾数做运算,最后还要规格化。这一套下来,功耗和周期数都上去了。

定点数就不一样了。它本质上就是个整数,只是我们人为约定了一个“小数点位置”。比如Q7格式,就是1位符号位 + 7位小数位,表示范围是[-1, 0.9921875]。运算时直接走整数ALU,一个周期搞定。

核心对比表:

特性float32Q7定点int8量化
表示范围±3.4e38[-1, 0.992][-128, 127]
精度~7位十进制~2位十进制整数级
运算周期(Cortex-M4)~14 cycles1 cycle1 cycle
内存占用4字节1字节1字节

我在项目中遇到过最典型的场景:一个3层CNN,浮点版本在STM32H7上跑一帧要800ms。量化到int8后,直接降到120ms。嗯,这就是定点的魅力。

4.2 量化误差:你丢掉的精度去哪了?

量化必然有误差。但误差不是均匀分布的。我习惯把误差来源分成三类:

  • 截断误差:比如0.3用Q7表示,实际存的是0.296875。差值0.003125就是截断误差。
  • 溢出误差:数值超出表示范围。比如int8最大127,你硬塞个128进去,它就变成-128。这种错误最致命。
  • 累积误差:多次运算后,误差像滚雪球一样变大。尤其在深度网络中,每一层都丢一点,最后一层可能面目全非。

避坑指南:我曾经在量化一个YOLO检测模型时,忽略了中间层的激活值范围。结果检测框全偏了。后来发现是ReLU后的值太大,int8直接溢出。解决方案很简单:先跑一遍校准数据集,统计每层的动态范围,再决定量化参数。

量化误差分析有个经典公式:

量化误差 = 真实值 - 量化值
相对误差 = |量化误差| / 真实值

对于均匀量化,步长 Δ = (max - min) / (2^N - 1)
最大量化误差 = Δ / 2

说白了,量化步长越小,误差越小。但步长小了,表示范围就窄了。这是个trade-off。

4.3 ARM平台下的量化工具:TFLite Micro实战

说到ARM平台的量化工具,TFLite Micro是绕不开的。我个人习惯把它看作“专为MCU设计的推理引擎”。它支持两种量化方式:

  1. 训练后量化(Post-training Quantization):模型训练完,直接转。简单粗暴,适合快速验证。
  2. 量化感知训练(Quantization-aware Training, QAT):训练时模拟量化误差,精度更高。我建议对精度敏感的任务用这个。

来看一个实际转换流程。假设你有一个Keras模型:

import tensorflow as tf

# 加载浮点模型
model = tf.keras.models.load_model('float_model.h5')

# 转换器配置
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset  # 校准数据集
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8

# 生成量化模型
tflite_quant_model = converter.convert()
with open('model_quant.tflite', 'wb') as f:
    f.write(tflite_quant_model)

这里有个关键点:representative_dataset。它用来校准量化参数。我见过有人随便扔几张图进去,结果量化后精度掉得厉害。正确的做法是:从训练集里均匀采样100-500张,覆盖各种光照、角度、背景。

小技巧:在ARM Cortex-M上跑TFLite Micro时,记得开启--use_xnnpack优化。虽然XNNPACK主要针对移动端,但它的量化卷积实现比默认的参考实现快2-3倍。我在Cortex-A55上实测过,确实有效。

4.4 量化后的部署:一个真实案例

去年我做了一个口罩检测项目,目标平台是瑞萨RA6M4(Cortex-M4内核)。浮点模型大小1.2MB,RAM占用300KB。MCU的Flash只有1MB,RAM 256KB。根本跑不了。

量化到int8后:

  • 模型大小:1.2MB → 320KB(压缩75%)
  • RAM占用:300KB → 80KB(减少73%)
  • 推理速度:一帧2.1s → 一帧0.35s(加速6倍)
  • 精度损失:mAP从0.89降到0.85(可接受)

为什么会这样?因为int8量化把权重和激活值都压缩到1字节,内存带宽需求大幅降低。ARM的SIMD指令(比如SMLAD)还能一次处理4个int8乘法累加。你想想看,这效率能不高吗?

4.5 知识体系图

下面这张图是我自己整理的量化知识体系,帮你理清脉络:

算法量化知识体系 浮点 vs 定点 量化误差分析 ARM量化工具 表示范围:±3.4e38 运算周期:~14 cycles 内存占用:4字节 表示范围:[-1, 0.992] 运算周期:1 cycle 内存占用:1字节 截断误差:步长Δ/2 溢出误差:超出范围 累积误差:逐层放大 校准数据集:100-500张 TFLite Micro 训练后量化(PTQ) 量化感知训练(QAT) XNNPACK优化 精度 ↔ 速度 ↔ 内存 三角博弈

4.6 写在最后

量化不是银弹。我见过有人把模型量化到int4,精度掉得一塌糊涂,还怪工具不好。其实是你没理解量化误差的本质。记住三点:

  • 先跑校准集,摸清数据分布
  • 优先用int8,int4留给极端场景
  • ARM平台下,TFLite Micro + XNNPACK是黄金组合

好了,这一章就到这里。量化是个实践活,多踩坑才能长记性。


专注资料整理