딥러닝 모델 (pytorch, keras, ...) 개발을 끝내고,
실 서비스를 위한 API화를 할 때
이전 까지 나는 보통 flask/gunicorn으로 감싸서 도커화 한 이후에
쿠버네티스에 띄우든, 독립된 서버에 띄우든 하는 방식을 생각해왔다.
이 때, AWS Lambda(람다)를 이용하면, API 서버를 직접 작성할 필요 없고,
특정 형식을 따르는 핸들러만 만들어 업로드하면 된다.
(물론 자잘한 config가 필요하긴 함)
API 서버 구현에서의 복잡도를 줄여주는 것 말고도
아마존웹서비스 람다 하면,
서비스 스케일 조정에 있어 매우 유연하다는 점을 빠뜨릴 수 없다.
어쨌든, 모델 및 예측 함수를 람다에 올리는 방법은 다양하다.
1. serverless 프레임워크를 이용
2. AWS의 sam(serverless application model) cli를 이용 (awscli & aws-sam-cli)
첫 번째로 시도 했던 것이
serverless 프레임워크를 이용해서 람다에 업로드하는 것이었는데,
사용법은 간편해보이지만 문제점은 라이브러리 및 코드의 크기가 250MB를 넘어서면
람다에 업로드할 수 없다는 점이다. (이 부분은 serverless 프레임워크의 문제가 아닌 람다의 제약)
추가로 주어지는 공간도 /tmp 500MB가 전부...
모델 웨잇 같은 경우엔 S3에 업로드 시켜놓고,
코드를 통해 메모리로 바로 로드시킬 수 있지만
라이브러리는 그럴 수 없었고, 이 부분에서 많은 시간을 소비한 끝에 포기하였다.
애초에 파이토치 및 토치 비전만 깔아도, 용량제한을 크게 넘어서는 문제가 있었다.
serverless 프레임워크의 플러그인을 깔고 라이브러리 압축을 하고, 필요 없는 파일을 제거해도...
해결할 수 없다. 어쨌든 unzip 상태에서 250mb를 넘으면 안되니깐..
만약 serverless 프레임워크로 람다에 업로드하고자 한다면, 아래 블로그 글만 봐도 쉽게 시도 해볼 수 있다.
만약 serverless 툴 사용 도중 aws credentials에 관한 에러가 뜬다면...
필요한 핵심 코드 중 하나는 다음과 같다.
load_model_from_s3 함수를 통해서 s3에 있는 모델을 메모리로 바로 로드하는 부분은 두고두고 유용하게 활용될 부분이라고 생각한다.
"""
ref
- https://www.philschmid.de/scaling-machine-learning-from-zero-to-hero
"""
try:
import unzip_requirements
except ImportError:
pass
import io
import os
import json
import tarfile
from utils import download_image_and_process
from text_det import TextDet
import boto3
S3_BUCKET = os.environ['S3_BUCKET'] if 'S3_BUCKET' in os.environ else 'pharmy-ocr-code'
MODEL_PATH = os.environ['MODEL_PATH'] if 'MODEL_PATH' in os.environ else 'det.tar.gz'
s3 = boto3.client('s3')
def load_model_from_s3():
try:
obj = s3.get_object(Bucket=S3_BUCKET, Key=MODEL_PATH)
bytestream = io.BytesIO(obj['Body'].read())
tar = tarfile.open(fileobj=bytestream, mode='r:gz')
for member in tar.getmembers():
if member.name.endswith('.pth'):
print('Model file is', member.name)
f = tar.extractfile(member)
model = TextDet()
return model
except Exception as e:
raise e
#--------------------
model = load_model_from_s3()
#--------------------
def detect_image(event, context):
response = {
"statusCode": 200,
"headers": {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Credentials": True
},
}
try:
body = event['body']
url = body['url']
polys = download_image_and_process(url, txt_det.process)
response["polys"] = json.dumps(polys)
return response
except Exception as e:
print(repr(e))
response['statusCode'] = 500
response['body'] = json.dumps({'error': repr(e)})
return response
두 번째 방법
그 이후에 시행착오를 겪다가
구글링 도중 작년(2020) 12월 부터 AWS 람다가 도커 컨테이너를 지원한다는 공지를 찾았다.
이 방법의 특 장점은 이미지 크기를 10GB까지 허용한다는 것!
관련 블로그를 찾고, 실습을 해보니 아주 잘 동작한다...
어떤 방식으로 도커 이미지를 람다에 업로드하는지 핵심만 정리해보면,
0. awscli 및 aws-sam-cli설치 후, aws configure 명령을 통해 IAM 생성 후 발급받은 정보 및 리전 등록
1. git clone (블로그에 있는 리포지토리 ; github.com/gokavak/lambda-docker-image-pytorch-xgboost)
2. bash pipeline.sh -s dev -r 을 통한 ECR 레지스트리 및 S3 생성
3. bash pipeline.sh -s dev -d ./pytorch-example/ 을 통한 이미지 빌드 및 배포
블로그의 내용중 잘못된 부분이 있는데,
3번 완료 후 아마존웹서비스 CloudFormation 페이지의 outputs 탭에 나오는 주소로 접근할 수 있는게 아니라
API Gateway 페이지의 Stages에서 각 스테이지 별로 접근 가능한 유알엘이 Invoke URL로 상단에 표기되어 있는데,
이 주소로 접근해야한다.
핵심 스크립트인 pipeline.sh는 다음과 같다. 변수명을 이해할 수 없어.. 좀 수정했다.
#!/bin/bash
# Remember to export the following ENV variables:
export CAPABILITIES="CAPABILITY_NAMED_IAM"
export PREFIX_LAMBDA="sam_templates/pytorch-lambda-l"
export LAMBDA_STACK_NAME="pytorch-lambda-l"
export REGISTRY_STACK_NAME="pytorch-example-l"
account=$(aws sts get-caller-identity --query Account --output text)
user=$(aws iam get-user --query User.UserName --output text)
DOCKER_REGISTRY="${account}.dkr.ecr.${REGION}.amazonaws.com"
REGION=$(aws configure get region)
while getopts ":s:rd:" OPTION
do
case $OPTION in
s)
stage=$OPTARG
stage=${stage:-dev} # stage defaults to dev if not defined
;;
r)
echo "Deploying CFN Template in stage ${stage}"
sam deploy -t registry.yaml --stack-name ${REGISTRY_STACK_NAME} --capabilities ${CAPABILITIES} --parameter-overrides Project=${REGISTRY_STACK_NAME} Stage=${stage} User=${user} --region ${REGION} --force-upload --no-confirm-changeset
;;
d)
path=$OPTARG
path=${path:-.}
echo "Retrieve Repository Name"
export ECR_NAME=$(aws cloudformation --region "${REGION}" describe-stacks --stack-name "$REGISTRY_STACK_NAME" --query "Stacks[0].Outputs[1].OutputValue")
echo "Retrieve Bucket Name"
export BUCKET=$(aws cloudformation --region "${REGION}" describe-stacks --stack-name "$REGISTRY_STACK_NAME" --query "Stacks[0].Outputs[0].OutputValue")
echo "Building template"
sam build -t template.yaml
echo "Deploying template"
sam deploy --stack-name ${LAMBDA_STACK_NAME} --capabilities "CAPABILITY_NAMED_IAM" --parameter-overrides Project="${LAMBDA_STACK_NAME}" Stage=${stage} --s3-bucket ${BUCKET//\"} --s3-prefix ${PREFIX_LAMBDA} --region "${REGION}" --image-repository "${DOCKER_REGISTRY}/${ECR_NAME//\"}" --force-upload --no-confirm-changeset
;;
\?)
echo "Usage: pipeline.sh [-s] <stage> [-r] [-d] <path>"
exit 1
;;
esac
done
'http&server' 카테고리의 다른 글
ec2 혹은 서버에 애플리케이션 서버를 https로 띄워야할 때 (0) | 2021.05.30 |
---|---|
celery + flask 를 이용한 파이썬 비동기 처리 API 구현 (0) | 2021.01.05 |
04. Nginx 기초 사용법 정리 2 (location, proxy, cache) (0) | 2020.07.29 |
03. Nginx 기초 사용법 정리 1 (conf, directives) (0) | 2020.07.29 |
02. flask + gunicorn + docker 조합 (0) | 2020.07.29 |