Repository navigation
[ROCm][MoRIIO] Support DCP for decode instance in PD disaggregated serving with MoRI WRITE mode - #64722
[ROCm][MoRIIO] Support DCP for decode instance in PD disaggregated serving with MoRI WRITE mode#64722kliuae wants to merge 5 commits into
Conversation
Signed-off-by: kliuae <kuanfu.liu@embeddedllm.com> Co-authored-by: Claude Opus <noreply@anthropic.com>
Signed-off-by: kliuae <kuanfu.liu@embeddedllm.com>
Signed-off-by: kliuae <kuanfu.liu@embeddedllm.com> Co-authored-by: Claude Opus <noreply@anthropic.com>
Signed-off-by: kliuae <kuanfu.liu@embeddedllm.com> Co-authored-by: Claude Opus <noreply@anthropic.com>
Signed-off-by: kliuae <kuanfu.liu@embeddedllm.com> Co-authored-by: Claude Opus <noreply@anthropic.com>
|
Documentation preview: https://vllm--64722.org.readthedocs.build/en/64722/ |
| "decode instance failed", | ||
| task.request_id, | ||
| ) | ||
| self._mark_request_done(task.transfer_id) |
There was a problem hiding this comment.
This can report the transfer finished while another producer is still writing its share. For a hybrid TP2 prefill → TP1 decode transfer, the decode rank has two writers. If rank 0 has posted writes and rank 1’s handshake fails, rank 1 sends write_failed here; the consumer immediately reports both failed_recving and finished_recving, clears its writer count, and allows the scheduler to free/reuse the destination blocks. Rank 0’s writes may still be in flight. This path also handles transient handshake errors, not just uniform layout refusals.
Please retain the failure state but wait for every expected writer to become terminal before reporting receiving finished. A focused fan-in regression with one pending writer and one failed handshake would cover this; the new refusal test currently uses one producer and therefore cannot expose it.
| if not handshake_ready: | ||
| # A request whose handshake failed is not retried; its writes | ||
| # reach the decode ranks that are connected or fail the request. | ||
| if not handshake_ready and not self._writer.is_unconnected( |
There was a problem hiding this comment.
Please also check terminal transfer state before starting a handshake here. The writer can process a refused request’s first task between layer hooks: _mark_request_done() marks it terminal and clears _unconnected, so the next hook enters this branch and starts another handshake. Its callback then re-adds the terminal transfer to _unconnected, but schedule_write() drops terminal tasks and nothing clears that entry afterward.
A CPU probe of this ordering produces two handshakes and a retained marker. Please prevent terminal callbacks from resurrecting the state, and extend the existing test to execute the first task before invoking the next layer hook.
Overview
In WRITE mode,
MoRIIOConnectorcopies prefill blockistraight into decode blocki, so when the two instances store tokens differerntly, like a decode instance with DCP and a prefill instance without DCP, ends up with its tokens on the wrong ranks. This PR makes each prefill rank write each decode rank exactly the tokens that rank holds for MLA models and at any interleave that divides the block size.Claims
Validation
Unit tests
The new tests are CPU tests in a new
tests/v1/kv_connector/unit/test_moriio_dcp_write.py(41 cases) and intest_moriio_hetero_tp_write.pyandtest_moriio_hma_scheduler.py, plus GPU test intest_moriio_connector.py. Intests/test_config.py, MoRIIO becomes a second case of main's interleave test, which is renamed totest_connector_does_not_adjust_dcp_interleave_sizebecause it now covers two connectors. Reverting each part of the change makes its tests fail.jwith decode blockjtest_write_lands_each_token_on_its_dcp_rank,test_write_refuses_decode_allocation_short_of_its_blockslocal_block_ids longer than remote_block_idserror)test_write_lands_each_token_on_its_dcp_ranktest_peer_dcp_layout_acceptedwrite_failedand a new handshake per layer in WRITE mode and a step that raises in READ modetest_refused_write_handshake_fails_the_request_on_every_decode_rank,test_read_refusal_fails_the_requests_on_every_tp_rankbuild_connector_metaraises)test_read_release_without_prefill_address_is_skippedkv_transfer_params)test_handshake_accepts_each_model_at_its_interleaves,test_hybrid_write_into_dcp_splits_mla_tokens_but_not_the_stateand the validator casesThe other new cases cover the API this PR adds, namely which layouts the handshake accepts and refuses (
test_peer_dcp_layout_accepted,test_peer_dcp_layout_refused), which caches may be written in groups (test_only_known_token_layouts_are_written_in_runs) how a hybrid model's layers are written (test_hybrid_write_into_dcp_splits_mla_tokens_but_not_the_state), and which models the handshake accepts at each interleave, from GLM-5.x and DeepSeek-V3.2 to Kimi-Linear, Kimi-K3 with its DSpark draft and the refused non-MLA cases (test_handshake_accepts_each_model_at_its_interleaves).test_write_between_equal_dcp_sizes_is_rank_to_rank(a producer with DCP 4 writes its own blocks to the decode rank with the same index),test_connector_does_not_adjust_dcp_interleave_size[moriio]and the GPU testtest_worker_accepts_decode_context_parallelism(both roles, READ and WRITE, DCP 2, interleave 1) pin main's behaviour where it should not change. At an interleave equal to the block size, the writer test also covers the whole-block path for both TP mappings.End-to-end verification with MLA (DeepSeek-R1)
DeepSeek-R1 runs on one node of 8 MI355X GPUs, with the prefill instance TP4 on GPUs 0-3 and the decode instance on GPUs 4-7. The runs use MoRIIO WRITE over xGMI, the toy proxy in vLLM example,
MORI_IO_XGMI_SCATTER_GATHER_THRESHOLD=0on the prefill, a bf16 KV cache, block size 64,--gpu-memory-utilization 0.90and--max-num-seqs 256. The runs include #64330, which fixes the completion-wait hang over xGMI.The benchmark is
vllm bench servewith random 8K-token prompts and 4K output tokens, 640 prompts at up to 256 concurrent requests and 2 requests/s. Throughput is per GPU over all eight GPUs and counts successful requests. At 256 sequences, about 3.1 M tokens are in flight, roughly twice what a TP4 decode instance holds, so TP4 -> TP4 spends the run waiting for decode blocks. Up to 204 requests wait, and 214 of them exceed the prefill's 60 sdefer_timeoutand fail under the defaultkv_load_failure_policy. With DCP4 the decode cache never gets past half full.DeepSeek-R1 commands
End-to-end verification with hybrid MLA (Kimi-Linear)
Kimi-Linear mixes MLA layers with KDA (linear attention) layers that keep a recurrent state. Both instances share two MI355X GPUs at
--gpu-memory-utilization 0.35each, with MoRIIO WRITE over xGMI, the toy proxy in vLLM example, block size 2,048 on both instances (hybrid attention blocks are sized to fit the per-rank state),VLLM_SSM_CONV_STATE_LAYOUT=DS,MORI_IO_XGMI_SCATTER_GATHER_THRESHOLD=0on the prefill. The runs include #64330, which fixes the completion-wait hang over xGMI.With DCP2 the decode cache grows by 54 % rather than doubling, because only the MLA layers are split across the DCP ranks, while every rank keeps its whole TP share of a request's KDA state.
Kimi-Linear commands
Details
For MLA models, a TPn decode instance keeps a full copy of each request's latent KV on every TP rank. DCP splits the tokens across the ranks instead, so the same GPUs hold up to n times the tokens, and the prefill instance doesn't need DCP for that.
With this PR, a WRITE transfer into a DCP decode instance goes through four steps.
--cp-kv-cache-interleave-size(I) tokens. With DCP N and block size B, one scheduler block covers N x B tokens, and each rank keeps its share in its own physical block under the same block id.write_done.On main the prefill rank pairs its block
iwith decode blocki.So a prompt longer than one decode block fails outright, and a shorter one puts its tokens on the wrong DCP ranks. At MoRIIO's default interleave of 1, a single prefill block is spread over all the decode ranks.
This PR makes the following changes:
dcp_write_runs). Tokentgoes to DCP rank(t // I) % Nat positionp = (t // (I * N)) * I + t % I, which is blockids[p // B], slotp % B, the placementvllm/v1/worker/block_table.pyuses. Since I divides B, each group of I tokens is one write, and at I = B the writes are the existing whole-block writes. MLA latents are written this way. Other DCP-sharded MLA caches, such as the sparse MLA indexer cache of DeepSeek-V3.2, GLM-5.2 and GLM-5.3, are written in whole blocks and need I = B. Recurrent-state layers of hybrid models (Mamba, KDA) are not split by DCP and keep main's TP-slice writes.dcp_sizeandcp_kv_cache_interleave_sizein the handshake, and the instance that opens the connection (the prefill instance in WRITE mode, the decode instance in READ mode) refuses layouts it can't transfer. Those are a DCP-sharded layer that isn't MLA (a GQA model, or a draft model without MLA, which the decode instance runs without DCP), an interleave that doesn't divide the block size or is below it for a whole-block layer, equal DCP sizes with different interleaves, and any other DCP size mismatch. A peer that doesn't advertise its layout is accepted as before.write_failedonce the decode allocation arrives, and the decode instance fails the request perkv_load_failure_policy. In READ mode the TP ranks agree on the refusal (HandshakeRefusedError), report the requests' blocks as invalid and release the prefill's blocks with an empty read, so the engine keeps running. A later release of such a request that carries no prefill address is skipped with a warning instead of raising inbuild_connector_meta.A producer with the same DCP size as the decode instance keeps the original rank-to-rank writes in READ and WRITE mode. MoRIIO also keeps its default interleave of 1, which speculative decoding with DCP on
ROCM_AITER_MLAneeds, while the block size gives the fewest writes.Notes:
AI assistant was used for this PR.
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.