Skip to content

Commit 1c9a902

Browse files
[TRTLLM-14198][refactor] Make FlashInfer a hard dependency for the Torch sampler (NVIDIA#16160)
Signed-off-by: ZhaoyangWang <zhaoyangw@nvidia.com>
1 parent 374cc09 commit 1c9a902

17 files changed

Lines changed: 146 additions & 449 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
@@ -38,6 +38,7 @@
3838
import numpy as np
3939
import torch
4040

41+
from tensorrt_llm._torch.flashinfer_utils import IS_FLASHINFER_AVAILABLE
4142
from tensorrt_llm._torch.pyexecutor.make_decoding_batch_input_output import (
4243
MakeDecodingBatchInputOutput,
4344
)
@@ -87,11 +88,11 @@
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)