STM32 NPU端侧AI推理:从模型量化到部署全流程
2026年嵌入式AI有一个标志性事件:ST推出了STM32N6,这颗芯片集成了NPU(Neural Processing Unit),能在MCU上跑硬件加速推理。以前在MCU上做AI推理,要么用软件浮点模拟(慢得离谱),要么外挂加速芯片(成本高)。现在一颗STM32就能搞定。
但端侧AI推理的门槛不只是硬件。从PC上训练好的模型到MCU上实际运行,中间有好几道工序:模型量化、格式转换、NPU适配、内存优化。这篇把全流程拆解清楚。
STM32N6的NPU能力
STM32N6的NPU叫Neural-ART Accelerator,支持INT8和INT4量化推理。几个关键指标:
| 指标 | STM32N6 NPU | 软件模拟(STM32H7) |
|---|---|---|
| INT8算力 | 1 TOPS | ~0.01 TOPS |
| 典型推理延迟(MobileNet) | 15ms | 2000ms+ |
| 支持算子 | Conv, DWConv, GEMM | 全部(软件) |
| 功耗 | 50-200mW | 300mW+ |
100倍的推理速度提升意味着在MCU上跑实时图像分类成为可能。但NPU不是万能的——它只加速特定算子(卷积和矩阵乘),不支持的算子会fallback到CPU执行,成为瓶颈。
从训练模型到MCU部署的完整链路
端侧AI部署的链路比云端复杂得多:
PyTorch模型(.pt) → ONNX格式(.onnx) → 量化(INT8) → STM32格式(.tflite/格式) → X-CUBE-AI工具链 → NPU固件 → MCU运行每一步都可能出问题:量化掉精度、格式转换丢失算子、NPU不支持的层回退CPU。下面逐步展开。
第一步:训练模型
在PC上用PyTorch训练一个简单的传感器异常检测模型。模型结构故意设计得简单——Conv1D + FC,因为MCU上复杂模型跑不动:
importtorchimporttorch.nnasnnclassSensorAnomalyDetector(nn.Module):def__init__(self,input_channels=6,seq_len=128):super().__init__()# 1D卷积提取时序特征self.conv1=nn.Conv1d(input_channels,16,3,padding=1)self.conv2=nn.Conv1d(16,32,3,padding=1)self.pool=nn.MaxPool1d(2)self.fc1=nn.Linear(32*(seq_len//4),32)self.fc2=nn.Linear(32,2)# 正常/异常defforward(self,x):x=torch.relu(self.conv1(x))x=self.pool(x)x=torch.relu(self.conv2(x))x=self.pool(x)x=x.view(x.size(0),-1)x=torch.relu(self.fc1(x))x=self.fc2(x)returnx# 训练后保存model=SensorAnomalyDetector()# ... 训练代码省略 ...torch.save(model.state_dict(),'sensor_model.pth')第二步:导出为ONNX
ONNX是模型交换的中间格式,STM32工具链从这里开始接手:
# 加载训练好的模型model=SensorAnomalyDetector()model.load_state_dict(torch.load('sensor_model.pth'))model.eval()# 创建虚拟输入dummy_input=torch.randn(1,6,128)# 导出ONNXtorch.onnx.export(model,dummy_input,'sensor_model.onnx',input_names=['input'],output_names=['output'],dynamic_axes={'input':{0:'batch'},'output':{0:'batch'}},opset_version=14# X-CUBE-AI支持的版本)opset_version=14是关键——STM32的X-CUBE-AI工具链对ONNX opset版本有要求,版本太高会不认。建议导出前查阅X-CUBE-AI的版本说明确认支持的opset。
第三步:PTQ量化
PTQ(Post-Training Quantization)是训练后量化,把FP32权重转为INT8。STM32提供STM32Cube.AI工具做量化,也可以先用ONNX Runtime做:
importonnxfromonnxruntime.quantizationimportquantize_dynamic,QuantType# 动态量化(权重量化,激活值保持FP32)quantize_dynamic(model_input='sensor_model.onnx',model_output='sensor_model_int8.onnx',weight_type=QuantType.QInt8)动态量化简单但精度损失可能较大。如果精度不够,需要做QAT(量化感知训练)——在训练过程中模拟量化误差,让模型适应低精度:
importtorch.ao.quantizationasquant# QAT准备model.qconfig=quant.get_default_qconfig('fbgemm')model=quant.prepare_qat(model,inplace=True)# 再训练几个epochoptimizer=torch.optim.Adam(model.parameters(),lr=1e-4)forepochinrange(10):fordata,labelintrain_loader:out=model(data)loss=nn.CrossEntropyLoss()(out,label)loss.backward()optimizer.step()# 转换为量化模型model=quant.convert(model.eval())QAT后INT8模型的精度通常能恢复到接近FP32水平(掉1-3%以内),但训练成本增加。
第四步:X-CUBE-AI转换
STM32Cube.AI(原名X-CUBE-AI)是ST官方工具链,把ONNX/TensorFlow Lite模型转换为C代码。在STM32CubeIDE中操作:
# 命令行方式(非IDE)# 安装STM32Cube.AI CLIstm32ai generate-msensor_model_int8.onnx\-nsensor_anomaly\-o./generated/\--targetstm32n6\--compression1\--verbosity1--target stm32n6指定目标芯片,工具会生成针对NPU优化的C代码。--compression 1启用权重压缩。
生成的主要文件:
generated/ ├── sensor_anomaly.c # 模型推理函数 ├── sensor_anomaly.h # 头文件 ├── sensor_anomaly_data.c # 量化权重数据 └── sensor_anomaly_config.h # 模型配置第五步:MCU端集成
在STM32工程中调用生成的推理函数:
#include"sensor_anomaly.h"#include"sensor_anomaly_data.h"/* AI运行时缓冲区 */AI_ALIGNED(32)staticai_u8 activations[AI_SENSOR_ANOMALY_ACTIVATIONS_SIZE];staticai_sensor_anomaly_in_float*input_buf;staticai_sensor_anomaly_out_float*output_buf;intai_init(void){ai_handle network;ai_buffer ai_input;ai_buffer ai_output;/* 创建网络实例 */if(ai_sensor_anomaly_create(&network,AI_SENSOR_ANOMALY_CONFIG)!=1){return-1;}/* 获取输入输出缓冲区 */ai_input=ai_sensor_anomaly_inputs_get(network,NULL);ai_output=ai_sensor_anomaly_outputs_get(network,NULL);input_buf=(ai_sensor_anomaly_in_float*)ai_input.data;output_buf=(ai_sensor_anomaly_out_float*)ai_output.data;return0;}intai_run(float*sensor_data,intlen,int*result){/* 填充输入数据 */for(inti=0;i<len&&i<AI_SENSOR_ANOMALY_IN_1_SIZE;i++){input_buf[0].input[i]=sensor_data[i];}/* 运行推理 */ai_i32 n_batch=ai_sensor_anomaly_run(network,&ai_input,&ai_output);if(n_batch!=1)return-1;/* 取输出:[正常概率, 异常概率] */if(output_buf[0].output[1]>output_buf[0].output[0]){*result=1;// 异常}else{*result=0;// 正常}return0;}NPU调用的关键细节
STM32N6的NPU不是自动启用的。需要在推理前配置NPU,让它接管支持的算子:
/* 启用NPU加速 */#include"stm32n6xx_hal_neai.h"voidenable_npu(void){/* 配置NPU时钟 */__HAL_RCC_NPU_CLK_ENABLE();/* 加载NPU固件(ST提供二进制文件) */HAL_NEAI_LoadFirmware(NPU_FIRMWARE_ADDRESS);/* 配置NPU工作模式 */NEAI_ConfigTypeDef config={0};config.mode=NEAI_MODE_INT8;config.clock=NEAI_CLOCK_HIGH;HAL_NEAI_Init(&config);}NPU固件是ST提供的二进制文件(在STM32Cube_FW_N6包里),必须加载才能使用NPU。不加载固件时,推理全部走CPU模拟,速度慢100倍。
内存优化:MCU的永恒主题
STM32N6有640KB SRAM,模型权重和激活值都要放进去。量化后的INT8模型体积大约是FP32的1/4,但还是要算清楚:
/* 在编译时检查内存占用 */#ifAI_SENSOR_ANOMALY_ACTIVATIONS_SIZE>(640*1024)#error"激活值缓冲区超过SRAM容量"#endif#ifAI_SENSOR_ANOMALY_WEIGHTS_SIZE>(2*1024*1024)#error"权重超过Flash容量,考虑更小的模型"#endif如果模型放不下SRAM,有两个选择:缩小模型(减少通道数和层数),或者用外部SDRAM放权重(通过FMC接口扩展)。外部SDRAM读取速度比SRAM慢,会降低推理速度。
实测推理性能
传感器异常检测模型(6通道输入,128时间步,Conv1D x2 + FC x2)在STM32N6上实测:
| 配置 | 推理延迟 | 内存占用 | 精度 |
|---|---|---|---|
| FP32软件模拟 | 1800ms | 220KB | 96.2% |
| INT8 CPU推理 | 45ms | 58KB | 94.8% |
| INT8 NPU加速 | 8ms | 58KB | 94.8% |
NPU加速让推理延迟从45ms降到8ms——对于50ms控制周期的实时系统来说,这个速度终于够用了。精度从96.2%降到94.8%,掉1.4个百分点,在异常检测场景完全可接受。
小结
STM32N6 NPU让MCU上的实时AI推理从理论变成现实。全链路的关键在量化——PTQ简单但可能掉精度多,QAT效果好但需要重新训练。X-CUBE-AI工具链已经比较成熟,但NPU固件加载和内存约束是需要重点关注的部分。模型设计阶段就要考虑MCU的内存限制,不能拿PC上的大模型直接量化塞进去。
搞嵌入式AI的同学如果正在评估STM32 NPU方案,这篇全流程记录应该能帮你理清思路。收藏一下方便后续查阅,我还会持续更新端侧AI在不同MCU平台上的实测对比,关注一下跟住后续内容。