news 2026/6/15 7:26:13

在STM32上跑AI模型?我折腾了一宿,翻了三次车

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在STM32上跑AI模型?我折腾了一宿,翻了三次车

去年接了个活儿,客户说要在STM32F4上跑一个手势识别模型。

我当时第一反应是——你认真的?这片子主频168MHz,RAM 192KB,跑AI?

但客户是甲方,甲方说行那就试试呗。

然后我花了一整个晚上,翻车三次,才终于跑起来。今天聊聊这三道坎儿。


第一翻:选了TensorFlow Lite Micro,发现装都装不上

最早的想法很简单:TFLite Micro嘛,谷歌官方出品,总不会太离谱。

结果编译环境先把我干趴下了。

# 克隆仓库 git clone --depth 1 https://github.com/tensorflow/tflite-micro.git cd tflite-micro

然后按文档跑:

make -f tensorflow/lite/micro/tools/make/Makefile TARGET=cortex_m_generic

跑了一堆下载,emmm...这玩意儿把XNNPACK、flatbuffers全家桶全拉下来了。下载完我看了眼,光算子库编译中间文件就吃了快800MB。一个几KB的模型,背后拖了一个庞然大物。

而且编译完生成的可执行文件,光是个sin检测demo就28KB。28KB跑个sin函数???

回头看了眼客户的BOM成本要求——STM32F407的Flash才512KB,算上通信协议栈、外设驱动,分给模型的预算不到80KB

这条路走不通。


第二翻:换方案,自己手写推理

既然框架太胖,那我就手撸推理代码呗。

模型是客户给的,一个三层全连接网络(16→32→16→5),ReLU激活,softmax输出。权重表是现成的,我就一个for循环乘加完事。

写了个原型:

// 手撸推理引擎,别笑,虽然原始但管用 // 输入:16维float数组 // 输出:5个分类的置信度 #define INPUT_DIM 16 #define HIDDEN1_SIZE 32 #define HIDDEN2_SIZE 16 #define OUTPUT_DIM 5 // 权重结构——直接把训练好的参数编译进固件 // 省掉文件读取和动态分配的麻烦 static const float w1[INPUT_DIM * HIDDEN1_SIZE] = { /* 512个浮点数,这里省略 */ }; static const float b1[HIDDEN1_SIZE] = { /* 32个偏置 */ }; // w2, w3, b2, b3 同理... // ReLU:取0和输入的较大值 static inline float relu(float x) { return x > 0 ? x : fmaxf(x, 0.0f); } void infer(float* input, float* output) { float h1[HIDDEN1_SIZE], h2[HIDDEN2_SIZE]; // 第一层:全连接 + ReLU for (int i = 0; i < HIDDEN1_SIZE; i++) { h1[i] = b1[i]; for (int j = 0; j < INPUT_DIM; j++) h1[i] += w1[i * INPUT_DIM + j] * input[j]; h1[i] = relu(h1[i]); } // 第二、三层类似... }

在PC上仿真跑通之后,烧进STM32——跑一次推理用了16.7毫秒

16.7毫秒一次,还只是三层小网络。客户要求30FPS处理摄像头画面,也就是每帧33毫秒内要完成前后处理+推理+通信+显示。推理这一步就吃掉一半预算了,后面啥都干不了。

我又打开代码看了半天,发现问题出在浮点运算上——STM32F4没有硬件FPU的单精度加速(虽然有FPU但效率一般),而且浮点数占4字节,cache利用率太差。


第三翻:量化,真不是改个数据类型那么简单

既然浮点慢,那就量化成int8呗。

我照着TensorFlow的量化文档改,把权重和激活值都映射到[-128, 127]范围。

然后发现一个最头疼的问题:ReLU之后的值分布不均匀

因为ReLU把所有负数清零,所以量化后的有效比特位只有正半轴。如果缩放因子算不对,8位精度里一大半都浪费了。

我踩的坑是:在PC上用float跑了前向传播算好scale和zero_point,手填进代码,然后精度从原来的95.3%掉到了82.1%。

// 量化推理中的一层——最坑的就是这个scale // 如果scale算得不对,13%的准确率说没就没 void infer_quantized(int8_t* input_q, float input_scale, int input_zp, int8_t* output_q, float* output_f) { // 反量化到float做乘加(部分量化的常见做法) float h1[HIDDEN1_SIZE]; for (int i = 0; i < HIDDEN1_SIZE; i++) { float acc = b1_float[i]; for (int j = 0; j < INPUT_DIM; j++) { float in_val = (input_q[j] - input_zp) * input_scale; acc += w1_float[i * INPUT_DIM + j] * in_val; } h1[i] = relu(acc); } // ... }

折腾到凌晨两点才搞明白——校准集不够代表性。我拿100张图算的scale,但实际场景里光照变化导致输入分布偏移,ReLU层的输出被截断了太多信息。

最后我换了个方案:不自己手写量化校准,而是用STM32Cube.AI工具链,输入h5模型直接自动生成优化代码。生成出来的推理函数只有2.3KB的代码量,一次推理8.1毫秒,比我自己手写的还快了将近两倍。


复盘

这三道坎儿,每一道都是"我寻思这很容易啊"然后又啪啪打脸。

第一翻告诉我:不要以为框架越官方的越好用。TFLite Mic罗在资源受限的MCU上,很多抽象层反而是包袱。

第二翻告诉我:手写推理虽然酷,但你对硬件的了解往往比你想象的要少。浮点运算在MCU上的真实代价、cache miss的影响,这些数据手册上都有,但你不踩一脚是不会记住的。

第三翻告诉我:量化是门玄学,校准集的覆盖比模型结构本身更重要

最后说一句:STM32跑AI确实能跑,但不要用PC的思维去规划嵌入式AI的资源预算。一个在电脑上毫秒级的运算,放到MCU上可能就是生死线。

现在回头看,那晚的三次翻车其实挺值的——至少以后不管是选方案还是做技术评估,心里有数了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 7:12:55

告别玄学调试:用这3招彻底根治LaunchScreen图片缓存(白屏/黑屏/不更新)

根治iOS启动图缓存顽疾&#xff1a;三套组合拳终结白屏黑屏乱象每次更新LaunchScreen启动图时&#xff0c;开发者总会遇到那些令人抓狂的"灵异现象"——模拟器显示正常但真机死活不更新、明明替换了资源却依然显示旧图、甚至直接白屏黑屏给你看。这些看似毫无规律的问…

作者头像 李华
网站建设 2026/6/15 7:08:50

LLM生产级推理优化:资源利用率与可观测性闭环实战

1. 项目概述&#xff1a;这不是一次小修小补&#xff0c;是生产级大模型工程范式的迁移 “Building LLMs for Production Gets a Massive Update!”——这个标题里没有具体技术名词&#xff0c;没有工具名&#xff0c;甚至没提哪家公司&#xff0c;但它在2024年中后期的工程圈里…

作者头像 李华
网站建设 2026/6/15 6:56:55

避开这3个坑!用LabVIEW连接X-Plane 11进行UDP通信的实战避坑指南

LabVIEW与X-Plane 11 UDP通信实战&#xff1a;3个高频问题深度解析与解决方案在飞行仿真开发领域&#xff0c;将专业工具LabVIEW与高精度飞行模拟器X-Plane 11结合&#xff0c;能快速构建低成本、高保真的测试环境。但实际开发中&#xff0c;UDP通信环节常成为"拦路虎&quo…

作者头像 李华