[ハイパフォーマンスかつ最小コストで推論を実現] Amazon EC2 Inf1 x AWS Neuron SDK [使ってみた] – 機械学習 on AWS Advent Calendar 2019

『機械学習 on AWS Advent Calendar 2019』の18日目のエントリです。
2019.12.18

こんにちは、Mr.Moです。

当エントリは『機械学習 on AWS Advent Calendar 2019』の18日目のエントリです。

機械学習の推論に特化したサービスがre:invent 2019 で発表されましたのでさっそく使ってみたいと思います。

[速報] Inferentia搭載!機械学習の推論むけインスタンス Inf1 が発表されました #reinvent

Amazon EC2 Inf1とは

Inf1 インスタンスでは、AWS が設計、開発した、ハイパフォーマンス機械学習推論チップ、AWS Inferentia チップを 16 基まで利用できます。さらに、推論チップを最新のカスタム第 2 世代インテル® Xeon® スケーラブルプロセッサおよび最大 100 Gbps のネットワークと組み合わせることにより、ハイスループットの推論を可能にしました。 Amazon EC2 G4 インスタンスと比較しても、推論作業あたり 3 倍のスループット、40% のコスト削減を実現 画像認識、音声認識、自然言語処理、パーソナライズ、不正検知といった大規模な機械学習推論アプリケーションを、クラウドで最小のコストで実行することができます。

https://aws.amazon.com/jp/about-aws/whats-new/2019/12/introducing-amazon-ec2-inf1-instances-high-performance-and-the-lowest-cost-machine-learning-inference-in-the-cloud/

また、AWS Inferentiaチップを使用して機械学習推論を実行するためのソフトウェア開発キット「AWS Neuron SDK」も用意されています。

さっそく試してみる

下記のドキュメントを参考に進めていきます。

https://github.com/aws/aws-neuron-sdk/blob/master/docs/tensorflow-neuron/tutorial-compile-infer.md

Amazon EC2 Inf1 インスタンス作成

現時点では、バージニア(us-east-1)、オレゴン(us-west-2)の2つのリージョンで利用可能ですね。

下記のURLからEC2のインスタンス作成画面を開きます。

https://console.aws.amazon.com/ec2/home?region=us-east-1#LaunchInstanceWizard:

今回はDeep Learning AMIを選択したいと思います。

[アップデート]Tensorflow 1.15/2.0、PyTorch 1.3.1、MXNet 1.6.0-rc0をサポートしたAWS Deep Learning AMIがリリースされました #reinvent

Deep Learning AMIは似たものが他にもありますが、 Neuronがプリインストールされている方を選びます。

image.png

次にインスタンスタイプを選択します、ここでは「inf1.6xlarge」を選択しました。あとはEC2コンソール画面の指示に従ってインスタンスの作成までを行います。

image.png

機械学習のモデルをNeuron実行可能ファイル形式(NEFF)にコンパイルする

立ち上がったEC2サーバーにSSHでログインして、既にTensorFlow-Neuron環境の環境が用意されているので source activate aws_neuron_tensorflow_p36 のコマンドを打ってActiveの状態にします。

image.png

  • compile_resnet50.py

ここでは学習済みの「ResNet50」のモデルを使用します。このモデルをNeuron実行可能ファイル形式(NEFF)にコンパイルします。 下記のプログラムをcompile_resnet50.pyというファイル名で保存して

import os
import time
import shutil
import tensorflow as tf
import tensorflow.neuron as tfn
import tensorflow.compat.v1.keras as keras
from tensorflow.keras.applications.resnet50 import ResNet50
from tensorflow.keras.applications.resnet50 import preprocess_input

# Create a workspace
WORKSPACE = './ws_resnet50'
os.makedirs(WORKSPACE, exist_ok=True)

# Prepare export directory (old one removed)
model_dir = os.path.join(WORKSPACE, 'resnet50')
compiled_model_dir = os.path.join(WORKSPACE, 'resnet50_neuron')
shutil.rmtree(model_dir, ignore_errors=True)
shutil.rmtree(compiled_model_dir, ignore_errors=True)

# Instantiate Keras ResNet50 model
keras.backend.set_learning_phase(0)
keras.backend.set_image_data_format('channels_last')

model = ResNet50(weights='imagenet')

# Export SavedModel
tf.saved_model.simple_save(
    session            = keras.backend.get_session(),
    export_dir         = model_dir,
    inputs             = {'input': model.inputs[0]},
    outputs            = {'output': model.outputs[0]})

# Compile using Neuron
tfn.saved_model.compile(model_dir, compiled_model_dir)    

# Prepare SavedModel for uploading to Inf1 instance
shutil.make_archive('./resnet50_neuron', 'zip', WORKSPACE, 'resnet50_neuron')

下記のコマンドを実行します。

$ python compile_resnet50.py

image.png

下記のようにコンパイルした成果物ができていますね。

image.png

推論の実行

それでは早速、さきほどコンパイルしたモデルを使用して推論を実行してみます。

まずはコンパイルしたモデルのzipファイルを解凍し、サンプルのイメージをダウンロードします。

$ unzip -o resnet50_neuron.zip
$ curl -O https://raw.githubusercontent.com/awslabs/mxnet-model-server/master/docs/images/kitten_small.jpg
$ pip install pillow # Necessary for loading images

ちなみに、サンプルのイメージは下記の猫の画像でしたー(私、下記の猫の種類全くわかりません...)

image.png

  • infer_resnet50.py

それでは先ほどのダウンロードしたサンプルの猫の画像を推論してます。 下記のプログラムをinfer_resnet50.pyというファイル名で保存して

import os
import time
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications import resnet50

tf.keras.backend.set_image_data_format('channels_last')

# Create input from image
img_sgl = image.load_img('kitten_small.jpg', target_size=(224, 224))
img_arr = image.img_to_array(img_sgl)
img_arr2 = np.expand_dims(img_arr, axis=0)
img_arr3 = resnet50.preprocess_input(img_arr2)

# Load model
COMPILED_MODEL_DIR = './resnet50_neuron/'
predictor_inferentia = tf.contrib.predictor.from_saved_model(COMPILED_MODEL_DIR)

# Run inference
model_feed_dict={'input': img_arr3}
infa_rslts = predictor_inferentia(model_feed_dict);

# Display results
print(resnet50.decode_predictions(infa_rslts["output"], top=5)[0])

下記のコマンドを実行します。

$ python infer_resnet50.py

実行結果として下記の推論結果が返ってきました!

[('n02123045', 'tabby', 0.6918919), ('n02127052', 'lynx', 0.12770271), ('n02123159', 'tiger_cat', 0.08277027), ('n02124075', 'Egyptian_cat', 0.06418919), ('n02128757', 'snow_leopard', 0.009290541)]

TensorFlow Servingによる推論

TensorFlow Servingを用いたサンプルコードも用意されていたのでついでに実施していこうと思います!

https://github.com/aws/aws-neuron-sdk/blob/master/docs/tensorflow-neuron/tutorial-tensorflow-serving.md

まず下記のコマンドを実行してみます。

$ tensorflow_model_server_neuron \
--model_name=resnet50 \
--model_base_path=/home/ec2-user/resnet50_neuron \
--port=8500

すると下記のメッセージが出続けて上手くいかず。。

W tensorflow_serving/sources/storage_path/file_system_storage_path_source.cc:267] No versions of servable resnet50 found under base path /home/ec2-user/resnet50_neuron

しかし、こちらの記事に解決方法が記載されていました!ありがたや〜 下記のコマンドを実行してバージョンフォルダを作りましょう!

$ cd resnet50_neuron
$ pwd
/home/ec2-user/resnet50_neuron
$ mkdir 1
$ mv * 1

再度、さきほどのコマンドを実行します!

$ tensorflow_model_server_neuron \
--model_name=resnet50 \
--model_base_path=/home/ec2-user/resnet50_neuron \
--port=8500

下記のメッセージが出て上手くいったようです!

I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: resnet50 version: 1}
I tensorflow_serving/model_servers/server.cc:353] Running gRPC ModelServer at 0.0.0.0:8500 ...
  • tfserving_resnet50.py

あとはターミナルをもう1つ起動して下記のプログラムを実行します。

import numpy as np
import grpc
import tensorflow as tf
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.resnet50 import preprocess_input
from tensorflow.keras.applications.resnet50 import decode_predictions
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc

if __name__ == '__main__':
    channel = grpc.insecure_channel('localhost:8500')
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    img_file = tf.keras.utils.get_file(
        "./kitten_small.jpg",
        "https://raw.githubusercontent.com/awslabs/mxnet-model-server/master/docs/images/kitten_small.jpg")
    img = image.load_img(img_file, target_size=(224, 224))
    img_array = preprocess_input(image.img_to_array(img)[None, ...])
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'resnet50'
    request.inputs['input'].CopyFrom(
        tf.contrib.util.make_tensor_proto(img_array, shape=img_array.shape))
    result = stub.Predict(request)
    prediction = tf.make_ndarray(result.outputs['output'])
    print(decode_predictions(prediction))

下記のコマンドを実行します。

$ python tfserving_resnet50.py

見事、実行結果として下記の推論結果が返ってきました!

[[('n02123045', 'tabby', 0.6918919), ('n02127052', 'lynx', 0.12770271), ('n02123159', 'tiger_cat', 0.08277027), ('n02124075', 'Egyptian_cat', 0.06418919), ('n02128757', 'snow_leopard', 0.009290541)]]

まとめ

AWS Neuron SDKがあるので既存のコードを数行変えるだけで導入できそうなのが良いですね。 今後はSageMakerやPyTorchへの対応も予定されているということで、機械学習を利用しているケースで今後多くの活用が期待されますね。