Skip to content
KernelIndex
Search⌘K

submission 85010

dpang · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

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

sub3.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-nvfp4-gemv-85010?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
120.4µs
#474 of 678
2025-11-19

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:e070ec1cfa21577f442e7e49b82ce6468bd803e6b9581d8c16f8e6223a693a6c
license declaredunknown
license concludedunknown
authorsdpang
imported2026-08-26

Techniques

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

fp4Optimized kernel for NVFP4 block-scaled GEMV.

Kernel source

sub3.py237 lines
import torch
import math
from typing import Tuple

# --- Type Definitions (Assuming availability in competition environment) ---
# Note: These types must be available in the PyTorch build used for the competition.
input_t = Tuple[torch.Tensor, ...]
output_t = torch.Tensor

# Scaling factor vector size
sf_vec_size = 16

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

# --- 1. GPU-Optimized Scale Factor Pre-processing ---

def optimized_to_blocked_batched(input_tensor: torch.Tensor) -> torch.Tensor:
    """
    GPU-native, batched implementation of the scale factor permutation.
    
    This function replaces the slow CPU-based 'to_blocked' inside the loop 
    and is run only ONCE before the main computation, eliminating CPU-GPU overhead.
    
    Args:
        input_tensor: A 3D tensor of shape (L, Rows, Cols) on the GPU.
                      (L is the batch size)
                      
    Returns:
        A 2D tensor of shape (L, BlockedSize) containing the permuted scales 
        on the GPU, ready for use by torch._scaled_mm.
    """
    l, rows, cols = input_tensor.shape
    
    # Calculate padding dimensions based on required tile sizes (128 and 4)
    n_row_blocks = ceil_div(rows, 128)
    n_col_blocks = ceil_div(cols, 4)
    
    # Pad the tensor on the GPU
    padded = torch.nn.functional.pad(
        input_tensor, 
        (0, n_col_blocks * 4 - cols, 0, n_row_blocks * 128 - rows)
    )
    
    # Reshape and Permute operations (Vectorized on GPU)
    
    # 1. Block the tensor: (L, n_row_blocks, 128, n_col_blocks, 4)
    blocks = padded.view(l, n_row_blocks, 128, n_col_blocks, 4)
    
    # 2. Permute: (L, n_row_blocks, n_col_blocks, 128, 4)
    blocks = blocks.permute(0, 1, 3, 2, 4)
    
    # 3. Reshape/View for final blocking structure: (L, -1, 4, 32, 4)
    # The -1 dimension combines the n_row_blocks and n_col_blocks * 128/4
    rearranged = blocks.reshape(l, -1, 4, 32, 4)
    
    # 4. Transpose/View final 4x32x4 structure to 32x4x4 (-> 32x16)
    rearranged = rearranged.transpose(2, 3)
    
    # 5. Final reshape: (L, -1, 32, 16)
    rearranged = rearranged.reshape(l, -1, 32, 16)
    
    # 6. Flatten the blocked dimensions (excluding the batch size L)
    return rearranged.flatten(start_dim=1)

# --- 2. Optimized Kernel (Renamed to custom_kernel) ---

def custom_kernel(
    data: input_t,
) -> output_t:
    """
    Optimized kernel for NVFP4 block-scaled GEMV.
    
    This implementation avoids the Host-to-Device latency by pre-processing 
    scale factors in a batched, GPU-native operation.
    """
    a_ref, b_ref, sfa_ref, sfb_ref, _, _, c_ref = data
    
    # Get batch size (L)
    l = c_ref.shape[2]

    # --- OPTIMIZATION STEP ---
    # 1. Pre-process the entire batch of scale factors on the GPU.
    # The input scales are (M, sf_k, L). Permute to (L, M, sf_k) for batching.
    scale_a_batched = optimized_to_blocked_batched(sfa_ref.permute(2, 0, 1))
    scale_b_batched = optimized_to_blocked_batched(sfb_ref.permute(2, 0, 1))
    # -------------------------

    # The loop is now lightweight and performs only GPU-side slicing and computation.
    for l_idx in range(l):
        # Slice the pre-computed scales, which are already on the GPU
        scale_a = scale_a_batched[l_idx]
        scale_b = scale_b_batched[l_idx]

        # Call the PyTorch internal function for NVFP4 GEMM
        res = torch._scaled_mm(
            a_ref[:, :, l_idx],
            b_ref[:, :, l_idx].transpose(0, 1),
            scale_a, # GPU-resident
            scale_b, # GPU-resident
            bias=None,
            out_dtype=torch.float16,
        )
        # Store the result back into the output tensor
        c_ref[:, 0, l_idx] = res[:, 0]
        
    return c_ref

# --- 3. Reference Kernel (Provided by the competition for comparison) ---
# Note: This kernel is provided here for the matching utility, but it is 
# inherently slow due to CPU work and HtoD transfers inside the loop.

def to_blocked(input_matrix):
    """Reference implementation of scale factor conversion (CPU-based)."""
    rows, cols = input_matrix.shape

    # 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)

    # Pad (Note: padding is required for correct view/reshape logic)
    padded = torch.nn.functional.pad(
        input_matrix,
        (0, n_col_blocks * 4 - cols, 0, n_row_blocks * 128 - rows)
    )
    
    # View and Permute sequence
    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 reference_kernel(
    data: input_t,
) -> output_t:
    """PyTorch reference implementation (SLOW version)."""
    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):
        # This is the slow part: running CPU logic inside a loop
        scale_a = to_blocked(sfa_ref_cpu[:, :, l_idx].cpu())
        scale_b = to_blocked(sfb_ref_cpu[:, :, l_idx].cpu())
        
        # (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(), # HtoD transfer inside loop
            scale_b.cuda(), # HtoD transfer inside loop
            bias=None,
            out_dtype=torch.float16,
        )
        c_ref[:, 0, l_idx] = res[:, 0]
    return c_ref

# --- 4. Input Generation (Required for matching the competition test harness) ---

def generate_input(
    m: int,
    k: int,
    l: int,
    seed: int,
):
    """
    Generate input tensors for NVFP4 block-scaled GEMV.
    """
    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)
    
    # Correctly cast the packed uint8 representation to the custom float4 type
    a_ref = a_ref.view(torch.uint8).view(torch.float4_e2m1fn_x2)
    b_ref = b_ref.view(torch.uint8).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
    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 int8 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 (M, K, L format)
        ref_f8_torch_tensor_permuted = ref_f8_torch_tensor.permute(*ref_permute_order)
        
        # --- Dummy tensors for the permuted output (required by the input_t structure) ---
        atom_m = (32, 4)
        atom_k = 4
        mma_shape = (
            l, ceil_div(mn, atom_m[0] * atom_m[1]), ceil_div(sf_k, atom_k),
            atom_m[0], atom_m[1], atom_k,
        )
        mma_permute_order = (3, 4, 1, 5, 2, 0)
        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)
        reordered_f8_torch_tensor = reordered_f8_torch_tensor.permute(*mma_permute_order)
        # End of dummy tensor creation
        
        # The key outputs for this problem are the CPU and GPU scale tensors
        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")
    
    # The output format for input_t is:
    # (a, b, sfa_ref_gpu, sfb_ref_gpu, sfa_permuted_gpu, sfb_permuted_gpu, c)
    return (a_ref, b_ref, sfa_ref, sfb_ref, sfa_permuted, sfb_permuted, c_ref)

scrolls · 237 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