Skip to content

[Spec][V2] Support MTP speculative decoding under pipeline parallelism - #46994

Open
eastwood-c wants to merge 15 commits into
vllm-project:mainfrom
eastwood-c:v2-mtp-pp-rebase
Open

[Spec][V2] Support MTP speculative decoding under pipeline parallelism#46994
eastwood-c wants to merge 15 commits into
vllm-project:mainfrom
eastwood-c:v2-mtp-pp-rebase

Conversation

@eastwood-c

@eastwood-c eastwood-c commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Purpose

MTP speculative decoding does not currently work under pipeline parallelism on the V2 model runner. This PR makes it functional for DeepSeek-family MTP drafts (DeepSeek-V3, GLM-5.2, Qwen3.5/3.6, …). Five independent issues, all on the PP>1 path. Fixes #1-#3 are DeepSeek-family-specific; fix #4 applies to all models using sparse MLA attention; fix #5 applies to Qwen3.5/3.6 MTP draft models on the last PP rank.

1. DeepSeekMTP does not implement SupportsPP — DeepSeek-family specific. The engine refuses to build the draft model under PP at all:

NotImplementedError: Pipeline parallelism is not supported for this model.
Supported models implement the `SupportsPP` interface.   [DeepSeekMTPModel]

The MTP draft runs only on the last PP stage, so it never actually consumes PP intermediate tensors, but the interface still requires the make_empty_intermediate_tensors factory. (This mirrors what #39704 does for the V1 runner.)

2. PPHandler sampled-token broadcast width mismatch (hang) — affects all MTP under PP. broadcast() sends sampled_token_ids at its natural width — 1 on any step with no draft tokens (prefill, first decode), num_spec+1 once rejection sampling has run — while receive() always posts a fixed [num_reqs, max_sample_len] buffer. NCCL broadcast doesn't negotiate element counts, so a width-1 send against a width-max_sample_len receive is a count mismatch that deadlocks the receiver. Fix: pad the source to max_sample_len (trailing -1, ignored by post_update).

3. Proposed draft tokens are never relayed to non-last PP ranks (garbage output / ~0 acceptance) — affects all MTP under PP. req_states.draft_tokens is written only on the last rank (the propose() path); non-last ranks keep the zero-init buffer. combine_sampled_and_draft_tokens then embeds zeros at the draft positions on rank 0, so the verification input is wrong. Fix: coalesce a third broadcast (the proposed draft tokens) into the existing deferred PPHandler sibling-group broadcast, and scatter it into req_states.draft_tokens on consume. No new collective; gated identically to the sampled-token broadcast so per-step op counts stay matched.

4. Stale topk_indices_buffer reference in sparse MLA backends (the acceptance fix) — affects all models using sparse MLA attention. Under MTP+PP, FlashAttnMLASparseImpl.__init__ stored indexer.topk_indices_buffer at construction time. When _maybe_share_lm_head later replaced Indexer.topk_indices_buffer with the target model's buffer, the impl's reference was stale — still pointing to the draft model's original (uninitialized) buffer. This caused garbage DSA attention → degenerate "repeat-the-current-token" drafts → ~27-33% acceptance instead of ~85%. Fix: store self._indexer = indexer in __init__, read self._indexer.topk_indices_buffer dynamically in forward_mqa. Applied to all three sparse MLA backends: flashattn_mla_sparse.py, flashmla_sparse.py, flashinfer_mla_sparse.py.

5. Apply fc projection on last PP rank for Qwen3.5 MTP — Qwen3.5/3.6-specific. Under MTP+PP, the Qwen3.5 MTP draft model on the last PP rank was using the target model's hidden_states directly, bypassing the fc projection entirely. This produced essentially random predictions (~1% acceptance) because the draft model's input was not properly projected. Fix: on the last PP rank, apply the same fc projection as the first rank (embed input_ids, normalize, concat with hidden_states, project through fc). This is the same pattern used on PP0 (first rank).

Test Plan

Serve a DeepSeek-family or Qwen3.5/3.6 MTP model under PP on the V2 runner and check it boots, produces correct output, and accepts drafts at a normal rate:

VLLM_USE_V2_MODEL_RUNNER=1 vllm serve zai-org/GLM-5.2-FP8 \
  --tensor-parallel-size 4 --pipeline-parallel-size 2 \
  --speculative-config '{"method":"mtp","num_speculative_tokens":1}'

Unit tests (tests/v1/worker/test_pp_utils.py):

  • test_deepseek_mtp_implements_supports_pp — verifies Fix #1
  • test_pphandler_broadcast_pads_to_max_sample_len — verifies Fix #2
  • test_sparse_mla_backend_reads_topk_indices_buffer_dynamically — verifies Fix #4

Test Result

Validated on GLM-5.2-FP8 (GlmMoeDsaForCausalLM, DeepSeek-Sparse-Attention MoE), TP4/PP2, on a current-main base (0.23.1rc1.dev531), serving real traffic for 5+ hours at K=3:

Metric Value
Drafts 91,088
Draft tokens 273,264
Accepted tokens 230,722
Overall acceptance 84.4%
pos0 92.1%
pos1 84.0%
pos2 77.2%

The stale-buffer fix (fix #4) lifts acceptance from ~27-33% (pre-fix, fixes #1-#3 only) to 84.4% at K=3 over 5+ hours of real traffic — matching the non-PP TP8 baseline (~85%).

Cross-model validation: fix #4 applies broadly

The stale-buffer fix was further validated across 8 Qwen3.5/3.6 model variants (dense BF16, AWQ, MoE BF16, MoE AWQ, MoE GPTQ-Int4), each at MTP K=1/2/3, PP=2/TP=1. All show high acceptance (83-96%) that scales gracefully with K, confirming the fix is not architecture-specific.

Model Quant K=1 K=2 K=3
Qwen3.5-27B-AWQ AWQ 4-bit 95.3% 90.5% 85.8%
Qwen3.5-27B (BF16) BF16 95.5% 91.1% 86.0%
Qwen3.5-35B-A3B (MoE, BF16) BF16 93.6% 88.0% 82.0%
Qwen3.6-27B (BF16) BF16 95.2% 91.1% 86.0%
Qwen3.6-27B-AWQ AWQ 4-bit 95.3% 90.5% 85.8%
Qwen3.6-35B-A3B (BF16) BF16 94.1% 88.6% 83.8%
Qwen3.6-35B-A3B-AWQ AWQ 4-bit 94.0% 88.8% 83.0%
Qwen3.6-35B-A3B-GPTQ-Int4 GPTQ 4-bit 94.1% 88.9% 83.5%

Full per-position breakdown and GSM8K accuracy available on request.

Remaining stale-buffer backends

Fix #4 was also applied to the three remaining sparse MLA backends that were not covered by the original fix commit (c175667db):

  • flashinfer_mla_sparse_sm120.pyFlashInferMLASparseSM120Impl (SM120 variant)
  • rocm_aiter_mla_sparse.pyROCmAiterMLASparseImpl (ROCm)
  • xpu_mla_sparse.pyXPUMLASparseImpl (Intel XPU)

These backends have the same stale topk_indices_buffer bug but are not reachable on our hardware (H200/SM90). The fix follows the exact same pattern as the already-validated fix: store self._indexer = indexer in __init__, read self._indexer.topk_indices_buffer dynamically in forward_mqa.

On the V2 model runner, MTP speculative decoding does not work under
pipeline parallelism. Three things are missing/broken, all on the path
that only runs once PP>1:

1. DeepSeekMTP does not implement SupportsPP, so the engine refuses to
   build it under PP at all (NotImplementedError at model resolution).
   The MTP draft runs only on the last PP stage, so it never consumes PP
   intermediate tensors, but SupportsPP still requires the
   make_empty_intermediate_tensors factory.

2. PPHandler.broadcast() sends sampled_token_ids at its natural width
   (1 on steps with no draft tokens, num_spec+1 once rejection sampling
   runs) while receive() always posts a [num_reqs, max_sample_len]
   buffer. NCCL broadcast does not negotiate element counts, so a width-1
   send against a width-max recv is a count mismatch that deadlocks the
   receiver. Pad the source to max_sample_len (trailing -1, ignored by
   post_update).

3. The proposed draft tokens are written into req_states.draft_tokens on
   the last rank only (the propose() path). Non-last ranks keep the
   zero-init buffer, so combine_sampled_and_draft_tokens embeds zeros at
   the draft positions on rank 0 -> garbage verification input and
   near-zero acceptance. Relay the proposed draft tokens to the non-last
   ranks by coalescing a third broadcast into the existing deferred
   PPHandler sibling-group broadcast, and scatter it into
   req_states.draft_tokens on consume. No new collective.

Validated on GLM-5.2-FP8 (DeepSeek-Sparse-Attention MoE), TP4/PP2, k=1,
on a current-main base: boots, serves coherent greedy output, and draft
acceptance is in the normal range (mean acceptance length ~1.3) rather
than ~0. A residual draft-acceptance gap specific to DSA models under PP
remains and is tracked separately.

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

… MTP+PP

Under MTP speculative decoding with pipeline parallelism, the sparse MLA
attention backends store a reference to `indexer.topk_indices_buffer` at
construction time. When `_maybe_share_lm_head` later replaces
`Indexer.topk_indices_buffer` with the target model's buffer, the impl's
reference is stale — still pointing to the draft model's original
(uninitialized) buffer. This causes garbage DSA attention and degenerate
"repeat-the-current-token" drafts (~27-33% acceptance instead of ~85%).

Fix: store `self._indexer = indexer` in each sparse MLA backend's
`__init__`, and read `self._indexer.topk_indices_buffer` dynamically in
`forward_mqa`. Applied to all three sparse MLA backends:
`flashattn_mla_sparse.py`, `flashmla_sparse.py`, `flashinfer_mla_sparse.py`.

After fix: ~90% acceptance (4261/4753 tokens) at K=1, ~74% at K=3
(3.3 tokens/step). Matches the non-PP TP8 baseline (~85%).

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>
@eastwood-c

eastwood-c commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

@njhill — thanks for referencing this PR from #47172.

Since the original post, we've widened the validation beyond GLM-5.2-FP8 to 8 Qwen3.5/3.6 model variants (dense BF16, AWQ, MoE BF16, MoE AWQ, MoE GPTQ-Int4), each at MTP K=1/2/3, PP=2/TP=1. All show high acceptance (83-96%) that scales gracefully with K, confirming the fix is not architecture-specific.

Model K=1 K=2 K=3
Qwen3.5-27B-AWQ 95.3% 90.5% 85.8%
Qwen3.5-27B (BF16) 95.5% 91.1% 86.0%
Qwen3.5-35B-A3B (MoE, BF16) 93.6% 88.0% 82.0%
Qwen3.6-27B (BF16) 95.2% 91.1% 86.0%
Qwen3.6-27B-AWQ 95.3% 90.5% 85.8%
Qwen3.6-35B-A3B (BF16) 94.1% 88.6% 83.8%
Qwen3.6-35B-A3B-AWQ 94.0% 88.8% 83.0%
Qwen3.6-35B-A3B-GPTQ-Int4 94.1% 88.9% 83.5%

Full per-position breakdown and GSM8K accuracy available on request. Happy to restructure the PR however you and the codeowners prefer.

Under MTP+PP, the Qwen3.5 MTP draft model on the last PP rank was using
the target model's hidden_states directly, bypassing the fc projection
entirely. This produced essentially random predictions (~1% acceptance)
because the draft model's input was not properly projected.

Fix: On the last PP rank, apply the same fc projection as the first rank
(embed input_ids, normalize, concat with hidden_states, project through
fc). This is the same pattern used on the first PP rank.

After fix: ~86.5% acceptance (6771/7828 tokens) on Qwen3.5-27B-AWQ
(TP1/PP2, MTP k=1). Validated on a different model architecture than
GLM-5.2-FP8, confirming the fix generalizes.

Cross-model validation: the fix was further validated across 8
Qwen3.5/3.6 model variants (dense BF16, AWQ, MoE BF16, MoE AWQ, MoE
GPTQ-Int4), each at MTP K=1/2/3, PP=2/TP=1. All show high acceptance
(83-96%) that scales gracefully with K, confirming the fix is not
architecture-specific.

| Model                   | Quant       | K=1   | K=2   | K=3   |
|-------------------------|-------------|-------|-------|-------|
| Qwen3.5-27B-AWQ         | AWQ 4-bit   | 95.3% | 90.5% | 85.8% |
| Qwen3.5-27B (BF16)      | BF16        | 95.5% | 91.1% | 86.0% |
| Qwen3.5-35B-A3B (BF16)  | BF16        | 93.6% | 88.0% | 82.0% |
| Qwen3.6-27B (BF16)      | BF16        | 95.2% | 91.1% | 86.0% |
| Qwen3.6-27B-AWQ         | AWQ 4-bit   | 95.3% | 90.5% | 85.8% |
| Qwen3.6-35B-A3B (BF16)  | BF16        | 94.1% | 88.6% | 83.8% |
| Qwen3.6-35B-A3B-AWQ     | AWQ 4-bit   | 94.0% | 88.8% | 83.0% |
| Qwen3.6-35B-A3B-GPTQ-Int4 | GPTQ 4-bit | 94.1% | 88.9% | 83.5% |

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>
@mergify mergify Bot added the qwen Related to Qwen models label Jun 30, 2026
…ends

Apply the same stale-buffer fix (commit c175667) to the three
remaining sparse MLA backends that were not covered by the original
fix commit:

- flashinfer_mla_sparse_sm120.py — FlashInferMLASparseSM120Impl (SM120)
- rocm_aiter_mla_sparse.py — ROCmAiterMLASparseImpl (ROCm)
- xpu_mla_sparse.py — XPUMLASparseImpl (Intel XPU)

These backends store indexer.topk_indices_buffer at construction time
and read it statically in forward_mqa, which is stale after
_maybe_share_lm_head replaces Indexer.topk_indices_buffer with the
target model's buffer. The fix is identical to the already-validated
fix: store self._indexer = indexer in __init__, read
self._indexer.topk_indices_buffer dynamically in forward_mqa.

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>
@mergify mergify Bot added rocm Related to AMD ROCm intel-gpu Related to Intel GPU labels Jun 30, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Jun 30, 2026
Add unit tests for the three core MTP+PP fixes in PR vllm-project#46994:

- Fix vllm-project#1: DeepSeekMTP implements SupportsPP interface
- Fix vllm-project#2: PPHandler.broadcast() pads sampled_token_ids to max_sample_len
- Fix vllm-project#4: Stale topk_indices_buffer is read dynamically via self._indexer

Tests are CPU-only (no GPU/distributed required) and follow vLLM's
pytest conventions.

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Nero10578 added a commit to Nero10578/vllm that referenced this pull request Jul 16, 2026
GLM4MoE's MTP draft (Glm4MoeMTP) did not implement the SupportsPP
interface, so the engine refused to build it under PP with the same
NotImplementedError as DeepSeekMTP before PR vllm-project#46994 fix #1. The draft
runs only on the last PP stage and never consumes PP intermediate tensors,
but the make_empty_intermediate_tensors factory is still required.

Mirrors the DeepSeek fix: add SupportsPP to the class bases and set
self.make_empty_intermediate_tensors via make_empty_intermediate_tensors_factory
with keys [hidden_states, residual], matching Glm4MoeModel. GLM4MoE
already loads its own embed_tokens in load_weights, so no embed fix needed.

Co-authored-by: Agent Name Here <agent@example.com>
Conflicts, and how they were resolved:

- flashattn/flashinfer/flashmla sparse MLA impls: upstream moved the field
  assignments into SparseMLACommonImpl.__init__. Took upstream's super() call
  and kept `self._indexer = indexer` alongside it. The base class only
  snapshots indexer.topk_indices_buffer at construction, and the whole point
  of this branch's sparse-MLA change is that the snapshot goes stale --
  forward_mqa has to re-read the buffer off the indexer, because it is swapped
  between the target and draft passes under MTP+PP. The three sparse backends
  upstream did not refactor (flashinfer_sm120, rocm_aiter, xpu) still inherit
  MLAAttentionImpl and set _indexer themselves; those merged cleanly.

- deepseek_mtp.py imports: union of both sides, so
  get_spec_layer_idx_from_weight_name (upstream) and SupportsPP /
  make_empty_intermediate_tensors_factory (ours) are all imported. Also drops
  `logger = init_logger(__name__)`, which this branch added and never used; the
  init_logger import itself was already gone from the merged result.

- autoregressive/speculator.py _multi_step_decode: both sides added a
  parameter. Kept both, with upstream's required seq_lens_cpu_upper_bound
  ahead of this branch's defaulted intermediate_tensors, and the single call
  site updated to match.

Plus a ruff-format reflow of the make_empty_intermediate_tensors assignment.
@mergify mergify Bot added mrv2 Model Runner V2 specific and removed needs-rebase labels Jul 28, 2026
@mergify

mergify Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @eastwood-c.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

Copy link
Copy Markdown

I independently reproduced the MRV2 MTP+PP draft-state synchronization issue and found during the required duplicate check that this PR already implements the same core fix. Per the repository's AGENTS.md, I am therefore not opening a duplicate PR without maintainer direction.

I prepared a focused version of only the generic transport change on current main:

The unit test passes, and I also validated the focused implementation end to end on accelerator hardware with two consecutive concurrent request batches. Both batches completed without request errors, hangs, collective mismatches, or service-health regressions.

This branch may be useful if maintainers prefer to split the generic MRV2 PP transport fix from the model-specific changes in this PR. Please let me know if an independent minimal PR is preferred; otherwise it is ready to cherry-pick or adapt here.

This implementation and validation write-up were AI-assisted and reviewed against the upstream diff and runtime logs.

Conflicts resolved against ~800 commits of upstream drift:

- spec_decode/speculator.py, autoregressive/speculator.py: import-only
  conflicts from the upstream move of the multimodal registry out of the
  autoregressive speculator into the base class.
- gpu/model_runner.py: propose() is now wrapped in use_workspace_lane and
  followed by adaptive_verification.record_confidences. Kept both upstream
  additions and re-applied intermediate_tensors= plus the broadcast_draft
  relay on top.

Follow-up fixes required by the merge:

- DFlashSpeculator.propose and MultiModuleMTPSpeculator.propose are new
  overrides that do not accept intermediate_tensors. The runner passes it
  unconditionally, so both raised TypeError at any PP size. Accept it (and
  ignore it -- neither drafter is PP-aware).
- PPHandler gated its third broadcast on max_sample_len > 1, which is also
  true for diffusion LLMs. Those set num_speculative_tokens > 0 but have no
  speculator, so the last rank never sent the relay the other ranks waited
  for -- a collective op-count mismatch that hangs PP. Gate both sides on an
  explicit relay_draft_tokens flag derived from speculative_config instead.

Signed-off-by: Chris Eastwood <chris.eastwood@pwn4g3.dev>
@mergify mergify Bot removed the needs-rebase label Aug 17, 2026
@eastwood-c

Copy link
Copy Markdown
Contributor Author

@njhill Checking back on this one. I had closed the other PR due to the complexity concerns you mentioned.

Let me know what else you'd like to see here, or if you'd prefer anything reworked. There seems to be at least some interest in the changes as a whole from the other comments here, so figured it was worth a small nudge.

I've been carrying a custom build with these changes for a while, and I'd really like to get the pieces that make sense upstream rather than keep rebasing a patch set onto each new release.

Appreciate the time, tyvm sir.

@AbdulrahmanHashem

Copy link
Copy Markdown

I just tried your PR + #50288 and V2 works and nvfp4 works with it too, good work and thank you ^_^.

@yewentao256 yewentao256 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the work!

Please take a look at these AI generated comments

Could we remove the newly added `intermediate_tensors` plumbing altogether?

Specifically:
- Remove the argument passed from `model_runner.py`.
- Remove the added argument and forwarding logic from `BaseSpeculator`, `AutoRegressiveSpeculator`, `DFlashSpeculator`, and `MultiModuleMTPSpeculator`.
- Remove the intermediate-tensor copy in `AutoRegressiveSpeculator._run_model()`.
- Remove the zero-filled intermediate tensors created in `qwen3_5_mtp.py`.

The drafter is instantiated only on the last PP rank. `DeepSeekMTP` ignores these tensors, while the new Qwen last-rank path uses the target hidden states directly and does not consume them. Therefore, this plumbing does not provide a meaningful data flow and currently also causes the fused multi-step `TypeError`.

The `SupportsPP` implementation and `make_empty_intermediate_tensors` factory should remain, since they are required by the model interface check.

Comment on lines 347 to 359
decode_fn = (
self._fused_multi_step_decode
if self.use_fused_multi_step_decode
else self._multi_step_decode
)
decode_fn(
num_reqs,
dummy_run and skip_attn_for_dummy_run,
decode_batch_desc,
num_tokens_across_dp,
input_batch.seq_lens_cpu_upper_bound,
intermediate_tensors=intermediate_tensors,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

def _fused_multi_step_decode(
self,
num_reqs: int,
skip_attn: bool,
batch_desc: BatchExecutionDescriptor,
num_tokens_across_dp: torch.Tensor | None,
seq_lens_cpu_upper_bound: torch.Tensor,
) -> None:

_fused_multi_step_decode doesn't have this arg

Comment thread tests/v1/worker/test_pp_utils.py Outdated
Co-authored-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com>
Signed-off-by: Chris Eastwood <106503529+eastwood-c@users.noreply.github.com>
@njhill

njhill commented Aug 21, 2026

Copy link
Copy Markdown
Member

Hey heads up there is another PR which kind of overlaps a bit which looks like we may be trying to land first #50514

@eastwood-c

eastwood-c commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

@yewentao256 Thanks sir, I had made these changes, push is pending actual testing (which I should have done after adding the mid-stream changes to this pr anyways) and the below.

@njhill Not a problem, I would gladly rebase and redo this pr (force push) to be stacked on top of that PR. I would ensure to validate against my running clusters (glm-5.2-fp8) and the qwen families on the combined work. Just let me know your preference sir

Surviving changes summary

  1. MTP models under PP

    • DeepSeekMTP and Qwen3_5MTP gain SupportsPP.
    • DeepSeek MTP must load its own embed_tokens. The checkpoint stores this as a top-level tied weight with spec_layer=None, so the loader otherwise skips it.
    • Under PP, the target model's copy is a PPMissingLayer on the draft stage. Without loading the draft's copy, it embeds with uninitialized weights.
    • Qwen3.5 MTP must apply its fc projection on the last rank as well as the first. Otherwise, it takes the intermediate-tensor path and projects uninitialized state.
    • [Core][MRV2] Support eagle3 spec decode with pipeline parallel #50514 deliberately does not address this: maybe_share_target_embed() returns early for MTP-style drafts because has_own_embed_tokens is EAGLE-only. The changes are complementary.
  2. Stale topk_indices_buffer in sparse MLA

    • Affects six backends plus deepseek_v32.
    • The indexer swaps its buffer between target and draft passes, so the snapshot captured in __init__ becomes stale.
    • The fix retains the indexer and re-reads the buffer in forward_mqa.
    • [Core][MRV2] Support eagle3 spec decode with pipeline parallel #50514 does not touch this DSA/MTP interaction because its dspark path does not hit it.
    • This can be split into a separate PR if preferred.
  3. Direct PPHandler relay test

    • Adds a unit test in tests/v1/worker/test_pp_utils.py.
    • Tests relay broadcast ordering and widths with the PP group stubbed.
    • [Core][MRV2] Support eagle3 spec decode with pipeline parallel #50514 covers aux-tap accounting, embedding sharing, and an EAGLE3 end-to-end test, but does not exercise PPHandler itself.
    • This test is worth keeping regardless of which relay implementation lands.

@mergify

mergify Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @eastwood-c.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepseek Related to DeepSeek models dflash intel-gpu Related to Intel GPU mrv2 Model Runner V2 specific needs-rebase nvidia qwen Related to Qwen models rocm Related to AMD ROCm speculative-decoding v1

Projects

Status: Todo
Status: No status
Status: Backlog

Development

Successfully merging this pull request may close these issues.

7 participants