MLIRコンパイラインフラストラクチャにおけるONNXモデルの表現と参照ローワーリング
GitHubでプロジェクトを見る onnx/onnx-mlir
このプロジェクトはonnxによって保守されています。
GitHub Pagesでホスト — テーマはorderedlistによって提供されています。
onnx-mlirでは、実装の正確性を確保するために3種類のテストがあります。
バックエンドテストは、onnxノードとモデルテストに基づいたonnx-mlirのエンドツーエンドテストです。C/C++の.soライブラリとJNIの.jarアーカイブの両方についてテストが可能です。各C/C++テストターゲットに対して-jni
サフィックスを追加すると、対応するJNIテストターゲットが得られます。テストを実行するには、次のコマンドを使用します。
cmake --build . --config Release --target check-onnx-backend[-jni]
バックエンドテストを実行するには、third_party/onnxなどのパッケージをインストールする必要があります。独自のonnxパッケージをpip install your-onnx-mlir/third_party/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
を使用して1つのテストケースをテストできます。例えば、
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にはIMPORTER_FORCE_DYNAMIC
が定義されている場合にパスしないテストを指定するリストtest_not_for_dynamicがあります。
onnxノードテストは実行時に入力テンソルを受け入れるため、onnxモデルのコンパイル時には入力が定数ではありません。しかし、実際には入力が定数になる場合があり、そのような状況をテストしたいと考えています。
定数入力でのテストは、チェッカーでも使用されている次のコマンドを使用することで最も簡単に実行できます。
cmake --build . --config Release --target check-onnx-backend-constant[-jni]
単一のonnxノード(例:test_add_cpu
)をテストするには、TEST_CONSTANT
とIMPORTER_FORCE_CONSTANT
の2つの環境変数を使用します(例:
TEST_CONSTANT=true IMPORTER_FORCE_CONSTANT="0" TEST_CASE_BY_USER=test_add_cpu make check-onnx-backend[-jni]
これにより、最初の入力(インデックス0)が定数になり、モデルの入力は2つから1つになります。
環境変数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
で定義されているツールは、TEST_CASE_BY_USER=selected_test_name make check-onnx-backend
コマンドを使用して生成されたものなど、.so
モデルからファイルを簡単に実行するために使用できます。 onnx-mlir/src/Compiler/CompilerUtils.cpp
ファイルのoverridePreserveFiles
値をKeepFilesOfType::All
などに設定することで、他の方法でビルドされたモデルも保存できます。
onnxモデルがonnx-mlirでサポートされている現在のバージョンよりも古い場合、環境変数INVOKECONVERTER
をtrueに設定してonnxバージョンコンバーターを呼び出すことができます。例えば、INVOKECONVERTER=true make check-onnx-backend
では、すべてのテストケースに対してコンバーターが呼び出されます。test.pyには、個々のケースでコンバーターを呼び出すためのtest_need_converter
というリストがあります。
このツールは、モデルによって提供されるシグネチャを直接スキャンし、必要な入力をランダムな値で初期化してから、モデルに関数呼び出しを行います。このプログラムは、gdb
、lldb
、valgrind
などの他のツールと組み合わせて使用できます。ユーティリティオプションを一覧表示するには、実行時に-h
または--help
フラグを使用するだけです。
まず、ツールのコンパイルを行う必要があります。これは2つのモードのいずれかで実行できます。最初のモードでは、ツールは静的にリンクされたモデルでコンパイルされます。このモードでは、.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
でライブラリのパスを確認できます。)
2番目のモードでは、モデルなしでツールがコンパイルされます。これは実行時に渡されます。このオプションを有効にするには、-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をチェックすることで、1つのパスの機能をテストできます。例えば、シェイプ推論のテストケースtest.mlirがあります。
func @test_default_transpose(%arg0 : tensor<5x5x1x32xf32>) -> tensor<*xf32> {
%0 = "onnx.Transpose"(%arg0) : (tensor<5x5x1x32xf32>) -> tensor<*xf32>
"std.return"(%0) : (tensor<*xf32>) -> ()
このテストケースでシェイプ推論パスを実行すると、次の出力が得られます。
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ダイアレクトのすべてのテストケースは、test/mlir/onnxディレクトリにまとめられています。これらのテストケースは、make check-onnx-lit
で実行できます。このターゲットはビルドの必須要件です。
ONNXパッケージが提供するテストに加えて、数値的な正確性をテストするために数値テストを使用します。目標は、広範な数値に基づいた単体テストを提供することです。これは、最適化変換が有効で正しいことを保証するために非常に重要です。特定のアーキテクチャパラメータ(ベクトル幅など)に合わせて専門化するにつれて、より多くのコーナーケースが発生します。数値テストは、テスト対象の演算の単純でナイーブな(そして非常に遅い)実装に基づいて、膨大な数の数値に基づいた単体テストを生成し、演算のローワーリングと最適化の正確性を検証するために使用されます。
数値テストは、以下の2つのコンポーネントが独立して分離されるように構成する必要があります。
テストケースパラメータを生成する方法は2つあります。
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
以下の2つの環境変数にシード値を記録することにより
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
を追加します。your_model_name.input.mlirという名前の、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)
インストルメンテーションの出力は、ONNX演算レベルでのgdbステップが正しく動作したことを示しています。インストルメンテーションでonnx-mlirを実行するには追加のフラグが必要ですが、gdbには必要ありません。ソースファイルはtest_add.input.mlirです。今後の作業の1つは、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で呼び出すだけです。
ソースファイルに1つのトレースメッセージしかない場合など、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
に追加することで有効にできます。
RunONNXModelZoo.pyというPythonスクリプトを提供して、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
フラグに加えて、便利なオプションとしては、onnxモデルを現在のディレクトリに.tgz
ファイルとして残すための-k
フラグ、および多くのデバッグ情報を表示するための-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
オプションを使用して出力するコンパイル時統計情報を知らせ、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.py
とRunONNXModelZoo.py
で同様に機能します。