Moment Note

LLM 추론 서버 최적화: vLLM으로 처리량 극대화하기

AI 개발 최적화 ·

LLM을 프로덕션에 올리는 순간 가장 먼저 마주치는 벽은 처리량(throughput)입니다. GPU 메모리는 한정되어 있고, 사용자 요청은 길이가 제각각이며, 응답을 기다리는 동안 GPU는 생각보다 많이 쉬고 있습니다. vLLM은 PagedAttention과 연속 배칭(Continuous Batching)이라는 두 가지 핵심 아이디어로 이 문제를 정면 돌파합니다. 이 글에서는 vLLM의 동작 원리부터 서버 파라미터 튜닝, 처리량 대 지연 트레이드오프까지 실무 중심으로 정리합니다.

기존 추론 서버의 병목: KV 캐시 낭비

트랜스포머 추론에서 어텐션 계산은 이전 토큰의 Key-Value 값을 캐시해 재사용합니다(KV 캐시). 문제는 전통적인 추론 프레임워크가 KV 캐시를 요청별 최대 시퀀스 길이만큼 연속된 GPU 메모리에 미리 할당한다는 것입니다. 실제 생성된 토큰이 절반도 안 되더라도 나머지 메모리는 사용되지 않은 채 점유됩니다. 이를 내부 단편화(internal fragmentation)라 하며, 실측 데이터에 따르면 전통 방식에서는 할당된 KV 캐시 메모리의 20~60%가 실제로는 낭비됩니다.

또한 전통적인 정적 배칭(Static Batching)은 가장 긴 요청이 끝날 때까지 모든 요청을 묶어 처리합니다. 짧은 요청이 완료되어도 GPU 슬롯을 반납하지 못하고 긴 요청을 기다려야 하므로 처리량이 크게 저하됩니다.

PagedAttention: OS의 페이지 테이블을 GPU로

vLLM의 첫 번째 혁신은 운영체제의 가상 메모리 개념을 KV 캐시에 적용한 PagedAttention입니다. KV 캐시를 고정 크기의 블록(block)으로 나누고, 블록 테이블을 통해 비연속적인 물리 메모리를 논리적으로 이어 붙입니다.

이 방식의 이점은 세 가지입니다. 첫째, 실제로 생성된 토큰만큼만 블록을 할당하므로 낭비가 거의 없습니다(낭비율 4% 미만). 둘째, 동일한 프롬프트를 공유하는 병렬 시퀀스(예: beam search, parallel sampling)가 블록을 물리적으로 공유해 메모리를 절약합니다(copy-on-write). 셋째, GPU 메모리가 부족할 때 LRU 방식으로 블록을 CPU로 swap-out했다가 필요 시 복구할 수 있습니다.

연속 배칭: 슬롯이 비는 즉시 채운다

PagedAttention으로 메모리 효율을 높인 것과 동시에 vLLM은 연속 배칭(Continuous Batching, 또는 Iteration-level Scheduling)을 구현합니다. 각 추론 스텝(토큰 1개 생성)이 끝날 때마다 완료된 시퀀스를 배치에서 제거하고, 대기 중인 새 요청을 즉시 추가합니다. 결과적으로 GPU는 쉬는 시간 없이 항상 유효한 요청을 처리합니다.

Orca 논문(2022)의 벤치마크에 따르면 연속 배칭은 정적 배칭 대비 처리량을 최대 23배까지 향상시킬 수 있습니다. 실제 프로덕션 환경에서는 워크로드 패턴에 따라 2~10배 수준이 일반적입니다.

vLLM 서버 실행과 핵심 파라미터

vLLM OpenAI 호환 서버를 띄우는 기본 명령은 다음과 같습니다.

pip install vllm

python -m vllm.entrypoints.openai.api_server 
  --model meta-llama/Llama-3.1-8B-Instruct 
  --host 0.0.0.0 
  --port 8000 
  --gpu-memory-utilization 0.90 
  --max-num-seqs 256 
  --max-model-len 8192 
  --tensor-parallel-size 2 
  --dtype bfloat16

멀티 GPU 환경에서 처리량을 더 끌어올리려면 파이프라인 병렬화와 양자화를 함께 적용합니다.

# AWQ 4-bit 양자화 모델 + 파이프라인 병렬
python -m vllm.entrypoints.openai.api_server 
  --model TheBloke/Llama-2-70B-Chat-AWQ 
  --quantization awq 
  --gpu-memory-utilization 0.92 
  --max-num-seqs 512 
  --max-model-len 4096 
  --tensor-parallel-size 4 
  --pipeline-parallel-size 2 
  --enable-chunked-prefill 
  --max-num-batched-tokens 8192 
  --swap-space 16          # CPU swap 공간 (GiB)

추론 엔드포인트는 OpenAI API 형식을 그대로 사용하므로 기존 클라이언트 코드를 수정 없이 전환할 수 있습니다.

curl http://localhost:8000/v1/chat/completions 
  -H "Content-Type: application/json" 
  -d '{
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "messages": [{"role": "user", "content": "vLLM이란 무엇인가요?"}],
    "max_tokens": 512,
    "temperature": 0.7
  }'

핵심 파라미터 해설과 처리량 vs 지연 트레이드오프

파라미터를 조정하는 것은 결국 처리량(더 많은 요청 처리)과 지연시간(더 빠른 응답) 사이의 균형을 맞추는 작업입니다. 주요 파라미터와 그 영향을 정리합니다.

파라미터 기본값 높일 때 효과 낮출 때 효과 추천 시나리오
–max-num-seqs 256 처리량 ↑, TTFT 지연 ↑ TTFT 단축, 처리량 ↓ 배치 처리: 높게 / 챗봇: 낮게
–gpu-memory-utilization 0.90 KV 캐시 풀 증가, OOM 위험 안전 여유 확보 안정 운영: 0.85~0.92
–max-num-batched-tokens 모델마다 다름 prefill 처리량 ↑ 짧은 응답 지연 개선 긴 문서 요약: 높게
–enable-chunked-prefill False 긴 프롬프트 지연 평탄화 혼합 길이 워크로드에 유리
–swap-space (GiB) 4 더 많은 시퀀스 수용 swap 비용 감소 버스트 트래픽 완충
–tensor-parallel-size 1 모델 분산, VRAM 분산 통신 오버헤드 감소 70B+: 최소 4

TTFT(Time To First Token)는 사용자가 가장 직접 체감하는 지표입니다. --max-num-seqs를 높이면 GPU가 더 많은 요청을 동시에 처리하므로 개별 요청의 TTFT가 늘어납니다. 반대로 낮추면 응답성은 좋아지지만 처리량이 떨어집니다. 챗봇 애플리케이션이라면 64~128, 오프라인 배치 처리라면 512 이상을 권장합니다.

실전 팁: 모니터링과 양자화 선택

vLLM은 /metrics 엔드포인트로 Prometheus 지표를 노출합니다. 필수 지표는 다음 세 가지입니다.

  • vllm:num_requests_running: 현재 GPU에서 추론 중인 시퀀스 수. max-num-seqs 한도에 자주 도달하면 GPU 추가나 파라미터 조정이 필요합니다.
  • vllm:gpu_cache_usage_perc: KV 캐시 사용률. 지속적으로 95% 이상이면 swap이 자주 발생하고 있을 가능성이 높습니다.
  • vllm:time_to_first_token_seconds: TTFT 히스토그램. p99 값이 SLA를 초과하면 max-num-seqs 축소를 검토합니다.

양자화 선택은 정확도와 속도의 교환입니다. FP16 대비 AWQ/GPTQ 4-bit는 메모리를 약 55% 절감하고 처리량은 1.5~2배 향상되지만, 일부 태스크(수학, 코드 생성)에서 정확도가 소폭 하락할 수 있습니다. FP8(bfloat8)은 H100 이상 GPU에서 정확도 손실 없이 FP16 대비 약 1.5배 처리량 향상을 제공하며 현재 가장 추천되는 절충점입니다.

멀티 GPU 환경이라면 단순히 GPU 수를 늘리기 전에 먼저 --tensor-parallel-size 를 조정해 단일 노드 내 효율을 최대화하세요. 노드 간 통신(NVLink 없는 PCIe 연결)은 텐서 병렬 효율을 크게 떨어뜨리므로, 70B 이하 모델은 단일 노드 8-GPU가 다중 노드 분산보다 일반적으로 유리합니다.

마무리

vLLM은 PagedAttention으로 GPU 메모리 낭비를 없애고, 연속 배칭으로 GPU 유휴 시간을 최소화함으로써 기존 대비 수 배에서 수십 배의 처리량 향상을 실현합니다. 핵심은 워크로드 특성을 먼저 파악하는 것입니다. 챗봇이라면 TTFT를 우선시하고, 문서 요약·배치 분류라면 처리량을 극대화하세요. 어느 쪽이든 /metrics로 실시간 모니터링을 걸어두고, 파라미터를 조금씩 바꾸며 실제 워크로드로 벤치마킹하는 것이 최선입니다.

자주 묻는 질문

Q. vLLM과 TensorRT-LLM 중 어느 것을 써야 하나요?

A. NVIDIA GPU를 사용하고 모델이 TensorRT-LLM이 지원하는 아키텍처라면, TensorRT-LLM이 최적화된 커널로 더 낮은 지연시간을 제공합니다. 반면 vLLM은 모델 지원 폭이 훨씬 넓고 설정이 간단합니다. 프로토타이핑과 다양한 모델 테스트에는 vLLM, 지연시간이 극도로 중요한 프로덕션에서는 TensorRT-LLM을 검토하세요. AMD GPU 환경이라면 vLLM(ROCm 지원)이 사실상 유일한 선택지입니다.

Q. –gpu-memory-utilization을 0.95 이상으로 올려도 되나요?

A. 권장하지 않습니다. PyTorch CUDA 컨텍스트와 모델 가중치 자체가 GPU 메모리를 일부 점유하는데, 이를 고려하지 않고 너무 높게 설정하면 런타임 OOM(Out of Memory)으로 서버가 크래시됩니다. 0.90~0.92를 기본값으로 두고, GPU 메모리 사용 패턴을 며칠 모니터링한 뒤 여유가 있을 때만 조금씩 높이세요.

Q. 단일 A100 80GB로 Llama-3.1-70B를 서빙할 수 있나요?

A. FP16으로는 불가능합니다(약 140GB 필요). 하지만 AWQ 4-bit 양자화 모델을 사용하면 약 35~40GB로 줄어들어 단일 A100에서 서빙 가능합니다. 정확도가 중요하다면 2×A100(텐서 병렬 2)에 FP8 양자화 조합이 좋은 절충점입니다.