Skip to content

[TRTLLM-13231][feat] Support top_p_decay in the PyTorch TorchSampler - #16184

Merged
zhaoyangwang-nvidia merged 9 commits into
NVIDIA:mainfrom
zhaoyangwang-nvidia:pr-topp-decay
Jul 22, 2026
Merged

[TRTLLM-13231][feat] Support top_p_decay in the PyTorch TorchSampler#16184
zhaoyangwang-nvidia merged 9 commits into
NVIDIA:mainfrom
zhaoyangwang-nvidia:pr-topp-decay

Conversation

@zhaoyangwang-nvidia

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

Copy link
Copy Markdown
Collaborator

Implement Top-P Decay on the FlashInfer sampling path, porting the semantics of the legacy C++ computeToppDecay kernel. The three SamplingParams fields top_p_decay / top_p_min / top_p_reset_ids were previously dropped by the PyTorch backend; they are now plumbed through and honored.

Each decode step, after the token is sampled, the per-request runtime top-p is updated as runtime_p = max(runtime_p * top_p_decay, top_p_min), or reset to its initial value when the sampled token equals top_p_reset_ids (gated on reset_id >= 0). The runtime top-p is carried as a per-row tensor into the FlashInfer batched sampling ops, never encoded into the cached strategy tuple.

Scope is limited to the FlashInfer path and single-token decode steps; decay combined with speculative decoding, beam search, or the non-FlashInfer backend is rejected early. Out-of-range top_p_decay / top_p_min warn-and-default, matching the C++ runtime.

Adds unit tests covering activation/routing, warn-and-default validation, numeric parity with the C++ recurrence, slot-reuse lifecycle, and mixed decay / non-decay batches.

Dev Engineer Review

  • Implemented FlashInfer-path Top-P decay in TorchSampler, including per-slot runtime state (top_p_decay, top_p_min, top_p_reset_ids, initial/runtime values, and reset handling) gated by a device-side “decay-active slot” mask, with slot lifecycle updates on request admission/retirement.
  • Added fused GPU operations to (a) gather per-row decayed runtime_top_p for FlashInfer sampling and (b) update runtime_top_p in-place after each sampled token using top_p_decay with top_p_min clamping and reset_ids recurrence (parity w/ legacy C++ computeToppDecay semantics).
  • Extended sampling strategy routing so top_p_decay < 1.0 activates top-p-capable strategies at runtime even when initial/static top_p implies greedy; added greedy-resolution predicates in SamplingParams to ensure explicit greedy intent can override decay activity.
  • Validation: Top-p decay parameters and reset IDs are validated in tensorrt_llm/sampling_params.py to be within supported ranges; out-of-range values raise ValueError. Requests combining Top-p decay with unsupported flows (e.g., speculative draft tokens / multi-token decode steps) are rejected per-request during admission.

QA Engineer Review

  • Updated tests/unittest/_torch/sampler/test_torch_sampler.py by adding TestTopPDecay with these new test methods:
    • test_strategy_routing
    • test_runtime_update_parity
    • test_out_of_range_decay_params_rejected
    • test_reject_speculative_draft_tokens
  • Test coverage mapping in tests/integration/test_lists/ (test-db/ and qa/) was not provided; Verdict: needs follow-up.
  • No test-list-only changes were identified in the provided context.

Description

Adds Top-P Decay (top_p_decay / top_p_min / top_p_reset_ids in SamplingParams)
to the PyTorch backend's TorchSampler. These parameters were previously only honored
by the legacy TRT sampler; on the torch path they were silently dropped.

Semantics (parity with the legacy C++ computeToppDecay kernel)

After each sampled token of a decay-active request:

runtime_top_p = initial_top_p                                if token == reset_id
              = max(runtime_top_p * top_p_decay, top_p_min)  otherwise
  • Decay is active iff top_p_decay is set and < 1.0; top_p_min / top_p_reset_ids
    alone do not activate it. Defaults mirror the C++ runtime (min=1e-6, reset_id=-1,
    a negative sentinel that never matches since token ids are non-negative).
  • An active decay forces a top-p-capable strategy even when top_p is 1.0/unset
    (initial top-p defaults to 1.0), so the decayed value can take effect on later steps.
    An explicit greedy control (top_k==1, top_p==0.0, temperature==0) wins over
    decay. The greedy/decay resolution is single-sourced in
    SamplingParams.params_imply_greedy_decoding and shared by _greedy_decoding and
    resolve_sampling_strategy.
  • BREAKING: out-of-range decay params (top_p_decay / top_p_min outside (0, 1],
    negative top_p_reset_ids) are now rejected by SamplingParams with a clear error,
    replacing the former warn-and-default behavior and mirroring the hard checks in the
    executor::SamplingConfig constructor.

Implementation (kernel-free)

No custom CUDA code. Per-slot runtime state lives in resident CUDA buffers
(TorchSampler.TopPDecayStore, the single documentation point for the feature's
semantics and slot lifecycle), gated by a device-side is_decay_slot mask plus a
host-side slot set used only for O(1) early-outs — the hot path needs no host/device
sync or host-side filtering.

  • The two per-step ops (pre-sample per-row top-p gather, post-sample in-place update)
    are native torch functions fused with torch.compile in ops.vanilla.Fusions,
    following the existing Fusions pattern (gather_log_softmax).
    mode="max-autotune-no-cudagraphs" — cudagraphs is deliberately disabled: the update
    mutates persistent per-slot state in place and the gather's output is consumed outside
    the compiled region, which is unsafe with cudagraph static buffers (verified: outputs
    get overwritten by subsequent replays). torch._dynamo.mark_dynamic on the
    batch-varying dims avoids recompilation as batch composition changes. Compilation is
    lazy (~1 s on the first decay-active request; non-decay workloads never trigger it).
  • The per-group override is routed through the existing StrategyMetadata mechanism
    (TopPDecayMetadata, mirroring BeamSearchMetadata) and consumed inside the
    TopP*/TopKTopP* strategy impls via TopPDecayMixin — the generic sampling entry
    point is unchanged.
  • Unsupported combinations (beam search; speculative draft tokens routed through
    TorchSampler) are rejected per-request at admission via Sampler.validate_request,
    with a debug-only invariant check at sample time.

Performance

Measured with an isolated sampler benchmark (H200, bs=32, steady-state decode, per-step
sampler cost beyond a fake model forward of the given length; p50 over 2048 steps):

Fake forward No decay This PR (compiled ops) Eager ops Fused CUDA kernels (dropped)
1000 µs 121 µs 233 µs 191 µs 147 µs
2000 µs 121 µs 141 µs 176 µs 142 µs

With a forward of ≥ 2 ms the compiled ops match the hand-written fused kernels (both
~20 µs decay overhead, ~35 µs ahead of eager): the compiled-function entry cost hides
behind the forward and the Inductor-fused kernels keep the GPU tail as short as the
custom kernels did. With a 1 ms forward the fixed per-call entry cost is exposed
(
+40 µs vs. eager). We took the kernel-free maintainability trade-off, as the decay
feature is not on the critical benchmarking path; an earlier revision of this PR
contains the fused-kernel implementation should that regime ever matter.

A broader sweep (all four torch.compile modes × dynamic/static/mark_dynamic,
12 configurations) and profiling notes are in the PR discussion.

Test Coverage

tests/unittest/_torch/sampler/test_torch_sampler.py::TestTopPDecay:

  • Strategy routing (decay forces top_p / top_k_top_p; explicit greedy wins;
    decay == 1.0 inactive).
  • Multi-step runtime-update parity against the C++ reference recurrence (cases ported
    from topPSamplingLayerTest.cpp TopPDecay, plus the top_p_min > initial rise case),
    including reset behavior.
  • Out-of-range decay params rejected at SamplingParams construction.
  • Per-request rejection of decay + speculative drafts via validate_request.

Additionally verified: a 300-step pipelined (no per-step sync) trajectory check of the
compiled ops against the analytic recurrence, and the full test_torch_sampler.py
suite (205 passed) in a matching container.

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-topp-decay branch 5 times, most recently from 185d692 to 49e2e80 Compare July 16, 2026 03:24
@zhaoyangwang-nvidia
zhaoyangwang-nvidia marked this pull request as ready for review July 16, 2026 03:27
@zhaoyangwang-nvidia
zhaoyangwang-nvidia requested review from a team as code owners July 16, 2026 03:27
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds full TorchSampler and FlashInfer support for per-sequence top-p decay, including parameter validation, slot state, grouped metadata, compiled GPU gather/update operations, request lifecycle handling, and updated sampler test instrumentation.

Changes

Top-P decay sampling

Layer / File(s) Summary
Sampling parameters and strategy contracts
tensorrt_llm/sampling_params.py, tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py
Adds top-p decay fields, validation and greedy-decoding semantics, and routes active decay requests through top-p-capable strategies.
Compiled top-p decay operations
tensorrt_llm/_torch/pyexecutor/sampler/ops/vanilla.py
Adds compiled GPU operations to gather runtime top-p values and update them using reset, decay, and minimum-value rules.
Sampler admission and slot lifecycle
tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
Adds per-slot decay buffers and membership tracking, propagates request parameters, validates supported requests, initializes new slots, and retires completed slots.
Grouped metadata and post-sample updates
tensorrt_llm/_torch/pyexecutor/sampler/sampler.py, tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py, tests/unittest/_torch/sampler/test_torch_sampler.py
Builds per-step decay metadata, applies runtime top-p values across grouped sampling strategies, updates state after sampling, and forwards the new test interception argument.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TorchSampler
  participant FlashInferGroupedStrategySampler
  participant Fusions
  TorchSampler->>FlashInferGroupedStrategySampler: pass TopPDecayMetadata
  FlashInferGroupedStrategySampler->>Fusions: gather runtime top-p by slot
  Fusions-->>FlashInferGroupedStrategySampler: return decayed top-p values
  FlashInferGroupedStrategySampler-->>TorchSampler: return sampled tokens
  TorchSampler->>Fusions: update sampled decay slots
  Fusions-->>TorchSampler: mutate runtime top-p state
Loading

Suggested reviewers: chang-l

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 is concise, specific, and accurately summarizes the main change with the ticket and feature tag.
Description check ✅ Passed The description includes the required sections and clearly explains the change, validation, and test coverage.
✨ 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 (2)
tests/unittest/_torch/sampler/test_top_p_decay.py (2)

332-358: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rejection coverage only exercises speculative decoding, not beam search or non-FlashInfer sampling.

The PR objectives state decay combined with speculative decoding, beam search, or non-FlashInfer sampling should be rejected/unsupported, but TestRejection only covers the speculative-decoding path (test_reject_speculative) plus the accept/no-op cases. Beam-search and non-FlashInfer-backend rejection are untested here.

Suggest adding test_reject_beam_search (decay + use_beam_search=True) and test_reject_non_flashinfer_backend (decay with a non-FlashInfer _grouped_sampler_cls) to tests/unittest/_torch/sampler/test_top_p_decay.py, or confirm these paths are already covered elsewhere and add a comment/reference here.

🤖 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_top_p_decay.py` around lines 332 - 358,
Extend TestRejection with coverage for both unsupported decay combinations: add
test_reject_beam_search using decay and use_beam_search=True, and
test_reject_non_flashinfer_backend using decay with a non-FlashInfer
_grouped_sampler_cls. Assert each request is rejected through
sampler.validate_request, reusing the existing rejection pattern and helpers.

Source: Path instructions


190-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

No end-to-end test exercises _build_top_p_runtime_override / _apply_top_p_runtime_override through sample_async.

TestRuntimeUpdate validates _update_top_p_decay_after_sample and top_p_decay_gather in isolation (good unit coverage), but nothing in this file or test_torch_sampler.py drives a decay-active request through the full sample_async pipeline to confirm TopPDecayOverride is actually constructed and consumed end-to-end (the wiring shown in sampling_utils.py/sampler.py context snippets). TestBatchedSampling._build_test_cases in test_torch_sampler.py also doesn't include a decay-active BASE_CASES entry.

Since this is the integration point tying together admission, per-slot state, and grouped sampling, an end-to-end regression here would catch wiring breaks that the isolated unit tests can't. Suggest adding an integration test (e.g. in tests/unittest/_torch/sampler/test_top_p_decay.py) that runs sample_async across multiple steps with a decay-active request and asserts the runtime top-p actually shrinks between steps, or flag this as a follow-up if it's intentionally out of scope for this PR.

🤖 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_top_p_decay.py` around lines 190 - 327,
Add an end-to-end test in TestRuntimeUpdate that drives a decay-active request
through sample_async for multiple steps, verifying _build_top_p_runtime_override
and _apply_top_p_runtime_override are wired correctly and the request’s runtime
top-p decreases between samples. Include the required request/admission setup
and assert the observed sampling top-p values, while preserving the existing
isolated unit tests.

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.

Inline comments:
In `@tensorrt_llm/_torch/pyexecutor/sampler/sampler.py`:
- Around line 4180-4249: The _build_top_p_runtime_override method must not raise
ValueError when a decay-active row has req_num_steps greater than one, since
this aborts the entire batch. Replace the exception path in the per-request
layout construction with request-scoped handling: skip the top-p decay override
for that row or otherwise fail only the affected request, while preserving
override behavior for valid single-token decay rows.

---

Nitpick comments:
In `@tests/unittest/_torch/sampler/test_top_p_decay.py`:
- Around line 332-358: Extend TestRejection with coverage for both unsupported
decay combinations: add test_reject_beam_search using decay and
use_beam_search=True, and test_reject_non_flashinfer_backend using decay with a
non-FlashInfer _grouped_sampler_cls. Assert each request is rejected through
sampler.validate_request, reusing the existing rejection pattern and helpers.
- Around line 190-327: Add an end-to-end test in TestRuntimeUpdate that drives a
decay-active request through sample_async for multiple steps, verifying
_build_top_p_runtime_override and _apply_top_p_runtime_override are wired
correctly and the request’s runtime top-p decreases between samples. Include the
required request/admission setup and assert the observed sampling top-p values,
while preserving the existing isolated unit tests.
🪄 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: 4627d58b-0abc-4344-aee9-b093cc208387

📥 Commits

Reviewing files that changed from the base of the PR and between 6013944 and 49e2e80.

📒 Files selected for processing (10)
  • cpp/tensorrt_llm/kernels/samplerKernels/toppDecayKernels.cu
  • cpp/tensorrt_llm/kernels/samplerKernels/toppDecayKernels.h
  • cpp/tensorrt_llm/thop/CMakeLists.txt
  • cpp/tensorrt_llm/thop/samplerOp.cpp
  • tensorrt_llm/_torch/pyexecutor/sampler/ops/trtllm.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py
  • tensorrt_llm/sampling_params.py
  • tests/unittest/_torch/sampler/test_top_p_decay.py
  • tests/unittest/_torch/sampler/test_torch_sampler.py

Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59692 [ run ] triggered by Bot. Commit: 3abbb4c Link to invocation

Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
Comment thread cpp/tensorrt_llm/kernels/samplerKernels/toppDecayKernels.cu Outdated
Comment thread cpp/tensorrt_llm/kernels/samplerKernels/toppDecayKernels.cu Outdated
Comment thread cpp/tensorrt_llm/kernels/samplerKernels/toppDecayKernels.cu Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampler.py Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py Outdated
Comment thread tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py Outdated
Comment thread tensorrt_llm/sampling_params.py Outdated
Comment thread tensorrt_llm/sampling_params.py Outdated
Comment thread tensorrt_llm/sampling_params.py Outdated
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59692 [ run ] completed with state SUCCESS. Commit: 3abbb4c
/LLM/main/L0_MergeRequest_PR pipeline #48124 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

Add Top-P Decay (top_p_decay / top_p_min / top_p_reset_ids) support to the
PyTorch backend TorchSampler, with semantics matching the C++ computeToppDecay
kernel: after each sampled token,
  runtime_top_p = initial_top_p                               if token == reset_id (reset_id >= 0)
                = max(runtime_top_p * top_p_decay, top_p_min) otherwise

- Per-slot runtime state in resident CUDA buffers, gated on-device by an
  is_decay_slot mask plus a host-side slot set for O(1) early-outs; the hot
  path needs no host/device sync.
- Two fused custom ops (thop/samplerOp.cpp, kernels/samplerKernels/):
  top_p_decay_gather (pre-sample per-row top-p, replaces index_select x2 +
  where) and top_p_decay_update (post-sample in-place decay with in-kernel
  token gather from the strided new_tokens view).
- Active decay forces a top-p-capable strategy even when top_p is 1.0/unset;
  an explicit greedy control (top_k==1 / top_p==0 / temperature==0) wins over
  decay (single-source predicate SamplingParams.params_imply_explicit_greedy).
- Out-of-range decay params follow the C++ warn-and-default policy.
- Unsupported combinations (beam search, speculative draft tokens through
  TorchSampler) are rejected per-request at admission via validate_request,
  with a req_num_steps == 1 guard at sample time; finished requests retire
  their slot so the early-outs re-arm.
- Unit tests (test_torch_sampler.py::TestTopPDecay): strategy routing,
  runtime-update parity with the C++ reference (cases ported from
  topPSamplingLayerTest.cpp), and per-request rejection of unsupported
  combinations.

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

Sampled token ids are non-negative, so the negative reset_ids sentinel (-1,
reset disabled) never matches without an explicit reset_id >= 0 test; compare
directly, matching the legacy computeToppDecay kernel. Update the recurrence
described in the op/kernel docs and the test reference accordingly.

Addresses review feedback from @ixlmar.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
…ent cleanups

- Extract all top-p-decay bookkeeping from setup_sampler_step into
  _setup_top_p_decay_for_new_requests; rename decay_params -> sampling_params.
- Encapsulate TopPDecayStore allocation as TopPDecayStore.create().
- _retire_top_p_decay_slot: move the GENERATION_COMPLETE check to the call
  sites so it is not re-checked redundantly.
- Narrow _build_top_p_runtime_override's strategy_key type to
  FlashInferGroupedStrategySampler.STRATEGY_KEY_TYPE.
- Inline the float_tensor helper; generalize the SamplingConfig unwrap comment
  to cover all sampling fields; document that decay param range validation
  already lives in the executor::SamplingConfig constructor.
- Tests: hoist the mock-request helper in TestTopPDecay.

Addresses review feedback from @ixlmar.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
…ct invalid decay params

Address review feedback from @ixlmar:

- Pass the top-p-decay override through the existing StrategyMetadata
  mechanism (TopPDecayMetadata, mirroring BeamSearchMetadata) instead of a
  decay_override kwarg on sample_grouped_strategies; the override is applied
  inside the TopP*/TopKTopP* strategy impls via TopPDecayMixin (which declares
  _top_p, removing the getattr/setattr workaround).
- Vectorize the per-STEP slot layout with torch.repeat_interleave; guard the
  single-token invariant in an if __debug__ block; use .item() over int().
- BREAKING: SamplingParams rejects out-of-range decay params (top_p_decay /
  top_p_min outside (0, 1], negative top_p_reset_ids) instead of
  warn-and-default, mirroring the executor::SamplingConfig hard checks.
- Single-source the greedy/decay resolution: params_imply_top_p_decay_active
  and an extended params_imply_greedy_decoding(top_p_decay=...) on
  SamplingParams, reused by _greedy_decoding and resolve_sampling_strategy
  (decay predicate now evaluated lazily).
- Consolidate the feature documentation: TopPDecayStore's docstring is the
  single source for semantics and the slot lifecycle; other sites keep their
  local contract and point back to it.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
_unbatch_sampling_results gained a required seq_slots_cuda kwarg (the
resident device copy used by the post-sample top-p-decay update), but the
direct caller in test_unbatch_sampling_results was not updated. Precompute
the device copy outside the no-sync region. Full test_torch_sampler.py now
passes (205/205) in a matching container.

Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
…rch.compile'd ops

Per review discussion (@ixlmar): keep the new sampler kernel-free. The two
decay ops (pre-sample per-row gather, post-sample in-place update) are now
native torch functions fused with torch.compile in
sampler/ops/top_p_decay.py; the custom CUDA kernels, the thop op wrappers,
and their build wiring are removed.

Compile configuration and rationale (benchmarked on H200, bs=32, per-step
sampler cost beyond a fake model forward):

- mode=max-autotune-no-cudagraphs: cudagraphs is unsafe here (in-place
  per-slot state, output consumed outside the compiled region) and the
  cudagraphs-enabled modes were either slower or produced overwritten
  outputs.
- torch._dynamo.mark_dynamic on the batch-varying dims avoids recompilation
  as batch composition changes.
- With a >=2ms forward, this matches the removed fused kernels
  (~141us vs ~142us per step, both ~35us ahead of eager); with a ~1ms
  forward it costs ~40us/step over eager (~90us over the fused kernels),
  the fixed compiled-entry cost being exposed. The kernel-free
  maintainability trade-off was judged worth it for the non-critical-path
  decay feature.
- Compilation is lazy: first decay-active request pays ~1s; non-decay
  workloads never trigger it.

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

They are pure-torch compiled ops; Fusions in ops/vanilla.py is the existing
home for torch.compile'd fused helpers (same impl + mark_dynamic wrapper
pattern as gather_log_softmax), so the standalone ops/top_p_decay.py module
is folded into it and call sites use Fusions.top_p_decay_{gather,update}.

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py (1)

399-419: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the static top-p tensor immutable across sampling calls.

Line 414 persists gathered runtime values in self._top_p. On a later group with reordered or newly non-decay rows, Line 418 uses those stale values as static_top_p, so plain top-p requests can sample with another request’s decayed threshold. Preserve _static_top_p, reset _top_p from it on every call, and gather from _static_top_p.

🤖 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/sampler/sampling_utils.py` around lines 399 -
419, The _maybe_apply_top_p_decay method currently mutates self._top_p, allowing
decayed values to leak into later sampling calls. Preserve the immutable static
tensor in _static_top_p, reset self._top_p from it on every invocation, and pass
_static_top_p as static_top_p to top_p_decay_gather while retaining the existing
metadata validation and non-decay behavior.
tensorrt_llm/_torch/pyexecutor/sampler/sampler.py (1)

2589-2595: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Prevent late speculative-decoding mismatches from aborting the batch.

get_draft_token_length() is explicitly best-effort at admission because drafter tokens may be attached later. Such a request can reach the assertion at Lines 4326-4338 with req_num_steps > 1; the assertion then raises during grouped metadata construction and aborts the whole sampler batch. Reject the request when draft tokens become known, or skip/fail only that request instead of asserting in the batch path. This is the same late top-p-decay mismatch previously reported.

Also applies to: 4326-4338

🤖 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/sampler/sampler.py` around lines 2589 - 2595,
Prevent late speculative-decoding requests from aborting grouped metadata
construction: update the admission/metadata flow around get_draft_token_length
and _build_top_p_decay_metadata so requests whose draft tokens become known with
req_num_steps > 1 are rejected or skipped individually before the batch-level
assertion. Remove or replace the assertion path in _build_top_p_decay_metadata
with per-request handling while preserving valid non-speculative requests.
🧹 Nitpick comments (1)
tensorrt_llm/_torch/pyexecutor/sampler/ops/top_p_decay.py (1)

72-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the public tensor contracts with Args: sections.

These exported functions have shape, dtype, mutation, and indexing requirements, but their docstrings do not document each argument in Google style. Add explicit Args: entries, including sampled_slots/slots indexing and the in-place mutation of runtime_top_p.

Also applies to: 108-124

🤖 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/sampler/ops/top_p_decay.py` around lines 72 -
94, Add Google-style Args sections to the exported functions top_p_decay_update
and the related function at the referenced later section, documenting each
tensor’s shape, dtype, indexing semantics, and role. Explicitly describe
sampled_slots/slots as indices into per-slot tensors and state that
runtime_top_p is mutated in place while the functions return None.

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.

Outside diff comments:
In `@tensorrt_llm/_torch/pyexecutor/sampler/sampler.py`:
- Around line 2589-2595: Prevent late speculative-decoding requests from
aborting grouped metadata construction: update the admission/metadata flow
around get_draft_token_length and _build_top_p_decay_metadata so requests whose
draft tokens become known with req_num_steps > 1 are rejected or skipped
individually before the batch-level assertion. Remove or replace the assertion
path in _build_top_p_decay_metadata with per-request handling while preserving
valid non-speculative requests.

In `@tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py`:
- Around line 399-419: The _maybe_apply_top_p_decay method currently mutates
self._top_p, allowing decayed values to leak into later sampling calls. Preserve
the immutable static tensor in _static_top_p, reset self._top_p from it on every
invocation, and pass _static_top_p as static_top_p to top_p_decay_gather while
retaining the existing metadata validation and non-decay behavior.

---

Nitpick comments:
In `@tensorrt_llm/_torch/pyexecutor/sampler/ops/top_p_decay.py`:
- Around line 72-94: Add Google-style Args sections to the exported functions
top_p_decay_update and the related function at the referenced later section,
documenting each tensor’s shape, dtype, indexing semantics, and role. Explicitly
describe sampled_slots/slots as indices into per-slot tensors and state that
runtime_top_p is mutated in place while the functions return None.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 269e9fdb-3ca0-4fe7-ab04-308a160806e0

📥 Commits

Reviewing files that changed from the base of the PR and between 8e2dea1 and 17451b1.

📒 Files selected for processing (3)
  • tensorrt_llm/_torch/pyexecutor/sampler/ops/top_p_decay.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py

…space change

- TopPDecayStore lifecycle step 1 still said 'fused kernels'; the CUDA
  kernels were replaced by torch.compile'd ops.
- Restore a blank line in setup_sampler_step accidentally dropped from the
  upstream code, keeping the diff against main minimal.

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

@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/sampler/ops/vanilla.py (1)

389-389: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Add fullgraph=True to the two @torch.compile(...) decorators
mode="max-autotune-no-cudagraphs" can be combined with fullgraph=True, and these in-place index updates/gathers are supported by torch.compile. Keeping fullgraph=True here would make any unexpected graph break fail loudly instead of silently falling back to eager on this per-step path.

🤖 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/sampler/ops/vanilla.py` at line 389, Update
both `@torch.compile` decorators in vanilla.py at lines 389-389 and 447-447 to
include fullgraph=True alongside mode="max-autotune-no-cudagraphs". Apply the
same change to both decorator sites and leave the wrapped functions unchanged.
🤖 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/sampler/ops/vanilla.py`:
- Line 389: Update both `@torch.compile` decorators in vanilla.py at lines 389-389
and 447-447 to include fullgraph=True alongside
mode="max-autotune-no-cudagraphs". Apply the same change to both decorator sites
and leave the wrapped functions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cad5f9e6-79e1-4ae5-8024-699589097c8a

📥 Commits

Reviewing files that changed from the base of the PR and between 17451b1 and 645df10.

📒 Files selected for processing (3)
  • tensorrt_llm/_torch/pyexecutor/sampler/ops/vanilla.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampler.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tensorrt_llm/_torch/pyexecutor/sampler/sampling_utils.py
  • tensorrt_llm/_torch/pyexecutor/sampler/sampler.py

@zhaoyangwang-nvidia

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60927 [ run ] triggered by Bot. Commit: 6133665 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60927 [ run ] completed with state FAILURE. Commit: 6133665
/LLM/main/L0_MergeRequest_PR pipeline #49192 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 enabled auto-merge (squash) July 22, 2026 11:02
…dexing

mypy rejected slicing seq_slots_cuda with Tensor.item() results (Slice
index must be an integer). Cast first_req/last_req to int, matching the
existing int()/cast(int, ...) pattern used elsewhere in this file.

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

xxi-nv commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60972 [ run ] triggered by Bot. Commit: 49c0c79 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60972 [ run ] completed with state FAILURE. Commit: 49c0c79
/LLM/main/L0_MergeRequest_PR pipeline #49234 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 22, 2026

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61001 [ run ] triggered by Bot. Commit: 49c0c79 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61001 [ run ] completed with state SUCCESS. Commit: 49c0c79
/LLM/main/L0_MergeRequest_PR pipeline #49259 completed with status: 'SUCCESS'

CI Report

Link to invocation

@zhaoyangwang-nvidia
zhaoyangwang-nvidia merged commit 5e87763 into NVIDIA:main Jul 22, 2026
7 checks passed
@zhaoyangwang-nvidia
zhaoyangwang-nvidia deleted the pr-topp-decay branch July 23, 2026 01:10
yuanjingx87 pushed a commit to yuanjingx87/TensorRT-LLM that referenced this pull request Jul 26, 2026
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