KV Cache Sizing
How a context length in tokens becomes bytes of KV cache, how tensor parallelism splits it, and how to choose storage.block_size.
This page shows how to turn a benchmark cell such as c=128, 64K into bytes of
KV cache, how those bytes divide across GPUs, and how to choose the MemKV slot
size that holds them.
64K is tokens, not bytes
In inference benchmark tables, the context column is the prompt length in
tokens. A row that reads 64K means 65,536 tokens of input. It has
nothing to do with KiB, MiB, or GiB.
A token is a unit of text that the model's tokenizer produces. The Llama-3.1 tokenizer has 128,256 entries, and English averages about 0.75 tokens per word. So a 64K-token prompt is about 50,000 English words, or a 200-page book.
The number of bytes depends on the model. For Llama-3.1-70B at BF16, a 64K prompt needs 20 GiB of KV cache per session. The rest of this section shows the calculation.
What the KV cache is
For each token, a transformer computes key (K) and value (V) vectors in every layer and keeps them, so that later tokens can attend to earlier ones without computing them again. These stored vectors are the KV cache.
Every token, whether from the prompt or generated, adds one K and one V vector per layer per KV head. So the KV cache grows linearly with context length: each token adds a fixed number of bytes.
The formula
For one session (batch size 1) with a context of N tokens:
KV bytes per session = 2 × N × num_layers × num_kv_heads × head_dim × bytes_per_elem
│ │ │ │ │ └── 2 for BF16/FP16
│ │ │ │ └────────────── e.g., 128
│ │ │ └─────────────────────────── after GQA reduction
│ │ └──────────────────────────────────────── e.g., 80 for 70B
│ └─────────────────────────────────────────────── tokens of context
└─────────────────────────────────────────────────── K and V are stored separatelyFor one token of context:
KV bytes per token = 2 × num_layers × num_kv_heads × head_dim × bytes_per_elemLMCache's calculator publishes this per-token value for each model. For Llama-3.1-70B:
2 × 80 × 8 × 128 × 2 = 327,680 bytes = 320 KiB per token (≈ 0.31 MiB)LMCache's KV Cache Calculator gives the same number for Llama-3.1-70B at BF16. VMware's LLM Inference Sizing and Performance Guidance derives the same per-token figure and uses it to compute "40 GB of KV cache for a single Llama-3.1-70B request at 128K context."
Do not drop a factor of 2. At BF16 the formula has two factors of 2:
one because K and V are stored separately, and one for bytes_per_elem = 2.
Some references write 2 × num_layers × num_kv_heads × head_dim and get half
the correct number. If your result is exactly half of LMCache's, this is the
cause.
Worked example: Llama-3.1-70B
The model's Hugging Face config.json gives:
| Field | Value |
|---|---|
num_hidden_layers | 80 |
num_key_value_heads | 8 (GQA — fewer than the 64 attention heads) |
head_dim | 128 |
| dtype | bfloat16 (2 bytes/element) |
Per-token KV (total across all GPUs)
2 × 80 × 8 × 128 × 2 = 327,680 bytes = 320 KiBPer-session KV at common context lengths
| Context | Tokens | KV per session |
|---|---|---|
| 4K | 4,096 | 1.25 GiB |
| 16K | 16,384 | 5.00 GiB |
| 32K | 32,768 | 10.00 GiB |
| 64K | 65,536 | 20.00 GiB |
| 128K | 131,072 | 40.00 GiB |
Each value is the total KV cache for one session, summed across all the GPUs that hold part of it.
Tensor-parallel sharding
With tensor parallelism (TP) of N, the KV heads are split across N GPUs. The
number of KV heads must divide evenly by TP (num_kv_heads % TP == 0), so for
Llama-3.1-70B, with 8 KV heads, TP can be 1, 2, 4, or 8.
At TP=8, each GPU holds 1 KV head. The KV per token on each GPU is:
2 × num_layers × (num_kv_heads / TP) × head_dim × bytes_per_elem
2 × 80 × (8 / 8) × 128 × 2
= 40,960 bytes = 40 KiB per token, per GPUFor one session at 64K context:
65,536 × 40 KiB = 2,621,440 KiB = 2.5 GiB per GPUAcross the 8 GPUs, that is 2.5 × 8 = 20 GiB total per session: the same
total as before, split eight ways. TP divides the KV cache across GPUs. It does
not change the total.
Adding concurrency
A load generator run with --concurrency C keeps C requests in flight. Each
request has its own KV cache, so the total live KV cache is:
total live KV = C × KV_per_sessionFor example, at c=128 and 64K context:
128 × 20 GiB = 2,560 GiB = 2.5 TiB of live KV state across the clusterAn 8× H200 server has 8 × 141 GB ≈ 1.05 TiB of HBM, and about 0.93 TiB of that is left for KV after the 70B model weights. 2.5 TiB is far more than 0.93 TiB, so most sessions cannot stay in HBM. They must be evicted to an offload tier or computed again on the next request. This is where an external KV cache tier is needed.
The agentic sweep in inference performance measures this point. It raises concurrency step by step for each model. Below the point where live KV exceeds HBM, the tier costs throughput. Above it, the tier keeps the GPUs decoding instead of computing prefill again. Use the calculation on this page to find which side your deployment is on.
When MemKV helps
MemKV helps in one way: it saves prefill compute on the GPU. Each KV block read from MemKV replaces prefill that the GPU would otherwise run. The longer the prefill it replaces, and the more often a prefix repeats, the larger the gain.
MemKV needs prefix reuse. If almost every prompt is unique, MemKV has nothing to return: each request still runs full prefill on the GPU, and storing its KV cache only adds I/O.
MemKV helps with:
- Long prompts where prefill is the largest cost, such as RAG over large documents, codebases in the context, and transcripts.
- Prefixes reused across requests, such as shared system prompts, multi-turn conversations, agent loops, and batch evaluation over the same context.
- Traffic larger than the prefix cache in GPU HBM, so prefixes are evicted and would otherwise be computed again.
MemKV does not help with:
- Mostly unique prompts with no shared prefix.
- Short prompts, where prefill is already cheap.
- Working sets that fit in HBM and are never evicted. This case has a cost: MemKV still stores KV but never reads it back, and the measured cost is a median 15% of throughput, up to 30% on the largest models. Size the deployment before you enable the tier.
How KV blocks reach MemKV
The inference engine stores the KV cache in fixed-size blocks. Recent vLLM
releases use 16-token blocks by default (set with --block-size). For
Llama-3.1-70B at TP=8, one vLLM block on one GPU is:
16 tokens × 40 KiB/token = 640 KiB per block per GPUThe engine groups consecutive blocks into larger transfers, so each transfer to MemKV is several MB to tens of MB. At these sizes MemKV reads at high bandwidth.
The client sends up to max_batch_size keys to a server in one control
message. The control messages use RC (Reliable Connection) queue pairs. The
server then moves the KV data for each key over RDMA, using DC (Dynamically
Connected) transport on mlx5 NICs and RC on other NICs. A DCI (DC initiator) is
one sender on the DC transport. These settings control the batching and
parallelism:
| Knob | Default | Configured via | What it changes |
|---|---|---|---|
max_batch_size | 64 | MEMKV_MAX_BATCH_SIZE env / MEMKV_CONFIG yaml | Ops per BatchRead/BatchWrite control message |
num_connections | 8 | MEMKV_NUM_CONNECTIONS env / MEMKV_CONFIG yaml | Per-server RC connection pool size; bounds in-flight batches per server |
| Client DCI pool | 256 | client-side, fixed | Concurrent DC data transfers fanned out within a batch on the client |
| Server DCI pool | 256 | rdma.num_dcis in /etc/memkv/config.yaml | Server-side DCI pool; size to expected fan-in across clients |
Start with the defaults and change one setting at a time. A larger
max_batch_size means fewer round trips but larger messages and more CPU
work. When a batch message grows past the control buffer, the client sends
each key separately instead, and latency rises sharply. More
num_connections uses more memory on each server. The right values depend
on how many parallel transfers the engine makes, the size of each value, and
the fabric latency.
For 70B BF16 at TP=8, 64K context, and c=128, this gives about:
- Per request: 20 GiB in total, 2.5 GiB per GPU read from MemKV.
- In total: 2.5 TiB of live KV cache. The part that does not fit in HBM goes to MemKV.
- On the network: batches of up to
max_batch_sizekeys, with several batches in flight on the connection pool.
None of this involves "64 kilobytes."
Choosing storage.block_size
storage.block_size is the size of one slot on the drive. MemKV gives
every value slots of its own:
- A value smaller than a slot takes one slot. The rest of the slot stays empty, and no other value can use it.
- A value larger than a slot is split across as many slots as it needs. Only the last of them is partly empty.
So the drive is used well when a value fills its slot or spans several
slots, and wasted when a value is much smaller than a slot. This applies
to the direct and file storage modes.
How much of a slot a value can use depends on the mode:
- In
directmode, keys are stored outside the slots, so a value fits in one slot if it is at mostblock_size. - In
filemode, each slot also holds a 544-byte header (32 bytes plus room for the longest key), so a value fits in one slot if it is at mostblock_size − 544bytes. A value of exactly 1 MiB therefore takes two 1 MiB slots infilemode.
What one value is
The value size comes from the engine, not from MemKV. For vLLM:
- With direct offload (
MemKVOffloadingSpec), one value is one GPU KV block from one tensor-parallel worker:vLLM block size in tokens × KV bytes per token per GPU. - With CPU tiering (
TieringOffloadingSpec), one value is one offloaded block across all tensor-parallel ranks, sized by theblock_sizeyou give the connector.
The KV bytes per token come from the formula earlier on this page, and depend on the model, the KV data type and the tensor-parallel size.
Measure your value size
You do not need to work the size out. After vLLM has stored some KV,
divide memkv_write_bytes_total by memkv_write_total from a MemKV
server's /v1/metrics. The vLLM plugin writes one value per request, so
this average is the value size. The counters cover every client of the
server, so take the reading while vLLM is the only one writing to it.
If the value size is a small fraction of block_size, most of each slot
is empty, and a smaller block_size would hold more KV on the same
drive.
Examples
| Workload | Value size | 4 MiB slots | 1 MiB slots |
|---|---|---|---|
| DeepSeek-V4-Flash, direct offload, TP=2 (measured on two DGX Sparks) | 1,002,240 bytes | 1 slot, 24% used | 1 slot, 96% used |
| Llama-3.1-70B BF16, direct offload, TP=8, 16-token vLLM blocks | 640 KiB | 1 slot, 16% used | 1 slot, 63% used |
| Qwen2.5-32B FP16, CPU tiering, 64-token offload blocks | 16 MiB | direct: 4 slots, 100% usedfile: 5 slots, 80% used | direct: 16 slots, 100% usedfile: 17 slots, 94% used |
For the DeepSeek-V4-Flash case, the console of a server with 1 MiB slots showed 5,669 stored blocks using 5.5 GiB, exactly one slot each. With the default 4 MiB slots, the same drive holds a quarter as much KV.
Which size to use
- Keep the default
4 MiBfor production unless you have measured your values. Large models, and offload blocks of many tokens, produce values of several megabytes, which 4 MiB slots hold with little waste. Fewer, larger slots also mean less metadata per byte stored. - If your values are well under 4 MiB, use the smallest size that holds one value in one slot. For DeepSeek-V4-Flash with direct offload that is 1 MiB, which stores four times as much KV.
- In
filemode, leave room for the 544-byte header. If your value is exactly a power of two, choose the next size up.
Changing the size
The slot size is fixed when MemKV formats a drive or creates a file. Changing it means starting with an empty cache:
filemode. If the config'sblock_sizediffers from the file's,memkv startstops with ablock size mismatcherror. Stop MemKV, move the old file away or delete it, and start MemKV again. It creates a new file with the new slot size.directmode. A formatted drive keeps the slot size it was formatted with, whatever the config says. To change it, reformat withmemkv setup --drives <device...> --force --shard-size <KB>:--shard-size 1024gives 1 MiB slots and--shard-size 4096gives 4 MiB. This erases the drives.
Reference numbers for other Llama-family models
Per-token KV at BF16, total across all GPUs:
| Model | Layers | KV heads | Head dim | Per token |
|---|---|---|---|---|
| Llama-3.1-8B | 32 | 8 | 128 | 128 KiB |
| Llama-3.1-70B | 80 | 8 | 128 | 320 KiB |
| Llama-3.1-405B | 126 | 8 | 128 | 504 KiB |
| gpt-oss-120b (MoE) | 36 | 8 | 64 | 72 KiB (half the layers cache full attention; the rest are sliding window — see config) |
For any model, use the same formula with the values from its config.json.
Check the result against LMCache's KV
calculator before you quote it.
Summary
When you see a benchmark cell like c=128, 64K:
- 64K = 65,536 input tokens (not bytes, not anything else).
- c=128 = 128 simultaneous in-flight requests
(
--concurrency 128on the load generator). - The bytes follow from
2 × N × num_layers × num_kv_heads × head_dim × bytes_per_elemfor one session, multiplied bycfor the aggregate. - For any specific model, run that arithmetic with the model's
num_layers,num_kv_heads,head_dim, anddtype— or paste it into the LMCache calculator.
Sources
- LMCache — KV Cache Size Calculator — canonical per-model KV per-token reference
- VMware Cloud Foundation Blog — LLM Inference Sizing and Performance Guidance
- Spheron — GPU Memory Requirements for LLMs: VRAM Calculator
- AWS Neuron — Training Llama-3.1-70B with Tensor Parallelism — TP sharding constraints
- JAX Scaling Book — Serving LLaMA 3-70B on TPUs — KV-head sharding strategy
- Hugging Face — meta-llama/Llama-3.1-70B-Instruct config.json — the architectural source of truth