Skip to content
KernelIndex
Search⌘K

submission 76782

ethylene · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

No package. Vendor the mirrored source: 583 lines, June 9 Researcher Reciprocity License v1.0.

submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-nvfp4-gemv-76782?include=source"
interfacepython
Compatibility
measured onNVIDIA B200
declared hardwareNVIDIA B200
architecturessm_100
dtypesfp8_e4m3, nvfp4

Benchmark evidence

1 measurement across 1 GPU, fastest first.

Operation / workload
Hardware
Latency
Rank
Observed
NVFP4 GEMVsuite of 3 cases
NVIDIA B200
37.7µs
#206 of 678
2025-11-14

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:279bab193faf7393a574eb880dbeab76158d763aa34d473faef00f348a3f92a8
license declaredunknown
license concludedunknown
authorsethylene
imported2026-08-15

Techniques

Extracted from the mirrored source by pattern, never inferred. Each row cites its line.

fp4PyTorch reference implementation of NVFP4 block-scaled GEMV.
fp8__forceinline__ static __device__ __half fp8e4m3_to_half(unsigned char x) {
shared-memoryextern __shared__ unsigned int shmem32[];

Kernel source

submission.py583 lines
# Auto-generated submission.py (2025-11-14T16:02:25Z)
# Combines:
#  - gemv/reference.py (verbatim)
#  - gemv/custom_kernel.cu (embedded as string)
#  - gemv/custom_kernel.py (adapted to use embedded CUDA source and in-file reference symbols)

# ===== reference.py =====
import torch
from task import input_t, output_t
from utils import make_match_reference

# Scaling factor vector size
sf_vec_size = 16


# Helper function for ceiling division
def ceil_div(a, b):
    return (a + b - 1) // b


# Helper function to convert scale factor tensor to blocked format
def to_blocked(input_matrix):
    rows, cols = input_matrix.shape

    # Please ensure rows and cols are multiples of 128 and 4 respectively
    n_row_blocks = ceil_div(rows, 128)
    n_col_blocks = ceil_div(cols, 4)

    padded = input_matrix
    blocks = padded.view(n_row_blocks, 128, n_col_blocks, 4).permute(0, 2, 1, 3)
    rearranged = blocks.reshape(-1, 4, 32, 4).transpose(1, 2).reshape(-1, 32, 16)

    return rearranged.flatten()


def ref_kernel(
    data: input_t,
) -> output_t:
    """
    PyTorch reference implementation of NVFP4 block-scaled GEMV.
    """
    a_ref, b_ref, sfa_ref_cpu, sfb_ref_cpu, _, _, c_ref = data

    # Get dimensions from MxNxL layout
    _, _, l = c_ref.shape

    # Call torch._scaled_mm to compute the GEMV result
    for l_idx in range(l):
        # Convert the scale factor tensor to blocked format
        scale_a = to_blocked(sfa_ref_cpu[:, :, l_idx])
        scale_b = to_blocked(sfb_ref_cpu[:, :, l_idx])
        # (m, k) @ (n, k).T -> (m, n)
        res = torch._scaled_mm(
            a_ref[:, :, l_idx],
            b_ref[:, :, l_idx].transpose(0, 1),
            scale_a.cuda(),
            scale_b.cuda(),
            bias=None,
            out_dtype=torch.float16,
        )
        c_ref[:, 0, l_idx] = res[:, 0]
    return c_ref


def generate_input(
    m: int,
    k: int,
    l: int,
    seed: int,
):
    """
    Generate input tensors for NVFP4 block-scaled GEMV.

    Args:
        m: Number of rows in matrix A
        k: Number of columns in A (and length of vector b)
        l: Batch size
        seed: Random seed for reproducibility

    Returns:
        Tuple of (a, b, scale_a, scale_b, c) where:
            a: [m, k, l] - Input matrix in torch.float4e2m1fn_x2 data type
            b: [1, k, l] - Input vector in torch.float4e2m1fn_x2 data type
            scale_a: [m, k, l] - Input scale factors in torch.float8e4m3fn data type
            scale_b: [1, k, l] - Input scale factors in torch.float8e4m3fn data type
            scale_a_permuted: [32, 4, rest_m, 4, rest_k, l] - Input scale factors in torch.float8e4m3fn data type
            scale_b_permuted: [32, 4, rest_n, 4, rest_k, l] - Input scale factors in torch.float8e4m3fn data type
            c: [m, 1, l] - Output vector in torch.float16 data type
    """
    torch.manual_seed(seed)

    # GEMV N dimension is always 1
    n = 1
    # Scaling factor needs to pad the N size to 128
    n_padded_128 = 128

    # Generate uint8 tensor, then convert to float4e2m1fn_x2 data type
    a_ref = torch.randint(
        0, 4, (l, m, k // 2), dtype=torch.uint8, device="cuda"
    ).permute(1, 2, 0)
    # Pad b tensor's N dimension to 128 to call torch._scaled_mm for nvfp4 dot product computation
    b_ref = torch.randint(
        0, 4, (l, n_padded_128, k // 2), dtype=torch.uint8, device="cuda"
    ).permute(1, 2, 0)
    a_ref = a_ref.view(torch.float4_e2m1fn_x2)
    b_ref = b_ref.view(torch.float4_e2m1fn_x2)

    # Create float16 output tensor
    c_ref = torch.randn((l, m, n), dtype=torch.float16, device="cuda").permute(1, 2, 0)

    # Helper function to prepare the scale factor tensors for both reference
    # kernel and customize kernel. The customized data layout can be found in:
    # https://docs.nvidia.com/cuda/cublas/index.html?highlight=fp4#d-block-scaling-factors-layout
    def create_scale_factor_tensors(l, mn, sf_k):
        # Create the reference scale factor tensor (mn, sf_k, l) on CPU.
        ref_shape = (l, mn, sf_k)
        ref_permute_order = (1, 2, 0)
        # Init with uint8 tensor, then convert to float8_e4m3fn
        ref_f8_random_int = torch.randint(
            0, 3, ref_shape, dtype=torch.int8, device="cuda"
        )
        ref_f8_torch_tensor = ref_f8_random_int.to(dtype=torch.float8_e4m3fn)
        # permute to match ref_permute_order
        ref_f8_torch_tensor_permuted = ref_f8_torch_tensor.permute(*ref_permute_order)

        atom_m = (32, 4)
        atom_k = 4
        mma_shape = (
            l,  # batch size
            ceil_div(mn, atom_m[0] * atom_m[1]),
            ceil_div(sf_k, atom_k),
            atom_m[0],
            atom_m[1],
            atom_k,
        )

        # Reorder scale factor tensor to (32, 4, rest_m, 4, rest_k, l) layout
        # Which is needed by the CuTe customized kernel
        mma_permute_order = (3, 4, 1, 5, 2, 0)
        # Generate a random int8 tensor, then convert to float8_e4m3fn
        rand_int_tensor = torch.randint(
            0, 3, mma_shape, dtype=torch.int8, device="cuda"
        )
        reordered_f8_torch_tensor = rand_int_tensor.to(dtype=torch.float8_e4m3fn)
        # Permute according to mma_permute_order
        reordered_f8_torch_tensor = reordered_f8_torch_tensor.permute(
            *mma_permute_order
        )

        # GPU-side vectorized reordering (replaces slow CPU nested loops)
        # Create index grids for all dimensions
        i_idx = torch.arange(mn, device="cuda")
        j_idx = torch.arange(sf_k, device="cuda")
        b_idx = torch.arange(l, device="cuda")

        # Create meshgrid for all combinations of (i, j, b)
        i_grid, j_grid, b_grid = torch.meshgrid(i_idx, j_idx, b_idx, indexing="ij")

        # Calculate target indices in vectorized manner
        mm = i_grid // (atom_m[0] * atom_m[1])
        mm32 = i_grid % atom_m[0]
        mm4 = (i_grid % 128) // atom_m[0]
        kk = j_grid // atom_k
        kk4 = j_grid % atom_k

        # Perform the reordering with advanced indexing (all on GPU)
        reordered_f8_torch_tensor[mm32, mm4, mm, kk4, kk, b_grid] = (
            ref_f8_torch_tensor_permuted[i_grid, j_grid, b_grid]
        )

        return ref_f8_torch_tensor_permuted.cpu(), reordered_f8_torch_tensor

    sf_k = ceil_div(k, sf_vec_size)
    sfa_ref_cpu, sfa_permuted = create_scale_factor_tensors(l, m, sf_k)
    sfb_ref_cpu, sfb_permuted = create_scale_factor_tensors(l, n_padded_128, sf_k)

    sfa_ref = sfa_ref_cpu.to("cuda")
    sfb_ref = sfb_ref_cpu.to("cuda")

    return (a_ref, b_ref, sfa_ref, sfb_ref, sfa_permuted, sfb_permuted, c_ref)


check_implementation = make_match_reference(ref_kernel, rtol=1e-03, atol=1e-03)

# ===== custom_kernel.cu (embedded) =====
custom_kernel_cuda_source = r"""
// CUTLASS-assisted NVFP4 GEMV with CTA-level B/SFB staging (B200 tuned)
#include <cutlass/cutlass.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda_fp16.h>
#include <cuda_fp4.h>
#include <cuda_fp8.hpp>
#include <cuda_runtime.h>
#include <torch/extension.h>

using at::Tensor;

static inline int64_t ceil_div(int64_t a, int64_t b) { return (a + b - 1) / b; }

__forceinline__ static __device__ __half fp8e4m3_to_half(unsigned char x) {
    __half_raw h = __nv_cvt_fp8_to_halfraw((__nv_fp8_storage_t)x, __NV_E4M3);
    return *reinterpret_cast<__half*>(&h);
}

__device__ __align__(4) unsigned int g_fp4x2_lut[256];
__forceinline__ static __device__ __half2 fp4x2e2m1_to_half2_lut(unsigned char x, const unsigned int* __restrict__ lut) {
    __half2 h2; reinterpret_cast<unsigned int&>(h2) = lut[x]; return h2;
}
__global__ void init_fp4x2_lut_kernel() {
    unsigned idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < 256u) {
        __half2_raw h2r = __nv_cvt_fp4x2_to_halfraw2((__nv_fp4x2_storage_t)idx, __NV_E2M1);
        g_fp4x2_lut[idx] = h2r.x;
    }
}

template <int WARPS_PER_BLOCK, int ROWS_PER_WARP, int MIN_BLOCKS>
__global__ __launch_bounds__(WARPS_PER_BLOCK * 32, MIN_BLOCKS)
void nvfp4_gemv_kernel_sfa2d_warp_rows(
    const unsigned char* __restrict__ A_l_m_k2_base,
    int64_t a_sL, int64_t a_sM, int64_t a_sK2,
    const unsigned char* __restrict__ B_l_k2_n_base,
    int64_t b_sL, int64_t b_sK2,
    const unsigned char* __restrict__ SFA2_base,
    int64_t sfa2_sM, int64_t sfa2_sJ, int64_t sfa2_sL,
    const unsigned char* __restrict__ SFB2_base,
    int64_t sfb2_sJ, int64_t sfb2_sL,
    __half* __restrict__ out_ml,
    int64_t out_sM, int64_t out_sL,
    int64_t M, int64_t L,
    int64_t sf_k) {

    extern __shared__ unsigned int shmem32[];
    // Layout of shared segment:
    // [0..255]   : LUT (uint32_t per entry)
    // [next .. ] : B decoded as __half2[8] per j  (32 bytes per j)
    // [next .. ] : SFB converted to __half (2 bytes per j)
    unsigned int* fp4x2_lut = shmem32;  // 256 entries (1KB)
    if (threadIdx.x < 256) shmem32[threadIdx.x] = g_fp4x2_lut[threadIdx.x];
    __syncthreads();

    const int li = blockIdx.y;
    const int warp_id = threadIdx.x >> 5;
    const int lane = threadIdx.x & 31;
    const int m_base = blockIdx.x * (WARPS_PER_BLOCK * ROWS_PER_WARP) + warp_id * ROWS_PER_WARP;
    if (li >= L || m_base >= M) return;

    const unsigned char* A_base = A_l_m_k2_base + (size_t)li * (size_t)a_sL;
    const unsigned char* B_base = B_l_k2_n_base + (size_t)li * (size_t)b_sL;
    const unsigned char* SFA_row_base[ROWS_PER_WARP];
    bool valid_row[ROWS_PER_WARP];
    const unsigned char* a_row_ptr[ROWS_PER_WARP];
    float acc[ROWS_PER_WARP];
#pragma unroll
    for (int r = 0; r < ROWS_PER_WARP; ++r) {
        const int m = m_base + r;
        valid_row[r] = (m < M);
        if (valid_row[r]) {
            SFA_row_base[r] = SFA2_base + (size_t)m * (size_t)sfa2_sM + (size_t)li * (size_t)sfa2_sL;
            a_row_ptr[r] = A_base + (size_t)m * (size_t)a_sM;
        } else {
            SFA_row_base[r] = nullptr;
            a_row_ptr[r] = nullptr;
        }
        acc[r] = 0.0f;
    }
    const unsigned char* SFB_l_base = SFB2_base + (size_t)li * (size_t)sfb2_sL;
    __half* out_base = out_ml + (size_t)li * (size_t)out_sL;
    const unsigned full_mask = 0xFFFFFFFFu;

    // Stage B (packed fp4x2 bytes) and SFB (fp8) into shared memory for this CTA.
    // Every block cooperatively loads all j in [0, sf_k).
    // Using 64-bit vectorized loads for B and 8-bit loads for SFB, then convert SFB to __half in registers.
    // Shared pointers (compute after LUT region):
    // Place B array aligned to 16 bytes after LUT (256*4 bytes).
    const size_t lut_bytes = 256u * sizeof(unsigned int);
    unsigned char* smem_bytes = reinterpret_cast<unsigned char*>(shmem32);
    unsigned long long* smem_B = reinterpret_cast<unsigned long long*>(smem_bytes + lut_bytes);
    __half* smem_SFBh = reinterpret_cast<__half*>(smem_B + (size_t)sf_k);

    // Cooperative load: distribute j across all threads in the block
    for (int64_t j = threadIdx.x; j < sf_k; j += blockDim.x) {
        // Load B 8B packet for group j
        const int64_t k2_base = j << 3;
        unsigned long long b_pack =
            __ldg(reinterpret_cast<const unsigned long long*>(B_base + (size_t)k2_base * (size_t)b_sK2));
        smem_B[j] = b_pack;
        // Load and convert SFB[j] to __half
        unsigned char sfb_u8 = __ldg(reinterpret_cast<const unsigned char*>(SFB_l_base + (size_t)j * (size_t)sfb2_sJ));
        __half sfb_h = fp8e4m3_to_half(sfb_u8);
        smem_SFBh[j] = sfb_h;
    }
    __syncthreads();

    // Each warp now iterates j using staged B/SFB from shared memory.
    for (int64_t j = lane; j < sf_k; j += 64) {
        const int64_t j0 = j;
        const int64_t j1 = j + 32;

        // j0
        const int64_t k2_base0 = j0 << 3;
        const uint64_t b_pack0 = smem_B[j0];
        const __half sb0 = smem_SFBh[j0];

        __half scale0_h[ROWS_PER_WARP];
        uint64_t a_pack0[ROWS_PER_WARP];
#pragma unroll
        for (int r = 0; r < ROWS_PER_WARP; ++r) {
            if (valid_row[r]) {
                const unsigned char* sfa_ptr = SFA_row_base[r] + (size_t)j0 * (size_t)sfa2_sJ;
                unsigned char sa0_u8 = __ldg(reinterpret_cast<const unsigned char*>(sfa_ptr));
                __half sa0_h = fp8e4m3_to_half(sa0_u8);
                scale0_h[r] = __hmul(sa0_h, sb0);
                a_pack0[r] = __ldg(reinterpret_cast<const unsigned long long*>(a_row_ptr[r] + (size_t)k2_base0 * (size_t)a_sK2));
            } else {
                scale0_h[r] = __float2half(0.0f);
                a_pack0[r] = 0ull;
            }
        }

        __half2 accA[ROWS_PER_WARP];
        __half2 accB[ROWS_PER_WARP];
#pragma unroll
        for (int r = 0; r < ROWS_PER_WARP; ++r) { accA[r] = __float2half2_rn(0.0f); accB[r] = __float2half2_rn(0.0f); }
        for (int t = 0; t < 8; t += 2) {
            unsigned char b_byte = (unsigned char)((b_pack0 >> (t * 8)) & 0xFFu);
            __half2 b_h2 = fp4x2e2m1_to_half2_lut(b_byte, fp4x2_lut);
#pragma unroll
            for (int r = 0; r < ROWS_PER_WARP; ++r) {
                if (!valid_row[r]) continue;
                unsigned char a_byte = (unsigned char)((a_pack0[r] >> (t * 8)) & 0xFFu);
                __half2 a_h2 = fp4x2e2m1_to_half2_lut(a_byte, fp4x2_lut);
                accA[r] = __hfma2(a_h2, b_h2, accA[r]);
            }
            b_byte = (unsigned char)((b_pack0 >> ((t + 1) * 8)) & 0xFFu);
            b_h2 = fp4x2e2m1_to_half2_lut(b_byte, fp4x2_lut);
#pragma unroll
            for (int r = 0; r < ROWS_PER_WARP; ++r) {
                if (!valid_row[r]) continue;
                unsigned char a_byte = (unsigned char)((a_pack0[r] >> ((t + 1) * 8)) & 0xFFu);
                __half2 a_h2 = fp4x2e2m1_to_half2_lut(a_byte, fp4x2_lut);
                accB[r] = __hfma2(a_h2, b_h2, accB[r]);
            }
        }
#pragma unroll
        for (int r = 0; r < ROWS_PER_WARP; ++r) {
            if (!valid_row[r]) continue;
            const __half2 s2 = __hadd2(accA[r], accB[r]);
            const __half s = __hadd(__low2half(s2), __high2half(s2));
            acc[r] += __half2float(__hmul(s, scale0_h[r]));
        }

        // j1
        if (j1 < sf_k) {
            const int64_t k2_base1 = j1 << 3;
            const uint64_t b_pack1 = smem_B[j1];
            const __half sb1 = smem_SFBh[j1];

            __half scale1_h[ROWS_PER_WARP];
            uint64_t a_pack1[ROWS_PER_WARP];
#pragma unroll
            for (int r = 0; r < ROWS_PER_WARP; ++r) {
                if (valid_row[r]) {
                    const unsigned char* sfa_ptr = SFA_row_base[r] + (size_t)j1 * (size_t)sfa2_sJ;
                    unsigned char sa1_u8 = __ldg(reinterpret_cast<const unsigned char*>(sfa_ptr));
                    __half sa1_h = fp8e4m3_to_half(sa1_u8);
                    scale1_h[r] = __hmul(sa1_h, sb1);
                    a_pack1[r] = __ldg(reinterpret_cast<const unsigned long long*>(a_row_ptr[r] + (size_t)k2_base1 * (size_t)a_sK2));
                } else {
                    scale1_h[r] = __float2half(0.0f);
                    a_pack1[r] = 0ull;
                }
            }

            __half2 accA1[ROWS_PER_WARP];
            __half2 accB1[ROWS_PER_WARP];
#pragma unroll
            for (int r = 0; r < ROWS_PER_WARP; ++r) { accA1[r] = __float2half2_rn(0.0f); accB1[r] = __float2half2_rn(0.0f); }
            for (int t = 0; t < 8; t += 2) {
                unsigned char b_byte = (unsigned char)((b_pack1 >> (t * 8)) & 0xFFu);
                __half2 b_h2 = fp4x2e2m1_to_half2_lut(b_byte, fp4x2_lut);
#pragma unroll
                for (int r = 0; r < ROWS_PER_WARP; ++r) {
                    if (!valid_row[r]) continue;
                    unsigned char a_byte = (unsigned char)((a_pack1[r] >> (t * 8)) & 0xFFu);
                    __half2 a_h2 = fp4x2e2m1_to_half2_lut(a_byte, fp4x2_lut);
                    accA1[r] = __hfma2(a_h2, b_h2, accA1[r]);
                }
                b_byte = (unsigned char)((b_pack1 >> ((t + 1) * 8)) & 0xFFu);
                b_h2 = fp4x2e2m1_to_half2_lut(b_byte, fp4x2_lut);
#pragma unroll
                for (int r = 0; r < ROWS_PER_WARP; ++r) {
                    if (!valid_row[r]) continue;
                    unsigned char a_byte = (unsigned char)((a_pack1[r] >> ((t + 1) * 8)) & 0xFFu);
                    __half2 a_h2 = fp4x2e2m1_to_half2_lut(a_byte, fp4x2_lut);
                    accB1[r] = __hfma2(a_h2, b_h2, accB1[r]);
                }
            }
#pragma unroll
            for (int r = 0; r < ROWS_PER_WARP; ++r) {
                if (!valid_row[r]) continue;
                const __half2 s2 = __hadd2(accA1[r], accB1[r]);
                const __half s = __hadd(__low2half(s2), __high2half(s2));
                acc[r] += __half2float(__hmul(s, scale1_h[r]));
            }
        }
    }

    float sum[ROWS_PER_WARP];
#pragma unroll
    for (int r = 0; r < ROWS_PER_WARP; ++r) sum[r] = acc[r];
#pragma unroll
    for (int offset = 16; offset > 0; offset >>= 1) {
#pragma unroll
        for (int r = 0; r < ROWS_PER_WARP; ++r) sum[r] += __shfl_down_sync(full_mask, sum[r], offset);
    }
    if ((threadIdx.x & 31) == 0) {
#pragma unroll
        for (int r = 0; r < ROWS_PER_WARP; ++r) {
            const int m = m_base + r;
            if (m < M) out_base[m * out_sM] = __float2half_rn(sum[r]);
        }
    }
}

// Host entry point (known-good launcher)
torch::Tensor custom_gemv_cuda(torch::Tensor a,
    torch::Tensor b,  // expects [Npad, K/2, L]
    torch::Tensor sfa, torch::Tensor sfb, torch::Tensor c) {
    TORCH_CHECK(a.device().is_cuda() && b.device().is_cuda() && c.device().is_cuda(), "a, b, c must be CUDA tensors");
    TORCH_CHECK(a.dim() == 3 && b.dim() == 3 && c.dim() == 3, "Expected a,b,c to be 3D tensors");

    const int64_t M = a.size(0);
    const int64_t K2 = a.size(1);  // K packed as K/2 columns (fp4x2)
    const int64_t L = a.size(2);

    Tensor a_lmk2 = a.permute({2, 0, 1});
    Tensor b_lk2n = b.permute({2, 1, 0});
    Tensor out_ml = c.select(1, 0);
    auto out_strides = out_ml.strides();

    const int64_t sf_k = ceil_div(K2, (int64_t)8);  // K/16
    auto a_strides = a_lmk2.strides();
    auto b_strides = b_lk2n.strides();

    static bool lut_initialized = false;
    if (!lut_initialized) { auto stream = at::cuda::getCurrentCUDAStream(); init_fp4x2_lut_kernel<<<1, 256, 0, stream>>>(); lut_initialized = true; }
    const size_t lut_bytes = 256u * sizeof(unsigned int);

    TORCH_CHECK(sfa.dim() == 3 && sfb.dim() == 3, "sfa/sfb must be 3D dense tensors [M,sf_k,L] and [Npad,sf_k,L]");
    auto sfa_s = sfa.strides();
    auto sfb_s = sfb.strides();

    if (L >= 8) {
        // For larger batch L, increase per-CTA parallelism to reuse staged B/SFB
        // Use a moderate WPB to balance occupancy and L2 pressure.
        constexpr int WPB = 4;           // warps per block
        constexpr int RPW = 4;           // rows per warp
        dim3 block(WPB * 32);
        dim3 grid((unsigned)((M + WPB * RPW - 1) / (WPB * RPW)), (unsigned)L);
        size_t shmem_bytes = 256u * sizeof(unsigned int)                     // LUT
                           + (size_t)ceil_div((int64_t)K2, (int64_t)8) * 8  // B staged (uint64 per j)
                           + (size_t)ceil_div((int64_t)K2, (int64_t)8) * sizeof(__half); // SFB staged (__half per j)
        nvfp4_gemv_kernel_sfa2d_warp_rows<WPB, RPW, 1><<<grid, block, shmem_bytes, at::cuda::getCurrentCUDAStream()>>>(
            reinterpret_cast<const unsigned char*>(a_lmk2.data_ptr()), a_strides[0], a_strides[1], a_strides[2],
            reinterpret_cast<const unsigned char*>(b_lk2n.data_ptr()), b_strides[0], b_strides[1],
            reinterpret_cast<const unsigned char*>(sfa.data_ptr()), sfa_s[0], sfa_s[1], sfa_s[2],
            reinterpret_cast<const unsigned char*>(sfb.data_ptr()), sfb_s[1], sfb_s[2],
            reinterpret_cast<__half*>(out_ml.data_ptr()), out_strides[0], out_strides[1],
            M, L, sf_k);
    } else {
        // For small L, keep moderate CTA size for occupancy
        constexpr int WPB = 4;
        constexpr int RPW = 4;
        dim3 block(WPB * 32);
        dim3 grid((unsigned)((M + WPB * RPW - 1) / (WPB * RPW)), (unsigned)L);
        size_t shmem_bytes = 256u * sizeof(unsigned int)
                           + (size_t)ceil_div((int64_t)K2, (int64_t)8) * 8
                           + (size_t)ceil_div((int64_t)K2, (int64_t)8) * sizeof(__half);
        nvfp4_gemv_kernel_sfa2d_warp_rows<WPB, RPW, 2><<<grid, block, shmem_bytes, at::cuda::getCurrentCUDAStream()>>>(
            reinterpret_cast<const unsigned char*>(a_lmk2.data_ptr()), a_strides[0], a_strides[1], a_strides[2],
            reinterpret_cast<const unsigned char*>(b_lk2n.data_ptr()), b_strides[0], b_strides[1],
            reinterpret_cast<const unsigned char*>(sfa.data_ptr()), sfa_s[0], sfa_s[1], sfa_s[2],
            reinterpret_cast<const unsigned char*>(sfb.data_ptr()), sfb_s[1], sfb_s[2],
            reinterpret_cast<__half*>(out_ml.data_ptr()), out_strides[0], out_strides[1],
            M, L, sf_k);
    }
    return c;
}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    m.def("custom_gemv_cuda", &custom_gemv_cuda, "NVFP4 GEMV custom kernel (raw CUDA)");
}
"""

# ===== custom_kernel.py (adapted) =====
from pathlib import Path
import torch
from torch.utils.cpp_extension import load_inline
import os


# read custom_kernel.cu
root_dir = Path(__file__).parent

_cc = "".join(map(str, torch.cuda.get_device_capability()))
_cuda_cflags = [
    "-O3",
    "--use_fast_math",
    "-Xptxas=-dlcm=ca",
    # Allow the compiler to choose register count for better ILP/latency hiding
    "-std=c++17",
    f"-gencode=arch=compute_{_cc},code=sm_{_cc}",
    f"-gencode=arch=compute_{_cc},code=compute_{_cc}",
]

custom_kernel_module = load_inline(
    name="custom_kernel_nvfp4_gemv",
    cpp_sources="""
#include <torch/extension.h>
torch::Tensor custom_gemv_cuda(torch::Tensor a, torch::Tensor b, torch::Tensor sfa, torch::Tensor sfb, torch::Tensor c);
""",
    cuda_sources=custom_kernel_cuda_source,
    extra_cuda_cflags=_cuda_cflags,
)
torch.cuda.empty_cache()


def custom_kernel(data: input_t) -> output_t:
    """Run the NVFP4 block-scaled GEMV kernel using dense 3D scale tensors.

    Expects:
      - sfa: [M, K/16, L] in torch.float8_e4m3fn
      - sfb: [Npad, K/16, L] in torch.float8_e4m3fn
    """
    assert len(data) == 7
    a, b, sfa, sfb, _sfa_perm, _sfb_perm, c = data

    custom_kernel_module.custom_gemv_cuda(a, b, sfa, sfb, c)
    return c


if __name__ == "__main__":

    def bench_custom(data_tuple, warmup=10, iters=50):
        for _ in range(warmup):
            _ = custom_kernel(data_tuple)
        torch.cuda.synchronize()

        times_ms = []
        for _ in range(iters):
            start = torch.cuda.Event(enable_timing=True)
            end = torch.cuda.Event(enable_timing=True)
            start.record()
            _ = custom_kernel(data_tuple)
            end.record()
            torch.cuda.synchronize()
            times_ms.append(start.elapsed_time(end))  # milliseconds
        return times_ms

    list_params = [
        (7168, 16384, 1),
        (4096, 7168, 8),
        (7168, 2048, 4),
    ]
    for params in list_params:
        M, K, L = params
        data = generate_input(M, K, L, seed=0)
        # Optional correctness check (set DO_CHECK=1 to enable)
        if os.environ.get("DO_CHECK", "0") == "1":
            out = custom_kernel(data)
            out = out.clone()
            results = check_implementation(data, out)
            print("Check implementation:", results)

        # End-to-end timing of custom_kernel (includes GPU scale prep + CUDA kernel)
        times = bench_custom(data, warmup=10, iters=50)
        avg_ms = sum(times) / len(times)
        min_ms = min(times)
        print(
            f"E2E custom_kernel M={M} K={K} L={L}: avg {avg_ms:.3f} ms, min {min_ms:.3f} ms over {len(times)} runs (10 warmups)"
        )
scrolls · 583 lines total

Source code from GPU Mode and the KernelBot dataset · June 9 Researcher Reciprocity License v1.0

Best evidence level for this revision: reported

JSON