Caching LLM applications
In an LLM application the model call is usually the most expensive thing that happens per request. It costs money per token (the word-pieces a model reads and writes), it takes seconds where a database read takes milliseconds, and much of the work is repeated: the same instructions at the top of every prompt, the same questions from different people, the same document read again and again. Caching an LLM application means finding those repeats and not paying for them twice.
There isn’t one LLM cache. There are four, at different depths, and they save different things:
| Layer | Keyed on | A hit skips, and so saves |
|---|---|---|
| Exact response cache | The whole request, byte for byte | The model call: all tokens, all latency |
| Semantic cache | The meaning of the question | The model call: all tokens, minus one embedding |
| Prefix cache | The identical start of the prompt | Re-reading that start: input-token cost and time to first token |
| KV cache | Tokens already processed in this generation | Recomputing them for every new token: work that would grow at least with the square of the length |
The first two sit in your application and decide whether to call the model. The last two sit in the inference engine, the server software that runs the model, and make the call cheaper when it happens.
Exact response cache
The simplest cache: if this exact request was answered before, return the stored answer. The only hard part is the key, the string the cache looks an answer up by. A response depends on everything that went into the model, so the key has to contain all of it:
- the model and its version,
- the system prompt (the standing instructions the application puts in front of every conversation),
- the tool definitions (the functions the model is allowed to call, described in the prompt),
- any retrieved documents pasted into the prompt,
- the conversation so far,
- the sampling settings (temperature, which sets how much randomness goes into picking each token, and the maximum length),
- the user’s message.
Leave one out and the cache returns an answer produced under different conditions. The classic bug is a key built from the user’s message alone: the system prompt changes, and the cache keeps serving answers written under the old one.
Two properties make this layer different from caching an ordinary function.
The same input doesn’t always give the same output. With sampling on, the same prompt gives different answers on different calls, and even at temperature zero most servers don’t promise identical output, because batching and floating-point arithmetic vary from call to call. A cache freezes one sample. Usually that’s a feature: the same question gets the same answer, which is easier to test and to explain. But it means the first answer stored is the one everybody sees, so a bad one is repeated, not just produced once.
Exact matches are rare in free text. “What time do you open on Saturday?” and “what time do you open on saturday” are different strings. Normalising the text (case, whitespace, punctuation) helps a little. Real traffic phrases the same need in dozens of ways, so exact caches work best where the input is generated by a program: a fixed classification prompt run over the same records, a summary of a document that hasn’t changed, a nightly batch job re-run after a crash.
Semantic cache
A semantic cache matches questions by meaning instead of spelling. It uses an embedding model: a small model that turns a piece of text into a list of numbers (a vector, typically a few hundred to a few thousand numbers long) such that texts with similar meaning get vectors pointing in similar directions. How close two vectors are is measured as cosine similarity: 1 for the same direction, 0 for unrelated directions, −1 for opposite. In practice text embeddings rarely use the whole range; unrelated texts often still score well above 0, and where “unrelated” sits depends on the embedding model.
The cache stores pairs of (question vector, answer). For a new question:
- Embed the question.
- Find the nearest stored question vector.
- If its similarity is at or above a threshold τ, return the stored answer. Otherwise call the model, and store the new pair.
“What time do you open on Saturday?”, “are you open saturday morning” and “Saturday opening hours?” land close together, so one model call answers all three. An embedding is far cheaper than a generation, usually by two or more orders of magnitude per request, because the embedding model is small and reads only the question while the generation reads the whole prompt and writes the answer. A vector lookup takes milliseconds, so a hit replaces seconds of generation with almost nothing.
The threshold is the whole design
Everything depends on τ, and it trades two errors against each other:
- Too high: only near-identical phrasings hit. The cache is safe and almost useless.
- Too low: questions that are close in wording but different in meaning hit. “How do I cancel my order?” and “How do I uncancel my order?” are a few characters apart and often score high. So do questions that differ only in a number or a name: “refund status for order 4411” against “refund status for order 4417”.
A false hit is worse than a miss. A miss costs one model call. A false hit gives a confident, fluent answer to a question nobody asked, and the user can’t tell. So τ is chosen from data, not guessed:
- Collect real question pairs and label each one same answer or different answer.
- Score every pair with the embedding model.
- Pick the lowest τ at which the share of different answer pairs above it is acceptable to you.
Then run the cache in shadow mode first: look up and log what it would have returned, still call the model, and compare. That measures the real hit rate and false-hit rate before any user sees a cached answer.
Making semantic hits safer
- Cache the first message, not the conversation. “And how much is it?” means nothing without the turn before it. A follow-up’s meaning depends on context the embedding of that one message doesn’t contain. First messages are also where the repetition is: many people ask one question and leave.
- Pull out the variable parts. Questions that differ only in an order number, a date or a city shouldn’t share an answer. Extract those values first, embed the rest, and either key on them exactly or keep them out of the cached answer so the answer is a template filled in per request.
- Store a chosen answer, not the first one. Group similar past questions into clusters, and for the biggest clusters write or pick one good, general answer. The cache then serves curated text instead of whatever the model happened to produce first.
- Partition the cache. Answers that depend on who is asking (their account, their language, their permissions) must never cross between users. Put the tenant (the customer or organisation the request belongs to), the locale and anything else that changes the answer into an exact part of the key, and search for similar questions only within that partition.
- Add a second check where it matters. For high-stakes answers, a hit above τ can go to a cheap verifier that confirms the two questions really ask the same thing before the stored answer goes out.
Prefix cache
Before a model writes its first word it reads the whole prompt, and that reading (the prefill) costs compute in proportion to the prompt’s length. Most prompts in an application start the same way: the same system instructions, the same tool definitions, the same reference document, the same conversation history from the previous turn. A prefix cache keeps the model’s internal state for a prompt start it has already read, and the next prompt that begins with exactly the same tokens picks up from there.
The rule is strict: the reuse runs from the first token up to the first token that differs, and stops there. Everything after a difference is recomputed, even if the rest is identical. That gives one design rule that matters more than any setting:
Put what never changes first, and what changes last. Stable instructions, then tool definitions, then long reference material, then the conversation, then the new message. A timestamp, a request ID or a user’s name near the top of the prompt makes every prompt unique from that point on and turns the cache off for everything below it.
What it saves, and what it doesn’t:
- It saves input work. Hosted services that offer it typically bill cached prompt tokens at a fraction of the normal input price (some charge extra to write an entry), and the time to the first token drops because the long prefill is skipped.
- It doesn’t save output work. The answer is still generated token by token at full cost. A prefix cache helps most when prompts are long and answers short: long documents, big tool lists, long chats.
- It is short-lived. The stored state lives in the memory of the accelerator (the GPU or similar chip that runs the model), which is scarce. Hosted services typically expire an entry after a few minutes without a hit; a self-hosted engine evicts whenever it needs the space. It is a cache for traffic that repeats within minutes, not a store.
- It is stored in blocks. Engines keep the state in fixed-size blocks of tokens (the size varies by engine, from a handful of tokens to a few hundred) and reuse whole blocks, so a match is rounded down to a block boundary. Many services also set a minimum prompt length below which nothing is cached.
A prefix cache doesn’t change what the model computes, only whether it computes it again, so it is safe to use by default. The only design work is the prompt order.
KV cache
Inside every single generation there is one more cache, and without it generation would be impractically slow. A transformer, the architecture behind today’s language models, writes one token at a time, and each new token looks back at every token before it. For each earlier token, each layer of the model computes two vectors, a key and a value, that the new token uses to decide what to pay attention to.
Those keys and values don’t change once computed. The KV cache stores them, so each new token computes its own key and value once and reuses everyone else’s. Without it, writing token number 1,000 would mean recomputing keys and values for the 999 before it, and the total work for an answer would grow at least with the square of its length.
The price is memory. The KV cache grows with every token of the prompt and the answer, for every request running at the same time, and in long-context serving it can take more accelerator memory than the model’s weights. Prefix caching is this same structure kept after the request finishes, so another request can start from it.
Around the model
Two more caches are cheap and often forgotten:
- Embedding cache. The same text through the same embedding model gives the same vector. Key on a hash (a short fingerprint) of the text plus the model version and never compute an embedding twice, which matters when documents are re-indexed or the same snippets recur.
- Tool and retrieval cache. An agent (a model calling tools in a loop) that looks up the same order, searches the same query or reads the same page several times in one task can reuse the results within that task. Keep the lifetime short: tool results describe the world, and the world changes.
When the cache must forget
An LLM cache has more reasons to go stale than a normal one, because the answer depends on more than the question:
- The model changes. A new model or version should start with an empty response cache. Put the version in the key and old entries simply stop matching.
- The prompt or tools change. Same: they are part of the key.
- The facts change. An answer that quoted a price, a stock level or an opening time is wrong when the fact changes, whatever the question. Tag cached answers with the facts they depend on and purge by tag when one changes, or give them a time limit no longer than the facts stay true.
- The answer was bad. A response cache repeats mistakes. Keep a way to delete one entry, or one cluster’s answer, without flushing everything.
Never cache what must not be repeated: answers built from one user’s private data (unless the cache is partitioned per user), answers about the current time or live state, and anything with side effects. A cached “your payment was sent” is a lie the second time.
Does it pay?
For the two application-side caches the arithmetic is short. Let CL be the cost of a model call, CE the cost of an embedding plus lookup, and h the hit rate. A semantic cache pays the lookup on every request and the model call only on misses:
cost per request = CE + (1 − h) · CL
That beats always calling the model whenever h > CE / CL. With an embedding at a hundredth of a generation, a 1 % hit rate already breaks even, and a 40 % hit rate cuts the cost by about 39 %. Latency works the same way: every request pays tens of milliseconds for the embedding and the lookup, and hits skip seconds.
The cost case is almost always made. What decides whether a semantic cache is worth running is the other number: the false-hit rate, which the arithmetic above doesn’t contain, and which only the labelled pairs and the shadow run can tell you.
Things that go wrong
- An incomplete key. The response cache ignores the system prompt, the tools or the model version, and serves answers written for a different configuration.
- A threshold copied from somewhere else. Similarity scores aren’t comparable across embedding models, languages or domains. A τ that works for short support questions in one language can be badly wrong for another.
- Negations and numbers. “Can I” and “can’t I”, order 4411 and order 4417: close vectors, opposite answers. The variable parts need exact handling.
- Follow-ups matched on their own. A short follow-up like “and for two people?” matches a stored question it has nothing to do with.
- A changing prompt start. A date, a counter or a shuffled list near the top of the prompt makes every prefix unique and the prefix cache never hits. Nobody notices, because nothing breaks; it is only slower and more expensive.
- Cross-user leaks. A personalised answer cached without the user in the key is served to the next person who asks something similar.
- A cache that learns from its own mistakes. If every model answer is stored automatically, one bad answer to a popular question becomes the answer for everybody until someone notices. Curate the answers for the big clusters, and keep a way to remove one.