はじめに
AWS Lambda で Pytorch の画像AIを動かしてみました
SAM CLI で簡単に構築できるので、ちょっとした AIサービスなら低コストで実現できそうです
実行環境
- macOS Big Sur 11.4
- SAM CLI 1.24.1
- Docker 20.10.6
参考にしたもの
2021/2/12 のブログです
こちらは自然言語処理のAIを動かしています
AWS Lambda とは
AWS Lambda はサーバレスコンピューティングサービスです
開発したコードをアップロードすると、呼び出されたときだけマシンが起動し、コードを実行して、自動的に終了してくれます
何が嬉しいかというと
- サーバーがないので、サーバー構築や保守が必要ない
- マシンが起動した時間とマシンに割り当てたメモリで課金されるため、使っていない間はコストがかからない
- 大量のリクエストを受けると必要な数だけマシンが立ち上がる
- コードをアップロードし直すだけで無停止のシステム更新が可能
つまり、低コストで高可用性のシステムが構築できるわけです
ただし、万能ではありません
-
マシン起動時に時間がかかる
遅延してもいいようにするか、常時1台はマシンが起動しているようにする
-
大量アクセスで思わぬ高コストになることもあり得る
認証、アクセス制限、API Gatewayによる使用量制限などを入れる
SAM とは
SAM = サーバーレスアプリケーションモデル
Lambda や API Gateway など、サーバレスアーキテクチャの構築を支援してくれます
インストール方法はこちら
昔、むかーしむかし、 Lambda の開発には Apex というツールを使っていました
Lambda のデプロイのややこしいところを上手くラッピングしてくれていたからです
しかし、2018年に開発が停止し、リポジトリーもアーカイブされ、インストールするためのリリースも消されました
今では Lambda 以外もまとめて Terraform 管理にしています
なので、実のところ SAM を触るのは初めてでした
Lambda の新機能
少し寄り道して、 Lambda で AI を動かせたのが嬉しかった理由を書いておきます
かつて、 AWS Lambda はコードを Web コンソールから直接入力するか、 Zip に固めてアップロードするしかありませんでした
必要なパッケージがある場合、パッケージも Zip に含める必要があります
しかし、 Pytorch は非常に大きいパッケージで、 Zip によるアップロードの上限を超えます
Zip 圧縮して 50MB が上限では、とても使えません
昔も画像AIを Lambda で動かしたいと思ったことがありましたが、この容量の問題で断念していました
ところが、 2020年12月、とても素晴らしい新機能が追加されたのです
なんと、コンテナイメージが動かせるようになったのです!
もう何でもありじゃないか、、、
自分で好きなパッケージを好き放題入れて動かせます!
AIでも何でもバッチコイです!
プロジェクト作成
何はともあれ、 SAM を動かしてみましょう
まず、以下のコマンドで対話しながらプロジェクトの大枠を作ります
sam init
実行すると、以下のように表示されます
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice:
今回は 1 - AWS Quick Start Templates
を選択します
続いて、どのようにコードをアップロードするか聞かれます
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type:
当然、 2 - Image
を選びます
続いてベースになるイメージの選択です
Which base image would you like to use?
1 - amazon/nodejs14.x-base
2 - amazon/nodejs12.x-base
3 - amazon/nodejs10.x-base
4 - amazon/python3.8-base
5 - amazon/python3.7-base
6 - amazon/python3.6-base
7 - amazon/python2.7-base
8 - amazon/ruby2.7-base
9 - amazon/ruby2.5-base
10 - amazon/go1.x-base
11 - amazon/java11-base
12 - amazon/java8.al2-base
13 - amazon/java8-base
14 - amazon/dotnet5.0-base
15 - amazon/dotnetcore3.1-base
16 - amazon/dotnetcore2.1-base
Base image:
Python で動かしたいので、 4 - amazon/python3.8-base
を選びます
プロジェクト名は適当に入れてください
Project name [sam-app]:
今回は py-sam-app
とします
次に、テンプレートを選びます
Cloning from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Lambda Image Example
2 - PyTorch Machine Learning Inference API
3 - Scikit-learn Machine Learning Inference API
4 - Tensorflow Machine Learning Inference API
5 - XGBoost Machine Learning Inference API
Template selection:
2 - PyTorch Machine Learning Inference API
を選べば、それだけで AI システムの大枠が作られます
もちろん、用途に応じて Tensorflow
等を選んでも OK です
以下の表示が出れば、必要なコードは揃っています
-----------------------
Generating application:
-----------------------
Name: py-sam-app
Base Image: amazon/python3.8-base
Dependency Manager: pip
Output Directory: .
Next steps can be found in the README file at ./py-sam-app/README.md
ビルド
Lambda をコンテナで動かすので、当然コンテナをビルドする必要があります
あらかじめ、自分の IAM ユーザーに以下のサービスを自由に使える権限があることを確認してください
なければ付与してください
- Lambda
- API Gateway
- ECR
- IAM
- CloudFormation
まず、 ECR にコンテナ用のリポジトリーを作ります
aws ecr create-repository \
--repository-name py-sam-app \
--image-scanning-configuration \
scanOnPush=true
以下のようなレスポンスが返ってきます
{
"repository": {
"repositoryArn": "arn:aws:ecr:<REGION>:<AWS_ACCOUNT_ID>:repository/py-sam-app",
"registryId": "<AWS_ACCOUNT_ID>",
"repositoryName": "py-sam-app",
"repositoryUri": "<AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/py-sam-app",
"createdAt": 1625620019.0,
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": true
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
続いて、リポジトリーにプッシュするために ECR にログインしておきます
<repositoryUri>
は上記 ECR リポジトリー作成のレスポンスに入っています
aws ecr get-login-password | docker login \
--username AWS \
--password-stdin <repositoryUri>
Login Succeeded
とレスポンスがあれば OK です
続いて、作成したプロジェクトのディレクトリーに移動し、ビルドします
cd py-sam-app \
&& sam build
内部で docker build
が実行され、最終的に以下のように結果が表示されます
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
では、ローカルで一度動かしてみましょう
sam local invoke InferenceFunction --event events/event.json
以下のように結果が返ってくれば、 AI モデルが動かせています
Invoking Container created from inferencefunction:python3.8-v1
Building image.........
Skip pulling image and use local one: inferencefunction:rapid-1.24.1.
START RequestId: f034abb7-2e1f-4c44-a754-ee67be8fb419 Version: $LATEST
END RequestId: f034abb7-2e1f-4c44-a754-ee67be8fb419
REPORT RequestId: f034abb7-2e1f-4c44-a754-ee67be8fb419 Init Duration: 2.00 ms Duration: 1577.01 ms Billed Duration: 1600 ms Memory Size: 5000 MB Max Memory Used: 5000 MB
{"statusCode": 200, "body": "{\"predicted_label\": 3}"}%
少し解説します
プロジェクトディレクトリー内の template.yml
を開くと、以下のように記述されています
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 50
MemorySize: 5000
Api:
BinaryMediaTypes:
- image/png
- image/jpg
- image/jpeg
Resources:
InferenceFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
PackageType: Image
Events:
Inference:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /classify_digit
Method: post
Metadata:
Dockerfile: Dockerfile
DockerContext: ./app
DockerTag: python3.8-v1
...
ここで、どういう API で、どういう関数で、といったことが定義されているわけです
この中の InferenceFunction
を先ほど指定して実行しました
引数に指定した events/event.json
を見てみましょう
色々と書かれていますが、一番下に関数に渡す値が定義されています
...
},
"body": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0..."
}
関数の中身を少し見てみます
関数は app/app.py
に定義されています
...
def lambda_handler(event, context):
image_bytes = event['body'].encode('utf-8')
image = Image.open(BytesIO(base64.b64decode(image_bytes))).convert(mode='L')
image = image.resize((28, 28))
probabilities = model.forward(image_transforms(np.array(image)).reshape(-1, 1, 28, 28))
label = torch.argmax(probabilities).item()
...
event
から body
を取り出し、 BASE64 デコードしていますね
つまり、入力となるデータは BASE64 エンコードされた画像データということがわかります
試しに以下のサイトで body
の中の文字列をデコードしてみましょう
デコード結果は数字の 3 の手書き文字でした
MNIST dataset のようですね
つまり、このアプリは手書き数字の 0 から 9 を判別する画像 AI を動かしているわけです
デプロイ
では、ビルドしたものをクラウド上にデプロイしましょう
init
のときと同じく、対話型で進めます
sam deploy --guided
まず、スタック名を入力します
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]:
SAM は内部で AWS CloudFormation を動かしています
スタックとは、一連の関連するリソースをまとめたものです
今回で言うと、 Lambda 関数や API Gateway 、 IAM ロールなどです
これらをスタックとしてまとめて実行するため、名前をつけます
今回はここも py-sam-app
としておきます
続いてリージョンを指定します(デフォルトで問題ありません)
AWS Region [ap-northeast-1]:
次に ECR のリポジトリーを聞かれるので、作ったリポジトリーのURL(前出の repositoryUri
)を入力します
Image Repository for InferenceFunction:
デプロイ前に確認するかと聞かれますが、今回は n
で良いでしょう
#Shows you resources changes to be deployed and require a 'y' to initiate deploy
Confirm changes before deploy [y/N]:
SAM が IAM ロールを作るのを許可するかと聞かれるので、もちろん y
と答えます
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]:
API に認証がかかってないけど大丈夫?と心配してくれます
業務で使う場合はもちろん認証が必要ですが、今回はお試しなので y
です
InferenceFunction may not have authorization defined, Is this okay? [y/N]:
設定を保存するか聞かれます
保存しておけば、次回以降、今回の内容がデフォルト値になるため、 y
です
Save arguments to configuration file [Y/n]:
設定ファイル名はデフォルトのままで良いでしょう
SAM configuration file [samconfig.toml]:
環境もデフォルトにします
SAM configuration environment [default]:
これで ECR へのプッシュが始まり、続けて各リソースが作られていきます
最後に成功のメッセージが表示されます
Successfully created/updated stack - py-sam-app in ap-northeast-1
実行
上記成功メッセージの上に、 API Gateway の作成結果が表示されています
Key InferenceApi
Description API Gateway endpoint URL for Prod stage for Inference function
Value https://xxx.execute-api.ap-
northeast-1.amazonaws.com/Prod/classify_digit/
この Value のところが作成された API のエンドポイントなので、ここに向けて BASE64 の文字列(event/event.json の body の値)を投げてみましょう
curl -XPOST https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/classify_digit/ \
-d "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0..."
最初はすごく時間が掛かって、もしかしたらタイムアウトするかもしれません
{"message": "Endpoint request timed out"}%
これは、 初回呼び出し時にマシンを起動しているのと、モデルの読み込みを行っているためです
本番ではタイムアウト時間を調節するなどしてください
もう一回呼んでみましょう
すると、正しい結果が返ってきます
{"predicted_label": 3}%
ちゃんと 3 が返ってきましたね!
注意
お試しで作った関数、API Gateway、ECRリポジトリー、IAMユーザーは削除しておきましょう
API を攻撃されたら高額請求が来るかも
最後に
あとは、 Dockerfile や app.py を編集して、自分の使いたいモデルを組み込めば何でも動かせます
私は物体検出を動かしてみましたが、無事動作できました
ただし、 template.yml
の MemorySize
に書いてあるように、メモリを 5,000 MB 割り当てています
実際、物体検出を動かした時は 3,000 MB 以上使われていました
これは結構高いので、多数アクセスがある場合、結局割高になりそうです
GPU を積んでいないため、処理に時間がかかる点も考慮しましょう
アクセスが多く、速度を求められるような場合、やはり Lambda は向いていないでしょう
とはいえ、これを活かせる場面もあると思うので、今後の選択肢に入れておきます