为 ONNX 添加新运算符或函数¶
或将现有运算符更新到新的 Opset 版本。
目录¶
向 ONNX 提议并提交新运算符或函数¶
运算符是定义 ONNX 模型的基本构建块。凭借丰富的运算符集,ONNX 可以描述来自各种框架的大多数 DNN 和 ML 模型。函数能够以更基础的运算符来表达复杂的运算符。ONNX 规范包含核心运算符集,可支持许多模型。添加所有可能的运算符并非目标,但会根据不断演变的需求添加更多运算符。
本文档描述了接受新提议运算符的流程,以及如何将新运算符作为 ONNX 标准的一部分正确提交。目标是根据我们从社区收集的经验、学习和反馈来改进我们现有的内容。
添加运算符的 4 个步骤¶
决定提议什么
提交新运算符/函数的 PR
Operators SIG 对 PR 进行审查
合并 PR 并包含在下一个 ONNX 版本中
步骤 1:提议新运算符/函数¶
为了提议新运算符/函数,需要满足以下条件:
如果运算符可以用其他 ONNX 运算符来表达,那么它应该是一个函数而不是一个运算符(我们在 ONNX 中有一个函数:MeanVarianceNormalization)。
如果运算符可以分解为新的基元,请改提议那些基元,并将运算符变成一个函数。
基于模型。这将帮助我们理解其用途,并证明它解决了实际问题。如果模型是私有的或受知识产权保护而无法共享,则该运算符不属于标准,应作为自定义 OP 实现。
该运算符至少需要由一个(知名的)框架实现。这有助于我们理解运算符的实际行为及其用途。
运算符签名和行为
如果运算符在 numpy 中可用,则优先使用 numpy 语义。
如果运算符在多个框架中可用,请确保您的设计是通用的,并涵盖这些框架。
优先使用属性而非输入。
运算符的复杂性不应超过用例所需的程度。但是,在不增加实现复杂性的前提下,运算符应尽可能通用。这需要仔细权衡通用性和复杂性。例如,对于某些运算符而言,将 3D 张量推广到 N 维张量(在实现上)是直接的,但对于其他运算符而言则很复杂。在这种情况下,选择将基于这种推广的复杂性。
步骤 2:提交 PR¶
一旦满足了提议新运算符/函数的标准,您就需要为此新运算符/函数提交一个 PR。以下是 PR 应包含的内容。审查者应在签核前验证 PR 的完整性。
描述
对运算符及其预期行为进行详细描述。基本上,描述应足够清晰,以避免实现者之间的混淆。
在描述中添加示例以说明用法。
在描述中引用运算符在相应框架中的来源(如果可能)。
在描述中写出数学公式或伪代码。核心算法需要非常清晰。
编写 Python 参考实现,该参考实现应涵盖运算符的所有预期行为。只有在极少数情况下,我们才会豁免此要求。
运算符版本:查看我们的版本控制文档
编写单元测试,涵盖主要用法和边界情况。
测试示例将提取到文档中。
我们还会为它生成二进制数据。
编写升级和降级测试
在onnx/test/version_converter/automatic_upgrade_test.py中使用
_test_op_upgrade
为您的运算符至少添加一个自动升级测试。这些测试会在给定的 opset 版本(通常是运算符引入的版本)下创建一个给定的运算符,并测试版本转换器是否能够将其转换为可用的最高版本。因此,对于新运算符,_test_op_upgrade
将不测试任何内容,但一旦运算符在未来的 opset 中得到更新,该测试将自动变得有意义。同样,在onnx/test/version_converter/automatic_downgrade_test.py中使用
_test_op_downgrade
为您的运算符至少添加一个自动降级测试。通过指定当前版本,一旦 op 在更高的 opset 版本下得到更新,测试将确保向下转换得到验证。
更新文档并生成测试数据。
运行脚本。如果您的文件位于
onnx/backend/test/data/node
目录下,并且无法由onnx/backend/test/case/node
中的脚本生成,请进一步使用python onnx/backend/test/cmd_tools.py generate-data --clean
清理目录并仅保留需要的测试数据,以更新文档并生成测试数据。
形状推断函数
如果形状推断有意义且适用,请提供形状推断函数。
如果无法进行形状推断,则必须至少包含执行秩推断的逻辑(为输出形状添加正确数量的维度)。
形状推断函数必须附带单元测试(onnx/test/shape_inference_test.py)。
在实现自己的函数时,可以参考
TopK
运算符的形状推断函数(onnx/defs/math/defs.cc)。
遵循的示例¶
PR 1959 是一个很好的遵循示例。
步骤 3:由 Operators SIG 进行 PR 审查¶
Operators SIG 负责 ONNX 规范中的运算符/函数。SIG 定期开会审查 PR。
签核¶
至少需要 Operators SIG 贡献者的两次签核。
步骤 4:ONNX 发布¶
一旦 PR 经过 Operators SIG 审查并签核,它将被合并。您的新运算符/函数将成为主分支的一部分,任何人都可以通过从源代码构建来使用。这些不是官方发布。ONNX 会定期发布官方新版本,这些版本是主分支的快照。您的新运算符/函数将包含在那个版本中。
更新现有运算符¶
当例如需要支持新场景或输入类型时,可能需要更新现有运算符的定义。该过程与创建新运算符的过程基本相同。
清单¶
更新现有运算符时请使用此清单:https://github.com/onnx/onnx/wiki/Checklist-for-updating-an-existing-operator
移除运算符或函数¶
有许多原因可以移除现有的 ONNX 运算符或函数,例如被不同的运算符取代或可以由一组其他运算符分解。本文档描述了从标准中移除现有 ONNX 运算符的标准。
移除运算符¶
ONNX 中的任何运算符都是因为模型和/或框架需要才添加的。为了弃用这样的运算符,我们需要做到以下几点:
除非有替代品,否则运算符不能被弃用。
替代品可以是一个更通用的运算符,取代旧的运算符。
或者是一组基元运算符,它们可以共同实现被弃用运算符(函数)的相同功能和行为。
如果被弃用的运算符可以被现有运算符分解,那么它必须被转换为一个函数。
如果替代品尚未包含在 ONNX 标准中,则先添加替代运算符或运算符集。
添加一个版本适配器,将运算符转换为版本转换器的替代品。示例:onnx/version_converter/adapters/upsample_9_10.h
弃用的运算符不需要宽限期。
移除函数¶
根据定义,函数由 ONNX 基元组成;然而,函数可能已被支持 ONNX 的框架或运行时加速。因此,不建议移除函数,除非添加另一个单一函数来取代其功能。
文档移除运算符或函数¶
为了确保每个人都意识到弃用,需要做到以下几点:
任何从 ONNX 中移除的运算符或函数都必须在发布说明中提及。
其旧文档需要更新,以显示新的替代品以及旧与新的映射关系。
只需移除
def.cc
,old.cc
将保留。old.cc
需要更新为指向替代品的映射。
ONNX 检查器需要更新,以显示带有适当消息的错误。
所有移除的运算符都需要附加到
operator.md
文件的末尾。