2. ONNX基础概念

在动手导出与优化之前,必须先把 ONNX 的“世界观”建立起来。ONNX(Open Neural Network Exchange)本质上是一套开放的模型中间表示规范:它用统一的数据结构描述神经网络的计算过程,让不同训练框架(PyTorch、TensorFlow、PaddlePaddle)产出的模型,能够被不同推理引擎(ONNX Runtime、TensorRT、OpenVINO)消费。理解它的五个核心概念——计算图、算子、张量、模型格式、opset 版本——是后续所有导出与图优化工作的地基。

一、核心原理:ONNX 用一张有向图描述计算

ONNX 模型的核心是一张有向无环图(DAG)。图中的节点是算子(Operator),边是张量(Tensor),张量沿着边的方向流动,从输入流向输出。每个算子接收若干输入张量,产生若干输出张量,并携带自己的属性(attribute),例如卷积的 kernel_shape、strides、pads。

张量是 ONNX 中唯一的数据载体,它由三要素定义:数据类型(如 float32、int64、uint8)、形状(shape,可含动态维度如 "batch")、数据本身(可以是初始值 initializer,也可以是运行时才产生的中间结果)。模型权重通常以 initializer 形式固化在图中,而激活值则是运行时张量。

算子不是随意定义的,而是由 opset(Operator Set) 版本约束。opset 是一组算子的版本化集合,每个 opset 版本会新增算子、修改算子语义或废弃旧算子。例如 Resize 算子在 opset 10、11、13 之间语义差异明显,导出时选错 opset 会导致推理结果错误或引擎不支持。

ONNX 模型结构:张量沿算子流动的有向无环图 输入张量 input: [N,3,224,224] Conv kernel=3x3 strides=1 Relu 逐元素激活 Gemm 全连接 输出张量 output: [N,10] 权重 initializer opset 版本约束算子语义 · 属性决定行为

二、模型格式:protobuf 序列化与文件结构

ONNX 模型以 Protocol Buffers 序列化,通常保存为 .onnx 文件。一个模型文件在逻辑上包含以下部分:

  • graph:计算图本体,含 node 列表、输入输出、initializer。
  • opset_import:声明模型使用的 opset 版本,可含多个域(如默认域和 ai.onnx.ml)。
  • ir_version:ONNX IR 规范版本,与 opset 版本相互独立。
  • metadata_props:生产者、版本等元信息,便于溯源。

可以用 Python 快速查看模型结构:

import onnx

model = onnx.load("model.onnx")
print("IR version:", model.ir_version)
print("Opset:", [(o.domain, o.version) for o in model.opset_import])
print("Inputs:", [i.name for i in model.graph.input])
print("Nodes:", len(model.graph.node))

# 逐节点查看算子类型与属性
for node in model.graph.node[:5]:
    print(node.op_type, list(node.input), list(node.output))
导出后第一件事就是检查 opset_import 与 ir_version,它们决定了模型能被哪些推理引擎加载。ONNX Runtime 对高版本 opset 的支持通常滞后于 ONNX 规范发布。

三、生态工具链:从导出到验证的完整链路

ONNX 不是单一工具,而是一条工具链。理解各工具的分工,能避免“用错工具解决错问题”。

工具作用典型命令/接口
torch.onnx.exportPyTorch 模型导出为 ONNXtorch.onnx.export(model, dummy, "m.onnx", opset_version=17)
onnx模型加载、检查、修改、版本转换onnx.checker.check_model()
onnxruntime推理执行与性能验证InferenceSession("m.onnx")
onnxsim常量折叠、冗余算子消除onnxsim in.onnx out.onnx
Netron可视化计算图结构图形界面打开 .onnx

一个稳健的导出流程通常是:导出 → checker 校验 → onnxsim 简化 → ORT 推理比对 → Netron 目视检查。其中推理比对是发现“导出成功但结果错误”的关键步骤。

四、误区与复盘:新手最容易踩的坑

误区一:把 opset 版本当成 IR 版本。两者独立演进,opset 决定算子语义,ir_version 决定文件结构。升级 opset 不一定需要升级 ir_version。

误区二:导出成功就认为模型正确。动态轴设置错误、算子语义差异都会导致输出数值偏差。必须用同一输入分别跑原框架和 ORT,比对最大误差。

误区三:忽略动态维度。若导出时固定了 batch=1,后续想变 batch 推理就会失败。应在导出时用 dynamic_axes 声明可变维度。

复盘一个典型场景:某模型导出后 ORT 推理报 Unsupported opset version。排查发现导出用了 opset 18,而当前 ORT 版本仅支持到 17。解决方案不是升级 ORT,而是回退导出 opset,或使用 onnx 的版本转换工具降级。这正说明:opset 版本是导出方与推理方之间的契约,必须双方协商一致。

掌握这五个概念后,你就拥有了阅读任意 ONNX 模型的“语法”。下一阶段无论是做图优化、算子融合还是跨引擎部署,都能从这张有向无环图和它的 opset 契约出发,定位问题、验证结果。