correct
40_Matmul_Scaling_ResidualAdd.py
started
2026-05-05T00:46:00Z
timingkernelbench.timing
kernel mean / median / std
11.20 / — / 0.50 μs
ref mean / median / std
11.50 / — / 0.23 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
12.00x
energyNVML (pynvml)
ratio (ref / kernel)
1.33x
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
2524.14 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):
"""
A model that performs a matrix multiplication, scaling, and residual addition.
Args:
in_features (int): Number of input features.
out_features (int): Number of output features.
scaling_factor (float): Scaling factor to apply after matrix multiplication.
"""
def __init__(self, in_features, out_features, scaling_factor):
super(Model, self).__init__()
self.matmul = nn.Linear(in_features, out_features)
self.scaling_factor = scaling_factor
def forward(self, x):
"""
Forward pass of the model.
Args:
x (torch.Tensor): Input tensor of shape (batch_size, in_features).
Returns:
torch.Tensor: Output tensor of shape (batch_size, out_features).
"""
x = self.matmul(x)
original_x = x.clone().detach()
x = x * self.scaling_factor
x = x + original_x
return x
batch_size = 16384
in_features = 4096
out_features = 4096
scaling_factor = 0.5
def get_inputs():
return [torch.rand(batch_size, in_features)]
def get_init_inputs():
return [in_features, out_features, scaling_factor]turn 0 · FINAL · 1 tool call(s) · 72.7s turn · 72.7s LLM · 8,251 chars (click to expand)
turn 072.7s turn · 72.7s LLM · 0.0s tools · FINAL
reasoning
**Crafting a Python Model**
I’m working on a Python model that needs to replace the “Linear + scale/add” process. The output should follow a specific format: linear(x) * (scaling + 1). However, the issue is that the original clone detaches the same values without gradients, making it a forward-only operation. I need to ensure the model accepts parameters, probably by using nn.Module with nn.Linear. Evaluating the model involves copying the state_dict, and I’m considering the efficiency of operations like GEMM and whether to use custom CUDA tasks. I also wonder if torch operations would be sensible here.
**Developing a Custom CUDA Solution**
I'm considering compiling an extension with a host wrapper using at::linear, but I realize that routes to PyTorch, which isn't ideal. I need to think about a custom CUDA kernel for the epilogue after cublas. The output is structured as output = alpha * (XW^T + b), so I want to utilize cublasSgemm for alpha * XW^T and then add the custom kernel for bias. By using cublas with beta initialization for the bias, I could efficiently manage the operations. I also want to leverage row-major tricks in PyTorch for optimization.
**Evaluating PyTorch and TF32**
I'm considering how PyTorch handles TF32, and how that might differ from full fp32. There's a chance the tolerance is loose, around 1e-4 at specific values. Many evaluations probably use TF32 by default, especially for convolutions. In recent versions, the default for matmul can be set to not allow TF32. I also wonder if full fp32 cublas operations could be slower than PyTorch. Plus, I need to think about handling bias, particularly how it's managed in the linear function.
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 fused_linear_scale_residual_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n double scaling_factor);\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <ATen/Context.h>\n#include <c10/cuda/CUDAGuard.h>\n#include <cuda_runtime.h>\n#include <cublas_v2.h>\n\n#define CHECK_CUBLAS(call) do { \\\n cublasStatus_t _status = (call); \\\n TORCH_CHECK(_status == CUBLAS_STATUS_SUCCESS, \\\n \"cuBLAS error: \", static_cast<int>(_status)); \\\n} while (0)\n\n__global__ void fill_scaled_bias_kernel(float* __restrict__ out,\n const float* __restrict__ bias,\n int64_t total,\n int64_t n,\n float scale) {\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n int64_t stride = (int64_t)blockDim.x * gridDim.x;\n for (; idx < total; idx += stride) {\n out[idx] = scale * bias[idx % n];\n }\n}\n\ntorch::Tensor fused_linear_scale_residual_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n double scaling_factor) {\n TORCH_CHECK(x.is_cuda() && weight.is_cuda() && bias.is_cuda(), \"all tensors must be CUDA tensors\");\n TORCH_CHECK(x.scalar_type() == at::kFloat && weight.scalar_type() == at::kFloat && bias.scalar_type() == at::kFloat,\n \"only float32 tensors are supported\");\n TORCH_CHECK(x.is_contiguous() && weight.is_contiguous() && bias.is_contiguous(),\n \"all tensors must be contiguous\");\n TORCH_CHECK(x.dim() == 2 && weight.dim() == 2 && bias.dim() == 1, \"bad tensor dimensions\");\n\n const int64_t M64 = x.size(0);\n const int64_t K64 = x.size(1);\n const int64_t N64 = weight.size(0);\n TORCH_CHECK(weight.size(1) == K64 && bias.size(0) == N64, \"incompatible shapes\");\n TORCH_CHECK(M64 <= INT_MAX && N64 <= INT_MAX && K64 <= INT_MAX, \"matrix too large for cuBLAS int API\");\n\n c10::cuda::CUDAGuard device_guard(x.device());\n auto out = torch::empty({M64, N64}, x.options());\n\n const float gamma = static_cast<float>(1.0 + scaling_factor);\n const int64_t total = M64 * N64;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n const int threads = 256;\n int blocks = (int)((total + threads - 1) / threads);\n if (blocks > 65535) blocks = 65535;\n fill_scaled_bias_kernel<<<blocks, threads, 0, stream>>>(\n out.data_ptr<float>(), bias.data_ptr<float>(), total, N64, gamma);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n\n cublasHandle_t handle = at::cuda::getCurrentCUDABlasHandle();\n\n const int M = static_cast<int>(M64);\n const int N = static_cast<int>(N64);\n const int K = static_cast<int>(K64);\n const float alpha = gamma;\n const float beta = 1.0f;\n\n // Row-major C[M,N] is viewed as column-major C^T[N,M].\n // Compute C^T = gamma * W[N,K] * X^T[K,M] + scaled_bias, using W and X storage directly.\n cublasComputeType_t compute_type = at::globalContext().allowTF32CuBLAS()\n ? CUBLAS_COMPUTE_32F_FAST_TF32\n : CUBLAS_COMPUTE_32F_PEDANTIC;\n\n CHECK_CUBLAS(cublasGemmEx(handle,\n CUBLAS_OP_T, CUBLAS_OP_N,\n N, M, K,\n &alpha,\n weight.data_ptr<float>(), CUDA_R_32F, K,\n x.data_ptr<float>(), CUDA_R_32F, K,\n &beta,\n out.data_ptr<float>(), CUDA_R_32F, N,\n compute_type,\n CUBLAS_GEMM_DEFAULT));\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"fused_linear_scale_residual_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"fused_linear_scale_residual_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n extra_ldflags=[\"-lcublas\"],\n with_cuda=True,\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, in_features, out_features, scaling_factor):\n super(ModelNew, self).__init__()\n self.matmul = nn.Linear(in_features, out_features)\n self.scaling_factor = scaling_factor\n\n def forward(self, x):\n return _ext.fused_linear_scale_residual_forward(\n x.contiguous(),\n self.matmul.weight.contiguous(),\n self.matmul.bias.contiguous(),\n float(self.scaling_factor),\n )\n"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 11.20 μs
Runtime stats: mean=11.20μs median=?μs std=0.50μs
Numerical precision: max_abs_err=2.38e-07 mean_abs_err=9.18e-09 max_rel_err=4.17e-02
Memory: 673.03 MB peak (ref: 1185.03 MB, ratio: 0.5679x)
Kernel launches: 1 (ref: 12, fusion ratio: 12.0)
Energy: 1441.56 mJ/run (ref: 1922.28 mJ, ratio: 1.3335x)
SOL score: 1.0
final submitted kernel (125 lines, 4,982 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 fused_linear_scale_residual_forward(torch::Tensor x,
torch::Tensor weight,
torch::Tensor bias,
double scaling_factor);
"""
_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <ATen/Context.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#define CHECK_CUBLAS(call) do { \
cublasStatus_t _status = (call); \
TORCH_CHECK(_status == CUBLAS_STATUS_SUCCESS, \
"cuBLAS error: ", static_cast<int>(_status)); \
} while (0)
__global__ void fill_scaled_bias_kernel(float* __restrict__ out,
const float* __restrict__ bias,
int64_t total,
int64_t n,
float scale) {
int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
int64_t stride = (int64_t)blockDim.x * gridDim.x;
for (; idx < total; idx += stride) {
out[idx] = scale * bias[idx % n];
}
}
torch::Tensor fused_linear_scale_residual_forward(torch::Tensor x,
torch::Tensor weight,
torch::Tensor bias,
double scaling_factor) {
TORCH_CHECK(x.is_cuda() && weight.is_cuda() && bias.is_cuda(), "all tensors must be CUDA tensors");
TORCH_CHECK(x.scalar_type() == at::kFloat && weight.scalar_type() == at::kFloat && bias.scalar_type() == at::kFloat,
"only float32 tensors are supported");
TORCH_CHECK(x.is_contiguous() && weight.is_contiguous() && bias.is_contiguous(),
"all tensors must be contiguous");
TORCH_CHECK(x.dim() == 2 && weight.dim() == 2 && bias.dim() == 1, "bad tensor dimensions");
const int64_t M64 = x.size(0);
const int64_t K64 = x.size(1);
const int64_t N64 = weight.size(0);
TORCH_CHECK(weight.size(1) == K64 && bias.size(0) == N64, "incompatible shapes");
TORCH_CHECK(M64 <= INT_MAX && N64 <= INT_MAX && K64 <= INT_MAX, "matrix too large for cuBLAS int API");
c10::cuda::CUDAGuard device_guard(x.device());
auto out = torch::empty({M64, N64}, x.options());
const float gamma = static_cast<float>(1.0 + scaling_factor);
const int64_t total = M64 * N64;
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
const int threads = 256;
int blocks = (int)((total + threads - 1) / threads);
if (blocks > 65535) blocks = 65535;
fill_scaled_bias_kernel<<<blocks, threads, 0, stream>>>(
out.data_ptr<float>(), bias.data_ptr<float>(), total, N64, gamma);
C10_CUDA_KERNEL_LAUNCH_CHECK();
cublasHandle_t handle = at::cuda::getCurrentCUDABlasHandle();
const int M = static_cast<int>(M64);
const int N = static_cast<int>(N64);
const int K = static_cast<int>(K64);
const float alpha = gamma;
const float beta = 1.0f;
// Row-major C[M,N] is viewed as column-major C^T[N,M].
// Compute C^T = gamma * W[N,K] * X^T[K,M] + scaled_bias, using W and X storage directly.
cublasComputeType_t compute_type = at::globalContext().allowTF32CuBLAS()
? CUBLAS_COMPUTE_32F_FAST_TF32
: CUBLAS_COMPUTE_32F_PEDANTIC;
CHECK_CUBLAS(cublasGemmEx(handle,
CUBLAS_OP_T, CUBLAS_OP_N,
N, M, K,
&alpha,
weight.data_ptr<float>(), CUDA_R_32F, K,
x.data_ptr<float>(), CUDA_R_32F, K,
&beta,
out.data_ptr<float>(), CUDA_R_32F, N,
compute_type,
CUBLAS_GEMM_DEFAULT));
return out;
}
"""
_ext = load_inline(
name="fused_linear_scale_residual_ext_v1",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["fused_linear_scale_residual_forward"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3"],
extra_ldflags=["-lcublas"],
with_cuda=True,
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self, in_features, out_features, scaling_factor):
super(ModelNew, self).__init__()
self.matmul = nn.Linear(in_features, out_features)
self.scaling_factor = scaling_factor
def forward(self, x):
return _ext.fused_linear_scale_residual_forward(
x.contiguous(),
self.matmul.weight.contiguous(),
self.matmul.bias.contiguous(),
float(self.scaling_factor),
)