The more I look at LLM serving, the more it feels like the main object is not the request, the model, or even the GPU.

The main object is the KV cache.

The old mental model is simple:

Animated old LLM serving mental model: request to GPU to response

The newer serving stack looks more like this:

Animated KV-centric serving stack with router, KV cache state, and scheduling decisions

That is the thread connecting vLLM, SGLang, LMCache, Mooncake, Dynamo, DistServe, and many newer systems. They are not only optimizing attention math. They are managing attention state: where it lives, how it is reused, and how quickly it moves through the serving stack.

The Core Problem: Prefill and Decode Are Different

An autoregressive LLM request has two major phases:

PhaseWhat happensWhat it stresses
PrefillThe model processes the prompt and creates the initial KV cache.Heavy parallel compute, prompt length, input batching.
DecodeThe model generates one token at a time, reading old KV and appending new KV.Memory bandwidth, active KV size, per-token latency.

These phases have very different shapes.

Prefill is compute-heavy because the whole input prompt can be processed in parallel. Long prompts, RAG documents, tool schemas, and few-shot examples all make this phase expensive.

Decode is sequential. Each new token depends on the previous token. The GPU repeatedly reads the existing KV cache, computes one more step, appends the new K/V, and repeats. This often underuses raw GPU compute while stressing memory bandwidth and latency.

When both phases run on the same GPU worker, they interfere with each other:

  • Long prefills can block decode steps and create spikes in TTFT, or time to first token.
  • Decode's low compute utilization can waste expensive GPU cycles.
  • Prefill and decode compete for the same HBM memory that stores model weights and KV cache.
  • One scheduling policy has to satisfy two very different workloads.

Animated prefill and decode phase KV cache transfer

Prefill is parallel and compute-heavy; decode is sequential and memory-bandwidth-heavy. Running both on the same worker creates scheduling and memory contention.

This is the reason people talk about prefill/decode disaggregation.

The Solution Shape: Disaggregate the Serving Stack

Disaggregated serving splits prefill and decode onto separate worker pools:

Animated disaggregated prefill and decode worker pools

Disaggregation lets prefill and decode scale separately, but it makes KV cache transfer and cache-aware routing part of the critical path.

The prefill cluster can be optimized for prompt throughput. The decode cluster can be optimized for token latency and concurrency. They can scale independently, use different parallelism strategies, and sometimes even use different GPU types.

That sounds clean, but it creates the hard new problem:

The KV cache produced by prefill must arrive at decode quickly enough that transfer does not erase the benefit of disaggregation.

So disaggregated serving is not just “split the workload.” It means the serving stack has to do several things at once:

  • Split the workload between prefill-heavy and decode-heavy workers.
  • Move KV cache efficiently so transfer latency does not erase the win.
  • Route requests based on cache locality, not only on open worker slots.
  • Manage cache tiers under memory pressure across GPU HBM, CPU DRAM, disk, and remote storage.

That is why modern systems increasingly feel KV-centric.

What the KV Cache Actually Stores

During generation, every token leaves behind key and value tensors.

The important detail is that KV is stored per layer. A token does not have one global K and one global V; it has a K/V pair at every transformer layer, and each pair is split by KV heads and head dimension.

Conceptually:

Animated KV cache stored per transformer layer

The rough memory formula is:

The 2 is for K and V.

For a Llama-2-7B-style model with multi-head attention:

ParameterValue
Layers32
KV heads32
Head dim128
KV dtypefp16, 2 bytes
KV per token524,288 bytes, about 512 KB

That means KV cache grows with both context length and concurrency.

Context length1 request10 concurrent100 concurrent
1K tokens0.5 GB5 GB50 GB
4K tokens2 GB20 GB200 GB
8K tokens4 GB40 GB400 GB
32K tokens16 GB160 GB1,600 GB
128K tokens64 GB640 GB6,400 GB

This table uses a standard MHA 7B-class example. Modern architectures reduce the slope:

TechniqueKV cache effect
MHAEvery query head has its own K/V head. This is the expensive baseline.
GQAGroups of query heads share fewer KV heads, often making KV much smaller.
MQAAll query heads share one KV head, shrinking cache further with tradeoffs.
MLACompresses KV into a latent representation, as in DeepSeek-style architectures.
KV quantizationStores KV in lower precision, such as fp8 or int8.
Sliding window attentionCaps the active KV window, trading long-range recall for bounded memory.

The exact number matters. A 10K token prompt on a 70B-class model can mean many GB of KV transfer. With old-style MHA, it can be tens of GB. With a modern GQA model such as Llama-3-70B, it is much smaller, but still large enough to dominate latency if transferred poorly.

So the right lesson is not one fixed number. The lesson is:

KV cache size is determined by layers, KV heads, head dimension, dtype, context length, and concurrency.

The KV Transfer Problem

In a disaggregated stack, prefill creates KV on one worker and decode needs it on another.

That makes the transfer path a first-class part of the serving system:

Transfer pathWhy it matters
NVLinkFast GPU-to-GPU movement inside a node.
PCIeCommon local device path, slower than NVLink.
InfiniBand RDMAHigh-throughput cross-node transfer in GPU clusters.
RoCERDMA over Ethernet for datacenter fabrics.
CPU DRAMUseful warm cache or staging layer, but adds movement.
NVMe SSDLarger and cheaper local capacity, much slower than memory.
Object storeHuge durable capacity, only useful when latency can be hidden or amortized.

Mooncake is a good example of this mental model. Its architecture treats KV cache as the central resource: a scheduler decides where work should go, and a transfer engine moves KV cache efficiently, using RDMA-style paths to reduce CPU involvement and overlap movement with compute where possible.

NVIDIA Dynamo makes the same issue visible in a practical serving stack. Its documentation separates the frontend, router, prefill workers, decode workers, and KV transfer path. It also warns that without RDMA or an equivalent fast fabric, KV movement can dominate TTFT and throughput.

The key point:

Disaggregation moves the bottleneck from “can this GPU do both phases?” to “can the system place and move KV cache fast enough?”

The Four-Tier KV Cache Hierarchy

Once KV cache becomes the main serving object, the system naturally becomes a memory hierarchy.

TierStorageRoleTradeoff
L1GPU HBMHot active KV cache used by attention kernels.Fastest and most expensive; capacity is scarce.
L2CPU DRAMWarm cache and staging layer.Larger and cheaper, but transfer adds latency.
L3NVMe SSDCold local cache.Much larger, useful for reuse and offload, but slower.
L4Object storeDurable remote cache.Huge capacity, highest latency.

This hierarchy explains why different projects emphasize different things.

At L1, vLLM, SGLang, TensorRT-LLM, LMDeploy, and vAttention focus on making GPU-resident KV usable under high concurrency.

At L2 and below, LMCache, FlexGen, Dynamo KVBM, AttentionStore-style systems, and other KV stores start to matter. They ask: what happens when KV should outlive one GPU worker, one request, or one hot HBM allocation?

The serving engine wants hot KV in HBM, but HBM is scarce. A single GPU may have 40-80 GB; large model weights consume much of that before user KV cache even arrives. So every serious serving stack needs policies for:

  • eviction
  • promotion and demotion
  • prefix reuse
  • tenant isolation
  • transfer scheduling
  • cache-aware routing

This is why cache policy is not a minor implementation detail. It decides whether the stack keeps useful attention state close to compute or spends the whole time recomputing and moving it.

L1 GPU HBM: Where vLLM and SGLang Fit

vLLM and SGLang are easiest to understand as L1 KV-cache systems with different emphasis.

vLLM starts from memory allocation:

How do we store dynamic KV cache in GPU memory without wasting huge amounts of HBM?

SGLang starts from prefix reuse:

How do we automatically find shared prompt prefixes and reuse their KV cache across requests?

Both are attention-serving optimizations, but they sit at different levels.

vLLM: PagedAttention

vLLM’s core idea is PagedAttention.

The analogy is operating-system virtual memory. A process sees a logical address space, but the physical memory pages do not need to be contiguous. vLLM applies this idea to KV cache.

Instead of reserving one large contiguous KV tensor per request, vLLM splits KV cache into fixed-size blocks. A request has logical blocks, and the engine maps them to physical GPU-memory blocks.

Animated vLLM PagedAttention logical KV blocks mapped to physical GPU KV blocks

The request still behaves as if its tokens are contiguous. The attention kernel knows how to gather the physical blocks.

This matters because request lengths are dynamic:

  • One request may stop after 20 tokens.
  • Another may continue for 4,000 tokens.
  • A new request may arrive while older requests are decoding.
  • Parallel sampling may share a prefix and then branch.

Without paging, the engine either over-reserves memory or suffers fragmentation. PagedAttention makes KV allocation granular and reusable.

On top of this block system, vLLM supports automatic prefix caching. It hashes full KV blocks using the parent block hash, tokens in the current block, and extra values such as LoRA IDs, multimodal hashes, or cache salts. If another request shares the same prefix blocks, vLLM can reuse the existing KV cache.

The practical implication is simple:

  • identical stable prefixes help
  • random timestamps near the front hurt
  • inconsistent templates hurt
  • block alignment matters because caching happens at block granularity

PagedAttention gives vLLM the memory substrate. Automatic prefix caching uses that substrate to avoid repeated prefill.

SGLang: RadixAttention

SGLang’s key idea is RadixAttention.

It stores reusable prompt and generation prefixes in a radix tree. A radix tree is like a compressed trie: edges can represent sequences of tokens rather than single tokens.

Conceptually:

root
+-- "system prompt + tool schemas"
|   +-- "user question A" -> cached KV
|   +-- "user question B" -> cached KV
+-- "few-shot examples"
    +-- "task A" -> cached KV
    +-- "task B" -> cached KV

When a new request arrives, SGLang looks for the longest matching prefix. If a matching prefix exists, the runtime reuses the corresponding KV cache and only computes the suffix.

This is especially useful for:

  • shared system prompts
  • repeated few-shot examples
  • multi-turn conversations
  • agent loops with stable tool context
  • self-consistency sampling from the same prompt

RadixAttention is not a new attention formula. It is a runtime strategy for reusing attention state.

PagedAttention vs. RadixAttention vs. APC

These terms are easy to mix up, so I like this table:

ConceptMain questionData structureMain win
PagedAttentionHow do we allocate KV memory efficiently?Fixed-size KV blocks and block tables.Less fragmentation and better HBM utilization.
Automatic Prefix CachingCan repeated block prefixes be reused?Hashes over full KV blocks.Avoids recomputing repeated prefill blocks.
RadixAttentionCan repeated token prefixes be found automatically?Radix tree over token prefixes.Reuses KV for shared prompt paths.

The short version:

vLLM made KV memory behave more like paged virtual memory. SGLang made repeated prompt programs behave more like prefix-tree lookups.

They are not competing mental models. They are different layers of the same KV-cache problem.

Framework Coverage by Cache Tier

This table is not meant to be a strict feature matrix. It is a mental map of where each system tends to focus.

SystemL1 GPU HBML2 CPU DRAML3 NVMe / diskL4 object / remote store
vLLMPagedAttention, APC, chunked prefillCPU swap / offload pathsMostly through integrationsMostly through integrations
SGLangRadixAttention, paged KV, chunked prefillCan integrate with lower-tier cache systemsBackend-dependentBackend-dependent
LMCacheIntegrates with engines such as vLLMPrimary warm-cache use caseDisk/offload supportRemote/persistent cache patterns
MooncakeKV-aware serving with hot GPU cacheUses cluster memory resourcesUses SSD resourcesKV-centric cluster-level design
Dynamo KVBMWorks with backend enginesKV cache offloading / stagingBackend and integration dependentBlob/object style integrations
FlexGenLimited GPU memory assumptionCPU offload is centralDisk offload is centralNot the main focus
vAttentionVirtual paging for KV cacheDemand paging modelNot the main focusNot the main focus
LMDeployTurboMind, continuous batching, KV quantizationLess centralLess centralLess central
TensorRT-LLMOptimized NVIDIA kernels, paged KV, in-flight batchingServing-stack dependentServing-stack dependentServing-stack dependent

The important correction to my own thinking: “framework support” is not binary. A serving engine may own L1, while a cache layer or distributed runtime owns L2-L4. In production these are composed.

Metrics Improved by Disaggregation

Disaggregation is valuable only if it improves the metrics users and operators care about.

MetricCoupled servingDisaggregated target
TTFTHigh variance when long prefills block decode.Lower and more predictable.
TPOTDecode latency can degrade during heavy prefill.More stable per-output-token latency.
GPU utilizationDecode can waste compute while prefill creates bursts.Each pool can run closer to its best utilization profile.
CostEvery worker needs to handle both phases.Different GPU types or replica ratios can serve different phases.
ScalabilityPrefill and decode scale together.Prefill and decode scale independently.

DistServe calls the real target goodput: how much traffic can be served while meeting both TTFT and TPOT constraints. That is a better target than raw throughput because a system that produces many tokens but violates latency SLOs is not actually good serving.

Major Systems and Their Approach

Here is the landscape from my notes, rewritten as a clearer map.

Mooncake

Mooncake is a KV-cache-centric disaggregated serving architecture used for Moonshot AI’s Kimi service.

The useful mental split:

  • Conductor: global scheduler
  • Transfer Engine: RDMA-based KV movement
  • KV-centric cache: treats KV cache as the first-class cluster resource

Mooncake is especially interesting for long-context workloads because repeated large prompts make KV placement and reuse extremely valuable.

NVIDIA Dynamo

Dynamo is a distributed inference serving stack with:

  • frontend and router components
  • backend workers for vLLM, SGLang, and TensorRT-LLM
  • KV-cache-aware routing
  • disaggregated serving
  • KV cache offloading integrations

The key term in Dynamo is NIXL, a transfer abstraction around paths such as NVLink, InfiniBand, and PCIe. The router can make decisions based on KV locality rather than only worker load.

DistServe

DistServe formalized the prefill/decode split as a goodput optimization problem.

It argues that prefill and decode should be placed and parallelized separately because they have different latency targets:

  • prefill affects TTFT
  • decode affects TPOT

The core lesson is that disaggregation is not only about utilization. It is about meeting both latency constraints at the same time.

vLLM Disaggregated Prefill

vLLM has experimental disaggregated prefilling support and examples that separate prefill and decode workers. The key abstraction is a transfer connector: the runtime needs a way to move KV state from the prefill side to the decode side.

In practice, this area connects vLLM to systems such as LMCache, NIXL/Dynamo-style transfer, and Mooncake-style serving architectures.

Splitwise

Splitwise explores heterogeneous serving: use different hardware for different phases.

The motivation is intuitive:

  • prefill may benefit from high-end compute-heavy GPUs
  • decode may run cost-effectively on different GPUs if memory and latency targets are satisfied

This matters because disaggregation is not only a latency trick. It can also be a cost-structure trick.

Framework Landscape by Problem

Another way to organize the ecosystem is by the problem each system is trying to solve.

ProblemSystemsMain idea
KV reuse / prefix cachingvLLM APC, SGLang RadixAttention, LMCache, CacheGenAvoid recomputing repeated prompt prefixes or compress/move KV more efficiently.
Prefill/decode disaggregationMooncake, DistServe, Splitwise, Dynamo, vLLM disagg prefillSplit prefill and decode into specialized pools.
Memory management / pagingvLLM PagedAttention, vAttention, FlexGenReduce fragmentation or offload KV beyond GPU HBM.
Full serving enginesvLLM, SGLang, TensorRT-LLM, LMDeploy, TGIProvide batching, scheduling, kernels, APIs, and production serving behavior.
Distributed KV sharingDynamo KVBM, LMCache, AttentionStore-like systemsMake KV reusable across workers, tiers, sessions, or restarts.
Research cache policiesChunkAttention, Pensieve, Quest, Scissorhands, MagicPIGExplore better sharing, migration, retrieval, and eviction policies.

This table is the bigger picture behind the vLLM/SGLang comparison. vLLM and SGLang are not isolated tools; they are two important nodes in a larger KV-management ecosystem.

Application Design Still Matters

Infrastructure can only reuse what the application gives it.

Good prompt structure improves cache reuse
  • Put stable content first.
  • Keep system prompts byte-for-byte identical.
  • Keep tool schemas in a stable order.
  • Put volatile request-specific fields later.
  • Reuse few-shot examples consistently when possible.
Bad prompt structure destroys reuse
  • random request IDs before the shared system prompt
  • shuffled tool definitions
  • inconsistent whitespace or templates
  • RAG chunks inserted before stable instructions
  • tenant-specific noise mixed into otherwise shared prefixes

The engine sees tokens, not your intent. If two prompts do not tokenize to the same prefix, the cache cannot help.

Final Mental Model

The attention formula tells us what the model computes:

The serving stack asks a different set of questions:

  • Where is the KV cache?
  • Is the useful prefix already cached?
  • Is it in GPU HBM, CPU DRAM, NVMe, or remote storage?
  • Should this request go to the least busy worker or the worker with the best KV locality?
  • Is it cheaper to recompute KV or transfer it?
  • Which KV blocks should be evicted?

That is the shift:

Modern LLM serving is becoming KV-cache management.

vLLM’s PagedAttention solves the hot HBM allocation problem. vLLM’s automatic prefix caching reuses repeated block prefixes. SGLang’s RadixAttention organizes repeated prompt paths for runtime reuse. Disaggregated systems such as Mooncake, Dynamo, and DistServe move the same idea up to the cluster level.

A rough cost instinct is:

It is not a pricing equation. It is a compass.

The goal is to compute less repeated KV, move fewer KV bytes, keep hot KV close to the GPU, and schedule requests around cache locality. Once that clicks, vLLM attention, SGLang attention, LMCache, and disaggregated serving stop feeling like separate topics.

They are all different layers of the same system:

A system for managing expensive attention state.