Skip to content

[TRTLLM-13235][feat] Support bad_words in TorchSampler#16052

Open
zhaoyangwang-nvidia wants to merge 4 commits into
NVIDIA:mainfrom
zhaoyangwang-nvidia:pr-15542-torch-badwords
Open

[TRTLLM-13235][feat] Support bad_words in TorchSampler#16052
zhaoyangwang-nvidia wants to merge 4 commits into
NVIDIA:mainfrom
zhaoyangwang-nvidia:pr-15542-torch-badwords

Conversation

@zhaoyangwang-nvidia

@zhaoyangwang-nvidia zhaoyangwang-nvidia commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of blocked words during text generation, making sampling respect user-provided forbidden words more consistently.
    • Multi-token blocked phrases now suppress the final token only when the recent context matches, reducing unintended matches.
    • Added coverage to verify blocked-word behavior in sampling.

Description

TorchSampler previously ignored bad_words / bad_token_ids (only the C++ TRTLLMSampler applied them). This PR adds enforcement, including correct behavior under the overlap scheduler.

Bad-words masking in TorchSampler

  • llm_request.py: mirror the executor request's bad_words as py_bad_words.
  • sampler.py: add TorchSampler._apply_bad_words — single-token words are banned unconditionally; a multi-token word bans its final token only when the generated suffix matches the word prefix (matches C++ ban_bad_words semantics). Speculative steps reconstruct per-step context from py_draft_tokens.

Overlap scheduler support

Under overlap, sample_async(i) runs before update_requests(i-1), so the host token list lags the device by one token — multi-token prefix matching could miss or misapply bans. Fix, with no added device-to-host sync:

  • Split the match: first k-2 prefix tokens on the host, the newest token compared on-GPU against store.new_tokens[0, seq_slot, 0]; conditional ban applied via additive shape-static index_put_.
  • Track per-slot pending (sampled-but-not-updated) steps to detect staleness; draft batches excluded.
  • GPU path triggers only for overlap + no speculation + no beam search; spec/beam under overlap keeps prior behavior with a one-time warning. Non-overlap path unchanged.

Post-merge review follow-ups for #16365 (@ixlmar)

  • ops/flashinfer.py: missing flashinfer now raises an actionable ImportError on op use (module stays importable; startup enforcement stays in TorchSampler.__init__).
  • sampling_utils.py: replace import y as y re-exports with plain imports + __all__; drop the greedy alias (callers use greedy_search_sampling_batch); sanitize_top_k uses clamp(...).masked_fill_(...).

Follow-up (out of scope)

Batched device-side bad-words matching via the stop-words machinery (past_tokens_cuda) would remove the host loop and cover spec/beam under overlap natively.

Test Coverage

  • test_torch_sampler.py::TestApplyBadWords (11 cases): regular path (single/multi-token hit/miss, prefix in prompt) and overlap path (device-side hit/miss, host+device combined, mixed stale/fresh batches, overlapping bans → no NaN).
  • Full test_torch_sampler.py suite: 207 passed, no regressions (B200; skips are Ampere-gated).

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

@zhaoyangwang-nvidia zhaoyangwang-nvidia force-pushed the pr-15542-torch-badwords branch 5 times, most recently from 03266f1 to 07a4945 Compare July 9, 2026 07:57
@zhaoyangwang-nvidia zhaoyangwang-nvidia marked this pull request as ready for review July 9, 2026 08:01
@zhaoyangwang-nvidia zhaoyangwang-nvidia requested a review from a team as a code owner July 9, 2026 08:01
@zhaoyangwang-nvidia zhaoyangwang-nvidia force-pushed the pr-15542-torch-badwords branch from 07a4945 to 294ee9b Compare July 9, 2026 08:22
@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds bad-words masking to TorchSampler. LlmRequest now stores raw bad-words token lists via a new py_bad_words attribute, and TorchSampler applies a new _apply_bad_words method to mask corresponding logits to -inf during sampling, with unit tests covering single- and multi-token cases.

Changes

Bad Words Masking in TorchSampler

Layer / File(s) Summary
Store raw bad words on request
tensorrt_llm/_torch/pyexecutor/llm_request.py
executor_request_to_llm_request now sets llm_request.py_bad_words from executor_request.bad_words as a list[list[int]].
Bad-words masking logic and pipeline wiring
tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
Adds TorchSampler._apply_bad_words() to mask single-token bans unconditionally and multi-token bans conditionally on matching generated prefixes, and calls it in _process_requests() after _apply_min_length_penalty().
Unit tests
tests/unittest/_torch/sampler/test_torch_sampler.py
Adds TestApplyBadWords with a MockLlmRequest stub validating unconditional and conditional bad-word banning behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant ProcessRequests as _process_requests
  participant MinLength as _apply_min_length_penalty
  participant BadWords as _apply_bad_words
  participant Logits as logits_cuda

  ProcessRequests->>MinLength: apply penalty(logits_cuda)
  ProcessRequests->>BadWords: apply_bad_words(logits_cuda, py_bad_words)
  BadWords->>Logits: set banned vocab columns to -inf
  ProcessRequests->>ProcessRequests: proceed to greedy/strategy sampling
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 clearly summarizes the main change: adding bad_words support in TorchSampler.
Description check ✅ Passed The description follows the template and includes clear Description, Test Coverage, and PR Checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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)
tests/unittest/_torch/sampler/test_torch_sampler.py (1)

2695-2710: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Coverage gap: speculative multi-step, multi-beam, and multi-request offsets are untested.

All three cases use num_steps=[1], num_beams=[1]. The most defect-prone path — multi-token bad words with num_steps > 1 (speculative decoding), where per-step draft context matters — is not exercised, nor are multi-beam or multi-request row-offset layouts. Adding a num_steps=[2] multi-token case would surface the per-step context issue flagged in sampler.py, and a two-request case would validate current_offset accounting.

Recommend follow-up tests in this file covering: speculative multi-step multi-token hit/miss, num_beams>1 per-beam banning, and two-request offset correctness.

As per path instructions ("Act as a QA engineer reviewing test changes and coverage for TensorRT-LLM ... suggest ... whether coverage is sufficient, insufficient, or needs follow-up").

🤖 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 `@tests/unittest/_torch/sampler/test_torch_sampler.py` around lines 2695 -
2710, Add follow-up coverage in the existing bad-words sampler tests to exercise
the uncovered paths in the sampler logic. Extend the test methods around
test_single_token_unconditional, test_multi_token_prefix_hit, and
test_multi_token_prefix_miss with cases for num_steps=[2] to verify speculative
multi-step multi-token hit/miss behavior, and add assertions for num_beams>1 and
a two-request scenario to validate per-beam banning and current_offset row
handling. Keep the focus on the sampler behavior in test_torch_sampler.py so the
cases hit the multi-step context path referenced by sampler.py.

Source: Path instructions

🤖 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 `@tests/unittest/_torch/sampler/test_torch_sampler.py`:
- Around line 2695-2710: Add follow-up coverage in the existing bad-words
sampler tests to exercise the uncovered paths in the sampler logic. Extend the
test methods around test_single_token_unconditional,
test_multi_token_prefix_hit, and test_multi_token_prefix_miss with cases for
num_steps=[2] to verify speculative multi-step multi-token hit/miss behavior,
and add assertions for num_beams>1 and a two-request scenario to validate
per-beam banning and current_offset row handling. Keep the focus on the sampler
behavior in test_torch_sampler.py so the cases hit the multi-step context path
referenced by sampler.py.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2bd5389c-6c50-4312-b217-e5ae1c547c9d

📥 Commits

Reviewing files that changed from the base of the PR and between 940050b and 294ee9b.

📒 Files selected for processing (3)
  • tensorrt_llm/_torch/pyexecutor/llm_request.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
  • tests/unittest/_torch/sampler/test_torch_sampler.py

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58425 [ run ] triggered by Bot. Commit: 294ee9b Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58425 [ run ] completed with state SUCCESS. Commit: 294ee9b
/LLM/main/L0_MergeRequest_PR pipeline #47042 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

@xxi-nv

xxi-nv commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58582 [ run ] triggered by Bot. Commit: 4dcbb4e Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58582 [ run ] completed with state FAILURE. Commit: 4dcbb4e
/LLM/main/L0_MergeRequest_PR pipeline #47176 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

@xxi-nv

xxi-nv commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58651 [ run ] triggered by Bot. Commit: 4dcbb4e Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58651 [ run ] completed with state FAILURE. Commit: 4dcbb4e
/LLM/main/L0_MergeRequest_PR pipeline #47239 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

@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

/bot run

@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

Hi @NVIDIA/trt-llm-runtime-devs please help to reivew this PR, only small feature, thanks~

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58948 [ run ] triggered by Bot. Commit: 4dcbb4e Link to invocation

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

@stnie Could this functionality be merged with the stop-words handling somehow?

Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58948 [ run ] completed with state SUCCESS. Commit: 4dcbb4e
/LLM/main/L0_MergeRequest_PR pipeline #47479 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

@zhaoyangwang-nvidia zhaoyangwang-nvidia requested a review from a team as a code owner July 14, 2026 06:59
@stnie

stnie commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@stnie Could this functionality be merged with the stop-words handling somehow?

AFAIK stop words is applied after sampling, whereas bad words needs to be applied before sampling to adjust the logits. One could try to reuse or adjust _are_stop_words for batched processing of all bad words instead of the loop.

The PyTorch TorchSampler previously ignored bad_words / bad_token_ids; only
the C++ decoding path (TRTLLMSampler) applied them. Add logits masking in the
TorchSampler so single- and multi-token bad words are banned, matching the C++
ban_bad_words kernel semantics.

- llm_request: mirror the executor request's bad_words as py_bad_words
  (list[list[int]]) for the pure-Python sampler path.
- sampler: add TorchSampler._apply_bad_words, called alongside the existing
  min-length penalty. Single-token words are banned unconditionally;
  multi-token words ban their final token only when the generated suffix
  matches the word prefix. Speculative steps reconstruct per-step context from
  py_draft_tokens up-front, matching the kernel's per-step progression.
- tests: add TestApplyBadWords covering single/multi-token hit and miss,
  speculative multi-step, early-return, per-beam, and multi-request offsets.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
With the overlap scheduler, sample_async for step i runs before
update_requests for step i-1, so LlmRequest.get_tokens() lags the device
state by one token. _apply_bad_words matched multi-token bad-word
prefixes against this stale host context, which could both miss bans
(prefix completed by the previous step's token) and apply spurious ones.

Split the prefix match for overlap-affected requests: the first k-2
prefix tokens are matched on the host, and the newest token is compared
on the GPU against new_tokens[0, seq_slot, 0], applying -inf via an
additive shape-static index_put_ so no device-to-host sync is added.

TorchSampler now tracks, per seq slot, how many sampled steps have not
been folded back into the request (incremented in sample_async,
decremented in update_requests, reset when a context request claims a
slot; draft batches excluded). The device-side path triggers only for
overlap mode with pending == 1, no speculation and no beam search;
speculative/beam overlap keeps the previous behavior and warns once.
The non-overlap path is unchanged.

Add TestApplyBadWords coverage for the stale-host path: device-side
hit/miss, host+device combined match, mixed stale/fresh batches, and
overlapping unconditional/conditional bans on the same logit.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
@zhaoyangwang-nvidia zhaoyangwang-nvidia force-pushed the pr-15542-torch-badwords branch 2 times, most recently from c83ea01 to e241caf Compare July 15, 2026 11:07
Apply ixlmar's post-merge review suggestions from NVIDIA#16365:

- ops/flashinfer.py: without flashinfer installed, calling a sampling op
  now raises an ImportError with installation guidance instead of a bare
  NameError. The module intentionally stays importable (sampling_utils
  imports it unconditionally and non-flashinfer sampler paths must keep
  working); startup-time enforcement remains in TorchSampler.__init__.
  Document that contract in the module docstring.
- sampling_utils.py: collapse the "from x import y as y" re-export lines
  into plain grouped imports plus an explicit __all__ (mypy runs strict,
  so implicit re-export is disabled).
- Drop the "greedy" alias of greedy_search_sampling_batch; use the
  descriptive name directly in drafting_loops.py and
  speculative/interface.py.
- sanitize_top_k: replace torch.where(...).clamp(...) with
  clamp(...).masked_fill_(...), avoiding the full_like intermediate.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

Hi @ixlmar This PR addresses your post-merge review comments on #16365 (commit aded2c79):

  • ops/flashinfer.py — no more silent late failures without flashinfer: calling any sampling op now raises an actionable ImportError with installation guidance instead of a bare NameError. The module intentionally stays importable without flashinfer (sampling_utils imports it unconditionally, and the non-flashinfer sampler paths must keep working); startup-time enforcement remains in TorchSampler.init, and the module docstring now documents that contract.
  • sampling_utils.py — redundant re-exports cleaned up: the from x import y as y lines are collapsed into plain grouped imports plus an explicit all (mypy runs in strict mode, so implicit re-export is disabled).
  • greedy alias removed: drafting_loops.py and speculative/interface.py now use greedy_search_sampling_batch directly.
  • sanitize_top_k simplified: torch.where(...).clamp(...) replaced with clamp(...).masked_fill_(...), avoiding the full_like intermediate; semantics are unchanged (verified against the old implementation for 0 / negative / INT32_MAX sentinels, input tensor not mutated).

Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
The pending-step accounting classifies a batch as draft/target from its
first request, assuming batches are homogeneous. That invariant is
structural (ModelDrafter issues all-draft batches on the shared sampler;
PyExecutor's are all-target) but was only documented. Add
_is_draft_batch(), which asserts homogeneity, and use it at all three
call sites so a mixed batch fails loudly instead of silently corrupting
the counters.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59693 [ run ] triggered by Bot. Commit: daf7710 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59693 [ run ] completed with state SUCCESS. Commit: daf7710
/LLM/main/L0_MergeRequest_PR pipeline #48125 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.

6 participants