ai papers
KV Cache는 왜 LLM 추론의 병목이 되었을까? 구조부터 TurboQuant까지
Junyoung Park · 2026-08-10 · 20 min
LLM은 긴 글을 읽고 답할 수 있게 되었지만, Context window가 길어질수록 추론에 필요한 Memory도 함께 커진다. Model weight는 그대로인데 동시 요청 수를 늘리거나 긴 문서를 넣으면 갑자기 GPU Memory가 부족해지는 이유 중 하나가 KV Cache다.
KV Cache는 원래 추론을 빠르게 만들기 위해 도입한 장치다. 이미 읽은 Token의 일부 계산 결과를 저장해두고 다음 Token을 만들 때 재사용한다. 그런데 저장해야 할 Token이 수만 개로 늘어나자, 이제는 이 Cache를 보관하고 읽는 비용이 새로운 병목이 되었다.
흥미로운 점은 KV Cache 효율화라는 이름 아래 서로 꽤 다른 연구가 모여 있다는 것이다.
- GPU Memory의 빈 공간을 줄이는 연구
- 애초에 적은 수의 KV head만 만드는 Model 구조
- 중요한 Token만 남기는 연구
- KV를 2~4 bit로 Quantization하는 연구
- KV를 GPU, CPU, SSD와 여러 Server 사이에 배치하는 Serving 연구
이 글에서는 먼저 KV Cache가 무엇인지부터 살펴보고, 대표 연구가 어떤 문제를 어떤 방식으로 줄였는지 순서대로 정리해본다. 마지막에는 높은 압축률이 항상 빠르고 좋은 Serving으로 이어지는지까지 살펴본다.
LLM은 답변을 한 Token씩 만든다
일반적인 Decoder-only LLM은 Autoregressive model이다. 다음 문장을 생성한다고 해보자.
KV Cache는 LLM의 추론을 빠르게 만든다.
Model은 이 문장을 통째로 만들지 않는다. 먼저 KV를 만들고, 지금까지의 Token을 보고 Cache를 고른다. 다시 KV Cache를 보고 다음 Token을 고르는 과정을 반복한다.
길이 인 출력의 확률은 다음과 같이 분해된다.
가 정해져야 을 계산할 수 있기 때문에 이 과정은 본질적으로 순차적이다.
각 Transformer Layer의 Self-Attention에서는 입력으로부터 Query, Key, Value를 만든다.
Query는 현재 위치에서 필요한 정보를 나타내고, Key는 각 과거 위치가 어떤 정보를 가진지 표시하는 색인에 가깝다. Query와 Key의 내적이 크면 해당 위치의 Value를 더 많이 가져온다.
새 Token 하나를 만들 때는 새 위치의 Query가 지금까지 나온 모든 Key를 조회하고, 그 점수로 Value를 가중합한다.
과거 Token의 Key와 Value는 다음 생성 단계에서도 바뀌지 않는다. Causal Attention에서는 앞쪽 Token이 뒤에 새로 생긴 Token을 볼 수 없기 때문이다.
그렇다면 이미 계산한 Key와 Value를 다음 단계에서 다시 계산할 이유가 없다.
KV Cache는 무엇을 저장할까?
KV Cache는 각 Transformer Layer에서 과거 Token의 Key와 Value를 저장한다.
예를 들어 Prompt가 1,000 Token이고 새 Token 하나를 생성했다고 하자. 다음 Token을 만들 때 필요한 것은 다음과 같다.
- 과거 1,001개 Token의 Key와 Value: Cache에서 읽는다.
- 가장 최근 Token의 새 Query, Key, Value: 이번 Forward에서 계산한다.
- 새 Key와 Value: Cache 끝에 추가한다.
Cache가 없다면 생성 단계마다 길어진 전체 Prefix를 다시 Model에 통과시켜야 한다. KV Cache를 사용하면 과거 Token의 K/V projection과 그 이전 Layer 계산을 되풀이하지 않아도 된다.
이 덕분에 KV Cache는 오늘날 LLM Serving에서 사실상 기본 기능이 되었다. 다만 중복 계산을 없앴다고 해서 생성 비용 전체가 사라지는 것은 아니다. 새 Query는 여전히 모든 과거 Key와 점수를 계산하고 Value를 읽어야 한다. Context가 길어지면 Token 하나를 생성할 때 읽는 Cache의 양도 늘어난다.
Prefill과 Decode를 구분해야 한다
LLM 추론은 보통 두 단계로 나눈다.
Prefill
사용자가 입력한 Prompt 전체를 한 번에 처리하고 첫 KV Cache를 만드는 단계다. Prompt 안의 여러 Token을 병렬적인 Matrix multiplication으로 계산할 수 있어 GPU 연산 자원을 비교적 잘 사용한다. 긴 Prompt에서는 Attention 계산량도 크기 때문에 Compute와 Memory 양쪽의 영향을 받는다.
Decode
이후 출력 Token을 하나씩 생성하는 단계다. 한 요청에서 새로 계산할 Token 축이 1에 가까워 Matrix multiplication이 작고, 각 Layer에서 Model weight와 지금까지의 KV Cache를 계속 읽는다. 그래서 Batch가 작거나 Context가 길 때는 연산 장치보다 Memory bandwidth에 묶이는 경우가 많다.
KV Cache 압축 논문을 읽을 때 Prefill 시간, 첫 Token까지 걸리는 TTFT(Time To First Token), Token 사이의 지연인 ITL(Inter-Token Latency), 전체 처리량 중 무엇을 측정했는지 확인해야 하는 이유다.
KV Cache가 얼마나 커지길래 문제일까?
일반적인 KV Cache의 대략적인 크기는 다음과 같이 계산할 수 있다.
각 기호의 의미는 다음과 같다.
- : Key와 Value 두 Tensor
- : Batch size, 즉 동시에 처리하는 요청 수
- : Transformer Layer 수
- : 현재까지의 Token 수
- : KV head 수
- : Head 하나의 차원
- : 원소 하나의 Byte 수. FP16/BF16이면 2 Byte다.
예를 들어 Llama 3.1 8B는 32개 Layer, 8개 KV head, 128의 head dimension을 사용한다. FP16으로 32K Token을 저장하면 요청 하나의 KV Cache만 약 4 GiB가 된다.
동시에 4개 요청을 처리하면 약 16 GiB다. 여기에 Model weight, Activation, Temporary buffer, Memory allocator의 관리 비용도 별도로 필요하다. Context를 128K로 늘리거나 Batch를 키우면 KV Cache는 그대로 선형 증가한다.
이 문제는 두 가지 형태로 나타난다.
- Memory capacity: 한 GPU에 올릴 수 있는 요청 수가 줄어든다.
- Memory bandwidth: 다음 Token을 만들 때 과거 KV를 읽는 시간이 길어진다.
첫 번째 문제를 해결하면 더 큰 Batch를 넣을 수 있고, GPU가 한 번 읽은 Model weight로 여러 요청을 함께 처리할 수 있다. 그래서 KV Cache를 줄인 논문에서 처리량이 크게 오르는 이유는 압축 연산 자체가 빠르기 때문이라기보다, 같은 GPU에 더 많은 요청을 넣을 수 있기 때문인 경우가 많다.
한 가지 더 구분할 것이 있다. FlashAttention은 Attention score Matrix를 HBM에 전부 기록하지 않도록 연산 순서를 바꾸는 방법이다. Attention의 중간 Tensor I/O를 줄이지만, 생성 중 계속 남아 있어야 하는 과거 Key와 Value 자체를 없애는 방법은 아니다. FlashAttention과 KV Cache 압축은 같은 Memory 문제를 다루더라도 줄이는 대상이 다르며 함께 사용할 수 있다.
KV Cache를 효율화하는 다섯 가지 방향
KV Cache Tensor를 으로 보면 연구 지형이 조금 선명해진다.
어떤 연구는 Tensor의 값을 전혀 바꾸지 않고 저장 공간만 효율적으로 관리한다. 어떤 연구는 Token 축을 줄이고, 다른 연구는 head 수나 Dimension, Bit 수를 줄인다. 실제 Serving system은 이 중 하나만 고르기보다 여러 방법을 조합한다.
1. Cache 값은 그대로 두고 Memory를 잘 관리하기
PagedAttention: 연속 Memory를 미리 예약하지 않는다
요청의 최종 출력 길이는 생성이 끝나기 전까지 알 수 없다. 단순한 구현은 요청마다 충분히 큰 연속 Memory를 미리 잡거나, 공간이 부족할 때 더 큰 영역으로 Cache를 복사한다. 이 과정에서 사용하지 않는 빈 공간과 재할당 비용이 생긴다.
PagedAttention은 운영체제의 Virtual memory와 비슷한 발상을 사용한다. KV Cache를 고정 크기의 Block으로 나누고, 논리적인 Token 순서와 실제 GPU Memory의 위치를 Block table로 연결한다. 요청이 길어질 때 필요한 Block만 추가할 수 있고, 동일한 Prefix를 여러 Sequence가 참조하는 것도 쉬워진다.
PagedAttention은 Key와 Value를 근사하거나 버리지 않는다. 따라서 Model 품질을 바꾸지 않으면서 Memory fragmentation을 줄이는 System-level 최적화다. vLLM이 높은 처리량을 얻는 핵심 구성요소로 알려진 이유도 여기에 있다.
Prefix Cache: 같은 Prompt는 한 번만 계산한다
System prompt, Few-shot example, 긴 문서처럼 동일한 Prefix가 여러 요청에서 반복되기도 한다. 이때 요청마다 Prefill을 다시 하고 동일한 KV를 여러 벌 저장할 필요는 없다.
Prompt Cache는 재사용 가능한 Prompt module의 KV를 미리 계산해 공유하는 방법을 제안했다. PagedAttention 기반 Serving에서도 Automatic prefix caching을 이용해 동일한 Token prefix에 해당하는 Block을 재사용할 수 있다.
다만 문자열이 비슷하다고 KV를 바로 공유할 수 있는 것은 아니다. Token sequence, Model, Position 처리와 다른 실행 조건이 Cache 생성 때와 일치해야 한다. Prefix Cache는 요청 안에서 과거를 재사용하는 일반 KV Cache와 달리, 요청 사이에서 동일한 앞부분을 재사용하는 기능이다.
Mooncake: KV Cache를 하나의 분산 저장소로 본다
Model이 커지고 Serving cluster가 여러 Node로 나뉘면 KV Cache는 한 GPU 안의 Tensor만이 아니다. Prefill worker가 만든 Cache를 Decode worker로 옮기고, GPU Memory에서 밀려난 Cache를 CPU DRAM이나 SSD에 보관하며, 다시 필요한 곳으로 가져와야 한다.
Mooncake는 Prefill과 Decode를 분리한 Serving에서 CPU, DRAM, SSD, RDMA network까지 묶어 KV Cache 중심의 분산 저장 구조를 설계했다. 이 계열의 연구에서 중요한 것은 압축률보다 Cache의 생성 위치, 이동 경로, 재사용률, Network 대역폭이다.
2. Model이 만드는 KV 자체를 줄이기
Serving 단계에서 Cache를 압축하는 대신 Model 구조를 설계할 때부터 KV를 적게 만들 수도 있다.
MQA와 GQA: Query head끼리 KV를 공유한다
기본 Multi-Head Attention(MHA)은 Query head마다 별도의 Key와 Value head를 둔다. Query head가 32개라면 KV head도 32개다.
Multi-Query Attention(MQA)은 모든 Query head가 하나의 Key head와 Value head를 공유한다. KV Cache와 이를 읽는 Memory bandwidth를 크게 줄일 수 있지만, 지나친 공유가 품질에 영향을 줄 수 있다.
Grouped-Query Attention(GQA)은 그 중간 지점이다. Query head를 몇 개의 Group으로 묶고 Group마다 KV head 하나를 사용한다. 32개 Query head와 8개 KV head를 사용하면 Cache는 같은 head dimension의 MHA보다 대략 1/4이 된다. 최근 LLM에서 GQA가 자주 사용되는 이유는 품질과 Serving 비용의 절충이 좋기 때문이다.
MLA: KV를 낮은 차원의 Latent로 저장한다
DeepSeek-V2가 도입한 Multi-head Latent Attention(MLA)은 Key와 Value의 정보를 낮은 차원의 Latent vector로 압축해 Cache한다. Decode 시 필요한 연산을 Weight에 흡수하거나 Latent에서 복원해 Attention을 수행한다.
MLA를 단순히 “KV를 한 Vector로 만든다”고 이해하면 RoPE 처리와 분리된 Key 성분을 놓치기 쉽다. 실제 설계에서는 압축된 KV latent와 Position 정보를 담당하는 Key 성분을 함께 저장한다. 그래도 핵심은 head별로 큰 K/V Tensor를 그대로 보관하는 대신 훨씬 작은 잠재 표현을 Cache한다는 것이다.
Layer 축도 줄일 수 있다
KV Cache는 Layer 수에도 비례한다. YOCO는 Decoder를 Self-Decoder와 Cross-Decoder로 나누고 KV를 한 번만 Cache하는 방향을 제시했다. MiniCache는 인접한 Layer의 KV가 유사하다는 관찰을 이용해 중간 Layer Cache를 합친다.
이처럼 Model architecture를 바꾸는 방법은 큰 절감 효과를 얻을 수 있지만, 이미 배포한 Model의 Cache만 바꾸는 Post-training 방법보다 적용 범위가 좁다. 새로 학습하거나 Fine-tuning해야 하는지, 기존 inference engine이 해당 Attention을 지원하는지도 확인해야 한다.
3. 중요한 Token의 KV만 남기기
긴 Context의 모든 Token이 매 생성 단계에서 똑같이 중요하지는 않다. 최근 대화, 질문과 직접 관련된 문장, 계속 높은 Attention을 받은 일부 위치가 더 중요한 경우가 많다. 이를 이용하면 Token 축을 줄일 수 있다.
Scissorhands와 H2O: 중요도는 어느 정도 지속된다
Scissorhands는 과거에 중요했던 Token이 이후에도 중요한 경향이 있다는 Persistence of Importance를 관찰했다. 이를 바탕으로 덜 중요한 KV를 제거한다.
H2O는 누적 Attention score가 큰 Heavy hitter와 최근 Token을 함께 보존한다. 오래되었다는 이유만으로 모두 버리면 초반의 지시문이나 핵심 정보가 사라질 수 있고, Attention이 컸던 위치만 남기면 최근에 등장한 정보가 평가될 기회를 잃는다. 두 종류를 함께 남기는 이유다.
StreamingLLM: Attention sink와 최근 Window
단순 Sliding window는 최근 Token만 남긴다. 하지만 오래 생성하다 보면 성능이 불안정해질 수 있다. StreamingLLM은 문장 의미와 무관하게 초반 Token이 큰 Attention을 받는 Attention sink 현상을 분석하고, 처음 몇 개 Token과 최근 Window를 함께 보존한다.
이 방법은 끝없이 들어오는 Stream을 고정 크기 Cache로 처리하기 좋다. 반면 Context 중간에 있는 특정 사실을 나중에 정확히 회수해야 하는 작업에는 불리할 수 있다. “긴 입력을 받을 수 있다”와 “긴 입력의 모든 정보를 기억한다”는 같은 뜻이 아니다.
SnapKV: Prompt 끝부분의 Attention으로 앞을 고른다
SnapKV는 Prompt 마지막의 Observation window에서 나타난 Attention pattern으로 앞쪽 Prompt의 중요한 위치를 고른다. 선택한 위치 주변을 Pooling해 국소적인 정보도 남긴다.
질문이나 Instruction이 Prompt 뒤쪽에 놓이는 일반적인 형태에서는 Observation window가 “이 요청이 앞의 어느 부분을 필요로 하는가”를 알려줄 수 있다. 하지만 생성 도중 관심 대상이 바뀌거나 여러 단계의 추론에서 나중에야 필요한 정보가 드러나면 초기에 제거한 KV를 되돌릴 수 없다는 한계가 있다.
Quest: 버리는 대신 이번 Query에서 읽을 Page를 고른다
Quest는 Token별 KV를 영구 삭제하는 방법과 조금 다르다. KV Cache를 Page 단위로 저장하고, 현재 Query와 관련 있을 가능성이 큰 Page만 선택해 Attention kernel이 읽는다.
Quest가 직접 줄이는 것은 주로 Attention 단계의 Memory I/O다. 전체 Cache storage는 남아 있기 때문에 다음 Query에서는 다른 Page를 고를 수 있다. Cache capacity를 줄이는 eviction과 bandwidth를 줄이는 sparse retrieval을 구분해야 한다.
최근 연구는 Token 하나보다 의미 단위를 본다
Token별 Attention score는 Head와 Layer마다 흔들리고, 개별 Token을 제거하면 문장 구조가 조각날 수 있다. ChunkKV는 의미적으로 연결된 Chunk 단위로 KV를 보존한다. KVzip은 Cache를 재사용할 수 있도록 Query-independent한 중요도를 학습하고, R-KV는 여러 Query와 긴 생성 과정에서도 견고한 선택을 목표로 한다.
연구의 초점이 “지금 Attention이 낮은 Token을 지운다”에서 “미래의 여러 Query와 추론 단계에서도 다시 필요할 정보를 어떻게 보존할까”로 이동하고 있는 셈이다.
4. KV를 더 낮은 Bit와 차원으로 표현하기
Token을 버리면 해당 정보는 완전히 사라진다. 반면 Quantization은 모든 Token을 남기되 숫자의 정밀도를 낮춘다. FP16의 원소를 4 bit로 저장하면 이론적인 원소 저장량은 1/4, 2 bit라면 1/8이 된다.
하지만 KV Cache는 단순한 Activation Tensor가 아니다. Key의 작은 오차는 Query와의 내적과 Softmax를 거치며 어느 위치를 볼지 바꿀 수 있고, Value의 오차는 Attention output에 직접 섞인다. Cache가 생성 중 계속 누적된다는 점도 Weight quantization과 다르다.
KIVI: Key와 Value는 같은 방식으로 Quantization하면 안 된다
KIVI는 Key에 특정 Channel만 유난히 큰 Outlier가 반복되고, Value에는 같은 패턴이 뚜렷하지 않다는 점을 관찰했다.
Quantization scale을 Token마다 잡으면 Key의 Outlier Channel 때문에 각 Token의 나머지 값이 좁은 구간에 뭉개질 수 있다. KIVI는 이 비대칭성을 반영해 Key는 Channel별로, Value는 Token별로 2 bit Quantization한다. 최근 일부 Token은 FP16 residual window로 남겨 Quantize/dequantize overhead와 오차를 줄인다.
여기서 얻을 수 있는 일반적인 교훈은 “K와 V가 같은 Shape이므로 같은 Quantizer를 쓰면 된다”가 아니라, Attention 안에서 하는 역할과 통계적 분포를 따로 봐야 한다는 것이다.
KVQuant와 GEAR: Outlier와 잔차를 별도로 다룬다
KVQuant는 KV의 분포에 맞춘 Non-uniform datatype, Per-channel Key quantization, Outlier 분리와 Dense-and-sparse attention kernel을 함께 설계했다. 숫자를 낮은 Bit로 바꾸는 것만으로는 Dequantization 비용 때문에 실제 속도가 나오지 않을 수 있으므로 Kernel까지 함께 다룬 점이 중요하다.
GEAR는 Quantization 뒤에 남는 Error가 저차원 구조와 Sparse outlier로 설명될 수 있다는 관찰을 이용한다. 낮은 Bit의 기본 Tensor에 Low-rank와 Sparse 보정을 더해 오차를 줄인다. 저장 형식은 복잡해지지만, 모든 값을 높은 정밀도로 보관하는 것보다 작게 만들 수 있다.
Palu: KV head를 낮은 차원으로 투영한다
Palu는 Key와 Value projection weight를 Low-rank로 분해하고 낮은 차원의 latent representation을 Cache한다. 이미 학습된 MHA/GQA Model에 적용할 수 있다는 점에서 Model architecture로서의 MLA와 구분된다.
Quantization이 원소당 Bit 축을 줄인다면 Low-rank 방법은 Dimension 축을 줄인다. 둘을 함께 적용할 수도 있지만, 복원 연산과 Kernel 지원까지 포함한 실제 지연 시간을 확인해야 한다.
TurboQuant는 어디에 위치하는가?
Google Research의 TurboQuant는 단순한 KV Cache 전용 Quantizer보다 조금 넓은 문제에서 출발한다. 논문은 내적을 보존해야 하는 고차원 Vector의 Online quantization을 다루고, 대표 적용 대상으로 Attention의 KV Cache를 제시한다. 2025년 arXiv에 공개된 뒤 ICLR 2026에 발표되었다.
이 때문에 KV Cache 논문을 “Token eviction 방법” 위주로 분류하면 빠지기 쉽다. 하지만 긴 Context의 Cache를 극단적으로 낮은 Bit로 저장하면서 Attention 내적을 유지하려는 대표 연구이므로, KV Cache 효율화를 다루는 글에서는 포함하는 편이 자연스럽다.
TurboQuant의 흐름을 단순화하면 다음과 같다.
1. Random rotation으로 Outlier를 퍼뜨린다
Vector의 일부 좌표에 큰 값이 몰려 있으면 낮은 Bit의 좁은 표현 범위를 효율적으로 쓰기 어렵다. TurboQuant는 구조화된 Random rotation을 적용해 Vector의 Energy를 여러 좌표로 고르게 분산시킨다. 분포를 Gaussian에 가까운 형태로 만들면 하나의 Scalar quantizer를 쓰기 쉬워진다.
2. 주 신호와 Quantization residual을 나누어 저장한다
회전한 Vector의 주 신호는 MSE를 줄이도록 설계한 Scalar quantizer로 bit에 저장한다. 여기에 Quantization하고 남은 residual을 1-bit QJL(Quantized Johnson-Lindenstrauss) sketch로 보정한다. 논문이 강조하는 것은 단순한 값의 복원 오차뿐 아니라 Query와 Key 사이 내적 추정의 Bias와 분산이다.
3. 2.5 bit와 3.5 bit는 평균적인 Channel당 저장량이다
논문의 2.5 bits/channel이나 3.5 bits/channel은 일반적인 정수 dtype 이름이 아니다. 여러 구성요소와 Group metadata를 합친 평균 저장 비용이다. 논문은 3.5 bits/channel 설정을 Quality-neutral 영역으로, 2.5 bits/channel을 작은 품질 저하가 있는 더 공격적인 설정으로 보고한다.
출처: TurboQuant, Table 1. Llama 3.1 8B에서 3.5-bit 설정의 평균 점수는 논문의 16-bit Full Cache와 같았고, 2.5-bit 설정은 소폭 낮았다.
표에서 Llama 3.1 8B의 Full Cache 평균은 50.06, TurboQuant 3.5-bit도 50.06, 2.5-bit는 49.44다. 이 결과는 “3.5 bit면 모든 Model과 작업에서 FP16과 항상 같다”는 보장이 아니다. 해당 Model, Dataset, 생성 설정에서 얻은 결과이며 LongBench의 세부 Task별 차이도 남아 있다.
TurboQuant가 중요한 이유는 가장 작은 숫자를 제시해서만은 아니다. Rotation으로 Quantization하기 쉬운 분포를 만들고, 주 신호와 residual에 서로 다른 표현을 쓰며, Attention이 실제로 필요한 내적의 통계적 성질까지 설계한다. KV Cache Quantization이 단순한 dtype 변환에서 Attention-aware numerical method로 발전한 사례에 가깝다.
OjaKV: 생성하면서 중요한 Subspace를 Online으로 추적한다
2026년의 OjaKV는 KV Cache가 낮은 차원의 Subspace에 놓인다는 점을 이용하되, 고정된 Low-rank basis를 미리 구하지 않는다. Token이 들어올 때 Oja's algorithm으로 주요 Subspace를 Online 업데이트하고, 현재 Basis로 KV를 투영해 저장한다.
Calibration data에 강하게 의존하지 않고 생성 중 분포 변화에 적응하려는 방향이다. 최근 연구가 “몇 bit로 줄이는가”뿐 아니라 “시간에 따라 바뀌는 Cache의 구조를 Online으로 어떻게 추적할까”까지 확장되고 있음을 보여준다.
압축률만 높으면 실제 Serving도 빨라질까?
여기까지 보면 KV Cache가 작을수록 모든 문제가 해결될 것처럼 보인다. 하지만 연구 결과를 해석할 때는 적어도 세 가지를 따로 봐야 한다.
- Cache가 실제로 몇 Byte가 되었는가?
- GPU가 압축된 Cache를 얼마나 빠르게 읽고 Attention을 계산하는가?
- 같은 Prompt에서 답변의 품질과 길이가 어떻게 바뀌었는가?
더 큰 Batch를 넣을 수 있을 때 처리량이 오른다
KIVI 논문의 다음 실험은 메모리 절감과 처리량의 관계를 잘 보여준다.
작은 Batch만 비교하면 Quantize/dequantize overhead 때문에 이득이 작을 수 있다. 반대로 Cache가 줄어 더 큰 Batch를 넣을 수 있으면 Weight를 읽는 비용을 여러 요청에 나누면서 전체 처리량이 올라간다. 따라서 “요청 하나가 몇 배 빨라졌는가”와 “GPU 한 장이 초당 몇 Token을 처리하는가”는 같은 질문이 아니다.
출력 길이가 바뀌면 Throughput 비교가 흔들린다
Rethinking KV Cache Compression Techniques for LLMs는 압축 방법을 실제 Serving stack에서 다시 평가하면서 중요한 문제를 지적했다. Lossy compression이 Model의 생성 분포를 바꾸면 같은 Prompt에도 답변 길이가 달라질 수 있다는 점이다.
출처: Rethinking KV Cache Compression Techniques for LLMs, Figure 4. KIVI, GEAR, H2O, StreamingLLM 모두 설정에 따라 Full Cache와 다른 길이의 응답을 만들었다.
예를 들어 압축 Model이 답변을 더 짧게 끝내면 요청당 지연 시간이 줄어든 것처럼 보일 수 있다. 반대로 반복이 생겨 출력이 길어지면 같은 Token 처리 성능이어도 End-to-end latency가 나빠진다. 고정 출력 길이에서 측정한 Token throughput, 실제 종료 조건을 포함한 요청 지연, 답변 품질을 함께 보고해야 한다.
논문에서 작성한 작은 PyTorch loop의 속도와 PagedAttention, Continuous batching, 최적화된 Kernel이 들어간 Serving engine의 속도는 다를 수 있다. 압축 기법이 이론상 읽는 Byte를 줄였는지뿐 아니라 실제 Kernel이 그 형식을 직접 처리하는지 확인해야 한다.
평균 Benchmark 점수는 어떤 능력이 먼저 무너지는지 숨긴다
2026년 ACL의 Pitfalls of KV Cache Compression for Instruction-Following in LLMs는 Instruction-following 관점에서 Token eviction 방법을 분석했다.
출처: Pitfalls of KV Cache Compression for Instruction-Following in LLMs, Figure 2. 압축에 따른 저하는 Instruction 종류와 단일·다중 지시 여부에 따라 균일하지 않았다.
형식 지키기, 길이 제한, 언어, 구두점처럼 Instruction 종류마다 성능이 무너지는 시점이 달랐다. 평균 점수 하나만 보면 특정 지시 능력이 먼저 사라지는 현상을 놓칠 수 있다.
더 주의할 부분은 System prompt다.
출처: Pitfalls of KV Cache Compression for Instruction-Following in LLMs, Figure 4. 일부 Model과 eviction 정책에서 압축률이 높아질수록 지시 준수는 낮아지고 System prompt와의 유사도는 증가했다.
논문은 공격적인 압축에서 System prompt의 지시를 따르는 능력이 낮아지거나, 답변에 System prompt 내용이 더 드러나는 현상을 보고했다. 모든 압축이 곧바로 정보 유출을 일으킨다는 뜻은 아니다. Model과 eviction 정책별 차이가 크다는 것이 오히려 핵심이다. Production에서 KV Cache 정책을 바꿀 때 일반 Long-context QA뿐 아니라 Instruction hierarchy, Prompt leakage, Tool-use 같은 실제 동작을 따로 평가해야 한다.
대표 논문의 흐름
아래 목록은 모든 KV Cache 논문을 모은 것이 아니라, 연구 방향이 바뀐 지점을 이해하기 좋은 논문을 골라 정리한 것이다.
| 시기 | 논문 | 핵심 질문 | 줄이는 대상 |
|---|---|---|---|
| 2019 | Multi-Query Attention | 여러 Query head가 KV를 공유할 수 있을까? | KV head |
| 2023 | GQA | MHA 품질과 MQA 효율 사이의 절충은? | KV head |
| 2023 | PagedAttention | 가변 길이 Cache의 빈 공간과 복사를 줄일 수 있을까? | Memory 낭비 |
| 2023 | Scissorhands, H2O | 과거의 중요한 Token을 골라 남길 수 있을까? | Token |
| 2024 | StreamingLLM | 고정 Cache로 무한 Stream을 안정적으로 처리할 수 있을까? | Token |
| 2024 | KIVI, KVQuant | KV를 2~4 bit로 낮추면서 Attention을 유지할 수 있을까? | Bit |
| 2024 | SnapKV, Quest | 현재 요청과 관련된 위치·Page만 선택할 수 있을까? | Token 또는 읽기 I/O |
| 2024 | DeepSeek-V2의 MLA, Palu | KV를 낮은 차원의 Latent로 저장할 수 있을까? | Dimension |
| 2024~2025 | Prompt Cache, Mooncake | 여러 요청과 장치 사이에서 Cache를 재사용·이동할 수 있을까? | Prefill과 저장 비용 |
| 2025 | TurboQuant | 회전과 residual sketch로 극저비트 내적을 보존할 수 있을까? | Bit |
| 2025 | ChunkKV, KVzip, R-KV | 의미 단위와 여러 미래 Query까지 고려해 남길 수 있을까? | Token |
| 2025~2026 | Rethinking KV Cache Compression, Pitfalls | 기존 압축의 실제 Serving 이득과 숨은 품질 저하는 무엇인가? | 평가 방법 |
| 2026 | OjaKV | 생성 중 변하는 주요 Subspace를 Online으로 추적할 수 있을까? | Dimension |
KV Cache Compression, But What Must We Give in Return? 같은 Survey는 이 분야를 Token-level, Model-level, System-level 최적화로 정리한다. 개별 논문을 더 찾아볼 때 좋은 출발점이다.
상황에 따라 무엇을 먼저 선택할까?
하나의 최적 방법이 있는 것은 아니다. 병목과 허용 가능한 품질 변화에 따라 출발점이 달라진다.
| 상황 | 먼저 볼 방법 | 이유와 확인할 점 |
|---|---|---|
| 일반적인 Online serving | GQA Model + PagedAttention + Continuous batching | 품질을 근사하지 않고 기본 Memory 효율을 높일 수 있다. |
| 동일한 System prompt나 문서를 반복 사용 | Prefix caching | Prefill 계산과 공통 KV 저장을 요청 사이에서 재사용한다. |
| 매우 긴 Stream, 최근 정보가 주로 중요 | StreamingLLM 계열 | Cache 크기를 고정할 수 있다. 중간의 오래된 정보 회수가 필요한지 확인해야 한다. |
| 긴 문서 QA, 질문이 Prompt 끝에 위치 | SnapKV·Chunk 기반 선택 | 질문 관련 위치를 보존하기 쉽다. Multi-turn과 긴 생성에서도 평가해야 한다. |
| Context 전체는 보관하되 GPU 읽기만 줄이고 싶음 | Quest 같은 Query-aware sparse attention | Cache capacity보다 Decode bandwidth가 병목일 때 적합하다. |
| 기존 Model의 모든 Token을 남기고 싶음 | KIVI·KVQuant·TurboQuant·Palu | Bit 또는 Dimension을 줄인다. 전용 Kernel과 품질 평가가 필요하다. |
| Prefill/Decode가 여러 Node에 분리됨 | Mooncake 계열의 KV storage | Network, CPU/SSD offload, Cache hit rate가 핵심 지표가 된다. |
실무에서는 다음 순서로 확인하는 편이 안전하다.
- 먼저 PagedAttention, Prefix caching처럼 Lossless한 System 최적화를 적용한다.
- Model 선택이 가능하면 MHA보다 GQA/MLA처럼 Cache 친화적인 구조를 검토한다.
- Memory capacity와 bandwidth 중 실제 병목을 Profiling한다.
- 그 뒤 Quantization, Token eviction, Sparse retrieval 중 병목에 맞는 방법을 고른다.
- LongBench 평균뿐 아니라 실제 Prompt 분포, 출력 길이, Instruction-following, Safety 동작을 함께 평가한다.
마치며
KV Cache는 LLM이 과거를 매번 다시 계산하지 않도록 만든 장치다. 짧은 대화에서는 확실한 이득이지만, Context와 Batch가 커지면 Layer마다 쌓인 Key와 Value를 저장하고 읽는 비용이 Model weight만큼 중요한 문제가 된다.
이 분야의 연구는 결국 다음 질문 중 하나에 답한다.
무엇을 반드시 기억해야 하고, 어떤 정밀도로 기억하며, 그 기억을 어디에 두고 언제 읽을 것인가?
PagedAttention은 저장 공간의 낭비를 줄이고, GQA와 MLA는 처음부터 Cache를 작게 만든다. H2O와 SnapKV는 중요한 Token을 고르고, Quest는 현재 필요한 Page만 읽는다. KIVI와 TurboQuant는 모든 Token을 더 적은 Bit로 남기려 한다. Mooncake는 이 Cache를 여러 장치와 Node에 걸친 저장 시스템으로 확장한다.
최근의 재평가 연구가 보여준 것처럼 높은 압축률만으로는 충분하지 않다. 답변이 짧아져서 빨라 보이지는 않았는지, 특정 지시 능력이 먼저 무너지지는 않았는지, 압축 형식을 실제 GPU Kernel이 효율적으로 처리하는지까지 확인해야 한다.
KV Cache 효율화의 목표는 Cache를 가장 작게 만드는 것이 아니다. Model이 다음 Token을 결정하는 데 필요한 과거를, 품질과 지연 시간의 경계 안에서 가장 싸게 유지하는 것에 가깝다.