向ONNX添加新的运算符或函数

或将现有运算符更新到新的Opset版本。

目录

向ONNX提议并提交新的运算符或函数

运算符是用于定义ONNX模型的基本构建块。通过丰富的运算符集,ONNX可以描述来自不同框架的大多数DNN和ML模型。函数能够用更基本的运算符表达复杂的运算符。ONNX规范包含一组核心运算符,这些运算符可以支持许多模型。添加所有可能的运算符并非目标,但会根据需要添加更多运算符以满足不断变化的需求。

本文档介绍了接受新的提议运算符的过程以及如何在ONNX标准中正确提交新的运算符。目的是根据我们的经验、学习和从社区收集的反馈,改进我们目前已有的内容。

添加运算符的4个步骤

  1. 决定提议什么

  2. 提交新的运算符/函数的PR

  3. 运算符SIG审查PR

  4. 合并PR并将其包含在下一个ONNX版本中

步骤1:提议新的运算符/函数

为了提议新的运算符/函数,需要以下内容

  1. 如果该运算符可以用其他ONNX运算符来表达,那么它应该是一个函数,而不是一个运算符(ONNX中有一个函数:MeanVarianceNormalization)。

  2. 如果运算符可以拆分为新的基本运算符,请提议这些基本运算符,并将运算符设为函数。

  3. 基于模型。这将帮助我们了解其用途,并确保它能解决实际问题。如果模型是私有的或涉及IP并且无法共享,则该运算符不属于标准,应作为自定义OP实现。

  4. 该运算符需要至少由一个(知名)框架实现。这将帮助我们了解运算符的实际行为及其用途。

  5. 运算符签名和行为

    1. 如果该运算符在numpy中可用,优先使用numpy语义。

    2. 如果该运算符在多个框架中可用,请确保您的设计是通用的,并涵盖这些框架。

  6. 优先使用属性而不是输入。

  7. 运算符不应比用例要求的更复杂。但是,运算符应该尽可能通用,只要不增加实现的复杂性。这需要仔细平衡通用性和复杂性。例如,对于一些运算符,从3-D张量泛化到N-D张量是比较简单的(在实现方面),但对于其他运算符则比较复杂。在这种情况下,选择将基于这种泛化的复杂性。

步骤2:提交PR

一旦满足了提议新的运算符/函数的标准,您将需要提交新的运算符/函数的PR。以下是PR应包含的内容。审核人员应在签署之前验证PR的完整性。

  1. 描述

    1. 编写有关该运算符及其预期行为的详细描述。描述应该足够清晰,以避免实现者之间的混淆。

    2. 在描述中添加一个示例,以说明其用法。

    3. 在描述中添加指向该运算符在相应框架中的来源的引用(如果可能)。

    4. 在描述中编写数学公式或伪代码。核心算法需要非常清晰。

  2. 用Python编写一个参考实现,这个参考实现应该涵盖运算符的所有预期行为。只有在极其罕见的情况下,我们才会放弃这一要求。

  3. 运算符版本:查看我们的版本控制文档

  4. 编写单元测试,涵盖主要用法和边缘情况。

    1. 测试示例将被提取到文档中。

    2. 我们也会为它生成二进制数据。

    3. 示例:onnx/backend/test/case/node/abs.py

  5. 编写升级和降级测试

    1. onnx/test/automatic_upgrade_test.py 中,使用 _test_op_upgrade 为您的运算符添加至少一个自动升级测试。这些测试会在给定的 opset 版本(通常是运算符引入的版本)下创建给定的运算符,并测试版本转换器是否能够将其转换为最高可用版本。因此,对于新的运算符,_test_op_upgrade 不会测试任何内容,但一旦运算符在未来 opset 中得到更新,测试将自动变得非平凡。

    2. 类似地,在 onnx/test/automatic_downgrade_test.py 中,使用 _test_op_downgrade 为您的运算符添加至少一个自动降级测试。指定当前版本,以便一旦运算符在更高 opset 版本中更新,测试将确保向下转换得到验证。

  6. 更新文档并生成测试数据。

    1. 运行 该脚本。如果您在 onnx/backend/test/data/node 下有无法从 onnx/backend/test/case/node 中的脚本生成的檔案,请进一步使用 python onnx/backend/test/cmd_tools.py generate-data --clean 来清理目录,并仅保留所需的测试数据。更新文档并生成测试数据。

  7. 形状推断函数

    1. 在有意义且适用的情况下,请提供形状推断函数。

    2. 在无法进行形状推断的情况下,它必须具有逻辑来至少执行秩推断(为输出形状添加正确的维度数量)

    3. 形状推断函数必须附带单元测试 (onnx/test/shape_inference_test.py).

    4. 在实现您自己的函数时,您可以参考 TopK 运算符的形状推断函数 (onnx/defs/math/defs.cc)

示例遵循

PR 1959 是一个很好的遵循示例。

步骤 3:运算符 SIG 对 PR 进行审查

运算符 SIG 负责 ONNX 规范中的运算符/函数。SIG 定期召开会议并审查 PR。

签字

运算符 SIG 的至少两位 贡献者 签字。

步骤 4:ONNX 发布

一旦 PR 被运算符 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.ccold.cc 将保留。

    • old.cc 需要更新为包含映射到替代方案。

  • ONNX 检查器需要更新为使用适当的消息报错。

  • 所有删除的运算符都需要追加到 operator.md 檔案的末尾。