incorrect
14_Matmul_for_upper_triangular_matrices.py
started
2026-05-05T00:55:29Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel mean / median / std
—
ref mean / median / std
0.37 / — / 8.63e-04 μs
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
115.23 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 matrix multiplication (C = A * B) for upper triangular matrices.
"""
def __init__(self):
super(Model, self).__init__()
def forward(self, A, B):
"""
Performs matrix multiplication for upper triangular matrices.
Args:
A (torch.Tensor): Upper triangular matrix of shape (N, N).
B (torch.Tensor): Upper triangular matrix of shape (N, N).
Returns:
torch.Tensor: The product of A and B, also an upper triangular matrix of shape (N, N).
"""
return torch.triu(torch.matmul(A, B))
N = 4096
def get_inputs():
"""
Generates upper triangular matrices for testing.
Returns:
list: A list containing two upper triangular matrices of shape (N, N).
"""
A = torch.triu(torch.rand(N, N))
B = torch.triu(torch.rand(N, N))
return [A, B]
def get_init_inputs():
"""
No specific initialization inputs are needed for this model.
Returns:
list: An empty list.
"""
return []turn 0 · FINAL · 1 tool call(s) · 84.5s turn · 84.5s LLM · 7,983 chars (click to expand)
turn 084.5s turn · 84.5s LLM · 0.0s tools · FINAL
reasoning
**Exploring PyTorch matmul and TF32**
I'm looking into PyTorch's matmul function and whether it uses TF32 on Ampere GPUs. It seems that PyTorch's matmul defaults might allow for TF32, which could affect output tolerance—possibly around 1e-4? I want to check if the reference uses torch matmul and if it really does apply TF32. For evaluation, I'm considering absolute and relative tolerance settings of 1e-4 as well. There’s a lot to unpack here!
**Considering model initialization**
I'm thinking about the ModelNew class and how its `__init__` method doesn't take any arguments. I need to address how to handle the `forward` calls with `ext.triu_m_forward(A, B)`. There's the question of whether the inputs from `triu` are contiguous; they probably are, which leads me to wonder if I should call `A.contiguous()` to make sure everything runs smoothly. It's crucial to get this right for the model's performance!
**Calculating multiplications for block computation**
I'm analyzing the computation needed for each 64x64 block. It requires 64x64 multiplications, factoring in masks. I'm trying to determine if this is more than the exact triangular value, considering the tile-level overhead. I'm looking to compute the total operations and sum of blocks, comparing it to the exact calculations like N^3/6. The overhead seems manageable, with a 5% increase, leading to some interesting numbers in my calculations. Everything seems to be falling into place!
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 triumm_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\n#define TILE_M 64\n#define TILE_N 64\n#define TILE_K 32\n\n__global__ __launch_bounds__(256, 2)\nvoid triumm64_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int n) {\n const int blockCol = blockIdx.x * TILE_N;\n const int blockRow = blockIdx.y * TILE_M;\n const int tx = threadIdx.x; // 0..15\n const int ty = threadIdx.y; // 0..15\n\n // Tiles strictly below the diagonal are exactly zero. Write them here so\n // the returned empty tensor has the same lower triangle as torch.triu.\n if (blockCol < blockRow) {\n #pragma unroll\n for (int rr = 0; rr < 4; ++rr) {\n int row = blockRow + ty + rr * 16;\n if (row < n) {\n #pragma unroll\n for (int cc = 0; cc < 4; ++cc) {\n int col = blockCol + tx + cc * 16;\n if (col < n) C[(long long)row * n + col] = 0.0f;\n }\n }\n }\n return;\n }\n\n __shared__ float As[TILE_M][TILE_K + 1];\n __shared__ float Bs[TILE_K][TILE_N + 1];\n\n float acc[4][4];\n #pragma unroll\n for (int i = 0; i < 4; ++i) {\n #pragma unroll\n for (int j = 0; j < 4; ++j) acc[i][j] = 0.0f;\n }\n\n const int tid = ty * 16 + tx;\n int kEnd = blockCol + TILE_N;\n if (kEnd > n) kEnd = n;\n\n // Because A and B are upper triangular, only k in [blockRow, blockCol+63]\n // can contribute to this 64x64 output tile. Values inside the boundary\n // tiles that are outside the true per-element range are already stored as\n // zeros in A or B.\n for (int kk = blockRow; kk < kEnd; kk += TILE_K) {\n for (int idx = tid; idx < TILE_M * TILE_K; idx += 256) {\n int r = idx / TILE_K;\n int c = idx - r * TILE_K;\n int ar = blockRow + r;\n int ac = kk + c;\n As[r][c] = (ar < n && ac < n) ? A[(long long)ar * n + ac] : 0.0f;\n }\n for (int idx = tid; idx < TILE_K * TILE_N; idx += 256) {\n int r = idx / TILE_N;\n int c = idx - r * TILE_N;\n int br = kk + r;\n int bc = blockCol + c;\n Bs[r][c] = (br < n && bc < n) ? B[(long long)br * n + bc] : 0.0f;\n }\n __syncthreads();\n\n #pragma unroll\n for (int k = 0; k < TILE_K; ++k) {\n float a0 = As[ty + 0][k];\n float a1 = As[ty + 16][k];\n float a2 = As[ty + 32][k];\n float a3 = As[ty + 48][k];\n float b0 = Bs[k][tx + 0];\n float b1 = Bs[k][tx + 16];\n float b2 = Bs[k][tx + 32];\n float b3 = Bs[k][tx + 48];\n\n acc[0][0] = fmaf(a0, b0, acc[0][0]);\n acc[0][1] = fmaf(a0, b1, acc[0][1]);\n acc[0][2] = fmaf(a0, b2, acc[0][2]);\n acc[0][3] = fmaf(a0, b3, acc[0][3]);\n acc[1][0] = fmaf(a1, b0, acc[1][0]);\n acc[1][1] = fmaf(a1, b1, acc[1][1]);\n acc[1][2] = fmaf(a1, b2, acc[1][2]);\n acc[1][3] = fmaf(a1, b3, acc[1][3]);\n acc[2][0] = fmaf(a2, b0, acc[2][0]);\n acc[2][1] = fmaf(a2, b1, acc[2][1]);\n acc[2][2] = fmaf(a2, b2, acc[2][2]);\n acc[2][3] = fmaf(a2, b3, acc[2][3]);\n acc[3][0] = fmaf(a3, b0, acc[3][0]);\n acc[3][1] = fmaf(a3, b1, acc[3][1]);\n acc[3][2] = fmaf(a3, b2, acc[3][2]);\n acc[3][3] = fmaf(a3, b3, acc[3][3]);\n }\n __syncthreads();\n }\n\n #pragma unroll\n for (int rr = 0; rr < 4; ++rr) {\n int row = blockRow + ty + rr * 16;\n if (row < n) {\n #pragma unroll\n for (int cc = 0; cc < 4; ++cc) {\n int col = blockCol + tx + cc * 16;\n if (col < n) {\n C[(long long)row * n + col] = (col >= row) ? acc[rr][cc] : 0.0f;\n }\n }\n }\n }\n}\n\ntorch::Tensor triumm_forward(torch::Tensor A, torch::Tensor B) {\n const int n = (int)A.size(0);\n auto C = torch::empty_like(A);\n dim3 block(16, 16);\n dim3 grid((n + TILE_N - 1) / TILE_N, (n + TILE_M - 1) / TILE_M);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n triumm64_kernel<<<grid, block, 0, stream>>>(A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), n);\n return C;\n}\n\"\"\"\n\n_triumm_ext = load_inline(\n name=\"triumm_upper64_ext\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"triumm_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n\n def forward(self, A, B):\n return _triumm_ext.triumm_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: ['0.053955', '0.050659', '0.047119', '0.047852', '0.050842']
avg_difference: ['0.002768', '0.002760', '0.002719', '0.002765', '0.002782']
final submitted kernel (156 lines, 5,079 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 triumm_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>
#define TILE_M 64
#define TILE_N 64
#define TILE_K 32
__global__ __launch_bounds__(256, 2)
void triumm64_kernel(const float* __restrict__ A,
const float* __restrict__ B,
float* __restrict__ C,
int n) {
const int blockCol = blockIdx.x * TILE_N;
const int blockRow = blockIdx.y * TILE_M;
const int tx = threadIdx.x; // 0..15
const int ty = threadIdx.y; // 0..15
// Tiles strictly below the diagonal are exactly zero. Write them here so
// the returned empty tensor has the same lower triangle as torch.triu.
if (blockCol < blockRow) {
#pragma unroll
for (int rr = 0; rr < 4; ++rr) {
int row = blockRow + ty + rr * 16;
if (row < n) {
#pragma unroll
for (int cc = 0; cc < 4; ++cc) {
int col = blockCol + tx + cc * 16;
if (col < n) C[(long long)row * n + col] = 0.0f;
}
}
}
return;
}
__shared__ float As[TILE_M][TILE_K + 1];
__shared__ float Bs[TILE_K][TILE_N + 1];
float acc[4][4];
#pragma unroll
for (int i = 0; i < 4; ++i) {
#pragma unroll
for (int j = 0; j < 4; ++j) acc[i][j] = 0.0f;
}
const int tid = ty * 16 + tx;
int kEnd = blockCol + TILE_N;
if (kEnd > n) kEnd = n;
// Because A and B are upper triangular, only k in [blockRow, blockCol+63]
// can contribute to this 64x64 output tile. Values inside the boundary
// tiles that are outside the true per-element range are already stored as
// zeros in A or B.
for (int kk = blockRow; kk < kEnd; kk += TILE_K) {
for (int idx = tid; idx < TILE_M * TILE_K; idx += 256) {
int r = idx / TILE_K;
int c = idx - r * TILE_K;
int ar = blockRow + r;
int ac = kk + c;
As[r][c] = (ar < n && ac < n) ? A[(long long)ar * n + ac] : 0.0f;
}
for (int idx = tid; idx < TILE_K * TILE_N; idx += 256) {
int r = idx / TILE_N;
int c = idx - r * TILE_N;
int br = kk + r;
int bc = blockCol + c;
Bs[r][c] = (br < n && bc < n) ? B[(long long)br * n + bc] : 0.0f;
}
__syncthreads();
#pragma unroll
for (int k = 0; k < TILE_K; ++k) {
float a0 = As[ty + 0][k];
float a1 = As[ty + 16][k];
float a2 = As[ty + 32][k];
float a3 = As[ty + 48][k];
float b0 = Bs[k][tx + 0];
float b1 = Bs[k][tx + 16];
float b2 = Bs[k][tx + 32];
float b3 = Bs[k][tx + 48];
acc[0][0] = fmaf(a0, b0, acc[0][0]);
acc[0][1] = fmaf(a0, b1, acc[0][1]);
acc[0][2] = fmaf(a0, b2, acc[0][2]);
acc[0][3] = fmaf(a0, b3, acc[0][3]);
acc[1][0] = fmaf(a1, b0, acc[1][0]);
acc[1][1] = fmaf(a1, b1, acc[1][1]);
acc[1][2] = fmaf(a1, b2, acc[1][2]);
acc[1][3] = fmaf(a1, b3, acc[1][3]);
acc[2][0] = fmaf(a2, b0, acc[2][0]);
acc[2][1] = fmaf(a2, b1, acc[2][1]);
acc[2][2] = fmaf(a2, b2, acc[2][2]);
acc[2][3] = fmaf(a2, b3, acc[2][3]);
acc[3][0] = fmaf(a3, b0, acc[3][0]);
acc[3][1] = fmaf(a3, b1, acc[3][1]);
acc[3][2] = fmaf(a3, b2, acc[3][2]);
acc[3][3] = fmaf(a3, b3, acc[3][3]);
}
__syncthreads();
}
#pragma unroll
for (int rr = 0; rr < 4; ++rr) {
int row = blockRow + ty + rr * 16;
if (row < n) {
#pragma unroll
for (int cc = 0; cc < 4; ++cc) {
int col = blockCol + tx + cc * 16;
if (col < n) {
C[(long long)row * n + col] = (col >= row) ? acc[rr][cc] : 0.0f;
}
}
}
}
}
torch::Tensor triumm_forward(torch::Tensor A, torch::Tensor B) {
const int n = (int)A.size(0);
auto C = torch::empty_like(A);
dim3 block(16, 16);
dim3 grid((n + TILE_N - 1) / TILE_N, (n + TILE_M - 1) / TILE_M);
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
triumm64_kernel<<<grid, block, 0, stream>>>(A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), n);
return C;
}
"""
_triumm_ext = load_inline(
name="triumm_upper64_ext",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["triumm_forward"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3", "--use_fast_math"],
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self):
super(ModelNew, self).__init__()
def forward(self, A, B):
return _triumm_ext.triumm_forward(A, B)