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]

个人经验:

我在做模型优化时,经常发现ReshapeTranspose是性能瓶颈。因为这两个算子会触发内存重排。如果你的模型里有很多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 如何快速查看算子信息

你可能会问:这么多算子,我怎么知道某个算子的具体参数?

我一般用两种方法:

  1. ONNX官方文档https://github.com/onnx/onnx/blob/main/docs/Operators.md,每个算子的输入输出、属性都写得清清楚楚。
  2. Netron可视化:直接打开模型,点击某个算子节点,右侧会显示它的所有属性。

小技巧:

如果你在写代码时不确定某个算子的参数,可以用Python的help()函数。比如:

import onnx
from onnx import helper

# 查看Conv算子的定义
help(helper.make_node('Conv'))

这样就能看到Conv算子需要哪些输入、哪些属性了。

3.5 本章小结

好了,这一章我们聊了ONNX算子的基本框架。你记住三点就行:

  • 标准算子集ai.onnx,大部分模型都用它。
  • 算子版本在演进,导出时选低版本更安全。
  • 常用算子分五类:张量操作、数学运算、神经网络层、控制流、数据预处理。

下一章,我们会深入一个具体的算子——Conv,看看它在不同框架下导出时有哪些坑。到时候我会分享一个我踩过的真实案例,保证让你印象深刻。