FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 23_virtual_all_reduce_mean.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
4716
wall clock
640.4s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
0.01 μs
ref runtime
0.01 μs
speedup
0.90x
started
2026-05-11T08:56:58Z
timingkernelbench.timing
kernel runtime
0.01 μs
ref runtime
0.01 μs
speedup
0.90x
kernel mean / median / std
0.01 / — / 3.07e-04 μs
ref mean / median / std
0.01 / — / 1.25e-04 μs
numerical precisioneval_kernel_against_ref
max abs error
0.00
mean abs error
0.00
max rel error
0.00
mean rel error
0.00
gpu memoryextended_metrics.measure_memory
kernel peak
33.01 MB
ref peak
33.01 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
2
fusion ratio (ref / kernel)
2.00x
total cuda time
37 μ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
3.51 GB/s
dram util %
0.1%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
12.3%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
98.0%
ld sectors / req
4.00
st sectors / req
—
regs / thread
26
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
2771.0%
short_scoreboard
123.7%
reference PyTorch source (the prompt the agent sees)
"""
Virtual ranks: input packs R per-rank tensors along dim 0. Output is the mean over ranks (B, D).

No distributed runtime required; the reference always reduces along dim 0.
"""

import torch
import torch.nn as nn

from kernelbench.distributed_collectives import default_device

R = 4
B = 16
D = 48


class Model(nn.Module):
    """x shape (R, B, D) -> mean over R -> (B, D)."""

    def __init__(self):
        super().__init__()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return x.mean(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) · 640.4s turn · 44.0s LLM · tokens in=1,008 · out=3,708 · total=4,716 · 17,726 chars (click to expand)
turn 0640.4s turn · 44.0s LLM · 596.4s tools · tokens in=1,008 · out=3,708 · total=4,716 · FINAL
reasoning
The task is to replace a PyTorch model that computes the mean along dimension 0 of a tensor with shape (R, B, D) where R=4, B=16, D=48. The output should be (B, D). Let me think about how to optimize this: 1. The operation is `x.mean(dim=0)` which computes the average over the first dimension R. 2. This is essentially a reduction operation - summing along dimension 0 and dividing by R. For a custom CUDA kernel, I need to: 1. Each output element at position (b, d) is the mean of all R elements at that position. 2. So for each (b, d) pair, we sum over r in [0, R-1] and divide by R. Given R=4 is small, this is actually a very lightweight operation. Each thread could handle one output element (b, d), reading 4 values from the input and computing the mean. Let me design the kernel: - Grid dimension: B x D blocks or threads - Each thread computes one output element - For thread (b, d), read x[0][b][d], x[1][b][d], x[2][b][d], x[3][b][d], sum them, divide by 4 With B=16 and D=48, we have 16*48 = 768 output elements. This is quite small, so we might want to use a single block with enough threads. Actually, let me think more carefully: - Input: (R, B, D) = (4, 16, 48) = 3072 elements - Output: (B, D) = (16, 48) = 768 elements For such a small problem, overhead might dominate. Let me design a simple kernel: Option 1: One thread per output element - Grid: (B, D) = (16, 48) - Block size: could be (16, 48) but that's 768 threads per block, which is fine - Each thread handles a single (b, d) coordinate, iterating through the R dimension to compute the mean I can launch a 1D grid with 768 threads, where each thread computes one output element by reading R values from consecutive memory locations. The linear index maps to (b, d) coordinates, allowing each thread to accumulate the sum across the R dimension and divide by R. For better memory throughput, I should leverage coalesced reads where threads in a warp access adjacent addresses. Since x is laid out as [R][B][D], threads processing consecutive d values will naturally hit adjacent memory locations, improving memory access patterns. The core implementation is straightforward: each thread iterates through R values, accumulating them into a sum, then divides by R to compute the mean. I'll need to handle the edge case where R is zero to avoid division issues. For the output tensor, I'll ensure contiguous memory allocation and return it properly. I'm considering whether a shared memory reduction would actually help here given that R is quite small—probably not worth the complexity compared to the direct sequential sum approach. The current kernel design should handle the computation efficiently enough. Let me work through the memory layout more carefully. With row-major ordering, x[r,b,d] sits at offset r*B*D + b*D + d. For a single (b,d) position across all R values, the memory stride between consecutive r values is B*D elements apart. That means when iterating through the R dimension for a fixed (b,d), we're making non-sequential accesses spaced far apart in memory. For thread t with d < D and b < B, we access x[r,b,d] where the stride between consecutive r values is B*D. This actually works well for coalescing since threads with adjacent d values access adjacent memory locations—each thread sums across the R dimension while maintaining coalesced reads across the warp. The indexing should handle non-contiguous inputs through strides. Rather than assuming a fixed layout, I can use x.stride(0), x.stride(1), x.stride(2) to compute element locations regardless of how the tensor is arranged in memory. This keeps the kernel flexible for transposed or otherwise non-contiguous inputs while still achieving good memory access patterns. For the output tensor, I allocate a contiguous tensor with the correct shape and then have each thread compute one output element by iterating through the R dimension. The key optimization is ensuring threads in the same warp access consecutive memory locations—since threads with adjacent threadIdx.x values process consecutive d indices, and each iteration accesses elements with matching d across different r values, the memory accesses coalesce nicely. I should verify the tensor dimensions are correct before proceeding, and handle the edge case where R equals zero. With B=16 and D=48, I have 768 total threads, which fits well within typical CUDA block constraints. The current kernel design might underutilize the GPU since each thread only performs 4 memory reads and minimal computation. I could explore using shared memory to improve memory access patterns, or leverage vectorized loads if the input tensor is contiguous—loading all R values at once could reduce the number of load instructions significantly. However, R=4 doesn't align well with typical vector widths like float4 (4 floats), since each thread needs to read values scattered across memory locations rather than consecutive ones. The memory layout means a thread reading all values for a single output element would access addresses spaced B*D apart, making vectorized loads impractical. The straightforward approach of having each thread iterate over R values and accumulate might actually be the most efficient here. For the kernel launch configuration, with B=16 and D=48 giving 768 threads, a single block should suffice since this stays well within the 1024-thread limit. I could launch with dim3(B, D) grid dimensions and block size of 1, but using a 1D configuration with 768 threads in one block is simpler to implement. The block size calculation (768 threads with 4 bytes per float for the output tensor) needs to be verified for register and shared memory constraints. Actually, the more standard approach would be using a 2D block structure where threads map directly to output coordinates. Setting blockSize to 16×16 = 256 threads per block, then calculating gridSize as (B + 15)/16 by (D + 15)/16 gives the right block count. This maps each thread to a specific output element based on its block and thread indices, checking boundaries to avoid out-of-bounds writes. However, for this problem size with B=16 and D=48, a 2D configuration creates an unbalanced distribution—16×16 blocks would give uneven coverage across the output dimensions. A 1D launch with 768 threads in a single block handles the entire output more directly, mapping each thread to a linear index and converting to coordinates for the output tensor. This avoids the complexity of multi-block coordination for such a small kernel. For larger problems though, a 2D block and grid structure scales better. With 256 threads per block (16×16), the grid dimensions adjust to cover the full output size. Each thread computes its global coordinates from block and thread indices, then processes its assigned output element. The kernel iterates through the R dimension, accumulating values from the input tensor at corresponding coordinates, then normalizes by R. Boundary checking ensures threads don't access invalid memory locations. The host function validates input dimensions, extracts the size parameters, and launches the kernel with a grid sized to cover all output elements. There's a potential issue with the current approach that needs reconsideration—the indexing logic might not correctly map threads to their assigned output positions across the full problem domain. should be divisible by blockSize.x and D should be divisible by blockSize.y. If not, some threads will be idle but the boundary check handles it. For B=16, D=48, blockSize=(16, 16), the grid becomes (1, 3) with 48 blocks total, which is fine. I should write a kernel implementation. Vectorization could help, though the input is x[r, b, d] where r is outermost. Vector loads work when accessing consecutive d values, but consecutive threads in a warp access different (b, d) pairs with consecutive d values, so threads 0-31 in a warp access x[0,b0,0] through x[0,b0,31] consecutively. This gets complicated, so starting simple makes sense. I'll implement a straightforward kernel first. Looking at the actual tensor dimensions (R, B, D) = (4, 16, 48), these are small sizes, so a single block kernel could handle all outputs with one thread per element. For this specific problem size with 768 threads needed, a single block of 1024 threads works well since max threads per block is 1024. Each thread computes one output element by iterating through the R dimension. For each output coordinate (b, d), a thread accumulates values across all R inputs and divides by R to get the mean. The kernel handles boundary checking for cases where the total output size doesn't perfectly match the thread count. The launch configuration determines grid and block dimensions based on whether the output fits in a single block. For larger outputs, it uses a 2D block structure with 256 threads per block and calculates the grid dimensions accordingly, while small outputs use a single block with enough threads to cover all elements. However, there's a critical issue with the indexing scheme. The 1D block approach uses `blockIdx.x * blockDim.x + threadIdx.x` to compute the thread index, which works for single-block launches but fails when multiple blocks are needed. With a 1D grid and 1D blocks, this indexing would cause blocks to compute identical coordinates rather than distributing across the output space. The boundary check would mask the problem by making extra threads idle, but this results in incomplete computation rather than proper parallelization. For a 2D output, I should use a 2D grid with 2D blocks instead. Setting blockSize to (16, 16) threads and calculating gridSize based on ceiling division of B and D ensures proper coverage of the output domain. This approach guarantees that each thread maps to a unique output element without overlap. However, I'm considering whether a single 1D block would be simpler for this problem. Since B*D equals 768 and fits within a single block, I could launch with a 1D block and have each thread compute its corresponding output index directly from the thread ID. The boundary condition check handles cases where threads exceed the output size, and all 768 threads needed for this problem would be active within a single block. I need to reconsider the general case though. For a 2D output with dimensions B and D, mapping a linear thread index to coordinates requires `b = idx // D` and `d = idx % D`. With multiple blocks, the thread index becomes `blockIdx.x * blockDim.x + threadIdx.x`, which handles larger outputs. So the code should work universally as long as gridSize and blockSize are properly sized for the output dimensions. For a (16, 48) output, I need at least 768 threads total. Using blockSize=1024 with gridSize=1 gives me that. But if I use blockSize=256 with gridSize=4, I'd need to verify the indexing still works correctly—the formula `idx = blockIdx.x * blockDim.x + threadIdx.x` should distribute threads across blocks properly. In the second configuration, threads are split across 4 blocks of 256 threads each, so threads 0-255 in block 0 map to output indices 0-255, threads 0-255 in block 1 map to 256-511, and so on. This works correctly with the 1D indexing scheme. The boundary check then skips any threads that exceed the output size. I should also consider a 2D version for potentially better memory access patterns, but the 1D approach handles all cases. For the 2D kernel, I'd use a block size of (16, 16) and calculate grid dimensions based on the output size. Each thread computes its global coordinates from block and thread indices, then accumulates values across the R dimension to compute the mean, writing the result only if within bounds. I could also try a vectorized version for contiguous inputs where I cast the input pointer to float4 and load all R values at once, but this requires R to match the vector width and the memory layout to be compatible, which adds complexity without clear benefit for most cases. I'll go with a straightforward kernel implementation that handles the accumulation across the R dimension and normalizes by the count. The forward pass checks input dimensions, creates the output tensor, extracts the size parameters, and launches the kernel with an appropriate thread configuration before returning the result.
call → submit_kernel
callsubmit_kernel { "kernel_code": "\"\"\"\nVirtual ranks: input packs R per-rank tensors along dim 0. Output is the mean over ranks (B, D).\n\nCustom CUDA kernel implementation for computing mean along dimension 0.\n\"\"\"\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel at module load time\ncuda_source = r\"\"\"\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void mean_dim0_kernel(\n const float* __restrict__ x,\n float* __restrict__ out,\n const int R,\n const int B,\n const int D,\n const long long stride_r,\n const long long stride_b,\n const long long stride_d\n) {\n // 2D indexing for output element (b, d)\n int b = blockIdx.y * blockDim.y + threadIdx.y;\n int d = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (b < B && d < D) {\n // Compute mean across dimension 0 (R values)\n float sum = 0.0f;\n \n #pragma unroll\n for (int r = 0; r < R; ++r) {\n sum += x[r * stride_r + b * stride_b + d * stride_d];\n }\n \n out[b * D + d] = sum / (float)R;\n }\n}\n\n// Optimized kernel for contiguous input with small, known R\n__global__ void mean_dim0_contiguous_kernel(\n const float* __restrict__ x,\n float* __restrict__ out,\n const int R,\n const int B,\n const int D\n) {\n // 2D indexing for output element (b, d)\n int b = blockIdx.y * blockDim.y + threadIdx.y;\n int d = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (b < B && d < D) {\n // Input is contiguous with layout [R][B][D]\n // For fixed (b, d), values across R are spaced B*D apart\n const float* base_ptr = x + b * D + d;\n const long long stride = static_cast<long long>(B) * D;\n \n float sum = 0.0f;\n \n // Unroll for small R values\n #pragma unroll 8\n for (int r = 0; r < R; ++r) {\n sum += base_ptr[r * stride];\n }\n \n out[b * D + d] = sum / (float)R;\n }\n}\n\n// Host function that launches the kernel\ntorch::Tensor mean_dim0_cuda(torch::Tensor x) {\n TORCH_CHECK(x.dim() == 3, \"Input must be 3D tensor\");\n \n const int R = x.size(0);\n const int B = x.size(1);\n const int D = x.size(2);\n \n TORCH_CHECK(R > 0, \"R must be positive\");\n \n // Create contiguous output tensor\n auto out = torch::empty({B, D}, x.options());\n \n // Use optimized kernel for contiguous input\n if (x.is_contiguous()) {\n dim3 blockSize(16, 16);\n dim3 gridSize((D + blockSize.x - 1) / blockSize.x,\n (B + blockSize.y - 1) / blockSize.y);\n \n mean_dim0_contiguous_kernel<<<gridSize, blockSize>>>(\n x.data_ptr<float>(),\n out.data_ptr<float>(),\n R, B, D\n );\n } else {\n // Generic kernel for non-contiguous input\n dim3 blockSize(16, 16);\n dim3 gridSize((D + blockSize.x - 1) / blockSize.x,\n (B + blockSize.y - 1) / blockSize.y);\n \n mean_dim0_kernel<<<gridSize, blockSize>>>(\n x.data_ptr<float>(),\n out.data_ptr<float>(),\n R, B, D,\n x.stride(0),\n x.stride(1),\n x.stride(2)\n );\n }\n \n return out;\n}\n\"\"\"\n\n# Load the CUDA extension\nmean_extension = load_inline(\n name='mean_dim0_extension',\n cpp_sources=[\n \"torch::Tensor mean_dim0_cuda(torch::Tensor x);\"\n ],\n cuda_sources=cuda_source,\n functions=['mean_dim0_cuda'],\n with_cuda=True,\n extra_cuda_cflags=['-O3', '-use_fast_math'],\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"x shape (R, B, D) -> mean over R -> (B, D).\n \n Uses custom CUDA kernel for computing mean along dimension 0.\n \"\"\"\n\n def __init__(self):\n super().__init__()\n\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return mean_extension.mean_dim0_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=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00 Memory: 33.01 MB peak (ref: 33.01 MB, ratio: 1.0x) Kernel launches: 1 (ref: 2, fusion ratio: 2.0) SOL score: 0.001
final submitted kernel (140 lines, 3,854 chars) — full source, untruncated
"""
Virtual ranks: input packs R per-rank tensors along dim 0. Output is the mean over ranks (B, D).

Custom CUDA kernel implementation for computing mean along dimension 0.
"""

import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

# Compile the CUDA kernel at module load time
cuda_source = r"""
#include <cuda.h>
#include <cuda_runtime.h>

__global__ void mean_dim0_kernel(
    const float* __restrict__ x,
    float* __restrict__ out,
    const int R,
    const int B,
    const int D,
    const long long stride_r,
    const long long stride_b,
    const long long stride_d
) {
    // 2D indexing for output element (b, d)
    int b = blockIdx.y * blockDim.y + threadIdx.y;
    int d = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (b < B && d < D) {
        // Compute mean across dimension 0 (R values)
        float sum = 0.0f;
        
        #pragma unroll
        for (int r = 0; r < R; ++r) {
            sum += x[r * stride_r + b * stride_b + d * stride_d];
        }
        
        out[b * D + d] = sum / (float)R;
    }
}

// Optimized kernel for contiguous input with small, known R
__global__ void mean_dim0_contiguous_kernel(
    const float* __restrict__ x,
    float* __restrict__ out,
    const int R,
    const int B,
    const int D
) {
    // 2D indexing for output element (b, d)
    int b = blockIdx.y * blockDim.y + threadIdx.y;
    int d = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (b < B && d < D) {
        // Input is contiguous with layout [R][B][D]
        // For fixed (b, d), values across R are spaced B*D apart
        const float* base_ptr = x + b * D + d;
        const long long stride = static_cast<long long>(B) * D;
        
        float sum = 0.0f;
        
        // Unroll for small R values
        #pragma unroll 8
        for (int r = 0; r < R; ++r) {
            sum += base_ptr[r * stride];
        }
        
        out[b * D + d] = sum / (float)R;
    }
}

// Host function that launches the kernel
torch::Tensor mean_dim0_cuda(torch::Tensor x) {
    TORCH_CHECK(x.dim() == 3, "Input must be 3D tensor");
    
    const int R = x.size(0);
    const int B = x.size(1);
    const int D = x.size(2);
    
    TORCH_CHECK(R > 0, "R must be positive");
    
    // Create contiguous output tensor
    auto out = torch::empty({B, D}, x.options());
    
    // Use optimized kernel for contiguous input
    if (x.is_contiguous()) {
        dim3 blockSize(16, 16);
        dim3 gridSize((D + blockSize.x - 1) / blockSize.x,
                      (B + blockSize.y - 1) / blockSize.y);
        
        mean_dim0_contiguous_kernel<<<gridSize, blockSize>>>(
            x.data_ptr<float>(),
            out.data_ptr<float>(),
            R, B, D
        );
    } else {
        // Generic kernel for non-contiguous input
        dim3 blockSize(16, 16);
        dim3 gridSize((D + blockSize.x - 1) / blockSize.x,
                      (B + blockSize.y - 1) / blockSize.y);
        
        mean_dim0_kernel<<<gridSize, blockSize>>>(
            x.data_ptr<float>(),
            out.data_ptr<float>(),
            R, B, D,
            x.stride(0),
            x.stride(1),
            x.stride(2)
        );
    }
    
    return out;
}
"""

# Load the CUDA extension
mean_extension = load_inline(
    name='mean_dim0_extension',
    cpp_sources=[
        "torch::Tensor mean_dim0_cuda(torch::Tensor x);"
    ],
    cuda_sources=cuda_source,
    functions=['mean_dim0_cuda'],
    with_cuda=True,
    extra_cuda_cflags=['-O3', '-use_fast_math'],
)


class ModelNew(nn.Module):
    """x shape (R, B, D) -> mean over R -> (B, D).
    
    Uses custom CUDA kernel for computing mean along dimension 0.
    """

    def __init__(self):
        super().__init__()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return mean_extension.mean_dim0_cuda(x)