시스템 프롬프트 캐싱, Prefix-aware 스케줄링, RadixAttention의 원리를 분석하고, 멀티턴 대화와 평가 워크플로우에서의 성능 개선을 다룹니다.
실제 LLM 서비스에서는 동일하거나 유사한 프롬프트가 반복적으로 등장합니다. 가장 대표적인 사례는 **시스템 프롬프트(System Prompt)**입니다.
요청 1: [시스템 프롬프트 800토큰] + [사용자: "오늘 날씨 알려줘"]
요청 2: [시스템 프롬프트 800토큰] + [사용자: "내일 일정 확인해줘"]
요청 3: [시스템 프롬프트 800토큰] + [사용자: "이메일 요약해줘"]
...수천 건의 요청...매 요청마다 동일한 800토큰의 시스템 프롬프트를 Prefill하면, 이 800토큰에 대한 KV 캐시 연산이 매번 반복됩니다. 시간당 10,000건의 요청을 처리한다면, 시스템 프롬프트 Prefill만으로 800만 토큰의 중복 연산이 발생합니다.
Prefix Caching은 이 중복을 제거합니다. 한 번 계산한 접두사(Prefix)의 KV 캐시를 저장해 두고, 동일한 접두사를 가진 후속 요청에서 재활용합니다.
Prefix Caching은 Prefill 단계의 중복 연산을 제거해 TTFT를 줄이는 기법입니다. 3장의 PagedAttention이 메모리 효율을, 4장의 Continuous Batching이 처리량을 최적화했다면, Prefix Caching은 반복 연산의 효율을 최적화합니다.
Prefix Caching은 다음과 같이 동작합니다.
캐시 없이:
요청 1 Prefill: [시스템 800토큰 + 사용자 50토큰] = 850토큰 연산
요청 2 Prefill: [시스템 800토큰 + 사용자 30토큰] = 830토큰 연산
Prefix Cache 적용:
요청 1 Prefill: [시스템 800토큰 + 사용자 50토큰] = 850토큰 연산 (캐시 저장)
요청 2 Prefill: [캐시 히트 + 사용자 30토큰] = 30토큰 연산 (96% 절감)KV 캐시를 재활용하려면, 입력 토큰 시퀀스가 정확히 일치하는지 확인해야 합니다. 이를 위해 토큰 시퀀스의 **해시(Hash)**를 캐시 키로 사용합니다.
3장에서 다룬 vLLM의 APC(Automatic Prefix Caching)는 블록 단위로 해시를 계산합니다. 블록 크기가 16토큰이라면, 16토큰 단위로 해시를 비교해 일치하는 블록의 KV 캐시를 재활용합니다.
요청 1 토큰: [A A A A | B B B B | C C C C | D D D D | E E ...]
블록0 블록1 블록2 블록3 블록4
hash=h0 hash=h1 hash=h2 hash=h3
요청 2 토큰: [A A A A | B B B B | C C C C | X X X X | Y Y ...]
블록0 블록1 블록2 블록3' 블록4'
hash=h0 hash=h1 hash=h2 hash=h3'
→ 블록0, 블록1, 블록2의 KV 캐시 재활용 (3블록 = 48토큰)
→ 블록3'부터 새로 PrefillPrefix Caching의 효과를 극대화하려면, 스케줄러가 캐시 상태를 인식하고 이를 반영해야 합니다.
여러 GPU 레플리카(Replica)가 요청을 처리하는 분산 환경에서, 동일한 시스템 프롬프트를 사용하는 요청을 같은 레플리카로 라우팅하면 캐시 히트율이 극대화됩니다.
단순한 라운드 로빈이나 부하 기반 라우팅 대신, 접두사 해시를 기준으로 라우팅하면 캐시 히트율이 50-70%에서 90%+ 이상으로 향상될 수 있습니다.
캐시 히트가 예상되는 요청을 우선 처리하면 전체 시스템의 효율이 높아집니다. 캐시 히트 요청은 Prefill이 빠르므로 GPU를 빠르게 비워 다음 요청을 처리할 수 있습니다.
SGLang은 UC Berkeley에서 개발한 추론 프레임워크로, RadixAttention이라는 고급 Prefix Caching 메커니즘을 제공합니다.
기존의 블록 해시 방식은 정확히 일치하는 접두사만 캐싱할 수 있습니다. RadixAttention은 **래딕스 트리(Radix Tree)**를 사용해 여러 요청의 접두사를 계층적으로 관리합니다.
[시스템 프롬프트]
/ \
[사용자 대화 A] [사용자 대화 B]
/ \ |
[턴3 질문] [턴3 다른질문] [턴2 질문]래딕스 트리의 장점은 다음과 같습니다.
SGLang은 Python DSL을 통해 복잡한 LLM 호출 패턴을 효율적으로 표현합니다. 프로그래머가 프롬프트 구조를 명시적으로 정의하면, SGLang이 자동으로 Prefix Caching을 최적화합니다.
import sglang as sgl
@sgl.function
def multi_turn_chat(s, system_prompt, messages):
s += sgl.system(system_prompt)
for msg in messages:
s += sgl.user(msg["user"])
s += sgl.assistant(sgl.gen("response", max_tokens=256))
# SGLang이 자동으로 공통 접두사를 감지하고 캐싱멀티턴 대화는 Prefix Caching의 가장 자연스러운 적용 대상입니다. 각 턴이 이전 턴의 전체 이력을 포함하므로, 이전 턴의 KV 캐시를 그대로 재활용할 수 있습니다.
턴 1: [시스템] + [사용자1] → Prefill 전체 + Decode
턴 2: [시스템] + [사용자1] + [응답1] + [사용자2] → 캐시 히트 + [응답1+사용자2] Prefill + Decode
턴 3: [시스템] + [사용자1] + [응답1] + [사용자2] + [응답2] + [사용자3]
→ 캐시 히트 + [응답2+사용자3]만 Prefill + Decode각 턴에서 새로 Prefill해야 하는 토큰은 이전 응답과 새 사용자 입력뿐입니다. 대화가 길어질수록 캐시 히트 비율이 높아져 TTFT가 거의 일정하게 유지됩니다.
멀티턴 대화에서 Prefix Caching이 효과적이려면, 이전 턴의 KV 캐시가 아직 메모리에 남아 있어야 합니다. 대화 간격이 길거나 GPU 메모리가 부족해 캐시가 Evict되면, 전체 이력을 다시 Prefill해야 합니다.
Prefix Caching이 가장 극적인 효과를 보이는 시나리오 중 하나가 평가(Evaluation) 워크플로우입니다.
LLM 품질 평가에서 흔히 사용되는 패턴은, 동일한 평가 기준 프롬프트에 서로 다른 응답을 붙여 점수를 매기는 것입니다.
공통 평가 프롬프트 (2000토큰):
"다음 기준에 따라 응답을 1-5점으로 평가하세요:
1. 정확성: ...
2. 관련성: ...
3. 유창성: ...
[상세 루브릭 2000토큰]"
평가 대상 1000건:
[공통 프롬프트 2000토큰] + [응답 1: 200토큰]
[공통 프롬프트 2000토큰] + [응답 2: 350토큰]
...
[공통 프롬프트 2000토큰] + [응답 1000: 150토큰]Prefix Caching 없이는 2000토큰의 공통 프롬프트를 1000번 Prefill해야 합니다. Prefix Caching이 적용되면 공통 프롬프트는 한 번만 Prefill하고, 이후 999건은 개별 응답(평균 200토큰)만 Prefill합니다.
실제로는 캐시 관리 오버헤드와 메모리 제약으로 이론적 최댓값에 도달하기는 어렵지만, 60-80%의 시간 절감은 충분히 달성 가능합니다.
모델 벤치마크(MMLU, HumanEval 등)에서도 동일한 지시문이 수백 개의 테스트 케이스에 반복됩니다. 대규모 벤치마크 스위트를 실행할 때 Prefix Caching으로 평가 시간을 크게 단축할 수 있습니다.
Prefix Cache는 추가 GPU 메모리를 소비합니다. 캐시 크기가 커지면 동시 처리 가능한 요청 수가 줄어들 수 있으므로, 적절한 상한을 설정해야 합니다.
총 GPU 메모리 예산:
모델 가중치 + 활성 KV 캐시 + Prefix Cache + 여유 공간
Prefix Cache 최대 크기 설정:
- 고유 접두사 수가 적은 경우: 전체 KV 예산의 20-30%
- 고유 접두사 수가 많은 경우: 전체 KV 예산의 10-15%
- LRU eviction으로 자주 사용되지 않는 캐시 자동 정리서비스 시작 시 자주 사용되는 시스템 프롬프트의 KV 캐시를 미리 계산해 두는 캐시 워밍(Cache Warming) 전략이 효과적입니다. 콜드 스타트(Cold Start) 시 첫 번째 요청의 지연시간을 줄일 수 있습니다.
vLLM에서 Prefix Caching을 활성화하려면 서버 시작 시 --enable-prefix-caching 플래그를 사용합니다. SGLang은 RadixAttention이 기본 활성화되어 있습니다.
이 장에서는 Prefix Caching의 원리와 적용 시나리오를 분석했습니다.
다음 장에서는 모델 자체를 작게 만드는 양자화(Quantization) 기법을 다룹니다. FP8, INT8, INT4 등의 저정밀도 형식이 어떻게 메모리를 절반으로 줄이면서도 정확도를 유지하는지 알아보겠습니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
양자화의 기초 개념부터 FP8의 부상, W8A8/W4A16 전략, GPTQ/AWQ/SmoothQuant 기법, KV 캐시 양자화까지 정확도와 성능의 트레이드오프를 분석합니다.
Draft-Verify 패러다임으로 자기회귀 디코딩을 가속하는 Speculative Decoding의 원리, 수학적 보장, 그리고 Medusa, Eagle 등 변형 기법을 분석합니다.
텐서 병렬화, 파이프라인 병렬화, 시퀀스 병렬화, Expert 병렬화의 원리를 분석하고, 멀티 GPU 추론 전략과 클러스터 수준 최적화를 다룹니다.