外观
ONNX Runtime 跨平台
一、概念定义:把"模型"从"框架"里解放出来
ONNX Runtime(ORT)是微软自 2018 年开源维护的跨平台推理引擎,输入是 ONNX(Open Neural Network Exchange)格式的模型文件,输出是"在哪都能跑"的推理结果。它的核心承诺一句话:一份模型,到处运行。
ONNX 是 2017 年由 Facebook 和微软联合提出的开放中间表示(IR),目的是打破框架间的壁垒——PyTorch 训的模型导出成 ONNX,在 C++、Java、C#、JS、移动端、WebGPU 上都能跑。理解 ONNX 与 ORT 的关系是关键:
┌──────────────────────────────────────────────────┐
│ 训练框架(PyTorch / TF / JAX / PaddlePaddle) │
└─────────────────┬────────────────────────────────┘
│ 导出(torch.onnx.export / tf2onnx)
▼
┌──────────────────┐
│ ONNX IR (.onnx) │ ← 标准算子集 + 标准格式
└─────────┬────────┘
│ ONNX Runtime 加载
▼
┌───────────────────────────────────────────┐
│ Execution Provider 选择 │
│ CPU / CUDA / TensorRT / DirectML / │
│ CoreML / QNN / OpenVINO / DML / NNAPI │
└─────────────┬─────────────────────────────┘
│
▼
推理结果(同一次推理可能跨多个 EP)ORT 与 TensorRT 的关系值得专门讲:TensorRT 是"GPU 上的旗舰",ORT 是"全平台的中军"。ORT 自己有 CUDA EP,也能把节点分发给 TensorRT EP 加速——它是上层调度器,TensorRT 是它的"GPU 加速器"之一。
二、ONNX IR:标准算子集 + 计算图
ONNX 用 protobuf 定义 IR,核心三件套:
- 节点(Node):一个算子调用,含 op_type(如
Conv、MatMul)、attributes(如kernel_size=3)、inputs/outputs。 - 张量(Tensor):权重、激活,带 dtype(FP32/FP16/INT8/...)和 shape。
- 图(Graph):节点 + 张量 + 初始化器(权重)的 DAG。
每个算子在 ONNX Opset 里有定义。Opset 版本号很关键——版本越高支持的算子越多,但运行时不一定都支持。导出时建议"用最低够用的 opset"(PyTorch 模型一般 opset 14–17 够用)。
python
# 验证 ONNX 模型
import onnx
model = onnx.load("model.onnx")
onnx.checker.check_model(model) # 静态检查 IR 合法性
print(model.opset_import[0].version) # opset 版本
# 简化模型(消除常量折叠、形状推断产生的冗余节点)
import onnxsim
model_sim, ok = onnxsim.simplify(model)
onnx.save(model_sim, "model_sim.onnx")onnxsim 是必装工具,它会把 Shape、Gather、Reshape 这类能静态确定的节点折叠掉,简化后通常更利于 runtime 优化。
三、Execution Provider:插件式硬件后端
ORT 的"跨平台"靠 Execution Provider(EP) 实现。每个 EP 封装一组算子的硬件实现,ORT 在加载模型时按节点的能力集合把节点分配给最合适的 EP。
| EP | 平台 / 硬件 | 适用场景 |
|---|---|---|
| CPU EP | 全平台默认 | 通用兜底,AVX2/AVX512 自动启用 |
| CUDA EP | NVIDIA GPU | 比 CPU EP 快 5–20× |
| TensorRT EP | NVIDIA GPU | 在 CUDA EP 基础上把可融合子图交给 TRT(见 TensorRT) |
| DirectML EP | Windows + 任何 DDXI 12 GPU | 跨硬件 Windows 推理(含 AMD/Intel/NVIDIA) |
| CoreML EP | iOS / macOS | Apple Neural Engine 加速 |
| NNAPI EP | Android | Android NPU/DSP |
| QNN EP | Qualcomm Hexagon | 骁龙 NPU 高性能路径 |
| OpenVINO EP | Intel CPU/iGPU | 见 OpenVINO |
| ROCm EP | AMD GPU | ROCm 栈 |
| XNNPACK / WebGPU | 移动 / Web | 轻量 + 浏览器 |
节点分配与回退
EP 分配是"贪心 + 能力匹配"。默认顺序是:开发者声明的优先级 → EP capability。没被任何加速 EP 认领的节点回退到 CPU EP——这是 ORT 兼容性的兜底机制,但代价是跨 EP 的"上下文切换"会有性能损耗。
python
import onnxruntime as ort
# 指定 EP 优先级
session = ort.InferenceSession(
"model.onnx",
providers=[
("CUDAExecutionProvider", {
"device_id": 0,
"arena_extend_strategy": "kSameAsRequested",
"gpu_mem_limit": 8 * 1024 * 1024 * 1024, # 8GB
}),
"TensorrtExecutionProvider", # 第二顺位
"CPUExecutionProvider", # 兜底
],
)
# 查询实际分配情况
print(session.get_providers()) # 实际启用的 EP
print(session._get_provider_options()) # 每个 EP 的配置EP 优先级不是越激进越好
激进启用 TensorRT EP 会让某些节点被 TRT 抓走、跑在 TRT 子图里,子图与 CPU EP 之间需要 memcpy,反而拖慢。先用 trt_plugin_creation 工具或 onnxruntime perf_analyzer 看子图划分,再决定哪些节点强制 CPU/CUDA。
四、图优化管线(GRPO)
ORT 内部用 GRPO(Graph Optimizer Pipeline) 做图优化,分为若干级:
- Basic level(L1–L3):常量折叠、形状推断、冗余算子消除、Cast 消除。无硬件依赖,所有 EP 受益。
- Extended level(L4–L6):融合算子替换(Conv+BN+Relu → FusedConv)、子图替换。依赖 EP 能力。
- Layout optimizer:NCHW ↔ NHWC 转换,根据 EP 偏好选择布局。
- Quantization / Pre-processor:与量化工具
onnxruntime.quantization协同。
原始: Conv → Add(bias) → Relu
优化: FusedConv (Conv+Add+Relu 一次 kernel 调用)五、量化:ORT 自带的动态 / 静态量化
ORT 内置量化 API,详见 量化。
python
from onnxruntime.quantization import quantize_dynamic, quantize_static, QuantType, QuantFormat
# 动态量化(权重 INT8,激活运行时量化,零样本)
quantize_dynamic(
model_input="model.onnx",
model_output="model_int8_dynamic.onnx",
weight_type=QuantType.QInt8,
)
# 静态量化(权重 + 激活都 INT8,需要 calibration data)
quantize_static(
model_input="model.onnx",
model_output="model_int8_static.onnx",
calibration_data_reader=MyDataReader(...),
quant_format=QuantFormat.QDQ, # QDQ 通用性最好
per_channel=True,
)- Dynamic quantization:零校准数据,只量化权重,激活推理时算 scale。CPU 友好,GPU 收益小。
- Static quantization:要校准数据,量化权重 + 激活,性能更好,但精度损失要评估。
- QDQ (QuantizeLinear/DequantizeLinear) vs QOperator:QDQ 用标准 ONNX 算子表达量化,跨 EP 兼容性最好;QOperator 用专用量化算子,性能略好但兼容性差。
六、跨框架工具链
把 PyTorch 模型变成 ONNX 是最常见链路:
python
import torch
class Model(torch.nn.Module):
def forward(self, x):
return torch.relu(self.fc(x))
model = Model().eval()
dummy = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model, (dummy,),
"model.onnx",
input_names=["input"],
output_names=["output"],
opset_version=17,
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, # 动态 batch
dynamo=True, # PyTorch 2.x 推荐:基于 Dynamo 的导出,更稳
)
# 验证
import onnxruntime as ort
import numpy as np
sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
np.testing.assert_allclose(
sess.run(None, {"input": dummy.numpy()})[0],
model(dummy).detach().numpy(),
rtol=1e-3, atol=1e-4,
)导出常见坑
- 动态维度太多:dynamic_axes 不是越多越好。完全动态会让 ORT 无法预分配 arena,性能下降;只把 batch / seq_len 设动态即可。
- opset 太新:opset 18+ 算子在旧 ORT 上不识别。生产环境锁定 opset 14–17 + ORT 1.16+。
- 算子不支持:
torch.nn.functional.ctc_loss、自定义 LSTM 变体常不支持,要么换实现要么写 ONNX Custom Op。 dynamo=Truevs 老版torch.onnx.export:PyTorch 2.9+ 推荐 dynamo,但某些动态控制流场景老链路更稳,二者并存一段时间。
工具链全景:
- torch.onnx.export:PyTorch → ONNX
- tf2onnx:TF → ONNX
- onnxsim:模型简化
- onnx.checker + onnx.shape_inference:静态检查
- onnxruntime.tools.onnx_test_runner:跨平台跑测
- onnxruntime.transformers:BERT/Transformer 优化工具
- netron:可视化 ONNX 模型结构
七、性能数据:CPU 上的"反直觉"
ORT 的"反直觉"优势在 CPU:很多场景下 ONNX Runtime CPU EP 比原生 PyTorch CPU 还快——因为 ORT 用了 MKL-DNN(oneDNN)、AVX512、融合 GEMM,而 PyTorch CPU 默认路径优化较少。
| 模型 | 硬件 | 精度 | PyTorch CPU | ONNX Runtime CPU | 加速 |
|---|---|---|---|---|---|
| BERT-base | Xeon 8358 | FP32 | 18 ms | 11 ms | 1.6× |
| ResNet-50 | Xeon 8358 | INT8 | 8 ms | 4.5 ms | 1.8× |
| MobileNetV2 | Xeon 8358 | FP32 | 4.0 ms | 2.2 ms | 1.8× |
| YOLOv8-m | Xeon 8358 | FP16 | 22 ms | 15 ms | 1.5× |
GPU 上 ORT CUDA EP 与 TensorRT 比,性能落后 1.2–1.8×(TensorRT 在融合与 kernel 调优上更深),但开发门槛低很多。详见 引擎对比。
八、局限与边界
- 新算子支持滞后:自定义 attention、新激活函数在 ONNX opset 里滞后,需要等 opset 升级或写 Custom Op。
- GPU 极致性能不及 TensorRT:在 NVIDIA GPU 上,能跑 TensorRT EP 就别只用 CUDA EP;但 TensorRT EP 的 build 时间 + 内存开销也更大。
- 大模型(LLM)场景偏弱:ORT 自身没有 PagedAttention、continuous batching 等 LLM 关键优化——LLM 推理请用 vLLM 或 TensorRT-LLM。ORT 的强项是中小模型与跨平台。
- 动态控制流限制:ONNX 原生
If/Loop支持有限,PyTorch 的if x.sum() > 0这种数据依赖分支导出时容易翻车。 - EP 之间切换开销:跨 EP 的子图边界有 memcpy,节点分配策略要慎调。
九、与同类对比
| 方案 | 与 ORT 的关系 |
|---|---|
| TensorRT | ORT 的 TensorRT EP 调用 TRT 做子图加速;ORT 是上层、TRT 是 backend |
| OpenVINO | OpenVINO EP 让 ORT 能跑 Intel 硬件;也可独立用 OpenVINO Runtime(更深度优化 Intel CPU) |
| Triton 推理服务 | Triton 的 onnxruntime backend 内部就是 ORT;Triton 是服务层、ORT 是引擎 |
| llama.cpp | 都偏"跨平台",但 llama.cpp 专 LLM、ORT 专中小模型 |
| 移动端部署 | ORT Mobile 是 ORT 的移动端裁剪版 |
十、可继续追踪
- 概念页:图优化原理、算子融合、量化、GPU 优化原理、延迟与吞吐
- 案例页:TensorRT、OpenVINO、Triton 推理服务、移动端部署
- 实践页:引擎对比、调优实践、基准测试、避坑指南
- 资源页:术语表、硬件入门、Awesome 集合
参考资料
- ONNX Runtime 官方文档 — 性能与 API 权威
- ONNX IR Spec — IR 定义
- ONNX Operators — 算子规范
- Microsoft. ONNX Runtime Performance Tuning — 调优指南
- onnxsim — 模型简化工具
- tf2onnx — TF 转 ONNX
- Netron — 在线可视化 ONNX