TVM は、あらゆる種類の CPU、GPU、その他の特殊なアクセラレータで動作するオープンソースのディープラーニング コンパイラです。その目標は、あらゆるハードウェア上でモデルを最適化して実行できるようにすることです。モデルの生産性に重点を置くディープラーニング フレームワークとは異なり、TVM はハードウェア上のモデルのパフォーマンスと効率に重点を置いています。 この記事では、TVM のコンパイル プロセスと、独自のモデルを自動的に調整する方法について簡単に説明します。さらに詳しい情報については、TVM公式コンテンツを参照してください。 - 書類: https://tvm.apache.org/docs/
- ソースコード: https://github.com/apache/tvm
コンパイルプロセスTVM ドキュメント デザインと建築 サンプルのコンパイル プロセス、論理構造コンポーネント、デバイス ターゲットの実装などについて説明します。プロセスを次の図に示します。
大まかに言えば、必要な手順は次のとおりです。 - インポート: フロントエンド コンポーネントは、モデルの内部表現 (IR) の関数のコレクションである IRModule にモデルを抽出します。
- 変換: コンパイラは、IRModule を機能的に同等またはほぼ同等の別の IRModule (量子化の場合など) に変換します。ほとんどの変換はターゲット (バックエンド) に依存しません。 TVM では、ターゲットがコンバージョン チャネルの構成に影響を与えることもできます。
- ターゲット変換: コンパイラは IRModule をターゲット上の実行可能形式に変換 (コード生成) します。ターゲット翻訳結果はruntime.Moduleとしてカプセル化され、ターゲットランタイム環境でエクスポート、ロード、実行できます。
- ランタイム実行: ユーザーは、runtime.Module をロードし、サポートされているランタイム環境でコンパイルされた関数を実行します。
モデルの調整TVM ユーザー チュートリアルは、モデルのコンパイルと最適化の方法から始まり、TE、TensorIR、Relay などの下位レベルの論理構造コンポーネントに徐々に進みます。 ここでは、AutoTVM を使用してモデルを自動的に調整する方法と、TVM によるモデルのコンパイル、調整、実行のプロセスを実際に理解する方法についてのみ説明します。原文 Python インターフェースを使用したモデルのコンパイルと最適化 (AutoTVM) 。 TVMの準備まず、TVMをインストールします。 TVM のインストールに関するドキュメント、または TVM のインストールに関する注記を参照してください。 その後、TVM Python API を使用してモデルを調整できます。まず、次の依存関係をインポートします。 onnx をインポートする tvm . contrib . download からdownload_testdata をインポート PIL インポート画像から numpyをnp としてインポートする tvm . relay をリレーとしてインポートする tvm をインポート tvm.contrib からgraph_executor をインポートします。
モデルを準備してロードする事前トレーニング済みの ResNet-50 v2 ONNX モデルを取得してロードします。 model_url = "" . join ( [ 「https://github.com/onnx/models/raw/」 、 「メイン/ビジョン/分類/resnet/モデル/」 、 "resnet50-v2-7.onnx" 、 ] )
model_path = download_testdata ( model_url 、 "resnet50-v2-7.onnx" 、 module = "onnx" ) onnx_model = onnx.load ( model_path ) です。
画像の準備と前処理テスト画像を取得し、224x224 NCHW 形式に前処理します。 img_url = "https://s3.amazonaws.com/model-server/inputs/kitten.jpg" img_path = download_testdata ( img_url 、 "imagenet_cat.png" 、 モジュール= "data" )
# 224x224 にサイズを変更します resized_image = 画像.open ( img_path ) .resize ( ( 224,224 )) img_data = np . asarray ( resized_image ). astype ( "float32" )
# 入力画像はHWC レイアウトですが、 ONNXはCHW 入力を期待しているので、 配列を変換します img_data = np . transpose ( img_data , ( 2 , 0 , 1 ))
# ImageNet 入力仕様に従って正規化する imagenet_mean = np . 配列([ 0.485 , 0.456 , 0.406 ] ). reshape (( 3 , 1 , 1 )) imagenet_stddev = np . 配列([ 0.229 , 0.224 , 0.225 ] ). reshape (( 3 , 1 , 1 )) norm_img_data = ( img_data / 255 - imagenet_mean ) / imagenet_stddev
# 4 次元の入力を想定しているため、 バッチ次元を追加します: NCHW 。 img_data = np . expand_dims ( norm_img_data 、 axis = 0 )
TVMリレーを使用してモデルをコンパイルするTVM は ONNX モデルを Relay にインポートし、TVM グラフ モデルを作成します。 ターゲット= 入力( "ターゲット [llvm]: " ) ターゲットでない場合: ターゲット= "llvm" # ターゲット= "llvm -mcpu=core-avx2" # ターゲット= "llvm -mcpu=skylake-avx512"
# 入力名はモデルの種類によって異なる場合があります。 ツールを使用できます # 入力名をチェックするNetron のような input_name = "データ" shape_dict = { input_name : img_data . shape }
mod 、 params = relay.frontend.from_onnx (onnx_model 、 shape_dict )
tvm.transform.PassContext ( opt_level = 3 ) の場合: lib = リレー.build ( mod 、 target = ターゲット、 params = パラメータ)
dev = tvm . デバイス( str ( ターゲット), 0 ) モジュール= graph_executor.GraphModule ( lib [ " default" ] ( dev ))
で ターゲット ターゲットハードウェアプラットフォームです。 llvm 使用される CPU を参照します。パフォーマンスを最適化するには、アーキテクチャ命令セットを指定することをお勧めします。 CPU を表示するには、次のコマンドを使用できます。 $ llc --version | grep CPU ホストCPU : skylake $ lscpu
または、製造元の Web サイト (Intel® 製品など) に直接アクセスして、製品パラメータを表示します。 TVMランタイムを使用してモデルを実行するTVM ランタイムでモデルを実行し、予測を行います。 dtype = "float32" モジュール.set_input ( input_name , img_data ) モジュール.run () 出力形状= ( 1 , 1000 ) tvm_output = module.get_output ( 0 , tvm.nd.empty ( output_shape ) ) . numpy ( )
最適化の前にパフォーマンスデータを収集する最適化の前にパフォーマンス データを収集します。 インポートtimeit
タイミング番号= 10 タイミングリピート= 10 最適化されていない= ( np . array ( timeit . Timer ( lambda : module . run () ). repeat ( repeat = Timing_repeat 、 number = Timing_number )) * 1000 / タイミング番号 ) 最適化されていない= { 「平均」 : np . 平均( 最適化されていない)、 「中央値」 : np . 中央値( 最適化されていない)、 "std" : np . std ( 最適化されていない)、 }
印刷( 最適化されていない)
その後、最適化後のパフォーマンスを比較するために使用されます。 予測結果を得るための後処理出力出力された予測結果は、読み取り可能な分類結果に後処理されます。 scipy から. special import softmax
# ラベルのリストをダウンロードする labels_url = "https://s3.amazonaws.com/onnx-model-zoo/synset.txt" labels_path = download_testdata ( labels_url 、 "synset.txt" 、 module = "data" )
open ( labels_path , "r" ) をf として: ラベル= [ l . rstrip () はlをf に入れる ]
# 出力を開いて出力テンソルを読み取ります スコア= ソフトマックス( tvm_output ) スコア= np . squeeze ( スコア) ランク= np . argsort ( スコア)[:: - 1 ] 順位[ 0 : 5 ] : print ( "クラス='%s'、確率=%f" % ( ラベル[ 順位]、 スコア[ 順位]))
モデルを調整し、調整データを取得するAutoTVM を使用して、ターゲット ハードウェア プラットフォームを自動的に調整し、調整データを取得します。 tvm . auto_scheduler をauto_scheduler としてインポートします。 tvm.autotvm.tuner からXGBTuner をインポート tvm からautotvm をインポート
数= 10 繰り返し= 1 min_repeat_ms = 0 # CPU でチューニングしているので、 0 に設定できます タイムアウト= 10 # 秒単位
# TVM ランナーを作成する ランナー= autotvm.LocalRunner ( 数= 数、 繰り返し= 繰り返し、 タイムアウト= タイムアウト、 min_repeat_ms = min_repeat_ms 、 enable_cpu_cache_flush = True 、 )
チューニングオプション= { 「チューナー」 : 「xgb」 、 「トライアル」 : 10 , 「早期停止」 : 100 、 "測定オプション" : autotvm . 測定オプション( ビルダー= autotvm . LocalBuilder ( build_func = "default" )、 ランナー= runner )、 "チューニングレコード" : "resnet-50-v2-autotuning.json" 、 }
# onnx モデルからタスクを抽出することから始めます タスク= autotvm . task . extract_from_program ( mod [ "main" ]、 ターゲット= target 、 パラメータ= params )
# 抽出されたタスクを順番にチューニングします。 i の場合、 タスクをenumerate ( タスク) します。 prefix = "[タスク %2d/%2d] " % ( i + 1 , len ( タスク)) tuner_obj = XGBTuner ( タスク、 loss_type = "rank" ) tuner_obj . チューン( n_trial = min ( tuning_option [ "trials" ], len (task.config_space ) ), early_stopping = チューニングオプション[ "early_stopping" ], measure_option = チューニングオプション[ "measure_option" ], コールバック= [ autotvm.callback.progress_bar ( tuning_option [ "trials" ] , prefix = prefix ), autotvm . callback . log_to_file ( tuning_option [ "tuning_records" ]), ]、 )
その上 チューニングオプション オプション XGBoost グリッド アルゴリズムは最適化された検索を実行し、データが記録されます チューニングレコード 。 モデルを再コンパイルし、調整されたデータを使用するチューニング データに基づいて最適化されたモデルを再コンパイルします。 autotvm . apply_history_best ( tuning_option [ "tuning_records" ] ) を使用する場合: tvm.transform.PassContext ( opt_level = 3 、 config = {} ) の場合: lib = リレー.build ( mod 、 target = ターゲット、 params = パラメータ)
dev = tvm . デバイス( str ( ターゲット), 0 ) モジュール= graph_executor.GraphModule ( lib [ " default" ] ( dev ))
# 最適化されたモデルが実行され、 同じ結果が生成されることを確認します
dtype = "float32" モジュール.set_input ( input_name , img_data ) モジュール.run () 出力形状= ( 1 , 1000 ) tvm_output = module.get_output ( 0 , tvm.nd.empty ( output_shape ) ) . numpy ( )
スコア= ソフトマックス( tvm_output ) スコア= np . squeeze ( スコア) ランク= np . argsort ( スコア)[:: - 1 ] 順位[ 0 : 5 ] : print ( "クラス='%s'、確率=%f" % ( ラベル[ 順位]、 スコア[ 順位]))
調整済みモデルと未調整モデルの比較最適化後のパフォーマンス データを収集し、最適化前のデータと比較します。 インポートtimeit
タイミング番号= 10 タイミングリピート= 10 最適化= ( np . array ( timeit . Timer ( lambda : module . run () ). repeat ( repeat = Timing_repeat 、 number = Timing_number )) * 1000 / タイミング番号 ) 最適化= { "平均" : np . 平均( 最適化)、 "中央値" : np . 中央値( 最適化)、 "標準値" : np . 標準値( 最適化)}
print ( "最適化済み: %s" % ( 最適化済み)) print ( "最適化されていません: %s" % ( 最適化されていません))
モデルを調整するプロセス全体の結果は次のとおりです。 $ 時間python autotvm_tune . py # TVM コンパイルしてモデルを実行する ## ONNX モデルのダウンロードと読み込み ## テストイメージのダウンロード、 前処理、 読み込み ## Relay でモデルをコンパイルする ターゲット[ llvm ]: llvm - mcpu = core - avx2 1 つ以上の演算子が調整されていません。 パフォーマンスを向上させるには、 モデルを調整してください。 詳細を表示するには、 DEBUG ログレベルを使用してください。 ## TVM ランタイムで実行 ## 基本的なパフォーマンスデータを収集する { '平均' : 44.97057118016528 、 '中央値' : 42.52320024970686 、 '標準偏差' : 6.870915251002107 } ## 出力を後処理する クラス= 'n02123045 トラ猫、トラ猫' 確率= 0.621104 クラス= 'n02123159 トラ猫' 確率= 0.356378 クラス= 'n02124075 エジプト猫' 確率= 0.019712 クラス= 'n02129604 トラ、Panthera tigris' 確率= 0.001215 クラス= 'n04040759 ラジエーター' 確率= 0.000262 # AutoTVM がモデルを調整します[ Y / n ] モデルを調整する [ タスク1/25 ] 現在/ 最高: 156.96 / 353.76 GFLOPS | 進行状況: ( 10/10 ) | 4.78 秒完了。 [ タスク2/25 ] 現在/ 最高: 54.66 / 241.25 GFLOPS | 進行状況: ( 10/10 ) | 2.88 秒完了。 [ タスク3/25 ] 現在/ 最高: 116.71 / 241.30 GFLOPS | 進行状況: ( 10/10 ) | 3.48 秒完了。 [ タスク4/25 ] 現在/ 最高: 119.92 / 184.18 GFLOPS | 進行状況: ( 10/10 ) | 3.48 秒完了。 [ タスク5/25 ] 現在/ 最高: 48.92 / 158.38 GFLOPS | 進行状況: ( 10/10 ) | 3.13 秒完了。 [ タスク6/25 ] 現在/ 最高: 156.89 / 230.95 GFLOPS | 進行状況: ( 10/10 ) | 2.82 秒完了。 [ タスク7/25 ] 現在/ 最高: 92.33 / 241.99 GFLOPS | 進行状況: ( 10/10 ) | 2.40 秒完了。 [ タスク8/25 ] 現在/ 最高: 50.04 / 331.82 GFLOPS | 進行状況: ( 10/10 ) | 2.64 秒完了。 [ タスク9/25 ] 現在/ 最高: 188.47 / 409.93 GFLOPS | 進行状況: ( 10/10 ) | 4.44 秒完了。 [ タスク10/25 ] 現在/ 最高: 44.81 / 181.67 GFLOPS | 進行状況: ( 10/10 ) | 2.32 秒完了。 [ タスク11/25 ] 現在/ 最高: 83.74 / 312.66 GFLOPS | 進行状況: ( 10/10 ) | 2.74 秒完了。 [ タスク12/25 ] 現在/ 最高: 96.48 / 294.40 GFLOPS | 進行状況: ( 10/10 ) | 2.82 秒完了。 [ タスク13/25 ] 現在/ 最高: 123.74 / 354.34 GFLOPS | 進行状況: ( 10/10 ) | 2.62 秒完了。 [ タスク14/25 ] 現在/ 最高: 23.76 / 178.71 GFLOPS | 進行状況: ( 10/10 ) | 2.90 秒完了。 [ タスク15/25 ] 現在/ 最高: 119.18 / 534.63 GFLOPS | 進行状況: ( 10/10 ) | 2.49 秒完了。 [ タスク16/25 ] 現在/ 最高: 101.24 / 172.92 GFLOPS | 進行状況: ( 10/10 ) | 2.49 秒完了。 [ タスク17/25 ] 現在/ 最高: 309.85 / 309.85 GFLOPS | 進行状況: ( 10/10 ) | 2.69 秒完了。 [ タスク18/25 ] 現在/ 最高: 54.45 / 368.31 GFLOPS | 進行状況: ( 10/10 ) | 2.46 秒完了。 [ タスク19/25 ] 現在/ 最高: 78.69 / 162.43 GFLOPS | 進行状況: ( 10/10 ) | 3.29 秒完了。 [ タスク20/25 ] 現在/ 最高: 40.78 / 317.50 GFLOPS | 進行状況: ( 10 / 10 ) | 4.52 秒完了。 [ タスク21/25 ] 現在/ 最高: 169.03 / 296.36 GFLOPS | 進行状況: ( 10/10 ) | 3.95 秒完了。 [ タスク22/25 ] 現在/ 最高: 90.96 / 210.43 GFLOPS | 進行状況: ( 10/10 ) | 2.28 秒完了。 [ タスク23/25 ] 現在/ 最高: 48.93 / 217.36 GFLOPS | 進行状況: ( 10/10 ) | 2.87 秒完了。 [ タスク25/25 ] 現在/ 最高: 0.00 / 0.00 GFLOPS | 進行状況: ( 0/10 ) | 0.00 秒完了。 [ タスク25/25 ] 現在/ 最高: 25.50 / 33.86 GFLOPS | 進行状況: ( 10/10 ) | 9.28 秒完了。 ## チューニングデータを使用して最適化されたモデルをコンパイルする クラス= 'n02123045 トラ猫、トラ猫' 確率= 0.621104 クラス= 'n02123159 トラ猫' 確率= 0.356378 クラス= 'n02124075 エジプト猫' 確率= 0.019712 クラス= 'n02129604 トラ、Panthera tigris' 確率= 0.001215 クラス= 'n04040759 ラジエーター' 確率= 0.000262 ## 調整済みモデルと未調整モデルの比較 最適化: { '平均' : 34.736288779822644 、 '中央値' : 34.547542000655085 、 '標準偏差' : 0.5144378649382363 } 最適化されていない: { '平均' : 44.97057118016528 、 '中央値' : 42.52320024970686 、 '標準偏差' : 6.870915251002107 }
実3 分23 秒904 ユーザー5 m2 .900 s システム5 分 37 秒099
パフォーマンス データを比較すると、調整されたモデルがより高速かつスムーズに実行されることがわかります。 |