
3비트 KV 캐시(KV Cache)는 무손실인가, TurboQuant 읽기
요약
TurboQuant 논문을 통해 KV 캐시 양자화 기술의 핵심 원리를 분석합니다. 무작위 직교 회전을 통해 이상치를 분산시킨 후 최적의 스칼라 양자화를 적용하여 정밀도 손실을 최소화하는 방법을 다룹니다.
핵심 포인트
- KV 캐시 압축은 긴 문맥 처리를 위한 GPU 메모리 확보의 핵심임
- TurboQuant는 무작위 직교 회전으로 데이터의 이상치를 고르게 분산시킴
- 회전된 분포에 최적화된 Lloyd-Max 양자화기를 적용해 정밀도 유지
- 추가 학습이나 캘리브레이션 없이 다양한 모델에 즉시 적용 가능
「3비트 KV 캐시(KV Cache)에서도 정밀도는 떨어지지 않는다」라는 헤드라인을 지난 몇 주 동안 몇 번인가 보았다. llama.cpp의 포크(fork) 버전으로 실제로 구동 가능해지면서 업계가 술렁였던 TurboQuant 이야기다. 하지만 원문 논문을 열어보니, 무손실을 주장하는 것은 3비트가 아니라 3.5비트였고, 실무자들이 권장하는 것은 4비트였다. 이 0.5~1비트의 차이는 사소한 문제처럼 보이지만, 사실 「왜 회전시키면 압축할 수 있는가」라는 이 기법의 핵심과 바로 연결되어 있다. 차근차근 읽어 나가보자.
LLM이 긴 문맥(context)을 다룰 때, 생성 속도와 사용할 수 있는 메모리를 결정하는 것은 가중치(weight)의 크기가 아니라 KV 캐시(KV Cache) 쪽이다. Transformer는 과거의 토큰마다 각 층의 Key/Value 벡터를 저장해 두었다가, 새로운 토큰을 내보낼 때마다 그것을 다시 읽는다. 이 메커니즘 덕분에 1토큰당 계산을 매번 새로 할 필요가 없지만, 대가로 캐시는 토큰 수·층 수·배치 사이즈(batch size)에 비례하여 선형적으로 팽창한다.
숫자로 보면 그 무게가 전달된다. llama.cpp의 구현 검증에 따르면, Qwen3.5-27B급 모델에서 FP16의 KV 캐시는 1토큰당 약 64KB이다. 32K 토큰만 채워도 그것만으로 수 GB가 VRAM에서 사라진다. 70B 클래스가 되면, FP16의 KV로 34GB GPU에 실을 수 있는 문맥 길이는 약 109K 토큰이 상한선이 된다. 긴 문맥을 원할수록 GPU가 부족해지는 구도다.
따라서 「KV 캐시를 몇 비트까지 줄일 수 있는가」는 곧 「동일한 GPU에서 몇 배 더 긴 문맥을 다룰 수 있는가」의 문제가 된다. 이 문제를 파고든 것이 Google Research와 NYU의 Amir Zandieh 등이 작성한 논문 TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate(arXiv:2504.19874, ICLR 2026)이다.
양자화(Quantization) 그 자체는 새로운 것이 아니다. FP16의 각 수치를 적은 비트 수의 대표값으로 반올림해 버리면 메모리는 줄어든다. 문제는 단순하게 실행하면 정밀도가 무너진다는 점이다. KV 벡터 안에는 값이 돌출된 차원(outlier)이 섞여 있는데, 그곳에 반올림의 입도를 맞추면 다른 대다수의 차원이 뭉개지고, 반대로 다수파에 맞추면 이상치(outlier) 정보가 날아간다. 단일 스케일로 모든 차원을 양자화하는 방식이 오랫동안 고전해 온 이유가 바로 이 편향이었다.
TurboQuant의 첫 번째 수단은 양자화하기 전에 벡터에 무작위 직교 회전(random orthogonal rotation)을 가하는 것이다. 회전 행렬은 데이터로부터 학습하는 것이 아니라 수학적으로 생성한다. 이것이 효과가 있는 이유는 다음과 같다. 무작위 직교 변환을 거치면, 회전 후의 각 좌표 값은 한 점에 집중된 베타 분포(Beta distribution, 차원 수가 충분히 크다면 거의 평균 0·분산 1/d인 정규 분포로 근사할 수 있음)를 따르게 된다. 에너지가 특정 좌표에 치우치지 않고 전체로 고르게 분산되므로, 돌출된 이상치 차원이 사라진다.
그렇게 정돈한 뒤에, 각 좌표에 대해 독립적으로 최적의 스칼라 양자화기(Scalar Quantizer, Lloyd-Max 양자화기: 특정 분포 하에서 반올림 오차의 제곱을 최소화하는 고전적인 최적화 방식)를 적용한다. 분포를 사전에 알고 있기 때문에 좌표마다 이론적인 최적해를 맞출 수 있다. 회전으로 분포를 기지의 형태로 밀어 넣고, 기지이기 때문에 최적으로 반올림한다, 라는 이단계 구조다.
회전 행렬이 학습이 필요 없다는 점은 실무적으로 매우 크다. 모델에 의존하지 않으므로 추가 학습(fine-tuning)이나 캘리브레이션(calibration) 데이터도 필요 없이, Gemma에서도 Llama에서도 Mistral에서도 동일한 메커니즘을 그대로 적용할 수 있다. 논문은 Gemma, Mistral, Llama-3.1-8B-Instruct를 대상으로 LongBench, RULER, ZeroSCROLLS와 같은 긴 문맥 벤치마크에서 검증을 수행했다.
여기서 간과하기 쉬운 점은, KV 캐시의 양자화는 「원래의 벡터를 얼마나 충실하게 복원할 수 있는가」만으로는 부족하다는 점이다. Attention이 실제로 수행하는 것은 Query와 Key의 내적(dot product) 계산이지, 벡터 그 자체의 재구성이 아니다. 복원 오차를 작게 하더라도 내적에 편향(bias)이 실리면 스코어가 계통적으로 어긋나게 된다.
TurboQuant가 2단계 양자화 (Two-stage Quantization)를 표방하는 지점이 바로 이 부분이다. 먼저 제곱 오차 (Squared Error)를 최소화하는 양자화기로 본체를 반올림(rounding)하고, 그 잔차 (residual)에 대해 1비트의 양자화된 JL 변환 (Quantized JL Transform; Johnson-Lindenstrauss, 내적을 대체로 유지하면서 차원을 압축하는 랜덤 투영의 일종)을 적용한다. 이를 통해 내적 추정치의 편향 (bias)이 제거되어 불편 (unbiased)한 내적을 얻을 수 있다. 복원의 충실도와 내적의 정확도는 별개의 문제이며, 후자를 보장하기 위해 잔차에 한 번 더 공을 들이는 것이다. 이러한 설계 사상은 KV 캐시 압축을 '벡터 복원'이 아닌 'Attention 스코어의 보존'으로 재정의했다는 점에서 새로움이 있다.
서두의 괴리로 돌아가 보자. 논문에서 실제로 보고하고 있는 정밀도는 다음과 같다.
| 설정 | 정밀도에 미치는 영향 (논문) |
|---|---|
| 3.5 bit / channel | 절대적인 품질 중립 (FP16과 차이 없음) |
| 2.5 bit / channel | 약간의 저하 |
즉, 논문이 '무손실'이라고 단언하는 것은 3.5비트이지, 정확히 3.0비트가 아니다. 왜곡률 (distortion rate)은 정보 이론적인 하한에 대해 작은 상수 배 (약 2.7배)까지 근접한다는 것이 이론 측의 주장이다.
반면 llama.cpp 측의 구현은 보다 실무적인 수치를 제시하고 있다.
| 방식 | 비트/값 | FP16 대비 압축률 | 품질 |
|---|---|---|---|
| TQ3 (turbo3) | 3.25 bit | 약 4.9배 | PPL이 약 1% 상승 |
| TQ4 (turbo4) | 4.25 bit | 약 3.8배 | 거의 FP16과 동등 |
실측에서는 35B 모델, 온도 0 설정 시 '출력이 FP16과 완전히 일치'하며, 퍼플렉시티 (Perplexity)는 6.20 대 6.19라는 오차 범위 수준의 차이로 수렴했다고 보고되었다. 다만 이는 비교적 큰 모델에서의 이야기이며, llama.cpp의 TurboQuant 토론 스레드나 구현자 및 DEV 커뮤니티의 해설에서도 3비트 대역은 8B 미만의 소형 모델에서 저하가 눈에 띄기 시작한다는 점에는 모두 의견이 일치한다. 권장 사항은 4비트다.
헤드라인의 '3비트 무손실'은 엄밀히 말하면 과장이며, 정확하게는 '큰 모델이라면 3비트 대역에서도 실용상 거의 무손실, 안전책은 4비트' 정도가 실태라고 할 수 있다. 이 부분은 타협하지 않고 짚고 넘어가고 싶다. 압축률과 품질은 트레이드오프 (trade-off) 관계이며, 모델 크기라는 제3의 변수가 그 경계선을 움직인다.
압축률만 본다면 매력은 명확하다. 동일한 70B 모델, 34GB VRAM 환경에서 KV 캐시를 FP16에서 TQ3로 전환하면, 다룰 수 있는 문맥 (context)이 약 109K 토큰에서 약 536K 토큰으로 늘어난다. 100만 토큰의 KV 캐시가 약 9.6GB에 들어온다는 수치도 나와 있다.
llama.cpp의 포크 (fork) 버전에서는 Key와 Value 양측의 캐시 타입을 지정하여 활성화한다.
# 3비트 (압축 중시)
--cache-type-k turbo3 --cache-type-v turbo3
# 4비트 (품질 중시의 안전책)
...
다만 현 시점에서 주의해야 할 점이 두 가지 있다. 하나는 이것이 아직 본래의 llama.cpp 메인 (main) 브랜치에 포함되지 않아, Metal/CUDA/Vulkan 각각의 전용 포크를 빌드해야 한다는 점이다. 또 하나, 놓치기 쉬운 점은 속도다. 양자화할 때마다 벡터를 회전시키는 처리 (구현에 따라 Walsh-Hadamard 변환으로 근사함)가 추가로 발생하기 때문에, 백엔드나 최적화 상황에 따라 생성 속도가 0.78배에서 수 배까지 느려진다는 보고가 있다. 메모리는 확실히 줄어들지만, 그 회전 비용을 어딘가에서 지불하고 있는 셈이다.
이 속도 페널티 (penalty)를 어떻게 해석하느냐가 사실 관건이다. 자기회귀 디코딩 (Autoregressive decoding)의 병목 (bottleneck)은 계산 그 자체보다 메모리 대역폭 (memory bandwidth), 즉 HBM에서 얼마나 많이 읽어오느냐에 있다. KV 캐시가 작아지면 읽어오는 양이 줄어들므로, 메모리 압박으로 GPU가 스왑 (swap) 직전까지 몰리는 상황에서는 오히려 처리량 (throughput)이 올라간다. 반대로 메모리에 여유가 있고 배치 (batch)도 가벼운 국면에서는 회전의 추가 계산이 순수한 걸림돌이 된다. TurboQuant가 효과를 발휘하는 지점은 '문맥을 늘리고 싶지만 VRAM이 부족한' 케이스이지, 남는 GPU를 더 빠르게 만드는 도구가 아니다. 효과가 나타나는 지점을 정확히 파악해야 하는 종류의 최적화다.
TurboQuant의 흥미로운 점은 압축률 수치보다 '데이터에 의존하지 않고, 분포를 수학적으로 기지(known)의 형태로 밀어 넣은 후 최적으로 반올림한다'는 설계에 있다. 학습(training)이나 캘리브레이션 (calibration)이 필요 없으므로 기존 추론 스택에 쉽게 덧붙일 수 있다. 내적의 불편성까지 설계하여 Attention을 망가뜨리지 않도록 배려한 점 또한, KV 캐시 양자화를 단순한 벡터 압축과 분리하여 바라봐야 할 대목이다.
실무자로서 얻을 수 있는 시사점은 세 가지로 압축할 수 있다. 긴 문맥(Long Context)을 VRAM 제약 때문에 포기하고 있었다면, 우선 TQ4부터 시도해 볼 가치가 있다. 3비트 대역의 무손실(Lossless)이라는 주장은 대형 모델을 전제로 하므로, 소형 모델에서는 효과가 다르게 나타날 수 있다. 그리고 메모리와 맞바꾸어 회전 계산(Rotation Computation) 비용을 지불해야 하므로, 자신의 병목(Bottleneck)이 메모리 대역폭(Memory Bandwidth)인지 연산(Compute)인지 먼저 확인해야 한다. 이 부분을 오판하면 수치상으로는 압축되었음에도 불구하고 체감 속도는 오히려 느려지는 함정에 빠질 수 있다.
아직 본류(Mainstream)에 편입된 기술은 아니지만, KV 캐시(KV Cache)를 줄이는 방향은 향후 긴 문맥 및 에이전트(Agent) 용도에서 피할 수 없는 흐름이다. 회전(Rotation)이라는 한 수로 분포를 정렬하는 발상은 다른 양자화(Quantization) 기술로도 파급될 것이다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기