当前位置: 首页 > news >正文

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

在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得用查表近似,这个近似误差会怎么影响最终的分类结果?

http://www.jsqmd.com/news/1153561/

相关文章:

  • Remix IDE:浏览器里的 Solidity 开发环境,免配置直接上手
  • 深度解析AKShare金融数据获取引擎的异步处理机制与多源融合架构
  • 基于智能代理技术的跨平台资源嗅探工具:重新定义网络内容获取新范式
  • 终极CS2物品管理指南:3分钟掌握智能存储单元批量转移技巧
  • 3分钟搞定!零基础也能用的浏览器视频下载神器完整指南
  • PyQt6中文教程:7天快速掌握Python桌面应用开发终极指南
  • 如何用留痕工具永久保存微信聊天记录:个人AI训练数据终极指南
  • 智读致用|《思辨与立场》07|你说不清自己是怎么得出那个结论的?——把思维拆成零件,一看就明白问题在哪
  • 171、print 调试的终结:为什么 pdb 比 print 快 10 倍定位问题
  • 从零掌握Codex:AI代码生成工具接入、应用与本地化实践
  • 机器学习:欠拟合、过拟合、偏差方差超全详解
  • Linux命令-restore(从转储文件恢复文件系统)
  • Mac终极指南:如何免费安装360Controller驱动,完美支持Xbox游戏手柄
  • HQ-SAM 高精度分割实战:8卡4小时微调,44K数据集提升边界细节
  • 创建和启动新线程的所有细微差别。守护进程。
  • Nginx Ingress性能优化完全指南:从连接池到TLS终止的48个调优参数实测
  • 解决ComfyUI-Impact-Pack中FaceDetailer节点种子参数缺失问题的完整技术指南
  • 龍魂系统 · 长期记忆
  • Pandas 2.2 实战:3种缺失值填充策略对比与泰坦尼克号数据集应用
  • 三步搞定!Mac安装Xbox手柄驱动终极指南,游戏体验全面升级
  • 360Controller:macOS系统下的Xbox控制器驱动解决方案
  • AI交易智能体:从原理到实践,赋能个人投资者的关键技术
  • AMD EPYC 9004 系列服务器CPU选型指南:5nm Zen 4 架构下 96 核与 128 核的抉择
  • RoboDojo评测基准立起“具身珠峰”,当前机器人模型距通用操作还有多远?
  • PaperxieAI 开题生成工具:三步标准化创作逻辑,轻松搞定开题撰写各类卡点
  • RAG在技术文档检索中的实战部署:ChromaDB与Embedding流水线
  • DeepSpeed: 大模型微调框架全面解析
  • Magpie终极指南:如何让老旧窗口瞬间高清化的完整教程
  • 上海靠谱的画册设计品牌哪家专业
  • Google Pay API v3 服务端订单校验:Java SDK 集成与 2 种验证方式对比