onnx-mlir

Logo

ONNX 模型在 MLIR 编译器基础设施中的表示和参考降低

在 GitHub 上查看项目 onnx/onnx-mlir

操作指南

使用 Python 进行推理
使用 C/C++ 进行推理
使用 Java 进行推理

参考

ONNX 方言
OMTensor C99 运行时 API
OMTensorList C99 运行时 API
OMTensor Java 运行时 API
OMTensorList Java 运行时 API
生成 ONNX 方言
关于文档

开发

添加操作
测试指南
错误处理
命令行选项
检测
常量传播
添加加速器

工具

工具

RunONNXModel.py
DocCheck

此项目由 onnx 维护

托管在 GitHub Pages 上 — 主题由 orderedlist 提供

测试

在 onnx-mlir 中,有三种类型的测试来确保实现的正确性

  1. ONNX 后端测试
  2. LLVM FileCheck 测试
  3. 数值测试
  4. 使用 gdb
  5. ONNX 模型动物园

ONNX 后端测试

后端测试是基于 onnx 节点和模型测试的 onnx-mlir 端到端测试。它们可用于测试 C/C++ .so 库和 JNI .jar 存档。对于每个 C/C++ 测试目标,添加 -jni 后缀将得到相应的 JNI 测试目标。要调用测试,请使用以下命令

cmake --build . --config Release --target check-onnx-backend[-jni]

需要安装诸如 third_party/onnx 之类的包才能运行后端测试。您可以使用命令 pip install your-onnx-mlir/third_party/onnx 安装您自己的 onnx 包。JNI 测试需要 jsoniter jar,如果系统上没有找到已安装的版本,则默认情况下会从其 Maven 存储库下载。如果用户在构建 ONNX-MLIR 时启用了 cmake 选项 ONNX_MLIR_BUILD_JSONITER,则 jsoniter jar 将从其 GitHub 存储库克隆的源代码本地构建。请注意,本地构建 jsoniter jar 需要安装 Maven 构建工具。

onnx 包提供的所有测试用例都列在文件 test/backend/all_test_names.txt 中。check-onnx-backend 将选择性地运行其中的一些。check-onnx-backend 将运行的 onnx 中的节点和模型测试由 test/backend/test.py 中的变量 test_to_enable 定义。用户可以通过环境变量 TEST_CASE_BY_USER 测试一个测试用例。例如,

TEST_CASE_BY_USER=selected_test_name cmake --build . --config Release --target check-onnx-backend[-jni]

指定了 TEST_CASE_BY_USER 后,中间结果、.onnx 文件和 .so 文件将保留在 build/test/backend 中以进行调试。如果您需要检查生成的共享库中是否包含特定指令,请将环境变量 TEST_INSTRUCTION_CHECK 设置为 true,并在测试名称后添加指令名称,例如 TEST_CASE_BY_USER=selected_test_name,instruction_name。请注意,在 onnx 测试名称后添加后缀 _cpu

ONNX 支持的测试用例

文件 test/backend/all_test_names.txt 包含 ONNX 包提供的所有测试用例。您可以通过将其添加到 test/backend/inference_backend.py 中来启用测试用例。all_test_names.txt 是使用命令“make check-onnx-backend-case”自动生成的。仅当 ONNX 包升级时才需要更新。

将 ONNX 支持的测试用例添加到当前的后端测试集中

添加运算符的 ONNX 到 Krnl 转换后,应将该运算符对应的后端测试添加到 test.py 中。可用的测试用例可以在 third_party/onnx/onnx/backend/test/case/node 中找到。您可以通过在 test/backend/all_test_names.txt 中查找新运算符来识别新测试。找到新测试后,您可以在 test/backend/inference_backend.py 中添加新测试。请注意,在 onnx 测试名称后添加后缀 _cpu。与测试相关联,您可以定义如何为新运算符运行测试。例如

        "test_and2d_cpu": {STATIC_SHAPE:{}, DYNAMIC_SHAPE:{-1:{-1}}, CONSTANT_INPUT:{-1}},

表示测试 test_and2d_cpu 可以 (1) 使用静态形状,(2) 强制其所有输入为动态形状,或 (3) 强制其所有输入为定义的常量运行。这是大多数运算符的推荐设置。但是,有些运算符无法容忍某些参数的动态形状;对于这些运算符,可以明确决定函数的哪个参数可以具有动态形状。这由 {-1:{-1}} 表达式指定。 test/backend/inference_backend.py 文件包含有关如何指定哪个参数和/或参数维度可以设置为动态的明确说明。

具有未知维度的测试

使用动态张量大小进行测试最容易通过使用以下命令来执行,我们的检查器也使用此命令。

cmake --build . --config Release --target check-onnx-backend-dynamic[-jni]

onnx 节点测试通常对输入张量的维度大小已知。因此,要测试具有未知维度的张量,模型导入器 (Build/FrontendONNXTransformer.cpp) 提供了一种生成此类用例的功能。当环境变量 IMPORTER_FORCE_DYNAMIC 设置时,前端导入将(默认情况下)将模型所有输入张量的所有维度都转换为 -1。例如,

IMPORTER_FORCE_DYNAMIC='-1:-1' all dimensions of all the inputs will be changed
IMPORTER_FORCE_DYNAMIC='0:-1' all dimensions of the first input will be changed
IMPORTER_FORCE_DYNAMIC='0:-1|1:0,1' all dimensions of the first input and the 1st and 2nd dimensions of the second input will be changed

IMPORTER_FORCE_DYNAMIC 的 Backus-Naur 范式 (BNF) 如下所示。

<ImportForceDynamicExpr> :== `'` <expr> `'`
                  <expr> ::= <inputString> | <inputString> `|` <expr>
            <inputString ::= <inputIndex> `:` <dimString>
             <dimString> ::= <dimIndex> | <dimIndex> `,` <dimString>
            <inputIndex> ::= <index>
              <dimIndex> ::= <index>
                 <index> ::= -1 | <number>
                <number> ::= <digit> | <digit><number>
                 <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

-1 在语义上表示所有输入或所有维度,并且具有最高优先级。例如,'0: -1, 0' 表示第一个输入的所有维度都将更改。输入和维度索引从 0 开始。

例如,test_add_cpu 的默认模型为

func @main_graph(%arg0: tensor<3x4x5xf32>, %arg1: tensor<3x4x5xf32>) -> tensor<3x4x5xf32>

使用 IMPORTER_FORCE_DYNAMIC='-1:-1',结果为

func @main_graph(%arg0: tensor<?x?x?xf32>, %arg1: tensor<?x?x?xf32>) -> tensor<?x?x?xf32>

使用 IMPORTER_FORCE_DYNAMIC='0:-1',结果为

func @main_graph(%arg0: tensor<?x?x?xf32>, %arg1: tensor<3x4x5xf32>) -> tensor<3x4x5xf32>

使用 IMPORTER_FORCE_DYNAMIC='0:0,2|1:1',结果为

func @main_graph(%arg0: tensor<?x4x?xf32>, %arg1: tensor<3x?x5xf32>) -> tensor<3x4x5xf32>

这是一种使用现有节点测试进行动态张量测试的方法。由于并非所有测试用例都能通过动态张量测试,因此 test/backend/test.py 中有一个列表 test_not_for_dynamic,用于指定哪些测试无法通过定义 IMPORTER_FORCE_DYNAMIC

具有常量输入的测试

因为 onnx 节点测试在运行时接受输入张量,所以编译 onnx 模型时输入不是常量。但是,在实践中,输入可以是常量,我们希望测试这种情况。

使用常量输入进行测试最容易通过使用以下命令来执行,我们的检查器也使用此命令。

cmake --build . --config Release --target check-onnx-backend-constant[-jni]

要测试单个 onnx 节点,例如 test_add_cpu,请使用两个环境变量 TEST_CONSTANTIMPORTER_FORCE_CONSTANT,例如

TEST_CONSTANT=true IMPORTER_FORCE_CONSTANT="0" TEST_CASE_BY_USER=test_add_cpu make check-onnx-backend[-jni]

这将第一个输入(索引 0)转换为常量,因此模型现在只有一个输入而不是两个。

环境变量 IMPORTER_FORCE_CONSTANT 是用 , 分隔的索引列表(从 0 开始,或 -1 表示所有输入索引),例如 0, 2, 3-1

输入签名测试

使用以下命令测试具有各种数据类型的 onnx 模型的输入签名,我们的检查器也使用此命令。

cmake --build . --config Release --target check-onnx-backend-signature

启用 SIMD 指令

在受支持的平台(目前仅限 s390x)上,后端测试可以为编译的模型生成 SIMD 指令。要启用 SIMD,请设置 TEST_MCPU 环境变量,例如,

TEST_MCPU=z14 cmake --build . --config Release --target check-onnx-backend[-jni]

后端测试的执行

utils/RunONNXLib.cpp 中定义的工具可用于轻松地从其 .so 模型执行文件,例如使用 TEST_CASE_BY_USER=selected_test_name make check-onnx-backend 命令生成的模型。通过将 onnx-mlir/src/Compiler/CompilerUtils.cpp 文件中的 overridePreserveFiles 值设置为 KeepFilesOfType::All,例如,以其他方式构建时也可以保留模型。

当 onnx 模型的版本早于 onnx-mlir 支持的当前版本时,可以调用 onnx 版本转换器,并将环境变量 INVOKECONVERTER 设置为 true。例如,对于 INVOKECONVERTER=true make check-onnx-backend,将为所有测试用例调用转换器。在 test.py 中,有一个名为 test_need_converter 的列表,供您在各个用例上调用转换器。

该工具直接扫描模型提供的签名,使用随机值初始化所需的输入,然后对模型进行函数调用。然后,该程序可以与其他工具(如 gdblldbvalgrind)结合使用。要列出实用程序选项,只需在运行时使用 -h--help 标记即可。

我们首先需要编译该工具,这可以通过两种模式之一完成。在第一种模式下,该工具使用静态链接的模型进行编译。除了包含 .so 文件外,此模式还需要在编译期间使用 -D LOAD_MODEL_STATICALLY=0 选项。最好使用 onnx-mlir/utils 目录中的 build-run-onnx-lib.sh 脚本编译该工具及其模型,该模型作为参数传递给脚本。为了避免 Mac 上的库路径问题,请在构建模型的目录中运行编译后的工具。

# Compile tool with model.
cd onnx-mlir/build
sh ../utils/build-run-onnx-lib.sh test/backend/test_add/test_add.so
# Run the tool to run the model (substitute `Release` for `Debug` for the release version).
Debug/bin/run-onnx-lib
# or, on Mac, run the tool in the directory where the model was built
(cd test/backend; ../../Debug/bin/run-onnx-lib)
# if test_add.so was built in `test/backend`:
cd test/backend; ../../Debug/bin/onnx-mlir --EmitLib test_add/test_add.onnx

(您可以在 Mac 上使用 otool -L test_add.so 查看库的路径。)

在第二种模式下,该工具在没有模型的情况下进行编译,这些模型将在运行时传递。要启用此选项,只需使用 -D LOAD_MODEL_STATICALLY=1 选项编译该工具即可。您可以使用与上面相同的脚本,但无需参数。然后,只要您在运行时将 .so 模型文件传递给工具,就可以从任何目录运行该工具。

# Compile tool without a model.
cd onnx-mlir/build
sh ../utils/build-run-onnx-lib.sh
# Run the tool with an argument pointing to the model.
Debug/bin/run-onnx-lib test/backend/test_add/test_add.so

LLVM FileCheck 测试

我们可以通过将中间表示作为输入并使用 LLVM FileCheck 工具检查输出 IR 来测试单个 Pass 的功能。例如,我们有一个用于形状推断的测试用例 test.mlir。

func @test_default_transpose(%arg0 : tensor<5x5x1x32xf32>) -> tensor<*xf32> {
  %0 = "onnx.Transpose"(%arg0) : (tensor<5x5x1x32xf32>) -> tensor<*xf32>
  "std.return"(%0) : (tensor<*xf32>) -> ()

您可以在此测试用例上运行形状推断 Pass,并获得以下输出。

module  {
  func @test_default_transpose(%arg0: tensor<5x5x1x32xf32>) -> tensor<32x1x5x5xf32> {
    %0 = "onnx.Transpose"(%arg0) {perm = [3, 2, 1, 0]} : (tensor<5x5x1x32xf32>) -> tensor<32x1x5x5xf32>
    return %0 : tensor<32x1x5x5xf32>
  }
}

手动检查输出是否正确。如果输出正确,请将其转换为将来可以自动检查的格式。使用以下命令:

Debug/bin/onnx-mlir-opt --shape-inference test.mlir | python ../utils/mlir2FileCheck.py 

您将获得以下内容:

// mlir2FileCheck.py
// CHECK-LABEL:  func @test_default_transpose
// CHECK-SAME:   ([[PARAM_0_:%.+]]: tensor<5x5x1x32xf32>) -> tensor<32x1x5x5xf32> {
// CHECK:           [[VAR_0_:%.+]] = "onnx.Transpose"([[PARAM_0_]]) {perm = [3, 2, 1, 0]} : (tensor<5x5x1x32xf32>) -> tensor<32x1x5x5xf32>
// CHECK:           return [[VAR_0_]] : tensor<32x1x5x5xf32>
// CHECK:         }

将源代码和检查代码合并,并添加到相应的测试用例中。所有 ONNX dialect 的测试用例都收集在 test/mlir/onnx 目录下。这些测试用例可以通过 make check-onnx-lit 调用。此目标是构建的基本要求。

数值测试

除了 ONNX 包提供的测试之外,数值测试还用于测试数值正确性。目标是提供广泛的基于数值的单元测试;这对于确保优化转换的有效性和正确性非常重要:随着我们针对特定架构参数(如向量宽度)进行专门化,将出现更多极端情况。数值测试基于被测操作的简单、朴素(且极其缓慢)的实现,生成大量的基于数值的单元测试,用于验证我们操作降低和优化的正确性。

数值测试的结构应确保以下两个组件独立且分离:

这样做的原因是我们希望以两种方式生成测试用例参数:

  // RapidCheck test case generation.
  bool success = rc::check("convolution implementation correctness", []() {
    const auto N = *rc::gen::inRange(1, 10);
    const auto C = *rc::gen::inRange(1, 20);
    const auto H = *rc::gen::inRange(5, 20);
    const auto W = *rc::gen::inRange(5, 20);

    const auto kH = *rc::gen::inRange(1, 15);
    const auto kW = *rc::gen::inRange(1, 15);

    // We don't want an entire window of padding.
    const auto pHBegin = *rc::gen::inRange(0, kH - 1);
    const auto pHEnd = *rc::gen::inRange(0, kH - 1);
    const auto pWBegin = *rc::gen::inRange(0, kW - 1);
    const auto pWEnd = *rc::gen::inRange(0, kW - 1);

    // Make sure we have at least 1 output per dimension.
    RC_PRE((H >= kH) && (W > kW));

    RC_ASSERT(isOMConvTheSameAsNaiveImplFor(
        N, C, H, W, kH, kW, pHBegin, pHEnd, pWBegin, pWEnd));
  });
  assert(success && "error while performing RapidCheck tests");

有时可以方便地查看与数值测试关联的 MLIR 文件。为此,最简单的方法是在 src/Compiler/CompilerUtils.cpp 中将 overridePreserveFiles 变量设置为要保留的文件类型(例如 KeepFilesOfType::All)。然后,无论您如何编译模型,输入和输出 MLIR 文件都将被保留,以及未优化的和优化的字节码文件以及一些其他二进制文件。

如果发生故障,RapidCheck(用于数值测试的基础结构)和 ONNX 模型都允许用户使用相同的值重新运行测试。运行测试时,您可能会看到以下输出。

Model will use the random number generator seed provided by "TEST_SEED=1440995966"
RapidCheck Matrix-Vector test case generation.
Using configuration: seed=4778673019411245358

通过记录以下两个环境变量中的种子值:

export RC_PARAMS="seed=4778673019411245358"
export TEST_SEED=1440995966

您可以分别强制 RapidCheck 中使用的随机种子和用于填充 ONNX 输入向量的随机种子相同。仅设置第一个 (RC_PARAMS),您将看到运行相同的测试配置,但使用不同的输入值。同时设置两者,您将看到使用相同的配置和相同的输入进行完全相同的运行。

如果需要更改 ATOL 和 RTOL 以进行精度检查,请将环境变量 TEST_ATOLTEST_RTOL 设置为新的值。

启用 SIMD 指令

在受支持的平台上(目前仅限 s390x),数值测试可以为编译后的模型生成 SIMD 指令。要启用 SIMD,请设置 TEST_ARGS 环境变量,例如:

TEST_ARGS="-mcpu=z14" CTEST_PARALLEL_LEVEL=$(nproc) cmake --build . --config Release --target check-onnx-numerical

特定加速器的测试

目前我们提供了对加速器 NNPA 的测试。其描述在此处

使用 gdb

获取 ONNX 模型的源代码

编译 ONNX 模型时,添加选项 --preserveMLIR。将创建以 MLIR 格式表示的模型源代码,命名为 your_model_name.input.mlir。操作的行信息将附加并一直传播到二进制文件。在 GDB 中运行编译后的库时,您可以停止在模型中并根据 ONNX 操作逐步执行。以下是模型 test_add.onnx 的示例:

$Debug/bin/onnx-mlir --preserveMLIR test_add.onnx
$. ../utils/build-run-onnx-lib.sh
$gdb Debug/bin/run-onnx-lib
(gdb) b run_main_graph
(gdb) run ./test_add.so
(gdb) list
1	builtin.module  {
2	  builtin.func @main_graph(%arg0: tensor<3x4x5xf32>, %arg1: tensor<3x4x5xf32>) -> tensor<3x4x5xf32> {
3	    %0 = "onnx.Add"(%arg0, %arg1) : (tensor<3x4x5xf32>, tensor<3x4x5xf32>) -> tensor<3x4x5xf32>
4	    return %0 : tensor<3x4x5xf32>
5	  }
(gdb) b 3
Breakpoint 2 at 0x3fffdf01778: file /home/chentong/onnx-mlir/build/test_add.input.mlir, line 3.
(gdb) c
Continuing.

Breakpoint 2, main_graph () at /home/chentong/onnx-mlir/build/test_add.input.mlir:3
3	    %0 = "onnx.Add"(%arg0, %arg1) : (tensor<3x4x5xf32>, tensor<3x4x5xf32>) -> tensor<3x4x5xf32>
(gdb) n
[Detaching after vfork from child process 2333437]
#  0) before op=     Add VMem:  6804
[Detaching after vfork from child process 2333470]
#  1) after  op=     Add VMem:  6804
4	    return %0 : tensor<3x4x5xf32>
(gdb)

请注意,检测结果表明 GDB 在 ONNX 操作级别正确地执行了步进。onnx-mlir 需要额外的标志才能在检测中运行,这对于 GDB 来说是不必要的。源文件是 test_add.input.mlir。未来的工作之一是在 GDB 中支持 ONNX 级别上的符号。如果可以在 GDB 中打印张量,那将非常有用。

使用 LLVM 调试支持

在 LLVM 和 MLIR 项目中添加跟踪代码的标准方法是使用 LLVM_DEBUG 宏。LLVM 的官方文档在此处

要在调试控制下插入单个“打印输出”,可以使用以下模板:

#include "llvm/Support/Debug.h"

#define DEBUG_TYPE "my_opt_name_here"
...
LLVM_DEBUG(llvm::dbgs() << "debug msg here" <<  obj_to_print << "\n");

要触发调试跟踪,只需使用 –debug-only=my_opt_name_here 调用编译器即可。

另一个名为 DEBUG_WITH_TYPE 的宏可用于源文件可能只有一个跟踪消息的情况。在这种情况下,您可以放弃定义 DEBUG_TYPE 并改为使用以下内容:

DEBUG_WITH_TYPE("my_debug_msg", llvm::dbgs() << "my trace msg here\n");

要保护较大的代码部分,可以使用此模板:

LLVM_DEBUG({
  for(i...) {
    llvm::dbgs() << "emit trace for a: " << a << "\n";
    compute b;  // should be side effects free
    llvm::dbgs() << "emit trace for 'b':" << b << "\n";
    ...
});

项目中使用此支持的一些示例位于以下文件中:

同样,可以通过将 --debug-only=my_opt_name_here 选项添加到 onnx-mlironnx-mlir-opt 来激活这些调试语句。

ONNX 模型动物园

我们提供了一个 Python 脚本 RunONNXModelZoo.py,用于使用 ONNX 模型库 中的模型检查推理精度。 RunONNXModelZoo.py 需要 RunONNXModel.py 位于同一文件夹中。例如,要检查 mnist-8 的推理精度:

$ mkdir test && cd test
$ ln -s /onnx-mlir/utils/RunONNXModel.py
$ ln -s /onnx-mlir/utils/RunONNXModelZoo.py
$ ONNX_MLIR_HOME=/onnx-mlir/build/Release/ python RunONNXModelZoo.py -m mnist-8 -c="-O3"

使用 -h 运行脚本以查看所有选项。除了用于指定模型的 -m 标志和用于指定编译选项的 -c 标志外,有用的选项还有 -k 标志(用于将 ONNX 模型保留在当前目录中作为 .tgz 文件)和 -l debug 标志(用于打印大量调试信息)。

要找出哪些模型可用,请使用 -p 运行脚本以打印可用模型列表;或使用 -m 后跟不完整的名称,脚本将建议准确的名称。

不使用 -m 指定模型,脚本将检查 ONNX 模型库中的所有模型。

ONNX 模型库性能分析

如果要收集有关模型库(或任何模型)的性能信息,最简单的方法是在编译时请求所需的统计信息(使用 -profile-ir 标志),将输出统计信息转移到文件中,然后使用 make-report.py 进行分析。例如:

> ONNX_MLIR_INSTRUMENT_FILE=run.log RunONNXModelZoo.py -c "-O3 -march=arm64 --profile-ir=Onnx" -m bertsquad-10
...
> make-report.py -r run.log
...
Statistics start (all ops).
  onnx.Add, 112, 0.0130570
  onnx.Cast, 105, 0.0001860
  onnx.Concat, 55, 0.0001290
  onnx.Constant, 473, 0.0008220

运行时性能分析信息也可以与特定的编译时统计信息结合使用。假设我们对 SIMD 统计信息感兴趣。我们使用 -opt-report 选项通知编译器要发出的编译时统计信息,并通知 RunONNXModelZoo.py 我们希望保留编译器输出,方法是使用 --log-to-file 选项。例如:

> ONNX_MLIR_INSTRUMENT_FILE=run.log RunONNXModelZoo.py -c "-O3 -march=arm64 -opt-report=Simd --profile-ir=Onnx" -m bertsquad-10 --log-to-file compile.log
...
> make-report.py -c compile.log -r run.log
...
Statistics start (all ops).
  onnx.Add-simd, 112, 0.0130570
  onnx.Cast, 23, 0.0000650
  onnx.Gemm, 1, 0.0003570
  onnx.Gemm-simd, 72, 0.8109330

在上面的列表中,已矢量化的操作将分别汇总,并在其各自的操作名称后附加 -simd 后缀。

相同的选项和环境变量对 RunONNXModel.pyRunONNXModelZoo.py 都适用。