3、TinyML模型基础:掌握神经网络模型的基本组成(权重、激活、中间张量)及其在MCU上的存储方式

在GD32系列MCU上部署TinyML模型之前,必须首先理解神经网络模型在内存中的真实形态。许多开发者初次接触TinyML时,往往将注意力集中在算法精度上,却忽略了模型在资源受限设备上的存储与运行机制。实际上,一个神经网络模型在MCU上运行时,其内存占用由三部分构成:权重(Weights)、激活值(Activations)和中间张量(Intermediate Tensors)。这三者分别对应模型的静态参数、动态计算状态和临时缓冲区,它们在Flash和RAM中的分配方式直接决定了模型能否在GD32上成功运行。本章将深入剖析这三个核心组成部分,并阐明它们在MCU存储体系中的具体存放位置与优化策略。

神经网络模型在MCU上的存储与运行架构 输入张量 隐藏层(Conv/Dense) 输出张量 权重(Weights)— 存储在 Flash Conv2D 3×3×32 Dense 128 BatchNorm 参数 偏置 Bias 激活值 & 中间张量 — 存储在 RAM 特征图(Feature Map) 池化输出 全连接中间结果 GD32 Flash 容量: 64KB~512KB 只读,掉电不丢失 存放权重+代码 GD32 SRAM 容量: 16KB~96KB 读写,掉电丢失 存放激活+中间张量 内存优化核心矛盾:Flash 容量决定模型大小上限,RAM 容量决定网络深度与输入分辨率上限 量化(int8)→ Flash↓ | 内存复用 → RAM↓ | 输入裁剪 → 中间张量↓ GD32F303: 256KB Flash / 48KB SRAM 可运行 2~5 层小型CNN

3.1 核心原理:神经网络模型的三大内存实体

一个典型的神经网络模型(以卷积神经网络CNN为例)在推理过程中,其内存占用可以分解为三个层次。理解这三个层次是进行内存优化的前提,因为它们分别对应不同的存储介质和生命周期。

  • 权重(Weights):这是模型训练后得到的固定参数,包括卷积核的数值、全连接层的矩阵系数、BatchNorm层的缩放因子与偏移量,以及偏置项。权重是模型的"知识"所在,其体积通常占模型总大小的90%以上。在MCU上,权重必须存放在Flash中,因为它们需要持久保存且只读访问。例如,一个MobileNetV1模型(输入224×224)的权重约为4.2MB(float32),而经过int8量化后可以压缩到约1.1MB。
  • 激活值(Activations):这是网络每一层在前向传播时产生的输出特征图。激活值具有临时性——当某一层计算完成后,其输出作为下一层的输入,但一旦下一层计算完毕,该激活值就不再需要(除非有残差连接)。激活值的大小与输入分辨率、通道数和网络深度直接相关。例如,一个输入为32×32×3的图像经过第一层16通道的3×3卷积后,会产生32×32×16=16384个激活值,若以float32存储则占用64KB,这已经接近许多MCU的RAM上限。
  • 中间张量(Intermediate Tensors):这是指在计算过程中临时生成的缓冲区,包括卷积运算的im2col矩阵、池化操作的滑动窗口缓存、以及某些算子(如Softmax)的中间结果。中间张量通常比激活值更短暂,往往在单个算子内部产生并立即释放。在内存优化中,中间张量往往被忽略,但在某些复杂模型中,它们可能占用与激活值相当的空间。

从存储介质的角度看,权重对应Flash,激活值和中间张量对应RAM。GD32系列MCU的典型配置为:Flash 64KB~512KB,SRAM 16KB~96KB。这意味着一个float32的模型权重若超过Flash容量,则根本无法烧录;而激活值若超过SRAM容量,则推理过程中会发生内存溢出(HardFault)。

内存实体存储介质生命周期典型大小占比优化手段
权重Flash持久(整个推理过程)模型总大小90%+量化(int8/int16)、剪枝、权重共享
激活值SRAM逐层产生与释放峰值可达RAM的60%~80%内存复用、减少通道数、降低输入分辨率
中间张量SRAM(栈或堆)单个算子内部通常<10%但不可忽略算子融合、避免im2col、使用就地运算

3.2 判断方法:如何估算模型在GD32上的内存占用

在选定一个模型架构后,开发者需要快速估算其内存需求,以判断是否能在目标GD32型号上运行。以下是标准估算流程:

  1. 计算权重体积:遍历模型每一层,统计参数数量。对于卷积层,参数数 = 输出通道 × 输入通道 × 卷积核高 × 卷积核宽 + 输出通道(偏置)。将所有层参数相加后乘以每个参数的字节数(float32为4字节,int8为1字节),即可得到Flash占用。
  2. 计算峰值激活值:逐层计算每一层输出的特征图大小(高×宽×通道数),取所有层中的最大值。该值乘以每个激活值的字节数,即为RAM中激活值的最小需求。注意,如果网络中存在残差连接(如ResNet),则需要同时保存两条路径的激活值,此时峰值会更高。
  3. 估算中间张量:对于卷积层,如果使用im2col算法,需要额外分配一个矩阵,其大小为(卷积核元素数 × 输出像素数)。例如,3×3卷积、输入32×32×16、输出32×32×32,im2col矩阵大小为(3×3×16)×(32×32)= 147456个元素,float32下占用576KB——这显然不可接受。因此,在MCU上通常采用直接卷积或间接卷积算法,避免im2col。
  4. 加上运行时开销:包括RTOS任务栈、全局变量、以及推理框架(如CMSIS-NN)自身的静态缓冲区。一般预留10%~20%的RAM作为安全余量。

实战估算示例:假设要在GD32F303(256KB Flash, 48KB SRAM)上部署一个二分类CNN:输入32×32×1,两层卷积(16通道3×3,32通道3×3),一层全连接(128神经元),输出2类。

  • 权重:第一层Conv: 3×3×1×16+16=160;第二层Conv: 3×3×16×32+32=4640;全连接: 32×32×32×128+128=4,194,304。总参数约4.2M,float32下16.8MB——远超Flash。改用int8量化后约4.2MB,仍然超限。因此需要进一步压缩:将全连接改为全局平均池化+1×1卷积,参数降至约5K,int8下仅5KB。
  • 激活值:第一层输出32×32×16=16K;第二层输出32×32×32=32K;全连接前特征图32×32×32=32K。峰值32K个激活值,int8下32KB,加上中间张量约8KB,总RAM需求约40KB,接近48KB上限但可行。

结论:该模型经过int8量化+结构简化后,可以在GD32F303上运行,但RAM余量较小,需要谨慎管理内存分配。

3.3 案例拆解:GD32上部署KWS(关键词唤醒)模型的存储优化实战

以语音关键词唤醒(KWS)模型为例,这是一个典型的TinyML应用场景。我们选用一个包含2层卷积+2层全连接的小型网络,输入为40×40的MFCC特征图(即40帧×40维MFCC系数)。

初始状态:模型使用float32训练,权重大小约1.8MB,激活值峰值约120KB。显然,这在GD32F303(256KB Flash/48KB SRAM)上无法运行。

优化步骤:

  1. 权重量化:使用TensorFlow Lite的post-training int8量化,将权重从float32转为int8。权重降至450KB,Flash占用可接受。
  2. 激活值裁剪:将输入MFCC特征从40×40裁剪为30×30,第一层卷积输出从30×30×16降至30×30×8,激活值峰值从120KB降至约30KB(int8)。
  3. 内存复用:在推理代码中,将第一层卷积的输出缓冲区与第二层卷积的输入缓冲区指向同一块内存(因为第一层输出在第二层计算后不再需要)。通过这种方式,将激活值峰值从30KB进一步压缩至22KB。
  4. 中间张量消除:将全连接层改为全局平均池化+1×1卷积,避免了大矩阵乘法所需的临时缓冲区。

结果对比:优化后模型Flash占用为412KB(int8),SRAM峰值占用为26KB(含中间张量),在GD32F303上稳定运行,推理时间约85ms,满足实时唤醒需求。

复盘经验:本案例的关键在于先量化权重解决Flash瓶颈,再通过结构简化和内存复用解决RAM瓶颈。如果一开始就尝试减少层数,可能会牺牲精度;而量化+裁剪的组合在保持95%以上准确率的同时,实现了约7倍的内存压缩。

3.4 常见误区与总结复盘

在TinyML模型存储优化中,开发者常陷入以下误区:

误区一:只关注权重大小,忽略激活值。许多开发者认为模型大小等于Flash占用,只要Flash放得下就能运行。实际上,激活值超限会导致推理过程中HardFault或随机数据损坏。在GD32上,RAM往往比Flash更稀缺。

误区二:盲目使用float32。GD32F3系列虽然支持FPU,但float32的存储开销是int8的4倍。在TinyML场景中,int8量化通常能保持95%以上的精度,而内存占用降低75%。

误区三:忽略中间张量的存在。某些算子(如Softmax、LSTM)会隐式分配大块临时内存。例如,Softmax需要保存所有输入值以计算分母,若输入为128维float32,则临时占用512字节,看似不大,但在多层叠加后可能累积。

总结复盘:在GD32上部署TinyML模型,必须建立"Flash存权重、RAM存激活"的清晰认知。优化优先级应为:① 权重量化(int8)→ ② 激活值峰值裁剪(降低输入分辨率/减少通道)→ ③ 内存复用(共享缓冲区)→ ④ 算子融合(消除中间张量)。同时,建议使用CMSIS-NN或GD32提供的DSP库,这些库针对Cortex-M4内核优化了卷积和全连接算子,能同时降低内存占用和推理时间。最后,务必在目标硬件上进行压力测试,使用malloc失败计数或栈高水位标记来验证内存余量。

经验提示:在工程实践中,建议先使用Python端工具(如TensorFlow Lite Model Analyzer)生成模型的内存报告,再结合GD32的Linker Map文件核对实际分配。如果发现RAM不足,优先考虑将输入分辨率减半(如从64×64降至32×32),这通常能减少75%的激活值,而精度损失往往在可接受范围内。