How a model gets smaller, and how to check it’s still the same model

Open-weight models are often run in smaller, quantised versions rather than as published. The Local board (configurations served on Forcebench’s own hardware) lists each configuration’s quantisation, with labels like NVFP4, FP8, AWQ-INT4 or EXL3 4.0bpw, and the quantisation study compares builds of the same model. This page explains what those labels mean, for readers who know Apex better than number formats.

Quantised files are often made by someone other than the lab that trained the model. So the page also covers a practical question: how far can you check that a downloaded quant really is the model it claims to be?

Illustrative

The animations use made-up weights to show how each method works; none is a measurement of a real model. The worked example, the format grids and the fingerprints are exact.

Before the methods

What’s in a model file

A model is a list of numbers

A language model is mostly numbers called weights (or parameters), set during training. They’re stored in tensors, named grids of numbers such as one layer’s attention weights, and saved to files. A “70B” model has about 70 billion weights.

Bits set the size

Each weight takes some number of bits. Open-weight models are usually published in 16-bit formats such as BF16: 2 bytes a weight, so 70 billion weights take about 140 GB. With n bits there are 2n possible values: 65,536 at 16 bits, but only 16 at 4 bits.

Memory is the limit

To run at a usable speed, the weights must fit in the accelerator’s memory, and at small batch sizes most of the time to generate a token goes on reading them. 140 GB is several times what a high-end consumer graphics card holds. Fewer bits a weight means a smaller file that fits on more hardware, and usually faster generation.

The price is error

Fewer bits means coarser numbers. As a rule of thumb, 8 bits is close to lossless, 4 bits is the common sweet spot, and below 4 bits quality drops noticeably; for a fixed memory budget, one large study found 4-bit weights almost always the best trade. How much a particular model loses is an empirical question, which the quantisation study measures on Salesforce work.

3

Analogy

Rounding methods need only the original weights, so anyone can repeat them and get the same file. Calibrated methods also run sample text through the model to decide how to round, so repeating them needs that text too. That difference decides how far a quant can be checked.

Needs only the weights

Round to nearest

How do you fit a number into 4 bits?

The idea

Round it. If every price in a shop were rounded to the nearest 5 cents, there’d be fewer different prices, and none would be off by more than 2.5 cents. Quantisation does the same to weights: it sets out a grid of allowed values and moves each weight to the nearest point on it.

The grid comes from a scale. A block of weights shares one scale, kept at higher precision; each weight is stored as a small whole number, and whole number × scale gives the weight back, approximately. Getting the approximate value back is called dequantising.

The worked example

  1. Four weights: 0.12, −0.53, 0.98 and 0.31. The target is 4-bit signed whole numbers from −7 to 7. (Four bits can hold −8 to 7; −7 to 7 keeps the grid symmetric.)
  2. The scale stretches the largest magnitude to the top of the range: 0.98 ÷ 7 = 0.14.
  3. Divide each weight by the scale and round: 0.86, −3.79, 7.00 and 2.21 become 1, −4, 7 and 2. Only those four small numbers and the one scale are stored.
  4. Dequantise by multiplying back: 0.14, −0.56, 0.98 and 0.28. The errors are 0.02, 0.03, 0.00 and 0.03.

Why the errors stay small

Rounding to the nearest whole number is never off by more than a half, so no weight moves by more than half a step: scale ÷ 2, or 0.07 here. That bound is what makes a round-to-nearest quant checkable (see Dequantise and compare). The slider shows the trade: every bit removed halves the number of grid points and roughly doubles the step.

Where you’ll see it

llama.cpp’s Q8_0 and Q4_0 round to nearest (Q4_0 maps each block’s largest weight to −8 and uses the whole −8 to 7 range), and so does MLX’s default quantisation, with a scale and an offset for every 64 weights. These need only the weights, so anyone can repeat them. MLX conversions can also use calibrated methods, so an “MLX 4-bit” label alone doesn’t say which.

What it can’t do. Every weight gets the same treatment, however much it matters to the output. At 8 bits, and often at 4, that’s fine; at 3 bits and below, plain rounding loses far more quality than the calibrated methods further down.

4

Exact
Needs only the weights

Blocks and scales

Why not one scale for the whole tensor?

The idea

One unusually large weight sets the scale for everything that shares it. If a whole tensor shared one scale, a single outlier would stretch the grid and flatten most small weights to zero or one step. Splitting the tensor into small blocks, each with its own scale, keeps the damage inside one block.

Smaller blocks are more accurate, but every scale costs bits. llama.cpp’s Q4_0 stores blocks of 32 weights with one 16-bit scale, so it spends 4 + 16 ÷ 32 = 4.5 bits a weight; Q8_0 uses the same blocks with 8-bit values, 8.5 bits a weight. At 4.5 bits, a 70-billion-weight model takes about 39 GB.

What the animation shows

  1. 32 made-up weights, one of them an outlier, rounded to 4 bits with one shared scale.
  2. Two blocks of 16: the block without the outlier gets a finer grid of its own.
  3. Four blocks of 8: only the outlier’s block stays coarse and the average error drops, but each weight now carries 6 bits.

K-quants

llama.cpp’s K-quants (Q4_K, Q5_K, Q6_K and the rest) go a level further: super-blocks of 256 weights split into sub-blocks, whose own scales are quantised to save space. Q4_K also stores a minimum for each sub-block and still comes to about 4.5 bits a weight. Instead of taking the largest weight, it searches for the scale with the smallest error, which can clip a few of the largest values. A suffix like Q4_K_M names a mix: the output layer and parts of the attention and feed-forward weights are kept at 6 bits (Q6_K).

What it can’t do. Blocks contain an outlier’s damage but don’t remove it: its block still gets a coarse grid. Methods that keep outliers at higher precision (SpQR) or rescale channels before rounding (AWQ) go further.

Illustrative
Needs only the weights

Floating-point formats: FP8, MXFP4, NVFP4

Why would 4 bits want uneven steps?

The idea

Whole numbers spread their values evenly. Floating-point numbers, like scientific notation, store an exponent and a few digits, so their values sit close together near zero and further apart as they grow. Trained weights cluster near zero with a few large ones, which suits that shape.

The 4-bit floating-point format, FP4 (called E2M1: a sign, two exponent bits and one mantissa bit), can hold 0, 0.5, 1, 1.5, 2, 3, 4 and 6, each positive or negative. FP8 comes in two layouts: E4M3, which reaches 448, and E5M2, which reaches 57,344.

Two 4-bit block formats

  • MXFP4, from the Open Compute Project’s Microscaling specification: blocks of 32 FP4 values sharing one 8-bit scale that can only be a power of two. That’s 4 + 8 ÷ 32 = 4.25 bits a weight.
  • NVFP4, from NVIDIA: blocks of 16 FP4 values, each block with an FP8 (E4M3) scale, plus one 32-bit scale for the whole tensor. Smaller blocks and finer scales cost a little more: about 4.5 bits a weight.

Rounding or calibrated?

Within each block the weights are rounded to nearest, but the format alone doesn’t say how a file was made. NVIDIA’s tools quantise both the weights and the activations (the numbers passed between layers) by default, and set the activations’ scales from sample text. A weight-only NVFP4 file made by rounding needs no sample text, and other tools may apply calibrated methods before rounding.

What it can’t do. A format sets the grid, not the method: a file labelled NVFP4 or MXFP4 doesn’t tell you on its own whether it was made by plain rounding.

Illustrative

Calibrated methods

Rounding that knows which weights matter

Round to nearest treats every weight alike. Calibrated methods run a few hundred passages of ordinary text through the model, see which weights matter for its outputs, and round so that the outputs change as little as possible. The trade is better quality at low bit widths, and a file nobody can rebuild exactly without the same sample text.

Uses sample text

GPTQ

Can one weight’s rounding error be made up by its neighbours?

The idea

Rounding a weight up or down changes the layer’s output a little. GPTQ’s insight is that the weights not yet rounded can be nudged to cancel some of that change. It works through a layer’s weights a column at a time: round a column, then adjust the columns still to come so the layer’s output on the sample text moves back towards the original.

Because each weight is rounded after earlier nudges, its final grid point can differ from the one nearest its original value. That’s deliberate: the layer as a whole stays closer to the original than plain rounding gets it. The animation is a real, tiny GPTQ run on made-up numbers.

Step by step

  1. Run sample text through the model and record the inputs each layer sees. The paper used 128 passages of 2,048 tokens from C4, a public web-text dataset.
  2. From those inputs, work out how the errors of different weights interact (second-order information about the layer).
  3. Round the first column to the grid.
  4. Adjust every column not yet rounded to cancel the error, then move to the next column. Real implementations do this 128 columns at a time on a GPU.

A relative

Intel’s AutoRound reaches for the same goal differently: it learns each weight’s rounding direction, and how much to clip, with about 200 steps of gradient descent on sample text.

What it can’t do. The corrections depend on the sample text and the order of the columns, so a GPTQ file can’t be rebuilt without them, and its weights break the half-step bound that checks plain rounding. The choice of sample text can also change the result.

Toy run
Uses sample text

AWQ and importance matrices

Which weights deserve the finest grid?

The idea

A weight’s rounding error reaches the output multiplied by the input it meets, so a weight whose input is usually large matters more than one whose input is usually small. AWQ (activation-aware weight quantisation) starts from the observation that protecting about 1% of weights, those on the channels with the largest inputs, recovers much of the quality rounding loses.

It protects them without spending extra bits. Each input channel gets a scale that grows with how large its inputs run on sample text: the weights are multiplied by it before rounding and the inputs divided by it, with the division folded into the operation before. The maths is unchanged, but the important weights lose less to rounding. The animation shows a simplified version, with one channel scaled by 2.

Step by step

  1. Run sample text and record the average size of each input channel.
  2. Search for the scaling strength that keeps the layer’s output closest to the original.
  3. Scale the weights, fold the inverse into the operation before, and round to nearest. No retraining is needed.

llama.cpp’s imatrix

An importance matrix (imatrix) uses the same insight differently. A tool runs sample text through the model and records how large each channel’s inputs are; the quantiser then weighs each rounding error by that importance when it chooses scales. The lowest-bit formats (IQ1, IQ2 and IQ3_XXS) refuse to quantise without one.

What it can’t do. AWQ’s scales are folded into neighbouring weights, so an AWQ file’s numbers differ from the original’s even before rounding; comparing them weight by weight means undoing those scales first. An imatrix quant can’t be rebuilt without its imatrix file and the same llama.cpp version.

Simplified
Uses sample text

Codebooks and training

What if the grid itself is learned?

The idea

Rounding gives each weight its own grid. Codebook methods round groups of weights together: they learn a small table of typical patterns, a codebook, and store for each group the number of its nearest entry. A table fitted to where the weights actually are wastes fewer values than a regular grid, which matters most at 2 to 3 bits a weight.

AQLM learns several codebooks and adds their entries together. QTIP, and the EXL3 format built on it, encodes weights along a trellis (see EXL3). llama.cpp’s 2-bit i-quants (IQ2_XXS and friends) use points of a fixed lattice (E8) instead of a learned table. All of them use sample text.

Training for low precision

Quantisation-aware training (QAT) goes further: the model is trained or fine-tuned while its weights are rounded on the way through, so it learns to tolerate the precision it will run at. Google released QAT versions of Gemma 3 this way, and Meta’s LLM-QAT paper did it for LLaMA models. A QAT model’s weights have legitimately moved away from the original’s.

What it can’t do. A codebook quant can’t be compared with the original weight by weight in any simple way, and QAT changes the weights on purpose. For both, the only check against the original is how the model behaves.

Toy run
Uses sample text

EXL3: rotations and trellises

How can a weight stored in 2 bits have 65,536 possible values?

The idea

EXL3 is the format of ExLlamaV3, a library for running models locally on consumer GPUs, and several configurations on the Local board use it. It’s a streamlined version of QTIP, a 2024 method from Cornell, and it combines three ideas: rotate the weights so outliers are spread thin, store them as one long string of bits read through a sliding window, and feed rounding errors forward as GPTQ does.

The sliding window is the unusual part. Each weight is decoded from a 16-bit window of the string, but at 2 bits a weight the window then moves on by only 2 bits, so neighbouring weights share most of their bits. Each weight can still take any of 65,536 values while costing only 2 new bits. The catch is that the weights can’t be chosen one at a time, so a search chooses the whole string at once.

Step by step

  1. Flip signs at random, then rotate each tensor on both sides with a Hadamard transform: a fixed pattern of sums and differences, applied in blocks of 128. Every weight is shared among many others, so outliers are spread thin and the weights take on a bell shape. The file keeps the signs and scales needed to undo it, and at run time the rotation is undone, either on each layer’s inputs and outputs or on the weights themselves.
  2. Run sample text through the model and record what each layer sees: by default a bundled mix of 250 rows of 2,048 tokens.
  3. Split each tensor into tiles of 16 × 16 weights, each stored as one string of bits. The Viterbi algorithm, a standard way to find the best path through a chain of linked choices, finds the string whose decoded values come closest to the rotated weights.
  4. As the tiles are encoded in turn, adjust the weights still to come to make up for the errors so far, using the sample text: the same kind of update as GPTQ.

Where the values come from

Each window’s 16 bits go through a short fixed formula instead of a stored table. In the default codebook, the window is multiplied by a constant, the four bytes of the result are added up, and the total is centred and scaled. A total of four random-looking bytes is bell-shaped, just as four dice total 14 far more often than 4, so the 65,536 possible windows give a bell-shaped spread of values to match the rotated weights. A formula needs no table lookups, which keeps decoding fast on a GPU.

Reading the labels

A label like 4.0bpw gives the average bits a weight. Each tensor gets a whole number of bits a weight from 1 to 8, or 1.5, 2.5 or 3.5 with the default codebook, and the converter mixes them so the whole model comes to about the target. A Local board label such as “EXL3 4.0bpw experts, FP8 dense” means the experts (the blocks of weights a mixture-of-experts model picks between for each token) are EXL3 at that average, and the other layers are FP8.

What it can’t do. An EXL3 tensor can be decoded and rotated back for comparison, but with shared bits and errors fed forward, no simple bound says how far each weight may move. The converter’s default sample text ships with it and its random signs are seeded from each layer’s position, so a rebuild with the same version and settings starts from the same inputs; nothing published says it gives identical bytes, on the same GPU or another. In practice, the check is comparing outputs.

Toy run

Checking a quant

Is it really the model it claims to be?

A downloaded quant could be a faithful build, a corrupted copy, a weaker model under a famous name, or something tampered with. There are three levels of checking against the original weights, from strongest to weakest, and which ones are open depends on how the quant was made.

Check 1 · strongest

Rebuild and compare hashes

Does rebuilding it from the original give the same bytes?

The idea

A hash is a short fingerprint computed from a file’s bytes; SHA-256 is the usual one. Change a single bit anywhere and the fingerprint changes completely, so two files with the same SHA-256 are, for practical purposes, identical.

Round-to-nearest methods have no randomness in them: the same weights, tool, version and settings give the same output. So a round-to-nearest quant can be rebuilt from the original weights and the two hashes compared. A match means the download is exactly what the original weights produce, without having to trust whoever made it.

Step by step

  1. Download the original weights at a fixed commit of the publisher’s repository. Hugging Face shows a SHA-256 for each large file, so each download can be checked against it.
  2. Quantise them with the same tool, version and settings the quant’s maker used.
  3. Hash the result and compare it with the download’s hash.

What it can’t do. A different tool version, or metadata written into the file, can change the bytes even when no weight differs; hashing tensor by tensor sidesteps the metadata. Calibrated methods can’t be rebuilt without their sample text. HQQ is a near miss: it needs no sample text, but it optimises each block’s zero point in a loop, and nothing published says the result is identical across GPUs and library versions.

Exact
Check 2

Dequantise and compare

Is every weight within half a step of the original?

The idea

When a quant can’t be rebuilt byte for byte, its numbers can still be compared. Dequantise each tensor and subtract the original, weight by weight. For round to nearest, the worked example’s bound must hold: no weight further than half a step from the original. A weight well outside means the quant wasn’t made from these weights by rounding.

There are edge cases. A format can clip a value at the edge of its range: Q4_0 can clip a value nearly as large as its block’s largest but of the opposite sign, and K-quants’ scale search can clip a few of the largest values. Floating-point formats have uneven steps, so their bound is half the local step.

How the other families look

  • imatrix quants stay close to the bound, but can’t be rebuilt exactly without the imatrix file.
  • GPTQ and AutoRound break it by design: moving weights off their nearest level is the point.
  • AWQ needs its scales undone first, since they’re folded into neighbouring weights.
  • Codebook, EXL3 and QAT quants have no simple weight-by-weight bound.

What it can’t do. The bound shows the numbers came from these weights by rounding. It can’t confirm methods that move weights on purpose, and it says nothing about whether the original weights can be trusted.

Illustrative
Check 3 · weakest

Compare outputs

Does it answer like the original?

The idea

When the numbers can’t be compared, compare behaviour. At each step a model gives every possible next token a probability. KL divergence measures how far the quant’s probabilities are from the full-precision model’s on the same text: zero means identical, and larger means more different.

llama.cpp’s perplexity tool has a mode for this. It first saves the full-precision model’s probabilities over a test text, then runs the quant against them and reports the mean KL divergence and how often both models give the same token the highest probability (“Same top p”), among other statistics.

Step by step

  1. Run the full-precision model over a test text and save its probabilities for every token.
  2. Run the quant over the same text.
  3. Average the KL divergence over every position, and count how often the top token agrees.

What it can’t do. This catches broken and badly degraded quants, but not subtle tampering. A backdoor that switches on only for a trigger phrase looks perfect on ordinary test text, because the text never contains the trigger, and such backdoors can survive safety training (Hubinger et al., 2024). The check also needs the full-precision model to run, which may not fit.

Illustrative
The file format

The file underneath: safetensors

Can opening a model file run code?

The idea

Older PyTorch checkpoints (.bin and .pt files) are saved with Python’s pickle, a general format for saving Python objects. A pickle is a little program: loading it follows its instructions, which can call any function. Python’s own documentation warns: “The pickle module is not secure. Only unpickle data you trust.”

Safetensors is a deliberately simple alternative. The first 8 bytes give the length of a JSON header listing each tensor’s name, data type, shape and byte range; the raw bytes follow. There’s no code in it, so there’s nothing to run. A 2023 audit by Trail of Bits found no flaw leading to code execution, and the transformers library has saved safetensors by default since version 4.35.

What it makes possible

  1. Safe loading: reading a header and copying bytes can’t execute anything.
  2. Fast loading: the file can be memory-mapped, so only the parts actually used are read, and one tensor can be loaded without the rest.
  3. Practical checking: each tensor’s bytes sit at a known place, ready to hash on their own or to dequantise and compare.

What it can’t do. Safetensors makes loading safe; it doesn’t make the weights trustworthy. A backdoored model saved as safetensors is still backdoored, so where the file came from still matters. PyTorch 2.6 and later also load .bin and .pt files more defensively by default (weights_only), but the pickle format itself still allows code.

Illustrative

Putting it together

Start from the original, then check what you can

  1. Get the original

    Download the publisher’s safetensors at a fixed commit, and check each file’s SHA-256 against the repository.

  2. Quantise it yourself

    A rounding method (Q8_0, Q4_0, FP8, or K-quants without an imatrix) gives a reproducible file. For imatrix, GPTQ or AWQ quality, make the sample-text files yourself from the originals; EXL3’s converter needs only the originals and a target bitrate.

  3. Compare outputs

    Run a KL-divergence check against the full-precision model to confirm nothing broke.

Quantisation methods and the strongest check against the original weights
MethodHow it roundsNeeds sample textStrongest check against the original
Q8_0, Q4_0To nearest, one scale per 32 weightsNoRebuild and compare hashes
K-quants without an imatrixTo nearest, with searched scalesNoRebuild and compare hashes
MLX default 4-bit and 8-bitTo nearest, a scale and offset per 64 weightsNoRebuild and compare hashes
FP8, MXFP4, NVFP4 weightsTo nearest, on a floating-point gridNot for the weightsRebuild and compare hashes, when the tool and settings are known
K-quants with an imatrixTo nearest, scales weighted by importanceYes, as an imatrix fileRebuild with the same imatrix file; otherwise dequantise and compare
HQQTo nearest, with optimised zero pointsNoDequantise and compare
AWQScaled, then to nearestYesDequantise and compare, after undoing the scales
GPTQ, AutoRoundOff nearest, to cancel errorsYesCompare outputs
EXL3Trellis codes on rotated weights, errors fed forwardYes, a bundled set by defaultCompare outputs; a rebuild may match, but isn’t documented to
AQLM, IQ1 to IQ3 i-quantsCodebooks, latticesYesCompare outputs
QATTrained for low precisionTraining dataCompare outputs

Other names you’ll see. W4A16 means 4-bit weights with 16-bit activations; W4A4 quantises the activations too. INT4 usually means 4-bit whole numbers with a scale per group, made by any of the methods above. BF16 is the unquantised 16-bit original.

Still open

What this page can’t settle yet

  • Detecting a backdoor that survives quantisation, or one that quantisation itself switches on. Researchers have built models that behave normally at full precision and turn malicious once quantised, first with simple rounding schemes (Egashira et al., 2024) and then with GGUF’s quantisation types (Egashira et al., 2025). A faithful quant of such a model passes every check above.
  • How often GPU arithmetic breaks bit-exact rebuilding in practice, for methods that run on GPUs, such as EXL3.
  • How the half-step bound behaves for K-quants, which search for scales and can clip, and for floating-point formats with uneven steps.
  • Whether hashing tensor by tensor should replace hashing whole files, since metadata can differ when the weights don’t.
  • How to run the KL-divergence check on models too large to run at full precision.

Sources

Where the claims on this page come from

Checked against the original papers, specifications and source code in October 2026. Formats and tools change quickly, so the newest details here may change.