在Cortex-M4上跑了个神经网络,然后我emo了

发布时间:2026/7/4 10:26:38
在Cortex-M4上跑了个神经网络,然后我emo了 上个月老板丢过来一句话把那个手势识别模型塞进STM32.我当时就愣了。模型是Keras训的2层卷积加全连接参数加起来几十万个浮点数——扔在PC笔记本上跑得好好的张嘴就要塞进一个只有256KB RAM的Cortex-M4开玩笑呢。但老板的话就是需求。硬着头皮上吧。模型量化第一个坑就够深的翻了一圈文档TensorFlow Lite Micro是唯一靠谱的选择。但量化这个事远比我想象的复杂。一开始我这么写converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] tflite_model converter.convert()你觉得量化完就全是int8了天真。这么干出来的模型里头某些层还是float——它偷偷fallback回去了。你根本不知道直到你在MCU上一跑RAM直接爆炸。因为float计算需要的临时buffer比int8大了四倍一来就超了。查了两天才发现要指定full integer quantization还得塞一个representative dataset让它校准量化范围def representative_dataset(): for data in tf.data.Dataset.from_tensor_slices(x_train).batch(1).take(100): yield [tf.dtypes.cast(data, tf.float32)] converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_dataset converter.target_spec.supported_ops [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type tf.int8 converter.inference_output_type tf.int8 tflite_quant_model converter.convert()模型从1.2MB缩到320KB。代价是准确率从92%掉到85%。85%其实凑合能用但如果你的产品需要高可靠性——比如工业缺陷检测或者涉及人身安全——你得想清楚接不接受这个tradeoff。我后来试了用更多数据做量化校准把精度拉回到88%也算勉强能接受了。内存分配我最惨的一次翻车转好的模型烧进STM32F407。一上电HardFault。debug了两天。原因说出来你可能不信——tensor arena给太小了。TFLite Micro要求你预先划一块内存池给它它所有中间计算结果都塞这里面。你猜多大合适没有文档告诉你你得算或者试。static uint8_t tensor_arena[64 * 1024];我给了64KB模型直接报kTfLiteError。这个错误码没有任何附加信息就一个enum值。翻了源码才发现是内存不够——某个卷积层的输出feature map加上权重缓冲区算下来需要80KB以上。最后改成128KB才跑起来static uint8_t tensor_arena[128 * 1024];但这个选择很痛。F407总共就192KB SRAM。128KB给了TFLite剩下64KB给FreeRTOS加业务逻辑——任务栈稍大一点就溢出。我后来不得不把GUI的buffer砍掉三分之二把几个全局数组改成malloc动态分配才算把内存管明白。网上有人建议先放一个超大的arena跑一次看error message里打印的arena size再往下调。可惜我没开那个logging宏白白浪费了一天。跑一次要半秒这谁顶得住程序跑起来那一刻还挺激动的。然后测推理时间——470ms。一帧32x32的单通道灰度图推理了将近半秒。做个手势识别手都挥酸了屏幕还没反应。优化三步走。第一步开CMSIS-NN。TFLite Micro编译时加上-DCMSIS_NN宏就成同样的模型直接降到120ms。CMSIS-NN用了arm汇编优化的矩阵乘法和激活函数效率比纯C翻了将近四倍。前提是你得在CubeMX里把CMSIS包勾上不然link阶段报undefined reference。第二步输入输出全用int8。我之前偷懒把输入层设成float32等于每次推理前要做一次类型转换——纯纯浪费算力。改完又省了30ms。第三步换芯片。STM32F407没有硬件乘累加器跑NN真的太勉强。后来换成STM32G474带FPU和CORDIC协处理器推理时间直接掉到55ms总算能看了。其实选芯片的时候就应该算清楚。F407是十年前的老架构了设计初衷就没考虑过AI推理。G4系列虽然也不算什么AI专用芯片但至少外设新、带硬件乘加跑tinyML够用。如果再往上H7系列带DSP和双精度FPU推理速度还能再翻倍。功耗是最后一道坎跑推理的时候芯片吃45mA电流对电池供电的设备来说有点大了。更何况如果连续做推理那电流根本下不来。我的做法是空闲时进stop模式。推理完马上调用HAL_PWR_EnterSTOPMode电流直接从45mA掉到几个µA。反正手势识别一秒判断个两三次就够了没必要让CPU一直傻转。几个关键点进stop之前把所有外设时钟关了GPIO设成analog模式减少漏电流。唤醒用RTC定时器或者外部中断按键。实测平均功耗能压到2mA以下——一颗18650电池大概2500mAh算下来能撑一个多月。如果你是做电池供电的产品这个功耗优化必须从第一天就考虑。有人最后才发现功耗压不下去改电路改到哭。说到底嵌入式AI没那么多玄乎的东西。模型量化、内存规划、硬件选型、功耗优化——跟做其他嵌入式开发没啥本质区别都是在资源受限的大前提下做加减法。只不过AI这一套工具链还不够成熟坑比想象中多了那么几个。别问我怎么知道的。问就是焊了好几块板子烧了好几个jlink debugger才悟出来的。