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