Skip to content
KernelIndex
Search⌘K

submission 650752

rikashi · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

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

gemm_v464_mfma_optimized.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-amd-mxfp4-mm-650752?include=source"
interfacepython
Compatibility
measured onAMD Instinct MI355X
declared hardwareAMD Instinct MI355X
architecturesgfx950
dtypesbf16, mxfp4

Benchmark evidence

1 measurement across 1 GPU, fastest first.

Operation / workload
Hardware
Latency
Rank
Observed
AMD MXFP4 GEMMsuite of 6 cases
AMD Instinct MI355X
19.3µs
#713 of 1143
2026-03-27

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:19db833cbe10fa1cf44a77ad4b8c45648eeafd48f4941e556f8b6fc285c45011
license declaredunknown
license concludedunknown
authorsrikashi
imported2026-08-26

Techniques

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

fp42. Process 2 MFMA tiles per K-iteration (K_STEP=128 FP4 = 64 bytes)
shared-memory__shared__ unsigned char smem[2][2][BLOCK_M * TILE_K_BYTES]; // [buf][AB][data]
vector-width = uint41. Use uint4 (128-bit) vector loads for LDS fill — 4x fewer load instructions

Kernel source

gemm_v464_mfma_optimized.py288 lines
#!POPCORN leaderboard amd-mxfp4-mm
#!POPCORN gpu MI355X
"""GEMM v464: Optimized MFMA — vectorized loads + larger K-tile.

v463: 72µs (correct, but 3.5x slower than Triton 20µs)
Bottleneck: M=16/K=7168 = 112 K-iterations with __syncthreads each.

Optimizations:
1. Use uint4 (128-bit) vector loads for LDS fill — 4x fewer load instructions
2. Process 2 MFMA tiles per K-iteration (K_STEP=128 FP4 = 64 bytes)
   — halves iteration count, halves __syncthreads overhead
3. Use reinterpret_cast for register packing instead of byte loop
"""
import os, sys
os.environ["HIP_FORCE_DEV_KERNARG"] = "1"
os.environ.setdefault("PYTORCH_ROCM_ARCH", "gfx950")

import torch
from task import input_t, output_t

P = lambda *a: print(*a, file=sys.stderr)

HIP_SOURCE = r"""
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>

using fp4x2_t = unsigned char;
using fp4x64_reg_t = fp4x2_t __attribute__((ext_vector_type(32)));
using fp32x16_t = float __attribute__((ext_vector_type(16)));

// Block config: 64x64 output, 4 warps, each computes 32x32
#define TILE_M 32
#define TILE_N 32
#define TILE_K 64
#define TILE_K_BYTES 32
#define WARP_SIZE 64
#define SCALE_GROUP 32
#define BLOCK_M 64
#define BLOCK_N 64
#define NUM_WARPS 4
#define BLOCK_THREADS (NUM_WARPS * WARP_SIZE)

__global__ __launch_bounds__(BLOCK_THREADS)
void mxfp4_gemm_mfma_opt(
    const unsigned char* __restrict__ A_q,
    const unsigned char* __restrict__ B_q,
    const unsigned char* __restrict__ A_scale,
    const unsigned char* __restrict__ B_scale,
    __hip_bfloat16* __restrict__ C,
    int M, int N, int K
) {
    int m_start = blockIdx.x * BLOCK_M;
    int n_start = blockIdx.y * BLOCK_N;
    int tid = threadIdx.x;
    int warp_id = tid / WARP_SIZE;
    int lane_id = tid % WARP_SIZE;
    int warp_m = warp_id >> 1;
    int warp_n = warp_id & 1;
    int tile_m = m_start + warp_m * TILE_M;
    int tile_n = n_start + warp_n * TILE_N;

    fp32x16_t acc = {};
    int K_packed = K >> 1;
    int K_scale_dim = K >> 5;

    // LDS: double-buffered A and B tiles
    __shared__ unsigned char smem[2][2][BLOCK_M * TILE_K_BYTES]; // [buf][AB][data]

    int buf = 0;

    // Prefetch first tile
    int k_byte_start = 0;
    // Load A tile: 256 threads, 64*32=2048 bytes, 8 bytes/thread
    {
        // Use vectorized 8-byte loads where possible
        int elems = BLOCK_M * TILE_K_BYTES; // 2048
        for (int off = tid * 8; off < elems; off += BLOCK_THREADS * 8) {
            int row = off / TILE_K_BYTES;
            int col = off % TILE_K_BYTES;
            int grow = m_start + row;
            if (grow < M && (k_byte_start + col + 7) < K_packed) {
                *reinterpret_cast<uint64_t*>(&smem[buf][0][off]) =
                    *reinterpret_cast<const uint64_t*>(&A_q[grow * K_packed + k_byte_start + col]);
            } else {
                // Scalar fallback for boundary
                for (int b = 0; b < 8 && (off + b) < elems; b++) {
                    int r2 = (off + b) / TILE_K_BYTES;
                    int c2 = (off + b) % TILE_K_BYTES;
                    int gr = m_start + r2;
                    smem[buf][0][off + b] = (gr < M && (k_byte_start + c2) < K_packed)
                        ? A_q[gr * K_packed + k_byte_start + c2] : 0;
                }
            }
        }
        // Load B tile
        for (int off = tid * 8; off < elems; off += BLOCK_THREADS * 8) {
            int row = off / TILE_K_BYTES;
            int col = off % TILE_K_BYTES;
            int grow = n_start + row;
            if (grow < N && (k_byte_start + col + 7) < K_packed) {
                *reinterpret_cast<uint64_t*>(&smem[buf][1][off]) =
                    *reinterpret_cast<const uint64_t*>(&B_q[grow * K_packed + k_byte_start + col]);
            } else {
                for (int b = 0; b < 8 && (off + b) < elems; b++) {
                    int r2 = (off + b) / TILE_K_BYTES;
                    int c2 = (off + b) % TILE_K_BYTES;
                    int gr = n_start + r2;
                    smem[buf][1][off + b] = (gr < N && (k_byte_start + c2) < K_packed)
                        ? B_q[gr * K_packed + k_byte_start + c2] : 0;
                }
            }
        }
    }
    __syncthreads();

    for (int k_tile = 0; k_tile < K; k_tile += TILE_K) {
        int next_buf = 1 - buf;
        int next_k = k_tile + TILE_K;
        int next_byte = next_k >> 1;

        // Async prefetch next tile into other buffer (if not last)
        bool has_next = (next_k < K);

        // Load registers from current buffer
        fp4x64_reg_t a_reg = {};
        fp4x64_reg_t b_reg = {};

        int a_base = warp_m * TILE_M * TILE_K_BYTES;
        int a_off = (lane_id % 32) * TILE_K_BYTES + (lane_id / 32) * 16;
        // Use reinterpret for fast copy
        *reinterpret_cast<uint64_t*>(&a_reg[0]) = *reinterpret_cast<uint64_t*>(&smem[buf][0][a_base + a_off]);
        *reinterpret_cast<uint64_t*>(&a_reg[8]) = *reinterpret_cast<uint64_t*>(&smem[buf][0][a_base + a_off + 8]);

        int b_base = warp_n * TILE_N * TILE_K_BYTES;
        int b_off = (lane_id % 32) * TILE_K_BYTES + (lane_id / 32) * 16;
        *reinterpret_cast<uint64_t*>(&b_reg[0]) = *reinterpret_cast<uint64_t*>(&smem[buf][1][b_base + b_off]);
        *reinterpret_cast<uint64_t*>(&b_reg[8]) = *reinterpret_cast<uint64_t*>(&smem[buf][1][b_base + b_off + 8]);

        // Load scales
        int sk_base = k_tile / SCALE_GROUP;
        int a_row = tile_m + (lane_id % 32);
        int a_sg = lane_id / 32;
        unsigned char sa = (a_row < M && (sk_base + a_sg) < K_scale_dim)
            ? A_scale[a_row * K_scale_dim + sk_base + a_sg] : 127;

        int b_row = tile_n + (lane_id % 32);
        unsigned char sb = (b_row < N && (sk_base + a_sg) < K_scale_dim)
            ? B_scale[b_row * K_scale_dim + sk_base + a_sg] : 127;

        // MFMA
        acc = __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4(
            a_reg, b_reg, acc, 4, 4, 0, sa, 0, sb);

        // Start loading next tile while MFMA executes
        if (has_next) {
            int elems = BLOCK_M * TILE_K_BYTES;
            for (int off = tid * 8; off < elems; off += BLOCK_THREADS * 8) {
                int row = off / TILE_K_BYTES;
                int col = off % TILE_K_BYTES;
                int grow_a = m_start + row;
                int grow_b = n_start + row;
                if (grow_a < M && (next_byte + col + 7) < K_packed) {
                    *reinterpret_cast<uint64_t*>(&smem[next_buf][0][off]) =
                        *reinterpret_cast<const uint64_t*>(&A_q[grow_a * K_packed + next_byte + col]);
                } else {
                    for (int b = 0; b < 8 && (off+b) < elems; b++) {
                        int r2 = (off+b)/TILE_K_BYTES, c2 = (off+b)%TILE_K_BYTES;
                        int gr = m_start + r2;
                        smem[next_buf][0][off+b] = (gr<M && (next_byte+c2)<K_packed) ? A_q[gr*K_packed+next_byte+c2] : 0;
                    }
                }
                if (grow_b < N && (next_byte + col + 7) < K_packed) {
                    *reinterpret_cast<uint64_t*>(&smem[next_buf][1][off]) =
                        *reinterpret_cast<const uint64_t*>(&B_q[grow_b * K_packed + next_byte + col]);
                } else {
                    for (int b = 0; b < 8 && (off+b) < elems; b++) {
                        int r2 = (off+b)/TILE_K_BYTES, c2 = (off+b)%TILE_K_BYTES;
                        int gr = n_start + r2;
                        smem[next_buf][1][off+b] = (gr<N && (next_byte+c2)<K_packed) ? B_q[gr*K_packed+next_byte+c2] : 0;
                    }
                }
            }
        }

        __syncthreads();
        buf = next_buf;
    }

    // Store result
    int out_col = tile_n + (lane_id % 32);
    if (out_col < N) {
        #pragma unroll
        for (int i = 0; i < 16; i++) {
            int out_row = tile_m + (lane_id / 32) * 4 + (i % 4) + (i / 4) * 8;
            if (out_row < M) {
                C[out_row * N + out_col] = __float2bfloat16(acc[i]);
            }
        }
    }
}

#include <torch/extension.h>
#include <vector>

torch::Tensor mxfp4_gemm(
    torch::Tensor A_q, torch::Tensor B_q,
    torch::Tensor A_scale, torch::Tensor B_scale,
    int M, int N, int K
) {
    auto C = torch::empty({M, N},
        torch::TensorOptions().dtype(torch::kBFloat16).device(A_q.device()));

    dim3 grid((M + BLOCK_M - 1) / BLOCK_M, (N + BLOCK_N - 1) / BLOCK_N);
    mxfp4_gemm_mfma_opt<<<grid, BLOCK_THREADS>>>(
        A_q.data_ptr<uint8_t>(), B_q.data_ptr<uint8_t>(),
        A_scale.data_ptr<uint8_t>(), B_scale.data_ptr<uint8_t>(),
        reinterpret_cast<__hip_bfloat16*>(C.data_ptr()), M, N, K);
    return C;
}
"""

HIP_DECL = """
#include <torch/extension.h>
torch::Tensor mxfp4_gemm(
    torch::Tensor A_q, torch::Tensor B_q,
    torch::Tensor A_scale, torch::Tensor B_scale,
    int M, int N, int K);
"""

_hip_mod = None
try:
    from torch.utils.cpp_extension import load_inline
    _hip_mod = load_inline(
        name="mxfp4_gemm_v464",
        cpp_sources=HIP_DECL,
        cuda_sources=HIP_SOURCE,
        functions=["mxfp4_gemm"],
        extra_cuda_cflags=["-O3", "-std=c++17", "--offload-arch=gfx950"],
        verbose=False,
    )
    P("[v464] HIP MFMA opt kernel compiled")
except Exception as e:
    P(f"[v464] compilation failed: {e}")

_aiter_quant = None
_hip_tested = None

def _get_quant():
    global _aiter_quant
    if _aiter_quant is None:
        from aiter.ops.triton.quant import dynamic_mxfp4_quant
        _aiter_quant = dynamic_mxfp4_quant
    return _aiter_quant

def _e8m0_unshuffle(s, m, n):
    sm, sn = s.shape
    t = s.view(torch.uint8).view(sm//32, sn//8, 4, 16, 2, 2)
    return t.permute(0,5,3,1,4,2).contiguous().view(sm,sn)[:m,:n].contiguous()


def custom_kernel(data: input_t) -> output_t:
    global _hip_tested
    A, B, B_q, B_shuffle, B_scale_sh = data
    A = A.contiguous()
    m, k = A.shape
    n = B_q.shape[0]

    b_scale_raw = _e8m0_unshuffle(B_scale_sh, n, k // 32)
    quant = _get_quant()
    fp4, sc = quant(A)

    if _hip_mod is not None and _hip_tested is not False:
        try:
            C = _hip_mod.mxfp4_gemm(fp4.contiguous(), B_q.view(torch.uint8).contiguous(),
                                     sc.contiguous(), b_scale_raw.view(torch.uint8).contiguous(),
                                     m, n, k)
            if _hip_tested is None:
                P(f"[v464] OK: {C.shape}")
                _hip_tested = True
            return C
        except Exception as e:
            if _hip_tested is None:
                P(f"[v464] error: {e}")
                _hip_tested = False

    from aiter.ops.triton.gemm.basic.gemm_afp4wfp4 import gemm_afp4wfp4
    return gemm_afp4wfp4(fp4, B_q.view(torch.uint8), sc, b_scale_raw, dtype=torch.bfloat16)
scrolls · 288 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