Hybrid attention memory: why calculators are 2–70× wrong
Published 2026-08-28 · updated 2026-08-28 · engineering note
Every popular VRAM calculator computes the KV cache the same way:
2 × layers × kv_heads × head_dim × context × bytes — memory that grows
linearly with context length on every layer. The 2026 model generation broke that
formula. Kimi K3, Qwen3-Next and Qwen3.5, Ling, Gemma 3, gpt-oss, Nemotron 3,
Granite 4.0 and Jamba replace most of their attention layers with mechanisms whose
memory does not grow with context at all, or grows only to a small
window. Calculators that don't know this overshoot by 2× on gpt-oss, 6× on Gemma 3,
and up to ~70× when a Multi-head-Latent-Attention model is read as plain GQA.
For Kimi K3 the public estimates we surveyed span a 200× range —
from "~6,060 GB" (a parameter-count heuristic) to the correct ~8 GB of KV at
256K context.
Three memory classes, one formula
A hybrid model's per-sequence attention memory is the sum of three classes (this is also exactly how vLLM's hybrid KV-cache manager allocates it):
per_seq = Σ full-attention layers × per_token_bytes × context # grows with context
+ Σ sliding-window layers × per_token_bytes × min(context, W) # capped at the window
+ Σ linear/SSM layers × state_bytes # constant — never grows
per_token_bytes is the classic 2 × kv_heads × head_dim × dtype_bytes
(or the MLA latent kv_lora_rank + rope_dim), and state_bytes is a
fixed recurrent state each sequence owns — it scales with the number of
concurrent users, not with tokens. That last property changes capacity planning:
at high concurrency the state term is a per-slot price, while the token-KV term is
a per-token price.
State formulas per mechanism
All variable names map directly to fields in each model's config.json.
| Mechanism (models) | Recurrent state per layer | Worked example (bf16) |
|---|---|---|
| KDA — Kimi K3, Kimi Linear, Ling 3.0 | heads × head_dim² + conv 3·heads·head_dim·(k−1) |
Kimi Linear: 32×128² ≈ 1 MiB/layer → 20 KDA layers ≈ 21 MiB, at any context |
| Gated DeltaNet — Qwen3-Next, Qwen3.5, Qwen3.6 | v_heads × dk × dv + conv (2·k_heads·dk + v_heads·dv)·(k−1) |
Qwen3-Next-80B: 32×128×128 = 1 MiB/layer → 36 layers ≈ 38 MiB |
| Mamba-2 — Nemotron 3/H, Granite 4.0, Falcon-H1 | d_inner × d_state + conv (d_inner + 2·groups·d_state)·(k−1) |
Nemotron-H: 16384×256 = 8 MiB per layer per sequence — at batch 50 the states alone are ~20 GB |
| Sliding window — Gemma 3 (W=1024, 5:1), gpt-oss (W=128, 1:1), Muse Glimmer (W=2048, 3:1) | ordinary KV, but capped: per_token_bytes × min(ctx, W) |
gpt-oss-120b @131K: 18 SWA layers hold 4.5 MiB total instead of 4.5 GB |
| MLA + DSA — DeepSeek V3.2 | latent KV unchanged (kv_lora_rank+rope = 576/token/layer); DSA adds an indexer cache ≈132 B/token/layer |
sparse attention cuts compute, not KV — the cache actually grows ~20% |
Two traps worth knowing: Falcon-H1 is a parallel hybrid — every layer runs attention and SSM, so there is no KV saving at all; and MiniMax M2 abandoned its predecessor's linear attention entirely — it is a classic full-attention model.
Naive vs actual — six models
fp16 KV cache, one sequence. "Naive" bills every layer as full GQA attention over the whole context.
| Model | Context | Naive | Actual | Error |
|---|---|---|---|---|
| gpt-oss-120b | 131K | 9.7 GB | 4.8 GB | 2× |
| Qwen3-Next-80B | 262K | 24 GB | 6.0 GB | 4× |
| Gemma 3 27B | 131K | 62 GB | 10.4 GB | 6× |
| Muse Glimmer 30B | 131K | 6.4–25 GB | 1.6 GB | 4–16× |
| Kimi Linear 48B | 1M | 432 GB | 7.9 GB | ~55× |
| DeepSeek V3.2 (MLA read as GQA) | 131K | ~625 GB | 8.6 GB | ~70× |
The Muse Glimmer row explains a famous thread: the model "actually fits on a single RTX 3090 at 128K context" precisely because 39 of its 52 layers cap at a 2048-token window and its full layers use only 2 KV heads — a naive reading predicts it can't.
What the runtimes actually allocate
The formula above is the physics; serving engines add their own billing rules:
- vLLM (v1 hybrid KV manager, ≥~0.21): SWA layers are billed at the window (older versions billed the full context — upgrading gave Gemma-class models a measured 9.5× KV-capacity jump); state pages are padded to a unified page size, which on small hybrids can over-allocate up to ~7× (open issue #37121).
- llama.cpp: window-sized SWA cache by default (
--swa-fullrestores full-context allocation for context reuse); recurrent states live per sequence slot and are not covered by KV-cache quantization. - SGLang / TensorRT-LLM: recurrent state defaults to float32 — twice the footprint of vLLM's bf16 for the same model.
- MTP (multi-token prediction, shipped by DeepSeek, Qwen3.5, Ling): the draft head is a real attention block — one extra layer of KV plus its weights when speculation is on.
Try it yourself
Our calculator models all three memory classes (and shows the naive-vs-actual delta
on every hybrid result). A pre-configured example —
gpt-oss-120b at 131K context —
or import any HuggingFace model: the hybrid layer mix is auto-detected from
config.json for all five encoding styles the 2026 model zoo uses.
Sources
- Kimi Linear / KDA: arXiv:2510.26692; model configs on HuggingFace (moonshotai/Kimi-Linear-48B-A3B, moonshotai/Kimi-K3)
- Gated DeltaNet geometry: Qwen/Qwen3-Next-80B-A3B and Qwen3.5/3.6 config.json (
linear_num_value_headsfamily) - Mamba-2 hybrids: nvidia/Nemotron-H & Nemotron-3, ibm-granite/granite-4.0-h, tiiuae/Falcon-H1 configs
- vLLM hybrid KV cache manager: design doc; PyTorch blog “Hybrid Models as First-Class Citizens in vLLM”; issues #37121, #43308
- llama.cpp iSWA: PR #13194, discussion #24543; SGLang hybrid support: PyTorch blog “Hybrid Models Meet SGLang”
- DeepSeek V3.2 DSA byte layout: vLLM & LMSYS day-0 posts (2025-09-29)
- All formulas are regression-tested in this site's calculator against the worked examples above.