Skip to content

[https://nvbugs/6858496][fix] Fail disagg gen requests whose grammar matcher cannot be created - #20903

Open
trtllm-agent wants to merge 1 commit into
NVIDIA:mainfrom
tensorrt-cicd:repair-bot-v2/fix-r54
Open

trtllm-agent wants to merge 1 commit into
NVIDIA:mainfrom
tensorrt-cicd:repair-bot-v2/fix-r54

Conversation

@trtllm-agent

@trtllm-agent trtllm-agent commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Description

In disaggregated serving, a generation worker creates the grammar matcher of each generation-only request in GuidedDecoder._init_disagg_gen_requests, before the model forward. That method called grammar_matcher_factory.create() with no per-request error handling. An error from the factory therefore escaped into the executor loop and terminated it, and every later request to that worker failed. Context requests create their matchers in _build, which already catches this error and fails only the affected request.

_init_disagg_gen_requests now catches the error per request, in the same way as _build:

  • the slot gets no matcher, and its rollback counters are cleared, so the rollback passes skip it;
  • the request is reported through the existing (request_id, error_msg) failed-requests list returned by the next _build. The executor already turns that list into an error response for the request, via _handle_guided_decoder_errors;
  • the other requests in the batch keep their matchers and stay guided.

Test Coverage

  • New unit tests in tests/unittest/_torch/misc/test_guided_decoder_bitmask.py:
    • test_disagg_init_failure_fails_only_that_request: the failing request is reported once by the next build, a matcher left in its slot is not reused, and the other request stays guided.
    • test_disagg_init_failure_is_skipped_by_rollback: the rollback passes run without error after a failed creation.
      On H100, both fail without the change (the factory error is raised out of _init_disagg_gen_requests) and pass with it. All 24 tests in the file pass with the change.
  • The guided-decoding rollback and host-function tests under tests/unittest/_torch/speculative/hw_agnostic/ (test_sa_guided_rollback.py, test_guided_hostfunc_drain.py, test_dflash_guided_rollback.py, test_pard_guided_rollback.py) pass before and after the change (30 passed).
  • Disaggregated deployment on one H100 (context worker, generation worker with guided_decoding_backend: llguidance and an NIXL cache transceiver, plus the disaggregated server), built from the main base of this PR. A request whose matcher cannot be created on the generation worker:
    • before: terminates the generation worker's executor loop, and the 5 ordinary requests sent afterwards get no response;
    • after: fails only that request, and the 5 ordinary requests sent afterwards all return 200.
      With guided decoding also enabled on the context worker, the results are the same before and after: valid guided requests and ordinary requests return 200.

Fixes https://nvbugs/6858496

🤖 Generated with Claude Code

Dev Engineer Review

  • Disaggregated generation matcher initialization now catches factory errors per request. It clears that slot’s matcher and rollback counters, then reports the request through the next _build.
  • The failure path is intended to prevent one invalid request from disrupting other requests in the batch. Verify that the existing executor failure handling returns the error response and that healthy requests remain guided.
  • No current review findings or severity counts were supplied. Test execution results are reported in the PR objectives, not independently verified here.

QA Engineer Review

  • Modified tests/unittest/_torch/misc/test_guided_decoder_bitmask.py. It adds test_disagg_init_failure_fails_only_that_request and test_disagg_init_failure_is_skipped_by_rollback. Both cover matcher creation failure; the first also checks that another request in the batch remains guided. The reported test results are 24 passing tests in this file and 30 passing tests across four related rollback and host-function files.
  • No matching entry for this test file was found in tests/integration/test_lists/. The supplied context does not identify it as an integration test requiring CI or manual-QA listing.
  • Coverage verdict: sufficient for the matcher initialization failure and rollback paths described. The reported deployment test also covers worker survival and subsequent requests, but its result was not independently verified.

Per-File QA Perspective

  • tensorrt_llm/_torch/pyexecutor/guided_decoder.py: Verify that a factory error is isolated to the affected disaggregated generation request, clears its matcher and rollback counters, and reaches the existing failed-request response path on the next build.
  • tests/unittest/_torch/misc/test_guided_decoder_bitmask.py: The added tests cover per-request failure isolation, retention of guided decoding for another request, one-time error reporting, and rollback behavior. No matching CI or manual-QA list entry was found.

…matcher cannot be created

GuidedDecoder._init_disagg_gen_requests created the grammar matchers of
generation-only requests without per-request error handling, so an error
from the matcher factory propagated into the executor loop. Context
requests already handle the same error in _build by failing only the
affected request.

Catch the error per request in _init_disagg_gen_requests: the slot keeps no
matcher, its rollback counters are cleared, and the request is reported by
the next _build through the existing failed-requests path, which the
executor turns into an error response for that request. Other requests in
the batch are unaffected.

Add unit tests for the failing request, the other request of the batch,
and the rollback passes after a failed creation.

Signed-off-by: trtllm-agent <296075020+trtllm-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 9619d5b and 6e9b41d.

📒 Files selected for processing: 2
  • tensorrt_llm/_torch/pyexecutor/guided_decoder.py
  • tests/unittest/_torch/misc/test_guided_decoder_bitmask.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Walkthrough

GuidedDecoder now records disaggregated matcher initialization failures and reports them through the next _build. It clears the failed slot’s matcher and resets its advancement counters. Tests cover failure reporting, valid requests, one-time reporting, and rollback calls.

Changes

Guided decoder initialization failure handling

Layer / File(s) Summary
Capture matcher initialization failures
tensorrt_llm/_torch/pyexecutor/guided_decoder.py, tests/unittest/_torch/misc/test_guided_decoder_bitmask.py
Failed matcher creation clears the affected slot’s matcher, resets both advancement counters, and records the request ID and error. Tests cover failed initialization and rollback calls.
Report failures on build
tensorrt_llm/_torch/pyexecutor/guided_decoder.py, tests/unittest/_torch/misc/test_guided_decoder_bitmask.py
_build consumes pending failures. Tests verify that it reports only the invalid request, preserves the valid request’s guided row, and reports the failure once.

Priority: ➖ Normal

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

Change: Bug fix

Suggested reviewers: schetlur-nv, zhaoyangwang-nvidia, brnguyen2

Merge Risk: ⚪ Minimal · up to 6e9b4

Matcher initialization failures can be returned to the affected request without preventing other requests from continuing. No merge-blocking issue remains identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description check Passed The description explains the failure mode, the per-request error-handling solution, affected behavior, and relevant unit and deployment tests. It covers the required Description and Test Coverage sect…
Title check Passed The title follows the required ticket, type, and summary format. It clearly identifies the fix for disaggregated generation requests whose grammar matcher cannot be created.
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.
Full details: Description check

Explanation

The description explains the failure mode, the per-request error-handling solution, affected behavior, and relevant unit and deployment tests. It covers the required Description and Test Coverage sections. The repository checklist is not reproduced, but the critical content is complete.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

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

Approving — the comments below are optional touch-ups, not blockers.

The fix contains the failure at the right point for GuidedDecoder: a None matcher is already skipped by _build's advance branch, and the executor's _handle_guided_decoder_errors handles the reporting. Both tests target that class.

Two follow-ups:

  1. One-model speculative decoding is only partly covered. CapturableGuidedDecoder reuses _init_disagg_gen_requests, so the crash goes away there too. But the failure list never reaches the executor (see inline), so the request silently runs unguided. Either report the failure on that path or state the limitation in the PR description, which currently claims the request is always failed.
  2. Consider rejecting invalid grammars when the request arrives. The context path and this new path both catch the error only after the request is scheduled. A check at request entry (e.g. compile the response_format grammar once in the serving layer) would return a 400 before any KV transfer happens, and the disagg client would not have to wait through a gen-side retry and timeout. This can be a separate PR.

req.seq_slot] = self.grammar_matcher_factory.create(
slot] = self.grammar_matcher_factory.create(
req.guided_decoding_params)
except Exception as e:

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.

In CapturableGuidedDecoder, both init_disagg_gen_requests and build are @hostfunc callbacks. build() returns the launch handle, not _build's list, and execute() returns that value to _execute_guided_decoder_if_present, which discards it. PyExecutor.guided_decoder is None in that mode. So for one-model spec decoding plus disagg, this branch clears the matcher, init_failed_requests is never delivered, and the request keeps generating with no grammar constraint and no error. The worker no longer dies, but the response silently ignores response_format.

Possible fixes: have the host side read init_failed_requests after _drain_host_functions() and route it to the executor's error handling, or at least log that the request will run unguided. A test with CapturableGuidedDecoder would cover this path.

reported as failed by the next build, like a context request whose
matcher creation fails in build.
"""
self.init_failed_requests = []

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.

Clearing the list here assumes every _init_disagg_gen_requests call is followed by a _build on the same batch before the next init. That holds for the current PyExecutor call sites. Please add a short note to the init_failed_requests comment in __init__ stating this ordering requirement, so a future caller that runs init twice (e.g. a retry or a second microbatch) does not drop failures silently.

This branch has not been deployed

No deployments
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