TensorRT 5 と NVIDIA T4 GPU を使用して大規模な TensorFlow 推論を実行する

このチュートリアルでは、NVIDIA TensorRT 5 および T4 GPU で大規模に推論を実行する方法を説明します。NVIDIA TensorRT™ は、高性能のディープ ラーニングによる推論を実現するプラットフォームです。このプラットフォームに組み込まれた、ディープ ラーニングによる推論向けのオプティマイザーとランタイムが、ディープ ラーニングを使用した推論アプリケーションで低レイテンシと高スループットを実現します。

このチュートリアルでは、GPU 使用率ベースの自動スケーリング グループを使用して推論を実行できるよう、マルチゾーン クラスタを設定します。

概要

このチュートリアルでは、次の項目について説明します。

  • 開発環境に適したスケーラブルな機械学習推論システムを Google Cloud に実装するためのリファレンス アーキテクチャ。インフラストラクチャとセキュリティのニーズはさまざまに異なるため、このチュートリアルで説明している構成はニーズに応じて調整できるようになっています。
  • このチュートリアルで TensorFlow モデルやその他の必要なコンポーネントをインストールするために使用するスクリプトが格納されている GitHub リポジトリ。
  • TensorRT を使用して TensorFlow モデルを量子化する方法、スクリプトをデプロイする方法、リファレンス アーキテクチャをデプロイする方法。
  • Cloud Load Balancing の構成手順。

このチュートリアルを完了すると、トレーニング済みの量子化されたモデルが Cloud Storage に格納されます。また、それぞれ異なるリージョン内でクラスタ化された 2 つの Compute Engine インスタンス グループが作成され、これらのインスタンス グループの前面で Cloud Load Balancing がウェブ トラフィックを負荷分散します。次の図に、このアーキテクチャを示します。

このチュートリアルで使用するアーキテクチャ

目標

  • 事前にトレーニングされたグラフを出発点として使用する。
  • TensorRT を使用してモデルを最適化し、さまざまな最適化ごとにモデルがどれだけ高速化されるかを確認する。
  • モデルを仕上げた後、TensorFlow、TensorFlow Serving、TensorRT 5 がプリインストールされた Compute Engine ディープ ラーニング VM をベースにクラスタを作成する。

料金

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

  • Compute Engine
  • Persistent Disk
  • Cloud Storage
  • ネットワーキング
  • NVIDIA T4 GPU

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。新しい Google Cloud ユーザーは無料トライアルをご利用いただけます。

始める前に

  1. Cloud Console で、[VM インスタンス] ページに移動します。

    プロジェクト セレクタのページに移動

  2. Cloud プロジェクトを選択または作成します。

  3. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Compute Engine and Cloud Logging API を有効にします。

    API を有効にする

  5. VM を作成するのに十分な GPU 割り当てがあることを確認します。

このチュートリアルを終了した後、作成したリソースを削除すると、それ以上の請求は発生しません。詳しくは、クリーンアップをご覧ください。

環境の準備

このセクションでは、チュートリアル全体で使用される値(リージョンやゾーンなど)のデフォルトの設定を行います。このチュートリアルでは、デフォルト リージョンとして us-central1 を使用し、デフォルト ゾーンとして us-central1-b を使用します。

また、Cloud Shell を再度開いて、設定を再初期化する必要がある場合に、変数を自動的に読み込めるように、すべての設定を含むファイルも作成します。

  1. Cloud Shell を開きます。

    Cloud Shell を開く

  2. デフォルトのリージョンとゾーンを設定します。

    gcloud compute project-info add-metadata \
        --metadata google-compute-default-region=us-central1,google-compute-default-zone=us-central1-a
    
  3. シェルを再初期化します。

    gcloud init --console-only
    
  4. 最初の 3 つの質問には 1 を押し、最後の質問にはプロジェクト ID を入力します。

TensorRT を使用してモデルを最適化する

  1. Cloud Shell で、モデルを準備するために使用できるインスタンスを作成します。

    export IMAGE_FAMILY="tf-latest-cu100"
    export INSTANCE_NAME="model-prep"
    
    gcloud compute instances create $INSTANCE_NAME \
        --image-family=$IMAGE_FAMILY \
        --machine-type=n1-standard-8 \
        --image-project=deeplearning-platform-release \
        --maintenance-policy=TERMINATE \
        --accelerator="type=nvidia-tesla-t4,count=1" \
        --metadata="install-nvidia-driver=True"
    

    TensorRT のさまざまな最適化モードを比較するのにも、単一の GPU をどれだけ高速化できるかを把握するのにも、1 つの GPU があれば十分です。

  2. VM インスタンスを作成したら、ssh を使用して VM に接続します。

  3. このインスタンス内で、TensorRT の最適化をテストするための resnetv2 モデルを公式の TensorFlow リポジトリからダウンロードします。

    wget -q http://download.tensorflow.org/models/official/resnetv2_imagenet_frozen_graph.pb
    

TensorRT によって推論を高速化できますが、さらに高速化するにはモデルを量子化します。線形モデル量子化では、重みと活性化が浮動小数点数から整数に変換されます。たとえば、モデルの初期の重みが FP32(浮動小数点型 32 ビット)の場合、精度を下げることによって INT8 を使用できます。ただし、量子化には犠牲が伴います。ストレージ表現を縮小すると、ごくわずかでもモデルの精度低下につながる可能性があります。ただし、FP32 から FP16 に変換する場合は、ほとんど影響はありません。

速度(重みの精度)とモデルの精度の間で適切な妥協点を見つけるにはどうすればよいでしょうか。そのためのコードは存在します。これにより、速度やその他の指標に照らして精度を測定できます。このコードでのテスト対象は画像認識モデルに限られていますが、このコードに基づくカスタムテストを実装するのは難しいことではありません。

  1. Cloud Shell で、カスタムテストをダウンロードして実装します。

    git clone https://github.com/tensorflow/models.git
    cd models
    git checkout f0e10716160cd048618ccdd4b6e18336223a172f
    touch research/__init__.py
    touch research/tensorrt/__init__.py
    cp research/tensorrt/labellist.json .
    cp research/tensorrt/image.jpg .
    
  2. テストを実行できるよう準備します。

    python -m research.tensorrt.tensorrt \
        --frozen_graph=$HOME/resnetv2_imagenet_frozen_graph.pb \
        --image_file=$HOME/models/image.jpg \
        --native --fp32 --fp16 --int8 \
        --output_dir=$HOME
    

    テストを実行するには、フリーズされたグラフ(先ほどダウンロードした resnetv2 モデル)と、テスト対象のさまざまな量子化モードに応じた引数が必要です。このコマンドは完了するまでに時間がかかります。

    コマンドの実行が完了すると、グラフの別のバージョンに対する推論結果の比較が出力されます。

    グラフの異なるバージョンに対する推論結果を比較する

    FP32 による結果と FP16 による結果は同じで、この 2 つの間で精度に変わりはありません。つまり、TensorRT で問題がなければ、すぐに FP16 を使い始められるということです。一方、INT8 による結果では、精度がわずかに下がっています。

  3. 精度の数値を表示します。

    cat $HOME/log.txt
    

    このコマンドでは、次の出力が表示されます。

    大規模な推論のログ

TensorRT 5 に示される次の結果は、すべてネイティブと比較されています。

  • FP32 の場合、スループットは約 34% 向上しています(319.1 fps から 428.2 fps に高速化)。
  • FP16 の場合、スループットは約 207% 向上しています(319.1 fps から 979.6 fps に高速化)。
  • INT8 の場合、スループットは約 376% 向上しています(319.1 fps から 1519.5 fps に高速化)。

以上の結果から、次のことがわかります。

  • ネイティブから TensorRT への移行は、不確実性を伴います。ただし、多少の犠牲を払ってもかまわないのであれば、直接 FP16 に移行できます。
  • INT8 は非常に高速ですが、不確実性はさらに大きくなります。

次のセクションでは INT8 モデルを使用します。

カスタムモデルを TensorRT に変換する

モデルを TensorRT グラフに変換するには、SavedModel が必要です。

  1. Cloud Shell で、事前にトレーニングされた次の SavedModel をダウンロードします。

    wget http://download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NCHW.tar.gz
    tar -xzvf resnet_v2_fp32_savedmodel_NCHW.tar.gz
    
  2. フリーズされたモデルを TensorRT グラフに変換する Python スクリプトを作成します。

    cat <<EOF > convert_to_rt.py
    import tensorflow.contrib.tensorrt as trt
    import argparse
    
    parser = argparse.ArgumentParser(description="Converts TF SavedModel to the TensorRT enabled graph.")
    
    parser.add_argument("--input_model_dir", required=True)
    parser.add_argument("--output_model_dir", required=True)
    parser.add_argument("--batch_size", type=int, required=True)
    parser.add_argument("--precision_mode", choices=["FP32", "FP16", "INT8"], required=True)
    
    args = parser.parse_args()
    
    trt.create_inference_graph(
        None, None, max_batch_size=args.batch_size,
        input_saved_model_dir=args.input_model_dir,
        output_saved_model_dir=args.output_model_dir,
        precision_mode=args.precision_mode)
    EOF
    
  3. モデルを TensorRT グラフに変換します。

    python ./convert_to_rt.py \
        --input_model_dir=$HOME/resnet_v2_fp32_savedmodel_NCHW/1538687196 \
        --output_model_dir=$HOME/resnet_v2_int8_NCHW/00001 \
        --batch_size=128 \
        --precision_mode="INT8"
    

    これで、INT8 モデルが $HOME/resnet_v2_int8_NCHW/00001 フォルダに格納されました。

  4. すべてが正常に機能することを確認するために、推論を実行します。

    tensorflow_model_server --model_base_path=$HOME/resnet_v2_int8_NCHW/ --rest_api_port=8888
    
  5. 推論が機能していることを確認するには、次のサンプル入力を送信します。

    curl -X POST localhost:8888/v1/models/default:predict -d '{"instances": [[[[1,1,1]]]]}'
    
  6. この curl コマンドによる結果が表示されたら、Ctrl+C キーを押して推論の実行を終了します。

  7. 最適化されたモデルをクラスタから使用するには、モデルを Cloud Storage にアップロードします。[GCS_PATH] は、実際の Cloud Storage バケット名に置き換えます。

    tar -zcvf model.tar.gz ./resnet_v2_int8_NCHW/
    export GCS_PATH=[GCS_PATH]
    gsutil cp model.tar.gz $GCS_PATH
    

    次回、このモデルを使用するときには、このプロセス全体を繰り返す必要はありません。代わりに、Cloud Storage バケットに格納されている INT8 のフリーズされたグラフを使用できます。

    gs://solutions-public-assets/tensorrt-t4-gpu/model.tar.gz
    

クラスタを設定する

Cloud Storage 内にモデルが格納された状態になったので、クラスタの作成を開始します。最初のステップは、VM テンプレートを作成することです。クラスタは VM テンプレートを使用して新しいインスタンスを作成します。

  1. Cloud Shell で、クラスタの設定に必要なコードをダウンロードします。

    git clone https://github.com/GoogleCloudPlatform/tensorflow-inference-tensorrt5-t4-gpu.git
    
  2. VM テンプレートを作成します。[PROJECT_NAME] は、実際のプロジェクト名に置き換えます。

    export PROJECT_NAME=[PROJECT_NAME]
    export INSTANCE_TEMPLATE_NAME="tf-inference-template"
    export IMAGE_FAMILY="tf-latest-cu100"
    
    gcloud beta compute --project=$PROJECT_NAME instance-templates create $INSTANCE_TEMPLATE_NAME \
        --machine-type=n1-standard-16 \
        --maintenance-policy=TERMINATE \
        --accelerator=type=nvidia-tesla-t4,count=4 \
        --min-cpu-platform=Intel\ Skylake \
        --tags=http-server,https-server \
        --image-family=$IMAGE_FAMILY \
        --image-project=deeplearning-platform-release \
        --boot-disk-size=100GB \
        --boot-disk-type=pd-ssd \
        --boot-disk-device-name=$INSTANCE_TEMPLATE_NAME \
        --metadata startup-script-url=gs://solutions-public-assets/tensorrt-t4-gpu/start_agent_and_inf_server.sh
    

    metadata パラメータで、起動スクリプトを指定します。この起動スクリプトが、VM テンプレートによって作成されるすべてのインスタンスにインストールされます。起動スクリプトは、VM インスタンスの起動時に次の処理を行います。

    • NVIDIA ドライバをインストールします。
    • GPU の使用状況をモニタリングするモニタリング エージェントをインストールします。
    • モデルをダウンロードします。
    • 推論サービスを開始します。

    テンプレートが使用可能になったら、マネージド インスタンス グループを作成できます。このグループはスケーリング グループではなく、ヘルスチェックも行われません。作成するグループでは、特定の複数のゾーン内における 2 つのインスタンスの稼働のみが保証されます。

  3. マネージド インスタンス グループを作成します。

    gcloud compute instance-groups managed create $INSTANCE_GROUP_NAME \
        --template $INSTANCE_TEMPLATE_NAME \
        --base-instance-name deeplearning-instances \
        --size 2 \
        --zones us-central1-a,us-central1-b
    

    INSTANCE_TEMPLATE_NAME の値は、前の手順で設定したインスタンスの名前です。GPU の可用性(すべての GPU がすべてのゾーンで利用可能なわけではありません)と割り当てに基づいてゾーンを選んでください。

    グループの作成には時間がかかります。

  4. 次のコマンドを実行して進行状況を確認します。

    gcloud compute instance-groups managed list-instances $INSTANCE_GROUP_NAME --region us-central1
    

    出力は次のようになります。

    グループの作成中

    作成が完了すると、次のような出力が表示されます。

    グループの作成後

  5. Cloud Console で [モニタリング] ページを開きます。

    モニタリング ページに移動

  6. 正しいプロジェクト ワークスペースを使用していることを確認します。ワークスペース名は左上隅に示されます。このページにアクセスするのが初めての場合は、新しいワークスペースを作成する必要があります。

  7. [Metrics Explorer] ページで、リソースタイプGCE VM Instance を選択し、指標custom/gpu_utilization を選択します。

    [Metrics Explorer] ページ

    データが取り込まれると、次のように表示されます。

    使用率ゼロを示す指標グラフ

  8. Cloud Shell で、グループに対して自動スケーリングを有効にします。

    gcloud compute instance-groups managed set-autoscaling $INSTANCE_GROUP_NAME \
        --custom-metric-utilization metric=custom.googleapis.com/gpu_utilization,utilization-target-type=GAUGE,utilization-target=85 \
        --max-num-replicas 4 \
        --cool-down-period 360 \
        --region us-central1
    

    ここで重要となる要素は使用率のパス custom.googleapis.com/gpu_utilization です。これは、指標のフルパスです。また、ターゲット レベルを 85 に指定したため、GPU 使用率が 85% に達するたびに、新しいインスタンスがグループ内に作成される点にも注意してください。

自動スケーリングをテストする

前のセクションで設定した自動スケーリングをテストするには、次の操作を行う必要があります。

  • SSH を使用して、ディープ ラーニング用 GPU インスタンスのいずれかに接続します。
  • すべての GPU に 100% の負荷をかけます。
  • もう 1 つのインスタンスを作成して、自動スケーリング グループがスケールアップする様子を観察します。

  1. ssh を使用してインスタンスに接続します。
  2. 600 秒間 GPU に 100% の負荷をかけます。

    git clone https://github.com/GoogleCloudPlatform/tensorflow-inference-tensorrt5-t4-gpu.git
    cd tensorflow-inference-tensorrt5-t4-gpu
    git submodule update --init --recursive
    cd third_party/gpu-burn
    make
    ./gpu_burn 600 > /dev/null &
    

    Cloud Console の [Metrics Explorer] ページで、アクティビティを確認します。

    アクティビティの急増を示す [Metrics Explorer] ページ

  3. 2 つ目のインスタンスを作成します。

  4. GCE Compute のページに移動し、[インスタンス グループ] の [モニタリング] タブでアクティビティを観察します。

    モニタリング中のアクティビティ急増

    この時点で、オートスケーラーが可能な限り多くのインスタンスを起動させて負荷を軽減しようとしています(ただし、対処しきれていません)。それが、現在の状況です。

    多数のインスタンスの起動

  5. インスタンスの起動を停止させて、アクティビティがスケールダウンする様子を観察します。

これまでの手順で、次のものが揃いました。

  • TensorRT 5(INT8)で最適化されたトレーニング済みモデル
  • ディープ ラーニング用インスタンスからなるマネージド グループ
  • GPU 使用率に基づく自動スケーリング

ロードバランサを作成する

最後のステップは、インスタンスの前面に配置するロードバランサを作成することです。

  1. Cloud Shell で、バックエンドの特定のホストがトラフィックを処理できるかどうかを判別するヘルスチェックを作成します。

    gcloud compute health-checks create http $HEALTH_CHECK_NAME \
        --request-path /v1/models/default \
        --port 8888
    
  2. ポート 8888 を介して提供される推論サービスに対し、ロードバランサがポート 80 を介して推論リクエストを転送できるよう、インスタンス グループの名前付きポートを構成します。

    gcloud compute instance-groups set-named-ports $INSTANCE_GROUP_NAME \
        --named-ports http:8888 \
        --region us-central1
    
  3. バックエンド サービスを作成します。

    export WEB_BACKED_SERVICE_NAME="tensorflow-backend"
    
    gcloud compute backend-services create $WEB_BACKED_SERVICE_NAME \
        --protocol HTTP \
        --health-checks $HEALTH_CHECK_NAME \
        --global
    

    実質的に、バックエンド サービスはヘルスチェックを行うインスタンス グループです。

  4. 新しく作成したバックエンド サービスにインスタンス グループを追加します。

    gcloud compute backend-services add-backend $WEB_BACKED_SERVICE_NAME \
        --balancing-mode UTILIZATION \
        --max-utilization 0.8 \
        --capacity-scaler 1 \
        --instance-group $INSTANCE_GROUP_NAME \
        --instance-group-region us-central1 \
        --global
    
  5. ロードバランサに対し、バックエンド サービスへの転送に使用する URL を指示します。

    export WEB_MAP_NAME="map-all"
    
    gcloud compute url-maps create $WEB_MAP_NAME \
        --default-service $WEB_BACKED_SERVICE_NAME
    
  6. ロードバランサを作成します。

    export LB_NAME="tf-lb"
    
    gcloud compute target-http-proxies create $LB_NAME \
        --url-map $WEB_MAP_NAME
    
  7. ロードバランサの外部 IP アドレスを作成します。

    export IP4_NAME="lb-ip4"
    
    gcloud compute addresses create $IP4_NAME \
        --ip-version=IPV4 \
        --global
    
  8. 作成した IP アドレスが割り当てられたことを確認します。

    gcloud compute addresses list
    
  9. Google Cloud がパブリック IP からのすべてのリクエストをロードバランサに転送するために使用する転送ルールを確認します。

    export IP=$(gcloud compute addresses list | grep ${IP4_NAME} | awk '{print $2}')
    export FORWARDING_RULE="lb-fwd-rule"
    
    gcloud compute forwarding-rules create $FORWARDING_RULE \
        --address $IP \
        --global \
        --target-http-proxy $LB_NAME \
        --ports 80
    

    グローバル転送ルールを作成した後、構成が伝播されるまでに数分かかることがあります。

  10. 外部インスタンスに接続するには、プロジェクトに対してファイアウォールを有効にします。

    gcloud compute firewall-rules create www-firewall-80 \
        --target-tags http-server --allow tcp:80
    
    gcloud compute firewall-rules create www-firewall-8888 \
        --target-tags http-server --allow tcp:8888
    
  11. 画像をサーバーに送信できる形式に変換します。

    cat <<EOF > load_and_convert_image.py
    from PIL import Image
    import numpy as np
    import json
    import codecs
    
    img = Image.open("image.jpg").resize((240, 240))
    img_array=np.array(img)
    result = {
        "instances":[img_array.tolist()]
    }
    file_path="/tmp/out.json"
    print(json.dump(result, codecs.open(file_path, 'w', encoding='utf-8'), separators=(',', ':'), sort_keys=True, indent=4))
    EOF
    
  12. 推論を実行します。

    wget https://pixnio.com/free-images/2017/10/31/2017-10-31-10-43-58-1032x825.jpg -O image.jpg
    python load_and_convert_image.py
    curl -X POST $IP/v1/models/default:predict -d @/tmp/out.json
    

    推論が正しく機能していれば、結果は次のようになります。

    推論が正常に実行された結果

クリーンアップ

このチュートリアルが終了したら、Google Cloud で作成したリソースをクリーンアップして、今後割り当ての消費や課金が発生しないようにします。次のセクションで、リソースを削除または無効にする方法を説明します。

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ