Skip to content
KernelIndex
Search⌘K

submission 725711

xiaozhang_01 · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

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

submission_v47b.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-amd-mxfp4-mm-725711?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
8.89µs
#95 of 1143
2026-04-04

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:248db9826fe6ff521408d767d522f3f1c95362e6134d4aa6c943bb34b00a980c
license declaredunknown
license concludedunknown
authorsxiaozhang_01
imported2026-08-15

Techniques

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

fp4…matrix multiplication Y = X @ W^T with BF16 activations and FP4 weights.\n\n Key parameters:\n x (torch.Tensor): BF16/FP16 input matrix X with shape (M, K).\n …
mma… "cache_modifier",\n "NUM_KSPLIT",\n ],\n)\n\n\n@triton.heuristics(\n {\n "EVEN_K": lambda args: (args["K"] % (args["BLOCK_SIZE_K"] // 2) == 0)\n and (…
num-warps = 1NUM_ITER, NUM_STAGES, NUM_WARPS = 1, 1, 4
split-k…aiter.ops.triton.gemm.basic.gemm_afp4wfp4 import (\n get_splitk,\n)\nfrom aiter.jit.utils.torch_guard import torch_compile_guard\n\n_LOGGER = AiterTritonLogger()\n\n\ndef gemm_a…
stages = 1NUM_WARPS, NUM_STAGES = 1, 1
tile-k = 256…l.constexpr = 16\n BLOCK_SIZE_N: tl.constexpr = 128\n BLOCK_SIZE_K: tl.constexpr = 256\n SCALE_GROUP_SIZE: tl.constexpr = 32\n NUM_KSPLIT: tl.constexpr = 14\n SPLITK…
tile-m = 16…onfig["NUM_KSPLIT"] > 1 and not atomic_add:\n REDUCE_BLOCK_SIZE_M = 16\n REDUCE_BLOCK_SIZE_N = 64\n # TODO: Need to debug - REDUCE_BLOCK_SIZE_N=128 with fp32 p…
tile-n = 1NUM_ITER, BLOCK_SIZE_M, BLOCK_SIZE_N = 1, triton.next_power_of_2(M), 32

Kernel source

submission_v47b.py357 lines
import os
import shutil
import json

# ==========================================
# v38: BLOCK_SIZE_K=256 + num_stages=2 for large K shapes
# Hypothesis: smaller K tiles → lower register pressure → better occupancy
#   + num_stages=2 → pipeline K loop (overlap load i+1 with compute i)
# ==========================================

KERNEL_FILE = '/home/runner/aiter/aiter/ops/triton/_triton_kernels/quant/quant.py'
WRAPPER_FILE = '/home/runner/aiter/aiter/ops/triton/quant/quant.py'
GEMM_WRAPPER_FILE = '/home/runner/aiter/aiter/ops/triton/gemm/basic/gemm_a16wfp4.py'
GEMM_KERNEL_FILE = '/home/runner/aiter/aiter/ops/triton/_triton_kernels/gemm/basic/gemm_a16wfp4.py'

# Optimized _mxfp4_quant_op: log2/exp2 → bit manipulation
KERNEL_CODE = r"""# SPDX-License-Identifier: MIT
import triton
import triton.language as tl

@triton.jit
def _static_per_tensor_quant_fp8_i8_kernel(
    qx_ptr, x_in_ptr, scale_in_ptr, cols: int, x_in_stride_r: int, NUM_COL_POW2: tl.constexpr,
):
    pid = tl.program_id(axis=0)
    tl.assume(pid > 0)
    tl.assume(x_in_stride_r > 0)
    offs = pid * x_in_stride_r + tl.arange(0, NUM_COL_POW2)
    mask = tl.arange(0, NUM_COL_POW2) < cols
    x = tl.load(x_in_ptr + offs, mask=mask, cache_modifier=".cg")
    scale = tl.load(scale_in_ptr)
    qx = (x / scale).to(qx_ptr.dtype.element_ty)
    tl.store(qx_ptr + offs, qx, mask=mask)

@triton.jit
def _dynamic_per_tensor_quant_fp8_i8_kernel(
    x_in_ptr, scale_out_ptr, cols: int, x_in_stride_r: int, NUM_COL_POW2: tl.constexpr, DTYPE_MAX: tl.constexpr,
):
    pid = tl.program_id(axis=0)
    tl.assume(pid > 0)
    tl.assume(x_in_stride_r > 0)
    offs = pid * x_in_stride_r + tl.arange(0, NUM_COL_POW2)
    mask = tl.arange(0, NUM_COL_POW2) < cols
    x = tl.load(x_in_ptr + offs, mask=mask, cache_modifier=".cg")
    m = tl.max(tl.abs(x))
    tl.atomic_max(scale_out_ptr, m / DTYPE_MAX, sem="relaxed")

@triton.jit
def _dynamic_per_token_quant_fp8_i8_kernel(
    qx_ptr, scale_out_ptr, x_in_ptr, cols: int, x_in_stride_r: int, NUM_COL_POW2: tl.constexpr, DTYPE_MAX: tl.constexpr,
):
    pid = tl.program_id(axis=0)
    tl.assume(pid > 0)
    tl.assume(x_in_stride_r > 0)
    offs = pid * x_in_stride_r + tl.arange(0, NUM_COL_POW2)
    mask = tl.arange(0, NUM_COL_POW2) < cols
    x = tl.load(x_in_ptr + offs, mask=mask, cache_modifier=".cg")
    m = tl.max(tl.abs(x), axis=-1)
    scale_out = m.to(tl.float32) / DTYPE_MAX
    qx = (x / scale_out).to(qx_ptr.dtype.element_ty)
    tl.store(scale_out_ptr + pid, scale_out)
    tl.store(qx_ptr + offs, qx, mask=mask, cache_modifier=".cs")

@triton.jit
def _mxfp4_quant_op(x, BLOCK_SIZE_N, BLOCK_SIZE_M, MXFP4_QUANT_BLOCK_SIZE):
    EXP_BIAS_FP32: tl.constexpr = 127
    EXP_BIAS_FP4: tl.constexpr = 1
    EBITS_F32: tl.constexpr = 8
    EBITS_FP4: tl.constexpr = 2
    MBITS_F32: tl.constexpr = 23
    MBITS_FP4: tl.constexpr = 1
    max_normal: tl.constexpr = 6
    min_normal: tl.constexpr = 1
    NUM_QUANT_BLOCKS: tl.constexpr = BLOCK_SIZE_N // MXFP4_QUANT_BLOCK_SIZE
    x = x.reshape(BLOCK_SIZE_M, NUM_QUANT_BLOCKS, MXFP4_QUANT_BLOCK_SIZE)
    amax = tl.max(tl.abs(x), axis=-1, keep_dims=True)
    amax_i32 = amax.to(tl.int32, bitcast=True)
    amax_i32 = (amax_i32 + 0x200000) & 0x7F800000
    scale_e8m0_unbiased = (amax_i32 >> 23) - 129
    scale_e8m0_unbiased = tl.where(scale_e8m0_unbiased < -127, -127, scale_e8m0_unbiased)
    scale_e8m0_unbiased = tl.where(scale_e8m0_unbiased > 127, 127, scale_e8m0_unbiased)
    bs_e8m0 = (scale_e8m0_unbiased + 127).to(tl.uint8)
    quant_scale = ((127 - scale_e8m0_unbiased) << 23).to(tl.float32, bitcast=True)
    qx = x * quant_scale
    qx = qx.to(tl.uint32, bitcast=True)
    s = qx & 0x80000000
    qx = qx ^ s
    qx_fp32 = qx.to(tl.float32, bitcast=True)
    saturate_mask = qx_fp32 >= max_normal
    denormal_mask = (not saturate_mask) & (qx_fp32 < min_normal)
    normal_mask = not (saturate_mask | denormal_mask)
    denorm_exp: tl.constexpr = (EXP_BIAS_FP32 - EXP_BIAS_FP4) + (MBITS_F32 - MBITS_FP4) + 1
    denorm_mask_int: tl.constexpr = denorm_exp << MBITS_F32
    denorm_mask_float: tl.constexpr = tl.cast(denorm_mask_int, tl.float32, bitcast=True)
    denormal_x = qx_fp32 + denorm_mask_float
    denormal_x = denormal_x.to(tl.int32, bitcast=True)
    denormal_x = denormal_x - denorm_mask_int
    denormal_x = denormal_x.to(tl.uint8)
    normal_x = qx
    mant_odd = (normal_x >> (MBITS_F32 - MBITS_FP4)) & 1
    val_to_add = ((EXP_BIAS_FP4 - EXP_BIAS_FP32) << MBITS_F32) + (1 << 21) - 1
    normal_x_int = normal_x.to(tl.int32, bitcast=True)
    normal_x_int = normal_x_int + val_to_add + mant_odd.to(tl.int32)
    normal_x = normal_x_int.to(tl.uint32, bitcast=True)
    normal_x = normal_x >> (MBITS_F32 - MBITS_FP4)
    normal_x = normal_x.to(tl.uint8)
    e2m1_value = tl.full(qx.type.get_block_shapes(), 0x7, dtype=tl.uint8)
    e2m1_value = tl.where(normal_mask, normal_x, e2m1_value)
    e2m1_value = tl.where(denormal_mask, denormal_x, e2m1_value)
    sign_lp = s >> (MBITS_F32 + EBITS_F32 - MBITS_FP4 - EBITS_FP4)
    sign_lp = sign_lp.to(tl.uint8)
    e2m1_value = e2m1_value | sign_lp
    e2m1_value = tl.reshape(e2m1_value,[BLOCK_SIZE_M, NUM_QUANT_BLOCKS, MXFP4_QUANT_BLOCK_SIZE // 2, 2])
    evens, odds = tl.split(e2m1_value)
    x_fp4 = evens | (odds << 4)
    x_fp4 = x_fp4.reshape(BLOCK_SIZE_M, BLOCK_SIZE_N // 2)
    return x_fp4, bs_e8m0.reshape(BLOCK_SIZE_M, NUM_QUANT_BLOCKS)

@triton.heuristics({"EVEN_M_N": lambda args: args["M"] % args["BLOCK_SIZE_M"] == 0 and args["N"] % (args["BLOCK_SIZE_N"] * args["NUM_ITER"]) == 0})
@triton.jit
def _dynamic_mxfp4_quant_kernel(
    x_ptr, x_fp4_ptr, bs_ptr, stride_x_m_in, stride_x_n_in, stride_x_fp4_m_in, stride_x_fp4_n_in,
    stride_bs_m_in, stride_bs_n_in, M, N, scaleN,
    BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, NUM_ITER: tl.constexpr, NUM_STAGES: tl.constexpr,
    MXFP4_QUANT_BLOCK_SIZE: tl.constexpr, SHUFFLE_SCALE: tl.constexpr, EVEN_M_N: tl.constexpr, SCALING_MODE: tl.constexpr,
):
    pid_m = tl.program_id(0)
    start_n = tl.program_id(1) * NUM_ITER
    stride_x_m = tl.cast(stride_x_m_in, tl.int64)
    stride_x_n = tl.cast(stride_x_n_in, tl.int64)
    stride_x_fp4_m = tl.cast(stride_x_fp4_m_in, tl.int64)
    stride_x_fp4_n = tl.cast(stride_x_fp4_n_in, tl.int64)
    stride_bs_m = tl.cast(stride_bs_m_in, tl.int64)
    stride_bs_n = tl.cast(stride_bs_n_in, tl.int64)
    NUM_QUANT_BLOCKS: tl.constexpr = BLOCK_SIZE_N // MXFP4_QUANT_BLOCK_SIZE
    for pid_n in tl.range(start_n, min(start_n + NUM_ITER, N), num_stages=NUM_STAGES):
        x_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
        x_offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
        x_offs = x_offs_m[:, None] * stride_x_m + x_offs_n[None, :] * stride_x_n
        if EVEN_M_N:
            x = tl.load(x_ptr + x_offs, cache_modifier=".cg").to(tl.float32)
        else:
            x_mask = (x_offs_m < M)[:, None] & (x_offs_n < N)[None, :]
            x = tl.load(x_ptr + x_offs, mask=x_mask, cache_modifier=".cg").to(tl.float32)
        out_tensor, bs_e8m0 = _mxfp4_quant_op(x, BLOCK_SIZE_N, BLOCK_SIZE_M, MXFP4_QUANT_BLOCK_SIZE)
        out_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
        out_offs_n = pid_n * BLOCK_SIZE_N // 2 + tl.arange(0, BLOCK_SIZE_N // 2)
        out_offs = out_offs_m[:, None] * stride_x_fp4_m + out_offs_n[None, :] * stride_x_fp4_n
        if EVEN_M_N:
            tl.store(x_fp4_ptr + out_offs, out_tensor)
        else:
            out_mask = (out_offs_m < M)[:, None] & (out_offs_n < (N // 2))[None, :]
            tl.store(x_fp4_ptr + out_offs, out_tensor, mask=out_mask)
        bs_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
        bs_offs_n = pid_n * NUM_QUANT_BLOCKS + tl.arange(0, NUM_QUANT_BLOCKS)
        if SHUFFLE_SCALE:
            o0 = bs_offs_m // 32
            o1 = (bs_offs_m % 32) // 16
            o2 = bs_offs_m % 16
            o3 = bs_offs_n // 8
            o4 = (bs_offs_n % 8) // 4
            o5 = bs_offs_n % 4
            shuffled_idx = (o1[:, None] + o4[None, :] * 2 + o2[:, None] * 4 +
                o5[None, :] * 64 + o3[None, :] * 256 + o0[:, None] * 32 * scaleN)
            if EVEN_M_N:
                tl.store(bs_ptr + shuffled_idx, bs_e8m0)
            else:
                bs_mask = (bs_offs_m < M)[:, None] & (bs_offs_n < scaleN)[None, :]
                tl.store(bs_ptr + shuffled_idx, bs_e8m0, mask=bs_mask)
        else:
            bs_offs = bs_offs_m[:, None] * stride_bs_m + bs_offs_n[None, :] * stride_bs_n
            if EVEN_M_N:
                tl.store(bs_ptr + bs_offs, bs_e8m0)
            else:
                bs_mask = (bs_offs_m < M)[:, None] & (bs_offs_n < scaleN)[None, :]
                tl.store(bs_ptr + bs_offs, bs_e8m0, mask=bs_mask)
"""

WRAPPER_CODE = r"""# SPDX-License-Identifier: MIT
import torch
import triton
from .._triton_kernels.quant.quant import (
    _dynamic_mxfp4_quant_kernel, _mxfp4_quant_op,
    _static_per_tensor_quant_fp8_i8_kernel,
    _dynamic_per_tensor_quant_fp8_i8_kernel,
    _dynamic_per_token_quant_fp8_i8_kernel,
)
from aiter.ops.triton.utils.logger import AiterTritonLogger
_LOGGER = AiterTritonLogger()
def static_per_tensor_quant_fp8_i8(qx, x_in, scale_in):
    rows, cols = x_in.shape
    _static_per_tensor_quant_fp8_i8_kernel[(rows,)](qx, x_in, scale_in, cols, x_in.stride(0), NUM_COL_POW2=triton.next_power_of_2(cols))
    return qx
def dynamic_per_tensor_quant_fp8_i8(qx, x_in, scale_out):
    rows, cols = x_in.shape
    p2 = triton.next_power_of_2(cols)
    dm = torch.finfo(qx.dtype).max if torch.is_floating_point(qx) else torch.iinfo(qx.dtype).max
    _dynamic_per_tensor_quant_fp8_i8_kernel[(rows,)](x_in, scale_out, cols, x_in.stride(0), NUM_COL_POW2=p2, DTYPE_MAX=dm)
    _static_per_tensor_quant_fp8_i8_kernel[(rows,)](qx, x_in, scale_out, cols, x_in.stride(0), NUM_COL_POW2=p2)
    return qx, scale_out
def dynamic_per_token_quant_fp8_i8(qx, x_in, scale_out):
    rows, cols = x_in.shape
    p2 = triton.next_power_of_2(cols)
    dm = torch.finfo(qx.dtype).max if torch.is_floating_point(qx) else torch.iinfo(qx.dtype).max
    _dynamic_per_token_quant_fp8_i8_kernel[(rows,)](qx, scale_out, x_in, cols, x_in.stride(0), NUM_COL_POW2=p2, DTYPE_MAX=dm)
    return qx, scale_out
def dynamic_mxfp4_quant(x, shuffle=False):
    M, N = x.shape
    MXFP4_QUANT_BLOCK_SIZE = 32
    assert (N // 2) % 2 == 0
    scaleN_raw = (N + MXFP4_QUANT_BLOCK_SIZE - 1) // MXFP4_QUANT_BLOCK_SIZE
    if M <= 32:
        NUM_ITER, BLOCK_SIZE_M, BLOCK_SIZE_N = 1, triton.next_power_of_2(M), 32
        NUM_WARPS, NUM_STAGES = 1, 1
    else:
        NUM_ITER, BLOCK_SIZE_M, BLOCK_SIZE_N = 4, 64, 64
        NUM_WARPS, NUM_STAGES = 4, 2
        if N <= 16384: BLOCK_SIZE_M, BLOCK_SIZE_N = 32, 128
    if N <= 1024:
        NUM_ITER, NUM_STAGES, NUM_WARPS = 1, 1, 4
        BLOCK_SIZE_N = max(32, min(256, triton.next_power_of_2(N)))
        BLOCK_SIZE_M = min(8, triton.next_power_of_2(M))
    x_fp4 = torch.empty((M, N // 2), dtype=torch.uint8, device=x.device)
    if shuffle:
        M_pad = (M + 255) // 256 * 256
        scaleN_pad = (scaleN_raw + 7) // 8 * 8
        bs_e8m0 = torch.zeros((M_pad * scaleN_pad,), dtype=torch.uint8, device=x.device)
    else:
        scaleN_pad = scaleN_raw
        bs_e8m0 = torch.empty((scaleN_raw, M), dtype=torch.uint8, device=x.device).T
    grid = (triton.cdiv(M, BLOCK_SIZE_M), triton.cdiv(N, BLOCK_SIZE_N * NUM_ITER))
    _dynamic_mxfp4_quant_kernel[grid](
        x, x_fp4, bs_e8m0, x.stride(0), x.stride(1), x_fp4.stride(0), x_fp4.stride(1),
        bs_e8m0.stride(0) if not shuffle else 0, bs_e8m0.stride(1) if not shuffle else 0,
        M, N, scaleN_pad, BLOCK_SIZE_M=BLOCK_SIZE_M, BLOCK_SIZE_N=BLOCK_SIZE_N,
        NUM_ITER=NUM_ITER, NUM_STAGES=NUM_STAGES, MXFP4_QUANT_BLOCK_SIZE=MXFP4_QUANT_BLOCK_SIZE,
        SHUFFLE_SCALE=shuffle, SCALING_MODE=0, num_warps=NUM_WARPS, waves_per_eu=0, num_stages=1)
    if shuffle: bs_e8m0 = bs_e8m0.view(M_pad, scaleN_pad)
    return x_fp4, bs_e8m0
"""

# ==========================================
# 2. Inject config files — v39: combine v37b + v38 + auto_search best
# Key changes vs v38:
#   - M=16/K=7168: BSN=256 (auto_search finding)
#   - M=64/K=2048: waves_per_eu=2 (v37b finding, +BSK=256 from v38)
#   - M=256/K=1536: waves_per_eu=1 (try lower occupancy)
# ==========================================
GEMM_WRAPPER_CODE = '# SPDX-License-Identifier: MIT\n# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.\n\nfrom typing import Optional\nimport torch\nimport triton\nimport aiter.ops.triton.utils._triton.arch_info as arch_info\nfrom aiter.ops.triton.utils.logger import AiterTritonLogger\nfrom aiter.ops.triton.utils.common_utils import serialize_dict, deserialize_str\nfrom aiter.ops.triton._triton_kernels.gemm.basic.gemm_a16wfp4 import (\n    _gemm_a16wfp4_kernel,\n    _gemm_a16wfp4_preshuffle_kernel,\n    _gemm_a16wfp4_preshuffle_m16_n2112_k7168_kernel,\n    _gemm_a16wfp4_preshuffle_m64_n7168_k2048_kernel,\n    _gemm_a16wfp4_preshuffle_m256_n3072_k1536_kernel,\n    _reduce_m16_n2112_k7168_fp32_kernel,\n    _get_config,\n)\nfrom aiter.ops.triton._triton_kernels.gemm.basic.gemm_afp4wfp4 import (\n    _gemm_afp4wfp4_reduce_kernel,\n)\nfrom aiter.ops.triton.gemm.basic.gemm_afp4wfp4 import (\n    get_splitk,\n)\nfrom aiter.jit.utils.torch_guard import torch_compile_guard\n\n_LOGGER = AiterTritonLogger()\n\n\ndef gemm_a16wfp4_fake_tensor(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    atomic_add: Optional[bool] = False,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[str] = None,\n) -> torch.Tensor:\n    if y is None:\n        M, _ = x.shape\n        N, _ = w.shape\n        return torch.zeros((M, N), dtype=dtype, device=x.device)\n    return y\n\n\n@torch_compile_guard(gen_fake=gemm_a16wfp4_fake_tensor)\ndef gemm_a16wfp4_(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    atomic_add: Optional[bool] = False,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[str] = None,\n) -> torch.Tensor:\n    """\n    Computes matrix multiplication Y = X @ W^T with BF16 activations and FP4 weights.\n\n    Key parameters:\n        x (torch.Tensor): BF16/FP16 input matrix X with shape (M, K).\n            Quantized to MXFP4 on-the-fly during GEMM.\n        w (torch.Tensor): FP4 E2M1 weight matrix W with shape (N, K//2).\n        w_scales (torch.Tensor): E8M0 per-group scale for w with shape (N, K//32).\n            One scale per 32 elements in K dimension.\n        atomic_add (Optional[bool]): use atomic_add for reduction\n        dtype (Optional[torch.dtype]): Output datatype (BF16 or FP16).\n        y (Optional[torch.Tensor]): Pre-allocated output tensor with shape (M, N).\n        config (Optional[str]): Kernel tuning parameters (BLOCK_SIZE_M, BLOCK_SIZE_N,\n            BLOCK_SIZE_K, GROUP_SIZE_M, NUM_KSPLIT, SPLITK_BLOCK_SIZE).\n\n    Returns:\n        y (torch.Tensor): Output with shape (M, N).\n    """\n\n    _LOGGER.info(\n        f"GEMM_A16WFP4: x={tuple(x.shape)} w={tuple(w.shape)} w_scale={tuple(w_scales.shape)} "\n    )\n\n    assert arch_info.is_fp4_avail(), "MXFP4 is not available on your device"\n\n    M, K = x.shape\n    N, K = w.shape\n\n    # inner kernel expects (K, N)\n    w = w.T\n\n    if config is None:\n        config, _ = _get_config(M, N, K)\n    else:\n        config = deserialize_str(config)\n\n    if y is None:\n        if atomic_add:\n            y = torch.zeros((M, N), dtype=dtype, device=x.device)\n        else:\n            y = torch.empty((M, N), dtype=dtype, device=x.device)\n\n    if config["NUM_KSPLIT"] > 1 and not atomic_add:\n        SPLITK_BLOCK_SIZE, BLOCK_SIZE_K, NUM_KSPLIT = get_splitk(\n            K, config["BLOCK_SIZE_K"], config["NUM_KSPLIT"]\n        )\n\n        config["SPLITK_BLOCK_SIZE"] = SPLITK_BLOCK_SIZE\n        config["BLOCK_SIZE_K"] = BLOCK_SIZE_K\n        config["NUM_KSPLIT"] = NUM_KSPLIT\n\n    if config["BLOCK_SIZE_K"] >= 2 * K:\n        config["BLOCK_SIZE_K"] = triton.next_power_of_2(2 * K)\n        config["SPLITK_BLOCK_SIZE"] = 2 * K\n        config["NUM_KSPLIT"] = 1\n    config["BLOCK_SIZE_K"] = max(config["BLOCK_SIZE_K"], 64)\n\n    if config["NUM_KSPLIT"] > 1 and not atomic_add:\n        y_pp = torch.empty(\n            (config["NUM_KSPLIT"], M, N), dtype=torch.float32, device=y.device\n        )\n    else:\n        config["SPLITK_BLOCK_SIZE"] = 2 * K\n        y_pp = None\n\n    grid = lambda META: (  # noqa: E731\n        (\n            META["NUM_KSPLIT"]\n            * triton.cdiv(M, META["BLOCK_SIZE_M"])\n            * triton.cdiv(N, META["BLOCK_SIZE_N"])\n        ),\n    )\n    _gemm_a16wfp4_kernel[grid](\n        x,\n        w,\n        y if y_pp is None else y_pp,\n        w_scales,\n        M,\n        N,\n        K,\n        x.stride(0),\n        x.stride(1),\n        w.stride(0),\n        w.stride(1),\n        0 if y_pp is None else y_pp.stride(0),\n        y.stride(0) if y_pp is None else y_pp.stride(1),\n        y.stride(1) if y_pp is None else y_pp.stride(2),\n        w_scales.stride(0),\n        w_scales.stride(1),\n        ATOMIC_ADD=atomic_add,\n        **config,\n    )\n\n    if config["NUM_KSPLIT"] > 1 and not atomic_add:\n        REDUCE_BLOCK_SIZE_M = 16\n        REDUCE_BLOCK_SIZE_N = 64\n        # TODO: Need to debug - REDUCE_BLOCK_SIZE_N=128 with fp32 partials fails\n        # NOTE: REDUCE_BLOCK_SIZE_N=16 gives best perf with fp32 partials and\n        # REDUCE_BLOCK_SIZE_N=128 gives best perf with bf16 partials\n        ACTUAL_KSPLIT = triton.cdiv(K, (config["SPLITK_BLOCK_SIZE"] // 2))\n\n        grid_reduce = (\n            triton.cdiv(M, REDUCE_BLOCK_SIZE_M),\n            triton.cdiv(N, REDUCE_BLOCK_SIZE_N),\n        )\n        _gemm_afp4wfp4_reduce_kernel[grid_reduce](\n            y_pp,\n            y,\n            M,\n            N,\n            y_pp.stride(0),\n            y_pp.stride(1),\n            y_pp.stride(2),\n            y.stride(0),\n            y.stride(1),\n            REDUCE_BLOCK_SIZE_M,\n            REDUCE_BLOCK_SIZE_N,\n            ACTUAL_KSPLIT,\n            triton.next_power_of_2(config["NUM_KSPLIT"]),\n        )\n\n    return y\n\n\ndef gemm_a16wfp4(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    atomic_add: Optional[bool] = False,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[dict] = None,\n) -> torch.Tensor:\n    config_hashable = serialize_dict(config) if config else None\n    return gemm_a16wfp4_(x, w, w_scales, atomic_add, dtype, y, config_hashable)\n\n\ndef gemm_a16wfp4_preshuffle_fake_tensor(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[str] = None,\n    skip_reduce: Optional[bool] = False,\n) -> torch.Tensor:\n    M, K = x.shape\n    N, _ = w.shape\n\n    config = deserialize_str(config)\n\n    num_ksplit = config["NUM_KSPLIT"]\n    block_size_k = config["BLOCK_SIZE_K"]\n\n    if num_ksplit > 1:\n        _, block_size_k, num_ksplit = get_splitk(K, block_size_k, num_ksplit)\n\n    if block_size_k >= 2 * K:\n        num_ksplit = 1\n\n    if num_ksplit > 1 and skip_reduce:\n        y_pp = torch.empty((num_ksplit, M, N), dtype=torch.float32, device=x.device)\n        return y_pp\n\n    return torch.empty((M, N), dtype=dtype, device=x.device)\n\n\n@torch_compile_guard(gen_fake=gemm_a16wfp4_preshuffle_fake_tensor)\ndef gemm_a16wfp4_preshuffle_(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    prequant: Optional[bool] = True,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[str] = None,\n    skip_reduce: Optional[bool] = False,\n) -> torch.Tensor:\n    """\n    Computes matrix multiplication Y = X @ W^T with BF16 activations and FP4 weights.\n\n    Key parameters:\n        x (torch.Tensor): BF16/FP16 input matrix X with shape (M, K).\n            Quantized to MXFP4 on-the-fly during GEMM.\n        w (torch.Tensor): FP4 E2M1 weight matrix W with shape (N, K//2).\n        w_scales (torch.Tensor): E8M0 per-group scale for w with shape (M//32, K).\n            One scale per 32 elements in K dimension.\n        dtype (Optional[torch.dtype]): Output datatype (BF16 or FP16).\n        y (Optional[torch.Tensor]): Pre-allocated output tensor with shape (M, N).\n        config (Optional[str]): Kernel tuning parameters (BLOCK_SIZE_M, BLOCK_SIZE_N,\n            BLOCK_SIZE_K, GROUP_SIZE_M, NUM_KSPLIT, SPLITK_BLOCK_SIZE).\n        skip_reduce (Optional[bool]): skip reduction, y becomes (SPK, M, N) where SPK is determined by config\n\n    Returns:\n        y (torch.Tensor): Output with shape (M, N).\n    """\n\n    _LOGGER.info(\n        f"GEMM_A16WFP4_PRESHUFFLE: x={tuple(x.shape)} w={tuple(w.shape)} w_scale={tuple(w_scales.shape)} "\n    )\n\n    assert arch_info.is_fp4_avail(), "MXFP4 is not available on your device"\n    assert prequant, "prequant == False is not supported yet"\n\n    M, K = x.shape\n    N, K = w.shape\n    N = N * 16\n    K = K // 16\n\n    if config is None:\n        config, _ = _get_config(M, N, K, True)\n    else:\n        config = deserialize_str(config)\n\n    if config["NUM_KSPLIT"] > 1:\n        SPLITK_BLOCK_SIZE, BLOCK_SIZE_K, NUM_KSPLIT = get_splitk(\n            K, config["BLOCK_SIZE_K"], config["NUM_KSPLIT"]\n        )\n\n        config["SPLITK_BLOCK_SIZE"] = SPLITK_BLOCK_SIZE\n        config["BLOCK_SIZE_K"] = BLOCK_SIZE_K\n        config["NUM_KSPLIT"] = NUM_KSPLIT\n\n    if config["BLOCK_SIZE_K"] >= 2 * K:\n        config["BLOCK_SIZE_K"] = triton.next_power_of_2(2 * K)\n        config["SPLITK_BLOCK_SIZE"] = 2 * K\n        config["NUM_KSPLIT"] = 1\n    config["BLOCK_SIZE_N"] = max(config["BLOCK_SIZE_N"], 32)\n\n    return_y_pp = config["NUM_KSPLIT"] > 1 and skip_reduce\n\n    if config["NUM_KSPLIT"] > 1:\n        y_pp = torch.empty(\n            (config["NUM_KSPLIT"], M, N), dtype=torch.float32, device=x.device\n        )\n    else:\n        config["SPLITK_BLOCK_SIZE"] = 2 * K\n        y_pp = None\n\n    if y is None and not return_y_pp:\n        y = torch.empty((M, N), dtype=dtype, device=x.device)\n\n\n    exact_dispatched = False\n    if prequant:\n        # Exact-shape fast paths for the three large bottleneck cases.\n        if (\n            M == 16 and N == 2112 and K == 3584\n            and config["BLOCK_SIZE_M"] == 16\n            and config["BLOCK_SIZE_N"] == 128\n            and config["BLOCK_SIZE_K"] == 256\n            and config["NUM_KSPLIT"] == 14\n        ):\n            grid = (config["NUM_KSPLIT"] * triton.cdiv(N, config["BLOCK_SIZE_N"]),)\n            _gemm_a16wfp4_preshuffle_m16_n2112_k7168_kernel[grid](\n                x,\n                w,\n                y if y_pp is None else y_pp,\n                w_scales,\n                x.stride(0),\n                x.stride(1),\n                w.stride(0),\n                w.stride(1),\n                0 if y_pp is None else y_pp.stride(0),\n                y.stride(0) if y_pp is None else y_pp.stride(1),\n                y.stride(1) if y_pp is None else y_pp.stride(2),\n                w_scales.stride(0),\n                w_scales.stride(1),\n                num_warps=config["num_warps"],\n                num_stages=config["num_stages"],\n                waves_per_eu=config["waves_per_eu"],\n                matrix_instr_nonkdim=config["matrix_instr_nonkdim"],\n            )\n            exact_dispatched = True\n        elif (\n            M == 64 and N == 7168 and K == 1024\n            and config["BLOCK_SIZE_M"] == 16\n            and config["BLOCK_SIZE_N"] == 128\n            and config["BLOCK_SIZE_K"] == 256\n            and config["NUM_KSPLIT"] == 1\n        ):\n            grid = (triton.cdiv(M, config["BLOCK_SIZE_M"]) * triton.cdiv(N, config["BLOCK_SIZE_N"]),)\n            _gemm_a16wfp4_preshuffle_m64_n7168_k2048_kernel[grid](\n                x,\n                w,\n                y,\n                w_scales,\n                x.stride(0),\n                x.stride(1),\n                w.stride(0),\n                w.stride(1),\n                0,\n                y.stride(0),\n                y.stride(1),\n                w_scales.stride(0),\n                w_scales.stride(1),\n                num_warps=config["num_warps"],\n                num_stages=config["num_stages"],\n                waves_per_eu=config["waves_per_eu"],\n                matrix_instr_nonkdim=config["matrix_instr_nonkdim"],\n            )\n            exact_dispatched = True\n        elif (\n            M == 256 and N == 3072 and K == 768\n            and config["BLOCK_SIZE_M"] == 16\n            and config["BLOCK_SIZE_N"] == 256\n            and config["BLOCK_SIZE_K"] == 256\n            and config["NUM_KSPLIT"] == 1\n        ):\n            grid = (triton.cdiv(M, config["BLOCK_SIZE_M"]) * triton.cdiv(N, config["BLOCK_SIZE_N"]),)\n            _gemm_a16wfp4_preshuffle_m256_n3072_k1536_kernel[grid](\n                x,\n                w,\n                y,\n                w_scales,\n                x.stride(0),\n                x.stride(1),\n                w.stride(0),\n                w.stride(1),\n                0,\n                y.stride(0),\n                y.stride(1),\n                w_scales.stride(0),\n                w_scales.stride(1),\n                num_warps=config["num_warps"],\n                num_stages=config["num_stages"],\n                waves_per_eu=config["waves_per_eu"],\n                matrix_instr_nonkdim=config["matrix_instr_nonkdim"],\n            )\n            exact_dispatched = True\n\n    if not exact_dispatched:\n\n        grid = lambda META: (  # noqa: E731\n            (\n                META["NUM_KSPLIT"]\n                * triton.cdiv(M, META["BLOCK_SIZE_M"])\n                * triton.cdiv(N, META["BLOCK_SIZE_N"])\n            ),\n        )\n        _gemm_a16wfp4_preshuffle_kernel[grid](\n            x,\n            w,\n            y if y_pp is None else y_pp,\n            w_scales,\n            M,\n            N,\n            K,\n            x.stride(0),\n            x.stride(1),\n            w.stride(0),\n            w.stride(1),\n            0 if y_pp is None else y_pp.stride(0),\n            y.stride(0) if y_pp is None else y_pp.stride(1),\n            y.stride(1) if y_pp is None else y_pp.stride(2),\n            w_scales.stride(0),\n            w_scales.stride(1),\n            PREQUANT=prequant,\n            **config,\n        )\n\n    if return_y_pp:\n        return y_pp\n    elif config["NUM_KSPLIT"] > 1:\n        if (\n            M == 16 and N == 2112 and K == 3584\n            and config["BLOCK_SIZE_M"] == 16\n            and config["BLOCK_SIZE_N"] == 128\n            and config["BLOCK_SIZE_K"] == 256\n            and config["NUM_KSPLIT"] == 14\n        ):\n            _reduce_m16_n2112_k7168_fp32_kernel[(33,)](\n                y_pp,\n                y,\n                y_pp.stride(0),\n                y_pp.stride(1),\n                y_pp.stride(2),\n                y.stride(0),\n                y.stride(1),\n                num_warps=4,\n                num_stages=1,\n            )\n        else:\n            REDUCE_BLOCK_SIZE_M = 16\n            REDUCE_BLOCK_SIZE_N = 64\n            # TODO: Need to debug - REDUCE_BLOCK_SIZE_N=128 with fp32 partials fails\n            # NOTE: REDUCE_BLOCK_SIZE_N=16 gives best perf with fp32 partials and\n            # REDUCE_BLOCK_SIZE_N=128 gives best perf with bf16 partials\n            ACTUAL_KSPLIT = triton.cdiv(K, (config["SPLITK_BLOCK_SIZE"] // 2))\n\n            grid_reduce = (\n                triton.cdiv(M, REDUCE_BLOCK_SIZE_M),\n                triton.cdiv(N, REDUCE_BLOCK_SIZE_N),\n            )\n            _gemm_afp4wfp4_reduce_kernel[grid_reduce](\n                y_pp,\n                y,\n                M,\n                N,\n                y_pp.stride(0),\n                y_pp.stride(1),\n                y_pp.stride(2),\n                y.stride(0),\n                y.stride(1),\n                REDUCE_BLOCK_SIZE_M,\n                REDUCE_BLOCK_SIZE_N,\n                ACTUAL_KSPLIT,\n                triton.next_power_of_2(config["NUM_KSPLIT"]),\n            )\n\n    return y\n\n\ndef gemm_a16wfp4_preshuffle(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    w_scales: torch.Tensor,\n    prequant: Optional[bool] = True,\n    dtype: Optional[torch.dtype] = torch.bfloat16,\n    y: Optional[torch.Tensor] = None,\n    config: Optional[dict] = None,\n    skip_reduce: Optional[bool] = False,\n) -> torch.Tensor:\n    if config is None:\n        config_hashable = None\n        M, _ = x.shape\n        N, K = w.shape\n        N = N * 16\n        K = K // 16\n        config, _ = _get_config(M, N, K, True)\n    config_hashable = serialize_dict(config)\n    return gemm_a16wfp4_preshuffle_(\n        x, w, w_scales, prequant, dtype, y, config_hashable, skip_reduce\n    )\n'

GEMM_KERNEL_CODE = '# SPDX-License-Identifier: MIT\n# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.\n\nimport triton.language as tl\nfrom aiter.ops.triton._triton_kernels.quant.quant import _mxfp4_quant_op\nfrom aiter.ops.triton.utils._triton.kernel_repr import make_kernel_repr\nfrom aiter.ops.triton.utils._triton.pid_preprocessing import pid_grid\nfrom aiter.ops.triton.utils.gemm_config_utils import get_gemm_config\n\nimport triton\n\n_gemm_a16wfp4_repr = make_kernel_repr(\n    "_gemm_a16wfp4_kernel",\n    [\n        "BLOCK_SIZE_M",\n        "BLOCK_SIZE_N",\n        "BLOCK_SIZE_K",\n        "GROUP_SIZE_M",\n        "num_warps",\n        "num_stages",\n        "waves_per_eu",\n        "matrix_instr_nonkdim",\n        "cache_modifier",\n        "NUM_KSPLIT",\n    ],\n)\n\n\n@triton.heuristics(\n    {\n        "EVEN_K": lambda args: (args["K"] % (args["BLOCK_SIZE_K"] // 2) == 0)\n        and (args["SPLITK_BLOCK_SIZE"] % args["BLOCK_SIZE_K"] == 0)\n        and (args["K"] % (args["SPLITK_BLOCK_SIZE"] // 2) == 0),\n        "GRID_MN": lambda args: triton.cdiv(args["M"], args["BLOCK_SIZE_M"])\n        * triton.cdiv(args["N"], args["BLOCK_SIZE_N"]),\n    }\n)\n@triton.jit(repr=_gemm_a16wfp4_repr)\ndef _gemm_a16wfp4_kernel(\n    a_ptr,\n    b_ptr,\n    c_ptr,\n    b_scales_ptr,\n    M,\n    N,\n    K,\n    stride_am,\n    stride_ak,\n    stride_bk,\n    stride_bn,\n    stride_ck,\n    stride_cm,\n    stride_cn,\n    stride_bsn,\n    stride_bsk,\n    # Meta-parameters\n    BLOCK_SIZE_M: tl.constexpr,\n    BLOCK_SIZE_N: tl.constexpr,\n    BLOCK_SIZE_K: tl.constexpr,\n    GROUP_SIZE_M: tl.constexpr,\n    NUM_KSPLIT: tl.constexpr,\n    SPLITK_BLOCK_SIZE: tl.constexpr,\n    EVEN_K: tl.constexpr,\n    num_warps: tl.constexpr,\n    num_stages: tl.constexpr,\n    waves_per_eu: tl.constexpr,\n    matrix_instr_nonkdim: tl.constexpr,\n    GRID_MN: tl.constexpr,\n    ATOMIC_ADD: tl.constexpr,\n    cache_modifier: tl.constexpr,\n):\n    """Kernel for computing the matmul C = A x B.\n    A and B inputs are in the microscale fp4 (mxfp4) format.\n    A_scales and B_scales are in e8m0 format.\n    A has shape (M, K), B has shape (K, N) and C has shape (M, N)\n    """\n\n    tl.assume(stride_am > 0)\n    tl.assume(stride_ak > 0)\n    tl.assume(stride_bk > 0)\n    tl.assume(stride_bn > 0)\n    tl.assume(stride_cm > 0)\n    tl.assume(stride_cn > 0)\n    tl.assume(stride_bsk > 0)\n    tl.assume(stride_bsn > 0)\n\n    # -----------------------------------------------------------\n    # Map program ids `pid` to the block of C it should compute.\n    # This is done in a grouped ordering to promote L2 data reuse.\n    pid_unified = tl.program_id(axis=0)\n    pid_k = pid_unified % NUM_KSPLIT\n    pid = pid_unified // NUM_KSPLIT\n    num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)\n    num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)\n\n    if NUM_KSPLIT == 1:\n        pid_m, pid_n = pid_grid(pid, num_pid_m, num_pid_n, GROUP_SIZE_M=GROUP_SIZE_M)\n    else:\n        pid_m = pid // num_pid_n\n        pid_n = pid % num_pid_n\n\n    tl.assume(pid_m >= 0)\n    tl.assume(pid_n >= 0)\n    tl.assume(pid_k >= 0)\n\n    # We assume 32 elements along K share the same scale.\n    SCALE_GROUP_SIZE: tl.constexpr = 32\n\n    if (pid_k * SPLITK_BLOCK_SIZE // 2) < K:\n\n        num_k_iter = tl.cdiv(SPLITK_BLOCK_SIZE // 2, BLOCK_SIZE_K // 2)\n\n        # Create pointers for first block of A and B input matrices\n        # The BLOCK sizes are of the elements and in fp4 we pack 2 per uint8 container.\n        offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)\n        offs_k_split_bf16 = pid_k * SPLITK_BLOCK_SIZE + offs_k_bf16\n        offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M\n        a_ptrs = a_ptr + (\n            offs_am[:, None] * stride_am + offs_k_split_bf16[None, :] * stride_ak\n        )\n\n        offs_k = tl.arange(0, BLOCK_SIZE_K // 2)\n        offs_k_split = pid_k * (SPLITK_BLOCK_SIZE // 2) + offs_k\n        offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N\n        b_ptrs = b_ptr + (\n            offs_k_split[:, None] * stride_bk + offs_bn[None, :] * stride_bn\n        )\n        # Create pointers for the first block of A and B scales\n        offs_ks = (pid_k * (SPLITK_BLOCK_SIZE // SCALE_GROUP_SIZE)) + tl.arange(\n            0, BLOCK_SIZE_K // SCALE_GROUP_SIZE\n        )\n        # B scales are N x K even though B operand is K x N.\n        b_scale_ptrs = (\n            b_scales_ptr + offs_bn[:, None] * stride_bsn + offs_ks[None, :] * stride_bsk\n        )\n\n        accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n\n        for k in range(pid_k * num_k_iter, (pid_k + 1) * num_k_iter):\n            b_scales = tl.load(b_scale_ptrs)\n            # Load the next block of A and B, generate a mask by checking the K dimension.\n            # If it is out of bounds, set it to 0.\n            if EVEN_K:\n                a_bf16 = tl.load(a_ptrs)\n                b = tl.load(b_ptrs, cache_modifier=cache_modifier)\n            else:\n                a_bf16 = tl.load(\n                    a_ptrs,\n                    mask=offs_k_bf16[None, :] < 2 * K - k * BLOCK_SIZE_K,\n                    other=0,\n                )\n                b = tl.load(\n                    b_ptrs,\n                    mask=offs_k[:, None] < K - k * (BLOCK_SIZE_K // 2),\n                    other=0,\n                    cache_modifier=cache_modifier,\n                )\n\n            a, a_scales = _mxfp4_quant_op(a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, 32)\n\n            accumulator += tl.dot_scaled(a, a_scales, "e2m1", b, b_scales, "e2m1")\n\n            # Advance the ptrs to the next K block.\n            a_ptrs += BLOCK_SIZE_K * stride_ak\n            b_ptrs += (BLOCK_SIZE_K // 2) * stride_bk\n            b_scale_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_SIZE) * stride_bsk\n\n        c = accumulator.to(c_ptr.type.element_ty)\n\n        # Write back the block of the output matrix C with masks.\n        offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)\n        offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)\n        c_ptrs = (\n            c_ptr\n            + stride_cm * offs_cm[:, None]\n            + stride_cn * offs_cn[None, :]\n            + pid_k * stride_ck\n        )\n        c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)\n        if ATOMIC_ADD:\n            tl.atomic_add(c_ptrs, c, mask=c_mask, sem="relaxed")\n        else:\n            tl.store(c_ptrs, c, mask=c_mask)\n\n\n_gemm_a16wfp4_preshuffle_repr = make_kernel_repr(\n    "_gemm_a16wfp4_preshuffle_kernel",\n    [\n        "BLOCK_SIZE_M",\n        "BLOCK_SIZE_N",\n        "BLOCK_SIZE_K",\n        "GROUP_SIZE_M",\n        "num_warps",\n        "num_stages",\n        "waves_per_eu",\n        "matrix_instr_nonkdim",\n        "cache_modifier",\n        "NUM_KSPLIT",\n    ],\n)\n\n\n@triton.heuristics(\n    {\n        "EVEN_K": lambda args: (args["K"] % (args["BLOCK_SIZE_K"] // 2) == 0)\n        and (args["SPLITK_BLOCK_SIZE"] % args["BLOCK_SIZE_K"] == 0)\n        and (args["K"] % (args["SPLITK_BLOCK_SIZE"] // 2) == 0),\n        "GRID_MN": lambda args: triton.cdiv(args["M"], args["BLOCK_SIZE_M"])\n        * triton.cdiv(args["N"], args["BLOCK_SIZE_N"]),\n    }\n)\n@triton.jit(repr=_gemm_a16wfp4_preshuffle_repr)\ndef _gemm_a16wfp4_preshuffle_kernel(\n    a_ptr,\n    b_ptr,\n    c_ptr,\n    b_scales_ptr,\n    M,\n    N,\n    K,\n    stride_am,\n    stride_ak,\n    stride_bn,\n    stride_bk,\n    stride_ck,\n    stride_cm,\n    stride_cn,\n    stride_bsn,\n    stride_bsk,\n    # Meta-parameters\n    BLOCK_SIZE_M: tl.constexpr,\n    BLOCK_SIZE_N: tl.constexpr,\n    BLOCK_SIZE_K: tl.constexpr,\n    GROUP_SIZE_M: tl.constexpr,\n    NUM_KSPLIT: tl.constexpr,\n    SPLITK_BLOCK_SIZE: tl.constexpr,\n    EVEN_K: tl.constexpr,\n    num_warps: tl.constexpr,\n    num_stages: tl.constexpr,\n    waves_per_eu: tl.constexpr,\n    matrix_instr_nonkdim: tl.constexpr,\n    GRID_MN: tl.constexpr,\n    PREQUANT: tl.constexpr,\n    cache_modifier: tl.constexpr,\n):\n    """Kernel for computing the matmul C = A x B.\n    A and B inputs are in the microscale fp4 (mxfp4) format.\n    A_scales and B_scales are in e8m0 format.\n    A has shape (M, K), B has shape (K, N) and C has shape (M, N)\n    """\n\n    tl.assume(stride_am > 0)\n    tl.assume(stride_ak > 0)\n    tl.assume(stride_bk > 0)\n    tl.assume(stride_bn > 0)\n    tl.assume(stride_cm > 0)\n    tl.assume(stride_cn > 0)\n    tl.assume(stride_bsk > 0)\n    tl.assume(stride_bsn > 0)\n\n    # -----------------------------------------------------------\n    # Map program ids `pid` to the block of C it should compute.\n    # This is done in a grouped ordering to promote L2 data reuse.\n    pid_unified = tl.program_id(axis=0)\n    pid_k = pid_unified % NUM_KSPLIT\n    pid = pid_unified // NUM_KSPLIT\n    num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)\n    num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)\n\n    if NUM_KSPLIT == 1:\n        pid_m, pid_n = pid_grid(pid, num_pid_m, num_pid_n, GROUP_SIZE_M=GROUP_SIZE_M)\n    else:\n        pid_m = pid // num_pid_n\n        pid_n = pid % num_pid_n\n\n    tl.assume(pid_m >= 0)\n    tl.assume(pid_n >= 0)\n    tl.assume(pid_k >= 0)\n\n    # We assume 32 elements along K share the same scale.\n    SCALE_GROUP_SIZE: tl.constexpr = 32\n\n    if (pid_k * SPLITK_BLOCK_SIZE // 2) < K:\n\n        num_k_iter = tl.cdiv(SPLITK_BLOCK_SIZE // 2, BLOCK_SIZE_K // 2)\n\n        # Create pointers for first block of A and B input matrices\n        # The BLOCK sizes are of the elements and in fp4 we pack 2 per uint8 container.\n        offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)\n        offs_k_split_bf16 = pid_k * SPLITK_BLOCK_SIZE + offs_k_bf16\n        offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M\n        a_ptrs = a_ptr + (\n            offs_am[:, None] * stride_am + offs_k_split_bf16[None, :] * stride_ak\n        )\n\n        offs_k_shuffle_arr = tl.arange(0, (BLOCK_SIZE_K // 2) * 16)\n        offs_k_shuffle = pid_k * (SPLITK_BLOCK_SIZE // 2) * 16 + offs_k_shuffle_arr\n        offs_bn = (pid_n * (BLOCK_SIZE_N // 16) + tl.arange(0, BLOCK_SIZE_N // 16)) % N\n        b_ptrs = b_ptr + (\n            offs_bn[:, None] * stride_bn + offs_k_shuffle[None, :] * stride_bk\n        )\n        # Create pointers for the first block of A and B scales\n        offs_bsn = (\n            pid_n * (BLOCK_SIZE_N // 32) + tl.arange(0, (BLOCK_SIZE_N // 32))\n        ) % N\n        offs_ks = (pid_k * (SPLITK_BLOCK_SIZE // SCALE_GROUP_SIZE) * 32) + tl.arange(\n            0, BLOCK_SIZE_K // SCALE_GROUP_SIZE * 32\n        )\n        # B scales are N x K even though B operand is K x N.\n        b_scale_ptrs = (\n            b_scales_ptr\n            + offs_bsn[:, None] * stride_bsn\n            + offs_ks[None, :] * stride_bsk\n        )\n\n        accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n\n        for k in range(pid_k * num_k_iter, (pid_k + 1) * num_k_iter):\n            b_scales = (\n                tl.load(b_scale_ptrs, cache_modifier=cache_modifier)\n                .reshape(\n                    BLOCK_SIZE_N // 32,\n                    BLOCK_SIZE_K // SCALE_GROUP_SIZE // 8,\n                    4,\n                    16,\n                    2,\n                    2,\n                    1,\n                )\n                .permute(0, 5, 3, 1, 4, 2, 6)\n                .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // SCALE_GROUP_SIZE)\n            )\n\n            # Load the next block of A and B, generate a mask by checking the K dimension.\n            # If it is out of bounds, set it to 0.\n            if EVEN_K:\n                a_bf16 = tl.load(a_ptrs)\n                b = tl.load(b_ptrs, cache_modifier=cache_modifier)\n            else:\n                a_bf16 = tl.load(\n                    a_ptrs,\n                    mask=offs_k_bf16[None, :] < 2 * K - k * BLOCK_SIZE_K,\n                    other=0,\n                )\n                b = tl.load(\n                    b_ptrs,\n                    mask=offs_bn[:, None] < (N // 16),\n                    other=0,\n                    cache_modifier=cache_modifier,\n                )\n\n            b = (\n                b.reshape(\n                    1,\n                    BLOCK_SIZE_N // 16,\n                    BLOCK_SIZE_K // 64,\n                    2,\n                    16,\n                    16,\n                )\n                .permute(0, 1, 4, 2, 3, 5)\n                .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // 2)\n                .trans(1, 0)\n            )\n\n            if PREQUANT:\n                a, a_scales = _mxfp4_quant_op(a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, 32)\n\n            accumulator += tl.dot_scaled(a, a_scales, "e2m1", b, b_scales, "e2m1")\n\n            # Advance the ptrs to the next K block.\n            a_ptrs += BLOCK_SIZE_K * stride_ak\n            b_ptrs += (BLOCK_SIZE_K // 2) * 16 * stride_bk\n            b_scale_ptrs += BLOCK_SIZE_K * stride_bsk\n\n        c = accumulator.to(c_ptr.type.element_ty)\n\n        # Write back the block of the output matrix C with masks.\n        offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)\n        offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)\n        c_ptrs = (\n            c_ptr\n            + stride_cm * offs_cm[:, None]\n            + stride_cn * offs_cn[None, :]\n            + pid_k * stride_ck\n        )\n        c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)\n        tl.store(c_ptrs, c, mask=c_mask)\n\n\n\n@triton.jit\ndef _gemm_a16wfp4_preshuffle_m16_n2112_k7168_kernel(\n    a_ptr,\n    b_ptr,\n    c_ptr,\n    b_scales_ptr,\n    stride_am,\n    stride_ak,\n    stride_bn,\n    stride_bk,\n    stride_ck,\n    stride_cm,\n    stride_cn,\n    stride_bsn,\n    stride_bsk,\n):\n    BLOCK_SIZE_M: tl.constexpr = 16\n    BLOCK_SIZE_N: tl.constexpr = 128\n    BLOCK_SIZE_K: tl.constexpr = 256\n    SCALE_GROUP_SIZE: tl.constexpr = 32\n    NUM_KSPLIT: tl.constexpr = 14\n    SPLITK_BLOCK_SIZE: tl.constexpr = 512\n    K_INTERNAL: tl.constexpr = 3584\n    N_PACK: tl.constexpr = 132\n    N_SCALE: tl.constexpr = 66\n\n    pid_unified = tl.program_id(axis=0)\n    pid_k = pid_unified % NUM_KSPLIT\n    pid_n = pid_unified // NUM_KSPLIT\n\n    offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)\n    offs_k_shuffle_arr = tl.arange(0, (BLOCK_SIZE_K // 2) * 16)\n\n    a_ptrs = a_ptr + (\n        tl.arange(0, BLOCK_SIZE_M)[:, None] * stride_am\n        + (pid_k * SPLITK_BLOCK_SIZE + offs_k_bf16)[None, :] * stride_ak\n    )\n\n    offs_bn = pid_n * (BLOCK_SIZE_N // 16) + tl.arange(0, BLOCK_SIZE_N // 16)\n    mask_bn = offs_bn < N_PACK\n    b_ptrs = b_ptr + (\n        offs_bn[:, None] * stride_bn\n        + (pid_k * (SPLITK_BLOCK_SIZE // 2) * 16 + offs_k_shuffle_arr)[None, :] * stride_bk\n    )\n\n    offs_bsn = pid_n * (BLOCK_SIZE_N // 32) + tl.arange(0, BLOCK_SIZE_N // 32)\n    mask_bsn = offs_bsn < N_SCALE\n    offs_ks = pid_k * (SPLITK_BLOCK_SIZE // SCALE_GROUP_SIZE) * 32 + tl.arange(\n        0, BLOCK_SIZE_K // SCALE_GROUP_SIZE * 32\n    )\n    b_scale_ptrs = (\n        b_scales_ptr\n        + offs_bsn[:, None] * stride_bsn\n        + offs_ks[None, :] * stride_bsk\n    )\n\n    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n\n    for _ in range(2):\n        b_scales = (\n            tl.load(b_scale_ptrs, mask=mask_bsn[:, None], other=0, cache_modifier=".cg")\n            .reshape(\n                BLOCK_SIZE_N // 32,\n                BLOCK_SIZE_K // SCALE_GROUP_SIZE // 8,\n                4,\n                16,\n                2,\n                2,\n                1,\n            )\n            .permute(0, 5, 3, 1, 4, 2, 6)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // SCALE_GROUP_SIZE)\n        )\n\n        a_bf16 = tl.load(a_ptrs)\n        b = tl.load(b_ptrs, mask=mask_bn[:, None], other=0, cache_modifier=".cg")\n\n        b = (\n            b.reshape(\n                1,\n                BLOCK_SIZE_N // 16,\n                BLOCK_SIZE_K // 64,\n                2,\n                16,\n                16,\n            )\n            .permute(0, 1, 4, 2, 3, 5)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // 2)\n            .trans(1, 0)\n        )\n\n        a, a_scales = _mxfp4_quant_op(a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, 32)\n        accumulator += tl.dot_scaled(a, a_scales, "e2m1", b, b_scales, "e2m1")\n\n        a_ptrs += BLOCK_SIZE_K * stride_ak\n        b_ptrs += (BLOCK_SIZE_K // 2) * 16 * stride_bk\n        b_scale_ptrs += BLOCK_SIZE_K * stride_bsk\n\n    c = accumulator.to(c_ptr.type.element_ty)\n    offs_cm = tl.arange(0, BLOCK_SIZE_M).to(tl.int64)\n    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)\n    c_ptrs = (\n        c_ptr\n        + pid_k * stride_ck\n        + stride_cm * offs_cm[:, None]\n        + stride_cn * offs_cn[None, :]\n    )\n    c_mask = (offs_cn[None, :] < 2112)\n    tl.store(c_ptrs, c, mask=c_mask)\n\n\n@triton.jit\ndef _reduce_m16_n2112_k7168_fp32_kernel(\n    y_pp_ptr,\n    y_ptr,\n    stride_ppk,\n    stride_ppm,\n    stride_ppn,\n    stride_ym,\n    stride_yn,\n):\n    BLOCK_SIZE_M: tl.constexpr = 16\n    BLOCK_SIZE_N: tl.constexpr = 64\n    ACTUAL_KSPLIT: tl.constexpr = 14\n\n    pid_n = tl.program_id(axis=0)\n    offs_m = tl.arange(0, BLOCK_SIZE_M)\n    offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)\n\n    acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n    for ks in tl.static_range(0, ACTUAL_KSPLIT):\n        pp_ptrs = (\n            y_pp_ptr\n            + ks * stride_ppk\n            + offs_m[:, None] * stride_ppm\n            + offs_n[None, :] * stride_ppn\n        )\n        acc += tl.load(pp_ptrs)\n\n    y = acc.to(y_ptr.type.element_ty)\n    y_ptrs = y_ptr + offs_m[:, None] * stride_ym + offs_n[None, :] * stride_yn\n    tl.store(y_ptrs, y)\n\n\n@triton.jit\ndef _gemm_a16wfp4_preshuffle_m64_n7168_k2048_kernel(\n    a_ptr,\n    b_ptr,\n    c_ptr,\n    b_scales_ptr,\n    stride_am,\n    stride_ak,\n    stride_bn,\n    stride_bk,\n    stride_ck,\n    stride_cm,\n    stride_cn,\n    stride_bsn,\n    stride_bsk,\n):\n    BLOCK_SIZE_M: tl.constexpr = 16\n    BLOCK_SIZE_N: tl.constexpr = 128\n    BLOCK_SIZE_K: tl.constexpr = 256\n    SCALE_GROUP_SIZE: tl.constexpr = 32\n    K_INTERNAL: tl.constexpr = 1024\n    NUM_PID_N: tl.constexpr = 56\n\n    pid = tl.program_id(axis=0)\n    pid_m = pid // NUM_PID_N\n    pid_n = pid % NUM_PID_N\n\n    offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)\n    offs_k_shuffle_arr = tl.arange(0, (BLOCK_SIZE_K // 2) * 16)\n    offs_am = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)\n\n    a_ptrs = a_ptr + (\n        offs_am[:, None] * stride_am + offs_k_bf16[None, :] * stride_ak\n    )\n\n    offs_bn = pid_n * (BLOCK_SIZE_N // 16) + tl.arange(0, BLOCK_SIZE_N // 16)\n    b_ptrs = b_ptr + (\n        offs_bn[:, None] * stride_bn + offs_k_shuffle_arr[None, :] * stride_bk\n    )\n\n    offs_bsn = pid_n * (BLOCK_SIZE_N // 32) + tl.arange(0, BLOCK_SIZE_N // 32)\n    offs_ks = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_SIZE * 32)\n    b_scale_ptrs = (\n        b_scales_ptr\n        + offs_bsn[:, None] * stride_bsn\n        + offs_ks[None, :] * stride_bsk\n    )\n\n    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n\n    for _ in range(8):\n        b_scales = (\n            tl.load(b_scale_ptrs, cache_modifier=".cg")\n            .reshape(\n                BLOCK_SIZE_N // 32,\n                BLOCK_SIZE_K // SCALE_GROUP_SIZE // 8,\n                4,\n                16,\n                2,\n                2,\n                1,\n            )\n            .permute(0, 5, 3, 1, 4, 2, 6)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // SCALE_GROUP_SIZE)\n        )\n\n        a_bf16 = tl.load(a_ptrs)\n        b = tl.load(b_ptrs, cache_modifier=".cg")\n\n        b = (\n            b.reshape(\n                1,\n                BLOCK_SIZE_N // 16,\n                BLOCK_SIZE_K // 64,\n                2,\n                16,\n                16,\n            )\n            .permute(0, 1, 4, 2, 3, 5)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // 2)\n            .trans(1, 0)\n        )\n\n        a, a_scales = _mxfp4_quant_op(a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, 32)\n        accumulator += tl.dot_scaled(a, a_scales, "e2m1", b, b_scales, "e2m1")\n\n        a_ptrs += BLOCK_SIZE_K * stride_ak\n        b_ptrs += (BLOCK_SIZE_K // 2) * 16 * stride_bk\n        b_scale_ptrs += BLOCK_SIZE_K * stride_bsk\n\n    c = accumulator.to(c_ptr.type.element_ty)\n    offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)\n    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)\n    c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]\n    tl.store(c_ptrs, c)\n\n\n@triton.jit\ndef _gemm_a16wfp4_preshuffle_m256_n3072_k1536_kernel(\n    a_ptr,\n    b_ptr,\n    c_ptr,\n    b_scales_ptr,\n    stride_am,\n    stride_ak,\n    stride_bn,\n    stride_bk,\n    stride_ck,\n    stride_cm,\n    stride_cn,\n    stride_bsn,\n    stride_bsk,\n):\n    BLOCK_SIZE_M: tl.constexpr = 16\n    BLOCK_SIZE_N: tl.constexpr = 256\n    BLOCK_SIZE_K: tl.constexpr = 256\n    SCALE_GROUP_SIZE: tl.constexpr = 32\n    NUM_PID_M: tl.constexpr = 16\n    NUM_PID_N: tl.constexpr = 12\n    GROUP_SIZE_M: tl.constexpr = 4\n\n    pid = tl.program_id(axis=0)\n    num_pid_in_group = GROUP_SIZE_M * NUM_PID_N\n    group_id = pid // num_pid_in_group\n    first_pid_m = group_id * GROUP_SIZE_M\n    group_size_m = tl.minimum(NUM_PID_M - first_pid_m, GROUP_SIZE_M)\n    pid_in_group = pid % num_pid_in_group\n    pid_m = first_pid_m + (pid_in_group % group_size_m)\n    pid_n = pid_in_group // group_size_m\n\n    offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)\n    offs_k_shuffle_arr = tl.arange(0, (BLOCK_SIZE_K // 2) * 16)\n    offs_am = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)\n\n    a_ptrs = a_ptr + (\n        offs_am[:, None] * stride_am + offs_k_bf16[None, :] * stride_ak\n    )\n\n    offs_bn = pid_n * (BLOCK_SIZE_N // 16) + tl.arange(0, BLOCK_SIZE_N // 16)\n    b_ptrs = b_ptr + (\n        offs_bn[:, None] * stride_bn + offs_k_shuffle_arr[None, :] * stride_bk\n    )\n\n    offs_bsn = pid_n * (BLOCK_SIZE_N // 32) + tl.arange(0, BLOCK_SIZE_N // 32)\n    offs_ks = tl.arange(0, BLOCK_SIZE_K // SCALE_GROUP_SIZE * 32)\n    b_scale_ptrs = (\n        b_scales_ptr\n        + offs_bsn[:, None] * stride_bsn\n        + offs_ks[None, :] * stride_bsk\n    )\n\n    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n\n    for _ in range(6):\n        b_scales = (\n            tl.load(b_scale_ptrs, cache_modifier=".cg")\n            .reshape(\n                BLOCK_SIZE_N // 32,\n                BLOCK_SIZE_K // SCALE_GROUP_SIZE // 8,\n                4,\n                16,\n                2,\n                2,\n                1,\n            )\n            .permute(0, 5, 3, 1, 4, 2, 6)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // SCALE_GROUP_SIZE)\n        )\n\n        a_bf16 = tl.load(a_ptrs)\n        b = tl.load(b_ptrs, cache_modifier=".cg")\n\n        b = (\n            b.reshape(\n                1,\n                BLOCK_SIZE_N // 16,\n                BLOCK_SIZE_K // 64,\n                2,\n                16,\n                16,\n            )\n            .permute(0, 1, 4, 2, 3, 5)\n            .reshape(BLOCK_SIZE_N, BLOCK_SIZE_K // 2)\n            .trans(1, 0)\n        )\n\n        a, a_scales = _mxfp4_quant_op(a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, 32)\n        accumulator += tl.dot_scaled(a, a_scales, "e2m1", b, b_scales, "e2m1")\n\n        a_ptrs += BLOCK_SIZE_K * stride_ak\n        b_ptrs += (BLOCK_SIZE_K // 2) * 16 * stride_bk\n        b_scale_ptrs += BLOCK_SIZE_K * stride_bsk\n\n    c = accumulator.to(c_ptr.type.element_ty)\n    offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)\n    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)\n    c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]\n    tl.store(c_ptrs, c)\n\n\ndef get_splitk(K: int, BLOCK_SIZE_K: int, NUM_KSPLIT: int):\n    # heuristics for make "EVEN_K == True" as much as possible\n    NUM_KSPLIT_STEP = 2\n    BLOCK_SIZE_K_STEP = 2\n    SPLITK_BLOCK_SIZE = (\n        triton.cdiv((2 * triton.cdiv(K, NUM_KSPLIT)), BLOCK_SIZE_K) * BLOCK_SIZE_K\n    )\n    while NUM_KSPLIT > 1 and BLOCK_SIZE_K > 16:\n        if (\n            K % (SPLITK_BLOCK_SIZE // 2) == 0\n            and SPLITK_BLOCK_SIZE % BLOCK_SIZE_K == 0\n            and K % (BLOCK_SIZE_K // 2) == 0\n        ):\n            break\n        elif K % (SPLITK_BLOCK_SIZE // 2) != 0 and NUM_KSPLIT > 1:\n            NUM_KSPLIT = NUM_KSPLIT // NUM_KSPLIT_STEP\n        elif SPLITK_BLOCK_SIZE % BLOCK_SIZE_K != 0:\n            if NUM_KSPLIT > 1:\n                NUM_KSPLIT = NUM_KSPLIT // NUM_KSPLIT_STEP\n            elif BLOCK_SIZE_K > 16:\n                BLOCK_SIZE_K = BLOCK_SIZE_K // BLOCK_SIZE_K_STEP\n        elif K % (BLOCK_SIZE_K // 2) != 0 and BLOCK_SIZE_K > 16:\n            BLOCK_SIZE_K = BLOCK_SIZE_K // BLOCK_SIZE_K_STEP\n        else:\n            break\n\n        SPLITK_BLOCK_SIZE = (\n            triton.cdiv((2 * triton.cdiv(K, NUM_KSPLIT)), BLOCK_SIZE_K) * BLOCK_SIZE_K\n        )\n\n    return SPLITK_BLOCK_SIZE, BLOCK_SIZE_K, NUM_KSPLIT\n\n\ndef _get_config(\n    M: int,\n    N: int,\n    K: int,\n    shuffle: bool = False,\n):\n    shuffle_suffix = "_PRESHUFFLED" if shuffle else ""\n    config_name = f"GEMM-A16WFP4{shuffle_suffix}"\n    # Note: Config files use K=2*K in their naming\n    return get_gemm_config(config_name, M, N, 2 * K)\n'

SHAPE_CONFIGS = {
    # K=512: BSK=512, waves=2 (from v37b)
    "gfx950-GEMM-A16WFP4_PRESHUFFLED-N=2880-K=512.json": {
        "M_LEQ_8": {"BLOCK_SIZE_M": 4, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 4, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "M_LEQ_32": {"BLOCK_SIZE_M": 8, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "any": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    },
    "gfx950-GEMM-A16WFP4_PRESHUFFLED-N=4096-K=512.json": {
        "M_LEQ_8": {"BLOCK_SIZE_M": 4, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 4, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "M_LEQ_32": {"BLOCK_SIZE_M": 8, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "any": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    },
    # M=16/K=7168: BSN=128 (reverted from v39's BSN=256 which was worse) + BSK=256 + stages=2
    "gfx950-GEMM-A16WFP4_PRESHUFFLED-N=2112-K=7168.json": {
        "M_LEQ_32": {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 14},
        "any": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 4, "num_warps": 8, "num_stages": 2, "waves_per_eu": 4, "matrix_instr_nonkdim": 16, "cache_modifier": None, "NUM_KSPLIT": 1},
    },
    # M=64/K=2048: BSK=256 + stages=2 + waves=2 (v37b+v38 combined!)
    "gfx950-GEMM-A16WFP4_PRESHUFFLED-N=7168-K=2048.json": {
        "M_LEQ_32": {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "M_LEQ_64": {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "any": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 4, "num_warps": 8, "num_stages": 2, "waves_per_eu": 4, "matrix_instr_nonkdim": 16, "cache_modifier": None, "NUM_KSPLIT": 1},
    },
    # M=256/K=1536: BSK=256 + stages=2 + waves=1 (try even lower occupancy)
    "gfx950-GEMM-A16WFP4_PRESHUFFLED-N=3072-K=1536.json": {
        "M_LEQ_64": {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 4, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 3},
        "M_LEQ_256": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 4, "num_warps": 8, "num_stages": 2, "waves_per_eu": 1, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
        "any": {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 4, "num_warps": 8, "num_stages": 2, "waves_per_eu": 1, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    },
}

# ==========================================
# 3. Execute injections + clear caches
# ==========================================
try:
    with open(KERNEL_FILE, 'w') as f:
        f.write(KERNEL_CODE)
    with open(WRAPPER_FILE, 'w') as f:
        f.write(WRAPPER_CODE)
    with open(GEMM_WRAPPER_FILE, 'w') as f:
        f.write(GEMM_WRAPPER_CODE)
    with open(GEMM_KERNEL_FILE, 'w') as f:
        f.write(GEMM_KERNEL_CODE)
    os.makedirs(CONFIGS_DIR, exist_ok=True)
    for fname, cfg in SHAPE_CONFIGS.items():
        with open(os.path.join(CONFIGS_DIR, fname), 'w') as f:
            json.dump(cfg, f, indent=2)
except Exception:
    pass

for d in [
    '/home/runner/aiter/aiter/ops/triton/_triton_kernels/quant/__pycache__',
    '/home/runner/aiter/aiter/ops/triton/quant/__pycache__',
    '/home/runner/aiter/aiter/ops/triton/_triton_kernels/gemm/basic/__pycache__',
    '/home/runner/aiter/aiter/ops/triton/gemm/basic/__pycache__',
]:
    if os.path.exists(d):
        try: shutil.rmtree(d)
        except Exception: pass

# ==========================================
# 4. Entry point — exact configs for all official shapes, with large-shape fast paths in injected GEMM
# ==========================================
from task import input_t, output_t


EXACT_CONFIGS = {
    (4, 2880, 512): {"BLOCK_SIZE_M": 4, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 4, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    (16, 2112, 7168): {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": None, "NUM_KSPLIT": 14},
    (32, 4096, 512): {"BLOCK_SIZE_M": 8, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 4, "num_stages": 1, "waves_per_eu": 1, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    (32, 2880, 512): {"BLOCK_SIZE_M": 8, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 512, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 1, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": None, "NUM_KSPLIT": 1},
    (64, 7168, 2048): {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 1, "num_warps": 8, "num_stages": 2, "waves_per_eu": 4, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
    (256, 3072, 1536): {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 256, "GROUP_SIZE_M": 4, "num_warps": 8, "num_stages": 2, "waves_per_eu": 2, "matrix_instr_nonkdim": 16, "cache_modifier": ".cg", "NUM_KSPLIT": 1},
}


def custom_kernel(data: input_t) -> output_t:
    import torch
    from aiter.ops.triton.gemm.basic.gemm_a16wfp4 import gemm_a16wfp4_preshuffle

    A, B, B_q, B_shuffle, B_scale_sh = data
    M, K = A.shape
    N = B_q.shape[0]
    A = A.contiguous()

    # Keep the proven v40 data contract. No extra materialization on the hot path.
    w = B_shuffle.view(torch.uint8).reshape(N // 16, -1)
    bs = B_scale_sh.view(torch.uint8)
    N_pad, scaleN_pad = bs.shape
    w_scales = bs.reshape(N_pad // 32, 32 * scaleN_pad)[:N // 32, :K]

    config = EXACT_CONFIGS.get((M, N, K))
    use_internal_reduce = (M, N, K) == (16, 2112, 7168)
    y_out = gemm_a16wfp4_preshuffle(
        A,
        w,
        w_scales,
        dtype=torch.bfloat16,
        config=config,
        skip_reduce=not use_internal_reduce,
    )
    if y_out.dim() == 3:
        return y_out.sum(dim=0).to(torch.bfloat16)
    return y_out
scrolls · 357 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