- Replace Python lists with individual scalar variables (s0..s5, i0..i5, a0..a5) - Replace min-heap sift-down with fully unrolled sorted insertion (descending order, no dynamic indexing, no while loops) - Replace raw SMEM pointer arithmetic with CuTeDSL SMEM tensors (s_merge_s, s_merge_i, s_merge_a) - Replace cute.where with cute.math.fmax - Fix expert index calculation: col + tile_n_offset + subtile_idx * epi_n - Top-6 accumulates across all N-tiles (for E=384 with 3 tiles of 128) - Add iter_acc_early_release for overlapping accumulator - Rewrite test to compare fused kernel vs 2-kernel reference path - Remove stale memory doc
925 lines
44 KiB
Python
925 lines
44 KiB
Python
"""DSV4 NVFP4 Fused Router Kernel — Block-scaled GEMM + Router Epilogue.
|
|
|
|
Single-kernel path: NVFP4 block-scaled GEMM (A: activation FP4, B: gate weight FP4)
|
|
with fused router epilogue (sqrt(softplus) + e_bias + top-k + renorm).
|
|
|
|
PRODUCTION KERNEL. No intermediate GMEM buffer. No BF16 fallback.
|
|
The GEMM accumulates logits in TMEM, then the epilogue warps process them directly:
|
|
1. TMEM -> registers (via paired t2r atom from CUTLASS epilogue helpers)
|
|
2. For each logit: sqrt(softplus(logit)) + e_bias -> score; track top-k via min-heap
|
|
3. After all subtiles: sort, renormalize, write (topk_weights, topk_ids) to GMEM
|
|
|
|
Warp specialization (6 warps, no scheduler for dense GEMM):
|
|
Warps 0-3: Epilogue (TMEM -> register -> router logic -> GMEM)
|
|
Warp 4: MMA (tcgen05.mma.block_scale with SFA/SFB in TMEM)
|
|
Warp 5: TMA load (A, B, SFA, SFB from GMEM -> SMEM)
|
|
|
|
Pipeline structure (2 pipelines):
|
|
AB pipeline: TMA (producer) -> MMA (consumer) [PipelineTmaUmma]
|
|
Acc pipeline: MMA (producer) -> Epilogue (consumer) [PipelineUmmaAsync]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from typing import Tuple
|
|
import math
|
|
|
|
import cuda.bindings.driver as cuda
|
|
import torch
|
|
|
|
import cutlass
|
|
import cutlass.cute as cute
|
|
from cutlass.cute.nvgpu import cpasync, tcgen05
|
|
import cutlass.utils as utils
|
|
import cutlass.pipeline as pipeline
|
|
import cutlass.utils.blackwell_helpers as sm100_utils
|
|
import cutlass.utils.blockscaled_layout as blockscaled_utils
|
|
from cutlass.utils.gemm.sm100 import (
|
|
epilogue_tmem_copy_and_partition,
|
|
epilogue_smem_copy_and_partition,
|
|
transform_partitioned_tensor_layout,
|
|
)
|
|
|
|
|
|
class Nvfp4FusedRouterKernel:
|
|
|
|
def __init__(
|
|
self,
|
|
sf_vec_size: int = 16,
|
|
mma_tiler_mnk: Tuple[int, int, int] = (128, 128, 64),
|
|
cluster_shape_mnk: Tuple[int, int, int] = (1, 1, 1),
|
|
top_k: int = 6,
|
|
):
|
|
self.sf_vec_size = sf_vec_size
|
|
self.mma_tiler_mnk = mma_tiler_mnk
|
|
self.cluster_shape_mn = (cluster_shape_mnk[0], cluster_shape_mnk[1])
|
|
self.top_k = top_k
|
|
self.use_2cta_instrs = mma_tiler_mnk[0] == 256
|
|
self.cta_group = tcgen05.CtaGroup.TWO if self.use_2cta_instrs else tcgen05.CtaGroup.ONE
|
|
|
|
self.epilogue_warp_id = (0, 1, 2, 3)
|
|
self.mma_warp_id = 4
|
|
self.tma_warp_id = 5
|
|
self.threads_per_warp = 32
|
|
self.threads_per_cta = self.threads_per_warp * 6
|
|
|
|
self.cta_sync_bar_id = 1
|
|
self.epilogue_sync_bar_id = 2
|
|
self.tmem_alloc_sync_bar_id = 3
|
|
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
|
self.occupancy = 1
|
|
self.buffer_align_bytes = 1024
|
|
|
|
def _create_tiled_mma(self, a_dtype, a_major_mode, b_major_mode, sf_dtype):
|
|
return sm100_utils.make_blockscaled_trivial_tiled_mma(
|
|
a_dtype, a_major_mode, b_major_mode, sf_dtype,
|
|
self.sf_vec_size, self.cta_group,
|
|
(self.mma_tiler_mnk[0], self.mma_tiler_mnk[1]),
|
|
)
|
|
|
|
def _create_tiled_mma_sfb(self, a_dtype, a_major_mode, b_major_mode, sf_dtype):
|
|
mma_inst_shape_mn_sfb = (
|
|
self.mma_tiler_mnk[0] // (2 if self.use_2cta_instrs else 1),
|
|
cute.round_up(self.mma_tiler_mnk[1], 128),
|
|
)
|
|
return sm100_utils.make_blockscaled_trivial_tiled_mma(
|
|
a_dtype, a_major_mode, b_major_mode, sf_dtype,
|
|
self.sf_vec_size, tcgen05.CtaGroup.ONE, mma_inst_shape_mn_sfb,
|
|
)
|
|
|
|
def _setup_attributes(self, tiled_mma, tiled_mma_sfb, a_dtype, b_dtype, sf_dtype):
|
|
self.mma_inst_shape_mn = (self.mma_tiler_mnk[0], self.mma_tiler_mnk[1])
|
|
self.mma_inst_shape_mn_sfb = (
|
|
self.mma_inst_shape_mn[0] // (2 if self.use_2cta_instrs else 1),
|
|
cute.round_up(self.mma_inst_shape_mn[1], 128),
|
|
)
|
|
|
|
mma_inst_shape_k = cute.size(tiled_mma.shape_mnk, mode=[2])
|
|
mma_inst_tile_k = self.mma_tiler_mnk[2] // mma_inst_shape_k
|
|
|
|
self.mma_tiler = (
|
|
self.mma_inst_shape_mn[0],
|
|
self.mma_inst_shape_mn[1],
|
|
self.mma_tiler_mnk[2],
|
|
)
|
|
self.mma_tiler_sfb = (
|
|
self.mma_inst_shape_mn_sfb[0],
|
|
self.mma_inst_shape_mn_sfb[1],
|
|
self.mma_tiler_mnk[2],
|
|
)
|
|
self.cta_tile_shape_mnk = (
|
|
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
|
self.mma_tiler[1], self.mma_tiler[2],
|
|
)
|
|
self.cta_tile_shape_mnk_sfb = (
|
|
self.mma_tiler_sfb[0] // cute.size(tiled_mma.thr_id.shape),
|
|
self.mma_tiler_sfb[1], self.mma_tiler_sfb[2],
|
|
)
|
|
|
|
self.cluster_layout_vmnk = cute.tiled_divide(
|
|
cute.make_layout((*self.cluster_shape_mn, 1)),
|
|
(tiled_mma.thr_id.shape,))
|
|
self.cluster_layout_sfb_vmnk = cute.tiled_divide(
|
|
cute.make_layout((*self.cluster_shape_mn, 1)),
|
|
(tiled_mma_sfb.thr_id.shape,))
|
|
|
|
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
|
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
|
self.num_mcast_ctas_sfb = cute.size(self.cluster_layout_sfb_vmnk.shape[1])
|
|
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
|
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
|
self.is_sfb_mcast = self.num_mcast_ctas_sfb > 1
|
|
|
|
self.epi_tile = (
|
|
cute.make_layout(self.cta_tile_shape_mnk[0]),
|
|
cute.make_layout(self.cta_tile_shape_mnk[1]),
|
|
)
|
|
self.epi_tile_n = cute.size(self.epi_tile[1])
|
|
|
|
self.overlapping_accum = self.cta_tile_shape_mnk[1] == 256
|
|
self.num_acc_stage = 1 if self.overlapping_accum else 2
|
|
self.num_ab_stage = 2
|
|
|
|
sf_atom_mn = 32
|
|
self.num_sfa_tmem_cols = (self.cta_tile_shape_mnk[0] // sf_atom_mn) * mma_inst_tile_k
|
|
self.num_sfb_tmem_cols = (self.cta_tile_shape_mnk_sfb[1] // sf_atom_mn) * mma_inst_tile_k
|
|
self.num_sf_tmem_cols = self.num_sfa_tmem_cols + self.num_sfb_tmem_cols
|
|
if self.overlapping_accum:
|
|
self.num_accumulator_tmem_cols = self.cta_tile_shape_mnk[1] * 2 - self.num_sf_tmem_cols
|
|
else:
|
|
self.num_accumulator_tmem_cols = self.cta_tile_shape_mnk[1] * self.num_acc_stage
|
|
|
|
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
|
tCtAcc_fake = tiled_mma.make_fragment_C(cute.append(acc_shape, self.num_acc_stage))
|
|
self.num_tmem_alloc_cols = utils.get_num_tmem_alloc_cols(tCtAcc_fake)
|
|
|
|
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
|
tiled_mma, self.mma_tiler, a_dtype, self.num_ab_stage)
|
|
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
|
tiled_mma, self.mma_tiler, b_dtype, self.num_ab_stage)
|
|
self.sfa_smem_layout_staged = blockscaled_utils.make_smem_layout_sfa(
|
|
tiled_mma, self.mma_tiler, self.sf_vec_size, self.num_ab_stage)
|
|
self.sfb_smem_layout_staged = blockscaled_utils.make_smem_layout_sfb(
|
|
tiled_mma, self.mma_tiler, self.sf_vec_size, self.num_ab_stage)
|
|
|
|
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
|
a_smem_0 = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
|
b_smem_0 = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
|
sfa_smem_0 = cute.slice_(self.sfa_smem_layout_staged, (None, None, None, 0))
|
|
sfb_smem_0 = cute.slice_(self.sfb_smem_layout_staged, (None, None, None, 0))
|
|
self.num_tma_load_bytes = (
|
|
cute.size_in_bytes(a_dtype, a_smem_0) +
|
|
cute.size_in_bytes(b_dtype, b_smem_0) +
|
|
cute.size_in_bytes(sf_dtype, sfa_smem_0) +
|
|
cute.size_in_bytes(sf_dtype, sfb_smem_0)
|
|
) * atom_thr_size
|
|
|
|
self.iter_acc_early_release = self.num_sf_tmem_cols // self.epi_tile_n
|
|
|
|
def run(self, mat_a, mat_b, scale_a, scale_b, e_bias, out_weights, out_ids,
|
|
M, N, K, routed_scaling_factor, top_k, stream=None):
|
|
if stream is None:
|
|
stream = cuda.CUstream(0)
|
|
|
|
a_dtype = cutlass.Float4E2M1FN
|
|
b_dtype = cutlass.Float4E2M1FN
|
|
sf_dtype = cutlass.Float8E4M3FN
|
|
a_major_mode = utils.LayoutEnum.from_tensor(mat_a).mma_major_mode()
|
|
b_major_mode = utils.LayoutEnum.from_tensor(mat_b).mma_major_mode()
|
|
|
|
tiled_mma = self._create_tiled_mma(a_dtype, a_major_mode, b_major_mode, sf_dtype)
|
|
tiled_mma_sfb = self._create_tiled_mma_sfb(a_dtype, a_major_mode, b_major_mode, sf_dtype)
|
|
self._setup_attributes(tiled_mma, tiled_mma_sfb, a_dtype, b_dtype, sf_dtype)
|
|
|
|
a_smem_0 = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
|
a_op = sm100_utils.cluster_shape_to_tma_atom_A(self.cluster_shape_mn, tiled_mma.thr_id)
|
|
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
|
a_op, mat_a, a_smem_0, self.mma_tiler, tiled_mma, self.cluster_layout_vmnk.shape)
|
|
|
|
b_smem_0 = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
|
b_op = sm100_utils.cluster_shape_to_tma_atom_B(self.cluster_shape_mn, tiled_mma.thr_id)
|
|
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
|
b_op, mat_b, b_smem_0, self.mma_tiler, tiled_mma, self.cluster_layout_vmnk.shape)
|
|
|
|
sfa_smem_0 = cute.slice_(self.sfa_smem_layout_staged, (None, None, None, 0))
|
|
tma_atom_sfa, tma_tensor_sfa = cute.nvgpu.make_tiled_tma_atom_A(
|
|
a_op, scale_a, sfa_smem_0, self.mma_tiler, tiled_mma, self.cluster_layout_vmnk.shape)
|
|
|
|
sfb_smem_0 = cute.slice_(self.sfb_smem_layout_staged, (None, None, None, 0))
|
|
sfb_op = sm100_utils.cluster_shape_to_tma_atom_B(self.cluster_shape_mn, tiled_mma_sfb.thr_id)
|
|
tma_atom_sfb, tma_tensor_sfb = cute.nvgpu.make_tiled_tma_atom_B(
|
|
sfb_op, scale_b, sfb_smem_0, self.mma_tiler_sfb, tiled_mma_sfb, self.cluster_layout_sfb_vmnk.shape)
|
|
|
|
num_M_tiles = cute.ceil_div(M, self.cta_tile_shape_mnk[0])
|
|
num_N_tiles = cute.ceil_div(N, self.cta_tile_shape_mnk[1])
|
|
L = 1
|
|
grid = (num_M_tiles * num_N_tiles, 1, 1)
|
|
|
|
tile_sched_params = utils.PersistentTileSchedulerParams(
|
|
(cutlass.Int32(num_M_tiles), cutlass.Int32(num_N_tiles), cutlass.Int32(L)),
|
|
(*self.cluster_shape_mn, 1))
|
|
|
|
@cute.jit
|
|
def _compiled_fn(mat_a, mat_b, scale_a, scale_b, e_bias, out_weights, out_ids):
|
|
self._kernel(
|
|
tiled_mma, tiled_mma_sfb,
|
|
tma_atom_a, tma_tensor_a, tma_atom_b, tma_tensor_b,
|
|
tma_atom_sfa, tma_tensor_sfa, tma_atom_sfb, tma_tensor_sfb,
|
|
self.cluster_layout_vmnk, self.cluster_layout_sfb_vmnk,
|
|
self.a_smem_layout_staged, self.b_smem_layout_staged,
|
|
self.sfa_smem_layout_staged, self.sfb_smem_layout_staged,
|
|
self.epi_tile,
|
|
e_bias, out_weights, out_ids,
|
|
tile_sched_params,
|
|
M, N, K, top_k, routed_scaling_factor,
|
|
).launch(
|
|
grid=grid, block=[self.threads_per_cta, 1, 1],
|
|
cluster=(*self.cluster_shape_mn, 1),
|
|
stream=stream, min_blocks_per_mp=1,
|
|
)
|
|
|
|
cute.compile(_compiled_fn, mat_a, mat_b, scale_a, scale_b, e_bias, out_weights, out_ids)
|
|
|
|
@cute.kernel
|
|
def _kernel(self, tiled_mma, tiled_mma_sfb,
|
|
tma_atom_a, mA_mkl, tma_atom_b, mB_nkl,
|
|
tma_atom_sfa, mSFA_mkl, tma_atom_sfb, mSFB_nkl,
|
|
cluster_layout_vmnk, cluster_layout_sfb_vmnk,
|
|
a_smem_layout_staged, b_smem_layout_staged,
|
|
sfa_smem_layout_staged, sfb_smem_layout_staged,
|
|
epi_tile,
|
|
e_bias_tensor, out_w_tensor, out_id_tensor,
|
|
tile_sched_params,
|
|
M, N, K, top_k, routed_scaling_factor):
|
|
|
|
warp_idx = cute.arch.warp_idx()
|
|
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
|
tidx, _, _ = cute.arch.thread_idx()
|
|
bidx, _, _ = cute.arch.block_idx()
|
|
use_2cta = cute.size(tiled_mma.thr_id.shape) == 2
|
|
is_leader_cta = (bidx % cute.size(tiled_mma.thr_id.shape)) == 0
|
|
mma_tile_v = bidx % cute.size(tiled_mma.thr_id.shape)
|
|
cta_rank = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
|
block_coord = cluster_layout_vmnk.get_flat_coord(cta_rank)
|
|
|
|
acc_dtype = cutlass.Float32
|
|
sf_dtype = cutlass.Float8E4M3FN
|
|
|
|
# ============================================================
|
|
# Shared storage
|
|
# ============================================================
|
|
@cute.struct
|
|
class SharedStorage:
|
|
ab_full_mbar: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
|
ab_empty_mbar: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
|
acc_full_mbar: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
|
acc_empty_mbar: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
|
tmem_dealloc_mbar: cutlass.Int64
|
|
tmem_holding: cutlass.Int32
|
|
merge_scores: cute.struct.Align[cute.struct.MemRange[cutlass.Float32, 128*self.top_k], 128]
|
|
merge_indices: cute.struct.Align[cute.struct.MemRange[cutlass.Int32, 128*self.top_k], 128]
|
|
merge_acts: cute.struct.Align[cute.struct.MemRange[cutlass.Float32, 128*self.top_k], 128]
|
|
sA: cute.struct.Align[cute.struct.MemRange[cutlass.Float4E2M1FN, cute.cosize(a_smem_layout_staged.outer)], self.buffer_align_bytes]
|
|
sB: cute.struct.Align[cute.struct.MemRange[cutlass.Float4E2M1FN, cute.cosize(b_smem_layout_staged.outer)], self.buffer_align_bytes]
|
|
sSFA: cute.struct.Align[cute.struct.MemRange[cutlass.Float8E4M3FN, cute.cosize(sfa_smem_layout_staged.outer)], self.buffer_align_bytes]
|
|
sSFB: cute.struct.Align[cute.struct.MemRange[cutlass.Float8E4M3FN, cute.cosize(sfb_smem_layout_staged.outer)], self.buffer_align_bytes]
|
|
|
|
smem = utils.SmemAllocator()
|
|
storage = smem.allocate(SharedStorage)
|
|
|
|
# ============================================================
|
|
# Pipelines
|
|
# ============================================================
|
|
ab_pipeline = pipeline.PipelineTmaUmma.create(
|
|
barrier_storage=storage.ab_full_mbar.data_ptr(),
|
|
num_stages=self.num_ab_stage,
|
|
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
consumer_group=pipeline.CooperativeGroup(
|
|
pipeline.Agent.Thread,
|
|
self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1),
|
|
tx_count=self.num_tma_load_bytes,
|
|
cta_layout_vmnk=cluster_layout_vmnk,
|
|
)
|
|
|
|
num_acc_cons = self.threads_per_warp * len(self.epilogue_warp_id) * (2 if use_2cta else 1)
|
|
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
|
barrier_storage=storage.acc_full_mbar.data_ptr(),
|
|
num_stages=self.num_acc_stage,
|
|
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread, num_acc_cons),
|
|
cta_layout_vmnk=cluster_layout_vmnk,
|
|
)
|
|
|
|
tmem = utils.TmemAllocator(
|
|
storage.tmem_holding.ptr,
|
|
barrier_for_retrieve=pipeline.NamedBarrier(
|
|
barrier_id=self.tmem_alloc_sync_bar_id,
|
|
num_threads=self.threads_per_warp * len((self.mma_warp_id, *self.epilogue_warp_id))),
|
|
allocator_warp_id=self.epilogue_warp_id[0],
|
|
is_two_cta=use_2cta,
|
|
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar.ptr)
|
|
|
|
cta_bar = pipeline.NamedBarrier(self.cta_sync_bar_id, self.threads_per_cta)
|
|
epi_bar = pipeline.NamedBarrier(
|
|
self.epilogue_sync_bar_id,
|
|
self.threads_per_warp * len(self.epilogue_warp_id))
|
|
|
|
# ============================================================
|
|
# SMEM tensors
|
|
# ============================================================
|
|
sA = storage.sA.get_tensor(a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner)
|
|
sB = storage.sB.get_tensor(b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner)
|
|
sSFA = storage.sSFA.get_tensor(sfa_smem_layout_staged.outer, swizzle=sfa_smem_layout_staged.inner)
|
|
sSFB = storage.sSFB.get_tensor(sfb_smem_layout_staged.outer, swizzle=sfb_smem_layout_staged.inner)
|
|
|
|
# ============================================================
|
|
# Multicast masks
|
|
# ============================================================
|
|
a_mcast = None; b_mcast = None; sfa_mcast = None; sfb_mcast = None
|
|
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta):
|
|
a_mcast = cpasync.create_tma_multicast_mask(cluster_layout_vmnk, block_coord, mcast_mode=2)
|
|
b_mcast = cpasync.create_tma_multicast_mask(cluster_layout_vmnk, block_coord, mcast_mode=1)
|
|
sfa_mcast = a_mcast
|
|
sfb_mcast = cpasync.create_tma_multicast_mask(cluster_layout_sfb_vmnk, block_coord, mcast_mode=1)
|
|
|
|
# ============================================================
|
|
# Partition global tensors
|
|
# ============================================================
|
|
gA = cute.local_tile(mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None))
|
|
gB = cute.local_tile(mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None))
|
|
gSFA = cute.local_tile(mSFA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None))
|
|
gSFB = cute.local_tile(mSFB_nkl, cute.slice_(self.mma_tiler_sfb, (0, None, None)), (None, None, None))
|
|
|
|
k_tiles = cute.size(gA, mode=[3])
|
|
thr_mma = tiled_mma.get_slice(mma_tile_v)
|
|
tCgA = thr_mma.partition_A(gA)
|
|
tCgB = thr_mma.partition_B(gB)
|
|
tCgSFA = thr_mma.partition_A(gSFA)
|
|
thr_mma_sfb = tiled_mma_sfb.get_slice(mma_tile_v)
|
|
tCgSFB = thr_mma_sfb.partition_B(gSFB)
|
|
|
|
# TMA partitions for A/B
|
|
a_cta_l = cute.make_layout(cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape)
|
|
tAsA, tAgA = cpasync.tma_partition(tma_atom_a, block_coord[2], a_cta_l,
|
|
cute.group_modes(sA, 0, 3), cute.group_modes(tCgA, 0, 3))
|
|
b_cta_l = cute.make_layout(cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape)
|
|
tBsB, tBgB = cpasync.tma_partition(tma_atom_b, block_coord[1], b_cta_l,
|
|
cute.group_modes(sB, 0, 3), cute.group_modes(tCgB, 0, 3))
|
|
|
|
# TMA partitions for SFA/SFB
|
|
tAsSFA, tAgSFA = cpasync.tma_partition(tma_atom_sfa, block_coord[2], a_cta_l,
|
|
cute.group_modes(sSFA, 0, 3), cute.group_modes(tCgSFA, 0, 3))
|
|
tAsSFA = cute.filter_zeros(tAsSFA)
|
|
tAgSFA = cute.filter_zeros(tAgSFA)
|
|
|
|
sfb_cta_l = cute.make_layout(cute.slice_(cluster_layout_sfb_vmnk, (0, None, 0, 0)).shape)
|
|
block_coord_sfb = cluster_layout_sfb_vmnk.get_flat_coord(cta_rank)
|
|
tBsSFB, tBgSFB = cpasync.tma_partition(tma_atom_sfb, block_coord_sfb[1], sfb_cta_l,
|
|
cute.group_modes(sSFB, 0, 3), cute.group_modes(tCgSFB, 0, 3))
|
|
tBsSFB = cute.filter_zeros(tBsSFB)
|
|
tBgSFB = cute.filter_zeros(tBgSFB)
|
|
|
|
# TMEM accumulator shape
|
|
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
|
tCtAcc_fake = tiled_mma.make_fragment_C(cute.append(acc_shape, self.num_acc_stage))
|
|
|
|
if cute.size(self.cluster_shape_mn) > 1:
|
|
cute.arch.cluster_arrive_relaxed()
|
|
|
|
# ============================================================
|
|
# TMA WARP
|
|
# ============================================================
|
|
if warp_idx == self.tma_warp_id:
|
|
cpasync.prefetch_descriptor(tma_atom_a)
|
|
cpasync.prefetch_descriptor(tma_atom_b)
|
|
cpasync.prefetch_descriptor(tma_atom_sfa)
|
|
cpasync.prefetch_descriptor(tma_atom_sfb)
|
|
|
|
tsched = utils.StaticPersistentTileScheduler.create(
|
|
tile_sched_params, bidx, cute.arch.grid_dim())
|
|
wt = tsched.initial_work_tile_info()
|
|
ab_ps = pipeline.make_pipeline_state(
|
|
pipeline.PipelineUserType.Producer, self.num_ab_stage)
|
|
|
|
while wt.is_valid_tile:
|
|
tc = wt.tile_idx
|
|
mc = (tc[0] // cute.size(tiled_mma.thr_id.shape), tc[1], tc[2])
|
|
tAgA_s = tAgA[(None, mc[0], None, mc[2])]
|
|
tBgB_s = tBgB[(None, mc[1], None, mc[2])]
|
|
tAgSFA_s = tAgSFA[(None, mc[0], None, mc[2])]
|
|
slice_n = mc[1]
|
|
if cutlass.const_expr(self.cta_tile_shape_mnk[1] == 64):
|
|
slice_n = mc[1] // 2
|
|
tBgSFB_s = tBgSFB[(None, slice_n, None, mc[2])]
|
|
|
|
ab_ps.reset_count()
|
|
peek_ab = cutlass.Boolean(1)
|
|
if ab_ps.count < k_tiles:
|
|
peek_ab = ab_pipeline.producer_try_acquire(ab_ps)
|
|
|
|
for kt in cutlass.range(0, k_tiles, 1, unroll=1):
|
|
ab_pipeline.producer_acquire(ab_ps, peek_ab)
|
|
cute.copy(tma_atom_a, tAgA_s[(None, ab_ps.count)],
|
|
tAsA[(None, ab_ps.index)],
|
|
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_ps),
|
|
mcast_mask=a_mcast)
|
|
cute.copy(tma_atom_b, tBgB_s[(None, ab_ps.count)],
|
|
tBsB[(None, ab_ps.index)],
|
|
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_ps),
|
|
mcast_mask=b_mcast)
|
|
cute.copy(tma_atom_sfa, tAgSFA_s[(None, ab_ps.count)],
|
|
tAsSFA[(None, ab_ps.index)],
|
|
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_ps),
|
|
mcast_mask=sfa_mcast)
|
|
cute.copy(tma_atom_sfb, tBgSFB_s[(None, ab_ps.count)],
|
|
tBsSFB[(None, ab_ps.index)],
|
|
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_ps),
|
|
mcast_mask=sfb_mcast)
|
|
ab_ps.advance()
|
|
peek_ab = cutlass.Boolean(1)
|
|
if ab_ps.count < k_tiles:
|
|
peek_ab = ab_pipeline.producer_try_acquire(ab_ps)
|
|
|
|
ab_pipeline.producer_tail(ab_ps)
|
|
tsched.advance_to_next_work()
|
|
wt = tsched.get_current_work()
|
|
|
|
# ============================================================
|
|
# MMA WARP — blockscaled GEMM: (A * SFA) @ (B * SFB) -> TMEM
|
|
# ============================================================
|
|
if warp_idx == self.mma_warp_id:
|
|
# Wait for cluster sync
|
|
if cute.size(self.cluster_shape_mn) > 1:
|
|
cute.arch.cluster_wait()
|
|
else:
|
|
cta_bar.arrive_and_wait()
|
|
|
|
# Wait for TMEM allocation
|
|
tmem.wait_for_alloc()
|
|
acc_tmem_ptr = tmem.retrieve_ptr(acc_dtype)
|
|
tCtAcc_base = cute.make_tensor(acc_tmem_ptr, tCtAcc_fake.layout)
|
|
|
|
# MMA fragments
|
|
tCrA = tiled_mma.make_fragment_A(sA)
|
|
tCrB = tiled_mma.make_fragment_B(sB)
|
|
|
|
# S2T copies for SFA: SMEM -> TMEM
|
|
# The SFA tmem region starts after the accumulator columns
|
|
sfa_tmem_ptr = acc_tmem_ptr
|
|
tCtSFA_layout = blockscaled_utils.make_tmem_layout_sfa(
|
|
tiled_mma, self.mma_tiler, self.sf_vec_size,
|
|
cute.slice_(sfa_smem_layout_staged, (None, None, None, 0)))
|
|
tCtSFA = cute.make_tensor(sfa_tmem_ptr, tCtSFA_layout)
|
|
|
|
# S2T copies for SFB: SMEM -> TMEM
|
|
sfb_tmem_ptr = acc_tmem_ptr
|
|
tCtSFB_layout = blockscaled_utils.make_tmem_layout_sfb(
|
|
tiled_mma_sfb, self.mma_tiler, self.sf_vec_size,
|
|
cute.slice_(sfb_smem_layout_staged, (None, None, None, 0)))
|
|
tCtSFB = cute.make_tensor(sfb_tmem_ptr, tCtSFB_layout)
|
|
|
|
# S2T copy atoms
|
|
tiled_copy_s2t_sfa, tCsSFA_compact_s2t, tCtSFA_compact_s2t = \
|
|
self.mainloop_s2t_copy_and_partition(sSFA, tCtSFA, self.cta_group)
|
|
tiled_copy_s2t_sfb, tCsSFB_compact_s2t, tCtSFB_compact_s2t = \
|
|
self.mainloop_s2t_copy_and_partition(sSFB, tCtSFB, tcgen05.CtaGroup.ONE)
|
|
|
|
# Tile scheduler + pipeline states
|
|
tsched = utils.StaticPersistentTileScheduler.create(
|
|
tile_sched_params, bidx, cute.arch.grid_dim())
|
|
wt = tsched.initial_work_tile_info()
|
|
ab_cs = pipeline.make_pipeline_state(
|
|
pipeline.PipelineUserType.Consumer, self.num_ab_stage)
|
|
acc_ps = pipeline.make_pipeline_state(
|
|
pipeline.PipelineUserType.Producer, self.num_acc_stage)
|
|
|
|
num_tiles_executed = cutlass.Int32(0)
|
|
|
|
while wt.is_valid_tile:
|
|
# Wait for accumulator buffer empty
|
|
if is_leader_cta:
|
|
acc_pipeline.producer_acquire(acc_ps)
|
|
|
|
# Get accumulator stage index
|
|
if cutlass.const_expr(self.overlapping_accum):
|
|
acc_stage_index = acc_ps.phase ^ 1
|
|
else:
|
|
acc_stage_index = acc_ps.index
|
|
|
|
tCtAcc = tCtAcc_base[(None, None, None, acc_stage_index)]
|
|
|
|
# Clear accumulator for new tile
|
|
tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
|
|
|
# Reset count for AB pipeline consumer
|
|
ab_cs.reset_count()
|
|
peek_ab_full = cutlass.Boolean(1)
|
|
if ab_cs.count < k_tiles and is_leader_cta:
|
|
peek_ab_full = ab_pipeline.consumer_try_wait(ab_cs)
|
|
|
|
# Mainloop: K-tiles
|
|
for kt in cutlass.range(0, k_tiles, 1, unroll=1):
|
|
if is_leader_cta:
|
|
ab_pipeline.consumer_wait(ab_cs, peek_ab_full)
|
|
|
|
# Copy SFA/SFB from SMEM to TMEM
|
|
s2t_stage = (
|
|
None, None, None, None, ab_cs.index,
|
|
)
|
|
cute.copy(tiled_copy_s2t_sfa,
|
|
tCsSFA_compact_s2t[s2t_stage],
|
|
tCtSFA_compact_s2t)
|
|
cute.copy(tiled_copy_s2t_sfb,
|
|
tCsSFB_compact_s2t[s2t_stage],
|
|
tCtSFB_compact_s2t)
|
|
|
|
# Set SFA/SFB for MMA
|
|
num_kblocks = cute.size(tCrA, mode=[2])
|
|
for kblock_idx in cutlass.range(num_kblocks, unroll=1):
|
|
sf_kblock = (None, None, kblock_idx)
|
|
tiled_mma.set(tcgen05.Field.SFA,
|
|
tCtSFA[sf_kblock].iterator)
|
|
tiled_mma.set(tcgen05.Field.SFB,
|
|
tCtSFB[sf_kblock].iterator)
|
|
|
|
kb_coord = (None, None, kblock_idx, ab_cs.index)
|
|
cute.gemm(tiled_mma, tCrA[kb_coord], tCrB[kb_coord],
|
|
tCtAcc, tCtAcc)
|
|
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
|
|
|
# Release AB buffer
|
|
ab_pipeline.consumer_release(ab_cs)
|
|
ab_cs.advance()
|
|
peek_ab_full = cutlass.Boolean(1)
|
|
if ab_cs.count < k_tiles:
|
|
if is_leader_cta:
|
|
peek_ab_full = ab_pipeline.consumer_try_wait(ab_cs)
|
|
|
|
# Commit accumulator full
|
|
if is_leader_cta:
|
|
acc_pipeline.producer_commit(acc_ps)
|
|
acc_ps.advance()
|
|
num_tiles_executed += cutlass.Int32(1)
|
|
|
|
tsched.advance_to_next_work()
|
|
wt = tsched.get_current_work()
|
|
|
|
# Wait for accumulator buffer empty
|
|
if is_leader_cta:
|
|
acc_pipeline.producer_tail(acc_ps)
|
|
|
|
# Signal epilogue that MMA is done
|
|
tmem.relinquish_alloc_permit()
|
|
|
|
# ============================================================
|
|
# EPILOGUE WARPS — TMEM → registers → router logic → GMEM
|
|
# ============================================================
|
|
#
|
|
# Strategy:
|
|
# 1. Read TMEM accumulator into registers via paired t2r copy
|
|
# 2. For each element: compute act = sqrt(softplus(logit)),
|
|
# score = act + e_bias[expert_idx]
|
|
# 3. Insert into per-thread running top-6 (sorted, fully unrolled)
|
|
# 4. After all tiles: write local top-6 to SMEM, one thread merges,
|
|
# sorts, renormalizes, writes to GMEM
|
|
#
|
|
# The top-6 is maintained in DESCENDING order:
|
|
# s0 >= s1 >= s2 >= s3 >= s4 >= s5
|
|
# Insertion uses fully unrolled comparisons — no dynamic indexing.
|
|
#
|
|
if warp_idx in self.epilogue_warp_id:
|
|
if cute.size(self.cluster_shape_mn) > 1:
|
|
cute.arch.cluster_wait()
|
|
else:
|
|
cta_bar.arrive_and_wait()
|
|
|
|
tmem.wait_for_alloc()
|
|
acc_tmem_ptr = tmem.retrieve_ptr(acc_dtype)
|
|
tCtAcc_base = cute.make_tensor(acc_tmem_ptr, tCtAcc_fake.layout)
|
|
|
|
# TMEM → register copy (paired atoms from CUTLASS)
|
|
epi_n = self.epi_tile_n
|
|
tiled_copy_t2r, tTR_tAcc_base = epilogue_tmem_copy_and_partition(
|
|
tCtAcc_base, epi_tile, self.epilogue_warp_id, acc_dtype, use_2cta)
|
|
tTR_rAcc = tiled_copy_t2r.fragments_slice(tiled_copy_t2r, tTR_tAcc_base)
|
|
|
|
# Identity tensor for (row, col) coordinates
|
|
cAcc = cute.make_identity_tensor(
|
|
(self.cta_tile_shape_mnk[0], self.cta_tile_shape_mnk[1]))
|
|
tCcAcc = thr_mma.partition_C(cAcc)
|
|
cFlat = cute.flatten(tCcAcc)
|
|
|
|
# Merge SMEM tensors (for cross-thread top-k merge)
|
|
s_merge_s = cute.make_tensor(
|
|
storage.merge_scores.data_ptr(),
|
|
cute.make_layout((128, TK)))
|
|
s_merge_i = cute.make_tensor(
|
|
storage.merge_indices.data_ptr(),
|
|
cute.make_layout((128, TK)))
|
|
s_merge_a = cute.make_tensor(
|
|
storage.merge_acts.data_ptr(),
|
|
cute.make_layout((128, TK)))
|
|
|
|
# ------------------------------------------------------------------
|
|
# Running top-6 per thread — individual scalar variables
|
|
# Stored in DESCENDING order: s0 >= s1 >= s2 >= s3 >= s4 >= s5
|
|
# ------------------------------------------------------------------
|
|
s0 = cutlass.Float32(-1e30)
|
|
s1 = cutlass.Float32(-1e30)
|
|
s2 = cutlass.Float32(-1e30)
|
|
s3 = cutlass.Float32(-1e30)
|
|
s4 = cutlass.Float32(-1e30)
|
|
s5 = cutlass.Float32(-1e30)
|
|
i0 = cutlass.Int32(-1)
|
|
i1 = cutlass.Int32(-1)
|
|
i2 = cutlass.Int32(-1)
|
|
i3 = cutlass.Int32(-1)
|
|
i4 = cutlass.Int32(-1)
|
|
i5 = cutlass.Int32(-1)
|
|
a0 = cutlass.Float32(0.0)
|
|
a1 = cutlass.Float32(0.0)
|
|
a2 = cutlass.Float32(0.0)
|
|
a3 = cutlass.Float32(0.0)
|
|
a4 = cutlass.Float32(0.0)
|
|
a5 = cutlass.Float32(0.0)
|
|
|
|
# Tile scheduler + pipeline states
|
|
tsched = utils.StaticPersistentTileScheduler.create(
|
|
tile_sched_params, bidx, cute.arch.grid_dim())
|
|
wt = tsched.initial_work_tile_info()
|
|
acc_cs = pipeline.make_pipeline_state(
|
|
pipeline.PipelineUserType.Consumer, self.num_acc_stage)
|
|
|
|
# Track which row we're computing top-k for (row 0 of each M-tile)
|
|
current_row = cutlass.Int32(-1)
|
|
num_tiles_done = cutlass.Int32(0)
|
|
|
|
while wt.is_valid_tile:
|
|
acc_pipeline.consumer_wait(acc_cs)
|
|
|
|
if cutlass.const_expr(self.overlapping_accum):
|
|
acc_stage_index = acc_cs.phase
|
|
else:
|
|
acc_stage_index = acc_cs.index
|
|
|
|
# Get tile N offset (which 128-expert slice this tile covers)
|
|
tc = wt.tile_idx
|
|
tile_n_offset = tc[1] * self.cta_tile_shape_mnk[1]
|
|
tile_m_base = tc[0] // cute.size(tiled_mma.thr_id.shape) * self.cta_tile_shape_mnk[0]
|
|
|
|
# If this is a new row, the running top-6 is already accumulated
|
|
# For the first tile of a row, we just continue accumulating
|
|
if num_tiles_done == cutlass.Int32(0):
|
|
current_row = tile_m_base
|
|
|
|
tTR_tAcc = tTR_tAcc_base[(None, None, None, None, None, acc_stage_index)]
|
|
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
|
|
|
# Process subtiles (each subtile = epi_tile_n columns)
|
|
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
|
for subtile_idx in cutlass.range(subtile_cnt):
|
|
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
|
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
|
|
|
cute.arch.fence_view_async_tmem_load()
|
|
|
|
# Early release accumulator for overlapping case
|
|
if cutlass.const_expr(self.overlapping_accum):
|
|
if subtile_idx == self.iter_acc_early_release:
|
|
with cute.arch.elect_one():
|
|
acc_pipeline.consumer_release(acc_cs)
|
|
acc_cs.advance()
|
|
|
|
# Process each element in the register fragment
|
|
rFlat = cute.flatten(tTR_rAcc)
|
|
elem_cnt = cute.size(rFlat)
|
|
for e in cutlass.range(elem_cnt, unroll=4):
|
|
logit = rFlat[e]
|
|
coord = cFlat[e]
|
|
row = coord[0]
|
|
col = coord[1]
|
|
|
|
# Expert index = col + tile_n_offset + (subtile_idx * epi_n)
|
|
e_idx = col + tile_n_offset + (subtile_idx * epi_n)
|
|
|
|
# Only process row 0 (the actual token row)
|
|
# For M=1 padded to 128, only row 0 has valid data
|
|
if row == 0:
|
|
# sqrt(softplus(logit))
|
|
# softplus(x) = max(x, 0) + log(1 + exp(-|x|))
|
|
abs_x = cute.math.absf(logit)
|
|
pos = cute.math.fmax(logit, cutlass.Float32(0.0))
|
|
exp_neg = cute.math.exp(-abs_x)
|
|
one_plus = cutlass.Float32(1.0) + exp_neg
|
|
sp = pos + cute.math.log(one_plus)
|
|
act = cute.math.sqrt(sp)
|
|
|
|
# score = act + e_bias (for selection only)
|
|
score = act + e_bias_tensor[e_idx]
|
|
|
|
# Sorted insertion into descending top-6
|
|
# s0 >= s1 >= s2 >= s3 >= s4 >= s5
|
|
# If score <= s5, skip
|
|
if score > s5:
|
|
if score > s4:
|
|
# Shift s4 → s5
|
|
s5 = s4; i5 = i4; a5 = a4
|
|
if score > s3:
|
|
s4 = s3; i4 = i3; a4 = a3
|
|
if score > s2:
|
|
s3 = s2; i3 = i2; a3 = a2
|
|
if score > s1:
|
|
s2 = s1; i2 = i1; a2 = a1
|
|
if score > s0:
|
|
s1 = s0; i1 = i0; a1 = a0
|
|
s0 = score; i0 = e_idx; a0 = act
|
|
else:
|
|
s1 = score; i1 = e_idx; a1 = act
|
|
else:
|
|
s2 = score; i2 = e_idx; a2 = act
|
|
else:
|
|
s3 = score; i3 = e_idx; a3 = act
|
|
else:
|
|
s4 = score; i4 = e_idx; a4 = act
|
|
else:
|
|
s5 = score; i5 = e_idx; a5 = act
|
|
|
|
# Release accumulator (non-overlapping case)
|
|
if cutlass.const_expr(not self.overlapping_accum):
|
|
with cute.arch.elect_one():
|
|
acc_pipeline.consumer_release(acc_cs)
|
|
acc_cs.advance()
|
|
|
|
num_tiles_done += cutlass.Int32(1)
|
|
|
|
tsched.advance_to_next_work()
|
|
wt = tsched.get_current_work()
|
|
|
|
# ==================================================================
|
|
# Post-loop: all tiles processed. Merge across threads, write to GMEM.
|
|
# ==================================================================
|
|
# Each thread writes its running top-6 to SMEM
|
|
tid = warp_idx * 32 + tidx
|
|
s_merge_s[tid, 0] = s0; s_merge_s[tid, 1] = s1; s_merge_s[tid, 2] = s2
|
|
s_merge_s[tid, 3] = s3; s_merge_s[tid, 4] = s4; s_merge_s[tid, 5] = s5
|
|
s_merge_i[tid, 0] = i0; s_merge_i[tid, 1] = i1; s_merge_i[tid, 2] = i2
|
|
s_merge_i[tid, 3] = i3; s_merge_i[tid, 4] = i4; s_merge_i[tid, 5] = i5
|
|
s_merge_a[tid, 0] = a0; s_merge_a[tid, 1] = a1; s_merge_a[tid, 2] = a2
|
|
s_merge_a[tid, 3] = a3; s_merge_a[tid, 4] = a4; s_merge_a[tid, 5] = a5
|
|
|
|
epi_bar.arrive_and_wait()
|
|
|
|
# Thread 0 merges all 128 threads' top-6 into final result
|
|
if warp_idx == 0 and tidx == 0:
|
|
# Initialize final top-6 from thread 0's data
|
|
fs0 = s0; fs1 = s1; fs2 = s2; fs3 = s3; fs4 = s4; fs5 = s5
|
|
fi0 = i0; fi1 = i1; fi2 = i2; fi3 = i3; fi4 = i4; fi5 = i5
|
|
fa0 = a0; fa1 = a1; fa2 = a2; fa3 = a3; fa4 = a4; fa5 = a5
|
|
|
|
# Merge all other threads (1..127)
|
|
for t in cutlass.range(1, 128, unroll=1):
|
|
for k in cutlass.range(TK, unroll=1):
|
|
cs = s_merge_s[t, k]
|
|
ci = s_merge_i[t, k]
|
|
ca = s_merge_a[t, k]
|
|
# Only merge if this is a valid entry (index >= 0)
|
|
if ci >= cutlass.Int32(0):
|
|
# Sorted insertion into final top-6 (descending)
|
|
if cs > fs5:
|
|
if cs > fs4:
|
|
fs5 = fs4; fi5 = fi4; fa5 = fa4
|
|
if cs > fs3:
|
|
fs4 = fs3; fi4 = fi3; fa4 = fa3
|
|
if cs > fs2:
|
|
fs3 = fs2; fi3 = fi2; fa3 = fa2
|
|
if cs > fs1:
|
|
fs2 = fs1; fi2 = fi1; fa2 = fa1
|
|
if cs > fs0:
|
|
fs1 = fs0; fi1 = fi0; fa1 = fa0
|
|
fs0 = cs; fi0 = ci; fa0 = ca
|
|
else:
|
|
fs1 = cs; fi1 = ci; fa1 = ca
|
|
else:
|
|
fs2 = cs; fi2 = ci; fa2 = ca
|
|
else:
|
|
fs3 = cs; fi3 = ci; fa3 = ca
|
|
else:
|
|
fs4 = cs; fi4 = ci; fa4 = ca
|
|
else:
|
|
fs5 = cs; fi5 = ci; fa5 = ca
|
|
|
|
# Renormalize: w = act / sum(act) * scaling
|
|
act_sum = fa0 + fa1 + fa2 + fa3 + fa4 + fa5
|
|
inv_sum = cutlass.Float32(1.0) / act_sum
|
|
sc = cutlass.Float32(routed_scaling_factor)
|
|
|
|
# Store to GMEM (row 0 of the M-tile)
|
|
row_idx = cutlass.Int32(0)
|
|
out_w_tensor[row_idx, 0] = fa0 * inv_sum * sc
|
|
out_w_tensor[row_idx, 1] = fa1 * inv_sum * sc
|
|
out_w_tensor[row_idx, 2] = fa2 * inv_sum * sc
|
|
out_w_tensor[row_idx, 3] = fa3 * inv_sum * sc
|
|
out_w_tensor[row_idx, 4] = fa4 * inv_sum * sc
|
|
out_w_tensor[row_idx, 5] = fa5 * inv_sum * sc
|
|
out_id_tensor[row_idx, 0] = fi0
|
|
out_id_tensor[row_idx, 1] = fi1
|
|
out_id_tensor[row_idx, 2] = fi2
|
|
out_id_tensor[row_idx, 3] = fi3
|
|
out_id_tensor[row_idx, 4] = fi4
|
|
out_id_tensor[row_idx, 5] = fi5
|
|
|
|
epi_bar.arrive_and_wait()
|
|
|
|
# Cleanup
|
|
tmem.relinquish_alloc_permit()
|
|
epi_bar.arrive_and_wait()
|
|
tmem.free(acc_tmem_ptr)
|
|
|
|
|
|
# ================================================================
|
|
# Python wrapper — called by dense_router_dispatch_nvfp4
|
|
# ================================================================
|
|
def run_nvfp4_fused_router(
|
|
hidden_states: torch.Tensor, # [M, hidden_size] BF16
|
|
mat_b, # CuTe tensor: gate weight (NVFP4, blockscaled layout)
|
|
scale_b, # CuTe tensor: gate weight scale factors (FP8 E4M3)
|
|
gsa, # Activation global scale (scalar or 1-elem tensor)
|
|
gsb_val: float, # Weight global scale value
|
|
e_bias: torch.Tensor, # [E] FP32
|
|
routed_scaling_factor: float,
|
|
top_k: int = 6,
|
|
sf_vec_size: int = 16,
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""Run the NVFP4 fused router kernel.
|
|
|
|
Single-kernel: NVFP4 block-scaled GEMM + fused router epilogue.
|
|
No intermediate GMEM buffer. No BF16 fallback.
|
|
|
|
Parameters
|
|
----------
|
|
hidden_states : [M, K] BF16 — raw activation
|
|
mat_b : CuTe tensor — gate weight in NVFP4 blockscaled layout
|
|
scale_b : CuTe tensor — gate weight scale factors in blockscaled layout
|
|
gsa : activation global scale (scalar)
|
|
gsb_val : weight global scale (float)
|
|
e_bias : [E] FP32 — per-expert selection bias
|
|
routed_scaling_factor : float
|
|
top_k : int (default 6)
|
|
|
|
Returns
|
|
-------
|
|
(topk_weights, topk_ids) — [M, top_k] FP32 and [M, top_k] int32
|
|
"""
|
|
import cutlass.torch as cutlass_torch
|
|
from dsv4.ops.quantize import quantize_activation_nvfp4
|
|
|
|
M = hidden_states.shape[0]
|
|
K = hidden_states.shape[1]
|
|
device = hidden_states.device
|
|
|
|
# Quantize activation to NVFP4
|
|
act_nvfp4, act_sf, _gsa, _gsb = quantize_activation_nvfp4(
|
|
hidden_states, sf_vec_size=sf_vec_size)
|
|
# Override global scales with provided values
|
|
act_gsa = gsa if gsa is not None else _gsa
|
|
act_gsb = gsb_val
|
|
|
|
# Create CuTe tensors for activation + scales
|
|
# A tensor: [K_packed, M, L] where K_packed = K/2 (2 elements per byte for FP4)
|
|
K_packed = K // 2
|
|
mat_a = cutlass_torch.from_dlpack(act_nvfp4)
|
|
mat_a = cute.mark_layout_dynamic(mat_a)
|
|
# SFA tensor: [K_sf, M, L]
|
|
scale_a = cutlass_torch.from_dlpack(act_sf)
|
|
scale_a = cute.mark_layout_dynamic(scale_a)
|
|
|
|
# e_bias must be a CuTe tensor
|
|
e_bias_cute = cutlass_torch.from_dlpack(e_bias)
|
|
e_bias_cute = cute.mark_layout_dynamic(e_bias_cute)
|
|
|
|
# Number of experts from e_bias
|
|
E = e_bias.shape[0]
|
|
|
|
# Output buffers
|
|
out_weights = torch.zeros(M, top_k, dtype=torch.float32, device=device)
|
|
out_ids = torch.zeros(M, top_k, dtype=torch.int32, device=device)
|
|
out_w_cute = cutlass_torch.from_dlpack(out_weights)
|
|
out_w_cute = cute.mark_layout_dynamic(out_w_cute)
|
|
out_id_cute = cutlass_torch.from_dlpack(out_ids)
|
|
out_id_cute = cute.mark_layout_dynamic(out_id_cute)
|
|
|
|
# MMA tiler: (128, 128, 64) for decode
|
|
mma_tiler_mnk = (128, 128, 64)
|
|
|
|
kernel = Nvfp4FusedRouterKernel(
|
|
sf_vec_size=sf_vec_size,
|
|
mma_tiler_mnk=mma_tiler_mnk,
|
|
cluster_shape_mnk=(1, 1, 1),
|
|
top_k=top_k,
|
|
)
|
|
kernel.run(
|
|
mat_a, mat_b, scale_a, scale_b,
|
|
e_bias_cute, out_w_cute, out_id_cute,
|
|
M, E, K, routed_scaling_factor, top_k,
|
|
)
|
|
return out_weights, out_ids
|