vLLM
Virtual Large Language Model
LLM 추론 최적화 라이브러리. PagedAttention으로 처리량 극대화.
Virtual Large Language Model
LLM 추론 최적화 라이브러리. PagedAttention으로 처리량 극대화.
vLLM은 UC Berkeley에서 개발한 고성능 LLM 추론 및 서빙 라이브러리입니다. 2023년 공개 이후 PagedAttention이라는 혁신적인 메모리 관리 기법으로 기존 대비 2~24배 높은 처리량(throughput)을 달성하며 업계 표준으로 자리잡았습니다. OpenAI API와 호환되는 서버를 쉽게 구축할 수 있어, 프로덕션 LLM 서비스 구축에 필수적인 도구가 되었습니다.
vLLM의 핵심 기술인 PagedAttention은 운영체제의 가상 메모리 페이징에서 영감을 받았습니다. 기존 LLM 추론에서는 KV 캐시(Key-Value Cache)가 연속적인 메모리 공간을 차지해야 해서, 시퀀스 길이에 따라 메모리를 사전 할당하고 남는 공간은 낭비되었습니다. PagedAttention은 KV 캐시를 고정 크기 블록으로 나누어 비연속적으로 저장함으로써 메모리 낭비를 60~80% 줄입니다.
vLLM은 Continuous Batching을 통해 동적으로 요청을 배치합니다. 전통적인 정적 배칭은 가장 긴 시퀀스가 끝날 때까지 GPU가 대기해야 했지만, vLLM은 완료된 요청을 즉시 제거하고 새 요청을 추가합니다. 또한 Tensor Parallelism과 Pipeline Parallelism을 지원해 다중 GPU 환경에서 대형 모델(70B+)을 효율적으로 서빙할 수 있습니다.
실무에서 vLLM은 Llama, Mistral, Qwen, Yi 등 대부분의 오픈소스 LLM을 지원하며, AWQ/GPTQ 양자화 모델도 서빙 가능합니다. Kubernetes 환경에서의 배포, Prometheus 메트릭 노출, OpenAI 호환 API 등 프로덕션에 필요한 기능을 기본 제공합니다. 2025년 현재 H100에서 Llama 8B 기준 2,300~2,500 tokens/s의 처리량을 기록하며, Ollama 대비 20배 이상의 처리량 차이를 보입니다.
# vLLM 완전 가이드: 서버 구축, 클라이언트, 벤치마킹
# pip install vllm openai
# ============================================
# 1. vLLM 서버 실행 (터미널에서)
# ============================================
# 기본 실행 (단일 GPU)
# python -m vllm.entrypoints.openai.api_server \
# --model meta-llama/Llama-3.1-8B-Instruct \
# --port 8000
# 멀티 GPU (Tensor Parallelism)
# python -m vllm.entrypoints.openai.api_server \
# --model meta-llama/Llama-3.1-70B-Instruct \
# --tensor-parallel-size 4 \
# --port 8000
# 양자화 모델 서빙 (메모리 절약)
# python -m vllm.entrypoints.openai.api_server \
# --model TheBloke/Llama-2-70B-Chat-AWQ \
# --quantization awq \
# --port 8000
# ============================================
# 2. Python에서 vLLM 직접 사용
# ============================================
from vllm import LLM, SamplingParams
def basic_vllm_inference():
"""vLLM으로 오프라인 배치 추론"""
# 모델 로드
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
tensor_parallel_size=1, # GPU 수
gpu_memory_utilization=0.9, # GPU 메모리 사용률
max_model_len=4096 # 최대 컨텍스트 길이
)
# 샘플링 파라미터
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.95,
max_tokens=512,
stop=["", "[/INST]"]
)
# 배치 프롬프트
prompts = [
"[INST] Python으로 피보나치 함수를 작성해주세요. [/INST]",
"[INST] 머신러닝과 딥러닝의 차이를 설명해주세요. [/INST]",
"[INST] Docker의 장점을 3가지 알려주세요. [/INST]",
]
# 추론 (자동 배칭)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt[:50]}...")
print(f"Output: {generated_text[:200]}...")
print("-" * 50)
return outputs
# ============================================
# 3. OpenAI 호환 클라이언트 사용
# ============================================
from openai import OpenAI
def vllm_openai_client():
"""vLLM 서버를 OpenAI 클라이언트로 호출"""
# vLLM 서버 주소 (로컬)
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="dummy" # vLLM은 API 키 검증 안 함
)
# Chat Completion
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": "당신은 유능한 AI 어시스턴트입니다."},
{"role": "user", "content": "vLLM의 장점을 설명해주세요."}
],
temperature=0.7,
max_tokens=500
)
print(response.choices[0].message.content)
return response
# ============================================
# 4. 스트리밍 응답
# ============================================
def vllm_streaming():
"""스트리밍으로 토큰 단위 응답 받기"""
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="dummy"
)
stream = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "user", "content": "Python의 GIL에 대해 설명해주세요."}
],
stream=True,
max_tokens=300
)
full_response = ""
for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_response += content
return full_response
# ============================================
# 5. 벤치마킹 및 성능 측정
# ============================================
import time
import asyncio
import aiohttp
async def benchmark_vllm(
base_url: str = "http://localhost:8000",
num_requests: int = 100,
concurrency: int = 10
):
"""vLLM 서버 벤치마킹"""
prompt = "What is the capital of France? Answer in one word."
async def single_request(session):
start = time.time()
async with session.post(
f"{base_url}/v1/completions",
json={
"model": "meta-llama/Llama-3.1-8B-Instruct",
"prompt": prompt,
"max_tokens": 50,
"temperature": 0
}
) as response:
result = await response.json()
latency = time.time() - start
tokens = result.get("usage", {}).get("completion_tokens", 0)
return latency, tokens
# 동시 요청 실행
async with aiohttp.ClientSession() as session:
semaphore = asyncio.Semaphore(concurrency)
async def bounded_request():
async with semaphore:
return await single_request(session)
start_time = time.time()
results = await asyncio.gather(*[
bounded_request() for _ in range(num_requests)
])
total_time = time.time() - start_time
# 결과 분석
latencies = [r[0] for r in results]
total_tokens = sum(r[1] for r in results)
print(f"=== vLLM Benchmark Results ===")
print(f"Total requests: {num_requests}")
print(f"Concurrency: {concurrency}")
print(f"Total time: {total_time:.2f}s")
print(f"Throughput: {num_requests / total_time:.2f} req/s")
print(f"Token throughput: {total_tokens / total_time:.2f} tokens/s")
print(f"Avg latency: {sum(latencies) / len(latencies) * 1000:.2f} ms")
print(f"P50 latency: {sorted(latencies)[len(latencies)//2] * 1000:.2f} ms")
print(f"P99 latency: {sorted(latencies)[int(len(latencies)*0.99)] * 1000:.2f} ms")
# ============================================
# 6. 프로덕션 설정 예시 (Docker Compose)
# ============================================
DOCKER_COMPOSE_EXAMPLE = """
# docker-compose.yml
version: '3.8'
services:
vllm:
image: vllm/vllm-openai:latest
ports:
- "8000:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
command: >
--model meta-llama/Llama-3.1-8B-Instruct
--tensor-parallel-size 1
--gpu-memory-utilization 0.9
--max-model-len 8192
--enable-prefix-caching
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
"""
# 실행 예시
if __name__ == "__main__":
# 오프라인 추론 (vLLM 직접 사용)
# basic_vllm_inference()
# 서버 클라이언트 사용
# vllm_openai_client()
# 스트리밍
# vllm_streaming()
# 벤치마크 실행
# asyncio.run(benchmark_vllm())
print("vLLM 코드 예제 준비 완료!")
print("서버 실행: python -m vllm.entrypoints.openai.api_server --model ")
vLLM 추론 성능 벤치마크 (2025년 기준)
| 모델 | GPU | 처리량 (tokens/s) | P99 지연시간 | 비고 |
|---|---|---|---|---|
| Llama 3.1 8B | H100 80GB | 2,300~2,500 | 80ms | 단일 GPU |
| Llama 3.1 70B | 4x A100 80GB | 400~600 | 150ms | TP=4 |
| Qwen2.5 7B | 3x V100 32GB | 1,782~2,474 | ~100ms | TP=2 |
| Gemma3 4B | A100 40GB | 3,000+ | 50ms | 소형 모델 |
| Mistral 7B AWQ | RTX 4090 24GB | 800~1,200 | 120ms | 양자화 |
vLLM vs 경쟁 솔루션 비교
| 솔루션 | 피크 처리량 | P99 지연시간 | 메모리 효율 |
|---|---|---|---|
| vLLM | 793 TPS | 80ms | 60~80% 절감 |
| Ollama | 41 TPS | 673ms | 기본 |
| HuggingFace TGI | ~500 TPS | ~120ms | Flash Attention |
| TensorRT-LLM | ~900 TPS | ~70ms | NVIDIA 최적화 |
GPU 요구사항 가이드
| 모델 크기 | FP16 VRAM | AWQ/GPTQ | 권장 GPU |
|---|---|---|---|
| 7B | ~14GB | ~4GB | RTX 4090 / A10 |
| 13B | ~26GB | ~7GB | A100 40GB |
| 34B | ~68GB | ~18GB | A100 80GB |
| 70B | ~140GB | ~35GB | 2x A100 80GB |
| 405B | ~810GB | ~100GB | 8x H100 80GB |
"LLM API 비용이 너무 높아서 자체 서빙을 검토 중인데요." - "vLLM으로 셀프호스팅하면 비용을 90% 이상 줄일 수 있어요. H100 한 대로 Llama 8B를 서빙하면 초당 2,000토큰 이상 처리 가능하고, OpenAI API와 100% 호환돼서 코드 변경도 base_url만 바꾸면 됩니다. 초기 GPU 비용이 있지만, 월 10만 요청 이상이면 6개월 내 회수돼요."
"vLLM의 PagedAttention이 왜 중요한가요?" - "기존 LLM 추론에서 KV 캐시는 시퀀스 최대 길이만큼 메모리를 미리 할당해야 했어요. 실제로 사용하는 건 일부인데 나머지는 낭비되죠. PagedAttention은 OS의 가상 메모리처럼 필요한 만큼만 블록 단위로 할당해서 메모리 낭비를 60~80% 줄입니다. 덕분에 같은 GPU로 더 많은 동시 요청을 처리할 수 있어요."
"vLLM vs TensorRT-LLM 어떤 걸 써야 하나요?" - "vLLM은 설치가 쉽고 다양한 모델을 바로 지원해서 빠른 프로토타이핑에 좋아요. TensorRT-LLM은 NVIDIA가 만들어서 H100에서 최적 성능이 나오지만, 모델 변환 과정이 필요하고 NVIDIA GPU 전용이에요. 처음 시작이거나 다양한 모델을 테스트할 땐 vLLM, 프로덕션에서 최대 성능이 필요하면 TensorRT-LLM을 고려하세요."
gpu_memory_utilization을 0.95 이상으로 설정하면 KV 캐시 공간이 부족해 OOM이 발생합니다. 0.85~0.90이 안전하며, 긴 컨텍스트가 필요하면 max_model_len을 명시적으로 제한하세요.
tensor_parallel_size는 모델의 attention head 수로 나눠떨어져야 합니다. 예를 들어 32개 head 모델에 TP=3은 불가능합니다. 또한 GPU 간 통신 대역폭이 낮으면 오히려 성능이 떨어질 수 있어 NVLink 연결을 권장합니다.
프로덕션에서는 --enable-prefix-caching 옵션으로 시스템 프롬프트 캐싱을 활성화하세요. Prometheus 메트릭(/metrics)을 모니터링하고, HuggingFace 모델은 사전 다운로드(huggingface-cli download)로 콜드 스타트를 방지하세요. 양자화 모델(AWQ/GPTQ)로 메모리 대비 처리량을 최적화할 수 있습니다.