向 ONNX 添加新运算符或函数¶
或将现有运算符更新到新的 Opset 版本。
目录¶
向 ONNX 提议并提交新运算符或函数¶
运算符是用于定义 ONNX 模型的基本构建块。凭借丰富的运算符集,ONNX 可以描述来自各种框架的大多数 DNN 和 ML 模型。函数能够用更原始的运算符来表达复杂的运算符。ONNX 规范包含一组核心运算符,可实现许多模型。添加所有可能的运算符并非目标,但是会根据不断变化的需求添加更多运算符。
在本文档中,我们描述了接受新提议运算符的过程以及如何将新运算符作为 ONNX 标准的一部分正确提交。目标是根据我们的经验、学习以及从社区收集的反馈来改进我们目前拥有的内容。
添加运算符的 4 个步骤¶
决定提议什么
为新运算符/函数提交 PR
Operators SIG 对 PR 进行审查
PR 合并并包含在下一个 ONNX 版本中
第 1 步:提议新运算符/函数¶
为了提议新运算符/函数,需要满足以下条件
如果运算符可以用其他 ONNX 运算符表示,那么它应该是一个函数而不是一个运算符(ONNX 中有一个函数:MeanVarianceNormalization)。
如果运算符可以分解为新的基元,则改为提议这些基元,并将运算符设为函数。
基于模型。这将帮助我们理解其用法以及它解决了实际问题。如果模型是私有的或受 IP 保护且无法共享,则该运算符不属于标准,应作为自定义操作符实现。
该运算符需要至少由一个(知名)框架实现。这有助于我们了解运算符的实际行为及其用法。
运算符签名和行为
如果运算符在 numpy 中可用,则优先使用 numpy 语义。
如果运算符在多个框架中可用,请确保您的设计是通用的,并涵盖这些框架。
优先使用属性而不是输入。
运算符不应比用例所需的更复杂。然而,运算符应尽可能通用,只要它不使实现更复杂。这需要仔细平衡通用性和复杂性。例如,对于某些运算符,从 3-D 张量推广到 N-D 张量是直接的(实现方面),但对于其他运算符则复杂。在这种情况下,选择将基于这种推广的复杂性。
第 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为您的运算符添加至少一个自动降级测试。指定当前版本,以便一旦操作符在更高 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文件的末尾。