Self-Hosted LLM Serving, Split in Two: vLLM's Multi-Turn Cache Trusts Your Router, and Its Own Router Doesn't Check
vLLM's new guide to prefill/decode disaggregation is careful about latency and honest about cost. Its one correctness warning points at an issue a bot closed as not planned ten days before the guide went up, and the reference proxy it ships performs none of the checking the docs assume.
A self-hosted LLM on vLLM usually starts as one vllm serve process doing
everything, and the guide names what that costs: a long prompt arrives, and
every stream already generating on that GPU stutters until it is processed.
On 29 September the vLLM project published a long, practical guide to the alternative, built on v0.30.0:
run prefill and decode as separate instances, move tokenization and parsing to
CPU-only boxes, and pass the KV cache between them.
The guide is candid about when not to do this. Its one correctness warning is where the adoption work sits.
What shipped, and what v0.30.0 breaks
v0.30.0 was released on 22 September. The guide assumes it, and builds on three
server shapes: an ordinary vllm serve with a KV connector role, a GPU-free render
tier started with vllm launch render, and a token-in, token-out engine started
with vllm serve --tokens-only. The render tier turns an OpenAI-style request
into token IDs, and a matching derender step turns output IDs back into
content, reasoning and tool calls.
The upgrade cost is one breaking change. The release notes put the scale-out
endpoints (/render, /derender, /inference/v1/generate) behind a new
--enable-scale-out flag on plain vllm serve, and remove the
VLLM_ENABLE_SCALE_OUT_ENDPOINTS environment variable that used to switch them
on. If you set that variable today, those endpoints disappear on upgrade.
Prefill decode disaggregation, by the guide's own numbers
The headline measurement is honest about its baseline. On one box with two L40S GPUs over PCIe, Qwen2.5-7B-Instruct and roughly 8k-token prompts, the collocated p99 inter-token latency jumped to 169 ms at 0.4 requests per second while the split setup held at 29 ms and never passed 52 ms.
The same box shows the price. The two L40S cards cannot copy peer to peer, and each prompt's roughly 470 MB of KV cache took about 1.3 s to reach decode. At 0.2 requests per second the split setup's median time to first token was 2.2 s against 0.7 s collocated. The guide's own table recommends staying collocated when time to first token is the binding constraint.
The independent figures point the same way. The llm-d project's P/D guide, run on gpt-oss-120b with a 20:1 input-to-output ratio at 45 queries per second, reports mean end-to-end latency down 59 percent and p95 down 67 percent. The same table shows mean time to first token rising from 532 ms to 1157 ms. A split deployment trades first-token latency for a flat tail.
The multi-turn mode, and the check nobody runs
Standard disaggregation moves the cache one way, so on turn two prefill
recomputes the answer decode just generated. Setting bidirectional_kv_xfer on
both instances lets prefill pull those blocks back from decode instead, and
compute only the new tokens.
It works only if the new prompt is an exact continuation of what decode
generated. The block alignment in _apply_prefix_caching in v0.30.0 trims by
block count from the end of the sequence; that function does not compare token
content. If a client removes anything from the middle of the history, prefill loads cache
computed for different tokens and the model answers from it. The guide notes
that Qwen3's own chat template drops thinking traces between turns, which is
enough to trigger it. The result is wrong output, not an error.
The guide points readers to issue #43094 as the tracking issue. That issue was closed by the stale bot on 19 September, as not planned, ten days before the guide was published. In its thread, a contributor added that many coding harnesses rewrite tool calls between turns, so the exposure is wider than reasoning models: any agent framework that compacts or edits history qualifies.
The NixlConnector documentation states that vLLM currently assumes the router
detects the mismatch. The router vLLM ships, disagg_proxy_multiturn.py, does
not. It caches decode's transfer parameters keyed on a client-supplied
conversation_id, and on the next turn with that ID it attaches them without
inspecting the messages. Pull request #44838 adds exactly the missing guard:
validate the continuation, and fall back to recompute when the history was
rewritten or the response held reasoning or tool-call fields the proxy cannot
reconstruct. It was opened on 8 June, has had no maintainer review, and was
marked stale on 16 September.
The failure policy will not save you either. kv_load_failure_policy defaults
to fail and can be set to recompute, but both handle a transfer that failed.
A mismatched transfer succeeds.
The verdict
Adopt one-way prefill/decode now if your p99 inter-token latency misses its target under load and your GPUs can talk over NVLink, peer to peer, or RDMA. Skip it if time to first token is what you are measured on.
Do not turn on bidirectional transfer behind the reference proxy for any client that edits history. That is the central claim here, and it is falsifiable: it is wrong if #44838 or an equivalent guard lands in the proxy, or if your router already rejects non-prefix continuations. Either one makes the mode safe to evaluate.
This is the gap our PoC-to-production checklist puts first: evaluation you can defend. A latency benchmark cannot see this bug, because wrong tokens arrive just as fast as right ones. It is also a sibling of the cache-lifetime problem in our look at vLLM's on-disk KV tier: in both, the cache is correct only under an assumption the serving layer does not enforce. Surfacing that kind of assumption is what a private LLM deployment review is for.
Before you split your self-hosted LLM
- Run
nvidia-smi topo -p2p rbetween the prefill and decode GPUs. Anything other than OK is the setup behind the guide's 1.3 s transfers; benchmark before committing. - Send long prompts one at a time and read decode's KV Transfer metrics line. An average transfer time in the hundreds of milliseconds is the first thing to fix.
- Benchmark against one collocated
vllm serve --data-parallel-size 2on the same GPUs using the guide'svllm bench servecommand with--goodput ttft:2000 tpot:30, comparing p99 inter-token latency, time to first token and goodput. - Before enabling
bidirectional_kv_xfer, make your router store each turn's prompt token IDs plus the IDs decode generated, and attach cached transfer parameters only when the next prompt's IDs start with exactly that sequence. A false miss costs a recompute; a false hit costs a wrong answer. - Then replay ten real multi-turn conversations from your agent harness at temperature 0 with the mode on and off. Any difference in output is the mismatch, and the mode stays off.
- If you set
VLLM_ENABLE_SCALE_OUT_ENDPOINTS, replace it with--enable-scale-outbefore upgrading to v0.30.0.
Sources
- Taking vLLM Apart: A Practical Guide to Disaggregated Serving - vLLM blog
- vLLM release v0.30.0
- NixlConnector usage, bidirectional KV transfer - vLLM documentation
- vllm-project/vllm issue #43094 - bidirectional KV transfer produces incorrect results when reasoning traces are stripped
- vllm-project/vllm pull request #44838 - guard bidirectional KV reuse for rewritten history
- examples/disaggregated/disaggregated_serving/disagg_proxy_multiturn.py at v0.30.0
- vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_worker.py at v0.30.0
- vllm/config/kv_transfer.py at v0.30.0
- llm-d P/D disaggregation guide
Related reading
- AI Engineering
Decision Model d1-3B Leads Its Size Class on Public Tests. On the Held-Out Split It Ranks 53rd.
By Petru Popa · Read - AI Engineering
Multimodal RAG on EmbeddingGemma 2: The 6x Storage Cut Is a Text-Only Number
By Petru Popa · Read - AI Engineering
AI Agent Evaluation by Repetition: ThinkingBox's 20-of-20 Column Is Not What Its Own Tool Prints
By Petru Popa · Read
Turn this into a plan for your team.
One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.