Skip to content

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

|

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

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의 공식 문서로, 엔진 빌드와 최적화 가이드를 포함한다.

vLLM PagedAttention Production Serving Optimization and Inference Engine Comparison Guide

vLLM PagedAttention

Introduction

The first wall you hit when serving LLMs in production is GPU memory. Loading Llama 3.1 70B in FP16 requires 140GB for model weights alone, and processing multiple requests concurrently demands an additional tens to hundreds of GB for the KV Cache. In real production environments, concurrent requests range from tens to hundreds, making KV Cache memory management the decisive factor for overall system throughput and latency.

In traditional Transformer inference implementations, KV Cache is pre-allocated for the maximum sequence length for each request. If a service allows up to 4,096 tokens but the actual average output is 512 tokens, 87% of the allocated memory is wasted. PagedAttention, published by UC Berkeley, fundamentally solves this problem, and vLLM is the open-source inference engine that implements it.

This article covers the full spectrum of production LLM serving: from how PagedAttention works, vLLM architecture, production deployment configuration, performance optimization techniques, Kubernetes-based autoscaling, comparison with SGLang/TensorRT-LLM, operational monitoring, and failure cases with recovery procedures.

How PagedAttention Works

Applying Virtual Memory Paging

PagedAttention is inspired by virtual memory management in operating systems. The OS provides processes with a contiguous virtual address space, but physical memory is allocated non-contiguously in fixed-size pages. PagedAttention applies this concept directly to the KV Cache.

The KV Cache is divided into fixed-size blocks, where each block stores Key-Value tensors for a fixed number of tokens. The default block size is 16 tokens. As new tokens are generated during request processing, when the current block is full, a new physical block is allocated and a mapping is added to the block table.

Traditional contiguous allocation:
Request A: [████████░░░░░░░░░░░░░░░░░░░░░░░░]  Actual 512, max 2048 allocated
Request B: [██████████████░░░░░░░░░░░░░░░░░░]  Actual 896, max 2048 allocated
Request C: [██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  Actual 128, max 2048 allocated
Total 6,144 slots allocated, only 1,536 used. 75% waste

PagedAttention block approach (block size = 16):
Request A: [B0][B1]...[B31]Only 32 blocks allocated (512 tokens)
Request B: [B0][B1]...[B55]Only 56 blocks allocated (896 tokens)
Request C: [B0]...[B7]Only 8 blocks allocated (128 tokens)
Only internal fragmentation in the last block. Average waste under 4%

Copy-on-Write and Prefix Sharing

Another strength of PagedAttention is its Copy-on-Write (CoW) mechanism. When generating multiple sequences from the same prompt, such as in beam search or parallel sampling, KV Cache blocks for the common prefix can be physically shared. New blocks are allocated only at the point of divergence, reducing beam search memory usage by up to 55%.

KV Cache Size Calculation

To accurately estimate GPU memory in production environments, you need to predict the KV Cache size. The following function calculates the KV Cache size for each model.

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:
    """
    KV Cache memory estimation based on vLLM PagedAttention.
    For GQA models, num_kv_heads is smaller than num_attention_heads.
    """
    # KV Cache bytes per token
    per_token_bytes = 2 * num_layers * num_kv_heads * head_dim * dtype_bytes
    # Bytes per block
    per_block_bytes = per_token_bytes * block_size
    # Total blocks needed for max concurrent processing
    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 concurrent requests × 4096 tokens requires 80GB for KV Cache alone

Looking at this calculation, serving a 70B model with 64 concurrent requests requires over 220GB of VRAM: 140GB for model weights + 80GB for KV Cache. That is a scale requiring three or more A100 80GB GPUs. PagedAttention maximizes efficiency by dynamically allocating this memory based on actual usage.

vLLM Architecture

V1 Engine and Core Components

The V1 engine introduced after vLLM 0.7.x significantly improved the architecture compared to previous versions. The core components are as follows.

Scheduler: Determines the priority of pending requests and decides which requests to execute based on GPU memory conditions. Through the preemption mechanism, if memory is insufficient, it swaps or recomputes the KV Cache of lower-priority requests to CPU memory.

Block Manager: The core of PagedAttention, managing allocation, deallocation, and sharing of physical blocks. It maintains the block table and tracks mappings between logical and physical blocks.

Worker: The process that performs actual model inference on GPUs. When Tensor Parallelism is enabled, multiple Workers collaborate to process a single request.

Model Runner: Executes the model's forward pass and calls optimized attention kernels like FlashAttention or FlashInfer.

Continuous Batching

In traditional static batching, new requests cannot be added until all requests in the batch are completed. Requests generating short responses must wait for long responses, reducing GPU utilization.

vLLM's Continuous Batching removes completed requests from the batch at each iteration and immediately adds waiting new requests. This maintains GPU utilization above 90% while significantly reducing average latency.

Static Batching:
Time →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]  Generation complete
Req 2:  [■■■■■■■■■■░░░░░░░░░░░░░░░░░░░░░░]  Finished early, waiting
Req 3:  [■■■■░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  Finished early, waiting
Cannot add new requests in Req 2, 3 slots

Continuous Batching:
Time →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]
Req 2:  [■■■■■■■■■■]Slot returned immediately upon completion
Req 4:             [■■■■■■■■■■■■■■■■■■■■■■]Immediately added
Req 3:  [■■■■]Complete
Req 5:       [■■■■■■■■■■■■■■]Immediately added
GPU always operates at maximum load

vLLM Installation and Basic Usage

Installation

vLLM can be easily installed via pip. It requires CUDA 12.1 or higher, and as of March 2026, the latest stable version is the 0.7.x series.

# Basic installation (for CUDA 12.4)
pip install vllm

# Install specific version
pip install vllm==0.7.2

# When using FlashInfer backend (recommended)
pip install flashinfer-python
pip install vllm

# Build from source (custom CUDA version)
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e .

# Verify installation
python -c "import vllm; print(vllm.__version__)"

Running an OpenAI-Compatible API Server

One of vLLM's greatest advantages is providing a server fully compatible with the OpenAI API. Existing OpenAI client code can be used without modification.

# Basic server launch
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

# Run 70B model with 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

Offline Inference with Python SDK

You can also use the vLLM engine directly from Python code without a server. This is useful for batch processing or benchmarking.

from vllm import LLM, SamplingParams

# Load model (PagedAttention applied automatically)
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,
)

# Set sampling parameters
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=1024,
    repetition_penalty=1.1,
    stop=["<|eot_id|>"],
)

# Batch inference
prompts = [
    "Explain best practices for managing GPU nodes in Kubernetes.",
    "Describe how Python asyncio's event loop works.",
    "Compare the pros and cons of PostgreSQL partitioning strategies.",
]

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("---")

Production Deployment Configuration

Docker-Based Deployment

In production environments, deploying via Docker containers is standard. You need to properly configure model cache and GPU settings based on the official vLLM image.

# Production deployment using official image
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

Note: If --shm-size is not set sufficiently, NCCL communication errors occur in Tensor Parallel mode. A minimum of 8GB is recommended, preferably 16GB.

GPU Memory Configuration Strategy

The --gpu-memory-utilization parameter determines the proportion of GPU memory vLLM will use. Setting it too high causes CUDA OOM, while setting it too low reduces concurrent throughput.

Environmentgpu-memory-utilizationReason
Development/Testing0.80Possible GPU sharing with other processes
Production (Dedicated GPU)0.90~0.92Maximum throughput with slight headroom
Production (with Monitoring)0.88~0.90Prometheus exporter etc. consume VRAM
Unstable Workloads0.85Buffer for sudden long sequences

Model Loading Optimization

Loading large models can take several minutes. Using the Safetensors format and local caching can significantly improve loading speed.

# Pre-download model to reduce loading time
huggingface-cli download meta-llama/Llama-3.1-70B-Instruct \
  --local-dir /data/models/llama-3.1-70b \
  --local-dir-use-symlinks False

# Load directly from local path (no download needed)
vllm serve /data/models/llama-3.1-70b \
  --tensor-parallel-size 4 \
  --load-format safetensors \
  --max-model-len 8192

Performance Optimization Techniques

Prefix Caching (Automatic Prefix Caching)

In many production workloads, the system prompt is included in every request. Prefix Caching caches and reuses the KV Cache for this common prefix. If the system prompt is 2,000 tokens and 100 requests per second come in, without Prefix Caching you must recompute the KV Cache for 2,000 tokens with every request. With Prefix Caching enabled, it is computed only for the first request and subsequent requests read from cache instantly.

# Enable Prefix Caching (disabled by default in vLLM 0.7.x)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --enable-prefix-caching \
  --max-model-len 8192

In benchmarks, Prefix Caching has been observed to improve TTFT (Time-To-First-Token) by up to 8x for workloads with a common system prompt. However, for workloads where prompts are completely different each time, cache hit rates are low and the effect is minimal.

Speculative Decoding

Speculative Decoding is a technique where a small draft model predicts multiple tokens in advance, and the original model (target) verifies them in a single forward pass. vLLM supports both the draft model approach and the ngram-based approach.

# Speculative Decoding with draft model
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-based Speculative Decoding (no additional model needed)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --speculative-model "[ngram]" \
  --num-speculative-tokens 5 \
  --ngram-prompt-lookup-max 4

Speculative Decoding is most effective with greedy decoding or low temperature. At high temperature, the draft model's prediction acceptance rate drops, potentially adding overhead instead.

Quantization and Serving

GPTQ, AWQ, and FP8 quantized models can be served directly with vLLM. Quantization is used to reduce model size for serving on fewer GPUs or to increase concurrent throughput.

Quantization MethodBits70B VRAMRelative QualityvLLM Support
FP16 (baseline)16~140GB100%Default
FP8 (W8A8)8~70GB99.5%Supported
AWQ (W4)4~35GB98.5%Supported
GPTQ (W4)4~35GB98.0%Supported
GGUF (Q4_K_M)4~35GB97.5%Limited
# Serving AWQ quantized model
vllm serve TheBloke/Llama-3.1-70B-Instruct-AWQ \
  --quantization awq \
  --tensor-parallel-size 2 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90

# Serving FP8 quantized model (GPUs supporting FP8 like H100/L40S)
vllm serve neuralmagic/Llama-3.1-70B-Instruct-FP8 \
  --quantization fp8 \
  --tensor-parallel-size 4 \
  --max-model-len 8192

Note: When serving AWQ/GPTQ models with Tensor Parallel, you must verify the split compatibility of the quantized model. Some quantized models only work correctly with specific TP sizes.

Kubernetes Deployment Patterns

Basic Deployment Configuration

When deploying vLLM on Kubernetes, you need to properly configure GPU resource requests, health checks, and 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 # Allow up to 10 minutes for model loading
          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-Based Autoscaling

LLM serving requires autoscaling due to significant traffic fluctuations. Using KEDA (Kubernetes Event-Driven Autoscaling), pods can be automatically scaled based on Prometheus metrics.

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 # Wait 5 minutes before scale down
  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'

Note: GPU pod scale-up is much slower than regular pods. Model loading takes 2-10 minutes, so set cooldownPeriod sufficiently long and maintain minReplicaCount at 1 or above. Be sure to account for the time gap between when scale-up is needed and when traffic can actually be processed.

Inference Engine Comparison

vLLM vs SGLang vs TensorRT-LLM vs LMDeploy

Here is a comparison of the major LLM inference engines as of 2026 from multiple perspectives. Benchmark numbers are based on Llama 3.1 8B, A100 80GB, with 1024 input tokens and 512 output tokens.

CategoryvLLM (0.7.x)SGLang (0.4.x)TensorRT-LLMLMDeploy
Throughput (req/s)~42~48~55~40
TTFT (ms)~85~72~60~90
ITL (ms/token)~12~11~9~13
Model Support RangeVery wideWideMediumWide
Installation DifficultyEasyEasyDifficultMedium
OpenAI API CompatibleFull supportFull supportPartialFull support
Multimodal SupportSupportedSupportedLimitedSupported
FP8 QuantizationSupportedSupportedNativeSupported
Prefix CachingSupportedRadixAttentionSupportedSupported
LoRA ServingSupportedSupportedLimitedSupported
Speculative DecodingSupportedSupportedSupportedLimited
Community ActivityVery highHighMediumMedium
Production MaturityHighHighVery highMedium
LicenseApache 2.0Apache 2.0Apache 2.0Apache 2.0

Engine Selection Guide

Choose vLLM when: You need the widest model compatibility, want to prototype quickly and carry through to production, or when community ecosystem and plugins matter. The OpenAI-compatible API is smooth, and support for new model architectures is the fastest.

Choose SGLang when: You need advanced prompt caching based on RadixAttention, have many structured outputs, or when TTFT optimization is critical for interactive workloads. SGLang's RadixTree-based caching is more efficient than vLLM's Prefix Caching for tree-structured multi-turn conversations.

Choose TensorRT-LLM when: Maximum throughput and minimum latency are absolutely critical, and you use NVIDIA GPUs exclusively and can tolerate the complexity of the engine build process. Since TensorRT engines must be pre-built per model, the deployment pipeline becomes more complex.

Choose LMDeploy when: You need high performance from the TurboMind engine along with deep integration with the PyTorch ecosystem, especially when serving InternLM family models.

Throughput Comparison by Batch Size

Concurrent RequestsvLLM (tok/s)SGLang (tok/s)TensorRT-LLM (tok/s)
18592105
8620680750
322,1002,3502,600
643,8004,2004,500
1285,2005,8006,100

These numbers are based on Llama 3.1 8B, A100 80GB, FP16 and can vary significantly depending on workload characteristics. Always run your own benchmarks in actual production environments.

Operational Considerations and Monitoring

Prometheus Metrics Collection

vLLM exposes Prometheus-format metrics through the /metrics endpoint. Key monitoring indicators are as follows.

MetricDescriptionAlert Threshold
vllm:num_requests_runningCurrently running requestsOver 90% of max_num_seqs
vllm:num_requests_waitingRequests in queueConsistently above 50
vllm:gpu_cache_usage_percGPU KV Cache utilizationAlert when over 95%
vllm:cpu_cache_usage_percCPU swap cache utilizationFrequent swapping when over 50%
vllm:avg_prompt_throughput_toks_per_sPrompt processing tokens/secUnder 50% of baseline
vllm:avg_generation_throughput_toks_per_sGeneration tokens/secUnder 50% of baseline
vllm:e2e_request_latency_secondsEnd-to-end request latencyp99 exceeds SLA
vllm:time_to_first_token_secondsTime to first tokenp99 exceeds 2 seconds

GPU Memory Monitoring

GPU-level memory monitoring is performed through the DCGM (Data Center GPU Manager) Exporter. You need to monitor both vLLM's own metrics and GPU hardware metrics to get the full picture.

# Prometheus query examples: for Grafana dashboards

# 1. GPU KV Cache utilization (vLLM internal)
# vllm:gpu_cache_usage_perc{model_name="llama-70b"}

# 2. Actual GPU memory usage (DCGM)
# DCGM_FI_DEV_FB_USED{gpu="0"} / DCGM_FI_DEV_FB_TOTAL{gpu="0"} * 100

# 3. Waiting request count trend
# rate(vllm:num_requests_waiting{model_name="llama-70b"}[5m])

# 4. p99 TTFT monitoring
# histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m]))

# 5. Tokens generated per second
# rate(vllm:avg_generation_throughput_toks_per_s[1m])

# AlertManager rule example
# ALERT VLLMHighCacheUsage
#   IF vllm:gpu_cache_usage_perc > 95
#   FOR 5m
#   LABELS { severity = "warning" }
#   ANNOTATIONS {
#     summary = "vLLM KV Cache utilization exceeds 95%",
#     description = "KV Cache utilization for model {{ $labels.model_name }}
#       is {{ $value }}%. Consider scaling up."
#   }

Key Operational Guidelines

  1. Log Level Management: Always use --disable-log-requests in production. Logging prompts for every request creates disk I/O bottlenecks and risks personal data exposure.

  2. Graceful Shutdown: Allow sufficient time to complete in-progress requests when receiving SIGTERM. Set Kubernetes terminationGracePeriodSeconds to 120 seconds or more.

  3. Health Check Separation: Separate startupProbe and readinessProbe. Since model loading takes several minutes, set startupProbe's failureThreshold sufficiently high. readinessProbe checks whether inference is actually possible.

  4. Shared Memory Configuration: In Tensor Parallel mode, NCCL communicates between GPUs via /dev/shm. You must set --shm-size=16g in Docker and emptyDir with medium: Memory in Kubernetes.

  5. Model Cache Persistence: Use PVC (PersistentVolumeClaim) to persist the HuggingFace model cache. Re-downloading models of tens of GBs every time a pod restarts is inefficient in both cost and time.

Failure Cases and Recovery Procedures

Case 1: CUDA Out of Memory (OOM)

Symptom: After the server starts, after some time torch.cuda.OutOfMemoryError occurs and all requests fail.

Cause: Occurs when --gpu-memory-utilization is set too high or --max-model-len is excessively large for the actual workload. KV Cache allocation exceeds physical GPU memory.

Recovery Procedure:

  1. Lower --gpu-memory-utilization to 0.85.
  2. Limit --max-model-len to the actual maximum length needed.
  3. Reduce --max-num-seqs to limit concurrent requests.
  4. Apply quantization (AWQ/FP8) to reduce model memory.
  5. Increase the Tensor Parallel count to distribute load per GPU.

Prevention: Always perform stress testing at 120% of expected maximum load before production deployment.

# Memory profiling for OOM debugging
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  # Disable CUDA Graph to reduce memory usage

Case 2: Model Loading Failure

Symptom: At server startup, ValueError: The model's max seq len is larger than the maximum number of tokens that can be stored in KV cache or Not enough memory error occurs.

Cause: The KV Cache required for the specified --max-model-len exceeds available GPU memory. After loading model weights, the remaining memory is insufficient to allocate even minimal KV Cache.

Recovery Procedure:

  1. Reduce --max-model-len (e.g., from 8192 to 4096).
  2. Increase --tensor-parallel-size.
  3. Use a quantized model.
  4. Add the --enforce-eager flag to reclaim memory occupied by CUDA Graphs.

Case 3: NCCL Timeout (Tensor Parallel)

Symptom: In multi-GPU environments, RuntimeError: NCCL communicator was aborted or Watchdog caught collective operation timeout occurs.

Cause: Insufficient GPU-to-GPU communication (NVLink/PCIe) bandwidth, insufficient /dev/shm size, or mixed use of heterogeneous GPUs.

Recovery Procedure:

  1. Set --shm-size to 16GB or more.
  2. Configure nodeSelector to use only GPUs with identical specifications.
  3. Check detailed logs with the NCCL_DEBUG=INFO environment variable.
  4. If there is no NVLink connection between GPUs, reduce the Tensor Parallel count considering PCIe bandwidth limitations.

Case 4: Response Quality Degradation

Symptom: After serving a quantized model, response quality noticeably drops. Repetitive sentences, context-free responses, and decreased code generation accuracy are observed.

Cause: Excessive quantization (INT4) has damaged the model's critical weights. Quality degradation with INT4 quantization is particularly pronounced for coding and math tasks.

Recovery Procedure:

  1. Switch to FP8 quantization (minimal quality loss).
  2. Try GPTQ instead of AWQ or vice versa.
  3. Consider FP16 serving by increasing the Tensor Parallel count without quantization.
  4. Perform benchmarks of the quantized model by major task type to establish quality baselines.

Case 5: Kubernetes Pod Infinite Restart

Symptom: vLLM pod falls into CrashLoopBackOff state. The livenessProbe fails before model loading completes, causing kubelet to force-terminate the container.

Cause: startupProbe was not configured, or failureThreshold is too low to cover the model loading time.

Recovery Procedure:

  1. Add startupProbe and set failureThreshold to 60 or higher (at 10-second intervals, this allows 10 minutes).
  2. Set livenessProbe's initialDelaySeconds sufficiently long.
  3. Pre-download the model to PVC to shorten loading time.

Checklist

Items that must be verified before deploying vLLM to production.

Infrastructure Preparation

  • Verify GPU driver and CUDA version compatibility with vLLM requirements
  • Calculation complete that GPU memory is sufficient for model weights + KV Cache
  • Check NVLink/NVSwitch connection status (when using Tensor Parallel)
  • Set /dev/shm size to 16GB or more (when using Tensor Parallel)
  • Verify model files are pre-downloaded to local PVC

Serving Configuration

  • Adjust --gpu-memory-utilization value for your workload
  • Set --max-model-len to the actual maximum sequence length needed
  • Set --max-num-seqs based on load test results
  • Decide on --enable-prefix-caching activation (essential when sharing system prompts)
  • Enable --disable-log-requests in production
  • Set --dtype auto or explicit dtype
  • Choose attention backend (FLASHINFER recommended)

Kubernetes Deployment

  • Configure startupProbe and set failureThreshold sufficiently high
  • Separate readinessProbe and livenessProbe
  • Set terminationGracePeriodSeconds to 120 seconds or more
  • Configure GPU nodeSelector or nodeAffinity
  • Persist model cache with PVC
  • Mount /dev/shm with emptyDir Medium: Memory
  • Configure KEDA or HPA autoscaling
  • Set minReplicaCount to 1 or higher (prevent cold starts)

Monitoring and Alerting

  • Configure Prometheus metrics collection path (/metrics)
  • Set up Grafana dashboards (KV Cache utilization, TTFT, throughput)
  • Configure OOM alerting
  • Configure waiting request count threshold alerts
  • GPU temperature and power monitoring (DCGM)
  • Build response quality monitoring pipeline (sampling-based)

Performance Validation

  • Stress test completed at 120% of expected maximum load
  • TTFT and ITL p50/p95/p99 measured and SLA compliance confirmed
  • Acceptance rate confirmed when applying Speculative Decoding (60% or higher recommended)
  • Quality benchmarks completed by major task type when applying quantization
  • Long-duration (24+ hours) stability test completed

References

  1. Efficient Memory Management for Large Language Model Serving with PagedAttention - The original PagedAttention paper, providing a detailed explanation of virtual memory paging for KV Cache.
  2. vLLM Official Documentation - The official reference documentation for vLLM, from installation to advanced configuration.
  3. vLLM GitHub Repository - Source code, issue tracker, and release notes.
  4. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving - A paper on optimizing serving efficiency by separating the prefill and decoding stages.
  5. vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 - Performance benchmark comparison analysis of major inference engines as of 2026.
  6. SGLang: Efficient Execution of Structured Language Model Programs - A paper covering SGLang's RadixAttention and structured output optimization techniques.
  7. NVIDIA TensorRT-LLM Documentation - Official TensorRT-LLM documentation, including engine build and optimization guides.

Quiz

Q1: What is the main topic covered in "vLLM PagedAttention Production Serving Optimization and Inference Engine Comparison Guide"?

A comprehensive LLM serving guide covering vLLM PagedAttention algorithm, production deployment, performance tuning, SGLang/TensorRT-LLM comparison, and Kubernetes integration.

Q2: How PagedAttention Works? Applying Virtual Memory Paging PagedAttention is inspired by virtual memory management in operating systems. The OS provides processes with a contiguous virtual address space, but physical memory is allocated non-contiguously in fixed-size pages.

Q3: Describe the vLLM Architecture. V1 Engine and Core Components The V1 engine introduced after vLLM 0.7.x significantly improved the architecture compared to previous versions. The core components are as follows.

Q4: What are the key steps for vLLM Installation and Basic Usage? Installation vLLM can be easily installed via pip. It requires CUDA 12.1 or higher, and as of March 2026, the latest stable version is the 0.7.x series.

Q5: What are the key steps for Production Deployment Configuration? Docker-Based Deployment In production environments, deploying via Docker containers is standard. You need to properly configure model cache and GPU settings based on the official vLLM image.