2. TinyML基础与性能瓶颈

TinyML 的核心挑战在于:我们试图在功耗预算仅为毫瓦级、内存以 KB 计、主频不过百 MHz 的微控制器(MCU)上,运行原本为 GPU/服务器设计的神经网络推理任务。理解这一矛盾的根源,是进行算子级 Profiling 的前提。本章将系统梳理 MCU 的资源天花板、典型 TinyML 模型的结构特征,以及衡量性能的三个核心维度——延迟、内存与能耗,为后续定位热点算子建立统一的度量坐标系。

MCU 资源约束 典型模型结构 性能关键指标 Flash: 256KB - 1MB 存储权重与代码 SRAM: 64KB - 512KB 激活值/中间张量/运行时 CPU: 48-240 MHz Cortex-M0/M4/M33/RISC-V 无 DDR / 无缓存 直接访问 Flash 与 SRAM 功耗: 1-50 mW 电池供电/能量采集 深度可分离卷积 MobileNet 核心单元 1x1 卷积 (Pointwise) 通道信息融合/降维 Global Average Pooling 替代全连接层 量化 (INT8/INT16) 权重/激活值定点化 残差连接 (Residual) 梯度流动/特征复用 参数量: 0.1M - 5M 延迟 (Latency) 单次推理耗时 (ms) 内存占用 (Memory) Flash + SRAM 峰值 能耗 (Energy) mJ/推理 或 mAh/千次 吞吐量 (Throughput) FPS / 每秒操作数 MACs / 算子占比 Profiling 核心输出 资源约束决定模型结构选择,模型结构决定性能指标分布

2.1 微控制器资源限制:性能分析的物理边界

与桌面级 CPU 或移动端 SoC 不同,MCU 的设计哲学是"够用就好"。这种极简主义带来了成本与功耗的优势,但也为神经网络推理设置了严苛的物理边界。理解这些边界,是判断一个算子"为什么慢"、"为什么占内存"的基础。

核心原理:存储层级与计算单元的差异

典型的 TinyML 目标平台(如 STM32F746、ESP32-S3、nRF5340)具有以下共性特征:

  • Flash(非易失存储):用于存放模型权重、推理代码和常量表。容量通常在 256KB 到 2MB 之间。权重以 INT8 格式存储时,1MB Flash 最多容纳约 100 万个参数。
  • SRAM(易失存储):用于存放输入/输出张量、中间激活值、算子运行时缓冲区。容量通常只有 Flash 的 1/4 到 1/2。这是最稀缺的资源,因为激活值无法压缩,且生命周期跨越整个推理过程。
  • CPU 与 DSP 扩展:Cortex-M4/M33 系列带有单精度 FPU 和可选的 DSP 指令(SIMD),但缺乏高吞吐的矩阵乘法单元。这意味着卷积运算需要依赖软件循环展开和指令级优化。
  • 无缓存层级:MCU 通常没有 L1/L2 数据缓存,CPU 直接通过总线访问 SRAM 和 Flash。这消除了缓存未命中的问题,但也意味着每次内存访问的延迟是确定且较高的(通常 2-4 个时钟周期)。
Profiling 启示:在 MCU 上,算子的计算时间与内存访问时间高度耦合。一个看似计算量很大的算子,如果数据能保持在寄存器或紧耦合 SRAM 中,可能比一个计算量小但频繁访问 Flash 的算子更快。因此,算子级 Profiling 必须同时测量"计算周期"和"内存访问周期"。

判断方法:资源预算的快速评估

在开始 Profiling 之前,建议先完成以下资源预算检查,以确定优化空间的上限:

  1. 权重体积估算:模型参数量 × 量化位数(如 INT8 为 1 字节)≤ Flash 容量的 70%(预留代码空间)。
  2. 激活峰值估算:找到模型中最大的中间张量(通常是第一个卷积层的输出),其大小 × 2(若需要双缓冲)≤ SRAM 容量的 60%。
  3. 计算量估算:总 MACs 数 ÷ CPU 有效 MAC 吞吐率(如 Cortex-M4 在 100MHz 下约 50-100 MMAC/s)≤ 目标延迟。
资源类型典型容量范围对 Profiling 的影响瓶颈特征
Flash256KB - 2MB决定模型能否完整部署权重无法全部加载时需分块读取
SRAM64KB - 512KB决定激活值能否驻留内存不足导致算子拆分或溢出
CPU 主频48 - 240 MHz决定计算吞吐上限高 MACs 算子耗时显著
总线带宽32-bit @ 主频决定数据搬运速度大张量搬运成为隐藏瓶颈

2.2 典型模型结构:TinyML 算子的分布规律

TinyML 模型并非简单地将大模型缩小,而是采用了一系列结构上的创新,以适配 MCU 的资源约束。这些结构直接决定了算子级 Profiling 时应当关注的重点。

核心原理:深度可分离卷积的主导地位

以 MobileNetV1/V2、EfficientNet-Lite 为代表的轻量级模型,其核心是深度可分离卷积(Depthwise Separable Convolution)。它将标准卷积分解为两个步骤:

  • Depthwise 卷积:每个输入通道使用独立的卷积核进行空间滤波,计算量为 K×K×C×H×W(K 为核大小,C 为通道数,H/W 为特征图尺寸)。
  • Pointwise 卷积(1x1):使用 1x1 卷积核进行通道间的线性组合,计算量为 C_in×C_out×H×W。

这种分解将标准卷积的计算量从 K×K×C_in×C_out×H×W 降低到 K×K×C_in×H×W + C_in×C_out×H×W,通常减少 8-9 倍。但这也意味着:Pointwise 卷积占据了模型绝大部分的 MACs(通常 80% 以上),而 Depthwise 卷积虽然计算量小,但内存访问模式不规则,容易成为访存瓶颈。

判断方法:识别模型中的热点算子类型

在 Profiling 时,可以按以下优先级关注算子:

  1. Pointwise 卷积:计算密集,应重点优化 MAC 利用率。
  2. Depthwise 卷积:访存密集,应关注数据复用和内存带宽。
  3. 激活函数(ReLU/ReLU6):计算量小,但若使用浮点实现,可能引入不必要的转换开销。
  4. 池化/上采样:通常不是瓶颈,但在某些模型中可能因内存访问模式导致延迟异常。
  5. 拼接(Concat):不产生计算,但涉及大量内存拷贝,在 SRAM 紧张时可能触发溢出。
实战经验:在 STM32F746 上运行 MobileNetV1(1.0, 224x224, INT8)时,Pointwise 卷积占总延迟的 65%,Depthwise 卷积占 25%,其余算子占 10%。优化 Pointwise 卷积的循环展开和寄存器重排,比优化其他所有算子收益更大。

案例拆解:一个典型 Keyword Spotting 模型的 Profiling 预分析

假设我们有一个基于 DS-CNN(Depthwise Separable Convolutional Neural Network)的关键词识别模型,输入为 49x10 的 MFCC 特征图,模型包含 5 个深度可分离卷积块。在 Profiling 之前,我们进行如下预分析:

  • 参数量:约 40K 参数,INT8 量化后占用 40KB Flash,完全可容纳。
  • 激活峰值:第一个卷积层输出为 49x10x64,约 31KB,加上输入和后续层,SRAM 峰值约 50KB,在 128KB SRAM 的 MCU 上可行。
  • 计算量:总 MACs 约 5.6M,在 100MHz Cortex-M4 上理论延迟约 56ms(假设 100% MAC 利用率),实际可能达到 80-120ms。

预分析结论:Pointwise 卷积是首要优化目标,因为其 MACs 占比超过 85%。Profiling 时应重点测量每个 Pointwise 层的实际周期数,并与理论值对比,找出利用率低的层。

2.3 性能关键指标:延迟、内存与能耗的度量体系

算子级 Profiling 的核心输出是量化每个算子的资源消耗。这三个指标相互关联,但优化时往往需要权衡。

核心原理:三类指标的物理含义

  • 延迟(Latency):从输入张量进入算子到输出张量就绪的时间,通常以毫秒(ms)或 CPU 周期数计。延迟是用户体验的直接体现,也是 Profiling 中最容易测量的指标。
  • 内存(Memory):分为静态内存(权重、常量)和动态内存(激活值、临时缓冲区)。峰值 SRAM 占用决定了模型能否在目标设备上运行。
  • 能耗(Energy):单次推理消耗的能量,单位通常为毫焦耳(mJ)。在电池供电设备中,这直接决定了续航时间。能耗 ≈ 平均功耗 × 延迟,而平均功耗与 CPU 活动、内存访问频率、外设状态相关。

判断方法:建立 Profiling 的度量基线

在进行算子级 Profiling 时,建议采用以下流程建立基线:

  1. 端到端测量:使用 DWT->CYCCNT 寄存器(Cortex-M 内核)或定时器捕获完整推理的周期数。
  2. 算子级插桩:在每个算子入口/出口插入时间戳记录,计算各算子耗时占比。
  3. 内存跟踪:通过链接器映射文件(.map)和运行时堆栈分析,确定各算子的 SRAM 峰值。
  4. 能耗估算:使用开发板上的电流采样电阻或高精度功耗分析仪(如 Joulescope),测量不同算子运行时的电流波形。
指标测量工具/方法单位优化目标常见陷阱
延迟DWT->CYCCNT, ARM DSP库周期/ms降低总周期数中断干扰导致测量偏差
内存.map 文件, 静态分析KB降低峰值 SRAM忽略对齐填充和临时缓冲区
能耗功耗分析仪, 电流探头mJ降低 mJ/推理外设功耗与 CPU 功耗混淆
MAC 利用率周期数 ÷ (MACs ÷ 主频)%接近 100%未考虑内存等待周期

案例拆解:延迟与能耗的权衡分析

考虑一个图像分类模型在 Cortex-M33(带 DSP 扩展)上的优化过程。初始 Profiling 显示:

  • 总延迟:45ms
  • 平均电流:12mA @ 3.3V
  • 单次推理能耗:12mA × 3.3V × 0.045s = 1.78 mJ

通过算子级 Profiling 发现,第一个 Pointwise 卷积层(输入 112x112x32,输出 112x112x64)占用了 15ms(33% 延迟)。分析其汇编代码,发现存在大量非对齐的 LDR 指令,导致总线等待。优化方案:

  1. 将输入张量在 SRAM 中按 4 字节对齐重排。
  2. 使用 ARM CMSIS-NN 库的 arm_convolve_HWC_q7_fast 函数替代手写循环。
  3. 启用循环展开(#pragma unroll 4)。

优化后,该层延迟降至 8ms,总延迟降至 35ms。由于 CPU 活跃时间减少,平均电流降至 10.5mA,单次推理能耗降至 1.21 mJ,降低 32%。

风险提示:优化延迟并不总是线性降低能耗。如果优化导致代码体积增大,Flash 访问频率上升,可能反而增加能耗。此外,某些优化(如增加并行缓冲区)会提高 SRAM 峰值,可能导致内存不足。每次优化后应同时复测三个指标。

常见误区与总结复盘

  • 误区一:只关注 MACs 数量。在 MCU 上,内存访问往往比计算更耗时。一个 MACs 少但访存不规则的算子可能比 MACs 多但访存连续的算子更慢。
  • 误区二:忽略量化效应。INT8 量化后,算子的延迟特性与浮点完全不同。例如,INT8 的 Depthwise 卷积可能受限于 8 位乘法器的吞吐,而浮点版本则受限于 FPU 流水线。
  • 误区三:将能耗等同于延迟。虽然能耗 ≈ 功耗 × 延迟,但不同算子的功耗差异很大。例如,访问 Flash 的功耗远高于访问 SRAM,因此一个频繁读取权重的算子即使延迟不高,也可能能耗较高。
  • 误区四:忽视编译器的"魔法"。编译器(如 arm-none-eabi-gcc -O3)可能自动进行循环优化,导致 Profiling 结果与源码级预期不符。建议在 Profiling 时查看反汇编代码,确认实际执行的指令序列。