onnx-mlir

Logo

MLIR 編譯器基礎架構中 ONNX 模型的表示和參考降階

在 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 測試預設會從其 maven 儲存庫下載 jsoniter jar。如果使用者在建置 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 Form (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 支援的目前版本舊時,可以使用設定為 true 的環境變數 INVOKECONVERTER 來調用 onnx 版本轉換器。例如,將會針對 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>) -> ()

您可以在這個測試案例上執行形狀推斷 (shape inference) 流程,並獲得以下輸出:

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 op 層級的逐步執行是正確的。您需要額外的標誌才能在檢測上執行 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 選項告知編譯器要發出的編譯時統計資料,並使用 --log-to-file 選項告知 RunONNXModelZoo.py 我們想要保留編譯器輸出。例如:

> 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