Skip to content

[https://nvbugs/6482606][fix] Enforce output length at disaggregated generation first-token handoff#16691

Open
xwang233 wants to merge 4 commits into
NVIDIA:mainfrom
xwang233:fix/disagg-gen-osl-6482606
Open

[https://nvbugs/6482606][fix] Enforce output length at disaggregated generation first-token handoff#16691
xwang233 wants to merge 4 commits into
NVIDIA:mainfrom
xwang233:fix/disagg-gen-osl-6482606

Conversation

@xwang233

@xwang233 xwang233 commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Dev Engineer Review

  • Updated disaggregated generation first-token handling to evaluate end-ID, output-length, and stop-word criteria on the context server’s first token before scheduling subsequent generation.
  • Ensured request completion is only marked GENERATION_COMPLETE when all beams satisfy stop conditions, preventing premature completion ordering issues and avoiding extra tokens (e.g., max_tokens=1).
  • Updated _handle_first_token_response to skip emitting a first-token response for disagg-gen requests that were already finished at injection time, preventing a redundant first-token/final-token double-count (nvbugs/6482606).
  • Added the needed TorchSampler import for stop-criteria evaluation; no public API/config changes.

QA Engineer Review

Added tests in tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py:

  • test_first_token_completes_request_when_max_tokens_is_one
  • test_first_token_does_not_complete_when_budget_remains
  • test_first_token_end_id_completes_request
  • test_first_token_stop_word_completes_request
  • test_none_max_seq_len_still_enforces_output_length
  • test_all_beams_complete_together_for_length
  • test_request_incomplete_until_all_beams_stop
  • test_handle_first_token_response_skips_finished_request

Coverage in tests/integration/test_lists/ (test-db/qa):

  • Not found in existing test-list/waiver references based on repository search.

Verdict: needs follow-up — CBTS coverage/list status is unavailable.

Description

In disaggregated serving, the context server produces the first output token and hands it to the generation server, which injects it and then schedules a generation step. The injection path in PyExecutor._prepare_disagg_gen_transmission_complete appended this first token without applying any stop criteria, so a request whose first token already satisfies a stop condition still ran one more generation step and emitted an extra token.

The visible symptom is a request with max_tokens=1 (OSL=1) receiving 2 output tokens. Aggregated serving does not have this problem because the sampler applies the stop check to the context step's first token and finishes the request before any generation step is scheduled.

The gap only manifests when the first token alone satisfies a stop condition (max_tokens=1, or the first token is the end id / completes a stop word); for max_tokens > 1 the per-token stop check already stops generation at exactly the requested length. The affected path is shared by disaggregated generation with and without speculative decoding.

Fix

  • Apply the same stop criteria the aggregated sampler applies (TorchSampler._handle_stop_criteria: end id, max-token / length, stop words) to the injected first token, before the generation step runs, and finish the request (per beam; complete only when all beams have stopped) so the sampler, which skips finished requests, does not append an extra token.
  • Skip the redundant first-token response for a request already finished at injection, mirroring the existing behavior in _emit_first_token_responses; otherwise a finished-at-injection request would emit two final responses and double-count the token.

Test Coverage

  • tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py (GPU-free unit tests):
    • max_tokens=1 finishes at the injected first token (FinishReason.LENGTH); max_tokens=2 does not.
    • First-token end id and single-token stop word each finish the request.
    • max_seq_len=None still enforces the per-request output-length budget.
    • Beam width > 1: all beams complete together for length; request stays in progress until every beam has stopped.
    • _handle_first_token_response skips a request already finished at injection.

PR Checklist

  • PR description clearly explains what and why.
  • PR follows the TRT-LLM coding guidelines.
  • Test cases are provided for the new code paths.
  • No API changes.

…generation first-token handoff

In disaggregated serving, the context server produces the first output
token and hands it to the generation server, which injects it and then
schedules a generation step. The injection path in
_prepare_disagg_gen_transmission_complete appended this first token
without applying any stop criteria, so a request whose first token
already satisfies a stop condition still ran one more generation step and
emitted an extra token. The visible symptom is a request with
max_tokens=1 (OSL=1) receiving 2 output tokens.

Aggregated serving does not hit this because the sampler applies the stop
check to the context step's first token and finishes the request before
any generation step is scheduled. The gap only manifests when the first
token alone satisfies a stop condition (max_tokens=1, or the first token
is the end id / completes a stop word); for max_tokens > 1 the per-token
stop check already stops at exactly the requested length. The path is
shared by disaggregated generation with and without speculative decoding.

Apply the same stop criteria the aggregated sampler applies
(TorchSampler._handle_stop_criteria: end id, max-token/length, stop words)
to the injected first token, before the generation step runs, and mark the
request finished (per beam; complete only when all beams stop) so the
sampler skips it. Also skip the redundant first-token response for a
request already finished at injection, mirroring _emit_first_token_responses;
otherwise it would emit two final responses and double-count the token.

Add tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py covering the
stop-criteria cases and the finished-request response skip.

Signed-off-by: Xiao Wang <24860335+xwang233@users.noreply.github.com>
@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b5d0ab84-bba1-47ff-a028-3dc5191dc467

📥 Commits

Reviewing files that changed from the base of the PR and between 0e0a64e and 86aacc7.

📒 Files selected for processing (1)
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tensorrt_llm/_torch/pyexecutor/py_executor.py

Walkthrough

Disaggregated generation now evaluates stop criteria on the context-side first token, completes requests only after all beams finish, and skips duplicate first-token responses for requests already completed during injection. Unit tests cover length, end-token, stop-word, beam, and response-handling cases.

Changes

Disaggregated first-token completion

Layer / File(s) Summary
Apply first-token stop criteria
tensorrt_llm/_torch/pyexecutor/py_executor.py, tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py
The executor evaluates end-token, output-length, and stop-word criteria per beam during disaggregated generation injection, with completion requiring all beams to finish.
Suppress completed first-token responses
tensorrt_llm/_torch/pyexecutor/py_executor.py, tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py
First-token responses are skipped for requests already finished during injection, and tests verify only unfinished requests are queued.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: bo-nv

Sequence Diagram(s)

sequenceDiagram
  participant ContextInjection
  participant PyExecutor
  participant TorchSampler
  participant ResponseQueue
  ContextInjection->>PyExecutor: inject first token
  PyExecutor->>TorchSampler: evaluate stop criteria per beam
  TorchSampler-->>PyExecutor: return finish reasons
  PyExecutor->>ResponseQueue: enqueue response only when request remains unfinished
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the required ticket/type format and clearly summarizes the main fix.
Description check ✅ Passed The description includes the required Description, Test Coverage, and PR Checklist sections and is specific about the change and tests.
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.
✨ 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.

Actionable comments posted: 1

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

5873-5874: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate the new Python functions.

  • tensorrt_llm/_torch/pyexecutor/py_executor.py#L5873-L5874: add precise types for req and first_gen_tokens.
  • tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py#L39-L186: annotate helper arguments/returns and test functions with -> None.
🤖 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 5873 - 5874,
Annotate _apply_disagg_gen_first_token_stop_criteria in
tensorrt_llm/_torch/pyexecutor/py_executor.py with precise types for req and
first_gen_tokens, preserving the existing beam_width annotation. In
tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py, annotate all helper
parameters and return types, and add -> None to each test function; apply no
other changes.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@tensorrt_llm/_torch/pyexecutor/py_executor.py`:
- Around line 5902-5907: The beam completion logic in py_executor.py must record
each non-None finish reason immediately, while transitioning to
GENERATION_COMPLETE only when all beams have finished; update the loop around
beam_finish_reasons accordingly. In
tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py lines 141-153, update
the assertion to verify beam 0 receives FinishReason.END_ID while the request
remains in progress.

---

Nitpick comments:
In `@tensorrt_llm/_torch/pyexecutor/py_executor.py`:
- Around line 5873-5874: Annotate _apply_disagg_gen_first_token_stop_criteria in
tensorrt_llm/_torch/pyexecutor/py_executor.py with precise types for req and
first_gen_tokens, preserving the existing beam_width annotation. In
tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py, annotate all helper
parameters and return types, and add -> None to each test function; apply no
other changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c01fb033-750b-4585-9bec-f7898657dbfe

📥 Commits

Reviewing files that changed from the base of the PR and between 6fabfdc and 0e0a64e.

📒 Files selected for processing (2)
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py

Comment on lines +5902 to +5907
# Complete the request only when every beam has stopped, matching the agg
# semantics in TorchSampler._handle_finish_reasons_impl.
if all(reason is not None for reason in beam_finish_reasons):
req.state = LlmRequestState.GENERATION_COMPLETE
for beam, reason in enumerate(beam_finish_reasons):
req.set_finished_reason(reason, beam)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Record finish reasons for each stopped beam.

A beam that hits END_ID or a stop word must be marked finished even while another beam continues. Deferring set_finished_reason() until all beams stop allows the stopped beam to keep decoding; the test currently codifies that incorrect behavior.

  • tensorrt_llm/_torch/pyexecutor/py_executor.py#L5902-L5907: call set_finished_reason(reason, beam) for every non-None reason, and only transition the request state to GENERATION_COMPLETE after all beams finish.
  • tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py#L141-L153: assert that beam 0 receives FinishReason.END_ID while the request remains in progress.
📍 Affects 2 files
  • tensorrt_llm/_torch/pyexecutor/py_executor.py#L5902-L5907 (this comment)
  • tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py#L141-L153
🤖 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 5902 - 5907, The
beam completion logic in py_executor.py must record each non-None finish reason
immediately, while transitioning to GENERATION_COMPLETE only when all beams have
finished; update the loop around beam_finish_reasons accordingly. In
tests/unittest/_torch/executor/test_disagg_gen_osl_stop.py lines 141-153, update
the assertion to verify beam 0 receives FinishReason.END_ID while the request
remains in progress.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60807 [ run ] triggered by Bot. Commit: 0e0a64e Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60807 [ run ] completed with state FAILURE. Commit: 0e0a64e
/LLM/main/L0_MergeRequest_PR pipeline #49083 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61011 [ run ] triggered by Bot. Commit: 7fc3680 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61011 [ run ] completed with state FAILURE. Commit: 7fc3680
/LLM/main/L0_MergeRequest_PR pipeline #49271 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61025 [ run ] triggered by Bot. Commit: 7fc3680 Link to invocation

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61032 [ run ] triggered by Bot. Commit: 86aacc7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61025 [ run ] completed with state ABORTED. Commit: 7fc3680

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61032 [ run ] completed with state FAILURE. Commit: 86aacc7
/LLM/main/L0_MergeRequest_PR pipeline #49291 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61060 [ run ] triggered by Bot. Commit: 86aacc7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61060 [ run ] completed with state FAILURE. Commit: 86aacc7
/LLM/main/L0_MergeRequest_PR pipeline #49315 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61183 [ run ] triggered by Bot. Commit: 0e98e17 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61183 [ run ] completed with state FAILURE. Commit: 0e98e17
/LLM/main/L0_MergeRequest_PR pipeline #49432 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run --reuse-test --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61366 [ run ] triggered by Bot. Commit: 0e98e17 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61366 [ run ] completed with state FAILURE. Commit: 0e98e17
/LLM/main/L0_MergeRequest_PR pipeline #49594 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

@xwang233

Copy link
Copy Markdown
Collaborator Author

/bot run --reuse-test --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61441 [ run ] triggered by Bot. Commit: 0e98e17 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61441 [ run ] completed with state SUCCESS. Commit: 0e98e17
/LLM/main/L0_MergeRequest_PR pipeline #49666 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

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.

2 participants