Amazon Web Services ブログ

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

機械学習と深層学習のモデルがより高度になるにつれて、高スループットで予測を素早く提供するためのハードウェアアクセラレーションの必要性も急増しています。本日より、AWS のお客様は、クラウドにおける高パフォーマンス性と最も低い予測コストのために Amazon ECS で Amazon EC2 Inf1 インスタンスをご利用いただけるようになります。これらのインスタンスは、数週間前から Amazon Elastic Kubernetes Service での利用が可能になっています。

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 のためのチュートリアルでもご覧いただけます。

以下のデモでは、Inf1 インスタンスの ECS クラスターにおけるニューロン最適化モデルのデプロイメント、および TensorFlow Serving を使った予測の提供を行う手順を説明します。今回使用するモデルは、自然言語処理タスクのための最新鋭モデルである BERT です。これは、何億ものパラメータを持つ巨大なモデルであることから、ハードウェアアクセラレーションに最適な候補となります。

Amazon ECS クラスターの作成
クラスターの作成は、CreateCluster API を呼び出すだけという極めてシンプルなものです。

$ aws ecs create-cluster --cluster-name ecs-inf1-demo

呼び出し後、新しいクラスターがコンソールにすぐに表示されます。

新しいクラスター

このクラスターにインスタンスを追加するには、いくつかの前提条件が必要です。

  • ECS インスタンスのための AWS Identity and Access Management (IAM) ロール: このロールがない場合は、ドキュメントで手順が確認してください。ここでは、ロールに ecsInstanceRole という名前を付けています。
  • ECS エージェント を含み、Inf1 インスタンスをサポートする Amazon マシーンイメージ (AMI)。独自の AMI を作成する、または ECS-optimized AMI for Inferentia を使用することができます。us-east-1 リージョンでの AMI の ID は ami-04450f16e0cd20356 です。
  • TensorFlow Serving のネットワークポート (gRPC には 8500、HTTP には 8501) を開くセキュリティグループ。私のセキュリティグループの識別子は sg-0994f5c7ebbb48270 です。
  • SSH アクセスを希望する場合、セキュリティグループはポート 22 も開く必要があり、SSH キーペアの名前を渡さなくてはなりません。ここでは admin という名前にしています。

また、インスタンスがクラスターに参加できるように、小さなユーザーデータファイルを作成する必要もあります。これは、ECS エージェントの設定ファイルに記述されている環境変数にクラスターの名前を保存することによって実行できます。

#!/bin/bash
echo ECS_CLUSTER=ecs-inf1-demo >> /etc/ecs/ecs.config

これで準備完了です。では、RunInstances API を使って Inf1 インスタンスをいくつか追加しましょう。コストを最小限に抑えるために、スポットインスタンスをリクエストします。

$ aws ec2 run-instances \
--image-id ami-04450f16e0cd20356 \
--count 2 \
--instance-type inf1.xlarge \
--instance-market-options '{"MarketType":"spot"}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ecs-inf1-demo}]' \
--key-name admin \
--security-group-ids sg-0994f5c7ebbb48270 \
--iam-instance-profile Name=ecsInstanceRole \
--user-data file://user-data.txt

EC2 コンソールに両方のインスタンスがすぐさま表示されます。

Inf1 インスタンス

数分後、クラスターでタスクを実行する準備が整います。

Inf1 インスタンス

インフラストラクチャの準備が整いました。それでは、BERT モデルを格納するコンテナを構築しましょう。

Inf1 インスタンス用コンテナの構築
Dockerfile は極めて単純です。

  • Amazon Linux 2 イメージを初めに、TensorFlow Serving のためのポート 8500 と 8501 を開きます。
  • 次に、Neuron SDK リポジトリをリポジトリのリストに追加し、AWS Inferentia をサポートする TensorFlow Serving のバージョンをインストールします。
  • 最後に、BERT モデルをコンテナ内にコピーし、起動時にそれをロードします。

こちらが完全なファイルです。

FROM amazonlinux:2
EXPOSE 8500 8501
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
COPY bert /bert
CMD ["/bin/sh", "-c", "/usr/local/bin/tensorflow_model_server_neuron --port=8500 --rest_api_port=8501 --model_name=bert --model_base_path=/bert/"]

次に、コンテナを構築し、それを Amazon Elastic Container Registry でホストされているリポジトリにプッシュします。いつも通りの作業です。

$ docker build -t neuron-tensorflow-inference .

$ aws ecr create-repository --repository-name ecs-inf1-demo

$ aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

$ docker tag neuron-tensorflow-inference 123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-inf1-demo:latest

$ docker push

ここで、このコンテナをクラスターで実行するためのタスク定義を作成する必要があります。

Inf1 インスタンス用のタスク定義の作成
実行ロール、つまり ECS エージェントがユーザーに代わって API コールを実行できるようにするロールがない場合は、まずこのロールを作成する必要があります。詳細情報は、ドキュメントに記載されています。私のロールは ecsTaskExecutionRole という名前です。

完全なタスク定義を以下に表示しています。お分かりいただけるように、これには 2 つのコンテナがあります。

  • 独自に構築した BERT コンテナ。
  • neuron-rtd という名前のサイドカーコンテナ。これは、BERT コンテナが Inf1 インスタンスにある NeuronCore にアクセスすることを可能にします。AWS_NEURON_VISIBLE_DEVICES 環境変数により、コンテナがどの NeuronCore を使用できるかを制御できます。これを使用して、1 つ、または複数の特定の NeuronCore にコンテナをピン留めすることができます。
{
  "family": "ecs-neuron",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [
        {
          "hostPort": 8500,
          "protocol": "tcp",
          "containerPort": 8500
        },
        {
          "hostPort": 8501,
          "protocol": "tcp",
          "containerPort": 8501
        },
        {
          "hostPort": 0,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "command": [
        "tensorflow_model_server_neuron --port=8500 --rest_api_port=8501 --model_name=bert --model_base_path=/bert"
      ],
      "cpu": 0,
      "environment": [
        {
          "name": "NEURON_RTD_ADDRESS",
          "value": "unix:/sock/neuron-rtd.sock"
        }
      ],
      "mountPoints": [
        {
          "containerPath": "/sock",
          "sourceVolume": "sock"
        }
      ],
      "memoryReservation": 1000,
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-inf1-demo:latest",
      "essential": true,
      "name": "bert"
    },
    {
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [],
      "command": [
        "neuron-rtd -g unix:/sock/neuron-rtd.sock"
      ],
      "cpu": 0,
      "environment": [
        {
          "name": "AWS_NEURON_VISIBLE_DEVICES",
          "value": "ALL"
        }
      ],
      "mountPoints": [
        {
          "containerPath": "/sock",
          "sourceVolume": "sock"
        }
      ],
      "memoryReservation": 1000,
      "image": "790709498068.dkr.ecr.us-east-1.amazonaws.com/neuron-rtd:latest",
      "essential": true,
      "linuxParameters": { "capabilities": { "add": ["SYS_ADMIN", "IPC_LOCK"] } },
      "name": "neuron-rtd"
    }
  ],
  "volumes": [
    {
      "name": "sock",
      "host": {
        "sourcePath": "/tmp/sock"
      }
    }
  ]
}

最後に、RegisterTaskDefinition API を呼び出して、ECS バックエンドにタスク定義を通知します。

$ aws ecs register-task-definition --cli-input-json file://inf1-task-definition.json

これで、コンテナを実行し、予測を行う準備が整いました。

Inf1 インスタンスでのコンテナの実行
これは予測サービスなので、クラスターで常時使用できることを確実にしたいと思います。単にタスクを実行するのではなく、必要な数のコンテナコピーが実行されていることを確実にする ECS サービスを作成して、障害が発生した場合にそれらを再起動できるようにします。

$ aws ecs create-service --cluster ecs-inf1-demo \
--service-name bert-inf1 \
--task-definition ecs-neuron:1 \
--desired-count 1

1 分たつと、クラスターで両方のタスクコンテナが実行されていることを確認できます。

実行中のコンテナ

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

ここでは、予測レイテンシーの測定のみに関心があるため、ダミーデータで問題ありません。128 個のゼロのシーケンスを格納する 100 個の予測リクエストを構築します。BERT コンテナの IP アドレスを使用して、リクエストを 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('18.234.61.31:8500')
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'bert'
    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)))

便宜上、このコードは Deep Learning AMI に基づく EC2 インスタンスで実行しています。これは、事前に TensorFlow および TensorFlow Serving 向けの Conda 環境にインストールされていることから、依存関係をインストールする必要がなくなります。

$ source activate tensorflow_p36
$ python predict.py

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

Ran 100 inferences successfully.Latency average = 0.05647835493087769

使用の開始
Amazon Elastic Compute Cloud (EC2) Inf1 インスタンスは、本日から米国東部 (バージニア北部)米国西部 (オレゴン) の各リージョンで Amazon ECS にデプロイできるようになります。Inf1 のデプロイメントが進むにつれて、より多くのリージョンで Amazon ECS との併用が可能になります。

Inf1 をぜひお試しいただき、通常の AWS サポート担当者、Amazon ECSAWS フォーラム、または Github のコンテナロードマップのいずれかを通じてフィードバックをお寄せください。

– Julien