AI Grimoire
Sheet
statusstandard
difficultyintermediate
timeO(1)
described2023
revised5w ago

Prefix Caching

Two requests with the same system prompt compute the same keys and values for it. Hash the blocks, keep them, and the second request skips straight to the part that differs.

[kv-cache][serving]Current standard

Standing

Current standardPart of the default recipe. Present in most frontier models trained today, and the thing a new design departs from rather than argues for.

Standard in serving, and the single largest win available for workloads with a shared system prompt.

judged as of 2026-09 · what the labels mean

Theory

Under a causal mask, the key and value vectors at position ii depend only on tokens i\le i. Two sequences sharing a prefix therefore produce identical KV entries across that prefix — not similar, identical.

x1:i=x1:i    (K,V)1:i=(K,V)1:ix_{1:i} = x'_{1:i} \;\Longrightarrow\; (K, V)_{1:i} = (K', V')_{1:i}
eq. 1 — the property the whole technique rests on

Given a block-structured cache, the implementation is a hash table. Each block is keyed by the hash of its own tokens chained with the hash of the block before it, so a match implies the entire prefix matches.

hb=H(hb1tokensb)h_b = H\bigl( h_{b-1} \,\|\, \text{tokens}_b \bigr)
eq. 2 — chained block hash

Blocks are reference-counted and evicted LRU when the pool runs short. A radix tree over the token sequence gives the same result with cheaper longest-prefix lookup, which is what SGLang uses.

What breaks it

  • Anything that varies at the front. A timestamp or user id in the system prompt gives every request a unique first block and a 0% hit rate. Put the variable part last.
  • Position-dependent state that is not positional. RoPE is applied to queries and keys at their absolute index, so a cached block is only valid at the position it was computed for. Reusing a block at a different offset is wrong, and it is wrong silently.
  • Sampling parameters. These do not affect KV at all, and a cache keyed on them is throwing away hits for nothing.

The security note is not hypothetical. If the cache is shared across tenants, the time to first token is an oracle for “has anyone recently sent this prefix”. Partition the cache per tenant unless the prefixes are public.

Implementation

python · sketch
import hashlib


class PrefixCache:
    """Maps a chained block hash to a physical KV block."""

    def __init__(self, block_size: int = 16):
        self.block_size = block_size
        self.blocks: dict[bytes, int] = {}
        self.refcount: dict[bytes, int] = {}

    def _hash(self, parent: bytes, tokens: tuple[int, ...]) -> bytes:
        payload = parent + b"".join(t.to_bytes(4, "little") for t in tokens)
        return hashlib.blake2b(payload, digest_size=16).digest()

    def match(self, tokens: list[int]) -> tuple[list[int], int]:
        """Returns physical blocks for the longest cached prefix."""
        hit, parent, matched = [], b"", 0
        for start in range(0, len(tokens) - self.block_size + 1, self.block_size):
            chunk = tuple(tokens[start : start + self.block_size])
            parent = self._hash(parent, chunk)
            if parent not in self.blocks:
                break
            hit.append(self.blocks[parent])
            self.refcount[parent] += 1
            matched += self.block_size
        return hit, matched

Only full blocks are cacheable — a partial trailing block has no stable hash because the next token will change it. Chaining the parent hash into each block is what makes a hit on block nn imply blocks 1n11 \dots n-1 matched too; hashing blocks independently would let two different prefixes collide onto the same cached block, which corrupts the output rather than merely slowing it down.

Prefill without
4200 tok
Prefill with
200 tok
TTFT
−94%
4k system prompt, 200-token user turn, 70B model

Related

References

[1]Zheng et al. — SGLang: Efficient Execution of Structured Language Model Programs (2023)arXiv:2312.07104
[2]Kwon et al. — Efficient Memory Management for LLM Serving with PagedAttention (2023)arXiv:2309.06180
[3]Gim et al. — Prompt Cache: Modular Attention Reuse for Low-Latency Inference (2023)arXiv:2311.04934