従来の推論の最適化は、通常、静的入力シェイプを持つモデルを対象としています。推論中に入力シェイプが変化すると、最適化の効果が失われる可能性があります。本番環境では、動的入力シェイプを持つモデルがますます一般的になり、さまざまな入力シェイプにわたって推論を最適化する必要性が高まっています。このトピックでは、ブレードを使用して動的入力シェイプを持つモデルを最適化する方法について説明します。
制限事項
このトピックで使用される環境は、次のバージョン要件を満たす必要があります:
-
システム環境:Linux 環境の Python 3.6 以降。
-
フレームワーク:PyTorch 1.7.1。
-
デバイスとバックエンド:NVIDIA T4、CUDA 11.0。
-
推論最適化ツール:ブレード 3.17.0 以降。
操作手順
以下の手順では、ブレードを使用して動的入力シェイプを持つ ResNet50 モデルを最適化する方法について説明します:
-
テストデータとモデルをビルドします。このトピックでは、torchvision の標準 ResNet50 モデルを使用します。
-
動的シェイプ範囲に基づいてブレードの構成を設定します。
-
blade.optimizeインターフェイスを呼び出してモデルを最適化し、最適化されたモデルを保存します。 -
元のモデルと最適化されたモデルの推論速度と結果をテストして、最適化レポートの情報を検証します。
-
ブレード SDK を統合して、最適化されたモデルをロードして推論を実行します。
ステップ 1:事前準備
-
事前学習済みモデルのパラメーターとテストデータをダウンロードします。
事前学習済みのパラメーターは torchvision からのもので、ダウンロードを高速化するために Object Storage Service (OSS) に保存されています。テストデータは ImageNet-1k 検証データセットからランダムに選択されています。データは事前処理済みで、ダウンロード後すぐに使用できます。
wget http://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/share/dynamic_ranges_pratice/resnet50-19c8e357.pth -O resnet50-19c8e357.pth wget http://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/share/dynamic_ranges_pratice/imagenet_val_example.pt -O imagenet_val_example.pt -
モデルを定義し、モデルのパラメーターとテストデータをロードし、TorchScript を生成します。
import torch import torchvision # Resnet50 をビルドします。 model = torchvision.models.resnet50().eval().cuda() # 事前学習済みのパラメーターをロードします。 ckpt = torch.load('resnet50-19c8e357.pth') model.load_state_dict(ckpt) # テストデータをロードします。 example_input = torch.load('imagenet_val_example.pt').cuda() # TorchScript を生成します。 traced_model = torch.jit.trace(model, example_input).cuda().eval()
ステップ 2:最適化のための構成の設定
動的シェイプ範囲に基づいてブレードの構成を設定します。ブレードは任意のディメンションの動的範囲をサポートしています。このトピックでは、バッチディメンションを使用して構成をデモンストレーションします。
-
動的シェイプ範囲を定義します。
有効な動的範囲には、次の 3 つのフィールドを含める必要があります:
-
min:動的シェイプの下限。
-
max:動的シェイプの上限。
-
opts:特別な最適化が必要なシェイプ。複数のシェイプを設定できます。最適化されたモデルでは、これらのシェイプの推論加速率が通常高くなります。
これら 3 つのフィールドは、次のルールに従う必要があります:
-
min、max、および opts の各シェイプグループの長さは、ネットワーク入力の数と等しくなければなりません。
-
min、max、および opts の各シェイプグループの対応する位置の値は、
min_num <= opt_num <= max_numを満たす必要があります。
例えば、次の動的シェイプ範囲を構築します。
shapes = { "min": [[1, 3, 224, 224]], "max": [[10, 3, 224, 224]], "opts": [ [[5, 3, 224, 224]], [[8, 3, 224, 224]], ] }さらに、ブレードは複数の動的範囲の設定をサポートしています。動的シェイプの上限と下限の間の範囲が大きすぎると、モデルの加速が顕著でない場合があります。大きな範囲を複数の小さな範囲に分割して、より良い加速を実現できます。複数の動的範囲を設定する方法については、「付録:複数の動的範囲の設定」をご参照ください。
-
-
定義された動的シェイプ範囲からブレードの構成をビルドします。
import blade import blade.torch as blade_torch # 動的シェイプを設定するための Blade Torch の構成。 blade_torch_cfg = blade_torch.Config() blade_torch_cfg.dynamic_tuning_shapes = shapes # 最高の加速を得るために FP16 精度チェックを無効にする Blade の構成。 gpu_config = { "disable_fp16_accuracy_check": True, } blade_config = blade.Config( gpu_config=gpu_config )
ステップ 3:ブレードを呼び出してモデルを最適化
-
blade.optimizeを呼び出してモデルを最適化します。以下のコードは一例です。このインターフェイスの詳細については、「Python インターフェイスドキュメント」をご参照ください。with blade_torch_cfg: optimized_model, _, report = blade.optimize( traced_model, # モデルパス。 'o1', # o1 はロスレス最適化用です。 config=blade_config, device_type='gpu', # GPU デバイス用に最適化します。 test_data=[(example_input,)] # テストデータ。 )モデルを最適化する際は、次の点にご注意ください:
-
blade.optimizeの最初の戻り値は、最適化されたモデルです。そのデータの型は入力モデルと同じです。この例では、入力は TorchScript であり、戻り値は最適化された TorchScript です。 -
入力
test_dataが定義された動的シェイプ範囲内にあることを確認してください。
-
-
最適化が完了したら、最適化レポートを出力します。
print("Report: {}".format(report))出力される最適化レポートは、次の出力のようになります。
Report: { "software_context": [ { "software": "pytorch", "version": "1.7.1+cu110" }, { "software": "cuda", "version": "11.0.0" } ], "hardware_context": { "device_type": "gpu", "microarchitecture": "T4" }, "user_config": "", "diagnosis": { "model": "unnamed.pt", "test_data_source": "user provided", "shape_variation": "undefined", "message": "Unable to deduce model inputs information (data type, shape, value range, etc.)", "test_data_info": "0 shape: (1, 3, 224, 224) data type: float32" }, "optimizations": [ { "name": "PtTrtPassFp16", "status": "effective", "speedup": "4.06", "pre_run": "6.55 ms", "post_run": "1.61 ms" } ], "overall": { "baseline": "6.54 ms", "optimized": "1.61 ms", "speedup": "4.06" }, "model_info": { "input_format": "torch_script" }, "compatibility_list": [ { "device_type": "gpu", "microarchitecture": "T4" } ], "model_sdk": {} }最適化レポートは、この例では
PtTrtPassFp16の最適化が有効であることを示しています。これにより、約 4.06 倍の速度向上が得られ、テストデータでのモデルの推論時間が 6.55 ms から 1.61 ms に短縮されます。これらの最適化結果はこの例のみのものです。実際の結果は異なる場合があります。最適化レポートのフィールドの詳細については、「最適化レポート」をご参照ください。 -
関連する PyTorch 関数を呼び出して、最適化された TorchScript モデルを保存およびロードします。
file_name = "resnet50_opt.pt" # 最適化されたモデルをローカルに保存します。 torch.jit.save(optimized_model, file_name) # ディスクから最適化されたモデルをロードします。 optimized_model = torch.jit.load(file_name)
ステップ 4:パフォーマンスと正確性の検証
最適化後、Python スクリプトを使用して最適化レポートの情報を検証します。
-
benchmarkメソッドを定義します。モデルを 10 回ウォームアップし、100 回実行して、平均推論時間を推論速度とします。import time @torch.no_grad() def benchmark(model, test_data): # モデルを評価モードに切り替えます。 model = model.eval() # ウォームアップ。 for i in range(0, 10): model(test_data) # 時間計測を開始します。 num_runs = 100 start = time.time() for i in range(0, num_runs): model(test_data) torch.cuda.synchronize() elapsed = time.time() - start rt_ms = elapsed / num_runs * 1000.0 # 結果を出力します。 print("{:.2f} ms.".format(rt_ms)) return rt_ms -
異なるシェイプの一連のテストデータを定義します。
dummy_inputs = [] batch_num = [1, 3, 5, 7, 9] for n in batch_num: dummy_inputs.append(torch.randn(n, 3, 224, 224).cuda()) -
各テストデータグループを反復処理します。
benchmarkメソッドを呼び出して、元のモデルと最適化されたモデルをテストし、結果を出力します。for inp in dummy_inputs: print(f'--------------test with shape {list(inp.shape)}--------------') print(" Origin model inference cost: ", end='') origin_rt = benchmark(traced_model, inp) print(" Optimized model inference cost: ", end='') opt_rt = benchmark(optimized_model, inp) speedup = origin_rt / opt_rt print(' Speed up: {:.2f}'.format(speedup)) print('')次のような結果が返されます。
--------------test with shape [1, 3, 224, 224]-------------- Origin model inference cost: 6.54 ms. Optimized model inference cost: 1.66 ms. Speed up: 3.94 --------------test with shape [3, 3, 224, 224]-------------- Origin model inference cost: 10.79 ms. Optimized model inference cost: 2.40 ms. Speed up: 4.49 --------------test with shape [5, 3, 224, 224]-------------- Origin model inference cost: 16.27 ms. Optimized model inference cost: 3.25 ms. Speed up: 5.01 --------------test with shape [7, 3, 224, 224]-------------- Origin model inference cost: 22.62 ms. Optimized model inference cost: 4.39 ms. Speed up: 5.16 --------------test with shape [9, 3, 224, 224]-------------- Origin model inference cost: 28.83 ms. Optimized model inference cost: 5.25 ms. Speed up: 5.49結果は、異なるシェイプのテストデータに対して、最適化されたモデルの推論速度が元のモデルよりも 3.94 倍から 5.49 倍速いことを示しています。これらの最適化結果はこの例のみのものです。実際の結果は異なる場合があります。
-
事前準備ステップの実際のテストデータ example_input を使用して、最適化されたモデルの正確性を検証します。
origin_output = traced_model(example_input) _, pred = origin_output.topk(1, 1, True, True) print("origin model output: {}".format(pred)) opt_output = optimized_model(example_input) _, pred = origin_output.topk(1, 1, True, True) print("optimized model output: {}".format(pred))次のような結果が返されます。
origin model output: tensor([[834]], device='cuda:0') optimized model output: tensor([[834]], device='cuda:0')結果は、元のモデルと最適化されたモデルの両方が、テストデータ example_input に対してクラス 834 を予測することを示しています。
ステップ 5:最適化されたモデルのロードと実行
検証後、モデルをデプロイする必要があります。ブレードは、統合用に Python と C++ の SDK を提供しています。C++ SDK の使用方法については、「SDK を使用して TensorFlow モデルを推論用にデプロイする」をご参照ください。次のセクションでは、Python SDK を使用してモデルをデプロイする方法について説明します。
オプション: 試用期間中に、認証失敗によるプログラムの予期せぬ終了を防ぐために、次の環境変数設定を追加します:
export BLADE_AUTH_USE_COUNTING=1PAI-Blade を使用するための認証を取得します。
export BLADE_REGION=<region> export BLADE_TOKEN=<token>ビジネス要件に基づいて、次のパラメーターを構成します:
<region>:PAI-Blade を使用するリージョン。PAI-Blade ユーザーの DingTalk グループに参加して、PAI-Blade を使用できるリージョンを取得できます。DingTalk グループの QR コードについては、「PAI-Blade のインストール」をご参照ください。
<token>:PAI-Blade の使用に必要な認証トークン。PAI-Blade ユーザーの DingTalk グループに参加して、認証トークンを取得できます。DingTalk グループの QR コードについては、「PAI-Blade のインストール」をご参照ください。
-
最適化されたモデルをロードして実行します。
import blade.runtime.torchの行を追加する以外に、ブレードを統合するために追加のコードを記述する必要はありません。元の推論コードに変更は不要です。import torch import blade.runtime.torch # <your_optimized_model_path> を最適化されたモデルのパスに置き換えます。 opt_model_dir = <your_optimized_model_path> # <your_infer_data> を推論用のデータに置き換えます。 infer_data = <your_infer_data> model = torch.jit.load(opt_model_dir) output = model(infer_data)
付録:複数の動的範囲の設定
動的シェイプの上限と下限の間の範囲が大きすぎると、モデルの加速が顕著でない場合があります。大きな範囲を複数の小さな範囲に分割して、より良い加速を実現できます。例えば、次の動的シェイプを設定します。
shapes1 = {
"min": [[1, 3, 224, 224]],
"max": [[5, 3, 224, 224]],
"opts": [
[[5, 3, 224, 224]],
]
}
shapes2 = {
"min": [[5, 3, 224, 224]],
"max": [[10, 3, 224, 224]],
"opts": [
[[8, 3, 224, 224]],
]
}
shapes = [shapes1, shapes2]
この shapes 構成を、前述の最適化構成に使用できます。詳細については、「ステップ 2:最適化のための構成の設定」をご参照ください。