correct
10_3D_tensor_matrix_multiplication.py
started
2026-05-05T02:29:25Z
timingkernelbench.timing
kernel mean / median / std
1.04 / — / 8.59e-04 μs
ref mean / median / std
1.04 / — / 7.67e-04 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
5.00x
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
431.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):
"""
Performs 3D tensor-matrix multiplication.
"""
def __init__(self):
super(Model, self).__init__()
def forward(self, A, B):
"""
Performs 3D tensor-matrix multiplication.
Args:
A (torch.Tensor): Input 3D tensor of shape (N, M, K).
B (torch.Tensor): Input matrix of shape (K, L).
Returns:
torch.Tensor: Output tensor of shape (N, M, L), resulting from the multiplication of A and B along the last dimension of A.
"""
return torch.matmul(A, B)
N = 16
M = 1024
K = 2048
L = 768
def get_inputs():
A = torch.rand(N, M, K)
B = torch.rand(K, L)
return [A, B]
def get_init_inputs():
return [] # No special initialization inputs neededturn 039.8s turn · 39.8s LLM · 0.0s tools · FINAL
reasoning
**Evaluating TF32 settings**
I'm looking into reference tolerances and noticing that the TF32 error seems too high, which might indicate it's disabled. In PyTorch, I found that the default allows TF32 but matmul may have a modern default that's set to false. I need to make sure the math mode is controlled properly and set to CUBLAS_DEFAULT_MATH to avoid TF32. I wonder if setting a global handle could impact results. Ultimately, it's crucial to ensure the evaluation aligns with the reference outputs for accuracy.
**Considering cublas settings**
It might be that the settings are similar but perhaps slightly faster. I’m wondering about the timing and whether there’s overhead with setting the cublas math mode for every call. Maybe it’s better to configure it just once instead of every single time? I could call cublasSetMathMode for each small call, but I need to think about PyTorch's handling of this—like if it defaults to not using TF32 for correctness.
**Considering cublas options**
I’m thinking about using `cublasGemmEx` with the `CUBLAS_GEMM_DEFAULT` algorithm. I wonder if `cublasSgemm` would also work. There's the option to utilize split-K as well for optimization. I’m a bit concerned about potential correctness issues since `cublas` can be non-deterministic, and I want to keep the precision within 1e-4. It looks like I’m ready to submit this approach!
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 matmul3d_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 <c10/cuda/CUDAGuard.h>\n#include <cublas_v2.h>\n\nstatic inline void check_cublas_status(cublasStatus_t status) {\n TORCH_CHECK(status == CUBLAS_STATUS_SUCCESS, \"cuBLAS call failed\");\n}\n\ntorch::Tensor matmul3d_forward(torch::Tensor A, torch::Tensor B) {\n c10::cuda::CUDAGuard device_guard(A.device());\n\n const int64_t N = A.size(0);\n const int64_t M = A.size(1);\n const int64_t K = A.size(2);\n const int64_t L = B.size(1);\n const int64_t rows = N * M;\n\n auto C = torch::empty({N, M, L}, A.options());\n\n // Row-major C[rows,L] = A[rows,K] @ B[K,L].\n // Interpreting the same buffers as column-major gives:\n // C_col[L,rows] = B_col[L,K] * A_col[K,rows]\n // which is exactly the transpose view of the desired row-major result.\n const float alpha = 1.0f;\n const float beta = 0.0f;\n\n cublasHandle_t handle = at::cuda::getCurrentCUDABlasHandle();\n auto stream = at::cuda::getCurrentCUDAStream();\n check_cublas_status(cublasSetStream(handle, stream.stream()));\n\n check_cublas_status(cublasSgemm(\n handle,\n CUBLAS_OP_N, CUBLAS_OP_N,\n static_cast<int>(L), static_cast<int>(rows), static_cast<int>(K),\n &alpha,\n B.data_ptr<float>(), static_cast<int>(L),\n A.data_ptr<float>(), static_cast<int>(K),\n &beta,\n C.data_ptr<float>(), static_cast<int>(L)\n ));\n\n return C;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"matmul3d_cublas_ext\",\n cpp_sources=[_cpp_src],\n cuda_sources=[_cuda_src],\n functions=[\"matmul3d_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n extra_ldflags=[\"-lcublas\"],\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, B):\n return _ext.matmul3d_forward(A, B)\n"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 1.04 μs
Runtime stats: mean=1.04μs median=?μs std=0.00μs
Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00
Memory: 215.0 MB peak (ref: 215.0 MB, ratio: 1.0x)
Kernel launches: 1 (ref: 5, fusion ratio: 5.0)
SOL score: 0.7721
final submitted kernel (76 lines, 2,112 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 matmul3d_forward(torch::Tensor A, torch::Tensor B);
"""
_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <cublas_v2.h>
static inline void check_cublas_status(cublasStatus_t status) {
TORCH_CHECK(status == CUBLAS_STATUS_SUCCESS, "cuBLAS call failed");
}
torch::Tensor matmul3d_forward(torch::Tensor A, torch::Tensor B) {
c10::cuda::CUDAGuard device_guard(A.device());
const int64_t N = A.size(0);
const int64_t M = A.size(1);
const int64_t K = A.size(2);
const int64_t L = B.size(1);
const int64_t rows = N * M;
auto C = torch::empty({N, M, L}, A.options());
// Row-major C[rows,L] = A[rows,K] @ B[K,L].
// Interpreting the same buffers as column-major gives:
// C_col[L,rows] = B_col[L,K] * A_col[K,rows]
// which is exactly the transpose view of the desired row-major result.
const float alpha = 1.0f;
const float beta = 0.0f;
cublasHandle_t handle = at::cuda::getCurrentCUDABlasHandle();
auto stream = at::cuda::getCurrentCUDAStream();
check_cublas_status(cublasSetStream(handle, stream.stream()));
check_cublas_status(cublasSgemm(
handle,
CUBLAS_OP_N, CUBLAS_OP_N,
static_cast<int>(L), static_cast<int>(rows), static_cast<int>(K),
&alpha,
B.data_ptr<float>(), static_cast<int>(L),
A.data_ptr<float>(), static_cast<int>(K),
&beta,
C.data_ptr<float>(), static_cast<int>(L)
));
return C;
}
"""
_ext = load_inline(
name="matmul3d_cublas_ext",
cpp_sources=[_cpp_src],
cuda_sources=[_cuda_src],
functions=["matmul3d_forward"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3"],
extra_ldflags=["-lcublas"],
with_cuda=True,
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self):
super(ModelNew, self).__init__()
def forward(self, A, B):
return _ext.matmul3d_forward(A, B)