GCP で構築する高性能かつスケーラブルなオンライン予測システム

Yaboo Oyabu
google-cloud-jp
Published in
38 min readJul 31, 2019

--

Yaboo Oyabu, Machine Learning Specialist, Google Cloud

Kazuhiro Yamasaki, Deep Learning Solution Architect, NVIDIA

概要

このチュートリアルでは GCP 上で NVIDIA Tesla T4 と TensorRT Inference Server (以降 TRTIS) を用いて高性能なオンライン予測システムを構築する手順と、そのパフォーマンス計測・チューニング方法を説明します。このチュートリアルを完了すると、TensorRT に最適化された機械学習モデルが Cloud Storage に格納されます。また、 オンライン予測と負荷テストを実施するための GKE クラスタが作成されます。

本記事は Google Cloud Next 2019 Tokyo におけるセッション『GCP で構築する高性能かつスケーラブルなオンライン予測システム』の発表内容を再現することも目的の一つとしています。

用語の整理

  • NVIDIA Tesla T4
    NVIDIA Turing アーキテクチャを基盤とする、主にディープラーニングの推論処理向けに開発された GPU 、高いエネルギー効率が特徴です。
  • Tensor コア
    NVIDIA Volta アーキテクチャ以降の GPU に搭載されている、ディープラーニングで必要とされる行列計算を高速化するための計算ユニットです。Volta アーキテクチャでは FP16 の演算が、Turing アーキテクチャでは FP16 / INT8 / INT4 の演算をサポートします。
  • TensorRT
    TensorRT は、ディープラーニングの推論向け、最適化エンジン兼ランタイムエンジンです。学習済みのモデルに対して、layer fusionや重みの量子化などの処理を適用し、高速に演算できるよう最適化します。同時に、フレームワーク非依存な形式にも変換することで、実行環境の依存関係を最小化します。
  • TF-TRT
    TensorRT は TensorFlow の機能の一部としても組み込まれており、TF-TRT と呼ばれています。TensorRT が直接適用可能な演算は限定されているため、複雑なモデルは別途追加の実装が必要となります。TF-TRT を利用することで、TensorRT 非対応の演算は TensorFlow の演算に自動的にフォールバックし、取り扱いが容易になります。
  • TensorRT Inference Server (TRTIS)
    学習済みモデルをデプロイするための API サーバのオープンソース実装です。モデルファイルと設定ファイルを、規定のディレクトリ構造に従って配置するだけで、HTTP ないし gRPC の API サーバとして動作させることができます。
  • Google Kubernetes Engine (GKE)
    Google Kubernetes Engineは、コンテナ化されたアプリケーションをデプロイするための管理された、運用準備が整った環境です。なお、 GKE で GPU を利用するときの注意点はこちらです。
  • Locust
    Locust は分散型の負荷テストツールです。負荷テストを通じてサービスにおける同時接続可能ユーザー数や許容トラヒック量を把握することができます。このツールを用いてオンライン予測システムの性能を計測します。

目標

  • TensorFlow で作成された学習済みモデルを出発点として使用する。
  • 学習済みモデルの最適化・量子化を TF-TRT を用いて行う。
  • モデルのオンライン予測用クラスタを TRTIS と GKE を用いて構築する。
  • 負荷テスト用のクラスタを Locust と GKE を用いて作成する。
  • オンライン予測用クラスタの性能を計測・チューニングする。

学習済みモデルの最適化

TensorFlow の SavedModel 形式で保存されたモデルを、 TF-TRT を用いて量子化・最適化する手順について説明します。なお、このセクションで構築する作業環境は後半のセクションでも利用します。

作業環境の構築

GCP の Compute Engine 上に Deep Learning VM Image を展開することで作業環境を構築します。次のコマンドにより Compute Engine のインスタンス作成時に、 NVIDIA Tesla T4 の接続と、 TensorRT 5.1.5 互換の NVIDIA GPU ドライバのインストールを実行できます。

$ export INSTANCE_NAME=YOUR-INSTANCE-NAME
$ export GCP_LOGIN_NAME=YOUR-LOGIN-ADDRESS
$ export IMAGE_FAMILY="common-cu101"
$ gcloud config set compute/zone us-west1-b
$ gcloud compute instances create $INSTANCE_NAME \
--scopes cloud-platform \
--image-family $IMAGE_FAMILY \
--image-project deeplearning-platform-release \
--machine-type n1-standard-8 \
--min-cpu-platform="Intel Skylake" \
--accelerator=type=nvidia-tesla-t4,count=1 \
--boot-disk-size=200GB \
--maintenance-policy=TERMINATE \
--metadata="proxy-user-mail=${GCP_LOGIN_NAME},install-nvidia-driver=True"

なお --metadata="proxy-user-mail=${GCP_LOGIN_NAME}" を追加することで、下図のように AI Platform Notebooks のインタフェースからワンクリックで Jupyter Lab を起動できるようになります。

TF-TRT を用いた学習済みモデルの量子化・最適化

このサブセクションでは学習済みモデルの量子化・最適化の手順を先ほど構築した Compute Engine 上で実施していきます。 AI プラットフォームのノートブックインスタンスの画面から、 「JUPYTERLAB を開く」ボタンで Jupyter Lab を起動します。Jupyter Lab が起動した後は、 ターミナルから次のコマンドを入力して今回のチュートリアルに必要なファイルを取得します。

$ git clone https://github.com/GoogleCloudPlatform/gcp-getting-started-lab-jp.git

レポジトリのクローンが完了したら、server ディレクトリに移動します。

$ cd gcp-getting-started-lab-jp/machine_learning/ml_infrastructure/inference-server-performance/server

次のコマンドを入力し、学習済みの Resnet50 モデルをローカルディレクトリにコピーします。

$ gsutil cp -R gs://sandbox-kathryn-resnet/export/1564508810/* models/resnet/original/00001

次のコマンドにより、 NVIDIA GPU Cloud で提供されているコンテナイメージをベースに TF-TRT で変換プログラムを実行する準備をします。

$ docker build ./ -t trt-optimizer

次のコマンドにより、作成したコンテナイメージの ID を確認します。この ID は TF-TRT の変換プログラムを実行するときに使います。

$ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
trt-optimizer latest 3fa16b1b864c About a minute ago 6.96GB
nvcr.io/nvidia/tensorflow 19.05-py3 01c8c4b0d7ff 2 months ago 6.96GB
gcr.io/inverting-proxy/agent <none> 81311f835221 2 months ago 856MB

変換プログラムを実行する準備が整いました。下記コマンドを実行することで TF-TRT で最適化・量子化したモデルを作成できます。環境に応じて イメージ ID を置き換えて実行することに注意しましょう。

# FP32で量子化したモデルを出力
$ nvidia-docker run --rm \
-v `pwd`/models/:/workspace/models 3fa16b1b864c \
--input-model-dir='models/resnet/original/00001' \
--output-dir='models/resnet' \
--precision-mode='FP32' \
--batch-size=64
# FP16で量子化したモデルを出力
$ nvidia-docker run --rm \
-v `pwd`/models/:/workspace/models 3fa16b1b864c \
--input-model-dir='models/resnet/original/00001' \
--output-dir='models/resnet' \
--precision-mode='FP16' \
--batch-size=64
# INT8で量子化したモデルを出力
$ nvidia-docker run --rm \
-v `pwd`/models/:/workspace/models 3fa16b1b864c \
--input-model-dir='models/resnet/original/00001' \
--output-dir='models/resnet' \
--precision-mode='INT8' \
--batch-size=64 \
--calib-image-dir='gs://sandbox-kathryn-data/imagenet' \
--calibration-epochs=10

INT8 量子化では Calibration という工程が含まれており、この工程には 複数の画像データが必要です。上記の例では IMAGENET のデータをこの工程に利用しており、このデータのダウンロード方法については公式サイト等を参考にしてください。

上記コマンドの実行が一通り終わると、 server/models ディレクトリ配下に、下記の通り、最適化・量子化を適用したモデルが出力されます。

models
└── resnet
├── FP16
│ └── 00001
│ ├── saved_model.pb
│ └── variables
├── FP32
│ └── 00001
│ ├── saved_model.pb
│ └── variables
├── INT8
│ └── 00001
│ ├── saved_model.pb
│ └── variables
└── original
└── 00001
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index

このサブセクションではプログラムの解説は行っていませんが、興味のある方は Dockerfileserver/scripts/tensorrt-optimization.py を読むことで具体的な処理内容を知ることができます。

オンライン予測用クラスタの構築手順

設定ファイルとモデルの準備

学習済みモデルを TRTIS にデプロイするには、 (1) 設定ファイルと (2) TF-TRT で最適化されたモデルが必要です。

  1. 設定ファイル (config.pbtxt)
  2. 学習済みモデル

設定ファイルでは次の内容を記述することができます。

  • 最大バッチサイズ
  • 入出力の対象とするテンソルの名前
  • 同一モデルの最大コピー数
  • 各モデルへの割り当て GPU
  • キューでの最大待機時間 (Dynamic batching 適用時)

この設定により、以下が規定されます。

  • /api/infer/original というエンドポイントを定義
  • TensorFlow の SavedModel 形式のファイルを使用
  • 最大バッチサイズは 64
  • 入出力テンソルとして、それぞれ “input” と “probabilities” を使う
  • SavedModel 形式の場合 saved_model_cli コマンドで表示される inputs / outputs が対応
  • モデルは “model” という名前で格納されているとみなす
  • SavedModel の場合ディレクトリに対応
  • GPU 上に最大 1 インスタンスをロード
  • リクエストキューの最大待ち時間は 20000 マイクロ秒

ディレクトリ構成は以下のようになります。

original/
├── 1
│ └── model
│ ├── saved_model.pb
│ └── variables
│ ├── variables.data-00000-of-00001
│ └── variables.index
├── config.pbtxt
└── imagenet1k_labels.txt

エンドポイントとして定義したいモデルごとにディレクトリを作成し、その直下に config.pbtxt とモデルファイルを配置します。NVIDIA のサイトからダウンロードできる imagenet1k_labels.txt は、分類タスクの場合などに、レスポンスにクラス名を含めたい場合に配置します。各行にクラス名を列挙したテキストファイルです。config.pbtxt の output.label_filename に設定したファイルが使用されます。なお、モデルのディレクトリを original 直下ではなく、1/ という数字のディレクトリ配下に置くことで、モデルのバージョン管理も可能になっています。次のコマンドにより、モデルと設定ファイルをセットにして、GCS にアップロードします。

$ cd ~/gcp-getting-started-lab-jp/machine_learning/ml_infrastructure/inference-server-performance/server$ mkdir -p original/1/model/
$ cp -r models/resnet/original/00001/* original/1/model/
$ vim original/config.pbtxt
$ export BUCKET_NAME=”YOUR-BUCKET-NAME”
$ gsutil mb gs://${BUCKET_NAME}
$ gsutil cp -R original/ gs://${BUCKET_NAME}/resnet/

GPU ノードの準備と TRTIS の起動

引き続き GPU ノードを準備し、TRTIS を起動します。まず GKE 上にクラスタと GPU ノードのノードプールを作成します。

$ gcloud auth login
$ gcloud config set compute/zone asia-northeast1-a
$ gcloud container clusters create tensorrt-testbed \
--zone asia-northeast1-a \
--num-nodes 20 \
--cluster-version=1.12.8-gke.10
$ gcloud container node-pools create t4-pool \
--num-nodes=1 \
--machine-type=n1-standard-8 \
--cluster=tensorrt-testbed \
--accelerator type=nvidia-tesla-t4,count=1
$ gcloud container clusters get-credentials tensorrt-testbed

一連のコマンドにより、tensorrt-testbed というクラスタ上に gpu-pool という名前でノードプールが作成されます。ノードプール内には、Tesla T4 が 1 枚付与された n1-standard-8 のマシンが 1 ノード起動します。

NVIDIA GPU のドライバをロードするための daemonSet を有効にします。

$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/stable/nvidia-driver-installer/cos/daemonset-preloaded.yaml

ノードが立ち上がったら、TRTIS を動かします。TRTIS を動かすための Service および Deployment の定義は次の通りです。${BUCKET_NAME}を書き換えることを忘れないように注意して下さい。

次のコマンドを入力して TRTIS を起動します。

$ kubectl create -f trtis_service.yaml
$ kubectl create -f trtis_deploy.yaml

次のコマンドを実行すると TRTIS の Cluster IP アドレスがわかります。この Cluster IP アドレスは Prometheus や Locust の設定に利用します。

$ kubectl get svc inference-server -o "jsonpath={.spec['clusterIP']}"

監視サービスの追加

ここまででオンライン予測用クラスタは構築できますが、実サービスではメトリクスの監視も重要です。TRTIS は Prometheus との連携できます。次の手順に従い Prometheus と Grafana によるサービス監視を有効にします。

監視サービス用のネームスペースを作成します。

$ kubectl create namespace monitoring

クラスタ内からアクセスするためのエンドポイントを作成します。

$ kubectl apply -f prometheus-service.yml -n monitoring

エンドポイントの Cluster IP アドレスを確認します。このアドレスは後述する Grafana の設定で利用します。

$ kubectl get svc prometheus-service -o "jsonpath={.spec['clusterIP']}:{.spec['ports'][0]['port']}" -n monitoring

ClusterRole を設定します。

$ kubectl create -f clusterRole.yml

Prometheus の設定を登録します。CLUSTER_IP_TRTIS の部分を先程の TRTIS の Cluster IP アドレスに置き換えることに注意します。

$ kubectl create -f prometheus-configmap.yml -n monitoring

次のコマンドを入力して、 Prometheus をデプロイします。

$ kubectl create -f prometheus-deployment.yml -n monitoring

次に Grafana にアクセスするためのエンドポイントを作成します。 Grafana は外部 IP でアクセスできるようにします。

$ kubectl create -f grafana-service.yml -n monitoring

次のコマンド入力して、 Grafana をデプロイします。

$ kubectl create -f grafana-deployment.yml -n monitoring

Grafana のエンドポイントにブラウザでアクセスしましょう。エンドポイントは下記コマンドにより確認することができます。

$ kubectl get svc grafana-service -o "jsonpath={.status['loadBalancer']['ingress'][0]['ip']}:{.spec['ports'][0]['port']}" -n monitoring

TRTIS の監視を行うためには、 Grafana にデータソースの設定をする必要があります。このデータソースには Prometheus のエンドポイントを設定します。データソースの設定の後は、監視したい項目を追加します。

TRTIS で取得できるメトリックはこちらに記載されています。GPU 使用率を知りたい場合には “nv_gpu_utilization” を監視、GPU メモリ使用量を知りたい場合は “nv_gpu_memory_used_bytes” を監視するようにします。

負荷テスト用クラスタの構築手順

このセクションではオープンソースの負荷試験ツール Locust と GKE を用いて負荷試験用クラスタを構築します。負荷試験用クラスタを構築するために (1) 負荷試験用のコンテナイメージを作成し、 (2) 負荷試験用のクラスタを立ち上げます。なお、動作確認は TensorRT 用にモデルを最適化するときに用いた AI Platform Notebooks のインスタンス上で行っています。 AI Platform Notebooks の Jupyter Lab 経由でコマンドラインを起動するか、インスタンスに直接ログインして以降のコマンドを実行することを推奨します。

負荷試験用のコンテナイメージを作成する

負荷試験用のコンテナイメージに TRTIS クライアントライブラリ、 負荷試験に用いるプログラムと画像ファイルをインストールする手順は以下の通りです。

まず、作業用ディレクトリに移動します。

$ cd ~/gcp-getting-started-lab-jp/machine_learning/ml_infrastructure/inference-server-performance/client

TRTIS クライアントのコンテナイメージを作成、 GCR に登録します。

$ git clone https://github.com/NVIDIA/tensorrt-inference-server.git
$ cd tensorrt-inference-server
$ git checkout r19.05
$ docker build -t tensorrtserver_client -f Dockerfile.client .
$ export PROJECT_NAME=’your-project-name’
$ gcloud auth configure-docker
$ docker tag tensorrtserver_client \
gcr.io/${PROJECT_NAME}/tensorrtserver_client
$ docker push gcr.io/${PROJECT_NAME}/tensorrtserver_client

上で作成したコンテナイメージをベースに Locust のライブラリをインストールしたコンテナイメージを作成し、 GCR に登録します。

$ cd ..
$ docker build -t locust_tester -f Dockerfile .
$ docker tag locust_tester gcr.io/${PROJECT_NAME}/locust_tester
$ docker push gcr.io/${PROJECT_NAME}/locust_tester

上記 Dockerfile の中身は以下の通りです。 Locust のインストールに加えて、ローカルディレクトリの locust と data をそれぞれコンテナイメージにコピーします。

なお client 配下のディレクトリ構造は次のようになっています。 locust 配下の trtis_grpc_client.py には TRTIS に gRPC 経由で予測リクエストを実行する手順が記述されています。また、 data 配下の画像ファイルは trtis_grpc_client.py が予測リクエストを発行するときに利用します。

client
├── locust
│ └── trtis_grpc_client.py
├── data
│ ├── 00001.jpg
│ ├── 00002.jpg
│ └── 00003.jpg
├── deployment_locust_master.yaml
├── deployment_locust_worker.yaml
├── Dockerfile
└── service_locust_master.yaml

負荷試験用のクラスタを構築する

このチュートリアルでは、負荷試験用ツールを用いて、 複数ノードで同時に予測リクエストを発行し、 TRTIS サーバの性能を計測します。これを Locust を用いて実現するためには、マスターとスレーブの設定が必要です。スレーブは Locust user (予測リクエストを発行する軽量のプロセス) を起動または終了する役割を持ちます。一方、マスターはスレーブに対して Locust user の起動命令を送信する役割を持ちます。また、マスターが提供する Web サービス経由でサーバ性能の推移をリアルタイムに確認することが出来ます。

マスターのエンドポイントを作成する

マスターの Web サービスを利用できるようにするために、マスターのエンドポイントを作成します。なお、このエンドポイントはマスターとスレーブ間の通信にも利用します。次のコマンドを実行することで、マスターのエンドポイントを作成します。

$ kubectl apply -f service_master.yaml

設定ファイルには、マスターのエンドポイントで利用するポートが定義されています。loc-master はマスターの Web サービスを利用するときに使われるポート、 loc-master-p1 と loc-master-p2 はマスターとスレーブ間の通信で使われるポートです。 Service type に LoadBalancer を指定することで、マスターが外部ネットワークから到達可能になります。

マスターのデプロイ

マスターをデプロイするために下記コマンドを実行します。

$ kubectl apply -f deployment_master.yaml

設定ファイルの 19 行目では、先ほど作成した負荷試験用コンテナイメージが指定されています。そして、 30–31 行目ではコンテナ起動時に実行するコマンドが記述されています。 19 行目の YOUR_PROJECT_NAME と 31 行目の CLUSTER_IP_TRTIS はみなさまの環境に合わせて修正しましょう。

スレーブのデプロイ

スレーブをデプロイするために下記コマンドを実行します。

$ kubectl apply -f deployment_slave.yaml

この設定ファイルの 19 行目では、先ほど作成した負荷試験用コンテナイメージが指定されています。そして、 20–21 行目ではコンテナ起動時に実行するコマンドが記述されています。 19 行目の YOUR_PROJECT_NAME と 21 行目の CLUSTER_IP_LOCUST_MASTER はみなさまの環境に合わせて修正しましょう。この設定を適用するためには下記コマンドを実行します。また、 8 行目で replicas が 3 に指定しているので、上記コマンドでスレーブ用の Pod が 3 つ起動します。

なお、次のコマンドでスレーブの Pod 数を増やすことができます。TRTIS サーバに対する負荷を高めたいが、 Pod の許容能力を超えているようなケースでは下記コマンドにより Pod 数を増やすか、 GKE のノード数を増やす設定が必要です。

$ kubectl scale deployment/locust-slave --replicas=10

Tesla T4 のパフォーマンスチューニング手順

チューニング手順

パフォーマンスチューニングには、さまざまなやり方がありますが、ここでは一例として以下の流れにしたがって性能の改善を試みます。

  1. 単体性能の確認と目標値の設定
  2. 最適化前のモデルで性能評価
  3. デフォルト設定で TensorRT を適用して改善効果確認
  4. FP16 適用の効果確認
  5. パラメータチューニング
  6. INT8 適用の効果確認

オンライン予測システムにおいて GPU の性能を引き出すことは、全体のパフォーマンスを向上させるために重要です。GPU はまとまった大量の演算を高速に処理できる半面、逐次処理するような演算には向かないことがあります。一方、オンライン予測システムには、無秩序に多数のリクエストが送信され、それを処理しなければならない、という制約があります。こうしたことから、リクエストを 1 つずつ処理するというのは、あまり効率的ではありません。そこで、以下のように処理することで、GPU を効率よく利用することを試みます。

  • 複数のリクエストを束ねてバッチとして処理
  • 同時に複数のバッチを処理
  • 一つの処理単位を大きくする
  • 量子化 (FP16 や INT8) の適用や専用演算器 (e.g., Tensor コア) の利用

最初の 2 つは GPU のコアを常時計算させるための改善です。それぞれ、一度の命令実行で処理する量を増やすことと、同時に処理する数を増やすことに相当します。これを実現するためには、リクエストをキューに蓄積し、一定時間ごとに複数あるスレッドへ処理を割り当てるなどの実装が必要となりますが、やや手間がかかります。TRTIS ではこれらの機能を提供しており、以下、TRTIS の機能を利用して、性能が改善するか確認します。

後者の 2 つは、各処理自体を効率化することに繋がります。いずれも、独自で実装するには細かい仕様を理解する必要があり、さほど簡単ではありません。TensorRT を適用すると、これらの改善を容易に適用することができます。後述のチューニングで、こうした最適化についても効果を確認します。

単体性能の確認と目標値の設定

まずチューニングに先立ち、サーバへモデルを組み込んでいない状態で、どの程度の性能が出るのかを確認します。これにより、サーバへ組み込んだあとの性能がおおよそ推測できるようになるため、チューニングの指針として有効です。

ここでは Running TensorFlow inference workloads at scale with TensorRT 5 and NVIDIA T4 GPUs で計測された数値を、単体の性能とみなします。上記の記事では、TensorRT を適用したモデルの性能を Tesla T4 上で計測しており、以下のような結果となっています。

  • TensorRT 未適用
    スループット (中央値) : 約 310 RPS
    遅延 (中央値) : 約 400 msec.
  • TF-TRT 適用 (FP32)
    スループット (中央値) : 約 440 RPS
    遅延 (中央値) : 約 290 msec.
  • TF-TRT 適用 (FP16)
    スループット (中央値) : 約 990 RPS
    遅延 (中央値) : 約 130 msec.
  • TF-TRT 適用 (INT8)
    スループット (中央値) : 約 1500 RPS
    遅延 (中央値) : 約 80 msec.

このモデルを複数同時に同一 GPU で動作させることもでき、その場合は更に性能向上する可能性はありますが、ここでは上記の数値から、チューニングの目標値として以下を設定します。

  • スループット: 約 1500 RPS
  • 遅延: 約 100 msec. 以下

TensorRT 適用前のモデルに対する性能

TensorRT を適用する前の TensorFlow モデルを、TRTIS へデプロイして性能を計測します。クラスタの構築手順でこのモデルはデプロイ済みですので、Locust を使って負荷をかけてみます。

立ち上げておいた locust-master の IP を確認し、Web ブラウザでアクセスすると、Locust のページが表示されます。

“Number of users to simulate” はシミュレートされるユーザ数で、同時接続数に関係します。”Hatch rate” は、1 秒間にどれだけユーザ数を増加させるかを指定します。”Hatch rate” を大きくすると、一気に負荷をかけることになります。それぞれ指定し ”Start swarming” すると、サーバに対して負荷がかかり始めます。

275 users まで負荷を上げると、遅延が増加し始めることが確認できます。

これ以上負荷をかけると目標としている遅延の 100 msec. を上回りはじめるため、現状の設定で TensorRT 適用前のモデルを用いると、以下の性能まで達成されることがわかります。

  • スループット: 約 260 RPS
  • 遅延: 約 90 msec. 以下

TF-TRT による改善確認

TensorRT を適用して、どの程度性能が改善されるかを確認します。「TensorRT 対応モデルの作成」の手順に従って、モデルを作成します。このとき、バッチサイズを 64 として TF-TRT を適用します。TRTIS にデプロイする設定とモデルのディレクトリ構造は、以下のようになります。

ディレクトリ構成は以下の通りです。

tftrt_fp32/
├── 1
│ └── model
│ ├── saved_model.pb
│ └── variables
├── config.pbtxt
└── imagenet1k_labels.txt

TF-TRT 適用後のモデルと設定ファイルを GCS にアップロードします。

$ mkdir -p tftrt_fp32/1/model/
$ cp -r models/resnet/FP32/00001/* tftrt_fp32/1/model/
$ vim tftrt_fp32/config.pbtxt
$ gsutil cp -R tftrt_fp32/ gs://${BUCKET_NAME}/resnet/

アップロード後に、TRTIS ではモデルの追加を検知してオートリロードが実行されますが、適切な計測のため、一度立ち上げ直します。

$ kubectl scale deployment/inference-server --replicas=0
$ kubectl scale deployment/inference-server --replicas=1

再起動後、改めて Locust で負荷をかけます。さきほどと同様に、locust-master の IP へ Web ブラウザでアクセスし、“Number of users to simulate”と ”Hatch rate” を指定して ”Start swarming” します。今回は 400 users まで負荷を上げると、遅延が増加し始めることが確認できます。

このことから、TF-TRT をデフォルト設定で適用したモデルを用いると、以下の性能まで達成されることがわかります。

  • スループット: 約 375 RPS
  • 遅延: 約 100 msec. 以下

FP16 適用の効果を確認する

続いて FP16 を有効にした条件での性能を確認します。バッチサイズは引き続き 64 を設定し、TF-TRT を適用したモデルを作成します。こちらの設定とモデルのディレクトリ構造は、以下のようになります。

次はTF-TRT の最適化 と FP16 量子化を適用したモデルのディレクトリ構成です。

tftrt_fp16
├── 1
│ └── model
│ ├── saved_model.pb
│ └── variables
├── config.pbtxt
└── imagenet1k_labels.txt

TF-TRT 適用後のモデルと設定ファイルを GCS にアップロードします。

$ mkdir -p tftrt_fp16/1/model/
$ cp -r models/resnet/FP16/00001/* tftrt_fp16/1/model/
$ vim tftrt_fp16/config.pbtxt
$ gsutil cp -R tftrt_fp16/ gs://${BUCKET_NAME}/resnet/

アップロード後に、TRTIS ではモデルの追加を検知してオートリロードが実行されますが、適切な計測のため、一度立ち上げ直します。

$ kubectl scale deployment/inference-server --replicas=0
$ kubectl scale deployment/inference-server --replicas=1

再起動後、改めて Locust で負荷をかけます。今回は “Number of users to simulate” を 1000 users に設定し、負荷をかけます。遅延は目標値を大きく下回っており、GPU 使用率も上がりきっていない様子がわかります。

そこで、ユーザ数を 1000 から 1100 に増加させてみます。すると顕著に遅延が増大し、性能が劣化します。このとき GPU 使用率を監視すると 45% 前後で、ボトルネックが GPU にないことがわかります。

TRTIS では前述の通りリクエストをキューに蓄積し、バッチにまとめて処理を GPU へ割り当てています。したがって、GPU の処理速度に対してリクエストが蓄積する速度が大きいと、遅延の増大に繋がります。一方で今回のケースは、GPU 自体の使用率が十分高くなっていないことから、処理の並列度を上げることで全体の性能を改善できそうだということもわかります。

パラメータチューニング

FP16 量子化したモデルに関する設定ファイルの項目のうち、instance_group という設定項目に着目します。先ほどは count に 1 を設定していましたが、この数字を変えることで同じモデルをいくつ並列に実行するか変更できます。

これにより、キューで滞留していたリクエストを並列に処理することができ、遅延の増加を防ぐことができるようになります。ここではさらに、dynamic_batching で指定されているバッチサイズも 64 から 8 に変更することで、各モデルの処理量を下げ、全体としてスループット向上と遅延低下が両立するようにします。

キューでの滞留時間は、TRTIS の性能指標 nv_inference_queue_duration_us で確認できます。この指標は累積値であることに注意してください。各時刻で処理したバッチサイズの平均は、Prometheus に対して下記のクエリを発行することで確かめることが出来ます。

delta(nv_inference_request_success[10s])/delta(nv_inference_exec_count[10s])

変更後の設定は以下のようになります。このとき、バッチサイズを 8 に変更したモデルも改めて作成しておきます。

ディレクトリ構成は以下の通りです。

tftrt_fp16_bs8_count4/
├── 1
│ └── model
│ ├── saved_model.pb
│ └── variables
├── config.pbtxt
└── imagenet1k_labels.txt

モデルと設定ファイルを GCS にアップロードします。

$ mkdir -p tftrt_fp16_bs8_count4/1/model/
$ cp -r models/resnet/FP16/00001/* tftrt_fp16_bs8_count4/1/model/
$ vim tftrt_fp16_bs8_count4/config.pbtxt
$ gsutil cp -R tftrt_fp16_bs8_count4/ gs://${BUCKET_NAME}/resnet/

TRTIS を立ち上げ直します。

$ kubectl scale deployment/inference-server --replicas=0
$ kubectl scale deployment/inference-server --replicas=1

Pod が Ready になるのを待ち、Locust で負荷をかけます。“Number of users to simulate” を 1100 users に設定しても、さきほどと異なり、遅延が低く抑えられていることがわかります。GPU 使用率を確認すると、こちらもさきほどと異なり 70% 程度と、負荷が適切にかかっていることが確認できます。

このままユーザ数を 1300 まで上げても、遅延はまだ十分低い状況が保たれます。一方 GPU 使用率は上限近くにまで到達します。

これに加え、ユーザ数を 1400 まで上げると遅延が 100 msec. を上回り始め、GPU 使用率も 90% を超えるため、さらなる性能向上には別の最適化が必要だとわかります。

ここまでに設定した内容で、以下の性能まで達成されました。

  • スループット: 約 1260 RPS
  • 遅延: 約 65 msec.

INT8 適用の効果を確認する

最後に直前の設定のまま、INT8 による量子化を適用したときの性能を確認します。バッチサイズ 8 で TF-TRT を適用したモデルを作成します。このときの設定およびディレクトリ構造は以下のようになります。

今回のディレクトリ構成は以下の通りです。

tftrt_int8_bs8_count4
├── 1
│ └── model
│ ├── saved_model.pb
│ └── variables
├── config.pbtxt
└── imagenet1k_labels.txt

モデルと設定ファイルを GCS へアップロードします。

$ mkdir -p tftrt_int8_bs8_count4/1/model/
$ cp -r models/resnet/INT8/00001/* tftrt_int8_bs8_count4/1/model/
$ vim tftrt_int8_bs8_count4/config.pbtxt
$ gsutil cp -R tftrt_int8_bs8_count4/ gs://${BUCKET_NAME}/resnet/

これまでと同様 TRTIS を立ち上げ直します。

$ kubectl scale deployment/inference-server --replicas=0
$ kubectl scale deployment/inference-server --replicas=1

INT8 化したモデルを実行するときのみ、初回アクセス時に warmup が走るため、レスポンスに時間がかかります。そのため計測のための負荷をかける前に、一度 Locust でユーザ数 100 などの小さい規模で一瞬負荷をかけ、負荷を停止させた後しばらく待つ必要があります。必要な warmup 処理が完了するまで GPU 使用率が高い状態になりますので、数分待ったあと、実際の負荷をかけ始めます。

今回は最初から “Number of users to simulate” を 1400 users に設定しましたが、十分小さい遅延でレスポンスを返せていることがわかります。またこのとき GPU 使用率は低く抑えられているため、さらに負荷をかけることができそうです。

最終的にユーザ数を 1700 まで上げると以下のようになります。遅延はおおむね目標値内に収まっているものの、ばらつきが大きくなっています。このとき GPU 使用率はまだ 60% 程度ですので、安定させるためには GPU 以外の部分についても、さらにチューニングが必要そうです。

しかしながら、冒頭掲げた目標であるスループット 1500 RPS は達成されそうということがわかりましたので、チューニングは一旦ここまでとします。

まとめ

このチュートリアルでは、GCP 上にオンライン予測システムを構築し、TensorFlow with TensorRT (TF-TRT)、TensorRT Inference Server (TRTIS) を活用して GPU リソースを使い切ることに挑戦しました。段階的にチューニングを施すことで、NVIDIA Tesla T4 の性能を引き出すことができました。さらに高速な処理が必要な場合には、TensorRT Inference with Tensorflow(GTC 2019) などを参考に、TensorRT を直接適用することを検討してください。

--

--