MLIR 编译器基础设施中 ONNX 模型的表示和参考降低
此项目由 onnx 维护
托管在 GitHub Pages 上 — 主题由 orderedlist 提供
ONNX-MLIR 定义了一个 ONNX 方言来表示 ONNX 指定的操作。ONNX 方言是使用 MLIR 表格生成工具创建的。每个操作的定义都通过 Python 脚本 utils/gen_onnx_mlir.py 从 ONNX 自动转换。此脚本从 ONNX 包中检索操作定义,以生成用于方言表格生成的 ONNXOps.td.inc 和用于 ONNX-MLIR 中 ONNX 模型导入器的 OpBuilderTable.inc。以下部分将描述如何使用 gen_onnx_mlir.py 将操作添加到 ONNX-MLIR 中的 ONNX 方言中,以及如何细化操作的定义。
要为 ONNX 方言生成操作,请将此操作添加到 gen_onnx_mlir.py 中的字典“version_dict”中。此目录的键是操作名称,值是此操作的操作集列表。通常只支持此操作的最高版本操作集(在 onnx-mlir/third_party/onnx 中)。有关版本控制的详细信息,请参阅版本部分。有了此条目,脚本将为 ONNX 方言生成操作定义。
Pure
特性。ResultTypeInferenceOpInterface
,请将其添加到字典OpsWithResultTypeInference
中。此接口推断结果张量的类型,而不是形状。HasOnnxSubgraphOpInterface
。此属性是从 ONNX 操作定义中推断出来的。OpsWithHelpers
为操作定义辅助函数。默认情况下,所有操作都具有形状推断接口和Pure
特性。如果操作具有ResultTypeInferenceOpInterface
,则使用字典OpsWithResultTypeInference
。此接口推断结果张量的类型,而不是形状。如果操作具有子图,则它将具有接口HasOnnxSubgraphOpInterface
。
如果应在传递过程中对操作本地应用转换,则可以使用规范化接口进行此转换。要为操作启用规范化,请将此操作的名称添加到OpsWithCanonicalizer
的此列表中,然后操作在其定义中将具有hasCanonicalizer = 1;
。
操作的默认构建器需要结果的类型作为参数。但是,可以推断结果的类型。自定义构建器可能有助于简化代码。根据推理类型,有两种构建器,无等级类型和广播类型。要为操作启用特殊构建器,您可以分别将其名称添加到custom_builder_unranked_ops_list
和custom_builder_broadcast_ops_list
中。
请注意,可以使用returnType
避免在重写规则中使用特殊构建器的需要。请参阅MLIR 文档或ONNX-MLIR 中的示例。更好的解决方案可能是将此类类型推断代码移到 ONNXOpHelper.cpp 中并摆脱自定义构建器。
请注意,可以使用returnType
避免在重写规则中使用特殊构建器的需要。更好的解决方案可能是将此类类型推断代码移到 ONNXOpHelper.cpp 中并摆脱自定义构建器。
操作的操作描述列出了每个输入/输出和属性的允许类型。表生成将生成一个默认验证器来检查 IR 的允许类型。如果操作具有额外的约束,则应定义自定义验证器以增强错误检测。例如,操作的两个输入可能需要相同的元素类型或相同的秩。此类信息可以在 ONNX 操作定义中找到,但不能用方言定义来表达。测试这些约束的最佳方法是在验证器中。要将自定义验证器的接口添加到操作,请在gen_onnx_mlir.py
中找到下面的数组并添加您的操作。
OpsWithVerifier = ['AveragePool', 'Conv', 'InstanceNormalization', 'Mod']
然后,您将在 ONNXOps.td.inc 中的操作定义中找到以下行
let verifier = [{ return ::verify(*this); }];
当新操作声明为使用自定义验证器时,您需要在src/Dialect/ONNX/ONNXOps.cpp
中添加实现代码。最好查看其他操作以获取通用模式,例如搜索static LogicalResult verify(ONNXInstanceNormalizationOp op)。请注意,每次创建此类操作时,验证器都会执行。因此,您需要确保它可以与张量和 MemRef 以及可能未排序的张量一起使用。因此,请将每个测试保护到适当的环境中。例如,一旦张量被排序,您就可以验证秩是否在批准的范围内(如果有此类约束);在它被排序之前,请不要执行此测试。
提示
operandAdaptor
对象获取输入(必须使用operandAdaptor
获取输入的当前值)以及op
对象获取属性(可以使用op
,因为属性通常是不可变的)。hasShapeAndRank(X)
测试输入X
是否当前已成形且已排序。如果不是,则返回成功,因为我们稍后将有机会使用此信息测试操作。请注意,某些输入也可能是标量,在这种情况下,它们可能被编码为形状类型,也可能不被编码为形状类型。mlir::cast<ShapedType>(X.getType())
来获取形状类型,从中您可以获取秩和维度。目前,我们只检查运行时已知值的维度有效性。未知维度被编码为负数。请仅在确定不会断言时使用转换,即类型确实是ShapedType
。op->emitError(msg)
报告带有友好错误消息的错误。special_op_handler
:在 frontend_dialect_transformer.cpp 中创建特殊导入函数。目前,特殊处理程序用于具有操作参数的操作
如果操作的定义需要除上述内容之外的其他代码,则可以将代码放在字典custom_definition_misc
中。键是操作名称,值是代码。
special_op_handler
:在 frontend_dialect_transformer.cpp 中创建特殊导入函数。目前,特殊处理程序用于具有操作参数的操作
如果操作的定义需要除上述内容之外的其他代码,则可以将代码放在字典custom_definition_misc
中。键是操作名称,值是代码。
为了运行 gen_onnx_mlir.py,必须安装 ONNX。请参阅自述文件。在您的构建目录中,执行以下命令。
make OMONNXOpsIncTranslation
此命令将生成这两个文件(src/Dialect/ONNX/ONNXOps.td.inc 和 OpBuilderTable.inc),并将它们复制到 src 目录中的正确位置。如果您修改了 gen_onnx_mlir.py,则也需要检入两个生成的文件。它们在 ONNX-MLIR 构建中被视为源文件,以便 ONNX-MLIR 的用户无需安装特定版本的 ONNX。请勿直接修改这些文件。您也可以使用 utils 目录中生成的文件直接运行脚本。python ../utils/gen_onnx_mlir.py
。
在添加新的操作版本或更改 ONNX 版本时,我们希望在支持的操作的 ONNX 文档中反映这些更改。虽然最新的ONNX 规范始终可用,但我们支持的规范通常会滞后一些,此外,我们还在版本化名称下支持旧版本,如上一节所述。
有一个方便的命令可以更新 ONNX 和 Krnl 方言,如下所示。
make onnx-mlir-docs
上述命令在通常的build
目录中运行,它会将新的方言 md 文件直接安装到docs/Dialects
目录中。
添加操作/对 Krnl 方言进行更改时应使用相同的命令。
ONNX-MLIR 项目在 ONNX 版本为 1.7.0 时启动,并且不打算向后兼容。我们依赖 onnx/converter 将模型转换为 ONNX-MLIR 支持的版本。随着 ONNX 版本的演变,ONNX-MLIR 试图跟随,但可能落后于最新版本。
如前所述,我们尝试支持最新版本的 ONNX 操作。当前支持的每个操作的版本记录在utils/gen_onnx_mlir.py中。此机制在版本中提供了一些稳定性。要检查版本中的更改,请使用标志“–check-version”运行 gen_onnx_mlir.py,并将报告更改。要移动到较新的版本,请手动更新脚本中的版本字典。
要支持操作的多个版本,应将选定的版本添加到utils/gen_onnx_mlir.py中的版本字典中。例如,ReduceSum 有两个受支持的版本(操作集),11 和 13。version_dic 中的相应条目为'ReduceSum': [13, 11]
。
在 ONNX 方言中,最高版本的运算符名称中没有版本号,而其他版本的名称后面则跟着“V”和版本号。例如,opset 13 的 ReduceSum 将是 ONNXReduceSumOp
,而 opset 11 的 ReduceSum 则是 'ONNXReduceSumV11Op'。由于大多数 ONNX 运算符在升级到更高版本时都是兼容的,因此我们可以在方言中保留运算符的名称,只需更新 gen_onnx_mlir.py 中的 version_dict,而无需修改 ONNX-MLIR 中的代码。
导入模型时,会使用不高于下一个可用版本的最高版本。以 ReduceSum 为例,如果 opset 为 12,则会选择 ONNXReduceSumV11Op。
要迁移新版本的 ONNX,首先需要升级 third_part/onnx 和您的 ONNX 安装。然后,您可以使用标志 --check_operation_version
运行 gen_onnx_mlir.py。所有操作的最高版本将输出为新的 version_dict
。如果运算符的接口保持不变(根据 ONNX 的变更文档),则可以直接使用新版本。如果接口发生了变化,则可以将新版本插入版本列表的开头。对于现有代码,所有相应的代码都需要更改。例如,当 ReduceSum 从版本 11 迁移到 13 时,首先将 ONNXReduceSumOp 替换为 ONNXReduceSumOpV11。然后,版本 13 的代码将使用 ONNXReduceSumOp。这种设计的理由是,大多数 ONNX 更改都不会改变接口。我们不希望开发人员承担记住使用哪个版本的运算符的负担,除非绝对必要。并不总是需要保留旧版本的代码,这些代码可以重写为新的运算符。因此,我们只需要方言定义,而不需要推理或降低的代码。