2. 基础知识

TinyML 模型在 PC 端训练与验证时,通常使用 32 位浮点数(FP32)进行张量计算和梯度更新。然而,当模型被部署到微控制器(MCU)或嵌入式 NPU 上时,受限于存储带宽、SRAM 容量以及缺乏 FPU(浮点运算单元)的算力约束,模型权重和激活值往往需要转换为定点数或低比特浮点数(如 FP16、INT8)。这种数值表示的转换,是导致 PC 端输出与板端输出产生偏差的根源之一。理解浮点数、定点数与量化之间的数学关系与误差传播机制,是进行精度回归测试、定位对齐偏差的第一项基本功。

数值表示与精度损失链路 FP32 基准 PC 端训练/验证 动态范围大, 精度高 定点数 / INT8 MCU 部署主流 固定小数位, 范围受限 FP16 / bf16 带 FPU 的 MCU 指数位减少, 尾数截断 量化误差来源 ① 舍入误差 (Round) 浮点→定点时尾数截断 ② 饱和误差 (Clamp) 超出动态范围被截断 ③ 缩放因子误差 零点与 scale 的校准偏差 影响:输出 logits 偏移 → 分类结果翻转 回归测试需设定容差阈值 (如 cosine > 0.99)

2.1 核心原理:从 FP32 到 INT8 的数值映射

在 PC 端,PyTorch 或 TensorFlow 默认使用 FP32 表示张量。FP32 由 1 位符号位、8 位指数位和 23 位尾数位组成,能够表示约 \(1.18 \times 10^{-38}\) 到 \(3.4 \times 10^{38}\) 的动态范围,且相对精度约为 \(10^{-7}\)。而 INT8 仅有 8 位,只能表示 256 个离散整数(通常为 -128 到 127 或 0 到 255)。将 FP32 映射到 INT8 的过程称为量化,其核心公式为:

real_value = (int8_value - zero_point) × scale

其中 scale 是一个浮点数,表示每个量化步长对应的实际数值大小;zero_point 是一个 INT8 整数,表示实数 0 对应的量化值。这种映射方式称为非对称量化,适用于激活值(通常非负分布)。对于权重,由于分布近似对称,常采用对称量化(zero_point=0)。

量化误差主要来自两个方面:舍入误差(将连续浮点数映射到离散整数时产生的精度损失)和截断误差(当浮点数超出 INT8 可表示范围时被强制截断为最大值或最小值)。例如,若某层激活值范围为 [0, 6.0],则 scale = 6.0/255 ≈ 0.0235。一个实际值 3.14159 将被量化为 round(3.14159/0.0235) = 134,反量化后为 134 × 0.0235 = 3.149,误差约 0.0074。这个误差在单层看似微小,但经过多层累加后,可能被放大到足以改变最终分类结果。

关键认知:量化不是简单的“精度降低”,而是引入了一个非线性映射过程。该过程会改变张量的分布特性,尤其是当激活值存在长尾分布或极端离群值时,量化误差会显著增大。因此,在精度回归测试中,不能仅比较最终准确率,还需要逐层分析中间张量的数值偏差。

2.2 判断方法:如何评估数值表示对精度的影响

在 TinyML 精度回归测试中,我们需要系统性地评估不同数值表示对模型输出的影响。以下是标准化的评估流程:

  1. 建立基准输出:使用 PC 端 FP32 模型对一组固定测试样本进行推理,保存每一层的输出张量(logits、中间激活值)作为“黄金参考”。
  2. 生成量化模型:使用 TensorFlow Lite for Microcontrollers 或 STM32Cube.AI 等工具,将模型量化为 INT8 或 FP16,并部署到目标板卡。
  3. 逐层对比:在板端运行相同测试样本,导出各层输出,与 FP32 基准计算余弦相似度(Cosine Similarity)或均方误差(MSE)。
  4. 设定容差阈值:通常要求最终输出的余弦相似度 > 0.99,或 Top-1 分类结果一致率 > 95%。对于回归任务(如预测连续值),则要求归一化均方根误差(NRMSE)< 5%。
数值格式位宽动态范围相对精度典型应用场景精度损失风险
FP3232~1e-38 ~ 3e38~1e-7PC 端训练与验证基准,无损失
FP1616~6e-5 ~ 6e4~1e-3带 FPU 的 MCU(如 Cortex-M7)低,但可能溢出
INT88-128 ~ 127~1/256绝大多数 MCU 与 NPU中高,需校准
定点数 Q1516-1.0 ~ 0.9999~3e-5CMSIS-DSP 库常用中,需防溢出

2.3 案例拆解:一个图像分类模型的量化偏差定位

假设我们有一个在 PC 上训练好的 MobileNetV1 图像分类模型(FP32,Top-1 准确率 85%),部署到 STM32F746 开发板上,使用 TFLite Micro 进行 INT8 量化推理。在精度回归测试中,发现板端 Top-1 准确率下降至 72%,且部分样本的 softmax 输出概率分布与 PC 端差异明显。

初始现象:对 1000 张测试图片进行对比,发现 130 张图片分类结果不一致。进一步分析,这些不一致样本的 PC 端输出概率通常在 0.4~0.6 之间(即置信度较低),而板端输出概率则偏向 0.2 或 0.8。

分析过程:我们首先检查了量化配置。该模型使用默认的“动态范围量化”,即仅对权重进行 INT8 量化,激活值仍为 FP32。但 TFLite Micro 在 MCU 上实际执行时,激活值也被强制转换为 INT8(因为 MCU 缺乏 FPU)。这导致激活值的量化误差被引入。我们使用 TensorFlow 的 representative_dataset 重新校准了量化参数,发现原始校准集仅包含 100 张图片,且未覆盖光照变化较大的场景,导致 scale 值偏大,激活值量化后信息丢失严重。

实际操作步骤:

  1. 将校准集扩充至 1000 张,覆盖不同光照、角度和背景的图片。
  2. 使用 tf.lite.TFLiteConverter 重新生成量化模型,并设置 optimizations=[tf.lite.Optimize.DEFAULT]。
  3. 在板端重新部署,并导出第 10 层(深度可分离卷积层)的输出张量,与 PC 端 FP32 输出计算余弦相似度。
  4. 结果显示,余弦相似度从 0.93 提升至 0.98,最终板端准确率回升至 82%。

结果对比与复盘:剩余的 3% 准确率差距主要来自 BatchNorm 层的折叠误差。在量化过程中,BatchNorm 的参数被折叠进卷积权重,但折叠操作在 FP32 下进行,随后才量化为 INT8,这引入了额外的舍入误差。我们通过将 BatchNorm 层显式保留为量化层(而非折叠)解决了该问题,最终准确率稳定在 84%。

风险提示:在量化过程中,不要盲目追求更低的位宽(如 INT4),除非模型经过量化感知训练(QAT)。后训练量化(PTQ)对分布敏感,尤其对于 MobileNet 这类深度可分离卷积网络,其激活值分布较宽,容易产生较大的量化误差。

2.4 常见误区与总结复盘

误区一:认为 FP16 一定比 INT8 精度高。实际上,FP16 虽然动态范围大于 INT8,但其尾数位仅 10 位,对于大数值(如 > 1024)的表示精度反而低于 INT8。在 TinyML 场景中,若模型权重范围在 [-1, 1] 之间,INT8 的精度(1/256 ≈ 0.0039)优于 FP16(在 1.0 附近精度约为 0.0005,但在 1000 附近精度约为 0.5)。因此,选择数值格式需结合具体张量分布。

误区二:忽略 zero_point 的影响。在对称量化中,zero_point 固定为 0,但非对称量化中,zero_point 的微小偏差会导致整体偏置。例如,若 zero_point 计算错误(偏差 1),则所有反量化值都会偏移一个 scale 单位,该误差在多层累加后可能被放大。

误区三:仅比较最终准确率,不检查中间层。最终准确率相同不代表中间层输出一致。两个模型可能在某些样本上得到相同的分类结果,但中间层特征图差异巨大,这会导致模型在对抗样本或分布外数据上表现迥异。精度回归测试必须包含逐层对比。

总结复盘:数值表示是 TinyML 精度回归测试的基石。工程师需要掌握以下核心技能:① 能够计算量化参数(scale/zero_point);② 能够使用工具(如 TFLite、PyTorch Quantization)生成量化模型;③ 能够编写脚本逐层对比 PC 与板端输出;④ 理解不同数值格式的误差边界。建议在项目初期就建立自动化的精度回归流水线,将 PC 端 FP32 输出保存为二进制文件,板端测试时直接加载并对比,避免每次测试都重新生成基准。

经验之谈:在 TinyML 项目中,建议保留一份“黄金样本集”(约 100~500 个样本),包含典型的边界情况(如低光照、遮挡、运动模糊)。每次修改量化配置或数值格式后,都运行该样本集进行回归测试。若余弦相似度下降超过 0.01,应视为重大变更,需要重新评估部署方案。