vLLM 완벽 가이드 — PagedAttention으로 LLM 추론 처리량 24배, GPU 비용 절감

vLLM 완벽 가이드 — PagedAttention으로 LLM 추론 처리량 24배, GPU 비용 절감

이 글의 핵심

vLLM은 UC Berkeley에서 시작된 오픈소스 LLM 추론 서버로, PagedAttention·Continuous Batching 기술로 동일 GPU에서 HuggingFace TGI 대비 최대 24배 높은 처리량을 보여줍니다. OpenAI 호환 API를 제공해 ChatGPT API 클라이언트를 그대로 self-hosting으로 전환할 수 있고 Llama·Qwen·DeepSeek·Mistral·Gemma 등 거의 모든 주요 오픈 모델을 지원합니다. 이 글은 배포·성능 튜닝·양자화·K8s 프로덕션 운영까지 체계적으로 다룹니다.

이 글의 핵심

vLLM은 2023년 UC Berkeley 연구에서 출발해 현재 Linux Foundation AI & Data의 사실상 표준 LLM 추론 서버입니다. 핵심 기여:

  • PagedAttention: KV cache를 OS 페이지처럼 블록 단위로 관리 → 메모리 단편화 제거
  • Continuous Batching: 요청 단위가 아닌 토큰 단위로 배치 → GPU utilization 극대화
  • Prefix Caching: 동일한 시스템 프롬프트를 여러 요청이 공유
  • Speculative Decoding: 작은 draft 모델로 후보 생성 후 대형 모델이 검증
  • Chunked Prefill: 긴 프롬프트를 토큰 단위로 쪼개 latency/throughput 균형

결과: 동일 GPU에서 HuggingFace TGI 대비 2-24배 처리량, OpenAI API 호환성까지.

설치

Docker 한 줄

docker run --runtime nvidia --gpus all \
  -p 8000:8000 --ipc=host \
  -v $HOME/.cache/huggingface:/root/.cache/huggingface \
  -e HUGGING_FACE_HUB_TOKEN=hf_xxx \
  vllm/vllm-openai:latest \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --max-model-len 8192

pip 설치

# CUDA 12.1 환경
pip install vllm

# OpenAI 호환 서버 실행
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.9

클라이언트 호출 (OpenAI SDK)

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="none",   # vLLM은 기본 auth 없음, 필요하면 --api-key 플래그
)

resp = client.chat.completions.create(
    model="meta-llama/Llama-3.1-8B-Instruct",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "vLLM을 한 문장으로 설명해줘"},
    ],
    temperature=0.7,
    max_tokens=256,
)
print(resp.choices[0].message.content)

LangChain·LlamaIndex·LiteLLM 클라이언트 모두 base URL만 바꾸면 동작합니다.

처리량을 좌우하는 핵심 옵션

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 16384 \
  --max-num-seqs 256 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --quantization awq \
  --dtype half
옵션의미권장
--tensor-parallel-size모델을 N GPU에 분할2의 거듭제곱 (2, 4, 8)
--max-model-len최대 context 길이실제 필요한 만큼 (크면 KV cache 소모)
--max-num-seqs동시 처리 sequence 수64-256 (KV cache 여유에 따라)
--gpu-memory-utilization단일 GPU 메모리 사용 비율0.9 (위험하면 0.85)
--enable-prefix-caching시스템 프롬프트 재사용대화형 API에 필수
--enable-chunked-prefill긴 prompt를 조각 처리긴 컨텍스트 워크로드
--quantizationawq/gptq/fp8메모리 1/2-1/4
--dtypehalf(fp16)/bfloat16Ampere+ 는 bfloat16

PagedAttention 간단 이해

전통 KV cache는 “한 요청당 연속 메모리”를 잡아 단편화로 GPU가 놀게 됩니다.

[seq1 길이 500][  빈 공간 3500  ][seq2 1800][ 빈 공간 2200 ]

PagedAttention은 block(기본 16 토큰) 단위로 매핑해:

[B1][B2][B3]   ←→   [B8][B9]
[B4][B5]            [B10][B11][B12]
...

KV 블록 풀에서 필요한 만큼만 할당 → 단편화 거의 0, 메모리 활용 2-3배 증가.

Continuous Batching

기존 static batching은 배치 내 모든 요청이 같은 토큰 수를 생성할 때까지 기다립니다. Continuous batching은 매 step마다 배치를 재구성해 짧은 요청이 끝나면 즉시 새 요청을 투입합니다. GPU가 놀지 않게 되어 처리량이 극적으로 오릅니다.

양자화 비교

형식메모리품질 손실추천 시나리오
FP16/BF16100%없음정확도가 최우선
AWQ 4-bit~28%매우 작음(1-3%)프로덕션 기본 선택
GPTQ 4-bit~28%작음(2-4%)AWQ 없을 때
FP8 (H100+)~50%거의 없음Hopper GPU
GGUF (llama.cpp)~25-50%모델별CPU+GPU 혼합

AWQ 모델은 HuggingFace의 TheBloke/*-AWQ, casperhansen/llama-3-8b-awq 등으로 쉽게 구할 수 있습니다.

Tool Calling (함수 호출)

tools = [{
  "type": "function",
  "function": {
    "name": "search_issues",
    "description": "GitHub 이슈 검색",
    "parameters": {
      "type": "object",
      "properties": {"q": {"type": "string"}},
      "required": ["q"]
    }
  }
}]

resp = client.chat.completions.create(
    model="meta-llama/Llama-3.1-70B-Instruct",
    messages=[{"role": "user", "content": "latest vllm issues"}],
    tools=tools,
    tool_choice="auto",
)
print(resp.choices[0].message.tool_calls)

vLLM은 모델별 native tool calling 파서를 내장해 Hermes/Llama-3.1/Mistral/Qwen 형식을 자동 인식합니다.

# 서버 시작 시
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --enable-auto-tool-choice \
  --tool-call-parser llama3_json

Structured Output (JSON Schema 강제)

schema = {
    "type": "object",
    "properties": {
        "answer": {"type": "string"},
        "confidence": {"type": "number"}
    },
    "required": ["answer", "confidence"]
}

resp = client.chat.completions.create(
    model="...",
    messages=[...],
    extra_body={"guided_json": schema},
)

vLLM은 outlines/xgrammar 기반 제약 디코딩으로 항상 유효한 JSON을 반환하도록 보장합니다. 프로덕션에서 후처리 파싱 실패를 거의 제거합니다.

Kubernetes 프로덕션 배포

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama70b
spec:
  replicas: 2
  selector: {matchLabels: {app: vllm-llama70b}}
  template:
    metadata:
      labels: {app: vllm-llama70b}
    spec:
      nodeSelector:
        accelerator: nvidia-h100
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model=meta-llama/Llama-3.1-70B-Instruct"
            - "--tensor-parallel-size=4"
            - "--max-model-len=16384"
            - "--max-num-seqs=128"
            - "--enable-prefix-caching"
            - "--gpu-memory-utilization=0.92"
            - "--quantization=fp8"
          ports: [{containerPort: 8000}]
          env:
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom: {secretKeyRef: {name: hf, key: token}}
          resources:
            limits:
              nvidia.com/gpu: 4
              memory: 128Gi
          volumeMounts:
            - {name: hf-cache, mountPath: /root/.cache/huggingface}
            - {name: shm, mountPath: /dev/shm}
          readinessProbe:
            httpGet: {path: /health, port: 8000}
            initialDelaySeconds: 300
            periodSeconds: 10
          livenessProbe:
            httpGet: {path: /health, port: 8000}
            initialDelaySeconds: 600
            periodSeconds: 30
      volumes:
        - name: hf-cache
          persistentVolumeClaim: {claimName: hf-cache}
        - name: shm
          emptyDir: {medium: Memory, sizeLimit: 32Gi}

포인트

  • initialDelaySeconds 크게 — 70B 모델 로딩 5-10분
  • /dev/shm을 emptyDir memory로 32GB — Tensor Parallel용 NCCL 통신
  • HF 캐시는 PVC로 공유해 재시작 시 다운로드 생략
  • 2 replica로 롤링 업데이트/장애 대응

Service / HPA

apiVersion: v1
kind: Service
metadata: {name: vllm-llama70b}
spec:
  selector: {app: vllm-llama70b}
  ports: [{port: 80, targetPort: 8000}]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: vllm-llama70b}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-llama70b
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_waiting
        target: {type: AverageValue, averageValue: "10"}

vLLM의 커스텀 메트릭(vllm_num_requests_waiting)을 Prometheus Adapter로 연결하면 대기 큐 깊이 기반 오토스케일링이 가능합니다.

관측: Prometheus 메트릭

vLLM은 :8000/metrics에 풍부한 메트릭을 노출합니다.

메트릭의미
vllm:num_requests_running처리 중 요청 수
vllm:num_requests_waiting대기 큐
vllm:gpu_cache_usage_percKV cache 사용률
vllm:time_to_first_token_secondsTTFT p50/p95
vllm:time_per_output_token_secondsTPOT
vllm:e2e_request_latency_seconds전체 지연
vllm:prompt_tokens_total / vllm:generation_tokens_total토큰 카운터

Grafana 공식 대시보드로 바로 시각화 가능합니다.

프리픽스 캐시 활용

동일한 system prompt를 쓰는 챗봇/agent는 매 요청마다 시스템 프롬프트를 다시 prefill하는 낭비가 큽니다. --enable-prefix-caching은 KV cache 블록을 hash로 식별해 재사용합니다.

예: 1000 토큰 시스템 프롬프트를 100명이 동시 사용 시

  • 캐시 없음: 100,000 토큰 prefill
  • 프리픽스 캐시: 1,000 토큰 prefill + 99회 재사용 → 99% 절감

프롬프트 일부만 동일해도 토큰 접두사가 일치하는 구간까지 캐시됩니다.

Speculative Decoding

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --speculative-model meta-llama/Llama-3.2-1B \
  --num-speculative-tokens 5 \
  --tensor-parallel-size 4

작은 draft 모델이 5 토큰 후보를 생성하면 큰 모델이 한 forward pass로 검증·재사용해 단일 사용자 TPS 1.5-3배 향상.

비용 최적화 사례

시나리오: Llama 3.1 70B를 하루 100만 요청 서빙

  1. FP16: A100 80GB × 4 Pod × 3 replicas = 12 GPU, 월 ~$20,000
  2. FP8 + prefix cache + speculative: H100 × 4 Pod × 2 replicas = 8 GPU, 처리량 2.5배 ↑
  3. AWQ 4-bit: L40S × 2 Pod × 3 replicas = 6 GPU, 품질 99%, 비용 40% ↓

동일 트래픽에서 월 $8,000까지 절감 가능 — 프로덕션 경험 범위의 실제 수치입니다.

문제 해결

OOM: KV cache out of memory

  • --max-num-seqs 낮추기
  • --max-model-len 줄이기 (실제 사용 길이에 맞게)
  • --gpu-memory-utilization 0.85 정도로
  • Chunked prefill 활성화

낮은 TPS

  • --enable-prefix-caching 확인
  • Tensor parallel이 비대칭 — 2의 거듭제곱만
  • 양자화 고려

Tool call 파싱 실패

  • 모델별 파서: llama3_json, hermes, mistral, pythonic 등 선택
  • guided_json으로 schema 강제

느린 시작

  • HF 캐시 PVC 준비 (콜드스타트 10분 → 1분)
  • 모델을 S3·GCS에 mirror해 download 가속

NCCL 통신 오류 (TP > 1)

  • /dev/shm 크기 32GB+
  • NCCL_DEBUG=INFO로 로그 확인
  • GPU 간 P2P/NVLink 지원 확인

언제 vLLM이 아닌 다른 선택지

  • CPU 서빙/엣지: llama.cpp, Ollama
  • 매우 작은 모델 & 초저지연 요구: TensorRT-LLM
  • 엔드투엔드 파인튜닝+서빙: Modal, Together, Anyscale
  • Windows 네이티브: LM Studio, Ollama
  • 관리형 서버리스: OpenRouter, Together AI API

체크리스트

  • 모델 크기별 최소 GPU 요건 계산
  • --tensor-parallel-size는 2의 거듭제곱
  • --enable-prefix-caching 항상 on
  • --max-num-seqs·--max-model-len 워크로드 맞춤 튜닝
  • AWQ/FP8 양자화 검토
  • HF 캐시를 PVC/공유 스토리지로
  • /dev/shm tmpfs 충분히
  • Prometheus 메트릭 대시보드·알림
  • 2+ replica + HPA로 HA 구성
  • OpenAI SDK 호환 테스트 (base_url만 교체)

마무리

vLLM은 2026년 현재 오픈소스 LLM 서빙의 사실상 표준입니다. PagedAttention·Continuous Batching이라는 연구 혁신이 프로덕션 추론 비용을 몇 배 절감해주고, OpenAI API 호환으로 기존 클라이언트 마이그레이션이 거의 공짜입니다. Llama·Qwen·DeepSeek 같은 최신 오픈 모델을 self-hosting해 데이터 주권과 비용을 잡고 싶다면 vLLM이 현재 최선의 선택이며, 이 가이드의 튜닝 옵션·K8s 패턴만 적용해도 프로덕션 수준의 서빙이 가능합니다.

관련 글

  • LLM 로컬 배포 완벽 가이드
  • Ollama 완벽 가이드
  • LiteLLM 완벽 가이드
  • GPU Kubernetes 배포 가이드