在Cortex-M上跑一个轻量级神经网络,比想象中简单
2026/7/9 9:45:06 网站建设 项目流程

在Cortex-M上跑一个轻量级神经网络,比想象中简单

一个Cortex-M4内核的MCU,主频不到200MHz,RAM只有几百KB——这样的硬件真的能跑神经网络推理吗?

几年前这个问题确实不太好回答。但TensorFlow Lite for Microcontrollers(简称TFLM)的出现,让这件事从"不可能"变成了"看你怎么做"。

关键一步:量化

先把目光放到模型本身。我们在PC上用Keras训练好的模型,权重通常是float32。一个几万参数的模型,光权重就吃掉几百KB的Flash——更别说推理时中间张量占的RAM了。

TFLM解决这个问题的思路很直接:量化。把float32的权重映射到int8,四个字节缩成一个字节,体积直接砍到四分之一。推理时的中间结果也用int8存,RAM占用跟着大幅下降。

// TFLM用int8做推理,以conv2d为例 // 假设输入张量 x 是 int8 类型,权重 w 也是 int8 // 实际计算的公式是: // output = (int8)(sum_i(x[i] * w[i]) * input_scale * weight_scale / output_scale + output_zero_point) // 但TFLM内部会把这个公式拼接成乘加指令,效率很高

量化的代价是精度损失。好的量化策略(比如每通道量化、校准集选得准)可以把损失控制在1%-2%以内,对大多数嵌入式场景完全可以接受。

推理过程长什么样

我们来看一个具体的例子。假设已经训练好一个手势识别模型,输入是3轴加速度计的64个时间步——说白了就是2KB的数据。输出是静态/挥手/画圈三类概率。

#include "tensorflow/lite/micro/all_ops_resolver.h" #include "tensorflow/lite/micro/micro_interpreter.h" #include "tensorflow/lite/schema/schema_generated.h" #include "gesture_model.h" // 量化后的模型数据,用xxd转出来的 static const int tensor_arena_size = 40 * 1024; static uint8_t tensor_arena[tensor_arena_size]; void setup_ai() { const tflite::Model* model = tflite::GetModel(g_gesture_model); static tflite::AllOpsResolver resolver; static tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, tensor_arena_size); interpreter.AllocateTensors(); TfLiteTensor* input = interpreter.input(0); TfLiteTensor* output = interpreter.output(0); // input->type 是 kTfLiteInt8,维度是 {1, 64, 3} // output->type 也是 kTfLiteInt8,维度是 {1, 3} }

分几步来拆解:

首先,模型数据以C数组的形式嵌在Flash里。TFLM不依赖文件系统,也不动态加载,所有东西编译时就确定了。tensor_arena是一块预分配的静态内存池,推理的中间数据全在这里流转。

AllOpsResolver注册了TFLM支持的所有算子。如果对Flash大小有极致要求,可以只注册模型用到的那几个,比如RegisterConv2D()RegisterFullyConnected(),剩下的全都去掉。

推理的过程更有意思。把加速度数据塞进input张量后:

int8_t* input_data = interpreter.input(0)->data.int8; for (int i = 0; i < 64 * 3; i++) { input_data[i] = (int8_t)(raw_accel[i] / input_scale + input_zero_point); } interpreter.Invoke(); int8_t* output_data = interpreter.output(0)->data.int8; // output_data[0], output_data[1], output_data[2] 就是三个类别的置信度 // 反量化回float:(output_data[i] - zero_point) * scale

这里有个容易踩的坑——输入数据的量化参数。训练时用的归一化方法(比如均值和标准差)需要在MCU上保持一致。如果训练时输入归一化到[-1, 1],那MCU上也得这么处理,否则量化后的分布和训练时不同,准确率会跳水。

关于性能的一点观察

Cortex-M4没有浮点加速,但int8的乘加指令是硬件支持的。以STM32F407(168MHz)为例,一个80KB的模型做一次推理大约需要30-50ms——取决于卷积层的数量和通道数。用在100Hz以下的实时控制场景,完全不拖后腿。

CMSIS-NN库对TFLM做了深度优化。同样是int8的卷积,CMSIS-NN用到了SIMD指令和查表法,比纯C实现快3-5倍。

训练时就该想好的事

很多人把模型训练和MCU部署分成两个阶段来做,结果训练出来的模型在MCU上跑不了——不是因为精度不够,而是结构太复杂。深度可分离卷积虽然在ImageNet上参数量少,但在MCU上不一定快,因为底层对普通卷积做了循环展开的优化,对depthwise卷积的优化反而没那么到位。

所以一个比较务实的做法是:一开始就定好目标MCU的RAM和Flash上限,设计模型结构时就把tensor_arena大小和Flash占用算清楚,看到会超就立刻砍层数或者减通道数,而不是等训完才发现部署不了。

TFLM官方提供了一个叫Model Maker的工具链,虽然主要面向语音和视觉场景,但它的量化管线和模型裁剪思路,可以借鉴到其他类型的模型上。

有一个问题是留给你的——如果模型的输出层接了softmax,MCU上做softmax得用查表近似,这个近似误差会怎么影响最终的分类结果?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询