Repository navigation
[Perf][KV Connector][Mooncake] Send the KV layout once per peer - #59825
Open
staryxchen wants to merge 4 commits into
Open
staryxchen wants to merge 4 commits into
staryxchen wants to merge 4 commits into
Conversation
staryxchen
requested review from
ApostaC,
NickLucche,
ivanium,
orozery,
wzhao18 and
xuechendi
as code owners
October 2, 2026 21:32
Later pulls carry only the layout version and block ids. A version miss makes prefill reject the pull so decode resends the layout. Signed-off-by: staryxchen <staryxchen@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…its id. Decode used to drop the layout after any FINISH, so a prefill that predates layout_version got layout-less pulls. Matching PP then failed every request, and mismatched PP aligned zero regions and reported success. - Prefill echoes the pull's layout_id in MooncakeXferResponse once it holds that layout. Decode sends short pulls only to peers that echoed it. - A tuple-cache hit also records the layout under the new id, so a repeated layout never leaves a stale entry behind. - layout_id is random per register_kv_caches, so ids do not collide across decode restarts. - A missing layout returns the new LAYOUT_MISS status instead of an error string, and decode logs the resend. Signed-off-by: staryxchen <staryxchen@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Each decode registration mints a new layout_id, so prefill kept one _layout_by_peer entry per decode restart forever. Keep at most 1024 entries in LRU order. An evicted peer gets LAYOUT_MISS on its next short pull and resends the layout, so eviction never affects correctness. Signed-off-by: staryxchen <staryxchen@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Check kv_caches_base_addr directly for a layout-less pull, and log the pull metadata size at DEBUG on every send instead of once per address at INFO. Signed-off-by: staryxchen <staryxchen@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com>
staryxchen
force-pushed
the
feat/mooncake-layout-once
branch
from
October 12, 2026 08:28
42c6778 to
d73fbb9
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
MooncakeConnectornow sends the decode KV layout to each prefill worker once, instead of on every pull. Later pulls carry only block ids and alayout_id, and decode stops attaching the layout only after prefill echoes that id back. This is the exchange-once, cache-per-peer modelNixlConnectoralready uses for its handshake. Follows the packed-region work in #57952.Claims
Validation
Unit tests: 123 passed.
The environment was
uv venv --system-site-packages, with the default test modelfacebook/opt-125m. The cu129 precompiled wheel for the base commit returned 404, so this is not aVLLM_USE_PRECOMPILED=1install.New tests cover:
LAYOUT_MISSresend;pre-commitpasses:ruff-check,ruff-format,typos, andmypy-3.12 --hook-stage manual.Version matrix (Qwen2.5-0.5B, greedy, TP=1, RDMA). "Old" is
origin/main(4e4d75c459). TheMooncakeXferMetadata to <addr> is N bytes (layout|blocks)line is a DEBUG log (VLLM_LOGGING_LEVEL=DEBUG). These runs used an earlier revision that logged the same line once per address at INFO.1615 B (layout), then256 B (blocks)(layout)only, never(blocks)(layout)only for both prefill addresses; nono matching consumeron prefillThe old/new-PP 1/1 and new/old rows were run on the pre-rebase commit; the Mooncake code is identical. The LRU commit does not touch the code those two rows run: an old prefill never executes it, and an old decode sends
layout_id=0, which the new prefill never stores.Bytes and TTFT (DeepSeek-V4-Flash, TP=4, EP, fp8, block 256, 4P4D on one node). Each TTFT row is 8 streamed requests with
max_tokens=1, after warmup.origin/mainThe control-plane time was measured from
send_kv_to_decodeentry to the end of region-length validation, on all 4 ranks, dropping the first pull. The timing code was temporary and is not part of this PR.Accuracy (gsm8k,
exact_match,strict-match, 1319 questions).tests/v1/kv_connector/mooncake_integration/run_accuracy_test.shmax_num_seqs=8Both P/D runs had 100% external prefix hits and 0 failed transfers.
Not covered end to end. Prefill side-channel ports come from
bind_to_random_port, and decode RPC ports from the transfer engine, so a restarted peer always comes back at a new address. Three paths therefore have unit coverage only:LAYOUT_MISSresend;Prefill and decode restarts were run end to end; they re-sent the layout to the new address, with unchanged output.
Details
Why. Every pull sends the whole registered layout in
MooncakeXferMetadata: base addresses, block lengths, layer names, group ids and row offsets. On a 146-region model that is ~9 KB per rank per pull, mostly layer names. Prefill then builds and hashes a tuple of all of it on every batch, just to hit_prepared_transfer_regions. Both costs grow with the number of registered regions, not with the request.Prior art:
NixlConnector. NIXL already exchanges the KV layout once per remote engine and keeps the per-request message small:NixlConnector(pull mode)MooncakeConnector, this PR_ensure_handshake; concurrent requests to the same engine share one handshake futureadd_remote_agentstoreskv_caches_base_addr[engine_id][tp_rank], block lengths, and region metadata_layout_by_peer, keyed by decode host, port, ranks, andlayout_idRemoteMeta: block ids, host, port,engine_id, request idreq_blocksandlayout_idengine_id; a restarted engine has a new id and is handshaken anew, and the replaced engine is cleaned up (_track_remote_engine_replacement)register_kv_cachesmints a newlayout_id; an unknown id getsLAYOUT_MISSand a resendMooncake has no separate handshake round trip; decode simply sends its pull to the prefill side channel. So instead of adding a handshake, this PR makes the first pull carry the layout. Prefill's echo of
layout_idthen plays the part of NIXL's handshake response: decode treats the layout as delivered only after it sees the echo, so a peer that never echoes keeps getting the full layout. NIXL's compatibility hash (NixlHandshakePayload.compatibility_hash) is out of scope here.Protocol.
register_kv_cachesdraws a random nonzero 63-bitlayout_id. A counter would restart at 1 in every process, so a restarted decode at the same address could match a stale prefill entry and be written with the old layout.(decode host, port, tp_rank, pp_size, tp_size, layout_id). It stores them on both a fresh alignment and a_prepared_transfer_regionshit. A hit can carry a new id for a layout prefill has already seen.layout_idinMooncakeXferResponseonce it holds that layout. Decode omits the layout for a worker address only after seeing the echo.FINISHis not enough. A prefill without this change ignores the new field and answersFINISH. With equal PP it would then reject every layout-less pull. With different PP,allow_partial_layerswould align zero regions, transfer nothing, and report success.LAYOUT_MISSstatus. Decode resends with the layout once; a second miss fails the request normally. Only layout-less pulls can receiveLAYOUT_MISS, and only a new decode sends those, so an old decode never sees the new enum value._layout_by_peeris an LRU capped at 1024 entries, because every decode re-registration mints a new id. Eviction is always safe: the evicted decode getsLAYOUT_MISSand resends.Tradeoff. About 130 net lines in the connector, for ~9 KB and ~0.06 ms per pull, with no TTFT change on DeepSeek-V4-Flash. The case for the change is that this overhead is fixed per pull and grows with the region count and layer-name length, so it grows with deeper and multi-cache models. It is not a latency win at today's scale.
Limitations (pre-existing, unchanged here).
_prepared_transfer_regionsis still unbounded. Its key includes decode base addresses, so it grows on every decode restart. Its entries could take the same LRU in a follow-up.engine_idand requests stall instead of failing.req_blocksgets no response.Related PRs.
remote_block_sizetoMooncakeXferMetadata, in code this PR moves into_build_pull_metadata. Whichever lands second should set it on both full and layout-less pulls.layout_idand clears the caches, so a woken peer re-sends or re-receives the layout. That PR also restructuressend_kv_to_decode, so expect a textual conflict.Duplicate check. No open PR sends the Mooncake layout once or caches it per peer. Searched
mooncake layout,mooncake handshakeandmooncake metadatawithgh pr list --repo vllm-project/vllm --state open --search ....AI assistance. This PR was written with AI assistance (Cursor). I reviewed every changed line and ran the validation above.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.