submission 102353
guojun · python · License unknown
Use it
Vendorable · source mirrored · license unknownView source →
No package. Vendor the mirrored source: 435 lines, June 9 Researcher Reciprocity License v1.0.
submission_v2.5.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-nvfp4-gemv-102353?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
Reported · How evidence levels are derived →
Source and license
sourceavailable
revision digestsha256:56b9ea12a274b37ff6c30c9d578013b23e0e8af7d539180d65e1b234b5d5c6c1
license declaredunknown
license concludedunknown
authorsguojun
imported2026-08-15
Techniques
Extracted from the mirrored source by pattern, never inferred. Each row cites its line.
fp4
const int K = a.size(1) * 2; // a is in fp4, so each uint8 contains 2 elementsfp8
using fp8_e4m3 = __nv_fp8_e4m3;shared-memory
__shared__ float result[BM];stages = 4
constexpr int STAGES = 4;tile-m = 8
constexpr int BM = 8; // Each block processes 128 elements along M dimensionvector-width = uint4
using Reg128 = uint4;Kernel source
submission_v2.5.py435 lines
import torch
from torch.utils.cpp_extension import load_inline
from task import input_t, output_t
gemv_cuda_src = """
#include <torch/extension.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>
#include <cuda_pipeline.h>
using Reg128 = uint4;
using Reg32 = uint32_t;
using Reg16 = uint16_t;
using f16 = __half;
using uint8 = uint8_t;
using fp8_e4m3 = __nv_fp8_e4m3;
// taken from https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/gemm/kernel/gemv_blockscaled.h#L564
__device__ __forceinline__ f16 blockscaled_multiply_add(
const Reg32& a0, const Reg32& a1, const Reg32& a2, const Reg32& a3,
const Reg32& b0, const Reg32& b1, const Reg32& b2, const Reg32& b3,
const Reg16& sfa,
const Reg16& sfb
)
{
f16 res;
Reg16* res_u16_ptr = reinterpret_cast<Reg16*>(&res);
asm volatile( \\
"{\\n" \\
// declare registers for A / B tensors
".reg .b8 byte0_0, byte0_1, byte0_2, byte0_3;\\n" \\
".reg .b8 byte0_4, byte0_5, byte0_6, byte0_7;\\n" \\
".reg .b8 byte1_0, byte1_1, byte1_2, byte1_3;\\n" \\
".reg .b8 byte1_4, byte1_5, byte1_6, byte1_7;\\n" \\
".reg .b8 byte2_0, byte2_1, byte2_2, byte2_3;\\n" \\
".reg .b8 byte2_4, byte2_5, byte2_6, byte2_7;\\n" \\
".reg .b8 byte3_0, byte3_1, byte3_2, byte3_3;\\n" \\
".reg .b8 byte3_4, byte3_5, byte3_6, byte3_7;\\n" \\
// declare registers for accumulators
".reg .f16x2 accum_0_0, accum_0_1, accum_0_2, accum_0_3;\\n" \\
".reg .f16x2 accum_1_0, accum_1_1, accum_1_2, accum_1_3;\\n" \\
".reg .f16x2 accum_2_0, accum_2_1, accum_2_2, accum_2_3;\\n" \\
".reg .f16x2 accum_3_0, accum_3_1, accum_3_2, accum_3_3;\\n" \\
// declare registers for scaling factors
".reg .f16x2 sfa_f16x2;\\n" \\
".reg .f16x2 sfb_f16x2;\\n" \\
".reg .f16x2 sf_f16x2;\\n" \\
// declare registers for conversion
".reg .f16x2 cvt_0_0, cvt_0_1, cvt_0_2, cvt_0_3;\\n" \\
".reg .f16x2 cvt_0_4, cvt_0_5, cvt_0_6, cvt_0_7;\\n" \\
".reg .f16x2 cvt_1_0, cvt_1_1, cvt_1_2, cvt_1_3;\\n" \\
".reg .f16x2 cvt_1_4, cvt_1_5, cvt_1_6, cvt_1_7;\\n" \\
".reg .f16x2 cvt_2_0, cvt_2_1, cvt_2_2, cvt_2_3;\\n" \\
".reg .f16x2 cvt_2_4, cvt_2_5, cvt_2_6, cvt_2_7;\\n" \\
".reg .f16x2 cvt_3_0, cvt_3_1, cvt_3_2, cvt_3_3;\\n" \\
".reg .f16x2 cvt_3_4, cvt_3_5, cvt_3_6, cvt_3_7;\\n" \\
".reg .f16 result_f16, lane0, lane1;\\n" \\
".reg .f16x2 mul_f16x2_0, mul_f16x2_1;\\n" \\
// convert scaling factors from fp8 to f16x2
"cvt.rn.f16x2.e4m3x2 sfa_f16x2, %1;\\n" \\
"cvt.rn.f16x2.e4m3x2 sfb_f16x2, %2;\\n" \\
// clear accumulators
"mov.b32 accum_0_0, 0;\\n" \\
"mov.b32 accum_0_1, 0;\\n" \\
"mov.b32 accum_0_2, 0;\\n" \\
"mov.b32 accum_0_3, 0;\\n" \\
"mov.b32 accum_1_0, 0;\\n" \\
"mov.b32 accum_1_1, 0;\\n" \\
"mov.b32 accum_1_2, 0;\\n" \\
"mov.b32 accum_1_3, 0;\\n" \\
"mov.b32 accum_2_0, 0;\\n" \\
"mov.b32 accum_2_1, 0;\\n" \\
"mov.b32 accum_2_2, 0;\\n" \\
"mov.b32 accum_2_3, 0;\\n" \\
"mov.b32 accum_3_0, 0;\\n" \\
"mov.b32 accum_3_1, 0;\\n" \\
"mov.b32 accum_3_2, 0;\\n" \\
"mov.b32 accum_3_3, 0;\\n" \\
// multiply, unpacking and permuting scale factors
"mul.rn.f16x2 sf_f16x2, sfa_f16x2, sfb_f16x2;\\n" \\
"mov.b32 {lane0, lane1}, sf_f16x2;\\n" \\
"mov.b32 mul_f16x2_0, {lane0, lane0};\\n" \\
"mov.b32 mul_f16x2_1, {lane1, lane1};\\n" \\
// unpacking A and B tensors
"mov.b32 {byte0_0, byte0_1, byte0_2, byte0_3}, %3;\\n" \\
"mov.b32 {byte0_4, byte0_5, byte0_6, byte0_7}, %4;\\n" \\
"mov.b32 {byte1_0, byte1_1, byte1_2, byte1_3}, %5;\\n" \\
"mov.b32 {byte1_4, byte1_5, byte1_6, byte1_7}, %6;\\n" \\
"mov.b32 {byte2_0, byte2_1, byte2_2, byte2_3}, %7;\\n" \\
"mov.b32 {byte2_4, byte2_5, byte2_6, byte2_7}, %8;\\n" \\
"mov.b32 {byte3_0, byte3_1, byte3_2, byte3_3}, %9;\\n" \\
"mov.b32 {byte3_4, byte3_5, byte3_6, byte3_7}, %10;\\n" \\
// convert A and B tensors from fp4 to f16x2
// A[0 - 7] and B[0 - 7]
"cvt.rn.f16x2.e2m1x2 cvt_0_0, byte0_0;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_1, byte0_1;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_2, byte0_2;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_3, byte0_3;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_4, byte0_4;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_5, byte0_5;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_6, byte0_6;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_0_7, byte0_7;\\n" \\
// A[8 - 15] and B[8 - 15]
"cvt.rn.f16x2.e2m1x2 cvt_1_0, byte1_0;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_1, byte1_1;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_2, byte1_2;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_3, byte1_3;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_4, byte1_4;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_5, byte1_5;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_6, byte1_6;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_1_7, byte1_7;\\n" \\
// A[16 - 23] and B[16 - 23]
"cvt.rn.f16x2.e2m1x2 cvt_2_0, byte2_0;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_1, byte2_1;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_2, byte2_2;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_3, byte2_3;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_4, byte2_4;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_5, byte2_5;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_6, byte2_6;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_2_7, byte2_7;\\n" \\
// A[24 - 31] and B[24 - 31]
"cvt.rn.f16x2.e2m1x2 cvt_3_0, byte3_0;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_1, byte3_1;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_2, byte3_2;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_3, byte3_3;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_4, byte3_4;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_5, byte3_5;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_6, byte3_6;\\n" \\
"cvt.rn.f16x2.e2m1x2 cvt_3_7, byte3_7;\\n" \\
// fma for A[0 - 7] and B[0 - 7]
"fma.rn.f16x2 accum_0_0, cvt_0_0, cvt_0_4, accum_0_0;\\n" \\
"fma.rn.f16x2 accum_0_1, cvt_0_1, cvt_0_5, accum_0_1;\\n" \\
"fma.rn.f16x2 accum_0_2, cvt_0_2, cvt_0_6, accum_0_2;\\n" \\
"fma.rn.f16x2 accum_0_3, cvt_0_3, cvt_0_7, accum_0_3;\\n" \\
// fma for A[8 - 15] and B[8 - 15]
"fma.rn.f16x2 accum_1_0, cvt_1_0, cvt_1_4, accum_1_0;\\n" \\
"fma.rn.f16x2 accum_1_1, cvt_1_1, cvt_1_5, accum_1_1;\\n" \\
"fma.rn.f16x2 accum_1_2, cvt_1_2, cvt_1_6, accum_1_2;\\n" \\
"fma.rn.f16x2 accum_1_3, cvt_1_3, cvt_1_7, accum_1_3;\\n" \\
// fma for A[16 - 23] and B[16 - 23]
"fma.rn.f16x2 accum_2_0, cvt_2_0, cvt_2_4, accum_2_0;\\n" \\
"fma.rn.f16x2 accum_2_1, cvt_2_1, cvt_2_5, accum_2_1;\\n" \\
"fma.rn.f16x2 accum_2_2, cvt_2_2, cvt_2_6, accum_2_2;\\n" \\
"fma.rn.f16x2 accum_2_3, cvt_2_3, cvt_2_7, accum_2_3;\\n" \\
// fma for A[24 - 31] and B[24 - 31]
"fma.rn.f16x2 accum_3_0, cvt_3_0, cvt_3_4, accum_3_0;\\n" \\
"fma.rn.f16x2 accum_3_1, cvt_3_1, cvt_3_5, accum_3_1;\\n" \\
"fma.rn.f16x2 accum_3_2, cvt_3_2, cvt_3_6, accum_3_2;\\n" \\
"fma.rn.f16x2 accum_3_3, cvt_3_3, cvt_3_7, accum_3_3;\\n" \\
// tree reduction for accumulators
"add.rn.f16x2 accum_0_0, accum_0_0, accum_0_1;\\n" \\
"add.rn.f16x2 accum_0_2, accum_0_2, accum_0_3;\\n" \\
"add.rn.f16x2 accum_1_0, accum_1_0, accum_1_1;\\n" \\
"add.rn.f16x2 accum_1_2, accum_1_2, accum_1_3;\\n" \\
"add.rn.f16x2 accum_2_0, accum_2_0, accum_2_1;\\n" \\
"add.rn.f16x2 accum_2_2, accum_2_2, accum_2_3;\\n" \\
"add.rn.f16x2 accum_3_0, accum_3_0, accum_3_1;\\n" \\
"add.rn.f16x2 accum_3_2, accum_3_2, accum_3_3;\\n" \\
"add.rn.f16x2 accum_0_0, accum_0_0, accum_0_2;\\n" \\
"add.rn.f16x2 accum_1_0, accum_1_0, accum_1_2;\\n" \\
"add.rn.f16x2 accum_2_0, accum_2_0, accum_2_2;\\n" \\
"add.rn.f16x2 accum_3_0, accum_3_0, accum_3_2;\\n" \\
"add.rn.f16x2 accum_0_0, accum_0_0, accum_1_0;\\n" \\
"add.rn.f16x2 accum_2_0, accum_2_0, accum_3_0;\\n" \\
// apply scaling factors and final reduction
"mul.rn.f16x2 accum_0_0, mul_f16x2_0, accum_0_0;\\n" \\
"mul.rn.f16x2 accum_2_0, mul_f16x2_1, accum_2_0;\\n" \\
"add.rn.f16x2 accum_0_0, accum_0_0, accum_2_0;\\n" \\
"mov.b32 {lane0, lane1}, accum_0_0;\\n" \\
"add.rn.f16 result_f16, lane0, lane1;\\n" \\
"mov.b16 %0, result_f16;\\n" \\
"}\\n"
: "=h"(res_u16_ptr[0]) // 0
: "h"(sfa), "h"(sfb), // 1, 2
"r"(a0), "r"(b0), // 3, 4
"r"(a1), "r"(b1), // 5, 6
"r"(a2), "r"(b2), // 7, 8
"r"(a3), "r"(b3) // 9, 10
: "memory"
);
return res;
}
template<const int BM, const int TK, const int STAGES>
__global__ void gemv_kernel(
const uint8* __restrict__ a,
const uint8* __restrict__ b,
const fp8_e4m3* __restrict__ sfa,
const fp8_e4m3* __restrict__ sfb,
f16* __restrict__ c,
const int B,
const int M,
const int K,
const int N)
{
const int tid = threadIdx.x;
const int lane_id = tid % 32;
const int l = blockIdx.y;
const int global_row = l * M + blockIdx.x * BM;
const int tcol = tid * TK;
// static shmem for result
__shared__ float result[BM];
extern __shared__ char dyn_smem[];
uint8* a_smem = reinterpret_cast<uint8*>(dyn_smem);
fp8_e4m3* sfa_smem = reinterpret_cast<fp8_e4m3*>(a_smem + STAGES * (K / 2));
if (tid < BM)
{
result[tid] = 0.0f;
}
// incremement global pointers
a += (global_row * K + tcol) / 2;
b += (l * N * K + tcol) / 2;
c += global_row;
sfa += (global_row * K + tcol) / 16;
sfb += (l * N * K + tcol) / 16;
// increment smem pointers
a_smem += tcol / 2;
sfa_smem += tcol / 16;
float accum[BM] = {0.0f}; // accumulate on float to preserve precision
Reg128 a_reg, b_reg;
Reg16 sfa_reg, sfb_reg;
__syncthreads();
if (tcol < K)
{
// load in data
// all threads load A and sfa
b_reg = reinterpret_cast<const Reg128*>(b)[0];
sfb_reg = reinterpret_cast<const Reg16*>(sfb)[0];
#pragma unroll
for (int compute_batch = 0, fetch_batch = 0; compute_batch < BM; compute_batch++)
{
// prefetch A and sfa into smem
for (; fetch_batch < BM && fetch_batch < (compute_batch + STAGES); fetch_batch++)
{
const int shared_idx = fetch_batch % STAGES;
__pipeline_memcpy_async(&a_smem[shared_idx * (K / 2)], a, 16); // load in 16B
if (lane_id % 2 == 0) // minimally load in 4B, hence only even lanes load in sfa
{
__pipeline_memcpy_async(&sfa_smem[shared_idx * (K / 16)], sfa, 4);
}
__pipeline_commit();
a += K / 2;
sfa += K / 16;
}
__pipeline_wait_prior(fetch_batch - compute_batch - 1);
__syncwarp(); // for sfa
const int shared_idx = compute_batch % STAGES;
a_reg = reinterpret_cast<const Reg128*>(&a_smem[shared_idx * (K / 2)])[0];
sfa_reg = reinterpret_cast<const Reg16*>(&sfa_smem[shared_idx * (K / 16)])[0];
// fma here
Reg32* a_reg32_ptr = reinterpret_cast<Reg32*>(&a_reg);
Reg32* b_reg32_ptr = reinterpret_cast<Reg32*>(&b_reg);
f16 res = blockscaled_multiply_add(
a_reg32_ptr[0], a_reg32_ptr[1], a_reg32_ptr[2], a_reg32_ptr[3],
b_reg32_ptr[0], b_reg32_ptr[1], b_reg32_ptr[2], b_reg32_ptr[3],
sfa_reg,
sfb_reg
);
accum[compute_batch] += __half2float(res);
}
}
// reduce within threads on the same k
#pragma unroll
for (int i = 0; i < BM; i++)
{
#pragma unroll
for (int offset = 1; offset < 32; offset <<= 1)
{
accum[i] += __shfl_xor_sync(0xFFFFFFFF, accum[i], offset);
}
}
if (lane_id < BM)
{
atomicAdd(&result[lane_id], accum[lane_id]);
}
__syncthreads();
// write back result only by the first thread in each K
if (tid < BM)
{
c[tid] = __float2half_rn(result[tid]);
}
}
void gemv(
torch::Tensor a,
torch::Tensor b,
torch::Tensor sfa,
torch::Tensor sfb,
torch::Tensor c)
{
const int M = a.size(0);
const int K = a.size(1) * 2; // a is in fp4, so each uint8 contains 2 elements
const int B = a.size(2);
const int N = b.size(0);
constexpr int TK = 32; // Each thread processes 32 elements along K dimension
constexpr int BM = 8; // Each block processes 128 elements along M dimension
constexpr int STAGES = 4;
assert(M % BM == 0); // M must be divisible by BM
const int NUM_THREADS = K / TK;
const dim3 grid(M / BM, B);
const size_t SMEM_SIZE = STAGES * (K / 2 + K / 16); // a + sfa
if (SMEM_SIZE > 48 * 1024)
{
cudaFuncSetAttribute(
gemv_kernel<BM, TK, STAGES>,
cudaFuncAttributeMaxDynamicSharedMemorySize,
SMEM_SIZE
);
}
gemv_kernel<BM, TK, STAGES><<<grid, NUM_THREADS, SMEM_SIZE>>>(
static_cast<uint8*>(a.data_ptr()),
static_cast<uint8*>(b.data_ptr()),
reinterpret_cast<fp8_e4m3*>(sfa.data_ptr<torch::Float8_e4m3fn>()),
reinterpret_cast<fp8_e4m3*>(sfb.data_ptr<torch::Float8_e4m3fn>()),
reinterpret_cast<f16*>(c.data_ptr<torch::Half>()),
B,
M,
K,
N
);
}
"""
gemv_cpp_src = """
#include <torch/extension.h>
void gemv(torch::Tensor a, torch::Tensor b, torch::Tensor sfa, torch::Tensor sfb, torch::Tensor c);
"""
gemv_module = load_inline(
name='gemv_cuda',
cpp_sources=gemv_cpp_src,
cuda_sources=gemv_cuda_src,
functions=['gemv'],
verbose=True,
extra_cuda_cflags=['-lineinfo', '-gencode=arch=compute_100a,code=sm_100a']
)
def gemv(
a: torch.Tensor,
b: torch.Tensor,
sfa: torch.Tensor,
sfb: torch.Tensor,
c: torch.Tensor,
) -> torch.Tensor:
return gemv_module.gemv(
a,
b,
sfa,
sfb,
c,
)
def custom_kernel(data: input_t) -> output_t:
"""
Reference implementation of block-scale fp8 gemv
Args:
data: Tuple that expands to:
a: torch.Tensor[float4e2m1fn] of shape [m, k, l],
b: torch.Tensor[float4e2m1fn] of shape [1, k, l],
sfa: torch.Tensor[float8_e4m3fnuz] of shape [m, k // 16, l], used by reference implementation
sfb: torch.Tensor[float8_e4m3fnuz] of shape [1, k // 16, l], used by reference implementation
sfa_permuted: torch.Tensor[float8_e4m3fnuz] of shape [32, 4, rest_m, 4, rest_k, l],
sfb_permuted: torch.Tensor[float8_e4m3fnuz] of shape [32, 4, rest_n, 4, rest_k, l],
c: torch.Tensor[float16] of shape [m, 1, l]
Returns:
Tensor containing output in float16
c: torch.Tensor[float16] of shape [m, 1, l]
"""
# c: [l, m, 1] is pre-allocated memory to avoid timing allocation overhead.
a, b, sfa, sfb, sfa_permuted, sfb_permuted, c = data
# Your implementation here
gemv(a, b, sfa, sfb, c)
return c
scrolls · 435 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