Amazon Web Services ブログ

Amazon EKS が EC2 Inf1 インスタンスのサポートを開始

Amazon Elastic Kubernetes Service (EKS) は、短期間で機械学習ワークロードのための主要な選択肢になりました。開発者の俊敏性と Kubernetes のスケーラビリティを組み合わせており、AWS で利用可能な Amazon Elastic Compute Cloud (EC2) インスタンスタイプの幅広い選択肢 (C5P3G4 ファミリーなど) からお選びいただけます。

モデルがより高度になるにつれ、高スループットで予測を素早く提供するためにハードウェアアクセラレーションがますます求められています。本日より、AWS のお客様は、Amazon Elastic Kubernetes Service で Amazon EC2 Inf1 インスタンスを使用できるようになりました。これにより、クラウドでの高いパフォーマンスを最小限の予測コストで実現できます。

EC2 Inf1 インスタンス入門
Inf1 インスタンスは、AWS re:Invent 2019 でリリースされました。これらは AWS が一から構築したカスタムチップの AWS Inferentia を使用しており、機械学習の推論ワークロードが加速します。

Inf1 インスタンスは複数のサイズで利用可能で、1、4、または 16 の AWS Inferentia チップがあり、最大 100 Gbps のネットワーク帯域幅と最大 19 Gbps の EBS 帯域幅があります。AWS Inferentia チップには 4 つの NeuronCore が含まれています。いずれも高性能のシストリックアレイ行列乗算エンジンを実装しているため、畳み込みや変換などの一般的な深層学習のオペレーションを大きく高速化します。NeuronCores には大容量のオンチップキャッシュも搭載されており、外部メモリからのアクセスを削減し、プロセスの I/O 時間を節約できます。複数の AWS Inferentia チップが Inf1 インスタンスで使用可能な場合、モデルを分割して、キャッシュメモリにすべて格納できます。あるいは、AWS Inferentia チップの NeuronCores を複数のモデルに分割して、単一の Inf1 インスタンスからマルチモデル予測を行うことも可能です。

EC2 Inf1 インスタンスのモデルのコンパイル
Inf1 インスタンスで機械学習モデルを実行するには、AWS Neuron SDK を使ってモデルをハードウェア最適化表現にコンパイルする必要があります。すべてのツールは AWS Deep Learning AMI ですぐに利用でき、独自のインスタンスにインストールすることもできます。手順については、Deep Learning AMI のドキュメント、および AWS Neuron SDK リポジトリの TensorFlow、PyTorch、Apache MXNet のチュートリアルをご覧ください。

以下のデモでは、Neuron 最適化モデルを Inf1 インスタンスの EKS クラスターにデプロイする方法と、TensorFlow Serving で予測を提供する方法を示します。対象となるモデルは、自然言語処理タスクのための最先端のモデルである BERT です。これは何億ものパラメータを持つ巨大なモデルであり、ハードウェアアクセラレーションの有力な候補となります。

EC2 Inf1 インスタンスの EKS クラスターの構築
まず、2 つの inf1.2xlarge インスタンスでクラスターを構築してみましょう。これは、EKS クラスターのプロビジョニングと管理を行うコマンドラインツールである eksctl を使用して簡単に行うことができます。インストール手順は、EKS のドキュメントに記載されています。

これが私のクラスターの設定ファイルです。Eksctl は、Inf1 インスタンスタイプでノードグループを起動していることを検出し、EKS に最適化された Accelerated AMI を使用してワーカーノードを起動します。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: cluster-inf1
  region: us-west-2
nodeGroups:
  - name: ng1-public
    instanceType: inf1.2xlarge
    minSize: 0
    maxSize: 3
    desiredCapacity: 2
    ssh:
      allow: true

次に、eksctl を使用してクラスターを作成します。このプロセスには約 10 分かかります。

$ eksctl create cluster -f inf1-cluster.yaml

Eksctl は、Neuron デバイスプラグインをクラスターに自動的にインストールします。このプラグインは、Neuron デバイスを Kubernetes スケジューラーにアドバタイズします。Kubernetes スケジューラーは、デプロイ仕様のコンテナから要求できます。デバイスプラグインコンテナが両方の Inf1 インスタンスで正常に動作していることを kubectl で確認できます。

$ kubectl get pods -n kube-system
NAME                                  READY STATUS  RESTARTS AGE
aws-node-tl5xv                        1/1   Running 0        14h
aws-node-wk6qm                        1/1   Running 0        14h
coredns-86d5cbb4bd-4fxrh              1/1   Running 0        14h
coredns-86d5cbb4bd-sts7g              1/1   Running 0        14h
kube-proxy-7px8d                      1/1   Running 0        14h
kube-proxy-zqvtc                      1/1   Running 0        14h
neuron-device-plugin-daemonset-888j4  1/1   Running 0        14h
neuron-device-plugin-daemonset-tq9kc  1/1   Running 0        14h

次に、Kubernetes シークレットで AWS 認証情報を定義します。これにより、S3 に保存されている BERT モデルを取得できます。両方のキーを base64 でエンコードする必要があることに注意してください。

apiVersion: v1 
kind: Secret 
metadata: 
  name: aws-s3-secret 
type: Opaque 
data: 
  AWS_ACCESS_KEY_ID: <base64-encoded value> 
  AWS_SECRET_ACCESS_KEY: <base64-encoded value>

最後に、これらの認証情報をクラスターに保存します。

$ kubectl apply -f secret.yaml

クラスターは正しくセットアップされています。それでは、Neuron 対応バージョンの TensorFlow Serving を格納するアプリケーションコンテナを作成してみましょう。

TensorFlow Serving のアプリケーションコンテナの構築
Dockerfile はとてもシンプルです。Amazon Linux 2 ベースイメージから始めます。次に、AWS CLI と、Neuron リポジトリで利用可能な TensorFlow Serving パッケージをインストールします。

FROM amazonlinux:2
RUN yum install -y awscli
RUN echo $'[neuron] \n\
name=Neuron YUM Repository \n\
baseurl=https://yum.repos.neuron.amazonaws.com \n\
enabled=1' > /etc/yum.repos.d/neuron.repo
RUN rpm --import https://yum.repos.neuron.amazonaws.com/GPG-PUB-KEY-AMAZON-AWS-NEURON.PUB
RUN yum install -y tensorflow-model-server-neuron

イメージを構築し、Amazon Elastic Container Registry リポジトリを作成して、イメージをそこにプッシュします。

$ docker build . -f Dockerfile -t tensorflow-model-server-neuron
$ docker tag IMAGE_NAME 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo
$ aws ecr create-repository --repository-name inf1-demo
$ docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo

アプリケーションコンテナの準備ができました。では、このコンテナを使用して BERT 予測を提供する Kubernetes サービスを定義しましょう。ここでは、Neuron SDK でコンパイル済みのモデルを使用しています。Neuron SDK リポジトリで参照できる手順を使用して、独自にコンパイルできます。

BERT を Kubernetes サービスとしてデプロイする
デプロイは、Neuron ランタイムコンテナと私のアプリケーションコンテナという 2 つのコンテナを管理します。Neuron ランタイムはサイドカーコンテナイメージとして実行され、AWS Inferentia チップとのインタラクションに使用されます。起動時に、後者は、適切なセキュリティ認証情報を使用して AWS CLI を設定します。次に、S3 から BERT モデルをフェッチします。最後に、TensorFlow Serving を起動し、BERT モデルを読み込み、予測リクエストを待ちます。この目的のために、HTTP および grpc ポートが開いています。これが完全なマニフェストです。

kind: Service
apiVersion: v1
metadata:
  name: eks-neuron-test
  labels:
    app: eks-neuron-test
spec:
  ports:
  - name: http-tf-serving
    port: 8500
    targetPort: 8500
  - name: grpc-tf-serving
    port: 9000
    targetPort: 9000
  selector:
    app: eks-neuron-test
    role: master
  type: ClusterIP
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: eks-neuron-test
  labels:
    app: eks-neuron-test
    role: master
spec:
  replicas: 2
  selector:
    matchLabels:
      app: eks-neuron-test
      role: master
  template:
    metadata:
      labels:
        app: eks-neuron-test
        role: master
    spec:
      volumes:
        - name: sock
          emptyDir: {}
      containers:
      - name: eks-neuron-test
        image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo:latest
        command: ["/bin/sh","-c"]
        args:
          - "mkdir ~/.aws/ && \
           echo '[eks-test-profile]' > ~/.aws/credentials && \
           echo AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID >> ~/.aws/credentials && \
           echo AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY >> ~/.aws/credentials; \
           /usr/bin/aws --profile eks-test-profile s3 sync s3://jsimon-inf1-demo/bert /tmp/bert && \
           /usr/local/bin/tensorflow_model_server_neuron --port=9000 --rest_api_port=8500 --model_name=bert_mrpc_hc_gelus_b4_l24_0926_02 --model_base_path=/tmp/bert/"
        ports:
        - containerPort: 8500
        - containerPort: 9000
        imagePullPolicy: Always
        env:
        - name: AWS_ACCESS_KEY_ID
          valueFrom:
            secretKeyRef:
              key: AWS_ACCESS_KEY_ID
              name: aws-s3-secret
        - name: AWS_SECRET_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              key: AWS_SECRET_ACCESS_KEY
              name: aws-s3-secret
        - name: NEURON_RTD_ADDRESS
          value: unix:/sock/neuron.sock

        resources:
          limits:
            cpu: 4
            memory: 4Gi
          requests:
            cpu: "1"
            memory: 1Gi
        volumeMounts:
          - name: sock
            mountPath: /sock

      - name: neuron-rtd
        image: 790709498068.dkr.ecr.us-west-2.amazonaws.com/neuron-rtd:1.0.6905.0
        securityContext:
          capabilities:
            add:
            - SYS_ADMIN
            - IPC_LOCK

        volumeMounts:
          - name: sock
            mountPath: /sock
        resources:
          limits:
            hugepages-2Mi: 256Mi
            aws.amazon.com/neuron: 1
          requests:
            memory: 1024Mi

私は kubectl を使用してサービスを作成します。

$ kubectl create -f bert_service.yml

数秒後、ポッドが稼働します。

$ kubectl get pods
NAME                           READY STATUS  RESTARTS AGE
eks-neuron-test-5d59b55986-7kdml 2/2   Running 0        14h
eks-neuron-test-5d59b55986-gljlq 2/2   Running 0        14h

最後に、サービスポート 9000 をローカルポート 9000 にリダイレクトして、予測クライアントがローカルに接続できるようにします。

$ kubectl port-forward svc/eks-neuron-test 9000:9000 &

これで、すべてを予測する準備ができたので、モデルを呼び出してみましょう。

EKS および Inf1 で BERT を使用して予測する
BERT の内部の仕組みは、この投稿の範囲外です。この特定のモデルは、128 個のトークンのシーケンスを想定しており、セマンティック等価性のために比較する 2 つの文に含まれる単語をエンコードしています。

ここでは、予測レイテンシーの測定のみに関心があるため、ダミーデータで問題ありません。128 個のゼロのシーケンスを格納する 100 個の予測リクエストを作成します。それらを grpc 経由で TensorFlow Serving エンドポイントに送信し、平均予測時間を計算します。

import numpy as np
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
import time

if __name__ == '__main__':
    channel = grpc.insecure_channel('localhost:9000')
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'bert_mrpc_hc_gelus_b4_l24_0926_02'
    i = np.zeros([1, 128], dtype=np.int32)
    request.inputs['input_ids'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))
    request.inputs['input_mask'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))
    request.inputs['segment_ids'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))

    latencies = []
    for i in range(100):
        start = time.time()
        result = stub.Predict(request)
        latencies.append(time.time() - start)
        print("Inference successful: {}".format(i))
    print ("Ran {} inferences successfully.Latency average = {}".format(len(latencies), np.average(latencies)))

平均して、予測には 5.92 ミリ秒かかりました。BERT に関する限り、これはかなり良好な結果です!

100 個の推論を正常に実行しました。レイテンシー平均 = 0.05920819044113159

実際には、スループットを向上させるために、予測リクエストをバッチ処理します。必要に応じて、複数の Inferentia チップをサポートする、より大きな Inf1 インスタンスにスケーリングし、低コストでさらに多くの予測パフォーマンスを提供することもできます。

開始方法
Kubernetes ユーザーは、Amazon Elastic Compute Cloud (EC2) Inf1インスタンスを、米国東部 (バージニア北部) および米国西部 (オレゴン) リージョンで、Amazon Elastic Kubernetes Service にすぐにデプロイできます。Inf1 のデプロイが進むにつれて、より多くのリージョンで Amazon Elastic Kubernetes Service を使用できるようになります。

ぜひお試しいただき、通常の AWS サポートの連絡先、Amazon Elastic Kubernetes ServiceAWS フォーラム、または Github のコンテナロードマップのいずれかからフィードバックをお寄せいただければ幸いです。

– Julien