incorrect
6_Matmul_with_large_K_dimension_.py
started
2026-05-05T00:07:58Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel mean / median / std
—
ref mean / median / std
1.30 / — / 7.66e-04 μs
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
222.45 GB/s
warp stalls (top 5)nsight
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Simple model that performs a single matrix multiplication (C = A * B) with a large K dimension
"""
def __init__(self):
super(Model, self).__init__()
def forward(self, A: torch.Tensor, B: torch.Tensor) -> torch.Tensor:
"""
Performs matrix multiplication of A and B.
Args:
A: Input tensor of shape (M, K)
B: Input tensor of shape (K, N)
Returns:
Output tensor of shape (M, N)
"""
return torch.matmul(A, B)
M = 256
N = 256
K = 131072 * 4
def get_inputs():
A = torch.rand(M, K)
B = torch.rand(K, N)
return [A, B]
def get_init_inputs():
return [] # No special initialization inputs neededturn 0 · FINAL · 1 tool call(s) · 118.4s turn · 118.4s LLM · 7,784 chars (click to expand)
turn 0118.4s turn · 118.4s LLM · 0.0s tools · FINAL
reasoning
**Considering naming for loading extensions**
I’m thinking about naming the loading function, perhaps `load_inline(name='matmul_tf32_ext')`. For the sources, I’m considering including both CPP and CUDA, and specifying the function I need, like `matmul_forward`. It might help to ensure the cache works if I repeat the extension name in the eval. Also, including optimization flags like `-O3` in `extra_cflags` could enhance performance. I’ll want to keep that in mind!
**Evaluating cublas performance**
I’m considering if using a full cublas wrapper would match the performance and maybe even run slightly faster. However, if I refer to tf32, the custom kernel could be faster or slower than cublas. It seems sticking with the cublas wrapper might be the best option since it's simpler and likely correct. But I’m not going with a custom kernel here, just simplifying my approach.
**Considering custom passes for WMMA**
I'm thinking about submitting WMMA and need to ensure that `--use_fast_math` doesn't get changed. I wonder if I should include `#if __CUDA_ARCH__ >= 800` since architectures pre-Ampere won't support tf32. It's likely there’s hardware to consider. To compile for a lower architecture, I might look into precision::tf32 but maybe not include ptx. Okay, I need to make the final tool call.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n\ntorch::Tensor matmul_forward(torch::Tensor A, torch::Tensor B);\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <mma.h>\n\nusing namespace nvcuda;\n\n// One CTA computes a 64x64 tile of C for a slice of K. The CTA has\n// 16 warps, one warp per 16x16 output subtile, and uses TF32 tensor cores.\n// Partial K-slices are accumulated into C with atomics (split-K).\n__global__ void matmul_256_tf32_splitk_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M, int N, int K,\n int k_slice) {\n extern __shared__ float smem[];\n float* As = smem; // 64 x 32\n float* Bs = As + 64 * 32; // 32 x 64\n float* Os = Bs + 32 * 64; // 16 warps x 16 x 16\n\n const int tid = threadIdx.x;\n const int warp_id = tid >> 5; // 0..15\n const int lane = tid & 31;\n\n const int warp_m = warp_id >> 2; // 0..3\n const int warp_n = warp_id & 3; // 0..3\n\n const int block_m = blockIdx.x * 64;\n const int block_n = blockIdx.y * 64;\n const int out_m = block_m + warp_m * 16;\n const int out_n = block_n + warp_n * 16;\n\n const int k_begin = blockIdx.z * k_slice;\n const int k_end = min(K, k_begin + k_slice);\n\n wmma::fragment<wmma::matrix_a, 16, 16, 8, wmma::precision::tf32, wmma::row_major> a_frag;\n wmma::fragment<wmma::matrix_b, 16, 16, 8, wmma::precision::tf32, wmma::row_major> b_frag;\n wmma::fragment<wmma::accumulator, 16, 16, 8, float> acc_frag;\n wmma::fill_fragment(acc_frag, 0.0f);\n\n // K is processed in 32-wide panels. Four mma.sync k=8 operations per panel.\n for (int kk = k_begin; kk < k_end; kk += 32) {\n // Cooperative global -> shared load. Bounds make the wrapper usable for\n // nearby sizes, although the benchmark dimensions are exact multiples.\n for (int idx = tid; idx < 64 * 32 + 32 * 64; idx += blockDim.x) {\n if (idx < 64 * 32) {\n int r = idx >> 5; // /32\n int c = idx & 31; // %32\n int gm = block_m + r;\n int gk = kk + c;\n As[idx] = (gm < M && gk < K) ? A[(long long)gm * K + gk] : 0.0f;\n } else {\n int j = idx - 64 * 32;\n int r = j >> 6; // /64\n int c = j & 63; // %64\n int gk = kk + r;\n int gn = block_n + c;\n Bs[j] = (gk < K && gn < N) ? B[(long long)gk * N + gn] : 0.0f;\n }\n }\n __syncthreads();\n\n #pragma unroll\n for (int ks = 0; ks < 32; ks += 8) {\n wmma::load_matrix_sync(a_frag, As + (warp_m * 16) * 32 + ks, 32);\n wmma::load_matrix_sync(b_frag, Bs + ks * 64 + warp_n * 16, 64);\n wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag);\n }\n __syncthreads();\n }\n\n float* my_out = Os + warp_id * 256;\n wmma::store_matrix_sync(my_out, acc_frag, 16, wmma::mem_row_major);\n __syncwarp();\n\n // Atomic split-K reduction into final output.\n for (int i = lane; i < 256; i += 32) {\n int r = i >> 4;\n int c = i & 15;\n int gm = out_m + r;\n int gn = out_n + c;\n if (gm < M && gn < N) {\n atomicAdd(C + (long long)gm * N + gn, my_out[i]);\n }\n }\n}\n\ntorch::Tensor matmul_forward(torch::Tensor A, torch::Tensor B) {\n const int M = (int)A.size(0);\n const int K = (int)A.size(1);\n const int N = (int)B.size(1);\n\n auto C = torch::empty({M, N}, A.options());\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n cudaMemsetAsync(C.data_ptr<float>(), 0, (size_t)M * (size_t)N * sizeof(float), stream);\n\n // 4096 K-elements per split gives ample parallelism while keeping atomics modest.\n const int k_slice = 4096;\n const int splits = (K + k_slice - 1) / k_slice;\n dim3 grid((M + 63) / 64, (N + 63) / 64, splits);\n dim3 block(512);\n size_t shmem = (64 * 32 + 32 * 64 + 16 * 256) * sizeof(float);\n matmul_256_tf32_splitk_kernel<<<grid, block, shmem, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M, N, K, k_slice);\n return C;\n}\n'''\n\n_ext = load_inline(\n name='matmul_256_largek_tf32_ext',\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=['matmul_forward'],\n extra_cflags=['-O3'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n with_cuda=True,\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n\n def forward(self, A: torch.Tensor, B: torch.Tensor) -> torch.Tensor:\n return _ext.matmul_forward(A, B)\n"
}
result ← submit_kernel (fail)
failsubmit_kernel
submit_kernel FAILED: correctness check did not pass ((0 / 5) trials).
correctness_issue: Output mismatch
max_difference: ['90.375000', '90.375000', '90.390625', '90.375000', '90.375000']
avg_difference: ['89.888306', '89.903687', '89.898399', '89.903458', '89.895851']
final submitted kernel (138 lines, 5,014 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
_cpp_src = r'''
#include <torch/extension.h>
torch::Tensor matmul_forward(torch::Tensor A, torch::Tensor B);
'''
_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <mma.h>
using namespace nvcuda;
// One CTA computes a 64x64 tile of C for a slice of K. The CTA has
// 16 warps, one warp per 16x16 output subtile, and uses TF32 tensor cores.
// Partial K-slices are accumulated into C with atomics (split-K).
__global__ void matmul_256_tf32_splitk_kernel(const float* __restrict__ A,
const float* __restrict__ B,
float* __restrict__ C,
int M, int N, int K,
int k_slice) {
extern __shared__ float smem[];
float* As = smem; // 64 x 32
float* Bs = As + 64 * 32; // 32 x 64
float* Os = Bs + 32 * 64; // 16 warps x 16 x 16
const int tid = threadIdx.x;
const int warp_id = tid >> 5; // 0..15
const int lane = tid & 31;
const int warp_m = warp_id >> 2; // 0..3
const int warp_n = warp_id & 3; // 0..3
const int block_m = blockIdx.x * 64;
const int block_n = blockIdx.y * 64;
const int out_m = block_m + warp_m * 16;
const int out_n = block_n + warp_n * 16;
const int k_begin = blockIdx.z * k_slice;
const int k_end = min(K, k_begin + k_slice);
wmma::fragment<wmma::matrix_a, 16, 16, 8, wmma::precision::tf32, wmma::row_major> a_frag;
wmma::fragment<wmma::matrix_b, 16, 16, 8, wmma::precision::tf32, wmma::row_major> b_frag;
wmma::fragment<wmma::accumulator, 16, 16, 8, float> acc_frag;
wmma::fill_fragment(acc_frag, 0.0f);
// K is processed in 32-wide panels. Four mma.sync k=8 operations per panel.
for (int kk = k_begin; kk < k_end; kk += 32) {
// Cooperative global -> shared load. Bounds make the wrapper usable for
// nearby sizes, although the benchmark dimensions are exact multiples.
for (int idx = tid; idx < 64 * 32 + 32 * 64; idx += blockDim.x) {
if (idx < 64 * 32) {
int r = idx >> 5; // /32
int c = idx & 31; // %32
int gm = block_m + r;
int gk = kk + c;
As[idx] = (gm < M && gk < K) ? A[(long long)gm * K + gk] : 0.0f;
} else {
int j = idx - 64 * 32;
int r = j >> 6; // /64
int c = j & 63; // %64
int gk = kk + r;
int gn = block_n + c;
Bs[j] = (gk < K && gn < N) ? B[(long long)gk * N + gn] : 0.0f;
}
}
__syncthreads();
#pragma unroll
for (int ks = 0; ks < 32; ks += 8) {
wmma::load_matrix_sync(a_frag, As + (warp_m * 16) * 32 + ks, 32);
wmma::load_matrix_sync(b_frag, Bs + ks * 64 + warp_n * 16, 64);
wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag);
}
__syncthreads();
}
float* my_out = Os + warp_id * 256;
wmma::store_matrix_sync(my_out, acc_frag, 16, wmma::mem_row_major);
__syncwarp();
// Atomic split-K reduction into final output.
for (int i = lane; i < 256; i += 32) {
int r = i >> 4;
int c = i & 15;
int gm = out_m + r;
int gn = out_n + c;
if (gm < M && gn < N) {
atomicAdd(C + (long long)gm * N + gn, my_out[i]);
}
}
}
torch::Tensor matmul_forward(torch::Tensor A, torch::Tensor B) {
const int M = (int)A.size(0);
const int K = (int)A.size(1);
const int N = (int)B.size(1);
auto C = torch::empty({M, N}, A.options());
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
cudaMemsetAsync(C.data_ptr<float>(), 0, (size_t)M * (size_t)N * sizeof(float), stream);
// 4096 K-elements per split gives ample parallelism while keeping atomics modest.
const int k_slice = 4096;
const int splits = (K + k_slice - 1) / k_slice;
dim3 grid((M + 63) / 64, (N + 63) / 64, splits);
dim3 block(512);
size_t shmem = (64 * 32 + 32 * 64 + 16 * 256) * sizeof(float);
matmul_256_tf32_splitk_kernel<<<grid, block, shmem, stream>>>(
A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M, N, K, k_slice);
return C;
}
'''
_ext = load_inline(
name='matmul_256_largek_tf32_ext',
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=['matmul_forward'],
extra_cflags=['-O3'],
extra_cuda_cflags=['-O3', '--use_fast_math'],
with_cuda=True,
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self):
super(ModelNew, self).__init__()
def forward(self, A: torch.Tensor, B: torch.Tensor) -> torch.Tensor:
return _ext.matmul_forward(A, B)