목차
- LLM 추론 비용, 이대로 괜찮을까요?
- vLLM이 뭔데 이렇게 빠를까요?
- 기존 LLM 추론의 문제점
- vLLM의 핵심 기술: PagedAttention
- vLLM 설치: 환경 세팅부터 차근차근
- 사전 요구사항 확인
- pip로 설치하기
- Docker로 설치하기 (권장)
- vLLM 사용법: 실전 추론 서버 구성
- 1단계: 기본 API 서버 실행
- 2단계: Python 코드로 직접 추론
- 3단계: OpenAI SDK로 API 서버 호출
- LLM 추론 성능 최적화: 핵심 파라미터 튜닝
- 주요 최적화 옵션 비교
- 양자화(Quantization)로 비용 절감하기
- 멀티 GPU 텐서 병렬화
- 스트리밍 응답으로 사용자 경험 개선
- ⚠️ 실제로 겪은 트러블슈팅 모음
- 문제 1: CUDA Out of Memory (OOM) 오류
- 문제 2: 모델 로딩이 너무 느림
- 문제 3: 멀티 GPU 환경에서 NCCL 오류
- 문제 4: 특정 모델 로드 실패
- 성능 모니터링: 제대로 잘 돌아가고 있는지 확인하기
- vLLM 내장 메트릭 확인
- 간단한 성능 테스트 스크립트
- LLM 비용 절감 전략 총정리
- 마무리: vLLM으로 LLM 운영, 이제 좀 해볼 만합니다
- 자주 묻는 질문 (FAQ)
- Q. vLLM은 무료인가요?
- Q. CPU만으로 vLLM을 실행할 수 있나요?
- Q. 어떤 모델이 vLLM에서 지원되나요?
- Q. vLLM과 TGI(Text Generation Inference)의 차이는 뭔가요?
LLM 추론 비용, 이대로 괜찮을까요?
요즘 LLM(Large Language Model, 대형 언어 모델)을 서비스에 붙이는 팀들이 정말 많아졌죠. 근데 막상 직접 추론 서버를 운영해보면 첫 번째 벽이 바로 비용이거든요. GPU 한 장 풀로 돌리면서 초당 몇 개 요청도 못 받는 상황, 저도 경험해봤습니다. 처음에 Hugging Face의 기본 파이프라인으로 모델을 띄웠을 때 처리량이 너무 낮아서 '이걸 어떻게 서비스에 쓰지?' 싶었거든요.
그때 동료한테 추천받은 게 바로 vLLM이었습니다. 처음엔 그냥 또 다른 추론 프레임워크겠지 했는데, 써보고 나서 진짜 놀랐어요. 같은 GPU, 같은 모델인데 처리량이 확 달라지더라고요. 이번 글에서는 vLLM이 뭔지, 어떻게 설치하고 실제로 어떻게 쓰는지, 그리고 LLM 추론 성능 최적화와 비용 절감을 위해 어떤 설정을 만져야 하는지 제가 직접 경험한 내용을 바탕으로 정리해드리겠습니다.
▲ vLLM의 핵심 아키텍처 — PagedAttention과 Continuous Batching이 어떻게 GPU 메모리를 효율적으로 활용하는지 보여주는 전체 구성도
vLLM이 뭔데 이렇게 빠를까요?
기존 LLM 추론의 문제점
쉽게 말해서, 기존 LLM 추론 방식은 GPU 메모리를 엄청 낭비하는 구조였어요. 각 요청마다 KV Cache(Key-Value Cache, 어텐션 연산의 중간 결과를 저장하는 공간)를 미리 크게 잡아놓거든요. 요청마다 최대 시퀀스 길이만큼 메모리를 예약해두니까, 실제로 짧은 답변만 생성해도 긴 메모리가 낭비되는 거죠. 이걸 메모리 단편화(Memory Fragmentation) 문제라고 부릅니다.
배치 처리(Batching)도 문제였어요. 여러 요청을 동시에 처리하려면 길이를 맞춰야 하는데, 길이가 제각각인 요청들을 묶으면 짧은 것들은 GPU가 쉬면서 기다리는 상황이 생기거든요. GPU 활용률이 뚝뚝 떨어지죠.
vLLM의 핵심 기술: PagedAttention
vLLM은 UC Berkeley에서 개발된 오픈소스 LLM 추론 엔진입니다. 핵심은 PagedAttention이라는 기술이에요. OS의 가상 메모리(Virtual Memory) 페이징 개념을 KV Cache에 적용한 건데요, 메모리를 고정 크기의 블록(Block)으로 나눠서 필요할 때만 할당하고 공유도 가능하게 만든 거예요.
- PagedAttention: KV Cache를 페이지 단위로 관리 → 메모리 낭비 최소화
- Continuous Batching(연속 배치): 요청이 끝나는 즉시 새 요청을 삽입 → GPU 유휴 시간 최소화
- Tensor Parallelism(텐서 병렬화): 여러 GPU에 모델을 분산 → 대형 모델도 처리 가능
- OpenAI 호환 API 서버: 기존 OpenAI SDK 코드를 거의 그대로 재사용 가능
이 조합 덕분에 같은 하드웨어에서 기존 대비 훨씬 높은 처리량을 낼 수 있는 거예요. 실제로 써보면 체감이 확실하게 됩니다.
vLLM 설치: 환경 세팅부터 차근차근
사전 요구사항 확인
설치 전에 먼저 환경부터 체크해야 해요. 저도 처음에 이 부분 대충 넘겼다가 삽질을 좀 했습니다.
- Python 3.8 이상 (3.10 권장)
- CUDA 11.8 이상 지원 NVIDIA GPU
- CUDA Toolkit 설치 확인:
nvcc --version - 충분한 GPU VRAM (7B 모델 기준 최소 16GB 권장)
⚠️ 주의: AMD GPU도 ROCm을 통해 지원되지만, NVIDIA CUDA 환경에 비해 안정성이 다를 수 있어요. 프로덕션 환경이라면 NVIDIA를 권장합니다.
pip로 설치하기
가장 간단한 방법은 pip 설치입니다. 가상환경을 쓰는 거 잊지 마세요!
# 가상환경 생성 및 활성화
python -m venv vllm-env
source vllm-env/bin/activate # Windows: vllm-env\Scripts\activate
# pip 업그레이드
pip install --upgrade pip
# vLLM 설치 (CUDA 버전에 맞게)
pip install vllm
# 설치 확인
python -c "import vllm; print(vllm.__version__)"
Docker로 설치하기 (권장)
프로덕션 환경이라면 Docker 이미지를 쓰는 게 훨씬 편해요. 의존성 충돌 걱정이 없거든요. 저도 홈랩에서는 Docker로 돌리고 있습니다.
# vLLM 공식 Docker 이미지로 서버 실행 예시
docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:latest \
--model meta-llama/Llama-2-7b-chat-hf
💡 팁: --ipc=host 옵션은 공유 메모리 관련 오류를 막아줘요. 빼먹으면 텐서 병렬화 쓸 때 오류가 날 수 있으니 꼭 넣어주세요.
▲ vLLM 서버 실행 후 터미널 출력 화면 — GPU 메모리 할당 현황과 서버 시작 로그를 확인할 수 있습니다
vLLM 사용법: 실전 추론 서버 구성
1단계: 기본 API 서버 실행
vLLM의 진짜 강점 중 하나가 OpenAI 호환 API 서버를 바로 띄울 수 있다는 거예요. 기존에 OpenAI API를 쓰던 코드를 엔드포인트만 바꿔서 그대로 쓸 수 있거든요. 이거 처음 알았을 때 진짜 편하다 싶었어요.
# OpenAI 호환 API 서버 실행
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--host 0.0.0.0 \
--port 8000 \
--max-model-len 4096
2단계: Python 코드로 직접 추론
API 서버 없이 Python 코드 안에서 직접 vLLM을 쓰는 방법도 있어요. 배치 처리나 파이프라인 구성할 때 더 유연하게 쓸 수 있습니다.
from vllm import LLM, SamplingParams
# 모델 로드
llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")
# 샘플링 파라미터 설정
sampling_params = SamplingParams(
temperature=0.7, # 창의성 조절 (0=결정적, 1=창의적)
top_p=0.9, # nucleus sampling
max_tokens=512, # 최대 생성 토큰 수
stop=["", "[INST]"] # 정지 토큰
)
# 프롬프트 목록 (배치 처리)
prompts = [
"[INST] 파이썬에서 리스트와 튜플의 차이점을 설명해줘 [/INST]",
"[INST] 도커 컨테이너와 가상머신의 차이점은? [/INST]",
"[INST] REST API와 GraphQL의 장단점을 비교해줘 [/INST]",
]
# 추론 실행
outputs = llm.generate(prompts, sampling_params)
# 결과 출력
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"프롬프트: {prompt[:50]}...")
print(f"생성 결과: {generated_text}")
print("-" * 50)
3단계: OpenAI SDK로 API 서버 호출
서버를 띄웠다면 기존 OpenAI SDK 코드를 그대로 쓸 수 있어요. base_url만 바꿔주면 됩니다.
from openai import OpenAI
# vLLM 서버를 가리키도록 설정
client = OpenAI(
api_key="token-abc123", # vLLM은 아무 값이나 넣어도 됨
base_url="http://localhost:8000/v1",
)
# 채팅 완성 요청
response = client.chat.completions.create(
model="meta-llama/Llama-2-7b-chat-hf",
messages=[
{"role": "system", "content": "당신은 친절한 인프라 엔지니어입니다."},
{"role": "user", "content": "Kubernetes와 Docker의 관계를 설명해주세요."},
],
max_tokens=512,
temperature=0.7,
)
print(response.choices[0].message.content)
LLM 추론 성능 최적화: 핵심 파라미터 튜닝
주요 최적화 옵션 비교
여기서 중요한 포인트! vLLM 서버 실행 시 옵션을 어떻게 주느냐에 따라 성능이 크게 달라집니다. 제가 여러 조합을 테스트해보면서 정리한 표예요.
| 옵션 | 설명 | 권장 값 | 영향 |
|---|---|---|---|
--gpu-memory-utilization |
GPU 메모리 사용 비율 | 0.85~0.90 | 처리량 ↑, 너무 높으면 OOM |
--max-num-batched-tokens |
배치당 최대 토큰 수 | 4096~8192 | 처리량 ↑, 메모리 사용 ↑ |
--max-num-seqs |
동시 처리 시퀀스 수 | 128~256 | 동시성 ↑, 지연 시간 ↑ |
--tensor-parallel-size |
텐서 병렬화 GPU 수 | GPU 개수 | 대형 모델 지원 |
--quantization |
양자화 방식 | awq / gptq | 메모리 ↓, 속도 ↑ |
--dtype |
데이터 타입 | bfloat16 | 속도 ↑, 정밀도 약간 ↓ |
양자화(Quantization)로 비용 절감하기
LLM 비용 절감에서 가장 효과적인 방법 중 하나가 바로 양자화예요. 모델의 가중치를 더 낮은 비트로 표현해서 메모리 사용량을 줄이는 기법인데요, vLLM은 AWQ(Activation-aware Weight Quantization)와 GPTQ를 지원합니다.
# AWQ 양자화 모델 사용 예시
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Llama-2-7B-Chat-AWQ \
--quantization awq \
--dtype half \
--gpu-memory-utilization 0.85 \
--max-model-len 4096
# GPTQ 양자화 모델 사용 예시
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Llama-2-7B-Chat-GPTQ \
--quantization gptq \
--dtype float16
저도 7B 모델을 AWQ 4비트로 양자화해서 쓰니까 메모리 사용량이 절반 가까이 줄더라고요. 품질 저하도 생각보다 크지 않아서 실제 서비스에 쓰기에 충분한 수준이었습니다.
멀티 GPU 텐서 병렬화
GPU가 여러 장 있다면 텐서 병렬화(Tensor Parallelism)를 활용할 수 있어요. 13B나 70B 같은 큰 모델을 단일 GPU에 올리기 힘들 때 진가를 발휘합니다.
# 4개 GPU로 70B 모델 실행
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-70b-chat-hf \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.85 \
--max-model-len 4096 \
--dtype bfloat16
스트리밍 응답으로 사용자 경험 개선
토큰이 생성되는 대로 바로바로 보내주는 스트리밍(Streaming)도 vLLM에서 쉽게 구현할 수 있어요. 사용자 입장에서 첫 응답까지 기다리는 시간이 줄어드니까 체감 성능이 확 좋아집니다.
from openai import OpenAI
client = OpenAI(
api_key="token-abc123",
base_url="http://localhost:8000/v1",
)
# 스트리밍 응답
stream = client.chat.completions.create(
model="meta-llama/Llama-2-7b-chat-hf",
messages=[{"role": "user", "content": "쿠버네티스 Pod의 생명주기를 설명해줘"}],
max_tokens=512,
stream=True, # 스트리밍 활성화
)
# 실시간으로 토큰 출력
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="", flush=True)
print() # 마지막 줄바꿈
▲ vLLM 설정별 추론 성능 비교 — Continuous Batching, 양자화, 텐서 병렬화 적용 전후의 처리량(tokens/sec) 변화를 시각화한 차트
⚠️ 실제로 겪은 트러블슈팅 모음
문제 1: CUDA Out of Memory (OOM) 오류
가장 흔한 문제예요. 처음 세팅할 때 저도 이거 때문에 한참 헤맸습니다.
증상: torch.cuda.OutOfMemoryError: CUDA out of memory
해결책:
--gpu-memory-utilization을 0.85 이하로 낮추기--max-model-len을 줄여서 KV Cache 크기 제한--max-num-seqs를 줄여서 동시 처리 시퀀스 제한- 양자화 모델 사용 고려
# OOM 방지를 위한 보수적인 설정
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--gpu-memory-utilization 0.80 \
--max-model-len 2048 \
--max-num-seqs 64
문제 2: 모델 로딩이 너무 느림
Hugging Face에서 모델을 매번 다운받으면 서버 재시작할 때마다 한세월이에요. 로컬 캐시를 제대로 설정해야 합니다.
# Hugging Face 캐시 디렉토리 명시적 설정
export HF_HOME=/data/huggingface/cache
export TRANSFORMERS_CACHE=/data/huggingface/cache
# 또는 --download-dir 옵션 사용
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--download-dir /data/models
문제 3: 멀티 GPU 환경에서 NCCL 오류
텐서 병렬화를 쓸 때 NCCL(NVIDIA Collective Communications Library, GPU 간 통신 라이브러리) 관련 오류가 나는 경우가 있어요.
해결책:
# NCCL 디버그 로그 활성화 (원인 파악용)
export NCCL_DEBUG=INFO
# PCIe 환경에서 P2P 비활성화 시도
export NCCL_P2P_DISABLE=1
# IPC 모드 설정 (Docker 환경)
# docker run 시 --ipc=host 옵션 필수
문제 4: 특정 모델 로드 실패
모든 모델이 vLLM에서 바로 돌아가지는 않아요. vLLM 공식 문서의 Supported Models 페이지에서 지원 모델을 확인할 수 있습니다. 지원하지 않는 모델은 별도 어댑터 작업이 필요하거나 아예 안 될 수도 있으니 먼저 확인하세요.
성능 모니터링: 제대로 잘 돌아가고 있는지 확인하기
vLLM 내장 메트릭 확인
vLLM은 Prometheus(프로메테우스, 모니터링 시스템) 형식의 메트릭을 기본으로 노출해줘요. 서버 실행 후 /metrics 엔드포인트로 확인할 수 있습니다.
# 메트릭 엔드포인트 확인
curl http://localhost:8000/metrics
# 주요 메트릭들:
# vllm:num_requests_running - 현재 처리 중인 요청 수
# vllm:num_requests_waiting - 대기 중인 요청 수
# vllm:gpu_cache_usage_perc - KV Cache 사용률 (%)
# vllm:num_requests_finished - 완료된 요청 수
# vllm:e2e_request_latency - 전체 요청 지연 시간
간단한 성능 테스트 스크립트
직접 성능을 테스트해보고 싶다면 아래 스크립트를 써보세요. 저도 새 설정을 적용할 때마다 이런 식으로 확인합니다.
import time
import asyncio
import aiohttp
async def send_request(session, prompt):
"""단일 요청 전송 및 시간 측정"""
start = time.time()
async with session.post(
"http://localhost:8000/v1/completions",
json={
"model": "meta-llama/Llama-2-7b-chat-hf",
"prompt": prompt,
"max_tokens": 100,
}
) as response:
result = await response.json()
elapsed = time.time() - start
tokens = result["usage"]["completion_tokens"]
return elapsed, tokens
async def benchmark(num_requests=50):
"""동시 요청 벤치마크"""
prompts = ["쿠버네티스란 무엇인가요?" for _ in range(num_requests)]
async with aiohttp.ClientSession() as session:
start_total = time.time()
tasks = [send_request(session, p) for p in prompts]
results = await asyncio.gather(*tasks)
total_time = time.time() - start_total
total_tokens = sum(r[1] for r in results)
print(f"총 요청 수: {num_requests}")
print(f"총 소요 시간: {total_time:.2f}초")
print(f"총 생성 토큰: {total_tokens}")
print(f"처리량: {total_tokens / total_time:.1f} tokens/sec")
print(f"평균 지연: {sum(r[0] for r in results) / len(results):.2f}초")
asyncio.run(benchmark())
LLM 비용 절감 전략 총정리
지금까지 다룬 내용을 비용 절감 관점에서 정리해볼게요. 결국 LLM 추론 비용은 GPU 사용 효율을 얼마나 높이느냐의 문제거든요.
▲ vLLM 기반 LLM 비용 절감 전략 요약 인포그래픽 — 양자화, 배치 최적화, GPU 활용률 향상을 통한 비용 절감 효과 비교
| 전략 | 방법 | 기대 효과 | 주의사항 |
|---|---|---|---|
| 양자화 적용 | AWQ/GPTQ 4비트 모델 사용 | 메모리 50% 절감, 더 작은 GPU로 운영 가능 | 미세한 품질 저하 가능 |
| 배치 최적화 | Continuous Batching 활용 | GPU 유휴 시간 최소화, 처리량 ↑ | 지연 시간 약간 증가 |
| max-model-len 조정 | 실제 필요한 길이로 제한 | KV Cache 절약, 더 많은 동시 요청 | 긴 컨텍스트 처리 불가 |
| 작은 모델 선택 | 7B vs 13B 용도별 분리 | GPU 비용 절반 이하 | 복잡한 태스크 품질 저하 |
| 텐서 병렬화 | 소형 GPU 여러 장 활용 | 대형 GPU 구매 비용 절감 | GPU 간 통신 오버헤드 |
마무리: vLLM으로 LLM 운영, 이제 좀 해볼 만합니다
처음에 LLM 추론 서버를 직접 운영한다고 했을 때 팀에서 반응이 냉담했어요. 비용이 너무 많이 든다고요. 근데 vLLM을 도입하고 양자화를 적용한 후로는 분위기가 달라졌습니다. 같은 GPU로 훨씬 많은 요청을 처리할 수 있게 됐거든요.
정리하면 이렇습니다.
- ✅ vLLM은 PagedAttention과 Continuous Batching으로 기존 대비 높은 처리량을 제공
- ✅ OpenAI 호환 API로 기존 코드 재사용 가능 — 마이그레이션 비용 최소화
- ✅ 양자화(AWQ/GPTQ)로 GPU 메모리 절약 → 더 작은 하드웨어에서 운영 가능
- ✅ 텐서 병렬화로 대형 모델도 멀티 GPU에서 실행 가능
- ✅ Prometheus 메트릭으로 성능 모니터링 가능
처음 세팅할 때는 OOM이니 NCCL 오류니 삽질이 좀 있지만, 한 번 안정화되면 정말 편하게 쓸 수 있어요. 특히 자체 LLM 인프라를 구축하려는 분들께 강력 추천합니다.
다음 글에서는 vLLM과 Kubernetes(쿠버네티스)를 연동해서 오토스케일링 추론 서버를 구성하는 방법을 다룰 예정이에요. GPU 노드를 자동으로 늘리고 줄이는 거라 비용 최적화에 더 도움이 될 거예요.
혹시 설정하다가 막히는 부분이 있으시면 댓글로 남겨주세요. 아는 선에서 최대한 도와드리겠습니다! 🎉
자주 묻는 질문 (FAQ)
Q. vLLM은 무료인가요?
네, vLLM은 Apache 2.0 라이선스의 오픈소스 프로젝트입니다. 소프트웨어 자체는 무료지만 실행할 GPU 하드웨어 비용은 별도로 발생합니다.
Q. CPU만으로 vLLM을 실행할 수 있나요?
vLLM은 기본적으로 NVIDIA GPU 환경에 최적화되어 있습니다. CPU 추론은 llama.cpp 같은 다른 도구가 더 적합할 수 있어요.
Q. 어떤 모델이 vLLM에서 지원되나요?
LLaMA, Mistral, Falcon, GPT-NeoX, OPT 등 주요 오픈소스 모델들을 지원합니다. 정확한 목록은 vLLM 공식 GitHub의 Supported Models 문서에서 확인하세요.
Q. vLLM과 TGI(Text Generation Inference)의 차이는 뭔가요?
TGI는 Hugging Face에서 만든 추론 서버이고, vLLM은 UC Berkeley에서 개발했습니다. 둘 다 고성능 LLM 추론을 목표로 하지만 내부 구현 방식이 다릅니다. 어떤 게 더 좋다기보다는 모델과 환경에 따라 성능 차이가 날 수 있으니 직접 테스트해보는 걸 권장합니다.
'IT > AI' 카테고리의 다른 글
| [AI] LLM 파인튜닝 실전 가이드: LoRA, QLoRA로 커스텀 모델 만들기 (0) | 2026.05.14 |
|---|---|
| [AI] Claude API 실전 활용 가이드: Anthropic 모델 선택부터 비용 최적화까지 (1) | 2026.05.14 |
| [AI] LangChain으로 AI 에이전트 구축: 복잡한 작업 자동화 실전 가이드 (0) | 2026.05.14 |
| [AI] Claude 모델 비교: Opus, Sonnet, Haiku 활용 전략 가이드 (1) | 2026.05.10 |
| [AI] vLLM 실전 가이드: 고성능 LLM 추론 및 API 서빙 최적화 (0) | 2026.05.09 |
| [AI] 로컬 LLM 활용: Ollama와 최신 Claude 모델 비교 분석 (1) | 2026.05.08 |