TF Servingを使用してKubernetes上に🤗 ViTをデプロイする

Using TF Serving to deploy 🤗 ViT on Kubernetes.

前の投稿では、TensorFlow Servingを使用して🤗 TransformersからVision Transformer(ViT)モデルをローカルに展開する方法を示しました。ビジョントランスフォーマーモデル内での埋め込み前処理および後処理操作、gRPCリクエストの処理など、さまざまなトピックをカバーしました!

ローカル展開は、有用なものを構築するための優れたスタート地点ですが、実際のプロジェクトで多くのユーザーに対応できる展開を実行する必要があります。この投稿では、前の投稿のローカル展開をDockerとKubernetesでスケーリングする方法を学びます。したがって、DockerとKubernetesに関する基本的な知識が必要です。

この投稿は前の投稿に基づいていますので、まずそれをお読みいただくことを強くお勧めします。この投稿で説明されているコードは、このリポジトリで確認することができます。

私たちの展開をスケールアップする基本的なワークフローは、次のステップを含みます:

  • アプリケーションロジックのコンテナ化:アプリケーションロジックには、リクエストを処理して予測を返すサービスモデルが含まれます。コンテナ化するために、Dockerが業界標準です。

  • Dockerコンテナの展開:ここにはさまざまなオプションがあります。最も一般的に使用されるオプションは、DockerコンテナをKubernetesクラスターに展開することです。Kubernetesは、展開に便利な機能(例:自動スケーリングとセキュリティ)を提供します。ローカルでKubernetesクラスターを管理するためのMinikubeのようなソリューションや、Elastic Kubernetes Service(EKS)のようなサーバーレスソリューションを使用することもできます。

SagemakerやVertex AIのような、MLデプロイメント固有の機能をすぐに利用できる時代に、なぜこのような明示的なセットアップを使用するのか疑問に思うかもしれません。それは考えるのは当然です。

上記のワークフローは、業界で広く採用され、多くの組織がその恩恵を受けています。長年にわたってすでに実戦投入されています。また、複雑な部分を抽象化しながら、展開に対してより細かな制御を持つことができます。

この投稿では、Google Kubernetes Engine(GKE)を使用してKubernetesクラスターをプロビジョニングおよび管理することを前提としています。GKEを使用する場合、請求を有効にしたGCPプロジェクトが既にあることを想定しています。また、GKEで展開を行うためにgcloudユーティリティを構成する必要があります。ただし、Minikubeを使用する場合でも、この投稿で説明されているコンセプトは同様に適用されます。

注意:この投稿で表示されるコードスニペットは、gcloudユーティリティとDocker、kubectlが構成されている限り、Unixターミナルで実行できます。詳しい手順は、付属のリポジトリで入手できます。

サービングモデルは、生のイメージ入力をバイトとして処理し、前処理および後処理を行うことができます。

このセクションでは、ベースのTensorFlow Servingイメージを使用してそのモデルをコンテナ化する方法を示します。TensorFlow Servingは、モデルをSavedModel形式で消費します。前の投稿でSavedModelを取得した方法を思い出してください。ここでは、SavedModeltar.gz形式で圧縮されていることを前提としています。万が一必要な場合は、ここから入手できます。その後、SavedModel<MODEL_NAME>/<VERSION>/<SavedModel>という特別なディレクトリ構造に配置する必要があります。これにより、TensorFlow Servingは異なるバージョンのモデルの複数の展開を同時に管理できます。

Dockerイメージの準備

以下のシェルスクリプトは、SavedModelを親ディレクトリmodelsの下のhf-vit/1に配置します。Dockerイメージの準備時には、それ内のすべてをコピーします。この例ではモデルが1つしかないですが、これは一般的に適用できるアプローチです。

$ MODEL_TAR=model.tar.gz
$ MODEL_NAME=hf-vit
$ MODEL_VERSION=1
$ MODEL_PATH=models/$MODEL_NAME/$MODEL_VERSION

$ mkdir -p $MODEL_PATH
$ tar -xvf $MODEL_TAR --directory $MODEL_PATH

以下に、ケースごとのmodelsディレクトリの構造を示します。

$ find /models
/models
/models/hf-vit
/models/hf-vit/1
/models/hf-vit/1/keras_metadata.pb
/models/hf-vit/1/variables
/models/hf-vit/1/variables/variables.index
/models/hf-vit/1/variables/variables.data-00000-of-00001
/models/hf-vit/1/assets
/models/hf-vit/1/saved_model.pb

カスタムTensorFlow Servingイメージは、ベースイメージを基に構築する必要があります。これには様々なアプローチがありますが、公式ドキュメントに示されているようにDockerコンテナを実行することで行います。まずはバックグラウンドモードでtensorflow/servingイメージを実行し、その後、modelsディレクトリ全体を以下のように実行中のコンテナにコピーします。

$ docker run -d --name serving_base tensorflow/serving
$ docker cp models/ serving_base:/models/

ベースとして公式のTensorFlow Serving Dockerイメージを使用しましたが、ソースからビルドしたイメージを使用することもできます。

注意: TensorFlow ServingはAVX512などの命令セットを活用したハードウェア最適化の恩恵を受けています。これらの命令セットは深層学習モデルの推論を高速化することができます。したがって、モデルがデプロイされるハードウェアを知っている場合は、TensorFlow Servingイメージの最適化ビルドを入手し、それを使用することがしばしば有益です。

実行中のコンテナに必要なファイルが適切なディレクトリ構造で揃ったところで、これらの変更を含んだ新しいDockerイメージを作成する必要があります。以下のdocker commitコマンドでこれを行い、$NEW_IMAGEという名前の新しいDockerイメージが生成されます。重要なポイントとして、この場合、モデル名であるhf-vitを環境変数MODEL_NAMEに設定する必要があることに注意してください。これにより、TensorFlow Servingにデプロイするモデルを指定することができます。

$ NEW_IMAGE=tfserving:$MODEL_NAME

$ docker commit \ 
    --change "ENV MODEL_NAME $MODEL_NAME" \ 
    serving_base $NEW_IMAGE

ローカルでDockerイメージを実行する

最後に、新しく構築したDockerイメージをローカルで実行して正常に動作するか確認できます。以下はdocker runコマンドの出力です。出力が冗長なため、重要な部分に絞って表示しています。また、gRPCエンドポイントとHTTP/RESTエンドポイントのポート8500および8501が開放されることも注目に値します。

$ docker run -p 8500:8500 -p 8501:8501 -t $NEW_IMAGE &


---------OUTPUT---------
(Re-)adding model: hf-vit
Successfully reserved resources to load servable {name: hf-vit version: 1}
Approving load for servable version {name: hf-vit version: 1}
Loading servable version {name: hf-vit version: 1}
Reading SavedModel from: /models/hf-vit/1
Reading SavedModel debug info (if present) from: /models/hf-vit/1
Successfully loaded servable version {name: hf-vit version: 1}
Running gRPC ModelServer at 0.0.0.0:8500 ...
Exporting HTTP/REST API at:localhost:8501 ...

Dockerイメージのプッシュ

最後のステップは、Dockerイメージをイメージリポジトリにプッシュすることです。ここではGoogle Container Registry (GCR)を使用します。次のコードでこれを行うことができます:

$ GCP_PROJECT_ID=<GCP_PROJECT_ID>
$ GCP_IMAGE=gcr.io/$GCP_PROJECT_ID/$NEW_IMAGE

$ gcloud auth configure-docker
$ docker tag $NEW_IMAGE $GCP_IMAGE
$ docker push $GCP_IMAGE

GCRを使用しているため、Dockerイメージのタグ(他の形式も注意)をgcr.io/<GCP_PROJECT_ID>で接頭辞付けする必要があります。Dockerイメージが準備され、GCRにプッシュされたので、Kubernetesクラスタにデプロイする準備が整いました。

Kubernetesクラスタ上でのデプロイには以下が必要です:

  • Google Kubernetes Engine (GKE)を使用してKubernetesクラスタをプロビジョニングすること。ただし、他のプラットフォームやツール(EKSやMinikubeなど)を使用することもできます。

  • デプロイを行うためにKubernetesクラスタへの接続。

  • YAMLマニフェストの作成。

  • マニフェストを使用してデプロイを実行するためのユーティリティツールであるkubectl

それぞれのステップについて見ていきましょう。

GKE上でKubernetesクラスタをプロビジョニングする

この場合、次のようなシェルスクリプトを使用して実行できます(こちらで入手可能):

$ GKE_CLUSTER_NAME=tfs-cluster
$ GKE_CLUSTER_ZONE=us-central1-a
$ NUM_NODES=2
$ MACHINE_TYPE=n1-standard-8

$ gcloud container clusters create $GKE_CLUSTER_NAME \
    --zone=$GKE_CLUSTER_ZONE \
    --machine-type=$MACHINE_TYPE \
    --num-nodes=$NUM_NODES

GCPでは、さまざまなマシンタイプを提供しており、デプロイメントを希望通りに構成することができます。詳細についてはドキュメントを参照することをお勧めします。

クラスターがプロビジョニングされたら、デプロイメントを実行するために接続する必要があります。ここではGKEが使用されているため、自分自身を認証する必要もあります。次のようなシェルスクリプトを使用して、これらの両方を行うことができます:

$ GCP_PROJECT_ID=<GCP_PROJECT_ID>

$ export USE_GKE_GCLOUD_AUTH_PLUGIN=True

$ gcloud container clusters get-credentials $GKE_CLUSTER_NAME \
    --zone $GKE_CLUSTER_ZONE \
    --project $GCP_PROJECT_ID

gcloud container clusters get-credentialsコマンドは、クラスターへの接続と認証の両方を処理します。これが完了すると、マニフェストの作成準備が整います。

Kubernetesマニフェストの作成

KubernetesマニフェストはYAMLファイルで作成されます。デプロイメントを実行するために単一のマニフェストファイルを使用することも可能ですが、別々のマニフェストファイルを作成することは、関心の分離を委任するためによく利用されます。これを達成するために、通常は次の3つのマニフェストファイルを使用します:

  • deployment.yamlは、デプロイメントの望ましい状態を定義します。Dockerイメージの名前、Dockerイメージを実行する際の追加の引数、外部アクセスのために開くポート、リソースの制限などを指定します。

  • service.yamlは、外部クライアントとKubernetesクラスタ内のポッドとの接続を定義します。

  • hpa.yamlは、デプロイメントを構成するポッドの数をスケールアップおよびスケールダウンするためのルールを定義します。たとえば、CPU使用率の割合などです。

この記事の関連するマニフェストはこちらで見つけることができます。以下では、これらのマニフェストの受け取り方についての概要を示します。

次に、各マニフェストの重要な部分について説明します。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: tfs-server
  name: tfs-server
...
spec:
  containers:
  - image: gcr.io/$GCP_PROJECT_ID/tfserving-hf-vit:latest
    name: tfs-k8s
    imagePullPolicy: Always
    args: ["--tensorflow_inter_op_parallelism=2", 
           "--tensorflow_intra_op_parallelism=8"] 
    ports:
    - containerPort: 8500
      name: grpc
    - containerPort: 8501
      name: restapi
    resources:
      limits:
        cpu: 800m
      requests:
        cpu: 800m
...

tfs-servertfs-k8sのような名前を任意の方法で構成することができます。containersでは、デプロイメントが使用するDockerイメージのURIを指定します。現在のリソース使用状況は、コンテナのためのresourcesの許容範囲を設定することで監視されます。これにより、Horizontal Pod Autoscaler(後述)がコンテナの数をスケールアップまたはスケールダウンするかどうかを決定できます。 requests.cpuは、オペレータが正しく動作するためにコンテナに必要な最小のCPUリソースです。ここでは800mは全体のCPUリソースの80%を意味します。したがって、HPAはすべてのポッドのrequests.cpuの合計から平均CPU使用率を監視し、スケーリングの決定を行います。

Kubernetes固有の設定に加えて、argsでTensorFlow Serving固有のオプションを指定することもできます。この場合、以下の2つがあります:

  • tensorflow_inter_op_parallelismは、独立した操作を実行するために並列に実行するスレッドの数を設定します。推奨値は2です。

  • tensorflow_intra_op_parallelismは、個々の操作を実行するために並列に実行するスレッドの数を設定します。推奨値はデプロイメントCPUの物理コア数です。

これらのオプション(およびその他)についての詳細や、デプロイメントのためのチューニングのヒントについては、こちらとこちらから詳細を学ぶことができます。

service.yaml :

apiVersion: v1
kind: Service
metadata:
  labels:
    app: tfs-server
  name: tfs-server
spec:
  ports:
  - port: 8500
    protocol: TCP
    targetPort: 8500
    name: tf-serving-grpc
  - port: 8501
    protocol: TCP
    targetPort: 8501
    name: tf-serving-restapi
  selector:
    app: tfs-server
  type: LoadBalancer

サービスのタイプを「LoadBalancer」に設定することで、エンドポイントがKubernetesクラスターの外部に公開されるようになります。指定したポートを介して外部クライアントとの接続を行うために、「tfs-server」デプロイメントが選択されます。「8500」と「8501」という2つのポートをgRPCとHTTP/RESTの接続用に開放します。

hpa.yaml :

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
 name: tfs-server

spec:
 scaleTargetRef:
   apiVersion: apps/v1
   kind: Deployment
   name: tfs-server
 minReplicas: 1
 maxReplicas: 3
 targetCPUUtilizationPercentage: 80

HPAはHorizontal Pod Autoscalerの略です。ターゲットデプロイメントのPodの数をスケーリングするタイミングを決定するための基準を設定します。Kubernetesが内部的に使用するオートスケーリングアルゴリズムについては、こちらで詳しく学ぶことができます。

ここでは、Kubernetesがどのようにオートスケーリングを処理するかを指定します。特に、オートスケーリングを実行するために制約条件を定義します – minReplicasmaxReplicas、およびターゲットCPU利用率です。targetCPUUtilizationPercentageはオートスケーリングにとって重要なメトリックです。以下のスレッドは、それが意味するものを適切にまとめたものです(こちらから引用):

CPU使用率は、デプロイメントのすべてのPodの直近1分間の平均CPU使用率を、このデプロイメントの要求されたCPUで割ったものです。PodのCPU使用率の平均が、定義したターゲットよりも高い場合、レプリカが調整されます。

デプロイメントマニフェストでresourcesを指定することを思い出してください。resourcesを指定することで、Kubernetesコントロールプレーンはメトリックの監視を開始し、targetCPUUtilizationが機能します。それ以外の場合、HPAはデプロイメントの現在の状態を把握できません。

要件に基づいてこれらを必要な数値に設定して実験することができますが、GKEはこれらのリソースを管理するためにGoogle Compute Engineを内部的に使用するため、オートスケーリングは利用可能なクオータに依存します。

デプロイの実行

マニフェストが準備できたら、kubectl applyコマンドを使用して現在接続されているKubernetesクラスターに適用することができます。

$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
$ kubectl apply -f hpa.yaml

デプロイを行うために各マニフェストを個別に適用することは問題ありませんが、異なるマニフェストが多数ある場合は、Kustomizeのようなユーティリティが役立ちます。以下のようにkustomization.yamlという名前の別の仕様を定義するだけです。

commonLabels:
  app: tfs-server
resources:
- deployment.yaml
- hpa.yaml
- service.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

その後、実際のデプロイを実行するためのコマンドはわずか1行です。

$ kustomize build . | kubectl apply -f -

詳しい手順はこちらでご覧いただけます。デプロイが完了したら、次のようにしてエンドポイントのIPを取得できます。

$ kubectl rollout status deployment/tfs-server
$ kubectl get svc tfs-server --watch

---------OUTPUT---------
NAME        TYPE          CLUSTER-IP   EXTERNAL-IP  PORT(S)                        AGE
tfs-server  LoadBalancer  xxxxxxxxxx   xxxxxxxxxx   8500:30869/TCP,8501:31469/TCP  xxx

利用可能になったら、外部IPをメモしてください。

これで、Kubernetes上でモデルを展開するためのすべてのステップが完了しました! Kubernetesは、自動スケーリングやクラスタ管理などの複雑な要素に対する抽象化を優雅に提供しながら、モデルの展開時に重要な要素に重点を置くことができます。これには、リソース利用、セキュリティ(ここではカバーしていません)、レイテンシなどのパフォーマンスに関連する重要な要素が含まれます。

エンドポイント用に外部IPを取得した場合、次のリストを使用してテストできます:

import tensorflow as tf 
import json
import base64

image_path = tf.keras.utils.get_file(
    "image.jpg", "http://images.cocodataset.org/val2017/000000039769.jpg"
)
bytes_inputs = tf.io.read_file(image_path)
b64str = base64.urlsafe_b64encode(bytes_inputs.numpy()).decode("utf-8")
data = json.dumps(
    {"signature_name": "serving_default", "instances": [b64str]}
)

json_response = requests.post(
    "http://<ENDPOINT-IP>:8501/v1/models/hf-vit:predict", 
    headers={"content-type": "application/json"}, 
    data=data
)
print(json.loads(json_response.text))

---------OUTPUT---------
{'predictions': [{'label': 'エジプトの猫', 'confidence': 0.896659195}]}

もし、この展開がより多くのトラフィックに対応する場合のパフォーマンスを知りたい場合は、この記事を参照することをお勧めします。Locustを使用してロードテストを実行し、結果を視覚化するための対応するリポジトリを参照してください。

TensorFlow Servingは、アプリケーションの使用ケースに基づいて展開をカスタマイズするためのさまざまなオプションを提供しています。以下では、いくつかのオプションについて簡単に説明します。

enable_batchingは、一定のタイミングウィンドウで入力リクエストを収集し、バッチとしてまとめ、バッチ推論を実行し、各リクエストの結果を適切なクライアントに返すバッチ推論機能を有効にします。 TensorFlow Servingは、展開のニーズに合わせて設定可能なオプション(max_batch_sizenum_batch_threadsなど)を豊富に提供しています。詳細については、こちらをご覧ください。バッチ処理は、モデルからの予測を即座に必要としないアプリケーションに特に有益です。その場合、通常、予測のために複数のサンプルをバッチでまとめて集め、それらのバッチを予測に送信します。幸運なことに、TensorFlow Servingは、バッチ処理機能を有効にするとこれらすべてを自動で設定できます。

enable_model_warmupは、遅延的にインスタンス化される一部のTensorFlowコンポーネントをダミーの入力データで暖機化します。これにより、すべてが適切にロードされ、実際のサービス時間中に遅延がないことを保証できます。

この記事と関連するリポジトリでは、🤗 TransformersのVision TransformerモデルをKubernetesクラスターに展開する方法について学びました。初めてこれを行う場合、手順は少し難しく見えるかもしれませんが、一度理解すれば、すぐにツールボックスの重要な要素になります。このワークフローにすでに慣れている場合でも、この記事が役立つことを願っています。

同じVision TransformerモデルのONNX最適化バージョンに対しても同じ展開ワークフローを適用しました。詳細については、このリンクをご覧ください。ONNX最適化モデルは、展開にx86 CPUを使用している場合に特に有益です。

次の投稿では、Vertex AIを使用してこの展開を行う方法を、model.deploy(autoscaling_config=...)のようなコードの量を大幅に削減して示します。私たちと同じくらいワクワクしていることを願っています。

実験を実施するためにGCPクレジットを提供してくれたGoogleのML Developer Relations Programチームに感謝します。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

人工知能

「ナレ・ヴァンダニャン、Ntropyの共同創設者兼CEO- インタビューシリーズ」

Ntropyの共同創設者兼CEOであるナレ・ヴァンダニアンは、開発者が100ミリ秒未満で超人的な精度で金融取引を解析することを可...

人工知能

「Kognitosの創設者兼CEO、ビニー・ギル- インタビューシリーズ」

ビニー・ギルは、複数の役職と企業を横断する多様で幅広い業務経験を持っていますビニーは現在、Kognitosの創設者兼CEOであり...

データサイエンス

「3つの質問:ロボットの認識とマッピングの研磨」

MIT LIDSのLuca CarloneさんとJonathan Howさんは、将来のロボットが環境をどのように知覚し、相互作用するかについて議論し...

AIテクノロジー

アンソニー・グーネティレケ氏は、Amdocsのグループ社長であり、テクノロジー部門および戦略部門の責任者です- インタビューシリーズ

アンソニー・グーネティレーケは、Amdocsでグループ社長、テクノロジーと戦略担当です彼と企業戦略チームは、会社の戦略を策...

人工知能

「トリントの創設者兼CEO、ジェフ・コフマンへのインタビューシリーズ」

ジェフ・コーフマンは、ABC、CBS、CBCニュースで30年のキャリアを持った後、Trintの創設者兼CEOとなりましたジェフは手作業の...

人工知能

「クリス・サレンス氏、CentralReachのCEO - インタビューシリーズ」

クリス・サレンズはCentralReachの最高経営責任者であり、同社を率いて、自閉症や関連する障害を持つ人々のために優れたクラ...