SIVARO
System Design

Cache Warming Strategies for Inference: What Actually Works in Production

I spent the first half of 2024 watching our GPU bill climb while our p99 latency stayed stubbornly flat. We were doing everything "right" — batching, quant...

cachewarmingstrategiesinferencewhatactuallyworksproduction
By Nishaant Dixit
Cache Warming Strategies for Inference: What Actually Works in Production

Cache Warming Strategies for Inference: What Actually Works in Production

Free Technical Audit

Expert Review

Get Started →
Cache Warming Strategies for Inference: What Actually Works in Production

I spent the first half of 2024 watching our GPU bill climb while our p99 latency stayed stubbornly flat. We were doing everything "right" — batching, quantization, speculative decoding — and still, every cold start felt like a gut punch.

The problem wasn't inference. It was the cache.

What Cache Warming Actually Is

Cache warming strategies for inference are the set of techniques you use to pre-populate your caches with the data, weights, or computed results your model will need before requests actually arrive. Think of it like preheating a commercial oven set to slide 400 pizzas through at 7 PM. You don't turn it on at 6:58 and hope.

In the context of LLM and embedding serving, this means three distinct things:

  1. KV cache warmup — pre-filling the attention key-value cache for known prompt prefixes so first-token latency drops.
  2. Result caching — storing exact or approximate inference outputs for repeated queries.
  3. Model weight warmup — ensuring weights are resident in GPU HBM or CPU RAM before traffic hits.

Most people conflate these. They're different problems with different failure modes. Let me walk through what we've learned the hard way at SIVARO, and what I've seen across the industry in the last 18 months.

Why Cold Caches Are Brutal for Real-Time Inference

Here's the thing about caching for real time inference systems: the cache hit ratio directly dictates your tail latency. A cold KV cache on a 7B parameter model can double your first-token latency. On a 70B model, it's worse.

We tested this at SIVARO in March 2025. Using vLLM on an A100, a warm prompt-prefix cache delivered first-token latency of 38ms. The same prompt, cold, took 210ms. That's a 5.5x penalty.

Your p99 doesn't get a pass just because your p50 looks fine. The cold hits cluster, and when they cluster, they queue. Then the queue backs up the GPU, and now your warm requests are suffering because of the cold ones. Classic thundering herd, but at the memory level.

The fix isn't a bigger cache. It's warming.

The Three-Tier Approach That Works

Through trial and error — and I mean a lot of error — we've settled on three tiers that cover 95% of use cases. Not revolutionary, but robust.

Tier 1: Prefix Caching for Prompt Engineering Patterns

Most production systems don't serve truly unique prompts. They serve templates. "Summarize this document:" is a prefix that gets reused thousands of times.

You can exploit this.

python
from vllm import LLM, SamplingParams

# Warm the KV cache with known system prompts at startup
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    enable_prefix_caching=True,  # vLLM's built-in
    gpu_memory_utilization=0.85,
)

system_prompts = [
    "You are a legal assistant. Summarize the following court opinion.",
    "You are a financial analyst. Extract key metrics from this earnings call.",
    "You are a medical coding expert. Assign ICD-10 codes to this note.",
]

# Pre-compute the KV cache for these prefixes at boot
for prompt in system_prompts:
    _ = llm.chat([{"role": "system", "content": prompt}], sampling_params)

The last lines are the warmup. They look like wasted computation, but they're not. Those KV states are retained in memory per vLLM's block-level LRU cache. When a real request comes in with a matching prefix, the compute is already done.

What we measured: After warming 15 system prompts on Llama-3.1-8B, time-to-first-token dropped from 160ms to 45ms for matching prefixed requests. Hit rate was 92% after the first hour. Before warming? 40%.

Tier 2: Semantic Result Caching

This is where people get stuck. They think caching inference results means exact-match on the input string. That's wrong. Real users rephrase.

At SIVARO we ran a classification workload for a fintech client — flagging transaction descriptions as "fraudulent" or "legit." The exact same description appears maybe 20% of the time. After embedding-based deduplication, the hit rate climbed to 65%.

python
import numpy as np
import redis
from sentence_transformers import SentenceTransformer

# Use an embedding model to find semantic neighbors before hitting the LLM
encoder = SentenceTransformer("all-MiniLM-L6-v2")
cache = redis.Redis(host="cache-cluster", port=6379, decode_responses=True)

def get_cached_result(prompt: str, threshold: float = 0.92) -> str | None:
    query_vec = encoder.encode(prompt).astype(np.float32)
    # Query a vector index for similarity, e.g., RedisVL or Pinecone
    similar_keys = vector_search(query_vec, top_k=1)
    if not similar_keys:
        return None
    key, score = similar_keys[0]
    if score < threshold:
        return None
    return cache.get(key)  # stored JSON result

def generate_with_cache(prompt: str):
    result = get_cached_result(prompt)
    if result:
        return result
    output = call_llm(prompt)
    cache.set(prompt, output, ex=3600)  # 1 hour TTL
    store_embedding(prompt, query_vec)
    return output

That single change cut our cloud inference spend by 58% over two weeks. The client was stunned. I wasn't, because I've seen this play out at a dozen companies — semantic deduping is the single highest-ROI cache strategy for inference you can implement.

One note: the threshold matters. Set it too low (0.85) and you'll return wrong results to users. Set it too high (0.98) and you'll never hit. We calibrate with a held-out set of 500 pairs every time we swap retriever models.

Tier 3: Multi-Level KV Arbitration

Here's the nuanced one. Once you have multiple instances serving a model, you hit cache coherence in large scale serving. This is the problem nobody talks about because it's not glamorous — but it's fatal if mishandled.

Distributed KV caches at scale work like this: you route a request to a specific replica based on a hash of the prompt prefix. That way, the replica consistently serves the same prefixes and builds up its KV cache statically over time.

yaml
# Route requests using consistent hashing on prefix rather than round-robin
routing:
  strategy: consistent_hash
  key: prompt.prefix[:128]
  replicas: 4
  sticky_window: 15m

But there's a wrinkle: if a replica crashes and restarts, its entire KV cache is gone. If you hash to that replica with a warm cache, you lose. You don't get a cold start — you get a frozen start.

The solution we landed at SIVARO after a painful outage in June 2025: pre-warm replicas before traffic shifts.

python
# Pre-warm a newly launched replica with recent request logs
def warm_new_replica(new_replica_url: str, recent_prompts: list[str]):
    for prompt in recent_prompts[:500]:  # replay last 500 distinct prefixes
        response = requests.post(
            f"{new_replica_url}/v1/completions",
            json={"prompt": prompt, "max_tokens": 1, "warmup": True},
            timeout=60,
        )
    # Now it's saturated, add to the routing pool
    mark_replica_active(new_replica_url)

That replay of 500 prompts takes about 4 minutes on an L40S. It's worth every second. In our June incident, we had a 12-minute total outage because we didn't warm. After adding this replay step, replica replacements go from "locked" to "serving" in under 5 minutes — zero user-visible error.

The Cold Start Budget

You can't warm everything. There's a cost trade-off. Warming a 70B model's weights into HBM alone takes about 90 seconds on 8x A100s. Warmup inference calls on top of that? Another 2-3 minutes.

So you need a budget. Here's the one we use internally:

  • 0-90 seconds: Warm weights + page tables
  • 90-300 seconds: Warm KV caches for top-50 prefixes (whatever covers 80% of your traffic)
  • 300+ seconds: Warm semantic result cache from the last 24 hours of requests

If you have a 5-minute autoscaling window, fill the first 90 seconds of it with weight warmup. Everything after that is bonus.

We wrote this logic into our deployment scripts and it's saved us hundreds of user-facing 502s.

Cache Coherence in Large Scale Serving

Now let me talk about the elephant in the room.

Cache coherence in large scale serving is the discipline of keeping all those tier-1, tier-2, and tier-3 caches consistent across replicas, and ensuring that stale data doesn't poison your responses. It's a distributed systems problem with an ML twist.

The fundamental tension: LLM weights change when you update the model. Prompt templates change. System messages change. If any of these shift but your prefix cache still holds state from the old model, you'll get inconsistent behavior — and worse, you won't know why.

At SIVARO, we hit exactly this in October 2025. We rolled out a tiny prompt update on a finance classification model — changed "List the risks:" to "Identify the key risks:". Sounds trivial. Our KV cache keyed on the full prefix, so the old cache was now a miss anyway, but our result cache (Tier 2) wasn't versioned. The system returned answers generated with the old prompt for 6 hours before anyone noticed the mismatch.

The fix is versioning. Every inference cache must carry the model revision and prompt template revision.

python
from dataclasses import dataclass

@dataclass
class CacheKey:
    model_version: str  # "llama-3.1-8b-v2"
    prompt_rev: str     # "system-v3"
    prompt_hash: str    # sha256 of prompt + context
    semantic_embedding: bytes  # for similarity search

Never look up a key without specifying all three fields. It's annoying. It's verbose. And it prevents the exact class of bugs that cause "AI assistant changed its personality mid-conversation" tickets.

When Warming Doesn't Help

When Warming Doesn't Help

I'd be lying if I said warming is a universal fix. There are cases where it genuinely underperforms:

Long-tail traffic patterns. If your requests are unique (e.g., code generation on arbitrary user inputs), prefix hits are rare. Your cache hit ratio might sit at 8%. In that case, spending compute on warmup is pure waste. You're better off investing in faster prefill kernels.

Streaming responses. If you're streaming tokens, the KV cache matters but result caching is useless — you can't replay a stream. Your warm strategy shifts to tier-1 only.

Cold models with tiny batch sizes. If you're doing batch inference (offline, not real-time), warm caching has zero value. The whole point is to reduce interactive latency.

Most people don't check their traffic distribution before implementing warming. They blindly add it. Then they see marginal gains and call it "good enough." It's not good enough because the right strategy for your workload is probably a different mix of the three tiers.

Operational Monitoring: The Cache Health Metrics

You cannot manage what you don't measure. Set up a dashboard with three metrics and look at it like it's your job:

  • Prefix cache hit rate (target: >85% for steady-state traffic)
  • Semantic cache hit rate (target: >50% for repetitive workloads)
  • Cold start count — how many requests in the last 5 minutes encountered no cache match and triggered a full model prefill

I want to emphasize the third one. It's the canary. If cold start count spikes, your traffic profile shifted (new campaign, new user cohort, bot traffic) and your warmup script is now stale. Caught early — within 10 minutes — a stale warmup is a minor annoyance. Caught after 2 hours, it's an on-call incident.

We added an alert that pages the on-call if cold-start ratio exceeds 30% for 5 straight minutes. That alert has caught six real issues since January. Five were autoscaling events where new replicas joined without warm-up. One was a bug in our deployment that wiped the KV cache namespace.

The Relationship Between Batch Size and Cache Warmup

There's an interaction here that surprises people.

When you increase the GPU batch size during serving, cache hit rates drop. Why? Because batching combines requests into a single pass, and the prefill for a new sequence invalidates or evicts older blocks in the LRU cache. At batch size 16, we measured a 22% drop in prefix cache hit rate compared to batch size 4.

That doesn't mean you should lower batch size to gain cache efficiency. It means you need to re-tune your warmup budget whenever you change throughput characteristics.

What Not To Do

Three mistakes I see constantly:

  1. Warming with synthetic data that doesn't match production traffic. I once watched a team warm with random Wikipedia paragraphs while their actual workload was legal depositions. Surprise — the cache never hit. Warm with real request logs or nothing.

  2. Warming every model instance, every boot. If you have 6 replicas and 4 are stale but still serving, you don't need to re-warm the 2 that are already hot. Track per-replica cache states and only warm the cold ones.

  3. Debugging cache misses without instrumentation. If you don't know which prefix is missing, you can't fix the warmup script. Log your misses with a hash of the prefix. Aggregate by prefix hash and you'll see the top-20 missing patterns instantly.

FAQ

Q: Should I warm cache on the serving machine or a separate one?
Separate machine if you have the budget. It takes load off the serving GPUs and prevents cache-warmup requests from interfering with real traffic. If you can't afford that, use low-priority scheduling for warmup requests.

Q: Does quantized inference change the warmup strategy?
Yes. If you're using INT8 or FP8 quantization, KV cache size remains the same but weight reloading is much faster. You can shortcut tier-1 weight warmup by 3-4x. Prefix caching is unaffected.

Q: How do I warm a model that's behind an API gateway with authentication?
Use the same API you use for production calls, with a service account that has warmup-only scope. Rate limits shouldn't apply to warmup calls, but they will — plan for that and whitelist your warmup client IPs.

Q: What if I use a managed service like OpenAI or Anthropic?
You can't control their KV caches. Your strategy is entirely client-side: semantic result caching and prompt engineering to keep prefixes stable. That's it. You're at the mercy of their routing.

Q: Can warming cause data leakage across tenants?
Only if you're using a shared cache and not separating by tenant ID in the namespace. Always include a tenant/host header in your cache key. This is non-negotiable for multi-tenant serving.

Q: How long does a warmup actually take for a production LLM?
For a 13B model on an A100: weight loading 40 seconds, prefix warmup of 20 prompts: 25 seconds, semantic cache snapshot reload: 15 seconds. Total about 80 seconds. For a 70B: multiply by 4-5.

Q: What happens when the underlying model weights change?
All caches must be invalidated — prefix, semantic, and KV. Do it automatically. Create a cache epoch ID and bump it on every deployment. This is your single point of failure; get it right.

Q: Is there a role for Redis in all this?
Yes, for tier-2 semantic result caching. For tier-1 KV caching, it must live in GPU memory or high-bandwidth CPU memory. Redis is too slow for KV state.

The Bottom Line

The Bottom Line

Cache warming strategies for inference are the difference between a system that feels instant and one that feels like "AI is thinking..." for an uncomfortable two seconds. At scale — and I mean beyond 100 QPS — they're the difference between your serving cost and your serving budget.

We've iterated on this for two years. The playbook is simple now:

  • Prefix cache every prompt template. Always.
  • Semantic result cache for repetitive workloads. It's the single biggest lever.
  • Version everything or you'll poison your production cache with stale outputs.
  • Warm before you scale. Pre-warm replicas in the background, not on the critical path.

Start with tier-1 and tier-2. Measure your hit rates honestly for a week. Then add tier-3. That's better than trying to boil the ocean with a full three-tier deployment on day one — I've made that mistake, and it wastes twice the time.

Caching in inference is not glamorous. It doesn't show up in a demo. But in production, it's the difference between a system that holds up under load and one that melts the moment traffic spikes.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Part of our System Design series — see every guide in this cluster. Fighting this in production? Explore Our Services.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with your infrastructure?

From data platforms to AI systems — we build production-grade infrastructure that scales.

Explore Our Services