FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 49_sum_gelu_across_virtual_ranks.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
6094
wall clock
684.7s
truncated
yes
backend
cuda fp32
variant
popcorn
runtime
0.01 μs
ref runtime
0.01 μs
speedup
1.55x
started
2026-05-11T09:09:34Z
timingkernelbench.timing
kernel runtime
0.01 μs
ref runtime
0.01 μs
speedup
1.55x
kernel mean / median / std
0.01 / — / 1.21e-04 μs
ref mean / median / std
0.01 / — / 4.73e-04 μs
numerical precisioneval_kernel_against_ref
max abs error
4.77e-07
mean abs error
2.90e-08
max rel error
2.91e-05
mean rel error
1.21e-07
gpu memoryextended_metrics.measure_memory
kernel peak
33.01 MB
ref peak
33.02 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
3
fusion ratio (ref / kernel)
3.00x
total cuda time
25 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.001
dram util %
0.1%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
4.51 GB/s
dram util %
0.1%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
10.6%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
98.5%
ld sectors / req
3.89
st sectors / req
—
regs / thread
30
smem / block
1024 B
block size
256
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
461.9%
short_scoreboard
114.2%
mio_throttle
0.1%
reference PyTorch source (the prompt the agent sees)
"""
Order-sensitive virtual collective: out = sum_r GELU(x_r), same shape as one rank slice (B, D).

Note: this is not equal to GELU(sum_r x_r).
"""

import torch
import torch.nn as nn
import torch.nn.functional as F

from kernelbench.distributed_collectives import default_device

R = 4
B = 14
D = 40


class Model(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return F.gelu(x).sum(dim=0)


def get_inputs():
    dev = default_device()
    p = popcorn_pri
    return [torch.randn(p.jitter_int(R), p.jitter_int(B), p.jitter_int(D, align=8), device=dev)]


def get_init_inputs():
    return []
turn 0 · FINAL · 1 tool call(s) · 684.7s turn · 49.3s LLM · tokens in=998 · out=5,096 · total=6,094 · 19,960 chars (click to expand)
turn 0684.7s turn · 49.3s LLM · 635.4s tools · tokens in=998 · out=5,096 · total=6,094 · FINAL · reasoning clipped
reasoning
reasoning (truncated 310 chars): Let me analyze the task: 1. The model computes `F.gelu(x).sum(dim=0)` on a tensor of shape `(R, B, D)` = `(4, 14, 40)` 2. The output shape is `(B, D)` = `(14, 40)` 3. This is equivalent to applying GELU element-wise to each element, then summing across the R dimension So I need to write a custom CUDA kernel that: 1. Applies GELU to each element 2. Sums across dimension 0 (the R dimension) Let me think about how to optimize this: - The input has shape (R, B, D) = (4, 14, 40) = 2240 elements - The output has shape (B, D) = (14, 40) = 560 elements - This is a small tensor, so we need to be careful about overhead GELU formula: `gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))` For a fused kernel approach: - Each thread could compute one output element (one (b, d) position) - The thread would loop over the R dimension, applying GELU and accumulating Let me write this kernel: ```python import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.cpp_extension import load_inline # GELU function: gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) gelu_cuda = load_inline( name='gelu_sum_cuda', cpp_sources=''' #include <torch/extension.h> torch::Tensor gelu_sum_cuda(torch::Tensor x); torch::Tensor gelu_sum(torch::Tensor x) { return gelu_sum_cuda(x); } ''', cuda_sources=''' #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <cmath> __device__ float gelu_device(float x) { // gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) constexpr float sqrt_2_over_pi = 0.7978845608028654f; // sqrt(2/pi) constexpr float coeff = 0.044715f; float x3 = x * x * x; float inner = sqrt_2_over_pi * (x + coeff * x3); float cdf = 0.5f * (1.0f + tanhf(inner)); return x * cdf; } __global__ void gelu_sum_kernel(const float* __restrict__ x, float* __restrict__ out, int R, int B, int D) { int bd = blockIdx.x * blockDim.x + threadIdx.x; int bd_total = B * D; if (bd < bd_total) { int b = bd / D; int d = bd % D; float sum = 0.0f; for (int r = 0; r < R; r++) { int idx = r * B * D + b * D + d; sum += gelu_device(x[idx]); } out[b * D + d] = sum; } } torch::Tensor gelu_sum_cuda(torch::Tensor x) { // x has shape (R, B, D) int R = x.size(0); int B = x.size(1); int D = x.size(2); auto out = torch::zeros({B, D}, x.options()); int bd_total = B * D; int threads = 256; int blocks = (bd_total + threads - 1) / threads; gelu_sum_kernel<<<blocks, threads>>>( x.data_ptr<float>(), out.data_ptr<float>(), R, B, D ); return out; } ''', functions=['gelu_sum'], verbose=True ) class ModelNew(nn.Module): def __init__(self): super().__init__() def forward(self, x: torch.Tensor) -> torch.Tensor: return gelu_cuda.gelu_sum(x) ``` Wait, I need to be more careful about the GELU implementation. Let me check the exact PyTorch GELU formula. PyTorch's F.gelu uses the "tanh" approximation by default: `gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))` Actually, let me check if PyTorch uses the exact or approximate form. The `F.gelu` function has an optional `approximate` parameter. By default it's `'none'` which uses the exact GELU: `x * Φ(x)` where Φ is the CDF of the standard normal distribution. But there's also `approximate='tanh'` which uses the tanh approximation. Let me use the exact formula using `erf`: `gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2)))` Actually, let me just use the approximate tanh version to match the default behavior in PyTorch. Let me double check... According to PyTorch docs: - `torch.nn.functional.gelu(input, approximate='none')` - When `approximate='none'`, uses `x * 0.5 * (1 + erf(x / sqrt(2)))` - When `approximate='tanh'`, uses `0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))` So the default is the exact version with erf. Let me use that: ```cpp __device__ float gelu_device(float x) { // gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2))) constexpr float sqrt_2 = 1.41421356237f; // sqrt(2) return x * 0.5f * (1.0f + erff(x / sqrt_2)); } ``` Actually, I think using the tanh approximation might be more numerically stable and faster. Let me check the tolerance requirements: fp32 uses atol=rtol=1e-4. Given the small sizes involved (R=4, B=14, D=40), I should try to optimize for launch overhead. Let me think about the kernel design: 1. Total output elements: B * D = 14 * 40 = 560 2. Each output element requires reading R=4 elements from input For such a small problem, a single thread block might be efficient: - 560 threads, each computing one output element - Or, we could have fewer threads and each thread handles multiple outputs Actually, with 560 elements, using one block of 256 threads (or even 512 threads, max per block is usually 1024) could work well. Let me also consider shared memory optimization, but given the small problem size, it might not be beneficial. Let me write a clean implementation: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline gelu_sum_extension = load_inline( name='gelu_sum_extension', cpp_sources=''' #include <torch/extension.h> torch::Tensor gelu_sum_forward(torch::Tensor x); TORCH_LIBRARY(gelu_sum, m) { m.def("forward", gelu_sum_forward); } ''', cuda_sources=''' #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <cmath> __device__ __forceinline__ float gelu_activation(float x) { // Exact GELU: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2))) constexpr float sqrt_2 = 1.41421356237f; return x * 0.5f * (1.0f + erff(x / sqrt_2)); } __global__ void gelu_sum_kernel(const float* __restrict__ x, float* __restrict__ out, int R, int B, int D) { const int bd = blockIdx.x * blockDim.x + threadIdx.x; const int bd_total = B * D; if (bd < bd_total) { const int b = bd / D; const int d = bd % D; float sum = 0.0f; for (int r = 0; r < R; r++) { const int idx = r * B * D + bd; sum += gelu_activation(x[idx]); } out[bd] = sum; } } torch::Tensor gelu_sum_forward(torch::Tensor x) { TORCH_CHECK(x.dim() == 3, "Input must be 3D"); TORCH_CHECK(x.scalar_type() == torch::kFloat32, "Input must be float32"); TORCH_CHECK(x.is_cuda(), "Input must be on CUDA"); const int R = x.size(0); const int B = x.size(1); const int D = x.size(2); auto out = torch::empty({B, D}, x.options()); const int bd_total = B * D; const int threads = 256; const int blocks = (bd_total + threads - 1) / threads; gelu_sum_kernel<<<blocks, threads>>>( x.data_ptr<float>(), out.data_ptr<float>(), R, B, D ); return out; } ''', functions=['gelu_sum_forward'], extra_cuda_cflags=['-O3'], verbose=False ) class ModelNew(nn.Module): def __init__(self): super().__init__() def forward(self, x: torch.Tensor) -> torch.Tensor: return gelu_sum_extension.gelu_sum_forward(x) ``` Actually, I realize I'm overcomplicating the cpp_sources. Let me simplify: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline gelu_sum_module = load_inline( name='gelu_sum_module', cpp_sources=''' #include <torch/extension.h> torch::Tensor gelu_sum_cuda(torch::Tensor x); ''', cuda_sources=''' #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> __device__ __forceinline__ float gelu_exact(float x) { // Exact GELU: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2))) return x * 0.5f * (1.0f + erff(x * 0.7071067811865475f)); // x / sqrt(2) = x * 0.707... } __global__ void gelu_sum_kernel(const flo ...[ omitted 310 characters ]... float sum = 0.0f; for (int r = 0; r < R; r++) { sum += gelu_exact(x[r * BD_total + bd]); } out[bd] = sum; } } torch::Tensor gelu_sum_cuda(torch::Tensor x) { const int R = x.size(0); const int B = x.size(1); const int D = x.size(2); const int BD_total = B * D; auto out = torch::empty({B, D}, x.options()); const int threads = 256; const int blocks = (BD_total + threads - 1) / threads; gelu_sum_kernel<<<blocks, threads>>>( x.data_ptr<float>(), out.data_ptr<float>(), R, BD_total ); return out; } ''', functions=['gelu_sum_cuda'], extra_cuda_cflags=['-O3'], verbose=False ) class ModelNew(nn.Module): def __init__(self): super().__init__() def forward(self, x: torch.Tensor) -> torch.Tensor: return gelu_sum_module.gelu_sum_cuda(x) ``` Wait, I need to double-check my understanding of the problem. The reference computes: ```python F.gelu(x).sum(dim=0) ``` where `x` has shape `(R, B, D)`. So this computes GELU for each element, then sums across dimension 0 (the R dimension). The output has shape `(B, D)`. My kernel does exactly that - each thread handles one `(b, d)` position, loops over the R dimension, applies GELU to each element, and accumulates. Let me think about potential optimizations: 1. **Vectorized loads**: Since we're summing across R=4, we could use float4 loads. But the memory layout means R elements are not contiguous in memory, so this won't help. 2. **Shared memory**: Not really beneficial here since we don't reuse data. 3. **Warp-level reduction**: Not applicable since each thread works on independent output elements. For such a small problem, the key is to minimize kernel launch overhead and ensure good memory access patterns. Actually, let me reconsider the memory access pattern. The input tensor `x` has shape `(R, B, D)` with strides `(B*D, D, 1)`. When thread `bd` accesses element at index `r * B * D + bd`, it's accessing memory at stride 1 (for the D dimension) across threads in a warp if we're processing consecutive `bd` values. This should be good for coalescing. Let me also consider using the approximate tanh GELU instead of the exact erf-based one, as it might be faster: ```cpp __device__ __forceinline__ float gelu_tanh(float x) { // tanh approximation of GELU const float sqrt_2_over_pi = 0.7978845608028654f; const float coeff = 0.044715f; float x3 = x * x * x; float inner = sqrt_2_over_pi * (x + coeff * x3); return 0.5f * x * (1.0f + tanhf(inner)); } ``` Actually, let me test both. But for correctness with the reference which uses `F.gelu(x)` (exact version by default), I should use the erf-based one. Let me also add some optimizations: 1. Use `__restrict__` for pointers 2. Use `__forceinline__` for the device function 3. Maybe use `__ldg` for read-only caching Here's my refined implementation: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline gelu_sum_module = load_inline( name='gelu_sum_module', cpp_sources=''' #include <torch/extension.h> torch::Tensor gelu_sum_cuda(torch::Tensor x); ''', cuda_sources=''' #include <torch/extension.h> #include <cuda_runtime.h> __device__ __forceinline__ float gelu_exact(float x) { // Exact GELU: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2))) // sqrt(2) = 1.4142135623730951 // 1/sqrt(2) = 0.7071067811865475 return x * 0.5f * (1.0f + erff(x * 0.7071067811865475f)); } __global__ void gelu_sum_kernel(const float* __restrict__ x, float* __restrict__ out, int R, int BD_total) { const int bd = blockIdx.x * blockDim.x + threadIdx.x; if (bd < BD_total) { float sum = 0.0f; for (int r = 0; r < R; r++) { sum += gelu_exact(__ldg(&x[r * BD_total + bd])); } out[bd] = sum; } } torch::Tensor gelu_sum_cuda(torch::Tensor x) { const int R = x.size(0); const int BD_total = x.size(1) * x.size(2); auto out = torch::empty({x.size(1), x.size(2)}, x.options()); const int threads = 256; const int blocks = (BD_total + threads - 1) / threads; gelu_sum_kernel<<<blocks, threads>>>( x.data_ptr<float>(), out.data_ptr<float>(), R, BD_total ); return out; } ''', functions=['gelu_sum_cuda'], extra_cuda_cflags=['-O3'], verbose=False ) class ModelNew(nn.Module): def __init__(self): super().__init__() def forward(self, x: torch.Tensor) -> torch.Tensor: return gelu_sum_module.gelu_sum_cuda(x) ``` Actually, I realized I should probably not use `__ldg` since modern CUDA compilers automatically cache read-only accesses through the texture cache. Let me also consider an alternative approach: process multiple output elements per thread using stride loops. This could reduce the number of blocks/warps and potentially improve efficiency for small inputs. But actually, for small inputs like this (560 output elements), the overhead of multiple thread blocks might be negligible. Let me just go with the straightforward approach. One more thing: I should make sure the kernel handles the case where the input might not be contiguous. PyTorch tensors might have non-contiguous strides. But typically for standard tensors, they're contiguous. Let me add a contiguous check just in case: Actually, let me keep it simple and assume contiguous input, which is the common case. The reference implementation doesn't handle non-contiguous inputs specially either. Let me finalize my implementation: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline # Compile the CUDA extension at module load time gelu_sum_module = load_inline( name='gelu_sum_module', cpp_sources=''' #include <torch/extension.h> torch::Tensor gelu_sum_cuda(torch::Tensor x); ''', cuda_sources=''' #include <torch/extension.h> #include <cuda_runtime.h> // Exact GELU activation: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2))) __device__ __forceinline__ float gelu_exact(float x) { return x * 0.5f * (1.0f + erff(x * 0.7071067811865475f)); } __global__ void gelu_sum_kernel(const float* __restrict__ x, float* __restrict__ out, int R, int BD_total) { const int bd = blockIdx.x * blockDim.x + threadIdx.x; if (bd < BD_total) { float sum = 0.0f; for (int r = 0; r < R; ++r) { sum += gelu_exact(x[r * BD_total + bd]); } out[bd] = sum; } } torch::Tensor gelu_sum_cuda(torch::Tensor x) { const int R = x.size(0); const int B = x.size(1); const int D = x.size(2); const int BD_total = B * D; auto out = torch::empty({B, D}, x.options()); const int threads = 256; const int blocks = (BD_total + threads - 1) / threads; gelu_sum_kernel<<<blocks, threads>>>( x.data_ptr<float>(), out.data_ptr<float>(), R, BD_total ); return out; } ''', functions=['gelu_sum_cuda'], extra_cuda_cflags=['-O3'], verbose=False ) class ModelNew(nn.Module): def __init__(self): super().__init__() def forward(self, x: torch.Tensor) -> torch.Tensor: return gelu_sum_module.gelu_sum_cuda(x) ``` This should work. Let me review it once more: 1. The kernel computes `gelu(x).sum(dim=0)` for a 3D tensor of shape `(R, B, D)`. 2. Each thread handles one output element at position `(b, d)`. 3. For each output element, the thread loops over the R dimension, applies GELU to each element, and accumulates. 4. The GELU function uses the exact formula with `erf`, matching PyTorch's default `F.gelu`. I think this is correct and should be faster than the PyTorch reference due to kernel fusion (applying GELU and sum in a single kernel vs two separate operations). Let me submit this.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA extension at module load time\ngelu_sum_module = load_inline(\n name='gelu_sum_module',\n cpp_sources='''\n#include <torch/extension.h>\ntorch::Tensor gelu_sum_cuda(torch::Tensor x);\n''',\n cuda_sources='''\n#include <torch/extension.h>\n#include <cuda_runtime.h>\n\n// Exact GELU activation: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2)))\n__device__ __forceinline__ float gelu_exact(float x) {\n return x * 0.5f * (1.0f + erff(x * 0.7071067811865475f));\n}\n\n__global__ void gelu_sum_kernel(const float* __restrict__ x, \n float* __restrict__ out, \n int R, int BD_total) {\n const int bd = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (bd < BD_total) {\n float sum = 0.0f;\n for (int r = 0; r < R; ++r) {\n sum += gelu_exact(x[r * BD_total + bd]);\n }\n out[bd] = sum;\n }\n}\n\ntorch::Tensor gelu_sum_cuda(torch::Tensor x) {\n const int R = x.size(0);\n const int B = x.size(1);\n const int D = x.size(2);\n const int BD_total = B * D;\n \n auto out = torch::empty({B, D}, x.options());\n \n const int threads = 256;\n const int blocks = (BD_total + threads - 1) / threads;\n \n gelu_sum_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(), out.data_ptr<float>(), R, BD_total\n );\n \n return out;\n}\n''',\n functions=['gelu_sum_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return gelu_sum_module.gelu_sum_cuda(x)" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.01 μs Runtime stats: mean=0.01μs median=?μs std=0.00μs Numerical precision: max_abs_err=4.77e-07 mean_abs_err=2.90e-08 max_rel_err=2.91e-05 Memory: 33.01 MB peak (ref: 33.02 MB, ratio: 0.9997x) Kernel launches: 1 (ref: 3, fusion ratio: 3.0) SOL score: 0.0013
final submitted kernel (63 lines, 1,715 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

# Compile the CUDA extension at module load time
gelu_sum_module = load_inline(
    name='gelu_sum_module',
    cpp_sources='''
#include <torch/extension.h>
torch::Tensor gelu_sum_cuda(torch::Tensor x);
''',
    cuda_sources='''
#include <torch/extension.h>
#include <cuda_runtime.h>

// Exact GELU activation: gelu(x) = x * 0.5 * (1 + erf(x / sqrt(2)))
__device__ __forceinline__ float gelu_exact(float x) {
    return x * 0.5f * (1.0f + erff(x * 0.7071067811865475f));
}

__global__ void gelu_sum_kernel(const float* __restrict__ x, 
                                 float* __restrict__ out, 
                                 int R, int BD_total) {
    const int bd = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (bd < BD_total) {
        float sum = 0.0f;
        for (int r = 0; r < R; ++r) {
            sum += gelu_exact(x[r * BD_total + bd]);
        }
        out[bd] = sum;
    }
}

torch::Tensor gelu_sum_cuda(torch::Tensor x) {
    const int R = x.size(0);
    const int B = x.size(1);
    const int D = x.size(2);
    const int BD_total = B * D;
    
    auto out = torch::empty({B, D}, x.options());
    
    const int threads = 256;
    const int blocks = (BD_total + threads - 1) / threads;
    
    gelu_sum_kernel<<<blocks, threads>>>(
        x.data_ptr<float>(), out.data_ptr<float>(), R, BD_total
    );
    
    return out;
}
''',
    functions=['gelu_sum_cuda'],
    extra_cuda_cflags=['-O3'],
    verbose=False
)

class ModelNew(nn.Module):
    def __init__(self):
        super().__init__()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return gelu_sum_module.gelu_sum_cuda(x)