vLLM Production Stack with LMCache KV Offloading
vLLM's production-stack is the reference Kubernetes deployment: router, engines, and observability wired together. LMCache is the KV-offloading layer that pulls KV cache out of GPU memory and reuses it across queries and engines, first in CPU DRAM, then on disk or Ceph. The vLLM 0.11.0 KV Offloading Connector (January 2026) makes that offload asynchronous and pluggable through the Connector API (v0.9.0+), so offload latency stays out of the user's path. LMCache can pay off even without shared prefixes: when a GPU runs out of KV slots, a preempted request can be restored from CPU instead of re-running prefill. In the published benchmarks on 16x H100 (80GB HBM) across 4 a3-highgpu-4g, native CPU offload and LMCache both improve throughput substantially once the KV cache exceeds HBM. At a low KV footprint the configs match baseline, with a small overhead.
Learning Objectives
- Diagram the vLLM production-stack layers: router, engines, KV offload, observability.
- Explain the KV Offloading Connector API (v0.9.0+) and how the 0.11.0 asynchronous path hides offload latency.
- Quantify when LMCache CPU-DRAM helps (KV > HBM) vs adds overhead (KV small enough to fit HBM).
- Pick between native vLLM CPU offload and LMCache connector given deployment constraints.
The Problem
Your vLLM serving shows GPUs at 100% HBM, with preemption events whenever concurrency climbs. Requests get evicted and requeued, and you re-prefill the same 2K-token prompt four times in a minute. GPU compute goes to redundant prefills, so goodput sits well below raw throughput.
Adding GPUs costs linearly, and you can't add HBM to a GPU you already own. CPU DRAM is a different story. One socket can hold 512 GB or more, and its latency is orders of magnitude worse than HBM, but that is fine for KV cache that only needs to stay temporarily warm.
LMCache extracts KV cache to CPU DRAM so preempted requests recover quickly. It also lets engines share cache for repeated prefixes, so each engine doesn't re-prefill them on its own.
The Concept
vLLM production-stack
github.com/vllm-project/production-stack is the reference Kubernetes deployment. It ships as a Helm chart plus an operator, and it has five parts:
- Router. Cache-aware (Phase 17 · 11), and it consumes KV events.
- Engines. vLLM workers, one per GPU or per TP/PP group.
- KV cache offload. An LMCache deployment or the native connector.
- Observability. Prometheus scrape, Grafana dashboards, OTel traces.
- Control plane. Service discovery, config, rolling updates.
The KV Offloading Connector API (v0.9.0+)
vLLM 0.9.0 introduced a Connector API for pluggable KV cache backends. The engine offloads blocks to the connector, and the connector stores them (RAM, disk, object storage, LMCache). When a request needs a block, the connector loads it back.
vLLM 0.11.0 (January 2026) adds an asynchronous offload path, so offload can happen in the background and the engine usually doesn't block on it. End-to-end latency and throughput still depend on workload shape, KV cache hit rate, and system pressure. vLLM's own notes say custom-kernel offload can degrade throughput at low hit rates, and that async scheduling has known interaction issues with speculative decoding.
Native CPU offload vs LMCache
Native vLLM CPU offload is engine-local. It stores KV blocks in host RAM, which is quick to set up and has no network hop. It doesn't cross engines.
LMCache connector works at cluster scale. It stores blocks in a shared LMCache server (CPU DRAM plus a Ceph/S3 tier), and any engine can read them. The 16x H100 benchmarks are published.
Pick native when a single engine has HBM pressure. Pick LMCache when multiple engines share prefixes, as with RAG over common system prompts or multi-tenant serving with shared templates.
Benchmark behavior
The test spreads 16x H100 (80 GB HBM) across 4 a3-highgpu-4g:
- Low KV footprint (short prompts, low concurrency): the configs match baseline, and LMCache adds roughly 3-5% overhead.
- Moderate footprint: LMCache starts to help on prefix reuse across engines.
- KV exceeds HBM: native CPU offload and LMCache both improve throughput substantially. LMCache gains more because of cross-engine sharing.
When LMCache is decisive
- Multi-tenant serving where tenants share system prompts.
- RAG where document chunks repeat across queries.
- Fine-tuned variants (LoRA) on one base model, where reusing the base model's KV cuts redundant work.
- Preemption-heavy workloads, where restoring from CPU is cheaper than re-prefilling.
When not to enable it
- Small HBM pressure. You pay the overhead and get no benefit.
- Short contexts (<1K tokens), where transfer time exceeds re-prefill time.
- A single-tenant, single-prompt workload, which has no reuse to capture.
Integration with disaggregated serving
Phase 17 · 17 disaggregated serving compounds with LMCache. KV transferred from the prefill pool to the decode pool lands in LMCache if nothing uses it, and later queries pull it from there. The Phase 17 · 11 cache-aware router can then route to the engine whose local or LMCache-shared cache matches.
Numbers to remember
- vLLM 0.9.0: Connector API shipped.
- vLLM 0.11.0 (Jan 2026): asynchronous offload path. The latency impact depends on workload, KV hit rate, and system pressure, so treat it as a tendency rather than a guarantee.
- 16x H100 benchmark: LMCache helps when the KV footprint exceeds HBM.
- Small HBM pressure: 3-5% overhead without benefit.
Use It
code/main.py simulates 200 requests over 4 engines that draw from 6 shared prefixes, under three configs: no offload, engine-local CPU offload, and a shared LMCache. It reports total time, prefill time, re-prefills avoided, and speedup against the no-offload baseline. It doesn't model HBM utilization (the per-engine capacity constant is declared but not used), so it can't give you a break-even point. Treat it as a model of reuse across engines, not of HBM pressure.
Ship It
This lesson produces outputs/skill-vllm-stack-decider.md. Given a workload shape and a vLLM deployment, it decides between native offload, LMCache, and neither.
I'd run main.py first and read the output skeptically. LMCache avoids the most re-prefills (194, against 176 for CPU offload and roughly 120 with no offload), yet it finishes slightly behind the baseline at 0.98x to 0.99x. The cause is the cost model. A shared-cache hit is charged 3.0 ms per 16-token block, which works out to about 5 tokens/ms, while a fresh prefill runs at 40 tokens/ms. Every LMCache hit is therefore slower than the prefill it replaces, and prefill time actually rises (14800 ms against roughly 9000 ms with no offload). Decode time also dominates the total in the toy, so the differences stay small. The engine-local path adds only 0.15 ms per block on a miss, so CPU offload comes out slightly ahead at about 1.02x. The numbers say that avoiding a prefill is not the same as saving time once you pay to move the blocks.
That's the trade I'd keep in mind from the body. The 3-5% overhead at low KV footprint and the preference for native offload on a single engine are the same point at cluster scale, and the toy shows a small version of it.
I'd also keep the simulator in proportion. Only the no-offload config evicts anything (it keeps at most 4 prefixes per engine, and which one it drops varies from run to run, so its numbers wobble slightly), and the HBM capacity constant never enters the arithmetic. It can't tell you when LMCache starts paying under memory pressure. For that I'd measure the real stack with my own prompts, since hit rate and prompt length decide the result more than anything in this file.
Exercises
- Run
code/main.py. Which config avoids the most re-prefills, and does it also finish first? (The no-offload row shifts a little between runs, so compare against the baseline rather than exact counts.) Then changehbm_capacity_blocks_per_engineand check whether anything moves. What does that tell you about what the simulator models? - A tenant shares a 6K-token system prompt across 200 queries/hour. Compute expected LMCache savings per tenant.
- The LMCache server is a single point of failure. Design the HA strategy (replicas, fallback to native).
- LMCache stores to Ceph on spinning disk. For a 4K-token KV at 70B FP8 (500 MB), what's the read time vs re-prefill?
- Argue whether the vLLM 0.11.0 asynchronous path is "free". Where does the overhead hide?
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Production-stack | "the reference deployment" | vLLM's Kubernetes Helm chart + operator |
| Connector API | "KV backend interface" | vLLM 0.9.0+ pluggable KV store interface |
| Native CPU offload | "engine-local spill" | Store KV in host RAM of same engine |
| LMCache | "cluster KV cache" | Cross-engine KV cache server on CPU DRAM + disk |
| 0.11.0 async | "non-blocking offload" | Offload hidden behind engine stream |
| Preemption | "evict to make room" | KV cache shuffle when HBM full |
| Prefix reuse | "same system prompt" | Multiple queries share beginning; cache hit |
| Ceph tier | "disk tier" | Durable storage below DRAM in the cache hierarchy |
Further Reading
- vLLM Blog — KV Offloading Connector (Jan 2026)
- vLLM Production Stack GitHub — Helm chart + operator.
- LMCache for Enterprise-Scale LLM Inference (arXiv:2510.09665)
- LMCache GitHub — Connector implementation.
- vLLM 0.11.0 release notes — asynchronous path details.
Code
- main.py (python, 121 lines)