MLIR 編譯器基礎架構中 ONNX 模型的表示和參考降階
此專案由 onnx 維護
託管於 GitHub Pages — 主題由 orderedlist 提供
在 onnx-mlir 中,有三種類型的測試來確保實作的正確性
後端測試是基於 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
。
檔案 test/backend/all_test_names.txt 包含 ONNX 套件提供的所有測試案例。您可以透過將測試案例新增至 test/backend/inference_backend.py 來啟用它。all_test_names.txt 會使用命令「make check-onnx-backend-case」自動產生。只有在升級 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_CONSTANT
和 IMPORTER_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
在支援的平台上(目前僅限 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
的清單,可讓您針對個別案例調用轉換器。
此工具會直接掃描模型提供的簽名,使用隨機值初始化所需的輸入,然後呼叫模型中的函式。接著可以將此程式與其他工具結合使用,例如 gdb
、lldb
或 valgrind
。若要列出公用程式選項,只需在執行階段使用 -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 公用程式檢查輸出 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 套件提供的測試外,數值測試還用於測試數值上的正確性。目標是提供基於數值的廣泛單元測試;這對於確保最佳化轉換的有效性和正確性非常重要:當我們針對特定架構參數 (如向量寬度) 進行特化時,將會出現更多邊緣情況。數值測試會根據被測試操作的簡單、原始 (且非常慢) 的實作,產生大量的基於數值的單元測試,用於驗證我們的操作降低和最佳化的正確性。
數值測試的結構應使以下兩個組件彼此獨立且分開:
這樣做的動機是我們希望以兩種方式產生測試案例參數:
isOMConvTheSameAsNaiveImplFor
。 // 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_ATOL
和 TEST_RTOL
設定為新的值。
在支援的平台上 (目前僅限 s390x),數值測試可以為編譯後的模型產生 SIMD 指令。若要啟用 SIMD,請設定 TEST_ARGS
環境變數,例如:
TEST_ARGS="-mcpu=z14" CTEST_PARALLEL_LEVEL=$(nproc) cmake --build . --config Release --target check-onnx-numerical
目前,我們提供加速器 NNPA 的測試。相關說明請參閱此處。
當您編譯 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 和 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-mlir
或 onnx-mlir-opt
來啟用。
我們提供了一個 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 模型動物園中的所有模型。
如果您想要收集模型動物園 (或任何模型) 的效能資訊,最簡單的方法是在編譯時 (使用 -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.py
和 RunONNXModelZoo.py
。