BACKEND

머신러닝 모델 서빙 및 추론 API 구축 가이드

junetapa 2026. 2. 18 2026. 6. 5 업데이트 14 min read

학습이 끝난 모델은 서빙되어야 가치가 생긴다. FastAPI 같은 단순 추론 서버부터 vLLM·NVIDIA Triton·BentoML·Ray Serve 같은 전용 서빙 엔진, 그리고 양자화·연속 배칭·분리형 서빙까지 — 2026년 프로덕션 추론 API를 어떻게 설계하는지 정리했다.

2026년 6월 업데이트

LLM 서빙이 일반화되면서 모델 서빙의 무게중심이 옮겨갔다. 기존 TensorFlow Serving·TorchServe 위주 설명에 더해, 2026년 현재 사실상 표준이 된 vLLM과 GPU 추론을 묶어주는 NVIDIA Triton / Dynamo, 패키징 중심의 BentoML, 분산 파이프라인의 Ray Serve를 실제 선택 기준으로 비교했다. 또한 FP8·INT4 양자화, 연속 배칭(continuous batching), 프리픽스 캐싱, 프리필·디코드 분리형 서빙 등 최신 추론 최적화 기법을 반영했다.

개요 및 중요성

모델 서빙은 학습이 끝난 모델을 실제 요청에 응답하는 추론 API로 만드는 단계다. 아무리 좋은 정확도를 가진 모델이라도 서빙되지 않으면 노트북 안의 파일일 뿐이다. 2026년 현재 백엔드 개발자가 마주하는 서빙 과제는 크게 두 갈래다. 하나는 추천·랭킹·분류처럼 비교적 가벼운 전통 ML 모델을 낮은 지연으로 대량 처리하는 것이고, 다른 하나는 수십억 파라미터 규모의 LLM을 GPU 메모리 한도 안에서 경제적으로 서빙하는 것이다.

이 둘은 요구사항이 전혀 다르다. 전통 ML은 단순한 FastAPI 추론 서버나 TorchServe로 충분한 경우가 많지만, LLM은 토큰을 하나씩 생성하는 자기회귀 구조 때문에 KV 캐시 메모리 관리, 가변 길이 요청 배칭, 양자화 같은 전용 최적화가 필수다. 그래서 vLLM·SGLang·TensorRT-LLM 같은 LLM 전용 엔진이 별도 카테고리로 자리잡았다.

이 글에서는 서빙 프레임워크 선택 기준부터 FastAPI 기반 기본 추론 서버 구현, LLM 추론 최적화 기법, 그리고 실제 운영에서의 트러블슈팅까지 단계적으로 다룬다.

TIP

서빙 엔진을 고를 때 첫 질문은 "내가 서빙하는 게 LLM인가 아닌가"다. LLM이면 vLLM 계열부터 검토하고, 일반 ML 모델이면 BentoML·Triton·Ray Serve 중 운영 환경(단일 컨테이너 vs 쿠버네티스 vs 분산 파이프라인)에 맞춰 고른다.

핵심 개념과 기본 원리

서빙 프레임워크들은 역할이 겹치는 듯 보여도 설계 철학이 다르다. 핵심은 추론 엔진(모델을 GPU에서 실제로 실행하는 부분)과 서빙 레이어(라우팅·버전 관리·관측·확장을 담당하는 부분)를 구분하는 것이다. 예를 들어 NVIDIA Triton은 서빙 레이어이며, 그 백엔드로 vLLM·PyTorch·TensorRT 같은 엔진을 끼워 쓴다.

2026년 주요 서빙 프레임워크 비교

프레임워크성격적합한 상황
vLLMLLM 전용 추론 엔진. PagedAttention으로 KV 캐시 낭비를 4% 미만으로 줄여 같은 GPU로 훨씬 많은 동시 요청 처리LLM·생성형 모델 서빙의 사실상 기본 선택
NVIDIA Triton엔진 중립 서빙 레이어. 모델 버전 관리·관측·고가용성 제공, vLLM/TensorRT/PyTorch 백엔드 연결여러 모델·여러 프레임워크를 한 서버에서 표준화해 운영
BentoML모델·의존성·API를 하나의 Bento 아티팩트로 패키징, 도커 이미지·클라우드 배포 단순화단일 서비스 패키징과 손쉬운 컨테이너 빌드가 중요할 때
Ray Serve분산 액터 모델 기반. 여러 모델을 묶는 앙상블·복합 파이프라인에 강함전·후처리와 다중 모델을 잇는 복잡한 추론 파이프라인

실무에서는 이들을 조합한다. 대표적으로 vLLM을 추론 엔진으로 두고 Triton 또는 Ray Serve로 라우팅·확장을 맡기는 구성이 흔하다. 일반 ML 모델 위주의 마이크로서비스라면 BentoML 단독으로 패키징·배포까지 끝내는 편이 단순하다.

쿠버네티스 환경이라면 KServe도 후보다. KServe는 KEDA와 결합해 트래픽이 없을 때 파드를 0으로 줄이는 scale-to-zero로 유휴 비용을 없애고, 카나리 배포를 위한 트래픽 분할을 기본 제공한다.

EXAMPLE

"Triton vs vLLM 중 무엇?"이라는 질문 자체가 잘못된 경우가 많다. Triton은 서빙 레이어, vLLM은 추론 엔진이라 보통 함께 쓴다. 둘 중 하나만 골라야 한다면, 단일 LLM을 빠르게 띄우는 데는 vLLM 단독, 여러 모델을 표준화해 운영하려면 Triton+vLLM이 답이다.

실전 구현 가이드

가장 단순한 출발점은 FastAPI로 모델을 감싸 추론 엔드포인트를 만드는 것이다. 전통 ML 모델이나 임베딩 모델처럼 추론 시간이 짧고 메모리 압박이 크지 않은 경우 이 패턴으로 충분하다. 핵심은 모델을 요청마다 로드하지 않고 앱 시작 시 한 번만 로드해 재사용하는 것이다.

python # FastAPI 기반 기본 추론 서버 from contextlib import asynccontextmanager from fastapi import FastAPI from pydantic import BaseModel import joblib ml_models = {} @asynccontextmanager async def lifespan(app: FastAPI): # 앱 시작 시 모델 1회 로드 (요청마다 로드 금지) ml_models["clf"] = joblib.load("model.joblib") yield ml_models.clear() app = FastAPI(lifespan=lifespan) class PredictRequest(BaseModel): features: list[float] @app.post("/predict") async def predict(req: PredictRequest): model = ml_models["clf"] pred = model.predict([req.features])[0] return {"prediction": int(pred)}

반면 LLM은 이렇게 직접 감싸면 안 된다. 토큰을 하나씩 생성하는 동안 GPU가 다른 요청을 처리하지 못해 처리량이 바닥나기 때문이다. 이때는 vLLM을 띄우는 것이 정답이다. vLLM은 OpenAI 호환 API 서버를 명령 한 줄로 제공하므로, 백엔드는 기존 OpenAI SDK 그대로 호출하면 된다.

bash # vLLM OpenAI 호환 서버 실행 (FP8 양자화 예시) vllm serve meta-llama/Llama-3.1-8B-Instruct \ --quantization fp8 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90
python # vLLM 서버를 OpenAI SDK로 호출 (스트리밍) from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="local") stream = client.chat.completions.create( model="meta-llama/Llama-3.1-8B-Instruct", messages=[{"role": "user", "content": "모델 서빙이 뭔가요?"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta.content or "" print(delta, end="", flush=True)

고급 패턴 및 최적화

LLM 서빙의 비용과 지연은 몇 가지 핵심 기법으로 크게 좌우된다. 잘 튜닝된 스택은 순진한 구현 대비 GPU당 처리 요청 수를 10~50배까지 끌어올린다. 2026년 기준 실무에서 반드시 챙겨야 할 최적화는 다음과 같다.

양자화 (Quantization)

모델 가중치의 정밀도를 16/32비트에서 8비트, 4비트로 낮춰 메모리 사용량과 대역폭을 줄인다. Hopper(H100·H200) 이상 GPU에서는 FP8이 사실상 무손실에 가깝게 VRAM을 절반으로 줄이고 처리량을 약 2배로 올리는 현재의 스위트 스폿이다. 메모리가 더 빠듯하면 INT4 weight-only(AWQ·GPTQ)가 의외로 8비트에 근접한 품질을 보여 선택지가 된다. vLLM은 GPTQ·AWQ·INT4·INT8·FP8을 모두 통합 지원한다.

연속 배칭 (Continuous Batching)

정적 배칭과 달리, 한 요청이 끝나면 빈 슬롯에 즉시 새 요청을 채워 넣는다. 부하 상황에서 평균 지연을 50~80% 줄이고 처리량을 2~3배 높인다. vLLM·TGI·TensorRT-LLM 등 현대 엔진은 이를 기본 적용한다.

프리픽스 캐싱 (Prefix Caching)

공통 시스템 프롬프트처럼 반복되는 앞부분의 연산 결과를 캐시해 프리필(prefill) 연산을 절약한다. RAG나 에이전트처럼 긴 공통 프롬프트를 쓰는 워크로드에서 효과가 크다.

분리형 서빙 (Disaggregated Serving)

LLM 요청은 입력을 한 번에 처리하는 프리필 단계와 토큰을 하나씩 생성하는 디코드 단계의 연산 특성이 완전히 다르다. NVIDIA Dynamo나 llm-d는 두 단계를 서로 다른 GPU로 분리해 각각에 맞게 독립적으로 확장한다. 여기에 스페큘레이티브 디코딩(작은 모델이 후보 토큰을 미리 만들고 큰 모델이 검증)까지 더하면 디코드 지연을 추가로 줄일 수 있다. Dynamo는 vLLM·SGLang·TensorRT-LLM 등 엔진에 중립적이다.

실무 적용 사례

실제 서비스에서 추론 API를 어떻게 운영하는지 추천 시스템 사례로 살펴본다. 캐시 우선 조회, 캐시 미스 시 실시간 추론, 그리고 추론 실패 시 폴백까지 — 프로덕션 추론 API가 갖춰야 할 기본 패턴이 모두 들어 있다. 동시에 GPU 추론 자원은 한정적이므로, 캐싱과 배칭으로 실제 모델 호출 횟수 자체를 줄이는 설계가 비용을 좌우한다.

python # 실무 적용 사례: 이커머스 추천 시스템 import asyncio import redis from concurrent.futures import ThreadPoolExecutor import logging class RecommendationService: def __init__(self): self.redis_client = redis.Redis(host='localhost', port=6379) self.thread_pool = ThreadPoolExecutor(max_workers=20) self.model_cache = {} async def get_product_recommendations(self, user_id: str, num_items: int = 10): """사용자 맞춤 상품 추천 API""" try: # 캐시된 추천 결과 확인 cached_result = await self.get_cached_recommendations(user_id) if cached_result: return cached_result # 실시간 모델 추론 user_features = await self.get_user_features(user_id) item_features = await self.get_item_features() # 병렬 추론으로 성능 최적화 recommendations = await self.parallel_inference( user_features, item_features, num_items ) # 결과 캐싱 (5분간) await self.cache_recommendations(user_id, recommendations, ttl=300) return { "user_id": user_id, "recommendations": recommendations, "generated_at": time.time() } except Exception as e: logging.error(f"Recommendation error for user {user_id}: {str(e)}") return await self.fallback_recommendations(user_id)
EXAMPLE

이커머스 추천 API 패턴: 인기 추천을 Redis로 캐싱해 캐시 히트 시 수 ms 내 응답, 캐시 미스만 모델로 추론, 추론 실패 시 베스트셀러 기반 폴백으로 가용성 확보 — 모델 자체보다 이 운영 설계가 체감 성능과 비용을 좌우한다.

트러블슈팅 및 성능 최적화

머신러닝 모델 서빙 시스템 운영 중 발생할 수 있는 다양한 문제들과 해결 방법을 체계적으로 정리했다. 메모리 누수, 추론 지연, 모델 버전 관리 등 실무에서 자주 마주치는 이슈들을 중점적으로 다룹니다.

python # 성능 최적화 및 모니터링 시스템 import psutil import time from dataclasses import dataclass import prometheus_client class ModelMonitoringSystem: def __init__(self): self.metrics_collector = prometheus_client.CollectorRegistry() self.inference_time_histogram = prometheus_client.Histogram( 'ml_inference_duration_seconds', 'ML model inference time', ['model_name', 'version'], registry=self.metrics_collector ) def monitor_model_performance(self, model_name: str): """모델 성능 실시간 모니터링""" def decorator(func): async def wrapper(*args, **kwargs): start_time = time.time() try: result = await func(*args, **kwargs) inference_time = time.time() - start_time # Prometheus 메트릭 업데이트 self.inference_time_histogram.labels( model_name=model_name, version='v1' ).observe(inference_time) # 성능 임계값 확인 if inference_time > 1.0: # 1초 이상 await self.send_performance_alert(model_name, inference_time) return result except Exception as e: await self.handle_inference_error(model_name, str(e)) raise return wrapper return decorator
TIP

• 모델 양자화로 메모리 50% 절약 • 배치 추론으로 처리량 3배 향상 • GPU 메모리 최적화로 동시 처리 증대

마무리

2026년의 모델 서빙은 "모델을 어떻게 API로 감싸느냐"보다 "한정된 GPU로 얼마나 많은 요청을 경제적으로 처리하느냐"의 문제로 옮겨갔다. 전통 ML 모델은 FastAPI나 BentoML로 단순하게 시작하고, LLM이라면 vLLM을 기본으로 두고 Triton·Ray Serve로 운영 레이어를 얹는 것이 합리적인 출발점이다.

최적화는 한 번에 다 적용할 필요 없다. 먼저 양자화(FP8 또는 INT4)와 연속 배칭으로 처리량과 메모리를 잡고, 공통 프롬프트가 많으면 프리픽스 캐싱을, 지연이 더 중요해지면 분리형 서빙과 스페큘레이티브 디코딩을 단계적으로 도입한다. 작게 시작해 측정하고 병목이 보이는 곳에만 기법을 더하는 것이 가장 빠른 길이다.

머신러닝 모델 서빙 MLOps FastAPI 추론 API 백엔드
junetapa
junetapa
AI 도구를 직접 써보고 솔직한 경험을 공유하는 개발자.
Twitter Facebook URL 복사