FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 27_permutation_all_to_all.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
3689
wall clock
821.1s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
0.01 μs
ref runtime
0.03 μs
speedup
4.97x
started
2026-05-11T09:56:55Z
timingkernelbench.timing
kernel runtime
0.01 μs
ref runtime
0.03 μs
speedup
4.97x
kernel mean / median / std
0.01 / — / 1.08e-04 μs
ref mean / median / std
0.03 / — / 0.00 μ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.00 MB
ref peak
33.00 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
2
ref launches
8
fusion ratio (ref / kernel)
4.00x
total cuda time
56 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
4.000e-04
dram util %
0.0%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
1.48 GB/s
dram util %
0.0%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
10.3%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
99.3%
ld sectors / req
6.04
st sectors / req
—
regs / thread
16
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
3154.3%
short_scoreboard
1837.8%
reference PyTorch source (the prompt the agent sees)
"""
Fixed permutation along the feature axis: y[b, j] = x[b, perm[j]].
"""

import torch
import torch.nn as nn

from kernelbench.distributed_collectives import default_device

B = 10
N = 36

_g = torch.Generator()
_g.manual_seed(10)
PERM = torch.randperm(N, generator=_g)


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

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return x[:, PERM.to(device=x.device)]


def get_inputs():
    dev = default_device()
    return [torch.randn(popcorn_pri.jitter_int(B), N, device=dev)]


def get_init_inputs():
    return []
turn 0 · FINAL · 1 tool call(s) · 821.1s turn · 32.1s LLM · tokens in=987 · out=2,702 · total=3,689 · 13,079 chars (click to expand)
turn 0821.1s turn · 32.1s LLM · 789.0s tools · tokens in=987 · out=2,702 · total=3,689 · FINAL
reasoning
Let me analyze this task: 1. The reference implementation does a fixed permutation along the feature axis: `y[b, j] = x[b, perm[j]]` 2. This is a simple operation that selects columns from the input tensor based on a permutation. The key insight is that this is essentially an index selection operation. The reference uses `x[:, PERM.to(device=x.device)]` which creates a new tensor by gathering columns. For a custom CUDA kernel, I need to: 1. Copy elements from `x[b, perm[j]]` to `y[b, j]` for all batch elements b and all output indices j Let me think about the optimal kernel design: - Input: x of shape (B, N) - Output: y of shape (B, N) - Permutation: PERM of shape (N,) For each element `y[b, j]`, we read from `x[b, perm[j]]`. Since B=10 and N=36, this is a relatively small tensor. But I should write a general kernel. The naive approach: - Each thread handles one output element `(b, j)` - Compute global index, determine b and j - Read `x[b, perm[j]]` and write to `y[b, j]` Let me think about memory access patterns: - Writing to y is contiguous in memory if threads are organized properly - Reading from x uses perm[j] as the column index, which is a scattered read Actually, for such small sizes, coalescing might not be as critical. But let me design a good kernel anyway. A simple kernel would be: ```cuda __global__ void permute_kernel(const float* __restrict__ x, float* __restrict__ y, const int* __restrict__ perm, int B, int N) { int idx = blockIdx.x * blockDim.x + threadIdx.x; int total = B * N; if (idx < total) { int b = idx / N; int j = idx % N; int src_j = perm[j]; y[b * N + j] = x[b * N + src_j]; } } ``` For optimization, I could consider using shared memory to cache the permutation array, but with N=36 elements, it fits easily in shared memory. However, the permutation is small and could even be loaded into registers or constant memory for efficiency. The core operation is straightforward: for each batch element and output column, read the corresponding input column using the permutation mapping, then write to the output tensor. Here's the kernel implementation: ```cuda __global__ void permute_kernel(const float* __restrict__ x, float* __restrict__ y, const int* __restrict__ perm, int B, int N) { int idx = blockIdx.x * blockDim.x + threadIdx.x; int total = B * N; if (idx < total) { int b = idx / N; int j = idx % N; int src_j = perm[j]; y[b * N + j] = x[b * N + src_j]; } } ``` Now I need to construct the complete solution. The main challenge is that `PERM` gets defined at the module level using a specific generator seed, and I need to access it from my kernel. Looking at the reference, PERM is constructed with a fixed seed to ensure determinism, so my implementation must use this exact same permutation array. For the custom kernel approach, I'll compile a CUDA kernel via `torch.utils.cpp_extension.load_inline`, then call it from ModelNew's forward method by passing in the PERM tensor directly as an argument. The kernel itself takes the input tensor, output tensor, permutation indices, batch size, and feature dimension. Each thread computes its position in the output, determines the source index using the permutation, and copies the appropriate element. I need to ensure the permutation tensor is moved to the correct device before passing it to the kernel. For the Python wrapper, I should handle both 1D and 2D inputs—if the input is 1D, the permutation is applied directly; if it's 2D with shape (B, N), I reshape it to apply the permutation across all elements, then restore the original shape. The kernel is launched with enough threads to cover all elements, using a block size of 256. There's a discrepancy I need to clarify: the kernel assumes row-major ordering where batch elements vary slowly and features vary quickly, but I should verify this matches the actual tensor layout. For a (B, N) shaped tensor, each batch is contiguous in memory with N elements per row, so b * N + j correctly accesses element j within batch b. The permutation logic in the reference swaps columns according to PERM[j], moving x[b, i] to y[b, j] where i = PERM[j]. This reverses the mapping I initially had—PERM[j] gives the source column index, not the destination. So the kernel should read from x[b, perm[j]] and write to y[b, j]. ```cuda int src_j = perm[j]; y[b * N + j] = x[b * N + src_j]; ``` This should work correctly. For input shape [B, N] = [10, 36], I need to flatten to [360] for the kernel, but the linear indexing formula `idx = b * N + j` already handles this, so I can keep the 2D shape as-is and let the kernel work with the linear indices directly. The kernel should handle arbitrary shapes by accepting total elements and stride. For a 2D tensor, I compute which row and column each thread handles based on the global thread index. Let me start simple with the basic 2D case first, then generalize if needed. The permutation kernel takes the input tensor, output tensor, permutation indices, and dimensions. Each thread calculates its batch and column indices, then applies the permutation mapping to locate the source column and copy the value. I launch with enough threads to cover all elements, using a standard block size of 256. In the forward pass, I need to ensure the permutation tensor matches the input device, then call the kernel with the appropriate grid and block dimensions. There's also a consideration for the PERM tensor defined externally—I should reference it directly from the global scope rather than recreating it. The forward method needs to handle the PERM tensor appropriately. Since PERM is defined globally at the module level, I should access it directly within the forward pass. Let me restructure the kernel launch to properly manage this global permutation tensor, ensuring it's moved to the correct device before being passed to the CUDA operation. from the device tensor. Since PERM is already 1D, I can cache the device-specific version after the first conversion rather than checking the device match every time. I'll track the current device in `self._perm_device` and update it only when needed. During the forward pass, if the input device differs from the cached permutation device, I move PERM to the correct device and update the cache. Then I launch the permutation kernel with the input, output buffer, permutation indices, batch size, and sequence length. The kernel itself handles the actual permutation operation—it uses CUDA to efficiently reorder elements according to the permutation tensor across batches. I need to account for 0-dimensional batches, which would indicate a 1D input tensor. If input.dim() == 1, then B is effectively 0 and N equals the total number of elements. Rather than relying on external globals, I'll generate the permutation directly using the fixed seed 10, ensuring it matches what the reference expects. This approach sidesteps potential scope issues with PERM and makes the initialization self-contained. For a 1D tensor with shape (N,), I should handle it by treating B as 1 in the kernel, or make the kernel more flexible to accommodate this case. The cleanest approach is to keep the kernel simple for the (B, N) case and ensure the permutation is generated consistently with the same seed. The reference uses `torch.randperm(N, generator=_g)` with a CPU generator seeded to 10, then moves the result to the GPU. Since the randperm operation happens on CPU before the device transfer, I need to replicate this exact sequence to match the reference behavior. My approach generates the permutation on CPU with the same seed, then moves it to the target device when needed. I also need to ensure the output tensor is contiguous for the kernel operation, handling both 2D and 1D cases appropriately. For the output allocation, I check if the input is already contiguous—if not, I create a contiguous copy. Then I get the batch and column dimensions, ensure the permutation is on the correct device, and launch the kernel with appropriate block and grid dimensions before returning the result. The kernel itself computes the batch and column indices from each thread's global index, then applies the permutation by mapping each output column to its corresponding input column. -d % N = 0, which means d = N. This confirms the formula works correctly. I'll finalize the solution with this indexing approach. However, I'm concerned about whether the global PERM variable will be properly resolved when the module loads. To address this, I could generate PERM internally within ModelNew, but the reference code defines PERM at the module level for sharing between the model and input generation. When my code gets executed, the globals dictionary should already contain PERM, so I'll attempt to access it directly. If that doesn't work, I'll need a fallback approach. Let me build the complete Python file with the CUDA kernel integrated properly.
call → submit_kernel
callsubmit_kernel { "kernel_code": "\"\"\"\nFixed permutation along the feature axis: y[b, j] = x[b, perm[j]].\nCustom CUDA kernel implementation.\n\"\"\"\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Generate permutation with same seed as reference\n_perm_generator = torch.Generator()\n_perm_generator.manual_seed(10)\nPERM = torch.randperm(36, generator=_perm_generator)\n\n# Compile CUDA kernel at module load time\n_cuda_source = \"\"\"\n__global__ void permute_kernel(const float* __restrict__ x, float* __restrict__ y,\n const int* __restrict__ perm, int B, int N) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * N;\n \n if (idx < total) {\n int j = idx % N;\n int b = idx / N;\n int src_j = perm[j];\n y[idx] = x[b * N + src_j];\n }\n}\n\ntorch::Tensor permute_cuda(torch::Tensor x, torch::Tensor perm) {\n TORCH_CHECK(x.dim() == 2, \"Input must be 2D\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"Input must be float\");\n TORCH_CHECK(perm.scalar_type() == torch::kInt32, \"Perm must be int32\");\n \n int B = x.size(0);\n int N = x.size(1);\n \n auto y = torch::empty_like(x);\n \n int total = B * N;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n permute_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n y.data_ptr<float>(),\n perm.data_ptr<int>(),\n B, N\n );\n \n return y;\n}\n\"\"\"\n\n_cpp_source = \"torch::Tensor permute_cuda(torch::Tensor x, torch::Tensor perm);\"\n\n_permute_module = load_inline(\n name=\"permute_ext\",\n cpp_sources=_cpp_source,\n cuda_sources=_cuda_source,\n functions=[\"permute_cuda\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n self._perm_device = None\n self._perm_int = None\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n # Handle 1D input by unsqueezing\n if x.dim() == 1:\n x_2d = x.unsqueeze(0)\n out_2d = self._forward_2d(x_2d)\n return out_2d.squeeze(0)\n return self._forward_2d(x)\n \n def _forward_2d(self, x: torch.Tensor) -> torch.Tensor:\n B, N = x.shape\n \n # Ensure contiguous input for kernel\n if not x.is_contiguous():\n x = x.contiguous()\n \n # Ensure perm is on correct device and int32\n if self._perm_device != x.device:\n self._perm_int = PERM.to(device=x.device, dtype=torch.int32)\n self._perm_device = x.device\n \n return _permute_module.permute_cuda(x, self._perm_int)" }
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.0 MB peak (ref: 33.0 MB, ratio: 1.0x) Kernel launches: 2 (ref: 8, fusion ratio: 4.0) SOL score: 0.0004
final submitted kernel (92 lines, 2,625 chars) — full source, untruncated
"""
Fixed permutation along the feature axis: y[b, j] = x[b, perm[j]].
Custom CUDA kernel implementation.
"""

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

# Generate permutation with same seed as reference
_perm_generator = torch.Generator()
_perm_generator.manual_seed(10)
PERM = torch.randperm(36, generator=_perm_generator)

# Compile CUDA kernel at module load time
_cuda_source = """
__global__ void permute_kernel(const float* __restrict__ x, float* __restrict__ y,
                               const int* __restrict__ perm, int B, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = B * N;
    
    if (idx < total) {
        int j = idx % N;
        int b = idx / N;
        int src_j = perm[j];
        y[idx] = x[b * N + src_j];
    }
}

torch::Tensor permute_cuda(torch::Tensor x, torch::Tensor perm) {
    TORCH_CHECK(x.dim() == 2, "Input must be 2D");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "Input must be float");
    TORCH_CHECK(perm.scalar_type() == torch::kInt32, "Perm must be int32");
    
    int B = x.size(0);
    int N = x.size(1);
    
    auto y = torch::empty_like(x);
    
    int total = B * N;
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    
    permute_kernel<<<blocks, threads>>>(
        x.data_ptr<float>(),
        y.data_ptr<float>(),
        perm.data_ptr<int>(),
        B, N
    );
    
    return y;
}
"""

_cpp_source = "torch::Tensor permute_cuda(torch::Tensor x, torch::Tensor perm);"

_permute_module = load_inline(
    name="permute_ext",
    cpp_sources=_cpp_source,
    cuda_sources=_cuda_source,
    functions=["permute_cuda"],
    verbose=False,
)


class ModelNew(nn.Module):
    def __init__(self):
        super().__init__()
        self._perm_device = None
        self._perm_int = None
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Handle 1D input by unsqueezing
        if x.dim() == 1:
            x_2d = x.unsqueeze(0)
            out_2d = self._forward_2d(x_2d)
            return out_2d.squeeze(0)
        return self._forward_2d(x)
    
    def _forward_2d(self, x: torch.Tensor) -> torch.Tensor:
        B, N = x.shape
        
        # Ensure contiguous input for kernel
        if not x.is_contiguous():
            x = x.contiguous()
        
        # Ensure perm is on correct device and int32
        if self._perm_device != x.device:
            self._perm_int = PERM.to(device=x.device, dtype=torch.int32)
            self._perm_device = x.device
        
        return _permute_module.permute_cuda(x, self._perm_int)