3、TinyML模型基础:神经网络模型在MCU上的表示形式、量化类型(INT8/FP16)与算子类型
在MCU上部署神经网络,本质上是在“资源极度受限”与“计算需求刚性”之间寻找工程平衡点。与云端GPU或边缘Linux设备不同,MCU通常只有几十KB到几MB的Flash和RAM,主频在几十MHz到几百MHz之间,且没有操作系统或仅有轻量级RTOS。因此,神经网络模型在MCU上的表示形式、数值精度和算子实现方式,直接决定了模型能否跑起来、跑多快、功耗多低。本章将系统拆解这三个核心维度,并给出内存占用的量化估算方法。
上图展示了本章的核心知识框架:模型的表示形式决定了它如何被存储和解析;量化类型决定了数值精度与内存/计算效率的取舍;算子类型则直接关联到Flash占用和推理延迟。三者相互耦合,必须在项目初期就统一规划。
3.1 神经网络模型在MCU上的表示形式
3.1.1 核心原理:从训练框架到MCU可执行格式
在PC端训练好的模型(如Keras的.h5或PyTorch的.pt)不能直接在MCU上运行。MCU上通常使用TensorFlow Lite for Microcontrollers (TFLM)作为推理引擎,其模型格式为FlatBuffer序列化的.tflite文件。FlatBuffer是一种零拷贝的序列化库,允许在无动态内存分配的情况下直接读取权重和算子图结构。模型文件在编译时被转换为C语言数组(const unsigned char model[]),嵌入到固件中,随固件一起烧录到Flash。
这种表示形式的核心优势在于:零解析开销。MCU上不需要像Linux那样加载动态库或解析JSON,而是直接通过指针访问模型数据。TFLM的MicroInterpreter在初始化时遍历图结构,为每个算子分配内存,并建立执行计划。
3.1.2 判断方法:如何选择表示形式
选择表示形式时,主要考虑以下三点:
- Flash容量:模型数组直接占用Flash。若Flash紧张,需考虑量化或模型剪枝。
- RAM预算:TFLM的
MicroAllocator会为中间激活值分配RAM。模型图结构越复杂,需要的临时缓冲区越多。 - 算子支持:TFLM只支持有限算子集(如CONV_2D、DEPTHWISE_CONV_2D、FULLY_CONNECTED、ADD、MUL、SOFTMAX等)。若模型包含不支持的算子(如LSTM),需手动实现或替换。
一个实用的判断流程是:先用TFLM的模型转换工具将模型转为.tflite,然后使用xxd -i命令生成C数组,最后通过TFLM的OpResolver检查算子兼容性。
3.1.3 案例拆解:将Keras模型部署到Cortex-M4
假设我们有一个用于关键词唤醒的2D卷积模型,输入为49×10的MFCC特征图,包含3个卷积层和1个全连接层。在PC上模型大小为180KB(FP32)。部署到具有512KB Flash和128KB RAM的Cortex-M4 MCU上时,我们执行以下步骤:
- 使用TFLite Converter将Keras模型转为.tflite,并指定
optimizations=[tf.lite.Optimize.DEFAULT],启用INT8量化。 - 转换后模型大小降至约48KB(INT8权重)。
- 使用
xxd -i生成C数组,并嵌入到固件工程中。 - 在MCU上初始化
MicroInterpreter,分配约32KB的RAM用于中间激活值(基于输入尺寸和层间张量大小估算)。
结果:模型成功运行,推理延迟为85ms,Flash占用总计约120KB(模型48KB + 运行时和算子代码72KB),RAM峰值约40KB。若使用FP16量化,模型大小约90KB,但延迟会降至60ms(因为Cortex-M4支持FP16硬件加速),但RAM占用会增至约60KB。
tflite_convert工具生成多种量化版本的模型,并逐一在目标MCU上基准测试。
3.2 量化类型:INT8与FP16的工程取舍
3.2.1 核心原理:数值精度与资源占用
量化是将浮点数值映射到低精度整数或半精度浮点的过程。在MCU上,主要使用两种量化类型:
- INT8量化:每个权重和激活值占用1字节。通过仿射变换将浮点范围映射到[-128, 127]。需要提供校准数据集来统计激活值的动态范围。推理时使用整数乘加运算,速度极快,但精度损失通常在1%-3%以内。
- FP16量化:每个权重占用2字节,使用半精度浮点格式(1位符号+5位指数+10位尾数)。精度损失极小,且Cortex-M7/M33等内核支持FP16硬件指令,计算速度接近FP32。
内存占用公式:模型Flash占用 = 权重数量 × 量化位宽 / 8。例如,一个100万参数的模型,FP32占用4MB,INT8占用1MB,FP16占用2MB。
3.2.2 判断方法:如何选择量化类型
选择量化类型时,需评估以下指标:
| 维度 | INT8 | FP16 | FP32(基准) |
|---|---|---|---|
| 权重大小 | 1字节 | 2字节 | 4字节 |
| RAM占用(激活值) | 低 | 中 | 高 |
| 推理速度 | 最快(整数运算) | 快(硬件加速) | 慢(软件浮点) |
| 精度损失 | 1%-3% | <0.5% | 无 |
| 硬件要求 | 任何MCU | 需FP16指令集 | 需FPU |
| 校准需求 | 需要校准数据集 | 无需 | 无需 |
判断流程:首先检查MCU是否支持FP16硬件加速(查看内核型号和CMSIS-DSP文档)。若支持,且Flash余量充足,优先选择FP16;若Flash紧张或MCU无FPU,则选择INT8。
3.2.3 实战场景:传感器数据分析模型
考虑一个用于工业振动分析的模型,输入为512点FFT频谱,模型为3层全连接网络(128-64-4),总参数约9,000个。目标MCU为STM32L4系列(Cortex-M4,支持FP16但不支持硬件整数向量化)。
初始方案使用FP32,模型Flash占用36KB,RAM占用约20KB(激活值),推理延迟为12ms。但Flash总预算仅剩40KB,无法容纳其他固件模块。我们改用INT8量化:
- 收集1000条振动数据作为校准集,使用TFLite转换工具进行INT8量化。
- 量化后模型Flash占用降至9KB,RAM占用降至5KB。
- 在MCU上测试,推理延迟降至8ms(整数运算更快)。
- 精度验证:分类准确率从98.2%降至97.5%,仍在可接受范围内。
最终方案采用INT8,释放了27KB Flash和15KB RAM,为其他功能留出空间。若使用FP16,模型Flash占用18KB,RAM占用10KB,延迟10ms,但Flash仍超预算。
3.3 算子类型与内存占用概览
3.3.1 核心原理:算子的计算与存储特征
不同算子对内存和计算的需求差异巨大。在MCU上,算子主要分为三类:
- 计算密集型:如CONV_2D、DEPTHWISE_CONV_2D、FULLY_CONNECTED。这些算子需要大量乘加运算,权重占用Flash,中间结果占用RAM。优化重点是减少权重存储和利用SIMD指令。
- 内存密集型:如RESHAPE、TRANSPOSE、CONCAT。这些算子不涉及复杂计算,但需要大量数据搬运,容易成为RAM带宽瓶颈。
- 逐元素操作:如ADD、MUL、RELU、SOFTMAX。计算简单,但需要频繁访问内存,适合用CMSIS-DSP优化。
内存占用概览:总RAM = 输入张量 + 所有中间激活值 + 输出张量。TFLM的MicroAllocator采用内存复用策略,即不同层的激活值可以共享同一块RAM区域(只要生命周期不重叠)。因此,实际RAM峰值通常远小于所有层激活值之和。
3.3.2 判断方法:如何估算内存占用
估算内存占用的步骤:
- 列出模型的每一层,记录输入/输出张量的形状和数据类型。
- 计算每层激活值大小:
张量元素数 × 量化位宽 / 8。 - 找出所有层中最大的激活值张量,作为RAM峰值下限。
- 加上输入和输出缓冲区,以及TFLM运行时的固定开销(约2-4KB)。
例如,一个输入为32×32×3的INT8模型,第一层卷积输出为30×30×8,激活值为7200字节;第二层输出为28×28×16,激活值为12544字节。由于TFLM复用内存,RAM峰值约为12544 + 输入3072 + 输出1024 + 运行时2048 ≈ 18.7KB。
3.3.3 案例拆解:图像分类模型的算子优化
我们为一个CIFAR-10分类模型(MobileNetV1风格)进行MCU适配。原始模型包含标准卷积、深度可分离卷积、平均池化和Softmax。在Cortex-M7上部署时,发现推理延迟为320ms,超出200ms的预算。
分析过程:
- 使用TFLM的profiler工具,发现标准卷积层占用70%的延迟。
- 检查算子实现,发现使用的是TFLM默认的参考实现,未启用CMSIS-NN优化。
- 启用CMSIS-NN的
arm_convolve_s8优化后,卷积层延迟降低至原来的1/4。 - 进一步将平均池化替换为全局平均池化(减少参数),并将Softmax替换为近似实现。
优化结果:
| 优化项 | 优化前 | 优化后 | 收益 |
|---|---|---|---|
| 卷积层延迟 | 224ms | 56ms | -75% |
| 总延迟 | 320ms | 152ms | -52% |
| Flash占用 | 210KB | 185KB | -12% |
| RAM占用 | 48KB | 42KB | -12.5% |
复盘经验:算子级别的优化往往比模型结构修改更直接有效。在MCU上,优先使用CMSIS-NN或厂商提供的DSP库,而不是依赖通用实现。
benchmark工具对每个算子进行单独计时。这能快速定位瓶颈算子,并指导后续优化方向。同时,注意检查算子是否支持INT8的per-channel量化,这能显著提高量化精度。
3.4 常见误区与总结复盘
3.4.1 常见误区
- 误区一:所有模型都能直接部署到MCU。实际上,模型必须经过转换、量化、算子兼容性检查,且可能需要进行结构修改(如将LSTM替换为GRU或1D卷积)。
- 误区二:INT8量化总是最好的。虽然INT8节省内存,但若MCU不支持硬件整数加速,推理速度可能反而比FP16慢。需根据具体内核测试。
- 误区三:忽略内存复用机制。有些开发者手动计算所有层激活值之和,导致RAM预算过高。实际上TFLM的内存复用可以大幅降低峰值。
- 误区四:只关注Flash和RAM,忽略延迟。内存优化可能增加数据搬运次数,反而导致延迟上升。需要联合优化。
3.4.2 总结复盘
本章的核心要点可以归纳为:
- 表示形式:使用TFLM的FlatBuffer格式,通过C数组嵌入固件,零拷贝解析。
- 量化类型:INT8适合Flash/RAM紧张且精度容忍度高的场景;FP16适合有硬件加速且需要更好精度的场景。
- 算子类型:计算密集型算子决定延迟,内存密集型算子决定RAM峰值,逐元素算子影响整体效率。
- 内存估算:使用TFLM的
MicroAllocator自动管理,但开发者需理解其复用机制,避免过度分配。
在实际项目中,建议采用以下工作流:先转换模型并生成多种量化版本,然后在目标MCU上进行基准测试,最后根据测试结果迭代优化。不要仅凭PC端模拟结果做决策,因为MCU的缓存、Flash等待状态和DMA行为都会影响实际性能。