본문 바로가기

LLM

KV Cache 완전 정리 — PagedAttention vs RadixAttention, SGLang이 빠른 이유

반응형

LLM이 토큰을 생성할 때마다 이전 토큰들의 중간 연산 결과를 저장해 두는 게 KV 캐시예요. 없으면 매 토큰마다 처음부터 다시 계산해야 해요.

근데 이 KV 캐시를 어떻게 관리하느냐에 따라 성능이 완전히 달라져요. vLLM과 SGLang은 서로 다른 방식으로 이 문제를 풀어요.


KV 캐시가 뭔가

트랜스포머의 어텐션 레이어는 매 스텝마다 이전 토큰들의 Key/Value 벡터를 참조해요.

1번째 토큰 생성: [토큰1] KV 계산
2번째 토큰 생성: [토큰1, 토큰2] — 토큰1 KV 재계산하면 낭비!

KV 캐시:
1번째 토큰 생성: [토큰1] KV 계산 → 저장
2번째 토큰 생성: 저장된 토큰1 KV 재사용 + 토큰2 KV만 계산
→ 계산량 대폭 감소

문제는 KV 캐시가 메모리를 많이 먹는다는 거예요.

Llama-3.1-8B 기준, 요청 1개 KV 캐시:
= 레이어 수 × 헤드 수 × 시퀀스 길이 × 헤드 차원 × 2(K+V) × 바이트
≈ 32 × 32 × 4096 × 128 × 2 × 2 = 약 2GB (FP16)

동시 요청 100개면 → 200GB 필요 → H100 3개 필요

이 메모리를 효율적으로 관리하는 게 핵심이에요.


PagedAttention — vLLM의 방식

문제: 기존 방식은 각 요청마다 최대 시퀀스 길이만큼 연속 메모리를 미리 할당해요. 실제론 그만큼 안 쓰는데 메모리가 낭비돼요 (메모리 단편화).

기존 방식:
요청 A: [████████████          ] 최대 4096 미리 할당, 실제 1000 토큰 사용
요청 B: [████                  ] 최대 4096 미리 할당, 실제 200 토큰 사용
→ GPU 메모리 20~38%만 실제 사용

PagedAttention 은 OS의 가상 메모리 개념을 KV 캐시에 적용했어요.

PagedAttention:
KV 캐시를 고정 크기 페이지(블록)로 분할
요청마다 필요한 만큼만 페이지 할당
사용 안 하는 페이지는 다른 요청에 재사용

→ GPU 메모리 96%+ 활용
→ 동시 처리 요청 수 대폭 증가

vLLM, SGLang, TensorRT-LLM 모두 PagedAttention을 기반으로 써요. 이제는 표준이에요.


RadixAttention — SGLang의 혁신

PagedAttention은 메모리 단편화 문제를 풀었어요. 근데 또 다른 낭비가 있어요.

여러 요청이 같은 프롬프트 앞부분을 공유하는 경우, 매번 다시 계산해요.

챗봇 요청 1000개:
모두 동일한 시스템 프롬프트 (1000 토큰) + 각자 다른 질문

기존 방식: 1000개 요청 × 1000 토큰 = 100만 토큰 프리필 연산
RadixAttention: 시스템 프롬프트 1번만 계산 + 999번 재사용
→ 프리필 연산 99% 절감

RadixAttention 은 KV 캐시를 Radix 트리에 저장해요. 공유 접두사를 자동으로 찾아 재사용해요.

Radix 트리 구조:

[시스템 프롬프트 KV] ← 모든 요청이 공유
├── [사용자A 질문 KV]
│   └── [응답A KV]
├── [사용자B 질문 KV]
│   └── [응답B KV]
└── [사용자C 질문 KV]
    └── [응답C KV]

새 요청이 오면 트리에서 가장 긴 공통 접두사를 찾아 재사용

LRU(가장 최근 사용 안 된 것 제거) 정책으로 메모리를 자동 관리해요.


어느 워크로드에서 얼마나 빠른가

RadixAttention 효과는 공유 접두사 비율에 비례해요.

공유 접두사 비율 | 캐시 히트율 | TTFT 개선
60%+           | 75~95%    | 매우 큼
40~60%         | 50~75%    | 중간
20% 이하        | ~0%       | 없음

효과 큰 워크로드:
✅ 챗봇 (동일 시스템 프롬프트)
✅ RAG (동일 문서 + 다른 질문)
✅ Few-shot 프롬프팅 (동일 예시)
✅ 에이전트 (동일 툴 정의 + 대화 히스토리)
✅ Tree-of-Thought (같은 문제, 다른 경로)

효과 없는 워크로드:
❌ 완전히 다른 프롬프트들
❌ 동적으로 매번 바뀌는 시스템 프롬프트

멀티턴 대화에서 SGLang이 vLLM 대비 약 10~20% 더 빠른 이유예요.


HiCache — 계층적 KV 캐시 (SGLang 최신 기능)

GPU 메모리만으로는 긴 컨텍스트나 많은 동시 대화를 감당하기 힘들어요. SGLang HiCache는 KV 캐시를 계층으로 관리해요.

GPU 메모리 (빠름, 비쌈, 적음)
    ↕ 자동 이동
CPU 메모리 (중간)
    ↕ 자동 이동
디스크/원격 스토리지 (느림, 싸고, 많음)

실제 효과:

  • Qwen3-Coder-480B 코딩 에이전트 (25K+ 토큰): TTFT 56% 감소, 처리량 2배
  • DeepSeek-R1 671B (PD 분리 환경): TTFT 84% 감소
# HiCache 활성화 (--enable-hicache)
python -m sglang.launch_server \
  --model-path meta-llama/Llama-3.1-8B-Instruct \
  --enable-hicache \
  --hicache-ratio 0.5  # CPU 메모리에 GPU의 50% 크기 캐시

FP8 KV 캐시 — 메모리 50% 추가 절감

KV 캐시를 FP16 대신 FP8로 저장하면 메모리를 절반으로 줄여요. 품질 손실은 미미해요.

python -m sglang.launch_server \
  --model-path meta-llama/Llama-3.1-70B-Instruct \
  --kv-cache-dtype fp8_e4m3

같은 GPU에서 더 많은 동시 요청을 처리할 수 있어요.


KV 캐시 히트율 올리는 실전 팁

RadixAttention을 최대로 활용하려면 프롬프트를 잘 설계해야 해요.

1. 시스템 프롬프트를 고정해요

# 나쁜 예 — 매번 다른 프롬프트
system_prompt = f"현재 시각은 {datetime.now()}입니다. 당신은 어시스턴트입니다."
# → 매번 캐시 미스

# 좋은 예 — 고정 접두사
system_prompt = "당신은 도움이 되는 어시스턴트입니다."
# → 모든 요청이 캐시 히트

2. 동적 내용은 뒤에 배치해요

# 나쁜 예
prompt = f"사용자 ID: {user_id}\n{공통_시스템_프롬프트}\n{질문}"
# → user_id가 앞에 있어서 캐시 미스

# 좋은 예
prompt = f"{공통_시스템_프롬프트}\n사용자 ID: {user_id}\n{질문}"
# → 공통 부분이 앞에 있어서 캐시 히트

3. RAG 문서를 앞에 배치해요

# 같은 문서로 여러 질문할 때
prompt = f"{공통_문서}\n\n질문: {각자_다른_질문}"
# → 문서 KV 캐시 재사용, 질문만 새로 계산

캐시 히트율 모니터링

# Prometheus 메트릭으로 확인
curl http://localhost:30000/metrics | grep cache

# 주요 지표
sglang:cache_hit_rate        # 캐시 히트율 (높을수록 좋음)
sglang:token_usage           # 전체 토큰 메모리 사용률
# 서버 상태 API로도 확인 가능
import requests
stats = requests.get("http://localhost:30000/get_server_info").json()
print(f"캐시 히트율: {stats['cache_hit_rate']:.1%}")

PagedAttention vs RadixAttention 정리

PagedAttention RadixAttention

목적 메모리 단편화 해결 공유 접두사 재사용
효과 메모리 효율 4~5배 TTFT 최대 95% 감소
적용 엔진 vLLM, SGLang 모두 SGLang 특화
워크로드 모든 워크로드 공유 프롬프트 워크로드
설정 기본 활성화 기본 활성화

둘은 경쟁 관계가 아니에요. SGLang은 PagedAttention + RadixAttention을 같이 써요.


마무리

KV 캐시 관리의 핵심을 정리하면 이래요.

PagedAttention — "메모리를 페이지로 쪼개서 낭비를 없앤다" RadixAttention — "같은 앞부분은 한 번만 계산한다"

SGLang이 챗봇, RAG, 에이전트 워크로드에서 vLLM보다 빠른 이유는 RadixAttention이에요. 공유 프롬프트가 많을수록 효과가 커요. 시스템 프롬프트를 고정하고 동적 내용을 뒤에 배치하는 것만으로도 캐시 히트율을 크게 올릴 수 있어요. 😄

 

반응형