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模型导出成功!")
这段代码做了什么事?
- 用PyTorch加载一个预训练的ResNet18模型。
- 构造一个虚拟输入(batch_size=1, 3通道, 224x224)。
- 调用
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可视化工具看模型图——那个工具我第一次用的时候,感觉就像打开了新世界的大门。