🧰 1. Files & comparison
Corpora under calibration_data/:
| File | Role |
|---|---|
corpus.cal.txt | imatrix collection + AWQ α search (wiki.test.raw + logtrain TRAIN slice, windowed packer) |
corpus.val.txt | held-out gate for per-tensor α (logtrain TEST slice, ~10k tokens) |
corpus.eval.txt | PPL / KLD eval (external code+math+tools, ~90k tokens) |
All rows benched on the same corpus.eval.txt, same llama.cpp build, ctx=4096. AWQ calibrate uses proxy_tokens=1024, ctx=4096; the Q2_K_S AWQ rows use the q2k_b16 codebook proxy and the IQ2_M AWQ row uses q2k_b16 base + iq3_s mix for the IQ3_S-bumped tensors. KLD and top_p are measured against google/gemma-4-31B-it FP16 (so QAT and vanilla rows are directly comparable). KLD column is median for robustness to per-token tails.
Original google/gemma-4-31B-it source
Here's the transposed (orientation-flipped) version of your table — columns are now quantization variants and rows are the metrics:
| Metric | FP16 (ref) | IQ4_XS | Q2_K (plain) | IQ2_XS (imatrix) | IQ2_XS (AWQ) | IQ2_M (imatrix) | IQ2_M (AWQ) | Q2_K_S (imatrix) | Q2_K_S (AWQ) |
|---|---|---|---|---|---|---|---|---|---|
| File | — | IQ4_XS | Q2_K | IQ2_XS | IQ2_XS_AWQ | IQ2_M | IQ2_M_AWQ | Q2_K_S | Q2_K_S_AWQ |
| Quality | ⭐⭐⭐ | ⭐ | ⭐ | ⭐ | ⭐ | ⭐ | ⭐ | ||
| Quant | FP16 | IQ4_XS | Q2_K | IQ2_XS | IQ2_XS | IQ2_M | IQ2_M | Q2_K_S | Q2_K_S |
| Technique | none (reference) | imatrix () | plain (no imatrix, no AWQ) | imatrix | AWQ cv-gate + imatrix | imatrix | AWQ cv-gate + imatrix (q2k_b16 + iq3_s mix) | imatrix | AWQ cv-gate + imatrix (q2k_b16) |
| Size (GiB) | 57.20 | 15.59 | 11.10 | 8.88 | 8.88 | 10.17 | 10.17 | 10.22 | 10.22 |
| BPW | 16.005 | 4.361 | 3.105 | 2.484 | 2.484 | 2.845 | 2.845 | 2.861 | 2.861 |
| PPL | 215.50 | 319.42 | 3522.63 | 8246.04 | 175.95 | 1958.74 | 895.86 | 843.96 | 157.76 |
| KLD (median) | 0.00000 | 0.073 | 5.211 | 3.006 | 1.801 | 1.571 | 1.602 | 1.538 | 1.719 |
| same_top_p | 100.00% | 78.78% | 25.44% | 34.54% | 47.16% | 46.57% | 48.09% | 46.88% | 49.03% |
QAT google/gemma-4-31B-it-qat-q4_0-unquantized source
Here's the transposed version of the QAT table:
| Metric | Q4_0 (QAT ref) | IQ2_XS (imatrix) | IQ2_XS (AWQ) | Q2_K_S (imatrix) | Q2_K_S (AWQ) |
|---|---|---|---|---|---|
| File | google/gemma-4-31B-it-qat-q4_0-gguf | QAT_IQ2_XS | QAT_IQ2_XS_AWQ | QAT_Q2_K_S | QAT_Q2_K_S_AWQ |
| Quant | Q4_0 | IQ2_XS | IQ2_XS | Q2_K_S | Q2_K_S |
| Technique | QAT (Google official, reference only) | imatrix (from QAT) | AWQ cv-gate + imatrix (from QAT) | imatrix only (from QAT) | AWQ cv-gate + imatrix (from QAT, q2k_b16) |
| Size (GiB) | 16.44 | 8.88 | 8.88 | 10.22 | 10.22 |
| BPW | 4.600 | 2.484 | 2.484 | 2.861 | 2.861 |
| PPL | — | 216.70 | 126.91 | 125.21 | 120.01 |
| KLD (median) | — | 1.349 | 1.222 | 1.219 | 1.088 |
| same_top_p | — | 45.87% | 47.85% | 48.22% | 48.64% |
Same approach as before — metrics run down the left side, each variant gets its own column, bold formatting and all links preserved with shortened link text for readability.
No IQ2_M-from-QAT row. IQ2_M quantization of the QAT weights collapses to garbage across every arm tested (PPL ≈ 2×10¹⁰, top_p = 0%) — the IQ2_M codebook and QAT-shaped weights are geometrically incompatible. Use the vanilla-source
IQ2_M_AWQ, or step to the QAT-sourced Q2_K_S / IQ2_XS.

📊 2. Metrics deep-dive
We benchmark each quant using a subset of the MMLU-pro dataset (50 questions from each category) and using 50 tool calling samples from real-usage logs extracted from claude code, qwen code and opencode. Each metric is reported as the average of five reps and in the case of MMLU-pro we use 2-shot prompting to speed up the evaluation (in a separate test we found the accuracy trends are very similar with >2 shots, but the eval time balloons). The performance factor is measured as the average ratio between different categories relative to the QAT Gemma-4-31b-Q4_0 model, recently released by Google. In most cases, we find that with proper calibration, the 2-bit quantizations can achieve performance within ~90% of the baseline in both tool calling and general knowledge. If a model is quantized at training it tends to have better performance once compressed further (e.g. to 2-bit) but with proper calibration (e.g. imatrix, awq, gptq, etc.), the gap can be narrowed significantly. The line between PPL, KLD and Top(p) is a bit blurry in relation to actual performance.
🔬 3. How they were made
Toolchain: AWQ + imatrix orchestrated by quant-tuner; final quantization with llama-quantize from llama.cpp pinned to commit f3e1828.
🚀 4. Usage
Ollama
Pull and run directly from Hugging Face — no manual download needed:
# Best overall (QAT source, lowest KLD)
ollama run hf.co/pearsonkyle/gemma-4-31B-it-awq-2bit-GGUF:QAT_Q2_K_S_AWQ
# Best vanilla (highest same_top_p)
ollama run hf.co/pearsonkyle/gemma-4-31B-it-awq-2bit-GGUF:Q2_K_S_AWQ
# Best compact (8.88 GiB, QAT + AWQ)
ollama run hf.co/pearsonkyle/gemma-4-31B-it-awq-2bit-GGUF:QAT_IQ2_XS_AWQ
# 4-bit reference (near-FP16 quality, 15.6 GiB)
ollama run hf.co/pearsonkyle/gemma-4-31B-it-awq-2bit-GGUF:IQ4_XS
All available tags:
| Tag | Size | medKLD | same_top_p |
|---|---|---|---|
IQ4_XS | 15.59 GiB | 0.073 | 78.8% |
IQ2_XS_AWQ | 8.88 GiB | 1.801 | 47.2% |
IQ2_M | 10.17 GiB | 1.571 | 46.6% |
IQ2_M_AWQ | 10.17 GiB | 1.602 | 48.1% |
Q2_K_S | 10.22 GiB | 1.538 | 46.9% |
Q2_K_S_AWQ | 10.22 GiB | 1.719 | 49.0% |
QAT_IQ2_XS | 8.88 GiB | 1.349 | 45.9% |
QAT_IQ2_XS_AWQ | 8.88 GiB | 1.222 | 47.9% |
QAT_Q2_K_S | 10.22 GiB | 1.219 | 48.2% |
QAT_Q2_K_S_AWQ | 10.22 GiB | 1.088 | 48.6% |
Building llama.cpp from source (GPU)
To run these models on the GPU, build llama.cpp with CUDA support:
# Install dependencies and clone repo
apt-get update && apt-get install pciutils build-essential cmake curl libcurl4-openssl-dev -y
git clone https://github.com/ggml-org/llama.cpp
# Build with CUDA (set -DGGML_CUDA=OFF for CPU/Metal)
cmake llama.cpp -B llama.cpp/build -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON
cmake --build llama.cpp/build --config Release -j --clean-first --target llama-cli llama-server
# Move binaries
cp llama.cpp/build/bin/llama-* llama.cpp/
Running the server
# Start the server with your chosen model
./llama-server \
--model gemma-4-31B-it-IQ2_M.gguf \
--ctx-size 16384 \
--n-gpu-layers 999 \
--split-mode layer \
--flash-attn on \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--parallel 1 \
--batch-size 2048 \
--ubatch-size 512 \
--host 0.0.0.0 \
--port 1234
Querying via the OpenAI compatible API
import json, base64, urllib.request
def ask(content, max_tokens=256):
body = {
"messages": [{"role": "user", "content": content}],
"max_tokens": max_tokens,
# Gemma 4 is a thinking model. Set this to False (or raise max_tokens),
# otherwise the reply lands in reasoning_content and "content" is empty.
"chat_template_kwargs": {"enable_thinking": False},
}
req = urllib.request.Request("http://127.0.0.1:1234/v1/chat/completions",
json.dumps(body).encode(),
{"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req).read())["choices"][0]["message"]["content"]
b64 = lambda p: base64.b64encode(open(p, "rb").read()).decode()
# Text
print(ask("What is 1+1?"))
⚡ Speculative decoding with the MTP drafter
This package ships one Multi-Token-Prediction (MTP) drafter — a small
speculative draft model that predicts several future tokens from the target's
last hidden state. It runs as a separate --model-draft (arch
gemma4-assistant), so none of the AWQ trunk files are modified.
One drafter serves every quant here (IQ2_XS / IQ2_M / Q2_K_S, vanilla and
QAT). It keys off the base model's hidden size and vocab — not the quantization —
so there is nothing per-quant to download or attach. It also cannot be merged
into a single file: a GGUF carries exactly one general.architecture, and the
drafter (gemma4-assistant) is a distinct architecture from the trunk (gemma4),
which has no in-graph MTP layers. (This is unlike Qwen3.5, where the MTP bundles
into the trunk's own arch.) Keep the drafter beside whichever trunk you run.
| File | Quant | Size | Notes |
|---|---|---|---|
mtp-gemma-4-31B-it.gguf | Q8_0 | 491 MiB | root copy, for -hf auto-discovery |
MTP/gemma-4-31B-it-Q8_0-MTP.gguf | Q8_0 | 491 MiB | recommended |
MTP/gemma-4-31B-it-BF16-MTP.gguf | BF16 | 911 MiB | full precision |
./llama-server \
--model gemma-4-31B-it-Q2_K_S_AWQ.gguf \
--model-draft mtp-gemma-4-31B-it.gguf \
--spec-type draft-mtp --spec-draft-n-max 2 \
--ctx-size 4096 --n-gpu-layers 999 --flash-attn on \
--host 0.0.0.0 --port 1234
Tune --spec-draft-n-max to your hardware. Speculative decoding only helps
when the target decode is latency-bound; on a bandwidth-bound 2-bit trunk the
draft overhead can outweigh the gain at large draft depths. Measured on an
Apple M4 Max (Metal), vanilla Q2_K_S_AWQ trunk:
| Config | decode tok/s | draft acceptance |
|---|---|---|
| no MTP (baseline) | 23.4 | — |
--spec-draft-n-max 1 | 25.0 | 0.92 |
--spec-draft-n-max 2 | 25.1 | 0.87 |
--spec-draft-n-max 4 | 14.5 | 0.73 |
On Metal, n-max 1–2 gives ~+7%; n-max ≥ 4 regresses. On a high-end discrete GPU (e.g. B200) the same drafter yields ~1.4× (per Unsloth's measurements), so push n-max higher there. Acceptance stays high (0.7–0.9) even on these 2-bit trunks.
🪪 5. License & attribution
- Inherits the Gemma Terms of Use from the base model.
- Base weights — vanilla-source files:
google/gemma-4-31B-it. - Base weights — QAT-source files (
qat-*):google/gemma-4-31B-it-qat-q4_0-unquantized, Google's quantization-aware-trained FP16 checkpoint. The Q4_0 reference row in §1 links to the matching official GGUF release. - Calibration + AWQ scaling + quantization performed locally with Quant-Tuner; vendored llama.cpp at commit
32782998. - Calibration data (usage logs) scraped using LogMiner.
- MTP drafter (
mtp-gemma-4-31B-it.gguf,MTP/*): derived from Google'sgoogle/gemma-4-31B-it-assistanthead; the mainline-loadablegemma4-assistantGGUF was published byunsloth/gemma-4-31B-it-GGUFand is redistributed here under the same Gemma terms. Run requires llama.cpp ≥ 2026-06-07 (PR #23398).