본 글은 local, 데스크톱에 호스팅된 GPU가 장착된 시스템에서 llama.cpp를 통해 Gemma 4의 다중 토큰 예측(Multi-Token Prediction, MTP) drafter와 함께하는 추측 디코딩(speculative decoding)에 대한 단계별 소개를 제공합니다. 이 글은 짧은 시리즈 중 첫 번째 부분이며, 여기서는 drafter가 무엇인지 그리고 왜 사용해야 하는지 설명하고, 다음 부분에서 측정 결과를 다룰 예정입니다.
[https://github.com/xbill9/gemma4-dev/tree/main/local-llamacpp-3050oem-2b-q4_0]
Gemma 4는 모든 크기에 대해 작은 보조 모델, 즉 drafter를 제공합니다. 이 drafter는 다음 몇 개의 토큰을 추측하여 메인 모델이 한 번의 패스로 이를 확인할 수 있게 합니다. Google의 QAT drafters는 4-비트 그리드에 맞춰 훈련되었으며, 이렇게 저장하면 크기가 97.8 MB에서 59.2 MB로 줄어들어 Hugging Face에 공개됩니다.
이 프로젝트가 하려는 것은 무엇인가?
이 레포지토리의 장치들은 로컬 하드웨어(데스크톱용 RTX 3050 OEM 및 노트북용 GTX 1650 Ti)에서 Gemma 4 E2B와 E4B를 구동합니다. 두 장치 모두 이미 메인 모델의 정확한 4-비트 QAT 빌드를 실행하고 있습니다. Google은 또한 각 Gemma 4 모델에 대한 drafter를 공개하며, llama.cpp가 이제 이를 실행할 수 있게 되었습니다.
이 부분에서는 세 가지 내용을 다룹니다: 추측 디코딩이 어떻게 작동하는지, Gemma 4의 drafter가 일반적인 draft model과 어떻게 다른지, 그리고 이 장치들이 구동하는 Google의 QAT drafters의 정확한 Q4_0 리팩(repacks)에 대해 설명합니다.
토큰을 하나씩 생성하는 것이 느린 이유는 무엇인가?
언어 모델은 순전파(forward pass)마다 하나의 토큰을 작성하며, 모든 패스는 모든 가중치(weight)를 읽습니다. 작은 GPU를 사용하는 사용자 한 명에게는 산술 연산은 가볍지만 메모리 트래픽이 무겁습니다. 즉, 카드는 각 단계에서 계산하는 것보다 가중치를 이동시키는 데 대부분의 시간을 사용합니다.
그 결과 매 단계마다 여분의 컴퓨팅 자원(spare compute)이 남게 됩니다. 여러 토큰을 한 번에 확인하는 것은 동일한 가중치를 한 번 읽고 약간 더 많은 산술 연산을 수행하므로, 하나를 생성하는 것보다 시간이 조금 더 걸릴 뿐입니다. 추측 디코딩은 이 여분의 용량을 사용하기 위해 존재합니다.
추측 디코딩은 어떻게 작동하는가?
각 단계는 두 부분으로 구성됩니다.
- Draft(초안 생성). 작고 빠른 모델이 다음 몇 개의 토큰을 추측합니다.
- Verify(검증). 타겟(target)이라고 불리는 주 모델이 모든 추측에 대해 한 번의 순방향 패스(forward pass)를 실행하고, 각 추측을 자신이 생성했을 내용과 비교하여 확인합니다.
타겟은 처음으로 의견이 다른 추측까지는 초안을 받아들입니다. 그 이후 나머지 추측들은 폐기하고, 해당 지점에서 자신만의 토큰 하나를 추가합니다. 따라서 한 단계(step)는 항상 최소한 하나의 토큰을 생성하며, 모든 추측이 맞을 경우에는 초안으로 만든 개수보다 하나 더 많은 토큰을 생성할 수 있습니다.
출력은 타겟의 것이며, 모든 토큰은 타겟이 선택했을 내용과 일치하거나 타겟 자체의 대체 토큰입니다. 초안 모델(drafter)은 오직 각 비용이 많이 드는 패스가 몇 개의 토큰을 생성하는지만 변경합니다.
Google의 모델 카드에는 다음과 같이 명시되어 있습니다:
Speculative Decoding 파이프라인에서 사용될 때, 초안 모델(draft model)은 여러 토큰을 미리 예측하고, 타겟 모델(target model)이 이를 병렬로 검증합니다.
Gemma 4의 Drafter가 다른 점은 무엇인가?
전통적인 초안 모델은 동일 계열의 더 작은 복사본이며, 자체 레이어와 자체 KV 캐시를 가지고 전체 대화에 대해 자신만의 패스를 수행합니다. Gemma 4의 drafter는 타겟에 추가되는 방식입니다. Google은 이 접근 방식을 다중 토큰 예측(multi-token prediction)이라고 부르며, drafter가 타겟 모델의 활성화 값(activations)과 KV 캐시를 사용하는 경량의 4개 레이어 모델이라고 설명합니다.
Hugging Face에 있는 E2B drafter의 config.json을 보면 얼마나 작은지 알 수 있습니다:
"architectures": ["Gemma4AssistantForCausalLM"],
"backbone_hidden_size": 1536,
"hidden_size": 256,
...
| 속성 | 의미 |
|---|
backbone_hidden_size: 1536 | drafter는 E2B 타겟의 1536차원 히든 스테이트(E4B의 경우 2560)를 읽습니다. |
| ... | |
| 두 가지 결과가 따릅니다. drafter는 대화에 대한 별도의 캐시를 유지하지 않으므로, 메모리 비용은 가중치와 작은 작업 버퍼로만 유지됩니다. 그리고 각 추측을 타겟이 이미 현재 토큰에 대해 계산한 것부터 시작하기 때문에, 이렇게 작은 모델도 잘 추측할 수 있습니다. | |
체크포인트는 또한 출력 레이어에 클러스터링된 단축 경로(clustered shortcut)를 가지고 있습니다(num_centroids: 2048). llama.cpp의 컨버터는 이를 제외하고 전체 어휘 사전을 점수화합니다.
Drafter를 사용하는 이유?
- 🟢 한 사용자에게 더 빠른 답변. 메모리를 기다리는 GPU에서, 두세 개의 토큰을 검증하는 각 패스는 두세 번의 패스 작업을 수행합니다. Google의 카드는 최대 3배까지 가능하다고 주장합니다.
- 🟢 재학습 및 메인 모델 변경 불필요. 대상 파일, 설정, 컨텍스트는 그대로 유지되며, drafter는 추가 파일 하나와 서버 플래그 두 개만 필요합니다.
- 🟢 적은 메모리 비용. drafter가 대상의 KV 캐시를 공유하므로, 가중치와 작은 작업 버퍼만 비용이 듭니다.
- ⚠️ 이득은 텍스트에 따라 달라집니다. 코드처럼 예측 가능한 출력은 잘 추측되지만, 자유 형식의 이야기는 추측 빈도가 낮고, 거부된 추측 하나하나가 낭비되는 작업입니다.
- ⚠️ 이득은 부하에 따라 달라집니다. 한 번에 많은 사용자가 있을 경우 GPU는 이미 바쁘며, 추측을 검사할 여유 용량이 줄어듭니다. 파트 2에서는 이 비용 지점을 측정합니다.
- ⚠️ 그리디(Greedy) 텍스트는 근접한 점수에서 변경될 수 있습니다. 두 단어가 거의 같은 점수를 받을 때, 하나의 배치 패스에서 여러 토큰을 검사하는 것이 선택을 다른 방향으로 바꿀 수 있습니다. 이는 배치의 산술적 반올림 방식이 다르게 적용된 것일 가능성이 높으며, 여기서는 테스트되지 않았습니다. 답변은 대상의 것을 유지합니다; 파트 2는 점수를 보여줍니다.
어떤 Drafter가 어떤 모델과 함께 사용되나요?
Google은 Gemma 4 크기별로 두 개의 drafter를 게시합니다: bf16 모델용 -it-assistant와 QAT 빌드용 -it-qat-q4_0-unquantized-assistant입니다. Google의 QAT 모델 카드에는 이 조합에 대해 명시되어 있습니다:
어시스턴트 모델과 함께 다중 토큰 예측(speculative decoding)을 사용하여 QAT 대상 모델을 사용할 때, 호환성을 보장하기 위해 어시스턴트 모델 역시 동일한 정밀도의 QAT 체크포인트여야 합니다.
이 장치들은 E2B와 E4B의 정확한 Q4_0 QAT 빌드를 사용하므로, QAT drafter를 사용합니다.
1단계 — QAT Drafter 재패키징하기
Google은 QAT drafter를 bf16으로 배포하지만, 그 값들은 이미 4비트 그리드 위에 놓여 있습니다. 즉, 모든 가중치 행렬에서 각 32개 블록은 최대 16개의 고유한 값을 가집니다. 이는 Q4_0의 자체 레이아웃이므로, 가중치를 다시 반올림하지 않고도 4비트로 저장할 수 있다는 의미입니다.
llama.cpp의 컨버터는 drafter를 Q8_0으로 작성합니다. 정확한 메인 모델 GGUF를 만드는 데 사용되는 동일한 빌더인 gguf_exact.py는 각 블록의 학습된 단계를 찾아 drafter를 대신 Q4_0으로 저장합니다. 어떤 블록도 그리드에서 벗어나지 않았습니다.
| E2B drafter | 파일 | Google의 bf16 대비 오차 |
|---|
| bf16, Google의 가중치 | 170.2 MB | 0 |
| ... | | |
정확한 Q4_0 파일은 Q8_0보다 39% 작고, 상대 오차(빌드 보고서의 산술 계산 기준)로 볼 때 Google의 가중치에 3.6배 더 가깝습니다. E4B drafter도 마찬가지입니다: 98.7 MB 대비 59.7 MB입니다.
단계 2 — Hugging Face에서 다운로드하기
두 drafter 모두 빌드 스크립트와 증거 자료와 함께 공개되었습니다:
xbill9/gemma-4-E2B-it-qat-assistant-q4_0-exact-gguf
xbill9/gemma-4-E4B-it-qat-assistant-q4_0-exact-gguf
리그(rig)에서는 make draft를 실행하여 고정된 리비전(revision)을 다운로드하고 해시를 확인합니다:
060f5a0158f8120d25137866b90e04e21cd0d2d23de1db7a9cc3b7f00e60bd11 gemma-4-E2B-it-qat-assistant-q4_0-exact.gguf
dc965cb963e56b86fbc04db8efec83163886085e88514574650a57fa55a2ee24 gemma-4-E4B-it-qat-assistant-q4_0-exact.gguf
단계 3 — Drafter로 타겟 서비스하기
llama.cpp는 Gemma 4의 drafter를 자체적인 추측 타입인 draft-mtp로 실행합니다. 일반 서버 명령어에 추가되는 두 개의 플래그가 있습니다:
llama-server -m gemma-4-E2B-it-q4_0-exact.gguf -ngl 99 -fa 1 \
-md gemma-4-E2B-it-qat-assistant-q4_0-exact.gguf \
--spec-type draft-mtp -ngld all --spec-draft-n-max 2
| 플래그 | 의미 |
|---|
-md | drafter 파일 |
| ... | |
| 서버 로그는 drafter가 로드되었음을 확인하고, 요청 건별로 타겟이 수락한 추측 횟수를 보고합니다: | |
That request was a coding prompt: 160 of 189 guesses accepted, and each target pass produced 2.68 tokens on average.
Drafter는 얼마나 많은 메모리를 차지할까요?
RTX 3050에서 8,192 토큰의 컨텍스트로 측정했으며, E2B 타겟만으로도 2,795 MiB가 필요합니다:
| Drafter 추가 여부 | GPU 메모리 | drafter 비용 |
|---|
| none | 2,795 MiB | — |
| ... | | |
| The exact drafter costs a third less memory than the Q8_0 변환. E4B에서도 패턴은 유지됩니다: +122에 비해 +84 MiB입니다. 4 GB 노트북 카드에서는 이 메가바이트 하나하나가 컨텍스트이거나 여유 공간(headroom)을 의미합니다. | | |
더 작은 파일은 추측된 토큰들을 변경하지 않습니다: Q4_0, Q8_0 및 Google의 bf16 drafter 모두 타겟 모델이 동일한 비율로 draft된 토큰을 수용했습니다. 파트 2에서는 이것이 속도에 어떤 의미를 갖는지 다룹니다.
🔎 팁: Drafter는 어디서 실행되나요?
여기서 측정된 경로는 llama.cpp의 draft-mtp입니다. 다른 두 가지 방법도 존재합니다:
- Hugging Face Transformers:
generate()에 assistant_model=을 전달하는 방식이며, Google이 문서화한 경로입니다.
- vLLM:
--speculative-config '{"method":"mtp", ...}'를 사용하며, 이 저장소에서는 Cloud TPU v5e에서 측정되었습니다.
🔎 팁: 두 개의 draft된 토큰부터 시작하세요
추가적인 추측은 그 다음의 추측이 맞아야만 유효하게 계산됩니다. RTX 3050과 두 모델 모두에 대해 단계당 두 개의 draft된 토큰이 가장 좋은 설정이었으며, GTX 1650 Ti 노트북 카드에서는 하나였습니다. 코딩처럼 예측 가능한 출력이 있는 경우에만 늘리세요.
비교 및 대조
| Classic draft model | Gemma 4 MTP drafter |
|---|
| 읽는 내용 | 컨버세이션 전체, 처음부터 | 타겟의 hidden state와 KV cache |
| ... | | |
| E2B QAT drafter | File | GPU memory added |
| --- | --- | --- |
| bf16 | 170.2 MB | +188 MiB |
| ... | | |
그렇다면, 어떤 것을 선택해야 할까요?
Gemma 4 QAT 타겟을 위해 llama.cpp로 서비스되는 정확한 Q4_0 QAT drafter입니다. 이 drafter는 Google이 요구하는 것과 동일한 정밀도를 가지며, 가장 작은 파일 크기이고, Google의 가중치에 가장 가깝고, 메모리 측면에서 가장 저렴합니다. 또한 타겟 모델은 이 drafter가 추측하는 내용을 큰 파일을 받아들이는 빈도만큼이나 자주 수용합니다.
이것을 한 명 또는 몇 명의 사용자가 메모리에 의존하는 GPU에서 켜보세요. 다음 부분에서는 이것이 얼마나 빨라지는지, 어떤 종류의 텍스트에서 그러한 속도가 나타나는지, 그리고 사용자 추가가 drafter의 비용 지불(paying)을 중단시키는 지점은 어디인지를 측정합니다.
요약
본 기사의 목표는 Gemma 4의 MTP drafter를 사용한 추측 디코딩(speculative decoding)을 설명하고, Google의 QAT drafter의 정확한 4비트 리팩(repacks) 버전을 소개하는 것이었습니다. 해결책의 핵심은 Google의 drafter 설계였는데, 이는 타겟 모델의 상태와 KV 캐시를 재사용하도록 훈련된 4개 레이어 모델이며, QAT 타겟과 동일한 4비트 그리드에 맞춰 훈련되었습니다. 그 결과는 다음과 같습니다:
- 🟢 drafter가 추측하고, 타겟이 검증: 하나의 패스에서 여러 토큰을 처리하며, 모든 토큰은 여전히 타겟 모델의 토큰입니다.
- 🟢 Gemma 4의 drafter는 자체 KV 캐시를 유지하지 않음: 타겟 모델의 은닉 상태(hidden state)를 읽는 너비 256의 4개 레이어로 구성되어 있습니다.
- 🟢 Hugging Face에서 정확한 Q4_0 QAT drafters 제공: E2B의 경우 59.2 MB, E4B의 경우 59.7 MB로, llama.cpp의 Q8_0 변환보다 39~40% 작고 Google의 가중치에 3.6배 더 가깝습니다.
- 🟢 E2B drafter를 사용하면 +80 MiB의 GPU 메모리 확보: Q8_0의 경우 +120 MiB, bf16의 경우 +188 MiB 대비 증가분입니다.
- ⚠️ 이득은 텍스트와 사용자 수에 따라 달라짐; 부분 2에서 측정 결과를 확인하세요.
- ⚠️ 탐욕적인(Greedy) 텍스트는 배치로 검증이 실행될 때 거의 동률인 지점에서 변화할 수 있습니다.
범위: 2026년 10월 14일, RTX 3050 OEM (8 GB) 한 대에서 8,192 토큰의 컨텍스트를 사용하여 gguf_exact.py로 리팩되고 llama.cpp 77c4540ce로 서비스된 Gemma 4 E2B 및 E4B에 대한 Google의 QAT drafters입니다. drafter에 대한 설명은 Google의 모델 카드, MTP 문서, 그리고 체크포인트의 config.json에서 가져왔습니다.
Gemma 4의 MTP drafter를 로컬 llama.cpp 배포에 추가하는 전략은 점진적인 단계별 접근 방식을 통해 검증되었습니다.
참고 자료