在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上可能就是生死线。
现在回头看,那晚的三次翻车其实挺值的——至少以后不管是选方案还是做技术评估,心里有数了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)