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 会导致推理结果错误或引擎不支持。
二、模型格式: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))
三、生态工具链:从导出到验证的完整链路
ONNX 不是单一工具,而是一条工具链。理解各工具的分工,能避免“用错工具解决错问题”。
| 工具 | 作用 | 典型命令/接口 |
|---|---|---|
| torch.onnx.export | PyTorch 模型导出为 ONNX | torch.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 契约出发,定位问题、验证结果。