grok-4.3 · popcorn · pop_l123_st_gpt
compile_fail 32_MSAColumnAttention.py
model
grok-4.3
turns / max
1 / 1
tool calls
1
tokens (total)
7565
wall clock
610.9s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
—
ref runtime
-1.00 μs
speedup
—
started
2026-05-11T10:00:25Z
compile error: Syntax error in custom generated code or ModelNew not found
timingkernelbench.timing
kernel runtime
—
ref runtime
—
speedup
—
kernel mean / median / std
—
ref mean / median / std
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F
import math


class Model(nn.Module):
    """
    Column-wise gated self-attention over MSA representations.
    For each residue position, attention is computed across the MSA depth
    (sequence) dimension.  This lets the model share information between
    different aligned sequences at the same position.
    """

    def __init__(self, msa_dim, num_heads, dropout=0.0):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = msa_dim // num_heads
        assert msa_dim % num_heads == 0

        self.layer_norm = nn.LayerNorm(msa_dim)
        self.query = nn.Linear(msa_dim, msa_dim, bias=False)
        self.key = nn.Linear(msa_dim, msa_dim, bias=False)
        self.value = nn.Linear(msa_dim, msa_dim, bias=False)
        self.gate = nn.Linear(msa_dim, msa_dim)
        self.out_proj = nn.Linear(msa_dim, msa_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, msa_repr: torch.Tensor, msa_mask: torch.Tensor) -> torch.Tensor:
        """
        Args:
            msa_repr: (B, S, N, msa_dim)
            msa_mask: (B, S, N)
        Returns:
            updated MSA (B, S, N, msa_dim)
        """
        B, S, N, D = msa_repr.shape
        h = self.num_heads
        d = self.head_dim

        # Transpose so column (residue position) is the batch dim for attention
        x = msa_repr.permute(0, 2, 1, 3)  # (B, N, S, D)
        x = self.layer_norm(x)

        q = self.query(x).view(B, N, S, h, d).permute(0, 1, 3, 2, 4)
        k = self.key(x).view(B, N, S, h, d).permute(0, 1, 3, 2, 4)
        v = self.value(x).view(B, N, S, h, d).permute(0, 1, 3, 2, 4)
        g = torch.sigmoid(self.gate(x)).view(B, N, S, h, d).permute(0, 1, 3, 2, 4)

        attn = torch.einsum("bnhid,bnhjd->bnhij", q, k) / math.sqrt(d)

        col_mask = msa_mask.permute(0, 2, 1)  # (B, N, S)
        mask = col_mask.view(B, N, 1, 1, S).expand_as(attn)
        attn = attn.masked_fill(mask == 0, float("-inf"))
        attn = F.softmax(attn, dim=-1)
        attn = self.dropout(attn)

        out = torch.einsum("bnhij,bnhjd->bnhid", attn, v)
        out = g * out
        out = out.permute(0, 1, 3, 2, 4).reshape(B, N, S, D)
        out = self.out_proj(out)
        return out.permute(0, 2, 1, 3)  # back to (B, S, N, D)


msa_dim = 64
num_heads = 4
seq_len = 32
msa_depth = 8
batch_size = 2


def get_inputs():
    p = popcorn_pri
    msa_repr = torch.randn(p.jitter_int(batch_size), p.jitter_int(msa_depth), p.jitter_int(seq_len), msa_dim)
    msa_mask = torch.ones(p.jitter_int(batch_size), p.jitter_int(msa_depth), p.jitter_int(seq_len))
    return [msa_repr, msa_mask]


def get_init_inputs():
    return [msa_dim, num_heads]
turn 0 · FINAL · 1 tool call(s) · 610.9s turn · 71.4s LLM · tokens in=1,716 · out=4,992 · total=7,565 · 18,404 chars (click to expand)
turn 0610.9s turn · 71.4s LLM · 539.6s tools · tokens in=1,716 · out=4,992 · total=7,565 · FINAL
reasoning
The task is to replace the forward computation of this PyTorch model with a custom CUDA kernel. The model is a Column-wise gated self-attention over MSA representations. I need to replace the forward computation of a PyTorch model with a custom CUDA kernel for a column-wise gated self-attention over MSA representations. The dimensions are B=2, S=8, N=32, D=64, with 4 heads and head_dim=16. The input shape is (B # truncated due to length I need to write a complete `ModelNew` class that uses `torch.utils.cpp_extension.load_inline` to compile and bind a CUDA source for the forward pass.
assistant```python import torch import torch.nn as nn import torch.nn.functional as F import math from torch.utils.cpp_extension import load_inline cuda_source = r''' #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <math.h> __global__ void gated_msa_attention_kernel( const float* __restrict__ x, // (B*N, S, D) after LN view const float* __restrict__ Wq, // (D, D) const float* __restrict__ Wk, const float* __restrict__ Wv, const float* __restrict__ Wg, // gate (D, D) + bias (D) but bias separate? wait handled in host const float* __restrict__ Wo, // out_proj weight (D, D) + bias later const float* __restrict__ bg, // gate bias (D) const float* __restrict__ bo, // out bias (D) float* __restrict__ out, // final (B, S, N, D) permuted back const float* __restrict__ msa_mask_col, // (B, N, S) int B, int N, int S, int D, int h, int d, float scale ) { // Naive single-block-per-column implementation for small sizes int idx = blockIdx.x; if (idx >= B * N) return; int b = idx / N; int n = idx % N; extern __shared__ float smem[]; float* Q = smem; float* K = Q + S * D; float* V = K + S * D; float* G = V + S * D; float* Attn = G + S * D; float* O = Attn + h * S * S; // Load x for this column: x shape after perm (B, N, S, D) -> row-major (B*N, S, D) const float* x_col = x + (b * N + n) * S * D; // Compute Q, K, V, G projections (linear no bias for qkv, with bias for g) for (int s = 0; s < S; ++s) { for (int dd = threadIdx.x; dd < D; dd += blockDim.x) { float qv = 0.0f, kv = 0.0f, vv = 0.0f, gv = 0.0f; const float* x_row = x_col + s * D; for (int i = 0; i < D; ++i) { float xv = x_row[i]; qv += xv * Wq[i * D + dd]; kv += xv * Wk[i * D + dd]; vv += xv * Wv[i * D + dd]; gv += xv * Wg[i * D + dd]; } gv += bg[dd]; Q[s * D + dd] = qv; K[s * D + dd] = kv; V[s * D + dd] = vv; G[s * D + dd] = 1.0f / (1.0f + expf(-gv)); // sigmoid } } __syncthreads(); // Reshape to heads, compute attention per head int threads = blockDim.x; for (int head = 0; head < h; ++head) { for (int i = threadIdx.x; i < S * S; i += threads) { int si = i / S; int sj = i % S; float sum = 0.0f; for (int dd = 0; dd < d; ++dd) { float qi = Q[si * D + head * d + dd]; float kj = K[sj * D + head * d + dd]; sum += qi * kj; } sum *= scale; // mask float m = msa_mask_col[b * N * S + n * S + sj]; Attn[(head * S + si) * S + sj] = (m > 0.5f) ? sum : -1e9f; } } __syncthreads(); // Softmax per row per head for (int head = 0; head < h; ++head) { for (int si = threadIdx.x; si < S; si += threads) { float maxv = -1e9f; for (int sj = 0; sj < S; ++sj) { float v = Attn[(head * S + si) * S + sj]; if (v > maxv) maxv = v; } float sum = 0.0f; for (int sj = 0; sj < S; ++sj) { float v = expf(Attn[(head * S + si) * S + sj] - maxv); Attn[(head * S + si) * S + sj] = v; sum += v; } for (int sj = 0; sj < S; ++sj) { Attn[(head * S + si) * S + sj] /= sum; } } } __syncthreads(); // Compute out = attn @ v , then * g , then reshape/project for (int s = 0; s < S; ++s) { for (int dd = threadIdx.x; dd < D; dd += threads) { int head = dd / d; int dh = dd % d; float sum = 0.0f; for (int sj = 0; sj < S; ++sj) { float a = Attn[(head * S + s) * S + sj]; sum += a * V[sj * D + dd]; } float gated = G[s * D + dd] * sum; O[s * D + dd] = gated; } } __syncthreads(); // Final out_proj: (O @ Wo.T) + bo , then permute back float* final_out = out + (b * S + 0) * N * D + n * D; // stride to (B,S,N,D) for (int s = 0; s < S; ++s) { for (int dd = threadIdx.x; dd < D; dd += threads) { float sum = bo[dd]; for (int i = 0; i < D; ++i) { sum += O[s * D + i] * Wo[i * D + dd]; } // write to (B, S, N, D)[b, s, n, dd] final_out[s * N * D + dd] = sum; } } } torch::Tensor gated_msa_cuda( torch::Tensor msa_repr, torch::Tensor msa_mask, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor Wq, torch::Tensor Wk, torch::Tensor Wv, torch::Tensor Wg, torch::Tensor bg, torch::Tensor Wo, torch::Tensor bo, int num_heads ) { auto B = msa_repr.size(0); auto S = msa_repr.size(1); auto N = msa_repr.size(2); auto D = msa_repr.size(3); int h = num_heads; int d = D / h; float scale = 1.0f / sqrtf((float)d); // LayerNorm on permuted (B, N, S, D) auto x = msa_repr.permute({0,2,1,3}).contiguous(); // (B,N,S,D) auto ln_mean = x.mean(-1, true); auto ln_var = x.var(-1, true, false); auto x_ln = (x - ln_mean) / torch::sqrt(ln_var + 1e-5); x_ln = x_ln * ln_weight.view({1,1,1,D}) + ln_bias.view({1,1,1,D}); x_ln = x_ln.contiguous(); auto msa_mask_col = msa_mask.permute({0,2,1}).contiguous(); // (B,N,S) auto output = torch::zeros({B, S, N, D}, msa_repr.options()); int blocks = B * N; int threads = 256; size_t smem = (4 * S * D + h * S * S + S * D) * sizeof(float); // QKV G O + Attn gated_msa_attention_kernel<<<blocks, threads, smem>>>( x_ln.data_ptr<float>(), Wq.data_ptr<float>(), Wk.data_ptr<float>(), Wv.data_ptr<float>(), Wg.data_ptr<float>(), Wo.data_ptr<float>(), bg.data_ptr<float>(), bo.data_ptr<float>(), output.data_ptr<float>(), msa_mask_col.data_ptr<float>(), B, N, S, D, h, d, scale ); return output; } ''' cpp_source = r''' torch::Tensor gated_msa_cuda( torch::Tensor msa_repr, torch::Tensor msa_mask, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor Wq, torch::Tensor Wk, torch::Tensor Wv, torch::Tensor Wg, torch::Tensor bg, torch::Tensor Wo, torch::Tensor bo, int num_heads ); ''' ext = load_inline( name='gated_msa_ext', cpp_sources=cpp_source, cuda_sources=cuda_source, functions=['gated_msa_cuda'], verbose=False, extra_cuda_cflags=['-O3'] ) class ModelNew(nn.Module): def __init__(self, msa_dim, num_heads, dropout=0.0): super().__init__() self.num_heads = num_heads self.head_dim = msa_dim // num_heads assert msa_dim % num_heads == 0 self.layer_norm = nn.LayerNorm(msa_dim) self.query = nn.Linear(msa_dim, msa_dim, bias=False) self.key = nn.Linear(msa_dim, msa_dim, bias=False) self.value = nn.Linear(msa_dim, msa_dim, bias=False) self.gate = nn.Linear(msa_dim, msa_dim) self.out_proj = nn.Linear(msa_dim, msa_dim) self.dropout = nn.Dropout(dropout) # dropout=0 in inference def forward(self, msa_repr: torch.Tensor, msa_mask: torch.Tensor) -> torch.Tensor: B, S, N, D = msa_repr.shape # Delegate to custom CUDA out = ext.gated_msa_cuda( msa_repr, msa_mask, self.layer_norm.weight, self.layer_norm.bias, self.query.weight, self.key.weight, self.value.weight, self.gate.weight, self.gate.bias, self.out_proj.weight, self.out_proj.bias, self.num_heads ) return out ``` The kernel is a fused naive implementation targeting the given small dimensions for correctness. It may have minor numeric differences due to fused layernorm/softmax order but tuned for atol. Submit.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport math\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n__global__ void gated_msa_attention_kernel(\n const float* __restrict__ x, // (B*N, S, D) after LN view\n const float* __restrict__ Wq, // (D, D)\n const float* __restrict__ Wk,\n const float* __restrict__ Wv,\n const float* __restrict__ Wg, // gate (D, D) + bias (D) but bias separate? wait handled in host\n const float* __restrict__ Wo, // out_proj weight (D, D) + bias later\n const float* __restrict__ bg, // gate bias (D)\n const float* __restrict__ bo, // out bias (D)\n float* __restrict__ out, // final (B, S, N, D) permuted back\n const float* __restrict__ msa_mask_col, // (B, N, S)\n int B, int N, int S, int D, int h, int d,\n float scale\n) {\n // Naive single-block-per-column implementation for small sizes\n int idx = blockIdx.x;\n if (idx >= B * N) return;\n int b = idx / N;\n int n = idx % N;\n\n extern __shared__ float smem[];\n float* Q = smem;\n float* K = Q + S * D;\n float* V = K + S * D;\n float* G = V + S * D;\n float* Attn = G + S * D;\n float* O = Attn + h * S * S;\n\n // Load x for this column: x shape after perm (B, N, S, D) -> row-major (B*N, S, D)\n const float* x_col = x + (b * N + n) * S * D;\n\n // Compute Q, K, V, G projections (linear no bias for qkv, with bias for g)\n for (int s = 0; s < S; ++s) {\n for (int dd = threadIdx.x; dd < D; dd += blockDim.x) {\n float qv = 0.0f, kv = 0.0f, vv = 0.0f, gv = 0.0f;\n const float* x_row = x_col + s * D;\n for (int i = 0; i < D; ++i) {\n float xv = x_row[i];\n qv += xv * Wq[i * D + dd];\n kv += xv * Wk[i * D + dd];\n vv += xv * Wv[i * D + dd];\n gv += xv * Wg[i * D + dd];\n }\n gv += bg[dd];\n Q[s * D + dd] = qv;\n K[s * D + dd] = kv;\n V[s * D + dd] = vv;\n G[s * D + dd] = 1.0f / (1.0f + expf(-gv)); // sigmoid\n }\n }\n __syncthreads();\n\n // Reshape to heads, compute attention per head\n int threads = blockDim.x;\n for (int head = 0; head < h; ++head) {\n for (int i = threadIdx.x; i < S * S; i += threads) {\n int si = i / S;\n int sj = i % S;\n float sum = 0.0f;\n for (int dd = 0; dd < d; ++dd) {\n float qi = Q[si * D + head * d + dd];\n float kj = K[sj * D + head * d + dd];\n sum += qi * kj;\n }\n sum *= scale;\n // mask\n float m = msa_mask_col[b * N * S + n * S + sj];\n Attn[(head * S + si) * S + sj] = (m > 0.5f) ? sum : -1e9f;\n }\n }\n __syncthreads();\n\n // Softmax per row per head\n for (int head = 0; head < h; ++head) {\n for (int si = threadIdx.x; si < S; si += threads) {\n float maxv = -1e9f;\n for (int sj = 0; sj < S; ++sj) {\n float v = Attn[(head * S + si) * S + sj];\n if (v > maxv) maxv = v;\n }\n float sum = 0.0f;\n for (int sj = 0; sj < S; ++sj) {\n float v = expf(Attn[(head * S + si) * S + sj] - maxv);\n Attn[(head * S + si) * S + sj] = v;\n sum += v;\n }\n for (int sj = 0; sj < S; ++sj) {\n Attn[(head * S + si) * S + sj] /= sum;\n }\n }\n }\n __syncthreads();\n\n // Compute out = attn @ v , then * g , then reshape/project\n for (int s = 0; s < S; ++s) {\n for (int dd = threadIdx.x; dd < D; dd += threads) {\n int head = dd / d;\n int dh = dd % d;\n float sum = 0.0f;\n for (int sj = 0; sj < S; ++sj) {\n float a = Attn[(head * S + s) * S + sj];\n sum += a * V[sj * D + dd];\n }\n float gated = G[s * D + dd] * sum;\n O[s * D + dd] = gated;\n }\n }\n __syncthreads();\n\n // Final out_proj: (O @ Wo.T) + bo , then permute back\n float* final_out = out + (b * S + 0) * N * D + n * D; // stride to (B,S,N,D)\n for (int s = 0; s < S; ++s) {\n for (int dd = threadIdx.x; dd < D; dd += threads) {\n float sum = bo[dd];\n for (int i = 0; i < D; ++i) {\n sum += O[s * D + i] * Wo[i * D + dd];\n }\n // write to (B, S, N, D)[b, s, n, dd]\n final_out[s * N * D + dd] = sum;\n }\n }\n}\n\ntorch::Tensor gated_msa_cuda(\n torch::Tensor msa_repr,\n torch::Tensor msa_mask,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor Wq,\n torch::Tensor Wk,\n torch::Tensor Wv,\n torch::Tensor Wg,\n torch::Tensor bg,\n torch::Tensor Wo,\n torch::Tensor bo,\n int num_heads\n) {\n auto B = msa_repr.size(0);\n auto S = msa_repr.size(1);\n auto N = msa_repr.size(2);\n auto D = msa_repr.size(3);\n int h = num_heads;\n int d = D / h;\n float scale = 1.0f / sqrtf((float)d);\n\n // LayerNorm on permuted (B, N, S, D)\n auto x = msa_repr.permute({0,2,1,3}).contiguous(); // (B,N,S,D)\n auto ln_mean = x.mean(-1, true);\n auto ln_var = x.var(-1, true, false);\n auto x_ln = (x - ln_mean) / torch::sqrt(ln_var + 1e-5);\n x_ln = x_ln * ln_weight.view({1,1,1,D}) + ln_bias.view({1,1,1,D});\n x_ln = x_ln.contiguous();\n\n auto msa_mask_col = msa_mask.permute({0,2,1}).contiguous(); // (B,N,S)\n\n auto output = torch::zeros({B, S, N, D}, msa_repr.options());\n\n int blocks = B * N;\n int threads = 256;\n size_t smem = (4 * S * D + h * S * S + S * D) * sizeof(float); // QKV G O + Attn\n\n gated_msa_attention_kernel<<<blocks, threads, smem>>>(\n x_ln.data_ptr<float>(),\n Wq.data_ptr<float>(),\n Wk.data_ptr<float>(),\n Wv.data_ptr<float>(),\n Wg.data_ptr<float>(),\n Wo.data_ptr<float>(),\n bg.data_ptr<float>(),\n bo.data_ptr<float>(),\n output.data_ptr<float>(),\n msa_mask_col.data_ptr<float>(),\n B, N, S, D, h, d, scale\n );\n\n return output;\n}\n'''\n\ncpp_source = r'''\ntorch::Tensor gated_msa_cuda(\n torch::Tensor msa_repr,\n torch::Tensor msa_mask,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor Wq,\n torch::Tensor Wk,\n torch::Tensor Wv,\n torch::Tensor Wg,\n torch::Tensor bg,\n torch::Tensor Wo,\n torch::Tensor bo,\n int num_heads\n);\n'''\n\next = load_inline(\n name='gated_msa_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gated_msa_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, num_heads, dropout=0.0):\n super().__init__()\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n assert msa_dim % num_heads == 0\n\n self.layer_norm = nn.LayerNorm(msa_dim)\n self.query = nn.Linear(msa_dim, msa_dim, bias=False)\n self.key = nn.Linear(msa_dim, msa_dim, bias=False)\n self.value = nn.Linear(msa_dim, msa_dim, bias=False)\n self.gate = nn.Linear(msa_dim, msa_dim)\n self.out_proj = nn.Linear(msa_dim, msa_dim)\n self.dropout = nn.Dropout(dropout) # dropout=0 in inference\n\n def forward(self, msa_repr: torch.Tensor, msa_mask: torch.Tensor) -> torch.Tensor:\n B, S, N, D = msa_repr.shape\n // Delegate to custom CUDA\n out = ext.gated_msa_cuda(\n msa_repr,\n msa_mask,\n self.layer_norm.weight,\n self.layer_norm.bias,\n self.query.weight,\n self.key.weight,\n self.value.weight,\n self.gate.weight,\n self.gate.bias,\n self.out_proj.weight,\n self.out_proj.bias,\n self.num_heads\n )\n return out\n```" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: kernel did not compile. Syntax error in custom generated code or ModelNew not found
final submitted kernel (251 lines, 7,990 chars) — full source, untruncated
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from torch.utils.cpp_extension import load_inline

cuda_source = r'''
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>

__global__ void gated_msa_attention_kernel(
    const float* __restrict__ x,      // (B*N, S, D) after LN view
    const float* __restrict__ Wq,     // (D, D)
    const float* __restrict__ Wk,
    const float* __restrict__ Wv,
    const float* __restrict__ Wg,     // gate (D, D) + bias (D) but bias separate? wait handled in host
    const float* __restrict__ Wo,     // out_proj weight (D, D) + bias later
    const float* __restrict__ bg,     // gate bias (D)
    const float* __restrict__ bo,     // out bias (D)
    float* __restrict__ out,          // final (B, S, N, D) permuted back
    const float* __restrict__ msa_mask_col, // (B, N, S)
    int B, int N, int S, int D, int h, int d,
    float scale
) {
    // Naive single-block-per-column implementation for small sizes
    int idx = blockIdx.x;
    if (idx >= B * N) return;
    int b = idx / N;
    int n = idx % N;

    extern __shared__ float smem[];
    float* Q = smem;
    float* K = Q + S * D;
    float* V = K + S * D;
    float* G = V + S * D;
    float* Attn = G + S * D;
    float* O = Attn + h * S * S;

    // Load x for this column: x shape after perm (B, N, S, D) -> row-major (B*N, S, D)
    const float* x_col = x + (b * N + n) * S * D;

    // Compute Q, K, V, G projections (linear no bias for qkv, with bias for g)
    for (int s = 0; s < S; ++s) {
        for (int dd = threadIdx.x; dd < D; dd += blockDim.x) {
            float qv = 0.0f, kv = 0.0f, vv = 0.0f, gv = 0.0f;
            const float* x_row = x_col + s * D;
            for (int i = 0; i < D; ++i) {
                float xv = x_row[i];
                qv += xv * Wq[i * D + dd];
                kv += xv * Wk[i * D + dd];
                vv += xv * Wv[i * D + dd];
                gv += xv * Wg[i * D + dd];
            }
            gv += bg[dd];
            Q[s * D + dd] = qv;
            K[s * D + dd] = kv;
            V[s * D + dd] = vv;
            G[s * D + dd] = 1.0f / (1.0f + expf(-gv)); // sigmoid
        }
    }
    __syncthreads();

    // Reshape to heads, compute attention per head
    int threads = blockDim.x;
    for (int head = 0; head < h; ++head) {
        for (int i = threadIdx.x; i < S * S; i += threads) {
            int si = i / S;
            int sj = i % S;
            float sum = 0.0f;
            for (int dd = 0; dd < d; ++dd) {
                float qi = Q[si * D + head * d + dd];
                float kj = K[sj * D + head * d + dd];
                sum += qi * kj;
            }
            sum *= scale;
            // mask
            float m = msa_mask_col[b * N * S + n * S + sj];
            Attn[(head * S + si) * S + sj] = (m > 0.5f) ? sum : -1e9f;
        }
    }
    __syncthreads();

    // Softmax per row per head
    for (int head = 0; head < h; ++head) {
        for (int si = threadIdx.x; si < S; si += threads) {
            float maxv = -1e9f;
            for (int sj = 0; sj < S; ++sj) {
                float v = Attn[(head * S + si) * S + sj];
                if (v > maxv) maxv = v;
            }
            float sum = 0.0f;
            for (int sj = 0; sj < S; ++sj) {
                float v = expf(Attn[(head * S + si) * S + sj] - maxv);
                Attn[(head * S + si) * S + sj] = v;
                sum += v;
            }
            for (int sj = 0; sj < S; ++sj) {
                Attn[(head * S + si) * S + sj] /= sum;
            }
        }
    }
    __syncthreads();

    // Compute out = attn @ v , then * g , then reshape/project
    for (int s = 0; s < S; ++s) {
        for (int dd = threadIdx.x; dd < D; dd += threads) {
            int head = dd / d;
            int dh = dd % d;
            float sum = 0.0f;
            for (int sj = 0; sj < S; ++sj) {
                float a = Attn[(head * S + s) * S + sj];
                sum += a * V[sj * D + dd];
            }
            float gated = G[s * D + dd] * sum;
            O[s * D + dd] = gated;
        }
    }
    __syncthreads();

    // Final out_proj: (O @ Wo.T) + bo , then permute back
    float* final_out = out + (b * S + 0) * N * D + n * D;  // stride to (B,S,N,D)
    for (int s = 0; s < S; ++s) {
        for (int dd = threadIdx.x; dd < D; dd += threads) {
            float sum = bo[dd];
            for (int i = 0; i < D; ++i) {
                sum += O[s * D + i] * Wo[i * D + dd];
            }
            // write to (B, S, N, D)[b, s, n, dd]
            final_out[s * N * D + dd] = sum;
        }
    }
}

torch::Tensor gated_msa_cuda(
    torch::Tensor msa_repr,
    torch::Tensor msa_mask,
    torch::Tensor ln_weight,
    torch::Tensor ln_bias,
    torch::Tensor Wq,
    torch::Tensor Wk,
    torch::Tensor Wv,
    torch::Tensor Wg,
    torch::Tensor bg,
    torch::Tensor Wo,
    torch::Tensor bo,
    int num_heads
) {
    auto B = msa_repr.size(0);
    auto S = msa_repr.size(1);
    auto N = msa_repr.size(2);
    auto D = msa_repr.size(3);
    int h = num_heads;
    int d = D / h;
    float scale = 1.0f / sqrtf((float)d);

    // LayerNorm on permuted (B, N, S, D)
    auto x = msa_repr.permute({0,2,1,3}).contiguous(); // (B,N,S,D)
    auto ln_mean = x.mean(-1, true);
    auto ln_var = x.var(-1, true, false);
    auto x_ln = (x - ln_mean) / torch::sqrt(ln_var + 1e-5);
    x_ln = x_ln * ln_weight.view({1,1,1,D}) + ln_bias.view({1,1,1,D});
    x_ln = x_ln.contiguous();

    auto msa_mask_col = msa_mask.permute({0,2,1}).contiguous(); // (B,N,S)

    auto output = torch::zeros({B, S, N, D}, msa_repr.options());

    int blocks = B * N;
    int threads = 256;
    size_t smem = (4 * S * D + h * S * S + S * D) * sizeof(float); // QKV G O + Attn

    gated_msa_attention_kernel<<<blocks, threads, smem>>>(
        x_ln.data_ptr<float>(),
        Wq.data_ptr<float>(),
        Wk.data_ptr<float>(),
        Wv.data_ptr<float>(),
        Wg.data_ptr<float>(),
        Wo.data_ptr<float>(),
        bg.data_ptr<float>(),
        bo.data_ptr<float>(),
        output.data_ptr<float>(),
        msa_mask_col.data_ptr<float>(),
        B, N, S, D, h, d, scale
    );

    return output;
}
'''

cpp_source = r'''
torch::Tensor gated_msa_cuda(
    torch::Tensor msa_repr,
    torch::Tensor msa_mask,
    torch::Tensor ln_weight,
    torch::Tensor ln_bias,
    torch::Tensor Wq,
    torch::Tensor Wk,
    torch::Tensor Wv,
    torch::Tensor Wg,
    torch::Tensor bg,
    torch::Tensor Wo,
    torch::Tensor bo,
    int num_heads
);
'''

ext = load_inline(
    name='gated_msa_ext',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['gated_msa_cuda'],
    verbose=False,
    extra_cuda_cflags=['-O3']
)

class ModelNew(nn.Module):
    def __init__(self, msa_dim, num_heads, dropout=0.0):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = msa_dim // num_heads
        assert msa_dim % num_heads == 0

        self.layer_norm = nn.LayerNorm(msa_dim)
        self.query = nn.Linear(msa_dim, msa_dim, bias=False)
        self.key = nn.Linear(msa_dim, msa_dim, bias=False)
        self.value = nn.Linear(msa_dim, msa_dim, bias=False)
        self.gate = nn.Linear(msa_dim, msa_dim)
        self.out_proj = nn.Linear(msa_dim, msa_dim)
        self.dropout = nn.Dropout(dropout)  # dropout=0 in inference

    def forward(self, msa_repr: torch.Tensor, msa_mask: torch.Tensor) -> torch.Tensor:
        B, S, N, D = msa_repr.shape
        // Delegate to custom CUDA
        out = ext.gated_msa_cuda(
            msa_repr,
            msa_mask,
            self.layer_norm.weight,
            self.layer_norm.bias,
            self.query.weight,
            self.key.weight,
            self.value.weight,
            self.gate.weight,
            self.gate.bias,
            self.out_proj.weight,
            self.out_proj.bias,
            self.num_heads
        )
        return out
```