Skip to content

[https://nvbugs/6427240][fix] Reserve MTP draft tokens in scheduler for one-model speculative decoding - #16101

Merged
jhaotingc merged 1 commit into
NVIDIA:mainfrom
jhaotingc:fix/mtp-one-model-draft-token-scheduling
Jul 11, 2026
Merged

[https://nvbugs/6427240][fix] Reserve MTP draft tokens in scheduler for one-model speculative decoding#16101
jhaotingc merged 1 commit into
NVIDIA:mainfrom
jhaotingc:fix/mtp-one-model-draft-token-scheduling

Conversation

@jhaotingc

@jhaotingc jhaotingc commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Background / motivation

Serving a one-model MTP model (e.g. Qwen3.6-35B-A3B-NVFP4, mtp_eagle_one_model) with enable_chunked_prefill and the overlap scheduler (disable_overlap_scheduler: false) aborts requests under load with:

AssertionError: total_num_tokens (N) should be less than or equal to max_num_tokens (M)

at tensorrt_llm/_torch/pyexecutor/model_engine.py::_prepare_tp_inputs, surfaced to clients as RequestError. It reproduces at moderate concurrency with long (chunk-prefilled) prompts.

Root cause

get_spec_drafter() returns None for MTP_EAGLE_ONE_MODEL / one-model speculative decoding, so the if self.drafter is not None: block in _prepare_and_schedule_batch is skipped — including the loop that sets request.draft_tokens = [0] * max_total_draft_tokens for generation requests. That loop is what makes the C++ micro-batch scheduler (microBatchScheduler.cpp, cost beam_width + getNumDraftTokens()) reserve the draft-token budget. Without it the scheduler under-reserves, while the forward builds a uniform 1 + runtime_draft_len per gen request (overlap path); the chunked-prefill context chunk is then sized past max_num_tokens, tripping the assertion. Two-model speculative decoding runs the normalization and is unaffected.

Summary

Add an elif branch that mirrors the two-model draft-token normalization for the one-model path (self.model_engine.is_spec_decode and self.max_total_draft_tokens > 0). It uses self.max_total_draft_tokens, so it stays consistent with dynamic draft_len_schedule (a hardcoded max reservation would over-reserve). Scheduling now reserves beam + max_total_draft_tokens per gen request, matching the forward.

Impact

  • Fixes the crash/abort for one-model MTP + chunked prefill + overlap scheduler.
  • No effect on two-model spec-decode (already normalized) or non-spec models (is_spec_decode is False).
  • Reservation-only: does not change draft-token content or accepted tokens.

Verification

Qwen3.6-35B-A3B-NVFP4, TP1 B200, MTP num_nextn_predict_layers=3, enable_chunked_prefill, overlap scheduler on, max_num_tokens=8192, max_batch_size=512. aiperf concurrency sweep (NIAH long-prompt dataset, OSL 6144, ignore_eos):

conc 8 conc 16 conc 32 conc 64 total_num_tokens asserts
Before clean tripped, errored 130
After 0 err 0 err 96 reqs, 0 err 192 reqs, 0 err 0

Notes

Opening as draft for approach review. Happy to add a regression unit test if desired.

Summary by CodeRabbit

  • Bug Fixes
    • Improved speculative decoding behavior when using a single-model setup.
    • Active generation requests now initialize draft-token tracking correctly, helping scheduling and token budgeting work reliably in this mode.

@jhaotingc
jhaotingc marked this pull request as ready for review July 8, 2026 05:11
@jhaotingc
jhaotingc requested a review from a team as a code owner July 8, 2026 05:11
@jhaotingc
jhaotingc requested a review from lancelly July 8, 2026 05:11
@jhaotingc jhaotingc changed the title fix: reserve MTP draft tokens in scheduler for one-model speculative decoding [fix][https://nvbugspro.nvidia.com/bug/6427240]: reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@jhaotingc jhaotingc changed the title [fix][https://nvbugspro.nvidia.com/bug/6427240]: reserve MTP draft tokens in scheduler for one-model speculative decoding [https://nvbugspro.nvidia.com/bug/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@jhaotingc
jhaotingc requested a review from nv-guomingz July 8, 2026 05:12
@jhaotingc jhaotingc changed the title [https://nvbugspro.nvidia.com/bug/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding [https://nvbug/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@jhaotingc jhaotingc changed the title [https://nvbug/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding [https://nvbugs/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a conditional branch in PyExecutor._prepare_and_schedule_batch() to initialize draft_tokens to a zero-filled list for generation-related requests when one-model speculative decoding is active, addressing a missing data path in that mode.

Changes

Draft Token Initialization

Layer / File(s) Summary
Initialize draft_tokens for one-model spec decode
tensorrt_llm/_torch/pyexecutor/py_executor.py
Adds an elif branch checking model_engine.is_spec_decode and max_total_draft_tokens > 0 to set request.draft_tokens to [0] * max_total_draft_tokens for GENERATION_IN_PROGRESS and DISAGG_GENERATION_INIT requests.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the fix and follows the required ticket/type style.
Description check ✅ Passed The description covers background, root cause, fix, impact, and validation, though it doesn't follow the template headings exactly.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
tensorrt_llm/_torch/pyexecutor/py_executor.py (1)

2980-3009: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the shared draft-token reservation loop.

The state-filter + draft_tokens assignment loop (lines 2981-2988 and 3004-3009) is duplicated between the two-model and one-model branches. Extracting a small helper (e.g. _reserve_draft_token_budget(requests, count)) would reduce duplication and keep the two normalization paths in sync going forward.

♻️ Example helper extraction
+    def _reserve_draft_token_budget(self, num_draft_tokens: int) -> None:
+        for request in self.active_requests:
+            if request.state not in (
+                    LlmRequestState.GENERATION_IN_PROGRESS,
+                    LlmRequestState.DISAGG_GENERATION_INIT):
+                continue
+            request.draft_tokens = [0] * num_draft_tokens
+
     if self.drafter is not None:
         ...
-        for request in self.active_requests:
-            if request.state not in (
-                    LlmRequestState.GENERATION_IN_PROGRESS,
-                    LlmRequestState.DISAGG_GENERATION_INIT):
-                continue
-            request.draft_tokens = [
-                0
-            ] * self.max_total_draft_tokens if self.max_total_draft_tokens > 0 else []
+        self._reserve_draft_token_budget(
+            self.max_total_draft_tokens if self.max_total_draft_tokens > 0 else 0)
         ...
     elif self.model_engine.is_spec_decode and self.max_total_draft_tokens > 0:
         ...
-        for request in self.active_requests:
-            if request.state not in (
-                    LlmRequestState.GENERATION_IN_PROGRESS,
-                    LlmRequestState.DISAGG_GENERATION_INIT):
-                continue
-            request.draft_tokens = [0] * self.max_total_draft_tokens
+        self._reserve_draft_token_budget(self.max_total_draft_tokens)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/pyexecutor/py_executor.py` around lines 2980 - 3009, The
state-filtered draft-token reservation loop is duplicated in both the two-model
and one-model speculative decoding branches. Extract the shared logic in
py_executor.py into a small helper (for example, a method near
_prepare_draft_requests or the surrounding scheduling code) that iterates over
self.active_requests, filters by LlmRequestState.GENERATION_IN_PROGRESS and
LlmRequestState.DISAGG_GENERATION_INIT, and assigns request.draft_tokens based
on the requested count; then call that helper from both branches so the
reservation behavior stays in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tensorrt_llm/_torch/pyexecutor/py_executor.py`:
- Around line 2980-3009: The state-filtered draft-token reservation loop is
duplicated in both the two-model and one-model speculative decoding branches.
Extract the shared logic in py_executor.py into a small helper (for example, a
method near _prepare_draft_requests or the surrounding scheduling code) that
iterates over self.active_requests, filters by
LlmRequestState.GENERATION_IN_PROGRESS and
LlmRequestState.DISAGG_GENERATION_INIT, and assigns request.draft_tokens based
on the requested count; then call that helper from both branches so the
reservation behavior stays in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3cedd69c-84b2-4bf3-9fd2-e73ab4fb1273

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb7feb and 3685480.

📒 Files selected for processing (1)
  • tensorrt_llm/_torch/pyexecutor/py_executor.py

@jhaotingc jhaotingc changed the title [https://nvbugs/6427240][fix]: reserve MTP draft tokens in scheduler for one-model speculative decoding [https://nvbugs/6427240][fix] reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@jhaotingc jhaotingc changed the title [https://nvbugs/6427240][fix] reserve MTP draft tokens in scheduler for one-model speculative decoding [https://nvbugs/6427240][fix] Reserve MTP draft tokens in scheduler for one-model speculative decoding Jul 8, 2026
@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 3685480 to 6f3bb4a Compare July 8, 2026 05:20
@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58152 [ run ] triggered by Bot. Commit: 6f3bb4a Link to invocation

@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58265 [ run ] triggered by Bot. Commit: 6f3bb4a Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58152 [ run ] completed with state ABORTED. Commit: 6f3bb4a
/LLM/main/L0_MergeRequest_PR pipeline #46805 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

@nv-guomingz
nv-guomingz requested a review from sunnyqgg July 8, 2026 17:03
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58265 [ run ] completed with state SUCCESS. Commit: 6f3bb4a
/LLM/main/L0_MergeRequest_PR pipeline #46904 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 6f3bb4a to 2e6e8bd Compare July 9, 2026 00:12
@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58327 [ run ] triggered by Bot. Commit: 2e6e8bd Link to invocation

@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 2e6e8bd to 9e45af3 Compare July 9, 2026 00:39
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58327 [ run ] completed with state SUCCESS. Commit: 2e6e8bd
/LLM/main/L0_MergeRequest_PR pipeline #46957 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 9e45af3 to a6eb3b5 Compare July 9, 2026 15:14
@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58481 [ run ] triggered by Bot. Commit: a6eb3b5 Link to invocation

@jhaotingc

Copy link
Copy Markdown
Collaborator Author

CI all passed, blocked by #16208

@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot kill

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58541 [ kill ] triggered by Bot. Commit: a6eb3b5 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58481 [ run ] completed with state ABORTED. Commit: a6eb3b5
/LLM/main/L0_MergeRequest_PR pipeline #47090 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58541 [ kill ] completed with state SUCCESS. Commit: a6eb3b5
Successfully killed previous jobs for commit a6eb3b5

Link to invocation

@sunnyqgg sunnyqgg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from a6eb3b5 to 057a3bb Compare July 10, 2026 01:55
@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run

@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 057a3bb to 2e11394 Compare July 10, 2026 01:56
…or one-model speculative decoding

One-model speculative decoding (MTP / Eagle3 one-model) has no separate
drafter, so get_spec_drafter() returns None and the `if self.drafter is not
None` block in _prepare_and_schedule_batch is skipped -- including the loop
that sets `request.draft_tokens = [0] * max_total_draft_tokens` for generation
requests. That loop is what makes the C++ micro-batch scheduler budget each
generation request as beam_width + getNumDraftTokens(). Without it, under
enable_chunked_prefill + the overlap scheduler, the scheduler under-reserves
and the forward's uniform (1 + runtime_draft_len) build overshoots
max_num_tokens, aborting requests with an AssertionError (total_num_tokens >
max_num_tokens) in _prepare_tp_inputs.

Add an elif branch mirroring the two-model draft-token normalization for the
one-model path so scheduling reserves the correct budget. Using
self.max_total_draft_tokens keeps it consistent with dynamic draft_len_schedule.
model_engine is guarded (getattr ... is not None) first so partially-
constructed executors in unit tests do not raise AttributeError. The state
filter {GENERATION_IN_PROGRESS, DISAGG_GENERATION_INIT} matches the two-model
normalization, so this single fix covers both aggregated and disaggregated
(decode-worker) serving.

Add a regression test (test_py_executor.py) that drives
_prepare_and_schedule_batch on a one-model-MTP executor with real LlmRequests
in GENERATION_IN_PROGRESS (aggregated) and DISAGG_GENERATION_INIT (disagg)
states, asserting both are normalized to max_total_draft_tokens while
CONTEXT_INIT is left untouched. The test fails without the fix and passes with
it.

Verified on Qwen3.6-35B-A3B-NVFP4 (MTP num_nextn_predict_layers=3, chunked
prefill, overlap scheduler on): a concurrency sweep that previously produced
130 assertions at concurrency 32 now completes with zero assertions and zero
request errors.

Signed-off-by: Jhao-Ting Chen <jtchen0528@gmail.com>
@jhaotingc
jhaotingc force-pushed the fix/mtp-one-model-draft-token-scheduling branch from 2e11394 to 9cb5493 Compare July 10, 2026 01:58
@jhaotingc

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58548 [ run ] triggered by Bot. Commit: 9cb5493 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58550 [ run ] triggered by Bot. Commit: 9cb5493 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58548 [ run ] completed with state ABORTED. Commit: 9cb5493

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58550 [ run ] completed with state FAILURE. Commit: 9cb5493
/LLM/main/L0_MergeRequest_PR pipeline #47150 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@nv-guomingz

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58662 [ run ] triggered by Bot. Commit: 9cb5493 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58662 [ run ] completed with state SUCCESS. Commit: 9cb5493
/LLM/main/L0_MergeRequest_PR pipeline #47248 completed with status: 'SUCCESS'

CI Report

Link to invocation

@jhaotingc
jhaotingc merged commit b0f3f29 into NVIDIA:main Jul 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants