Skip to main content

BentoML 넌 누구냐!

Overview

(공식 문서 내용을 기반으로 추가 내용을 덧붙여 정리함)

What is BentoML?

BentoMLis a unified AI application framework for building reliable, scalable, and cost-efficient AI applications. It provides an end-to-end solution for streamlining the deployment process, incorporating everything you need for model serving, application packaging, and production deployment.

설명에 나와있듯, BentoML의 초점은 model serving, application packaging, and production deployment 에 있다. 실제로 production-level에서의 모델 서빙을 위해 BentoML을 활용하는 use-case가 다수 존재한다.

그런데, 왜 이름이 BentoML일까?

벤또는 도시락을 일컫는 일본어이다. - 나무위키

머신러닝 모델을 서빙할 때 필요한 요소들을 한 번에 제공한다는 취지이다. 내 머신러닝 도시락을 받아랏!

Who is BentoML for?

  1. 안정적이고, 확장 가능하며, 비용 효율적인 방식으로 머신 러닝(ML) 모델을 프로덕션에 도입하려는 팀
  2. 사전 학습된 최신 모델(state-of-the-art pretrained model)을 애플리케이션에 쉽게 통합하려는 AI 서비스 개발자 [예제]
  3. 생성한 모델을 간편하게 프로덕션에 배포하고 싶은 팀

Why BentoML?

BentoML은 (1) 통합 배포 형식을 제공하고, (2) 간소화된 AI 아키텍처를 사용하고, (3) 원하는 곳에 배포할 수 있도록 도와준다. 이를 위한 5가지 특징을 소개한다.

[1] Streamline distribution with a unified format: 통합 배포 형식 Bento

BentoML은 통합 배포 형식인 Bento라는 파일 아카이브를 통해 배포 프로세스를 간소화 한다. BentoML에서 제공하는 SDK를 사용하면, 배포에 필요한 모든 구성 요소를 손쉽게 Bento로 패키징할 수 있다.

Bento를 생성할 때 서비스를 위한 파일 경로를 설정해주면 BentoML이 자동으로 API 서버를 생성 한다. API 서버는 REST API, gRPC, 장기 실행 추론 작업을 지원한다. 또한, Bento에는 자동으로 생성된 Dockerfile이 있어서, 프로덕션 배포를 위한 컨테이너 작업을 손쉽게 수행 할 수 있다.

[2] Build applications with any AI models: 다양한 ML 프레임워크와의 호환

BentoML은 팀이 선호하는 도구로 모든 AI 애플리케이션을 구축할 수 있는 유연성과 편의성을 제공합니다. 모델 허브 에서 가져온 모델이나, 다양한 프레임워크(ex> PyTorch, TensorFlow, Keras, Scikit-Learn, XGBoost 등)로 구축한 자체 모델을 사용할 수 있고, 편하게 애플리케이션으로 빌드할 수 있다.

또한, Large Language Model (LLM) inference,Generative AI,embedding creation, andmulti-modal AI applications 에 대한 native support를 지원하고, 머신러닝을 위한 다앙한 툴(ex> MLFlowLangChainKubeflowTritonSparkRay)과의 통합을 지원한다.

[3] Inference optimization for AI applications: 추론 최적화

BentoML은 AI 애플리케이션의 빠른 응답시간, 높은 처리량, 확장 가능하고 비용 효율적인 서빙 인프라 를 제공한다. 이를 위해 지원하는 기능은 다음과 같다.

  • Model inference parallelization (모델 추론 병렬화)
  • Adaptive batching (적응형 배치)
  • OpenLLM과 같은 특정 모델 아키텍처에 대한 최적화
  • ONNX-runtime, TorchScript와 같은 고성능 런타임에 대한 지원

[4] Simplify modern AI application architectures: 다양한 추론 방식 지원 및 손쉬운 디버깅

BentoML은 복잡한 AI 워크로드를 손쉽게 확장 및 실행할 수 있도록 도와준다.

  • 여러 모델의 동시(parallel), 순차(sequential) 실행
  • GPU 사용 지원
  • k8s 동작 지원
  • Mac, Window, Linux 등 다양한 OS에서의 로컬 실행 및 디버깅 지원

[5] Build once. Deploy anywhere: Dockerfile을 활용한 손쉬운 배포

BentoML은 저장된 모델 형식, 서비스 API 정의, Bento 빌드 프로세스를 표준화하여 다양한 배포 오셥을 제공한다. 지원하는 배포 옵션은 다음과 같다.

  • BentoCloud(fully-managed)에 원클릭 배포
  • BentoML 에코시스템의 오픈소스 플랫폼인 Yatai에 푸쉬하고, k8s에서 모델 관리 및 배포
  • Docker가 실행되는 모든 환경

How does BentoML work?

(사용된 코드는 공식 문서의 quickstart 를 활용함)

일반적인 ML 워크플로우는 다음과 같다.

  1. Data Preparation
  2. Model Training
  3. Model Evaluation
  4. Deployment
  5. Monitoring
  6. 1번에서 5번까지 반복 (=Continuous Training)

위 과정을 BentoML로 수행하는 과정은 다음과 같다.

[1] Define a Model

서빙에 사용할 모델을 준비한다. 다양한 프레임워크(ex> Tensorflow, PyTorch)를 이용해서 직접 만들어도 되고, 모델 허브(ex> huggingface)를 통해 다운 받아도 된다.

[ 예제 코드 ]

# download_model.py

import bentoml

from sklearn import svm
from sklearn import datasets

# Load training data set
iris = datasets.load_iris()
X, y = iris.data, iris.target

# Train the model
clf = svm.SVC(gamma='scale')
clf.fit(X, y)

[2] Save a Model

학습된 모델을 BentoML 로컬 모델 스토어에 BentoML에서 제공하는 SDK를 이용해서 등록한다. bentoml.diffusers.import_model() 과 같은 간단한 메소드로 등록이 가능하다.

[ 예제 코드 ]

# download_model.py

# Save model to the BentoML local Model Store
saved_model = bentoml.sklearn.save_model("iris_clf", clf)

[3] Create a Service

모델을 감싸고 서빙 로직을 구현한 service.py 파일을 작성한다. 이 파일에서는 추론을 최적화하기 위해 추상화된 Runner 객체와, 외부 유저와 소통하기 위한 API 서버 및 엔드포인트 관리를 위한 Service 객체를 사용한다. Service 객체를 사용해서, 모델 서빙을 테스트하고 HTTP 또는 gRPC 요청을 통해 예측을 얻을 수 있다.

[ 예제 코드 ]

# service.py

import numpy as np
import bentoml
from bentoml.io import NumpyNdarray

# BentoML 로컬 모델 스토어에 저장된 모델을 불러와서 Runner 생성
iris_clf_runner = bentoml.sklearn.get("iris_clf:latest").to_runner()

# Runner를 주입해 Service 객체 생성
svc = bentoml.Service("iris_classifier", runners=[iris_clf_runner])

# 엔드포인트 정의
@svc.api(input=NumpyNdarray(), output=NumpyNdarray())
def classify(input_series: np.ndarray) -> np.ndarray:
result = iris_clf_runner.predict.run(input_series)
return result

[4] Build a Bento

Model, service, python packages, description, build options이 포함된 YAML 파일을 통해 Bento를 생성한다. 생성된 Bento에는 컨테이너화를 위해 자동 생성된 Dockerfile이 포함된다.

[ 예제 코드 ]

# bentofile.yaml

service: "service:svc" # Same as the argument passed to `bentoml serve`
labels:
owner: bentoml-team
stage: dev
include:
- "*.py" # A pattern for matching which files to include in the Bento
python:
packages: # Additional pip packages required by the Service
- scikit-learn
- pandas
models: # The model to be used for building the Bento.
- iris_clf:latest
  • 필요한 파이썬 라이브러리 입력할 때, python.requirements_txt key를 사용하면 requirements.txt 경로를 주입할 수 있음.
bentoml build # bentofile.yaml을 이용해 bento 빌드
bentoml list # bento 목록 조회

[5] Deploy a Bento

Bento를 생성할 때, 자동 생성된 Dockerfile을 이용해 도커 이미지를 생성한다. 생성된 도커 이미지는 Kubernetes와 같은 모든 Docker가 실행되는 모든 환경에 배포할 수 있다.

[ 예제 코드 ]

bentoml containerize iris_classifier:latest

요약

BentoML을 이용해 모델을 서빙하는 절차를 요약하면 다음과 같다.

  1. 모델을 학습하고 BentoML SDK를 이용해 저장한다.
  2. 서비스를 위한 파일을 정의한다.
  3. bentofile.yaml을 이용해 Bento를 생성한다(=모델 패키징). Bento에는 소스 코드, 모델 파일, 파이썬 패키지 의존성, Dockerfile 등이 있다.
  4. Dockerfile을 활용해 도커 이미지를 생성한다.
  5. 도커 이미지를 모델 서빙에 활용한다.

BentoML Ecosystem

BentoML은 프로덕션 환경에서 AI 애플리케이션의 배포와 관리를 간소화할 수 있는 도구와 플랫폼 제품을 제공한다.

BentoCloud

  • AI 애플리케이션 구축 및 운영을 위한 완전 관리형 플랫폼
  • Serveless solution
  • Autoscaling (including scale-to-zero)
  • Great observability for monitoring
  • 모델을 Bento로 패키징하고, BentoCloud에 푸쉬하는 것으로 배포하는 과정을 간소화할 수 있음.

OpenLLM

  • 대규모 언어 모델(LLM)의 배포와 fine-tuning을 간소화하도록 설계된 오픈 소스 플랫폼
  • StableLM, Falcon, Dolly, Flan-T5, ChatGLM, StarCoder, OPT, Llama 등 다양한 오픈 소스 LLM을 지원
  • OpenLLM은 BentoML에 대한 최고 수준의 지원을 제공
  • BentoML 사용자는 OpenLLM 모델을 BentoML 러너로 쉽게 통합해 사용 가능

Yatai

  • Kubernetes에서 대규모 머신 러닝(ML) 배포를 자동화하고 실행하기 위한 오픈 소스 엔드투엔드 솔루션
  • BentoML 에코시스템의 핵심 부분인 Yatai는 BentoML 프레임워크로 구축된 ML 서비스의 배포를 간소화
  • 클라우드 네이티브 도구인 Yatai는 통합 가시성(observability)을 위한 Grafana 스택, 트래픽 제어를 위한 Istio와 같은 다양한 에코시스템 도구와 원활하게 통합

bentoctl

  • 머신 러닝 모델을 클라우드에서 프로덕션에 바로 사용할 수 있는 API 엔드포인트로 쉽게 배포할 수 있도록 설계
  • AWS SageMaker, AWS Lambda, EC2, Google Compute Engine, Azure, Heroku 등을 비롯한 다양한 클라우드 플랫폼을 지원
  • 머신 러닝 모델을 배포, 업데이트, 삭제, 롤백하는 프로세스를 간소화할 수 있도록 도와줌
  • Terraform 템플릿을 사용자가 정의하여 DevOps의 요구 사항에 맞게 bentoctl을 조정할 수 있음

Features for inference

Adaptive Batching

(공식문서의 Adaptive Batching 를 참고함)

Batching(=일괄 처리)란 여러개의 입력을 묶어서 동시에 처리 할 때 사용되는 용어이다. 여러개의 메세지를 한 번에 처리하는 것이, 개별 메세지를 하나씩 처리하는 처리하는 것보다 빠르다는 개념이다. 많은 ML 프레임워크는 기본적으로 하드웨어 최적화 덕분에, Batching 기능을 지원하고 있다.

“While serving a TensorFlow model, batching individual model inference requests together can be important for performance. In particular, batching is necessary to unlock the high throughput promised by hardware accelerators such as GPUs.” – TensorFlow documentation

Adaptive Batching은 클라이언트 측이 아닌, 서버 측에서 구현된다. 클라이언트 측에 구현하는 것과 비교했을 때, 클라이언트의 로직을 단순화 할 수 있고, 트래픽 양에 따라 효율적인 운영이 가능하기 때문이다.

Adaptive Batching에는 중요한 두 가지 개념이 있다.

  1. Batching Window: 일괄 처리를 위해 대기해야 하는 최대 시간 이다. 유입되는 메세지의 양이 적을 경우, 배치 처리를 위해 긴 시간을 기다려야 하는 상황을 방지하는데 유용하다.
  2. Batch Size: 일괄 처리로 한 번에 처리할 수 있는 최대 크기 이다. Batching Window를 벗어나지 않는 시간 범위 내에서 적용되는 개념이다.

Architecture

배치 매커니즘은 각 모델의 Runner에 위치해 있다. 모델 Runner는 추론 요청을 받아서 최적의 지연 시간을 기준으로 추론 요청을 배치 처리한다. 요청이 배치 처리되는 순서는 다음과 같다. (정확한 이해가 아닐 수 있다😅)

  • 로드 밸런서가 요청을 API 서버에 분배한다.
  • API 서버는 요청을 차례대로 모델 러너에 보낸다.
  • 모델 러너에 있는 dispatcher가 들어오는 요청을 배치 단위로 묶는다. 이 때, random algorithm을 사용해서 요청을 여러 배치로 분배한다.
    • round robin 방식보다 효율적이다.
    • 향후 디스패치 알고리즘을 업데이트할 계획이다.

Questions:

  1. dispatcher가 관리하는 배치의 개수는 어떻게 설정하는가? 해당 문서에서는 찾을 수 없었다.
  2. 여러개의 배치에 요청을 분배하는 이유는 무엇인가? 차례대로 묶어서 처리하면 되는 것 아닌가?

Configuring Batching

YAML 파일을 통해 관리할 수 있다.

runners:
iris_clf:
batching:
enabled: true
max_batch_size: 100
max_latency_ms: 500
  • max_batch_size, max_latency_ms의 적절한 수준은 실험을 통해 찾아야 한다.

Inference Graph

(공식문서의 Inference Graph 를 참고함)

머신러닝 추론에는 모델 앙상블 기능이 필요한 상황이 빈번하다. 이를 위해 BentoML은 간단한 파이썬 코드로 모델 추론의 순서가 병렬(parallel) 및 순차적(sequential)으로 진행되도록 제어할 수 있다. 예제 코드를 통해 inference graph를 제어 하는 방법을 알아보자.

예제 서비스는 다음과 같이 수행된다.

  1. 텍스트 입력 (input text)

  2. 입력을 세 개의 텍스트 생성 모델(text generation model)에 병렬(parallel)로 전달하고, 세 개의 생성된 텍스트를 리턴받는다.

  3. 생성된 텍스트 목록을 텍스트 분류 모델(text classification model)에 순차적으로(sequential) 입력하고, 분류 결과를 리턴 받는다.

  4. 생성된 텍스트 목록과 분류 결과를 JSON 출력으로 반환한다.

    # JSON 출력 예제

    [
    {
    "generated": "I have an idea! Please share with, like and subscribe and leave a comment below!\n\nIf you like this post, please consider becoming a patron of Reddit or becoming a patron of the author.",
    "score": 0.5149753093719482
    },
    {
    "generated": "I have an idea! One that won't break your heart but will leave you gasping in awe. A book about the history of magic. And because what's better than magic? Some. It's a celebration of our ancient, universal gift of awe.\"\n\nThe result was the \"Vox Populi: A Memoir of the Ancient World\" by E.V. Okello (Ace Books), published in 1999.\n\nIn the past 20 years, Okello, professor of history at Ohio State University and author of such titles as \"The American Imagination\" and \"Walking With Elephants",
    "score": 0.502700924873352
    },
    {
    "generated": "I have an idea! I've been wondering what the name of this thing is. What's the point?\" - The Simpsons\n\n\n\"It's bigger, bigger than she needs!\" - SpongeBob SquarePants\n\n\n\"That's a funny thing. It's like my brain is the most gigantic living thing. I just like thinking big.\" - Simpsons\n\n\n\"Ooookay! Here comes Barty-Icarus himself! (pause)\" - A Christmas Tale\n\n\nBackground information Edit\n\nFormal name: Homer's Brain.\n\nHomer's Brain. Special name: Brain.\n\nAppearances Edit",
    "score": 0.536346971988678
    }
    ]

Declare Runners

gpt2_generator = bentoml.transformers.get("gpt2-generation:latest").to_runner()
distilgpt2_generator = bentoml.transformers.get("distilgpt2-generation:latest").to_runner()
distilbegpt2_medium_generator = bentoml.transformers.get("gpt2-medium-generation:latest").to_runner()
bert_base_uncased_classifier = bentoml.transformers.get("bert-base-uncased-classification:latest").to_runner()

BentoML SDK를 활용해 저장된 모델을 불러와서(get), Runner 객체로 생성(to_runner)한다.

Create Service

RunnerService 를 생성할 때 사용한다.

svc = bentoml.Service(
"inference_graph",
runners=[
gpt2_generator,
distilgpt2_generator,
distilbegpt2_medium_generator,
bert_base_uncased_classifier,
],
)

Define API

API를 정의하는 순서는 다음과 같다.

  1. API의 이름(=메소드 이름)을 정의하고, input과 output의 type을 선언한다.

    @svc.api(input=Text(), output=JSON())
    async def classify_generated_texts(original_sentence: str) -> dict:
    ...
  2. text generation model로 생성한 Runner에 input을 병렬적(asyncio.gather)으로 입력한다.

    		...
    generated_sentences = [
    result[0]["generated_text"]
    for result in await asyncio.gather(
    gpt2_generator.async_run(
    original_sentence,
    max_length=MAX_LENGTH,
    num_return_sequences=NUM_RETURN_SEQUENCE,
    ),
    distilgpt2_generator.async_run(
    original_sentence,
    max_length=MAX_LENGTH,
    num_return_sequences=NUM_RETURN_SEQUENCE,
    ),
    distilbegpt2_medium_generator.async_run(
    original_sentence,
    max_length=MAX_LENGTH,
    num_return_sequences=NUM_RETURN_SEQUENCE,
    ),
    )
    ]
  3. 생성된 텍스트 목록을 text classification model로 생성한 Runner에 input으로 순차적으로(async_run) 입력한다.

    		...		
    results = []
    for sentence in generated_sentences:
    score = (await bert_base_uncased_classifier.async_run(sentence))[0]["score"]
    results.append(
    {
    "generated": sentence,
    "score": score,
    }
    )
  4. 텍스트 분류 결과를 리턴한다.

[ 전체 코드 ]

@svc.api(input=Text(), output=JSON())
async def classify_generated_texts(original_sentence: str) -> dict:
generated_sentences = [
result[0]["generated_text"]
for result in await asyncio.gather(
gpt2_generator.async_run(
original_sentence,
max_length=MAX_LENGTH,
num_return_sequences=NUM_RETURN_SEQUENCE,
),
distilgpt2_generator.async_run(
original_sentence,
max_length=MAX_LENGTH,
num_return_sequences=NUM_RETURN_SEQUENCE,
),
distilbegpt2_medium_generator.async_run(
original_sentence,
max_length=MAX_LENGTH,
num_return_sequences=NUM_RETURN_SEQUENCE,
),
)
]

results = []
for sentence in generated_sentences:
score = (await bert_base_uncased_classifier.async_run(sentence))[0]["score"]
results.append(
{
"generated": sentence,
"score": score,
}
)

return results

총평

[1] 모델 패키징이 쉽고, 패키징을 위한 다양한 기능을 지원한다. 이를 통해 모델을 production으로 배포하는 workflow를 개발할 때 생산성을 높일 수 있다.

  • BentoML은 모델 패키징을 위한 Bento 이라는 자체 형식을 사용한다.
  • Bento를 만들면 서빙에 필요한 모든 것(소스 코드, 모델 파일, 파이썬 패키지 의존성, Dockerfile)이 있다.
  • Bento를 만들 때 사용되는 bentofile.yaml에서 다양한 기능을 지원해서, 요구사항에 맞는 Bento를 생성하기가 편리하다.
  • bentofile.yaml에서 지원하는 다양한 기능에는 다음과 같은 것들이 있다.
    • 소스 코드에 포함시킬(include) 파일과, 제외시킬(exclude) 파일을 .gitignore 에서 사용하는 패턴 방식을 통해 설정할 수 있다. .bentoignore 파일도 사용할 수 있다.
    • 파이썬 패키지 의존성을 기입할 때, requirements_txt, trusted_host, extra_index_url 등 세부적인 설정이 가능하다.
    • 도커 파일의 base image를 설정할 수 있다.
    • 더 많은 기능들은 공식 문서를 참고하자.

[2] 모델 추론을 위한 API 서버를 쉽게 구현/운영할 수 있다.

  • 웹서버를 별도로 만들 필요 없이 추상화된 Service 객체를 사용하면 된다.
  • 웹서버를 실행시키는 것도 Bento에서 생성한 Dockerfile에 포함되어 있다.
  • 즉, 모델 서빙을 위한 서비스 로직에만 집중하면 된다.
  • API 서버에 대한 Swagger를 제공한다.

[3] 모델 추론 API 서버의 성능을 높이기 위한 기능을 지원한다.

  • 대표적으로 Adaptive Batching 기능이 있다.
  • Adaptive Batching에 대한 내용은 위에 정리된 내용을 참고하거나, 공식 문서를 참고하자.

[4] 모델 추론에서 사용되는 모델 앙상블(sequential, parallel) 기능을 쉽게 구현할 수 있다.

  • Inference Graph 기능을 통해 구현이 가능하다.
  • 자세한 내용은 위에 정리된 내용을 참고하거나, 공식 문서를 참고하자.

[5] 소규모 데이터에 대한 오프라인 서빙을 하는 시나리오에서는 BentoML을 사용하는게 이점이 크게 없어 보인다.

  • 서빙을 위한 API 서버를 두는게 불필요하다.
  • 지금처럼 서빙 결과를 빅쿼리에 저장한다면, API 서버가 아닌 함수 호출이 더 효율적이다.
  • 모델 앙상블을 구현하는게 편리한 것은 장점이다. 모델 앙상블이 필요하다면 고려해보자.
  • 모델 패키징이 쉬운 것 또한 장점이다. 다만, Bento를 빌드할 때 자동으로 생성되는 Dockerfile이 API 서버를 띄우는 것에 최적화 되어있는데, 오프라인 서빙을 하기 위해 entrypoint를 변경해도 괜찮은지와 쿠버네티스에 Job으로 배포해도 정상적으로 동작하는지에 대해서는 테스트가 필요하다.
  • 모델 패키징 기능이 필요하다면, MLflow에서 제공하는 모델 패키징 기능을 학습해보는 것도 좋은 대안으로 보인다. 현재 시점에서는 직접 구현한 패키징 기능으로도 충분해서 큰 니즈가 없다.

참고 자료