Blog ยท November 2026
Cornell University
We quantize language models to fit them on hardware with limited memory, or to make inference faster by relieving the memory-bandwidth bottleneck. In our experience, the common outcome at low bit widths is a model that runs but has lost a lot of accuracy.
With post-training quantization, the calibration data matters more than it should. The result depends heavily on the calibration set: which domains it covers, in what proportions, and at what sample lengths. Choosing these is mostly trial and error, because a mix that improves math can make code worse, and we only find out after quantizing and evaluating. Even after we recompose the calibration data, some benchmarks recover while others get worse, and overall accuracy can still fall short.
Our starting point is weight-space programming (WSP)3, a recent post showing that LLM agents can analyze and edit a model's weights directly. We tested whether an agent could repair an existing 3-bit checkpoint instead of quantizing the model again. The agent edited 0.0008% of the model's weights, with no gradient updates. On LiveCodeBench v6, the repair raised the score from 30.0% to 47.8%, while quantizing the model again with new calibration data reached only 35.6%.
We gave an agent, Claude Opus 5.5, a Python session with two checkpoints of Nemotron-3.5-Lightning-30B-A3B: a poorly calibrated 3-bit GPTQ1 checkpoint, whose calibration set was small and narrow, and NVIDIA's 4-bit NVFP42 release as a reference. The NVFP4 checkpoint shows what every weight and activation should look like, so the agent can measure exactly where the 3-bit checkpoint has drifted.
The agent's instruction was:
This 3-bit checkpoint should behave like the NVFP4 model. Find what is broken and fix it inside the 3-bit format. You may run the model and read its weights and activations; you may not fit weights to data directly (no training).
That is only the task section of the prompt. The full version, with the rules, the repair loop and the evaluation harness the agent uses, is in program.md in the wsq repository.
The agent also gets a set of validation dataโ from public datasets, and it drives the repair loop itself. In each round it runs both models on the validation data to see where their next-token predictions differ, traces the difference to specific weights, works out why those weights went wrong, and edits them. It keeps an edit only if the 3-bit model then scores better on the validation data. When no further edit helps, we evaluate the final model once on test sets the agent has never seen.
The repair loop. After each edit the agent scores the model on validation data: KL divergence to NVFP4 on chat math, code, and prose, plus perplexity, MMLU-val, 1,000 GSM8K training questions, and how well it predicts NVFP4's own answers. The agent runs a greedy search, in which an edit stays only if the scores improve and nothing regresses.
We started from the poorly calibrated 3-bit checkpoint*, which scored well below NVIDIA's NVFP4 release, and compared two ways to recover the lost accuracy. In the first, we reran GPTQ, starting from the weights of the 3-bit checkpoint, with the validation dataโ as the new calibration set. In the second, the agent repaired the existing checkpoint, with NVFP4 as its reference and the validation data to check its edits. We evaluated all four checkpoints on the same held-out benchmarks.
| Checkpoint | MATH-500 โ | LiveCodeBench v6 โ | GPQA Diamond โ | MMLU โ | WikiText-2 PPL โ |
|---|---|---|---|---|---|
NVFP4 release | 88.0 | 55.6 | 72.1 | 77.0 | 8.08 |
| Poorly calibrated (GPTQ) | 61.6 | 30.0 | 46.6 | 66.6 | 11.76 |
| + re-calibration (GPTQ) | 84.4 | 35.6 | 62.6 | 68.5 | 11.18 |
| + agent edits (WSP) | 83.0 | 47.8 | 64.3 | 74.3 | 9.38 |
The agent's edits improve every benchmark; re-calibration recovers MATH-500 and GPQA Diamond but little else. Bold marks the best 3-bit score in each column. The evaluation settings are in footnote โก.
Both approaches use the same validation data. Re-calibration gives the data to GPTQ as a new calibration set and quantizes the whole model again, which helps, but unevenly. It recovers MATH-500 (84.4%, against 83.0% for the repaired checkpoint) and GPQA Diamond (62.6% against 64.3%), but it reaches only 35.6% on LiveCodeBench, 68.5% on MMLU and 11.18 in perplexity, close to where the poorly calibrated checkpoint started.
The agent uses the same data to find where the 3-bit model differs from NVFP4 and to check each edit. Its repair changes only 0.0008% of the weights and improves every benchmark. It matches re-calibration on MATH-500 and GPQA Diamond and is well ahead on the other three: it reaches 47.8% on LiveCodeBench against 35.6%, 74.3% on MMLU against 68.5%, and a perplexity of 9.38 against 11.18. The reference makes this possible, because the agent can compare the 3-bit weights and activations with NVFP4 and repair the specific weights that went wrong, instead of relying on a different mix of data to fix them.
The edits can raise a score in two ways. First, more samples can finish: on the benchmarks run with thinking on, a quantized model sometimes falls into a reasoning loop that runs until the token limit, and such a sample almost never counts as correct. Second, the answers can get better among the samples that do finish. The figure below splits each benchmark's improvement into these two parts, and most of it comes from better answers.
Mostly better answers. Accuracy points gained by the agent's edits on the poorly calibrated checkpoint, split into samples that now finish within the token limit and better answers among samples that finish.
Case study
The agent first locates the error and finds that one matrix holds most of the damage. The cause is that one input to this matrix is huge on the first few tokens, while the stored values of the weights it multiplies are much larger than they should be, so the input turns this storage error into a large error in the matrix's output. Fix 1 gives these weights a new scale, so that they are stored almost exactly. GPTQ had adjusted other weights to cancel the old error, and after fix 1 these adjustments have nothing left to cancel, so fix 2 removes them. The last section shows how each fix changes the error.
The agent began by measuring how far the 3-bit model's next-token predictions are from NVFP4's, as KL divergence on the validation data (chat math, code and prose). It then restored quantized matrices to their NVFP4 values and checked how much of that gap went away. In a first pass, it restored all matrices of one kind at once, such as every Mamba output projection in the model, to see which kind of matrix matters most. In a second pass, it restored the matrices of that kind one layer at a time, to find the single matrix that matters most.
One kind of matrix at a time, all layers
One Mamba out_proj at a time (top 6 layers)
Share of the KL gap removed by restoring matrices to NVFP4. Each group of bars shows the share for chat math, code and prose, and the number printed next to it is the share for prose. Shares can add up to more than 100%, because errors in different parts of the model interact.
The matrix that stands out is the Mamba output projection (out_proj) in layer 4, because restoring it alone removes about two thirds of the code and prose gap.
To see why this one matrix does so much damage, it helps to first look at what it computes. It is a linear layer that multiplies its input \(\mathbf{X}\) by its weights \(\mathbf{W}\) to produce its output, \(\mathbf{Y} = \mathbf{X}\mathbf{W}\). Each input channel is a column of \(\mathbf{X}\) and is multiplied by its own row of \(\mathbf{W}\), and each column of \(\mathbf{W}\) produces one output channel of \(\mathbf{Y}\).
To find out what this input looks like in practice, the agent ran the NVFP4 model on a few dozen validation sequences of chat math and prose and recorded \(\mathbf{X}\). One input channel turned out to be huge on the first three tokens, with values around 50, where a typical channel stays below 0.01. We call this channel the outlier input, channel \(k\), and the row of \(\mathbf{W}\) it multiplies row \(k\). Huge values on the first tokens are common in language models, where they are known as massive activations4 and are linked to attention sinks5.
The input \(\mathbf{X}\) to layer 4's Mamba out_proj, channel ร token. Mean \(|x|\) over the held-out sequences, first 64 tokens, log scale.
A huge input would be harmless if the weights were stored exactly, but quantization always leaves a small error in each weight: the stored weight \(\hat w\) differs from the NVFP4 weight \(w\) by \(\hat w - w\). When the matrix is applied, this error is multiplied by the weight's input \(x\), so what reaches \(\mathbf{Y}\) is:
For most weights, the input they are multiplied by is small, so their error barely shows in \(\mathbf{Y}\). Row \(k\) is different, because its weights are multiplied by the outlier input, so on the first tokens even a small weight error becomes a large error in \(\mathbf{Y}\). Row \(k\)'s weights are also stored worse than most weights. They are much smaller than the other weights that share their scale, so the format rounds them up to values far larger than they should be, as the section on fix 1 explains.
How one input channel amplifies weight error. On the first three tokens, row \(k\)'s small weight errors are multiplied by the outlier input, so they show up in every output channel of \(\mathbf{Y}\).
MSE in \(\mathbf{Y}\). To find which weights cause the damage, the agent measured each weight's MSE in \(\mathbf{Y}\), which is the weight's error in \(\mathbf{Y}\) (the equation above), squared and averaged over the recorded tokens:
The two forms are equal because \(\hat w - w\) is the same on every token. So a weight's MSE in \(\mathbf{Y}\) is large only if the weight is badly stored and its input is large. A row's MSE in \(\mathbf{Y}\) is the sum over its weights. This measure has one caveat that matters for fix 2, namely that each weight is measured as if it were the only wrong one, so the measure cannot see errors from different weights cancelling each other.
Measured this way, row \(k\) alone holds most of the matrix's total MSE in \(\mathbf{Y}\), far more than any other row.
In short, the defect is that the outlier input multiplies weights whose stored values are much larger than the weights themselves, because the format has no level as small as these weights.
Each group of 32 weights in a column of \(\mathbf{W}\) shares one scale \(\Delta\), and each weight is stored as one of 8 levels, \(\pm c_1\Delta, \dots, \pm c_4\Delta\). GPTQ sets \(\Delta\) to fit the group's largest weights, which leads to a large rounding error on row \(k\)'s weight, because even the smallest level, \(c_1\Delta\), is far larger than this weight.
What fix 1 changes in \(\mathbf{W}\). Rows are input channels, columns output channels. Fix 1 gives a new \(\Delta\) to one group per output channel, namely the group that holds row \(k\)'s weight.
Fix 1 targets the groups that contain a weight of row \(k\), which is one group per output channel. For each of them, it tries the four scales \(\Delta = |w| / c_i\) under which row \(k\)'s weight \(w\) falls exactly on a level, and it rounds the rest of the group to the nearest levels under each scale. The format stores each scale in 8 bits, so each of the four scales is itself rounded, and row \(k\)'s stored weight ends up slightly off \(w\). Fix 1 keeps the scale under which this stored weight is closest to \(w\).
The levels of one group under each scale. Illustration. (a) GPTQ's \(\Delta\) fits the large weight, but the closest level to row \(k\)'s weight is \(c_1\Delta\), which leaves a large rounding error. (b) Fix 1's smaller \(\Delta\) puts a level on row \(k\)'s weight, and the large weight is clipped to the largest level, \(c_4\Delta\).
The fix results in a smaller \(\Delta\), which leads to a larger clipping error on the other weights in the group. However, this cost is acceptable, because the trade-off between rounding error and clipping error depends on the input that each weight multiplies. The other weights have relatively small inputs, which limits their impact on the final output \(\mathbf{Y}\), while row \(k\)'s weight multiplies the outlier input and has to be stored well. Fix 1 therefore treats the groups differently, adjusting the scale in the groups that contain a weight of row \(k\) and leaving the scale of every other group as GPTQ set it.
Connection to AWQ. The agent's first fix follows a principle already used in AWQ6: a weight multiplied by a large input needs to be stored accurately. AWQ applies this principle while it quantizes a full-precision model, whereas the agent arrived at it while investigating a checkpoint that GPTQ had already quantized. The principle itself is not new, and what this post shows is that the agent identified the problem and worked out a suitable repair through its own inspection of the weights and activations.
What if we used AWQ instead? We also quantized the model with AWQ, using the same validation dataโ for calibration. AWQ scores 74.5% on MMLU and 8.96 in WikiText-2 perplexity, close to the repaired checkpoint's 74.3% and 9.38. On the two benchmarks run with thinking on, the repaired checkpoint is clearly more accurate: it reaches 47.8% on LiveCodeBench v6 and 64.3% on GPQA Diamond, against 40.1% and 57.0% for AWQ. AWQ produces a new quantized checkpoint, while the agent repairs an existing one.
GPTQ's compensation. GPTQ quantizes \(\mathbf{W}\) one row at a time. After each row, GPTQ adjusts the rows it has not yet quantized, so that on its calibration set these rows cancel as much as possible of the error it just made in \(\mathbf{Y}\). For most rows these adjustments are small, but row \(k\)'s error in \(\mathbf{Y}\) is large because of the outlier input, so GPTQ compensated for most of it by making large adjustments to some of the rows after row \(k\). We call these rows the compensating rows, and the adjustments their compensation.
Once fix 1 removes almost all of row \(k\)'s error, the compensation has little left to cancel, so it becomes an error in \(\mathbf{Y}\) of its own.
What fix 2 changes in \(\mathbf{W}\). GPTQ quantizes the rows from top to bottom. The compensating rows (hatched) sit scattered after row \(k\).
To locate the compensating rows, the agent measured, for each row, how far two versions of it are from the NVFP4 weights. The first version is GPTQ's weights, and the second is the NVFP4 weights rounded to the nearest level under the same scales. In a typical row the two distances are about equal, but in a compensating row GPTQ's weights are much farther away, because they carry the compensation. The agent marked the rows where this happens as compensating rows, and they turned out to be a small fraction of all rows.
Fix 2 keeps every scale and re-rounds the compensating rows from their NVFP4 values to the nearest level, which removes the compensation. All other rows keep GPTQ's values.
One compensating row: NVFP4 vs stored. Real data, one dot per output channel for a sample of them. Under GPTQ the weights sit far from the diagonal, many with the wrong sign, and that distance is the compensation. After fix 2 they lie close to the diagonal.
Neither fix adds storage, so the checkpoint's size and speed are unchanged.
The figure below plots each row's MSE in \(\mathbf{Y}\) before the fixes, after fix 1, and after fix 2.
MSE in \(\mathbf{Y}\) of each row of \(\mathbf{W}\), fix by fix. Each bar is one row's MSE in \(\mathbf{Y}\), on a log scale. Fix 1 does not touch the compensating rows, so their bars are the same in (a) and (b). Because each row is measured alone, the bars cannot show that in (a) those rows are cancelling row \(k\)'s error. In (b), row \(k\) drops down among the other rows; the 8-bit scale (see fix 1) keeps it from reaching zero. In (c), the compensating rows drop too.
A broken quantized model does not always have to be fixed by changing the calibration data and quantizing again. In our experiment, a newly composed calibration set recovered MATH-500 and GPQA Diamond but still fell short on LiveCodeBench, MMLU and perplexity. An agent given the same data and the NVFP4 reference instead traced most of the damage to one matrix, worked out what GPTQ had got wrong, and repaired it by editing 0.0008% of the weights, with no training. Every benchmark improved, with LiveCodeBench rising from 30.0% to 47.8%. The repaired checkpoint stays in the same 3-bit format, so it can still be loaded and run by the same inference code, at the same size and speed, with no extra code change required.
Future work. This post repairs one GPTQ checkpoint, but the approach does not depend on how the checkpoint was made. Other quantization methods, such as AWQ6, may avoid the defect we found here and leave different errors of their own, so a natural next step is to give the agent an AWQ checkpoint and see whether it can improve that too. Another is to apply the same kind of diagnosis to stabilize quantization-aware training (QAT).
In the longer term, we see this approach going much further, where agents, or swarms of agents, quantize trillion-parameter models through weight space programming, starting from the full-precision weights, instead of only repairing a checkpoint afterwards.
@misc{emara2026repairing,
title = {Repairing a Quantized {LLM} using Weight Space Programming},
author = {Emara, Yahya and Chang, Chi-Chih and Abdelfattah, Mohamed},
year = {2026},
month = nov,
note = {Blog post, Cornell University}
}
โ The validation data is published as wsp-agent-data. It holds about 370K tokens, made up of the NVFP4 model's own answers to 256 GSM8K training questions, the WikiText-2 validation split in 259 windows of 1,024 tokens, the 1,816 questions of the MMLU validation and development splits, and 1,000 further GSM8K training questions. The agent also measured KL on 32 code files, which are not in the published set. We use the same data as the new calibration set in the results below.
* All 3-bit checkpoints use the same format (8 levels per matrix, an FP8 scale per 32 weights). The poorly calibrated one was calibrated on a small, narrow set of 64 code files plus a few hundred model-generated reasoning traces and MMLU answers, and the re-calibrated one on the validation dataโ . No calibration set overlaps the test sets.
โก We run MATH-500 with greedy decoding and thinking off. We run LiveCodeBench v6 (131 problems from February 2025 on) and GPQA Diamond with thinking on and a 32K-token limit, and we average their scores over 3 and 4 samples. MMLU is the 0-shot test split, and WikiText-2 is the perplexity on the test split.