http&server

딥러닝 모델의 API화 (AWS lambda)

식피두 2021. 1. 27. 09:55

딥러닝 모델 (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 프레임워크로 람다에 업로드하고자 한다면, 아래 블로그 글만 봐도 쉽게 시도 해볼 수 있다.

 

Scaling Machine Learning from ZERO to HERO

Scale your machine learning models by using AWS Lambda, the Serverless Framework, and PyTorch. I will show you how to build scalable deep learning inference architectures.

www.philschmid.de

 

Serverless BERT with HuggingFace and AWS Lambda

Build a serverless question-answering API with BERT, HuggingFace, the Serverless Framework, and AWS Lambda.

towardsdatascience.com

만약 serverless 툴 사용 도중 aws credentials에 관한 에러가 뜬다면...

 

AWS Lambda Serverless deploy asking for AWS provider credentials

I have configured serverless with key and secret. When i try to run serverless deploy it says: ServerlessError: AWS provider credentials not found. Learn how to set up AWS provider credentials in...

stackoverflow.com

필요한 핵심 코드 중 하나는 다음과 같다.

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까지 허용한다는 것!

 

New for AWS Lambda – Container Image Support | Amazon Web Services

With AWS Lambda, you upload your code and run it without thinking about servers. Many customers enjoy the way this works, but if you’ve invested in container tooling for your development workflows, it’s not easy to use the same approach to build applic

aws.amazon.com

관련 블로그를 찾고, 실습을 해보니 아주 잘 동작한다...

 

Deploying Deep Learning Models as Serverless API

Build a Serverless Pytorch API using AWS Lambda + Docker in just two steps

towardsdatascience.com

어떤 방식으로 도커 이미지를 람다에 업로드하는지 핵심만 정리해보면,

 

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