1、ONNX基础认知:什么是ONNX、ONNX的诞生背景与优势、ONNX生态全景图、ONNX与PyTorch/TensorFlow的关系

大家好,我是你们这趟ONNX落地之旅的向导。咱们直接开门见山——今天聊ONNX。

说实话,我最早接触ONNX是在2018年。那时候团队里模型训练用PyTorch,推理部署却要用TensorFlow Serving,两边模型格式不互通,每次转换都像在拆炸弹。后来遇到ONNX,才算是找到了一个通用的“中间人”。

1.1 什么是ONNX?

ONNX,全称Open Neural Network Exchange,直译过来就是“开放神经网络交换格式”。

说白了,它就是一个模型描述语言。你训练好的模型,不管是用PyTorch、TensorFlow还是其他框架写的,都可以导出成一个.onnx文件。这个文件里记录了网络的结构、权重、算子,甚至输入输出的数据类型和形状。

你可以把它想象成模型界的“通用语言”。就像不同国家的人用英语交流一样,不同框架的模型用ONNX来互通。

核心要点:ONNX不是一个新的训练框架,它只是一个中间表示(Intermediate Representation, IR)。你不需要用ONNX来训练模型,只需要在训练完成后,把模型“翻译”成ONNX格式。

1.2 ONNX的诞生背景与优势

为什么会有ONNX?这得从2017年说起。

当时AI框架百花齐放,PyTorch、TensorFlow、Caffe2、MXNet……每个框架都有自己的模型格式。这就带来一个很现实的问题:

你在PyTorch里训练了一个牛逼的模型,想部署到移动端,结果发现TensorFlow Lite只认自己的格式。怎么办?

要么重写一遍模型,要么写一堆转换脚本。这两种方式都让人头大。我在一个工业质检项目里就踩过这个坑——模型从PyTorch转到TensorFlow,精度掉了2个点,排查了整整三天才发现是某个算子实现不一致。

所以,2017年9月,微软和Facebook联合推出了ONNX。后来亚马逊、英特尔、AMD、NVIDIA等巨头也纷纷加入。它的目标很明确:让模型在不同框架和硬件之间自由流动。

ONNX的优势,我总结为三点:

  • 互操作性:一次导出,到处运行。你可以在PyTorch训练,在ONNX Runtime推理,或者在TensorRT上加速。
  • 硬件加速:NVIDIA的TensorRT、Intel的OpenVINO、AMD的ROCm,都原生支持ONNX。这意味着你可以直接利用硬件厂商的优化库,不用自己手写算子。
  • 生产环境友好:ONNX Runtime是微软推出的高性能推理引擎,支持C++、Python、C#等多种语言,部署起来非常方便。

个人经验:我建议你在项目初期就考虑ONNX。哪怕你当前只用PyTorch,也养成导出ONNX的习惯。这样后续换硬件、换框架时,你会感谢自己当初的“多此一举”。

1.3 ONNX生态全景图

ONNX不是孤立的,它背后有一套完整的生态。我画个图帮你理解:

层级 组件 说明
模型格式层 ONNX Protobuf 模型序列化格式,基于Protocol Buffers
算子标准层 ONNX Operators 定义了标准算子集,如Conv、Relu、MatMul等
推理引擎层 ONNX Runtime 微软出品的高性能推理引擎,支持CPU/GPU/NPU
硬件加速层 TensorRT, OpenVINO, CoreML 各硬件厂商的优化库,直接消费ONNX模型
工具链层 ONNX Simplifier, ONNX Optimizer 模型简化、优化、调试工具

嗯,这里要注意一点:ONNX生态里最核心的其实是算子标准。为什么?因为不同框架对同一个算子的实现可能有细微差别。比如PyTorch的Resize和TensorFlow的ResizeBilinear,对齐方式不同。ONNX通过定义统一的算子规范,来消除这种差异。

我在实际项目中遇到过一个问题:导出的ONNX模型在ONNX Runtime上跑没问题,但转到TensorRT就报错。后来发现是某个自定义算子不在ONNX标准算子集里。所以,尽量使用标准算子,少用自定义算子,这是避坑的第一条铁律。

避坑指南:我曾经因为用了PyTorch的torch.where,导出ONNX后算子不兼容,折腾了两天。后来改用torch.masked_select + 标准算子组合才解决。记住:ONNX不是万能的,它只支持标准算子集里的操作。

1.4 ONNX与PyTorch/TensorFlow的关系

这个问题很多新手会搞混。我直接说结论:

  • ONNX不是PyTorch的替代品,也不是TensorFlow的替代品。它只是一个中间格式。
  • PyTorch和TensorFlow是训练框架,你用它来设计网络、训练参数。
  • ONNX是模型交换格式,你把训练好的模型导出成ONNX,然后交给其他工具去推理。

举个例子:

# PyTorch训练
import torch
import torchvision.models as models

model = models.resnet18(pretrained=True)
model.eval()

# 导出ONNX
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    model, 
    dummy_input, 
    "resnet18.onnx",
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}
)
print("ONNX模型导出成功!")

这段代码做了什么事?

  1. 用PyTorch加载一个预训练的ResNet18模型。
  2. 构造一个虚拟输入(batch_size=1, 3通道, 224x224)。
  3. 调用torch.onnx.export导出ONNX文件。

导出之后,这个resnet18.onnx文件就可以被ONNX Runtime、TensorRT、OpenVINO等工具直接加载推理了。你甚至可以用C++来调用它,完全脱离Python环境。

对于TensorFlow用户,流程类似:

# TensorFlow导出ONNX(需要tf2onnx工具)
import tensorflow as tf
import tf2onnx

model = tf.keras.applications.ResNet50(weights="imagenet")
model.output_names = ["output"]

# 导出ONNX
spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name="input"),)
output_path = model.output_names[0] + ".onnx"
model_proto, _ = tf2onnx.convert.from_keras(model, input_signature=spec, output_path=output_path)
print("TensorFlow模型导出ONNX成功!")

你想想看,有了ONNX之后,你完全不用纠结“我用PyTorch训练,但客户要求用TensorFlow部署”这种问题了。直接导出ONNX,两边都认。

一句话总结:PyTorch/TensorFlow是“造模型”的,ONNX是“运模型”的。各司其职,互不冲突。

好了,第一章的内容就到这里。ONNX的基础认知你心里应该有数了。下一章我们会深入ONNX的模型结构,看看.onnx文件里到底长什么样。到时候我会手把手教你用Netron可视化工具看模型图——那个工具我第一次用的时候,感觉就像打开了新世界的大门。