Measuring Quantisation Damage: Perplexity, KL Divergence and Task Evaluations
You now have several ways to make a smaller file out of a model. This lesson is about the question that decides which one you keep: how much did it cost, and cost at what?
By the end you will be able to say what perplexity does and does not tell you, compute and read a KL divergence against the full-precision original, choose between the three levels of measurement according to what you are deciding, read a provider’s quality claim for what it actually asserts, and record a comparison in the format the rest of this course expects.
Perplexity, and what it is silent about
Section titled “Perplexity, and what it is silent about”Perplexity is the standard first measurement, and the llama.cpp tool that produces it puts the definition plainly: “perplexity measures how well the model can predict the next token with lower values being better”.
Mechanically, you take a held-out text, feed it through the model, and at every position ask what probability the model assigned to the token that actually came next. Average the negative logarithms of those probabilities and exponentiate. A model that assigned probability one to every correct token would score one; a model choosing uniformly among a fifty-thousand-token vocabulary would score fifty thousand. Real models sit in the single digits or low tens on ordinary prose.
It is cheap, it needs no labels, and it is a genuine signal. It also has four blind spots that matter for exactly the question this part asks.
It averages. A quantisation that leaves ninety-nine per cent of positions untouched and destroys the model’s behaviour on the remaining one per cent will move the mean very little. Those are often the positions you care about: the token that opens a JSON object, the one that selects a tool, the digit in the middle of an arithmetic step.
It is measured under teacher forcing. At every position the model is shown the correct prefix and asked about the next token. That is not what generation does. Generation feeds the model its own output, so an error compounds, and a small increase in the chance of a wrong token becomes a large increase in the chance of a wrong paragraph. Perplexity cannot see compounding.
It is measured on somebody’s generic text. The standard corpora are prose. If your workload is tool-calling JSON in a chat template, a perplexity computed on Wikipedia is measuring a different distribution from the one you serve.
It is not portable. The README is explicit that “perplexity is not directly comparable between models, especially if they use different tokenizers”. Two models with different vocabularies are solving different prediction problems, and comparing their perplexities compares nothing. Even within one model, two runs on different texts are not comparable.
KL divergence: compare the model with itself
Section titled “KL divergence: compare the model with itself”If the useful form of perplexity is a difference, then measure the difference directly.
For a given prompt, the full-precision model produces a probability distribution over the next token. So does the quantised model. The Kullback-Leibler divergence between those two distributions is a single number saying how far apart they are, and llama.cpp’s documentation gives the interpretation you need: it is “a measure of how similar the FP16 and the quantized logit distributions are with a value of 0 indicating that the distribution are the same”.
That single property is why this course uses it as the primary quantisation metric. It compares the model with itself, so the tokeniser is identical, the text is identical, the architecture is identical, and the only thing that changed is the rounding. It has a meaningful zero. It is sensitive to changes at positions where perplexity averages them away, because it looks at the whole distribution rather than at the probability of one correct token. And it is cheap: no labels, no judge, no generation.
The tool computes it in two passes. The first runs the full-precision model and records its logits to a file; the second runs the quantised model against that file.
Fragment — not complete on its own
llama-perplexity \ -m models/gguf/model-BF16.gguf \ -f wiki.test.raw \ --kl-divergence-base model-BF16.kldFragment — not complete on its own
llama-perplexity \ -m models/gguf/model-Q4_K_M.gguf \ -f wiki.test.raw \ --kl-divergence-base model-BF16.kld \ --kl-divergenceThe second pass prints a block of statistics, and the field names are worth learning because they
answer different questions: Mean PPL(Q) and Mean PPL(base) give both perplexities so you can see
the classical number too; Mean KLD, Median KLD, Maximum KLD, 99.0% KLD and 99.9% KLD
describe the distribution of the divergence rather than only its centre; Mean Δp, Maximum Δp and
RMS Δp report how far the probability of the base model’s chosen token moved; and Same top p
reports how often the two models would have picked the same token at all.
When the tool is not available
Section titled “When the tool is not available”llama-perplexity needs both models as GGUF files that llama.cpp can load. That covers Tracks S, X
and N comfortably and covers Track M for GGUF, but it does not cover an MLX model, an AWQ
checkpoint, or a comparison between two different engines.
For those cases the lab in this part ships kl-divergence.py, which computes the same quantity
through two OpenAI-compatible servers. It sends identical prompts to both, asks each for the
log-probabilities of the top candidate tokens at each position, and accumulates the divergence over
the overlapping support.
That approach has an honest limitation and you should know it before you use the number. The API
returns only the top few candidates rather than the full distribution, so the result is a divergence
computed over a truncated support, not the exact quantity llama-perplexity reports. It is
comparable between quantisations measured the same way, on the same prompts, against the same
reference, and it is not comparable with a figure from the tool. The script records which method
produced each number for exactly this reason.
The three levels, and what each is for
Section titled “The three levels, and what each is for”The measurement ladder: cheapest first, most meaningful last
- Does it fit, and does it loadFile size plus KV cache against your memory, then one prompt. Costs seconds. Eliminates most of the candidate list.
- Perplexity on a fixed textOne number per quantisation, comparable only within this model and this text. Useful as a smoke test: a large jump means something is badly wrong.
- KL divergence against the full-precision originalMean, median, tail percentiles and Same top p. The course's primary quantisation metric: sensitive, cheap, unambiguous, and it isolates the rounding from everything else.
- Your own task set, per quantisationPart 10's runner and judge, run once per file. This is where format compliance, tool calling and refusals show up, and none of the levels above can see them.
- A standard suiteThe next lesson. Slowest, and the only level that produces a number other people can compare with. Run it on the quantisation you actually intend to keep.
The levels are not substitutes. A quantisation can pass level three and fail level four: a model whose next-token distributions barely moved can still lose the habit of closing a JSON object, because that habit lives in a handful of high-stakes positions per response. And a model can pass level four and fail level five in the other direction, because your task set is twenty things you care about and a standard suite is a thousand things somebody else did.
The results table
Section titled “The results table”Every comparison in this part, and every quantisation decision you make afterwards, goes into one shape. Here it is, empty, as the recording sheet the lab fills in.
| Quantisation | File GB | Mean KLD | 99.0% KLD | Same top p | Task set passed | Judge mean | tg128 tokens/s | Peak memory GB |
|---|---|---|---|---|---|---|---|---|
| BF16 (reference) | — | 0 | 0 | 1.00 | — | — | — | — |
| Q8_0 | — | — | — | — | — | — | — | — |
| Q5_K_M | — | — | — | — | — | — | — | — |
| Q4_K_M, imatrix | — | — | — | — | — | — | — | — |
| Q4_K_M, no imatrix | — | — | — | — | — | — | — | — |
| AWQ 4-bit or MLX 4-bit | — | — | — | — | — | — | — | — |
your machine: track, chip and memory, your operating system and version · llama.cpp for the GGUF rows; the engine that loads the fifth row for that row the build string from llama-cli --version, recorded once per row · your fine-tune from Part 13, or a reference model, as listed in the first column · 4,096 tokens of context · the date you ran it
Empty on purpose. The reference row is BF16 by definition: its divergence from itself is zero and it agrees with itself on every token, which is what makes the other rows readable. Record the calibration text used for the imatrix rows in your notebook, because a quantisation whose calibration text has been lost cannot be reproduced.
Two columns in that table do work that is easy to miss. The two Q4_K_M rows differ only by the
importance matrix, which turns “imatrix quants are better” from a slogan into a number on your model.
And the speed and memory columns are there because the whole point of the exercise is a trade: a
row that costs a little quality and buys a great deal of speed is a different decision from one that
costs the same quality and buys nothing.
Reading a provider’s quality claim
Section titled “Reading a provider’s quality claim”Quantisation providers publish quality claims, and the good ones are checkable. Six questions separate the two kinds.
Against what reference? A claim is only meaningful relative to something. “Minimal quality loss” against the BF16 original is a claim; against another quantisation it is a different and weaker one; against nothing it is a mood.
Measured with which metric? Perplexity, KL divergence, a task suite and a judge answer different questions, and a provider quoting perplexity alone has not looked at the tail.
On which text or task? A perplexity claim is inseparable from its corpus, and a task claim from its task.
Mean or tail? As above: this is where the difference between a quantisation you can serve and one you cannot usually lives.
Which exact file? Providers ship dozens of files per model and the claim usually applies to some of them. “Our four-bit quants are near-lossless” says nothing about which of the seven four-bit files in the repository was measured.
On what date, with which version of the tools? Quantisation code changes; a claim from a year ago describes a file produced by different code.
Judged that way, Google’s Gemma 3 QAT claim quoted in the previous lesson is a good one: it names the reference implicitly as the unquantised model, names the metric and the tool, names the format, and reports a relative reduction rather than an absolute equivalence. AWQ’s abstract is another: it states that the method “does not rely on any backpropagation or reconstruction, so it generalizes to different domains and modalities without overfitting the calibration set”, which is a claim about method rather than a number about a file, and is testable as such.
The course’s quantisation rule
Section titled “The course’s quantisation rule”One sentence, and everything in this part is machinery for applying it.
Measure on your task, and keep the format the measurement supports.
The corollaries are worth spelling out. The reference is always the full-precision model, because a comparison between two quantisations tells you which is closer to the other, not which is closer to the truth. The metric is chosen for the decision: KL divergence to shortlist, your task set to choose, a standard suite to publish. The measurement is recorded with its conditions, so that next month’s comparison is against something rather than against memory. And the answer “the difference is smaller than my measurement can see, so I will take the smaller file” is a perfectly good answer, which is why the lab has you estimate the noise before you interpret the differences.
Use paired failures to locate a regression
Section titled “Use paired failures to locate a regression”Evaluate the original and quantised model on identical task IDs. Mark four outcomes per task: both pass, both fail, only the original passes and only the quantised model passes. The last two categories show the changes that an overall average can hide.
Inspect the changed cases for a pattern: rare identifiers, arithmetic, long context, JSON termination or tool arguments. Keep generation settings and representation metadata with every response. Repeated sampled runs can distinguish a stable regression from ordinary output variation, though they do not create new independent task examples.
Use distributional metrics as diagnostics and application checks as acceptance criteria. Perplexity on one corpus does not cover every task, and divergence between token distributions does not state the cost of an incorrect action. Define a tolerance before inspecting the result: perhaps no regression on critical exact-output cases and a bounded change on the broader set. If the quantised variant fails that contract, choose another representation or a smaller model whose quality is adequate; memory fit alone is insufficient.
Perplexity measures next-token prediction on a held-out text under teacher forcing; it averages,
it cannot see compounding, it is tied to its corpus, and it is not comparable across tokenisers, so
it is a smoke test rather than a verdict. KL divergence against the full-precision original compares
the model with itself, has a meaningful zero, and exposes the tail behaviour perplexity hides;
llama.cpp computes it in two passes with --kl-divergence-base and --kl-divergence, reporting the
mean, the median, the percentiles, the movement in the base model’s chosen token and how often the
two models agree on the top token. Where that tool cannot reach, the same quantity can be
approximated through two servers’ log-probabilities, over a truncated support, and compared only
with figures produced the same way. Above KL divergence sit your own task set, where format and
tool-calling failures live, and a standard suite, which is the only level that produces a number
others can compare with. Provider claims are read by asking what reference, what metric, what text,
mean or tail, which file and what date. And the rule is to measure on your task and keep the format
the measurement supports.
Check your understanding
Sources for this lesson
5 verified · checked 2026-09-09
- 01llama.cpp — llama-perplexity README§ What perplexity measures; KL divergence mode; output fieldsgithub.com/ggml-org/llama.cpp/blob/master/tools/perplexity/README.md2026-09-09
- 02llama.cpp — llama-quantize README§ Quantisation types; bits per weightgithub.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md2026-09-09
- 03Gemma 3 QAT Models: Bringing state-of-the-art AI to consumer GPUs§ Perplexity drop claimdevelopers.googleblog.com/en/gemma-3-quantized-aware-trained-state-of-the-art-ai-to-consumer-gpus2026-09-09
- 04AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration (Lin et al., arXiv:2306.00978)§ Abstractarxiv.org/abs/2306.009782026-09-09
- 05llama.cpp — llama-server README§ Completion endpoint; logprobsgithub.com/ggml-org/llama.cpp/blob/master/tools/server/README.md2026-09-09
Every technical claim on this page was checked against the official documentation of the tool, vendor or model publisher on the date shown, at the version pinned for the course. Where the course disagrees with folklore, the source is how you can tell which one to trust.