Split View: vLLM PagedAttention 기반 LLM 프로덕션 서빙 최적화와 추론 엔진 비교 가이드
vLLM PagedAttention 기반 LLM 프로덕션 서빙 최적화와 추론 엔진 비교 가이드
- 들어가며
- PagedAttention 동작 원리
- vLLM 아키텍처
- vLLM 설치와 기본 사용법
- 프로덕션 배포 설정
- 성능 최적화 기법
- Kubernetes 배포 패턴
- 추론 엔진 비교
- 운영 시 주의사항과 모니터링
- 실패 사례와 복구 절차
- 체크리스트
- 참고자료

들어가며
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.90 | Prometheus 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 | ~140GB | 100% | 기본 |
| FP8 (W8A8) | 8 | ~70GB | 99.5% | 지원 |
| AWQ (W4) | 4 | ~35GB | 98.5% | 지원 |
| GPTQ (W4) | 4 | ~35GB | 98.0% | 지원 |
| GGUF (Q4_K_M) | 4 | ~35GB | 97.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-LLM | LMDeploy |
|---|---|---|---|---|
| 처리량 (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.0 | Apache 2.0 | Apache 2.0 | Apache 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) |
|---|---|---|---|
| 1 | 85 | 92 | 105 |
| 8 | 620 | 680 | 750 |
| 32 | 2,100 | 2,350 | 2,600 |
| 64 | 3,800 | 4,200 | 4,500 |
| 128 | 5,200 | 5,800 | 6,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_perc | GPU KV Cache 사용률 | 95% 초과 시 경고 |
vllm:cpu_cache_usage_perc | CPU 스왑 캐시 사용률 | 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 }}%입니다. 스케일업을 검토하세요."
# }
핵심 운영 지침
-
로그 레벨 관리: 프로덕션에서는 반드시
--disable-log-requests를 사용한다. 요청마다 프롬프트를 로깅하면 디스크 I/O 병목과 개인정보 유출 위험이 생긴다. -
Graceful Shutdown: SIGTERM 수신 시 진행 중인 요청을 완료할 시간을 충분히 확보한다. Kubernetes의
terminationGracePeriodSeconds를 120초 이상으로 설정하라. -
Health Check 구분:
startupProbe와readinessProbe를 분리한다. 모델 로딩은 수 분이 걸리므로startupProbe의failureThreshold를 충분히 높게 설정한다.readinessProbe는 실제 추론 가능 여부를 확인한다. -
shared memory 설정: Tensor Parallel 모드에서 NCCL은
/dev/shm을 통해 GPU 간 통신한다. Docker에서--shm-size=16g, Kubernetes에서emptyDir의medium: Memory를 반드시 설정해야 한다. -
모델 캐시 영속화: PVC(PersistentVolumeClaim)를 사용하여 HuggingFace 모델 캐시를 영속화한다. 파드 재시작 시마다 수십 GB 모델을 다시 다운로드하는 것은 비용과 시간 모두에서 비효율적이다.
실패 사례와 복구 절차
Case 1: CUDA Out of Memory (OOM)
증상: 서버 시작 후 일정 시간이 지나면 torch.cuda.OutOfMemoryError가 발생하며 모든 요청이 실패한다.
원인: --gpu-memory-utilization을 너무 높게 설정했거나, --max-model-len이 실제 워크로드 대비 과도하게 클 때 발생한다. KV Cache 할당이 물리 GPU 메모리를 초과한다.
복구 절차:
--gpu-memory-utilization을 0.85로 낮춘다.--max-model-len을 실제 필요한 최대 길이로 제한한다.--max-num-seqs를 줄여 동시 요청 수를 제한한다.- 양자화(AWQ/FP8)를 적용하여 모델 메모리를 줄인다.
- 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도 할당하기에 부족하다.
복구 절차:
--max-model-len을 줄인다 (예: 8192에서 4096으로).--tensor-parallel-size를 늘린다.- 양자화 모델을 사용한다.
--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 혼합 사용이 원인이다.
복구 절차:
--shm-size를 16GB 이상으로 설정한다.- 동일 사양의 GPU만 사용하도록 nodeSelector를 설정한다.
NCCL_DEBUG=INFO환경 변수로 상세 로그를 확인한다.- NVLink 연결이 없는 GPU 조합이면 PCIe 대역폭의 한계를 감안하여 Tensor Parallel 수를 줄인다.
Case 4: 응답 품질 저하
증상: 양자화 모델 서빙 후 응답 품질이 눈에 띄게 떨어진다. 반복적인 문장, 맥락 없는 응답, 코드 생성 정확도 하락 등이 관측된다.
원인: 과도한 양자화(INT4)가 모델의 핵심 가중치를 손상시켰다. 특히 코딩이나 수학 태스크에서 INT4 양자화의 품질 저하가 두드러진다.
복구 절차:
- FP8 양자화로 전환한다 (품질 손실 최소화).
- AWQ 대신 GPTQ를 시도하거나 그 반대를 시도한다.
- 양자화 없이 Tensor Parallel 수를 늘려 FP16 서빙을 검토한다.
- 양자화 모델의 벤치마크를 주요 태스크별로 수행하여 품질 기준을 설정한다.
Case 5: Kubernetes 파드 무한 재시작
증상: vLLM 파드가 CrashLoopBackOff 상태에 빠진다. 모델 로딩이 완료되기 전에 livenessProbe가 실패하여 kubelet이 컨테이너를 강제 종료한다.
원인: startupProbe를 설정하지 않았거나, failureThreshold가 너무 낮아 모델 로딩 시간을 커버하지 못한다.
복구 절차:
startupProbe를 추가하고failureThreshold를 60 이상으로 설정한다 (10초 간격이면 10분).livenessProbe의initialDelaySeconds를 충분히 길게 설정한다.- 모델을 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충분히 높게 설정 -
readinessProbe와livenessProbe분리 -
terminationGracePeriodSeconds120초 이상 - GPU nodeSelector 또는 nodeAffinity 설정
- PVC로 모델 캐시 영속화
-
emptyDirMedium: Memory로/dev/shm마운트 - KEDA 또는 HPA 오토스케일링 구성
-
minReplicaCount1 이상 (콜드 스타트 방지)
모니터링과 알림
- Prometheus 메트릭 수집 경로 설정 (
/metrics) - Grafana 대시보드 구성 (KV Cache 사용률, TTFT, 처리량)
- OOM 발생 시 알림 설정
- 대기 요청 수 임계치 알림 설정
- GPU 온도 및 전력 모니터링 (DCGM)
- 응답 품질 모니터링 파이프라인 구축 (샘플링 기반)
성능 검증
- 예상 최대 부하의 120%로 스트레스 테스트 완료
- TTFT와 ITL의 p50/p95/p99 측정 및 SLA 충족 확인
- Speculative Decoding 적용 시 수락률 확인 (60% 이상 권장)
- 양자화 적용 시 주요 태스크별 품질 벤치마크 완료
- 장시간(24시간 이상) 안정성 테스트 완료
참고자료
- Efficient Memory Management for Large Language Model Serving with PagedAttention - PagedAttention 원본 논문으로, KV Cache의 가상 메모리 페이징 기법을 상세히 설명한다.
- vLLM 공식 문서 - 설치부터 고급 설정까지 vLLM의 공식 레퍼런스 문서다.
- vLLM GitHub 리포지토리 - 소스 코드, 이슈 트래커, 릴리스 노트를 확인할 수 있다.
- DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving - Prefill과 Decoding 단계를 분리하여 서빙 효율을 최적화하는 기법을 다룬 논문이다.
- vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 - 2026년 기준 주요 추론 엔진의 성능 벤치마크 비교 분석이다.
- SGLang: Efficient Execution of Structured Language Model Programs - SGLang의 RadixAttention과 구조화된 출력 최적화 기법을 다룬 논문이다.
- NVIDIA TensorRT-LLM Documentation - TensorRT-LLM의 공식 문서로, 엔진 빌드와 최적화 가이드를 포함한다.
vLLM PagedAttention Production Serving Optimization and Inference Engine Comparison Guide
- Introduction
- How PagedAttention Works
- vLLM Architecture
- vLLM Installation and Basic Usage
- Production Deployment Configuration
- Performance Optimization Techniques
- Kubernetes Deployment Patterns
- Inference Engine Comparison
- Operational Considerations and Monitoring
- Failure Cases and Recovery Procedures
- Checklist
- References
- Quiz

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-sizeis 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.
| Environment | gpu-memory-utilization | Reason |
|---|---|---|
| Development/Testing | 0.80 | Possible GPU sharing with other processes |
| Production (Dedicated GPU) | 0.90~0.92 | Maximum throughput with slight headroom |
| Production (with Monitoring) | 0.88~0.90 | Prometheus exporter etc. consume VRAM |
| Unstable Workloads | 0.85 | Buffer 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 Method | Bits | 70B VRAM | Relative Quality | vLLM Support |
|---|---|---|---|---|
| FP16 (baseline) | 16 | ~140GB | 100% | Default |
| FP8 (W8A8) | 8 | ~70GB | 99.5% | Supported |
| AWQ (W4) | 4 | ~35GB | 98.5% | Supported |
| GPTQ (W4) | 4 | ~35GB | 98.0% | Supported |
| GGUF (Q4_K_M) | 4 | ~35GB | 97.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
cooldownPeriodsufficiently long and maintainminReplicaCountat 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.
| Category | vLLM (0.7.x) | SGLang (0.4.x) | TensorRT-LLM | LMDeploy |
|---|---|---|---|---|
| Throughput (req/s) | ~42 | ~48 | ~55 | ~40 |
| TTFT (ms) | ~85 | ~72 | ~60 | ~90 |
| ITL (ms/token) | ~12 | ~11 | ~9 | ~13 |
| Model Support Range | Very wide | Wide | Medium | Wide |
| Installation Difficulty | Easy | Easy | Difficult | Medium |
| OpenAI API Compatible | Full support | Full support | Partial | Full support |
| Multimodal Support | Supported | Supported | Limited | Supported |
| FP8 Quantization | Supported | Supported | Native | Supported |
| Prefix Caching | Supported | RadixAttention | Supported | Supported |
| LoRA Serving | Supported | Supported | Limited | Supported |
| Speculative Decoding | Supported | Supported | Supported | Limited |
| Community Activity | Very high | High | Medium | Medium |
| Production Maturity | High | High | Very high | Medium |
| License | Apache 2.0 | Apache 2.0 | Apache 2.0 | Apache 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 Requests | vLLM (tok/s) | SGLang (tok/s) | TensorRT-LLM (tok/s) |
|---|---|---|---|
| 1 | 85 | 92 | 105 |
| 8 | 620 | 680 | 750 |
| 32 | 2,100 | 2,350 | 2,600 |
| 64 | 3,800 | 4,200 | 4,500 |
| 128 | 5,200 | 5,800 | 6,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.
| Metric | Description | Alert Threshold |
|---|---|---|
vllm:num_requests_running | Currently running requests | Over 90% of max_num_seqs |
vllm:num_requests_waiting | Requests in queue | Consistently above 50 |
vllm:gpu_cache_usage_perc | GPU KV Cache utilization | Alert when over 95% |
vllm:cpu_cache_usage_perc | CPU swap cache utilization | Frequent swapping when over 50% |
vllm:avg_prompt_throughput_toks_per_s | Prompt processing tokens/sec | Under 50% of baseline |
vllm:avg_generation_throughput_toks_per_s | Generation tokens/sec | Under 50% of baseline |
vllm:e2e_request_latency_seconds | End-to-end request latency | p99 exceeds SLA |
vllm:time_to_first_token_seconds | Time to first token | p99 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
-
Log Level Management: Always use
--disable-log-requestsin production. Logging prompts for every request creates disk I/O bottlenecks and risks personal data exposure. -
Graceful Shutdown: Allow sufficient time to complete in-progress requests when receiving SIGTERM. Set Kubernetes
terminationGracePeriodSecondsto 120 seconds or more. -
Health Check Separation: Separate
startupProbeandreadinessProbe. Since model loading takes several minutes, setstartupProbe'sfailureThresholdsufficiently high.readinessProbechecks whether inference is actually possible. -
Shared Memory Configuration: In Tensor Parallel mode, NCCL communicates between GPUs via
/dev/shm. You must set--shm-size=16gin Docker andemptyDirwithmedium: Memoryin Kubernetes. -
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:
- Lower
--gpu-memory-utilizationto 0.85. - Limit
--max-model-lento the actual maximum length needed. - Reduce
--max-num-seqsto limit concurrent requests. - Apply quantization (AWQ/FP8) to reduce model memory.
- 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:
- Reduce
--max-model-len(e.g., from 8192 to 4096). - Increase
--tensor-parallel-size. - Use a quantized model.
- Add the
--enforce-eagerflag 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:
- Set
--shm-sizeto 16GB or more. - Configure nodeSelector to use only GPUs with identical specifications.
- Check detailed logs with the
NCCL_DEBUG=INFOenvironment variable. - 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:
- Switch to FP8 quantization (minimal quality loss).
- Try GPTQ instead of AWQ or vice versa.
- Consider FP16 serving by increasing the Tensor Parallel count without quantization.
- 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:
- Add
startupProbeand setfailureThresholdto 60 or higher (at 10-second intervals, this allows 10 minutes). - Set
livenessProbe'sinitialDelaySecondssufficiently long. - 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/shmsize to 16GB or more (when using Tensor Parallel) - Verify model files are pre-downloaded to local PVC
Serving Configuration
- Adjust
--gpu-memory-utilizationvalue for your workload - Set
--max-model-lento the actual maximum sequence length needed - Set
--max-num-seqsbased on load test results - Decide on
--enable-prefix-cachingactivation (essential when sharing system prompts) - Enable
--disable-log-requestsin production - Set
--dtype autoor explicit dtype - Choose attention backend (FLASHINFER recommended)
Kubernetes Deployment
- Configure
startupProbeand setfailureThresholdsufficiently high - Separate
readinessProbeandlivenessProbe - Set
terminationGracePeriodSecondsto 120 seconds or more - Configure GPU nodeSelector or nodeAffinity
- Persist model cache with PVC
- Mount
/dev/shmwithemptyDirMedium: Memory - Configure KEDA or HPA autoscaling
- Set
minReplicaCountto 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
- 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.
- vLLM Official Documentation - The official reference documentation for vLLM, from installation to advanced configuration.
- vLLM GitHub Repository - Source code, issue tracker, and release notes.
- 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.
- vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 - Performance benchmark comparison analysis of major inference engines as of 2026.
- SGLang: Efficient Execution of Structured Language Model Programs - A paper covering SGLang's RadixAttention and structured output optimization techniques.
- 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.