2、推理框架概览:主流嵌入式推理框架对比与选型
做嵌入式AI这几年,我接触过的推理框架少说也有七八个。每个框架刚出来的时候都号称「最快」、「最轻量」,但实际用起来,坑是真不少。今天我就把几个主流框架掰开揉碎了聊聊,帮你选型时少走弯路。
2.1 为什么需要推理框架?
说白了,你训练好的模型是个「胖子」。PyTorch或者TensorFlow的模型动辄几百兆,里面还带着一堆训练用的算子。到了嵌入式设备上,内存就几百KB,算力也有限,根本跑不动。
推理框架干的事就三件:
- 模型压缩:去掉训练相关的冗余信息,只保留推理需要的计算图
- 算子优化:针对ARM、x86、RISC-V等不同架构,用汇编或者NEON指令重写关键算子
- 内存管理:预分配内存池,避免运行时频繁malloc/free
嗯,这里要注意,不是所有框架都适合你的芯片。选错了,性能可能差10倍以上。
2.2 五大主流框架速览
| 框架 | 维护方 | 核心语言 | 典型场景 | 我的评价 |
|---|---|---|---|---|
| TensorFlow Lite | C++ | Android、树莓派 | 生态最全,但性能一般 | |
| ONNX Runtime | Microsoft | C++ | 跨平台、云端+端侧 | 兼容性最好,但体积偏大 |
| NCNN | 腾讯 | C++ | 手机端、ARM Linux | 轻量、稳定,我项目里用得最多 |
| MNN | 阿里巴巴 | C++ | 手机端、IoT | 性能激进,但文档略坑 |
| Tengine | OPEN AI LAB | C | 安防、工业、ARM Linux | 国产之光,硬件适配好 |
2.3 逐个拆解:每个框架的脾气秉性
2.3.1 TensorFlow Lite
Google的亲儿子,生态确实没得说。你随便搜个模型,基本都有TFLite的转换脚本。我个人习惯用它做快速原型验证,因为从TF训练到TFLite部署,一条龙服务。
但说实话,性能方面它不算顶尖。我记得有一次在RK3399上跑MobileNetV2,TFLite的推理速度比NCNN慢了将近30%。原因很简单——TFLite的算子优化偏通用,没有针对特定芯片做极致调优。
2.3.2 ONNX Runtime
微软搞的这个框架,最大的优势是「中间人」角色。你训练用PyTorch,部署用ONNX Runtime,中间只需要过一次ONNX格式。跨框架迁移非常丝滑。
不过,ONNX Runtime的体积是个问题。完整版动态库几十兆,对嵌入式设备来说太奢侈了。我建议只编译你需要的算子,能省掉一大半体积。
onnxruntime::SessionOptions 设置 SetIntraOpNumThreads 和 SetInterOpNumThreads,可以精细控制多线程推理。我一般设成2线程,既能提速又不至于把CPU占满。
2.3.3 NCNN
腾讯开源的这个框架,是我个人最偏爱的。为什么?因为它「干净」。代码风格简洁,依赖极少,编译起来几乎不会报错。你想想看,在嵌入式环境里,能一次编译通过有多幸福。
NCNN对ARM架构的优化做得相当到位。它用了很多手写汇编,比如卷积的Winograd算法、深度可分离卷积的优化。我在海思3516上跑过YOLOv5s,NCNN比TFLite快了将近一倍。
// NCNN加载模型的典型代码
ncnn::Net net;
net.load_param("model.param");
net.load_model("model.bin");
ncnn::Mat in = ncnn::Mat::from_pixels(pixels, ncnn::Mat::PIXEL_BGR, 224, 224);
ncnn::Extractor ex = net.create_extractor();
ex.input("data", in);
ncnn::Mat out;
ex.extract("output", out);
嗯,这里要注意,NCNN的输入数据格式是NHWC,和很多训练框架的NCHW不一样。我第一次用的时候忘了转格式,结果推理结果全是错的。
2.3.4 MNN
阿里的MNN,性能上确实激进。它用了很多编译优化技术,比如算子融合、内存复用、计算图优化。在骁龙865上跑分类模型,MNN的延迟比NCNN还要低10%左右。
但MNN的文档嘛……说实话,有点跟不上代码的更新速度。我遇到过好几次,照着文档写代码,结果API已经变了。建议直接看源码里的示例,比文档靠谱。
2.3.5 Tengine
国产框架里,Tengine的硬件适配做得最好。它支持RK3399、RV1126、算能、地平线等国产芯片,而且针对每个平台都有专门的优化库。我在安防项目里用过Tengine,部署到海思3559上,几乎开箱即用。
Tengine的另一个亮点是支持动态形状输入。有些场景下输入图片尺寸不固定,其他框架需要重新创建session,Tengine可以直接复用。
// Tengine加载模型的示例
graph_t graph = create_graph(nullptr, "tengine", model_path);
tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0);
set_tensor_shape(input_tensor, dims, 4);
prerun_graph(graph);
run_graph(graph, 1);
2.4 选型建议:到底该用哪个?
这个问题没有标准答案,但我可以给你一些实际经验:
- 如果你在Android上做App:首选TFLite,系统自带支持,集成最简单
- 如果你在ARM Linux上做产品:NCNN或Tengine,性能稳定,社区活跃
- 如果你需要跨框架迁移:ONNX Runtime,兼容性最好
- 如果你追求极致性能:MNN,但要做好踩坑的准备
- 如果你用国产芯片:Tengine,硬件适配最省心
我的个人建议:不要迷信「最快」的框架。选型时考虑三个因素:
- 你的芯片架构(ARM v7/v8?RISC-V?)
- 你的模型算子(有没有特殊层?)
- 你的团队维护能力(遇到bug能自己修吗?)
我见过太多人为了追求那5%的性能提升,选了一个冷门框架,结果遇到bug没人管,项目延期。稳定大于一切。
2.5 小结
这一章我们聊了五个主流推理框架的特点和坑。说白了,没有完美的框架,只有最适合你项目的框架。下一章我会深入讲NCNN的模型转换和量化,这是嵌入式部署里最常踩坑的环节。
记住一句话:选框架就像选工具,顺手比锋利更重要。