1、ONNX初探:什么是ONNX?为什么需要ONNX?ONNX在AI部署中的生态位

1.1 从一个真实的故事说起

我记得刚入行那会儿,团队里有个项目特别折腾。

算法同学用PyTorch训了一个图像分类模型,精度不错。到了部署阶段,我们得把它搬到C++推理引擎上跑。你猜怎么着?PyTorch的模型格式,那个引擎根本不认。没办法,只能手动把网络结构一层层翻译过去,再逐个核对权重。

整整两周,我都在跟各种维度对不上、算子不兼容的问题死磕。最后虽然跑通了,但心里总有个疙瘩——这中间要是哪一步写错了,线上就得翻车。

后来我接触到ONNX,才恍然大悟:原来早有人替我们想好了这条路。

1.2 什么是ONNX?

ONNX,全称是Open Neural Network Exchange。说白了,它就是AI模型界的「通用语言」。

你想想看,世界上有那么多深度学习框架:PyTorch、TensorFlow、PaddlePaddle、MXNet……每个框架都有自己的模型保存格式。PyTorch存成.pt或.pth,TensorFlow存成.pb或.h5,彼此之间互不兼容。

ONNX要解决的就是这个痛点。它定义了一套统一的计算图表示方法。不管你是用哪个框架训练的模型,都可以导出成ONNX格式。然后,任何支持ONNX的推理引擎都能直接加载运行。

核心要点:ONNX不是一个新的训练框架,它只是一个中间表示格式。你的模型还是在PyTorch或TensorFlow里训练,只是在部署前多了一步——导出为ONNX。

1.3 为什么需要ONNX?

这个问题,我在项目里体会特别深。原因主要有三个:

  • 框架解耦:训练和推理可以分开选型。你可以在PyTorch里训,然后导出ONNX,用TensorRT或者ONNX Runtime去推理。各取所长,互不绑架。
  • 硬件适配:不同的硬件厂商(NVIDIA、Intel、ARM)都有自己的推理优化库。ONNX作为一个中间层,让模型可以方便地对接各种硬件加速器。我在部署边缘设备时,就靠ONNX在CPU和NPU之间来回切换,省了不少事。
  • 生态互通:很多模型库(比如Hugging Face、TorchVision)都直接提供ONNX版本的预训练模型。你下载下来就能用,不用纠结框架版本对不对。

我的经验:有一次客户要求模型必须跑在华为昇腾芯片上。训练用的是PyTorch,但昇腾的推理引擎只认ONNX。我们花了半天把模型导出成ONNX,再做一些算子适配,就顺利跑通了。如果没有ONNX,这活真不知道要干多久。

1.4 ONNX在AI部署中的生态位

要理解ONNX的位置,你得先看清AI部署的全链路:

阶段 工具 ONNX的角色
模型训练 PyTorch / TensorFlow / PaddlePaddle 不参与
模型导出 torch.onnx.export / tf2onnx 作为导出目标格式
模型优化 ONNX Runtime / TensorRT / OpenVINO 作为输入格式
模型推理 ONNX Runtime / TensorRT / Tengine 直接加载运行

你看,ONNX正好卡在训练和推理中间。它不参与训练,也不直接做推理加速。它的核心价值就是「翻译」——把各种框架的模型,翻译成一套通用的计算图描述。

嗯,这里要注意:ONNX不是万能的。有些框架特有的算子,ONNX可能不支持。这时候就需要做算子映射或者自定义实现。我在项目中就遇到过几次,PyTorch里的F.grid_sample在ONNX里没有直接对应,得绕个弯子才能导出。

1.5 ONNX的核心组件

ONNX本身由两部分组成:

  • ONNX IR(中间表示):定义了计算图的数据结构。包括节点(算子)、张量(权重和中间结果)、图拓扑关系等。说白了,就是一套描述模型结构的「语法」。
  • ONNX OpSet(算子集):定义了标准算子库。比如Conv、Relu、BatchNormalization这些常见操作,都有对应的ONNX算子。每个版本会新增或废弃一些算子。

避坑指南:我曾经因为OpSet版本没选对,导出的ONNX模型在推理引擎上死活跑不起来。后来发现,高版本的OpSet引入了新算子,但推理引擎只支持低版本。所以导出时,建议先确认目标推理引擎支持的OpSet版本范围。

1.6 一个简单的例子

光说不练假把式。我们来看一个最简单的ONNX导出流程:

import torch
import torch.nn as nn

# 定义一个简单的网络
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = nn.Conv2d(3, 16, 3)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

model = SimpleNet()
model.eval()

# 构造一个虚拟输入
dummy_input = torch.randn(1, 3, 224, 224)

# 导出为ONNX
torch.onnx.export(
    model,
    dummy_input,
    "simplenet.onnx",
    input_names=["input"],
    output_names=["output"],
    opset_version=11
)

print("ONNX模型已导出")

跑完这段代码,你就会得到一个simplenet.onnx文件。这个文件里包含了网络的结构、权重、输入输出信息。任何支持ONNX的推理引擎都能读懂它。

个人习惯:我每次导出ONNX后,都会用onnx.checker.check_model()验证一下模型是否完整。这一步虽然简单,但能提前发现很多低级错误。

1.7 小结

ONNX不是什么高深莫测的东西。它就是一个中间格式,一个「翻译官」。它的存在,让AI模型的训练和部署可以解耦,让不同框架、不同硬件之间的协作成为可能。

在接下来的课程里,我们会一步步深入ONNX的内部结构,学习如何解析、调试、优化ONNX模型。你会发现,掌握了ONNX,就等于掌握了AI部署的「通用钥匙」。

准备好了吗?我们下一章见。