This document is relevant for: Trn2, Trn3
Disaggregated Inference — Design Document#
Overview#
Disaggregated inference (DI) separates the prefill phase (prompt processing) from the decode phase (token generation) into independent server instances. This enables:
Independent scaling — prefill and decode have different compute profiles; scaling them separately matches hardware to workload.
Latency isolation — decode instances run exclusively decode batches, eliminating inter-token latency (ITL) spikes caused by prefill interference.
Asymmetric parallelism — each role can use different TP/DP/EP configurations optimized for its workload.
This document covers the DI architecture as implemented in upstream vLLM and the Neuron-specific extensions in vllm-neuron.
Architecture#
The diagram below illustrates the default read mode (decode pulls KV), which is what the toy proxy server drives.
+----------+ (1) request +-----------------------------+
| Client | ---------------> | Proxy Server |
| | <--------------- | (load balancer; CPU-only, |
+----------+ (5) SSE | no Neuron cores) |
+-----------------------------+
| |
(2) dispatch | | (3) dispatch
(max_tokens=1) | | (w/ kv_transfer_params)
v v
+----------------------+ +----------------------+
| Prefill Server | | Decode Server |
| (kv_producer) | | (kv_consumer) |
| KV cache in device | | generates ALL out- |
| memory | | put tokens, incl. |
| | | the first |
+----------------------+ +----------------------+
^ |
| (3') NIXL RDMA READ: | (4) stream
| decode pulls KV | tokens
+------------------------------+
Flow (read mode): (1) client → proxy. (2) proxy dispatches to prefill
(max_tokens=1) and blocks; prefill produces KV and returns kv_transfer_params.
(3) proxy dispatches to decode with those params; (3’) the decode worker pulls the
KV from the prefill server via a NIXL RDMA READ. (4)/(5) decode generates and
streams tokens back through the proxy to the client.
Components#
Component |
Role |
vLLM Configuration |
|---|---|---|
Prefill server |
Processes prompt, produces KV cache (the one token it samples is discarded) |
|
Decode server |
Pulls KV, generates all output tokens including the first |
|
Proxy server |
Load balancer / router — relays SSE responses. Runs CPU-only (no Neuron cores) |
Application-level (not vLLM) |
Request Flow (read mode)#
Client sends request to the proxy server.
Proxy dispatches the request to a prefill server with
max_tokens=1,stream=false, anddo_remote_decode=true, and blocks on the response.Prefill server computes the forward pass, producing KV cache in its device memory, and returns
kv_transfer_params(engine id, block ids, host/port). The single token it samples is discarded — it exists only to drive KV production.Proxy dispatches the request to a decode server, attaching the prefill’s
kv_transfer_params. When the decode worker starts the request (start_load_kv), it issues a NIXL RDMA READ to pull the KV blocks from the prefill server’s device memory; the request waits inWAITING_FOR_REMOTE_KVSuntil the read completes.Decode server runs autoregressive generation on the transferred KV — the first output token is generated by the decode server — and streams tokens back through the proxy to the client.
In write mode, the prefill server pushes KV to decode and the proxy can dispatch both concurrently (lower TTFT); see the Transfer Modes section. The diagram shows read mode for clarity.
Upstream vLLM Implementation#
KV Connector Framework#
Located in vllm/distributed/kv_transfer/, the connector framework provides:
KVConnectorBase_V1(kv_connector/v1/base.py) — abstract interface with scheduler-side and worker-side methods.Scheduler-side:
get_num_new_matched_tokens(),build_connector_meta(),update_state_after_alloc(),request_finished()Worker-side:
start_load_kv(),save_kv_layer(),wait_for_save(),register_kv_caches()
Connector Implementation#
vLLM ships several KV connectors, but Neuron DI uses NixlConnector:
Connector |
Transport |
Use Case |
|---|---|---|
|
RDMA (NIXL library) |
Production — GPU & Neuron |
Transfer Modes#
Mode |
Direction |
Proxy Behavior |
Latency Impact |
|---|---|---|---|
Read (decode pulls) |
D ← P |
Serial: await prefill, then dispatch decode |
Higher TTFT |
Write (prefill pushes) |
P → D |
Concurrent: both dispatched simultaneously |
Lower TTFT |
V1 Engine Integration Points#
# Scheduler integration
connector.get_num_new_matched_tokens(request, num_computed_tokens)
connector.update_state_after_alloc(request, blocks)
meta = connector.build_connector_meta(scheduler_output)
# Worker integration (context manager in model runner)
with kv_connector_context(scheduler_output):
connector.start_load_kv(forward_context) # Decode: load KV
model.forward(...) # Compute
connector.save_kv_layer(layer, kv, meta) # Prefill: save KV
connector.wait_for_save() # Prefill: block until done
Request States#
WAITING → WAITING_FOR_REMOTE_KVS → WAITING → RUNNING → FINISHED
↑
(KV transfer complete)
vllm-neuron Implementation#
Integration Strategy#
Neuron DI uses the upstream NixlConnector for all topologies, including
hybrid-TP. EFA/LIBFABRIC is also upstream: it is selected via the
kv_connector_extra_config {"backends": ["LIBFABRIC"]} option.
Neuron-Specific DI Features#
Hybrid TP (Asymmetric Parallelism)#
Different tensor parallelism on prefill vs decode:
Prefill: TP=4 (fewer cores, optimized for compute-heavy prompt processing)
Decode: TP=8 (more cores, optimized for memory-bandwidth-bound generation)
The connector handles the head-count mismatch by computing head_ratio and inverse_head_ratio during the NIXL handshake.
Component DP (Decode-Side Module Sharding)#
Component DP shards individual model components (attention, embedding, MLP, lm_head) across DP replicas differently:
{
"neuron_config": {
"attention_dp_size": 4,
"embedding_dp_size": 2,
"mlp_dp_size": 4,
"lm_head_dp_size": 2
}
}
Only supported on the decode server (
kv_role: "kv_consumer")Requires
--data-parallel-size >= 2Forces the MoE engine path (DPEngineCoreProc) to maintain cross-DP process groups
Speculative Decoding with DI (Eagle3)#
Eagle3 speculative decoding works with DI:
Prefill server produces the target KV cache and runs spec decoding to generate the draft KV cache, so both are available to the decode side.
Decode server runs the Eagle3 draft model + verification with rejection sampling.
Tested with DP+EP topologies (e.g.,
dp4tp8_dp4tp8ep32).
Bi-directional KV Transfer (D→P)#
For multi-turn conversations, the decode server sends KV cache back to the prefill server so the next turn avoids recomputation:
export VLLM_NEURON_BIDIRECTIONAL_KV=1
The connector/scheduler handles the D→P direction (is_bidirectional_kv_xfer_enabled):
get_num_new_matched_tokens: Reportsremote_block_idsfrom D as externally cached tokens.update_state_after_alloc: Populates_reqs_need_recvfor the prefill worker to pull KV from decode.request_finished: Returnskv_transfer_paramswithremote_block_idsfor the proxy to forward to the next prefill request.
Supports a remote_pull_threshold — minimum token count before pulling from remote vs recomputing locally.
Streaming KV Transfer Params#
When the decode server streams its response (SSE), kv_transfer_params are emitted in the final streaming chunk; the proxy reads them to relay decode block IDs back to the prefill server (used by bidirectional KV).
This is part of upstream vLLM (≥ 0.21.0).
Role-Specific Warmup#
Environment variables to skip compilation for the unused phase:
VLLM_NEURON_SKIP_PREFILL_WARMUP=1 # Decode-only server
VLLM_NEURON_SKIP_DECODE_WARMUP=1 # Prefill-only server
Saves compilation time — no need to compile prefill NEFFs on a decode-only server.
Configuration Reference#
Prefill Server#
vllm serve $MODEL \
--tensor-parallel-size 8 \
--kv-transfer-config '{
"kv_connector": "NixlConnector",
"kv_role": "kv_producer",
"kv_buffer_device": "cuda",
"kv_connector_extra_config": {"backends": ["LIBFABRIC"]}
}'
Decode Server#
--data-parallel-size / --enable-expert-parallel are only needed for DP+EP
(MoE) decode; a basic decode server omits them.
vllm serve $MODEL \
--tensor-parallel-size 8 \
--kv-transfer-config '{
"kv_connector": "NixlConnector",
"kv_role": "kv_consumer",
"kv_buffer_device": "cuda",
"kv_connector_extra_config": {"backends": ["LIBFABRIC"]}
}'
# DP+EP (MoE) decode adds:
# --data-parallel-size 4 --enable-expert-parallel
Environment Variables#
Variable |
Default |
Description |
|---|---|---|
|
|
NIXL metadata exchange bind address |
|
|
NIXL metadata exchange port |
|
|
Skip prefill NEFF compilation (decode-only) |
|
|
Skip decode NEFF compilation (prefill-only) |
|
|
Enable D→P KV transfer for multi-turn |
|
all |
Core partitioning for single-node DI |
Proxy Server#
The proxy is a CPU-only load balancer (no Neuron cores). A reference
implementation and launch script are in
examples/vllm_neuron/vllm/disaggregated_inference/ — toy_proxy_server.py
(round-robin over prefill/decode instances) and server.sh:
python examples/vllm_neuron/vllm/disaggregated_inference/toy_proxy_server.py \
--prefiller-hosts <P_HOST> --prefiller-ports 8100 \
--decoder-hosts <D_HOST> --decoder-ports 8200 \
--port 8000
Supported Topologies#
Topology |
Description |
Single-Node |
Multi-Node |
|---|---|---|---|
xPyD |
General multi-prefill multi-decode (covers 1P1D, 2P1D, 1P2D, …) |
✓¹ |
✓ |
Hybrid TP |
Different TP per role |
✓ |
✓ |
DP+EP |
Data parallel with expert parallel on decode |
✓ |
✓ |
Spec decode (Eagle3) + DI |
Draft model on decode side |
✓ |
✓ |
¹ Single-node xPyD is supported as long as the device has enough cores and memory for all prefill + decode instances.
Upstream vs. Plugin Responsibility#
Capability |
Upstream vLLM |
vllm-neuron |
|---|---|---|
KV Connector framework (base interfaces) |
✓ |
— |
NixlConnector (RDMA transfer) |
✓ |
— |
LIBFABRIC/EFA backend |
✓ |
— |
Hybrid TP head splitting |
✓ |
— |
Bi-directional KV (D→P) |
✓ |
✓ (config/enablement) |
Streaming kv_transfer_params in SSE |
✓ (≥ 0.21.0) |
— |
Component DP with DI |
— |
✓ |
Role-specific warmup skip |
— |
✓ |
Prefill/decode batch separation |
— |
✓ (NeuronScheduler) |
Bucket padding for compiled NEFFs |
— |
✓ (NeuronScheduler) |
Proxy server (toy) |
✓ (examples) |
✓ (examples) |
Production orchestration |
— |
AIBrix, production-stack |
Data Flow (Detailed)#
Prefill Server (data flow)#
Request arrives → NeuronScheduler
→ Pads to prefill bucket
→ Allocates KV blocks
→ NeuronModelRunner.execute_model()
→ _prepare_model_input() (input_ids, positions, slot_mapping)
→ with maybe_get_kv_connector_output():
→ model.forward() [compiled NEFF]
→ connector.save_kv_layer() per attention layer
→ connector.wait_for_save()
→ sample one token (DISCARDED — only drives KV production; the first
output token is produced by the decode server)
→ Return response (with kv_transfer_params for proxy)
Decode Server (data flow)#
Request arrives (from proxy) → NeuronScheduler
→ Request enters WAITING_FOR_REMOTE_KVS state
→ connector.start_load_kv() triggers NIXL RDMA READ
→ Polls until transfer complete
→ Request moves to RUNNING
→ NeuronModelRunner.execute_model()
→ _prepare_model_input() (uses transferred KV blocks)
→ model.forward() [compiled decode NEFF]
→ sample token
→ Stream tokens back through proxy
NIXL Transfer#
Decode worker Prefill worker
──────────── ─────────────
1. register_kv_caches() 1. register_kv_caches()
→ NIXL memory region registration → NIXL memory region registration
→ Create block descriptors → Create block descriptors
2. Handshake (one-time per P-D pair)
← Exchange: agent handles, block lens, base addrs
3. Transfer (per request)
→ Map block_ids to descriptor_ids
→ Issue RDMA READ (NIXL) → Device memory read by D
→ Poll for completion
→ Notify P to free blocks → Free KV blocks
Performance Characteristics#
Why DI Helps on Neuron#
Prefill and decode use different compiled NEFFs — they can never share a batch anyway (unlike GPU which can mix). DI makes this explicit and eliminates scheduling contention.
Dedicated decode instances maintain steady ITL — no prefill compilation or execution interrupts decode.
Asymmetric parallelism exploits Neuron’s core partitioning — prefill can use fewer cores with higher utilization, decode can use more cores with DP for throughput.
Overheads#
Overhead |
Source |
Mitigation |
|---|---|---|
TTFT increase |
KV transfer latency (RDMA) |
Write mode overlaps transfer with prefill |
First-request latency |
NIXL handshake (one-time) |
Cached per P-D pair |
Memory |
Decode holds KV for all in-flight requests |
Tune |
Compilation time |
Each role compiles only its buckets |
|
Future Directions#
Production orchestration — AIBrix and production-stack integration for autoscaling, health checks, and SLO-aware routing.
This document is relevant for: Trn2, Trn3