Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reference-grade guide to caching in LLM inference — provider prompt caching (Anthropic cache_control breakpoints, OpenAI automatic prefix caching, Gemini implicit/explicit), semantic caching, and KV-cache internals & management (PagedAttention/vLLM, RadixAttention/SGLang, eviction, quantized KV, memory pressure, multi-tenant safety). Concrete numbers, formulas, failure modes.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 177% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 365% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 271% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 341% | 0% |
Three distinct caches get confused under one word. They live at different layers, have different correctness properties, and fail differently.
| Cache | Layer | Match | Returns | Risk if wrong | |-------|-------|-------|---------|---------------| | Prompt cache | Provider / serving engine | Exact token-prefix | Skips prefill compute, regenerates output | None (cheaper, identical output) | | Semantic cache | Your app, in front of the API | Embedding similarity | A stored prior response | Serves a wrong answer | | KV cache | GPU, inside the model | Per-token K/V tensors | Enables O(1) decode | OOM / preemption |
Rule of thumb: prompt cache is exact + cheap + safe — on always. Semantic cache is fuzzy + risky — use surgically. KV cache is not optional (it's how decode works) and at scale dominates GPU memory.
The model's prefill phase computes K/V for every prompt token before the first output token, at cost O(prompt_length). Prompt caching stores that computed K/V keyed by an exact token prefix; on a hit the engine skips prefill for the cached span and resumes from the first divergent token. Matching is on exact tokens from position 0 — a prefix match, not fuzzy or substring. One changed byte invalidates everything from that byte onward. Prefill is the dominant cost for RAG/agent/long-context workloads where the prompt dwarfs the completion, so caching it turns repeated large prefixes into near-free reads.
Anthropic (explicit, cache_control breakpoints):
also checks shorter prefixes, so you don't need a breakpoint per turn.
Break-even: a write costs 0.25× extra (5-min), each read saves 0.9×, so a cached prefix pays for itself after ~1 reuse within the TTL (0.25 < 0.9). A 50-call agent loop reusing a 20k-token system+tools prefix pays 1.25× once + 0.1× × 49 instead of 1.0× × 50 — roughly an ~85% input-cost cut on that prefix.
OpenAI (automatic prefix caching):
increments (1024, 1152, 1280, …).
models advertise deeper discounts. No separate write surcharge.
prompt family to the same backend with a stable prompt_cache_key (or legacy user field) to raise hit rate.
usage.prompt_tokens_details.cached_tokens reports the hit size.Gemini:
portion, needs the common prefix at the start of the request) plus explicit caching (CachedContent — you set TTL, get a guaranteed discount + a storage fee per token-hour).
Order the prompt most-stable → most-volatile:
[ system instructions ] ← never changes ┐
[ tool / function defs ] ← changes per deploy │ cache this prefix
[ static context: docs, ← changes per session │ (breakpoint here)
schemas, few-shot ] ┘
------------------------------------------------- ← cache breakpoint
[ retrieved chunks ] ← per query ┐ keep AFTER the breakpoint
[ conversation history ] ← per turn │ so the stable prefix
[ user's current message ] ← per call ┘ survivespython# Anthropic: cache the big static prefix, leave volatile tail uncached messages = [{ "role": "user", "content": [ {"type": "text", "text": SYSTEM_AND_TOOLS}, # stable {"type": "text", "text": STATIC_DOCS, "cache_control": {"type": "ephemeral", "ttl": "1h"}}, # breakpoint {"type": "text", "text": user_query}, # volatile, uncached ], }]
Cache hit rate is the metric. Track: cache_read_tokens / (cache_read + cache_write + uncached_input). Healthy agent/RAG loops hit 60–90%+. A sudden drop to ~0% means the prefix went unstable (see failure modes).
Date.now() injected into the system prompt.swap (cache is per exact model).
Embed the incoming query → nearest-neighbour search in a vector store → if a stored entry is within a similarity threshold, return its cached response without calling the model.
pythondef semantic_lookup(query, threshold=0.92): q = embed(query) hit, score = vector_store.nearest(q) # cosine similarity if score >= threshold and not is_stale(hit): return hit.response # skip the LLM entirely resp = call_llm(query) vector_store.upsert(embed(query), resp, ts=now()) return resp
prompts, doc Q&A where many users ask the same thing different ways.
balance", "current price", "summarize this doc"). Two queries that look similar can require different answers.
is aggressive (more hits, real false-hit risk). Tune on labelled pairs, not vibes — measure precision of "same intent" at each threshold.
the cache serves the wrong stored answer. This is the defining hazard: embeddings collapse negation ("can I cancel" vs "can I not cancel"), numbers, and entities. Mitigate: raise threshold, add a cheap LLM/rule re-rank gate on borderline hits, key on normalized intent + critical entities, never cache responses containing user-specific data.
(price, policy, docs). Attach a TTL and a content-version tag; invalidate by version bump or event, not just time. Always cache with an embedding model version — re-embedding on model change is required.
| | Prompt cache | Semantic cache | |---|---|---| | Match | Exact prefix | Fuzzy (embedding) | | Returns | Recomputed correct output | Stored prior output | | Correctness | Always correct | Can serve wrong answer | | Saves | Prefill compute / input cost | Whole inference call | | Owner | Provider/engine | You | | Default stance | On always | Off until justified |
They compose: semantic cache catches whole-query repeats; prompt cache makes the misses cheaper. Layer semantic in front, prompt cache underneath.
During decode, each new token attends to all previous tokens' keys and values. Recomputing K/V for the whole sequence every step is O(n²) total. Instead the engine caches K and V per token per layer once, so each decode step only projects the one new token and attends against the cached set — O(1) in past-token work. The KV cache is what makes autoregressive decode tractable, and it grows by one token's worth of K/V every step.
kv_bytes = 2 · num_layers · num_kv_heads · head_dim · seq_len · batch · dtype_bytes
│
└─ the 2 is for K *and* Vnum_kv_heads is the GQA/MQA head count (often ≪ attention heads — this is a primary KV-memory lever). dtype_bytes: FP16/BF16 = 2, FP8/INT8 = 1.
Worked example — Llama-3-8B, 32 layers, 8 KV heads (GQA), head_dim 128, FP16:
2 · 32 · 8 · 128 · 2 = 131,072 B ≈ 128 KiB/token.128 KiB · 8192 ≈ 1.0 GiB. Batch of 32: ~32 GiB of KV —on top of ~16 GiB of FP16 weights. On an 80 GB A100/H100 you are KV-bound long before weight-bound.
Llama-3-70B (80 layers, 8 KV heads, head_dim 128, FP16): ~320 KiB/token → a single 32k-context request needs ~10 GiB of KV by itself. KV cache, not weights, sets the throughput ceiling at long context / high batch.
Takeaway: KV scales linearly with seq_len × batch; weights are fixed. Past a point, KV dominates total memory.
Naive serving pre-allocates a contiguous KV buffer sized to max_seq_len per request → internal fragmentation and reserved-but-unused memory (often 60–80% waste). PagedAttention borrows OS virtual memory: KV is split into fixed-size blocks (e.g. 16 tokens), stored non-contiguously, with a block table mapping logical → physical. Result: near-zero fragmentation, on-demand allocation, and copy-on-write block sharing for identical prefixes (parallel samples, beam search, shared system prompts). vLLM reports up to ~2–4× throughput vs naive allocation, mostly from packing more concurrent sequences into the same VRAM.
Caches prefixes in a radix tree keyed by token sequence, so any request sharing a prefix (system prompt, few-shot block, multi-turn history, tree-of-thought branches) reuses the computed KV instead of re-running prefill (LRU eviction on the tree). Automatic cross-request KV reuse — the server-side analogue of provider prompt caching. Huge for agent fan-out; hit rates of 50–90% are common. vLLM's --enable-prefix-caching does the same via hashed block reuse.
KV blocks are a finite pool. When full:
costs compute. Default for short prompts.
Saves recompute, costs PCIe bandwidth (offload ~tens of GB/s vs HBM ~TB/s). Better for long prompts where recompute is expensive.
Store K/V at 1 byte/elem instead of 2 → ~2× longer context or batch in the same VRAM. FP8 KV typically costs <1% quality; INT8 needs per-channel/per-token scales to stay clean. (--kv-cache-dtype fp8 in vLLM.)
Models with sliding-window attention (e.g. Mistral) attend only to the last W tokens, so KV is bounded at W — O(W) memory, not O(n). StreamingLLM keeps a few "attention-sink" initial tokens + a recent window to run effectively unbounded streams without OOM (dropping the true middle) — for long-running chats/logs where old context is expendable.
Persisting KV beyond one request (RadixAttention, vLLM prefix cache, or external stores like LMCache offloading to CPU/disk) turns repeated prefixes into reads — provider prompt caching implemented in your own serving layer when you self-host.
The core tradeoff on a fixed GPU:
VRAM ≈ weights + activations + KV(batch, context) (KV is the variable term)Since KV ∝ batch · context, you trade them against each other:
| Lever | Effect | Cost | |-------|--------|------| | ↑ batch size | ↑ throughput (tok/s aggregate) | ↑ KV → OOM risk, ↑ per-request latency | | ↑ context length | longer prompts/outputs | ↑ KV per seq → fewer concurrent seqs | | ↓ KV dtype (FP8) | ~2× capacity | small quality hit | | ↓ num_kv_heads (GQA/MQA) | big KV cut | model-architecture decision | | sliding window | bounded KV | loses long-range context |
OOM happens when active + cached KV exceeds the pool — usually triggered by a burst of long-context requests, not steady state. Defend with: a hard max_num_seqs / max_num_batched_tokens cap, gpu_memory_utilization headroom (e.g. 0.90, not 0.98), admission control / queueing, and preemption (swap or recompute) so the server degrades to higher latency instead of crashing.
Capacity planning (back-of-envelope):
kv_budget = (VRAM · util) − weights − activation_overhead
max_tokens = kv_budget / bytes_per_token # from §3 formula
max_concurrent ≈ max_tokens / avg_seq_lenPlan for p95 sequence length, not mean — long tails are what OOM you. Leave ~10–15% headroom for activation spikes and fragmentation.
| Cache | Leakage vector | Isolation | |-------|----------------|-----------| | KV prefix sharing | Tenant B reads tenant A's reused prefix KV → content/timing leak | Namespace the cache key by tenant; don't share blocks across tenants for sensitive prefixes | | Semantic cache | Cached response contains tenant A's PII, served to tenant B on a fuzzy match | Per-tenant cache partition; never cache PII-bearing responses | | Provider prompt cache | Same-org reuse only (providers scope to API key/org); a cache timing side-channel can reveal "someone cached this prefix" | Treat presence of a hit as low-sensitivity; don't put secrets in shared system prompts |
Rules:
A global prefix cache is safe only for genuinely shared content (the system prompt, public docs).
could match against.
(tenant_id, normalized_query) and exclude anyresponse embedding user data — or scope the vector index per tenant.
the cached prefix. Symptom: cache_read_tokens collapse, input cost spikes. → Move all volatile content after the breakpoint; make tool/JSON order deterministic; diff two consecutive prompts byte-for-byte.
entity difference collapsed by the embedding. → Raise threshold, add a re-rank/verify gate, key on critical entities, exclude personalized intents.
didn't. → Version-tag + event-driven invalidation, not TTL alone.
→ Cap max_num_seqs / batched_tokens, enable preemption (swap/recompute), FP8 KV, lower gpu_memory_utilization, admission-queue.
tenant's data to another. → Partition keys by tenant; never cache PII in a shared namespace.
cache. → Size cache to the working set, pin hot prefixes, or shorten the prefix.
cache-write surcharge. → Use the 1-hr TTL for long runs; hits refresh the TTL.
Do
correctness-wise and pays off after one reuse.
Don't
Other measured skills in the registry, with their headline benchmark lift.