Models & AlgorithmsEN

TurboQuant 실전 — llama.cpp와 HuggingFace에서 KV Cache 압축하기

llama.cpp turbo3 빌드, HuggingFace 통합, 메모리 계산기, 최적 설정 가이드. 70B 모델 536K 컨텍스트 실현.

TurboQuant 실전 — llama.cpp와 HuggingFace에서 KV Cache 압축하기

TurboQuant 실전 — llama.cpp와 HuggingFace에서 KV Cache 압축하기

TurboQuant는 KV Cache를 3~4비트로 압축하여 추론 시 메모리 사용량을 극적으로 줄이는 양자화 기법입니다. 이론은 알겠는데, 실제로 어떻게 쓰는 걸까요? 이 글에서는 llama.cpp와 HuggingFace 두 환경에서 TurboQuant를 직접 설정하고 사용하는 방법을 단계별로 다룹니다.

1. 준비

필요한 것들

  • llama.cpp TurboQuant fork (또는 공식 지원 빌드)
  • Python 3.10+ 및 HuggingFace transformers 라이브러리
  • GPU: 8GB+ VRAM이면 충분 (NVIDIA CUDA 또는 Apple Metal)
  • CPU에서도 동작하지만, 성능은 GPU 대비 느림

Python 환경 설정

bash
pip install transformers torch accelerate
pip install turboquant  # TurboQuant Python 패키지

GGUF 모델 준비

llama.cpp에서 사용하려면 GGUF 포맷의 모델이 필요합니다. HuggingFace Hub에서 직접 다운로드하거나, 기존 모델을 변환할 수 있습니다.

bash
# HuggingFace에서 GGUF 모델 다운로드 예시
huggingface-cli download TheBloke/Llama-3-70B-GGUF \
  llama-3-70b.Q4_K_M.gguf --local-dir ./models

2. llama.cpp에서 TurboQuant 사용하기

빌드

TurboQuant를 지원하는 llama.cpp fork를 클론하고 빌드합니다.

bash
git clone https://github.com/TheTom/llama-cpp-turboquant.git
cd llama-cpp-turboquant
git checkout feature/turboquant-kv-cache
cmake -B build -DGGML_METAL=ON  # macOS (Apple Silicon)
# 또는
# cmake -B build -DGGML_CUDA=ON  # NVIDIA GPU
cmake --build build --config Release -j

빌드가 완료되면 build/bin/ 디렉토리에 실행 파일들이 생성됩니다.

서버 실행

turbo3 KV cache 양자화를 적용하여 서버를 실행합니다.

bash
./build/bin/llama-server -m model.gguf \
  --cache-type-k turbo3 --cache-type-v turbo3 \
  -c 262144 -fa on --port 8080

각 옵션의 의미는 다음과 같습니다.

옵션설명
--cache-type-k turbo3Key 캐시에 TurboQuant 3비트 적용
--cache-type-v turbo3Value 캐시에 TurboQuant 3비트 적용
-c 262144컨텍스트 길이 262K 토큰
-fa onFlash Attention 활성화 (필수)
--port 8080API 서버 포트

서버가 실행되면 OpenAI 호환 API로 접근할 수 있습니다.

bash
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3-70b",
    "messages": [{"role": "user", "content": "Hello!"}],
    "max_tokens": 256
  }'

벤치마크 비교

FP16, Q8, TurboQuant 3비트를 비교하여 성능 차이를 확인합니다.

bash
./build/bin/llama-bench -m model.gguf \
  -fa 1 -ngl 99 \
  -ctk f16 -ctv f16 \
  -ctk q8_0 -ctv q8_0 \
  -ctk turbo3 -ctv turbo3

일반적인 결과 예시 (Llama 3 8B, RTX 4090 기준):

KV Cache 타입메모리 사용토큰/초 (pp512)토큰/초 (tg128)
f164.2 GB3,20095
q8_02.1 GB3,18093
turbo30.9 GB3,15091

메모리 사용량이 약 78% 줄어드는 반면, 생성 속도는 거의 동일합니다.

3. HuggingFace에서 TurboQuant 사용하기

HuggingFace transformers와 함께 TurboQuant를 사용하는 두 가지 방법을 소개합니다.

방법 1: Drop-in Cache 교체

기존 generate 코드에 cache 객체만 바꿔 끼우면 됩니다.

python
from transformers import AutoTokenizer, AutoModelForCausalLM
from turboquant import TurboQuantCache

# 모델 로드
model_name = "meta-llama/Llama-3-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto",
)

# TurboQuant 캐시 생성 (4비트)
cache = TurboQuantCache(bits=4)

# 텍스트 생성
inputs = tokenizer("대한민국의 수도는", return_tensors="pt").to(model.device)
outputs = model.generate(
    **inputs,
    past_key_values=cache,
    use_cache=True,
    max_new_tokens=512,
)

print(tokenizer.decode(outputs[0], skip_special_tokens=True))

TurboQuantCache(bits=4) 한 줄만 추가하면 KV Cache가 자동으로 4비트로 압축됩니다. 기존 코드를 거의 수정하지 않아도 되는 것이 장점입니다.

방법 2: 직접 양자화 API

벡터를 직접 양자화/역양자화하여 정밀도를 확인하고 싶을 때 사용합니다.

python
import torch
from turboquant import TurboQuantMSE

# TurboQuant 양자화기 생성
tq = TurboQuantMSE(dim=128, bits=4, device='cuda')

# 랜덤 벡터 생성 (실제로는 Key/Value 텐서)
vectors = torch.randn(1024, 128, device='cuda')

# 양자화
indices, norms = tq.quantize(vectors)

# 역양자화
vectors_hat = tq.dequantize(indices, norms)

# 오차 확인
mse = ((vectors - vectors_hat) ** 2).mean()
print(f"MSE: {mse:.6f}")
# 출력 예시: MSE: 0.002341

3비트와 4비트의 MSE 차이를 비교해보면 다음과 같습니다.

python
for bits in [2, 3, 4]:
    tq = TurboQuantMSE(dim=128, bits=bits, device='cuda')
    indices, norms = tq.quantize(vectors)
    vectors_hat = tq.dequantize(indices, norms)
    mse = ((vectors - vectors_hat) ** 2).mean()
    print(f"{bits}-bit MSE: {mse:.6f}")

# 출력 예시:
# 2-bit MSE: 0.018742
# 3-bit MSE: 0.005123
# 4-bit MSE: 0.002341

3비트에서도 MSE가 충분히 낮아 대부분의 사용 사례에서 품질 저하를 체감하기 어렵습니다.

4. 메모리 절약 계산기

KV Cache의 메모리 사용량은 다음 공식으로 계산할 수 있습니다.

KV Cache 크기 = 2 × num_layers × num_heads × head_dim × seq_len × bytes_per_element

여기서 2는 Key와 Value 두 개의 캐시를 의미합니다.

예시: Llama 3 70B, 128K 컨텍스트

모델 파라미터:
- num_layers: 80
- num_heads: 64
- head_dim: 128
- seq_len: 131,072 (128K)
양자화bytes_per_elementKV Cache 크기절약량
FP162.0~27.3 GB-
Q8_01.0~13.7 GB13.6 GB
TQ4 (4-bit)0.55~7.5 GB19.8 GB
TQ3 (3-bit)0.41~5.6 GB21.7 GB

TQ3을 사용하면 FP16 대비 21.7GB를 절약할 수 있습니다. 이 절약된 메모리로 더 큰 모델을 로드하거나, 더 긴 컨텍스트를 처리할 수 있습니다.

빠른 계산 스크립트

python
def calc_kv_cache_size(num_layers, num_heads, head_dim, seq_len, bytes_per_elem):
    """KV Cache 메모리 사용량 계산 (GB 단위)"""
    size_bytes = 2 * num_layers * num_heads * head_dim * seq_len * bytes_per_elem
    return size_bytes / (1024 ** 3)

# Llama 3 70B
configs = {
    "FP16":  2.0,
    "Q8_0":  1.0,
    "TQ4":   0.55,
    "TQ3":   0.41,
}

print("Llama 3 70B @ 128K context:")
for name, bpe in configs.items():
    size = calc_kv_cache_size(80, 64, 128, 131072, bpe)
    print(f"  {name:6s}: {size:6.1f} GB")

5. 최적 설정 가이드

모델 크기별 권장 설정

모델 크기권장 설정이유
3B 미만turbo4 (4-bit)작은 모델은 정밀도에 더 민감함
3B~13Bturbo3 (3-bit)대부분의 모델에서 최적의 균형점
13B 이상turbo3 (3-bit)메모리 절약 극대화, 품질 저하 무시 가능

작은 모델일수록 각 파라미터가 담당하는 정보의 비중이 크기 때문에, 양자화에 의한 오차가 상대적으로 더 큰 영향을 미칩니다. 13B 이상의 모델에서는 3비트 양자화를 적용해도 출력 품질에 거의 영향이 없습니다.

컨텍스트 길이별 영향

컨텍스트 길이절약 효과권장
1K 미만무시할 수준TurboQuant 불필요
1K~4K중간 (~500MB~1GB)선택적 사용
4K~32K상당함 (1~4GB 절약)적극 권장
32K 이상매우 큼TurboQuant가 진가를 발휘하는 구간

핵심: 컨텍스트가 길어질수록 TurboQuant의 가치가 커집니다. 128K 이상의 긴 컨텍스트를 사용한다면 TurboQuant는 선택이 아닌 필수입니다.

6. 주의사항과 트러블슈팅

Flash Attention은 반드시 켜야 합니다

bash
# 올바른 사용
./build/bin/llama-server -m model.gguf --cache-type-k turbo3 -fa on

# 잘못된 사용 (Flash Attention 없이)
./build/bin/llama-server -m model.gguf --cache-type-k turbo3
# 경고: FA 없이도 동작하지만, 성능이 크게 저하됨

Flash Attention이 꺼져 있으면 KV Cache에서 역양자화를 매 어텐션 계산마다 수행해야 하므로 오히려 느려질 수 있습니다.

컨텍스트 스케일링 회귀

매우 긴 컨텍스트(100K+ 토큰)에서 속도가 급격히 떨어지는 현상이 보고되고 있습니다. 이는 현재 수정 중인 알려진 이슈입니다. 임시 해결책으로는 컨텍스트를 분할하여 처리하는 방법이 있습니다.

Value가 Key보다 양자화에 취약합니다

Key 벡터는 양자화에 강한 반면, Value 벡터는 양자화 오차에 더 민감합니다. 품질이 중요한 경우 다음과 같이 Key와 Value에 다른 설정을 적용할 수 있습니다.

bash
# Key는 3비트, Value는 4비트로 차등 적용
./build/bin/llama-server -m model.gguf \
  --cache-type-k turbo3 --cache-type-v turbo4 \
  -c 131072 -fa on

Residual Window 설정

최근 128~256개 토큰은 FP16으로 유지하면 품질을 크게 개선할 수 있습니다. 이를 "residual window"라고 부릅니다.

python
# HuggingFace에서 residual window 설정
cache = TurboQuantCache(
    bits=3,
    residual_length=256,  # 최근 256 토큰은 FP16 유지
)

최근 토큰이 다음 토큰 예측에 가장 큰 영향을 미치므로, 이 부분만 고정밀도로 유지해도 출력 품질이 크게 개선됩니다.

Metal (macOS) 주의사항

Apple Silicon에서 Metal 백엔드를 사용할 때, 커스텀 헤더가 포함된 GGUF 파일은 JIT 컴파일이 실패하면서 조용히 CPU로 폴백될 수 있습니다. GPU 사용률이 예상보다 낮다면 이 문제를 의심해보세요.

bash
# Metal GPU 사용 확인
# Activity Monitor > GPU History에서 GPU 사용률 확인
# 또는 로그에서 Metal 관련 경고 확인
./build/bin/llama-server -m model.gguf --cache-type-k turbo3 -fa on 2>&1 | grep -i metal

Perplexity 검증은 필수입니다

"텍스트가 그럴듯해 보인다"는 것만으로는 품질을 판단할 수 없습니다. 반드시 perplexity를 측정하여 정량적으로 확인하세요.

bash
./build/bin/llama-perplexity -m model.gguf \
  -f wikitext-2-raw/wiki.test.raw \
  --cache-type-k turbo3 --cache-type-v turbo3 \
  -fa on -c 4096

FP16 대비 perplexity 증가가 0.1 이내라면 실용적으로 문제가 없는 수준입니다.

7. 기존 양자화와 조합

TurboQuant는 KV Cache 양자화입니다. 모델 가중치 양자화(GGUF Q4_K_M 등)와 함께 사용하면 메모리 절약 효과가 배가됩니다.

최적의 조합

모델 가중치: GGUF Q4_K_M (4비트 가중치 양자화)
KV Cache:   TurboQuant turbo3 (3비트 KV 캐시)
결과:       소비자 GPU에서 70B 모델 + 128K+ 컨텍스트 실행 가능

구체적인 메모리 계산 (Llama 3 70B, 128K)

FP16 모델 + FP16 KV Cache:
  모델: ~140 GB + KV Cache: ~27.3 GB = ~167.3 GB  (불가능)

Q4_K_M 모델 + FP16 KV Cache:
  모델: ~40 GB + KV Cache: ~27.3 GB = ~67.3 GB  (A100 80GB 1장)

Q4_K_M 모델 + TQ3 KV Cache:
  모델: ~40 GB + KV Cache: ~5.6 GB = ~45.6 GB  (A6000 48GB 1장)

Q4_K_M 모델 + TQ3 KV Cache (32K context):
  모델: ~40 GB + KV Cache: ~1.4 GB = ~41.4 GB  (RTX 4090 가능)

가중치 양자화와 KV Cache 양자화를 조합하면, 원래 A100 클러스터가 필요했던 설정을 소비자 GPU 한 장으로 실행할 수 있습니다.

llama.cpp 실행 예시

bash
# Q4_K_M 가중치 + TurboQuant KV Cache
./build/bin/llama-server \
  -m llama-3-70b.Q4_K_M.gguf \
  --cache-type-k turbo3 --cache-type-v turbo3 \
  -c 131072 -fa on -ngl 99 \
  --port 8080

8. 커뮤니티 구현 현황

TurboQuant는 오픈소스 커뮤니티에서 활발하게 구현되고 있습니다.

프로젝트언어상태
back2matching/turboquantPython/HFDrop-in 교체, OpenAI API 호환
TheTom/turboquant_plusC/Python/Metalllama.cpp + Apple Silicon 최적화
0xSero/turboquantPython/TritonvLLM 어댑터
Aaryan-Kapoor/llama.cppCCPU 전용 tq3_0 타입
RecursiveIntell/turbo-quantRust독립 실행형

각 프로젝트별 특징

back2matching/turboquant — HuggingFace transformers와 가장 잘 통합됩니다. pip install turboquant로 설치하고, TurboQuantCache 클래스를 사용하면 기존 코드에 최소한의 수정으로 적용할 수 있습니다.

TheTom/turboquant_plus — Apple Silicon에서 Metal 가속을 지원합니다. M1 이상의 Mac에서 로컬 추론을 할 때 가장 적합합니다.

0xSero/turboquant — 프로덕션 서빙 환경(vLLM)에 TurboQuant를 적용하고 싶을 때 사용합니다. Triton 커널을 사용하여 GPU에서 높은 처리량을 달성합니다.

마무리

TurboQuant는 아직 초기 단계이지만, 이미 실용적으로 사용할 수 있는 수준에 도달했습니다. 특히 긴 컨텍스트를 다루는 환경에서 메모리 절약 효과가 극적입니다.

시작하기 가장 쉬운 방법은 다음과 같습니다.

  1. llama.cpp fork를 빌드하고 --cache-type-k turbo3 --cache-type-v turbo3를 추가
  2. llama-bench로 벤치마크 돌려서 자신의 환경에서의 성능 확인
  3. perplexity 검증으로 품질 확인

설정 한 줄로 수십 GB의 메모리를 절약할 수 있다는 점에서, 시도하지 않을 이유가 없습니다.

더 많은 콘텐츠를 받아보세요

SNS에서 새로운 글과 튜토리얼 소식을 가장 먼저 받아보세요

이메일로 받아보기

관련 포스트