FastAPI를 BentoML로 대체하기

Dev Smile·2026년 3월 29일

인공지능

목록 보기
4/4

TL;DR

항목FastAPI (기존)BentoML (신규)
핵심 파일app/main.py, routes/predict.pyservice.py
모델 로드 시점종종 전역 또는 Dependency Injection서비스 인스턴스 초기화 시 (__init__)
배포 패키징Dockerfile 직접 작성 필요bentofile.yaml을 통한 자동화
성격범용 HTTP 서버ML 추론 최적화 서비스

제게는 FastAPI가 익숙하고 빠르기 때문에 FastAPI를 사용한 AI 서빙을 소개하였습니다. 하지만 프로젝트의 성격이 일반적인 '웹 서비스'가 아니라 '모델 추론' 그 자체에 집중되어 있다면, 범용 웹 프레임워크보다는 ML Serving 전용 프레임워크를 고민해 볼 수 있습니다.

이번 글에서는 기존의 FastAPI 기반 손글씨 숫자 인식 백엔드를 BentoML 구조로 전환하여, 더욱 AI 모델 서빙에 최적화된 구조로 개선하는 방법을 살펴봅니다.

1. 왜 FastAPI 대신 BentoML인가?

FastAPI는 매우 훌륭한 범용 웹 API 프레임워크입니다. 하지만 모델 서빙 관점에서 보면 다음과 같은 차이가 있습니다.

  • 서빙 엔트리포인트의 단순화: 모델 로드, 전처리, API 정의를 하나의 'Service' 클래스 안에서 직관적으로 관리할 수 있습니다.
  • ML 특화 기능: 추후 GPU 가속을 위한 Runner, 마이크로서비스 확장을 위한 배치(Batch) 처리, 모델 버전 관리 등을 별도 설정 없이 바로 사용할 수 있습니다.
  • 표준화된 패키징: bentofile.yaml 하나로 의존성과 환경을 정의하고 Docker 이미지까지 쉽게 빌드할 수 있습니다.

결국, 지금 프로젝트는 웹 서비스라기보다 모델 추론 서비스에 가깝기 때문에 BentoML로 옮겨볼 명분이 충분하다고 생각합니다.

2. 현재 backend 구조 vs 목표 구조

기존 FastAPI 구조는 웹 애플리케이션의 관례를 따르느라 파일이 여러 갈래로 찢어져 있었습니다.

기존 FastAPI 구조

backend/
├─ app/
│  ├─ api/routes/predict.py  # 엔드포인트
│  ├─ core/config.py         # 설정
│  ├─ schemas/prediction.py  # Pydantic 모델
│  └─ services/
│     ├─ image_preprocessing.py # 전처리 로직
│     └─ inference.py           # 모델 로드 및 추론
├─ models/
│  └─ digit_model_28.joblib
└─ main.py

변경 후 BentoML 구조

digit-recognition/
├─ bentoml_backend/
│  ├─ service.py         # 핵심 서빙 로직 (통합)
│  ├─ preprocessing.py    # 전처리 (재사용)
│  ├─ bentofile.yaml     # 패키징 설정
│  ├─ requirements.txt    # 의존성
│  └─ models/
│     └─ digit_model_28.joblib
└─ frontend/
기존 FastAPI 구조BentoML 대체 구조
app/main.pyservice.py
api/routes/predict.pyservice.py@bentoml.api
schemas/prediction.pyservice.pyPydantic 모델
services/inference.pyservice.py 내 모델 로직
services/image_preprocessing.pypreprocessing.py

3. BentoML 서비스 코드 구현

핵심은 service.py입니다. 기존에 흩어져 있던 책임을 하나의 클래스로 모읍니다.

from __future__ import annotations

import base64
import io
from pathlib import Path

import bentoml
import joblib
import numpy as np
from PIL import Image
from pydantic import BaseModel

# 기존 전처리 로직 재사용
from preprocessing import preprocess_canvas_png_to_28x28

MODEL_PATH = Path(__file__).resolve().parent / "models" / "digit_model_28.joblib"

class ImageRequest(BaseModel):
    image: str  # Base64 data URL

class PredictionResponse(BaseModel):
    digit: int

@bentoml.service(
    name="digit-recognizer",
    traffic={"timeout": 10},
)
class DigitRecognizerService:
    def __init__(self) -> None:
        # 서비스 시작 시 모델을 한 번만 로드하여 메모리에 유지
        self.model = joblib.load(MODEL_PATH)

    @bentoml.api
    def predict(self, request: ImageRequest) -> PredictionResponse:
        # 1. Base64 이미지 디코딩
        _, encoded = request.image.split(",", 1)
        image_data = base64.b64decode(encoded)
        image = Image.open(io.BytesIO(image_data))

        # 2. 전처리 (28x28 grayscale 변환 등)
        data_28 = preprocess_canvas_png_to_28x28(image)
        
        # 3. 모델 입력 규격($1 \times 784$)에 맞게 변형
        values = data_28.flatten().reshape(1, -1)
        
        # 4. 추론
        prediction = self.model.predict(values)[0]

        return PredictionResponse(digit=int(prediction))

전처리 코드(preprocessing.py)

이 프로젝트의 품질을 결정하는 전처리 로직(Crop, Resize, Center of Mass 등)은 프레임워크와 무관한 순수 로직이므로 파일명만 바꿔서 그대로 재사용합니다.


4. 패키징 설정: bentofile.yaml

BentoML의 강력함은 이 설정 파일에서 나옵니다. 어떤 파일을 포함할지, 어떤 라이브러리가 필요한지 명시합니다.

service: "service:DigitRecognizerService"
labels:
  project: "digit-recognition"
  framework: "bentoml"
include:
  - "*.py"
  - "models/*.joblib"
python:
  packages:
    - bentoml
    - joblib
    - numpy
    - pillow
    - scikit-learn
    - pydantic

5. 실행 및 프론트엔드 연동

실행 방식의 변화

기존 uvicorn 대신 bentoml serve 명령어를 사용합니다.

# 개발 모드 실행
bentoml serve service:DigitRecognizerService --reload

이 경우, http://localhost:3000/ 로 접속 시 아래의 Swagger 페이지를 확인할 수 있습니다.

프론트엔드(Vue.js 등)의 대응

이번 BentoML의 변경에서 가장 좋은 점은 프론트엔드 코드를 거의 고칠 필요가 없다는 것입니다. BentoML도 동일하게 POST /predict 엔드포인트를 생성하며, 우리가 정의한 ImageRequest 스키마가 기존 FastAPI의 스키마와 동일하다면 HTTP 계약(Contract)이 유지되기 때문입니다.

포트 번호나 API 베이스 URL 정도만 새 서버 주소에 맞게 업데이트하면 됩니다.


6. 마이그레이션 순서 (Best Practice)

안전한 전환을 위해 다음 순서를 권장합니다.

  1. 로직 분리: 기존 image_preprocessing.py를 독립된 파일로 추출합니다.
  2. 서비스 작성: service.py를 작성하고 모델 로드 테스트를 진행합니다.
  3. 계약 검증: bentoml serve를 띄운 후, Postman이나 curl로 기존과 동일한 JSON 응답이 오는지 확인합니다.
  4. 프론트엔드 연결: VITE_API_BASE_URL 등을 수정하여 실제 캔버스와 연동합니다.
  5. 정리: 검증이 완료되면 더 이상 필요 없는 app/, main.py 등 FastAPI 관련 파일을 제거합니다.

7. 마무리

FastAPI에서 BentoML로의 교체는 단순히 라이브러리를 바꾸는 작업이 아닙니다. 이것은 프로젝트의 중심을 "범용 웹 API"에서 "전문화된 모델 서비스"로 옮기는 작업입니다.

이렇게 구조를 정리해두면, 나중에 모델을 업데이트하거나, 다른 모델(예: PyTorch 기반)로 교체하거나, 대규모 트래픽을 처리하기 위해 스케일 아웃을 할 때 훨씬 유연하게 대응할 수 있습니다.

0개의 댓글