Skip to content
KernelIndex
Search⌘K

submission 116542

Samir · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

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

submission6.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-nvfp4-gemv-116542?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
23.9µs
#79 of 678
2025-11-30

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:ebe2dea04725423e5f4924a8326ed5921779a344253dea013b38cf03f5503213
license declaredunknown
license concludedunknown
authorsSamir
imported2026-08-15

Techniques

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

shared-memory__shared__ float smem[THREADS_PER_BLOCK];
vector-width = half2__device__ __forceinline__ half2 cvt_fp8x2_to_h2(uint16_t packed) {

Kernel source

submission6.py285 lines
import torch
from torch.utils.cpp_extension import load_inline
from task import input_t, output_t

# Kernel configuration parameters
sf_vec_size = 16

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


# CUDA kernel source code for NVFP4 block-scaled GEMV
# Optimized with PTX intrinsics for FP4->FP16 conversion and warp-per-row design
cuda_source = r"""
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>

// Configuration
#define THREADS_PER_BLOCK 128
#define WARPS_PER_BLOCK (THREADS_PER_BLOCK / 32)

// Kernel parameters struct - passed by value (fits in registers)
struct KernelParams {
    const uint8_t* a;
    const uint8_t* b;
    const uint8_t* sfa;
    const uint8_t* sfb;
    __half* c;
    int m;
    int k;
    int l;
};

// Convert 2 packed FP8 E4M3 values to half2 using PTX
__device__ __forceinline__ half2 cvt_fp8x2_to_h2(uint16_t packed) {
    uint32_t result;
    asm volatile(
        "{\n\t"
        ".reg .b16 fp8x2;\n\t"
        "mov.b16 fp8x2, %1;\n\t"
        "cvt.rn.f16x2.e4m3x2 %0, fp8x2;\n\t"
        "}"
        : "=r"(result)
        : "h"(packed)
    );
    return *reinterpret_cast<half2*>(&result);
}

// Warp-level reduction using shuffle
__device__ __forceinline__ float warp_reduce_sum(float val) {
    val += __shfl_down_sync(0xffffffff, val, 16);
    val += __shfl_down_sync(0xffffffff, val, 8);
    val += __shfl_down_sync(0xffffffff, val, 4);
    val += __shfl_down_sync(0xffffffff, val, 2);
    val += __shfl_down_sync(0xffffffff, val, 1);
    return val;
}

// Convert 4 bytes (8 FP4 E2M1 values) to 4 half2 values using PTX
__device__ __forceinline__ void cvt_fp4x8_to_h2x4(uint32_t packed, half2& h0, half2& h1, half2& h2, half2& h3) {
    uint32_t f16x2_0, f16x2_1, f16x2_2, f16x2_3;

    asm volatile(
        "{\n\t"
        ".reg .b8 b0, b1, b2, b3;\n\t"
        "mov.b32 {b0, b1, b2, b3}, %4;\n\t"
        "cvt.rn.f16x2.e2m1x2 %0, b0;\n\t"
        "cvt.rn.f16x2.e2m1x2 %1, b1;\n\t"
        "cvt.rn.f16x2.e2m1x2 %2, b2;\n\t"
        "cvt.rn.f16x2.e2m1x2 %3, b3;\n\t"
        "}"
        : "=r"(f16x2_0), "=r"(f16x2_1), "=r"(f16x2_2), "=r"(f16x2_3)
        : "r"(packed)
    );

    h0 = *reinterpret_cast<half2*>(&f16x2_0);
    h1 = *reinterpret_cast<half2*>(&f16x2_1);
    h2 = *reinterpret_cast<half2*>(&f16x2_2);
    h3 = *reinterpret_cast<half2*>(&f16x2_3);
}


// ============================================================================
// Tunable GEMV kernel with WARPS_PER_ROW and M_TILE parameters
// WARPS_PER_ROW: Number of warps cooperating on each row (1, 2, 4, or 8)
// M_TILE: Number of rows each warp-group processes
// Processes 16 bytes (32 FP4 = 2 scale factors) per thread per iteration
// Simplified: Assumes M % 128 == 0 and K % 64 == 0
// ============================================================================
template <int WARPS_PER_ROW, int M_TILE>
__global__ void nvfp4_gemv_tunable_kernel(KernelParams p) {
    constexpr int THREADS_PER_ROW = WARPS_PER_ROW * 32;
    constexpr int ROW_GROUPS_PER_BLOCK = THREADS_PER_BLOCK / THREADS_PER_ROW;
    constexpr int ROWS_PER_BLOCK = ROW_GROUPS_PER_BLOCK * M_TILE;

    int tidx = threadIdx.x;
    int row_group = tidx / THREADS_PER_ROW;      // Which row-group within this block
    int tid_in_row = tidx % THREADS_PER_ROW;     // Thread ID within the row-group

    int row_tile = blockIdx.x;
    int batch = blockIdx.y;

    // Each row-group handles M_TILE rows
    int row_start = row_tile * ROWS_PER_BLOCK + row_group * M_TILE;

    int k_half = p.k / 2;
    int sf_k = p.k / 16;

    // B pointers (shared across all rows)
    const uint4* b_row_vec4 = reinterpret_cast<const uint4*>(p.b + batch * 128 * k_half);
    const uint16_t* sfb_row_vec2 = reinterpret_cast<const uint16_t*>(p.sfb + batch * 128 * sf_k);

    // Half2 accumulators for M_TILE rows - keeps partial sums in FP16
    // Each half2 holds {sum_for_scale_group_0, sum_for_scale_group_1}
    half2 acc_h2[M_TILE];
    #pragma unroll
    for (int r = 0; r < M_TILE; r++) {
        acc_h2[r] = __float2half2_rn(0.0f);
    }

    // Number of 16-byte chunks
    int num_vec4 = k_half / 16;

    // Threads stride by THREADS_PER_ROW across the K dimension
    for (int vec4_idx = tid_in_row; vec4_idx < num_vec4; vec4_idx += THREADS_PER_ROW) {
        // === PHASE 1: Issue all loads first ===
        uint4 a_vec4[M_TILE];
        uint16_t sfa_packed[M_TILE];
        #pragma unroll
        for (int r = 0; r < M_TILE; r++) {
            int row = row_start + r;
            const uint4* a_row_vec4 = reinterpret_cast<const uint4*>(p.a + row * k_half + batch * p.m * k_half);
            const uint16_t* sfa_row_vec2 = reinterpret_cast<const uint16_t*>(p.sfa + row * sf_k + batch * p.m * sf_k);
            a_vec4[r] = a_row_vec4[vec4_idx];
            sfa_packed[r] = sfa_row_vec2[vec4_idx];
        }

        uint4 b_vec4 = b_row_vec4[vec4_idx];
        uint16_t sfb_packed = sfb_row_vec2[vec4_idx];

        // === PHASE 2: Convert B (once, reused for all rows) ===
        half2 b_h2[16];
        cvt_fp4x8_to_h2x4(b_vec4.x, b_h2[0], b_h2[1], b_h2[2], b_h2[3]);
        cvt_fp4x8_to_h2x4(b_vec4.y, b_h2[4], b_h2[5], b_h2[6], b_h2[7]);
        cvt_fp4x8_to_h2x4(b_vec4.z, b_h2[8], b_h2[9], b_h2[10], b_h2[11]);
        cvt_fp4x8_to_h2x4(b_vec4.w, b_h2[12], b_h2[13], b_h2[14], b_h2[15]);

        // Convert B scales to half2: {scale_b0, scale_b1}
        half2 scale_b_h2 = cvt_fp8x2_to_h2(sfb_packed);

        // === PHASE 3: Convert A and compute for each row ===
        #pragma unroll
        for (int r = 0; r < M_TILE; r++) {
            half2 a_h2[16];
            cvt_fp4x8_to_h2x4(a_vec4[r].x, a_h2[0], a_h2[1], a_h2[2], a_h2[3]);
            cvt_fp4x8_to_h2x4(a_vec4[r].y, a_h2[4], a_h2[5], a_h2[6], a_h2[7]);
            cvt_fp4x8_to_h2x4(a_vec4[r].z, a_h2[8], a_h2[9], a_h2[10], a_h2[11]);
            cvt_fp4x8_to_h2x4(a_vec4[r].w, a_h2[12], a_h2[13], a_h2[14], a_h2[15]);

            // Convert A scales to half2 and compute combined scales in FP16
            half2 scale_a_h2 = cvt_fp8x2_to_h2(sfa_packed[r]);
            half2 combined_scale_h2 = __hmul2(scale_a_h2, scale_b_h2);

            // Dot product for first 16 FP4 elements (scale group 0)
            half2 sum_h2_0 = __float2half2_rn(0.0f);
            #pragma unroll
            for (int i = 0; i < 8; i++) {
                sum_h2_0 = __hfma2(a_h2[i], b_h2[i], sum_h2_0);
            }
            // Horizontal sum for scale group 0
            __half dot0 = __hadd(sum_h2_0.x, sum_h2_0.y);

            // Dot product for second 16 FP4 elements (scale group 1)
            half2 sum_h2_1 = __float2half2_rn(0.0f);
            #pragma unroll
            for (int i = 8; i < 16; i++) {
                sum_h2_1 = __hfma2(a_h2[i], b_h2[i], sum_h2_1);
            }
            __half dot1 = __hadd(sum_h2_1.x, sum_h2_1.y);

            // Pack dots into half2 and accumulate with scaling - stays in FP16
            half2 dots_h2 = make_half2(dot0, dot1);
            acc_h2[r] = __hfma2(dots_h2, combined_scale_h2, acc_h2[r]);
        }
    }

    // Convert half2 accumulators to float for reduction
    // Sum both scale groups: acc_h2[r].x + acc_h2[r].y
    float acc[M_TILE];
    #pragma unroll
    for (int r = 0; r < M_TILE; r++) {
        acc[r] = __half2float(acc_h2[r].x) + __half2float(acc_h2[r].y);
    }

    // === Reduction across THREADS_PER_ROW threads ===
    if constexpr (WARPS_PER_ROW == 1) {
        // Single warp: just warp reduction
        #pragma unroll
        for (int r = 0; r < M_TILE; r++) {
            float result = warp_reduce_sum(acc[r]);
            if (tid_in_row == 0) {
                p.c[row_start + r + batch * p.m] = __float2half(result);
            }
        }
    } else {
        // Multiple warps: need shared memory reduction
        __shared__ float smem[THREADS_PER_BLOCK];

        #pragma unroll
        for (int r = 0; r < M_TILE; r++) {
            // First do warp-level reduction
            float warp_sum = warp_reduce_sum(acc[r]);

            // Lane 0 of each warp writes to shared memory
            int warp_in_row = tid_in_row / 32;
            int lane_id = tid_in_row % 32;
            if (lane_id == 0) {
                smem[row_group * WARPS_PER_ROW + warp_in_row] = warp_sum;
            }
            __syncthreads();

            // First thread of each row-group reduces across warps
            if (tid_in_row == 0) {
                float result = 0.0f;
                #pragma unroll
                for (int w = 0; w < WARPS_PER_ROW; w++) {
                    result += smem[row_group * WARPS_PER_ROW + w];
                }
                p.c[row_start + r + batch * p.m] = __float2half(result);
            }
            __syncthreads();
        }
    }
}

// Launcher
template <int WARPS_PER_ROW, int M_TILE>
void launch_tunable_kernel(KernelParams p) {
    dim3 block(THREADS_PER_BLOCK);
    constexpr int THREADS_PER_ROW = WARPS_PER_ROW * 32;
    constexpr int ROW_GROUPS_PER_BLOCK = THREADS_PER_BLOCK / THREADS_PER_ROW;
    constexpr int ROWS_PER_BLOCK = ROW_GROUPS_PER_BLOCK * M_TILE;
    dim3 grid((p.m + ROWS_PER_BLOCK - 1) / ROWS_PER_BLOCK, p.l);
    nvfp4_gemv_tunable_kernel<WARPS_PER_ROW, M_TILE><<<grid, block>>>(p);
}

void nvfp4_gemv_cuda(std::vector<torch::Tensor> data) {
    // Unpack: data = (a, b, sfa, sfb, _, _, c)
    KernelParams p;
    p.a = reinterpret_cast<const uint8_t*>(data[0].data_ptr());
    p.b = reinterpret_cast<const uint8_t*>(data[1].data_ptr());
    p.sfa = reinterpret_cast<const uint8_t*>(data[2].data_ptr());
    p.sfb = reinterpret_cast<const uint8_t*>(data[3].data_ptr());
    p.c = reinterpret_cast<__half*>(data[6].data_ptr());
    p.m = data[0].size(0);
    p.k = data[0].size(1) * 2;
    p.l = data[0].size(2);

    launch_tunable_kernel<1, 1>(p);
}
"""

cpp_source = "void nvfp4_gemv_cuda(std::vector<torch::Tensor> data);"

# Compile at module load time
print("Compiling CUDA kernel...")
_module = load_inline(
    name='nvfp4_gemv',
    cpp_sources=[cpp_source],
    cuda_sources=[cuda_source],
    functions=['nvfp4_gemv_cuda'],
    verbose=True,
    extra_cflags=['-O3'],
    extra_cuda_cflags=['-O3', '--expt-relaxed-constexpr', '-gencode', 'arch=compute_100a,code=sm_100a']
)
_kernel = _module.nvfp4_gemv_cuda


def custom_kernel(data: input_t) -> output_t:
    _kernel(data)
    return data[6]
scrolls · 285 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