3、ONNX算子概览:标准算子集(ai.onnx)、算子版本演进、常用算子分类
好,咱们进入第三章。这一章,说白了就是给你一张ONNX的“全家福”。
你想想看,一个模型从PyTorch或者TensorFlow导出来,变成ONNX格式,里面到底装了些什么?核心就是一堆算子(Operator)。就像乐高积木,不同的算子拼在一起,就搭出了整个神经网络。
我个人习惯,拿到一个陌生的ONNX模型,第一件事就是打开Netron看一眼算子列表。这能让我快速判断:这个模型有没有踩坑?有没有用了不常见的算子?
3.1 标准算子集:ai.onnx
ONNX定义了一套标准算子,放在ai.onnx这个命名空间下。你可以把它理解为“官方通用语言”。
为什么要有这个标准?因为不同框架(PyTorch、TensorFlow、Keras)都有自己的算子,比如PyTorch的torch.nn.Conv2d,到了ONNX里就得映射成Conv。这个映射过程,就是ONNX转换器干的核心活。
核心要点:
- ai.onnx:官方标准算子集,所有ONNX runtime都必须支持。
- ai.onnx.ml:机器学习扩展算子集,比如SVM、决策树这些传统ML算法。
- ai.onnx.training:训练相关算子,一般推理用不到。
我在项目中遇到过一个问题:某个模型导出来,在ONNX Runtime上跑报错,说算子不支持。后来一查,发现它用了ai.onnx.ml下的一个算子,而我们的推理引擎只实现了ai.onnx标准集。嗯,这里要注意,跨平台部署时,一定要确认目标平台支持哪些算子集。
3.2 算子版本演进:别被版本坑了
ONNX算子是有版本的。每个算子从诞生到现在,可能经历了多次升级。比如Conv算子,从opset 1就有了,但到了opset 11增加了dilations参数的支持。
为什么会这样?因为深度学习技术在发展,新的需求不断出现。比如Transformer火了之后,ONNX就在opset 14里加入了Attention算子。
| Opset版本 | 发布时间 | 重要变化 |
|---|---|---|
| 1-6 | 2017-2018 | 基础算子定义,Conv、Pool、FC等 |
| 7-10 | 2018-2019 | 增加RNN、LSTM、GRU支持 |
| 11-13 | 2020-2021 | 量化算子、动态形状支持 |
| 14-18 | 2021-2023 | Transformer相关算子、稀疏算子 |
| 19+ | 2023-至今 | 更多高级算子,比如GroupNorm、LayerNorm |
避坑指南:
我曾经在部署一个YOLOv5模型时,发现导出时用了opset 12,但目标设备的ONNX Runtime只支持到opset 10。结果就是模型加载失败。后来我强制指定opset_version=10重新导出,才解决问题。
所以我的建议是:导出时尽量用低版本opset,除非你确定目标平台支持高版本。一般opset 11或12是比较稳妥的选择。
3.3 常用算子分类:一张图看懂
ONNX算子虽然多,但可以分成几大类。我按自己的理解给你梳理一下:
3.3.1 张量操作类
这类算子负责张量的形状变换、拼接、切片等。说白了就是“搬砖”的。
- Reshape:改变张量形状。注意,它要求输入张量的元素总数不变。
- Transpose:转置,比如把NHWC变成NCHW。
- Concat:沿某个维度拼接多个张量。
- Split:把一个张量切成多块。
- Slice:切片操作,类似Python的
list[start:end]。
个人经验:
我在做模型优化时,经常发现Reshape和Transpose是性能瓶颈。因为这两个算子会触发内存重排。如果你的模型里有很多Reshape,建议看看能不能合并掉。
3.3.2 数学运算类
加减乘除、指数对数、三角函数等。这些是神经网络的基础运算。
- Add / Sub / Mul / Div:四则运算。
- MatMul:矩阵乘法,全连接层的核心。
- Gemm:通用矩阵乘法,比MatMul多了偏置和转置选项。
- Softmax:分类层的标配。
- Relu / Sigmoid / Tanh:激活函数。
3.3.3 神经网络层类
这些是构建深度学习模型的主力算子。
- Conv:卷积操作,支持1D、2D、3D。
- MaxPool / AveragePool:池化操作。
- BatchNormalization:批归一化。
- LSTM / GRU / RNN:循环神经网络。
- Dropout:训练时用的,推理时一般被优化掉。
3.3.4 控制流类
这类算子让ONNX模型有了“逻辑判断”能力。
- If:条件分支,类似if-else。
- Loop:循环,支持for和while。
- Scan:扫描操作,常用于RNN的展开。
注意:控制流算子在ONNX里比较“重”,很多推理引擎对它们的支持不够好。我建议尽量用静态图的方式避免控制流,实在避不开,一定要在目标平台上充分测试。
3.3.5 数据预处理类
这些算子负责图像、文本的预处理。
- Resize:图像缩放,支持最近邻、双线性等插值方式。
- Crop:裁剪。
- Normalization:归一化,比如减去均值除以方差。
- OneHot:独热编码。
3.4 如何快速查看算子信息
你可能会问:这么多算子,我怎么知道某个算子的具体参数?
我一般用两种方法:
- ONNX官方文档:
https://github.com/onnx/onnx/blob/main/docs/Operators.md,每个算子的输入输出、属性都写得清清楚楚。 - Netron可视化:直接打开模型,点击某个算子节点,右侧会显示它的所有属性。
小技巧:
如果你在写代码时不确定某个算子的参数,可以用Python的help()函数。比如:
import onnx
from onnx import helper
# 查看Conv算子的定义
help(helper.make_node('Conv'))
这样就能看到Conv算子需要哪些输入、哪些属性了。
3.5 本章小结
好了,这一章我们聊了ONNX算子的基本框架。你记住三点就行:
- 标准算子集是
ai.onnx,大部分模型都用它。 - 算子版本在演进,导出时选低版本更安全。
- 常用算子分五类:张量操作、数学运算、神经网络层、控制流、数据预处理。
下一章,我们会深入一个具体的算子——Conv,看看它在不同框架下导出时有哪些坑。到时候我会分享一个我踩过的真实案例,保证让你印象深刻。