Skip to content

[v0.26.0rc][BugFix][Mooncake] Fix DCP mapping and support P/D-only DCP in PD disaggregation (#16492) - #16844

Open
lllyys wants to merge 2 commits into
vllm-project:releases/v0.26.0rcfrom
lllyys:cp/16492-v0.26.0rc
Open

lllyys wants to merge 2 commits into
vllm-project:releases/v0.26.0rcfrom
lllyys:cp/16492-v0.26.0rc

Conversation

@lllyys

@lllyys lllyys commented Sep 18, 2026 •

Copy link
Copy Markdown

What this PR does / why we need it?

Cherry-pick of #16492 to releases/v0.26.0rc. Stacked on #16833 — please
merge that one first
(this PR shows both commits until then).

Fix DCP mapping in the Mooncake connector and add P/D-only DCP support in PD
disaggregation: a decode-only DCP fast path (_get_decode_only_dcp_metadata)
for P without CP → D with DCP, corrected unequal-DCP block/port mapping
(min()-based shard mapping that also supports local DCP > remote DCP for
pure-DCP setups), and replicate-K indexer pull filtering so non-replicate-K
ports do not overwrite the full indexer cache transfer.

Original PR: #16492 (main commit 00b0b97)

Backport scope note (PCP): on main, #16492 also reworks PCP+DCP semantics
on top of the MRV2 invariant that the DCP group spans PCP (PCP ranks hold
complete KV replicas, from #15809/#16080 — not on this branch). This branch
still treats PCP as a KV shard axis. This backport therefore keeps the
existing product-based CP geometry wherever PCP is involved — numerically
identical to main's DCP-only forms when PCP == 1 — and gates the new
decode-only DCP path to PCP == 1 on both sides. Result: pure-DCP setups get
all of #16492's fixes; existing PCP transfer behavior on this branch is
unchanged. The PCP+DCP portion of #16492 is intentionally not ported.

Backport conflicts and how they were resolved

All 6 conflict hunks are in mooncake_connector.py. The root cause is the
semantic divergence above, so each resolution follows one rule: main's
DCP-only expressions are adopted where they are equivalent or purely additive
for PCP == 1, and this branch's product-based forms are kept wherever PCP > 1
behavior would change.

  1. _get_kv_split_metadata gate: added main's decode-only-DCP early
    return, additionally gated to self.pcp_size == 1 and meta.remote_pcp_size == 1; kept this branch's product fast-path gate
    (remote_pcp * remote_dcp * pcp * dcp == 1) so remote-PCP transfers keep
    taking the CP path that populates remote_port_send_num (completion
    accounting).
  2. Function rename _get_cp_shard_pulls → _get_dcp_shard_pulls: this
    branch's body already matched main's, including the remote_pcp_size
    parameter; only name and docstring changed.
  3. pp_rank formula: took main's (port - base) // (ptp * remote_pcp),
    a strict generalization of this branch's
    0 if remote_pcp > 1 else (port - base) // ptp (PCP and PP are mutually
    exclusive, so results agree on all legal layouts).
  4. HMA group-pulls gate: kept this branch's product cp_transfer gate,
    plus a decode-only-DCP exception that routes those ports to the hybrid
    rank-table lookup — their ports are selected via the rank table, and the
    shard-pulls builder would drop attention pulls for later PP stages.
  5. Call site updated for the rename in (2).
  6. add_request loop: took main's side (replicate-K-filtered
    group_pulls, shard_idx indexing) and renamed this branch's loop
    variable pcp_dcp_rank → shard_idx to match.

Other adaptations:

  • Inlined two helpers the new decode-only path calls, verbatim from main,
    because this branch lacks them: _get_selected_pcp_rank (origin [Feature][MRV2][P/D] Support PCP KV transfer #16080)
    and _get_kernel_block_scale (origin [Feature] Mooncake_connector support MinmaxM3 #13457).
  • The relaxed either-direction CP-size divisibility applies to pure-DCP
    only; whenever either side has PCP > 1 the parent's one-direction rule
    (remote % local == 0) is kept, because completion accounting for a
    larger local CP size is only implemented for pure DCP.
  • Tests: the ownership test runs the pure-DCP geometry valid on this branch
    (remote PCP=1, remote DCP bounded by prefill TP, port layout
    source_cp_rank = offset % remote_dcp); the SFA decode-only test is
    restricted to PCP=1 (matching the gated path); the replicate-K ratio test
    fixture sets vllm_config (this branch reads vllm_config.model_config
    directly).

Independent review (Codex)

Reviewed pre-submission with Codex over three rounds; it executed extracted
connector methods with real CPU PyTorch against both the parent branch and
the backport:

  • Round 1 — REQUEST CHANGES. A faithful DCP-only port (first attempt)
    broke remote-PCP transfers: the DCP-only fast path skipped
    remote_port_send_num population while the retained product gate indexes
    it (reproduced KeyError for P TP=1/PCP=2 → D TP=1, including a
    previously-working one-block prompt), and unselected P-side PCP ranks
    would stay pending until the abort timeout; the decode-only path's
    pcp_offset is never removed by this branch's table lookup (reproduced
    KeyError). This review drove the resolution model above.
  • Round 2 — REQUEST CHANGES. The restored product gate routed
    decode-only-DCP ports through the shard-pulls builder, dropping attention
    pulls for later pipeline stages (reproduced with P TP=2/PP=2 → D
    TP=2/DCP=2, MLA+Mamba groups); the either-direction divisibility newly
    accepted PCP configs whose completion accounting under-counts (sender
    frees KV after the first pull); two added tests still encoded main's PCP
    topology. All fixed as described above.
  • Round 3 — APPROVE. All six added/modified test methods pass in
    isolated execution (67 subcases, including all 18 ownership cases);
    1,224 parent-vs-backport comparisons match, including 624 accepted and
    456 rejected PCP cases plus 27 replicate-K comparisons; no remaining
    inconsistency between path selection, port mapping, and completion
    accounting.

Does this PR introduce any user-facing change?

No API changes. P/D-only DCP (P without CP → D with DCP) PD disaggregation
now works on this branch; previously-rejected unequal pure-DCP
configurations (local DCP > remote DCP) are now supported.

How was this patch tested?

  • Unit tests from the original PR adapted to this branch in
    tests/ut/kv_offload/test_mooncake_connector.py (decode-only DCP cache
    group handling, KDA state/TP owners, SFA decode-only block mapping,
    replicate-K ratio, DCP ownership coverage).

  • Method-level isolated execution of the added tests and 1,224 before/after
    mapping/count comparisons against the parent branch (accepted and rejected
    PCP cases match the parent exactly) — see the review notes above.

  • CI unit tests on this branch.

  • vLLM main: vllm-project/vllm@d02df74

…vllm-project#16487)

Fix shape mismatches when DCP is enabled with PD-disaggregated
recomputation. Short recomputation requests, including last-token
recomputation, are now correctly classified as decode requests in the
attention, MLA, and SFA metadata builders.

Cache DCP state during initialization and use the builder's
configuration so metadata construction works outside the current-config
context.

No API or configuration changes. This fixes failures in DCP-enabled
PD-disaggregated recomputation.

- Added regression tests covering PD recomputation, mixed query lengths,
and behavior when the override does not apply.
- Added coverage for metadata construction without an active
current-config context.

- vLLM main:
vllm-project/vllm@a97dacb

---------

Signed-off-by: weiguihua2 <weiguihua2@huawei.com>
(cherry picked from commit cab3719)
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi! Thank you for contributing to the vLLM Ascend project. The following points will speed up your PR merge:‌‌

  • A PR should do only one thing, smaller PRs enable faster reviews.
  • Every PR should include unit tests and end-to-end tests ‌to ensure it works and is not broken by other future PRs.
  • Write the commit message by fulfilling the PR description to help reviewer and future developers understand.

If CI fails, you can run linting and testing checks locally according Contributing and Testing.


Tip

💡 Consider Linking a Related Issue or RFC

Your PR title contains the [BugFix] tag, indicating a bug fix or new feature.

Linking a related issue or RFC in the PR description is strongly encouraged — it gives reviewers helpful context and speeds up the review. You can use any of these keywords:

  • Fixes #<issue_number>
  • Closes #<issue_number>
  • Resolves #<issue_number>
  • Refs #<rfc_or_issue_number> (for RFCs)

🙏 Thanks for helping us keep the project well-organized!

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request backports critical fixes and enhancements for the Mooncake connector to the v0.26.0rc release branch. It introduces support for P/D-only DCP disaggregation, improves block and port mapping for unequal DCP configurations, and ensures robust indexer cache transfers by refining replicate-K filtering. These changes enhance the reliability and flexibility of KV offloading in context-parallel setups.

Highlights

  • DCP Mapping Fixes: Corrected unequal-DCP block and port mapping to support configurations where local DCP is greater than remote DCP.
  • P/D-only DCP Support: Added a decode-only DCP fast path for P without CP to D with DCP disaggregation, enabling new deployment geometries.
  • Replicate-K Indexer Filtering: Updated replicate-K indexer pull filtering to prevent non-replicate-K ports from overwriting the full indexer cache transfer.
  • Backport Adaptations: Integrated necessary helper functions and adapted tests to ensure compatibility with the release branch while maintaining existing PCP transfer behavior.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Suggested PR Title:

[Attention][Feature] Support decode-only DCP and adapt CP layout for PCP shard axis

Suggested PR Summary:

### What this PR does / why we need it?
This PR introduces support for decode-only DCP (Distributed Context Parallel) transfers and adapts the context parallel layout to handle configurations where PCP (Pipeline Context Parallel) remains a shard axis. Specifically, it:
- Implements `_get_decode_only_dcp_metadata` in `MooncakeConnectorWorker` to map global blocks to each rank during decode-only DCP.
- Updates metadata builders (`AscendAttentionDCPMetadataBuilder`, `AscendSFADCPMetadataBuilder`, and `AscendMLAMetadataBuilder`) to treat short extends as decodes when DCP and the PD decode recompute scheduler are enabled.
- Refines CP layout validation and parameter checks to support bidirectional divisibility of P/D CP sizes.
- Adds comprehensive unit tests for these changes.

Feedback:
- In `_get_decode_only_dcp_metadata`, a potential `IndexError` or incorrect block mapping may occur if `meta.local_full_block_ids` is `None` and `meta.num_computed_tokens > 0`. A safer fallback to `meta.local_block_ids` with proper index offset handling is recommended.
- In `start_load_kv`, a guard should be added to prevent a potential `KeyError` when accessing `self.kv_group2layeridx[pull.group_id]`.

### Does this PR introduce _any_ user-facing change?
No.

### How was this patch tested?
Tested with new and updated unit tests in `test_attention_cp.py`, `test_mla_v1.py`, `test_sfa_cp.py`, and `test_mooncake_connector.py`.

Comment on lines +2848 to +2857
local_blocks = (meta.local_full_block_ids or meta.local_block_ids)[group_id]
remote_blocks = meta.remote_block_ids[group_id]
first_block = meta.num_computed_tokens // self.block_size
first_block += (self.dcp_rank - first_block) % self.dcp_size
# P owns the full sequence; D rank r owns r, r + DCP, ... .
global_blocks = range(first_block, min(meta.num_prompt_blocks, len(remote_blocks)), self.dcp_size)
scale = self._get_kernel_block_scale(layer_indices)
local_block_ids[block_id_idx] = self._expand_block_ids(
[local_blocks[block // self.dcp_size] for block in global_blocks], scale
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In _get_decode_only_dcp_metadata, if meta.local_full_block_ids is None (e.g., when prefix caching is disabled or not triggered), local_blocks falls back to meta.local_block_ids. Since meta.local_block_ids only contains the blocks for the external tokens, indexing it with block // self.dcp_size (which is a global block index) will lead to an IndexError or incorrect block mapping when meta.num_computed_tokens > 0. We should safely handle both full and incremental block ID lists and guard against potential None or out-of-bounds accesses.

            if meta.local_full_block_ids and group_id < len(meta.local_full_block_ids):
                local_blocks = meta.local_full_block_ids[group_id]
                is_full = True
            elif meta.local_block_ids and group_id < len(meta.local_block_ids):
                local_blocks = meta.local_block_ids[group_id]
                is_full = False
            else:
                raise ValueError(f"No local block IDs found for group {group_id}")
            remote_blocks = meta.remote_block_ids[group_id] if meta.remote_block_ids and group_id < len(meta.remote_block_ids) else []
            first_block = meta.num_computed_tokens // self.block_size
            first_block += (self.dcp_rank - first_block) % self.dcp_size
            # P owns the full sequence; D rank r owns r, r + DCP, ... .
            global_blocks = range(first_block, min(meta.num_prompt_blocks, len(remote_blocks)), self.dcp_size)
            scale = self._get_kernel_block_scale(layer_indices)
            local_indices = [
                block // self.dcp_size if is_full else (block - first_block) // self.dcp_size
                for block in global_blocks
            ]
            local_block_ids[block_id_idx] = self._expand_block_ids(
                [local_blocks[idx] for idx in local_indices if idx < len(local_blocks)], scale
            )

Comment on lines +3730 to +3735
group_pulls = [
pull
for pull in group_pulls
if self.kv_group2layeridx[pull.group_id][0]["kv_cache_spec_type"]
!= "AscendSFAIndexerCacheSpec"
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Add a guard to ensure pull.group_id exists in self.kv_group2layeridx before accessing it, preventing potential KeyError exceptions during group pulls filtering.

                        group_pulls = [
                            pull
                            for pull in group_pulls
                            if pull.group_id in self.kv_group2layeridx
                            and self.kv_group2layeridx[pull.group_id][0]["kv_cache_spec_type"]
                            != "AscendSFAIndexerCacheSpec"
                        ]

…CP in PD disaggregation (vllm-project#16492)

Mooncake's MRV2 context-parallel transfers can select incorrect source
ports and KV blocks when DCP already spans PCP ranks. A decode worker
with DCP also needs to select its local shards from an unsharded prefill
cache, while the replicated indexer must be transferred only once.

This patch corrects the port/block mapping and keeps decode-only DCP in
an explicit branch: `self.dcp_size > 1 and meta.remote_dcp_size == 1`
calls `_get_decode_only_dcp_metadata`. The existing non-DCP branch and
general CP flow remain separate. The helper handles MLA blocks,
replicated Indexer caches and KDA/Mamba state explicitly, and retains
the equal P/D block-size requirement. This avoids combining the existing
transfer paths into a new global block-mapping abstraction.

Based on upstream commit cdad5a3. This PR contains the standalone
Mooncake connector fix and its tests; it does not include the separate
SFA operator or attention-backend experiments.

Fixes MRV2 DCP port/block selection for P/D disaggregation, including
one-sided DCP and PCP+DCP configurations. No new configuration or public
interface.

- Restored the connector and its regression tests to the exact contents
of bfea331, before the combined
block-mapping refactors. The restoration commit is
037f514.
- Verified that the restored tree has no file differences from that
historical commit; `git diff --check` passed.
- Runtime tests have not been rerun for this restoration. Earlier
regression results do not establish full-model or all-model coverage for
the current PR. Fresh CI and P/D regression remain required.
- No test logs, reports, experimental operator patches or model weights
are committed.

Paired vLLM source:
vllm-project/vllm@84030bb

- vLLM main:
vllm-project/vllm@84030bb

Signed-off-by: chengruiqi (C) <c00913489@china.huawei.com>
Co-authored-by: chengruiqi (C) <c00913489@china.huawei.com>
(cherry picked from commit 00b0b97)
Signed-off-by: lllyys <221728326+lllyys@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants