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
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.
fp4
2. 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 = uint4
1. Use uint4 (128-bit) vector loads for LDS fill — 4x fewer load instructionsKernel 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