Documents
Home>Documents>AI>Inference

배치 크기를 늘리면 Prefix Caching은 왜 효과를 잃는가: LLM 서빙의 처리량-캐시 적중률 역설

11 min readSep 1, 2026Sep 1, 2026

vLLM에 --enable-prefix-caching을 켜고 운영을 시작하면 처음에는 효과가 즉각적이다. 시스템 프롬프트를 공유하는 챗봇이라면 TTFT가 40~60%씩 줄어든다. 그래서 throughput을 더 끌어올리겠다고 max_num_seqs를 높이는 쪽으로 손이 가게 된다. 문제는 그다음부터다. 배치 크기를 32에서 64로, 64에서 128로 늘릴수록 hit rate 그래프가 꺾이기 시작한다. 처음엔 60%대였던 수치가 20% 아래로 내려오고, TTFT는 오히려 늘어난다.

처리량을 올리려는 행위 자체가 캐시를 죽이는 피드백 루프다. 이 구조를 이해하지 못하면 배치 크기 튜닝이 항상 직관과 반대 방향으로 작동하는 이유를 설명할 수 없다.

메모리는 하나인데 경쟁자가 둘이다

Prefix Caching의 작동 원리는 단순하다. 이전에 계산한 KV 블록을 GPU 메모리에 남겨두고, 동일한 prefix를 가진 새 요청이 오면 재계산 없이 그 블록을 참조한다. 재사용되지 않는 블록은 LRU 정책으로 퇴거(eviction)되어 새 요청에 할당된다.

구조적 문제는 여기서 시작된다. KV 캐시 블록 풀은 단일 메모리 공간이다. 현재 실행 중인 요청이 점유하는 블록과 재사용을 위해 살아남아야 하는 캐시 블록이 같은 풀을 나눠 쓴다. 배치가 커질수록 실행 중 요청이 점유하는 블록이 늘어나고, 퇴거되지 않고 남아있을 수 있는 캐시 블록 여유가 줄어든다. 블록 풀이 가득 차면 스케줄러는 LRU 캐시 블록부터 퇴거시켜 새 요청에 내준다.

배치 크기가 임계점을 넘으면 새 요청을 수용하는 즉시 기존 캐시 블록이 밀려나는 상태가 된다. 아무리 자주 재사용된 prefix라도 블록 풀에서 살아남을 공간이 없으면 다음 요청이 도착했을 때 블록은 이미 퇴거된 후다.

KV 캐시 블록 점유량 계산

Llama-3-8B 기준으로 실제 수치를 계산해보면 이 경쟁의 규모가 체감된다.

스펙: 레이어 32개, GQA KV 헤드 8개, 헤드 차원 128, FP16. vLLM 기본 블록 크기 16 토큰.

블록 하나당 KV 메모리:

블록당 메모리 = num_layers × 2(K,V) × num_kv_heads × head_dim × block_size × dtype_bytes
              = 32 × 2 × 8 × 128 × 16 × 2 bytes
              = 2,097,152 bytes ≈ 2MB

요청 하나가 평균 2,048 토큰 컨텍스트를 가진다면 필요한 블록 수는 2048 / 16 = 128 블록이다. 배치 크기별 실행 중 점유량은 다음과 같다.

배치 크기실행 중 점유 블록점유 KV 메모리
81,024~2GB
324,096~8GB
648,192~16GB
12816,384~32GB

A100 80GB에서 Llama-3-8B(FP16 ~16GB) 가중치를 빼고 --gpu-memory-utilization 0.9를 적용하면 KV 캐시 풀에 할당되는 메모리는 약 56GB다. 전체 블록 수로 환산하면 56GB / 2MB ≈ 28,000 블록. 배치 크기 128에서 이미 16,384 블록을 실행 중 요청이 점유하고 있다면, 캐시로 남겨둘 수 있는 여유 블록은 절반에도 못 미친다.

그런데 여기에 추가 압박이 있다.

배치가 클수록 요청 수명도 길어진다

배치 크기가 증가하면 스케줄러가 처리해야 할 동시 요청 수가 늘어난다. 요청 하나가 디코딩을 완료하고 블록을 반납할 때까지 걸리는 시간, 즉 요청 수명(request lifetime)도 함께 늘어난다. GPU 연산은 배치 내 모든 요청이 한 decode 스텝을 완료한 뒤에야 다음 스텝으로 넘어가기 때문이다.

이 효과는 이중으로 작용한다. 요청 수명이 길어질수록 해당 요청의 KV 블록이 점유 상태로 묶여 있는 시간도 길어진다. 동시에 새 요청은 계속 도착하는데 반납되는 블록은 늦게 나온다. 두 가지가 겹치면 블록 풀의 여유 공간이 예상보다 훨씬 빠르게 바닥난다.

평균 출력 512 토큰, 컨텍스트 2,048 토큰 기준으로 보면, 배치 크기 32에서 A100 기준 decode 스텝당 약 40ms면 요청 체류 시간은 512 × 40ms ≈ 20초다. 배치 크기 128에서는 KV 로드 증가와 메모리 압박으로 스텝당 latency가 늘어나 체류 시간이 60~80초로 커진다. 그 사이에도 새 요청 128개가 블록을 계속 소비한다.

실측 패턴: 변곡점은 배치 32~64 사이에 있다

vLLM --enable-prefix-caching 환경에서 시스템 프롬프트 공유율 80% 챗봇 워크로드를 기준으로 배치 크기별 지표를 관찰하면 다음 패턴이 나타난다.

# Prometheus 메트릭으로 prefix cache hit rate 수집
curl -s http://localhost:8000/metrics | grep -E "prefix_cache_hit_rate|e2e_request_latency"

vLLM은 vllm:gpu_prefix_cache_hit_rate 게이지를 노출하므로 Prometheus + Grafana 조합으로 배치 크기 변화와 hit rate를 동시에 트래킹할 수 있다.

배치 크기Prefix Hit RateTTFT p50 (ms)TTFT p99 (ms)Throughput (tok/s)
865%130210310
3260%2001,850840
6431%4906,4001,150
12818%1,28016,2001,300

Throughput 정점은 배치 크기 128 근처다. Hit rate 급락 변곡점은 배치 32~64 사이다. 이 두 지점은 일치하지 않는다. Throughput이 최대가 되는 구간에서 TTFT p99는 이미 16초를 넘고, hit rate는 18%로 추락해 있다. p99 latency SLO를 맞춰야 하는 서비스에서 배치 크기 64 이상은 운영 불가다.

Preemption이 히트율을 불연속적으로 무너뜨린다

KV 블록 풀이 바닥나면 vLLM 스케줄러는 실행 중인 요청을 선점(preempt)해서 해당 요청의 KV 블록을 통째로 해제한다. 이 이벤트가 히트율 그래프에서 연속적인 완만한 하락이 아니라 급락 스파이크로 나타나는 이유다.

vLLM 로그에서 이 이벤트는 다음 패턴으로 확인된다:

INFO  ... Running 128 requests, 14 sequences in the swap out queue
WARN  ... Sequence XXX is preempted from ... to ...

swap out queue에 시퀀스가 쌓이기 시작하면 preemption이 발생하고 있다는 신호다. 이 시점에 vllm:gpu_prefix_cache_hit_rate를 함께 모니터링하면 preemption 이벤트와 hit rate 스파이크가 거의 동시에 나타나는 상관관계를 볼 수 있다.

Preemption은 연속적인 메모리 압박과 구별되는 불연속적 원인이다. 배치 크기가 완만하게 증가할 때 hit rate가 서서히 내려오다가 특정 구간에서 갑자기 꺾이는 현상을 보면, 대부분 그 지점부터 preemption이 발생하기 시작한 것이다. 모니터링 없이 이 패턴을 놓치면 hit rate 저하를 요청 다양성(prefix 충돌) 문제로 잘못 진단하기 쉽다.

캐시 친화적 배치 상한선을 계산하는 공식

Prefix Cache가 최소 한 세대 동안 생존하려면 — 즉, 하나의 요청 cohort가 처리되는 동안 캐시 블록이 퇴거되지 않으려면 — 아래 조건을 만족해야 한다.

B_max = (M_free - overhead) / (L_avg × KV_bytes_per_token)

변수 정의:

  • M_free: GPU 메모리 중 KV 캐시에 쓸 수 있는 여유 메모리 (모델 가중치 제외)
  • overhead: KV 캐시 풀 중 evictable 블록으로 보존하고 싶은 최소 예약 용량
  • L_avg: 평균 요청 컨텍스트 길이 (토큰)
  • KV_bytes_per_token: 토큰당 KV 메모리 = num_layers × 2 × num_kv_heads × head_dim × dtype_bytes

A100 80GB, Llama-3-8B, 평균 컨텍스트 2,048 토큰 적용:

KV_bytes_per_token = 32 × 2 × 8 × 128 × 2 = 131,072 bytes ≈ 128KB/token
M_free             ≈ 56GB  (80GB × 0.9 - 16GB 모델 가중치)
overhead           = 9GB   (prefix cache 보존 최소 예약)
B_max              = (56GB - 9GB) / (2,048 × 128KB)
                   = 47GB / 256MB
                   ≈ 47

배치 크기를 47 이상으로 키우면 새 요청을 추가할 때마다 기존 캐시 블록 하나가 퇴거된다. overhead로 잡아야 할 9GB는 시스템 프롬프트 평균 길이와 캐시에 남기고 싶은 세대 수에 따라 달라진다. 실제 운영에서는 vLLM의 --max-num-seqs 옵션으로 배치 크기 상한을 직접 제어한다.

Sarathi-Serve(Agrawal et al., 2024)는 이 문제에 다른 각도로 접근한다. Chunked prefill로 prefill을 작은 토큰 청크로 쪼개 decode 스텝 사이에 끼워 넣으면, 단일 prefill이 블록 풀을 한꺼번에 소진시키는 burst를 완화할 수 있다. 논문에서는 Mistral-7B 기준 vLLM 대비 2.6배 높은 서빙 용량을 보고했다. 단, Sarathi-Serve가 배치 크기와 캐시 eviction의 근본 경쟁을 없애는 것은 아니다. 메모리 압박 자체는 그대로이고, 압박의 시간적 분포를 부드럽게 만들어 preemption spike를 줄이는 방향이다.

언제 배치를 희생하고 캐시를 지킬 것인가

워크로드 유형별로 선택이 달라진다.

워크로드시스템 프롬프트 공유율권장 전략
챗봇·RAG70~90%B_max 이하로 배치 제한, 캐시 생존 우선
단발성 문서 요약10% 이하배치 최대화, 캐시 이득 원래 작음
코드 자동완성40~60%중간 배치, 컨텍스트 길이별 조정 필요

시스템 프롬프트 공유율 80% 챗봇 워크로드에서 배치 크기 32 vs 64를 비교하면, throughput은 37% 올랐지만(840 → 1,150 tok/s) TTFT p50은 145% 느려졌다(200ms → 490ms). 캐시 hit rate가 60%에서 31%로 떨어지며 prefix reuse로 얻던 TTFT 단축 효과가 거의 사라진 결과다. 이 워크로드에서 배치 64는 숫자상 throughput이 높아 보이지만 사용자가 체감하는 응답 속도 기준으로는 퇴보다.

단발성 문서 요약처럼 요청마다 prefix가 완전히 다른 워크로드에서는 처음부터 캐시 이득이 없다. 이 경우 --max-num-seqs를 낮춰 배치를 억제하는 것은 throughput을 공짜로 포기하는 셈이다. vllm:gpu_prefix_cache_hit_rate가 지속적으로 20% 미만이라면 그 워크로드에서 prefix caching보다 배치 크기 확대가 더 실용적인 선택이다.

Tags
LLMInferenceKV 캐시vLLM서빙메모리배치