第9篇:ONNX Runtime推理实现 — 备用推理引擎方案
第9篇:ONNX Runtime推理实现 — 备用推理引擎方案
一、ONNX Runtime简介
1.1 什么是ONNX Runtime
ONNX Runtime是微软推出的一款跨平台推理引擎,专门用于运行ONNX格式的模型。它和ONNX格式是"亲兄弟"——ONNX定义了模型的标准格式,ONNX Runtime则是这个格式的"官方御用"推理引擎。
ONNX Runtime的设计目标:
- 高性能:通过各种图优化和算子优化,提供最佳的推理性能
- 跨平台:支持Windows、Linux、macOS、Android、iOS等几乎所有平台
- 跨硬件:支持CPU、GPU、NPU等各种硬件(通过Execution Provider机制)
- 灵活性:既可以用于服务器端部署,也可以用于移动端和边缘设备
如果说OpenVINO是"Intel CPU御用引擎",那ONNX Runtime就是"全能型选手"——它在哪都能跑,通用性极强。
1.2 ONNX Runtime的核心优势
ONNX Runtime之所以能成为最流行的推理引擎之一,有几个关键优势:
通用性强
- 原生支持ONNX格式,不存在兼容性问题
- 支持的算子最全面,几乎所有ONNX算子都能跑
- 各种框架导出的ONNX模型,在ONNX Runtime上基本都能直接运行
跨平台能力
- Windows / Linux / macOS 桌面端
- Android / iOS 移动端
- 嵌入式设备
- 浏览器(WebAssembly版本)
- 一套代码,到处运行
Execution Provider机制
这是ONNX Runtime最具特色的设计。它本身是一个"调度器",真正的计算可以交给不同的后端(Execution Provider)来执行:
- CPU:默认的CPU后端,基于MLAS(Microsoft Linear Algebra Subroutine)
- CUDA:NVIDIA GPU加速
- TensorRT:NVIDIA GPU深度优化
- OpenVINO:Intel硬件加速
- DirectML:Windows平台的GPU加速
- CoreML:Apple设备加速
- NNAPI:Android神经网络API
- …
这种"插件化"的设计,让ONNX Runtime可以轻松扩展到各种硬件平台。
性能优秀
- 图优化能力强(算子融合、常量折叠、死代码消除等)
- 内存优化到位
- 支持量化(INT8、FP16)
- 在CPU上的性能已经非常接近甚至超过很多专用引擎
1.3 为什么需要双引擎
GrainServer项目同时集成了OpenVINO和ONNX Runtime两个推理引擎。你可能会问:有一个不就行了吗?为什么要搞两个?
这其实是一种很经典的工程设计思想——主备方案 + 灵活选择。
让我们从几个维度分析双引擎的价值:
维度1:容错与兼容性
- OpenVINO虽然在Intel CPU上性能好,但可能遇到某些算子不支持的问题
- ONNX Runtime的算子支持最全面,兼容性最好
- 如果OpenVINO跑不起来(比如驱动问题、依赖缺失),可以立刻切换到ONNX Runtime
- 双引擎相当于给系统上了"双保险"
维度2:性能对比与选择
- 不同的模型、不同的硬件、不同的batch size,最优引擎可能不一样
- 有两个引擎可以实际跑一跑对比性能,选最快的那个
- 比如在AMD CPU上,ONNX Runtime可能比OpenVINO更快
- 在Intel CPU上,通常OpenVINO更快,但也不是绝对的
维度3:部署灵活性
- 用户的电脑千差万别,有的装了OpenVINO运行时,有的没有
- ONNX Runtime的依赖更简单,部署更方便
- 可以根据部署环境灵活选择使用哪个引擎
维度4:验证与交叉核对
- 同一个模型,两个引擎都跑一遍,可以互相验证结果是否一致
- 如果结果差异很大,说明可能哪里有bug
- 这是一种很实用的质量保障手段
在GrainServer项目中,OpenVINO是默认的主力引擎(openvino_predict是默认调用的),ONNX Runtime是备用方案(onnx_predict被注释掉了,但代码是完整的)。这种"主备双引擎"的设计,体现了很强的工程思维。
二、InferenceSession创建
2.1 会话(Session)的概念
在ONNX Runtime中,推理是通过InferenceSession(推理会话)来完成的。什么是会话?你可以把它理解为"模型的一个运行实例"。
创建会话的过程,相当于:
- 把模型从磁盘加载到内存
- 进行图优化
- 分配内存
- 准备好一切运行时需要的资源
会话创建好之后,就可以反复调用它的run方法来执行推理了。
让我们看OnnxRuntimeInfer类的构造函数:
importonnxruntimeclassOnnxRuntimeInfer(object):def__init__(self,onnx_path,input_shape,num_classes):self.model_tools=ModelTools()self.model_config=ModelConfig(onnx_path,input_shape,num_classes)self.onnx_path=self.model_config.get_config("onnx_path")self.input_shape=self.model_config.get_config("input_shape")self.num_classes=self.model_config.get_config("num_classes")self.onnx_session=onnxruntime.InferenceSession(self.onnx_path)self.input_name=[input.nameforinputinself.onnx_session.get_inputs()]self.output_name=[output.nameforoutputinself.onnx_session.get_outputs()]# ... 颜色映射表 ...self.model_tools.show_config(self.model_config.config)核心的一行是:
self.onnx_session=onnxruntime.InferenceSession(self.onnx_path)这行代码创建了一个推理会话。让我们看看它背后发生了什么:
- 模型加载:读取ONNX文件,解析计算图
- 图优化:根据设置的优化级别,对计算图进行各种优化
- 执行提供程序选择:选择合适的Execution Provider(默认是CPU)
- 内存规划:规划推理时的内存使用
- 内核编译:为每个算子选择并编译最优的内核实现
创建会话是一次性的开销,创建完成后,每次推理的速度就很快了。
2.2 与OpenVINO加载流程的对比
同样是加载模型,ONNX Runtime和OpenVINO的API风格有什么不同呢?
| 步骤 | OpenVINO | ONNX Runtime |
|---|
