🤖 AI/ML

KV Cache

Key-Value 캐시, 트랜스포머 추론 속도를 높이는 기법

📖 상세 설명

KV Cache(Key-Value Cache)는 트랜스포머 모델의 추론 속도를 획기적으로 개선하는 기법입니다. Attention 계산에서 이전 토큰들의 Key와 Value 텐서를 저장해두고 재사용함으로써, 각 새 토큰 생성 시 전체 시퀀스를 다시 계산하는 중복 연산을 제거합니다.

LLM이 텍스트를 생성할 때는 autoregressive 방식으로 한 토큰씩 출력합니다. KV Cache가 없으면 N번째 토큰 생성 시 1~N-1번째 토큰의 K,V를 매번 재계산해야 하므로 O(N^2) 복잡도가 됩니다. KV Cache로 이를 O(N)으로 줄일 수 있습니다.

2023년 vLLM의 PagedAttention은 KV Cache 메모리 관리를 혁신했습니다. GPU 메모리를 페이지 단위로 동적 할당해 메모리 단편화를 4% 미만으로 줄였고(기존 70%), 같은 GPU로 2-4배 더 많은 요청을 처리할 수 있게 되었습니다.

2025년 기준 128K 토큰 컨텍스트가 표준이 되면서 KV Cache 최적화가 더욱 중요해졌습니다. LMCache는 GPU HBM, CPU RAM, SSD의 계층적 저장을 활용해 15배 throughput 향상을 달성했고, llm-d의 KV Cache Aware Routing은 87% 캐시 히트율로 TTFT를 88% 단축했습니다.

💻 코드 예제

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import time

# 모델 로드
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)

prompt = "인공지능의 미래에 대해 설명해주세요:"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

# KV Cache 비활성화 (비교용 - 매우 느림)
print("=== KV Cache 비활성화 ===")
start = time.time()
with torch.no_grad():
    output_no_cache = model.generate(
        **inputs,
        max_new_tokens=100,
        use_cache=False,  # KV Cache 비활성화
        do_sample=False
    )
time_no_cache = time.time() - start
print(f"생성 시간: {time_no_cache:.2f}초")

# KV Cache 활성화 (기본값 - 빠름)
print("\n=== KV Cache 활성화 ===")
start = time.time()
with torch.no_grad():
    output_with_cache = model.generate(
        **inputs,
        max_new_tokens=100,
        use_cache=True,   # KV Cache 활성화 (기본값)
        do_sample=False
    )
time_with_cache = time.time() - start
print(f"생성 시간: {time_with_cache:.2f}초")

speedup = time_no_cache / time_with_cache
print(f"\n속도 향상: {speedup:.1f}배")

# KV Cache 직접 관리 (streaming 용도)
past_key_values = None
generated_tokens = []

for _ in range(50):
    with torch.no_grad():
        outputs = model(
            **inputs if past_key_values is None else {"input_ids": next_token_id},
            past_key_values=past_key_values,
            use_cache=True
        )
    past_key_values = outputs.past_key_values  # KV Cache 저장
    next_token_id = outputs.logits[:, -1, :].argmax(dim=-1, keepdim=True)
    generated_tokens.append(next_token_id.item())

    # KV Cache 크기 확인 (첫 레이어 기준)
    if len(generated_tokens) == 1:
        kv_shape = past_key_values[0][0].shape
        print(f"KV Cache shape: {kv_shape} (layer, batch, heads, seq, dim)")

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"현재 LLM 서비스가 GPU 메모리 부족으로 동시 요청 처리가 제한됩니다. vLLM의 PagedAttention으로 전환하면 KV Cache 메모리 효율이 70%에서 96%로 개선되어 같은 GPU로 3배 더 많은 요청을 처리할 수 있습니다."
💬 면접에서
"KV Cache는 이전 토큰들의 Key, Value를 저장해서 재사용하는 겁니다. 없으면 N번째 토큰 생성 시 1~N-1번 토큰을 모두 재계산해야 해서 O(N^2)이 되는데, KV Cache로 O(N)으로 줄일 수 있어요. 단점은 메모리 사용량 증가입니다."
💬 기술 토론에서
"128K 컨텍스트에서 KV Cache가 수십 GB까지 커집니다. LMCache처럼 CPU RAM이나 SSD로 오프로드하거나, Entropy-Guided Caching으로 중요도 낮은 레이어의 캐시를 줄이는 방법이 있어요."

⚠️ 흔한 실수 & 주의사항

긴 컨텍스트에서 메모리 부족 무시

32K 토큰 이상에서 KV Cache만 10GB 이상 차지할 수 있습니다. max_length 제한이나 캐시 압축을 적용하세요.

배치 처리 시 패딩 토큰 캐싱

패딩 토큰까지 KV Cache에 저장하면 메모리 낭비입니다. attention_mask를 올바르게 설정하세요.

vLLM이나 TensorRT-LLM 활용

프로덕션 환경에서는 PagedAttention이 구현된 추론 엔진을 사용해 메모리 효율과 throughput을 극대화하세요.

🔗 관련 용어

📚 더 배우기