- Published on
BentoML로 GPU 모델 서빙·롤백 1시간 구축
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 파이프라인을 처음부터 제대로 만들려면 보통 시간이 오래 걸립니다. 모델 아티팩트 관리, GPU 런타임 의존성, 배포 단위(이미지) 고정, 헬스체크, 트래픽 전환, 롤백까지 한 번에 엮어야 하기 때문입니다.
BentoML은 이 과정을 “모델 + 코드 + 런타임”을 하나의 번들로 패키징하고, 그 번들을 컨테이너 이미지로 만들고, 동일한 방식으로 어디서든 실행하게 해줍니다. 이 글에서는 GPU 모델을 BentoML로 서빙하고, 태그 기반으로 즉시 롤백 가능한 형태까지 1시간 안에 구축하는 현실적인 절차를 정리합니다.
아래 예시는 PyTorch 계열을 기준으로 하지만, Transformers, ONNX Runtime, XGBoost 등도 동일한 패턴으로 확장됩니다.
목표 아키텍처 (1시간 버전)
- BentoML 서비스: HTTP 추론 API
- 모델 버전: BentoML Model Store에서 태그로 관리
- 배포: Docker 이미지로 고정(재현성 확보)
- GPU:
--gpus all로 컨테이너에 할당 - 롤백: “이전 이미지 태그” 또는 “이전 Bento 태그”로 즉시 전환
- 운영 체크:
/healthz와 추론 응답 기반 스모크 테스트
롤백 전략은 두 가지 중 하나를 선택하면 됩니다.
- 이미지 태그 롤백:
my-svc:2026-02-25-1같은 태그를 남겨두고 이전 태그로 재배포 - Bento 태그 롤백: 같은 코드 이미지에서
BENTO_TAG환경변수만 바꿔 이전 모델 번들을 로딩
운영에서는 1번이 가장 단순하고 강력합니다(이미지가 곧 배포 단위). 다만 2번은 이미지 재빌드 없이 모델만 바꿀 수 있어 실험 속도가 빠릅니다.
0) 준비물
- NVIDIA GPU 서버(드라이버 설치 완료)
- Docker 및 NVIDIA Container Toolkit
- Python 3.10+ 권장
서버에서 아래가 동작해야 합니다.
nvidia-smi
1) 프로젝트 스캐폴딩
디렉토리 구조 예시입니다.
.
├─ service.py
├─ bentofile.yaml
├─ requirements.txt
└─ tools/
├─ build.sh
├─ smoke_test.sh
└─ rollback.sh
requirements.txt 예시:
bentoml==1.3.9
torch
transformers
accelerate
버전은 팀 표준에 맞추되, 운영에서는 고정 버전을 권장합니다.
2) BentoML 서비스 코드 (GPU 추론)
핵심은 “모델 로딩 시점”과 “GPU 디바이스 선택”입니다. BentoML은 서비스 시작 시 리소스를 준비해두고 요청마다 빠르게 추론하도록 만드는 게 일반적입니다.
아래 예시는 Transformers 파이프라인을 단순화한 형태입니다.
# service.py
import os
import bentoml
from bentoml.io import JSON
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
MODEL_ID = os.getenv("HF_MODEL_ID", "distilbert-base-uncased-finetuned-sst-2-english")
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
@bentoml.service(
name="gpu-text-clf",
traffic={"timeout": 30},
)
class GpuTextClassifier:
def __init__(self):
self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
self.model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID)
self.model.to(DEVICE)
self.model.eval()
@bentoml.api(input=JSON(), output=JSON())
def predict(self, body: dict) -> dict:
text = body.get("text", "")
inputs = self.tokenizer(text, return_tensors="pt", truncation=True)
inputs = {k: v.to(DEVICE) for k, v in inputs.items()}
with torch.inference_mode():
logits = self.model(**inputs).logits
prob = torch.softmax(logits, dim=-1)[0].detach().cpu().tolist()
return {
"model_id": MODEL_ID,
"device": DEVICE,
"prob": prob,
}
@bentoml.api(input=JSON(), output=JSON(), route="/healthz")
def healthz(self, _body: dict) -> dict:
return {"status": "ok", "device": DEVICE}
포인트:
torch.inference_mode()로 추론 오버헤드 감소- 서비스 초기화에서 모델을 GPU로 올려 콜드스타트 비용을 요청 경로에서 제거
/healthz를 제공해 배포/롤백 자동화에서 활용
3) bentofile.yaml로 빌드 단위 고정
BentoML은 bentofile.yaml 로 의존성과 엔트리포인트를 정의합니다.
# bentofile.yaml
service: "service.py:GpuTextClassifier"
labels:
owner: "ml-platform"
stage: "prod"
include:
- "service.py"
python:
packages:
- bentoml==1.3.9
- torch
- transformers
- accelerate
운영 팁:
torch는 CUDA 버전과 맞는 빌드가 중요합니다. 컨테이너 베이스 이미지를 CUDA 런타임 포함으로 고르면 충돌이 줄어듭니다.- GPU OOM, 배치/워커 튜닝 이슈는 서빙 프레임워크가 달라도 자주 터집니다. TorchServe 기준이지만 원인/접근이 비슷하니 함께 참고하면 좋습니다: TorchServe GPU OOM 해결 - 배치·워커·A10 최적화
4) 로컬에서 Bento 빌드 및 실행
Bento 번들을 만들고 태그를 확인합니다.
bentoml build
bentoml list
실행:
bentoml serve --production
간단 호출:
curl -s http://127.0.0.1:3000/healthz -H 'Content-Type: application/json' -d '{}'
curl -s http://127.0.0.1:3000/predict -H 'Content-Type: application/json' -d '{"text":"it is great"}'
5) GPU 컨테이너 이미지 만들기
운영에서 가장 깔끔한 방식은 Bento 태그로 이미지 생성 후, 해당 이미지를 배포하는 것입니다.
# 최신 Bento 태그를 골라 이미지로 빌드
bentoml containerize gpu-text-clf:latest -t gpu-text-clf:2026-02-25-1
서버에서 GPU로 실행:
docker run --rm -p 3000:3000 --gpus all gpu-text-clf:2026-02-25-1
여기서 중요한 점:
--gpus all이 빠지면 컨테이너는 GPU를 못 봅니다- 컨테이너가 GPU를 인식하는지
/healthz로 확인하세요
6) 배포 스크립트: 헬스체크 포함 (실패 시 자동 롤백)
“1시간 구축”의 핵심은 자동화된 안전장치입니다. 배포 스크립트는 최소한 아래를 해야 합니다.
- 새 이미지 pull 또는 로컬 빌드
- 새 컨테이너 기동
- 헬스체크 및 스모크 테스트
- 성공 시 트래픽 전환(기존 컨테이너 종료)
- 실패 시 새 컨테이너 폐기, 이전 컨테이너 유지
단일 서버에서 가장 단순한 방식은 “포트 고정 + 컨테이너 교체”입니다.
tools/smoke_test.sh:
#!/usr/bin/env bash
set -euo pipefail
BASE_URL=${1:-http://127.0.0.1:3000}
curl -fsS "${BASE_URL}/healthz" \
-H 'Content-Type: application/json' \
-d '{}' > /dev/null
curl -fsS "${BASE_URL}/predict" \
-H 'Content-Type: application/json' \
-d '{"text":"hello"}' > /dev/null
tools/build.sh:
#!/usr/bin/env bash
set -euo pipefail
TAG=${1:-"$(date +%F)-1"}
bentoml build
bentoml containerize gpu-text-clf:latest -t "gpu-text-clf:${TAG}"
echo "Built image gpu-text-clf:${TAG}"
tools/rollback.sh (이전 태그로 재기동):
#!/usr/bin/env bash
set -euo pipefail
PREV_TAG=${1:?"pass previous tag"}
docker rm -f gpu-text-clf || true
docker run -d --name gpu-text-clf \
--restart unless-stopped \
-p 3000:3000 \
--gpus all \
"gpu-text-clf:${PREV_TAG}"
./tools/smoke_test.sh http://127.0.0.1:3000
echo "Rolled back to gpu-text-clf:${PREV_TAG}"
이 구성만으로도 “문제 생기면 이전 이미지 태그로 즉시 복구”가 됩니다.
7) Nginx 리버스 프록시를 붙일 때 주의점
대부분 운영 환경에서는 Nginx 앞단을 둡니다. 이때 헬스체크 경로와 타임아웃을 명확히 설정하세요.
- 추론은 길어질 수 있으니
proxy_read_timeout을 늘리기 /healthz는 짧게 유지해 로드밸런서/모니터링이 안정적으로 확인하게 하기
HTTPS, OAuth 콜백, 리다이렉트가 섞이면 502나 무한 리다이렉트처럼 “서빙은 정상인데 프록시에서 터지는” 문제가 자주 생깁니다. 프록시 레이어 디버깅은 아래 글의 체크리스트가 도움이 됩니다.
8) 운영에서 자주 터지는 이슈와 빠른 해결
8.1 GPU는 있는데 cuda 를 못 잡는 경우
- 컨테이너 실행 옵션에
--gpus all누락 - NVIDIA Container Toolkit 미설치
- 호스트 드라이버와 컨테이너 CUDA 런타임 불일치
검증 순서:
nvidia-smi
docker run --rm --gpus all nvidia/cuda:12.2.0-runtime-ubuntu22.04 nvidia-smi
8.2 롤백이 느린 이유는 “다운로드” 때문이다
롤백은 빨라야 의미가 있습니다. 느린 롤백의 가장 흔한 원인은:
- 이미지가 크고, 서버에 캐시가 없음
- 모델 가중치를 런타임에 외부에서 매번 받음
대응:
- 이미지 레지스트리와 서버 간 네트워크 최적화
- 모델을 이미지에 포함하거나, 최소한 노드 로컬 디스크에 캐시
- “이전 버전 이미지”는 서버에 항상 남겨두기
8.3 배포 자동화가 크론에서만 실패하는 경우
배포/정리 스크립트를 크론으로 돌릴 때는 환경변수, PATH, 쉘 차이 때문에 “터미널에서 되는데 크론에서만 안 됨”이 흔합니다. 배포 자동화까지 염두에 둔다면 아래 글의 점검 포인트를 같이 보세요.
9) 1시간 체크리스트 (현실 버전)
-
service.py에서 모델 로딩 및/healthz구현 -
bentofile.yaml로 의존성 고정 -
bentoml build후 태그 확인 -
bentoml containerize로 이미지 태그 생성 - 서버에서
docker run --gpus all로 실행 -
smoke_test.sh로 헬스체크 및 간단 추론 검증 - 실패 시
rollback.sh로 이전 태그 즉시 복귀
마무리: BentoML을 쓰는 이유는 “재현 가능한 롤백”
GPU 모델 서빙에서 진짜 어려운 지점은 단순히 API를 띄우는 게 아니라, 문제 발생 시 빠르게 이전 상태로 복구하는 운영 능력입니다. BentoML은 모델과 런타임을 번들로 만들고, 그 번들을 이미지로 고정하기 쉬워서 “재현 가능한 배포/롤백”의 난도를 크게 낮춥니다.
다음 단계로는 단일 서버 교체 배포에서 벗어나, 블루/그린(또는 카나리) 트래픽 전환, 메트릭 기반 자동 롤백, 멀티 GPU 워커 튜닝까지 확장하면 됩니다. 하지만 오늘 목표가 “1시간 안에 돌아가는 GPU 서빙 + 롤백”이라면, 이 글의 구성만으로도 운영 가능한 최소 단위를 충분히 만들 수 있습니다.