Skip to content

Commit 5d97c03

Browse files
[TRTLLM-13212][refactor] Make FlashInfer a hard dependency for Torch sampler and spec
Convert the Torch sampler and one-model speculative decoding from "FlashInfer-optional with torch fallback" to a hard FlashInfer dependency. The failure is raised at sampler / speculative-worker construction time (TorchSampler.__init__, SpecWorkerBase) with a clear ImportError, so sampling_utils stays importable for torch-only consumers (AutoDeploy demollm, drafting greedy helpers). - IS_FLASHINFER_AVAILABLE semantics are unchanged (its ~46 legitimate fallback consumers are not touched). - ops/flashinfer.py: keep flashinfer.sampling behind an import guard so the module stays importable without flashinfer; ops are only reached after the hard-dependency check at construction time. - TorchSampler.__init__ enforces the hard dependency once (out of the CUDA-graph-captured loop) before selecting the grouped sampler class. - Flatten the single-implementation grouped sampler: drop the GroupedStrategySampler ABC and keep only FlashInferGroupedStrategySampler. - Remove the SimpleGroupedStrategySampler torch backend and the disable_flashinfer_sampling LlmArg + its plumbing; update the api_stability and usage golden files and the sampling/telemetry docs. - compute_probs_from_logits becomes FlashInfer-only (drop the CUDA C++ op and CPU vanilla branches). - speculative/interface.py: drop the availability guards, keep the flashinfer>=0.6.4 version gate and its version-gated branches. - Delete now-dead fallback kernels (forward_native_sampling, _apply_top_k_top_p, _random_sample, vanilla.compute_probs_from_logits_op); keep beam-search/greedy/rejection/_Fusions and the torch sample() path used by rejection-sampling draft probs and demollm. - Prune torch-vs-flashinfer parametrized sampler tests. Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
1 parent 940050b commit 5d97c03

16 files changed

Lines changed: 115 additions & 423 deletions

File tree

docs/source/developer-guide/telemetry.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ unset or when the safety sanitizer rejects the runtime value.
7070
| `cuda_graph_config.mode` | `Literal['decode']` | `categorical` | | `decode`, `encode` |
7171
| `cuda_graph_config.num_tokens` | `Optional[List[Annotated[int, Gt(gt=0)]]]` | `value` | | |
7272
| `cuda_graph_config.seq_lens` | `Optional[List[Annotated[int, Gt(gt=0)]]]` | `value` | | |
73-
| `disable_flashinfer_sampling` | `<class 'bool'>` | `value` | | |
7473
| `disable_overlap_scheduler` | `<class 'bool'>` | `value` | | |
7574
| `dtype` | `<class 'str'>` | `categorical` | allowlist | `auto`, `float16`, `bfloat16`, `float32` |
7675
| `dwdp_config.contention_opt` | `<class 'bool'>` | `value` | | |

docs/source/features/sampling.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,12 @@ llm.generate(["Hello, my name is",
104104
### Performance
105105

106106
The Torch Sampler leverages the optimized sampling kernels provided by
107-
[FlashInfer](https://docs.flashinfer.ai/api/sampling.html). The sampler
108-
also uses the [sorting-free implementations](https://flashinfer.ai/2025/03/10/sampling.html)
107+
[FlashInfer](https://docs.flashinfer.ai/api/sampling.html), which is a required
108+
dependency for the Torch Sampler. The sampler also uses the
109+
[sorting-free implementations](https://flashinfer.ai/2025/03/10/sampling.html)
109110
whenever possible. This optimization does not compute the complete set of token sampling probabilities
110111
(after top-k / top-p masking etc.), which typically can be omitted unless requested by the user or
111112
required for speculative decoding (rejection sampling).
112-
In case of unexpected problems, the use of FlashInfer in Torch Sampler can
113-
be disabled via the `disable_flashinfer_sampling` config option (note that this option is likely
114-
to be removed in a future TensorRT LLM release).
115113

116114
Moreover, Torch Sampler internally batches requests with compatible sampling parameters. This
117115
can greatly reduce the overall latency of the sampling step when request batches are comprised

tensorrt_llm/_torch/pyexecutor/_util.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,7 +2436,6 @@ def create_torch_sampler_args(
24362436
speculative_config: SpeculativeConfig,
24372437
max_beam_width: int,
24382438
disable_overlap_scheduler: bool,
2439-
disable_flashinfer_sampling: bool,
24402439
enable_async_worker: bool,
24412440
enable_speculative_beam_history_d2h: bool,
24422441
):
@@ -2452,7 +2451,6 @@ def create_torch_sampler_args(
24522451
max_total_draft_tokens=max_total_draft_tokens,
24532452
max_num_sequences=max_num_sequences,
24542453
max_beam_width=max_beam_width,
2455-
disable_flashinfer_sampling=disable_flashinfer_sampling,
24562454
disable_overlap_scheduler=disable_overlap_scheduler,
24572455
enable_async_worker=enable_async_worker,
24582456
enable_speculative_beam_history_d2h=enable_speculative_beam_history_d2h,
@@ -2471,7 +2469,6 @@ def instantiate_sampler(
24712469
speculative_config: SpeculativeConfig,
24722470
decoding_config: trtllm.DecodingConfig,
24732471
kv_cache_config: KvCacheConfig,
2474-
disable_flashinfer_sampling: bool,
24752472
):
24762473
enable_async_worker = (confidential_compute_enabled()
24772474
or llm_args.sampler_force_async_worker)
@@ -2483,7 +2480,6 @@ def instantiate_sampler(
24832480
speculative_config=speculative_config,
24842481
max_beam_width=max_beam_width,
24852482
disable_overlap_scheduler=llm_args.disable_overlap_scheduler,
2486-
disable_flashinfer_sampling=disable_flashinfer_sampling,
24872483
enable_async_worker=enable_async_worker,
24882484
enable_speculative_beam_history_d2h=llm_args.
24892485
enable_speculative_beam_history_d2h,

tensorrt_llm/_torch/pyexecutor/py_executor_creator.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,6 @@ def drafting_loop_wrapper(model):
796796
speculative_config=spec_config,
797797
decoding_config=decoding_config,
798798
kv_cache_config=kv_cache_config,
799-
disable_flashinfer_sampling=llm_args.disable_flashinfer_sampling,
800799
)
801800
logger.info(f"Using Sampler: {type(sampler).__name__}")
802801

tensorrt_llm/_torch/pyexecutor/sampler/ops/flashinfer.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414

1515
"""FlashInfer-accelerated sampling kernels.
1616
17-
Pure kernel functions with no dependency on the sampling_utils interface
18-
or other backend implementation modules. All flashinfer imports are guarded by
19-
IS_FLASHINFER_AVAILABLE. Beam search is excluded (torch-only per design).
17+
These ops depend on flashinfer; the import is guarded so the module stays
18+
importable without it.
2019
"""
2120

2221
from typing import Optional

tensorrt_llm/_torch/pyexecutor/sampler/ops/interface.py

Lines changed: 0 additions & 57 deletions
This file was deleted.

tensorrt_llm/_torch/pyexecutor/sampler/ops/vanilla.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -353,71 +353,6 @@ def _safely_apply_temperature_inplace(
353353
return logits_inout.div_(safe_temp.unsqueeze(dim=1))
354354

355355

356-
def _apply_top_k_top_p(
357-
logits: torch.Tensor,
358-
k: Optional[torch.Tensor],
359-
p: Optional[torch.Tensor],
360-
) -> torch.Tensor:
361-
logits_sort, logits_idx = logits.sort(dim=-1, descending=False)
362-
if k is not None:
363-
top_k_mask = logits_sort.size(1) - k.to(torch.long)
364-
top_k_mask = top_k_mask.clamp(min=0)
365-
top_k_mask = logits_sort.gather(1, top_k_mask.unsqueeze(dim=1))
366-
top_k_mask = logits_sort < top_k_mask
367-
logits_sort.masked_fill_(top_k_mask, -float("inf"))
368-
if p is not None:
369-
probs_sort = logits_sort.softmax(dim=-1)
370-
probs_sum = torch.cumsum(probs_sort, dim=-1, out=probs_sort)
371-
top_p_mask = probs_sum <= 1 - p.unsqueeze(dim=1)
372-
top_p_mask[:, -1] = False
373-
logits_sort.masked_fill_(top_p_mask, -float("inf"))
374-
return logits_sort.scatter(dim=-1, index=logits_idx, src=logits_sort)
375-
376-
377-
def _random_sample(probs: torch.Tensor) -> torch.Tensor:
378-
q = torch.empty_like(probs).exponential_()
379-
return probs.div_(q).argmax(dim=-1).view(-1)
380-
381-
382-
def forward_native_sampling(
383-
logits: torch.Tensor,
384-
k: Optional[torch.Tensor],
385-
p: Optional[torch.Tensor],
386-
) -> torch.Tensor:
387-
logits = _apply_top_k_top_p(logits, k, p)
388-
probs = logits.softmax(dim=-1, dtype=torch.float32)
389-
return _random_sample(probs)
390-
391-
392-
def compute_probs_from_logits_op(
393-
logits: torch.Tensor,
394-
temperatures: torch.Tensor,
395-
top_k: Optional[torch.Tensor],
396-
top_p: Optional[torch.Tensor],
397-
) -> torch.Tensor:
398-
"""Pure-PyTorch CPU fallback for probability computation."""
399-
is_greedy = temperatures <= _GREEDY_TEMPERATURE_THRESHOLD
400-
# Greedy rows must pick the argmax of the *original* logits (before temperature).
401-
# Capture the argmax up front; _safely_apply_temperature_inplace then guards the
402-
# division against the greedy sentinel.
403-
argmax_ids = logits.argmax(dim=-1, keepdim=True)
404-
405-
logits = _safely_apply_temperature_inplace(logits, temperatures)
406-
logits = _apply_top_k_top_p(logits, top_k, top_p)
407-
probs = logits.softmax(dim=-1, dtype=torch.float32)
408-
409-
# Turn the greedy rows into a one-hot at argmax by editing `probs` in place,
410-
# instead of building a full [batch, vocab] one-hot buffer and a [batch, vocab]
411-
# torch.where copy. The torch.where here only runs on a [batch, 1] tensor.
412-
# NB: argwhere/index-select on the greedy rows would give a data-dependent shape
413-
# that breaks the surrounding torch.compile graph, so we keep it dense.
414-
greedy_col = is_greedy.unsqueeze(1)
415-
new_at_argmax = torch.where(greedy_col, 1.0, probs.gather(1, argmax_ids))
416-
probs.masked_fill_(greedy_col, 0.0)
417-
probs.scatter_(1, argmax_ids, new_at_argmax)
418-
return probs
419-
420-
421356
class _Fusions:
422357
@staticmethod
423358
@torch.compile(dynamic=None, fullgraph=True)

tensorrt_llm/_torch/pyexecutor/sampler/sampler.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,15 @@
8484
from ...speculative.spec_tree_manager import SpecTreeManager
8585
from ...utils import torch_multi_arange
8686
from ..finish_reason import FinishedState
87+
from ..flashinfer_utils import IS_FLASHINFER_AVAILABLE
8788
from ..llm_request import LlmRequest, LlmRequestState, get_draft_token_length
8889
from ..resource_manager import ResourceManager, ResourceManagerType
8990
from ..scheduler import ScheduledRequests
90-
from .ops.interface import SamplerConfig, resolve_sampling_backend
9191
from .sampling_utils import (
9292
BEAM_SEARCH_PAD_TOKEN,
9393
GREEDY,
9494
BeamSearchMetadata,
95+
FlashInferGroupedStrategySampler,
9596
GenericStrategyKeyType,
9697
Strategy,
9798
StrategyMetadata,
@@ -2321,7 +2322,6 @@ class Args:
23212322
max_beam_width: int
23222323
max_total_draft_tokens: int
23232324
disable_overlap_scheduler: bool = False
2324-
disable_flashinfer_sampling: bool = False
23252325
enable_async_worker: bool = False
23262326
enable_speculative_beam_history_d2h: bool = False
23272327

@@ -2345,13 +2345,15 @@ def __init__(self, args: Args):
23452345
self.LOGPROBS_SHAPE = (self.max_num_sequences, self.max_beam_width, self.max_tokens)
23462346
self.TOPK_LOGPROBS_SHAPE = (self.max_num_sequences, self.max_tokens, self.max_topk_logprobs)
23472347

2348-
self._grouped_sampler_cls = resolve_sampling_backend(
2349-
is_cuda=True,
2350-
config=SamplerConfig(
2351-
# IS_FLASHINFER_AVAILABLE is checked inside resolve_sampling_backend.
2352-
use_flashinfer=not args.disable_flashinfer_sampling,
2353-
),
2354-
)
2348+
# The Torch sampler hard-depends on flashinfer. Enforce it once here, at
2349+
# construction, so the check stays out of the CUDA-graph-captured
2350+
# sampling loop.
2351+
if not IS_FLASHINFER_AVAILABLE:
2352+
raise ImportError(
2353+
"flashinfer is not available, please install the version pinned "
2354+
"in requirements.txt."
2355+
)
2356+
self._grouped_sampler_cls = FlashInferGroupedStrategySampler
23552357

23562358
# AutoDeploy build creates the sampler in inference mode,
23572359
# which would disallow in-place mutating of new_tokens.
@@ -4598,7 +4600,7 @@ def _process_logprobs(
45984600
sampled_indices_cuda = group_next_tokens_cuda.squeeze(1)
45994601

46004602
# sampled_rank_cuda contains the 0-based rank, it will be corrected to 1-based in handle_logprobs
4601-
# NB: Computation of sampled rank could be lowered into GroupedStrategySampler, s.t., e.g., for
4603+
# NB: Computation of sampled rank could be lowered into FlashInferGroupedStrategySampler, s.t., e.g., for
46024604
# greedy sampling, logits management and log_softmax could be completely skipped (sampled rank
46034605
# computation is trivial in this case).
46044606
sampled_rank_cuda = _Fusions.determine_sampled_rank(

0 commit comments

Comments
 (0)