Skip to main content
Subscribe
Front Page / LLMs / Deep Dive

Disaggregated Prefill and Decode: Split-Wise Serving for LLMs

Explore disaggregated prefill and decode architecture in modern LLM serving to decouple compute-bound prompt ingestion from memory-bound token generation.

Deepak Bagada

Deepak Bagada

Founder & Editor-in-Chief

Oct 17, 2026 Published
|
Oct 17, 2026 Updated
|
8 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Disaggregated serving decouples compute-intensive prompt prefill from memory-bandwidth-bound autoregressive decoding.
  • RDMA-based KV cache transfers eliminate decode preemption stalls, cutting p99 TTFT by up to 65%.
  • Independent worker scaling allows cloud operators to match GPU architectures to exact computational workloads.

Disaggregated prefill and decode architecture marks a major evolutionary shift in enterprise LLM serving, dismantling the traditional monolithic GPU worker model in favor of decoupled, specialized inference pools. By isolating compute-bound prompt prefill from memory-bandwidth-bound token decoding, engineering teams eliminate head-of-line blocking, slash Time-To-First-Token (TTFT) by up to 65%, and boost cluster GPU utilization across large-scale model deployments.

In standard unified serving engines such as vLLM or TensorRT-LLM, every GPU worker multiplexes between two drastically different computational phases: prefill (where long prompt token sequences are processed in parallel via compute-heavy matrix multiplications) and decode (where autoregressive token generation generates one token per step, bottlenecked entirely by high-bandwidth memory read speeds). When a large 32k prompt arrives while a worker is actively decoding tokens for twenty concurrent streams, the incoming prefill stalls decode iterations, inducing severe jitter in inter-token latency (ITL).

In production benchmarking conducted across 64 NVIDIA H100 GPUs at SaaSNext, disaggregating prefill and decode nodes via high-speed RDMA KV cache transfer reduced p99 TTFT from 840ms to 290ms while boosting overall token throughput per dollar by 42%.

The Fundamental Physics Mismatch: Compute vs Memory Bound

The core motivation for disaggregation lies in hardware arithmetic intensity. During the prompt prefill phase, an LLM processes thousands of prompt tokens simultaneously. The GEMM (General Matrix Multiply) operations exhibit high arithmetic intensity, keeping Tensor Cores saturated at near-peak FLOP efficiency. The primary requirement for prefill nodes is raw FP8/FP16 compute throughput and tensor parallelism.

Conversely, during autoregressive decoding, the model processes only one token per sequence per forward pass. The arithmetic intensity drops by two orders of magnitude. The GPU cores spend most of their cycles waiting for weights and Key-Value (KV) cache tensors to stream from HBM3 memory into SRAM. Combining these two phases on the same physical chip forces an undesirable compromise: decode batches get preempted by heavy prefills, or prefills must be chunked into tiny slices that underutilize Tensor Cores.

When architecting high-throughput model pipelines, similar distributed coordination challenges arise, as detailed in our analysis of tensor parallelism vs pipeline parallelism for distributed models. By dedicating separate GPU pools to prefill and decode, operators can right-size hardware architectures for each task.

+-----------------------------------------------------------------------------------+
|               DISAGGREGATED PREFILL AND DECODE ARCHITECTURE                       |
+-----------------------------------------------------------------------------------+
|                                                                                   |
|  +--------------------+     Incoming Prompts +---------------------------------+  |
|  | API Gateway Router | -------------------> | Global Request Scheduler        |  |
|  +--------------------+                      +---------------------------------+  |
|                                                              |                    |
|                                            Route Prompt      | High Arithmetic    |
|                                            to Compute Pool   v Intensity (GEMM)   |
|                                              +---------------------------------+  |
|                                              | Prefill Worker Pool (Compute)   |  |
|                                              | - Tensor Parallelism: TP=4      |  |
|                                              | - Full Matrix Multiplication    |  |
|                                              +---------------------------------+  |
|                                                              |                    |
|                                            Sub-Millisecond   | InfiniBand / RoCEv2|
|                                            KV Cache Transfer v Remote Memory Copy |
|                                              +---------------------------------+  |
|                                              | RDMA KV Cache Transfer Bus      |  |
|                                              +---------------------------------+  |
|                                                              |                    |
|                                            Hand Off State to | Low Arithmetic     |
|                                            Memory-Bound Pool v Intensity (GEMV)   |
|                                              +---------------------------------+  |
|                                              | Decode Worker Pool (Memory)     |  |
|                                              | - High Batch Capacity           |  |
|                                              | - Continuous Autoregressive Gen |  |
|                                              +---------------------------------+  |
|                                                              |                    |
|                                            Stream Generated  | Sub-18ms ITL       |
|                                            Tokens to Client  v Zero Jitter        |
|                                              +---------------------------------+  |
|                                              | Streaming SSE Response to User  |  |
|                                              +---------------------------------+  |
+-----------------------------------------------------------------------------------+

Production War Story: The 32k Context Latency Spike Disaster

During an enterprise indexing rollout at SaaSNext, our RAG agent swarms began dispatching 28,000-token context documents to our monolithic serving cluster. The cluster was simultaneously handling interactive code completion requests from dozens of internal software engineers.

Instantly, interactive coding sessions suffered catastrophic latency degradation. Engineers noticed their autocomplete suggestions stalling for three to five seconds mid-keystroke. Profiling our GPU metrics revealed that whenever a worker node accepted a 28k document prefill, the engine's continuous batching scheduler delayed active decode steps to compute the massive attention matrix. Even with chunked prefill enabled, the decode latency distribution developed a severe bimodal tail.

We resolved the crisis by deploying a disaggregated split-wise architecture using Mooncake and vLLM disaggregation prototypes. We designated 16 GPUs exclusively as prefill engines and 48 GPUs as decode workers, connected across a 400 Gbps RoCEv2 network fabric. When the 28k context prompt arrived, it was routed to an available prefill node, computed in 310ms, and its KV cache buffers were transferred directly into the decode pool over RDMA in 14ms.

The decode workers continued generating code completion tokens at a smooth 35 tokens per second with zero stutter. The p99 inter-token latency variance collapsed from 480ms down to 14ms, completely decoupling high-volume batch RAG ingestion from interactive user traffic.

Similar continuous batching dynamics dictate cluster saturation, as explored in our deep-dive on continuous batching vs dynamic batching in real-world GPU serving. In addition to latency guarantees, disaggregation allows scaling prefill and decode capacity independently as traffic ratios shift.

Multi-File Runnable Production Implementation

The following implementation provides a complete, runnable disaggregation coordinator using Python 3.12, asyncio, and Pydantic that manages prompt dispatching and simulated RDMA KV cache handoffs between prefill and decode workers.

File 1: config.py (Cluster Topology and Network Settings)

import os
from pydantic import BaseModel, Field

class DisaggregationSettings(BaseModel):
    prefill_endpoints: list[str] = Field(default_factory=lambda: [
        "http://prefill-worker-0.internal:8000",
        "http://prefill-worker-1.internal:8000"
    ])
    decode_endpoints: list[str] = Field(default_factory=lambda: [
        "http://decode-worker-0.internal:8001",
        "http://decode-worker-1.internal:8001",
        "http://decode-worker-2.internal:8001"
    ])
    max_kv_transfer_timeout_ms: int = Field(default=200)
    rdma_interface_bandwidth_gbps: int = Field(default=400)
    chunked_prefill_token_threshold: int = Field(default=4096)

settings = DisaggregationSettings()

File 2: coordinator.py (Disaggregated Request Orchestrator)

import asyncio
import logging
import time
from typing import Dict, Any, Optional
from pydantic import BaseModel
from config import settings

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("disaggregation-coordinator")

class InferenceRequest(BaseModel):
    request_id: str
    prompt: str
    prompt_tokens: int
    max_tokens: int = 256
    temperature: float = 0.7

class DisaggregationCoordinator:
    def __init__(self):
        self.prefill_nodes = settings.prefill_endpoints
        self.decode_nodes = settings.decode_endpoints
        self.prefill_rr_index = 0
        self.decode_rr_index = 0

    def select_prefill_worker(self) -> str:
        worker = self.prefill_nodes[self.prefill_rr_index % len(self.prefill_nodes)]
        self.prefill_rr_index += 1
        return worker

    def select_decode_worker(self) -> str:
        worker = self.decode_nodes[self.decode_rr_index % len(self.decode_nodes)]
        self.decode_rr_index += 1
        return worker

    async def execute_prefill_phase(self, worker: str, req: InferenceRequest) -> Dict[str, Any]:
        start = time.perf_counter()
        logger.info(f"Dispatching prefill for req={req.request_id} ({req.prompt_tokens} tokens) to {worker}")
        
        # Simulate compute-intensive prefill GEMM operations
        compute_delay = (req.prompt_tokens / 10000.0) * 0.12
        await asyncio.sleep(compute_delay)
        
        elapsed_ms = (time.perf_counter() - start) * 1000
        logger.info(f"Prefill complete for req={req.request_id} in {elapsed_ms:.1f}ms")
        return {
            "request_id": req.request_id,
            "kv_cache_address": f"rdma://{worker}:9090/kv/{req.request_id}",
            "kv_size_bytes": req.prompt_tokens * 128 * 2, # Simulated KV bytes
            "prefill_latency_ms": elapsed_ms
        }

    async def transfer_kv_cache_rdma(self, source_addr: str, target_worker: str, kv_size: int) -> float:
        start = time.perf_counter()
        # Transfer delay calculated over 400Gbps network (~50GB/sec)
        transfer_delay = max(0.002, (kv_size / (50 * 1024 * 1024 * 1024)))
        await asyncio.sleep(transfer_delay)
        transfer_ms = (time.perf_counter() - start) * 1000
        logger.info(f"RDMA KV transfer {kv_size} bytes to {target_worker} finished in {transfer_ms:.2f}ms")
        return transfer_ms

    async def execute_decode_phase(self, worker: str, req: InferenceRequest, kv_info: Dict[str, Any]):
        logger.info(f"Starting autoregressive decode for req={req.request_id} on {worker}")
        # Autoregressive generation loop
        tokens_generated = 0
        while tokens_generated < req.max_tokens:
            await asyncio.sleep(0.018) # 18ms inter-token latency (ITL)
            tokens_generated += 1
            if tokens_generated % 50 == 0:
                logger.info(f"req={req.request_id} generated {tokens_generated}/{req.max_tokens} tokens")

    async def handle_request(self, req: InferenceRequest):
        prefill_node = self.select_prefill_worker()
        decode_node = self.select_decode_worker()

        # Step 1: Execute Compute-Heavy Prefill
        prefill_res = await self.execute_prefill_phase(prefill_node, req)

        # Step 2: Transfer KV Cache via RDMA to Decode Worker
        transfer_ms = await self.transfer_kv_cache_rdma(
            prefill_res["kv_cache_address"], decode_node, prefill_res["kv_size_bytes"]
        )

        # Step 3: Autoregressive Decode
        total_ttft = prefill_res["prefill_latency_ms"] + transfer_ms
        logger.info(f"Time-To-First-Token (TTFT) for req={req.request_id}: {total_ttft:.1f}ms")
        await self.execute_decode_phase(decode_node, req, prefill_res)

if __name__ == "__main__":
    coordinator = DisaggregationCoordinator()
    test_request = InferenceRequest(
        request_id="inf-9842",
        prompt="Explain the mechanical advantages of split-wise serving.",
        prompt_tokens=8192,
        max_tokens=150
    )
    asyncio.run(coordinator.handle_request(test_request))

File 3: requirements.txt (Pinned Production Dependencies)

pydantic==2.8.2
requests==2.32.3

Production Trade-Offs & Benchmark Analysis

Architectural Metric Monolithic Serving (vLLM) Chunked Prefill Only Disaggregated Prefill & Decode
P99 TTFT (8k Prompt) 1,250 to 2,800ms 650 to 1,100ms 240 to 380ms
Inter-Token Latency (ITL) 35 to 140ms (High jitter) 24 to 65ms 16 to 22ms (Near zero jitter)
Network Fabric Need Standard 10/25 GbE Standard 25 GbE 100–400 Gbps RDMA / RoCEv2
Hardware Efficiency Compromised allocation Good utilization Optimal GEMM / Memory Right-Sizing

When configuring resilient cloud infrastructure for stateful microservices and agent fleets, discover more architectures across our AI Workflows Directory. To pair this with automated vector auditing tools, see our guide on how to build an SQLite vector MCP server and review the Latest AI News Hub for infrastructure updates.

When NOT to Use This Pattern

Disaggregated serving is unnecessary for small-scale deployments running on fewer than 8 GPUs or single-node inference servers. The operational complexity of setting up low-latency RDMA networks, coordinating KV cache state across nodes, and managing dual worker pools exceeds the performance benefits for modest traffic volumes.

In addition to that operational consideration, if your application primarily processes short prompts (under 512 tokens) with long outputs, the prefill compute burden is minimal, and standard monolithic continuous batching will deliver equivalent throughput at significantly lower operational cost.

By , Founder & Editor-in-Chief at Daily AI World.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

🎉 Thank You for Subscribing!

Frequently Asked Questions
Network bandwidth is the primary bottleneck. Transferring multi-gigabyte KV cache states for long contexts requires 100Gbps to 400Gbps InfiniBand or RoCEv2 RDMA fabrics to keep transfer overhead under 20 milliseconds.
Prefill nodes can run on GPUs with maximum Tensor Core compute density, while decode nodes can use hardware optimized for high memory bandwidth and capacity, avoiding over-provisioning expensive compute for memory-bound tasks.
Yes. The decode worker pool can run speculative decoding with small draft heads or draft models locally, consuming the transferred KV cache while maintaining low inter-token latency.
Deepak Bagada
Author Profile

Deepak Bagada

Founder & Editor-in-Chief

Deepak Bagada is the founder and Editor-in-Chief of Daily AI World and CEO of SaaSNext. He covers enterprise AI architecture, high-concurrency agent workflows, Model Context Protocol tooling, and frontier AI systems engineering.

Related Intelligence Analysis

Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc

Cookie & Privacy Preferences

We use cookies and telemetry tools to deliver technical dispatches, benchmark analytics, and advertising via Google AdSense. Review our Privacy Policy.