콘텐츠로 이동

11주차: vLLM 고처리량 추론 최적화

Phase 411주차고급강의일: 2026-05-12

개념 관점

PagedAttention, prefix caching, chunked prefill, speculative decoding, disaggregated prefill 5가지 최적화가 각각 어떤 병목을 해결하는지 1문장으로 설명한다.

설계 관점

workload(채팅·배치·RAG·에이전트) 특성을 진단하고 어떤 옵션을 어느 순서로 켤지 의사결정 표로 정리한다.

구현 관점

vllm serve 명령으로 단일/멀티 GPU/MIG 서버를 띄우고, OpenAI 호환 client에서 latency·throughput 지표를 기록하며, admission control 큐를 코드로 구현한다.

운영 관점

TTFT, TPOT, queue time, cache hit ratio, error rate 5개 핵심 지표를 Prometheus/Grafana에 노출하고, 옵션 변경 전·후 차이를 정량적으로 보고한다.


추론 서버는 모델의 운영체제다

섹션 제목: “추론 서버는 모델의 운영체제다”

LLM을 직접 배포할 때 모델 파일만으로는 아무 것도 운영되지 않는다. 실제 서비스 품질은 추론 서버가 결정한다.

운영 문제추론 서버가 하는 일
GPU 메모리 부족KV cache 관리, quantization, tensor parallelism
요청 지연continuous batching, chunked prefill, speculative decoding
반복 프롬프트 비용automatic prefix caching
장문 입력 폭주prefill/decode 분리, max context policy
장애 분석metrics, trace, request log, token accounting

vLLM은 이 문제를 OpenAI 호환 API로 감싸서 agent harness가 모델을 바꾸더라도 같은 인터페이스를 유지하게 한다.

전통적 LLM 추론은 KV cache를 연속 메모리로 크게 예약한다. 요청 길이가 서로 다르면 빈 공간이 생기고, 장문 요청이 섞이면 fragmentation이 심해진다. vLLM의 PagedAttention은 OS 가상 메모리처럼 KV cache를 block 단위로 나누어 필요한 만큼 연결한다.

전통 방식[KV cache: 최대 길이 예약]짧은 요청도 큰 공간 점유
PagedAttention[block] [block] [block]필요한 블록만 동적 연결
PagedAttention KV Cache Mapping
논리 KV cache (sequence별)
seq A — t0..t511두 블록 사용 (block 0, 1)
seq B — t0..t1023세 블록 사용 (block 2, 3, 4)
seq C — t0..t127한 블록 사용 (block 5)
물리 GPU 메모리 블록 (16-token 페이지)
block 0→ seq A
block 1→ seq A
block 2→ seq B
block 3→ seq B
block 4→ seq B
block 5→ seq C
block 6free

논리 sequence는 page table을 통해 가용한 물리 블록으로 흩어져 매핑된다. 새 요청이 들어와도 free block을 할당하면 되므로 fragmentation이 사실상 사라진다.

PagedAttention은 기본기다. 2026년 운영에서 중요한 질문은 “PagedAttention을 쓰는가”가 아니라 어떤 workload에 어떤 최적화를 켤 것인가다.

Automatic Prefix Caching

반복되는 system prompt, tool schema, AGENTS.md/CLAUDE.md prefix의 KV cache를 재사용한다. Ralph loop처럼 정적 prefix가 긴 workload에서 효과가 크다.

Chunked Prefill

긴 입력 prefill을 작은 청크로 나누어 decode 요청과 섞는다. 장문 요청 하나가 전체 큐를 막는 현상을 줄인다.

Speculative Decoding

작은 draft model 또는 n-gram speculation으로 후보 토큰을 먼저 만들고 큰 모델이 검증한다. latency 절감이 목표다.

Disaggregated Prefill

prefill과 decode를 다른 worker/GPU로 분리한다. 장문 입력과 짧은 응답 요청이 섞인 production workload에 유리하다.

Structured Outputs

JSON schema, tool call, reasoning output을 serving 계층에서 강제한다. agent harness의 parser 실패율을 줄인다.

레버주 효과가장 큰 위험측정 지표
Prefix CachingTTFT 감소, prefill 비용 절감매번 prompt가 미세하게 다르면 hit 0%cache hit ratio, TTFT
Chunked Prefill장문 입력의 head-of-line blocking 완화chunk 단위가 너무 작으면 오버헤드 증가p95 latency, queue time
Speculative Decoding평균 TPOT 감소draft model 품질이 낮으면 reject로 오히려 느려짐acceptance rate, TPOT
Disaggregated Prefill장문/단문 mix workload throughput ↑추가 inter-node 통신 비용throughput, network util
Structured Outputsparser 실패율 감소일부 schema는 sampling 품질 저하 유발invalid JSON rate, score
시나리오정적 prefix동적 prefix예상 cache hitTTFT 영향
Ralph loop, 같은 PROMPT.md 반복길다 (3-8K)작은 turn 차이70-95%매우 큼
Multi-tenant chatbot, system prompt 공통중간 (500-2K)user message가 매번 다름30-60%보통
RAG with 매번 다른 retrieved chunks거의 없음거의 전부0-15%거의 없음
동일 코드베이스 반복 분석매우 길다 (10K+)파일 diff만 변함80-99%매우 큼

Speculative decoding 시퀀스 다이어그램

섹션 제목: “Speculative decoding 시퀀스 다이어그램”
Speculative Decoding Sequence
Client
Target (large)
Draft (small)
① Client → Target: prompt
until stop token
② Target → Draft: speculate next k tokens
③ Draft → Target: candidate tokens [t1..tk]
④ Target verifies all k tokens (one forward pass)
accept all kTarget → Client: emit k tokens
reject at jemit [t1..tj] + 1 corrected

draft model이 정확할수록 한 번에 받아들여지는 토큰이 많아져 평균 TPOT가 줄어든다. draft가 자주 틀리면 verify 비용만 늘어나서 오히려 느려질 수 있다는 점에 유의한다.

Disaggregated Prefill Architecture
Frontend — Load Balancer요청 수신, 응답 반환
▼ prefill 단계로 분배
Prefill Worker 1긴 입력 KV 생성
Prefill Worker 2긴 입력 KV 생성
▼ KV cache 핸드오프
KV Cache Storeprefill 결과 임시 보관 / 빠른 lookup
▼ decode 단계로 라우팅
Decode Worker 1짧은 토큰 생성
Decode Worker 2짧은 토큰 생성
Decode Worker 3짧은 토큰 생성
▲ 응답을 LB로 반환

긴 입력은 prefill 워커가 처리하고 KV cache만 decode 워커로 넘기는 패턴이다. 장문 입력과 짧은 응답이 혼합된 production에서 자원 활용도를 끌어올린다.

모든 옵션을 한꺼번에 켜는 것이 최적화가 아니다. workload별 병목을 먼저 보고 하나씩 켠다.

Workload주 병목먼저 켤 옵션측정할 지표
긴 repository context + 짧은 수정prefill 비용prefix caching, chunked prefillTTFT, cache hit ratio
짧은 질의가 매우 많음batching 효율continuous batching, 적절한 max_num_seqsthroughput, queue time
긴 문서 요약과 짧은 코드 요청 혼합long prefill blockingchunked prefill, disaggregated prefillp95 latency, queue time
interactive coding assistant첫 토큰 지연speculative decoding, 작은 max_tokensTTFT, perceived latency
strict JSON/tool outputparser 실패structured outputs, tool parser 설정invalid JSON rate
Ralph 루프 캡스톤prefill 반복prefix caching + chunked prefillTTFT, cache hit

실습 보고서에는 “옵션을 켰다”가 아니라 옵션을 켜기 전/후 병목 지표가 어떻게 변했는지를 기록한다.

Terminal window
vllm serve deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct \
--served-model-name local-coder \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--enable-prefix-caching \
--port 8000

Throughput와 latency를 동시에 봐야 한다

섹션 제목: “Throughput와 latency를 동시에 봐야 한다”

추론 서버 튜닝에서 흔한 실수는 tokens/sec만 보는 것이다. 에이전트 시스템에서는 다음 지표를 함께 봐야 한다.

지표의미나쁜 신호
TTFT(Time to First Token)사용자가 첫 응답을 보기까지 시간장문 prefill이 queue를 막음
TPOT(Time per Output Token)토큰 생성 간격decode 단계 GPU utilization 저하
Throughput초당 처리 토큰batching 부족 또는 memory fragmentation
Queue time요청이 대기한 시간concurrency 과다, admission control 부재
Cache hit ratioprefix 재사용률prompt 조립 방식이 매번 달라짐
Error rate실패/timeout 비율max_model_len, OOM, parser 실패

vLLM은 /metrics 엔드포인트를 기본 제공한다. 캡스톤 발표용 핵심 패널은 다음 PromQL로 만든다.

# Panel 1: TTFT p95 (모델별)
histogram_quantile(0.95,
sum by (le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m])))
# Panel 2: 처리량 (tokens/sec)
sum by (model_name) (rate(vllm:generation_tokens_total[1m]))
# Panel 3: prefix cache hit ratio
sum(rate(vllm:cache_hit_tokens_total[5m]))
/
sum(rate(vllm:prompt_tokens_total[5m]))
# Panel 4: 큐 대기 (active vs queued)
vllm:num_requests_running
vllm:num_requests_waiting

발표 자료에는 옵션을 변경한 시각을 annotation으로 표시해 “켜기 전후 차이”가 한눈에 보이게 한다.

추론 서버가 느려지는 순간 무작정 더 많은 요청을 받으면 전체 팀의 실습이 무너진다. production serving은 요청을 받기 전에 다음을 판단한다.

통제 항목정책 예시
최대 입력 길이max_model_len보다 낮은 course-level limit을 둔다
동시 요청 수팀별 queue와 전역 queue를 분리한다
우선순위demo/replay 요청을 실험성 batch보다 우선한다
timeoutprefill timeout과 decode timeout을 분리 기록한다
fallbacklocal model 실패 시 commercial API 또는 작은 모델로 reroute한다
# admission.py — 팀별 queue 와 토큰 예산 관리
import asyncio, time
from dataclasses import dataclass
@dataclass
class Budget:
max_concurrent: int = 4
max_prompt_tokens: int = 16000
timeout_s: float = 60.0
team_locks: dict[str, asyncio.Semaphore] = {}
async def admit(team: str, prompt_tokens: int, budget: Budget) -> bool:
if prompt_tokens > budget.max_prompt_tokens:
return False
sem = team_locks.setdefault(team, asyncio.Semaphore(budget.max_concurrent))
try:
await asyncio.wait_for(sem.acquire(), timeout=budget.timeout_s)
return True
except asyncio.TimeoutError:
return False
async def release(team: str):
team_locks[team].release()

Agent OS Runtime 관점에서는 admission control도 policy gate다. “요청을 거절했다”는 것도 실패가 아니라, 시스템이 경계를 지킨 성공 이벤트일 수 있다.

DGX H100 환경에서 학생 팀별 실습을 운영할 때는 “모두가 같은 대형 모델을 공유”하는 방식보다 resource boundary를 명확히 나누는 편이 안정적이다.

Terminal window
# 팀 A: 1g.10gb slice, 교육용 모델
CUDA_VISIBLE_DEVICES=MIG-GPU-a vllm serve deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct \
--served-model-name team-a-coder \
--max-model-len 16384 \
--port 8001
# 팀 B: 3g.40gb slice, 더 큰 모델
CUDA_VISIBLE_DEVICES=MIG-GPU-b vllm serve Qwen/Qwen3-Coder-30B-A3B-Instruct \
--served-model-name team-b-coder \
--max-model-len 32768 \
--port 8002

vLLM은 Prometheus/Grafana, OpenTelemetry 예제를 제공한다. 12주차에서 본격적으로 다루지만, 11주차 Lab에서도 최소한 다음 로그는 남겨야 한다.

{
"request_id": "run-20260512-001",
"model": "local-coder",
"prompt_tokens": 1842,
"completion_tokens": 419,
"ttft_ms": 820,
"tpot_ms": 34,
"cache_hit": true,
"finish_reason": "stop"
}

이 형식은 Agent OS Runtime의 .events.jsonl과도 연결된다. serving layer는 토큰과 지연시간을 기록하고, agent runtime은 tool call, approval, test result, replay 상태를 기록한다.

  1. 기준 서버 실행

    Terminal window
    vllm serve deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct \
    --served-model-name local-coder \
    --max-model-len 32768 \
    --port 8000
  2. prefix caching 전후 비교

    같은 system prompt와 같은 repository summary를 포함한 요청 20개를 실행한다. --enable-prefix-caching을 끈 상태와 켠 상태의 TTFT, throughput, cache hit를 비교한다.

  3. chunked prefill 전후 비교

    20K token 이상의 긴 파일 요약 요청과 짧은 코드 생성 요청을 섞는다. 긴 요청이 짧은 요청의 대기 시간을 얼마나 늘리는지 측정한다.

  4. OpenAI-compatible client 작성

    from openai import OpenAI
    import time
    client = OpenAI(base_url="http://localhost:8000/v1", api_key="local")
    started = time.perf_counter()
    response = client.chat.completions.create(
    model="local-coder",
    messages=[{"role": "user", "content": "pytest fixture 예시를 작성해줘"}],
    temperature=0.2,
    )
    elapsed = time.perf_counter() - started
    print(elapsed, response.choices[0].message.content[:200])
  5. admission control 큐 도입

    admission.py 패턴을 적용해 팀별 동시 요청 수를 제한한다. 거절된 요청은 별도 metric(admission.rejected)으로 카운트한다.

  6. 대시보드 준비

    Prometheus/Grafana 또는 간단한 CSV 로그로 ttft_ms, tokens/sec, error_rate, cache_hit을 기록한다. 12주차에서는 이 값을 OpenTelemetry trace와 연결한다.

항목통과 기준
모델 로딩cold start와 warm restart 시간이 기록됨
API 호환성OpenAI client에서 chat completion 성공
안정성100개 요청 중 timeout/OOM 0-2개 이하
비용GPU 시간당 비용과 API 비용 비교표 작성
관측성최소 request_id, model, tokens, latency 기록
보안외부 네트워크/파일 접근 권한 분리
Admission control팀별 queue가 분리되고 거절 이벤트가 기록됨

12주차에서는 이 vLLM 서버 위에 텔레메트리, event store, LLM-as-Judge 평가 게이트를 구축한다. 11주차가 “빠르게 생성하는 법”이라면, 12주차는 “생성 결과를 믿을 수 있게 운영하는 법”이다.

  1. 추론 서버는 모델의 OS: 모델 가중치만으로는 운영이 성립하지 않는다. KV cache·batching·queue·관측성을 함께 설계한다.
  2. PagedAttention은 출발점: fragmentation을 거의 제거하지만, 그 위에 어떤 최적화를 켤지가 진짜 운영 결정이다.
  3. 5가지 레버를 따로 평가하라: prefix caching / chunked prefill / speculative decoding / disaggregated prefill / structured outputs는 각자 다른 병목을 푼다.
  4. 워크로드 진단이 먼저: 옵션을 무작정 켜지 말고 TTFT·TPOT·queue time·cache hit 중 어디가 병목인지부터 측정한다.
  5. Throughput 단독 지표는 함정: 에이전트 UX는 TTFT와 p95 latency, queue time이 결정한다.
  6. Admission control도 policy gate: 거절은 실패가 아니라 경계 보호 이벤트로 기록한다.
  7. Multi-tenant는 boundary로 해결: MIG slice·port 분리·팀별 model name으로 격리하면 한 팀의 실수가 전체 실습을 무너뜨리지 않는다.

핵심 문서

관측성

논문 / 리포트

  • Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention” (SOSP 2023)
  • Leviathan et al., “Fast Inference from Transformers via Speculative Decoding” (ICML 2023)
  • Patel et al., “Splitwise: Efficient Generative LLM Inference Using Phase Splitting” (ISCA 2024)