Sagemaker Serverless Inference (まだPreview)で独自のアルゴリズムで学習した骨格検知モデルをリアルタイム推論してみた

2022.02.04

せーのでございます。

先日、骨格検知のスタンダードでもあるOpenposeを変更して軽くしたアルゴリズム「Real-time 2D Multi-Person Pose Estimation on CPU: Lightweight OpenPose」をSagemakerコンテナに注入して学習したモデルを使って推論処理をしてみました。

今日はこのアルゴリズムを去年のRe:Invent 2021で発表されたばかりの新機能「Sagemaker Serverless Inference」を使ってサーバレス化してみたいと思います。

Sagemaker Serverless Inferenceとは

Sagemaker Serverless Inferenceとはその名の通り、Sagemakerを使ったリアルタイム推論をサーバレスで行えるサービスです。リクエストが来たときだけAWS側でサーバを立ち上げて推論処理をしてくれるので余計なコストの削減につながります。

現在はまだプレビュー中で限られたリージョンでのみ使用できますが、断続的な推論のリクエストが来るケースなど使いみちは多そうですね。

SDKはまだ整備途中

前回のブログではAWSが提供するSagemaker SDKを使って推論ロジックを作っていたのでそのまま流用したいと思っていたのですが、実際ロジックを組んでいくと

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-d576fe1e4d54> in <module>
     19 
     20 # デプロイ
---> 21 predictor = serverless_model.deploy(**deploy_params)

~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/model.py in deploy(self, initial_instance_count, instance_type, serializer, deserializer, accelerator_type, endpoint_name, tags, kms_key, wait, data_capture_config, serverless_inference_config, **kwargs)
    797                 self._base_name = "-".join((self._base_name, compiled_model_suffix))
    798 
--> 799         self._create_sagemaker_model(instance_type, accelerator_type, tags)
    800 
    801         serverless_inference_config_dict = (

~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/model.py in _create_sagemaker_model(self, instance_type, accelerator_type, tags)
    273                 /api/latest/reference/services/sagemaker.html#SageMaker.Client.add_tags
    274         """
--> 275         container_def = self.prepare_container_def(instance_type, accelerator_type=accelerator_type)
    276 
    277         self._ensure_base_name_if_needed(container_def["Image"])

~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/pytorch/model.py in prepare_container_def(self, instance_type, accelerator_type)
    231             if instance_type is None:
    232                 raise ValueError(
--> 233                     "Must supply either an instance type (for choosing CPU vs GPU) or an image URI."
    234                 )
    235 

ValueError: Must supply either an instance type (for choosing CPU vs GPU) or an image URI.

このように「インスタンスタイプが設定されていないよ」というエラーが出ました。サーバレスなのでインスタンスタイプは設定していないのですが、どうやらそれは許されない様子。
元となるSDKがあるGithubを見てみると

https://github.com/aws/sagemaker-python-sdk/pull/2831

Add support for Liberty feature aka serverless inference:

Add sagemaker.serverless.serverless_inference_config as the configuration class for serverless inference

Add support in sagemaker.estimator, sagemaker.model, sagemaker.tensorflow.model to let users deploy serverless endpoint by passing the configuration object.

3.Add support in sagemaker.session to generate production variants with serverless config and also create endpoints using serverless configurations.

Detailed Design is in this doc

どうやら前回使っていたPytorchModelに対してはまだServerless Inferenceの実装がされていないようです。
ですのでboto3を使ってAPIを叩いていくことで実装することにしました。

流れ

APIを使ったServerless Inferenceの実装はこのようになります。

整理すると

  • モデルを作る
  • エンドポイント設定を作る(ここでサーバレスを指定)
  • エンドポイントを作る
  • 呼び出す

という流れです。基本は通常のSagemakerのリアルタイム推論サーバを構築する手段と変わらないですね。ではやっていきましょう。

やってみた

まずは構築用のSagemaker Notebooksを作成します。Pytorchを使うので「conda_pytorch_latest_p36」を選びました。Sagemakerのリージョンはap-northeast-1(東京)です。

最新の機能を使うのでライブラリを最新にアップデートしておきます。

import sys
import IPython
install_needed = True  # Set to True to upgrade
if install_needed:
    print("installing deps and restarting kernel")
    !{sys.executable} -m pip install -U pip
    !{sys.executable} -m pip install -U sagemaker
    IPython.Application.instance().kernel.do_shutdown(True)

Sagemakerライブラリとboto3をインポートし

import sagemaker
import boto3

sagemaker.__version__
#client setup
client = boto3.client(service_name="sagemaker")
runtime = boto3.client(service_name="sagemaker-runtime")

'2.74.0'

IAMロールを取得します。

from sagemaker import get_execution_role
role = get_execution_role()

これで準備は完了です。

CreateModelAPIでモデルを作成

まずはモデルを作成します。

ここで重要なのはAPIにはSDKのように推論に使うライブラリを入れるプロパティがありません。
SDKでは下のコードのようにモデルのパスの他に推論コードに必要なライブラリを入れるsource_dirという引数があるのですが、APIにはありません。

from sagemaker.pytorch.model import PyTorchModel

s3_path="s3://XXXXXXXap-northeast-1/outputs/sagemaker-pytorchXXXXXXXXX/output/model.tar.gz"


pytorch_model = sagemaker.pytorch.model.PyTorchModel(model_data=s3_path,
                             role=role,
                             framework_version='1.4.0',
                             py_version="py3",
                             source_dir='inference',
                             entry_point="inf.py")

ですので、APIではモデルを固めているmodel.tar.gzファイルの中に推論コードを入れ込んで、まとめて固めます。

前回のSDKを使って推論サーバを作った際にできたモデルをマネージメントコンソールから確認してみます。

コード上で指定していたS3のパスと「モデルデータの場所」として指定されているS3のパスが違いました。つまりSDKの中でコード上のS3パスからダウンロードしてきて、コードを入れて固め直しているかと思います。

※後ほどSDKのコードを確認してみた所、固め直しているような場所を発見しました。

この固め直したパスから直接model.tar.gzをダウンロードして、解凍してみます。

学習したモデルと同じレベルでcodeというディレクトリが作られ、その中にsource_dirの中身がまるっと入っていました。つまり、これと同じものを作ってAPIのmodelを指定するパスに入れればOKのようです。今回は固め直すコードは省略して、このSDKで作られたmodel.tar.gzを使いたいと思います(3分クッキング風)。

boto_session = boto3.session.Session()
region = boto_session.region_name

image_uri = sagemaker.image_uris.retrieve(
    framework='pytorch',
    region=region,
    version='1.4.0',
    py_version="py3",
    image_scope='inference',
    instance_type='ml.t2.medium'
)

s3_repack_path="s3://sagemaker-ap-northeast-1-XXXXXXXXX/pytorch-inference-2022-02-03-10-09-31-058/model.tar.gz"
model_name = "ServerlessTest"

# dummy environment variables
byo_container_env_vars = {"SAGEMAKER_CONTAINER_LOG_LEVEL": "20",
                          "SAGEMAKER_PROGRAM": "inf.py",
                          "SAGEMAKER_REGION": region
                         }
create_model_response = client.create_model(
 ModelName=model_name,
 Containers=[
 {
 "Image": image_uri,
 "Mode": "SingleModel",
 "ModelDataUrl": s3_repack_path,
 "Environment": byo_container_env_vars,
 }
 ],
 ExecutionRoleArn=role,
)

print("Model Arn: " + create_model_response["ModelArn"])

Model Arn: arn:aws:sagemaker:ap-northeast-1:XXXXXXXXXXXXX:model/ServerlessTest

推論サーバの元となるコンテナはフレームワークの種類やバージョンが決められていますので、こちらを参考に推論に必要なコンテナを選択しましょう。決めたらsagemaker.image_uris.retrieveメソッドを使ってコンテナのimage_uriを取得します。もちろんこのリンクから直接コピーして文字列で書き込んでもOKです。
その他の環境変数はSDKで作られたモデルと同じものを使いました。これで無事モデルの完成です。

CreateEndpointConfigでServerlessConfigを設定

次にエンドポイント設定を作ります。これはGUIでもできるようですが、せっかくなのでコードで書いてみたいと思います。

epc_name="ServerlessTestConfig"

endpoint_config_response = client.create_endpoint_config(
    EndpointConfigName=epc_name,
    ProductionVariants=[
        {
        "VariantName": "AllTraffic",
        "ModelName": model_name,
        "ServerlessConfig": {
        "MemorySizeInMB": 4096,
        "MaxConcurrency": 1,
        },
        },
    ],
)

print("Endpoint Configuration Arn: " + endpoint_config_response["EndpointConfigArn"])

Endpoint Configuration Arn: arn:aws:sagemaker:ap-northeast-1:XXXXXXXXXXXX:endpoint-config/ServerlessTestConfig

ここでサーバレスに関する設定(ServerlessConfig)をするのが今回の最大ポイントとなります。

サーバレスに関する設定は

  • メモリサイズ: 1024 MB、2048 MB、3072 MB、4096 MB、5120 MB、6144MBから選択します。メモリサイズによって使えるvCPUの数が違い、値段もかわります。
  • 同時接続数: このコンテナが同時に立ち上げられる最大数を設定します。エンドポイントごとの最大は50まで、アカウントのリージョンごとにすべてのサーバーレスエンドポイント間で共有できる同時実行性の合計は200となります。これは上限緩和が可能なようなので、やり方がよくわからない方はぜひメンバーズを利用して上限緩和をしてみてください。

の2つです。ちなみにサーバレス推論で与えられるストレージは5GBとなります。

CreateEndpointでエンドポイントを作成

最後に今までの設定を使ってエンドポイントを作ります。

endpoint_name = "ServerlessTestEndpoint"
create_endpoint_response = client.create_endpoint(
    EndpointName=endpoint_name,
    EndpointConfigName=epc_name,
)

print("Endpoint Arn: " + create_endpoint_response["EndpointArn"])

# wait for endpoint to reach a terminal state (InService) using describe endpoint
import time
describe_endpoint_response = client.describe_endpoint(EndpointName=endpoint_name)
while describe_endpoint_response["EndpointStatus"] == "Creating":
 describe_endpoint_response = client.describe_endpoint(EndpointName=endpoint_name)
 print(describe_endpoint_response["EndpointStatus"])
 time.sleep(15)

describe_endpoint_response

エンドポイントの作成には数分かかるため、経緯がわかるようにステータスを表示させるロジックを下に書いてあります。

これでサーバレス推論の準備が整いました。

invoke_endpointでエンドポイントを呼び出す

では実際に呼び出してみたいと思います。ここは前回のブログと同じものになります。

まずテスト用にアップしていた画像を読み込み、確認として表示します。

import matplotlib.pyplot as plt

import numpy as np
import cv2


filename = 'test/test.jpg'

# インライン表示
%matplotlib inline

# Read image into memory
payload = None
with open(filename, 'rb') as f:
    payload = f.read()

img = cv2.imdecode(np.frombuffer(payload, dtype='uint8'), cv2.IMREAD_UNCHANGED)
#画像の表示
plt.imshow(img) 
plt.show()

そしてエンドポイント名を指定して推論処理を行います。

import json

Request.

response = runtime.invoke_endpoint( EndpointName=endpoint_name, ContentType='application/x-image', Accept='application/json', Body=bytearray(payload) )

response_dict = json.loads(response['Body'].read().decode("utf-8")) print(json.dumps(response_dict)) [/pytnon]

{"results": [[[585, 176, 0.7670751214027405], [562, 187, 0.9505834579467773], [588, 195, 0.9093048572540283], [581, 172, 0.28749969601631165], [570, 153, 0.5822606086730957], [532, 176, 0.9301615357398987], [525, 131, 0.8460462093353271], [532, 93, 0.6852066516876221]], [[270, 165, 0.8985937237739563], [285, 183, 0.9405729174613953], [311, 180, 0.8065033555030823], [330, 146, 0.8526697158813477], [315, 112, 0.9707766175270081], [258, 187, 0.8102609515190125], [255, 172, 0.5221396088600159], [258, 172, 0.3417767882347107]], [[382, 138, 0.962157130241394], [390, 180, 0.9582875967025757], [438, 176, 0.8330955505371094], [450, 198, 0.7568647265434265], [427, 202, 0.43543940782546997], [345, 187, 0.9023558497428894], [348, 210, 0.3467266857624054], [356, 198, 0.1827089786529541]]]}

無事、推論が成功しました!

注意点

前回SDKで作ったときよりもレスポンスが遅いかな、と感じました。

サーバレスなので、しばらく使わないとコールドスタート(プロビジョニングから始まるので処理に時間がかかる)になります。CloudwatchからSagemakerのModelSetupTimeを見るとコールドスタートの時間を測ることができます。

ですが私の場合ログを確認したところ、そもそもの推論にかかる時間がSDKで作ったときよりもかかっているようです。

ですので

  • メモリサイズを上げる
  • コンテナに指定したターゲットインスタンスタイプを変える
  • GPU対応にする

などしたらもう少し早くなるかな、と思っています。

まとめ

以上、Sagemakerの新機能、Sagemaker Serverless Inferenceを使って独自アルゴリズムをリアルタイム推論してみました。サーバレス化自体はAPIを使えばそんなに難しい処理ではないので、ぜひ試してみていただければと思います。

参考リンク