The KV Cache is the second-largest consumer of HBM after weights, and it is what actually caps concurrency.
The formula
| Symbol | Meaning |
|---|---|
| layers | |
| KV heads | |
| head dim | |
| bytes per element (2 for FP16/BF16, 1 for FP8) | |
| , | sequence length, batch |
Per-token, per-sequence cost, memorize this one:
Reducing
With query heads and group size , GQA divides the cache by . See Attention Variants for Inference.
- MHA:
- GQA: , typically
- MQA:
MLA (latent compression)
Cache a single low-rank latent plus a small decoupled RoPE part , shared across all heads: Example , , , : KB/token, versus MB/token for equivalent MHA. ~57× compression. The cost is that K and V must be re-expanded (or the projections absorbed into /) inside the kernel.
Worked numbers
8B model: , , , .
| Scheme | 32K context, 1 seq | |
|---|---|---|
| MHA () | 512 KiB | 16 GB |
| GQA-8 () | 128 KiB | 4 GB |
| GQA-8 + FP8 KV | 64 KiB | 2 GB |
| MQA () | 16 KiB | 0.5 GB |
How it caps batch size
80 GB device, GB, 6 GB workspace/activations/graphs → 66 GB free. With KiB that is 503K cached tokens: 245 sequences at 2K context, or 61 at 8K, or 15 at 32K. Concurrency falls linearly in context length.
Fragmentation eats into this unless you use Paged Attention; reuse recovers some via Prefix Caching.
Rule of thumb
. Divide free HBM by it to get your total token budget, that number, not “batch size”, is what the scheduler is really allocating.