Where is the headroom for 2-bit values? Attention-weighted bits, output-aligned bits, an error ledger and a gain fix, 4B model, 64K
Only one idea cleared its screening bar. Keeping the last 512 tokens plus the most-attended older tokens at 4 bits (2.26 bits per value on average) closed 77% of the gap between plain 2-bit and 4-bit values, but it still lost to plain 4-bit keys and values in all six spans, and an oracle version that used future attention did much worse, so the gain probably comes from the recent-token window. Aligning value bits with the output projection, an error ledger shared by tokens with similar keys, and a per-layer gain fix all failed.
| Experiment | E22O |
| Date | 2026-10-30 |
| Benchmark | Next-token KL against the same model with an unquantized cache, plus top-1 agreement. Six WikiText-2 test spans (the 4,096 tokens ending at token 72K, 108K, 144K, 180K, 216K and 252K of the Hugging Face tokenization), each scored with 65,536 tokens of context before it, chunked prefill. |
| Model | Qwen3.5-4B in Hugging Face transformers, NF4 weights (bitsandbytes), bf16 compute. Reference is the same model with an unquantized bf16 cache. |
| Hardware | One of my dedicated test rigs: RTX 3080 10 GB, 128 GB system RAM |
| Verdict | FAIL |
What I tested
Same harness as E22l to E22n: post-RoPE keys and values are quantized then dequantized as they enter the cache, 8 full-attention layers only, keys rotated 4-bit with group 128 in every value arm. Four ideas for where 2-bit values lose quality. (1) Spend extra bits on the tokens that get the most attention: 1a picks the top 6% or 18.5% of tokens by attention received over the whole run, including future queries (meant as an upper bound); 1b is causal, keeping the last 512 tokens at 4 bits and, as tokens leave that window, keeping those with the highest attention so far at 4 bits and dropping the rest to 2 bits. (2) Give 3 bits to the 16 or 80 directions of each head that matter most to the output projection (W_o), against the same split on a Hadamard basis as a control. (3) Carry each token's rounding error forward to later tokens whose keys fall in the same group (by key sign pattern or k-means), against random groups, after first checking whether tokens with similar keys are actually attended together. (4) A per-layer gain on 2-bit values, set by variance matching or by a small grid search on held-out calibration text. Bits per value include scales and flags.
Pass bar, set before the run
Set before the run: an idea stays in the brief if it closes at least 50% of the gap between rotated 2-bit (G128) and rotated 4-bit (G128) values at about 2.25 bits per value in most spans. Attention-weighted bits are dropped if even the future-attention upper bound (1a) closes under 25% at 2.25 bits. The error ledger only goes ahead if tokens in the same key group are attended together clearly more than random groups (a margin of at least 0.1 in some layer). Matching plain 4-bit keys and values (group 32) is the overall goal.
Result
| Arm | Bits per value | Mean KL | Top-1 agreement | Gap closed (2-bit = 0%, 4-bit = 100%) | Per-span KL (72K to 252K) |
|---|---|---|---|---|---|
| Plain 4-bit keys and values (group 32) | 4.5 | 0.0251 | 95.86% | - | 0.0302 / 0.0460 / 0.0272 / 0.0137 / 0.0074 / 0.0263 |
| Rotated 4-bit values, group 128 | 4.125 | 0.0217 | 95.73% | 100% | 0.0276 / 0.0282 / 0.0315 / 0.0109 / 0.0080 / 0.0241 |
| Rotated 2-bit values, group 128 | 2.125 | 0.0959 | 88.88% | 0% | 0.0956 / 0.1233 / 0.1119 / 0.0830 / 0.0689 / 0.0928 |
| 1b: last 512 tokens plus most-attended 6% at 4 bits | 2.26 | 0.0387 | 94.06% | 77% (at least 50% in 6 of 6) | 0.0356 / 0.0540 / 0.0460 / 0.0284 / 0.0207 / 0.0473 |
| 1b: last 512 tokens plus most-attended 18.5% at 4 bits | 2.51 | 0.0332 | 94.63% | 85% | 0.0338 / 0.0524 / 0.0352 / 0.0199 / 0.0183 / 0.0393 |
| 1a: top 6% by total attention, future included | 2.25 | 0.0820 | 89.87% | 19% | 0.0861 / 0.1070 / 0.0975 / 0.0673 / 0.0551 / 0.0792 |
| 1a: top 18.5% by total attention, future included | 2.50 | 0.0700 | 90.56% | 35% | 0.0770 / 0.0893 / 0.0862 / 0.0540 / 0.0478 / 0.0659 |
| 2: 16 W_o-aligned directions at 3 bits | 2.25 | 0.1114 | 86.71% | -20% | 0.1125 / 0.1570 / 0.1147 / 0.0968 / 0.0790 / 0.1085 |
| 2: 80 W_o-aligned directions at 3 bits | 2.5 | 0.0900 | 88.90% | 8% | 0.0978 / 0.1166 / 0.0942 / 0.0725 / 0.0650 / 0.0939 |
| 2 control: 16 Hadamard directions at 3 bits | 2.25 | 0.0869 | 89.55% | 12% | 0.0884 / 0.1140 / 0.1062 / 0.0785 / 0.0620 / 0.0723 |
| 2 control: 80 Hadamard directions at 3 bits | 2.5 | 0.0676 | 90.82% | 39% | 0.0693 / 0.0994 / 0.0750 / 0.0586 / 0.0457 / 0.0576 |
| 3/4: rotated 2-bit values, one scale per 256 (plain) | 2.06 | 0.1025 | 88.40% | -8% | 0.0993 / 0.1388 / 0.1254 / 0.0817 / 0.0735 / 0.0964 |
| 3: error ledger, key-sign groups | 2.06 | 0.1613 | 84.31% | -87% | 0.1514 / 0.2204 / 0.1812 / 0.1451 / 0.1180 / 0.1515 |
| 3: error ledger, k-means key groups | 2.06 | 0.1620 | 84.38% | -88% | 0.1711 / 0.2160 / 0.1894 / 0.1377 / 0.1154 / 0.1426 |
| 3 control: error ledger, random groups (5 seeds, range of means) | 2.06 | 0.1593 to 0.1645 | 84.40% to 84.83% | -85% to -92% | see results.tsv |
| 4: per-layer gain, variance match | 2.06 | 0.1094 | 88.01% | -18% | 0.1043 / 0.1432 / 0.1350 / 0.0979 / 0.0795 / 0.0964 |
| 4: per-layer gain, calibration grid | 2.06 | 0.0993 | 88.79% | -5% (beats plain in 4 of 6) | 0.1066 / 0.1243 / 0.1213 / 0.0873 / 0.0681 / 0.0881 |
No arm at about 2 to 2.5 bits per value reached plain 4-bit keys and values in any span, so the overall goal was not met.
Idea 1 is a contradiction I am reporting as is. The causal version (1b) passed the 50% bar in all six spans, but the future-attention version (1a), meant as the upper bound, closed only 19% at 2.25 bits, which under the pre-set rule drops attention-weighted bits. The difference is that 1b also keeps the most recent 512 tokens at 4 bits, which is where the scored tokens look most. The likely explanation is that the recent window, not attention ranking, does the work. The next check is a recent-window-only arm at the same bits.
The error ledger's premise failed: tokens with similar keys were not attended together much more than random groups (margins 0.044 to 0.095 for k-means groups and 0.015 to 0.037 for sign groups, against a pre-set 0.1, in all 8 layers and 6 spans). The ledger itself was worse than no ledger in 6 of 6 spans and no better than random groups, so the follow-up ledger run was skipped.
For idea 2, measuring the value error before and after the output projection showed the projection mostly made the relative error larger, not smaller, in all but one layer.
Gap closed is measured per span against rotated 2-bit (0%) and rotated 4-bit (100%) at group 128, then averaged. The 2.06-bit arms use one scale per 256 values, so they start slightly behind the 2.125-bit reference.
Peak GPU memory was 6.98 GiB. This is a simulation in Hugging Face transformers, not llama.cpp, so it says nothing about speed or real memory use.
Per-span numbers for every arm are in results.tsv in the code folder.
What happened
The run took about 2 hours 50 minutes on the dedicated test rig after two out-of-memory crashes on the arm that keeps a full-precision copy of the values for comparison; moving that copy to pinned CPU memory fixed it. The goal was to find where 2-bit values leave room for improvement before inventing anything. Three of the four ideas failed outright, including the error ledger, whose premise did not hold on real cache data. The one arm that did well mostly points at a known idea, keeping recent tokens at higher precision, so the next step is to measure that window on its own.
How to rerun
python e22o.py smoke
python e22o.py
Code
All code for E22O: __pycache__, e22o.py, results.tsv.
Prior work
No novelty claim. This was a headroom measurement before any invention. Mixed precision by token importance (for example MiKV and ZipCache) and keeping recent tokens at higher precision (KIVI) are known; the W_o-aligned bit split, the key-group error ledger and the per-layer gain were screened here and failed, so no prior-art search was run for them.
Written by Andrew Rose with help from AI tools. Every number comes from the run logs.
Questions or ideas? me@andrewroseit.com or @AndrewRoseIT.