chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,461 @@
#pragma once
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/runtime.cuh>
#include <sgl_kernel/utils.cuh>
#include <cuda/ptx>
#include <tvm/ffi/container/tensor.h>
#include "cute/tensor.hpp"
#include <cuda.h>
#include <cuda_bf16.h>
#include <cuda_fp16.h>
namespace expert_specialization {
using namespace cute;
constexpr uint32_t THREAD_BLOCK_SIZE = 128;
constexpr uint32_t WARP_SIZE = 32;
constexpr int BLOCK_M = 128;
constexpr int BLOCK_K = 128;
using ThrLayout = Layout<Shape<_16, _8>, Stride<_8, _1>>;
using ValLayout = Layout<Shape<_1, _16>>;
using SfR2SThrLayout = Layout<Shape<_16, _4>, Stride<_4, _1>>;
using SfR2SValLayout = Layout<Shape<_1, _1>>;
using ScaleFactorTileLayout = Layout<Shape<Shape<_32, _4>, _4>, Stride<Stride<_16, _4>, _1>>;
// Fast reciprocal.
inline __device__ float reciprocal_approximate_ftz(float a) {
float b;
asm volatile("rcp.approx.ftz.f32 %0, %1;\n" : "=f"(b) : "f"(a));
return b;
}
// Some code references TRT-LLM:
// https://github.com/NVIDIA/TensorRT-LLM/blob/main/cpp/tensorrt_llm/kernels/quantization.cuh
template <typename FragmentS, typename FragmentD>
__inline__ __device__ uint8_t cvt_warp_fp16_to_mxfp8(FragmentS& fragment_s, FragmentD& fragment_d) {
using FragmentSLayout = typename FragmentS::layout_type;
using FragmentDLayout = typename FragmentD::layout_type;
FragmentSLayout fragment_s_layout;
FragmentDLayout fragment_d_layout;
static_assert(is_static<FragmentSLayout>::value && size(fragment_s_layout) == 16);
static_assert(is_static<FragmentDLayout>::value && size(fragment_d_layout) == 16);
constexpr int eles_per_thr = 16;
using ValType = typename FragmentS::element_type;
using VecType = std::conditional_t<std::is_same_v<ValType, __nv_bfloat16>, __nv_bfloat162, __half2>;
VecType vec[8];
// Assign vals
vec[0].x = fragment_s(Int<0>{});
vec[0].y = fragment_s(Int<1>{});
vec[1].x = fragment_s(Int<2>{});
vec[1].y = fragment_s(Int<3>{});
vec[2].x = fragment_s(Int<4>{});
vec[2].y = fragment_s(Int<5>{});
vec[3].x = fragment_s(Int<6>{});
vec[3].y = fragment_s(Int<7>{});
vec[4].x = fragment_s(Int<8>{});
vec[4].y = fragment_s(Int<9>{});
vec[5].x = fragment_s(Int<10>{});
vec[5].y = fragment_s(Int<11>{});
vec[6].x = fragment_s(Int<12>{});
vec[6].y = fragment_s(Int<13>{});
vec[7].x = fragment_s(Int<14>{});
vec[7].y = fragment_s(Int<15>{});
auto local_max = __habs2(vec[0]);
for (int i = 1; i < eles_per_thr / 2; i++) {
local_max = __hmax2(__habs2(vec[i]), local_max);
}
local_max = __hmax2(__shfl_xor_sync(uint32_t(-1), local_max, 1), local_max);
// Get the final absolute maximum values.
float block_max(0.0f);
if constexpr (std::is_same_v<ValType, __nv_bfloat16>) {
block_max = __bfloat162float(__hmax(local_max.x, local_max.y));
} else {
block_max = __half2float(__hmax(local_max.x, local_max.y));
}
// Get the SF (max value of the vector / max value of mxfp8).
float sf_val = block_max * reciprocal_approximate_ftz(448.0f);
// 8 bits representation of the SF.
uint8_t fp8_sf_val;
__nv_fp8_e8m0 tmp_sf_val;
tmp_sf_val.__x = __nv_cvt_float_to_e8m0(sf_val, __NV_SATFINITE, cudaRoundPosInf);
sf_val = static_cast<float>(tmp_sf_val);
fp8_sf_val = tmp_sf_val.__x;
// Get the output scale (reciprocal of the SFValue).
float output_scale = block_max != 0.f ? reciprocal_approximate_ftz(sf_val) : 0.0f;
// Convert the input to float.
float2 fp2_vals[eles_per_thr / 2];
#pragma unroll
for (int i = 0; i < eles_per_thr / 2; i++) {
if constexpr (std::is_same_v<ValType, __half>) {
fp2_vals[i] = __half22float2(vec[i]);
} else {
fp2_vals[i] = __bfloat1622float2(vec[i]);
}
fp2_vals[i].x *= output_scale;
fp2_vals[i].y *= output_scale;
}
union {
uint8_t bytes[16];
__nv_fp8x2_e4m3 elts[8];
} u;
u.elts[0] = __nv_fp8x2_e4m3(fp2_vals[0]);
u.elts[1] = __nv_fp8x2_e4m3(fp2_vals[1]);
u.elts[2] = __nv_fp8x2_e4m3(fp2_vals[2]);
u.elts[3] = __nv_fp8x2_e4m3(fp2_vals[3]);
u.elts[4] = __nv_fp8x2_e4m3(fp2_vals[4]);
u.elts[5] = __nv_fp8x2_e4m3(fp2_vals[5]);
u.elts[6] = __nv_fp8x2_e4m3(fp2_vals[6]);
u.elts[7] = __nv_fp8x2_e4m3(fp2_vals[7]);
fragment_d(Int<0>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[0]);
fragment_d(Int<1>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[1]);
fragment_d(Int<2>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[2]);
fragment_d(Int<3>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[3]);
fragment_d(Int<4>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[4]);
fragment_d(Int<5>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[5]);
fragment_d(Int<6>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[6]);
fragment_d(Int<7>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[7]);
fragment_d(Int<8>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[8]);
fragment_d(Int<9>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[9]);
fragment_d(Int<10>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[10]);
fragment_d(Int<11>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[11]);
fragment_d(Int<12>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[12]);
fragment_d(Int<13>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[13]);
fragment_d(Int<14>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[14]);
fragment_d(Int<15>{}) = cutlass::float_e4m3_t::bitcast(u.bytes[15]);
return fp8_sf_val;
}
template <
typename TensorS,
typename TensorP,
typename TensorD,
typename TensorSharedSF,
typename TensorSF,
typename TiledCopyG2R,
typename TiledCopyR2G,
typename TiledCopyR2S>
__inline__ __device__ void mxfp8_group_quant_tile(
TensorS& tensor_s,
TensorP& tensor_p,
TensorD& tensor_d,
TensorSharedSF& tensor_shared_sf,
TensorSF& tensor_sf,
int m,
TiledCopyG2R& tiled_copy_g2r,
TiledCopyR2G& tiled_copy_r2g,
TiledCopyR2S& tiled_copy_r2s) {
static_assert(
size(get<0>(typename TensorS::layout_type{})) == 128 && size(get<1>(typename TensorS::layout_type{})) == 128 &&
stride(get<1>(typename TensorS::layout_type{})) == 1);
static_assert(
size(get<0>(typename TensorD::layout_type{})) == 128 && size(get<1>(typename TensorD::layout_type{})) == 128 &&
stride(get<1>(typename TensorD::layout_type{})) == 1);
static_assert(
size(get<0>(typename TensorP::layout_type{})) == 128 && size(get<1>(typename TensorP::layout_type{})) == 128);
static_assert(
size(get<0>(typename TensorSharedSF::layout_type{})) == 128 &&
size(get<1>(typename TensorSharedSF::layout_type{})) == 4);
static_assert(
size(get<0>(typename TensorSF::layout_type{})) == 128 && size(get<1>(typename TensorSF::layout_type{})) == 4);
using Tiler_MN = typename TiledCopyG2R::Tiler_MN;
auto tiler_mn = Tiler_MN{};
static_assert(size<0>(tiler_mn) == 16 && size<1>(tiler_mn) == 128);
auto tiled_tensor_s = tiled_divide(tensor_s, tiler_mn);
auto tiled_tensor_p = tiled_divide(tensor_p, tiler_mn);
auto tiled_tensor_d = tiled_divide(tensor_d, tiler_mn);
static_assert(size<2>(tiled_tensor_s) == 1);
static_assert(size<2>(tiled_tensor_p) == 1);
static_assert(size<2>(tiled_tensor_d) == 1);
auto squeeze_tiled_tensor_s = take<0, 2>(tiled_tensor_s);
auto squeeze_tiled_tensor_p = take<0, 2>(tiled_tensor_p);
auto squeeze_tiled_tensor_d = take<0, 2>(tiled_tensor_d);
using SF_Tiler_MN = typename TiledCopyR2S::Tiler_MN;
auto sf_tiler_mn = SF_Tiler_MN{};
static_assert(size<0>(sf_tiler_mn) == 16 && size<1>(sf_tiler_mn) == 4);
auto tiled_tensor_sf = tiled_divide(tensor_sf, sf_tiler_mn);
auto tiled_tensor_shared_sf = tiled_divide(tensor_shared_sf, sf_tiler_mn);
auto squeeze_tiled_tensor_sf = take<0, 2>(tiled_tensor_sf);
auto squeeze_tiled_tensor_shared_sf = take<0, 2>(tiled_tensor_shared_sf);
constexpr int tile_loop_count = size<1>(tiled_tensor_s);
constexpr int rows_in_tile = 16;
// We don't need to clear shared memory
// clear(squeeze_tiled_tensor_shared_sf);
#pragma unroll 4
for (int t = 0; t < tile_loop_count; t++) {
if (t * rows_in_tile >= m) {
break;
}
auto current_copy_tile_s = tensor<0>(squeeze_tiled_tensor_s(_, t));
auto current_copy_tile_p = tensor<0>(squeeze_tiled_tensor_p(_, t));
auto current_copy_tile_d = tensor<0>(squeeze_tiled_tensor_d(_, t));
auto current_copy_tile_sf = tensor<0>(squeeze_tiled_tensor_sf(_, t));
auto current_copy_tile_shared_sf = tensor<0>(squeeze_tiled_tensor_shared_sf(_, t));
// Global to Register copy
auto thr_copy_g2r = tiled_copy_g2r.get_thread_slice(threadIdx.x);
auto thr_tile_g2r_s = thr_copy_g2r.partition_S(current_copy_tile_s);
auto thr_tile_g2r_p = thr_copy_g2r.partition_S(current_copy_tile_p);
auto input_fragment = make_fragment_like(thr_tile_g2r_s);
// Register to Global copy
auto thr_copy_r2g = tiled_copy_r2g.get_thread_slice(threadIdx.x);
auto thr_tile_r2g_d = thr_copy_r2g.partition_D(current_copy_tile_d);
auto thr_tile_r2g_p = thr_copy_r2g.partition_D(current_copy_tile_p);
auto output_fragment = make_fragment_like(thr_tile_r2g_d);
// Register to Shared copy
auto thr_copy_r2s = tiled_copy_r2s.get_thread_slice(threadIdx.x / 2);
auto thr_tile_r2s_shared_sf = thr_copy_r2s.partition_D(current_copy_tile_shared_sf);
auto shared_sf_fragment = make_fragment_like(thr_tile_r2s_shared_sf);
// CopyG2R & convert & CopyR2G
copy_if(tiled_copy_g2r, thr_tile_g2r_p, thr_tile_g2r_s, input_fragment);
uint8_t fp8_sf_val = cvt_warp_fp16_to_mxfp8(input_fragment, output_fragment);
copy_if(tiled_copy_r2g, thr_tile_r2g_p, output_fragment, thr_tile_r2g_d);
shared_sf_fragment[0] = fp8_sf_val;
// Before first copy r2s, clear shared memory and wait previous group
if (t == 0 && threadIdx.x == 0) {
// Wait for the group to have completed reading from shared memory.
cuda::ptx::cp_async_bulk_wait_group_read(cuda::ptx::n32_t<0>());
}
__syncthreads();
if (threadIdx.x % 2 == 0) {
copy(tiled_copy_r2s, shared_sf_fragment, thr_tile_r2s_shared_sf);
}
__syncthreads();
}
// Wait for shared memory writes to be visible to TMA engine.
cuda::ptx::fence_proxy_async(cuda::ptx::space_shared); // b)
__syncthreads();
if (threadIdx.x == 0) {
cuda::ptx::cp_async_bulk(
cuda::ptx::space_global,
cuda::ptx::space_shared,
squeeze_tiled_tensor_sf.data().get(),
squeeze_tiled_tensor_shared_sf.data().get(),
512);
// Wait for TMA transfer to have finished reading shared memory.
// Create a "bulk async-group" out of the previous bulk copy operation.
cuda::ptx::cp_async_bulk_commit_group();
}
__syncthreads();
}
template <typename T_IN, typename TiledCopyG2R, typename TiledCopyR2G, typename TiledCopyR2S>
__global__ void mxfp8_group_quant(
const T_IN* input,
const int* tokens_per_expert,
const int* expert_offsets,
const int* blockscale_offsets,
cutlass::float_e4m3_t* quant_output,
uint8_t* scale_factor,
int groups,
int k,
TiledCopyG2R tiled_copy_g2r,
TiledCopyR2G tiled_copy_r2g,
TiledCopyR2S tiled_copy_r2s) {
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 1000
__shared__ __align__(512) uint8_t shared_memory[512];
ScaleFactorTileLayout scale_factor_tile_layout{};
auto scale_factor_shared = make_tensor(
make_smem_ptr(shared_memory),
scale_factor_tile_layout); // ((_32,_4), _4):((_16,_4), _1)
// Transform Groupwise Schedule into Flatten Schedule
uint group_total_tiles = 0;
uint head_cta_id = 0;
for (int g = 0; g < groups; g++) {
int m = tokens_per_expert[g];
int64_t expert_offset = static_cast<int64_t>(expert_offsets[g]);
int64_t blockscale_offset = static_cast<int64_t>(blockscale_offsets[g]);
auto input_tensor = make_tensor(
make_gmem_ptr(input + expert_offset * k),
make_layout(make_shape(m, k), LayoutRight{})); // (M, K):(K, 1) half_t/bfloat16_t
auto quant_output_tensor = make_tensor(
make_gmem_ptr(quant_output + expert_offset * k),
make_layout(make_shape(m, k), LayoutRight{})); // (M, K):(K, 1) cutlass::float_e4m3_t
auto scale_factor_shape = make_shape(ceil_div(m, 128) * 128, k / 32);
auto scale_factor_layout = tile_to_shape(scale_factor_tile_layout, scale_factor_shape, LayoutRight{});
// layout<0>(layout<0>(scale_factor_layout)) (_32,_4):(_16,_4) -- static
// layout<1>(layout<0>(scale_factor_layout)) M_align_128 / 128 -- dynamic shape dynamic stride
// layout<0>(layout<1>(scale_factor_layout)) _4:_1 -- static
// layout<1>(layout<1>(scale_factor_layout)) (K / 32) / 4 : _512 -- dynamic shape static stride
// Reshape to zipped layout for 1D indexing
auto zipped_scale_factor_layout = make_layout(
make_layout(layout<0>(layout<0>(scale_factor_layout)), layout<0>(layout<1>(scale_factor_layout))),
make_layout(
layout<1>(layout<0>(scale_factor_layout)),
layout<1>(layout<1>(
scale_factor_layout)))); // (((_32,_4),_4),(M_align_128 / 128,(K / 32) / 4)):(((_16,_4),_1),(?,_512))
auto scale_factor_tensor =
make_tensor(make_gmem_ptr(scale_factor + blockscale_offset * (k / 32)), zipped_scale_factor_layout);
// Used for cases where M is not divisible by 128 (most scenarios).
auto input_shape = shape(input_tensor); // (M, K):(K, 1)
auto identity_tensor = make_identity_tensor(input_shape);
auto predict_tensor = cute::lazy::transform(identity_tensor, [&](auto c) { return elem_less(c, input_shape); });
// (_128, _128)
auto tiler = make_shape(Int<BLOCK_M>{}, Int<BLOCK_K>{});
auto tiled_input_tensor = zipped_divide(input_tensor, tiler); // ((128, 128), (cdiv(M, 128), cdiv(K, 128)))
auto tiled_quant_output_tensor =
zipped_divide(quant_output_tensor, tiler); // ((128, 128), (cdiv(M, 128), cdiv(K, 128)))
auto tiled_predict_tensor = zipped_divide(predict_tensor, tiler); // ((128, 128), (cdiv(M, 128), cdiv(K, 128)))
auto total_tiles = size<1>(tiled_input_tensor); // cdiv(M, 128) * cdiv(K, 128)
group_total_tiles += total_tiles;
auto blk_offset = (blockIdx.x + (gridDim.x - head_cta_id)) % gridDim.x;
head_cta_id = group_total_tiles % gridDim.x;
while (blk_offset < total_tiles) {
auto current_input_tile = tensor<0>(tiled_input_tensor(_, blk_offset));
auto current_quant_output_tile = tensor<0>(tiled_quant_output_tensor(_, blk_offset));
auto current_predict_tile = tensor<0>(tiled_predict_tensor(_, blk_offset));
auto current_scale_factor_tile = tensor<0>(scale_factor_tensor(_, blk_offset));
mxfp8_group_quant_tile<
decltype(current_input_tile),
decltype(current_predict_tile),
decltype(current_quant_output_tile),
decltype(scale_factor_shared),
decltype(current_scale_factor_tile),
TiledCopyG2R,
TiledCopyR2G,
TiledCopyR2S>(
current_input_tile,
current_predict_tile,
current_quant_output_tile,
scale_factor_shared,
current_scale_factor_tile,
m,
tiled_copy_g2r,
tiled_copy_r2g,
tiled_copy_r2s);
blk_offset += gridDim.x;
}
}
#endif
}
template <typename T_IN>
void launch_es_sm100_mxfp8_blockscaled_grouped_quant(
const T_IN* input,
const int* tokens_per_expert,
const int* expert_offsets,
const int* blockscale_offsets,
cutlass::float_e4m3_t* quant_output,
uint8_t* scale_factor,
int num_experts,
int k,
int sm_count,
cudaStream_t stream) {
ThrLayout thr_layout{};
ValLayout val_layout{};
SfR2SThrLayout r2s_thr_layout{};
SfR2SValLayout r2s_val_layout{};
using CopyOpG2R = UniversalCopy<cutlass::AlignedArray<T_IN, size(val_layout)>>;
using CopyAtomG2R = cute::Copy_Atom<CopyOpG2R, T_IN>;
auto tiled_copy_g2r = cute::make_tiled_copy(CopyAtomG2R{}, thr_layout, val_layout); // Tiler_MN: (16, 128)
using CopyOpR2G = UniversalCopy<cutlass::AlignedArray<cutlass::float_e4m3_t, size(val_layout)>>;
using CopyAtomR2G = cute::Copy_Atom<CopyOpR2G, cutlass::float_e4m3_t>;
auto tiled_copy_r2g = cute::make_tiled_copy(CopyAtomR2G{}, thr_layout, val_layout); // Tiler_MN: (16, 128)
using CopyOpR2S = UniversalCopy<cutlass::AlignedArray<uint8_t, size(r2s_val_layout)>>;
using CopyAtomR2S = cute::Copy_Atom<CopyOpR2S, uint8_t>;
auto tiled_copy_r2s = cute::make_tiled_copy(CopyAtomR2S{}, r2s_thr_layout, r2s_val_layout); // Tiler_MN: (16, 4)
int max_active_blocks_per_sm = -1;
auto error_code = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
&max_active_blocks_per_sm,
mxfp8_group_quant<T_IN, decltype(tiled_copy_g2r), decltype(tiled_copy_r2g), decltype(tiled_copy_r2s)>,
THREAD_BLOCK_SIZE,
0);
host::RuntimeCheck(error_code == cudaSuccess, "cudaOccupancyMaxActiveBlocksPerMultiprocessor failed");
dim3 grid(sm_count * max_active_blocks_per_sm, 1, 1);
dim3 block(THREAD_BLOCK_SIZE, 1, 1);
mxfp8_group_quant<T_IN, decltype(tiled_copy_g2r), decltype(tiled_copy_r2g), decltype(tiled_copy_r2s)>
<<<grid, block, 0, stream>>>(
input,
tokens_per_expert,
expert_offsets,
blockscale_offsets,
quant_output,
scale_factor,
num_experts,
k,
tiled_copy_g2r,
tiled_copy_r2g,
tiled_copy_r2s);
}
} // namespace expert_specialization
template <typename DType>
struct EsSm100MXFP8BlockscaledGroupQuant {
static void
run(const tvm::ffi::TensorView input,
const tvm::ffi::TensorView tokens_per_expert,
const tvm::ffi::TensorView expert_offsets,
const tvm::ffi::TensorView blockscale_offsets,
tvm::ffi::TensorView quant_output,
tvm::ffi::TensorView scale_factor) {
using namespace host;
auto N = SymbolicSize{"num_tokens"};
auto D = SymbolicSize{"hidden_size"};
auto G = SymbolicSize{"num_experts"};
auto N_SF_Alinged = SymbolicSize{"num_tokens_sf_aligned"};
auto D_SF = SymbolicSize{"hidden_size_sf"};
auto device = SymbolicDevice{};
device.set_options<kDLCUDA>();
TensorMatcher({N, D}).with_strides({D, 1}).with_dtype<DType>().with_device(device).verify(input);
TensorMatcher({G}).with_dtype<int>().with_device(device).verify(tokens_per_expert);
TensorMatcher({G}).with_dtype<int>().with_device(device).verify(expert_offsets);
TensorMatcher({G}).with_dtype<int>().with_device(device).verify(blockscale_offsets);
RuntimeCheck(D.unwrap() % 128 == 0, "k must align to 128");
TensorMatcher({N, D}).with_strides({D, 1}).with_dtype<fp8_e4m3_t>().with_device(device).verify(quant_output);
TensorMatcher({N_SF_Alinged, D_SF}).with_dtype<uint8_t>().with_device(device).verify(scale_factor);
RuntimeCheck(D.unwrap() / 32 == D_SF.unwrap(), "Scale factor K should be hidden_size / 32");
cudaStream_t stream = LaunchKernel::resolve_device(device.unwrap());
expert_specialization::launch_es_sm100_mxfp8_blockscaled_grouped_quant<DType>(
reinterpret_cast<const DType*>(input.data_ptr()),
reinterpret_cast<const int*>(tokens_per_expert.data_ptr()),
reinterpret_cast<const int*>(expert_offsets.data_ptr()),
reinterpret_cast<const int*>(blockscale_offsets.data_ptr()),
reinterpret_cast<cutlass::float_e4m3_t*>(quant_output.data_ptr()),
reinterpret_cast<uint8_t*>(scale_factor.data_ptr()),
static_cast<int>(G.unwrap()),
static_cast<int>(D.unwrap()),
runtime::get_sm_count(device.unwrap().device_id),
stream);
}
};
@@ -0,0 +1,217 @@
#pragma once
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/runtime.cuh>
#include <sgl_kernel/utils.cuh>
#include "cute/tensor.hpp"
#include "es_sm100_mxfp8_blockscaled_moe_group_gemm_functor.cuh"
#include "es_sm100_mxfp8_blockscaled_moe_group_gemm_traits.cuh"
namespace expert_specialization {
using namespace host;
template <typename GemmTraits>
void es_sm100_mxfp8_blockscaled_moe_group_gemm_pre_compute(
tvm::ffi::TensorView b,
tvm::ffi::TensorView sfb,
tvm::ffi::TensorView expert_offsets,
tvm::ffi::TensorView blockscale_offsets,
tvm::ffi::TensorView b_ptrs,
tvm::ffi::TensorView sfb_ptrs,
tvm::ffi::TensorView d,
tvm::ffi::TensorView d_ptrs,
int num_experts,
int m,
int k,
cudaStream_t stream) {
using OffsetFunctor = Sm100Mxfp8BlockScaledMoeGroupGemmOffsetFunctor<GemmTraits>;
using ElementB = typename OffsetFunctor::ElementB;
using ElementSF = typename OffsetFunctor::ElementSF;
using ElementD = typename OffsetFunctor::ElementD;
host::RuntimeCheck(num_experts <= 1024, "num_experts more than 1024");
OffsetFunctor offset_functor(
reinterpret_cast<int*>(expert_offsets.data_ptr()),
reinterpret_cast<int*>(blockscale_offsets.data_ptr()),
reinterpret_cast<ElementB*>(b.data_ptr()),
reinterpret_cast<ElementSF*>(sfb.data_ptr()),
reinterpret_cast<ElementD*>(d.data_ptr()),
reinterpret_cast<ElementB**>(b_ptrs.data_ptr()),
reinterpret_cast<ElementSF**>(sfb_ptrs.data_ptr()),
reinterpret_cast<ElementD**>(d_ptrs.data_ptr()));
sm100_mxfp8_blockscaled_moe_group_gemm_pre_compute_kernel<<<1, num_experts, 0, stream>>>(offset_functor, m, k);
}
template <typename GemmTraits>
void es_sm100_mxfp8_blockscaled_moe_group_gemm(
tvm::ffi::TensorView a,
tvm::ffi::TensorView sfa,
tvm::ffi::TensorView tokens_per_expert,
tvm::ffi::TensorView b_ptrs,
tvm::ffi::TensorView sfb_ptrs,
tvm::ffi::TensorView d_ptrs,
tvm::ffi::TensorView workspace,
int num_experts,
int m,
int n,
int k,
int device_id,
int sm_count,
cudaStream_t stream) {
using Gemm = typename GemmTraits::Gemm;
using ElementA = typename Gemm::ElementA;
using ElementB = typename Gemm::ElementB;
using ElementSF = typename GemmTraits::ElementSF;
using ElementD = typename GemmTraits::ElementD;
cutlass::KernelHardwareInfo hw_info;
hw_info.device_id = device_id;
hw_info.sm_count = sm_count;
hw_info.cluster_shape = GemmTraits::MMAConfig::preferred_cluster;
hw_info.cluster_shape_fallback = GemmTraits::MMAConfig::fallback_cluster;
typename Gemm::Arguments arguments = {
cutlass::gemm::GemmUniversalMode::kGrouped,
{m, n, k, num_experts, reinterpret_cast<int*>(tokens_per_expert.data_ptr())},
{reinterpret_cast<const ElementA*>(a.data_ptr()),
reinterpret_cast<const ElementB**>(b_ptrs.data_ptr()),
reinterpret_cast<const ElementSF*>(sfa.data_ptr()),
reinterpret_cast<const ElementSF**>(sfb_ptrs.data_ptr())},
{{}, nullptr, nullptr, reinterpret_cast<ElementD**>(d_ptrs.data_ptr()), nullptr},
hw_info,
{} // Scheduler
};
Gemm gemm;
auto can_implement_status = gemm.can_implement(arguments);
host::RuntimeCheck(can_implement_status == cutlass::Status::kSuccess, "Can not implement MoE Group GEMM");
auto status = gemm.initialize(arguments, reinterpret_cast<uint8_t*>(workspace.data_ptr()), stream);
host::RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to initialize MoE Group GEMM");
status = gemm.run(stream, nullptr);
host::RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to run MoE Group GEMM");
}
template <typename DType> // CUTLASS dtype
void es_sm100_mxfp8_blockscaled_moe_group_gemm_dispatch_dtype(
tvm::ffi::TensorView a,
tvm::ffi::TensorView b,
tvm::ffi::TensorView sfa,
tvm::ffi::TensorView sfb,
tvm::ffi::TensorView expert_offsets,
tvm::ffi::TensorView blockscale_offsets,
tvm::ffi::TensorView tokens_per_expert,
tvm::ffi::TensorView b_ptrs,
tvm::ffi::TensorView sfb_ptrs,
tvm::ffi::TensorView d,
tvm::ffi::TensorView d_ptrs,
tvm::ffi::TensorView workspace,
int num_experts,
int m,
int n,
int k,
int device_id,
int sm_count,
cudaStream_t stream) {
using GemmTraits = ExpertSpecializationSm100MXFP8BlockscaledMoeGroupGemmTraits<MMA2SMConfig, DType>;
es_sm100_mxfp8_blockscaled_moe_group_gemm_pre_compute<GemmTraits>(
b, sfb, expert_offsets, blockscale_offsets, b_ptrs, sfb_ptrs, d, d_ptrs, num_experts, m, k, stream);
es_sm100_mxfp8_blockscaled_moe_group_gemm<GemmTraits>(
a,
sfa,
tokens_per_expert,
b_ptrs,
sfb_ptrs,
d_ptrs,
workspace,
num_experts,
m,
n,
k,
device_id,
sm_count,
stream);
}
} // namespace expert_specialization
template <typename DType>
struct EsSm100MXFP8BlockscaledMoeGroupGemm {
static void
run(tvm::ffi::TensorView a,
tvm::ffi::TensorView b,
tvm::ffi::TensorView sfa,
tvm::ffi::TensorView sfb,
tvm::ffi::TensorView expert_offsets,
tvm::ffi::TensorView blockscale_offsets,
tvm::ffi::TensorView tokens_per_expert,
tvm::ffi::TensorView b_ptrs,
tvm::ffi::TensorView sfb_ptrs,
tvm::ffi::TensorView d,
tvm::ffi::TensorView d_ptrs,
tvm::ffi::TensorView workspace) {
using namespace host;
auto num_tokens = SymbolicSize{"num_tokens"};
auto num_sf_tokens = SymbolicSize{"num_sf_tokens"};
auto hidden_size = SymbolicSize{"hidden_size"};
auto num_experts = SymbolicSize{"num_experts"};
auto M = SymbolicSize{"M"};
auto K = SymbolicSize{"K"};
auto M_SF = SymbolicSize{"M_SF"};
auto K_SF = SymbolicSize{"K_SF"};
auto device = SymbolicDevice{};
device.set_options<kDLCUDA>();
TensorMatcher({num_experts, M, K}).with_dtype<fp8_e4m3_t>().with_device(device).verify(a);
TensorMatcher({num_tokens, K}).with_dtype<fp8_e4m3_t>().with_device(device).verify(b);
TensorMatcher({num_experts, M_SF, K_SF}).with_dtype<uint8_t>().with_device(device).verify(sfa);
TensorMatcher({num_sf_tokens, K_SF}).with_dtype<uint8_t>().with_device(device).verify(sfb);
RuntimeCheck(K.unwrap() % 128 == 0, "K should align 128");
RuntimeCheck(K.unwrap() / 32 == K_SF.unwrap(), "K dimension mismatch");
TensorMatcher({num_experts}).with_dtype<int>().with_device(device).verify(expert_offsets);
TensorMatcher({num_experts}).with_dtype<int>().with_device(device).verify(blockscale_offsets);
TensorMatcher({num_experts}).with_dtype<int>().with_device(device).verify(tokens_per_expert);
TensorMatcher({num_experts}).with_dtype<int64_t>().with_device(device).verify(b_ptrs);
TensorMatcher({num_experts}).with_dtype<int64_t>().with_device(device).verify(sfb_ptrs);
TensorMatcher({num_experts}).with_dtype<int64_t>().with_device(device).verify(d_ptrs);
// Check output
TensorMatcher({num_tokens, M}).with_strides({M, 1}).with_dtype<DType>().with_device(device).verify(d);
cudaStream_t stream = LaunchKernel::resolve_device(device.unwrap());
int device_id = device.unwrap().device_id;
if constexpr (std::is_same_v<DType, bf16_t> || std::is_same_v<DType, fp16_t>) {
using CUTLASS_DTYPE = std::conditional_t<std::is_same_v<DType, bf16_t>, cutlass::bfloat16_t, cutlass::half_t>;
expert_specialization::es_sm100_mxfp8_blockscaled_moe_group_gemm_dispatch_dtype<CUTLASS_DTYPE>(
a,
b,
sfa,
sfb,
expert_offsets,
blockscale_offsets,
tokens_per_expert,
b_ptrs,
sfb_ptrs,
d,
d_ptrs,
workspace,
static_cast<int>(num_experts.unwrap()),
static_cast<int>(M.unwrap()),
static_cast<int>(num_tokens.unwrap()),
static_cast<int>(K.unwrap()),
device_id,
static_cast<int>(runtime::get_sm_count(device_id)),
stream);
} else {
Panic("Unsupported dtype");
}
}
};
@@ -0,0 +1,64 @@
#pragma once
#include "cute/tensor.hpp"
#include "es_sm100_mxfp8_blockscaled_moe_group_gemm_traits.cuh"
#include <cuda.h>
namespace expert_specialization {
using namespace cute;
template <typename GemmTraits>
struct Sm100Mxfp8BlockScaledMoeGroupGemmOffsetFunctor {
using ElementB = typename GemmTraits::Gemm::ElementB;
using ElementSF = typename GemmTraits::ElementSF;
using ElementD = typename GemmTraits::ElementD;
// Input
int* expert_offsets{nullptr};
int* blockscale_offsets{nullptr};
// Output
ElementB* b_base{nullptr};
ElementSF* sfb_base{nullptr};
ElementD* d_base{nullptr};
ElementB** b_offsets{nullptr};
ElementSF** sfb_offsets{nullptr};
ElementD** d_offsets{nullptr};
Sm100Mxfp8BlockScaledMoeGroupGemmOffsetFunctor() = default;
Sm100Mxfp8BlockScaledMoeGroupGemmOffsetFunctor(
int* _expert_offsets,
int* _blockscale_offsets,
ElementB* _b_base,
ElementSF* _sfb_base,
ElementD* _d_base,
ElementB** _b_offsets,
ElementSF** _sfb_offsets,
ElementD** _d_offsets)
: expert_offsets{_expert_offsets},
blockscale_offsets{_blockscale_offsets},
b_base(_b_base),
sfb_base(_sfb_base),
d_base(_d_base),
b_offsets(_b_offsets),
sfb_offsets(_sfb_offsets),
d_offsets(_d_offsets) {}
void CUTE_DEVICE operator()(int expert_id, int m, int k) {
int64_t expert_offset = static_cast<int64_t>(expert_offsets[expert_id]);
int64_t blockscale_offset = static_cast<int64_t>(blockscale_offsets[expert_id]);
int64_t b_stride = expert_offset * k;
int64_t sfb_stride = blockscale_offset * (k / 32);
int64_t d_stride = expert_offset * m;
b_offsets[expert_id] = b_base + b_stride;
sfb_offsets[expert_id] = sfb_base + sfb_stride;
d_offsets[expert_id] = d_base + d_stride;
}
};
template <typename OffsetFunctor>
__global__ void sm100_mxfp8_blockscaled_moe_group_gemm_pre_compute_kernel(OffsetFunctor offset_functor, int m, int k) {
int expert_id = static_cast<int>(threadIdx.x);
offset_functor(expert_id, m, k);
}
} // namespace expert_specialization
@@ -0,0 +1,123 @@
#pragma once
// Misc
#include "cute/tensor.hpp"
#include "cutlass/arch/arch.h"
#include "cutlass/arch/mma.h"
#include "cutlass/cutlass.h"
#include "cutlass/detail/sm100_blockscaled_layout.hpp"
#include "cutlass/epilogue/dispatch_policy.hpp"
#include "cutlass/gemm/dispatch_policy.hpp"
#include "cutlass/gemm/group_array_problem_shape.hpp"
#include "cutlass/layout/layout.h"
#include "cutlass/numeric_conversion.h"
#include "cutlass/numeric_size.h"
// Collective Builder
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/epilogue/fusion/sm90_callbacks_tma_warpspecialized.hpp"
#include "cutlass/epilogue/thread/activation.h"
#include "cutlass/gemm/collective/collective_builder.hpp"
// Integration
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/gemm/kernel/gemm_universal.hpp"
namespace expert_specialization {
using namespace cute;
// Different configs for 1SM and 2SM MMA kernel
struct MMA2SMConfig {
using MmaTileShape = Shape<_256, _128, _128>;
using KernelSchedule = cutlass::gemm::KernelPtrArrayTmaWarpSpecialized2SmMxf8f6f4Sm100;
using EpilogueSchedule = cutlass::epilogue::PtrArrayTmaWarpSpecialized2Sm;
const static dim3 preferred_cluster;
const static dim3 fallback_cluster;
};
const dim3 MMA2SMConfig::preferred_cluster(4, 1, 1);
const dim3 MMA2SMConfig::fallback_cluster(2, 1, 1);
template <typename _MMAConfig, typename OutputDtype>
struct ExpertSpecializationSm100MXFP8BlockscaledMoeGroupGemmTraits {
using MMAConfig = _MMAConfig;
using ElementInput = cutlass::float_e4m3_t;
using ElementOutput = OutputDtype;
using ProblemShape = cutlass::gemm::MoEProblemShape<Shape<int, int, int>>; // <M,N,K> per group
// A matrix configuration
using ElementA = cutlass::mx_float8_t<ElementInput>;
using LayoutA = cutlass::layout::RowMajor;
constexpr static int AlignmentA = 16;
// B matrix configuration
using ElementB = cutlass::mx_float8_t<ElementInput>;
using LayoutB = cutlass::layout::ColumnMajor;
constexpr static int AlignmentB = 16;
// C/D matrix configuration
using ElementC = void;
using ElementD = ElementOutput;
using LayoutC = cutlass::layout::ColumnMajor;
using LayoutD = cutlass::layout::ColumnMajor;
constexpr static int AlignmentC = 128 / cutlass::sizeof_bits<ElementD>::value;
constexpr static int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
using ElementAccumulator = float;
static constexpr auto RoundStyle = cutlass::FloatRoundStyle::round_to_nearest;
using CustomEVTIdentity = // acc
cutlass::epilogue::fusion::Sm90EVT<
cutlass::epilogue::fusion::
Sm90Compute<cutlass::epilogue::thread::Identity, ElementD, ElementAccumulator, RoundStyle>,
cutlass::epilogue::fusion::Sm90AccFetch>;
// Core kernel configurations
using ArchTag = cutlass::arch::Sm100;
using OperatorClass = cutlass::arch::OpClassBlockScaledTensorOp;
using StageCountType = cutlass::gemm::collective::StageCountAuto;
// Runtime Cluster Shape
using ClusterShape = Shape<int32_t, int32_t, _1>;
// Define Epilogue
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
ArchTag,
OperatorClass,
typename MMAConfig::MmaTileShape,
ClusterShape,
cutlass::epilogue::collective::EpilogueTileAuto,
ElementAccumulator,
ElementAccumulator,
ElementC,
LayoutC*,
AlignmentC,
ElementD,
LayoutD*,
AlignmentD,
typename MMAConfig::EpilogueSchedule,
CustomEVTIdentity>::CollectiveOp;
// Define Mainloop
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
ArchTag,
OperatorClass,
ElementA,
LayoutA,
AlignmentA,
ElementB,
LayoutB*,
AlignmentB,
ElementAccumulator,
typename MMAConfig::MmaTileShape,
ClusterShape,
cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
sizeof(typename CollectiveEpilogue::SharedStorage))>,
typename MMAConfig::KernelSchedule>::CollectiveOp;
// Define GemmKernel
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop, CollectiveEpilogue>;
using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
using ElementSF = typename GemmKernel::ElementSF;
};
} // namespace expert_specialization
@@ -0,0 +1,582 @@
/* Copyright 2025 SGLang Team. All Rights Reserved.
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.
==============================================================================*/
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/utils.cuh>
#include <tvm/ffi/container/tensor.h>
#include <algorithm>
#ifndef WARP_SIZE
#define WARP_SIZE 32
#endif
#define CEILDIV(x, y) (((x) + (y) - 1) / (y))
#define VEC_SIZE 4
using Vec = int4;
inline uint32_t next_pow2(uint32_t x) noexcept {
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
namespace moe {
__device__ __forceinline__ int warp_exclusive_scan(int v, unsigned mask = 0xffffffffu) {
int original = v;
#pragma unroll
for (int offset = 1; offset < WARP_SIZE; offset <<= 1) {
int n = __shfl_up_sync(mask, v, offset);
if ((threadIdx.x & (WARP_SIZE - 1)) >= offset) v += n;
}
return v - original;
}
template <typename scalar_t>
__global__ void count_and_sort_expert_tokens_kernel(
const scalar_t* __restrict__ topk_ids,
int32_t* __restrict__ sorted_token_ids,
int32_t* __restrict__ cumsum_buffer,
size_t numel) {
const size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
const size_t stride = blockDim.x * gridDim.x;
for (size_t i = tid; i < numel; i += stride) {
int32_t expert_id = topk_ids[i] + 1;
int32_t rank_post_pad = atomicAdd(&cumsum_buffer[expert_id], 1);
sorted_token_ids[rank_post_pad] = i;
}
}
template <typename scalar_t>
__global__ void moe_align_block_size_kernel(
const scalar_t* __restrict__ topk_ids,
int32_t* __restrict__ sorted_token_ids,
int32_t* __restrict__ expert_ids,
int32_t* __restrict__ total_tokens_post_pad,
int32_t num_experts,
int32_t block_size,
size_t numel,
int32_t* __restrict__ cumsum,
bool pad_sorted_token_ids,
const int32_t scan_size,
int32_t max_num_tokens_padded) {
// Use a separate thread block to populate sorted_token_ids
if (blockIdx.x == 1) {
if (pad_sorted_token_ids) {
Vec fill_vec;
fill_vec.x = fill_vec.y = fill_vec.z = fill_vec.w = numel;
int32_t total_vecs = (max_num_tokens_padded + VEC_SIZE - 1) / VEC_SIZE;
Vec* out_ptr = reinterpret_cast<Vec*>(sorted_token_ids);
for (int32_t i = threadIdx.x; i < total_vecs; i += blockDim.x) {
out_ptr[i] = fill_vec;
}
}
return;
}
extern __shared__ int32_t smem[];
int32_t* shared_counts = smem; // [num_experts]
int32_t* prefix = shared_counts + num_experts; // [num_experts + 1]
int32_t* scan_buf = prefix + num_experts + 1; // [scan_size]
__shared__ int32_t s_total_tokens_post_pad;
const size_t tid = threadIdx.x;
const size_t stride = blockDim.x;
if (tid < num_experts) {
shared_counts[tid] = 0;
}
__syncthreads();
for (size_t i = tid; i < numel; i += stride) {
int expert_id = topk_ids[i] + 1;
atomicAdd(&shared_counts[expert_id], 1);
}
__syncthreads();
int32_t padded_count = 0;
if (tid < num_experts) {
int32_t count = shared_counts[tid];
padded_count = (count + block_size - 1) / block_size * block_size;
scan_buf[tid] = padded_count;
}
#ifndef __CUDA_ARCH__ // HIP
if (tid >= num_experts && tid < scan_size) {
scan_buf[tid] = 0;
}
__syncthreads();
// Blelloch scan
int offset = 1;
#pragma unroll
for (int d = scan_size >> 1; d > 0; d >>= 1) {
if (tid < d) {
int ai = offset * (2 * tid + 1) - 1;
int bi = offset * (2 * tid + 2) - 1;
scan_buf[bi] += scan_buf[ai];
}
offset <<= 1;
__syncthreads();
}
// down-sweep
if (tid == 0) {
prefix[num_experts] = scan_buf[scan_size - 1];
scan_buf[scan_size - 1] = 0;
}
__syncthreads();
#pragma unroll
for (int d = 1; d < scan_size; d <<= 1) {
offset >>= 1;
if (tid < d) {
int ai = offset * (2 * tid + 1) - 1;
int bi = offset * (2 * tid + 2) - 1;
if (bi < scan_size) {
int temp = scan_buf[ai];
scan_buf[ai] = scan_buf[bi];
scan_buf[bi] += temp;
}
}
__syncthreads();
}
if (tid < num_experts) {
prefix[tid] = scan_buf[tid];
}
if (tid == 0) {
s_total_tokens_post_pad = prefix[num_experts];
*total_tokens_post_pad = s_total_tokens_post_pad;
}
__syncthreads();
#else // CUDA
// Intra warp prefix sum
int32_t* warp_sums = scan_buf + scan_size; // [<= 32]
const int warp_id = tid / WARP_SIZE;
const int lane_id = tid & (WARP_SIZE - 1);
const int num_warps_for_scan = (scan_size + WARP_SIZE - 1) / WARP_SIZE;
const int warp_sum = warp_exclusive_scan(padded_count) + padded_count;
if (lane_id == WARP_SIZE - 1) warp_sums[warp_id] = warp_sum;
__syncthreads();
// warp0 accumulate all the block's prefix sum
if (tid < WARP_SIZE) {
int val = (tid < num_warps_for_scan) ? warp_sums[tid] : 0;
int incl = warp_exclusive_scan(val) + val;
warp_sums[tid] = incl;
}
__syncthreads();
// Every thread obtains the whole block's sum
if (tid == 0) {
prefix[num_experts] = warp_sums[num_warps_for_scan - 1];
s_total_tokens_post_pad = prefix[num_experts];
*total_tokens_post_pad = s_total_tokens_post_pad;
}
__syncthreads();
// Fill 0 to scan_buf extended area (tid >= num_expert)
if (tid >= num_experts && tid < scan_size) scan_buf[tid] = 0;
__syncthreads();
// Perform 2 level exclusive-prefix-sum to scan_buf
int v = (tid < scan_size) ? scan_buf[tid] : 0;
int pre = warp_exclusive_scan(v);
if (lane_id == WARP_SIZE - 1) warp_sums[warp_id] = pre + v;
__syncthreads();
if (warp_id == 0) {
int val = (lane_id < num_warps_for_scan) ? warp_sums[lane_id] : 0;
warp_sums[lane_id] = warp_exclusive_scan(val);
}
__syncthreads();
int offset = warp_sums[warp_id];
if (tid < scan_size) scan_buf[tid] = pre + offset;
__syncthreads();
// Write prefix[0..num_experts - 1] and cumsum
if (tid < num_experts) prefix[tid] = scan_buf[tid];
#endif
if (tid <= num_experts) {
cumsum[tid] = prefix[tid];
}
// fill expert_ids
const int32_t num_blocks = s_total_tokens_post_pad / block_size;
for (int32_t i = tid; i < num_blocks; i += stride) {
int32_t block_start = i * block_size;
int left = 0, right = num_experts;
while (left < right) {
int mid = (left + right) >> 1;
if (prefix[mid] <= block_start) {
left = mid + 1;
} else {
right = mid;
}
}
expert_ids[i] = left - 2;
}
}
template <typename scalar_t, int32_t fill_threads>
__global__ void moe_align_block_size_small_batch_expert_kernel(
const scalar_t* __restrict__ topk_ids,
int32_t* __restrict__ sorted_token_ids,
int32_t* __restrict__ expert_ids,
int32_t* __restrict__ total_tokens_post_pad,
int32_t num_experts,
int32_t block_size,
size_t numel,
bool pad_sorted_token_ids,
int32_t max_num_tokens_padded) {
// Adapted from
// https://github.com/vllm-project/vllm/pull/29642/files#diff-5647b1413f4ae9aacba904eca8f8a8aee9079321eadff4c10101a2c6962dcc53R226
// Use an additional group of threads to fill sorted_token_ids.
// Since the kernel will use sorted_token_ids afterward,
// we fill sorted_token_ids within the same threadblock to make
// synchronization easier.
if (threadIdx.x < fill_threads) {
// Initialize sorted_token_ids with numel
if (pad_sorted_token_ids) {
for (int32_t it = threadIdx.x; it < max_num_tokens_padded; it += fill_threads) {
sorted_token_ids[it] = numel;
}
}
// Three __syncthreads() corresponding to the other threads
__syncthreads();
__syncthreads();
__syncthreads();
return;
}
const size_t tid = threadIdx.x - fill_threads;
const size_t stride = blockDim.x - fill_threads;
extern __shared__ int32_t shared_mem[];
int32_t* cumsum = shared_mem;
int32_t* tokens_cnts = (int32_t*)(shared_mem + num_experts + 1);
for (int i = 0; i < num_experts; ++i) {
tokens_cnts[(tid + 1) * num_experts + i] = 0;
}
for (size_t i = tid; i < numel; i += stride) {
int32_t expert_id = topk_ids[i] + 1;
++tokens_cnts[(tid + 1) * num_experts + expert_id];
}
__syncthreads();
if (tid < num_experts) {
tokens_cnts[tid] = 0;
for (int i = 1; i <= stride; ++i) {
tokens_cnts[i * num_experts + tid] += tokens_cnts[(i - 1) * num_experts + tid];
}
}
__syncthreads();
if (tid == 0) {
cumsum[0] = 0;
for (int i = 1; i <= num_experts; ++i) {
cumsum[i] = cumsum[i - 1] + CEILDIV(tokens_cnts[stride * num_experts + i - 1], block_size) * block_size;
}
*total_tokens_post_pad = static_cast<int32_t>(cumsum[num_experts]);
}
__syncthreads();
if (tid < num_experts) {
for (int i = cumsum[tid]; i < cumsum[tid + 1]; i += block_size) {
expert_ids[i / block_size] = tid - 1;
}
}
for (size_t i = tid; i < numel; i += stride) {
int32_t expert_id = topk_ids[i] + 1;
int32_t rank_post_pad = tokens_cnts[tid * num_experts + expert_id] + cumsum[expert_id];
sorted_token_ids[rank_post_pad] = i;
++tokens_cnts[tid * num_experts + expert_id];
}
}
// v2 kernel: supports >1024 experts via EXPERTS_PER_THREAD templating
// and a two-level warp scan (no cub dependency). Uses the same +1 offset
// convention as the original kernel (topk_ids shifted by +1 so -1 maps to 0).
// Launched with <<<2, 1024>>>: block 1 fills sorted_token_ids in parallel
// with block 0 doing the alignment compute.
//
// With 1024 threads and EXPERTS_PER_THREAD=4, covers at most 4096 expert
// indices. Since num_experts includes the +1 offset bucket, this supports
// up to 4095 real experts.
template <typename scalar_t, int EXPERTS_PER_THREAD>
__global__ void moe_align_block_size_kernel_v2(
const scalar_t* __restrict__ topk_ids,
int32_t* __restrict__ sorted_token_ids,
int32_t* __restrict__ expert_ids,
int32_t* __restrict__ total_tokens_post_pad,
int32_t num_experts,
int32_t padded_num_experts,
int32_t block_size,
size_t numel,
int32_t* __restrict__ cumsum,
bool pad_sorted_token_ids,
int32_t max_num_tokens_padded) {
// Use a separate thread block to populate sorted_token_ids
if (blockIdx.x == 1) {
if (pad_sorted_token_ids) {
Vec fill_vec;
fill_vec.x = fill_vec.y = fill_vec.z = fill_vec.w = numel;
int32_t total_vecs = (max_num_tokens_padded + VEC_SIZE - 1) / VEC_SIZE;
Vec* out_ptr = reinterpret_cast<Vec*>(sorted_token_ids);
for (int32_t i = threadIdx.x; i < total_vecs; i += blockDim.x) {
out_ptr[i] = fill_vec;
}
}
return;
}
extern __shared__ int32_t smem[];
// Layout: shared_counts[padded_num_experts] | warp_sums[WARP_SIZE]
int32_t* shared_counts = smem;
int32_t* warp_sums = smem + padded_num_experts;
const size_t tid = threadIdx.x;
const int warp_id = tid / WARP_SIZE;
const int lane_id = tid & (WARP_SIZE - 1);
// Phase 1: Zero shared counts and count tokens per expert
const int my_start = tid * EXPERTS_PER_THREAD;
for (size_t i = tid; i < padded_num_experts; i += blockDim.x) {
shared_counts[i] = 0;
}
__syncthreads();
for (size_t i = tid; i < numel; i += blockDim.x) {
int expert_id = topk_ids[i] + 1; // +1 offset convention
if (expert_id < num_experts) {
atomicAdd(&shared_counts[expert_id], 1);
}
}
__syncthreads();
// Phase 2: Compute padded counts and two-level warp exclusive prefix sum
int32_t local_padded[EXPERTS_PER_THREAD];
int32_t thread_sum = 0;
for (int i = 0; i < EXPERTS_PER_THREAD; ++i) {
int eid = my_start + i;
if (eid < num_experts) {
local_padded[i] = CEILDIV(shared_counts[eid], block_size) * block_size;
} else {
local_padded[i] = 0;
}
thread_sum += local_padded[i];
}
// Level 1: intra-warp exclusive scan on thread_sum
int32_t warp_prefix = warp_exclusive_scan(thread_sum);
int32_t warp_total = warp_prefix + thread_sum;
if (lane_id == WARP_SIZE - 1) warp_sums[warp_id] = warp_total;
__syncthreads();
// Level 2: warp 0 scans the per-warp totals
const int num_warps = (blockDim.x + WARP_SIZE - 1) / WARP_SIZE;
if (tid < WARP_SIZE) {
int val = (tid < num_warps) ? warp_sums[tid] : 0;
warp_sums[tid] = warp_exclusive_scan(val);
}
__syncthreads();
// Combine: thread_prefix = warp_sums[warp_id] + warp_prefix
int32_t thread_prefix = warp_sums[warp_id] + warp_prefix;
// Local sequential prefix sum within each thread's expert group
int32_t running = 0;
for (int i = 0; i < EXPERTS_PER_THREAD; ++i) {
int eid = my_start + i;
if (eid <= num_experts) {
cumsum[eid] = thread_prefix + running;
}
running += local_padded[i];
}
// Last thread writes total
if (tid == blockDim.x - 1) {
cumsum[num_experts] = thread_prefix + thread_sum;
*total_tokens_post_pad = thread_prefix + thread_sum;
}
__syncthreads();
// Phase 3: Fill expert_ids (eid - 1 to match sgl-kernel convention)
for (int i = 0; i < EXPERTS_PER_THREAD; ++i) {
int eid = my_start + i;
if (eid < num_experts) {
for (int j = cumsum[eid]; j < cumsum[eid + 1]; j += block_size) {
expert_ids[j / block_size] = eid - 1;
}
}
}
}
} // namespace moe
namespace {
template <typename scalar_t>
struct MoeAlignBlockSizeKernel {
static void
run(tvm::ffi::TensorView topk_ids,
int64_t num_experts,
int64_t block_size,
tvm::ffi::TensorView sorted_token_ids,
tvm::ffi::TensorView expert_ids,
tvm::ffi::TensorView num_tokens_post_pad,
tvm::ffi::TensorView cumsum_buffer,
bool pad_sorted_token_ids) {
using namespace host;
auto device = topk_ids.device();
const cudaStream_t stream = LaunchKernel::resolve_device(device);
int threads = 1024;
threads = ((threads + WARP_SIZE - 1) / WARP_SIZE) * WARP_SIZE;
int64_t max_num_tokens_padded = sorted_token_ids.size(0);
// num_experts from Python is actual_num_experts + 1 (for EP offset convention).
// The v2 kernel (>1024 experts) uses 1024 threads with EXPERTS_PER_THREAD up
// to 8, covering at most 8192 expert indices. This supports up to 8191 real
// experts, sufficient for LoRA virtual experts (num_moe_experts * max_loras).
RuntimeCheck(num_experts <= 8192, "moe_align_block_size: num_experts must be <= 8192, got ", num_experts);
const scalar_t* topk_ids_ptr = static_cast<const scalar_t*>(topk_ids.data_ptr());
int32_t* sorted_token_ids_ptr = static_cast<int32_t*>(sorted_token_ids.data_ptr());
int32_t* expert_ids_ptr = static_cast<int32_t*>(expert_ids.data_ptr());
int32_t* num_tokens_post_pad_ptr = static_cast<int32_t*>(num_tokens_post_pad.data_ptr());
int32_t* cumsum_buffer_ptr = static_cast<int32_t*>(cumsum_buffer.data_ptr());
size_t numel = topk_ids.numel();
bool small_batch_expert_mode = (numel < 1024) && (num_experts <= 64);
if (small_batch_expert_mode) {
const int32_t num_thread = std::max((int32_t)num_experts, (int32_t)WARP_SIZE);
constexpr int32_t fill_threads = 256;
const int32_t shared_mem_size = ((num_thread + 1) * num_experts + (num_experts + 1)) * sizeof(int32_t);
auto kernel = moe::moe_align_block_size_small_batch_expert_kernel<scalar_t, fill_threads>;
LaunchKernel(dim3(1), dim3(fill_threads + num_thread), stream, shared_mem_size)(
kernel,
topk_ids_ptr,
sorted_token_ids_ptr,
expert_ids_ptr,
num_tokens_post_pad_ptr,
(int32_t)num_experts,
(int32_t)block_size,
numel,
pad_sorted_token_ids,
(int32_t)max_num_tokens_padded);
} else if (num_experts <= 1024) {
const size_t scan_size = next_pow2(num_experts);
const size_t shared_mem_size = (num_experts + (num_experts + 1) + scan_size + WARP_SIZE) * sizeof(int32_t);
auto align_kernel = moe::moe_align_block_size_kernel<scalar_t>;
LaunchKernel(dim3(2), dim3(threads), stream, shared_mem_size)(
align_kernel,
topk_ids_ptr,
sorted_token_ids_ptr,
expert_ids_ptr,
num_tokens_post_pad_ptr,
(int32_t)num_experts,
(int32_t)block_size,
numel,
cumsum_buffer_ptr,
pad_sorted_token_ids,
(int32_t)scan_size,
(int32_t)max_num_tokens_padded);
const int block_threads = std::min(256, threads);
const int num_blocks = (numel + block_threads - 1) / block_threads;
const int max_blocks = 65535;
const int actual_blocks = std::min(num_blocks, max_blocks);
auto sort_kernel = moe::count_and_sort_expert_tokens_kernel<scalar_t>;
LaunchKernel(dim3(actual_blocks), dim3(block_threads), stream)(
sort_kernel, topk_ids_ptr, sorted_token_ids_ptr, cumsum_buffer_ptr, numel);
} else {
// v2 path for >1024 experts: two-level warp scan with EXPERTS_PER_THREAD
int64_t padded_num_experts = ((num_experts + WARP_SIZE - 1) / WARP_SIZE) * WARP_SIZE;
size_t shared_mem_size = (padded_num_experts + WARP_SIZE) * sizeof(int32_t);
auto launch_v2 = [&](auto ept_tag) {
constexpr int EPT = decltype(ept_tag)::value;
auto v2_kernel = moe::moe_align_block_size_kernel_v2<scalar_t, EPT>;
LaunchKernel(dim3(2), dim3(threads), stream, shared_mem_size)(
v2_kernel,
topk_ids_ptr,
sorted_token_ids_ptr,
expert_ids_ptr,
num_tokens_post_pad_ptr,
(int32_t)num_experts,
(int32_t)padded_num_experts,
(int32_t)block_size,
numel,
cumsum_buffer_ptr,
pad_sorted_token_ids,
(int32_t)max_num_tokens_padded);
};
if (padded_num_experts <= 2048) {
launch_v2(std::integral_constant<int, 2>{});
} else if (padded_num_experts <= 4096) {
launch_v2(std::integral_constant<int, 4>{});
} else {
launch_v2(std::integral_constant<int, 8>{});
}
const int block_threads = std::min(256, threads);
const int num_blocks = (numel + block_threads - 1) / block_threads;
const int max_blocks = 65535;
const int actual_blocks = std::min(num_blocks, max_blocks);
auto sort_kernel = moe::count_and_sort_expert_tokens_kernel<scalar_t>;
LaunchKernel(dim3(actual_blocks), dim3(block_threads), stream)(
sort_kernel, topk_ids_ptr, sorted_token_ids_ptr, cumsum_buffer_ptr, numel);
}
}
};
} // namespace
@@ -0,0 +1,418 @@
// Copyright (c) 2026 LightSeek Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/*
* Fused MoE finalize + shared-output add (bf16 output, SM>=90 for PDL).
*
* Forked from flashinfer's ``finalizeKernel`` and ``finalizeKernelVecLoad``
* (trtllm_fused_moe_dev_kernel.cu:639 and :803), stripped of the MoE
* backend's KernelParams / UsePdl templating, and extended with an
* optional shared_output residual add on the epilogue side.
*
* For each token t, computes:
* out[t] = Σ_k expert_weights[t, k] * gemm2_out[permuted_idx(t, k)]
* + shared_output[t] // if non-null
*
* Eliminates the native PyTorch ``routed + shared_output`` add (and the
* separate ``*= routed_scaling_factor`` kernel when applicable) from
* ``DeepseekV3MoE.forward``, and gives the downstream allreduce+rmsnorm
* a clean PDL handoff.
*
* Expert-weight dtype is templated on ``TypeExpW`` so we support both the
* bf16 and fp32 topk-weight paths (DSv3/K2.5 trtllm backends use fp32
* because their ``_routing_logits_dtype = torch.float32``; other backends
* use bf16).
*
* Expert-weight scale convention: in our target backends
* (flashinfer trtllm nvfp4 + unquantized), ``apply_routed_scaling_factor_on_output``
* is True, so the routed scaling factor is already folded into
* ``expert_weights`` at topk time. This kernel does not apply any
* additional scale.
*/
#include <cutlass/array.h>
#include <cutlass/numeric_conversion.h>
#include <cutlass/numeric_types.h>
#include "tvm_ffi_utils.h"
#include <cuda_runtime.h>
namespace sglang {
using BF16 = cutlass::bfloat16_t;
constexpr int FINALIZE_THREADS_PER_BLOCK = 256;
constexpr int MAX_TOPK = 64;
// ---------------------------------------------------------------------------
// General kernel — one CTA per (hidden_chunk, token). Picks up small-to-mid
// workloads where the block count fits in a few waves.
// ---------------------------------------------------------------------------
template <typename TypeExpW>
__global__ void moeFinalizeKernel(
int numTokens,
int hiddenDim,
int hiddenDimPadded,
int topK,
BF16 const* __restrict__ inPtr,
int const* __restrict__ expandedIdxToPermutedIdx,
TypeExpW const* __restrict__ expertWeightsPtr,
BF16 const* __restrict__ sharedBiasPtr,
BF16* __restrict__ outPtr) {
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
cudaGridDependencySynchronize();
#endif
for (int64_t tokenIdx = blockIdx.y; tokenIdx < numTokens; tokenIdx += gridDim.y) {
for (int64_t hiddenIdx = threadIdx.x + blockDim.x * blockIdx.x; hiddenIdx < hiddenDim;
hiddenIdx += blockDim.x * gridDim.x) {
float acc = 0.0f;
for (int k = 0; k < topK; k++) {
int64_t const expandedIdx = tokenIdx * topK + k;
int64_t const permutedIdx = expandedIdxToPermutedIdx[expandedIdx];
if (permutedIdx == -1) {
continue;
}
float const scale = static_cast<float>(expertWeightsPtr[expandedIdx]);
float const val = static_cast<float>(inPtr[permutedIdx * hiddenDimPadded + hiddenIdx]);
acc += scale * val;
}
if (sharedBiasPtr != nullptr) {
acc += static_cast<float>(sharedBiasPtr[tokenIdx * hiddenDim + hiddenIdx]);
}
outPtr[tokenIdx * hiddenDim + hiddenIdx] = static_cast<BF16>(acc);
}
}
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
cudaTriggerProgrammaticLaunchCompletion();
#endif
}
// ---------------------------------------------------------------------------
// Vectorized-load kernel — one CTA per token, 128-bit loads, topK unrolled.
// Better at prefill shapes where the general kernel's block count saturates
// many waves and the indirect gather from gemm2_out dominates.
// ---------------------------------------------------------------------------
__device__ inline float4 vectorizedLoadPtx(float4 const* ptr) {
float4 ret;
asm volatile("ld.global.v4.f32 {%0, %1, %2, %3}, [%4];"
: "=f"(ret.x), "=f"(ret.y), "=f"(ret.z), "=f"(ret.w)
: "l"(ptr));
return ret;
}
template <int TopKUnrollFactor>
struct IdxPackedTraits;
template <>
struct IdxPackedTraits<1> {
using Packed = int;
};
template <>
struct IdxPackedTraits<2> {
using Packed = int2;
};
template <>
struct IdxPackedTraits<4> {
using Packed = int4;
};
template <typename TypeExpW, int TopKUnrollFactor>
__global__ void moeFinalizeKernelVecLoad(
int numTokens,
int hiddenDim,
int hiddenDimPadded,
int topK,
BF16 const* __restrict__ inPtr,
int const* __restrict__ expandedIdxToPermutedIdx,
TypeExpW const* __restrict__ expertWeightsPtr,
BF16 const* __restrict__ sharedBiasPtr,
BF16* __restrict__ outPtr) {
static_assert(
TopKUnrollFactor == 1 || TopKUnrollFactor == 2 || TopKUnrollFactor == 4, "TopKUnrollFactor must be 1, 2, or 4");
using IdxPackedType = typename IdxPackedTraits<TopKUnrollFactor>::Packed;
using IdxArrayType = cutlass::Array<int, TopKUnrollFactor>;
using ScaleArrayType = cutlass::Array<TypeExpW, TopKUnrollFactor>;
// 128 bits per thread → 8 bf16 elements.
constexpr int FINALIZE_ELEM_PER_THREAD = 8;
using InputElem = cutlass::Array<BF16, FINALIZE_ELEM_PER_THREAD>;
using OutputElem = cutlass::Array<BF16, FINALIZE_ELEM_PER_THREAD>;
using ComputeElem = cutlass::Array<float, FINALIZE_ELEM_PER_THREAD>;
int64_t const tokenIdx = blockIdx.x;
int64_t const startOffset = threadIdx.x;
int64_t const stride = FINALIZE_THREADS_PER_BLOCK;
int64_t const numElemsInPaddedCol = hiddenDimPadded / FINALIZE_ELEM_PER_THREAD;
int64_t const numElemsInCol = hiddenDim / FINALIZE_ELEM_PER_THREAD;
// Stage the per-token (topK/unroll) indices + scales into smem.
__shared__ ScaleArrayType scaleArrSmem[MAX_TOPK / TopKUnrollFactor];
__shared__ IdxArrayType permutedIdxArrSmem[MAX_TOPK / TopKUnrollFactor];
for (int kChunkIdx = threadIdx.x; kChunkIdx < topK / TopKUnrollFactor; kChunkIdx += blockDim.x) {
int64_t const expandedIdx = tokenIdx * topK + kChunkIdx * TopKUnrollFactor;
auto const permutedIdxPacked =
reinterpret_cast<IdxPackedType const*>(expandedIdxToPermutedIdx)[expandedIdx / TopKUnrollFactor];
permutedIdxArrSmem[kChunkIdx] = *reinterpret_cast<IdxArrayType const*>(&permutedIdxPacked);
#pragma unroll
for (int ki = 0; ki < TopKUnrollFactor; ++ki) {
scaleArrSmem[kChunkIdx][ki] = expertWeightsPtr[expandedIdx + ki];
}
}
BF16* outputPtr = outPtr + tokenIdx * hiddenDim;
auto* outElemPtr = reinterpret_cast<OutputElem*>(outputPtr);
auto const* inElemPtr = reinterpret_cast<InputElem const*>(inPtr);
auto const* sharedElemPtr =
sharedBiasPtr != nullptr ? reinterpret_cast<InputElem const*>(sharedBiasPtr + tokenIdx * hiddenDim) : nullptr;
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
cudaGridDependencySynchronize();
#endif
__syncthreads();
for (int elemIndex = startOffset; elemIndex < numElemsInCol; elemIndex += stride) {
ComputeElem threadOutput;
threadOutput.fill(0.0f);
for (int kChunkIdx = 0; kChunkIdx < topK / TopKUnrollFactor; kChunkIdx++) {
IdxArrayType permutedIdxArr = permutedIdxArrSmem[kChunkIdx];
InputElem inputElemArr[TopKUnrollFactor];
#pragma unroll
for (int ki = 0; ki < TopKUnrollFactor; ++ki) {
int const permutedIdx = permutedIdxArr[ki];
if (permutedIdx == -1) {
continue;
}
auto const* inputPermutedPtr = inElemPtr + permutedIdx * numElemsInPaddedCol;
float4 input = vectorizedLoadPtx(reinterpret_cast<float4 const*>(&inputPermutedPtr[elemIndex]));
inputElemArr[ki] = *reinterpret_cast<InputElem const*>(&input);
}
ScaleArrayType scaleArr = scaleArrSmem[kChunkIdx];
#pragma unroll
for (int ki = 0; ki < TopKUnrollFactor; ++ki) {
int const permutedIdx = permutedIdxArr[ki];
if (permutedIdx == -1) {
continue;
}
float const scale = static_cast<float>(scaleArr[ki]);
cutlass::NumericArrayConverter<float, BF16, FINALIZE_ELEM_PER_THREAD> toFloat;
ComputeElem expertResult = toFloat(inputElemArr[ki]);
#pragma unroll
for (int e = 0; e < FINALIZE_ELEM_PER_THREAD; ++e) {
threadOutput[e] += scale * expertResult[e];
}
}
}
if (sharedElemPtr != nullptr) {
float4 shared = vectorizedLoadPtx(reinterpret_cast<float4 const*>(&sharedElemPtr[elemIndex]));
InputElem sharedElem = *reinterpret_cast<InputElem const*>(&shared);
cutlass::NumericArrayConverter<float, BF16, FINALIZE_ELEM_PER_THREAD> toFloat;
ComputeElem sharedFloat = toFloat(sharedElem);
#pragma unroll
for (int e = 0; e < FINALIZE_ELEM_PER_THREAD; ++e) {
threadOutput[e] += sharedFloat[e];
}
}
cutlass::NumericArrayConverter<BF16, float, FINALIZE_ELEM_PER_THREAD> toBF16;
outElemPtr[elemIndex] = toBF16(threadOutput);
}
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
cudaTriggerProgrammaticLaunchCompletion();
#endif
}
// ---------------------------------------------------------------------------
// Typed dispatch
// ---------------------------------------------------------------------------
template <typename TypeExpW>
void dispatchFinalize(
int numTokens,
int hiddenDim,
int hiddenDimPadded,
int topK,
BF16 const* inPtr,
int const* expandedIdxPtr,
void const* weightsPtrVoid,
BF16 const* sharedPtr,
BF16* outPtr,
bool useVecLoad,
cudaStream_t stream,
cudaLaunchAttribute const* attrs,
int numAttrs) {
auto const* weightsPtr = static_cast<TypeExpW const*>(weightsPtrVoid);
constexpr int kNumThreads = 256;
if (!useVecLoad) {
int const numBlocksX = (hiddenDim + kNumThreads - 1) / kNumThreads;
int const numBlocksY = std::min(8192, numTokens);
cudaLaunchConfig_t config;
config.gridDim = dim3(numBlocksX, numBlocksY);
config.blockDim = dim3(kNumThreads);
config.dynamicSmemBytes = 0;
config.stream = stream;
config.numAttrs = numAttrs;
config.attrs = const_cast<cudaLaunchAttribute*>(attrs);
cudaLaunchKernelEx(
&config,
moeFinalizeKernel<TypeExpW>,
numTokens,
hiddenDim,
hiddenDimPadded,
topK,
inPtr,
expandedIdxPtr,
weightsPtr,
sharedPtr,
outPtr);
return;
}
auto launch = [&](auto unroll_tag) {
constexpr int UNROLL = decltype(unroll_tag)::value;
cudaLaunchConfig_t config;
config.gridDim = dim3(numTokens);
config.blockDim = dim3(FINALIZE_THREADS_PER_BLOCK);
config.dynamicSmemBytes = 0;
config.stream = stream;
config.numAttrs = numAttrs;
config.attrs = const_cast<cudaLaunchAttribute*>(attrs);
cudaLaunchKernelEx(
&config,
moeFinalizeKernelVecLoad<TypeExpW, UNROLL>,
numTokens,
hiddenDim,
hiddenDimPadded,
topK,
inPtr,
expandedIdxPtr,
weightsPtr,
sharedPtr,
outPtr);
};
// Match flashinfer's LAUNCH_TOPK_EXPW dispatch order.
if (topK % 4 == 0) {
launch(std::integral_constant<int, 4>{});
} else if (topK % 2 == 0) {
launch(std::integral_constant<int, 2>{});
} else {
launch(std::integral_constant<int, 1>{});
}
}
} // namespace sglang
// ---------------------------------------------------------------------------
// Host launcher
// ---------------------------------------------------------------------------
void moe_finalize_fuse_shared(
TensorView out,
TensorView gemm2_out,
TensorView expanded_idx_to_permuted_idx,
TensorView expert_weights,
TensorView shared_output,
int64_t top_k,
bool enable_pdl) {
TVM_FFI_ICHECK_EQ(out.ndim(), 2) << "out must be 2-D [numTokens, hiddenDim]";
TVM_FFI_ICHECK_EQ(gemm2_out.ndim(), 2) << "gemm2_out must be 2-D [totalNumPaddedTokens, hiddenDimPadded]";
TVM_FFI_ICHECK_EQ(expanded_idx_to_permuted_idx.ndim(), 1);
TVM_FFI_ICHECK_EQ(expert_weights.ndim(), 2) << "expert_weights must be 2-D [numTokens, topK]";
int const numTokens = int(out.size(0));
int const hiddenDim = int(out.size(1));
int const hiddenDimPadded = int(gemm2_out.size(1));
TVM_FFI_ICHECK_LE(top_k, sglang::MAX_TOPK);
TVM_FFI_ICHECK_EQ(expanded_idx_to_permuted_idx.size(0), numTokens * top_k);
TVM_FFI_ICHECK_EQ(expert_weights.size(0), numTokens);
TVM_FFI_ICHECK_EQ(expert_weights.size(1), top_k);
bool const hasShared = shared_output.numel() > 0;
if (hasShared) {
TVM_FFI_ICHECK_EQ(shared_output.ndim(), 2);
TVM_FFI_ICHECK_EQ(shared_output.size(0), numTokens);
TVM_FFI_ICHECK_EQ(shared_output.size(1), hiddenDim);
}
auto const* inPtr = static_cast<sglang::BF16 const*>(gemm2_out.data_ptr());
auto const* expandedIdxPtr = static_cast<int const*>(expanded_idx_to_permuted_idx.data_ptr());
auto const* sharedPtr = hasShared ? static_cast<sglang::BF16 const*>(shared_output.data_ptr()) : nullptr;
auto* outPtr = static_cast<sglang::BF16*>(out.data_ptr());
cudaSetDevice(out.device().device_id);
cudaStream_t const stream = get_stream(out.device());
// Dispatch heuristic (matches flashinfer): few waves → general kernel,
// many waves → vectorized. The 1184 threshold comes from 148 SMs × 8
// blocks/SM on Blackwell.
constexpr int kNumThreads = 256;
int const numBlocksX = (hiddenDim + kNumThreads - 1) / kNumThreads;
int const numBlocksY = std::min(8192, numTokens);
bool const useVecLoad = (numBlocksX * numBlocksY) >= 1184 && (hiddenDim % 8 == 0) && (hiddenDimPadded % 8 == 0);
cudaLaunchAttribute attrs[1];
attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
attrs[0].val.programmaticStreamSerializationAllowed = enable_pdl;
auto ew_dtype = expert_weights.dtype();
if (ew_dtype == DLDataType{kDLFloat, 32, 1}) {
sglang::dispatchFinalize<float>(
numTokens,
hiddenDim,
hiddenDimPadded,
int(top_k),
inPtr,
expandedIdxPtr,
expert_weights.data_ptr(),
sharedPtr,
outPtr,
useVecLoad,
stream,
attrs,
1);
} else if (ew_dtype == DLDataType{kDLBfloat, 16, 1}) {
sglang::dispatchFinalize<sglang::BF16>(
numTokens,
hiddenDim,
hiddenDimPadded,
int(top_k),
inPtr,
expandedIdxPtr,
expert_weights.data_ptr(),
sharedPtr,
outPtr,
useVecLoad,
stream,
attrs,
1);
} else {
TVM_FFI_ICHECK(false) << "expert_weights dtype must be float32 or bfloat16";
}
cudaError_t const err = cudaGetLastError();
TVM_FFI_ICHECK(err == cudaSuccess) << "moe_finalize_fuse_shared launch failed: " << cudaGetErrorString(err);
}
TVM_FFI_DLL_EXPORT_TYPED_FUNC(moe_finalize_fuse_shared, moe_finalize_fuse_shared);
@@ -0,0 +1,367 @@
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/runtime.cuh>
#include <sgl_kernel/utils.cuh>
#include <sgl_kernel/warp.cuh>
#include <tvm/ffi/container/tensor.h>
#include <cfloat>
#include <cstdint>
namespace {
constexpr uint32_t kWarpSize = 32;
constexpr uint32_t kWarpsPerCTA = 6;
constexpr uint32_t kSmallTokenThreshold = 512;
constexpr uint32_t kMaxExperts = 512;
constexpr uint32_t kMaxTopK = 16;
enum class ScoringFunc : uint32_t {
kSigmoid = 0,
kSqrtSoftplus = 1,
};
struct MoEFusedGateParams {
const float* __restrict__ input;
const float* __restrict__ bias;
float* __restrict__ output;
int32_t* __restrict__ indices;
uint32_t num_rows;
uint32_t num_experts;
uint32_t topk;
uint32_t num_fused_shared_experts;
bool renormalize;
float routed_scaling_factor;
bool apply_routed_scaling_factor_on_output;
};
template <ScoringFunc kScoringFunc>
__device__ __forceinline__ float compute_score(float x) {
if constexpr (kScoringFunc == ScoringFunc::kSigmoid) {
// sigmoid(x) = 1 / (1 + exp(-x))
return 1.0f / (1.0f + expf(-x));
} else {
// sqrt(softplus(x)) = sqrt(log(1 + exp(x)))
float softplus = log1pf(expf(x));
return sqrtf(softplus);
}
}
template <uint32_t kWarpsPerToken, ScoringFunc kScoringFunc>
__global__ void moe_fused_gate_kernel_small_token(const MoEFusedGateParams __grid_constant__ params) {
const auto& [input, bias, output, indices, num_rows, num_experts, topk, num_fused_shared_experts, renormalize, routed_scaling_factor, apply_routed_scaling_factor_on_output] =
params;
uint32_t row_idx = blockIdx.x;
if (row_idx >= num_rows) return;
// number of routed experts to select (excluding fused shared experts)
const uint32_t topk_routed = topk - num_fused_shared_experts;
uint32_t tid = threadIdx.x;
uint32_t warp_id = tid / kWarpSize;
uint32_t lane_id = tid % kWarpSize;
// Actual warps launched (<= kWarpsPerToken). num_experts that need fewer than
// kWarpsPerToken warps leave the upper warp_maxs/warp_experts slots unwritten,
// so the cross-warp reduction below must only read the launched warps.
const uint32_t num_warps = blockDim.x / kWarpSize;
extern __shared__ float shared_mem[];
float* shared_scores = shared_mem;
float* shared_original_scores = shared_mem + num_experts;
// For warp-level reduction
__shared__ float warp_maxs[kWarpsPerToken];
__shared__ int warp_experts[kWarpsPerToken];
__shared__ int selected_experts[kMaxTopK];
for (uint32_t e = tid; e < num_experts; e += blockDim.x) {
float input_val = input[row_idx * num_experts + e];
float bias_val = bias[e];
float score_val = compute_score<kScoringFunc>(input_val);
float biased_val = score_val + bias_val;
shared_scores[e] = biased_val;
shared_original_scores[e] = score_val;
}
__syncthreads();
// only select topk_routed experts (excluding shared experts)
for (uint32_t k = 0; k < topk_routed; k++) {
float my_val = -FLT_MAX;
int my_expert = -1;
for (uint32_t e = tid; e < num_experts; e += blockDim.x) {
if (shared_scores[e] > my_val) {
my_val = shared_scores[e];
my_expert = e;
}
}
float warp_max_val = my_val;
int warp_max_expert = my_expert;
#pragma unroll
for (int offset = 16; offset > 0; offset /= 2) {
float other_val = __shfl_down_sync(0xFFFFFFFF, warp_max_val, offset);
int other_expert = __shfl_down_sync(0xFFFFFFFF, warp_max_expert, offset);
if (other_val > warp_max_val) {
warp_max_val = other_val;
warp_max_expert = other_expert;
}
}
if (lane_id == 0 && warp_id < kWarpsPerToken) {
warp_maxs[warp_id] = warp_max_val;
warp_experts[warp_id] = warp_max_expert;
}
__syncthreads();
if (warp_id == 0) {
float final_max = (lane_id < num_warps) ? warp_maxs[lane_id] : -FLT_MAX;
int final_expert = (lane_id < num_warps) ? warp_experts[lane_id] : -1;
#pragma unroll
for (int offset = 16; offset > 0; offset /= 2) {
float other_val = __shfl_down_sync(0xFFFFFFFF, final_max, offset);
int other_expert = __shfl_down_sync(0xFFFFFFFF, final_expert, offset);
if (other_val > final_max) {
final_max = other_val;
final_expert = other_expert;
}
}
if (lane_id == 0) {
selected_experts[k] = final_expert;
}
}
__syncthreads();
int selected = selected_experts[k];
if (selected >= 0 && tid == 0) {
shared_scores[selected] = -FLT_MAX;
}
__syncthreads();
}
static_assert(kMaxTopK <= device::kWarpThreads);
if (tid >= device::kWarpThreads) return;
// only use the first warp to perform write to global operation
float routed_weight = 0.0f;
int32_t selected_expert = 0;
if (tid < topk_routed) {
int expert_id = selected_experts[tid];
float score = shared_original_scores[expert_id];
if (expert_id >= 0 && expert_id < static_cast<int>(num_experts)) {
routed_weight = score;
selected_expert = expert_id;
}
}
const auto routed_sum = device::warp::reduce_sum<kMaxTopK>(routed_weight);
if (tid < topk) {
const bool is_shared = tid >= topk_routed;
const auto output_offset = row_idx * topk + tid;
const auto weight = is_shared ? (routed_sum / routed_scaling_factor) : routed_weight;
const auto expert_id = is_shared ? (num_experts + tid - topk_routed) : selected_expert;
const auto scale = apply_routed_scaling_factor_on_output ? routed_scaling_factor : 1.0f;
const auto norm = renormalize && routed_sum > 0.0f ? routed_sum : 1.0f;
output[output_offset] = weight / norm * scale;
indices[output_offset] = expert_id;
}
}
template <ScoringFunc kScoringFunc>
__global__ void moe_fused_gate_kernel(const MoEFusedGateParams __grid_constant__ params) {
const auto& [input, bias, output, indices, num_rows, num_experts, topk, num_fused_shared_experts, renormalize, routed_scaling_factor, apply_routed_scaling_factor_on_output] =
params;
uint32_t row_idx = blockIdx.x * kWarpsPerCTA + threadIdx.y;
if (row_idx >= num_rows) return;
// number of routed experts to select (excluding fused shared experts)
const uint32_t topk_routed = topk - num_fused_shared_experts;
uint32_t lane_id = threadIdx.x;
uint32_t warp_id = threadIdx.y;
extern __shared__ float shared_mem[];
float* shared_scores = shared_mem + warp_id * num_experts * 2;
float* shared_original_scores = shared_scores + num_experts;
__shared__ int selected_experts[kWarpsPerCTA][kMaxTopK];
int* warp_selected_experts = selected_experts[warp_id];
for (uint32_t e = lane_id; e < num_experts; e += kWarpSize) {
float input_val = input[row_idx * num_experts + e];
float bias_val = bias[e];
float score_val = compute_score<kScoringFunc>(input_val);
float biased_val = score_val + bias_val;
shared_scores[e] = biased_val;
shared_original_scores[e] = score_val;
}
__syncwarp();
// only select topk_routed experts
for (uint32_t k = 0; k < topk_routed; k++) {
float max_val = -FLT_MAX;
int max_expert = -1;
for (uint32_t expert = lane_id; expert < num_experts; expert += kWarpSize) {
if (shared_scores[expert] > max_val) {
max_val = shared_scores[expert];
max_expert = expert;
}
}
for (int offset = kWarpSize / 2; offset > 0; offset /= 2) {
float other_val = __shfl_down_sync(0xFFFFFFFF, max_val, offset);
int other_expert = __shfl_down_sync(0xFFFFFFFF, max_expert, offset);
if (other_val > max_val || (other_val == max_val && other_expert < max_expert)) {
max_val = other_val;
max_expert = other_expert;
}
}
if (lane_id == 0) {
warp_selected_experts[k] = max_expert;
if (max_expert != -1) {
shared_scores[max_expert] = -FLT_MAX;
}
}
__syncwarp();
}
static_assert(kMaxTopK <= device::kWarpThreads);
float routed_weight = 0.0f;
int32_t selected_expert = 0;
if (lane_id < topk_routed) {
int expert_id = warp_selected_experts[lane_id];
if (expert_id >= 0 && expert_id < static_cast<int>(num_experts)) {
routed_weight = shared_original_scores[expert_id];
selected_expert = expert_id;
}
}
const auto routed_sum = device::warp::reduce_sum<kMaxTopK>(routed_weight);
if (lane_id < topk) {
const bool is_shared = lane_id >= topk_routed;
const auto output_idx = row_idx * topk + lane_id;
const auto weight = is_shared ? (routed_sum / routed_scaling_factor) : routed_weight;
const auto expert_id = is_shared ? (num_experts + lane_id - topk_routed) : selected_expert;
const auto scale = apply_routed_scaling_factor_on_output ? routed_scaling_factor : 1.0f;
const auto norm = renormalize && routed_sum > 0.0f ? routed_sum : 1.0f;
output[output_idx] = weight / norm * scale;
indices[output_idx] = expert_id;
}
}
template <ScoringFunc kScoringFunc>
void dispatch_small_token_kernel(
uint32_t num_rows,
uint32_t threads_per_block,
uint32_t warps_per_token,
DLDevice device,
size_t smem_per_row,
const MoEFusedGateParams& params) {
using namespace host;
if (warps_per_token <= 8) {
LaunchKernel(num_rows, threads_per_block, device, smem_per_row)(
moe_fused_gate_kernel_small_token<8, kScoringFunc>, params);
} else if (warps_per_token <= 12) {
LaunchKernel(num_rows, threads_per_block, device, smem_per_row)(
moe_fused_gate_kernel_small_token<12, kScoringFunc>, params);
} else {
LaunchKernel(num_rows, threads_per_block, device, smem_per_row)(
moe_fused_gate_kernel_small_token<16, kScoringFunc>, params);
}
}
struct MoEFusedGateKernel {
static void
run(const tvm::ffi::TensorView input,
const tvm::ffi::TensorView bias,
const tvm::ffi::TensorView output,
const tvm::ffi::TensorView indices,
uint32_t topk,
uint32_t scoring_func, // 0 = sigmoid, 1 = sqrtsoftplus
uint32_t num_fused_shared_experts,
bool renormalize,
float routed_scaling_factor,
bool apply_routed_scaling_factor_on_output) {
using namespace host;
auto N = SymbolicSize{"num_rows"};
auto E = SymbolicSize{"num_experts"};
auto K = SymbolicSize{"topk"};
auto device = SymbolicDevice{};
K.set_value(topk);
device.set_options<kDLCUDA>();
TensorMatcher({N, E}).with_dtype<float>().with_device(device).verify(input);
TensorMatcher({E}).with_dtype<float>().with_device(device).verify(bias);
TensorMatcher({N, K}).with_dtype<float>().with_device(device).verify(output);
TensorMatcher({N, K}).with_dtype<int32_t>().with_device(device).verify(indices);
const auto num_rows = static_cast<uint32_t>(N.unwrap());
const auto num_experts = static_cast<uint32_t>(E.unwrap());
RuntimeCheck(num_experts <= kMaxExperts, "num_experts exceeds maximum supported value");
RuntimeCheck(scoring_func <= 1, "scoring_func must be 0 (sigmoid) or 1 (sqrtsoftplus)");
RuntimeCheck(topk > num_fused_shared_experts, "topk must be greater than num_fused_shared_experts");
const auto params = MoEFusedGateParams{
.input = static_cast<const float*>(input.data_ptr()),
.bias = static_cast<const float*>(bias.data_ptr()),
.output = static_cast<float*>(output.data_ptr()),
.indices = static_cast<int32_t*>(indices.data_ptr()),
.num_rows = num_rows,
.num_experts = num_experts,
.topk = topk,
.num_fused_shared_experts = num_fused_shared_experts,
.renormalize = renormalize,
.routed_scaling_factor = routed_scaling_factor,
.apply_routed_scaling_factor_on_output = apply_routed_scaling_factor_on_output,
};
const size_t smem_per_row = 2 * num_experts * sizeof(float);
bool use_small_token_kernel = num_rows <= kSmallTokenThreshold;
if (use_small_token_kernel) {
// 1 token per block
uint32_t warps_per_token = div_ceil(num_experts, kWarpSize);
warps_per_token = std::min(warps_per_token, 16u);
uint32_t threads_per_block = warps_per_token * kWarpSize;
if (scoring_func == 0) {
dispatch_small_token_kernel<ScoringFunc::kSigmoid>(
num_rows, threads_per_block, warps_per_token, device.unwrap(), smem_per_row, params);
} else {
dispatch_small_token_kernel<ScoringFunc::kSqrtSoftplus>(
num_rows, threads_per_block, warps_per_token, device.unwrap(), smem_per_row, params);
}
} else {
// multiple tokens per block
uint32_t num_blocks = div_ceil(num_rows, kWarpsPerCTA);
dim3 block_dim(kWarpSize, kWarpsPerCTA);
size_t large_smem = smem_per_row * kWarpsPerCTA;
if (scoring_func == 0) {
LaunchKernel(num_blocks, block_dim, device.unwrap(), large_smem)(
moe_fused_gate_kernel<ScoringFunc::kSigmoid>, params);
} else {
LaunchKernel(num_blocks, block_dim, device.unwrap(), large_smem)(
moe_fused_gate_kernel<ScoringFunc::kSqrtSoftplus>, params);
}
}
}
};
} // namespace
@@ -0,0 +1,544 @@
// Adapt from https://github.com/vllm-project/vllm/blob/v0.7.3/csrc/moe/topk_softmax_kernels.cu
// which is originally adapted from
// https://github.com/NVIDIA/TensorRT-LLM/blob/v0.7.1/cpp/tensorrt_llm/kernels/mixtureOfExperts/moe_kernels.cu
#pragma once
#include <sgl_kernel/utils.h>
#include <sgl_kernel/utils.cuh>
#include <cub/cub.cuh>
#include <cub/util_type.cuh>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/optional.h>
// CUDA 12.9+ deprecated cub::Max/Min in favour of cuda::maximum/minimum
#if CUDA_VERSION >= 12090
#include <cuda/functional>
using MaxReduceOp = cuda::maximum<>;
using MinReduceOp = cuda::minimum<>;
#else
using MaxReduceOp = cub::Max;
using MinReduceOp = cub::Min;
#endif
#include <cfloat>
#include <cstdint>
#include <type_traits>
using tvm::ffi::TensorView;
#ifndef MOE_TOPK_SIGMOID_WARP_SIZE
#define MOE_TOPK_SIGMOID_WARP_SIZE 32
#endif
namespace {
static constexpr int WARP_SIZE = MOE_TOPK_SIGMOID_WARP_SIZE;
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
// ---------------------------------------------------------------------------
// Aligned array — avoids CUTLASS dependency; identical semantics.
// ---------------------------------------------------------------------------
template <typename T, int N, int Alignment = static_cast<int>(sizeof(T) * N)>
class alignas(Alignment) AlignedArray {
T data[N];
};
// ---------------------------------------------------------------------------
// Type conversion helper
// ---------------------------------------------------------------------------
template <typename T>
__device__ float convert_to_float(T x) {
if constexpr (std::is_same_v<T, __half>) {
return __half2float(x);
} else if constexpr (std::is_same_v<T, __nv_bfloat16>) {
return __bfloat162float(x);
} else {
return static_cast<float>(x);
}
}
// ---------------------------------------------------------------------------
// moeSigmoid — fallback sigmoid kernel (used for non-power-of-2 experts)
// ---------------------------------------------------------------------------
template <typename T, int TPB>
__launch_bounds__(TPB) __global__
void moeSigmoid(const T* input, const bool* finished, float* output, const int num_cols) {
const int thread_row_offset = blockIdx.x * num_cols;
if ((finished != nullptr) && finished[blockIdx.x]) {
return;
}
for (int ii = threadIdx.x; ii < num_cols; ii += TPB) {
const int idx = thread_row_offset + ii;
float val = convert_to_float<T>(input[idx]);
val = 1.0f / (1.0f + expf(-val));
output[idx] = val;
}
}
// ---------------------------------------------------------------------------
// moeTopK — fallback top-k kernel (used for non-power-of-2 experts)
// ---------------------------------------------------------------------------
template <int TPB>
__launch_bounds__(TPB) __global__ void moeTopK(
const float* inputs_after_sigmoid,
const bool* finished,
float* output,
int* indices,
const int num_experts,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias,
double routed_scaling_factor,
int num_fused_shared_experts) {
using cub_kvp = cub::KeyValuePair<int, float>;
using BlockReduce = cub::BlockReduce<cub_kvp, TPB>;
__shared__ typename BlockReduce::TempStorage tmpStorage;
cub_kvp thread_kvp;
cub::ArgMax arg_max;
const int block_row = blockIdx.x;
const int topk = k + num_fused_shared_experts;
const bool row_is_active = finished ? !finished[block_row] : true;
const int thread_read_offset = blockIdx.x * num_experts;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
thread_kvp.key = 0;
thread_kvp.value = -1.f;
cub_kvp inp_kvp;
for (int expert = threadIdx.x; expert < num_experts; expert += TPB) {
const int idx = thread_read_offset + expert;
inp_kvp.key = expert;
inp_kvp.value = inputs_after_sigmoid[idx];
if (correction_bias != nullptr) {
inp_kvp.value += correction_bias[expert];
}
for (int prior_k = 0; prior_k < k_idx; ++prior_k) {
const int prior_winning_expert = indices[topk * block_row + prior_k];
if (prior_winning_expert == expert) {
inp_kvp = thread_kvp;
}
}
thread_kvp = arg_max(inp_kvp, thread_kvp);
}
const cub_kvp result_kvp = BlockReduce(tmpStorage).Reduce(thread_kvp, arg_max);
if (threadIdx.x == 0) {
const int expert = result_kvp.key;
const bool node_uses_expert = expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
const int idx = topk * block_row + k_idx;
float val;
if (correction_bias != nullptr) {
val = inputs_after_sigmoid[thread_read_offset + expert];
} else {
val = result_kvp.value;
}
output[idx] = val;
indices[idx] = should_process_row ? (expert - start_expert) : num_experts;
assert(indices[idx] >= 0);
row_sum_for_renormalize += val;
}
__syncthreads();
}
if (num_fused_shared_experts > 0 && threadIdx.x == 0) {
const int last_idx = topk * block_row + k;
if (renormalize) {
output[last_idx] = 1.0f;
} else {
output[last_idx] = row_sum_for_renormalize / routed_scaling_factor;
}
indices[last_idx] = num_experts;
}
if (renormalize && threadIdx.x == 0) {
float row_sum_for_renormalize_inv = routed_scaling_factor / (row_sum_for_renormalize + 1e-20f);
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = topk * block_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
// ---------------------------------------------------------------------------
// topkGatingSigmoid — optimised kernel for power-of-2 expert counts
// ---------------------------------------------------------------------------
template <typename T, int VPT, int NUM_EXPERTS, int WARPS_PER_CTA, int BYTES_PER_LDG>
__launch_bounds__(WARPS_PER_CTA* WARP_SIZE) __global__ void topkGatingSigmoid(
const T* input,
const bool* finished,
float* output,
const int num_rows,
int* indices,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias,
double routed_scaling_factor,
int num_fused_shared_experts) {
static_assert(VPT == (VPT & -VPT), "VPT must be power of 2");
static_assert(NUM_EXPERTS == (NUM_EXPERTS & -NUM_EXPERTS), "NUM_EXPERTS must be power of 2");
static_assert(BYTES_PER_LDG == (BYTES_PER_LDG & -BYTES_PER_LDG), "BYTES_PER_LDG must be power of 2");
static_assert(BYTES_PER_LDG <= 16, "BYTES_PER_LDG must be leq 16");
static constexpr int ELTS_PER_LDG = BYTES_PER_LDG / sizeof(T);
static constexpr int ELTS_PER_ROW = NUM_EXPERTS;
static constexpr int THREADS_PER_ROW = ELTS_PER_ROW / VPT;
static constexpr int LDG_PER_THREAD = VPT / ELTS_PER_LDG;
static_assert(VPT % ELTS_PER_LDG == 0, "");
static_assert(WARP_SIZE % THREADS_PER_ROW == 0, "");
static_assert(THREADS_PER_ROW == (THREADS_PER_ROW & -THREADS_PER_ROW), "");
static_assert(THREADS_PER_ROW <= WARP_SIZE, "");
static constexpr int ELTS_PER_WARP = WARP_SIZE * VPT;
static constexpr int ROWS_PER_WARP = ELTS_PER_WARP / ELTS_PER_ROW;
static constexpr int ROWS_PER_CTA = WARPS_PER_CTA * ROWS_PER_WARP;
static_assert(ELTS_PER_WARP % ELTS_PER_ROW == 0, "");
const int cta_base_row = blockIdx.x * ROWS_PER_CTA;
const int warp_base_row = cta_base_row + threadIdx.y * ROWS_PER_WARP;
const int thread_row_in_warp = threadIdx.x / THREADS_PER_ROW;
const int thread_row = warp_base_row + thread_row_in_warp;
const int topk = k + num_fused_shared_experts;
if (thread_row >= num_rows) {
return;
}
const bool row_is_active = finished ? !finished[thread_row] : true;
const T* thread_row_ptr = input + thread_row * ELTS_PER_ROW;
const int thread_group_idx = threadIdx.x % THREADS_PER_ROW;
const int first_elt_read_by_thread = thread_group_idx * ELTS_PER_LDG;
const T* thread_read_ptr = thread_row_ptr + first_elt_read_by_thread;
using AccessType = AlignedArray<T, ELTS_PER_LDG>;
T row_chunk_temp[VPT];
AccessType* row_chunk_vec_ptr = reinterpret_cast<AccessType*>(&row_chunk_temp);
const AccessType* vec_thread_read_ptr = reinterpret_cast<const AccessType*>(thread_read_ptr);
#pragma unroll
for (int ii = 0; ii < LDG_PER_THREAD; ++ii) {
row_chunk_vec_ptr[ii] = vec_thread_read_ptr[ii * THREADS_PER_ROW];
}
float row_chunk[VPT];
#pragma unroll
for (int ii = 0; ii < VPT; ++ii) {
float val = convert_to_float<T>(row_chunk_temp[ii]);
val = 1.0f / (1.0f + expf(-val));
if (correction_bias != nullptr) {
const int group_id = ii / ELTS_PER_LDG;
const int local_id = ii % ELTS_PER_LDG;
const int expert_idx = first_elt_read_by_thread + group_id * THREADS_PER_ROW * ELTS_PER_LDG + local_id;
val = val + correction_bias[expert_idx];
}
row_chunk[ii] = val;
}
int start_col = first_elt_read_by_thread;
static constexpr int COLS_PER_GROUP_LDG = ELTS_PER_LDG * THREADS_PER_ROW;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
float max_val = row_chunk[0];
int expert = start_col;
#pragma unroll
for (int ldg = 0, col = start_col; ldg < LDG_PER_THREAD; ++ldg, col += COLS_PER_GROUP_LDG) {
#pragma unroll
for (int ii = 0; ii < ELTS_PER_LDG; ++ii) {
float val = row_chunk[ldg * ELTS_PER_LDG + ii];
if (val > max_val) {
max_val = val;
expert = col + ii;
}
}
}
#pragma unroll
for (int mask = THREADS_PER_ROW / 2; mask > 0; mask /= 2) {
float other_max = __shfl_xor_sync(0xffffffff, max_val, mask, THREADS_PER_ROW);
int other_expert = __shfl_xor_sync(0xffffffff, expert, mask, THREADS_PER_ROW);
if (other_max > max_val || (other_max == max_val && other_expert < expert)) {
max_val = other_max;
expert = other_expert;
}
}
if (thread_group_idx == 0) {
const bool node_uses_expert = expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
const int idx = topk * thread_row + k_idx;
float out_val;
if (correction_bias != nullptr) {
out_val = convert_to_float<T>(thread_row_ptr[expert]);
out_val = 1.0f / (1.0f + expf(-out_val));
} else {
out_val = max_val;
}
output[idx] = out_val;
indices[idx] = should_process_row ? (expert - start_expert) : NUM_EXPERTS;
row_sum_for_renormalize += out_val;
}
if (k_idx + 1 < k) {
const int ldg_group_for_expert = expert / COLS_PER_GROUP_LDG;
const int thread_to_clear_in_group = (expert / ELTS_PER_LDG) % THREADS_PER_ROW;
if (thread_group_idx == thread_to_clear_in_group) {
const int offset_for_expert = expert % ELTS_PER_LDG;
row_chunk[ldg_group_for_expert * ELTS_PER_LDG + offset_for_expert] = -10000.f;
}
}
}
if (num_fused_shared_experts > 0 && thread_group_idx == 0) {
const int last_idx = topk * thread_row + k;
if (renormalize) {
output[last_idx] = 1.0f;
} else {
output[last_idx] = row_sum_for_renormalize / routed_scaling_factor;
}
indices[last_idx] = NUM_EXPERTS;
}
if (renormalize && thread_group_idx == 0) {
float row_sum_for_renormalize_inv = routed_scaling_factor / (row_sum_for_renormalize + 1e-20f);
#pragma unroll
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = topk * thread_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
// ---------------------------------------------------------------------------
// Compile-time constants helper
// ---------------------------------------------------------------------------
namespace detail {
template <typename T, int EXPERTS, int BYTES_PER_LDG>
struct TopkConstants {
static constexpr int ELTS_PER_LDG = BYTES_PER_LDG / sizeof(T);
static_assert(EXPERTS / (ELTS_PER_LDG * WARP_SIZE) == 0 || EXPERTS % (ELTS_PER_LDG * WARP_SIZE) == 0, "");
static constexpr int VECs_PER_THREAD = MAX(1, EXPERTS / (ELTS_PER_LDG * WARP_SIZE));
static constexpr int VPT = VECs_PER_THREAD * ELTS_PER_LDG;
static constexpr int THREADS_PER_ROW = EXPERTS / VPT;
static constexpr int ROWS_PER_WARP = WARP_SIZE / THREADS_PER_ROW;
};
} // namespace detail
// ---------------------------------------------------------------------------
// Per-expert-count launcher helper
// ---------------------------------------------------------------------------
template <typename T, int EXPERTS, int WARPS_PER_TB>
void topkGatingSigmoidLauncherHelper(
const T* input,
const bool* finished,
float* output,
int* indices,
const int num_rows,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias,
double routed_scaling_factor,
int num_fused_shared_experts,
cudaStream_t stream) {
static constexpr std::size_t MAX_BYTES_PER_LDG = 16;
static constexpr int BYTES_PER_LDG = MIN(MAX_BYTES_PER_LDG, sizeof(T) * EXPERTS);
using Constants = detail::TopkConstants<T, EXPERTS, BYTES_PER_LDG>;
static constexpr int VPT = Constants::VPT;
static constexpr int ROWS_PER_WARP = Constants::ROWS_PER_WARP;
const int num_warps = (num_rows + ROWS_PER_WARP - 1) / ROWS_PER_WARP;
const int num_blocks = (num_warps + WARPS_PER_TB - 1) / WARPS_PER_TB;
dim3 block_dim(WARP_SIZE, WARPS_PER_TB);
topkGatingSigmoid<T, VPT, EXPERTS, WARPS_PER_TB, BYTES_PER_LDG><<<num_blocks, block_dim, 0, stream>>>(
input,
finished,
output,
num_rows,
indices,
k,
start_expert,
end_expert,
renormalize,
correction_bias,
routed_scaling_factor,
num_fused_shared_experts);
}
// ---------------------------------------------------------------------------
// Dispatch macro — used inside topkGatingSigmoidKernelLauncher
// ---------------------------------------------------------------------------
#define LAUNCH_SIGMOID(TYPE, NUM_EXPERTS, WARPS_PER_TB) \
topkGatingSigmoidLauncherHelper<TYPE, NUM_EXPERTS, WARPS_PER_TB>( \
gating_output, \
nullptr, \
topk_weights, \
topk_indices, \
num_tokens, \
topk - num_fused_shared_experts, \
0, \
num_experts, \
renormalize, \
correction_bias, \
routed_scaling_factor, \
num_fused_shared_experts, \
stream)
// ---------------------------------------------------------------------------
// Main launcher: dispatches on num_experts
// ---------------------------------------------------------------------------
template <typename T>
void topkGatingSigmoidKernelLauncher(
const T* gating_output,
float* topk_weights,
int* topk_indices,
float* sigmoid_workspace,
const int num_tokens,
const int num_experts,
const int topk,
const bool renormalize,
const float* correction_bias,
double routed_scaling_factor,
int num_fused_shared_experts,
cudaStream_t stream) {
static constexpr int WARPS_PER_TB = 4;
switch (num_experts) {
case 1:
LAUNCH_SIGMOID(T, 1, WARPS_PER_TB);
break;
case 2:
LAUNCH_SIGMOID(T, 2, WARPS_PER_TB);
break;
case 4:
LAUNCH_SIGMOID(T, 4, WARPS_PER_TB);
break;
case 8:
LAUNCH_SIGMOID(T, 8, WARPS_PER_TB);
break;
case 16:
LAUNCH_SIGMOID(T, 16, WARPS_PER_TB);
break;
case 32:
LAUNCH_SIGMOID(T, 32, WARPS_PER_TB);
break;
case 64:
LAUNCH_SIGMOID(T, 64, WARPS_PER_TB);
break;
case 128:
LAUNCH_SIGMOID(T, 128, WARPS_PER_TB);
break;
case 256:
LAUNCH_SIGMOID(T, 256, WARPS_PER_TB);
break;
default: {
// Fallback: non-power-of-2 or >256 experts
using namespace host;
RuntimeCheck(
sigmoid_workspace != nullptr, "sigmoid_workspace must be provided for num_experts that are not a power of 2");
static constexpr int TPB = 256;
moeSigmoid<T, TPB><<<num_tokens, TPB, 0, stream>>>(gating_output, nullptr, sigmoid_workspace, num_experts);
moeTopK<TPB><<<num_tokens, TPB, 0, stream>>>(
sigmoid_workspace,
nullptr,
topk_weights,
topk_indices,
num_experts,
topk - num_fused_shared_experts,
0,
num_experts,
renormalize,
correction_bias,
routed_scaling_factor,
num_fused_shared_experts);
}
}
}
#undef LAUNCH_SIGMOID
} // namespace
// ---------------------------------------------------------------------------
// Host launcher (tvm-ffi interface)
// ---------------------------------------------------------------------------
template <typename T>
void topk_sigmoid(
TensorView gating_output,
TensorView topk_weights,
TensorView topk_ids,
TensorView workspace,
bool renormalize,
tvm::ffi::Optional<TensorView> correction_bias,
double routed_scaling_factor,
int num_fused_shared_experts) {
using namespace host;
// --- Input validation ---
RuntimeCheck(gating_output.dim() == 2, "gating_output must be 2-D");
RuntimeCheck(topk_weights.dim() == 2, "topk_weights must be 2-D");
RuntimeCheck(topk_ids.dim() == 2, "topk_ids must be 2-D");
const int64_t num_tokens = gating_output.shape()[0];
const int64_t num_experts = gating_output.shape()[1];
const int64_t topk = topk_weights.shape()[1];
RuntimeCheck(
topk_weights.shape()[0] == num_tokens && topk_ids.shape()[0] == num_tokens,
"topk_weights and topk_ids must have num_tokens rows");
RuntimeCheck(topk_ids.shape()[1] == topk, "topk_ids second dim must match topk_weights");
RuntimeCheck(topk <= num_experts, "topk must be <= num_experts");
RuntimeCheck(num_fused_shared_experts <= 1, "num_fused_shared_experts must be <= 1");
// correction_bias validation
if (correction_bias.has_value()) {
const auto& bias = correction_bias.value();
RuntimeCheck(bias.dim() == 1, "correction_bias must be 1-D");
RuntimeCheck(bias.shape()[0] == num_experts, "correction_bias size must equal num_experts");
RuntimeCheck(
bias.dtype().code == DLDataTypeCode::kDLFloat && bias.dtype().bits == 32, "correction_bias must be float32");
}
const T* gating_ptr = static_cast<const T*>(gating_output.data_ptr());
float* weights_ptr = static_cast<float*>(topk_weights.data_ptr());
int* indices_ptr = static_cast<int*>(topk_ids.data_ptr());
float* workspace_ptr = static_cast<float*>(workspace.data_ptr());
const float* bias_ptr =
correction_bias.has_value() ? static_cast<const float*>(correction_bias.value().data_ptr()) : nullptr;
cudaStream_t stream = LaunchKernel::resolve_device(gating_output.device());
topkGatingSigmoidKernelLauncher<T>(
gating_ptr,
weights_ptr,
indices_ptr,
workspace_ptr,
static_cast<int>(num_tokens),
static_cast<int>(num_experts),
static_cast<int>(topk),
renormalize,
bias_ptr,
routed_scaling_factor,
num_fused_shared_experts,
stream);
}
@@ -0,0 +1,882 @@
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/runtime.cuh>
#include <sgl_kernel/utils.cuh>
#include <cutlass/arch/arch.h>
#include <cutlass/cutlass.h>
#include "cute/tensor.hpp"
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/epilogue/collective/default_epilogue.hpp"
#include "cutlass/epilogue/thread/linear_combination.h"
#include "cutlass/gemm/collective/collective_builder.hpp"
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/gemm/dispatch_policy.hpp"
#include "cutlass/gemm/group_array_problem_shape.hpp"
#include "cutlass/gemm/kernel/gemm_universal.hpp"
#include "cutlass/tensor_ref.h"
#include "cutlass/util/command_line.h"
#include "cutlass/util/distribution.h"
#include "cutlass/util/host_tensor.h"
#include "cutlass/util/packed_stride.hpp"
#include "cutlass/util/reference/device/gemm.h"
#include "cutlass/util/reference/device/tensor_compare.h"
#include "cutlass/util/reference/host/gett.hpp"
#include "cutlass/util/reference/host/tensor_compare.h"
#include "cutlass/util/reference/host/tensor_fill.h"
#include "cutlass/util/reference/host/tensor_norm.h"
#include "cutlass/util/tensor_view_io.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <unordered_map>
using namespace host;
using namespace cute;
struct WorkspaceKey {
int device_id;
uintptr_t stream;
auto operator==(const WorkspaceKey&) const -> bool = default;
};
struct WorkspaceKeyHash {
auto operator()(const WorkspaceKey& key) const -> size_t {
size_t h1 = std::hash<int>{}(key.device_id);
size_t h2 = std::hash<uintptr_t>{}(key.stream);
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
}
};
struct WorkspaceState {
void* ptr = nullptr;
size_t bytes = 0;
};
inline auto get_cached_workspace(size_t required_bytes, int device_id, cudaStream_t stream) -> void* {
if (required_bytes == 0) {
return nullptr;
}
thread_local std::unordered_map<WorkspaceKey, WorkspaceState, WorkspaceKeyHash> cache;
WorkspaceKey key{device_id, reinterpret_cast<uintptr_t>(stream)};
auto& ws = cache[key];
if (ws.ptr != nullptr && ws.bytes >= required_bytes) {
return ws.ptr;
}
RuntimeDeviceCheck(cudaSetDevice(device_id));
if (ws.ptr != nullptr) {
RuntimeDeviceCheck(cudaFreeAsync(ws.ptr, stream));
ws.ptr = nullptr;
ws.bytes = 0;
}
RuntimeDeviceCheck(cudaMallocAsync(&ws.ptr, required_bytes, stream));
ws.bytes = required_bytes;
return ws.ptr;
}
inline int getSMVersion(int device_id) {
int sm_major = 0;
int sm_minor = 0;
RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_major, cudaDevAttrComputeCapabilityMajor, device_id));
RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_minor, cudaDevAttrComputeCapabilityMinor, device_id));
return sm_major * 10 + sm_minor;
}
template <
typename ElementAB,
typename ElementC,
typename ElementSF,
typename ElementAccumulator,
typename LayoutSFA,
typename LayoutSFB,
typename ScaleConfig>
__global__ void __get_group_gemm_starts(
ElementAB** a_offsets,
ElementAB** b_offsets,
ElementC** out_offsets,
ElementSF** a_scales_offsets,
ElementSF** b_scales_offsets,
ElementAccumulator** alpha_offsets,
LayoutSFA* layout_sfa_base_as_int,
LayoutSFB* layout_sfb_base_as_int,
ElementAB* a_base_as_int,
ElementAB* b_base_as_int,
ElementC* out_base_as_int,
ElementSF* a_scales_base_as_int,
ElementSF* b_scales_base_as_int,
ElementAccumulator* alphas_base_as_int,
const int32_t* expert_offsets,
const int32_t* sf_offsets,
const int32_t* problem_sizes_as_shapes,
const int K,
const int N) {
int64_t expert_id = threadIdx.x;
if (expert_id >= gridDim.x * blockDim.x) {
return;
}
// Originally int32_t but upcasting to int64_t to avoid overflow
// during offset calculations
int64_t expert_offset = static_cast<int64_t>(expert_offsets[expert_id]);
int64_t sf_offset = static_cast<int64_t>(sf_offsets[expert_id]);
// size for block in block scale.
int64_t group_size = 16;
int64_t m = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3]);
int64_t n = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 1]);
int64_t k = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 2]);
assert((m >= 0 && n == N && k == K && k % 2 == 0) && "unexpected problem sizes");
int64_t half_k = static_cast<int64_t>(k / 2);
int64_t group_k = static_cast<int64_t>(k / group_size);
// Shape of A as uint8/byte = [M, K // 2]
// Shape of B as uint8/byte = [E, N, K // 2]
a_offsets[expert_id] = a_base_as_int + expert_offset * half_k;
b_offsets[expert_id] = b_base_as_int + expert_id * n * half_k;
// Shape of C = [M, N]
out_offsets[expert_id] = out_base_as_int + expert_offset * n;
// Shape of a_scale = [sum(sf_sizes), K // group_size]
a_scales_offsets[expert_id] = a_scales_base_as_int + sf_offset * group_k;
assert((reinterpret_cast<uintptr_t>(a_scales_offsets[expert_id]) % 128) == 0 && "TMA requires 128-byte alignment");
// Shape of B scale = [E, N, K // group_size]
b_scales_offsets[expert_id] = b_scales_base_as_int + expert_id * n * group_k;
assert((reinterpret_cast<uintptr_t>(b_scales_offsets[expert_id]) % 128) == 0 && "TMA requires 128-byte alignment");
// Shape of alpha = [E]
alpha_offsets[expert_id] = alphas_base_as_int + expert_id;
LayoutSFA* layout_sfa_ptr = layout_sfa_base_as_int + expert_id;
LayoutSFB* layout_sfb_ptr = layout_sfb_base_as_int + expert_id;
*layout_sfa_ptr = ScaleConfig::tile_atom_to_shape_SFA(
cute::make_shape(static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
*layout_sfb_ptr = ScaleConfig::tile_atom_to_shape_SFB(
cute::make_shape(static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
}
#define __CALL_GET_STARTS_KERNEL_BLOCKSCALE( \
ELEMENT_AB_TYPE, SF_TYPE, TYPE_CHECK, C_TYPE, LayoutSFA, LayoutSFB, ScaleConfig) \
else if (TYPE_CHECK) { \
__get_group_gemm_starts<ELEMENT_AB_TYPE, C_TYPE, SF_TYPE, float, LayoutSFA, LayoutSFB, ScaleConfig> \
<<<1, num_experts, 0, stream>>>( \
static_cast<ELEMENT_AB_TYPE**>(a_starts.data_ptr()), \
static_cast<ELEMENT_AB_TYPE**>(b_starts.data_ptr()), \
static_cast<C_TYPE**>(out_starts.data_ptr()), \
static_cast<SF_TYPE**>(a_scales_starts.data_ptr()), \
static_cast<SF_TYPE**>(b_scales_starts.data_ptr()), \
static_cast<float**>(alpha_starts.data_ptr()), \
reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()), \
reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr()), \
static_cast<ELEMENT_AB_TYPE*>(a_tensors.data_ptr()), \
static_cast<ELEMENT_AB_TYPE*>(b_tensors.data_ptr()), \
static_cast<C_TYPE*>(out_tensors.data_ptr()), \
static_cast<SF_TYPE*>(a_scales.data_ptr()), \
static_cast<SF_TYPE*>(b_scales.data_ptr()), \
static_cast<float*>(alphas.data_ptr()), \
static_cast<int32_t*>(expert_offsets.data_ptr()), \
static_cast<int32_t*>(sf_offsets.data_ptr()), \
static_cast<int32_t*>(problem_sizes.data_ptr()), \
K, \
N); \
}
template <typename LayoutSFA, typename LayoutSFB, typename ScaleConfig>
void run_get_group_gemm_starts(
const tvm::ffi::TensorView a_starts,
const tvm::ffi::TensorView b_starts,
const tvm::ffi::TensorView out_starts,
const tvm::ffi::TensorView a_scales_starts,
const tvm::ffi::TensorView b_scales_starts,
const tvm::ffi::TensorView alpha_starts,
const tvm::ffi::TensorView layout_sfa,
const tvm::ffi::TensorView layout_sfb,
/*these are used for their base addresses*/
tvm::ffi::TensorView const& a_tensors,
tvm::ffi::TensorView const& b_tensors,
tvm::ffi::TensorView const& out_tensors,
tvm::ffi::TensorView const& a_scales,
tvm::ffi::TensorView const& b_scales,
tvm::ffi::TensorView const& alphas,
tvm::ffi::TensorView const& expert_offsets,
tvm::ffi::TensorView const& sf_offsets,
tvm::ffi::TensorView const& problem_sizes,
int M,
int N,
int K) {
int num_experts = static_cast<int>(expert_offsets.size(0));
auto stream = LaunchKernel::resolve_device(a_tensors.device());
RuntimeCheck(out_tensors.size(1) == N, "Output tensor shape doesn't match expected shape");
RuntimeCheck(
K / 2 == b_tensors.size(2),
"b_tensors(dim = 2) and a_tensors(dim = 1) trailing"
" dimension must match");
if (false) {
}
//(ELEMENT_AB_TYPE, BS_TYPE, TENSOR_C_TYPE, C_TYPE, LayoutSFA, LayoutSFB,
// ScaleConfig)
__CALL_GET_STARTS_KERNEL_BLOCKSCALE(
cutlass::float_e2m1_t,
cutlass::float_ue4m3_t,
host::is_type<bf16_t>(out_tensors.dtype()),
cutlass::bfloat16_t,
LayoutSFA,
LayoutSFB,
ScaleConfig)
__CALL_GET_STARTS_KERNEL_BLOCKSCALE(
cutlass::float_e2m1_t,
cutlass::float_ue4m3_t,
host::is_type<fp16_t>(out_tensors.dtype()),
cutlass::half_t,
LayoutSFA,
LayoutSFB,
ScaleConfig)
else {
Panic("Invalid output type (must be float16 or bfloat16)");
}
}
void run_fp4_blockwise_scaled_group_mm_sm120(
tvm::ffi::TensorView output,
const tvm::ffi::TensorView a,
const tvm::ffi::TensorView b,
const tvm::ffi::TensorView a_blockscale,
const tvm::ffi::TensorView b_blockscales,
const tvm::ffi::TensorView alphas,
const tvm::ffi::TensorView ab_strides,
const tvm::ffi::TensorView c_strides,
const tvm::ffi::TensorView problem_sizes,
const tvm::ffi::TensorView expert_offsets,
const tvm::ffi::TensorView sf_offsets,
const tvm::ffi::TensorView a_ptrs,
const tvm::ffi::TensorView b_ptrs,
const tvm::ffi::TensorView out_ptrs,
const tvm::ffi::TensorView a_scales_ptrs,
const tvm::ffi::TensorView b_scales_ptrs,
const tvm::ffi::TensorView alpha_ptrs,
const tvm::ffi::TensorView layout_sfa,
const tvm::ffi::TensorView layout_sfb,
int M,
int N,
int K) {
using ProblemShape = cutlass::gemm::GroupProblemShape<Shape<int32_t, int32_t, int32_t>>;
using ElementType = cutlass::float_e2m1_t;
using ElementSFType = cutlass::float_ue4m3_t;
using ElementA = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
using ElementB = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
using ElementC = cutlass::bfloat16_t;
using ElementD = cutlass::bfloat16_t;
using ElementAccumulator = float;
// Layout definitions
using LayoutA = cutlass::layout::RowMajor;
using LayoutB = cutlass::layout::ColumnMajor;
using LayoutC = cutlass::layout::RowMajor;
using LayoutD = cutlass::layout::RowMajor;
// Alignment constraints
static constexpr int AlignmentA = 32;
static constexpr int AlignmentB = 32;
static constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
static constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
// Architecture definitions
using ArchTag = cutlass::arch::Sm120;
using OperatorClass = cutlass::arch::OpClassBlockScaledTensorOp;
using StageCountType = cutlass::gemm::collective::StageCountAuto;
using ThreadBlockShape = Shape<_128, _128, _128>;
// on the tile size
using ClusterShape = Shape<_1, _1, _1>;
using FusionOperation =
cutlass::epilogue::fusion::LinearCombination<ElementD, ElementAccumulator, ElementC, ElementAccumulator>;
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
ArchTag,
OperatorClass,
ThreadBlockShape,
ClusterShape,
cutlass::epilogue::collective::EpilogueTileAuto,
ElementAccumulator,
ElementAccumulator,
ElementC,
LayoutC*,
AlignmentC,
ElementD,
LayoutC*,
AlignmentD,
cutlass::epilogue::collective::EpilogueScheduleAuto,
FusionOperation>::CollectiveOp;
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
ArchTag,
OperatorClass,
ElementA,
LayoutA*,
AlignmentA,
ElementB,
LayoutB*,
AlignmentB,
ElementAccumulator,
ThreadBlockShape,
ClusterShape,
cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
sizeof(typename CollectiveEpilogue::SharedStorage))>,
cutlass::gemm::KernelPtrArrayTmaWarpSpecializedPingpong>::CollectiveOp;
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop, CollectiveEpilogue>;
using Gemm1SM = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
using Gemm = Gemm1SM;
using StrideA = typename Gemm::GemmKernel::InternalStrideA;
using StrideB = typename Gemm::GemmKernel::InternalStrideB;
using StrideC = typename Gemm::GemmKernel::InternalStrideC;
using StrideD = typename Gemm::GemmKernel::InternalStrideD;
using LayoutSFA = typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFA;
using LayoutSFB = typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFB;
using ScaleConfig = typename Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;
using UnderlyingProblemShape = ProblemShape::UnderlyingProblemShape;
int num_experts = static_cast<int>(expert_offsets.size(0));
run_get_group_gemm_starts<LayoutSFA, LayoutSFB, ScaleConfig>(
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb,
a,
b,
output,
a_blockscale,
b_blockscales,
alphas,
expert_offsets,
sf_offsets,
problem_sizes,
M,
N,
K);
// Create an instance of the GEMM
Gemm gemm_op;
// Initialize problem_sizes_as_shapes correctly
UnderlyingProblemShape* problem_sizes_as_shapes = static_cast<UnderlyingProblemShape*>(problem_sizes.data_ptr());
// Set the Scheduler info
cutlass::KernelHardwareInfo hw_info;
using RasterOrderOptions = cutlass::gemm::kernel::detail::RasterOrderOptions;
typename Gemm::GemmKernel::TileSchedulerArguments scheduler;
scheduler.raster_order = RasterOrderOptions::AlongM;
hw_info.device_id = a.device().device_id;
static std::unordered_map<int, int> cached_sm_counts;
if (cached_sm_counts.find(hw_info.device_id) == cached_sm_counts.end()) {
cached_sm_counts[hw_info.device_id] =
cutlass::KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
}
hw_info.sm_count = std::min(cached_sm_counts[hw_info.device_id], std::numeric_limits<int>::max());
// Mainloop Arguments
typename GemmKernel::MainloopArguments mainloop_args{
static_cast<const ElementType**>(a_ptrs.data_ptr()),
static_cast<StrideA*>(ab_strides.data_ptr()),
static_cast<const ElementType**>(b_ptrs.data_ptr()),
static_cast<StrideB*>(ab_strides.data_ptr()),
static_cast<const ElementSFType**>(a_scales_ptrs.data_ptr()),
reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),
static_cast<const ElementSFType**>(b_scales_ptrs.data_ptr()),
reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr())};
// Epilogue Arguments
typename GemmKernel::EpilogueArguments epilogue_args{
{}, // epilogue.thread
nullptr,
static_cast<StrideC*>(c_strides.data_ptr()),
static_cast<ElementD**>(out_ptrs.data_ptr()),
static_cast<StrideC*>(c_strides.data_ptr())};
auto& fusion_args = epilogue_args.thread;
fusion_args.alpha_ptr_array = reinterpret_cast<float**>(alpha_ptrs.data_ptr());
fusion_args.dAlpha = {_0{}, _0{}, 1};
fusion_args.beta = 0.0f;
// Gemm Arguments
typename GemmKernel::Arguments args{
cutlass::gemm::GemmUniversalMode::kGrouped,
{num_experts, problem_sizes_as_shapes, nullptr},
mainloop_args,
epilogue_args,
hw_info,
scheduler};
size_t workspace_size = Gemm::get_workspace_size(args);
const cudaStream_t stream = LaunchKernel::resolve_device(a.device());
void* workspace = get_cached_workspace(workspace_size, hw_info.device_id, stream);
auto can_implement_status = gemm_op.can_implement(args);
RuntimeCheck(
can_implement_status == cutlass::Status::kSuccess,
"Failed to implement GEMM: ",
cutlassGetStatusString(can_implement_status));
// Run the GEMM
auto status = gemm_op.initialize(args, workspace);
RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to initialize GEMM: ", cutlassGetStatusString(status));
status = gemm_op.run(args, workspace, stream);
RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to run GEMM: ", cutlassGetStatusString(status));
}
template <typename OutType>
void run_fp4_blockwise_scaled_group_mm_sm100(
tvm::ffi::TensorView output,
const tvm::ffi::TensorView a,
const tvm::ffi::TensorView b,
const tvm::ffi::TensorView a_blockscale,
const tvm::ffi::TensorView b_blockscales,
const tvm::ffi::TensorView alphas,
const tvm::ffi::TensorView ab_strides,
const tvm::ffi::TensorView c_strides,
const tvm::ffi::TensorView problem_sizes,
const tvm::ffi::TensorView expert_offsets,
const tvm::ffi::TensorView sf_offsets,
const tvm::ffi::TensorView a_ptrs,
const tvm::ffi::TensorView b_ptrs,
const tvm::ffi::TensorView out_ptrs,
const tvm::ffi::TensorView a_scales_ptrs,
const tvm::ffi::TensorView b_scales_ptrs,
const tvm::ffi::TensorView alpha_ptrs,
const tvm::ffi::TensorView layout_sfa,
const tvm::ffi::TensorView layout_sfb,
int M,
int N,
int K) {
using ProblemShape = cutlass::gemm::GroupProblemShape<Shape<int32_t, int32_t, int32_t>>;
using ElementType = cutlass::float_e2m1_t;
using ElementSFType = cutlass::float_ue4m3_t;
using ElementA = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
using ElementB = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
using ElementC = OutType;
using ElementD = ElementC;
using ElementAccumulator = float;
// Layout definitions
using LayoutA = cutlass::layout::RowMajor;
using LayoutB = cutlass::layout::ColumnMajor;
using LayoutC = cutlass::layout::RowMajor;
using LayoutD = LayoutC;
// Alignment constraints
static constexpr int AlignmentA = 32;
static constexpr int AlignmentB = 32;
static constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
static constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
// Architecture definitions
using ArchTag = cutlass::arch::Sm100;
using EpilogueOperatorClass = cutlass::arch::OpClassTensorOp; // Epilogue Operator class tag
using MainloopOperatorClass = cutlass::arch::OpClassBlockScaledTensorOp; // Mainloop Operator class tag
using StageCountType = cutlass::gemm::collective::StageCountAuto; // Stage count maximized based
// on the tile size
using ClusterShape = Shape<_1, _1, _1>;
struct MMA1SMConfig {
using MmaTileShape = Shape<_128, _128, _128>;
using KernelSchedule = cutlass::gemm::KernelPtrArrayTmaWarpSpecialized1SmNvf4Sm100; // Kernel to launch
using EpilogueSchedule = cutlass::epilogue::PtrArrayTmaWarpSpecialized1Sm; // Epilogue to launch
};
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
ArchTag,
EpilogueOperatorClass,
typename MMA1SMConfig::MmaTileShape,
ClusterShape,
Shape<_128, _64>,
ElementAccumulator,
ElementAccumulator,
ElementC,
LayoutC*,
AlignmentC,
ElementD,
LayoutC*,
AlignmentD,
typename MMA1SMConfig::EpilogueSchedule>::CollectiveOp;
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
ArchTag,
MainloopOperatorClass,
ElementA,
LayoutA*,
AlignmentA,
ElementB,
LayoutB*,
AlignmentB,
ElementAccumulator,
typename MMA1SMConfig::MmaTileShape,
ClusterShape,
cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
sizeof(typename CollectiveEpilogue::SharedStorage))>,
typename MMA1SMConfig::KernelSchedule>::CollectiveOp;
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop, CollectiveEpilogue>;
using Gemm1SM = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
using Gemm = Gemm1SM;
using StrideA = typename Gemm::GemmKernel::InternalStrideA;
using StrideB = typename Gemm::GemmKernel::InternalStrideB;
using StrideC = typename Gemm::GemmKernel::InternalStrideC;
using StrideD = typename Gemm::GemmKernel::InternalStrideD;
using LayoutSFA = typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFA;
using LayoutSFB = typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFB;
using ScaleConfig = typename Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;
using UnderlyingProblemShape = ProblemShape::UnderlyingProblemShape;
int num_experts = static_cast<int>(expert_offsets.size(0));
run_get_group_gemm_starts<LayoutSFA, LayoutSFB, ScaleConfig>(
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb,
a,
b,
output,
a_blockscale,
b_blockscales,
alphas,
expert_offsets,
sf_offsets,
problem_sizes,
M,
N,
K);
// Create an instance of the GEMM
Gemm gemm_op;
// Initialize problem_sizes_as_shapes correctly
UnderlyingProblemShape* problem_sizes_as_shapes = static_cast<UnderlyingProblemShape*>(problem_sizes.data_ptr());
// Set the Scheduler info
cutlass::KernelHardwareInfo hw_info;
using RasterOrderOptions = typename cutlass::gemm::kernel::detail::PersistentTileSchedulerSm100GroupParams<
typename ProblemShape::UnderlyingProblemShape>::RasterOrderOptions;
typename Gemm::GemmKernel::TileSchedulerArguments scheduler;
scheduler.raster_order = RasterOrderOptions::AlongM;
hw_info.device_id = a.device().device_id;
static std::unordered_map<int, int> cached_sm_counts;
if (cached_sm_counts.find(hw_info.device_id) == cached_sm_counts.end()) {
cached_sm_counts[hw_info.device_id] =
cutlass::KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
}
hw_info.sm_count = std::min(cached_sm_counts[hw_info.device_id], std::numeric_limits<int>::max());
// Mainloop Arguments
typename GemmKernel::MainloopArguments mainloop_args{
static_cast<const ElementType**>(a_ptrs.data_ptr()),
static_cast<StrideA*>(ab_strides.data_ptr()),
static_cast<const ElementType**>(b_ptrs.data_ptr()),
static_cast<StrideB*>(ab_strides.data_ptr()),
static_cast<const ElementSFType**>(a_scales_ptrs.data_ptr()),
reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),
static_cast<const ElementSFType**>(b_scales_ptrs.data_ptr()),
reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr())};
// Epilogue Arguments
typename GemmKernel::EpilogueArguments epilogue_args{
{}, // epilogue.thread
nullptr,
static_cast<StrideC*>(c_strides.data_ptr()),
static_cast<ElementD**>(out_ptrs.data_ptr()),
static_cast<StrideC*>(c_strides.data_ptr())};
auto& fusion_args = epilogue_args.thread;
fusion_args.alpha_ptr_array = reinterpret_cast<float**>(alpha_ptrs.data_ptr());
fusion_args.dAlpha = {_0{}, _0{}, 1};
// Gemm Arguments
typename GemmKernel::Arguments args{
cutlass::gemm::GemmUniversalMode::kGrouped,
{num_experts, problem_sizes_as_shapes, nullptr},
mainloop_args,
epilogue_args,
hw_info,
scheduler};
size_t workspace_size = Gemm::get_workspace_size(args);
const cudaStream_t stream = LaunchKernel::resolve_device(a.device());
void* workspace = get_cached_workspace(workspace_size, hw_info.device_id, stream);
auto can_implement_status = gemm_op.can_implement(args);
RuntimeCheck(
can_implement_status == cutlass::Status::kSuccess,
"Failed to implement GEMM: ",
cutlassGetStatusString(can_implement_status));
// Run the GEMM
auto status = gemm_op.initialize(args, workspace);
RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to initialize GEMM: ", cutlassGetStatusString(status));
status = gemm_op.run(args, workspace, stream);
RuntimeCheck(status == cutlass::Status::kSuccess, "Failed to run GEMM: ", cutlassGetStatusString(status));
}
void cutlass_fp4_group_mm_sm100a_sm120a(
tvm::ffi::TensorView output,
const tvm::ffi::TensorView a,
const tvm::ffi::TensorView b,
const tvm::ffi::TensorView a_blockscale,
const tvm::ffi::TensorView b_blockscales,
const tvm::ffi::TensorView alphas,
const tvm::ffi::TensorView ab_strides,
const tvm::ffi::TensorView c_strides,
const tvm::ffi::TensorView problem_sizes,
const tvm::ffi::TensorView expert_offsets,
const tvm::ffi::TensorView sf_offsets,
const tvm::ffi::TensorView a_ptrs,
const tvm::ffi::TensorView b_ptrs,
const tvm::ffi::TensorView out_ptrs,
const tvm::ffi::TensorView a_scales_ptrs,
const tvm::ffi::TensorView b_scales_ptrs,
const tvm::ffi::TensorView alpha_ptrs,
const tvm::ffi::TensorView layout_sfa,
const tvm::ffi::TensorView layout_sfb) {
auto check_cuda_contig = [](const tvm::ffi::TensorView t, const char* name) {
RuntimeCheck(t.device().device_type == kDLCUDA, name, " must be a CUDA tensor");
RuntimeCheck(t.is_contiguous(), name, " must be contiguous");
};
check_cuda_contig(output, "output");
check_cuda_contig(a, "a");
check_cuda_contig(b, "b");
check_cuda_contig(a_blockscale, "a_blockscale");
check_cuda_contig(b_blockscales, "b_blockscales");
check_cuda_contig(alphas, "alphas");
check_cuda_contig(ab_strides, "ab_strides");
check_cuda_contig(c_strides, "c_strides");
check_cuda_contig(problem_sizes, "problem_sizes");
check_cuda_contig(expert_offsets, "expert_offsets");
check_cuda_contig(sf_offsets, "sf_offsets");
check_cuda_contig(a_ptrs, "a_ptrs");
check_cuda_contig(b_ptrs, "b_ptrs");
check_cuda_contig(out_ptrs, "out_ptrs");
check_cuda_contig(a_scales_ptrs, "a_scales_ptrs");
check_cuda_contig(b_scales_ptrs, "b_scales_ptrs");
check_cuda_contig(alpha_ptrs, "alpha_ptrs");
check_cuda_contig(layout_sfa, "layout_sfa");
check_cuda_contig(layout_sfb, "layout_sfb");
RuntimeCheck(
output.device() == a.device() && a.device() == b.device() && a.device() == a_blockscale.device() &&
a.device() == b_blockscales.device() && a.device() == alphas.device() && a.device() == ab_strides.device() &&
a.device() == c_strides.device() && a.device() == problem_sizes.device() &&
a.device() == expert_offsets.device() && a.device() == sf_offsets.device() && a.device() == a_ptrs.device() &&
a.device() == b_ptrs.device() && a.device() == out_ptrs.device() && a.device() == a_scales_ptrs.device() &&
a.device() == b_scales_ptrs.device() && a.device() == alpha_ptrs.device() &&
a.device() == layout_sfa.device() && a.device() == layout_sfb.device(),
"all tensors must be on the same device");
RuntimeCheck(host::is_type<uint8_t>(a.dtype()), "a must be uint8");
RuntimeCheck(host::is_type<uint8_t>(b.dtype()), "b must be uint8");
RuntimeCheck(host::is_type<fp8_e4m3_t>(a_blockscale.dtype()), "a_blockscale must be float8_e4m3fn");
RuntimeCheck(host::is_type<fp8_e4m3_t>(b_blockscales.dtype()), "b_blockscales must be float8_e4m3fn");
RuntimeCheck(host::is_type<float>(alphas.dtype()), "alphas must be float32");
RuntimeCheck(host::is_type<int64_t>(ab_strides.dtype()), "ab_strides must be int64");
RuntimeCheck(host::is_type<int64_t>(c_strides.dtype()), "c_strides must be int64");
RuntimeCheck(host::is_type<int32_t>(problem_sizes.dtype()), "problem_sizes must be int32");
RuntimeCheck(host::is_type<int32_t>(expert_offsets.dtype()), "expert_offsets must be int32");
RuntimeCheck(host::is_type<int32_t>(sf_offsets.dtype()), "sf_offsets must be int32");
RuntimeCheck(host::is_type<int64_t>(a_ptrs.dtype()), "a_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(b_ptrs.dtype()), "b_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(out_ptrs.dtype()), "out_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(a_scales_ptrs.dtype()), "a_scales_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(b_scales_ptrs.dtype()), "b_scales_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(alpha_ptrs.dtype()), "alpha_ptrs must be int64");
RuntimeCheck(host::is_type<int64_t>(layout_sfa.dtype()), "layout_sfa must be int64");
RuntimeCheck(host::is_type<int64_t>(layout_sfb.dtype()), "layout_sfb must be int64");
RuntimeCheck(
host::is_type<bf16_t>(output.dtype()) || host::is_type<fp16_t>(output.dtype()),
"output must be bfloat16 or float16");
RuntimeCheck(a.dim() == 2, "a must be 2D");
RuntimeCheck(b.dim() == 3, "b must be 3D");
RuntimeCheck(a_blockscale.dim() == 2, "a_blockscale must be 2D");
RuntimeCheck(b_blockscales.dim() == 3, "b_blockscales must be 3D");
RuntimeCheck(alphas.dim() == 1, "alphas must be 1D");
RuntimeCheck(ab_strides.dim() == 1, "ab_strides must be 1D");
RuntimeCheck(c_strides.dim() == 1, "c_strides must be 1D");
RuntimeCheck(problem_sizes.dim() == 2, "problem_sizes must be 2D");
RuntimeCheck(expert_offsets.dim() == 1, "expert_offsets must be 1D");
RuntimeCheck(sf_offsets.dim() == 1, "sf_offsets must be 1D");
RuntimeCheck(a_ptrs.dim() == 1, "a_ptrs must be 1D");
RuntimeCheck(b_ptrs.dim() == 1, "b_ptrs must be 1D");
RuntimeCheck(out_ptrs.dim() == 1, "out_ptrs must be 1D");
RuntimeCheck(a_scales_ptrs.dim() == 1, "a_scales_ptrs must be 1D");
RuntimeCheck(b_scales_ptrs.dim() == 1, "b_scales_ptrs must be 1D");
RuntimeCheck(alpha_ptrs.dim() == 1, "alpha_ptrs must be 1D");
RuntimeCheck(layout_sfa.dim() == 2, "layout_sfa must be 2D");
RuntimeCheck(layout_sfb.dim() == 2, "layout_sfb must be 2D");
RuntimeCheck(problem_sizes.size(1) == 3, "problem_sizes must have shape (num_experts, 3)");
const int num_experts = static_cast<int>(expert_offsets.size(0));
RuntimeCheck(problem_sizes.size(0) == num_experts, "problem_sizes size mismatch with expert_offsets");
RuntimeCheck(sf_offsets.size(0) == num_experts, "sf_offsets size mismatch with expert_offsets");
RuntimeCheck(alphas.size(0) == num_experts, "alphas size mismatch with expert_offsets");
RuntimeCheck(ab_strides.size(0) == num_experts, "ab_strides size mismatch with expert_offsets");
RuntimeCheck(c_strides.size(0) == num_experts, "c_strides size mismatch with expert_offsets");
RuntimeCheck(a_ptrs.size(0) == num_experts, "a_ptrs size mismatch with expert_offsets");
RuntimeCheck(b_ptrs.size(0) == num_experts, "b_ptrs size mismatch with expert_offsets");
RuntimeCheck(out_ptrs.size(0) == num_experts, "out_ptrs size mismatch with expert_offsets");
RuntimeCheck(a_scales_ptrs.size(0) == num_experts, "a_scales_ptrs size mismatch with expert_offsets");
RuntimeCheck(b_scales_ptrs.size(0) == num_experts, "b_scales_ptrs size mismatch with expert_offsets");
RuntimeCheck(alpha_ptrs.size(0) == num_experts, "alpha_ptrs size mismatch with expert_offsets");
RuntimeCheck(layout_sfa.size(0) == num_experts && layout_sfa.size(1) == 5, "layout_sfa must be [num_experts, 5]");
RuntimeCheck(layout_sfb.size(0) == num_experts && layout_sfb.size(1) == 5, "layout_sfb must be [num_experts, 5]");
int M = static_cast<int>(a.size(0));
int N = static_cast<int>(b.size(1));
int K = static_cast<int>(2 * b.size(2));
RuntimeCheck(output.dim() == 2, "output must be 2D");
RuntimeCheck(output.size(0) == M && output.size(1) == N, "output shape mismatch");
auto sm_version = getSMVersion(a.device().device_id);
if (sm_version == 100 || sm_version == 103) {
if (host::is_type<bf16_t>(output.dtype())) {
run_fp4_blockwise_scaled_group_mm_sm100<cutlass::bfloat16_t>(
output,
a,
b,
a_blockscale,
b_blockscales,
alphas,
ab_strides,
c_strides,
problem_sizes,
expert_offsets,
sf_offsets,
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb,
M,
N,
K);
} else {
run_fp4_blockwise_scaled_group_mm_sm100<cutlass::half_t>(
output,
a,
b,
a_blockscale,
b_blockscales,
alphas,
ab_strides,
c_strides,
problem_sizes,
expert_offsets,
sf_offsets,
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb,
M,
N,
K);
}
} else if (sm_version >= 120) {
if (host::is_type<bf16_t>(output.dtype())) {
run_fp4_blockwise_scaled_group_mm_sm120(
output,
a,
b,
a_blockscale,
b_blockscales,
alphas,
ab_strides,
c_strides,
problem_sizes,
expert_offsets,
sf_offsets,
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb,
M,
N,
K);
} else {
Panic("SM120 path currently supports only bfloat16 output");
}
} else {
RuntimeCheck(false, "Unsupported SM version: ", sm_version);
}
}
void cutlass_fp4_group_mm(
tvm::ffi::TensorView output,
const tvm::ffi::TensorView a,
const tvm::ffi::TensorView b,
const tvm::ffi::TensorView a_blockscale,
const tvm::ffi::TensorView b_blockscales,
const tvm::ffi::TensorView alphas,
const tvm::ffi::TensorView ab_strides,
const tvm::ffi::TensorView c_strides,
const tvm::ffi::TensorView problem_sizes,
const tvm::ffi::TensorView expert_offsets,
const tvm::ffi::TensorView sf_offsets,
const tvm::ffi::TensorView a_ptrs,
const tvm::ffi::TensorView b_ptrs,
const tvm::ffi::TensorView out_ptrs,
const tvm::ffi::TensorView a_scales_ptrs,
const tvm::ffi::TensorView b_scales_ptrs,
const tvm::ffi::TensorView alpha_ptrs,
const tvm::ffi::TensorView layout_sfa,
const tvm::ffi::TensorView layout_sfb) {
cutlass_fp4_group_mm_sm100a_sm120a(
output,
a,
b,
a_blockscale,
b_blockscales,
alphas,
ab_strides,
c_strides,
problem_sizes,
expert_offsets,
sf_offsets,
a_ptrs,
b_ptrs,
out_ptrs,
a_scales_ptrs,
b_scales_ptrs,
alpha_ptrs,
layout_sfa,
layout_sfb);
}
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2023 by FlashInfer team.
*
* 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.
*/
#pragma once
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/extra/c_env_api.h>
#include <tvm/ffi/function.h>
#include "dlpack/dlpack.h"
using tvm::ffi::Tensor;
using tvm::ffi::TensorView;
namespace ffi = tvm::ffi;
inline constexpr int64_t encode_dlpack_dtype(DLDataType dtype) {
return (dtype.code << 16) | (dtype.bits << 8) | dtype.lanes;
}
constexpr DLDataType dl_uint8 = DLDataType{kDLUInt, 8, 1};
constexpr DLDataType dl_uint16 = DLDataType{kDLUInt, 16, 1};
constexpr DLDataType dl_uint32 = DLDataType{kDLUInt, 32, 1};
constexpr DLDataType dl_uint64 = DLDataType{kDLUInt, 64, 1};
constexpr DLDataType dl_int8 = DLDataType{kDLInt, 8, 1};
constexpr DLDataType dl_int16 = DLDataType{kDLInt, 16, 1};
constexpr DLDataType dl_int32 = DLDataType{kDLInt, 32, 1};
constexpr DLDataType dl_int64 = DLDataType{kDLInt, 64, 1};
constexpr DLDataType dl_float16 = DLDataType{kDLFloat, 16, 1};
constexpr DLDataType dl_float32 = DLDataType{kDLFloat, 32, 1};
constexpr DLDataType dl_float64 = DLDataType{kDLFloat, 64, 1};
constexpr DLDataType dl_float8_e4m3fn = DLDataType{kDLFloat8_e4m3fn, 8, 1};
constexpr DLDataType dl_float8_e5m2 = DLDataType{kDLFloat8_e5m2, 8, 1};
constexpr DLDataType dl_float4_e2m1fn = DLDataType{kDLFloat4_e2m1fn, 4, 1};
constexpr DLDataType dl_float4_e2m1fn_x2 = DLDataType{kDLFloat4_e2m1fn, 4, 2};
constexpr DLDataType dl_bfloat16 = DLDataType{kDLBfloat, 16, 1};
constexpr DLDataType dl_bool = DLDataType{kDLBool, 8, 1};
constexpr int64_t float16_code = encode_dlpack_dtype(dl_float16);
constexpr int64_t bfloat16_code = encode_dlpack_dtype(dl_bfloat16);
constexpr int64_t float32_code = encode_dlpack_dtype(dl_float32);
constexpr int64_t uint8_code = encode_dlpack_dtype(dl_uint8);
constexpr int64_t int32_code = encode_dlpack_dtype(dl_int32);
constexpr int64_t int64_code = encode_dlpack_dtype(dl_int64);
constexpr int64_t float8_e4m3fn_code = encode_dlpack_dtype(dl_float8_e4m3fn);
constexpr int64_t float8_e5m2_code = encode_dlpack_dtype(dl_float8_e5m2);
constexpr int64_t float4_e2m1fn_code = encode_dlpack_dtype(dl_float4_e2m1fn);
constexpr DLDevice cpu = DLDevice{kDLCPU, 0};
#define CHECK_CUDA(x) TVM_FFI_ICHECK_EQ(x.device().device_type, kDLCUDA) << #x " must be a CUDA tensor";
#define CHECK_CPU(x) TVM_FFI_ICHECK_EQ(x.device().device_type, kDLCPU) << #x " must be a host tensor";
#define CHECK_CONTIGUOUS(x) TVM_FFI_ICHECK(x.IsContiguous()) << #x " must be contiguous";
#define CHECK_LAST_DIM_CONTIGUOUS(x) \
TVM_FFI_ICHECK_EQ(x.stride(-1), 1) \
#x "must be contiguous at last dimension";
#define CHECK_INPUT(x) \
CHECK_CUDA(x); \
CHECK_CONTIGUOUS(x)
#define CHECK_INPUT_TYPE(x, st) TVM_FFI_ICHECK_EQ(x.dtype(), st) << "Inconsistency of Tensor type: " #x;
#define CHECK_INPUT_AND_TYPE(x, st) \
CHECK_CUDA(x); \
CHECK_CONTIGUOUS(x); \
CHECK_INPUT_TYPE(x, st)
#define CHECK_LAST_DIM_CONTIGUOUS_INPUT(x) \
CHECK_CUDA(x); \
CHECK_LAST_DIM_CONTIGUOUS(x)
#define CHECK_DIM(d, x) TVM_FFI_ICHECK_EQ(x.ndim(), d) << #x " must be a " #d "D tensor";
#define CHECK_DEVICE(a, b) \
TVM_FFI_ICHECK_EQ(a.device().device_type, b.device().device_type); \
TVM_FFI_ICHECK_EQ(a.device().device_id, b.device().device_id);
inline cudaStream_t get_current_stream() {
int device;
cudaGetDevice(&device);
return static_cast<cudaStream_t>(TVMFFIEnvGetStream(kDLCUDA, device));
}
inline cudaStream_t get_stream(DLDevice device) {
return static_cast<cudaStream_t>(TVMFFIEnvGetStream(device.device_type, device.device_id));
}
inline int64_t get_element_size(ffi::Tensor x) {
return (x.dtype().bits * x.dtype().lanes) / 8;
}
inline int64_t get_element_size(ffi::TensorView x) {
return (x.dtype().bits * x.dtype().lanes) / 8;
}
inline ffi::Tensor alloc_tensor(tvm::ffi::Shape shape, DLDataType dtype, DLDevice device) {
return ffi::Tensor::FromEnvAlloc(TVMFFIEnvTensorAlloc, shape, dtype, device);
}