1、ONNX基础:ONNX是什么、ONNX的由来、ONNX的生态与优势、ONNX模型结构解析
1.1 ONNX到底是什么?
ONNX,全称是Open Neural Network Exchange。说白了,它就是一个「模型交换格式」。你可以把它想象成AI界的通用语言。
我刚开始做模型部署那会儿,最头疼的就是框架不互通。PyTorch训练好的模型,想转到TensorRT上推理?得先转成Caffe,再转成别的格式,折腾半天还容易出错。ONNX的出现,彻底解决了这个问题。
核心定义:ONNX是一种开放式的模型表示标准,用于描述神经网络的计算图。它不依赖任何特定的训练框架,是模型从训练到部署的「中间桥梁」。
1.2 ONNX的由来:一段不得不说的历史
2017年,微软和Facebook联合推出了ONNX。为什么会有这个项目?
你想想看,当时AI圈是什么局面?PyTorch、TensorFlow、Caffe、MXNet……各家都有自己的模型格式。训练好的模型想换个平台跑,基本等于重写一遍。我在项目中就遇到过这种情况:客户用PyTorch训练了一个检测模型,但他们的生产环境只支持TensorFlow Serving。那叫一个折腾。
ONNX的目标很明确:一次训练,到处部署。后来微软、Facebook、亚马逊、英特尔、英伟达等巨头纷纷加入,ONNX成了事实上的行业标准。
个人经验:我记得ONNX 1.0刚发布时,支持的算子还很少,很多模型根本转不了。但现在ONNX已经发展到1.15+版本,算子集超过200个,覆盖了绝大多数主流模型结构。
1.3 ONNX的生态与优势
ONNX的优势,我总结为三点:
- 框架互通:PyTorch、TensorFlow、Keras、MXNet……几乎所有主流框架都支持导出ONNX。你可以在PyTorch里训练,在TensorRT里推理,中间只需要一个ONNX模型。
- 硬件适配:英伟达的TensorRT、英特尔的OpenVINO、苹果的CoreML、ARM的NN……这些推理引擎都原生支持ONNX。说白了,ONNX是连接训练框架和推理引擎的「万能插头」。
- 工具链丰富:ONNX Runtime、ONNX Simplifier、ONNX Optimizer……社区提供了大量工具,帮你优化、调试、可视化ONNX模型。
避坑指南:我曾经遇到过一个坑——ONNX的算子兼容性问题。有些框架特有的算子(比如PyTorch的F.grid_sample),ONNX原生不支持。这时候就需要用ONNX的「自定义算子」机制,或者用ONNX Simplifier做图优化。嗯,这块后面会详细讲。
1.4 ONNX模型结构解析
一个ONNX模型,本质上是一个计算图。它由三部分组成:
| 组件 | 说明 | 我的理解 |
|---|---|---|
| Graph | 计算图主体,包含所有节点和边 | 相当于整个神经网络的结构蓝图 |
| Node | 图中的每个操作节点(Conv、Relu、MatMul等) | 就是网络中的每一层 |
| Tensor | 节点之间的数据流(权重、输入、输出) | 就是网络中的参数和中间结果 |
我们来看一个实际的ONNX模型结构。假设你有一个简单的卷积网络,导出成ONNX后,它的结构大概是这样的:
# 一个简化的ONNX模型结构示例
ir_version: 8
graph {
node {
input: "input"
output: "conv1_output"
op_type: "Conv"
attribute {
name: "kernel_shape"
ints: 3
ints: 3
}
attribute {
name: "strides"
ints: 1
ints: 1
}
}
node {
input: "conv1_output"
output: "relu1_output"
op_type: "Relu"
}
node {
input: "relu1_output"
output: "output"
op_type: "MaxPool"
attribute {
name: "kernel_shape"
ints: 2
ints: 2
}
}
initializer {
name: "conv1_weight"
data_type: 1 # FLOAT
dims: 16
dims: 3
dims: 3
dims: 3
raw_data: "..."
}
input {
name: "input"
type {
tensor_type {
elem_type: 1
shape {
dim { dim_value: 1 }
dim { dim_value: 3 }
dim { dim_value: 224 }
dim { dim_value: 224 }
}
}
}
}
output {
name: "output"
type {
tensor_type {
elem_type: 1
shape {
dim { dim_value: 1 }
dim { dim_value: 16 }
dim { dim_value: 112 }
dim { dim_value: 112 }
}
}
}
}
}
注意:ONNX模型中的权重数据(initializer)是以二进制格式存储的。你看到的raw_data字段就是序列化后的权重。所以ONNX文件通常比原始框架的模型文件大一些,因为它是自包含的——模型结构+权重都在一个文件里。
1.5 如何查看ONNX模型?
我常用的工具有两个:
- Netron:一个在线可视化工具,直接把ONNX文件拖进去就能看到完整的计算图。节点、边、参数一目了然。我个人习惯用这个做快速检查。
- ONNX Runtime的Python API:可以用代码解析模型结构,适合批量处理或自动化检查。
举个例子,用Python读取ONNX模型:
import onnx
# 加载模型
model = onnx.load("model.onnx")
# 打印计算图信息
print(f"IR版本: {model.ir_version}")
print(f"算子集版本: {model.opset_import[0].version}")
# 遍历所有节点
for node in model.graph.node:
print(f"节点: {node.name}, 类型: {node.op_type}")
print(f" 输入: {node.input}")
print(f" 输出: {node.output}")
# 查看输入输出
print(f"输入: {model.graph.input[0].name}")
print(f"输出: {model.graph.output[0].name}")
小技巧:如果你发现导出的ONNX模型结构很乱,有很多冗余节点,可以用ONNX Simplifier做一次简化。它能合并一些不必要的操作,让计算图更干净。我在项目中几乎每次都会跑一遍simplify,尤其是准备转TensorRT之前。
1.6 为什么ONNX对TensorRT这么重要?
TensorRT的输入格式,最推荐的就是ONNX。为什么?
因为TensorRT的解析器(Parser)对ONNX的支持最完善。你从PyTorch导出的ONNX模型,可以直接丢给TensorRT做编译优化。相比之下,如果你用Caffe或者UFF格式,支持的算子就少很多,经常需要手动处理。
我个人的工作流是这样的:
- PyTorch训练 → 导出ONNX
- ONNX Simplifier做图优化
- ONNX → TensorRT编译
- TensorRT引擎推理
这个流程我用了两年多,基本没出过大问题。嗯,前提是ONNX导出时要注意一些细节,比如动态轴、算子兼容性这些。后面几章我会专门讲。
总结一下:ONNX是模型部署的基石。你不需要成为ONNX专家,但一定要理解它的核心概念和结构。因为后续所有的TensorRT优化,都是基于ONNX模型来做的。这一章打好基础,后面学起来就顺了。