No items found.
Start Building
Technical

vLLM Prefix Caching Explained: Reduce Latency for Repeated Prompts

Prefix caching is already on in your vLLM deployment. Here is what it actually does, how to tune it, and how to tell if it is earning its keep.

Author photo
packet.ai Team
August 5, 2026

vLLM's automatic prefix caching reuses the KV cache from a previous request's prompt when a new request shares the same starting tokens, skipping recomputation for anything already cached. It's enabled by default on vLLM's V1 engine as of recent releases, so most teams running vLLM already have it on without realizing it. This is a form of KV cache reuse and, more broadly, LLM prompt caching at the infrastructure level, distinct from the provider-side prompt caching offered by hosted APIs. This post covers what the mechanism actually does, how to confirm it's active and tune it, and how to read your own hit rate.

Key takeaways

  • Prefix caching is on by default on vLLM's V1 engine; the flag you'll actually reach for most often is --no-enable-prefix-caching to turn it off, not on
  • The cache is hash-based, not tree-based: each 16-token block is identified by a chain hash of its own tokens plus the hash of the block before it, so a hit requires an exact token-by-token match up to that point
  • Only full blocks get cached. A prefix that ends mid-block gets no credit for that partial block, which has real implications for how you structure prompts
  • The current V1 engine exposes hit rate as two Prometheus counters, vllm:prefix_cache_queries and vllm:prefix_cache_hits, not a single hit-rate gauge; the old gpu_prefix_cache_hit_rate_perc gauge is deprecated and being removed
  • Both vLLM and SGLang's RadixAttention solve the same prefix-reuse problem; they differ in cache data structure (flat hash table vs. radix tree), not in what they're fundamentally trying to do

Prefix caching is one of the more consequential vLLM defaults most teams never look at directly. It works quietly in the background on any workload with repeated prompt structure, which describes most production chat, RAG, and agent traffic. This post belongs to the packet.ai LLM serving frameworks cluster; for the broader deployment picture, see the vLLM deployment tutorial and the vLLM Docker guide, which covers PagedAttention's memory management in depth. This post is about the mechanism itself and how to work with it directly: what the hash-based cache actually does, how to enable, disable, or tune it, and how to read your own hit rate. It isn't about whether caching is worth it in dollar terms for your specific traffic; that's a cost-modeling question with its own dedicated coverage elsewhere on this site. If you're trying to size the financial upside of caching before you touch any config, that's the post to read first.

What Automatic Prefix Caching Actually Does

Every request to an LLM starts with prefill: processing the full input prompt to build the initial KV cache before generation begins. If two requests share the same opening tokens, a system prompt, a set of few-shot examples, an accumulated chat history, the first one already did the compute work the second one is about to repeat. System prompt caching is the most common real-world case: nearly every production deployment sends the same system prompt on every call, and it's exactly the kind of static prefix this mechanism is built to catch. Prefix caching is vLLM keeping that work around so the second request doesn't have to redo it.

vLLM manages its KV cache in fixed-size blocks (16 tokens each by default) through the vLLM block manager, and prefix caching identifies each block with a hash that chains it to the block before it: the hash covers the block's own token IDs plus the hash of its parent block. That chaining means a cache hit isn't "roughly similar text," it's an exact token-by-token match of the entire prefix up to and including that block. When a new request comes in, vLLM checks its block hashes against what's already cached; anywhere the chain matches, it reuses the existing physical block instead of recomputing it. The first point where the chain breaks is where fresh computation starts.

This is the same block-based, hash-table approach used for PagedAttention's memory management generally, extended to do double duty as a cache key. There's no separate tree structure to maintain; every block is independent, addressable by its own hash, and can be allocated or freed on its own.

Enabling, Disabling, and Tuning It

Here's the part that surprises people coming to this fresh: on vLLM's V1 engine, prefix caching is already on. The flag most documentation still shows as --enable-prefix-caching exists, but if you're running a reasonably current vLLM version, you're more likely to need its opposite:

# Confirm it's on (this is the default, so this line does nothing new)
vllm serve meta-llama/Llama-3.1-8B-Instruct --enable-prefix-caching

# Explicitly turn it off
vllm serve meta-llama/Llama-3.1-8B-Instruct --no-enable-prefix-caching

There are legitimate reasons to disable it: multi-tenant setups where you don't want any KV state persisting across requests as a hard security boundary, or workloads where every prompt is genuinely unique and the hash lookup itself adds overhead with zero hit rate to show for it. For the more common case, shared system prompts, RAG contexts, multi-turn chat, leaving it on is the right default.

Two configuration details worth knowing about beyond the on/off switch:

Block size and partial-block credit. Blocks default to 16 tokens, and only full blocks are cached; a prefix that ends 3 tokens into a new block gets no caching credit for those 3 tokens. If your shared prefix is consistently just short of a clean block boundary, small structural changes (padding a system prompt, reordering static content to the front) can meaningfully change how much of it actually gets cached. This full-blocks-only rule is the standing behavior for standard attention models; newer architectures with linear-attention components have required vLLM to relax it in places, so don't assume it's absolute if you're running something unconventional.

Hash algorithm. The block hash function is configurable via --prefix-caching-hash-algo: sha256 (the current default, addressing a collision risk present in earlier hashing approaches), sha256_cbor for a reproducible hash across different Python or vLLM versions, or xxhash for faster, non-cryptographic hashing when reproducibility across environments doesn't matter.

Cache isolation with cache_salt. In multi-tenant environments where you want cache reuse within a tenant but not across tenants, vLLM supports an optional per-request cache_salt value. It gets folded into the hash of the first block, so only requests carrying the same salt can share cached blocks with each other. This is also a real defense against timing-based side-channel attacks, where an adversary could otherwise infer cached content by observing latency differences between requests, worth knowing about if that's a concern for your deployment.

Measuring Your Actual Hit Rate

Whether prefix caching is doing anything for your workload is an empirical question, not something to assume from the fact that it's enabled. vLLM's V1 engine exposes this through two Prometheus counters: vllm:prefix_cache_queries and vllm:prefix_cache_hits. An older single gauge, vllm:gpu_prefix_cache_hit_rate_perc, is deprecated and being phased out; the current approach is deliberately counter-based so you calculate the rate yourself over whatever time window you choose, rather than trusting a built-in average that can dilute a sudden change in traffic pattern. A simple PromQL rate query, hits divided by queries over your chosen interval, gives you the number.

⚡ Note

This section is about reading the metric operationally, not converting it into a dollar figure. A 70% hit rate tells you something concrete about how your traffic overlaps; what that's worth in compute cost saved is a separate calculation with its own assumptions about GPU pricing and workload shape.

A useful way to think about the resulting number: it's a direct readout of how much prefill computation your traffic is genuinely repeating. A workload where every request opens with the same system prompt and tool definitions should show a high, stable hit rate. A workload of one-off, structurally distinct prompts will show a low one regardless of how well-configured caching is, because there's nothing to reuse. If your hit rate is lower than the shared-prefix structure of your traffic would suggest, that's a signal to check whether your prompts are actually byte-for-byte identical where you think they are, since even a single-token difference early in the prompt breaks the hash chain from that point forward.

Calculate the ratio over a representative traffic window, not a single request or a short burst. Hit rate is a property of your traffic pattern over time, and a handful of early cache-cold requests will understate it if you're not aggregating over a long enough interval.

When This Actually Helps, and When It Doesn't

Prefix caching earns its keep specifically on workloads with real, repeated prompt structure:

✓ Genuinely helps

  • Chat applications with a fixed or slowly-changing system prompt
  • RAG pipelines that repeatedly query the same document or context
  • Multi-turn conversations, where history accumulates as a shared prefix
  • Agent loops that resend the same tool-definition block every call

✗ Little to no benefit

  • Batch jobs with structurally unique prompts per request
  • Traffic where user-specific content sits at the start of the prompt, before any shared prefix
  • Single-turn, one-off requests with no repeated structure across calls

One structural mistake worth calling out directly: putting user-specific or highly variable content at the front of a prompt, before the static system instructions or shared context, breaks the prefix match immediately, since the hash chain diverges from the very first block. If your prompt template puts a user ID, timestamp, or per-request variable ahead of your static system prompt, prefix caching has effectively nothing to work with no matter how much of the rest of the prompt repeats. Structuring prompts with static, shared content first and variable content last is a small change that determines whether caching applies at all.

How This Differs From SGLang's RadixAttention

vLLM's approach and SGLang's RadixAttention solve the same underlying problem, reusing computed KV cache across requests that share a prefix, with genuinely different data structures. vLLM's hash table treats every block independently: no shared parent-child relationship needs to be maintained beyond what's encoded in the chain hash itself. RadixAttention instead builds and maintains an actual radix tree (trie) across all cached sequences, which lets it find the longest common prefix against any prior request more directly, at the cost of maintaining that tree structure.

In practice, vLLM's own eviction policy is deliberately designed to approximate RadixAttention's behavior for standard attention models even without a tree, evicting the least-recently-used block, and preferring to evict blocks at the end of longer prefixes first, which keeps more broadly-shared, shorter prefixes alive longer. The two approaches converge on similar practical outcomes for many workloads; SGLang's tree structure tends to show its advantage more clearly at very high concurrency with heavily overlapping prefixes. A related but separate concept worth knowing exists is cache aware routing, directing a request to the specific replica that already holds its prefix warm in a multi-instance deployment; that's a load-balancer-level decision sitting above prefix caching itself, not a configuration option within it. For a full breakdown of when the RadixAttention gap actually shows up in benchmarks and which engine to pick for prefix-heavy traffic specifically, the packet.ai SGLang vs vLLM vs TensorRT-LLM guide covers that comparison directly; this post stays focused on vLLM's own implementation.

Running This on packet.ai

Prefix caching is fundamentally about reusing something expensive to recompute instead of paying for it twice, and packet.ai's Dynamic tier applies the same principle one layer up: instead of one engine reusing KV cache across requests, the packet.ai scheduler shares physical GPU capacity across tenants, keeping latency close to a dedicated card's while billing hourly rather than requiring a fixed allocation. That makes Dynamic a reasonable place to actually test prefix-caching configuration against your own traffic, different block sizes, cache-salt setups, prompt template restructuring, without committing to a Dedicated instance before you've confirmed what your real hit rate looks like. Once you've validated a configuration and settled on steady-state traffic, moving to Dedicated removes scheduler variance from the picture entirely.

Frequently asked questions

Automatic prefix caching is a vLLM feature that reuses the KV cache computed for a previous request's prompt when a new request shares the same starting tokens. Instead of recomputing the shared prefix, vLLM identifies matching 16-token blocks by a chain hash and reuses the existing cached blocks, skipping that prefill work entirely for the matched portion.
Yes, on vLLM's V1 engine, prefix caching is enabled by default. The --enable-prefix-caching flag still exists and can be set explicitly, but most current deployments already have it on without any extra configuration. To turn it off, use --no-enable-prefix-caching.
vLLM's V1 engine exposes this through two Prometheus counters, vllm:prefix_cache_queries and vllm:prefix_cache_hits; divide hits by queries over your chosen time window to get a rate. An older single gauge, vllm:gpu_prefix_cache_hit_rate_perc, is deprecated and being removed, so newer vLLM versions require calculating the ratio yourself rather than reading one built-in number.
Both reuse cached KV blocks across requests sharing a prefix, but vLLM uses a flat hash table where each block is independently addressable by a chain hash, while SGLang's RadixAttention maintains an actual radix tree across all cached sequences. vLLM's eviction policy is designed to approximate RadixAttention's behavior for standard attention models without needing the tree structure. The practical gap between them varies by workload and concurrency level.
The most common cause is variable content, a user ID, timestamp, or session-specific value, sitting at the start of the prompt ahead of the static shared content. Since caching requires an exact token-by-token match from the very first block, any variation that early breaks the match for the entire prompt regardless of how much of the rest is genuinely identical. Restructuring the prompt template to put static content first and variable content last usually fixes this.
Yes. vLLM supports an optional per-request cache_salt value that gets folded into the hash of the first block, so only requests carrying the same salt can share cached KV blocks with each other. This lets you keep cache reuse within a tenant while preventing KV state from leaking across tenants in a shared deployment.

Last reviewed: August 5, 2026. For the cost and ROI side of prompt caching decisions, see packet.ai's dedicated cost guide. For engine-level comparisons including RadixAttention benchmarks, see the SGLang vs vLLM vs TensorRT-LLM guide.

Waste less compute.

Same models. Same API. Fraction of the cost. Start free — no credit card required.

Start Building →

More from the blog