Amazon Elastic Inference動かしてみた

[aptpod Advent Calendar 2018](https://qiita.com/advent-calendar/2018/aptpod) の15日目です

## 概要

AWS re:Invent 2018で発表された[Amazon Elastic Inference](https://aws.amazon.com/jp/machine-learning/elastic-inference/)を[Amazon EC2インスタンスドキュメンテーション用Amazon Elastic Inference](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/elastic-inference.html)の資料通りに動かしてみた結果です

## 背景

業務で機械学習にGPUを使用する際は（[7日目の記事](https://qiita.com/brownbro/items/c967384369fe52f6e4ed)でご紹介した）JupyterHub (on Docker) を中心に利用し、学習したモデルを利用する際は（GPUが必要なものもCPUだけで足りるものもだいたい同じ手順でデプロイできるように）推論を行うPythonコードを(Nvidia) Docker上で動作させるような構成で管理していました

ただ

- CPU: 推論速度がデータの流量を捌ききれずパンクしてしまうことがある
- GPU: 調整なしだと1つのPythonプログラムがGPUまるまる1枚を確保してしまうなど、リソースの無駄が起こりうる

と、調整／管理が地味に面倒だなーと思っていたところで「任意のEC2インスタンスに推論用のGPU？銀の弾丸では？？」なサービスが発表されたので試してみました

## 手順

[Working with Amazon EI - Amazon Elastic Compute Cloud](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/working-with-ei.html)内の手順を上から順にポチポチしました

（以下は雑な日本語訳がほとんどと若干の補足くらいですので、ぜひ本家のドキュメントもご参照ください:pray:）

### Amazon EI用のセキュリティグループの作成

https://console.aws.amazon.com/vpc/ 内の「セキュリティグループ」のパネルから、Inbound Rules / Outbound Rules共に、

- ポート: 443
- プロトコル: HTTPS
- ソース／宛先: 任意の場所

の値を設定した、使用するVPCと紐づくセキュリティグループを作成します

（必要に応じてソース／宛先は絞ってください）

### Amazon EI用のVPCエンドポイントの作成

https://console.aws.amazon.com/vpc/ 内の「エンドポイント」のパネルから、

1. 「エンドポイントの作成」ボタンを選択
2. 「サービスカテゴリ」内の「サービスを名前で検索」を選択
3. 「サービス名」に `com.amazonaws.ap-northeast-1.elastic-inference.runtime` を入力して「検証」を選択
    - <img width="1175" alt="スクリーンショット 2018-12-14 23.38.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/55726/ddb24b35-a891-ad5c-4df7-60b7bb7b48fd.png">


4. 「VPC」と「サブネット」にそれぞれ使用するものを選択
5. 「プライベート DNS 名を有効にする」をチェック
6. 「セキュリティグループ」に先ほど作成した Amazon EI用のセキュリティグループを選択

の手順でVPCエンドポイントを作成します

### Amazon EIを使用するためのIAM Roleの作成

#### IAM Policyの作成

https://console.aws.amazon.com/iam/ 内の「ポリシー」のパネルから「ポリシーの作成」を選択し、以下の内容を ` ec2-role-trust-policy.json` などの名前で保存します

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "elastic-inference:Connect",
      "Resource": "*"
    }
  ]
}
```

#### IAM Roleの作成

https://console.aws.amazon.com/iam/ 内の「ロール」のパネルから「ロールの作成」を選択し、

1. 以下の内容を選択
    - 信頼されたエンティティの種類を選択：AWS サービス
    - このロールを使用するサービスを選択：EC2
2. 先ほど作成した（`ec2-role-trust-policy.json`などの名前の）ポリシーを選択
3. （タグはよしなに記入）
4. ロール名を記入し保存

の手順でロールを作成します

### EC2 with Amazon EIのインスタンスの作成

https://console.aws.amazon.com/ec2/ 内から「インスタンスの作成」を選択し、

1. 「Deep Learning AMI (Amazon Linux) Version 20.0」を選択
2. インスタンスタイプを適宜選択（お試しには `c5` とかでよいのではないかと思います）
3. 「インスタンスの詳細の設定」から以下を中心に適宜選択
    - IAMロール：先ほど作成したもの
    - Elastic Inference：Add an Elastic Inference acceleratorにチェック
        - Type：適宜選択（とりあえず `eia1.medium` でよいのではないかと思います）
4. 残りは通常のEC2インスタンスを立てるときと同様に設定

の手順でインスタンスを生成します

### Amazon EI TensorFlow Servingの利用

インスタンスに接続し、ドキュメントに記載の手順通りにTensorFlow Serving用の準備をします

```bash
[ec2-user@ip-xxx ~]$ curl -O https://s3-us-west-2.amazonaws.com/aws-tf-serving-ei-example/inception.zip
[ec2-user@ip-xxx ~]$ unzip inception.zip
[ec2-user@ip-xxx ~]$ curl -O https://upload.wikimedia.org/wikipedia/commons/b/b5/Siberian_Husky_bi-eyed_Flickr.jpg
[ec2-user@ip-xxx ~]$ AmazonEI_TensorFlow_Serving_v1.11_v1 --model_name=inception --model_base_path=/home/ec2-user/SERVING_INCEPTION/SERVING_INCEPTION --port=9000
(略)
Using Amazon Elastic Inference Client Library Version: 1.2.8
Number of Elastic Inference Accelerators Available: 1
Elastic Inference Accelerator ID: eia-b8bf2717bfb54be1b41f66de711c67fa
Elastic Inference Accelerator Type: eia1.medium
(略)
2018-12-14 15:16:08.629584: I tensorflow_serving/model_servers/server.cc:283] Running gRPC ModelServer at 0.0.0.0:9000 ...
```

こんな感じにフォアグラウンドでTensorFlow Servingが動き始めますので、もう一つ別の窓を準備し、TensorFlow Servingに推論させるようなコードを実行します

```bash
[ec2-user@ip-xxx ~]$ cat inception_client.py 
from __future__ import print_function

import grpc
import tensorflow as tf

from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc


tf.app.flags.DEFINE_string('server', 'localhost:9000',
                           'PredictionService host:port')
tf.app.flags.DEFINE_string('image', '', 'path to image in JPEG format')
FLAGS = tf.app.flags.FLAGS


def main(_):
  channel = grpc.insecure_channel(FLAGS.server)
  stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
  # Send request
  with open(FLAGS.image, 'rb') as f:
    # See prediction_service.proto for gRPC request/response details.
    data = f.read()
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'inception'
    request.model_spec.signature_name = 'predict_images'
    request.inputs['images'].CopyFrom(
        tf.contrib.util.make_tensor_proto(data, shape=[1]))
    result = stub.Predict(request, 10.0)  # 10 secs timeout
    print(result)
  print("Inception Client Passed")

if __name__ == '__main__':
  tf.app.run()

[ec2-user@ip-xxx ~]$ python inception_client.py --server=localhost:9000 --image Siberian_Husky_bi-eyed_Flickr.jpg
Traceback (most recent call last):
  File "inception_client.py", line 3, in <module>
    import grpc
ModuleNotFoundError: No module named 'grpc'
```

`grpc`モジュールがないよエラーが出てしまいましたが、そういえばインスタンスへのアクセス時に以下のような内容が出力されてました

```bash
=============================================================================
       __|  __|_  )
       _|  (     /   Deep Learning AMI (Amazon Linux) Version 20.0
      ___|\___|___|
=============================================================================

Please use one of the following commands to start the required environment with the framework of your choice:
for MXNet(+Keras2) with Python3 (CUDA 9.0 and Intel MKL-DNN) _____________________________________ source activate mxnet_p36
for MXNet(+Keras2) with Python2 (CUDA 9.0 and Intel MKL-DNN) _____________________________________ source activate mxnet_p27
for MXNet(+Amazon Elastic Inference) with Python3 _______________________________________ source activate amazonei_mxnet_p36
for MXNet(+Amazon Elastic Inference) with Python2 _______________________________________ source activate amazonei_mxnet_p27
for TensorFlow(+Keras2) with Python3 (CUDA 9.0 and Intel MKL-DNN) ___________________________ source activate tensorflow_p36
for TensorFlow(+Keras2) with Python2 (CUDA 9.0 and Intel MKL-DNN) ___________________________ source activate tensorflow_p27
for Tensorflow(+Amazon Elastic Inference) with Python2 _____________________________ source activate amazonei_tensorflow_p27
for Theano(+Keras2) with Python3 (CUDA 9.0) _____________________________________________________ source activate theano_p36
for Theano(+Keras2) with Python2 (CUDA 9.0) _____________________________________________________ source activate theano_p27
for PyTorch with Python3 (CUDA 10.0 and Intel MKL) _____________________________________________ source activate pytorch_p36
for PyTorch with Python2 (CUDA 10.0 and Intel MKL) _____________________________________________ source activate pytorch_p27
for CNTK(+Keras2) with Python3 (CUDA 9.0 and Intel MKL-DNN) _______________________________________ source activate cntk_p36
for CNTK(+Keras2) with Python2 (CUDA 9.0 and Intel MKL-DNN) _______________________________________ source activate cntk_p27
for Caffe2 with Python2 (CUDA 9.0) ______________________________________________________________ source activate caffe2_p27
for Caffe with Python2 (CUDA 8.0) ________________________________________________________________ source activate caffe_p27
for Caffe with Python3 (CUDA 8.0) ________________________________________________________________ source activate caffe_p35
for Chainer with Python2 (CUDA 9.0 and Intel iDeep) ____________________________________________ source activate chainer_p27
for Chainer with Python3 (CUDA 9.0 and Intel iDeep) ____________________________________________ source activate chainer_p36
for base Python2 (CUDA 9.0) ________________________________________________________________________ source activate python2
for base Python3 (CUDA 9.0) ________________________________________________________________________ source activate python3

Official Conda User Guide: https://conda.io/docs/user-guide/index.html
AWS Deep Learning AMI Homepage: https://aws.amazon.com/machine-learning/amis/
Developer Guide and Release Notes: https://docs.aws.amazon.com/dlami/latest/devguide/what-is-dlami.html
Support: https://forums.aws.amazon.com/forum.jspa?forumID=263
For a fully managed experience, check out Amazon SageMaker at https://aws.amazon.com/sagemaker
=============================================================================
```

ということで言われた通りの環境をアクティベートして再度試します

```bash
[ec2-user@ip-xxx ~]$ source activate amazonei_tensorflow_p27
(amazonei_tensorflow_p27) [ec2-user@ip-xxx ~]$ python --version
Python 2.7.15 :: Anaconda custom (64-bit)
(amazonei_tensorflow_p27) [ec2-user@ip-xxx ~]$ python inception_client.py --server=localhost:9000 --image Siberian_Husky_bi-eyed_Flickr.jpg
outputs {
  key: "classes"
  value {
    dtype: DT_STRING
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 5
      }
    }
    string_val: "Eskimo dog, husky"
    string_val: "Siberian husky"
    string_val: "malamute, malemute, Alaskan malamute"
    string_val: "white wolf, Arctic wolf, Canis lupus tundrarum"
    string_val: "timber wolf, grey wolf, gray wolf, Canis lupus"
  }
}
outputs {
  key: "scores"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 5
      }
    }
    float_val: 8.89673233032
    float_val: 8.72353553772
    float_val: 4.88978672028
    float_val: 3.06171226501
    float_val: 2.88761281967
  }
}
model_spec {
  name: "inception"
  version {
    value: 1
  }
  signature_name: "predict_images"
}

Inception Client Passed
```

無事動きました:tada:

## 結果

- 無事Amazon Elastic Inferenceを用いてTensorFlow Servingを動かすことができました
    - SageMakerからElastic Inferenceを使用する例は[公式のNotebook](https://github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_amazon_algorithms/imageclassification_caltech/Image-classification-fulltraining-elastic-inference.ipynb)が明瞭かと思います
        - エンドポイントのコンフィグに1項目(`'AcceleratorType': 'ml.eia1.large',`)足すだけですね
- 「Dockerを使っている今の構造のまま幸せになれる」わけではなかったです
    - GPUの素性も `nvidia` ではなく `Amazon.com, Inc.` としっかり書いてありました
        - `lspci | grep VGA → 00:03.0 VGA compatible controller: Amazon.com, Inc. Device 1111`
    - TensorFlow Serving（やAmazon SageMakerなど）を推論に使う構成も検討・試行していたので、もちろんこちらを採用するケースでは大変幸せになれそうです
- 過去にMXNet（やONNX）を使ったことがなかったので、そちらのサンプルは試せてません
    - （コードを流し読みする限り）コードの中でより明示的にEIを利用できる感があったので、どなたか詳しい方が記事書かれないかなと期待しています