618ZXW

[TVMチュートリアル] NVIDIA GPU向け畳み込みネットワークの自動チューニング

Apache TVMは、CPU、GPU、そして様々な機械学習アクセラレータチップに適した、エンドツーエンドのディープラーニングビルドフレームワークです。TVMの中国語版ドキュメントはこちらをご覧ください→Apache TVMは、CPU、GPU、そして様々な機械学習アクセラレータチップに適した、エンドツーエンドのディープラーニングビルドフレームワークです。| Apache TVM中国語版ウェブサイト

最適なパフォーマンスを実現するには、特定のデバイスとワークロードに合わせた自動チューニングが不可欠です。この記事では、NVIDIA GPU向けに畳み込みネットワーク全体をチューニングする方法について説明します。

TVMでは、NVIDIA GPU向けの演算子実装はテンプレート形式で記述されており、タイル係数、アンローリングなど、多くの調整可能なパラメータが含まれています。ニューラルネットワーク内のすべての畳み込み演算子と深度方向畳み込み演算子をチューニングした後、必要なすべての演算子の最適なパラメータ値を格納するログファイルが生成されます。TVMコンパイラはこれらの演算子をコンパイルする際に、このログファイルを照会して最適なパラメータ値を取得します。

NVIDIA GPU 用のプリセット パラメータもいくつかリリースしました。詳細については、NVIDIA GPU ベンチマークをご覧ください。

このチュートリアルはWindowsまたは最新バージョンのmacOSでは動作しませんのでご注意ください。実行するには、チュートリアルの本文をif __name__ == "__main__": ` コードブロック内に配置してください。

依存関係をインストールする

TVM で autotvm パッケージを使用するには、追加の依存関係をインストールする必要があります (Python 2 を使用している場合は、「3」を「2」に変更してください)。

 pip3 install --user psutil xgboost tornado cloudpickle

チューニング中のTVMパフォーマンスを向上させるには、TVMのFFIとしてCythonを使用することをお勧めします。TVMのルートディレクトリで、以下のコマンドを実行してください。

 pip3 install --user cython sudo make cython3

Python コードでパッケージをインポートします。

 import os import numpy as np import tvm from tvm import relay, autotvm import tvm.relay.testing from tvm.autotvm.tuner import XGBTuner, GATuner, RandomTuner, GridSearchTuner import tvm.contrib.graph_executor as runtime

ネットワークを定義する

まず、RelayフロントエンドAPIでネットワークを定義する必要がありますtvm.relay.testingから定義済みのネットワークを読み込むことができます。MXNet、ONNX、TensorFlowのモデルも読み込むことができます。

 def get_network(name, batch_size): """获取网络的符号定义和随机权重""" input_shape = (batch_size, 3, 224, 224) output_shape = (batch_size, 1000) if "resnet" in name: n_layer = int(name.split("-")[1]) mod, params = relay.testing.resnet.get_workload( num_layers=n_layer, batch_size=batch_size, dtype=dtype ) elif "vgg" in name: n_layer = int(name.split("-")[1]) mod, params = relay.testing.vgg.get_workload( num_layers=n_layer, batch_size=batch_size, dtype=dtype ) elif name == "mobilenet": mod, params = relay.testing.mobilenet.get_workload(batch_size=batch_size, dtype=dtype) elif name == "squeezenet_v1.1": mod, params = relay.testing.squeezenet.get_workload( batch_size=batch_size, version="1.1", dtype=dtype ) elif name == "inception_v3": input_shape = (batch_size, 3, 299, 299) mod, params = relay.testing.inception_v3.get_workload(batch_size=batch_size, dtype=dtype) elif name == "mxnet": # MXNet 模型的示例from mxnet.gluon.model_zoo.vision import get_model block = get_model("resnet18_v1", pretrained=True) mod, params = relay.frontend.from_mxnet(block, shape={"data": input_shape}, dtype=dtype) net = mod["main"] net = relay.Function( net.params, relay.nn.softmax(net.body), None, net.type_params, net.attrs ) mod = tvm.IRModule.from_expr(net) else: raise ValueError("Unsupported network: " + name) return mod, params, input_shape, output_shape

最適化オプションを設定する

最適化の前にシステムを構成します。

 #### 设备配置#### target = tvm.target.cuda() #### 调优OPTION #### network = "resnet-18" log_file = "%s.log" % network dtype = "float32" tuning_option = { "log_filename": log_file, "tuner": "xgb", "n_trial": 2000, "early_stopping": 600, "measure_option": autotvm.measure_option( builder=autotvm.LocalBuilder(timeout=10), runner=autotvm.LocalRunner(number=20, repeat=3, timeout=4, min_repeat_ms=150), ), }

出力結果:

 /workspace/python/tvm/target/target.py:389: UserWarning: Try specifying cuda arch by adding 'arch=sm_xx' to your target. warnings.warn("Try specifying cuda arch by adding 'arch=sm_xx' to your target.")

最適化オプションの設定方法に関する注意: 一般に、提供されているデフォルト値は非常にうまく機能します。
チューニングに十分な時間がある場合は、 n_trialearly_stopping大きく設定して、チューニング プロセスをより長い期間実行できるようにすることができます。
複数のデバイスがある場合は、それらすべてをテストに使用して、チューニング プロセスを高速化できます。(以下のScale up measurementセクションを参照してください)。

最適化を開始する

次に、ネットワークからチューニングタスクを抽出し、チューニングを開始します。次に、シンプルなユーティリティ関数を提供します。これは、タスクリストを順番にチューニングする初期実装です。将来的には、より複雑なチューニングスケジューラを導入する予定です。

 # 可跳过此函数的实现。 def tune_tasks( tasks, measure_option, tuner="xgb", n_trial=1000, early_stopping=None, log_filename="tuning.log", use_transfer_learning=True, ): # 创建tmp 日志文件tmp_log_file = log_filename + ".tmp" if os.path.exists(tmp_log_file): os.remove(tmp_log_file) for i, tsk in enumerate(reversed(tasks)): prefix = "[Task %2d/%2d] " % (i + 1, len(tasks)) # 创建调优器if tuner == "xgb": tuner_obj = XGBTuner(tsk, loss_type="reg") elif tuner == "xgb_knob": tuner_obj = XGBTuner(tsk, loss_type="reg", feature_type="knob") elif tuner == "xgb_itervar": tuner_obj = XGBTuner(tsk, loss_type="reg", feature_type="itervar") elif tuner == "xgb_curve": tuner_obj = XGBTuner(tsk, loss_type="reg", feature_type="curve") elif tuner == "xgb_rank": tuner_obj = XGBTuner(tsk, loss_type="rank") elif tuner == "xgb_rank_knob": tuner_obj = XGBTuner(tsk, loss_type="rank", feature_type="knob") elif tuner == "xgb_rank_itervar": tuner_obj = XGBTuner(tsk, loss_type="rank", feature_type="itervar") elif tuner == "xgb_rank_curve": tuner_obj = XGBTuner(tsk, loss_type="rank", feature_type="curve") elif tuner == "xgb_rank_binary": tuner_obj = XGBTuner(tsk, loss_type="rank-binary") elif tuner == "xgb_rank_binary_knob": tuner_obj = XGBTuner(tsk, loss_type="rank-binary", feature_type="knob") elif tuner == "xgb_rank_binary_itervar": tuner_obj = XGBTuner(tsk, loss_type="rank-binary", feature_type="itervar") elif tuner == "xgb_rank_binary_curve": tuner_obj = XGBTuner(tsk, loss_type="rank-binary", feature_type="curve") elif tuner == "ga": tuner_obj = GATuner(tsk, pop_size=100) elif tuner == "random": tuner_obj = RandomTuner(tsk) elif tuner == "gridsearch": tuner_obj = GridSearchTuner(tsk) else: raise ValueError("Invalid tuner: " + tuner) if use_transfer_learning: if os.path.isfile(tmp_log_file): tuner_obj.load_history(autotvm.record.load_from_file(tmp_log_file)) # 开始调优tsk_trial = min(n_trial, len(tsk.config_space)) tuner_obj.tune( n_trial=tsk_trial, early_stopping=early_stopping, measure_option=measure_option, callbacks=[ autotvm.callback.progress_bar(tsk_trial, prefix=prefix), autotvm.callback.log_to_file(tmp_log_file), ], ) # 选择最佳记录到缓存文件autotvm.record.pick_best(tmp_log_file, log_filename) os.remove(tmp_log_file)

最後に、チューニング プロセスが開始され、エンドツーエンドのパフォーマンスが評価されます。

 def tune_and_evaluate(tuning_opt): # 从relay 程序中提取工作负载print("Extract tasks...") mod, params, input_shape, out_shape = get_network(network, batch_size=1) tasks = autotvm.task.extract_from_program( mod["main"], target=target, params=params, ops=(relay.op.get("nn.conv2d"),) ) # 运行调优任务print("Tuning...") tune_tasks(tasks, **tuning_opt) # 编译具有历史最佳记录的内核with autotvm.apply_history_best(log_file): print("Compile...") with tvm.transform.PassContext(opt_level=3): lib = relay.build_module.build(mod, target=target, params=params) # 加载参数dev = tvm.device(str(target), 0) module = runtime.GraphModule(lib["default"](dev)) data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype)) module.set_input("data", data_tvm) # 评估print("Evaluate inference time cost...") print(module.benchmark(dev, number=1, repeat=600)) # 不在网页服务器中运行调优,因为它需要的时间太长。 # 取消注释运行下一行# tune_and_evaluate(tuning_option)

サンプル出力

チューニングには多くのプログラムをコンパイルし、特徴を抽出する必要があるため、高性能なCPUが推奨されます。以下は出力例です。32TBのAMD Ryzen Threadripperでは、以下の出力が表示されるまでに約4時間かかりました。チューニング対象はNVIDIA 1080 Tiです。(コンパイル中にエラーが発生する場合がありますが、チューニングを続行する場合は無視できます。)

 Extract tasks... Tuning... [Task 1/12] Current/Best: 541.83/3570.66 GFLOPS | Progress: (960/2000) | 1001.31 s Done. [Task 2/12] Current/Best: 0.56/ 803.33 GFLOPS | Progress: (704/2000) | 608.08 s Done. [Task 3/12] Current/Best: 103.69/1141.25 GFLOPS | Progress: (768/2000) | 702.13 s Done. [Task 4/12] Current/Best: 2905.03/3925.15 GFLOPS | Progress: (864/2000) | 745.94 sterminate called without an active exception [Task 4/12] Current/Best: 2789.36/3925.15 GFLOPS | Progress: (1056/2000) | 929.40 s Done. [Task 5/12] Current/Best: 89.06/1076.24 GFLOPS | Progress: (704/2000) | 601.73 s Done. [Task 6/12] Current/Best: 40.39/2129.02 GFLOPS | Progress: (1088/2000) | 1125.76 s Done. [Task 7/12] Current/Best: 4090.53/5007.02 GFLOPS | Progress: (800/2000) | 903.90 s Done. [Task 8/12] Current/Best: 4.78/1272.28 GFLOPS | Progress: (768/2000) | 749.14 s Done. [Task 9/12] Current/Best: 1391.45/2325.08 GFLOPS | Progress: (992/2000) | 1084.87 s Done. [Task 10/12] Current/Best: 1995.44/2383.59 GFLOPS | Progress: (864/2000) | 862.60 s Done. [Task 11/12] Current/Best: 4093.94/4899.80 GFLOPS | Progress: (224/2000) | 240.92 sterminate called without an active exception [Task 11/12] Current/Best: 3487.98/4909.91 GFLOPS | Progress: (480/2000) | 534.96 sterminate called without an active exception [Task 11/12] Current/Best: 4636.84/4912.17 GFLOPS | Progress: (1184/2000) | 1381.16 sterminate called without an active exception [Task 11/12] Current/Best: 50.12/4912.17 GFLOPS | Progress: (1344/2000) | 1602.81 s Done. [Task 12/12] Current/Best: 3581.31/4286.30 GFLOPS | Progress: (736/2000) | 943.52 s Done. Compile... Evaluate inference time cost... Mean inference time (std dev): 1.07 ms (0.05 ms)

参照ベースラインは MXNet + TensorRT で、ResNet-18 での時間コストは 1.30 ミリ秒なので、少し速くなります。

述べる
困難に直面していますか?
セルフチューニングモジュールはエラーが発生しやすい傾向があります。常に「0.00/0.00 GFLOPS」と表示される場合は、問題が発生していることを示しています。
まず、デバイス設定が正しく設定されていることを確認してください。次に、スクリプトの先頭に次の行を追加してデバッグ情報を出力します。これにより、各テスト結果が出力され、そこから役立つエラーメッセージを見つけることができます。
インポートログ
ログ記録.getLogger('autotvm').setLevel(ログ記録.DEBUG)

https://discuss.tvm.apache.org でいつでもコミュニティに支援を求めることができます。

複数のデバイスを使用してテストを加速する

デバイスが複数ある場合、すべてのデバイスをテストに使用できます。TVMはRPC Tracker(集中型コントローラーノード)を使用して分散デバイスを管理します。GPUカードが10枚ある場合、それらすべてをトラッカーに登録し、10個のテストを並列実行することで、チューニングプロセスを高速化できます。

RPCトラッカーを起動するには、ホストマシンで次のコマンドを実行します。トラッカーはチューニングプロセス全体を通して必要なので、このコマンドを実行するには新しいターミナルを開く必要があります。

 python -m tvm.exec.rpc_tracker --host=0.0.0.0 --port=9190

期待される出力:

 INFO:RPCTracker:bind to 0.0.0.0:9190

デバイスごとに新しいターミナルを開き、専用のRPCサーバーを起動する必要があります。デバイスの種類を識別するためにキーが使用されます。(注:rocmバックエンドでは内部コンパイラエラーが発生するため、パラメータリストに「--no-fork」を追加する必要があります。)

 python -m tvm.exec.rpc_server --tracker=127.0.0.1:9190 --key=1080ti

デバイスを登録した後、rpc_tracker をクエリして登録が成功したかどうかを確認できます。

 python -m tvm.exec.query_rpc_tracker --host=127.0.0.1 --port=9190

たとえば、GTX 1080 Ti が 4 台、Titan X が 2 台、GFX900 が 1 台ある場合、出力は次のようになります。

 Queue Status ---------------------------------- key total free pending ---------------------------------- 1080ti 4 4 0 titanx 2 2 0 gfx900 1 1 0 ----------------------------------

最後に、RPCRunnerを使用するようにチューニングオプションを変更します。上記の該当部分を以下のコードに置き換えてください。

 tuning_option = { "log_filename": log_file, "tuner": "xgb", "n_trial": 2000, "early_stopping": 600, "measure_option": autotvm.measure_option( builder=autotvm.LocalBuilder(timeout=10), runner=autotvm.RPCRunner( "1080ti", # change the device key to your key "127.0.0.1", 9190, number=20, repeat=3, timeout=4, min_repeat_ms=150, ), ), }

Pythonソースコードをダウンロードする: tune_relay_cuda.py

Jupyter Notebook をダウンロード: tune_relay_cuda.ipynb