gpt-5.5-priority · original · l123_single_gpt
correct 3_DeepNarrowMLP.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
1.72 μs
ref runtime
1.83 μs
speedup
1.06x
started
2026-05-05T00:32:32Z
timingkernelbench.timing
kernel runtime
1.72 μs
ref runtime
1.83 μs
speedup
1.06x
kernel mean / median / std
1.72 / — / 0.03 μs
ref mean / median / std
1.83 / — / 0.04 μs
numerical precisioneval_kernel_against_ref
max abs error
2.42e-08
mean abs error
1.86e-09
max rel error
0.00
mean rel error
8.16e-07
gpu memoryextended_metrics.measure_memory
kernel peak
349.19 MB
ref peak
349.19 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
116
ref launches
117
fusion ratio (ref / kernel)
1.01x
total cuda time
4073 μs
energyNVML (pynvml)
kernel mJ / run
243.24
ref mJ / run
243.22
ratio (ref / kernel)
1.00x
avg power
141.60 W
ref avg power
133.77 W
SOL (speed-of-light)heuristic_from_speedup
SOL score (0–1)
0.106
dram util %
—
compute util %
—
bottleneck
unknown
dominant pipe
—
arithmetic intensity
—
ridge point
—
roofline / occupancyheuristic
dram bandwidth
—
dram util %
—
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
—
bottleneck
—
dominant pipe %
—
arithmetic intensity
—
ridge point
—
L1 hit %
—
L2 hit %
—
ld sectors / req
—
st sectors / req
—
regs / thread
—
smem / block
—
block size
—
peak bw
—
peak fp32 tflops
—
peak fp16 tflops
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self, input_size, hidden_layer_sizes, output_size):
        """
        :param input_size: The number of input features
        :param hidden_layer_sizes: A list of ints containing the sizes of each hidden layer
        :param output_size: The number of output features
        """
        super(Model, self).__init__()
        
        layers = []
        current_input_size = input_size
        
        for hidden_size in hidden_layer_sizes:
            layers.append(nn.Linear(current_input_size, hidden_size))
            layers.append(nn.ReLU())
            current_input_size = hidden_size
        
        layers.append(nn.Linear(current_input_size, output_size))
        
        self.network = nn.Sequential(*layers)
    
    def forward(self, x):
        """
        :param x: The input tensor, shape (batch_size, input_size)
        :return: The output tensor, shape (batch_size, output_size)
        """
        return self.network(x)

# Test code
batch_size = 1024
input_size = 8192
hidden_layer_sizes = [1024] * 16  # deep network with wider layers
output_size = 8192

def get_inputs():
    return [torch.rand(batch_size, input_size)]

def get_init_inputs():
    return [input_size, hidden_layer_sizes, output_size]
turn 0 · FINAL · 1 tool call(s) · 27.4s turn · 27.4s LLM · 7,562 chars (click to expand)
turn 027.4s turn · 27.4s LLM · 0.0s tools · FINAL
reasoning
**Determining numerical equivalence** I’m thinking about needing a numerical equivalence for fp32 with a tolerance of 1e-4. I wonder if PyTorch matmul uses TF32 by default, which might affect how I approach this. It's crucial to get these details right, especially in computations where precision matters. I’ll have to verify the PyTorch documentation or settings to confirm the default behavior of matmul and ensure that my calculations align with the tolerance requirement. **Considering precision implementation** I'm thinking about implementing fp32 precision with a default random state. I might use float only and consider a fallback with AT_DISPATCH if half precision isn't needed. My focus here is on ensuring correctness for fp32. Also, I need to make sure that hidden tensors are contiguous. It's a bit of a technical challenge, but I'm keen on getting this right! I wonder how best to proceed while keeping everything efficient.
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#include <vector>\n\nvoid bias_relu_launcher(torch::Tensor inp, torch::Tensor bias, torch::Tensor out);\n\n// Forward for the fixed MLP pattern: all but the last layer are Linear+ReLU,\n// last layer is Linear. The heavy work is still dispatched to cuBLAS through\n// ATen, while the elementwise bias+ReLU epilogue is fused in a small CUDA\n// kernel and the whole network is driven from one Python/C++ call.\ntorch::Tensor mlp_forward(torch::Tensor x, std::vector<torch::Tensor> weights, std::vector<torch::Tensor> biases) {\n TORCH_CHECK(weights.size() == biases.size(), \"weights/biases length mismatch\");\n TORCH_CHECK(weights.size() >= 1, \"expected at least one layer\");\n auto h = x;\n const int64_t L = (int64_t)weights.size();\n for (int64_t i = 0; i < L - 1; ++i) {\n // h: [B, in], weight: [out, in] -> tmp: [B, out]\n auto tmp = at::matmul(h, weights[i].t());\n auto y = at::empty_like(tmp);\n bias_relu_launcher(tmp, biases[i], y);\n h = y;\n }\n // final linear, use the same addmm formulation as nn.Linear\n auto out = at::addmm(biases[L-1], h, weights[L-1].t());\n return out;\n}\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAGuard.h>\n\n__global__ void bias_relu_float4_kernel(const float* __restrict__ inp,\n const float* __restrict__ bias,\n float* __restrict__ out,\n int64_t n4,\n int cols) {\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= n4) return;\n float4 v = reinterpret_cast<const float4*>(inp)[idx];\n int col4 = ((int)(idx % (cols / 4))) * 4;\n float4 b = reinterpret_cast<const float4*>(bias)[col4 / 4];\n v.x = fmaxf(v.x + b.x, 0.0f);\n v.y = fmaxf(v.y + b.y, 0.0f);\n v.z = fmaxf(v.z + b.z, 0.0f);\n v.w = fmaxf(v.w + b.w, 0.0f);\n reinterpret_cast<float4*>(out)[idx] = v;\n}\n\n__global__ void bias_relu_scalar_kernel(const float* __restrict__ inp,\n const float* __restrict__ bias,\n float* __restrict__ out,\n int64_t n,\n int cols) {\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= n) return;\n int col = (int)(idx % cols);\n float v = inp[idx] + bias[col];\n out[idx] = v > 0.0f ? v : 0.0f;\n}\n\nvoid bias_relu_launcher(torch::Tensor inp, torch::Tensor bias, torch::Tensor out) {\n c10::cuda::CUDAGuard device_guard(inp.device());\n TORCH_CHECK(inp.is_cuda() && bias.is_cuda() && out.is_cuda(), \"CUDA tensors expected\");\n TORCH_CHECK(inp.scalar_type() == torch::kFloat32, \"only float32 supported\");\n TORCH_CHECK(inp.is_contiguous() && out.is_contiguous() && bias.is_contiguous(), \"contiguous tensors expected\");\n const int64_t n = inp.numel();\n const int cols = (int)inp.size(-1);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n const int threads = 256;\n if ((cols % 4) == 0 && ((uintptr_t)inp.data_ptr<float>() % 16) == 0 && ((uintptr_t)out.data_ptr<float>() % 16) == 0 && ((uintptr_t)bias.data_ptr<float>() % 16) == 0) {\n int64_t n4 = n / 4;\n int blocks = (int)((n4 + threads - 1) / threads);\n bias_relu_float4_kernel<<<blocks, threads, 0, stream>>>(inp.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(), n4, cols);\n } else {\n int blocks = (int)((n + threads - 1) / threads);\n bias_relu_scalar_kernel<<<blocks, threads, 0, stream>>>(inp.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(), n, cols);\n }\n}\n'''\n\n_ext = load_inline(\n name='mlp_fused_bias_relu_ext_v1',\n cpp_sources=[_cpp_src],\n cuda_sources=[_cuda_src],\n functions=['mlp_forward'],\n extra_cflags=['-O3'],\n extra_cuda_cflags=['-O3'],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_size, hidden_layer_sizes, output_size):\n super(ModelNew, self).__init__()\n layers = []\n current_input_size = input_size\n for hidden_size in hidden_layer_sizes:\n layers.append(nn.Linear(current_input_size, hidden_size))\n layers.append(nn.ReLU())\n current_input_size = hidden_size\n layers.append(nn.Linear(current_input_size, output_size))\n self.network = nn.Sequential(*layers)\n # Cache the Linear modules; Parameter objects remain the authoritative\n # module parameters and are used directly in forward.\n self._linear_indices = [i for i, m in enumerate(self.network) if isinstance(m, nn.Linear)]\n\n def forward(self, x):\n weights = []\n biases = []\n for i in self._linear_indices:\n m = self.network[i]\n weights.append(m.weight)\n biases.append(m.bias)\n return _ext.mlp_forward(x, weights, biases)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 1.72 μs Runtime stats: mean=1.72μs median=?μs std=0.03μs Numerical precision: max_abs_err=2.42e-08 mean_abs_err=1.86e-09 max_rel_err=3.91e-03 Memory: 349.19 MB peak (ref: 349.19 MB, ratio: 1.0x) Kernel launches: 116 (ref: 117, fusion ratio: 1.0086) Energy: 243.24 mJ/run (ref: 243.22 mJ, ratio: 0.9999x) SOL score: 0.1064
final submitted kernel (120 lines, 5,109 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>
#include <vector>

void bias_relu_launcher(torch::Tensor inp, torch::Tensor bias, torch::Tensor out);

// Forward for the fixed MLP pattern: all but the last layer are Linear+ReLU,
// last layer is Linear.  The heavy work is still dispatched to cuBLAS through
// ATen, while the elementwise bias+ReLU epilogue is fused in a small CUDA
// kernel and the whole network is driven from one Python/C++ call.
torch::Tensor mlp_forward(torch::Tensor x, std::vector<torch::Tensor> weights, std::vector<torch::Tensor> biases) {
    TORCH_CHECK(weights.size() == biases.size(), "weights/biases length mismatch");
    TORCH_CHECK(weights.size() >= 1, "expected at least one layer");
    auto h = x;
    const int64_t L = (int64_t)weights.size();
    for (int64_t i = 0; i < L - 1; ++i) {
        // h: [B, in], weight: [out, in] -> tmp: [B, out]
        auto tmp = at::matmul(h, weights[i].t());
        auto y = at::empty_like(tmp);
        bias_relu_launcher(tmp, biases[i], y);
        h = y;
    }
    // final linear, use the same addmm formulation as nn.Linear
    auto out = at::addmm(biases[L-1], h, weights[L-1].t());
    return out;
}
'''

_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>

__global__ void bias_relu_float4_kernel(const float* __restrict__ inp,
                                        const float* __restrict__ bias,
                                        float* __restrict__ out,
                                        int64_t n4,
                                        int cols) {
    int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= n4) return;
    float4 v = reinterpret_cast<const float4*>(inp)[idx];
    int col4 = ((int)(idx % (cols / 4))) * 4;
    float4 b = reinterpret_cast<const float4*>(bias)[col4 / 4];
    v.x = fmaxf(v.x + b.x, 0.0f);
    v.y = fmaxf(v.y + b.y, 0.0f);
    v.z = fmaxf(v.z + b.z, 0.0f);
    v.w = fmaxf(v.w + b.w, 0.0f);
    reinterpret_cast<float4*>(out)[idx] = v;
}

__global__ void bias_relu_scalar_kernel(const float* __restrict__ inp,
                                        const float* __restrict__ bias,
                                        float* __restrict__ out,
                                        int64_t n,
                                        int cols) {
    int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= n) return;
    int col = (int)(idx % cols);
    float v = inp[idx] + bias[col];
    out[idx] = v > 0.0f ? v : 0.0f;
}

void bias_relu_launcher(torch::Tensor inp, torch::Tensor bias, torch::Tensor out) {
    c10::cuda::CUDAGuard device_guard(inp.device());
    TORCH_CHECK(inp.is_cuda() && bias.is_cuda() && out.is_cuda(), "CUDA tensors expected");
    TORCH_CHECK(inp.scalar_type() == torch::kFloat32, "only float32 supported");
    TORCH_CHECK(inp.is_contiguous() && out.is_contiguous() && bias.is_contiguous(), "contiguous tensors expected");
    const int64_t n = inp.numel();
    const int cols = (int)inp.size(-1);
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    const int threads = 256;
    if ((cols % 4) == 0 && ((uintptr_t)inp.data_ptr<float>() % 16) == 0 && ((uintptr_t)out.data_ptr<float>() % 16) == 0 && ((uintptr_t)bias.data_ptr<float>() % 16) == 0) {
        int64_t n4 = n / 4;
        int blocks = (int)((n4 + threads - 1) / threads);
        bias_relu_float4_kernel<<<blocks, threads, 0, stream>>>(inp.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(), n4, cols);
    } else {
        int blocks = (int)((n + threads - 1) / threads);
        bias_relu_scalar_kernel<<<blocks, threads, 0, stream>>>(inp.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(), n, cols);
    }
}
'''

_ext = load_inline(
    name='mlp_fused_bias_relu_ext_v1',
    cpp_sources=[_cpp_src],
    cuda_sources=[_cuda_src],
    functions=['mlp_forward'],
    extra_cflags=['-O3'],
    extra_cuda_cflags=['-O3'],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, input_size, hidden_layer_sizes, output_size):
        super(ModelNew, self).__init__()
        layers = []
        current_input_size = input_size
        for hidden_size in hidden_layer_sizes:
            layers.append(nn.Linear(current_input_size, hidden_size))
            layers.append(nn.ReLU())
            current_input_size = hidden_size
        layers.append(nn.Linear(current_input_size, output_size))
        self.network = nn.Sequential(*layers)
        # Cache the Linear modules; Parameter objects remain the authoritative
        # module parameters and are used directly in forward.
        self._linear_indices = [i for i, m in enumerate(self.network) if isinstance(m, nn.Linear)]

    def forward(self, x):
        weights = []
        biases = []
        for i in self._linear_indices:
            m = self.network[i]
            weights.append(m.weight)
            biases.append(m.bias)
        return _ext.mlp_forward(x, weights, biases)