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格式,支持的算子就少很多,经常需要手动处理。

我个人的工作流是这样的:

  1. PyTorch训练 → 导出ONNX
  2. ONNX Simplifier做图优化
  3. ONNX → TensorRT编译
  4. TensorRT引擎推理

这个流程我用了两年多,基本没出过大问题。嗯,前提是ONNX导出时要注意一些细节,比如动态轴、算子兼容性这些。后面几章我会专门讲。

总结一下:ONNX是模型部署的基石。你不需要成为ONNX专家,但一定要理解它的核心概念和结构。因为后续所有的TensorRT优化,都是基于ONNX模型来做的。这一章打好基础,后面学起来就顺了。