Skip to content

ONNX Runtime 跨平台

本页速览 ONNX Runtime 把"模型即文件"做到了极致——一个 .onnx 文件在 CPU、CUDA、DirectML、CoreML、QNN 上跑得起来,靠的是 Execution Provider 机制与一套严格的图优化管线。本文拆解 ONNX IR、EP 机制、GRPO 优化器与跨框架工具链。

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(如 ConvMatMul)、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 是必装工具,它会把 ShapeGatherReshape 这类能静态确定的节点折叠掉,简化后通常更利于 runtime 优化。

三、Execution Provider:插件式硬件后端

ORT 的"跨平台"靠 Execution Provider(EP) 实现。每个 EP 封装一组算子的硬件实现,ORT 在加载模型时按节点的能力集合把节点分配给最合适的 EP。

EP平台 / 硬件适用场景
CPU EP全平台默认通用兜底,AVX2/AVX512 自动启用
CUDA EPNVIDIA GPU比 CPU EP 快 5–20×
TensorRT EPNVIDIA GPU在 CUDA EP 基础上把可融合子图交给 TRT(见 TensorRT
DirectML EPWindows + 任何 DDXI 12 GPU跨硬件 Windows 推理(含 AMD/Intel/NVIDIA)
CoreML EPiOS / macOSApple Neural Engine 加速
NNAPI EPAndroidAndroid NPU/DSP
QNN EPQualcomm Hexagon骁龙 NPU 高性能路径
OpenVINO EPIntel CPU/iGPUOpenVINO
ROCm EPAMD GPUROCm 栈
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 协同。

详见 图优化原理算子融合。一个典型 fusion:

原始:   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,
)

导出常见坑

  1. 动态维度太多:dynamic_axes 不是越多越好。完全动态会让 ORT 无法预分配 arena,性能下降;只把 batch / seq_len 设动态即可。
  2. opset 太新:opset 18+ 算子在旧 ORT 上不识别。生产环境锁定 opset 14–17 + ORT 1.16+。
  3. 算子不支持torch.nn.functional.ctc_loss、自定义 LSTM 变体常不支持,要么换实现要么写 ONNX Custom Op。
  4. dynamo=True vs 老版 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 CPUONNX Runtime CPU加速
BERT-baseXeon 8358FP3218 ms11 ms1.6×
ResNet-50Xeon 8358INT88 ms4.5 ms1.8×
MobileNetV2Xeon 8358FP324.0 ms2.2 ms1.8×
YOLOv8-mXeon 8358FP1622 ms15 ms1.5×

GPU 上 ORT CUDA EP 与 TensorRT 比,性能落后 1.2–1.8×(TensorRT 在融合与 kernel 调优上更深),但开发门槛低很多。详见 引擎对比

八、局限与边界

  1. 新算子支持滞后:自定义 attention、新激活函数在 ONNX opset 里滞后,需要等 opset 升级或写 Custom Op。
  2. GPU 极致性能不及 TensorRT:在 NVIDIA GPU 上,能跑 TensorRT EP 就别只用 CUDA EP;但 TensorRT EP 的 build 时间 + 内存开销也更大。
  3. 大模型(LLM)场景偏弱:ORT 自身没有 PagedAttention、continuous batching 等 LLM 关键优化——LLM 推理请用 vLLMTensorRT-LLM。ORT 的强项是中小模型与跨平台。
  4. 动态控制流限制:ONNX 原生 If / Loop 支持有限,PyTorch 的 if x.sum() > 0 这种数据依赖分支导出时容易翻车。
  5. EP 之间切换开销:跨 EP 的子图边界有 memcpy,节点分配策略要慎调。

九、与同类对比

方案与 ORT 的关系
TensorRTORT 的 TensorRT EP 调用 TRT 做子图加速;ORT 是上层、TRT 是 backend
OpenVINOOpenVINO EP 让 ORT 能跑 Intel 硬件;也可独立用 OpenVINO Runtime(更深度优化 Intel CPU)
Triton 推理服务Triton 的 onnxruntime backend 内部就是 ORT;Triton 是服务层、ORT 是引擎
llama.cpp都偏"跨平台",但 llama.cpp 专 LLM、ORT 专中小模型
移动端部署ORT Mobile 是 ORT 的移动端裁剪版

十、可继续追踪

参考资料