FW-GLM-5-1 · popcorn · pop_l123_st_gpt
incorrect 5_LayerNorm_GELU_DilatedConv1d_ResAdd.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
4700
wall clock
293.2s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
—
ref runtime
2.78 μs
speedup
—
started
2026-05-11T09:41:03Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
2.78 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
2.78 / — / 0.02 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.359
dram util %
17.4%
compute util %
35.9%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
583.58 GB/s
dram util %
17.4%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
123.3%
bottleneck
compute-bound
dominant pipe %
35.9%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
135.6%
ld sectors / req
6.83
st sectors / req
—
regs / thread
64
smem / block
6144 B
block size
768
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
3462.9%
short_scoreboard
204.4%
mio_throttle
1.5%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Pre-norm 1D dilated convolution block: LayerNorm, GELU activation, dilated Conv1d, and residual addition.
    Common in genomic sequence models (Enformer, Basenji) for capturing long-range dependencies in DNA/RNA sequences.
    """
    def __init__(self, channels, seq_len, kernel_size, dilation):
        super(Model, self).__init__()
        self.norm = nn.LayerNorm([channels, seq_len])
        self.activation = nn.GELU()
        self.conv = nn.Conv1d(channels, channels, kernel_size, padding=dilation, dilation=dilation)

    def forward(self, x):
        residual = x
        x = self.norm(x)
        x = self.activation(x)
        x = self.conv(x)
        x = x + residual
        return x

batch_size = 32
channels = 256
seq_len = 4096
kernel_size = 3
dilation = 4

def get_inputs():
    return [torch.randn(popcorn_pri.jitter_int(batch_size), channels, seq_len)]

def get_init_inputs():
    return [channels, seq_len, kernel_size, dilation]
turn 0 · FINAL · 1 tool call(s) · 293.2s turn · 38.9s LLM · tokens in=1,070 · out=3,630 · total=4,700 · 16,368 chars (click to expand)
turn 0293.2s turn · 38.9s LLM · 254.3s tools · tokens in=1,070 · out=3,630 · total=4,700 · FINAL
reasoning
Let me analyze this task. I need to implement a custom CUDA kernel that replaces the PyTorch forward computation for a pre-norm 1D dilated convolution block. The reference model does: 1. LayerNorm on input x 2. GELU activation 3. Dilated Conv1d 4. Residual addition (x + residual) I need to implement all of these operations efficiently in CUDA. Let me think about the implementation: 1. **LayerNorm**: Normalize across channels and seq_len dimensions. For an input of shape (batch, channels, seq_len), LayerNorm normalizes each sample independently. The normalized shape is [channels, seq_len]. 2. **GELU**: Gaussian Error Linear Unit activation. GELU(x) = x * Φ(x) where Φ is the CDF of standard normal. Can be approximated as 0.5 * x * (1 + tanh(sqrt(2/π) * (x + 0.044715 * x^3))) 3. **Dilated Conv1d**: 1D convolution with dilation. For kernel_size=3 and dilation=4, the effective receptive field is 1 + (3-1)*4 = 9. 4. **Residual addition**: Simple element-wise addition. Let me write a fused kernel that does all of these operations efficiently. For the convolution, I need to handle the weights and bias from the Conv1d layer. The Conv1d has: - in_channels = channels - out_channels = channels - kernel_size = kernel_size - dilation = dilation - padding = dilation (same padding for this configuration) Let me design the kernel: Option 1: Single fused kernel doing all operations Option 2: Multiple kernels (one for norm+gelu, one for conv, one for add) For efficiency, a single fused kernel would be ideal, but it might be complex due to the convolution requiring different memory access patterns. Let me think about the data layout: - Input: (batch, channels, seq_len) - LayerNorm normalizes over the last two dimensions combined - Conv1d operates on the sequence dimension I can approach this in stages. First, I'll implement a LayerNorm + GELU kernel that processes each batch sample independently, normalizing across the channels and sequence positions. Then a separate convolution kernel for the dilated Conv1d. This modular approach lets me optimize each operation independently while still benefiting from kernel fusion where possible. The key is ensuring data dependencies are respected—the normalized output feeds into GELU, which then feeds into the convolution. Let me start by implementing the LayerNorm + GELU combination in a single kernel to minimize memory transfers between operations. elements across (channels, seq_len) dimensions per batch sample - GELU: element-wise - Conv1d: sliding window operation - Add: element-wise Actually, fusing LayerNorm + GELU makes sense since they can be computed together. The Conv1d is more complex because of the sliding window nature. Let me try a fully fused approach where each thread handles one output element. For the convolution part: - Output shape is (batch, channels, seq_len) - For each output position (b, c_out, s_out), we compute: sum_{c_in, k} weight[c_out, c_in, k] * input[b, c_in, s_out + padding - k*dilation] Actually, for this model, it's a bit special because channels is preserved (same input and output channels). Let me write a kernel where: 1. First, we load the input data and compute LayerNorm stats (mean, variance) 2. Then we compute GELU on the normalized values 3. Then we compute the convolution 4. Finally, add the residual The challenge is that LayerNorm requires reduction across the entire (channels, seq_len) space for each batch sample. This is typically done with a two-pass approach or with shared memory reduction. I'll implement a two-kernel approach instead: 1. Kernel 1: Fused LayerNorm + GELU 2. Kernel 2: Conv1d + Residual Add This should be more manageable while still providing significant speedup. For Kernel 1 (LayerNorm + GELU): - Use block-level reduction to compute mean and variance - Normalize and apply GELU For Kernel 2 (Conv1d + Residual): - Use tiled convolution approach - Load input tiles into shared memory - Compute convolution and add residual I'm using fp32 precision, so I need to be careful with numerical accuracy. Let me start coding this solution now.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Define CUDA source\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n// Constants for GELU approximation\nconstexpr float GELU_COEF = 0.7978845608028654f; // sqrt(2/M_PI)\nconstexpr float GELU_CUBIC = 0.035677408136300125f; // 0.044715 * sqrt(2/M_PI) approximation adjustment\n\n// Fused LayerNorm + GELU kernel\ntemplate <int BLOCK_SIZE>\n__global__ void layernorm_gelu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n int batch_size,\n int channels,\n int seq_len,\n const float* __restrict__ weight,\n const float* __restrict__ bias\n) {\n const int batch_idx = blockIdx.x;\n const int tid = threadIdx.x;\n const int stride = blockDim.x;\n const int size = channels * seq_len;\n \n if (batch_idx >= batch_size) return;\n \n // Shared memory for reduction\n __shared__ float s_mean[BLOCK_SIZE];\n __shared__ float s_var[BLOCK_SIZE];\n \n const float* batch_input = input + batch_idx * size;\n float* batch_output = output + batch_idx * size;\n \n // First pass: compute mean\n float local_sum = 0.0f;\n for (int i = tid; i < size; i += stride) {\n local_sum += batch_input[i];\n }\n s_mean[tid] = local_sum;\n __syncthreads();\n \n // Reduction for mean\n for (int s = BLOCK_SIZE / 2; s > 0; s >>= 1) {\n if (tid < s) {\n s_mean[tid] += s_mean[tid + s];\n }\n __syncthreads();\n }\n \n float mean = s_mean[0] / size;\n \n // Second pass: compute variance\n float local_var_sum = 0.0f;\n for (int i = tid; i < size; i += stride) {\n float diff = batch_input[i] - mean;\n local_var_sum += diff * diff;\n }\n s_var[tid] = local_var_sum;\n __syncthreads();\n \n // Reduction for variance\n for (int s = BLOCK_SIZE / 2; s > 0; s >>= 1) {\n if (tid < s) {\n s_var[tid] += s_var[tid + s];\n }\n __syncthreads();\n }\n \n float variance = s_var[0] / size;\n float inv_std = rsqrtf(variance + 1e-5f);\n \n // Third pass: normalize and apply GELU\n for (int i = tid; i < size; i += stride) {\n int c = i / seq_len;\n int s = i % seq_len;\n int weight_idx = c * seq_len + s;\n \n float normalized = (batch_input[i] - mean) * inv_std;\n normalized = normalized * weight[weight_idx] + bias[weight_idx];\n \n // GELU approximation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))\n float x = normalized;\n float x3 = x * x * x;\n float inner = GELU_COEF * (x + 0.044715f * x3);\n float tanh_out = tanhf(inner);\n batch_output[i] = 0.5f * x * (1.0f + tanh_out);\n }\n}\n\n// Dilated Conv1d + Residual Add kernel (optimized with shared memory)\n__global__ void dilated_conv1d_residual_kernel(\n const float* __restrict__ input,\n const float* __restrict__ residual,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int batch_size,\n int channels,\n int seq_len,\n int kernel_size,\n int dilation,\n int padding\n) {\n const int batch_idx = blockIdx.z;\n const int out_channel = blockIdx.y;\n const int seq_pos = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || out_channel >= channels || seq_pos >= seq_len) return;\n \n float sum = 0.0f;\n \n // Compute convolution for this output position\n for (int in_channel = 0; in_channel < channels; in_channel++) {\n for (int k = 0; k < kernel_size; k++) {\n int input_pos = seq_pos + padding - k * dilation;\n if (input_pos >= 0 && input_pos < seq_len) {\n int weight_idx = out_channel * channels * kernel_size + in_channel * kernel_size + k;\n int input_idx = batch_idx * channels * seq_len + in_channel * seq_len + input_pos;\n sum += input[input_idx] * weight[weight_idx];\n }\n }\n }\n \n // Add bias\n sum += bias[out_channel];\n \n // Add residual\n int residual_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;\n sum += residual[residual_idx];\n \n // Write output\n int output_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;\n output[output_idx] = sum;\n}\n\n// Optimized dilated Conv1d kernel using shared memory for input channels\n__global__ void dilated_conv1d_residual_optimized_kernel(\n const float* __restrict__ input,\n const float* __restrict__ residual,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int batch_size,\n int channels,\n int seq_len,\n int kernel_size,\n int dilation,\n int padding\n) {\n // Each block processes one (batch, out_channel) pair\n const int batch_idx = blockIdx.y;\n const int out_channel = blockIdx.x;\n const int seq_pos = threadIdx.x;\n \n if (batch_idx >= batch_size || out_channel >= channels) return;\n \n // Shared memory for weights of this output channel\n extern __shared__ float shared_mem[];\n float* channel_weights = shared_mem; // channels * kernel_size floats\n \n // Cooperatively load weights for this output channel\n const int weight_offset = out_channel * channels * kernel_size;\n for (int i = threadIdx.x; i < channels * kernel_size; i += blockDim.x) {\n channel_weights[i] = weight[weight_offset + i];\n }\n __syncthreads();\n \n float sum = 0.0f;\n \n // Process each input channel\n for (int in_channel = 0; in_channel < channels; in_channel++) {\n // Process each kernel position\n for (int k = 0; k < kernel_size; k++) {\n int input_pos = seq_pos + padding - k * dilation;\n if (input_pos >= 0 && input_pos < seq_len) {\n int input_idx = batch_idx * channels * seq_len + in_channel * seq_len + input_pos;\n int weight_idx = in_channel * kernel_size + k;\n sum += input[input_idx] * channel_weights[weight_idx];\n }\n }\n }\n \n // Add bias\n sum += bias[out_channel];\n \n // Add residual\n int residual_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;\n sum += residual[residual_idx];\n \n // Write output\n int output_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;\n output[output_idx] = sum;\n}\n\n// Host functions\nvoid layernorm_gelu_cuda(\n const torch::Tensor& input,\n torch::Tensor& output,\n const torch::Tensor& weight,\n const torch::Tensor& bias\n) {\n int batch_size = input.size(0);\n int channels = input.size(1);\n int seq_len = input.size(2);\n int size = channels * seq_len;\n \n const int BLOCK_SIZE = 512;\n int blocks = batch_size;\n \n layernorm_gelu_kernel<BLOCK_SIZE><<<blocks, BLOCK_SIZE>>>(\n input.data_ptr<float>(),\n output.data_ptr<float>(),\n batch_size,\n channels,\n seq_len,\n weight.data_ptr<float>(),\n bias.data_ptr<float>()\n );\n}\n\nvoid dilated_conv1d_residual_cuda(\n const torch::Tensor& input,\n const torch::Tensor& residual,\n torch::Tensor& output,\n const torch::Tensor& weight,\n const torch::Tensor& bias,\n int dilation\n) {\n int batch_size = input.size(0);\n int channels = input.size(1);\n int seq_len = input.size(2);\n int kernel_size = weight.size(2);\n int padding = dilation;\n \n const int BLOCK_SIZE = 256;\n dim3 grid((seq_len + BLOCK_SIZE - 1) / BLOCK_SIZE, channels, batch_size);\n \n dilated_conv1d_residual_kernel<<<grid, BLOCK_SIZE>>>(\n input.data_ptr<float>(),\n residual.data_ptr<float>(),\n output.data_ptr<float>(),\n weight.data_ptr<float>(),\n bias.data_ptr<float>(),\n batch_size,\n channels,\n seq_len,\n kernel_size,\n dilation,\n padding\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nvoid layernorm_gelu_cuda(\n const torch::Tensor& input,\n torch::Tensor& output,\n const torch::Tensor& weight,\n const torch::Tensor& bias\n);\n\nvoid dilated_conv1d_residual_cuda(\n const torch::Tensor& input,\n const torch::Tensor& residual,\n torch::Tensor& output,\n const torch::Tensor& weight,\n const torch::Tensor& bias,\n int dilation\n);\n\"\"\"\n\n# Load the CUDA extension\nmodule = load_inline(\n name='dilated_conv_extension',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['layernorm_gelu_cuda', 'dilated_conv1d_residual_cuda'],\n extra_cuda_cflags=['-O3', '-use_fast_math'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Pre-norm 1D dilated convolution block: LayerNorm, GELU activation, dilated Conv1d, and residual addition.\n Optimized with custom CUDA kernels.\n \"\"\"\n def __init__(self, channels, seq_len, kernel_size, dilation):\n super(ModelNew, self).__init__()\n \n # LayerNorm parameters\n self.channels = channels\n self.seq_len = seq_len\n \n # LayerNorm weights (gamma and beta)\n self.norm_weight = nn.Parameter(torch.ones(channels, seq_len))\n self.norm_bias = nn.Parameter(torch.zeros(channels, seq_len))\n \n # Conv1d weights\n self.conv_weight = nn.Parameter(torch.empty(channels, channels, kernel_size))\n self.conv_bias = nn.Parameter(torch.zeros(channels))\n self.dilation = dilation\n self.kernel_size = kernel_size\n \n # Initialize weights like Conv1d would\n nn.init.kaiming_uniform_(self.conv_weight, a=math.sqrt(5))\n \n # Register the loaded module\n self.cuda_module = module\n \n def forward(self, x):\n residual = x\n \n # Allocate output tensor for LayerNorm + GELU\n x_norm_gelu = torch.empty_like(x)\n \n # Fused LayerNorm + GELU\n self.cuda_module.layernorm_gelu_cuda(\n x, x_norm_gelu, self.norm_weight, self.norm_bias\n )\n \n # Allocate output tensor for Conv1d + Residual\n output = torch.empty_like(x)\n \n # Fused Dilated Conv1d + Residual Add\n self.cuda_module.dilated_conv1d_residual_cuda(\n x_norm_gelu, residual, output, self.conv_weight, self.conv_bias, self.dilation\n )\n \n return output\n\n\nimport math # Needed for initialization" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: correctness check did not pass ((0 / 5) trials). correctness_issue: Output mismatch max_difference: ['2.387173', '2.272834', '2.464982', '2.274559', '2.161207'] avg_difference: ['0.312314', '0.312238', '0.312198', '0.312266', '0.312129']
final submitted kernel (340 lines, 10,408 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

# Define CUDA source
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>

// Constants for GELU approximation
constexpr float GELU_COEF = 0.7978845608028654f;  // sqrt(2/M_PI)
constexpr float GELU_CUBIC = 0.035677408136300125f;  // 0.044715 * sqrt(2/M_PI) approximation adjustment

// Fused LayerNorm + GELU kernel
template <int BLOCK_SIZE>
__global__ void layernorm_gelu_kernel(
    const float* __restrict__ input,
    float* __restrict__ output,
    int batch_size,
    int channels,
    int seq_len,
    const float* __restrict__ weight,
    const float* __restrict__ bias
) {
    const int batch_idx = blockIdx.x;
    const int tid = threadIdx.x;
    const int stride = blockDim.x;
    const int size = channels * seq_len;
    
    if (batch_idx >= batch_size) return;
    
    // Shared memory for reduction
    __shared__ float s_mean[BLOCK_SIZE];
    __shared__ float s_var[BLOCK_SIZE];
    
    const float* batch_input = input + batch_idx * size;
    float* batch_output = output + batch_idx * size;
    
    // First pass: compute mean
    float local_sum = 0.0f;
    for (int i = tid; i < size; i += stride) {
        local_sum += batch_input[i];
    }
    s_mean[tid] = local_sum;
    __syncthreads();
    
    // Reduction for mean
    for (int s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
        if (tid < s) {
            s_mean[tid] += s_mean[tid + s];
        }
        __syncthreads();
    }
    
    float mean = s_mean[0] / size;
    
    // Second pass: compute variance
    float local_var_sum = 0.0f;
    for (int i = tid; i < size; i += stride) {
        float diff = batch_input[i] - mean;
        local_var_sum += diff * diff;
    }
    s_var[tid] = local_var_sum;
    __syncthreads();
    
    // Reduction for variance
    for (int s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
        if (tid < s) {
            s_var[tid] += s_var[tid + s];
        }
        __syncthreads();
    }
    
    float variance = s_var[0] / size;
    float inv_std = rsqrtf(variance + 1e-5f);
    
    // Third pass: normalize and apply GELU
    for (int i = tid; i < size; i += stride) {
        int c = i / seq_len;
        int s = i % seq_len;
        int weight_idx = c * seq_len + s;
        
        float normalized = (batch_input[i] - mean) * inv_std;
        normalized = normalized * weight[weight_idx] + bias[weight_idx];
        
        // GELU approximation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
        float x = normalized;
        float x3 = x * x * x;
        float inner = GELU_COEF * (x + 0.044715f * x3);
        float tanh_out = tanhf(inner);
        batch_output[i] = 0.5f * x * (1.0f + tanh_out);
    }
}

// Dilated Conv1d + Residual Add kernel (optimized with shared memory)
__global__ void dilated_conv1d_residual_kernel(
    const float* __restrict__ input,
    const float* __restrict__ residual,
    float* __restrict__ output,
    const float* __restrict__ weight,
    const float* __restrict__ bias,
    int batch_size,
    int channels,
    int seq_len,
    int kernel_size,
    int dilation,
    int padding
) {
    const int batch_idx = blockIdx.z;
    const int out_channel = blockIdx.y;
    const int seq_pos = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (batch_idx >= batch_size || out_channel >= channels || seq_pos >= seq_len) return;
    
    float sum = 0.0f;
    
    // Compute convolution for this output position
    for (int in_channel = 0; in_channel < channels; in_channel++) {
        for (int k = 0; k < kernel_size; k++) {
            int input_pos = seq_pos + padding - k * dilation;
            if (input_pos >= 0 && input_pos < seq_len) {
                int weight_idx = out_channel * channels * kernel_size + in_channel * kernel_size + k;
                int input_idx = batch_idx * channels * seq_len + in_channel * seq_len + input_pos;
                sum += input[input_idx] * weight[weight_idx];
            }
        }
    }
    
    // Add bias
    sum += bias[out_channel];
    
    // Add residual
    int residual_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;
    sum += residual[residual_idx];
    
    // Write output
    int output_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;
    output[output_idx] = sum;
}

// Optimized dilated Conv1d kernel using shared memory for input channels
__global__ void dilated_conv1d_residual_optimized_kernel(
    const float* __restrict__ input,
    const float* __restrict__ residual,
    float* __restrict__ output,
    const float* __restrict__ weight,
    const float* __restrict__ bias,
    int batch_size,
    int channels,
    int seq_len,
    int kernel_size,
    int dilation,
    int padding
) {
    // Each block processes one (batch, out_channel) pair
    const int batch_idx = blockIdx.y;
    const int out_channel = blockIdx.x;
    const int seq_pos = threadIdx.x;
    
    if (batch_idx >= batch_size || out_channel >= channels) return;
    
    // Shared memory for weights of this output channel
    extern __shared__ float shared_mem[];
    float* channel_weights = shared_mem;  // channels * kernel_size floats
    
    // Cooperatively load weights for this output channel
    const int weight_offset = out_channel * channels * kernel_size;
    for (int i = threadIdx.x; i < channels * kernel_size; i += blockDim.x) {
        channel_weights[i] = weight[weight_offset + i];
    }
    __syncthreads();
    
    float sum = 0.0f;
    
    // Process each input channel
    for (int in_channel = 0; in_channel < channels; in_channel++) {
        // Process each kernel position
        for (int k = 0; k < kernel_size; k++) {
            int input_pos = seq_pos + padding - k * dilation;
            if (input_pos >= 0 && input_pos < seq_len) {
                int input_idx = batch_idx * channels * seq_len + in_channel * seq_len + input_pos;
                int weight_idx = in_channel * kernel_size + k;
                sum += input[input_idx] * channel_weights[weight_idx];
            }
        }
    }
    
    // Add bias
    sum += bias[out_channel];
    
    // Add residual
    int residual_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;
    sum += residual[residual_idx];
    
    // Write output
    int output_idx = batch_idx * channels * seq_len + out_channel * seq_len + seq_pos;
    output[output_idx] = sum;
}

// Host functions
void layernorm_gelu_cuda(
    const torch::Tensor& input,
    torch::Tensor& output,
    const torch::Tensor& weight,
    const torch::Tensor& bias
) {
    int batch_size = input.size(0);
    int channels = input.size(1);
    int seq_len = input.size(2);
    int size = channels * seq_len;
    
    const int BLOCK_SIZE = 512;
    int blocks = batch_size;
    
    layernorm_gelu_kernel<BLOCK_SIZE><<<blocks, BLOCK_SIZE>>>(
        input.data_ptr<float>(),
        output.data_ptr<float>(),
        batch_size,
        channels,
        seq_len,
        weight.data_ptr<float>(),
        bias.data_ptr<float>()
    );
}

void dilated_conv1d_residual_cuda(
    const torch::Tensor& input,
    const torch::Tensor& residual,
    torch::Tensor& output,
    const torch::Tensor& weight,
    const torch::Tensor& bias,
    int dilation
) {
    int batch_size = input.size(0);
    int channels = input.size(1);
    int seq_len = input.size(2);
    int kernel_size = weight.size(2);
    int padding = dilation;
    
    const int BLOCK_SIZE = 256;
    dim3 grid((seq_len + BLOCK_SIZE - 1) / BLOCK_SIZE, channels, batch_size);
    
    dilated_conv1d_residual_kernel<<<grid, BLOCK_SIZE>>>(
        input.data_ptr<float>(),
        residual.data_ptr<float>(),
        output.data_ptr<float>(),
        weight.data_ptr<float>(),
        bias.data_ptr<float>(),
        batch_size,
        channels,
        seq_len,
        kernel_size,
        dilation,
        padding
    );
}
"""

cpp_source = """
void layernorm_gelu_cuda(
    const torch::Tensor& input,
    torch::Tensor& output,
    const torch::Tensor& weight,
    const torch::Tensor& bias
);

void dilated_conv1d_residual_cuda(
    const torch::Tensor& input,
    const torch::Tensor& residual,
    torch::Tensor& output,
    const torch::Tensor& weight,
    const torch::Tensor& bias,
    int dilation
);
"""

# Load the CUDA extension
module = load_inline(
    name='dilated_conv_extension',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['layernorm_gelu_cuda', 'dilated_conv1d_residual_cuda'],
    extra_cuda_cflags=['-O3', '-use_fast_math'],
    verbose=False
)


class ModelNew(nn.Module):
    """
    Pre-norm 1D dilated convolution block: LayerNorm, GELU activation, dilated Conv1d, and residual addition.
    Optimized with custom CUDA kernels.
    """
    def __init__(self, channels, seq_len, kernel_size, dilation):
        super(ModelNew, self).__init__()
        
        # LayerNorm parameters
        self.channels = channels
        self.seq_len = seq_len
        
        # LayerNorm weights (gamma and beta)
        self.norm_weight = nn.Parameter(torch.ones(channels, seq_len))
        self.norm_bias = nn.Parameter(torch.zeros(channels, seq_len))
        
        # Conv1d weights
        self.conv_weight = nn.Parameter(torch.empty(channels, channels, kernel_size))
        self.conv_bias = nn.Parameter(torch.zeros(channels))
        self.dilation = dilation
        self.kernel_size = kernel_size
        
        # Initialize weights like Conv1d would
        nn.init.kaiming_uniform_(self.conv_weight, a=math.sqrt(5))
        
        # Register the loaded module
        self.cuda_module = module
    
    def forward(self, x):
        residual = x
        
        # Allocate output tensor for LayerNorm + GELU
        x_norm_gelu = torch.empty_like(x)
        
        # Fused LayerNorm + GELU
        self.cuda_module.layernorm_gelu_cuda(
            x, x_norm_gelu, self.norm_weight, self.norm_bias
        )
        
        # Allocate output tensor for Conv1d + Residual
        output = torch.empty_like(x)
        
        # Fused Dilated Conv1d + Residual Add
        self.cuda_module.dilated_conv1d_residual_cuda(
            x_norm_gelu, residual, output, self.conv_weight, self.conv_bias, self.dilation
        )
        
        return output


import math  # Needed for initialization