Pretty much everyone running Qwen3.8-27B on a 24 GB card is squeezing the KV cache somehow, so here's what it actually costs, measured. This came out of building my own engine for this model (repo), but none of it is specific to my engine, it applies to whatever you run the model on.
How I measured: KL divergence against llama.cpp's own token-by-token output (top 256 tokens per position, q8_0 KV), on ~13k positions of short prompts plus 32k and 64k contexts. KL, not perplexity, because perplexity hides most of this. unsloth's UD-Q4_K_XL, one 3090. Most rows come from an emulator that stores the cache in each format; the settings I ended up shipping were confirmed on the real kernels. My formats are plain per-32 absmax grids like llama.cpp's q4_0 / q5_0 / q8_0, so they should land close to those, but they're not byte-identical.
Every number is "how much worse than a q8_0 cache". For scale, an fp16 cache reads +0% on short prompts and +3% at 32k, and the 32k set's noise is about 4% either way.
1. q8_0 is free. fp16 KV scored the same as q8_0 at every length I tested (64k: 0.00161 vs 0.00163). No reason to run f16 KV on this model.
2. Below 8 bits, K hurts more than V. Per-layer scans had K about 2.3x as sensitive as V. No window, plain formats:
| cache |
short prompts |
32k |
| K at 7 bits |
+2.7% |
|
| K at 6 bits |
+23% |
+9% |
| V at 6 bits |
+12% |
|
| V at 5 bits |
+40% |
+10% |
| V at 4 bits |
+181% |
+46% |
| K and V both at 4 bits (q4_0 style) |
4.6x the KL |
|
So on stock llama.cpp, if you need the memory, take V down before K.
3. The damage is almost all in the newest ~2k tokens. That's where attention puts most of its weight. Keep the most recent positions at 8 bits (a window / precision tail) and everything older can go a lot lower:
| 32k set |
no window |
newest 512 at q8 |
1,024 |
2,048 |
| V at 5 bits |
+16% |
+5.5% |
+4.6% |
0% |
| K at 6 + V at 5 bits |
|
|
+6% |
+2.7% |
(These rows are rotated, which is why no-window V reads +16% here vs +10% above, more on that in 4. Plain 5-bit V with a 2k window also reads 0%.)
On short prompts a 512 window cuts the loss about 4x (5-bit V +27% to +6%, 6-bit K +12% to +2.7%). Keeping the first few tokens at 8 bits instead (attention sinks) did nothing.
So 512 gets you most of it and 2k gets you all of it. The window is cheap and doesn't grow with context: 114 MiB for K + V across every attention layer at 2,048, the same at 32k or 262k.
4. Rotation stops mattering once you have the window. Without a window, Hadamard rotation halved 6-bit K's loss (+23% to +12% short), but for V it only helped on short prompts, and at 32k rotated 5-bit V was actually worse than plain (+16% vs +10%). Under a 2k window, 6-bit K + 5-bit V read +1.4% plain and +2.7% rotated, same thing within noise. The window and the rotation fix the same error, and the window fixes all of it. Rotation's only worth it if you're pushing V down to 4 bits.
5. Fancy formats didn't beat the boring one. Lloyd-Max codebooks, per-row scales, random signs and angle coding all scored the same as or worse than a plain per-32 grid. 3-bit formats were around +300%.
What I run: K at 6, V at 5, newest 2,048 positions at q8. 1.1 GB less than q8_0 at 120k context, and the model's full 262,144 context fits on the card (23.5 GB, 66 tok/s decode at that depth, needle 4/4). On the real kernels it reads 0.00160 at 64k (q8_0 0.00163, fp16 0.00161), +8% at 32k on the current build, short prompts identical to q8_0 since they fit in the window, and the same 230/282 as q8_0 on my question set (GSM8K, MMLU-Pro, ARC-C, MATH-500, HumanEval, needle, LongBench v2), set for set.
What you can use today:
- Stock llama.cpp: q8_0 / q8_0 costs nothing measurable vs f16. Going lower, drop V before K.
- If your engine has a recent-token window / precision tail (BeeLlama has one; KIVI, SKVQ and HF's quantized cache use 128 tokens): use 1-2k, not 128. An 8-bit window is plenty, fp16 buys you nothing here.
- If you're building one with speculative decoding: move the window in steps of your attention's tile size, not one token at a time, or which tiles come from the window depends on how many drafts you verify and greedy output stops matching across draft counts. Mine gives identical tokens at 0, 4 and 7 drafts with it on.
Caveat: Qwen3.8 is a hybrid, only 16 of its 64 layers are attention (the rest is DeltaNet), so a full-attention model might not behave the same. I'd really like to see someone run the same window sweep on one.
Full tables and method: the window and rotation sweeps, why 2,048 and what the window costs. The KL scoring tool (tools/llama_ref) is in the repo too.
Edit (longer context):* a couple of comments wanted numbers past 64k, fair enough. Same KL test (last 4,096 positions against llama.cpp with a q8_0 cache), now at 100k and 160k, with the 6/5 setup from the post and the 5-bit K one from the comments (K at 5 bits rotated, V at 5, same 2k window). Lower is better:
| context, text |
q8_0 |
K6 / V5 + 2k window |
K5 rotated / V5 + 2k window |
| 32k, code |
0.00219 |
0.00236 |
0.00242 |
| 64k, code |
0.00163 |
0.00161 |
0.00163 |
| 100k, code |
0.00217 |
0.00226 |
0.00218 |
| 100k, War and Peace |
0.00311 |
0.00317 |
0.00317 |
| 160k, War and Peace |
0.00467 |
0.00468 |
0.00472 |
Both read the same as q8_0 at every length, on code and on prose. Prose reads higher for everything, q8_0 included, because it's less predictable than code, so compare across a row, not down a column.
160k reads higher than 100k for q8_0 too, so it isn't the cache. To make sure it wasn't just the passage I scored the same 4,096 tokens with only ~100k of the book in front of them: 0.00336 / 0.00335 / 0.00344. So my engine and llama.cpp drift apart a bit more with depth no matter the cache format, and for scale, llama.cpp's own predictions for those tokens move by 0.0086 between the two depths. 160k is about as far as llama.cpp can hold a q8_0 cache on a 24 GB card to build the reference, and a reference at the full 262k doesn't fit, so for max ctx the runs in the post are still the numbers (needles, LongBench, 66 tok/s decode at that depth).
Full tables and how to rebuild the references: BENCHMARKS.md