Skip to content
Published on

vLLM PagedAttention 기반 LLM 프로덕션 서빙 최적화와 추론 엔진 비교 가이드

Authors
  • Name
    Twitter
vLLM PagedAttention

들어가며

LLM을 프로덕션에서 서빙할 때 가장 먼저 부딪히는 벽은 GPU 메모리다. Llama 3.1 70B를 FP16으로 로딩하면 모델 가중치만 140GB가 필요하고, 동시에 여러 요청을 처리하려면 KV Cache가 추가로 수십~수백 GB를 점유한다. 실제 프로덕션 환경에서는 동시 요청 수가 수십에서 수백 개에 달하므로, KV Cache 메모리 관리가 전체 시스템의 처리량(throughput)과 지연 시간(latency)을 결정짓는 핵심 요소가 된다.

전통적인 Transformer 추론 구현에서는 각 요청에 대해 최대 시퀀스 길이만큼의 KV Cache를 사전 할당한다. 최대 4,096 토큰을 허용하는 서비스에서 실제 평균 출력이 512 토큰이라면, 할당된 메모리의 87%가 낭비된다. 이 문제를 근본적으로 해결한 것이 UC Berkeley에서 발표한 PagedAttention 알고리즘이며, 이를 구현한 오픈소스 추론 엔진이 바로 vLLM이다.

이 글에서는 PagedAttention의 동작 원리부터 vLLM의 아키텍처, 프로덕션 배포 설정, 성능 최적화 기법, Kubernetes 기반 오토스케일링, SGLang/TensorRT-LLM과의 비교, 운영 모니터링, 그리고 실패 사례와 복구 절차까지 프로덕션 LLM 서빙에 필요한 전 영역을 다룬다.

PagedAttention 동작 원리

가상 메모리 페이징의 적용

PagedAttention는 운영체제의 가상 메모리 관리에서 영감을 받았다. OS는 프로세스에 연속적인 가상 주소 공간을 제공하지만, 실제 물리 메모리는 고정 크기의 페이지(page) 단위로 비연속적으로 할당한다. PagedAttention은 이 개념을 KV Cache에 그대로 적용한다.

KV Cache를 고정 크기 블록(block)으로 분할하고, 각 블록은 고정된 수의 토큰에 해당하는 Key-Value 텐서를 저장한다. 기본 블록 크기는 16 토큰이다. 요청이 진행되면서 새로운 토큰이 생성될 때마다, 현재 블록이 가득 차면 새 물리 블록을 할당하고 블록 테이블(block table)에 매핑을 추가한다.

기존 연속 할당 방식:
Request A: [████████░░░░░░░░░░░░░░░░░░░░░░░░]  실제 512, 최대 2048 할당
Request B: [██████████████░░░░░░░░░░░░░░░░░░]  실제 896, 최대 2048 할당
Request C: [██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  실제 128, 최대 2048 할당
→ 총 6,144 슬롯 할당, 실제 1,536 사용. 낭비율 75%

PagedAttention 블록 방식 (블록 크기 = 16):
Request A: [B0][B1]...[B31]32 블록만 할당 (512 토큰)
Request B: [B0][B1]...[B55]56 블록만 할당 (896 토큰)
Request C: [B0]...[B7]8 블록만 할당 (128 토큰)
→ 마지막 블록의 내부 단편화만 존재. 평균 낭비율 4% 이하

Copy-on-Write와 Prefix Sharing

PagedAttention의 또 다른 강점은 Copy-on-Write(CoW) 메커니즘이다. Beam search나 parallel sampling처럼 동일한 프롬프트에서 여러 시퀀스를 생성하는 경우, 공통 접두사(prefix)에 해당하는 KV Cache 블록을 물리적으로 공유할 수 있다. 분기가 발생하는 시점에서만 새 블록을 할당하므로, beam search의 메모리 사용량이 최대 55%까지 감소한다.

KV Cache 크기 계산

프로덕션 환경에서 GPU 메모리를 정확히 산정하려면 KV Cache 크기를 예측해야 한다. 다음 함수로 모델별 KV Cache 크기를 계산할 수 있다.

def estimate_kv_cache_memory(
    num_layers: int,
    num_kv_heads: int,
    head_dim: int,
    max_seq_len: int,
    max_batch_size: int,
    dtype_bytes: int = 2,  # FP16
    block_size: int = 16,
) -> dict:
    """
    vLLM PagedAttention 기반 KV Cache 메모리 추정.
    GQA 모델은 num_kv_heads가 num_attention_heads보다 작다.
    """
    # 토큰 하나당 KV Cache 바이트
    per_token_bytes = 2 * num_layers * num_kv_heads * head_dim * dtype_bytes
    # 블록 하나의 바이트
    per_block_bytes = per_token_bytes * block_size
    # 최대 동시 처리 시 필요한 총 블록 수
    total_tokens = max_seq_len * max_batch_size
    total_blocks = (total_tokens + block_size - 1) // block_size
    total_bytes = total_blocks * per_block_bytes
    total_gb = total_bytes / (1024 ** 3)

    return {
        "per_token_kv_bytes": per_token_bytes,
        "per_block_kv_bytes": per_block_bytes,
        "total_blocks": total_blocks,
        "total_kv_cache_gb": round(total_gb, 2),
    }

# Llama 3.1 70B (GQA: 8 KV heads, 80 layers, head_dim=128)
result = estimate_kv_cache_memory(
    num_layers=80,
    num_kv_heads=8,
    head_dim=128,
    max_seq_len=4096,
    max_batch_size=64,
    dtype_bytes=2,
)
print(result)
# {'per_token_kv_bytes': 327680, 'per_block_kv_bytes': 5242880,
#  'total_blocks': 16384, 'total_kv_cache_gb': 80.0}
# → 64 동시 요청 × 4096 토큰이면 KV Cache만 80GB 필요

이 계산 결과를 보면, 70B 모델을 64 동시 요청으로 서빙하려면 모델 가중치 140GB + KV Cache 80GB = 220GB 이상의 VRAM이 필요하다. A100 80GB 3장 이상이 필요한 규모다. PagedAttention은 이 메모리를 실제 사용량 기준으로 동적 할당하여 효율을 극대화한다.

vLLM 아키텍처

V1 엔진과 핵심 구성요소

vLLM 0.7.x 이후 도입된 V1 엔진은 이전 버전 대비 아키텍처를 대폭 개선했다. 핵심 구성요소는 다음과 같다.

Scheduler: 대기 중인 요청들의 우선순위를 결정하고, GPU 메모리 상황에 따라 어떤 요청을 실행할지 결정한다. Preemption(선점) 메커니즘을 통해 메모리가 부족하면 낮은 우선순위 요청의 KV Cache를 CPU 메모리로 스왑하거나 재계산한다.

Block Manager: PagedAttention의 핵심으로, 물리 블록의 할당/해제/공유를 관리한다. 블록 테이블을 유지하며 논리 블록과 물리 블록 간의 매핑을 추적한다.

Worker: 실제 GPU에서 모델 추론을 수행하는 프로세스다. Tensor Parallelism이 활성화된 경우 여러 Worker가 협력하여 하나의 요청을 처리한다.

Model Runner: 모델의 forward pass를 실행하고, FlashAttention이나 FlashInfer 같은 최적화된 어텐션 커널을 호출한다.

Continuous Batching

전통적인 정적 배칭(static batching)에서는 배치 내 모든 요청이 완료될 때까지 새 요청을 추가할 수 없다. 짧은 응답을 생성하는 요청이 긴 응답을 기다려야 하므로 GPU 활용률이 떨어진다.

vLLM의 Continuous Batching은 각 이터레이션마다 완료된 요청을 배치에서 제거하고 대기 중인 새 요청을 즉시 추가한다. 이를 통해 GPU 활용률을 90% 이상으로 유지하면서 평균 지연 시간을 크게 줄인다.

정적 배칭:
시간 →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]  생성 완료
Req 2:  [■■■■■■■■■■░░░░░░░░░░░░░░░░░░░░░░]  일찍 완료, 대기 중
Req 3:  [■■■■░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  일찍 완료, 대기 중
Req 2, 3 자리에 새 요청을 넣을 수 없음

Continuous Batching:
시간 →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]
Req 2:  [■■■■■■■■■■] ← 완료 즉시 슬롯 반환
Req 4:             [■■■■■■■■■■■■■■■■■■■■■■]  ← 즉시 투입
Req 3:  [■■■■] ← 완료
Req 5:       [■■■■■■■■■■■■■■] ← 즉시 투입
GPU가 항상 최대 부하로 동작

vLLM 설치와 기본 사용법

설치

vLLM은 pip로 간단히 설치할 수 있다. CUDA 12.1 이상이 필요하며, 2026년 3월 기준 최신 안정 버전은 0.7.x 계열이다.

# 기본 설치 (CUDA 12.4 기준)
pip install vllm

# 특정 버전 설치
pip install vllm==0.7.2

# FlashInfer 백엔드 사용 시 (권장)
pip install flashinfer-python
pip install vllm

# 소스에서 빌드 (커스텀 CUDA 버전)
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e .

# 설치 확인
python -c "import vllm; print(vllm.__version__)"

OpenAI 호환 API 서버 실행

vLLM의 가장 큰 장점 중 하나는 OpenAI API와 완전 호환되는 서버를 제공한다는 점이다. 기존 OpenAI 클라이언트 코드를 수정 없이 사용할 수 있다.

# 기본 서버 실행
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 0.0.0.0 \
  --port 8000 \
  --tensor-parallel-size 1 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90 \
  --enable-prefix-caching \
  --dtype auto

# 70B 모델을 4-GPU Tensor Parallel로 실행
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --max-num-seqs 256 \
  --disable-log-requests

Python SDK를 이용한 오프라인 추론

서버 없이 Python 코드에서 직접 vLLM 엔진을 사용할 수도 있다. 배치 처리나 벤치마크 시 유용하다.

from vllm import LLM, SamplingParams

# 모델 로딩 (자동으로 PagedAttention 적용)
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    tensor_parallel_size=1,
    max_model_len=4096,
    gpu_memory_utilization=0.90,
    enable_prefix_caching=True,
)

# 샘플링 파라미터 설정
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=1024,
    repetition_penalty=1.1,
    stop=["<|eot_id|>"],
)

# 배치 추론
prompts = [
    "Kubernetes에서 GPU 노드를 관리하는 모범 사례를 설명해주세요.",
    "Python asyncio의 이벤트 루프 동작 원리를 알려주세요.",
    "PostgreSQL 파티셔닝 전략의 장단점을 비교해주세요.",
]

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"Generated: {generated_text[:200]}...")
    print(f"Tokens: {len(output.outputs[0].token_ids)}")
    print("---")

프로덕션 배포 설정

Docker 기반 배포

프로덕션 환경에서는 Docker 컨테이너로 배포하는 것이 표준이다. vLLM 공식 이미지를 기반으로 모델 캐시와 GPU 설정을 적절히 구성해야 한다.

# 공식 이미지를 사용한 프로덕션 배포
docker run -d \
  --name vllm-server \
  --gpus '"device=0,1,2,3"' \
  --shm-size=16g \
  -p 8000:8000 \
  -v /data/models:/root/.cache/huggingface \
  -e HUGGING_FACE_HUB_TOKEN=${HF_TOKEN} \
  -e VLLM_ATTENTION_BACKEND=FLASHINFER \
  --restart unless-stopped \
  vllm/vllm-openai:v0.7.2 \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --max-num-seqs 256 \
  --served-model-name llama-70b \
  --disable-log-requests \
  --uvicorn-log-level warning

주의: --shm-size를 충분히 설정하지 않으면 Tensor Parallel 모드에서 NCCL 통신 오류가 발생한다. 최소 8GB 이상, 가능하면 16GB를 권장한다.

GPU 메모리 설정 전략

--gpu-memory-utilization 파라미터는 vLLM이 사용할 GPU 메모리 비율을 결정한다. 이 값을 너무 높게 설정하면 CUDA OOM이 발생하고, 너무 낮게 설정하면 동시 처리량이 줄어든다.

환경gpu-memory-utilization이유
개발/테스트0.80다른 프로세스와 GPU 공유 가능성
프로덕션 (전용 GPU)0.90~0.92최대 처리량 확보, 약간의 여유
프로덕션 (모니터링 포함)0.88~0.90Prometheus exporter 등이 VRAM 소비
불안정한 워크로드0.85갑작스러운 긴 시퀀스 대비

모델 로딩 최적화

대형 모델의 로딩 시간은 수 분이 걸릴 수 있다. Safetensors 포맷과 로컬 캐시를 활용하면 로딩 속도를 크게 개선할 수 있다.

# 모델을 미리 다운로드하여 로딩 시간 단축
huggingface-cli download meta-llama/Llama-3.1-70B-Instruct \
  --local-dir /data/models/llama-3.1-70b \
  --local-dir-use-symlinks False

# 로컬 경로에서 직접 로딩 (다운로드 불필요)
vllm serve /data/models/llama-3.1-70b \
  --tensor-parallel-size 4 \
  --load-format safetensors \
  --max-model-len 8192

성능 최적화 기법

Prefix Caching (Automatic Prefix Caching)

많은 프로덕션 워크로드에서 시스템 프롬프트는 모든 요청에 공통으로 포함된다. Prefix Caching은 이 공통 접두사의 KV Cache를 캐싱하여 재사용한다. 시스템 프롬프트가 2,000 토큰이고 초당 100 요청이 들어온다면, Prefix Caching 없이는 매 요청마다 2,000 토큰의 KV Cache를 다시 계산해야 한다. Prefix Caching을 활성화하면 첫 요청에서만 계산하고 이후 요청은 캐시에서 즉시 읽어온다.

# Prefix Caching 활성화 (vLLM 0.7.x에서는 기본 비활성화)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --enable-prefix-caching \
  --max-model-len 8192

벤치마크에서 Prefix Caching은 공통 시스템 프롬프트가 있는 워크로드에서 TTFT(Time-To-First-Token)를 최대 8배까지 개선하는 것으로 관측된다. 다만, 프롬프트가 매번 완전히 다른 워크로드에서는 캐시 적중률이 낮아 효과가 미미하다.

Speculative Decoding

Speculative Decoding은 작은 Draft 모델로 여러 토큰을 미리 예측하고, 원본 모델(Target)이 이를 한 번의 forward pass로 검증하는 기법이다. vLLM은 Draft 모델 방식과 ngram 기반 방식 모두를 지원한다.

# Draft 모델을 이용한 Speculative Decoding
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --speculative-model meta-llama/Llama-3.1-8B-Instruct \
  --num-speculative-tokens 5 \
  --speculative-disable-mqa-scorer \
  --tensor-parallel-size 4

# ngram 기반 Speculative Decoding (추가 모델 불필요)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --speculative-model "[ngram]" \
  --num-speculative-tokens 5 \
  --ngram-prompt-lookup-max 4

Speculative Decoding은 greedy decoding이나 낮은 temperature에서 가장 효과적이다. 높은 temperature에서는 Draft 모델의 예측 수락률이 떨어져 오히려 오버헤드가 될 수 있다.

양자화(Quantization)와 서빙

GPTQ, AWQ, FP8 양자화 모델을 vLLM에서 직접 서빙할 수 있다. 양자화는 모델 크기를 줄여 더 적은 GPU로 서빙하거나 동시 처리량을 높이는 데 활용된다.

양자화 기법비트70B VRAM상대 품질vLLM 지원
FP16 (기준)16~140GB100%기본
FP8 (W8A8)8~70GB99.5%지원
AWQ (W4)4~35GB98.5%지원
GPTQ (W4)4~35GB98.0%지원
GGUF (Q4_K_M)4~35GB97.5%제한적
# AWQ 양자화 모델 서빙
vllm serve TheBloke/Llama-3.1-70B-Instruct-AWQ \
  --quantization awq \
  --tensor-parallel-size 2 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90

# FP8 양자화 모델 서빙 (H100/L40S 등 FP8 지원 GPU)
vllm serve neuralmagic/Llama-3.1-70B-Instruct-FP8 \
  --quantization fp8 \
  --tensor-parallel-size 4 \
  --max-model-len 8192

주의: AWQ/GPTQ 모델을 Tensor Parallel로 서빙할 때, 양자화된 모델의 분할 호환성을 반드시 확인해야 한다. 일부 양자화 모델은 특정 TP 크기에서만 정상 동작한다.

Kubernetes 배포 패턴

기본 Deployment 구성

Kubernetes에서 vLLM을 배포할 때는 GPU 리소스 요청, 헬스 체크, 그리고 Graceful Shutdown을 적절히 구성해야 한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama-70b
  namespace: llm-serving
  labels:
    app: vllm
    model: llama-70b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm
      model: llama-70b
  template:
    metadata:
      labels:
        app: vllm
        model: llama-70b
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '8000'
        prometheus.io/path: '/metrics'
    spec:
      terminationGracePeriodSeconds: 120
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.7.2
          args:
            - '--model'
            - 'meta-llama/Llama-3.1-70B-Instruct'
            - '--tensor-parallel-size'
            - '4'
            - '--max-model-len'
            - '8192'
            - '--gpu-memory-utilization'
            - '0.90'
            - '--enable-prefix-caching'
            - '--max-num-seqs'
            - '256'
            - '--served-model-name'
            - 'llama-70b'
            - '--disable-log-requests'
          ports:
            - containerPort: 8000
              name: http
          env:
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: token
            - name: VLLM_ATTENTION_BACKEND
              value: 'FLASHINFER'
          resources:
            requests:
              cpu: '8'
              memory: '32Gi'
              nvidia.com/gpu: '4'
            limits:
              cpu: '16'
              memory: '64Gi'
              nvidia.com/gpu: '4'
          volumeMounts:
            - name: model-cache
              mountPath: /root/.cache/huggingface
            - name: shm
              mountPath: /dev/shm
          startupProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 60 # 모델 로딩에 최대 10분 허용
          readinessProbe:
            httpGet:
              path: /health
              port: http
            periodSeconds: 5
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: http
            periodSeconds: 15
            failureThreshold: 5
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-cache-pvc
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 16Gi
      nodeSelector:
        nvidia.com/gpu.product: 'NVIDIA-A100-SXM4-80GB'
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-llama-70b
  namespace: llm-serving
spec:
  selector:
    app: vllm
    model: llama-70b
  ports:
    - port: 8000
      targetPort: http
      name: http
  type: ClusterIP

KEDA 기반 오토스케일링

LLM 서빙은 트래픽 변동이 크기 때문에 오토스케일링이 필수적이다. KEDA(Kubernetes Event-Driven Autoscaling)를 활용하면 Prometheus 메트릭 기반으로 파드를 자동 스케일링할 수 있다.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-scaler
  namespace: llm-serving
spec:
  scaleTargetRef:
    name: vllm-llama-70b
  minReplicaCount: 1
  maxReplicaCount: 8
  pollingInterval: 15
  cooldownPeriod: 300 # 스케일 다운 전 5분 대기
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: vllm_pending_requests
        query: |
          avg(vllm:num_requests_waiting{model_name="llama-70b"})
        threshold: '10'
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: vllm_gpu_cache_usage
        query: |
          avg(vllm:gpu_cache_usage_perc{model_name="llama-70b"})
        threshold: '85'

주의: GPU 파드의 스케일업은 일반 파드보다 훨씬 느리다. 모델 로딩에 2~10분이 소요되므로, cooldownPeriod를 충분히 길게 설정하고 minReplicaCount를 최소 1 이상으로 유지해야 한다. 스케일업이 필요한 시점과 실제 트래픽 처리가 가능한 시점의 시간차를 반드시 고려하라.

추론 엔진 비교

vLLM vs SGLang vs TensorRT-LLM vs LMDeploy

2026년 현재 주요 LLM 추론 엔진을 여러 관점에서 비교한다. 벤치마크 수치는 Llama 3.1 8B, A100 80GB, 입력 1024 토큰 / 출력 512 토큰 기준이다.

항목vLLM (0.7.x)SGLang (0.4.x)TensorRT-LLMLMDeploy
처리량 (req/s)~42~48~55~40
TTFT (ms)~85~72~60~90
ITL (ms/token)~12~11~9~13
모델 지원 범위매우 넓음넓음중간넓음
설치 난이도쉬움쉬움어려움중간
OpenAI API 호환완전 지원완전 지원부분 지원완전 지원
멀티모달 지원지원지원제한적지원
FP8 양자화지원지원네이티브지원
Prefix Caching지원RadixAttention지원지원
LoRA 서빙지원지원제한적지원
Speculative Decoding지원지원지원제한적
커뮤니티 활성도매우 높음높음중간중간
프로덕션 성숙도높음높음매우 높음중간
라이선스Apache 2.0Apache 2.0Apache 2.0Apache 2.0

엔진 선택 가이드

vLLM을 선택할 때: 가장 넓은 모델 호환성이 필요할 때, 빠르게 프로토타이핑하고 프로덕션까지 이어갈 때, 커뮤니티 생태계와 플러그인이 중요할 때 적합하다. OpenAI 호환 API가 매끄럽고, 새로운 모델 아키텍처 지원이 가장 빠르다.

SGLang을 선택할 때: RadixAttention 기반의 고급 프롬프트 캐싱이 필요할 때, 구조화된 출력(structured output)이 많을 때, TTFT 최적화가 중요한 인터랙티브 워크로드에 적합하다. SGLang의 RadixTree 기반 캐싱은 트리 구조의 다중 턴 대화에서 vLLM의 Prefix Caching보다 효율적이다.

TensorRT-LLM을 선택할 때: 최대 처리량과 최저 지연 시간이 절대적으로 중요할 때, NVIDIA GPU를 전용으로 사용하고 엔진 빌드 과정의 복잡성을 감수할 수 있을 때 적합하다. 모델별로 TensorRT 엔진을 미리 빌드해야 하므로 배포 파이프라인이 복잡해진다.

LMDeploy를 선택할 때: TurboMind 엔진의 높은 성능과 함께 PyTorch 생태계와의 깊은 통합이 필요할 때, 특히 InternLM 계열 모델을 서빙할 때 효과적이다.

배치 크기별 처리량 비교

동시 요청 수vLLM (tok/s)SGLang (tok/s)TensorRT-LLM (tok/s)
18592105
8620680750
322,1002,3502,600
643,8004,2004,500
1285,2005,8006,100

이 수치는 Llama 3.1 8B, A100 80GB, FP16 기준이며 워크로드 특성에 따라 크게 달라질 수 있다. 실제 프로덕션에서는 반드시 자체 벤치마크를 수행해야 한다.

운영 시 주의사항과 모니터링

Prometheus 메트릭 수집

vLLM은 /metrics 엔드포인트를 통해 Prometheus 형식의 메트릭을 노출한다. 핵심 모니터링 지표는 다음과 같다.

메트릭설명경고 기준
vllm:num_requests_running현재 실행 중인 요청 수max_num_seqs의 90% 초과
vllm:num_requests_waiting대기열의 요청 수지속적으로 50 이상
vllm:gpu_cache_usage_percGPU KV Cache 사용률95% 초과 시 경고
vllm:cpu_cache_usage_percCPU 스왑 캐시 사용률50% 초과 시 스왑 빈번
vllm:avg_prompt_throughput_toks_per_s프롬프트 처리 토큰/초기준치 대비 50% 이하
vllm:avg_generation_throughput_toks_per_s생성 토큰/초기준치 대비 50% 이하
vllm:e2e_request_latency_seconds요청별 전체 지연 시간p99가 SLA 초과
vllm:time_to_first_token_seconds첫 토큰까지의 시간p99가 2초 초과

GPU 메모리 모니터링

GPU 수준의 메모리 모니터링은 DCGM(Data Center GPU Manager) Exporter를 통해 수행한다. vLLM 자체 메트릭과 GPU 하드웨어 메트릭을 함께 모니터링해야 전체 그림을 파악할 수 있다.

# Prometheus 쿼리 예시: Grafana 대시보드용

# 1. GPU KV Cache 사용률 (vLLM 내부)
# vllm:gpu_cache_usage_perc{model_name="llama-70b"}

# 2. 실제 GPU 메모리 사용량 (DCGM)
# DCGM_FI_DEV_FB_USED{gpu="0"} / DCGM_FI_DEV_FB_TOTAL{gpu="0"} * 100

# 3. 대기 요청 수 추이
# rate(vllm:num_requests_waiting{model_name="llama-70b"}[5m])

# 4. p99 TTFT 모니터링
# histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m]))

# 5. 초당 생성 토큰 수
# rate(vllm:avg_generation_throughput_toks_per_s[1m])

# AlertManager 규칙 예시
# ALERT VLLMHighCacheUsage
#   IF vllm:gpu_cache_usage_perc > 95
#   FOR 5m
#   LABELS { severity = "warning" }
#   ANNOTATIONS {
#     summary = "vLLM KV Cache 사용률 95% 초과",
#     description = "{{ $labels.model_name }} 모델의 KV Cache 사용률이
#       {{ $value }}%입니다. 스케일업을 검토하세요."
#   }

핵심 운영 지침

  1. 로그 레벨 관리: 프로덕션에서는 반드시 --disable-log-requests를 사용한다. 요청마다 프롬프트를 로깅하면 디스크 I/O 병목과 개인정보 유출 위험이 생긴다.

  2. Graceful Shutdown: SIGTERM 수신 시 진행 중인 요청을 완료할 시간을 충분히 확보한다. Kubernetes의 terminationGracePeriodSeconds를 120초 이상으로 설정하라.

  3. Health Check 구분: startupProbereadinessProbe를 분리한다. 모델 로딩은 수 분이 걸리므로 startupProbefailureThreshold를 충분히 높게 설정한다. readinessProbe는 실제 추론 가능 여부를 확인한다.

  4. shared memory 설정: Tensor Parallel 모드에서 NCCL은 /dev/shm을 통해 GPU 간 통신한다. Docker에서 --shm-size=16g, Kubernetes에서 emptyDirmedium: Memory를 반드시 설정해야 한다.

  5. 모델 캐시 영속화: PVC(PersistentVolumeClaim)를 사용하여 HuggingFace 모델 캐시를 영속화한다. 파드 재시작 시마다 수십 GB 모델을 다시 다운로드하는 것은 비용과 시간 모두에서 비효율적이다.

실패 사례와 복구 절차

Case 1: CUDA Out of Memory (OOM)

증상: 서버 시작 후 일정 시간이 지나면 torch.cuda.OutOfMemoryError가 발생하며 모든 요청이 실패한다.

원인: --gpu-memory-utilization을 너무 높게 설정했거나, --max-model-len이 실제 워크로드 대비 과도하게 클 때 발생한다. KV Cache 할당이 물리 GPU 메모리를 초과한다.

복구 절차:

  1. --gpu-memory-utilization을 0.85로 낮춘다.
  2. --max-model-len을 실제 필요한 최대 길이로 제한한다.
  3. --max-num-seqs를 줄여 동시 요청 수를 제한한다.
  4. 양자화(AWQ/FP8)를 적용하여 모델 메모리를 줄인다.
  5. Tensor Parallel 수를 늘려 GPU당 부하를 분산한다.

예방 조치: 프로덕션 배포 전 반드시 예상 최대 부하의 120%로 스트레스 테스트를 수행한다.

# OOM 디버깅을 위한 메모리 프로파일링
VLLM_LOGGING_LEVEL=DEBUG vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --gpu-memory-utilization 0.85 \
  --max-model-len 4096 \
  --max-num-seqs 64 \
  --enforce-eager  # CUDA Graph 비활성화로 메모리 사용 감소

Case 2: 모델 로딩 실패

증상: 서버 시작 시 ValueError: The model's max seq len is larger than the maximum number of tokens that can be stored in KV cache 또는 Not enough memory 에러가 발생한다.

원인: 지정한 --max-model-len에 필요한 KV Cache가 가용 GPU 메모리를 초과한다. 모델 가중치를 로딩한 후 남은 메모리가 최소한의 KV Cache도 할당하기에 부족하다.

복구 절차:

  1. --max-model-len을 줄인다 (예: 8192에서 4096으로).
  2. --tensor-parallel-size를 늘린다.
  3. 양자화 모델을 사용한다.
  4. --enforce-eager 플래그를 추가하여 CUDA Graph가 차지하는 메모리를 반환한다.

Case 3: NCCL 타임아웃 (Tensor Parallel)

증상: 멀티 GPU 환경에서 RuntimeError: NCCL communicator was aborted 또는 Watchdog caught collective operation timeout이 발생한다.

원인: GPU 간 통신(NVLink/PCIe) 대역폭 부족, /dev/shm 크기 부족, 또는 이기종 GPU 혼합 사용이 원인이다.

복구 절차:

  1. --shm-size를 16GB 이상으로 설정한다.
  2. 동일 사양의 GPU만 사용하도록 nodeSelector를 설정한다.
  3. NCCL_DEBUG=INFO 환경 변수로 상세 로그를 확인한다.
  4. NVLink 연결이 없는 GPU 조합이면 PCIe 대역폭의 한계를 감안하여 Tensor Parallel 수를 줄인다.

Case 4: 응답 품질 저하

증상: 양자화 모델 서빙 후 응답 품질이 눈에 띄게 떨어진다. 반복적인 문장, 맥락 없는 응답, 코드 생성 정확도 하락 등이 관측된다.

원인: 과도한 양자화(INT4)가 모델의 핵심 가중치를 손상시켰다. 특히 코딩이나 수학 태스크에서 INT4 양자화의 품질 저하가 두드러진다.

복구 절차:

  1. FP8 양자화로 전환한다 (품질 손실 최소화).
  2. AWQ 대신 GPTQ를 시도하거나 그 반대를 시도한다.
  3. 양자화 없이 Tensor Parallel 수를 늘려 FP16 서빙을 검토한다.
  4. 양자화 모델의 벤치마크를 주요 태스크별로 수행하여 품질 기준을 설정한다.

Case 5: Kubernetes 파드 무한 재시작

증상: vLLM 파드가 CrashLoopBackOff 상태에 빠진다. 모델 로딩이 완료되기 전에 livenessProbe가 실패하여 kubelet이 컨테이너를 강제 종료한다.

원인: startupProbe를 설정하지 않았거나, failureThreshold가 너무 낮아 모델 로딩 시간을 커버하지 못한다.

복구 절차:

  1. startupProbe를 추가하고 failureThreshold를 60 이상으로 설정한다 (10초 간격이면 10분).
  2. livenessProbeinitialDelaySeconds를 충분히 길게 설정한다.
  3. 모델을 PVC에 미리 다운로드하여 로딩 시간을 단축한다.

체크리스트

프로덕션 vLLM 배포 전 반드시 확인해야 할 항목들이다.

인프라 준비

  • GPU 드라이버와 CUDA 버전이 vLLM 요구사항과 호환되는지 확인
  • GPU 메모리가 모델 가중치 + KV Cache에 충분한지 계산 완료
  • NVLink/NVSwitch 연결 상태 확인 (Tensor Parallel 사용 시)
  • /dev/shm 크기 16GB 이상 설정 (Tensor Parallel 사용 시)
  • 모델 파일이 로컬 PVC에 사전 다운로드되었는지 확인

서빙 설정

  • --gpu-memory-utilization 값을 워크로드에 맞게 조정
  • --max-model-len을 실제 필요한 최대 시퀀스 길이로 설정
  • --max-num-seqs를 부하 테스트 결과에 기반하여 설정
  • --enable-prefix-caching 활성화 여부 결정 (시스템 프롬프트 공유 시 필수)
  • --disable-log-requests 프로덕션에서 활성화
  • --dtype auto 또는 명시적 dtype 설정
  • Attention 백엔드 선택 (FLASHINFER 권장)

Kubernetes 배포

  • startupProbe 설정 및 failureThreshold 충분히 높게 설정
  • readinessProbelivenessProbe 분리
  • terminationGracePeriodSeconds 120초 이상
  • GPU nodeSelector 또는 nodeAffinity 설정
  • PVC로 모델 캐시 영속화
  • emptyDir Medium: Memory로 /dev/shm 마운트
  • KEDA 또는 HPA 오토스케일링 구성
  • minReplicaCount 1 이상 (콜드 스타트 방지)

모니터링과 알림

  • Prometheus 메트릭 수집 경로 설정 (/metrics)
  • Grafana 대시보드 구성 (KV Cache 사용률, TTFT, 처리량)
  • OOM 발생 시 알림 설정
  • 대기 요청 수 임계치 알림 설정
  • GPU 온도 및 전력 모니터링 (DCGM)
  • 응답 품질 모니터링 파이프라인 구축 (샘플링 기반)

성능 검증

  • 예상 최대 부하의 120%로 스트레스 테스트 완료
  • TTFT와 ITL의 p50/p95/p99 측정 및 SLA 충족 확인
  • Speculative Decoding 적용 시 수락률 확인 (60% 이상 권장)
  • 양자화 적용 시 주요 태스크별 품질 벤치마크 완료
  • 장시간(24시간 이상) 안정성 테스트 완료

참고자료

  1. Efficient Memory Management for Large Language Model Serving with PagedAttention - PagedAttention 원본 논문으로, KV Cache의 가상 메모리 페이징 기법을 상세히 설명한다.
  2. vLLM 공식 문서 - 설치부터 고급 설정까지 vLLM의 공식 레퍼런스 문서다.
  3. vLLM GitHub 리포지토리 - 소스 코드, 이슈 트래커, 릴리스 노트를 확인할 수 있다.
  4. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving - Prefill과 Decoding 단계를 분리하여 서빙 효율을 최적화하는 기법을 다룬 논문이다.
  5. vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 - 2026년 기준 주요 추론 엔진의 성능 벤치마크 비교 분석이다.
  6. SGLang: Efficient Execution of Structured Language Model Programs - SGLang의 RadixAttention과 구조화된 출력 최적화 기법을 다룬 논문이다.
  7. NVIDIA TensorRT-LLM Documentation - TensorRT-LLM의 공식 문서로, 엔진 빌드와 최적화 가이드를 포함한다.