Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

672 lines
24 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2025, Tri Dao.
# SPDX-FileCopyrightText: Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This file contains code derived from the Quack library:
# https://github.com/Dao-AILab/quack
# Originally integrated into TensorRT-LLM:
# https://github.com/NVIDIA/TensorRT-LLM/blob/main/tensorrt_llm/_torch/cute_dsl_kernels/argmax.py
#
# Argmax kernel using CuTe DSL.
#
# This module pulls in ``cuda.bindings.driver`` and ``cutlass``, which are
# NVIDIA-only Python packages. **Do not import this module directly from the
# runtime.** Use ``tokenspeed_kernel.ops.sampling.cute_dsl`` instead — that
# wrapper gates on ``current_platform().is_nvidia`` before importing this file
# and falls back to ``torch.argmax`` on every other platform.
from typing import Optional, Tuple, Type
import cuda.bindings.driver as cuda
import cutlass
import cutlass.cute as cute
import torch
from cutlass._mlir.dialects import llvm
from cutlass.cute.arch.nvvm_wrappers import FULL_MASK
from cutlass.cute.runtime import from_dlpack
from cutlass.cute.typing import Float32, Int, Int32
from cutlass.cutlass_dsl import T, dsl_user_op
# ============================================================================
# Torch to CuTE dtype mapping
# ============================================================================
torch2cute_dtype_map = {
torch.float16: cutlass.Float16,
torch.bfloat16: cutlass.BFloat16,
torch.float32: cutlass.Float32,
}
# ============================================================================
# CUDA Graph compatibility wrapper
# ============================================================================
class CUDAGraphCompatibleWrapper:
"""Wrapper to make tensors compatible with CUDA graph capture for DLPack export."""
def __init__(self, tensor):
self._tensor = tensor
def __dlpack__(self, stream=None):
return self._tensor.__dlpack__(stream=-1)
def __dlpack_device__(self):
return self._tensor.__dlpack_device__()
# ============================================================================
# Utility functions from quack/utils.py
# ============================================================================
@dsl_user_op
def elem_pointer(
x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None
) -> cute.Pointer:
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
@dsl_user_op
def set_block_rank(
smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: cute.Int32, *, loc=None, ip=None
) -> cutlass.Int32:
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
return cutlass.Int32(
llvm.inline_asm(
T.i32(),
[smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
"mapa.shared::cluster.u32 $0, $1, $2;",
"=r,r,r",
has_side_effects=False,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
@dsl_user_op
def store_shared_remote(
val: float | Float32 | cutlass.Int64,
smem_ptr: cute.Pointer,
mbar_ptr: cute.Pointer,
peer_cta_rank_in_cluster: cute.typing.Int,
*,
loc=None,
ip=None,
) -> None:
remote_smem_ptr_i32 = set_block_rank(
smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
).ir_value()
remote_mbar_ptr_i32 = set_block_rank(
mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
).ir_value()
if cutlass.const_expr(isinstance(val, float)):
val = Float32(val)
assert isinstance(val, (Float32, Int32, cutlass.Int64))
suffix = {Float32: "f32", Int32: "s32", cutlass.Int64: "s64"}[type(val)]
constraint = {Float32: "f", Int32: "r", cutlass.Int64: "l"}[type(val)]
llvm.inline_asm(
None,
[remote_smem_ptr_i32, val.ir_value(loc=loc, ip=ip), remote_mbar_ptr_i32],
f"st.async.shared::cluster.mbarrier::complete_tx::bytes.{suffix} [$0], $1, [$2];",
f"r,{constraint},r",
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
@cute.jit
def predicate_k(tAcA: cute.Tensor, limit: cutlass.Int32) -> cute.Tensor:
tApA = cute.make_rmem_tensor(
cute.make_layout(
(
cute.size(tAcA, mode=[0, 1]),
cute.size(tAcA, mode=[1]),
cute.size(tAcA, mode=[2]),
),
stride=(cute.size(tAcA, mode=[2]), 0, 1),
),
cutlass.Boolean,
)
for rest_v in cutlass.range_constexpr(tApA.shape[0]):
for rest_k in cutlass.range_constexpr(tApA.shape[2]):
tApA[rest_v, 0, rest_k] = cute.elem_less(
tAcA[(0, rest_v), 0, rest_k][1], limit
)
return tApA
@cute.jit
def fill_oob(
tXsX: cute.Tensor, tXpX: Optional[cute.Tensor], fill_value: cute.Numeric
) -> None:
tXrX_fill = cute.make_fragment_like(tXsX[(None, 0), None, 0])
tXrX_fill.fill(fill_value)
for rest_v in cutlass.range_constexpr(tXsX.shape[0][1]):
for rest_k in cutlass.range_constexpr(tXsX.shape[2]):
if cutlass.const_expr(tXpX is not None):
if not tXpX[rest_v, 0, rest_k]:
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
else:
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
@dsl_user_op
def domain_offset_i64(
coord: cute.Coord, tensor: cute.Tensor, *, loc=None, ip=None
) -> cute.Tensor:
flat_coord_i64 = tuple(cutlass.Int64(c) for c in cute.flatten(coord))
flat_stride = cute.flatten_to_tuple(tensor.stride)
offset = sum(c * s for c, s in zip(flat_coord_i64, flat_stride))
new_ptr = cute.make_ptr(
tensor.element_type,
tensor.iterator.toint() + offset * tensor.element_type.width // 8,
tensor.memspace,
assumed_align=tensor.iterator.max_alignment,
)
return cute.make_tensor(new_ptr, tensor.layout)
# ============================================================================
# Inline PTX for redux.sync operations
# ============================================================================
@dsl_user_op
def ptx_redux_sync_max_f32(
value: Float32, mask: Int = FULL_MASK, *, loc=None, ip=None
) -> Float32:
return Float32(
llvm.inline_asm(
T.f32(),
[
Float32(value).ir_value(loc=loc, ip=ip),
Int32(mask).ir_value(loc=loc, ip=ip),
],
"""redux.sync.max.f32 $0, $1, $2;""",
"=f,f,i",
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
@dsl_user_op
def ptx_redux_sync_min_u32(
value: Int32, mask: Int = FULL_MASK, *, loc=None, ip=None
) -> Int32:
return Int32(
llvm.inline_asm(
T.i32(),
[
Int32(value).ir_value(loc=loc, ip=ip),
Int32(mask).ir_value(loc=loc, ip=ip),
],
"""redux.sync.min.u32 $0, $1, $2;""",
"=r,r,i",
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
@dsl_user_op
def ptx_select_argmax_candidate(
current_max: Float32, warp_max: Float32, current_argmax: Int32, *, loc=None, ip=None
) -> Int32:
return Int32(
llvm.inline_asm(
T.i32(),
[
Float32(current_max).ir_value(loc=loc, ip=ip),
Float32(warp_max).ir_value(loc=loc, ip=ip),
Int32(current_argmax).ir_value(loc=loc, ip=ip),
],
"""{
.reg .pred p;
setp.eq.f32 p, $1, $2;
selp.s32 $0, $3, 0xffffffff, p;
}""",
"=r,f,f,r",
has_side_effects=False,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
@cute.jit
def warp_argmax_redux(current_max: Float32, current_argmax: Int32):
"""Redux-based warp argmax - only works on sm_100f (Blackwell)."""
warp_max = ptx_redux_sync_max_f32(current_max)
candidate_idx = ptx_select_argmax_candidate(current_max, warp_max, current_argmax)
winning_idx = ptx_redux_sync_min_u32(candidate_idx)
return warp_max, winning_idx
@cute.jit
def warp_reduce_argmax(current_max: Float32, current_argmax: Int32):
"""Shuffle-based warp argmax - works on all architectures (Hopper+).
Ties are broken to the lowest index, matching ``torch.argmax``. The redux
path (``warp_argmax_redux``) already does this via ``redux.sync.min.u32``;
this path used to keep whichever side the strict ``>`` happened to retain,
so the tie-break-to-lowest invariant could fail on Hopper. The extra
``==`` arm makes the two paths agree on Hopper and Blackwell.
"""
warp_max = current_max
warp_argmax = current_argmax
# Butterfly shuffle reduction.
for i in cutlass.range_constexpr(int(5)): # log2(32) = 5 iterations
other_max = cute.arch.shuffle_sync_bfly(warp_max, offset=1 << i)
other_argmax = cute.arch.shuffle_sync_bfly(warp_argmax, offset=1 << i)
if other_max > warp_max:
warp_max = other_max
warp_argmax = other_argmax
elif other_max == warp_max:
if other_argmax < warp_argmax:
warp_argmax = other_argmax
return warp_max, warp_argmax
# ============================================================================
# Reduction Base class
# ============================================================================
class ReductionBase:
def __init__(
self,
dtype: Type[cutlass.Numeric],
N: int,
stage: int,
reduction_dtype=cutlass.Float32,
):
self.dtype = dtype
self.N = N
self.stage = stage
self.reduction_dtype = reduction_dtype
def _calculate_threads_per_row(self):
raise NotImplementedError()
def _set_cluster_n(self):
self.cluster_n = 1
def _get_num_threads(self):
return 128 if self.N <= 16384 else 256
def _get_tv_layout(self, num_copy_bits=128):
vecsize = num_copy_bits // self.dtype.width
num_threads = self._get_num_threads()
threads_per_row = self._calculate_threads_per_row()
num_blocks_N = cute.ceil_div(
self.N // vecsize, threads_per_row * self.cluster_n
)
cols_per_block = num_threads // threads_per_row
tiler_mn = (cols_per_block, vecsize * num_blocks_N * threads_per_row)
tv_layout = cute.make_layout(
((threads_per_row, cols_per_block), (vecsize, num_blocks_N)),
stride=(
(vecsize * cols_per_block, 1),
(cols_per_block, cols_per_block * vecsize * threads_per_row),
),
)
return tiler_mn, tv_layout
def _smem_size_in_bytes(self, tiler_mn, num_warps):
return (
cute.size_in_bytes(self.dtype, cute.make_layout(tiler_mn))
+ self.stage
* num_warps
* self.cluster_n
* (self.reduction_dtype.width // 8)
+ self.stage * (cutlass.Int64.width // 8)
)
def _get_reduction_buffer_layout(self, tv_layout: cute.Layout, cluster_n: int):
num_warps = cute.size(tv_layout, mode=[0]) // cute.arch.WARP_SIZE
warps_per_row = max(tv_layout.shape[0][0] // cute.arch.WARP_SIZE, 1)
return cute.make_ordered_layout(
(num_warps // warps_per_row, (warps_per_row, cluster_n), self.stage),
order=(1, 0, 2),
)
def _allocate_reduction_buffer_and_mbar(
self, smem: cutlass.utils.SmemAllocator, tv_layout: cute.Layout
) -> Tuple[cute.Tensor, Optional[cute.Pointer]]:
reduction_buffer = smem.allocate_tensor(
self.reduction_dtype,
self._get_reduction_buffer_layout(tv_layout, self.cluster_n),
byte_alignment=4,
)
if cutlass.const_expr(self.cluster_n > 1):
mbar_ptr = smem.allocate_array(cutlass.Int64, num_elems=self.stage)
else:
mbar_ptr = None
return reduction_buffer, mbar_ptr
@cute.jit
def _initialize_cluster(
self, tidx: cutlass.Int32, mbar_ptr: cute.Pointer, num_warps: int
):
if cutlass.const_expr(self.cluster_n > 1):
if tidx < self.stage:
cute.arch.mbarrier_init(mbar_ptr + tidx, 1)
cute.arch.mbarrier_init_fence()
cute.arch.cluster_arrive_relaxed()
# ============================================================================
# Argmax Kernel class
# ============================================================================
class ArgmaxKernel(ReductionBase):
def __init__(self, dtype: Type[cutlass.Numeric], N: int, use_redux: bool = False):
super().__init__(dtype, N, stage=1, reduction_dtype=cutlass.Float32)
# use_redux=True for Blackwell (sm_100f), False for Hopper (sm_90)
self.use_redux = use_redux
def _calculate_threads_per_row(self):
N = self.N
return (
8
if N <= 64
else (
16
if N <= 128
else (
32
if N <= 3072
else (64 if N <= 6144 else (128 if N <= 16384 else 256))
)
)
)
def _set_cluster_n(self):
N = self.N
if cutlass.const_expr(self.dtype.width == 16):
self.cluster_n = (
1
if N <= 16 * 1024
else (
2
if N <= 32 * 1024
else (4 if N <= 64 * 1024 else (8 if N <= 128 * 1024 else 16))
)
)
else:
self.cluster_n = (
1
if N <= 32 * 1024
else (
2
if N <= 64 * 1024
else (4 if N <= 128 * 1024 else (8 if N <= 256 * 1024 else 16))
)
)
def _get_reduction_buffer_layout(self, tv_layout: cute.Layout, cluster_n: int):
num_warps = cute.size(tv_layout, mode=[0]) // cute.arch.WARP_SIZE
warps_per_row = max(tv_layout.shape[0][0] // cute.arch.WARP_SIZE, 1)
return cute.make_ordered_layout(
(num_warps // warps_per_row, (warps_per_row, cluster_n), self.stage, 2),
order=(1, 0, 2, 3),
)
def _smem_size_in_bytes(self, tiler_mn, num_warps):
return (
cute.size_in_bytes(self.dtype, cute.make_layout(tiler_mn))
+ 2
* self.stage
* num_warps
* self.cluster_n
* (self.reduction_dtype.width // 8)
+ self.stage * (cutlass.Int64.width // 8)
)
@cute.jit
def __call__(
self,
mX: cute.Tensor,
mO_max: cute.Tensor,
mO_idx: cute.Tensor,
stream: cuda.CUstream,
):
self._set_cluster_n()
tiler_mn, tv_layout = self._get_tv_layout()
num_threads = cute.size(tv_layout, mode=[0])
num_warps = num_threads // cute.arch.WARP_SIZE
self.kernel(mX, mO_max, mO_idx, tv_layout, tiler_mn).launch(
grid=[cute.ceil_div(mX.shape[0], tiler_mn[0]), self.cluster_n, 1],
block=[num_threads, 1, 1],
cluster=(
[1, self.cluster_n, 1]
if cutlass.const_expr(self.cluster_n > 1)
else None
),
smem=self._smem_size_in_bytes(tiler_mn, num_warps),
stream=stream,
)
@cute.kernel
def kernel(
self,
mX: cute.Tensor,
mO_max: cute.Tensor,
mO_idx: cute.Tensor,
tv_layout: cute.Layout,
tiler_mn: cute.Shape,
):
tidx, _, _ = cute.arch.thread_idx()
bidx, bidy, bidz = cute.arch.block_idx()
if cutlass.const_expr(self.cluster_n > 1):
cluster_y = cute.arch.block_idx()[1]
else:
cluster_y = cutlass.const_expr(0)
shape = mX.shape
idX = cute.make_identity_tensor(shape)
mX = domain_offset_i64((bidx * tiler_mn[0], 0), mX)
gX = cute.local_tile(mX, tiler_mn, (0, cluster_y))
# Each output is 1D (M,); only a row-axis offset is needed.
mO_max = domain_offset_i64((bidx * tiler_mn[0],), mO_max)
mO_idx = domain_offset_i64((bidx * tiler_mn[0],), mO_idx)
cX = cute.local_tile(idX, tiler_mn, (bidx, cluster_y))
smem = cutlass.utils.SmemAllocator()
sX = smem.allocate_tensor(
mX.element_type,
cute.make_ordered_layout(tiler_mn, order=(1, 0)),
byte_alignment=16,
)
reduction_buffer, mbar_ptr = self._allocate_reduction_buffer_and_mbar(
smem, tv_layout
)
copy_atom_load_X = cute.make_copy_atom(
cute.nvgpu.cpasync.CopyG2SOp(), mX.element_type, num_bits_per_copy=128
)
thr_copy_X = cute.make_tiled_copy(
copy_atom_load_X, tv_layout, tiler_mn
).get_slice(tidx)
tXgX = thr_copy_X.partition_S(gX)
tXsX = thr_copy_X.partition_D(sX)
tXcX = thr_copy_X.partition_S(cX)[(0, None), None, None]
tvlayout_cX = cute.composition(cX, tv_layout)
thr_coord = (tidx, (None, None))
thr_cX = tvlayout_cX[thr_coord]
tXrX = cute.make_fragment_like(tXgX)
num_warps = cute.size(tv_layout, mode=[0]) // cute.arch.WARP_SIZE
self._initialize_cluster(tidx, mbar_ptr, num_warps)
is_even_N = cutlass.const_expr(shape[1] == tiler_mn[1] * self.cluster_n)
tXpX = (
predicate_k(thr_copy_X.partition_S(cX), limit=shape[1])
if not is_even_N
else None
)
if tXcX[0][0] < shape[0]:
cute.copy(copy_atom_load_X, tXgX, tXsX, pred=tXpX)
cute.arch.cp_async_commit_group()
cute.arch.cp_async_wait_group(0)
if cutlass.const_expr(not is_even_N):
fill_oob(tXsX, tXpX, -tXsX.element_type.inf)
cute.autovec_copy(tXsX, tXrX)
x = tXrX.load().to(cute.Float32)
current_max = -tXsX.element_type.inf
current_argmax = Int32(0xFFFFFFFF)
for i in cutlass.range_constexpr(thr_cX.shape[0]):
for j in cutlass.range_constexpr(thr_cX.shape[1]):
col_idx = thr_cX[i, j][1]
linear_idx = i + j * thr_cX.shape[0]
element_value1 = x[linear_idx]
if element_value1 > current_max:
current_max = element_value1
current_argmax = Int32(col_idx)
lane_idx, warp_idx = cute.arch.lane_idx(), cute.arch.warp_idx()
if cutlass.const_expr(self.use_redux):
warp_max, warp_argmax = warp_argmax_redux(current_max, current_argmax)
else:
warp_max, warp_argmax = warp_reduce_argmax(current_max, current_argmax)
if cutlass.const_expr(self.cluster_n == 1):
warps_per_row = cute.size(reduction_buffer.shape[1])
row_idx, col_idx = warp_idx // warps_per_row, warp_idx % warps_per_row
if lane_idx == 0:
reduction_buffer[row_idx, col_idx, 0, 0] = warp_max
reduction_buffer[row_idx, col_idx, 0, 1] = warp_argmax.to(
cutlass.Float32
)
cute.arch.barrier()
block_reduce_max = -tXsX.element_type.inf
block_reduce_argmax = Int32(0xFFFFFFFF)
if lane_idx < warps_per_row:
block_reduce_max = reduction_buffer[row_idx, lane_idx, 0, 0]
block_reduce_argmax = reduction_buffer[row_idx, lane_idx, 0, 1].to(
cutlass.Int32
)
if cutlass.const_expr(self.use_redux):
warp_max, warp_argmax = warp_argmax_redux(
block_reduce_max, block_reduce_argmax
)
else:
warp_max, warp_argmax = warp_reduce_argmax(
block_reduce_max, block_reduce_argmax
)
else:
cute.arch.cluster_wait()
warps_per_row, cluster_n = reduction_buffer.shape[1]
cta_rank_in_cluster = cute.arch.block_idx_in_cluster()
rows_per_block, (warps_per_row, cluster_n), _, _ = reduction_buffer.shape
row_idx, col_idx = warp_idx // warps_per_row, warp_idx % warps_per_row
if warp_idx == 0:
with cute.arch.elect_one():
num_warps = rows_per_block * warps_per_row
cute.arch.mbarrier_arrive_and_expect_tx(
mbar_ptr,
num_warps
* cluster_n
* 2
* reduction_buffer.element_type.width
// 8,
)
if lane_idx < cluster_n:
store_shared_remote(
warp_max,
elem_pointer(
reduction_buffer,
(row_idx, (col_idx, cta_rank_in_cluster), 0, 0),
),
mbar_ptr,
peer_cta_rank_in_cluster=lane_idx,
)
store_shared_remote(
warp_argmax.to(cutlass.Float32),
elem_pointer(
reduction_buffer,
(row_idx, (col_idx, cta_rank_in_cluster), 0, 1),
),
mbar_ptr,
peer_cta_rank_in_cluster=lane_idx,
)
cute.arch.mbarrier_wait(mbar_ptr, phase=0)
block_reduce_val = -tXsX.element_type.inf
block_reduce_argmax = Int32(0xFFFFFFFF)
num_iter = cute.ceil_div(warps_per_row * cluster_n, cute.arch.WARP_SIZE)
for i in cutlass.range_constexpr(num_iter):
idx = lane_idx + i * cute.arch.WARP_SIZE
if idx < cute.size(reduction_buffer, mode=[1]):
element_max = reduction_buffer[row_idx, idx, 0, 0]
element_argmax = reduction_buffer[row_idx, idx, 0, 1].to(
cutlass.Int32
)
# Tie-break to the lowest index so the cluster-side
# reduction agrees with torch.argmax / the redux path.
if element_max > block_reduce_val:
block_reduce_val = element_max
block_reduce_argmax = element_argmax
elif element_max == block_reduce_val:
if element_argmax < block_reduce_argmax:
block_reduce_argmax = element_argmax
if cutlass.const_expr(self.use_redux):
warp_max, warp_argmax = warp_argmax_redux(
block_reduce_val, block_reduce_argmax
)
else:
warp_max, warp_argmax = warp_reduce_argmax(
block_reduce_val, block_reduce_argmax
)
row_idx = tXcX[0][0]
warps_per_row = tv_layout.shape[0][0] // cute.arch.WARP_SIZE
local_row_idx = row_idx - (bidx * tiler_mn[0])
first_warp_for_row = local_row_idx * warps_per_row
first_thread_for_row = first_warp_for_row * cute.arch.WARP_SIZE
if (
tidx == first_thread_for_row
and row_idx < shape[0]
and local_row_idx >= 0
and local_row_idx < tiler_mn[0]
and (self.cluster_n == 1 or bidy == 0)
):
# A row whose elements never beat -inf (all-NaN / all -inf) leaves
# the argmax at its 0xFFFFFFFF sentinel; emit the in-range index 0.
if warp_argmax == Int32(0xFFFFFFFF):
warp_argmax = Int32(0)
mO_max[local_row_idx] = warp_max.to(mO_max.element_type)
mO_idx[local_row_idx] = warp_argmax.to(mO_idx.element_type)