BentoML

-·2022년 1월 17일
2

강의정리 - MLOps

목록 보기
15/18
post-custom-banner

네이버 부스트캠프 ai tech 변성윤님의 model serving 강의자료를 보며 실습하고 정리하는 글입니다. 틀린점이나, 문제가 있다면 말씀해주세요 :)

변성윤님이 직접 정리해주신 bentoml !
후에는 위에걸로 다시 실습해보자.

목차

  • BetoML
    -- Introduction
    -- BentoML 소개
    -- BentoML 특징
    -- BentoML이 해결하는 문제

  • BentoML 시작하기
    -- BentoML 설치하기
    -- BentoML 사용 Flow

  • BentoML Component
    -- BentoService
    -- Service Environment
    -- Model Artifact
    -- Model Artifact Metadata
    -- Model Management & Yatai
    -- API Function and Adapters
    -- Model Serving
    -- Labels
    -- Retrieving BentoServices
    -- WEB UI

  • BentoML으로 Serving 코드 리팩토링하기
    -- BentoService 정의하기
    -- Model Pack

BetoML

Introduction

  • FastAPI로 직접 머신러닝 서버 개발
    -- 1~2개의 모델을 만들 때는 시도하면서 직접 개발 가능

  • 만약 30개~50개의 모델을 만들어야한다면?
    -- 많은 모델을 만들다보니 반복되는 작업이 존재(Config, FastAPI 설정 등)

  • 여전히 Serving은 어렵다.

이 조차도 더 빠르게 간단하게 하고 싶다. 더 쉽게 만들고 싶다. 추상화 불가능할까?

더 쉬운 개발을 위해 본질적인 "serving"에 특화된 라이브러리를 원하게 됨
이런 목적의 라이브러리들이 점점 등장하기 시작

BentoML 소개

Serving Infra는 모든 회사에서 사용될 수 있는 Base Component
많은 라이브러리가 등장하고 있음
모든 라이브러리는 해결하고 하는 핵심 문제가 존재
어떻게 문제를 해결했는지가 다른 라이브러리

Serving에 집중하는 가벼운 Library, BentoML !
2019부터 개발 시작해서 최근 가파른 성장
관련 Library

BentoML 특징

  • 쉬운 사용성
  • Online/Offline Serving 지원
  • TF,PyTorch,Keras,XGBoost등 major 프레임워크 지원
  • Docker, Kubernetes, AWS, Azure 등 배포 환경 지원 및 가이드 제공
  • Flask 대비 100배의 처리량
  • 모델 저장소(Yatai) 웹 대시보드 제공
  • 데이터 사이언스와 DevOps 사이의 간격을 이어주며 높은 성능의 Serving이 가능하게 함

BentoML이 해결하는 문제

문제 1: Model Serving Infra의 어려움

  • serving을 위해 다양한 라이브러리, Artifact, Asset 등 사이즈가 큰 파일을 패키징
  • Cloud Service에 지속적인 배포를 위한 많은 작업이 필요
  • BentoML은 CLI로 이 문제의 복잡도를 낮춤(CLI 명령어로 모두 진행 가능하도록)

문제 2: Online Serving의 Monitoring 및 Error Handling

  • Online Serving으로 API형태로 생성
  • Error 처리, Logging을 추가로 구현해야함
  • BentoML은 Python Logging Module을 사용해 Access Log, Prediction Log를 기본으로 제공
  • Config를 수정해 Logging도 커스텀할 수 있고, prometheus 같은 Metric 수집 서버에 전송할 수 있음

문제 3: Online Serving 퍼포먼스 튜닝의 어려움

  • BentoML은 Adaptive Micro Batch 방식을 채택해 동시에 많은 요청이 들어와도 높은 처리량을 보여줌

BentoML 시작하기

BentoML 설치하기

  • BentoML은 Python 3.6 이상 버전을 지원
  • pyenv 등으로 python version을 3.8으로 설정
  • 가상환경설정(virtualenv or poetry)
pip install bentoml

BentoML 사용 Flow

1.모델 학습 코드 생성

2.Prediction Service Class 생성

3.Prediction Service에 모델 저장(Pack)

4.(Local) Serving

5.Docker Image Build(컨테이너화)

6.Serving 배포

모델 학습 코드 iris Classifier

from sklearn import svm
from sklearn import datasets

clf = svm.SVC(gamma='scale')
iris = datasets.load_iris()
X,y= iris.data, iris.target
clf.fit(X,y)

(기존에) Serving하기 위해 해야하는 작업

  • FastAPI Web Server 생성
  • Input, Output 정의
  • 의존성 작업(requirements.txt, Docker 등)

1. BentoService를 활용해 Prediction Service Class 생성

예측할 때 사용하는 API를 위한 Class

#bento_service.py

import pandas as pd
from bentoml import env, artifacts, 
from bentoml.adapters import DataframeInput
from bentoml.frameworks.sklearn import SklearnModelArtifact

@env(infer_pip_packages=True) 
@artifacts([SklearnModelArtifact('model')])
class IrisClassifier(BentoService):

	@api(input=DataframeInput(), batch=True) # /predict로 접근 가능
	def predict(self, df: pd.DataFrame):
		"""
		Doc String !!!
		"""
		return self.artifacts.model.predict(df)
  • @env : 파이썬 패키지, install script 등 서비스에 필요한 의존성을 정의

  • @artifacts : 서비스에서 사용할 Artifact 정의 => Sklearn

  • BentoService를 상속하면, 해당 서비스를 Yatai(모델 이미지 레지스트리)에 저장

  • @api : API생성
    -- Input과 Output을 원하는 형태(Dataframe, Tensor, JSON 등)으로 선택할 수 있음
    -- Doc String으로 Swagger에 들어갈 내용을 추가할 수 있음

  • @artifacts에 사용한 이름을 토대로 self.artifacts.model로 접근
    -- API에 접근할때 해당 Method 호출

2. Prediction Service 저장(Pack)

학습한 모델을 Prediction Service에 Pack

#bento_packer.py
#모델 학습
from sklearn import svm
from sklearn import datasets

clf = svb.SVC(gamma='scale')
iris = datasets.load_iris()
X,y= iris.data, iris.target
clf.fit(X,y)

from bento_service import IrisClassifier
#IrisClassifier 인스턴스 생성
iris_classifier_service= IrisClassifier()
iris_classifier_service.pack('model',clf)

#Model Serving을 위한 서비스를 Diskdp 저장
saved_path = iris_classifier_service.save()
  • bento_service에 정의한 Prediction Service

  • Model Artifact를 주입

  • BentoML Bundle : Prediction Service를 실행할 때 필요한 모든 코드, 구성이 포함된 폴더, 모델 제공을 위한 바이너리

CLI에서 bento_packer.py 실행 => Saved to ~ 경로가 보임

BentoML에 저장된 Prediction Service 확인

bentoml list

BentoML에 저장된 Prediction Service 폴더로 이동 후, 파일확인
cd /Users/{User name}/bentoml/repository
tree 명령어를 통해 파일 구조 확인

우리가 생성한 bento_service.py
Code와 동일

우리가 만들지 않은 init.py

  • create_bento_service_cli

bentoml.yml에 모델의 메타정보가 저장됨

  • 패키지환경
  • API Input/Output, Docs 등

Dockerfile도 자동으로 생성되며, 설정을 가지고 설치하는 코드

다른 이름의 모델이 Pack되면 어떻게 될까?!
bento_service.py의 IrisClassifier 클래스를 IrisClassifier1로 수정
(bento_packer.py의 import부분도 수정)한 후 코드 실행

새로운 IrisClassifier1 이 생성됨

bentoml list

3. Serving

다음 명령어로 Serving

bentoml serve IrisClassifier:latest

웹서버 실행
localhost:5000로 접근하면 Swagger UI가 보임

우리가 생성한 /predict를 클릭하면 코드에서 정의한 내용을 볼 수 있고, API Test도 가능
Try it out 클릭
Request Body에 임의로 Input을 넣고 Execute !

Curl, Request URL이 보이며, Response 400

이번엔 파라미터를 맞춰서 Execute !

Response 200에 예측값 0 !

BentoML serve한 터미널에선 다음과 같은 로그가 발생

로그는 ~/bentoml/logs에 저장됨

cat predict.log

로 위의 로그 확인 가능

터미널에서 curl로 Request해도 정상적으로 실행됨

curl -i\
--header "Content-Type: application/json"\ 
--request POST\ 
--data'[[5.1,3.5,1.4,02.]]'\
http://localhost:5000/predict

4. Yatai Service 실행

bentoml yatai-service-start

Web UI : localhost:3000

5. Docker Image Build

bentoml containerize IrisClassifier:latest -t iris-classifier

조금 조금 오래 오래 기다리면 Container Image가 빌드됨

Docker Image로 빌드된 이미지 확인

docker images

docker명령어나 FastAPI를 사용하지 않고 웹 서버를 띄우고, 이미지 빌드 !
=> 예전보다 더 적은 리소스로 개발 가능

BentoML Component

BentoService

  • bentoml.BentoService 는 에측 서비스를 만들기 위한 베이스 클래스
  • @bentoml.artifacts : 여러 머신러닝 모델 포함할 수 있음
  • @bentoml.api : Input/Output 정의
    -- API함수 코드에서 self.artifacts.{ARTIFACT_NAME} 으로 접근할 수 있음
  • 파이썬 코드와 관련된 종속성 저장
# bento_svc.py
import bentoml
from bentoml.adapters import JsonInput
from bentoml.frameworks.keras import KerasModelArtifact
from bentoml.service.artifacts.common import PickleArtifact

@bentoml.env(pip_packages=['tensorflow', 'scikit-learn', 'pandas'],\
	docker_base_image="bentoml/model-server:0.12.1-py38-gpu")
@bentoml.artifacts([KerasModelArtifact('model'), PickleArtifact('tokenizer')])
class TensorflowService(bentoml.BentoService):
	@api(input=JsonInput())
    def predict(self,parsed_json):
    	return self.artifacts.model.predict(input_data)
#bento_packer.py
from bento_svc import TensorflowService
#OPTIONAL: to remove tf memory limit on our card
config.experimental.set_memory_growth(gpu[0],True)

model = load_model()
tokenizer=load_tokenizer()

bento_svc= TensorflowService()
bento_svc.pack('model',model)
bento_svc.pack('tokenizer', tokenizer)

saved_path= bento_svc.save()

Service Environment

  • 파이썬 관련 환경, Docker 등을 설정할 수 있음
  • @bentoml.env(infer_pip_packages=True): import를 기반으로 필요한 라이브러리 추론
  • requiremnets_txt_file 을 명시할 수도 있음
  • pip_pacakages= [] 를 사용해 버전을 명시할 수 있음
  • docker_base_image를 사용해 Base Image를 지정할 수 있음
  • setup_sh를 지정해 Docker Build 과정을 커스텀할 수 있음
@bentoml.env(
	requirements_txt_file="./requirements.txt"
)
class ExamplePredictionService(bentoml.BentoService):
	@bentoml.api(input=DataframeInput(), batch=True)
    	def predict(self, df):
    		return self.artifacts.model.predict(df)
#setup_sh
@bentoml.env(
	infer_pip_package=True,
    	setup_sh="./my_init_script.sh"
)
class ExamplePredictionService(bentoml.BentoService):
	...

@bentoml.evn(
	infer_pip_packages=True,
    	setup_sh="""\
#!/bin/bash
set -e

apt-get install --no-install-recommands nvidia-driver-430
...
"""
)
class ExamplePredictionService(bentoml.BentoService):
	...
  • @bentoml.ver를 사용해 버전 지정할 수 있음
from bentoml import ver, artifacts
from bentoml.service.artifacts.common import PickleArtifact

@ver(major=1, minor=4)
@artifacts([PickleArtifact('model')])
class MyMLService(BentoService):
	pass

svc= MyMLService()
svc.pack("model",trained_classifer)
svc.set_version("2022-01.iteration20")
svc.save()
#The final produced BentoService bundle will have version:
# "1.4 2019-08.iteration20"

Model Artifact

  • @bentoml.artifacts : 사용자가 만든 모델을 저장해 pretrain model을 읽어 Serialization, Deserialization
  • 여러 모델을 같이 저장할 수 있음
  • A 모델의 예측 결과를 B모델의 Input으로 사용할 경우
  • 보통 하나의 서비스 당 하나의 모델을 권장
import bentoml
from bentoml.adapters import DataframeInput
from bentoml.frameworks.sklearn import SklearnModelArtifact
from bentoml.frameworks.xgboost import XgboostModelArtifact

@bentoml.env(infer_pip_packages=True)
@bentoml.artifacts([
	SklearnModelArtifact("model_a"),
    	XgboostModelArtifact("model_b")
])
class MyPredictionService(bentoml.BentoService):
	@bentoml.api(input=DataframeInput(), batch=True)
    	def predict(self,df):
    		#assume the output of model_a will be the input of model_b in this exam
        	df = self.artifacts.model_a.predict(df)
        	return self.artifacts.model_b.predict(df)

svc=MyPredictionService()
svc.pack('model_a', my_sklearn_model_object)
svc.pack('model_b", my_xgboost_model_object)
svc.save()

다음 라이브러리의 Artifact를 지원

Model Artifact Metadata

  • 해당 모델의 Metadata(Metric-Accuracy, 사용한 데이터셋, 생성한 사람, Static 정보 등)
  • Pack에서 metadata인자에 넘겨주면 메타데이터 저장
  • 메타데이터는 immutable
svc=MyPredictionService()
svc.pack(
	'model_a',
    	my_sklearn_model_object,
    	metadata={
    		'precision_score': 0.876,
        	'created_by': 'JODONG2'
	}
)
svc.pack(
	'model_b',
    	my_xgboost_model_object,
    	metadata={
    		'precision_score': 0.792,
        	'mean_absolute_error': 0.88
	}
)
svc.save()
  • Metadata에 접근하고 싶은 경우
    1. CLI
bentoml get model:version
    1. REST API
bentoml serve 한 후, /metadata 로 접근
    1. Python
from bentoml import load
svc = load('path_to_bento_service')
print(svc.artifacts['model'].metadata)

Model Management & Yatai

  • BentoService의 save 함수는 BentoML Bundle을 ~/bentoml/repository/{서비스이름}/{서비스버전}에 저장
  • 모델 리스트 확인
bentoml list
  • 특정 모델 정보 가져오기
bentoml get Iris Classifier
  • YataiService : 모델 저장 및 배포를 처리하는 컴포넌트
bentoml yatai-service-start

API Function and Adapters

  • BentoService API는 클라이언트가 예측서비스에 접근하기 위한 End Point 생성

  • Adapter는 Input/Output을 추상화해서 중간 부분을 연결하는 Layer
    -- 예) csv파일 형식으로 예측 요청할 경우 => DataframeInput을 사용하고 있으면 내부적으로 pandas.DataFrame객체로 변환하고 API함수에 전달함

  • @bentoml.api를 사용해 입력받은 데이터를 InputAdapter 인스턴스에 넘김

  • 데이터 처리하는 함수도 작성할 수 있음

from my_lib import preprocessing,postprocessing, fetch_user_profile_from_database
class ExmplePredictionService(bentoml.BentoService):
	@bentoml.api(input=DataframeInput(), batch=True)
	def predict(self,df):
		user_profile_column = fetch_user_profile_from_database(df['user_id'])
		df['user_profile'] = user_profile_column
		model_input = preprocessing(df)
		model_output = self.artifacts.model.predict(model_input)
		return postprocessing(model_output)
  • Input 데이터가 이상할 경우 오류 코드를 반환할 수 있음
from typing import List
from bentoml import env, artifacts, api, BentoService
from bentoml.adapters import JsonInput
from bentoml.types import JsonSerializable, InfereceTask # type annotations are optional

@env(infer_pip_packages=True)
@artifacts([SklearnModelArtifact('classifier')])
class MyPredictionService(BentoService):
	@api(input=JsonInput(), batch=True)
	def predict_batch(self, parsed_json_list: List[JsonSerializable], tasks: List[InferenceTask]):
		model_input = []
		for json, task in zip(parsed_json_list, tasks):
			if "text" in json:
				model_input.append(json['text'])
			else:
				task.discard(http_status=400,err_msg="input json must contain 'text' field")
		results= self.artifacts.classifier(model_input)
		return results
    
  • 세밀하게 Response 를 정의할 수 있음
import bentoml
from bentoml.types import JsonSerializable, InferenceTask, inferenceError

class MyService(bentoml.BentoService):
	@bentoml.api(input=JsonInput(), batch= False)
	def predict(self,parsed_json: JsonSerializable, task: InferenceTask) -> InfereceResult:
		if task.http_headers['Accept'] == 'application/json':
			return InferenceResult(
				data=predictions[0],
				http_status=200,
				http_headers={"Content-Type":"application/json"},
			)
		else:
			return InferenceError(
            			err_msg="application/json output only", http_status =400
                        )
  • BentoService가 여러 API를 포함할 수 있음
from my_lib import process_custom_json_format

class ExamplePredictionService(bentoml.BentomlService):
	@bentoml.api(input=DataframeInput(), batch=True)
	def predict(self, df: pandas.Dataframe):
		return self.artifacts.model.predict(df)
	
	@bentoml.api(input=JsonInput(), batch= True)
	def predict_json(self,json_arr):
		df=process_custom_json_format(json-arr)
		return self.artifacts.model.predict(df)

Model Serving

BentoService가 벤또로 저장되면, 여러 방법으로 배포할 수 있음

  • 1. Online Serving : 클라이언트가 REST API Endpoint로 근 실시간으로 예측 요청
  • 2. Offline Batch Serving : 예측을 계산한 후, Storage에 저장
  • 3. Edge Serving : 모바일, IoT Device에 배포

Labels

Retrieving BentoServices

  • 학습한 모델을 저장한 후, Artifact bundle을 찾을 수 있음 '--target_dir flag' 사용
bentoml retrieve ModelServe --target_dir=~/bentoml_bundle/

WEB UI

  • @bentoml.web_static_content를 사용하면 웹 프론트엔드에 추가할 수 있음
    참고링크 : 링크

BentoML Command

bentoml --help

BentoML으로 Serving 코드 리팩토링하기

BentoService 정의하기

github boostcamp model serving repo 확인

import 된 모듈로 패키지 의존성을 추론해 추가
마스크 분류 모델에서 사용한 PytorchModelArtifact추가
image input을 사용해서 업르도된 이미지로부터 imageio.Array를 함수 인자로 주입
output은 json으로 클라이언트에게 제공

사용하기 전에 공식 Document를 보는 습관을 갖자

@api 데코레이터가 붙은 Method말고도 self.transform 같은 추가 Method도 사용가능

Model Pack

Service를 초기화하고 Model Load

if __name__=="__main__":
	import torch
    bento_svc = MaskAPIService()
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model= MyEfficientNet().to(device)
    state_dict = troch.load(
    	"../../../assets/mask_task/model.pth", map_location=device
	)
    model.load_state_dict(state_dict= state_dict)
    bento_svc.pack('model', model)
    saved_path = bento_svc.save()
    print(saved_path)

Special Mission

  1. BentoML 공식 문서 읽고 정리하기
  2. 기존에 작업한 FastAPI코드를 BentoML로 리팩토링
  3. BentoML => Cloud Run 배포해보기
profile
-
post-custom-banner

0개의 댓글