Skip to content
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
4a2376a
Long's cuda dsl gvr initial impl.
limin2021 May 20, 2026
bec3708
Switch DSL gvr_topk_decode to fake_tensor compile + fix test pre_idx …
limin2021 May 20, 2026
b0bbf2a
Align gvr_topk_decode with CUDA reference (Batch 1: barriers / snap_l…
limin2021 May 20, 2026
3b11d0f
Align gvr_topk_decode Phase 2/3 loops with CUDA (Batch 2: runtime whi…
limin2021 May 20, 2026
89edd73
Align gvr_topk_decode with CUDA: AddressSpace.gmem + preIdx unroll
limin2021 May 21, 2026
b459a8f
Align block_count_ge with CUDA: 4-way unrolled vec loop (fast-path + …
limin2021 May 21, 2026
a01f6c5
Add strided layout + 2-way medium cascade with dtype-aware defaults
limin2021 May 21, 2026
5b3d34c
Add phase3 unroll + constant_hint switches; cascade for phase3_collect
limin2021 May 25, 2026
45c53c5
Switch Phase 2/3 to cutlass.range(unroll=4); add 3-tier min_blocks he…
limin2021 May 25, 2026
44035a5
Add use_256bit_load and configurable num_threads_per_block
limin2021 May 26, 2026
882c767
Add enable_warp_parallel_reduce switch and clean dead unroll/layout k…
limin2021 May 27, 2026
19b1682
Align DSL GvrParams kFTarget=kK with CUDA PR #14413
limin2021 May 27, 2026
cd656ad
Consolidate kernel constants into GvrTopKKernel.__init__
limin2021 May 27, 2026
b610d13
Add compress_ratio support for DSv4 indexer (cr=4)
limin2021 May 27, 2026
6bf5c87
Auto-resolve host wrapper defaults: wp_parallel + per-(T,dtype) tier-…
limin2021 May 27, 2026
eb261b9
Auto-resolve num_threads_per_block + use_256bit_load in host wrapper
limin2021 May 27, 2026
2ef50d3
Add max_seq_len hint for graph-safe (T, V) selection
limin2021 May 27, 2026
99340a0
Use N_dec for min_blocks tier check too (consistency with T/V)
limin2021 May 27, 2026
15af2ef
Wave-fit aware tier-3 mb for fp32: mb=3 when num_rows in (2*sms, 3*sms]
limin2021 May 28, 2026
d8b3f13
Add wave-fit N cutoff for fp32 tier-3 + cleanup file comments
limin2021 May 28, 2026
d474eb9
Add cute_dsl_gvr_topk_decode custom op + unittest
limin2021 May 28, 2026
3ccd70e
Skip STG.value path in GVR Top-K when caller only consumes indices
limin2021 May 29, 2026
ddd872f
Address PR review on cute_dsl_gvr_topk_decode tests
limin2021 May 29, 2026
335dd11
Validate logits.shape[0] divisibility by next_n in GVR op
limin2021 May 29, 2026
48d44e2
Inline N_cols + key info_once on shape signature
limin2021 May 29, 2026
c055ad6
Mirror kernel N_eff formula in test reference
limin2021 May 29, 2026
e219a00
Address PR review feedback for GVR Top-K DSL port
limin2021 Jun 1, 2026
d23ba74
Address second-round PR review for GVR Top-K DSL port
limin2021 Jun 1, 2026
97c47dc
Remove unused s_thr_extra smem allocation in GVR Top-K kernel
limin2021 Jun 1, 2026
8cb1e34
[None][feat] GVR Top-K V5 + cluster: multi-CTA-per-row MVP
limin2021 Jun 3, 2026
0e7220a
[None][perf] GVR Top-K cluster: Shift D — cluster-parallel Phase 3 + …
limin2021 Jun 3, 2026
513890f
[None][perf] GVR Top-K cluster: auto-dispatch cluster_size by (N, BS)
limin2021 Jun 3, 2026
f42c9e2
[None][exp] GVR Top-K cluster: Shift E SMEM slice cache (disabled by …
limin2021 Jun 4, 2026
34ccad2
[None][feat] GVR Top-K cluster: varlen (per-row seq_len) support
limin2021 Jun 4, 2026
fc31330
Merge remote-tracking branch 'github-upstream/main' into cute-dsl-gvr…
limin2021 Jun 10, 2026
10505e3
[None][feat] GVR Top-K: unify single-CTA and multi-CTA cluster paths
limin2021 Jun 10, 2026
54291a5
[None][chore] GVR Top-K: polish comments and docstrings
limin2021 Jun 10, 2026
89ca42c
[None][fix] GVR Top-K: clamp cluster_size to device hardware limit
limin2021 Jun 10, 2026
795c35e
[None][chore] GVR Top-K: fix misleading "cs=4 can crash" comment
limin2021 Jun 10, 2026
dda0a04
[None][perf] GVR Top-K: retune cluster_size auto-dispatch (add cs=8, …
limin2021 Jun 10, 2026
6110513
[None][chore] GVR Top-K: delete legacy cluster fork files
limin2021 Jun 10, 2026
b95fef1
[None][refactor] GVR Top-K: extract kernel body into run_one_row method
limin2021 Jun 11, 2026
b119960
[None][chore] GVR Top-K: drop dead do_cluster_aggregation parameter
limin2021 Jun 11, 2026
bc6d0e8
[None][fix] GVR Top-K: drop relaxed arrive on DSMEM producer→consumer…
limin2021 Jun 11, 2026
62ba581
[None][fix] GVR Top-K: cap peer_cnt to kC in cluster leader gather
limin2021 Jun 11, 2026
f4454df
[None][test] GVR Top-K: add (fp16, K=1024) cell to keep fp16 path cov…
limin2021 Jun 11, 2026
8fb7e58
[None][docs] GVR Top-K op: document the cluster_size arg
limin2021 Jun 11, 2026
55d21cc
[None][feat] GVR Top-K LB: prepare kernel + heterogeneous-cluster mai…
limin2021 Jun 11, 2026
61b6d9d
[None][test] GVR Top-K LB: decouple prepare + add unit tests
limin2021 Jun 11, 2026
005966d
[None][refactor] GVR Top-K LB: API cleanup and test simplification
limin2021 Jun 11, 2026
362dc16
[None][refactor] GVR Top-K LB: hoist wrappers to run_gvr_topk.py for …
limin2021 Jun 12, 2026
c9547bd
[None][feat] GVR Top-K: seqlen_sorted entry path via order_row indire…
limin2021 Jun 12, 2026
31e9035
[None][feat] GVR Top-K wrapper: plumb seqlen_sorted + add gvr_topk_so…
limin2021 Jun 12, 2026
f30e724
[None][feat] GVR Top-K custom op + UT: order_row LJF dispatch path
limin2021 Jun 12, 2026
3416524
Merge remote-tracking branch 'github-upstream/main' into gvr-topk-loa…
limin2021 Jun 15, 2026
be52699
[None][test] GVR Top-K: drop conftest.py onnx stub
limin2021 Jun 15, 2026
ad410c9
[None][fix] GVR Top-K: address PR review nits (coderabbitai)
limin2021 Jun 15, 2026
e51c837
[None][docs] GVR Top-K op: add TODO note for dsa.py order_row wiring
limin2021 Jun 15, 2026
b435266
[None][docs] GVR Top-K op: rename LJF -> row reorder (LJF) in comments
limin2021 Jun 15, 2026
6ee3093
[None][fix] GVR Top-K LB: assert max_batch_size pow2 in [64, 1024]
limin2021 Jun 16, 2026
baf3aec
[None][fix] GVR Top-K: address remaining PR review should-fix items
limin2021 Jun 16, 2026
a99fb4f
[None][test] GVR Top-K LB: address review nits + extend LB UT to next…
limin2021 Jun 16, 2026
d085398
[None][fix] GVR Top-K LB: address yuxianq review (drop unused long_th…
limin2021 Jun 16, 2026
32ddd3e
[None][docs] GVR Top-K: fix gvr_topk_sort_prepare docstring (request-…
limin2021 Jun 17, 2026
83789d9
[None][refactor] GVR Top-K LB: merge LBDecodeRunner into decode Runne…
limin2021 Jun 17, 2026
bc53988
[None][test] GVR Top-K: inline argsort, drop run_gvr_topk dependency
limin2021 Jun 17, 2026
fdd4ad3
[None][docs] GVR Top-K decode forward: fix "(tensor, ts)" typo in dis…
limin2021 Jun 17, 2026
0562e62
[None][docs] GVR Top-K: update LJF wiring TODO with measured gate (2 …
limin2021 Jun 23, 2026
9a34a89
Merge branch 'main' into gvr-topk-load-balance
xxi-nv Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -5734,11 +5734,13 @@ def _compile(
compress_ratio: int,
return_output_values: bool,
cluster_size: int,
seqlen_sorted: bool,
) -> None:
key = (dtype, top_k, next_n, enable_unroll_4, enable_phase3_unroll,
use_constant_hint, min_blocks_per_mp, use_256bit_load,
num_threads_per_block, enable_warp_parallel_reduce,
compress_ratio, return_output_values, cluster_size)
compress_ratio, return_output_values, cluster_size,
seqlen_sorted)
if key in cls.kernel_cache:
return
n_rows = cute.sym_int()
Expand Down Expand Up @@ -5769,6 +5771,14 @@ def _compile(
cutlass.Int32, (n_rows, top_k),
stride_order=(1, 0),
assumed_align=16)
# seqlen_sorted=False compiles the LJF indirection out via
# const_expr → no order_row read → fake = None (mirrors the
# out_values optional pattern). seqlen_sorted=True compiles
# the indirection in; fake shape = batch (n_batch) since
# order_row is request-level, not row-level.
order_row_fake = (cute.runtime.make_fake_compact_tensor(
cutlass.Int32,
(n_batch, ), stride_order=(0, )) if seqlen_sorted else None)
fake_stream = cute.runtime.make_fake_stream(
use_tvm_ffi_env_stream=True)

Expand All @@ -5786,6 +5796,7 @@ def _compile(
compress_ratio=compress_ratio,
return_output_values=return_output_values,
cluster_size=cluster_size,
seqlen_sorted=seqlen_sorted,
)
cls.kernel_cache[key] = cute.compile(
kernel,
Expand All @@ -5794,6 +5805,7 @@ def _compile(
seq_lens_fake,
out_values_fake,
out_indices_fake,
order_row_fake,
stream=fake_stream,
options="--enable-tvm-ffi",
)
Expand All @@ -5811,6 +5823,7 @@ def forward(
compress_ratio: int = 1,
max_seq_len: Optional[int] = None,
cluster_size: Optional[int] = None,
order_row: Optional[torch.Tensor] = None,
) -> None:
cute_dtype = _TORCH_TO_CUTLASS_DTYPE[logits.dtype]
num_rows = logits.shape[0]
Expand Down Expand Up @@ -5947,6 +5960,19 @@ def forward(
else:
min_blocks_per_mp = 1

# seqlen_sorted = (order_row is not None). When True the
# kernel resolves the owning row via order_row[req] * next_n
# + nn so longer rows hit earlier waves (LJF). order_row is
# request-level (shape == seq_lens.shape).
seqlen_sorted = order_row is not None
if seqlen_sorted:
assert (
order_row.dtype == torch.int32 and order_row.is_cuda
and order_row.shape == seq_lens.shape
), ("order_row must be int32, CUDA, shape == seq_lens.shape "
f"(={tuple(seq_lens.shape)}); got dtype={order_row.dtype} "
f"shape={tuple(order_row.shape)}")

cls._compile(
cute_dtype,
top_k,
Expand All @@ -5961,18 +5987,23 @@ def forward(
compress_ratio,
return_output_values,
cluster_size,
seqlen_sorted,
)
key = (cute_dtype, top_k, next_n, enable_unroll_4,
enable_phase3_unroll, use_constant_hint, min_blocks_per_mp,
use_256bit_load, num_threads_per_block,
enable_warp_parallel_reduce, compress_ratio,
return_output_values, cluster_size)
return_output_values, cluster_size, seqlen_sorted)
# TVM FFI: pass raw torch tensors directly, env stream picked
# up automatically (no from_dlpack, no stream argument).
# ``output_values=None`` matches the kernel's compile-time
# ``return_output_values=False`` constexpr — no writes for top-k values.
# ``out_values = None`` — return_output_values=False, kernel
# skips STG.value.
# ``order_row`` — None when seqlen_sorted=False (no
# LJF indirection), else the
# caller-provided request-level
# dispatch order.
cls.kernel_cache[key](logits, pre_idx, seq_lens, None,
output_indices)
output_indices, order_row)

@torch.library.custom_op("trtllm::cute_dsl_gvr_topk_decode",
mutates_args=("output_indices", ),
Expand All @@ -5987,6 +6018,7 @@ def cute_dsl_gvr_topk_decode(
compress_ratio: int = 1,
max_seq_len: Optional[int] = None,
cluster_size: Optional[int] = None,
order_row: Optional[torch.Tensor] = None,
) -> None:
"""CuTe DSL GVR (Guess-Verify-Refine) Top-K decode for Blackwell.

Expand All @@ -6013,6 +6045,20 @@ def cute_dsl_gvr_topk_decode(
processing one row via DSMEM + cluster sync). ``None``
(default) delegates to the wrapper's auto-dispatch heuristic
based on (N, BS); pass an explicit int to pin the choice.
order_row: Optional request-level LJF (longest-job-first)
dispatch order. When provided, must be ``int32`` on CUDA
with ``shape == seq_lens.shape``; ``order_row[i]`` is the
original request_id of the i-th-priority request. The
kernel then resolves the owning row per CTA as
``order_row[cluster_id // next_n] * next_n +
(cluster_id % next_n)`` so longer rows hit earlier waves
— mitigates wave-tail imbalance when ``num_rows >
num_sms / cluster_size``. ``None`` (default) keeps the
legacy static mapping and the kernel const_expr elides
the indirection (legacy SASS unchanged). Typically built
once per decode step via :func:`gvr_topk_sort_prepare`
and reused across layers (``seq_lens`` is layer-
invariant).
"""
if not is_sm_100f():
raise ValueError(
Expand Down Expand Up @@ -6054,6 +6100,7 @@ def cute_dsl_gvr_topk_decode(
compress_ratio=compress_ratio,
max_seq_len=max_seq_len,
cluster_size=cluster_size,
order_row=order_row,
)

@torch.library.register_fake("trtllm::cute_dsl_gvr_topk_decode")
Expand All @@ -6067,6 +6114,7 @@ def _(
compress_ratio: int = 1,
max_seq_len: Optional[int] = None,
cluster_size: Optional[int] = None,
order_row: Optional[torch.Tensor] = None,
) -> None:
return None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def __init__(
cluster_size: int = 1,
enable_smem_cache: bool = False,
smem_cache_elems: int = 32768,
seqlen_sorted: bool = False,
):
# cluster_size: number of CTAs cooperating per row. 1 = single-CTA
# path; 2/4 = thread-block cluster with DSMEM aggregation. Capped at
Expand All @@ -239,6 +240,16 @@ def __init__(
f"cluster_size must be in [1, 16] (B200 GPC limit); got {cluster_size}"
)
self.cluster_size = cluster_size
# When True, the kernel resolves the owning row per CTA via a
# caller-provided ``order_row`` indirection — an LJF host-side
# dispatch order so longer rows hit earlier waves. ``order_row``
# is REQUEST-level: int32[batch_size = num_rows / next_n],
# typically a descending argsort of seq_lens. The kernel
# expands to row level as ``order_row[req] * next_n + nn`` so a
# request's ``next_n`` rows stay contiguous. Compatible with
# cluster_size > 1: all cs CTAs in a cluster see the same
# cluster_id (= bidx // cluster_size), hence the same row.
self.seqlen_sorted = seqlen_sorted
# SMEM slice cache (optional): pre-stage each CTA's slice into SMEM
# once between Phase 1 and Phase 2, so Phase 2/3's GE-count scans
# read LDS instead of re-streaming GMEM. Requires
Expand Down Expand Up @@ -1722,25 +1733,51 @@ def gvr_topk_kernel(
seq_lens: cute.Tensor, # [numRows / next_n] int32
output_values: cute.Tensor, # [numRows, top_k] dtype
output_indices: cute.Tensor, # [numRows, top_k] int32
order_row: cute.Tensor, # [batch_size] int32 (or None when seqlen_sorted=False)
):
"""Thin entry: bidx → (row_idx, cta_in_cluster), then run_one_row.

grid = (num_rows * cluster_size,); 1 row per thread-block cluster.
cluster_id == row index, cta_in_cluster ∈ [0, cluster_size). CTA r
scans row[r * N / cs : (r+1) * N / cs] in Phase 2, so the per-row
GE-count scales as 1 / cs. At cluster_size == 1 this collapses to
one CTA per row scanning the whole row.
"""Thin entry: bidx → row_idx → run_one_row.

grid = (num_rows * cluster_size,) where num_rows = batch_size *
next_n. cluster_id = bidx // cluster_size, cta_in_cluster ∈
[0, cluster_size). CTA r scans row[r * N / cs : (r+1) * N / cs]
in Phase 2, so the per-row GE-count scales as 1 / cs. At
cluster_size == 1 this collapses to one CTA per row scanning
the whole row.

When ``self.seqlen_sorted`` is True, the LJF dispatch order
operates at REQUEST granularity (``order_row`` has length
batch_size = num_rows / next_n). The owning row is resolved as
``order_row[cluster_id // next_n] * next_n + cluster_id % next_n``
so the ``next_n`` rows of one request stay contiguous in
dispatch order. All ``cluster_size`` CTAs within a cluster see
the same ``cluster_id`` and therefore the same row, preserving
cluster-sync semantics.

Body is extracted into :meth:`run_one_row` so other entries (e.g.
the LB load-balance variant) can resolve ``row_idx`` differently
from the static ``bidx // cluster_size`` mapping used here.
from the mappings used here.
"""
bidx, _, _ = cute.arch.block_idx()
cluster_size = cutlass.const_expr(self.cluster_size)
seqlen_sorted = cutlass.const_expr(self.seqlen_sorted)
next_n = cutlass.const_expr(self.next_n)
if cutlass.const_expr(cluster_size > 1):
row_idx = bidx // cluster_size
cluster_id = bidx // cluster_size
else:
cluster_id = bidx
if cutlass.const_expr(seqlen_sorted):
# order_row is request-level (batch_size); expand to row-level
# via req_id * next_n + nn so a request's next_n rows stay
# contiguous in dispatch order (mirrors the LB main entry).
if cutlass.const_expr(next_n == 1):
row_idx = order_row[cluster_id]
else:
req_offset = cluster_id // cutlass.Int32(next_n)
nn = cluster_id % cutlass.Int32(next_n)
req_id = order_row[req_offset]
row_idx = req_id * cutlass.Int32(next_n) + nn
else:
row_idx = bidx
row_idx = cluster_id
self.run_one_row(
row_idx,
input_data,
Expand Down Expand Up @@ -2168,6 +2205,7 @@ def __call__(
seq_lens: cute.Tensor,
output_values: cute.Tensor, # or None.
output_indices: cute.Tensor,
order_row: cute.Tensor, # or None when seqlen_sorted=False
stream,
):
num_rows = input_data.shape[0]
Expand All @@ -2183,6 +2221,7 @@ def __call__(
seq_lens,
output_values,
output_indices,
order_row,
).launch(
grid=(total_ctas, 1, 1),
block=(self.num_threads, 1, 1),
Expand Down
Loading
Loading