FW-GLM-5-1 · popcorn · pop_l123_default_gpt
correct 21_TriangularMultiplicativeUpdateIncoming.py
model
FW-GLM-5-1
turns / max
5 / 12
tool calls
5
tokens (total)
41018
wall clock
1370.6s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
1.59 μs
ref runtime
0.37 μs
speedup
0.23x
started
2026-05-11T10:47:22Z
timingkernelbench.timing
kernel runtime
1.59 μs
ref runtime
0.37 μs
speedup
0.23x
kernel mean / median / std
1.59 / — / 0.00 μs
ref mean / median / std
0.37 / — / 2.73 μs
numerical precisioneval_kernel_against_ref
max abs error
7.75e-07
mean abs error
7.38e-08
max rel error
3.05
mean rel error
9.01e-06
gpu memoryextended_metrics.measure_memory
kernel peak
35.40 MB
ref peak
36.13 MB
kernel / ref
0.98x
kernel launches / fusiontorch.profiler
kernel launches
62
ref launches
101
fusion ratio (ref / kernel)
1.63x
total cuda time
716 μs
energyNVML (pynvml)
kernel mJ / run
466.48
ref mJ / run
0.00
ratio (ref / kernel)
—
avg power
136.87 W
ref avg power
0.00 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.232
dram util %
12.7%
compute util %
23.2%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
426.02 GB/s
dram util %
12.7%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
146.4%
bottleneck
compute-bound
dominant pipe %
23.2%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
1537.0%
ld sectors / req
286.49
st sectors / req
—
regs / thread
364
smem / block
16384 B
block size
2816
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
34069.0%
short_scoreboard
2810.9%
mio_throttle
10.6%
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):
    """
    Triangular multiplicative update – incoming edges.
    Mirror of the outgoing variant: updates pair (i,j) by aggregating over
    the shared index k via edges (k,i) and (k,j).
    """

    def __init__(self, pair_dim, hidden_dim):
        super().__init__()
        self.layer_norm_in = nn.LayerNorm(pair_dim)
        self.proj_a = nn.Linear(pair_dim, hidden_dim)
        self.gate_a = nn.Linear(pair_dim, hidden_dim)
        self.proj_b = nn.Linear(pair_dim, hidden_dim)
        self.gate_b = nn.Linear(pair_dim, hidden_dim)
        self.layer_norm_out = nn.LayerNorm(hidden_dim)
        self.out_proj = nn.Linear(hidden_dim, pair_dim)
        self.out_gate = nn.Linear(pair_dim, pair_dim)

    def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:
        """
        Args:
            pair_repr: (B, N, N, pair_dim)
            pair_mask: (B, N, N)
        Returns:
            updated pair representation (B, N, N, pair_dim)
        """
        mask = pair_mask.unsqueeze(-1)
        x = self.layer_norm_in(pair_repr)

        a = self.proj_a(x) * torch.sigmoid(self.gate_a(x)) * mask  # (B, N, N, H)
        b = self.proj_b(x) * torch.sigmoid(self.gate_b(x)) * mask

        # Incoming: aggregate over k with a[k,i] * b[k,j]
        out = torch.einsum("bkih,bkjh->bijh", a, b)

        out = self.layer_norm_out(out)
        out = self.out_proj(out)
        out = torch.sigmoid(self.out_gate(pair_repr)) * out
        return out


pair_dim = 64
hidden_dim = 32
seq_len = 32
batch_size = 2


def get_inputs():
    p = popcorn_pri
    pair_repr = torch.randn(p.jitter_int(batch_size), p.jitter_int(seq_len), p.jitter_int(seq_len), pair_dim)
    pair_mask = torch.ones(p.jitter_int(batch_size), p.jitter_int(seq_len), p.jitter_int(seq_len))
    return [pair_repr, pair_mask]


def get_init_inputs():
    return [pair_dim, hidden_dim]
turn 011.1s turn · 11.1s LLM · 0.0s tools · tokens in=1,725 · out=459 · total=2,184
reasoning
Let me analyze this task. I need to create a custom CUDA kernel that replaces the PyTorch model's forward pass. The model does: 1. LayerNorm on input pair_repr (B, N, N, pair_dim) 2. Compute a = proj_a(x) * sigmoid(gate_a(x)) * mask 3. Compute b = proj_b(x) * sigmoid(gate_b(x)) * mask 4. Einsum: out = torch.einsum("bkih,bkjh->bijh", a, b) - This is the key computation 5. LayerNorm on out 6. out_proj(out) 7. sigmoid(out_gate(pair_repr)) * out The einsum "bkih,bkjh->bijh" means: - For each (b, i, j, h) in output: - Sum over k: a[b, k, i, h] * b[b, k, j, h] So output[b, i, j, h] = sum_k (a[b, k, i, h] * b[b, k, j, h]) This is essentially a batched matrix multiplication type operation but with a specific pattern. Let me think about how to optimize this: 1. The einsum is the main computational bottleneck - it's O(B * N^3 * H) 2. We can fuse the layer norm, projections, gates, and sigmoid operations 3. The mask application is simple broadcasting Let me start by understanding the shapes: - pair_repr: (B, N, N, pair_dim) = (2, 32, 32, 64) - pair_mask: (B, N, N) = (2, 32, 32) - a, b: (B, N, N, hidden_dim) = (2, 32, 32, 32) - out: (B, N, N, hidden_dim) = (2, 32, 32, 32) - final: (B, N, N, pair_dim) = (2, 32, 32, 64) I'll retrieve the GPU specifications to inform my kernel design approach.
call → get_gpu_specs
callget_gpu_specs {}
result ← get_gpu_specs (ok)
okget_gpu_specs GPU specs for NVIDIA H100 80GB HBM3: total memory (runtime): 79.1 GB spec entry: H100 GPU Architecture: Hopper GPU Memory: 80GB Memory Bandwidth: 3.35 TB/s FP64 TFLOPS: 34 FP64 Tensor Core TFLOPS: 67 FP32 TFLOPS: 67 TF32 Tensor Core TFLOPS: 989 with sparsity BFLOAT16 Tensore Core TFLOPS: 1979 with sparsity FP16 Tensor Core TFLOPS: 1979 with sparsity FP8 Tensor Core TFLOPS: 3958 with sparsity INT8 Tensor Core TOPS: 3958 with sparsity Register File Size: 64K 32-bit registers per SM Maximum number of registers per thread: 255 Maximum number of thread blocks per SM: 32 Shared memory capacity per SM: 228 KB Maximum shared memory per thread block: 227 KB
turn 1 · 1 tool call(s) · 670.0s turn · 56.2s LLM · tokens in=1,973 · out=3,348 · total=5,321 · 14,364 chars (click to expand)
turn 1670.0s turn · 56.2s LLM · 613.8s tools · tokens in=1,973 · out=3,348 · total=5,321
reasoning
Now let me understand the computation: 1. LayerNorm on input 2. Two linear projections (proj_a, gate_a) with sigmoid gating 3. Two linear projections (proj_b, gate_b) with sigmoid gating 4. Einsum: "bkih,bkjh->bijh" - this is the key computation 5. LayerNorm on output 6. Linear projection 7. Sigmoid gating with original input Let me write a first version of the kernel. I'll need to handle all these operations efficiently. The key challenge is the einsum operation which is essentially a matrix-matrix multiplication pattern across k dimension. For each (i,j), we compute sum over k of a[k,i,h] * b[k,j,h] for each h. This can be thought of as: for each batch and each h, we have a matrix A of shape (N, N) where A[k,i] = a[k,i,h] and B of shape (N, N) where B[k,j] = b[k,j,h]. Then output[i,j] = sum_k A[k,i] * B[k,j] = (A^T @ B)[i,j]. So this is essentially N * hidden_dim matrix multiplications of size (N, N) x (N, N). Let me start with a basic implementation:
assistantLet me analyze this model and create a custom CUDA kernel. The key operations are: 1. LayerNorm on input 2. Linear projections with sigmoid gating 3. Einsum: "bkih,bkjh->bijh" (incoming edge aggregation) 4. LayerNorm on output + final projection Let me start with a complete implementation:
call → compile_kernel
callcompile_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\nimport math\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\nconstexpr int BLOCK_SIZE = 16;\n\n// LayerNorm kernel\ntemplate <typename scalar_t>\n__global__ void layer_norm_kernel(\n const scalar_t* __restrict__ input,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ weight,\n const scalar_t* __restrict__ bias,\n int N, int dim, int stride) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= N) return;\n \n const scalar_t* in_ptr = input + idx * stride;\n scalar_t* out_ptr = output + idx * stride;\n \n // Compute mean\n scalar_t mean = 0;\n for (int i = 0; i < dim; i++) {\n mean += in_ptr[i];\n }\n mean /= dim;\n \n // Compute variance\n scalar_t var = 0;\n for (int i = 0; i < dim; i++) {\n scalar_t diff = in_ptr[i] - mean;\n var += diff * diff;\n }\n var /= dim;\n \n scalar_t inv_std = rsqrt(var + scalar_t(1e-5));\n \n // Normalize and apply affine transform\n for (int i = 0; i < dim; i++) {\n out_ptr[i] = (in_ptr[i] - mean) * inv_std * weight[i] + bias[i];\n }\n}\n\n// Linear + sigmoid gating kernel\ntemplate <typename scalar_t>\n__global__ void linear_gated_kernel(\n const scalar_t* __restrict__ input,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ mask,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int output_offset = ((b * N + i) * N + j) * out_dim;\n \n scalar_t mask_val = mask ? mask[(b * N + i) * N + j] : scalar_t(1.0);\n \n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n scalar_t gate_val = gate_bias[h];\n \n for (int d = 0; d < in_dim; d++) {\n scalar_t x = input[input_offset + d];\n proj_val += x * proj_weight[d * out_dim + h];\n gate_val += x * gate_weight[d * out_dim + h];\n }\n \n // Sigmoid\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = proj_val * gate_val * mask_val;\n }\n}\n\n// Einsum kernel: bkih,bkjh->bijh\ntemplate <typename scalar_t>\n__global__ void einsum_incoming_kernel(\n const scalar_t* __restrict__ a,\n const scalar_t* __restrict__ b,\n scalar_t* __restrict__ out,\n int batch_size, int N, int H) {\n \n int b_idx = blockIdx.z;\n int h = blockIdx.y;\n int ij = blockIdx.x * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n scalar_t sum = 0;\n for (int k = 0; k < N; k++) {\n int a_idx = ((b_idx * N + k) * N + i) * H + h;\n int b_idx_local = ((b_idx * N + k) * N + j) * H + h;\n sum += a[a_idx] * b[b_idx_local];\n }\n \n out[((b_idx * N + i) * N + j) * H + h] = sum;\n}\n\n// Output projection kernel\ntemplate <typename scalar_t>\n__global__ void output_proj_kernel(\n const scalar_t* __restrict__ pair_repr,\n const scalar_t* __restrict__ norm_out,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int pair_offset = ((b * N + i) * N + j) * out_dim;\n int output_offset = pair_offset;\n \n // Compute projection\n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n for (int d = 0; d < in_dim; d++) {\n proj_val += norm_out[input_offset + d] * proj_weight[d * out_dim + h];\n }\n \n // Compute gate\n scalar_t gate_val = gate_bias[h];\n for (int d = 0; d < out_dim; d++) {\n gate_val += pair_repr[pair_offset + d] * gate_weight[d * out_dim + h];\n }\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = gate_val * proj_val;\n }\n}\n\n// Host functions\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias) {\n auto sizes = input.sizes();\n int total = 1;\n for (int i = 0; i < sizes.size() - 1; i++) total *= sizes[i];\n int dim = sizes[sizes.size() - 1];\n \n auto output = torch::empty_like(input);\n \n const int threads = 256;\n const int blocks = (total + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"layer_norm_cuda\", ([&] {\n layer_norm_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n weight.data_ptr<scalar_t>(),\n bias.data_ptr<scalar_t>(),\n total, dim, dim);\n }));\n \n return output;\n}\n\ntorch::Tensor linear_gated_cuda(\n torch::Tensor input,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n torch::Tensor mask, int out_dim) {\n \n auto sizes = input.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, input.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n bool has_mask = mask.defined() && mask.numel() > 0;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"linear_gated_cuda\", ([&] {\n linear_gated_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n has_mask ? mask.data_ptr<scalar_t>() : nullptr,\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b) {\n auto sizes = a.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int H = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, H}, a.options());\n \n dim3 blocks((N * N + 255) / 256, H, batch_size);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(a.scalar_type(), \"einsum_incoming_cuda\", ([&] {\n einsum_incoming_kernel<scalar_t><<<blocks, threads>>>(\n a.data_ptr<scalar_t>(),\n b.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, H);\n }));\n \n return output;\n}\n\ntorch::Tensor output_proj_cuda(\n torch::Tensor pair_repr,\n torch::Tensor norm_out,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n int out_dim) {\n \n auto sizes = norm_out.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, norm_out.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(norm_out.scalar_type(), \"output_proj_cuda\", ([&] {\n output_proj_kernel<scalar_t><<<blocks, threads>>>(\n pair_repr.data_ptr<scalar_t>(),\n norm_out.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias);\ntorch::Tensor linear_gated_cuda(torch::Tensor input, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, torch::Tensor mask, int out_dim);\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b);\ntorch::Tensor output_proj_cuda(torch::Tensor pair_repr, torch::Tensor norm_out, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, int out_dim);\n\"\"\"\n\nmodule = load_inline(\n name='tri_mul_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['layer_norm_cuda', 'linear_gated_cuda', 'einsum_incoming_cuda', 'output_proj_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, hidden_dim):\n super().__init__()\n self.layer_norm_in = nn.LayerNorm(pair_dim)\n self.proj_a = nn.Linear(pair_dim, hidden_dim)\n self.gate_a = nn.Linear(pair_dim, hidden_dim)\n self.proj_b = nn.Linear(pair_dim, hidden_dim)\n self.gate_b = nn.Linear(pair_dim, hidden_dim)\n self.layer_norm_out = nn.LayerNorm(hidden_dim)\n self.out_proj = nn.Linear(hidden_dim, pair_dim)\n self.out_gate = nn.Linear(pair_dim, pair_dim)\n \n self.pair_dim = pair_dim\n self.hidden_dim = hidden_dim\n\n def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:\n pair_repr = pair_repr.contiguous()\n pair_mask = pair_mask.contiguous()\n \n # LayerNorm input\n x = module.layer_norm_cuda(\n pair_repr,\n self.layer_norm_in.weight,\n self.layer_norm_in.bias\n )\n \n # Linear + gated for a\n a = module.linear_gated_cuda(\n x,\n self.proj_a.weight.transpose(0, 1).contiguous(),\n self.proj_a.bias,\n self.gate_a.weight.transpose(0, 1).contiguous(),\n self.gate_a.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Linear + gated for b\n b = module.linear_gated_cuda(\n x,\n self.proj_b.weight.transpose(0, 1).contiguous(),\n self.proj_b.bias,\n self.gate_b.weight.transpose(0, 1).contiguous(),\n self.gate_b.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Einsum incoming\n out = module.einsum_incoming_cuda(a, b)\n \n # LayerNorm output\n out = module.layer_norm_cuda(\n out,\n self.layer_norm_out.weight,\n self.layer_norm_out.bias\n )\n \n # Output projection with gate\n result = module.output_proj_cuda(\n pair_repr,\n out,\n self.out_proj.weight.transpose(0, 1).contiguous(),\n self.out_proj.bias,\n self.out_gate.weight.transpose(0, 1).contiguous(),\n self.out_gate.bias,\n self.pair_dim\n )\n \n return result" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 2 · 1 tool call(s) · 164.6s turn · 26.0s LLM · tokens in=5,058 · out=3,014 · total=8,072 · 13,153 chars (click to expand)
turn 2164.6s turn · 26.0s LLM · 138.6s tools · tokens in=5,058 · out=3,014 · total=8,072
reasoning
Good, the kernel compiled. Now let me check correctness.
call → run_correctness
callrun_correctness { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\nimport math\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\nconstexpr int BLOCK_SIZE = 16;\n\n// LayerNorm kernel\ntemplate <typename scalar_t>\n__global__ void layer_norm_kernel(\n const scalar_t* __restrict__ input,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ weight,\n const scalar_t* __restrict__ bias,\n int N, int dim, int stride) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= N) return;\n \n const scalar_t* in_ptr = input + idx * stride;\n scalar_t* out_ptr = output + idx * stride;\n \n // Compute mean\n scalar_t mean = 0;\n for (int i = 0; i < dim; i++) {\n mean += in_ptr[i];\n }\n mean /= dim;\n \n // Compute variance\n scalar_t var = 0;\n for (int i = 0; i < dim; i++) {\n scalar_t diff = in_ptr[i] - mean;\n var += diff * diff;\n }\n var /= dim;\n \n scalar_t inv_std = rsqrt(var + scalar_t(1e-5));\n \n // Normalize and apply affine transform\n for (int i = 0; i < dim; i++) {\n out_ptr[i] = (in_ptr[i] - mean) * inv_std * weight[i] + bias[i];\n }\n}\n\n// Linear + sigmoid gating kernel\ntemplate <typename scalar_t>\n__global__ void linear_gated_kernel(\n const scalar_t* __restrict__ input,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ mask,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int output_offset = ((b * N + i) * N + j) * out_dim;\n \n scalar_t mask_val = mask ? mask[(b * N + i) * N + j] : scalar_t(1.0);\n \n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n scalar_t gate_val = gate_bias[h];\n \n for (int d = 0; d < in_dim; d++) {\n scalar_t x = input[input_offset + d];\n proj_val += x * proj_weight[d * out_dim + h];\n gate_val += x * gate_weight[d * out_dim + h];\n }\n \n // Sigmoid\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = proj_val * gate_val * mask_val;\n }\n}\n\n// Einsum kernel: bkih,bkjh->bijh\ntemplate <typename scalar_t>\n__global__ void einsum_incoming_kernel(\n const scalar_t* __restrict__ a,\n const scalar_t* __restrict__ b,\n scalar_t* __restrict__ out,\n int batch_size, int N, int H) {\n \n int b_idx = blockIdx.z;\n int h = blockIdx.y;\n int ij = blockIdx.x * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n scalar_t sum = 0;\n for (int k = 0; k < N; k++) {\n int a_idx = ((b_idx * N + k) * N + i) * H + h;\n int b_idx_local = ((b_idx * N + k) * N + j) * H + h;\n sum += a[a_idx] * b[b_idx_local];\n }\n \n out[((b_idx * N + i) * N + j) * H + h] = sum;\n}\n\n// Output projection kernel\ntemplate <typename scalar_t>\n__global__ void output_proj_kernel(\n const scalar_t* __restrict__ pair_repr,\n const scalar_t* __restrict__ norm_out,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int pair_offset = ((b * N + i) * N + j) * out_dim;\n int output_offset = pair_offset;\n \n // Compute projection\n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n for (int d = 0; d < in_dim; d++) {\n proj_val += norm_out[input_offset + d] * proj_weight[d * out_dim + h];\n }\n \n // Compute gate\n scalar_t gate_val = gate_bias[h];\n for (int d = 0; d < out_dim; d++) {\n gate_val += pair_repr[pair_offset + d] * gate_weight[d * out_dim + h];\n }\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = gate_val * proj_val;\n }\n}\n\n// Host functions\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias) {\n auto sizes = input.sizes();\n int total = 1;\n for (int i = 0; i < sizes.size() - 1; i++) total *= sizes[i];\n int dim = sizes[sizes.size() - 1];\n \n auto output = torch::empty_like(input);\n \n const int threads = 256;\n const int blocks = (total + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"layer_norm_cuda\", ([&] {\n layer_norm_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n weight.data_ptr<scalar_t>(),\n bias.data_ptr<scalar_t>(),\n total, dim, dim);\n }));\n \n return output;\n}\n\ntorch::Tensor linear_gated_cuda(\n torch::Tensor input,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n torch::Tensor mask, int out_dim) {\n \n auto sizes = input.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, input.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n bool has_mask = mask.defined() && mask.numel() > 0;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"linear_gated_cuda\", ([&] {\n linear_gated_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n has_mask ? mask.data_ptr<scalar_t>() : nullptr,\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b) {\n auto sizes = a.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int H = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, H}, a.options());\n \n dim3 blocks((N * N + 255) / 256, H, batch_size);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(a.scalar_type(), \"einsum_incoming_cuda\", ([&] {\n einsum_incoming_kernel<scalar_t><<<blocks, threads>>>(\n a.data_ptr<scalar_t>(),\n b.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, H);\n }));\n \n return output;\n}\n\ntorch::Tensor output_proj_cuda(\n torch::Tensor pair_repr,\n torch::Tensor norm_out,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n int out_dim) {\n \n auto sizes = norm_out.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, norm_out.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(norm_out.scalar_type(), \"output_proj_cuda\", ([&] {\n output_proj_kernel<scalar_t><<<blocks, threads>>>(\n pair_repr.data_ptr<scalar_t>(),\n norm_out.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias);\ntorch::Tensor linear_gated_cuda(torch::Tensor input, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, torch::Tensor mask, int out_dim);\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b);\ntorch::Tensor output_proj_cuda(torch::Tensor pair_repr, torch::Tensor norm_out, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, int out_dim);\n\"\"\"\n\nmodule = load_inline(\n name='tri_mul_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['layer_norm_cuda', 'linear_gated_cuda', 'einsum_incoming_cuda', 'output_proj_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, hidden_dim):\n super().__init__()\n self.layer_norm_in = nn.LayerNorm(pair_dim)\n self.proj_a = nn.Linear(pair_dim, hidden_dim)\n self.gate_a = nn.Linear(pair_dim, hidden_dim)\n self.proj_b = nn.Linear(pair_dim, hidden_dim)\n self.gate_b = nn.Linear(pair_dim, hidden_dim)\n self.layer_norm_out = nn.LayerNorm(hidden_dim)\n self.out_proj = nn.Linear(hidden_dim, pair_dim)\n self.out_gate = nn.Linear(pair_dim, pair_dim)\n \n self.pair_dim = pair_dim\n self.hidden_dim = hidden_dim\n\n def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:\n pair_repr = pair_repr.contiguous()\n pair_mask = pair_mask.contiguous()\n \n # LayerNorm input\n x = module.layer_norm_cuda(\n pair_repr,\n self.layer_norm_in.weight,\n self.layer_norm_in.bias\n )\n \n # Linear + gated for a\n a = module.linear_gated_cuda(\n x,\n self.proj_a.weight.transpose(0, 1).contiguous(),\n self.proj_a.bias,\n self.gate_a.weight.transpose(0, 1).contiguous(),\n self.gate_a.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Linear + gated for b\n b = module.linear_gated_cuda(\n x,\n self.proj_b.weight.transpose(0, 1).contiguous(),\n self.proj_b.bias,\n self.gate_b.weight.transpose(0, 1).contiguous(),\n self.gate_b.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Einsum incoming\n out = module.einsum_incoming_cuda(a, b)\n \n # LayerNorm output\n out = module.layer_norm_cuda(\n out,\n self.layer_norm_out.weight,\n self.layer_norm_out.bias\n )\n \n # Output projection with gate\n result = module.output_proj_cuda(\n pair_repr,\n out,\n self.out_proj.weight.transpose(0, 1).contiguous(),\n self.out_proj.bias,\n self.out_gate.weight.transpose(0, 1).contiguous(),\n self.out_gate.bias,\n self.pair_dim\n )\n \n return result" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=7.75e-07 mean_abs_err=7.38e-08 max_rel_err=3.05e+00
turn 3 · 1 tool call(s) · 27.4s turn · 27.4s LLM · tokens in=8,117 · out=3,138 · total=11,255 · 13,680 chars (click to expand)
turn 327.4s turn · 27.4s LLM · 0.0s tools · tokens in=8,117 · out=3,138 · total=11,255
reasoning
The correctness passed. The max relative error is 3.05 but that's likely for near-zero values. Let me run a static check and then submit. But let me also optimize this kernel further since the max relative error is a bit high. Actually, looking at the max_abs_err=7.75e-07 and mean_abs_err=7.38e-08, these are within tolerance for fp32 (atol=rtol=1e-4). The max_rel_err is high but that's probably for values very close to zero. Let me run static check and submit this kernel.
assistantThe kernel passes correctness. Let me run a static check and submit:
call → static_check
callstatic_check { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\nimport math\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\nconstexpr int BLOCK_SIZE = 16;\n\n// LayerNorm kernel\ntemplate <typename scalar_t>\n__global__ void layer_norm_kernel(\n const scalar_t* __restrict__ input,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ weight,\n const scalar_t* __restrict__ bias,\n int N, int dim, int stride) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= N) return;\n \n const scalar_t* in_ptr = input + idx * stride;\n scalar_t* out_ptr = output + idx * stride;\n \n // Compute mean\n scalar_t mean = 0;\n for (int i = 0; i < dim; i++) {\n mean += in_ptr[i];\n }\n mean /= dim;\n \n // Compute variance\n scalar_t var = 0;\n for (int i = 0; i < dim; i++) {\n scalar_t diff = in_ptr[i] - mean;\n var += diff * diff;\n }\n var /= dim;\n \n scalar_t inv_std = rsqrt(var + scalar_t(1e-5));\n \n // Normalize and apply affine transform\n for (int i = 0; i < dim; i++) {\n out_ptr[i] = (in_ptr[i] - mean) * inv_std * weight[i] + bias[i];\n }\n}\n\n// Linear + sigmoid gating kernel\ntemplate <typename scalar_t>\n__global__ void linear_gated_kernel(\n const scalar_t* __restrict__ input,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ mask,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int output_offset = ((b * N + i) * N + j) * out_dim;\n \n scalar_t mask_val = mask ? mask[(b * N + i) * N + j] : scalar_t(1.0);\n \n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n scalar_t gate_val = gate_bias[h];\n \n for (int d = 0; d < in_dim; d++) {\n scalar_t x = input[input_offset + d];\n proj_val += x * proj_weight[d * out_dim + h];\n gate_val += x * gate_weight[d * out_dim + h];\n }\n \n // Sigmoid\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = proj_val * gate_val * mask_val;\n }\n}\n\n// Einsum kernel: bkih,bkjh->bijh\ntemplate <typename scalar_t>\n__global__ void einsum_incoming_kernel(\n const scalar_t* __restrict__ a,\n const scalar_t* __restrict__ b,\n scalar_t* __restrict__ out,\n int batch_size, int N, int H) {\n \n int b_idx = blockIdx.z;\n int h = blockIdx.y;\n int ij = blockIdx.x * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n scalar_t sum = 0;\n for (int k = 0; k < N; k++) {\n int a_idx = ((b_idx * N + k) * N + i) * H + h;\n int b_idx_local = ((b_idx * N + k) * N + j) * H + h;\n sum += a[a_idx] * b[b_idx_local];\n }\n \n out[((b_idx * N + i) * N + j) * H + h] = sum;\n}\n\n// Output projection kernel\ntemplate <typename scalar_t>\n__global__ void output_proj_kernel(\n const scalar_t* __restrict__ pair_repr,\n const scalar_t* __restrict__ norm_out,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int pair_offset = ((b * N + i) * N + j) * out_dim;\n int output_offset = pair_offset;\n \n // Compute projection\n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n for (int d = 0; d < in_dim; d++) {\n proj_val += norm_out[input_offset + d] * proj_weight[d * out_dim + h];\n }\n \n // Compute gate\n scalar_t gate_val = gate_bias[h];\n for (int d = 0; d < out_dim; d++) {\n gate_val += pair_repr[pair_offset + d] * gate_weight[d * out_dim + h];\n }\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = gate_val * proj_val;\n }\n}\n\n// Host functions\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias) {\n auto sizes = input.sizes();\n int total = 1;\n for (int i = 0; i < sizes.size() - 1; i++) total *= sizes[i];\n int dim = sizes[sizes.size() - 1];\n \n auto output = torch::empty_like(input);\n \n const int threads = 256;\n const int blocks = (total + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"layer_norm_cuda\", ([&] {\n layer_norm_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n weight.data_ptr<scalar_t>(),\n bias.data_ptr<scalar_t>(),\n total, dim, dim);\n }));\n \n return output;\n}\n\ntorch::Tensor linear_gated_cuda(\n torch::Tensor input,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n torch::Tensor mask, int out_dim) {\n \n auto sizes = input.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, input.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n bool has_mask = mask.defined() && mask.numel() > 0;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"linear_gated_cuda\", ([&] {\n linear_gated_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n has_mask ? mask.data_ptr<scalar_t>() : nullptr,\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b) {\n auto sizes = a.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int H = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, H}, a.options());\n \n dim3 blocks((N * N + 255) / 256, H, batch_size);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(a.scalar_type(), \"einsum_incoming_cuda\", ([&] {\n einsum_incoming_kernel<scalar_t><<<blocks, threads>>>(\n a.data_ptr<scalar_t>(),\n b.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, H);\n }));\n \n return output;\n}\n\ntorch::Tensor output_proj_cuda(\n torch::Tensor pair_repr,\n torch::Tensor norm_out,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n int out_dim) {\n \n auto sizes = norm_out.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, norm_out.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(norm_out.scalar_type(), \"output_proj_cuda\", ([&] {\n output_proj_kernel<scalar_t><<<blocks, threads>>>(\n pair_repr.data_ptr<scalar_t>(),\n norm_out.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias);\ntorch::Tensor linear_gated_cuda(torch::Tensor input, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, torch::Tensor mask, int out_dim);\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b);\ntorch::Tensor output_proj_cuda(torch::Tensor pair_repr, torch::Tensor norm_out, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, int out_dim);\n\"\"\"\n\nmodule = load_inline(\n name='tri_mul_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['layer_norm_cuda', 'linear_gated_cuda', 'einsum_incoming_cuda', 'output_proj_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, hidden_dim):\n super().__init__()\n self.layer_norm_in = nn.LayerNorm(pair_dim)\n self.proj_a = nn.Linear(pair_dim, hidden_dim)\n self.gate_a = nn.Linear(pair_dim, hidden_dim)\n self.proj_b = nn.Linear(pair_dim, hidden_dim)\n self.gate_b = nn.Linear(pair_dim, hidden_dim)\n self.layer_norm_out = nn.LayerNorm(hidden_dim)\n self.out_proj = nn.Linear(hidden_dim, pair_dim)\n self.out_gate = nn.Linear(pair_dim, pair_dim)\n \n self.pair_dim = pair_dim\n self.hidden_dim = hidden_dim\n\n def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:\n pair_repr = pair_repr.contiguous()\n pair_mask = pair_mask.contiguous()\n \n # LayerNorm input\n x = module.layer_norm_cuda(\n pair_repr,\n self.layer_norm_in.weight,\n self.layer_norm_in.bias\n )\n \n # Linear + gated for a\n a = module.linear_gated_cuda(\n x,\n self.proj_a.weight.transpose(0, 1).contiguous(),\n self.proj_a.bias,\n self.gate_a.weight.transpose(0, 1).contiguous(),\n self.gate_a.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Linear + gated for b\n b = module.linear_gated_cuda(\n x,\n self.proj_b.weight.transpose(0, 1).contiguous(),\n self.proj_b.bias,\n self.gate_b.weight.transpose(0, 1).contiguous(),\n self.gate_b.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Einsum incoming\n out = module.einsum_incoming_cuda(a, b)\n \n # LayerNorm output\n out = module.layer_norm_cuda(\n out,\n self.layer_norm_out.weight,\n self.layer_norm_out.bias\n )\n \n # Output projection with gate\n result = module.output_proj_cuda(\n pair_repr,\n out,\n self.out_proj.weight.transpose(0, 1).contiguous(),\n self.out_proj.bias,\n self.out_gate.weight.transpose(0, 1).contiguous(),\n self.out_gate.bias,\n self.pair_dim\n )\n \n return result" }
result ← static_check (ok)
okstatic_check static_check PASSED (with warnings): WARNING: Uses torch.nn compute layer (only containers, Parameter, init allowed)
turn 4 · FINAL · 1 tool call(s) · 497.4s turn · 24.9s LLM · tokens in=11,160 · out=3,026 · total=14,186 · 13,472 chars (click to expand)
turn 4497.4s turn · 24.9s LLM · 472.5s tools · tokens in=11,160 · out=3,026 · total=14,186 · FINAL
reasoning
The static check passed with a warning about using torch.nn compute layers. Let me submit the kernel and see how it performs.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\nimport math\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\nconstexpr int BLOCK_SIZE = 16;\n\n// LayerNorm kernel\ntemplate <typename scalar_t>\n__global__ void layer_norm_kernel(\n const scalar_t* __restrict__ input,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ weight,\n const scalar_t* __restrict__ bias,\n int N, int dim, int stride) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= N) return;\n \n const scalar_t* in_ptr = input + idx * stride;\n scalar_t* out_ptr = output + idx * stride;\n \n // Compute mean\n scalar_t mean = 0;\n for (int i = 0; i < dim; i++) {\n mean += in_ptr[i];\n }\n mean /= dim;\n \n // Compute variance\n scalar_t var = 0;\n for (int i = 0; i < dim; i++) {\n scalar_t diff = in_ptr[i] - mean;\n var += diff * diff;\n }\n var /= dim;\n \n scalar_t inv_std = rsqrt(var + scalar_t(1e-5));\n \n // Normalize and apply affine transform\n for (int i = 0; i < dim; i++) {\n out_ptr[i] = (in_ptr[i] - mean) * inv_std * weight[i] + bias[i];\n }\n}\n\n// Linear + sigmoid gating kernel\ntemplate <typename scalar_t>\n__global__ void linear_gated_kernel(\n const scalar_t* __restrict__ input,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n const scalar_t* __restrict__ mask,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int output_offset = ((b * N + i) * N + j) * out_dim;\n \n scalar_t mask_val = mask ? mask[(b * N + i) * N + j] : scalar_t(1.0);\n \n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n scalar_t gate_val = gate_bias[h];\n \n for (int d = 0; d < in_dim; d++) {\n scalar_t x = input[input_offset + d];\n proj_val += x * proj_weight[d * out_dim + h];\n gate_val += x * gate_weight[d * out_dim + h];\n }\n \n // Sigmoid\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = proj_val * gate_val * mask_val;\n }\n}\n\n// Einsum kernel: bkih,bkjh->bijh\ntemplate <typename scalar_t>\n__global__ void einsum_incoming_kernel(\n const scalar_t* __restrict__ a,\n const scalar_t* __restrict__ b,\n scalar_t* __restrict__ out,\n int batch_size, int N, int H) {\n \n int b_idx = blockIdx.z;\n int h = blockIdx.y;\n int ij = blockIdx.x * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n scalar_t sum = 0;\n for (int k = 0; k < N; k++) {\n int a_idx = ((b_idx * N + k) * N + i) * H + h;\n int b_idx_local = ((b_idx * N + k) * N + j) * H + h;\n sum += a[a_idx] * b[b_idx_local];\n }\n \n out[((b_idx * N + i) * N + j) * H + h] = sum;\n}\n\n// Output projection kernel\ntemplate <typename scalar_t>\n__global__ void output_proj_kernel(\n const scalar_t* __restrict__ pair_repr,\n const scalar_t* __restrict__ norm_out,\n const scalar_t* __restrict__ proj_weight,\n const scalar_t* __restrict__ proj_bias,\n const scalar_t* __restrict__ gate_weight,\n const scalar_t* __restrict__ gate_bias,\n scalar_t* __restrict__ output,\n int batch_size, int N, int in_dim, int out_dim) {\n \n int b = blockIdx.x;\n int ij = blockIdx.y * blockDim.x + threadIdx.x;\n int i = ij / N;\n int j = ij % N;\n \n if (i >= N || j >= N) return;\n \n int input_offset = ((b * N + i) * N + j) * in_dim;\n int pair_offset = ((b * N + i) * N + j) * out_dim;\n int output_offset = pair_offset;\n \n // Compute projection\n for (int h = 0; h < out_dim; h++) {\n scalar_t proj_val = proj_bias[h];\n for (int d = 0; d < in_dim; d++) {\n proj_val += norm_out[input_offset + d] * proj_weight[d * out_dim + h];\n }\n \n // Compute gate\n scalar_t gate_val = gate_bias[h];\n for (int d = 0; d < out_dim; d++) {\n gate_val += pair_repr[pair_offset + d] * gate_weight[d * out_dim + h];\n }\n gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));\n \n output[output_offset + h] = gate_val * proj_val;\n }\n}\n\n// Host functions\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias) {\n auto sizes = input.sizes();\n int total = 1;\n for (int i = 0; i < sizes.size() - 1; i++) total *= sizes[i];\n int dim = sizes[sizes.size() - 1];\n \n auto output = torch::empty_like(input);\n \n const int threads = 256;\n const int blocks = (total + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"layer_norm_cuda\", ([&] {\n layer_norm_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n weight.data_ptr<scalar_t>(),\n bias.data_ptr<scalar_t>(),\n total, dim, dim);\n }));\n \n return output;\n}\n\ntorch::Tensor linear_gated_cuda(\n torch::Tensor input,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n torch::Tensor mask, int out_dim) {\n \n auto sizes = input.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, input.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n bool has_mask = mask.defined() && mask.numel() > 0;\n \n AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"linear_gated_cuda\", ([&] {\n linear_gated_kernel<scalar_t><<<blocks, threads>>>(\n input.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n has_mask ? mask.data_ptr<scalar_t>() : nullptr,\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b) {\n auto sizes = a.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int H = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, H}, a.options());\n \n dim3 blocks((N * N + 255) / 256, H, batch_size);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(a.scalar_type(), \"einsum_incoming_cuda\", ([&] {\n einsum_incoming_kernel<scalar_t><<<blocks, threads>>>(\n a.data_ptr<scalar_t>(),\n b.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, H);\n }));\n \n return output;\n}\n\ntorch::Tensor output_proj_cuda(\n torch::Tensor pair_repr,\n torch::Tensor norm_out,\n torch::Tensor proj_weight, torch::Tensor proj_bias,\n torch::Tensor gate_weight, torch::Tensor gate_bias,\n int out_dim) {\n \n auto sizes = norm_out.sizes();\n int batch_size = sizes[0];\n int N = sizes[1];\n int in_dim = sizes[3];\n \n auto output = torch::zeros({batch_size, N, N, out_dim}, norm_out.options());\n \n dim3 blocks(batch_size, (N * N + 255) / 256);\n const int threads = 256;\n \n AT_DISPATCH_FLOATING_TYPES(norm_out.scalar_type(), \"output_proj_cuda\", ([&] {\n output_proj_kernel<scalar_t><<<blocks, threads>>>(\n pair_repr.data_ptr<scalar_t>(),\n norm_out.data_ptr<scalar_t>(),\n proj_weight.data_ptr<scalar_t>(),\n proj_bias.data_ptr<scalar_t>(),\n gate_weight.data_ptr<scalar_t>(),\n gate_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, N, in_dim, out_dim);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias);\ntorch::Tensor linear_gated_cuda(torch::Tensor input, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, torch::Tensor mask, int out_dim);\ntorch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b);\ntorch::Tensor output_proj_cuda(torch::Tensor pair_repr, torch::Tensor norm_out, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, int out_dim);\n\"\"\"\n\nmodule = load_inline(\n name='tri_mul_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['layer_norm_cuda', 'linear_gated_cuda', 'einsum_incoming_cuda', 'output_proj_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, hidden_dim):\n super().__init__()\n self.layer_norm_in = nn.LayerNorm(pair_dim)\n self.proj_a = nn.Linear(pair_dim, hidden_dim)\n self.gate_a = nn.Linear(pair_dim, hidden_dim)\n self.proj_b = nn.Linear(pair_dim, hidden_dim)\n self.gate_b = nn.Linear(pair_dim, hidden_dim)\n self.layer_norm_out = nn.LayerNorm(hidden_dim)\n self.out_proj = nn.Linear(hidden_dim, pair_dim)\n self.out_gate = nn.Linear(pair_dim, pair_dim)\n \n self.pair_dim = pair_dim\n self.hidden_dim = hidden_dim\n\n def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:\n pair_repr = pair_repr.contiguous()\n pair_mask = pair_mask.contiguous()\n \n # LayerNorm input\n x = module.layer_norm_cuda(\n pair_repr,\n self.layer_norm_in.weight,\n self.layer_norm_in.bias\n )\n \n # Linear + gated for a\n a = module.linear_gated_cuda(\n x,\n self.proj_a.weight.transpose(0, 1).contiguous(),\n self.proj_a.bias,\n self.gate_a.weight.transpose(0, 1).contiguous(),\n self.gate_a.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Linear + gated for b\n b = module.linear_gated_cuda(\n x,\n self.proj_b.weight.transpose(0, 1).contiguous(),\n self.proj_b.bias,\n self.gate_b.weight.transpose(0, 1).contiguous(),\n self.gate_b.bias,\n pair_mask,\n self.hidden_dim\n )\n \n # Einsum incoming\n out = module.einsum_incoming_cuda(a, b)\n \n # LayerNorm output\n out = module.layer_norm_cuda(\n out,\n self.layer_norm_out.weight,\n self.layer_norm_out.bias\n )\n \n # Output projection with gate\n result = module.output_proj_cuda(\n pair_repr,\n out,\n self.out_proj.weight.transpose(0, 1).contiguous(),\n self.out_proj.bias,\n self.out_gate.weight.transpose(0, 1).contiguous(),\n self.out_gate.bias,\n self.pair_dim\n )\n \n return result" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 1.59 μs Runtime stats: mean=1.59μs median=?μs std=0.00μs Numerical precision: max_abs_err=7.75e-07 mean_abs_err=7.38e-08 max_rel_err=3.05e+00 Memory: 35.4 MB peak (ref: 36.13 MB, ratio: 0.9799x) Kernel launches: 62 (ref: 101, fusion ratio: 1.629) Energy: 466.48 mJ/run (ref: 0.0 mJ, ratio: -1x) SOL score: 0.2318
final submitted kernel (359 lines, 11,398 chars) — full source, untruncated
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline
import math

cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>

constexpr int BLOCK_SIZE = 16;

// LayerNorm kernel
template <typename scalar_t>
__global__ void layer_norm_kernel(
    const scalar_t* __restrict__ input,
    scalar_t* __restrict__ output,
    const scalar_t* __restrict__ weight,
    const scalar_t* __restrict__ bias,
    int N, int dim, int stride) {
    
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= N) return;
    
    const scalar_t* in_ptr = input + idx * stride;
    scalar_t* out_ptr = output + idx * stride;
    
    // Compute mean
    scalar_t mean = 0;
    for (int i = 0; i < dim; i++) {
        mean += in_ptr[i];
    }
    mean /= dim;
    
    // Compute variance
    scalar_t var = 0;
    for (int i = 0; i < dim; i++) {
        scalar_t diff = in_ptr[i] - mean;
        var += diff * diff;
    }
    var /= dim;
    
    scalar_t inv_std = rsqrt(var + scalar_t(1e-5));
    
    // Normalize and apply affine transform
    for (int i = 0; i < dim; i++) {
        out_ptr[i] = (in_ptr[i] - mean) * inv_std * weight[i] + bias[i];
    }
}

// Linear + sigmoid gating kernel
template <typename scalar_t>
__global__ void linear_gated_kernel(
    const scalar_t* __restrict__ input,
    const scalar_t* __restrict__ proj_weight,
    const scalar_t* __restrict__ proj_bias,
    const scalar_t* __restrict__ gate_weight,
    const scalar_t* __restrict__ gate_bias,
    scalar_t* __restrict__ output,
    const scalar_t* __restrict__ mask,
    int batch_size, int N, int in_dim, int out_dim) {
    
    int b = blockIdx.x;
    int ij = blockIdx.y * blockDim.x + threadIdx.x;
    int i = ij / N;
    int j = ij % N;
    
    if (i >= N || j >= N) return;
    
    int input_offset = ((b * N + i) * N + j) * in_dim;
    int output_offset = ((b * N + i) * N + j) * out_dim;
    
    scalar_t mask_val = mask ? mask[(b * N + i) * N + j] : scalar_t(1.0);
    
    for (int h = 0; h < out_dim; h++) {
        scalar_t proj_val = proj_bias[h];
        scalar_t gate_val = gate_bias[h];
        
        for (int d = 0; d < in_dim; d++) {
            scalar_t x = input[input_offset + d];
            proj_val += x * proj_weight[d * out_dim + h];
            gate_val += x * gate_weight[d * out_dim + h];
        }
        
        // Sigmoid
        gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));
        
        output[output_offset + h] = proj_val * gate_val * mask_val;
    }
}

// Einsum kernel: bkih,bkjh->bijh
template <typename scalar_t>
__global__ void einsum_incoming_kernel(
    const scalar_t* __restrict__ a,
    const scalar_t* __restrict__ b,
    scalar_t* __restrict__ out,
    int batch_size, int N, int H) {
    
    int b_idx = blockIdx.z;
    int h = blockIdx.y;
    int ij = blockIdx.x * blockDim.x + threadIdx.x;
    int i = ij / N;
    int j = ij % N;
    
    if (i >= N || j >= N) return;
    
    scalar_t sum = 0;
    for (int k = 0; k < N; k++) {
        int a_idx = ((b_idx * N + k) * N + i) * H + h;
        int b_idx_local = ((b_idx * N + k) * N + j) * H + h;
        sum += a[a_idx] * b[b_idx_local];
    }
    
    out[((b_idx * N + i) * N + j) * H + h] = sum;
}

// Output projection kernel
template <typename scalar_t>
__global__ void output_proj_kernel(
    const scalar_t* __restrict__ pair_repr,
    const scalar_t* __restrict__ norm_out,
    const scalar_t* __restrict__ proj_weight,
    const scalar_t* __restrict__ proj_bias,
    const scalar_t* __restrict__ gate_weight,
    const scalar_t* __restrict__ gate_bias,
    scalar_t* __restrict__ output,
    int batch_size, int N, int in_dim, int out_dim) {
    
    int b = blockIdx.x;
    int ij = blockIdx.y * blockDim.x + threadIdx.x;
    int i = ij / N;
    int j = ij % N;
    
    if (i >= N || j >= N) return;
    
    int input_offset = ((b * N + i) * N + j) * in_dim;
    int pair_offset = ((b * N + i) * N + j) * out_dim;
    int output_offset = pair_offset;
    
    // Compute projection
    for (int h = 0; h < out_dim; h++) {
        scalar_t proj_val = proj_bias[h];
        for (int d = 0; d < in_dim; d++) {
            proj_val += norm_out[input_offset + d] * proj_weight[d * out_dim + h];
        }
        
        // Compute gate
        scalar_t gate_val = gate_bias[h];
        for (int d = 0; d < out_dim; d++) {
            gate_val += pair_repr[pair_offset + d] * gate_weight[d * out_dim + h];
        }
        gate_val = scalar_t(1.0) / (scalar_t(1.0) + exp(-gate_val));
        
        output[output_offset + h] = gate_val * proj_val;
    }
}

// Host functions
torch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias) {
    auto sizes = input.sizes();
    int total = 1;
    for (int i = 0; i < sizes.size() - 1; i++) total *= sizes[i];
    int dim = sizes[sizes.size() - 1];
    
    auto output = torch::empty_like(input);
    
    const int threads = 256;
    const int blocks = (total + threads - 1) / threads;
    
    AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), "layer_norm_cuda", ([&] {
        layer_norm_kernel<scalar_t><<<blocks, threads>>>(
            input.data_ptr<scalar_t>(),
            output.data_ptr<scalar_t>(),
            weight.data_ptr<scalar_t>(),
            bias.data_ptr<scalar_t>(),
            total, dim, dim);
    }));
    
    return output;
}

torch::Tensor linear_gated_cuda(
    torch::Tensor input,
    torch::Tensor proj_weight, torch::Tensor proj_bias,
    torch::Tensor gate_weight, torch::Tensor gate_bias,
    torch::Tensor mask, int out_dim) {
    
    auto sizes = input.sizes();
    int batch_size = sizes[0];
    int N = sizes[1];
    int in_dim = sizes[3];
    
    auto output = torch::zeros({batch_size, N, N, out_dim}, input.options());
    
    dim3 blocks(batch_size, (N * N + 255) / 256);
    const int threads = 256;
    
    bool has_mask = mask.defined() && mask.numel() > 0;
    
    AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), "linear_gated_cuda", ([&] {
        linear_gated_kernel<scalar_t><<<blocks, threads>>>(
            input.data_ptr<scalar_t>(),
            proj_weight.data_ptr<scalar_t>(),
            proj_bias.data_ptr<scalar_t>(),
            gate_weight.data_ptr<scalar_t>(),
            gate_bias.data_ptr<scalar_t>(),
            output.data_ptr<scalar_t>(),
            has_mask ? mask.data_ptr<scalar_t>() : nullptr,
            batch_size, N, in_dim, out_dim);
    }));
    
    return output;
}

torch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b) {
    auto sizes = a.sizes();
    int batch_size = sizes[0];
    int N = sizes[1];
    int H = sizes[3];
    
    auto output = torch::zeros({batch_size, N, N, H}, a.options());
    
    dim3 blocks((N * N + 255) / 256, H, batch_size);
    const int threads = 256;
    
    AT_DISPATCH_FLOATING_TYPES(a.scalar_type(), "einsum_incoming_cuda", ([&] {
        einsum_incoming_kernel<scalar_t><<<blocks, threads>>>(
            a.data_ptr<scalar_t>(),
            b.data_ptr<scalar_t>(),
            output.data_ptr<scalar_t>(),
            batch_size, N, H);
    }));
    
    return output;
}

torch::Tensor output_proj_cuda(
    torch::Tensor pair_repr,
    torch::Tensor norm_out,
    torch::Tensor proj_weight, torch::Tensor proj_bias,
    torch::Tensor gate_weight, torch::Tensor gate_bias,
    int out_dim) {
    
    auto sizes = norm_out.sizes();
    int batch_size = sizes[0];
    int N = sizes[1];
    int in_dim = sizes[3];
    
    auto output = torch::zeros({batch_size, N, N, out_dim}, norm_out.options());
    
    dim3 blocks(batch_size, (N * N + 255) / 256);
    const int threads = 256;
    
    AT_DISPATCH_FLOATING_TYPES(norm_out.scalar_type(), "output_proj_cuda", ([&] {
        output_proj_kernel<scalar_t><<<blocks, threads>>>(
            pair_repr.data_ptr<scalar_t>(),
            norm_out.data_ptr<scalar_t>(),
            proj_weight.data_ptr<scalar_t>(),
            proj_bias.data_ptr<scalar_t>(),
            gate_weight.data_ptr<scalar_t>(),
            gate_bias.data_ptr<scalar_t>(),
            output.data_ptr<scalar_t>(),
            batch_size, N, in_dim, out_dim);
    }));
    
    return output;
}
"""

cpp_source = """
torch::Tensor layer_norm_cuda(torch::Tensor input, torch::Tensor weight, torch::Tensor bias);
torch::Tensor linear_gated_cuda(torch::Tensor input, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, torch::Tensor mask, int out_dim);
torch::Tensor einsum_incoming_cuda(torch::Tensor a, torch::Tensor b);
torch::Tensor output_proj_cuda(torch::Tensor pair_repr, torch::Tensor norm_out, torch::Tensor proj_weight, torch::Tensor proj_bias, torch::Tensor gate_weight, torch::Tensor gate_bias, int out_dim);
"""

module = load_inline(
    name='tri_mul_cuda',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['layer_norm_cuda', 'linear_gated_cuda', 'einsum_incoming_cuda', 'output_proj_cuda'],
    extra_cuda_cflags=['-O3'],
    verbose=False
)


class ModelNew(nn.Module):
    def __init__(self, pair_dim, hidden_dim):
        super().__init__()
        self.layer_norm_in = nn.LayerNorm(pair_dim)
        self.proj_a = nn.Linear(pair_dim, hidden_dim)
        self.gate_a = nn.Linear(pair_dim, hidden_dim)
        self.proj_b = nn.Linear(pair_dim, hidden_dim)
        self.gate_b = nn.Linear(pair_dim, hidden_dim)
        self.layer_norm_out = nn.LayerNorm(hidden_dim)
        self.out_proj = nn.Linear(hidden_dim, pair_dim)
        self.out_gate = nn.Linear(pair_dim, pair_dim)
        
        self.pair_dim = pair_dim
        self.hidden_dim = hidden_dim

    def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:
        pair_repr = pair_repr.contiguous()
        pair_mask = pair_mask.contiguous()
        
        # LayerNorm input
        x = module.layer_norm_cuda(
            pair_repr,
            self.layer_norm_in.weight,
            self.layer_norm_in.bias
        )
        
        # Linear + gated for a
        a = module.linear_gated_cuda(
            x,
            self.proj_a.weight.transpose(0, 1).contiguous(),
            self.proj_a.bias,
            self.gate_a.weight.transpose(0, 1).contiguous(),
            self.gate_a.bias,
            pair_mask,
            self.hidden_dim
        )
        
        # Linear + gated for b
        b = module.linear_gated_cuda(
            x,
            self.proj_b.weight.transpose(0, 1).contiguous(),
            self.proj_b.bias,
            self.gate_b.weight.transpose(0, 1).contiguous(),
            self.gate_b.bias,
            pair_mask,
            self.hidden_dim
        )
        
        # Einsum incoming
        out = module.einsum_incoming_cuda(a, b)
        
        # LayerNorm output
        out = module.layer_norm_cuda(
            out,
            self.layer_norm_out.weight,
            self.layer_norm_out.bias
        )
        
        # Output projection with gate
        result = module.output_proj_cuda(
            pair_repr,
            out,
            self.out_proj.weight.transpose(0, 1).contiguous(),
            self.out_proj.bias,
            self.out_gate.weight.transpose(0, 1).contiguous(),
            self.out_gate.bias,
            self.pair_dim
        )
        
        return result