Skip to content

Commit 4093d14

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 06df8da commit 4093d14

17 files changed

Lines changed: 125 additions & 427 deletions

File tree

docs/source/developer-guide/telemetry.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ unset or when the safety sanitizer rejects the runtime value.
6363
| `cuda_graph_config.mode` | `Literal['decode']` | `categorical` | | `decode`, `encode` |
6464
| `cuda_graph_config.num_tokens` | `Optional[List[Annotated[int, Gt(gt=0)]]]` | `value` | | |
6565
| `cuda_graph_config.seq_lens` | `Optional[List[Annotated[int, Gt(gt=0)]]]` | `value` | | |
66-
| `disable_flashinfer_sampling` | `<class 'bool'>` | `value` | | |
6766
| `disable_overlap_scheduler` | `<class 'bool'>` | `value` | | |
6867
| `dtype` | `<class 'str'>` | `categorical` | allowlist | `auto`, `float16`, `bfloat16`, `float32` |
6968
| `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
@@ -2343,7 +2343,6 @@ def create_torch_sampler_args(
23432343
speculative_config: SpeculativeConfig,
23442344
max_beam_width: int,
23452345
disable_overlap_scheduler: bool,
2346-
disable_flashinfer_sampling: bool,
23472346
enable_async_worker: bool,
23482347
enable_speculative_beam_history_d2h: bool,
23492348
):
@@ -2359,7 +2358,6 @@ def create_torch_sampler_args(
23592358
max_total_draft_tokens=max_total_draft_tokens,
23602359
max_num_sequences=max_num_sequences,
23612360
max_beam_width=max_beam_width,
2362-
disable_flashinfer_sampling=disable_flashinfer_sampling,
23632361
disable_overlap_scheduler=disable_overlap_scheduler,
23642362
enable_async_worker=enable_async_worker,
23652363
enable_speculative_beam_history_d2h=enable_speculative_beam_history_d2h,
@@ -2378,7 +2376,6 @@ def instantiate_sampler(
23782376
speculative_config: SpeculativeConfig,
23792377
decoding_config: trtllm.DecodingConfig,
23802378
kv_cache_config: KvCacheConfig,
2381-
disable_flashinfer_sampling: bool,
23822379
):
23832380
enable_async_worker = (confidential_compute_enabled()
23842381
or llm_args.sampler_force_async_worker)
@@ -2390,7 +2387,6 @@ def instantiate_sampler(
23902387
speculative_config=speculative_config,
23912388
max_beam_width=max_beam_width,
23922389
disable_overlap_scheduler=llm_args.disable_overlap_scheduler,
2393-
disable_flashinfer_sampling=disable_flashinfer_sampling,
23942390
enable_async_worker=enable_async_worker,
23952391
enable_speculative_beam_history_d2h=llm_args.
23962392
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
@@ -763,7 +763,6 @@ def drafting_loop_wrapper(model):
763763
speculative_config=spec_config,
764764
decoding_config=decoding_config,
765765
kv_cache_config=kv_cache_config,
766-
disable_flashinfer_sampling=llm_args.disable_flashinfer_sampling,
767766
)
768767
logger.info(f"Using Sampler: {type(sampler).__name__}")
769768

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,
@@ -2323,7 +2324,6 @@ class Args:
23232324
max_beam_width: int
23242325
max_total_draft_tokens: int
23252326
disable_overlap_scheduler: bool = False
2326-
disable_flashinfer_sampling: bool = False
23272327
enable_async_worker: bool = False
23282328
enable_speculative_beam_history_d2h: bool = False
23292329

@@ -2347,13 +2347,15 @@ def __init__(self, args: Args):
23472347
self.LOGPROBS_SHAPE = (self.max_num_sequences, self.max_beam_width, self.max_tokens)
23482348
self.TOPK_LOGPROBS_SHAPE = (self.max_num_sequences, self.max_tokens, self.max_topk_logprobs)
23492349

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

23582360
# AutoDeploy build creates the sampler in inference mode,
23592361
# which would disallow in-place mutating of new_tokens.
@@ -4627,7 +4629,7 @@ def _process_logprobs(
46274629
sampled_indices_cuda = group_next_tokens_cuda.squeeze(1)
46284630

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

0 commit comments

Comments
 (0)