边缘侧的模型部署从来不是一蹴而就的事。当团队决定把训练好的模型推向手机、嵌入式设备或 IoT 网关时,很快就会遇到一个很现实的问题:该选哪个推理框架。TensorFlow Lite、ONNX Runtime 和 NCNN 是目前最常被提起的三个选项,但翻看官方文档和社区讨论,往往只能看到一堆性能跑分或简单的 “Hello World” 示例。真正在工程中走一圈,你会发现这些框架的差异远不止端侧推理速度那几毫秒。

这篇文章不会给你一个“XX 框架最好”的结论,而是想从工程视角把三个框架的底层逻辑、它们擅长什么、在什么情况下会翻车,以及怎么根据自己团队的实际情况做选择,聊清楚。如果你正在做移动端或嵌入式 AI 功能的落地,或正被模型转换、算子兼容性问题折磨,以下内容或许能给你一些锚点。
三个框架的设计原点
理解一个框架,首先要看它从哪来、为谁设计。
TensorFlow Lite 是 Google 围绕 TensorFlow 生态打造的端侧推理引擎。它的最大优势是端到端——从训练到转换再到部署,流程高度集成。如果你用 TensorFlow 或 Keras 训练模型,只需几行代码就能导出 .tflite 文件,然后直接在 Android(通过 Java/Kotlin 或 C++)或 iOS 上跑。TFLite 还内建了丰富的量化工具,可以将 Float32 模型压缩为 INT8 甚至更激进的格式,大幅降低内存占用和推理延迟。但它的封闭性也很明显:模型格式强绑定 .tflite,虽然支持部分 ONNX 和 TF 2.x 的 SavedModel 转换,但算子覆盖和灵活度常常受限。如果你的训练环境不是 TensorFlow,迁移成本会明显上升。
ONNX Runtime 是微软主导的跨平台推理引擎,定位更像一个“通用运行时”。它本身不绑定任何训练框架,而是通过 ONNX(Open Neural Network Exchange)作为中间表示,来承接 PyTorch、TensorFlow、Keras、MXNet 等导出的模型。这种中间层设计让它在后端服务、云端推理甚至部分桌面端应用中非常灵活。针对移动端和边缘设备,ONNX Runtime 也提供了轻量化的 build 选项,并支持多种 Execution Provider(如 CPU、CUDA、TensorRT、OpenVINO、DirectML),可以利用硬件加速。但它的问题在于,移动端和嵌入式场景的优化深度,暂时还比不上完全为移动端而生的框架。
NCNN 是腾讯优图实验室推出的专为移动端 CPU 优化的前向推理框架,完全用 C++ 实现,没有第三方库依赖,极度轻量。它最突出的特点是对 ARM 架构的深度优化,使用手工汇编和 Neon 指令集来榨取性能,在大量低端移动设备上也能跑出不错的帧率。NCNN 一开始就不追求万能,而是聚焦在常见的视觉模型(如分类、检测、人脸关键点)上,所以它的算子支持范围相对窄,自定义算子或冷门结构很可能需要自己实现。但如果你需要的是在千元机上稳定跑 30fps 的人脸检测,NCNN 往往会成为首选。
关键维度对比:不只是跑分
下面这张表不是想列一个功能清单,而是把工程中真正影响决策的维度摆出来。每个维度背后都可能对应一段加班经历。
| 维度 | TensorFlow Lite | ONNX Runtime | NCNN |
|---|---|---|---|
| 原生模型格式 | .tflite | .onnx | param+bin |
| 训练框架兼容性 | TensorFlow 生态为主 | 多框架(通过 ONNX) | 依赖转换工具(onnx2ncnn 等) |
| 移动端优化深度 | 高(XNNPACK delegate、GPU delegate) | 中等(ARM NN、XNNPACK 等 backends) | 极高(手工汇编、Neon 优化) |
| 量化支持 | 训练后量化、量化感知训练、全整数量化 | 支持 INT8、FP16,依赖 Execution Provider | 支持 INT8,但工具链相对原始 |
| 算子覆盖 | 较广,但部分控制流算子不支持 | 非常广,持续更新 | 聚焦视觉类常见算子,冷门算子覆盖少 |
| 首次推理延迟 | 一般,需注意初始化分配 | 中等,可配置 | 较低,轻量级设计 |
| 二进制体积 | 较小(约 500KB+) | 较大(可裁剪,但默认约 2MB+) | 极小(约 300KB+) |
| 社区与生态 | Google 官方维护,Android 集成好 | 微软主导,多厂商参与 | 腾讯优图,社区活跃度中等 |
这张表里最容易引起误判的是“移动端优化深度”。NCNN 的极致优化并不意味着它永远是移动端跑得最快的框架,因为 TFLite 的 XNNPACK delegate 和 GPU delegate 在不少新设备上表现也很出色,而且 NCNN 的优化主要集中在 CPU 路径,如果模型需要用到 GPU 或 NPU,TFLite 的集成度往往更高。所以看优化深度不能脱离硬件平台。
真正卡人的地方:模型转换
很多团队在选型时过分关注推理速度,却忽略了模型转换这一环。实际上,一个推理框架能不能用,首先取决于你的模型能不能顺利转过去,并且转换后的精度和性能是否可接受。
三个框架的转换链路截然不同:
- TensorFlow Lite:从 TensorFlow 模型转换最平滑,但如果你用的是 PyTorch 或其他框架,通常需要先转 ONNX,再通过 tf2onnx 或第三方的 onnx-tf 转成 TensorFlow 格式,最后才能到 TFLite。这条链路长,算子兼容性容易出问题。
- ONNX Runtime:理论上只要你的框架能导出 ONNX,就能用。但 ONNX 的算子版本和 opset 选择经常让人头疼,不同框架导出时的默认行为不一致,导致推理结果与预期不符。
- NCNN:依赖外部的模型转换工具,最常见的是从 ONNX 转换(onnx2ncnn),或者从 Caffe、TensorFlow 等转换。转换过程可能丢失算子,需要手动处理不支持的操作,比如某些激活函数或自定义层。
一个典型的场景是:团队用 PyTorch 训练了一个包含 GELU 激活和自定义 attention 的轻量模型,想部署到 Android 设备上。如果直接导 ONNX 用 ONNX Runtime,可能遇到 GELU 算子版本不匹配;转 TFLite 则要经过 onnx→TF→tflite 的长链路,每一步都可能出现精度损失;转 NCNN 则可能因为自定义算子完全不支持而卡住。此时,最稳妥的方案可能是用 ONNX Runtime 并手动处理算子兼容问题,或者重新用 TensorFlow 重构模型。
所以,选框架前务必先跑通模型转换的 PoC,不要等到最后一周才去填坑。
性能调优的几个关键点
抛开模型转换,性能调优才是日常工作的重头戏。这里不准备罗列所有优化技巧,而是挑几个容易踩坑的地方说。
第一个坑是只看平均推理时间,不看首次推理延迟。很多框架在第一次推理时会进行大量的内存分配、算子 tuning 等工作,导致首帧耗时是后续帧的数倍。对于需要快速启动的应用(如相机预览),这一点很致命。TFLite 可以通过预热(warm-up)来缓解,NCNN 因为体量轻,首帧延迟通常较低,但也不是绝对。
第二个坑是量化并非万能。INT8 量化确实能显著降低模型大小和推理延迟,但精度损失取决于模型结构和数据集。有些模型对量化敏感,如果直接采用训练后量化,精度可能掉到不可接受的程度。TFLite 提供了量化感知训练(QAT)来改善,但这就意味着需要重新训练,增加了项目周期。NCNN 的 INT8 推理需要预先计算量化参数,工具链相对原始,对新手不太友好。
第三个坑是多线程与绑核策略。在移动端,CPU 核心的调度策略直接影响推理性能。TFLite 允许设置线程数,并通过 XNNPACK 自动利用多核;NCNN 则提供了更细粒度的线程控制,可以手动绑定大核小核,这在发热和功耗敏感的场景下很有用。ONNX Runtime 的 CPU 执行提供者也支持多线程,但具体行为依赖后端实现。
下面这段代码展示了用 TFLite C++ API 进行推理的基本流程,也顺便体现了一些常见的性能设计点(如使用 flatbuffer 模型避免解析开销,提前分配 tensor):
// 加载模型(一次加载,复用)
std::unique_ptr<tflite::FlatBufferModel> model =
tflite::FlatBufferModel::BuildFromFile("model.tflite");
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);
// 可选:启用 XNNPACK delegate 加速
TfLiteXNNPACKDelegateOptions xnnpack_options =
TfLiteXNNPACKDelegateOptionsDefault();
auto delegate = TfLiteXNNPACKDelegateCreate(&xnnpack_options);
interpreter->ModifyGraphWithDelegate(delegate);
interpreter->AllocateTensors();
// 获取输入 tensor 并填充数据
float* input = interpreter->typed_input_tensor<float>(0);
// ... 填充 input ...
// 执行推理
interpreter->Invoke();
// 读取输出
float* output = interpreter->typed_output_tensor<float>(0);
ONNX Runtime 和 NCNN 的推理流程也类似:加载模型、准备输入、执行推理、读取输出。差异在于 API 细节和模型格式。NCNN 的流程更轻量,直接通过 net.load_param 和 net.load_model 加载,然后创建 Extractor 进行推理。ONNX Runtime 则通过 Session 来管理。
场景化选型:什么时候该用哪个
很多团队在选型时容易陷入“性能至上”的陷阱,其实正确的顺序是先看团队的技术栈和业务约束。
场景一:移动端 App 团队,已有 TensorFlow 训练管线
如果你团队里不少人熟悉 TensorFlow 或 Keras,而且业务要求快速上线,TensorFlow Lite 可能是最顺滑的选择。Android 原生集成、Google 的官方支持、丰富的量化工具,都能让端侧 AI 功能尽快落地。但要是未来需要支持 PyTorch 训练的模型,就得提前考虑模型转换链路的健壮性。
场景二:初创公司,训推分离,需要快速验证多种模型
在很多 AI 初创团队里,算法研究员用 PyTorch 快速迭代,工程团队则需要把模型推到边缘设备上。此时 ONNX Runtime 的跨框架特性就很有价值,因为研究员可以随时导出 ONNX,工程侧不用每次都重新适配。但代价是移动端性能可能不是最优,且需要额外投入精力在 Execution Provider 的选择和调优上。
场景三:嵌入式设备,极度关注 CPU 效率和体积
对于资源极度受限的嵌入式设备,比如采用 ARM Cortex-A 系列的低端芯片,NCNN 的轻量化和手工优化优势就体现出来了。但团队需要接受其算子覆盖有限的事实,甚至可能需要自行补全一些算子。如果模型恰好是常见的视觉网络,且不需要 GPU 加速,NCNN 很难被替代。
落地建议:别在最后一步翻车
无论选哪个框架,以下几条经验能帮你避开不少坑:
- 尽早做模型转换和精度验证,不要等到集成阶段才发现算子不支持或精度不达标。
- 在真实目标设备上测试性能,模拟器或桌面端的跑分意义有限,内存带宽、散热、系统调度都会影响实际表现。
- 对量化做好预期管理,如果原始模型本身就很敏感,优先考虑 FP16 或混合精度方案,而不是强行 INT8。
- 关注推理之外的开销,比如图像前处理、后处理,这些在端侧常常是瓶颈,框架优化再好也可能被它们拖累。
- 保留退路,不要把所有逻辑硬编码到单一框架。抽象出推理接口,未来切换框架时成本会低很多。
边缘 AI 部署的复杂性往往不在于框架本身,而在于模型、硬件、系统、业务需求之间的互相拉扯。TensorFlow Lite、ONNX Runtime 和 NCNN 就像是三种不同风格的利刃,在合适的场景下都能切开难题,但选错了方向,调试的痛苦会成倍放大。最后的选择,一定是基于你自己的工程约束做出的权衡,而不是哪个框架在 benchmark 上多跑了几帧。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/447/