HomeJournalThis post

Disaggregated LLM Inference That Scales

Compare unified, chunked-prefill, and split serving before paying the cache-transfer cost of separate prompt and generation pools.

JP
JP Casabianca
UI/UX designer and full-stack engineer · Bogotá

Disaggregated LLM inference can protect both first-token speed and decode smoothness by placing prefill and generation on different workers. This guide shows when that separation beats a unified server and when transferring the cache costs more than it saves.

The intended reader operates a GPU-backed model service and can replay representative prompts. You will leave with a three-mode benchmark, a placement rule, and a rollback receipt that compare user-visible latency instead of raw accelerator utilization.

The operating vocabulary separates prefill decode separation, KV transfer, inference serving, and time to first token; each names one part of the same capacity decision.

disaggregated LLM inference: prefill and decode pools joined by a measured state handoff An authored system diagram connects Request, Prefill pool, Cache link, Decode pool as one decision path. RequestPrefill poolCache linkDecode pool
  1. Request
  2. Prefill pool
  3. Cache link
  4. Decode pool
Figure 1: The topology gives prompt work and token generation separate capacity pools, but the handoff remains inside the measured request path.

Disaggregated LLM inference starts with two clocks

disaggregated LLM inference begins with measuring request arrival-to-first-token separately from the spacing between later tokens. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision. The vLLM disaggregated-prefill documentation documents the supported separation and explicitly frames it as a way to control interference rather than improve total token throughput.

Work through four explicit moves:

  • Record queue, prompt, handoff, and generation time
  • Group results by prompt and output length
  • Report medians beside p95 and p99
  • Reject wins that move pain between clocks

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is averaging the entire response into one latency number. Its consequence is a faster prompt path can hide visibly uneven generation.

Mitigate it with two explicit service objectives and per-cohort histograms. The release receipt is a paired latency report with request counts and percentile definitions. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Choose separation for interference, not fashion

A useful disaggregated LLM inference decision depends on proving that prompt-heavy work is blocking token generation on the same devices. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision. The DistServe research paper provides the original resource-separation argument and motivates evaluating prompt and generation objectives independently.

Work through four explicit moves:

  • Replay the production length distribution
  • Measure queue overlap on one worker pool
  • Reserve an equivalent decode cohort
  • Compare the same arrival trace after separation

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is splitting a service that has no material phase interference. Its consequence is network hops and idle fragments add cost without improving experience.

Mitigate it with a unified control and a chunked-prompt alternative. The release receipt is one table showing all three modes under identical traffic. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Benchmark unified, chunked, and split modes

The worked disaggregated LLM inference fixture makes keeping models, tokenizers, prompts, outputs, concurrency, and warm-up conditions identical. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Freeze model revision and numerical settings
  • Replay one versioned request corpus
  • Sweep concurrency without changing arrivals
  • Store raw samples for independent calculation

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is giving the new topology a friendlier workload. Its consequence is the benchmark becomes a capacity demo rather than a causal comparison.

Mitigate it with one seeded trace and one harness for every mode. The release receipt is hashes for the corpus, configuration, and raw result file. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

SignalDecisionProof
Long promptsSeparate poolsFirst-token tail improves
Tiny promptsStay unifiedHandoff cost exceeds gain
Decode pressureReserve generationToken cadence stays stable
Figure 2: Prompt length and queue interference decide placement; a topology diagram alone cannot prove the split is useful.

Make the handoff a versioned contract

disaggregated LLM inference needs an explicit rule for binding every transferred block to request, model, tokenizer, adapter, layer range, and destination ownership. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Create an immutable transfer identifier
  • Declare expected block count and byte length
  • Acknowledge only after complete validation
  • Abort to a safe route on mismatch

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is continuing from incomplete or incompatible prompt state. Its consequence is the decoder can produce corrupt output or read another request's memory.

Mitigate it with length checks, identity fields, checksums, and destination-scoped leases. The release receipt is a transfer log that proves validation before continuation. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Runnable artifact. Save this as disaggregated-llm-inference.test.mjs and run node --test disaggregated-llm-inference.test.mjs. Expected result: PASS: choose split only after net TTFT gain. The checked-in copy lives with this batch's evidence.

import assert from "node:assert/strict";
import test from "node:test";

const modes = [
  { name: "unified", ttft: 820, itl: 32, transfer: 0 },
  { name: "chunked", ttft: 690, itl: 31, transfer: 0 },
  { name: "split", ttft: 510, itl: 29, transfer: 85 },
];

function choose(rows, minimumNetGain = 150) {
  const unified = rows.find((row) => row.name === "unified");
  const split = rows.find((row) => row.name === "split");
  const netGain = unified.ttft - (split.ttft + split.transfer);
  return netGain >= minimumNetGain && split.itl <= unified.itl ? "split" : "unified";
}

test("chooses the topology from user-visible clocks", () => {
  assert.equal(choose(modes), "split");
  assert.equal(choose(modes, 250), "unified");
  console.log("PASS: choose split only after net TTFT gain");
});

Model bandwidth as serving capacity

In production, disaggregated LLM inference turns on treating the cache link as a finite shared resource rather than a free internal detail. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Measure state bytes per prompt token
  • Cap concurrent handoffs per link
  • Prioritize requests by deadline and size
  • Expose saturation before queuing explodes

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is sizing only the GPU pools. Its consequence is a congested fabric becomes the new serialized prompt queue.

Mitigate it with link budgets, bounded queues, and admission control. The release receipt is handoff throughput beside queue age and dropped work. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Route by workload, not a global toggle

Safe disaggregated LLM inference requires letting short prompts, long prompts, and latency-sensitive streams take different paths under one declared policy. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Define cohorts before inspecting results
  • Assign a default and an exception route
  • Log the chosen rule and reason
  • Measure fallback traffic separately

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is forcing every request through one fashionable architecture. Its consequence is small requests pay unnecessary transport while heavy ones still contend.

Mitigate it with a deterministic cohort router with conservative defaults. The release receipt is a routing confusion matrix and outcome by cohort. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

  1. RouteRoute

    Classify prompt size and service objective.

  2. PrefillPrefill

    Produce the prompt state on the assigned pool.

  3. TransferTransfer

    Move only the versioned blocks required to continue.

  4. DecodeDecode

    Generate while reporting cadence and queue delay.

Figure 3: The handoff is successful only when model identity, block completeness, and continuation ownership agree before decoding begins.

Roll out with a unified escape route

A disaggregated LLM inference rollout should preserve preserving a tested single-pool path when workers, links, or transfer metadata become unhealthy. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Probe every phase independently
  • Drain rather than strand owned work
  • Route new traffic to the control
  • Rehearse recovery during representative load

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is treating the split pool as mandatory hidden capacity. Its consequence is one degraded link can stop otherwise healthy generation.

Mitigate it with load-tested failover and bounded retry ownership. The release receipt is a fault-injection trace with no duplicate or orphaned request. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Decide with service-level evidence

The evidence for disaggregated LLM inference is strongest when requiring a meaningful latency gain for target cohorts without higher error rate, cost, or unfairness. This is the narrow boundary for this section; everything outside it belongs in a separate capacity, policy, or product decision.

Work through four explicit moves:

  • Set the improvement threshold in advance
  • Compare equal successful-request populations
  • Inspect slow and rejected cohorts
  • Record the boundary where unified still wins

In a working review, I would put the first move beside the input fixture, use the second to expose the decision boundary, and make the third observable before polishing the interface. The fourth move is the release check. This order matters because a convincing happy path can still conceal incompatible state, unfair scheduling, inaccessible fallback, or ownership ambiguity. Keeping each move named also lets another engineer reproduce the result without inheriting private context.

The named failure mode is shipping because one percentile moved in one run. Its consequence is noise or selective reporting becomes architecture.

Mitigate it with repeated trials, confidence intervals, and declared exclusions. The release receipt is a signed decision note linking samples to the rollout rule. Those fields connect the implementation to the article's single question and make a later update comparable instead of anecdotal.

Put the decision into practice

Disaggregated LLM inference is valuable when prompt work measurably interferes with generation and a separate pool restores both clocks by more than the state-transfer cost. It is not a universal throughput trick; the method earns its complexity one workload cohort at a time.

Begin with the single seeded trace in the runnable artifact, then replace its toy samples with raw observations from unified, chunked, and split modes. Ship only the routing boundary that survives equal traffic, repeated trials, and a failed-handoff rehearsal.

The method connects to four existing Journal notes: KV cache optimization, LLM admission control, backpressure and flow control, SLOs that follow user journeys. Each link covers an adjacent boundary while this article stays focused on one outcome. Keep the fixture, visual evidence, command output, and release receipt together so the next review can test the claim against the same starting conditions.