Skip to content

[BugFix][Worker] Preserve decode graph for PD recompute - #17032

Closed
iKeybot-code wants to merge 1 commit into
vllm-project:mainfrom
iKeybot-code:fix/pd-decode-with-hybrid-batch
Closed

iKeybot-code wants to merge 1 commit into
vllm-project:mainfrom
iKeybot-code:fix/pd-decode-with-hybrid-batch

Conversation

@iKeybot-code

@iKeybot-code iKeybot-code commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it?

MRV2 classifies a PD consumer's post-transfer tail-token recomputation as prefill, so has_prefill=True makes mixed decode batches miss FULL_DECODE_ONLY graphs. This patch carries an explicit Mooncake request marker across the no-forward receive step and reclassifies only the first local recomputation; ordinary final prefill tokens remain unchanged. The fix is self-contained in vLLM Ascend, requires no vLLM upstream patch, and has been validated with vLLM v0.28.0 and v0.29.0.

Does this PR introduce any user-facing change?

Yes. PD decode hybrid batches retain uniform-decode graph eligibility without changing APIs.

How was this patch tested?

Environment / validation Baseline Fixed Result
v0.28 related UT files - 179 passed; 1 pre-existing unrelated failure Pass
v0.28 Qwen3-32B-W8A8 PD 128/128 128/128 Pass
v0.28 aggregate throughput 95.722 token/s 104.737 token/s +9.42%
v0.28 migrated decode mean / P99 3.085 / 3.679 s 2.322 / 2.576 s -24.73% / -29.98%
v0.29 targeted UT - 7 + 3 passed Pass
v0.29 TP4+TP4 PD 128/128 128/128 Pass
v0.29 aggregate throughput 117.961 token/s 134.574 token/s +14.08%
v0.29 migrated decode mean / P99 3.294 / 4.585 s 2.587 / 2.867 s -21.47% / -37.47%

The v0.29 run used VLLM_USE_V2_MODEL_RUNNER=1, Mooncake, FULL_DECODE_ONLY, Qwen3-32B-W8A8, and NPU TP4+TP4. Scheduler-to-worker marker propagation and the decode reclassification branch were also confirmed on all four decode workers.

@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 addresses a performance issue in MRV2 where PD consumer recomputation of the final prompt token is incorrectly classified as prefill. By explicitly tracking these requests and reclassifying the first local recomputation step, the system can maintain uniform-decode graph eligibility for mixed batches, significantly improving throughput and latency for migrated requests.

Highlights

  • PD Decode Recompute Logic: Implemented a mechanism to correctly identify and reclassify the final token recomputation of PD consumers as decode instead of prefill, preventing unnecessary graph switches.
  • Request Tracking: Added tracking for PD decode recompute request IDs to ensure accurate state management across scheduler steps and model runner execution.
  • Performance Impact: Improved aggregate output throughput by approximately 9.4% and reduced mean decode latency by nearly 25% for migrated requests.
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:

[Ops][BugFix] Handle PD decode tail recomputation as decode to prevent disabling FULL_DECODE_ONLY replay

Suggested PR Summary:

### What this PR does / why we need it?
This PR addresses an issue where a PD (Prefill-Decode) decode consumer recomputes the last prompt token after installing the transferred hybrid state. Since upstream `ModelRunnerV2` derives `is_prefilling` solely from the prompt boundary, this one-token recomputation incorrectly classifies a mixed decode batch as a prefill batch, disabling `FULL_DECODE_ONLY` replay.

To resolve this, the PR:
1. Tracks requests undergoing PD decode tail-token recomputation in `MooncakeConnector` and `MooncakeScheduler`.
2. Clears these tracked request IDs upon request completion or preemption to prevent leakage.
3. Updates `NPUModelRunner.gather_batch_req_state` to reclassify these tail-recomputation requests as decode rather than prefill, maintaining a uniform decode batch and keeping `FULL_DECODE_ONLY` replay enabled.
4. Adds comprehensive unit tests for both the connector and the model runner.

Additionally, a review comment suggests improving `_get_kv_transfer_req_ids` in `vllm_ascend/worker/v2/model_runner.py` to safely handle cases where `metadata` attributes (like `metadata` or `pd_decode_recompute_req_ids`) might be explicitly set to `None`, preventing potential `TypeError` exceptions.

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

### How was this patch tested?
Tested with new unit tests added in `tests/ut/kv_offload/test_mooncake_connector.py` and `tests/ut/worker/test_model_runner_v2.py`.

Comment on lines +90 to +98
def _get_kv_transfer_req_ids(metadata: object | None) -> set[str]:
"""Return requests explicitly marked for PD tail recomputation."""
if metadata is None:
return set()

req_ids = set(getattr(metadata, "pd_decode_recompute_req_ids", ()))
for child_metadata in getattr(metadata, "metadata", ()):
req_ids.update(_get_kv_transfer_req_ids(child_metadata))
return req_ids

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_kv_transfer_req_ids, calling getattr(metadata, "metadata", ()) can return None if the metadata attribute is explicitly set to None on the object (rather than being absent). This will cause a TypeError: 'NoneType' object is not iterable when the loop attempts to iterate over it. Similarly, pd_decode_recompute_req_ids could also be None in some metadata implementations, which would cause set(None) to raise a TypeError.

Using getattr(..., None) or () is a safer pattern that guarantees an iterable fallback.

Suggested change
def _get_kv_transfer_req_ids(metadata: object | None) -> set[str]:
"""Return requests explicitly marked for PD tail recomputation."""
if metadata is None:
return set()
req_ids = set(getattr(metadata, "pd_decode_recompute_req_ids", ()))
for child_metadata in getattr(metadata, "metadata", ()):
req_ids.update(_get_kv_transfer_req_ids(child_metadata))
return req_ids
def _get_kv_transfer_req_ids(metadata: object | None) -> set[str]:
"""Return requests explicitly marked for PD tail recomputation."""
if metadata is None:
return set()
req_ids = set(getattr(metadata, "pd_decode_recompute_req_ids", None) or ())
for child_metadata in getattr(metadata, "metadata", None) or ():
req_ids.update(_get_kv_transfer_req_ids(child_metadata))
return req_ids

@iKeybot-code
iKeybot-code force-pushed the fix/pd-decode-with-hybrid-batch branch from 173a1e5 to 9fbc96e Compare September 20, 2026 16:24
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Track Mooncake remote-prefill requests across the no-forward receive step and mark only their first local recompute as decode-like. This keeps mixed decode batches eligible for uniform decode graphs without misclassifying ordinary final prefill tokens.

Add lifecycle cleanup and regression coverage for mixed batches, multi-token decode queries, nested connector metadata, aborted requests, and reused request IDs.

Signed-off-by: likailong <likailong5@huawei.com>
@iKeybot-code
iKeybot-code force-pushed the fix/pd-decode-with-hybrid-batch branch from 3633762 to 3c21c0f Compare September 21, 2026 08:15
@iKeybot-code
iKeybot-code marked this pull request as draft September 21, 2026 12:01
realliujiaxu pushed a commit that referenced this pull request Sep 22, 2026
…17128)

## Summary

When PD decode uses `recompute_scheduler_enable=true`, the last
prompt-token recompute is reported as prefill by the upstream batch
state. This keeps `has_prefill` true and makes a mixed decode batch miss
`FULL_DECODE_ONLY` graphs. Reclassify only the prompt-boundary recompute
on a PD consumer, then refresh `has_prefill` and the uniform decode
token count. This path does not require MTP and does not depend on
#17032.

| Validation | Baseline | Fixed |
|---|---:|---:|
| Targeted UT | — | 4 passed |
| Qwen3-32B-W8A8 requests | 128/128 | 128/128 |
| Aggregate output throughput | 121.84 tok/s | 137.59 tok/s (+12.93%) |
| Mean join decode latency | 3.2335 s | 2.3539 s (-27.20%) |

Full `test_model_runner_v2.py`: 40 passed; its one remaining Spec-PP
failure is also reproducible on the unmodified baseline.
- vLLM main:
vllm-project/vllm@84030bb

Signed-off-by: likailong <likailong5@huawei.com>
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

tangdafu pushed a commit to tangdafu/vllm-ascend that referenced this pull request Sep 23, 2026
…llm-project#17128)

## Summary

When PD decode uses `recompute_scheduler_enable=true`, the last
prompt-token recompute is reported as prefill by the upstream batch
state. This keeps `has_prefill` true and makes a mixed decode batch miss
`FULL_DECODE_ONLY` graphs. Reclassify only the prompt-boundary recompute
on a PD consumer, then refresh `has_prefill` and the uniform decode
token count. This path does not require MTP and does not depend on
vllm-project#17032.

| Validation | Baseline | Fixed |
|---|---:|---:|
| Targeted UT | — | 4 passed |
| Qwen3-32B-W8A8 requests | 128/128 | 128/128 |
| Aggregate output throughput | 121.84 tok/s | 137.59 tok/s (+12.93%) |
| Mean join decode latency | 3.2335 s | 2.3539 s (-27.20%) |

Full `test_model_runner_v2.py`: 40 passed; its one remaining Spec-PP
failure is also reproducible on the unmodified baseline.
- vLLM main:
vllm-project/vllm@84030bb

Signed-off-by: likailong <likailong5@huawei.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.

1 participant