Your Cache Hit Rate Is Mostly Measuring the System Prompt
Three independent reports move the cost lever from the model to the allocator, and each one quietly turns cache warmth into a confound your experiment arms are not logging.
Caching pays for itself at these prices; the cost sits in silent invalidation
Anthropic and OpenAI both price prompt caching at 1.25x the base input rate to write and 0.1x to read. Solve 1.25 + 0.1n against 1 + n and break-even arrives when a written entry is read about 0.28 times inside its TTL, roughly a one-in-four chance. Broad enablement is the correct default, which is why nobody audits it afterward.
Invalidation is where the money goes, and it goes quietly. On a miss, Anthropic walks backward through a capped 20 blocks looking for an older write; put more conversation than that between two calls and the hits stop with no error, and the extra spend gets attributed to traffic growth instead. Daily Dose of Data Science lists the rest of the surface, and every item is routine optimization-sprint work: a timestamp or request ID at the front of the prompt, tool schemas reordered ahead of the system prompt, history summarization rewriting the prefix, and provider toggles — web search, citations, thinking config, tool_choice — that rewrite the rendered prompt text. Truncating tool outputs in place is the one edit that leaves the prefix byte-identical.
The router arithmetic can invert
Unwind AI relays a constraint that should sit in the architecture diagram: cache entries are model-scoped. Route an in-flight session to a cheaper model and the whole accumulated history re-prefills at cold rates. On a 100K-token agent session heavy with tool output, one switch can erase whatever per-token arbitrage the cascade earned. The design correction is that the unit of routing is the session, not the request: classify once at session start, partition context so cheap and expensive models never share a prefix, or accept the re-billing and measure it.
| Layer | What it stores | Key | Cost of a miss |
|---|---|---|---|
| KV cache | Attention K/V tensors, one request | Request lifetime | Always on |
| Prefix caching | Same tensors, server-side blocks | Chain hash over token IDs | Prefill compute and latency |
| Prompt caching | Same tensors, provider-held | Exact prefix on rendered context | 1.0x input rate instead of 0.1x |
| Semantic caching | Finished response strings | Cosine similarity over an embedding | Correctness, plus an embedding round trip |
Only the last row can miss and still return a wrong answer with a 200 status code, and it is the only layer that keeps billing an embedding call on every miss. Published similarity thresholds span 0.75 to 0.97, a 22-point spread that reflects local traffic, so a threshold lifted from someone else's write-up carries no information about yours. Some failures never respond to threshold tuning: negated sentences sit close together in embedding space, and two prompts sharing a sentence frame while differing in one operational value score as near-identical because the frame dominates the vector. The metric to track is a wrong-answer-rate-at-threshold curve built on roughly 500 adversarial pairs.
One clean experiment, and the capacity wall behind it
TLDR Data's block-paging result is the rare capacity win with no model-risk surface: fixed-size blocks plus a block table replace contiguous per-request allocation, and attention outputs do not change. A regression suite should return bit-comparable results, so no quality re-validation is owed. The thing the 2–4x spread doesn't tell you is where a given workload lands inside it; no hardware, model size, or sequence-length distribution was disclosed, and the range almost certainly tracks shared-prefix fraction and sequence-length variance. The prefix-sharing ratio in production sets the number.
Capacity planning is where this bites hardest. Decode is memory-bandwidth-bound: the whole KV cache streams from HBM on every decode step, and cache size grows linearly with tokens, about 40GB for a single 128K context on a 70B BF16 model, comparable to the entire model at 4-bit weights. The prefix cache draws from the same GPU pool as the running batch, so a bigger cache buys fewer concurrent sequences, and vLLM's LRU eviction takes the long shared prefixes first. Size on KV memory and bandwidth rather than FLOPs, and benchmark GQA or FP8 cache quantization on a local accuracy suite before enabling it. TheSequence's point lands here too: most teams emit neither MFU nor batch-fill, so they pay full depreciation for a minority of available FLOPs.
A miss on three of the four caches costs money; a miss on the fourth returns a wrong answer, so semantic caching needs its own wrong-answer metric alongside the hit-rate dashboards.
What to do
Log cached versus cold input tokens per experiment arm as a near-term priority, and carry per-arm cache warmth as a covariate in the A/B analysis pipeline.
Run a canonicalization experiment this sprint: sort retrieved chunks by deterministic chunk ID instead of relevance rank, keep the query last, encode ranking as text metadata, and measure prefix hit rate, prefill tokens saved, and answer faithfulness against the current build.
Re-derive the model-routing and cascade break-even this sprint with a cold-prefill term for the full accumulated context at the destination model.