llama.cpp에서 TurboQuant를 쓸 수 있나: 공식판에는 없고, 포크에서 turbo4로 씁니다
공식 llama.cpp에는 TurboQuant가 없습니다(PR #21089, 2026년 6월 병합 없이 종료). TheTom·buun 포크에서 turbo4를 켜는 방법과, 저희 측정에서 turbo3가 16K 컨텍스트에서 perplexity를 세 배로 올린 결과를 정리했습니다.

llama.cpp에서 TurboQuant를 쓸 수 있나: 공식판에는 없고, 포크에서 turbo4로 씁니다
공식 llama.cpp에는 없습니다. TurboQuant 캐시 타입을 공식 llama.cpp에 넣자는 PR(#21089, CPU 전용 tbq3_0·tbq4_0)은 2026년 6월 2일에 병합되지 않은 채 닫혔습니다. 그래서 공식 빌드에는 turbo 캐시 타입이 하나도 없습니다. 지금 TurboQuant를 쓰려면 길이 세 가지입니다.
- llama.cpp 포크: TheTom/llama-cpp-turboquant는
turbo2부터turbo6까지 있고, 소스에 CUDA·Metal·Vulkan·SYCL용 코드가 들어 있습니다. spiritbuun/buun-llama-cpp는 CUDA와 ROCm용이고, 예전 이름은llama-cpp-turboquant-cuda입니다. - vLLM v0.20 이상:
--kv-cache-dtype turboquant_4bit_nc를 포함한 프리셋 네 개가 있습니다. 실서비스용 엔진 가운데 TurboQuant를 정식으로 넣은 곳은 여기뿐입니다. 그래도 vLLM 자체 연구는 용량이 꼭 더 필요한 경우가 아니면 FP8을 기본으로 권합니다(3편). - HuggingFace transformers: Google과 무관한
turboquantpip 패키지가 있습니다. 오차가 어떻게 생기는지 살펴보기에는 좋지만, 0.2.0 버전의 캐시는 저희 측정에서 기본 캐시보다 메모리를 더 썼습니다(3절).
포크를 쓴다면 turbo3가 아니라 turbo4를 고르세요. 저희 A100 측정에서 turbo4는 q4_0과 품질이 같았고(f16 대비 perplexity +0.65%), turbo3는 16K와 32K 컨텍스트에서 perplexity가 세 배가 됐습니다(5편).
정정 (2026년 10월): 이 글의 이전 판에는 틀린 내용이 여럿 있었습니다.turbo3를 권했고, 값(V)이 키(K)보다 민감하다고 썼지만 저희 측정은 반대였습니다. 근거 없이 13B 이상 모델에 3비트 캐시를 권했고, 직접 재지 않은 8B 벤치마크 표와 MSE 출력을 실었습니다. 70B 캐시 크기를 너무 작게 적어(128K에서 f16 27.3GB, 실제로는 40GiB) 70B를 128K로 48GB 카드에 올릴 수 있는 것처럼 보이게 했고, 계산 스크립트는 KV 헤드를 8개가 아니라 64개로 넣었습니다. 타입을 비교하지 못하는llama-bench명령을 안내했고,turboquantpip 캐시를 메모리 절약 수단으로 소개했고, 패키지에 없는residual_length인자를 썼습니다. "공식 llama.cpp에 병합됐다"는 주장은 8월에 이미 정정했습니다. 아래 내용은 모두 고쳤고, 숫자는 저희 실행 결과, 포크 README, 또는 링크한 글에서 가져왔습니다.
TurboQuant가 아니라 공식 llama.cpp의 KV 캐시 양자화(-ctk q8_0, q4_0, -fa on)를 찾아오셨다면, 실측 결과는 llama.cpp KV 캐시 양자화, A100 한 장에서 실측에, 그대로 복사해 쓸 옵션은 KV 캐시 양자화 가이드에 있습니다.
1. 상황별로 고르기
| 지금 쓰는 것 | 쓸 것 | TurboQuant 옵션 |
|---|---|---|
| 공식 llama.cpp나 Ollama | 없습니다 | 없음 (llama.cpp는 -ctk q8_0 -ctv q8_0, Ollama는 OLLAMA_KV_CACHE_TYPE=q8_0) |
| NVIDIA·Apple·Intel GPU에서 llama.cpp | TheTom 포크 | -ctk turbo4 -ctv turbo4 |
| NVIDIA·AMD에서 llama.cpp, 층별 자동 조절을 원함 | buun 포크 | 기본 VBR 캐시, 또는 -ctk turbo4 -ctv turbo4 |
| 서빙 스택 | vLLM v0.20 이상 | --kv-cache-dtype turboquant_4bit_nc |
| 파이썬에서 오차를 보고 싶음 | pip install turboquant | TurboQuantMSE(dim, bits) |
Ollama도 사정이 같습니다. Ollama의 TurboQuant PR(#15505)은 2026년 5월에 닫혔습니다. 프로젝트별 현황은 3편: TurboQuant 현황 점검에 정리했습니다.
2. llama.cpp 포크에서 쓰기
아래 명령은 각 포크의 문서를 따랐습니다. 이 절의 측정값은 2026년 8월의 spiritbuun 포크(지금의 buun)에서 나왔습니다. TheTom 포크는 저희가 직접 빌드해 보지 않았습니다.
빌드
TheTom 포크는 TurboQuant 작업을 기본 브랜치 feature/turboquant-kv-cache에 두고 있습니다.
git clone https://github.com/TheTom/llama-cpp-turboquant.git
cd llama-cpp-turboquant
cmake -B build -DGGML_CUDA=ON # NVIDIA
# cmake -B build -DGGML_METAL=ON # Apple Silicon
cmake --build build --config Release -j실행 파일은 build/bin/에 생깁니다.
GGUF 모델
GGUF라면 무엇이든 됩니다. TurboQuant는 가중치가 아니라 캐시를 바꾸기 때문입니다. 예를 들어 Llama 3.1 70B Instruct(128K 컨텍스트)의 Q4_K_M 파일(39.6GiB)은 이렇게 받습니다.
hf download bartowski/Meta-Llama-3.1-70B-Instruct-GGUF \
Meta-Llama-3.1-70B-Instruct-Q4_K_M.gguf --local-dir ./modelshf는 HuggingFace CLI의 지금 이름입니다. 오래된 안내에는 huggingface-cli로 나옵니다.
서버 실행
./build/bin/llama-server -m ./models/Meta-Llama-3.1-70B-Instruct-Q4_K_M.gguf \
-ctk turbo4 -ctv turbo4 \
-c 131072 -fa on -ngl 99 --port 8080| 옵션 | 하는 일 |
|---|---|
-ctk turbo4 (--cache-type-k) | K 캐시를 TurboQuant 4비트로 |
-ctv turbo4 (--cache-type-v) | V 캐시를 TurboQuant 4비트로 |
-c 131072 | 컨텍스트 128K 토큰 |
-fa on | flash attention. turbo 커널은 flash attention 경로 안에 있습니다 |
-ngl 99 | 모든 층을 GPU에 올림 |
서버는 OpenAI API 형식으로 응답합니다.
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 256}'buun 포크의 기본 캐시는 고정돼 있지 않습니다
buun-llama-cpp는 가변 비트레이트 캐시(VBR)를 기본으로 켭니다. 캐시 옵션을 주지 않으면 f16으로 시작하지만, 메모리가 모자라기 시작하면 층을 하나씩 f16 → turbo8 → turbo4 → … 순서로 내려 압축하고, turbo4에서 멈춥니다. 그래서 옵션 없이 돌린 결과는 f16 기준선이 아닙니다. 얼마나 압축됐는지는 컨텍스트 길이와 남은 VRAM에 따라 달라집니다. f16 기준선이 필요하면 -ctk f16 -ctv f16을, 타입 하나로 고정하려면 -ctk turbo4 -ctv turbo4를 주세요.
내 GPU에서 비교하기
llama-bench는 쉼표 목록을 받으면 모든 조합을 돌립니다. 그래서 -ctk f16,turbo4 -ctv f16,turbo4라고 쓰면 K와 V가 다른 조합까지 섞여 나옵니다. 같은 타입끼리만 비교하려면 반복문으로 돌립니다.
for t in f16 q8_0 turbo4 turbo3; do
./build/bin/llama-bench -m model.gguf -fa 1 -ngl 99 \
-ctk $t -ctv $t -p 512 -n 128 -d 0,8192,32768,65536
done-d는 캐시에 토큰을 그만큼 채워 둔 상태에서 디코드 속도를 잽니다. KV 캐시의 대역폭 차이는 여기서 드러납니다. 저희 A100 수치는 TheTom 포크가 아니라 2026년 8월의 spiritbuun 포크(지금의 buun)에서 쟀습니다. Qwen3-8B, 깊이 64K에서 f16은 초당 79.6토큰, turbo4는 57.8토큰(f16의 73%), q8_0은 22.8토큰(29%)이었습니다. 전체 표는 5편에 있습니다.
3. HuggingFace transformers에서 쓰기
PyPI의 turboquant 패키지(back2matching/turboquant, Google과 무관)는 양자화기와 캐시 클래스를 줍니다. 아래 두 예제와 같은 코드를 2026년 10월 25일에 CPU에서 돌렸습니다. 버전은 turboquant 0.2.0, transformers 5.20.2, torch 2.14.1입니다.
pip install transformers torch accelerate "turboquant==0.2.0" "numpy<2.4"numpy<2.4는 꼭 고정하세요. turboquant 0.2.0은 np.trapz를 부르는데 numpy 2.4에서 이 함수가 사라졌습니다. 고정하지 않으면 import는 되지만 양자화기를 만드는 순간 실패합니다.
벡터를 양자화하고 오차 보기
import torch
from turboquant import TurboQuantMSE
torch.manual_seed(0)
vectors = torch.randn(1024, 128) # K나 V 행 대신 쓰는 임의 벡터
for bits in [2, 3, 4]:
tq = TurboQuantMSE(dim=128, bits=bits, device="cpu")
indices, norms = tq.quantize(vectors)
vectors_hat = tq.dequantize(indices, norms)
mse = ((vectors - vectors_hat) ** 2).mean()
rel = ((vectors - vectors_hat) ** 2).sum() / (vectors ** 2).sum()
print(f"{bits}-bit MSE: {mse:.6f} relative: {rel:.4f}")저희 출력입니다.
2-bit MSE: 0.129699 relative: 0.1293
3-bit MSE: 0.049991 relative: 0.0499
4-bit MSE: 0.019725 relative: 0.0197비트가 하나 늘 때마다 오차가 약 2.5배 줄어듭니다. 3비트에서 벡터당 상대 오차 5%는 작아 보이지만 품질을 보장하지는 않습니다. 어텐션은 키 수천 개의 순위를 한꺼번에 매기기 때문입니다. llama.cpp에서 다른 3비트 구현인 포크의 turbo3는 16K에서 perplexity를 세 배로 올렸습니다(5절).
0.2.0의 캐시 클래스는 메모리를 줄이지 않습니다
패키지에는 기존 캐시 자리에 바로 끼우는 캐시도 있습니다.
from turboquant import TurboQuantCache
outputs = model.generate(**inputs, past_key_values=TurboQuantCache(bits=4), use_cache=True)돌아가기는 합니다. 하지만 메모리를 줄이려고 쓴다면 소스를 먼저 보세요. 0.2.0은 층마다 가장 최근 토큰 128개를 원래 정밀도로 둡니다. 이 값은 고정이고 바꾸는 인자가 없습니다. 그보다 오래된 토큰은 값마다 uint8 인덱스 하나와 norm으로 저장합니다. 그런데 매 스텝마다 이 압축본 전체를 원래 정밀도로 되돌린 사본을 만들어 계속 들고 있습니다. Llama 3.2 1B Instruct(bf16)에 2,401토큰짜리 프롬프트를 넣은 뒤, 캐시 객체가 잡고 있는 텐서를 모두 더했습니다.
| 캐시 | 잡고 있는 텐서 | memory_usage_bytes() 보고값 |
|---|---|---|
기본 DynamicCache | 75.1 MiB | 없음 |
TurboQuantCache(bits=4) | 116.9 MiB | 41.8 MiB |
TurboQuantCache(bits=3) | 116.9 MiB | 41.8 MiB |
기본 캐시는 4절의 공식과 맞습니다(16층 × KV 헤드 8 × 64 × 2 × 2바이트 × 2,404토큰. 프롬프트 2,401토큰에 생성 토큰 3개가 더해진 길이입니다). TurboQuant 캐시는 그 1.56배를 잡았습니다. 되돌린 사본이 압축본 옆에 그대로 있기 때문입니다. 패키지 자체 보고는 압축된 부분만 세고, bits=3과 bits=4가 같은 크기로 나오는 것은 둘 다 인덱스 하나에 1바이트를 쓰기 때문입니다. 함께 확인한 것이 두 가지 있습니다.
- 프롬프트와 생성 토큰을 합쳐 128개가 안 되면 아무것도 양자화되지 않습니다. 그래서 "The capital of France is" 뒤에 몇 토큰만 생성하는 짧은 시험은 품질에 대해 아무것도 알려 주지 않습니다. 저희 첫 시험이 바로 그랬습니다. 20토큰을 생성했고, 세 캐시가 모두 같은 답을 냈습니다.
- 0.2.0에서는
TurboQuantCache(bits=3, protected_layers=[0, -1])와TurboQuantCache(bits=3, residual_length=256)모두TypeError가 납니다. GitHub main 브랜치에는protected_layers가 추가됐지만 되돌린 사본은 여전히 만들고, 그 뒤로 나온 릴리스는 없습니다.
이 패키지는 TurboQuant의 오차가 어떻게 생기는지 볼 때 쓰세요. 메모리를 줄이려면 llama.cpp 포크나 vLLM을 쓰세요.

4. 포크로 캐시 메모리가 얼마나 줄어드나
토큰 하나당 캐시 크기는 층 수 × KV 헤드 수 × 헤드 차원 × 2(K와 V) × 값당 바이트입니다. 여기에는 어텐션 헤드 수가 아니라 KV 헤드 수(config.json의 num_key_value_heads)를 넣습니다. Llama 3.1 70B는 어텐션 헤드가 64개지만 KV 헤드는 8개입니다.
Llama 3.1 70B(80층, KV 헤드 8, 헤드 차원 128), 131,072토큰 기준입니다.
| 캐시 타입 | 값당 비트 | KV 캐시 |
|---|---|---|
| f16 | 16 | 40.0 GiB |
| q8_0 | 8.5 | 21.2 GiB |
| q4_0 | 4.5 | 11.2 GiB |
| turbo4 | 4.25 | 10.6 GiB |
| turbo3 | 3.25 | 8.1 GiB |
값당 비트에는 블록마다 붙는 스케일이 포함돼 있습니다. turbo 값은 TheTom 포크 형식입니다. buun의 지금 turbo4는 4.125비트, 스칼라 turbo3는 3.5비트라서(README) 두 포크의 캐시 크기는 조금씩 다릅니다. 5편에서 잰 VRAM 차이도 이 값을 따라갔습니다. Qwen3-8B 32K에서 turbo4와 q4_0의 최대 사용량은 6.57GB와 6.55GB로 같은 수준이었습니다.
def kv_cache_gib(layers, kv_heads, head_dim, tokens, bits_per_value):
return 2 * layers * kv_heads * head_dim * tokens * bits_per_value / 8 / 2**30
for name, bits in [("f16", 16), ("q8_0", 8.5), ("q4_0", 4.5), ("turbo4", 4.25), ("turbo3", 3.25)]:
print(f"{name:7s} {kv_cache_gib(80, 8, 128, 131072, bits):5.1f} GiB")전체 메모리는 여기에 가중치와 계산 버퍼를 더해야 합니다. Q4_K_M 가중치(39.6GiB)에 128K f16 캐시를 더하면 버퍼를 빼고도 약 80GiB라서 80GB A100에 들어가지 않습니다. turbo4로 바꾸면 약 50GiB입니다.
5. 어떤 타입을 고를지
아래 권고는 5편의 측정에서 나왔습니다. A100 한 장에서 Qwen3-8B Q4_K_M의 wikitext-2 perplexity를 쟀습니다.
| 캐시 타입 | 4K에서 f16 대비 | 8K | 16K | 32K |
|---|---|---|---|---|
turbo4 | +0.65% | +1.8% | +1.5% | +1.0% |
turbo3 | +5.5% | +23.6% | +210% | +193% |
K q8_0 / V turbo3 | +0.18% | +0.1% | +0.4% | +0.1% |
turbo2 | +83% | — | — | — |
| 상황 | 쓸 것 |
|---|---|
| f16 캐시가 들어감 | f16 |
| 캐시를 약 3.8배 줄여야 함 | `turbo4` |
| 값만 3비트로 내리고 싶음 | -ctk q8_0 -ctv turbo3 (키는 8비트 유지. 64K 디코드는 f16의 29%로 느려짐) |
| 컨텍스트 8K 이상 | `turbo3`는 쓰지 않음 |
| 어떤 경우든 | turbo2는 쓰지 않음 |
품질 손실은 키 쪽에서 생겼습니다. 키를 q8_0에 두면 값을 3비트로 내려도 컨텍스트 길이와 상관없이 거의 그대로였습니다. 모델 하나, GPU 하나, 2026년 8월의 포크 빌드 하나에서 나온 숫자이므로 정확한 퍼센트는 저희 조건의 값으로 보세요. 직접 확인할 것은 패턴입니다. 4비트는 버티고, 3비트 키는 컨텍스트가 길어질수록 무너집니다.
perplexity는 실제로 서비스할 컨텍스트 길이에서 재세요. 4K에서는 turbo3도 괜찮아 보였습니다. 16K에서는 아니었습니다.
./build/bin/llama-perplexity -m model.gguf \
-f wikitext-2-raw/wiki.test.raw \
-ctk turbo4 -ctv turbo4 -fa on -c 16384 --chunks 3시험 파일은 포크 소스 트리의 scripts/get-wikitext-2.sh로 받습니다. 같은 명령을 -ctk f16 -ctv f16으로 한 번 더 돌려, 같은 텍스트에서 나온 두 숫자를 비교하세요.
6. 자주 막히는 곳
`Unsupported cache type`이 나올 때
공식 빌드에서 -ctk turbo4를 주면 Unsupported cache type으로 멈춥니다. 이 메시지가 나오면 포크가 아니라 공식 바이너리를 실행하고 있는 것입니다. PATH에서 어느 llama-server가 먼저 잡히는지 확인하세요.
flash attention
turbo 커널은 flash attention 경로에 맞춰 작성돼 있습니다. -fa on을 유지하세요. buun README에는 기본 VBR 캐시가 flash attention을 필요로 하고 스스로 켠다고 적혀 있습니다.
Metal
Apple Silicon에서는 캐시가 정말 GPU에 올라갔는지 확인하세요. 서버를 -v로 띄워 Metal 버퍼 줄을 보거나, 활성 상태 보기의 GPU 기록을 보면 됩니다. 저희는 포크를 Metal에서 재지 않았습니다. TheTom 포크는 M5 Max에서 turbo3가 q8_0 속도의 98.9%라고 보고합니다(3편).
7. 커뮤니티 구현
| 프로젝트 | 백엔드 | 상태 (llama.cpp 행과 pip 패키지는 2026년 10월 확인, vLLM과 Ollama는 8월 확인) |
|---|---|---|
| ggml-org/llama.cpp (공식) | 없음 | 병합 안 됨: PR #21089, 2026년 6월 2일 종료 |
| Ollama | 없음 | 없음: PR #15505, 2026년 5월 종료 |
| vLLM v0.20 이상 | Triton | --kv-cache-dtype turboquant_*, 정식으로 넣은 유일한 실서비스 엔진 |
| TheTom/llama-cpp-turboquant | CUDA, Metal, Vulkan, SYCL | turbo2부터 turbo6까지 |
| spiritbuun/buun-llama-cpp | CUDA, ROCm | turbo4, turbo3, trellis 부호화 turbo3_tcq, 기본값 VBR |
| back2matching/turboquant | PyTorch | 양자화기와 TurboQuantCache. 0.2.0은 되돌린 사본을 들고 있음(3절) |
| Aaryan-Kapoor/llama.cpp | CPU | TQ3_0 타입 |
백엔드 목록은 저희가 돌려 본 결과가 아니라 각 포크의 소스 트리에서 확인한 것입니다. 저희가 잰 것은 CUDA뿐입니다. TheTom 소스에서 CUDA·Metal·SYCL에는 turbo용 flash attention 커널이 있고, Vulkan에는 turbo2부터 turbo4까지 역양자화 셰이더가 있습니다. Vulkan의 flash attention 경로가 어디까지 지원하는지는 확인하지 못했습니다.
정리
- 공식 llama.cpp에는 TurboQuant가 없습니다. TheTom이나 buun 포크를 빌드하거나 vLLM을 쓰세요.
-ctk turbo4 -ctv turbo4 -fa on으로 시작하세요.- 믿고 쓰기 전에, 서비스할 컨텍스트 길이에서
llama-perplexity를 f16과 나란히 돌려 보세요.
TurboQuant는 KV 캐시 비용을 줄이는 열두 가지 방법 중 하나이고, 열두 가지는 치르는 비용의 단위가 서로 다릅니다. 같은 A100에서 열두 가지를 모두 재서 KV 캐시 줄이는 12가지, 1편에 정리했습니다.
캐시를 양자화하는 것과 달리, 어텐션 층이 처음부터 무엇을 저장할지 바꾸는 방법도 있습니다. 새 연재는 거기서 시작합니다. 첫 글은 DeepSeek config에는 KV 헤드가 16개인데 캐시는 9분의 1입니다.
저희 숫자를 읽는 대신 GPTQ, AWQ, GGUF, QLoRA를 직접 돌려 보고 싶다면 LLM Quantization and Compression Hands-On 강의에 24강으로 정리해 두었습니다. 3강까지는 무료입니다.
새 측정은 Paper of the Week로 매주 나갑니다. 다음 편을 받으시려면 아래에서 구독하세요.