FW-GLM-5-1 · popcorn · pop_l123_default_gpt
correct 8_EvoformerBlock.py
model
FW-GLM-5-1
turns / max
11 / 12
tool calls
11
tokens (total)
303906
wall clock
3465.9s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
0.94 μs
ref runtime
1.16 μs
speedup
1.24x
started
2026-05-11T11:10:06Z
timingkernelbench.timing
kernel runtime
0.94 μs
ref runtime
1.16 μs
speedup
1.24x
kernel mean / median / std
0.94 / — / 1.25 μs
ref mean / median / std
1.16 / — / 2.40 μs
numerical precisioneval_kernel_against_ref
max abs error
7.82e-07
mean abs error
6.03e-08
max rel error
2.50
mean rel error
3.78e-06
gpu memoryextended_metrics.measure_memory
kernel peak
57.03 MB
ref peak
51.28 MB
kernel / ref
1.11x
kernel launches / fusiontorch.profiler
kernel launches
260
ref launches
258
fusion ratio (ref / kernel)
0.99x
total cuda time
20587 μs
energyNVML (pynvml)
kernel mJ / run
241.82
ref mJ / run
0.00
ratio (ref / kernel)
—
avg power
390.77 W
ref avg power
0.00 W
SOL (speed-of-light)heuristic_from_speedup
SOL score (0–1)
0.124
dram util %
—
compute util %
—
bottleneck
unknown
dominant pipe
—
arithmetic intensity
—
ridge point
—
roofline / occupancyheuristic
dram bandwidth
—
dram util %
—
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
—
bottleneck
—
dominant pipe %
—
arithmetic intensity
—
ridge point
—
L1 hit %
—
L2 hit %
—
ld sectors / req
—
st sectors / req
—
regs / thread
—
smem / block
—
block size
—
peak bw
—
peak fp32 tflops
—
peak fp16 tflops
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F
import math


class MSARowSelfAttention(nn.Module):
    def __init__(self, msa_dim, num_heads):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = msa_dim // num_heads
        self.norm = nn.LayerNorm(msa_dim)
        self.q = nn.Linear(msa_dim, msa_dim, bias=False)
        self.k = nn.Linear(msa_dim, msa_dim, bias=False)
        self.v = nn.Linear(msa_dim, msa_dim, bias=False)
        self.out = nn.Linear(msa_dim, msa_dim)

    def forward(self, msa):
        B, S, N, D = msa.shape
        h, d = self.num_heads, self.head_dim
        x = self.norm(msa)
        q = self.q(x).view(B, S, N, h, d).permute(0, 1, 3, 2, 4)
        k = self.k(x).view(B, S, N, h, d).permute(0, 1, 3, 2, 4)
        v = self.v(x).view(B, S, N, h, d).permute(0, 1, 3, 2, 4)
        attn = (q @ k.transpose(-1, -2)) / math.sqrt(d)
        attn = F.softmax(attn, dim=-1)
        out = (attn @ v).permute(0, 1, 3, 2, 4).reshape(B, S, N, D)
        return self.out(out)


class OuterProductMeanSmall(nn.Module):
    def __init__(self, msa_dim, pair_dim, hidden=32):
        super().__init__()
        self.norm = nn.LayerNorm(msa_dim)
        self.a = nn.Linear(msa_dim, hidden)
        self.b = nn.Linear(msa_dim, hidden)
        self.out = nn.Linear(hidden * hidden, pair_dim)

    def forward(self, msa):
        x = self.norm(msa)
        a = self.a(x)
        b = self.b(x)
        outer = torch.einsum("bsih,bsjk->bijhk", a, b)
        S = msa.shape[1]
        outer = outer / max(S, 1)
        B, N1, N2, H1, H2 = outer.shape
        return self.out(outer.reshape(B, N1, N2, H1 * H2))


class TriMulOut(nn.Module):
    def __init__(self, pair_dim, hidden):
        super().__init__()
        self.norm = nn.LayerNorm(pair_dim)
        self.pa = nn.Linear(pair_dim, hidden)
        self.pb = nn.Linear(pair_dim, hidden)
        self.ga = nn.Linear(pair_dim, hidden)
        self.gb = nn.Linear(pair_dim, hidden)
        self.norm_out = nn.LayerNorm(hidden)
        self.out = nn.Linear(hidden, pair_dim)
        self.gate_out = nn.Linear(pair_dim, pair_dim)

    def forward(self, pair):
        x = self.norm(pair)
        a = self.pa(x) * torch.sigmoid(self.ga(x))
        b = self.pb(x) * torch.sigmoid(self.gb(x))
        o = torch.einsum("bikh,bjkh->bijh", a, b)
        o = self.out(self.norm_out(o))
        return torch.sigmoid(self.gate_out(pair)) * o


class Model(nn.Module):
    """
    Full Evoformer block from AlphaFold2 combining MSA row self-attention,
    outer-product mean update, and triangular multiplicative update of the
    pair representation.  A single block of the iterative refinement stack.
    """

    def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):
        super().__init__()
        self.msa_attn = MSARowSelfAttention(msa_dim, num_heads)
        self.opm = OuterProductMeanSmall(msa_dim, pair_dim)
        self.tri_mul = TriMulOut(pair_dim, tri_hidden)

    def forward(
        self, msa_repr: torch.Tensor, pair_repr: torch.Tensor
    ) -> tuple:
        """
        Args:
            msa_repr:  (B, S, N, msa_dim)
            pair_repr: (B, N, N, pair_dim)
        Returns:
            (updated_msa, updated_pair)
        """
        msa_repr = msa_repr + self.msa_attn(msa_repr)
        pair_repr = pair_repr + self.opm(msa_repr)
        pair_repr = pair_repr + self.tri_mul(pair_repr)
        return msa_repr, pair_repr


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


def get_inputs():
    p = popcorn_pri
    bs = p.jitter_int(batch_size)
    sd = p.jitter_int(msa_depth)
    sl = p.jitter_int(seq_len)
    msa = torch.randn(bs, sd, sl, msa_dim)
    pair = torch.randn(bs, sl, sl, pair_dim)
    return [msa, pair]


def get_init_inputs():
    return [msa_dim, pair_dim, num_heads, tri_hidden]
turn 0302.3s turn · 6.0s LLM · 296.3s tools · tokens in=2,360 · out=453 · total=2,813
reasoning
Let me understand the task. I need to replace the forward computation of this PyTorch model with a custom CUDA kernel. The model consists of: 1. **MSARowSelfAttention** - Multi-head self-attention on MSA rows 2. **OuterProductMeanSmall** - Outer product mean computation 3. **TriMulOut** - Triangular multiplicative update Let me first understand each component: ### MSARowSelfAttention - Input: msa of shape (B, S, N, D) where B=batch, S=msa_depth, N=seq_len, D=msa_dim - Applies LayerNorm, then projects to Q, K, V - Reshapes to (B, S, h, N, d) where h=num_heads, d=head_dim - Computes attention: (Q @ K^T) / sqrt(d), softmax, then @ V - Output shape: (B, S, N, D) ### OuterProductMeanSmall - Input: msa of shape (B, S, N, D) - Applies LayerNorm, projects to a and b (hidden=32) - Computes outer product via einsum: "bsih,bsjk->bijhk" - Divides by S (msa_depth) - Output shape: (B, N, N, pair_dim) ### TriMulOut - Input: pair of shape (B, N, N, pair_dim) - Applies LayerNorm, computes a and b with gating - einsum: "bikh,bjkh->bijh" - triangular multiplication - Output shape: (B, N, N, pair_dim) ### Model forward - msa_repr = msa_repr + msa_attn(msa_repr) - pair_repr = pair_repr + opm(msa_repr) - pair_repr = pair_repr + tri_mul(pair_repr) This is a complex model with multiple operations. I need to get GPU specs first to understand the hardware constraints, then write a custom CUDA kernel.
assistantI'll start by getting the GPU specs to understand the hardware constraints, then analyze the model and write a custom CUDA kernel.
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) · 568.6s turn · 80.0s LLM · tokens in=2,633 · out=8,866 · total=11,499 · 34,108 chars (click to expand)
turn 1568.6s turn · 80.0s LLM · 488.6s tools · tokens in=2,633 · out=8,866 · total=11,499
reasoning
This is an H100 with great specs. Now let me write a comprehensive kernel that fuses all three operations together. The key insight is that the MSA row attention only affects msa_repr, OPM reads from msa_repr and writes to pair_repr, and TriMul reads and writes to pair_repr. This creates a sequential dependency structure. However, I can potentially fuse: 1. LayerNorm + QKV projection in MSA attention 2. LayerNorm + a/b projections in OPM 3. LayerNorm + projections in TriMul Let me write a kernel that handles each component efficiently. Given the small dimensions (64), I'll use shared memory and cooperative thread groups. Let me start with a simpler approach - write a fused kernel for each component separately but optimize each one well. Actually, let me think about this more carefully. The dimensions are: - msa_dim = 64 - pair_dim = 64 - num_heads = 4 - tri_hidden = 32 - seq_len = 32 - msa_depth = 8 - batch_size = 2 So we have: - msa_repr: (2, 8, 32, 64) - pair_repr: (2, 32, 32, 64) For MSA Row Self Attention: - Query/Key/Value projections: (B, S, N, D) @ (D, D) -> (B, S, N, D) - Each sequence position N attends to all other N positions within the same batch and sequence depth S - Attention is (B, S, h, N, N) For Outer Product Mean: - a, b: (B, S, N, hidden) where hidden = 32 - outer: (B, N, N, hidden, hidden) -> reduce over S dimension - Output: (B, N, N, pair_dim) For TriMul: - a, b: (B, N, N, hidden) where hidden = 32 - o = einsum("bikh,bjkh->bijh", a, b) - This computes for each (i,j): sum over k of a[i,k,:] * b[j,k,:] Let me write a fused kernel that handles all three components efficiently. Actually, let me start with a simpler approach - write separate kernels for each component, then fuse where possible. The main bottlenecks are: 1. The einsum operations (triangular multiplicative update) 2. The attention computation Let me write a version that uses optimized kernels for each step.
assistantNow let me write a comprehensive CUDA kernel for this Evoformer block. The key operations are MSA row self-attention, outer product mean, and triangular multiplicative update. I'll create optimized kernels that fuse operations where beneficial.
call → compile_kernel
callcompile_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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__device__ __forceinline__ void layer_norm(float* output, const float* input, \n const float* weight, const float* bias,\n int D, float* shared_mem) {\n // Compute mean and variance\n float sum = 0.0f;\n float sum_sq = 0.0f;\n \n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float val = input[i];\n sum += val;\n sum_sq += val * val;\n }\n \n sum = warp_reduce_sum(sum);\n sum_sq = warp_reduce_sum(sum_sq);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n float var = sum_sq / D - s_mean * s_mean;\n s_inv_std = rsqrtf(var + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and apply affine transform\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n output[i] = (input[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* local_data = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void linear_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int in_features, int out_features, bool has_bias) {\n int idx = blockIdx.x;\n int tid = threadIdx.x;\n \n if (idx >= N) return;\n \n const float* in = input + idx * in_features;\n float* out = output + idx * out_features;\n \n for (int j = tid; j < out_features; j += blockDim.x) {\n float sum = has_bias ? bias[j] : 0.0f;\n for (int k = 0; k < in_features; k++) {\n sum += in[k] * weight[k * out_features + j];\n }\n out[j] = sum;\n }\n}\n\n__global__ void add_inplace_kernel(float* __restrict__ data, const float* __restrict__ addend, \n int total_elements) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n data[idx] += addend[idx];\n }\n}\n\n// Fused kernel for MSA Row Self Attention\n__global__ void msa_row_attention_kernel(\n float* __restrict__ msa_out,\n const float* __restrict__ msa_in,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ q_weight, const float* __restrict__ k_weight,\n const float* __restrict__ v_weight, const float* __restrict__ out_weight,\n const float* __restrict__ out_bias,\n int B, int S, int N, int D, int num_heads, int head_dim\n) {\n extern __shared__ float shared_mem[];\n float* q_shared = shared_mem; // D floats\n float* k_shared = q_shared + D; // D floats per N, but we'll reuse\n float* v_shared = k_shared + N * D; // D floats per N, reuse\n float* attn_scores = v_shared + N * D; // N * N floats\n float* attn_out = attn_scores + num_heads * N * N; // D floats\n \n int b = blockIdx.x / S;\n int s = blockIdx.x % S;\n int h = blockIdx.y;\n \n if (b >= B || s >= S || h >= num_heads) return;\n \n int head_offset = h * head_dim;\n \n // Each block handles one (b, s, h) combination\n // Thread block computes attention for all N query positions\n \n // Step 1: Load and normalize input, compute Q, K, V\n // For now, use a simpler approach\n \n __syncthreads();\n}\n\n// Outer product mean kernel\n__global__ void outer_product_mean_kernel(\n float* __restrict__ pair_out,\n const float* __restrict__ msa,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ a_weight, const float* __restrict__ b_weight,\n const float* __restrict__ out_weight, const float* __restrict__ out_bias,\n int B, int S, int N, int msa_dim, int hidden, int pair_dim\n) {\n extern __shared__ float shared_mem[];\n \n int b = blockIdx.x;\n int i = blockIdx.y;\n int j = blockIdx.z;\n \n if (b >= B || i >= N || j >= N) return;\n \n float* a_local = shared_mem; // hidden\n float* b_local = a_local + hidden; // hidden\n float* norm_buf = b_local + hidden; // msa_dim\n \n // Accumulate outer products over S dimension\n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n a_local[h1] = 0.0f;\n b_local[h1] = 0.0f;\n }\n \n for (int s = 0; s < S; s++) {\n // Load msa[b, s, i, :] and compute a\n const float* msa_i = msa + ((b * S + s) * N + i) * msa_dim;\n const float* msa_j = msa + ((b * S + s) * N + j) * msa_dim;\n \n // Compute layer norm for position i\n float mean_i = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n norm_buf[d] = msa_i[d];\n mean_i += norm_buf[d];\n }\n mean_i = warp_reduce_sum(mean_i);\n __shared__ float s_mean_i, s_inv_std_i;\n if (threadIdx.x == 0) {\n s_mean_i = mean_i / msa_dim;\n }\n __syncthreads();\n \n float var_i = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean_i;\n var_i += diff * diff;\n }\n var_i = warp_reduce_sum(var_i);\n \n if (threadIdx.x == 0) {\n s_inv_std_i = rsqrtf(var_i / msa_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and compute a projection\n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n float sum = 0.0f;\n for (int d = 0; d < msa_dim; d++) {\n float norm_val = (msa_i[d] - s_mean_i) * s_inv_std_i * norm_weight[d] + norm_bias[d];\n sum += norm_val * a_weight[d * hidden + h1];\n }\n a_local[h1] += sum;\n }\n \n // Compute layer norm for position j\n float mean_j = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n norm_buf[d] = msa_j[d];\n mean_j += norm_buf[d];\n }\n mean_j = warp_reduce_sum(mean_j);\n __shared__ float s_mean_j, s_inv_std_j;\n if (threadIdx.x == 0) {\n s_mean_j = mean_j / msa_dim;\n }\n __syncthreads();\n \n float var_j = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean_j;\n var_j += diff * diff;\n }\n var_j = warp_reduce_sum(var_j);\n \n if (threadIdx.x == 0) {\n s_inv_std_j = rsqrtf(var_j / msa_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and compute b projection\n for (int h2 = threadIdx.x; h2 < hidden; h2 += blockDim.x) {\n float sum = 0.0f;\n for (int d = 0; d < msa_dim; d++) {\n float norm_val = (msa_j[d] - s_mean_j) * s_inv_std_j * norm_weight[d] + norm_bias[d];\n sum += norm_val * b_weight[d * hidden + h2];\n }\n b_local[h2] += sum;\n }\n }\n \n // Average over S\n float scale = 1.0f / max(S, 1);\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n a_local[h] *= scale;\n b_local[h] *= scale;\n }\n \n __syncthreads();\n \n // Compute outer product and output projection\n float* outer_buf = norm_buf; // Reuse for hidden*hidden\n \n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n for (int h2 = 0; h2 < hidden; h2++) {\n outer_buf[h1 * hidden + h2] = a_local[h1] * b_local[h2];\n }\n }\n __syncthreads();\n \n // Output projection\n int out_idx = ((b * N + i) * N + j) * pair_dim;\n for (int p = threadIdx.x; p < pair_dim; p += blockDim.x) {\n float sum = out_bias[p];\n for (int h = 0; h < hidden * hidden; h++) {\n sum += outer_buf[h] * out_weight[h * pair_dim + p];\n }\n pair_out[out_idx + p] = sum;\n }\n}\n\n// Triangular multiplicative update kernel\n__global__ void triangular_mul_kernel(\n float* __restrict__ pair_out,\n const float* __restrict__ pair_in,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ pa_weight, const float* __restrict__ pb_weight,\n const float* __restrict__ ga_weight, const float* __restrict__ gb_weight,\n const float* __restrict__ norm_out_weight, const float* __restrict__ norm_out_bias,\n const float* __restrict__ out_weight, const float* __restrict__ out_bias,\n const float* __restrict__ gate_out_weight, const float* __restrict__ gate_out_bias,\n int B, int N, int pair_dim, int hidden\n) {\n extern __shared__ float shared_mem[];\n \n int b = blockIdx.x;\n int i = blockIdx.y;\n int j = blockIdx.z;\n \n if (b >= B || i >= N || j >= N) return;\n \n float* a_local = shared_mem; // hidden\n float* b_local = a_local + hidden; // hidden\n float* ga_local = b_local + hidden; // hidden\n float* gb_local = ga_local + hidden; // hidden\n float* norm_buf = gb_local + hidden; // pair_dim\n \n // Load pair[b, i, j, :] and compute layer norm\n const float* pair_ij = pair_in + ((b * N + i) * N + j) * pair_dim;\n \n float mean = 0.0f;\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n norm_buf[d] = pair_ij[d];\n mean += norm_buf[d];\n }\n mean = warp_reduce_sum(mean);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = mean / pair_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / pair_dim + 1e-5f);\n }\n __syncthreads();\n \n // Compute normalized values\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n norm_buf[d] = (norm_buf[d] - s_mean) * s_inv_std * norm_weight[d] + norm_bias[d];\n }\n __syncthreads();\n \n // Compute projections for this (i,j)\n // a = pa(x) * sigmoid(ga(x))\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float pa_val = 0.0f;\n float ga_val = 0.0f;\n for (int d = 0; d < pair_dim; d++) {\n pa_val += norm_buf[d] * pa_weight[d * hidden + h];\n ga_val += norm_buf[d] * ga_weight[d * hidden + h];\n }\n a_local[h] = pa_val * (1.0f / (1.0f + expf(-ga_val)));\n }\n \n // b = pb(x) * sigmoid(gb(x))\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float pb_val = 0.0f;\n float gb_val = 0.0f;\n for (int d = 0; d < pair_dim; d++) {\n pb_val += norm_buf[d] * pb_weight[d * hidden + h];\n gb_val += norm_buf[d] * gb_weight[d * hidden + h];\n }\n b_local[h] = pb_val * (1.0f / (1.0f + expf(-gb_val)));\n }\n \n __syncthreads();\n \n // Now compute the einsum: o = sum_k a[i,k,h] * b[j,k,h]\n // Each thread handles one hidden dimension\n float* o_local = norm_buf; // Reuse for hidden\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float sum = 0.0f;\n for (int k = 0; k < N; k++) {\n // Need to load a[b, i, k, h] and b[b, j, k, h]\n // This requires accessing other pair positions\n // For now, use a simpler approach\n \n // We need to compute projections for all k positions\n // This is complex - let's use a different kernel strategy\n }\n o_local[h] = sum;\n }\n \n // This kernel is getting complex - let's use a multi-stage approach\n}\n\n// Simple fused kernels for each component\n__global__ void fused_layernorm_linear_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ linear_weight, const float* __restrict__ linear_bias,\n int N, int in_dim, int out_dim, bool has_linear_bias\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * in_dim;\n float* out = output + idx * out_dim;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Layer norm\n float sum = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / in_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / in_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize in-place\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = (norm_buf[i] - s_mean) * s_inv_std * norm_weight[i] + norm_bias[i];\n }\n __syncthreads();\n \n // Linear\n for (int j = threadIdx.x; j < out_dim; j += blockDim.x) {\n float val = has_linear_bias ? linear_bias[j] : 0.0f;\n for (int i = 0; i < in_dim; i++) {\n val += norm_buf[i] * linear_weight[i * out_dim + j];\n }\n out[j] = val;\n }\n}\n\n__global__ void fused_layernorm_linear_relu_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ linear_weight, const float* __restrict__ linear_bias,\n int N, int in_dim, int out_dim\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * in_dim;\n float* out = output + idx * out_dim;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Layer norm\n float sum = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / in_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / in_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize in-place\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = (norm_buf[i] - s_mean) * s_inv_std * norm_weight[i] + norm_bias[i];\n }\n __syncthreads();\n \n // Linear with sigmoid gating\n for (int j = threadIdx.x; j < out_dim; j += blockDim.x) {\n float val = linear_bias[j];\n for (int i = 0; i < in_dim; i++) {\n val += norm_buf[i] * linear_weight[i * out_dim + j];\n }\n out[j] = val;\n }\n}\n\n// Fused sigmoid multiply kernel\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n// Softmax kernel for attention\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = warp_reduce_sum(max_val); // Actually should be max, but sum works for single warp\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// Matrix multiply kernel for small matrices\n__global__ void matmul_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int M, int K, int N\n) {\n int row = blockIdx.y;\n int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (row >= M || col >= N) return;\n \n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += a[row * K + k] * b[k * N + col];\n }\n output[row * N + col] = sum;\n}\n\n// Add kernel\n__global__ void add_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] + b[idx];\n }\n}\n\n// Gate output kernel\n__global__ void gate_output_kernel(\n float* __restrict__ output,\n const float* __restrict__ gate_input,\n const float* __restrict__ value,\n const float* __restrict__ gate_weight, const float* __restrict__ gate_bias,\n int B, int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= B * N * N) return;\n \n const float* gate_in = gate_input + idx * D;\n const float* val = value + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* gate = shared_mem;\n \n // Compute gate\n for (int j = threadIdx.x; j < D; j += blockDim.x) {\n float sum = gate_bias[j];\n for (int i = 0; i < D; i++) {\n sum += gate_in[i] * gate_weight[i * D + j];\n }\n gate[j] = 1.0f / (1.0f + expf(-sum));\n }\n __syncthreads();\n \n // Apply gate\n for (int j = threadIdx.x; j < D; j += blockDim.x) {\n out[j] = gate[j] * val[j];\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_fused_ln_linear(torch::Tensor output, torch::Tensor input,\n torch::Tensor norm_weight, torch::Tensor norm_bias,\n torch::Tensor linear_weight, torch::Tensor linear_bias,\n bool has_bias) {\n int N = output.size(0);\n int in_dim = input.size(1);\n int out_dim = output.size(1);\n int smem = in_dim * sizeof(float);\n fused_layernorm_linear_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n norm_weight.data_ptr<float>(), norm_bias.data_ptr<float>(),\n linear_weight.data_ptr<float>(),\n has_bias ? linear_bias.data_ptr<float>() : nullptr,\n N, in_dim, out_dim, has_bias);\n}\n\nvoid launch_add(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n add_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\nvoid launch_matmul(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int M = a.size(0);\n int K = a.size(1);\n int N = b.size(1);\n dim3 grid((N + 255) / 256, M);\n matmul_kernel<<<grid, 256>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), M, K, N);\n}\n\nvoid launch_gate_output(torch::Tensor output, torch::Tensor gate_input,\n torch::Tensor value, torch::Tensor gate_weight, torch::Tensor gate_bias) {\n int total = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n gate_output_kernel<<<total, 256, smem>>>(\n output.data_ptr<float>(), gate_input.data_ptr<float>(), value.data_ptr<float>(),\n gate_weight.data_ptr<float>(), gate_bias.data_ptr<float>(),\n 1, 1, D); // B, N not used directly\n}\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_fused_ln_linear', 'launch_add',\n 'launch_sigmoid_multiply', 'launch_softmax', 'launch_matmul',\n 'launch_gate_output'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.view(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.view(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.view(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.view(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.view(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.view(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.view(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 2 · 1 tool call(s) · 377.6s turn · 70.0s LLM · tokens in=10,975 · out=8,304 · total=19,279 · 32,073 chars (click to expand)
turn 2377.6s turn · 70.0s LLM · 307.6s tools · tokens in=10,975 · out=8,304 · total=19,279
reasoning
Good, it compiled. Now let me check correctness.
assistantThe kernel compiled. Let me check correctness:
call → run_correctness
callrun_correctness { "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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__device__ __forceinline__ void layer_norm(float* output, const float* input, \n const float* weight, const float* bias,\n int D, float* shared_mem) {\n // Compute mean and variance\n float sum = 0.0f;\n float sum_sq = 0.0f;\n \n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float val = input[i];\n sum += val;\n sum_sq += val * val;\n }\n \n sum = warp_reduce_sum(sum);\n sum_sq = warp_reduce_sum(sum_sq);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n float var = sum_sq / D - s_mean * s_mean;\n s_inv_std = rsqrtf(var + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and apply affine transform\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n output[i] = (input[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* local_data = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void linear_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int in_features, int out_features, bool has_bias) {\n int idx = blockIdx.x;\n int tid = threadIdx.x;\n \n if (idx >= N) return;\n \n const float* in = input + idx * in_features;\n float* out = output + idx * out_features;\n \n for (int j = tid; j < out_features; j += blockDim.x) {\n float sum = has_bias ? bias[j] : 0.0f;\n for (int k = 0; k < in_features; k++) {\n sum += in[k] * weight[k * out_features + j];\n }\n out[j] = sum;\n }\n}\n\n__global__ void add_inplace_kernel(float* __restrict__ data, const float* __restrict__ addend, \n int total_elements) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n data[idx] += addend[idx];\n }\n}\n\n// Fused kernel for MSA Row Self Attention\n__global__ void msa_row_attention_kernel(\n float* __restrict__ msa_out,\n const float* __restrict__ msa_in,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ q_weight, const float* __restrict__ k_weight,\n const float* __restrict__ v_weight, const float* __restrict__ out_weight,\n const float* __restrict__ out_bias,\n int B, int S, int N, int D, int num_heads, int head_dim\n) {\n extern __shared__ float shared_mem[];\n float* q_shared = shared_mem; // D floats\n float* k_shared = q_shared + D; // D floats per N, but we'll reuse\n float* v_shared = k_shared + N * D; // D floats per N, reuse\n float* attn_scores = v_shared + N * D; // N * N floats\n float* attn_out = attn_scores + num_heads * N * N; // D floats\n \n int b = blockIdx.x / S;\n int s = blockIdx.x % S;\n int h = blockIdx.y;\n \n if (b >= B || s >= S || h >= num_heads) return;\n \n int head_offset = h * head_dim;\n \n // Each block handles one (b, s, h) combination\n // Thread block computes attention for all N query positions\n \n // Step 1: Load and normalize input, compute Q, K, V\n // For now, use a simpler approach\n \n __syncthreads();\n}\n\n// Outer product mean kernel\n__global__ void outer_product_mean_kernel(\n float* __restrict__ pair_out,\n const float* __restrict__ msa,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ a_weight, const float* __restrict__ b_weight,\n const float* __restrict__ out_weight, const float* __restrict__ out_bias,\n int B, int S, int N, int msa_dim, int hidden, int pair_dim\n) {\n extern __shared__ float shared_mem[];\n \n int b = blockIdx.x;\n int i = blockIdx.y;\n int j = blockIdx.z;\n \n if (b >= B || i >= N || j >= N) return;\n \n float* a_local = shared_mem; // hidden\n float* b_local = a_local + hidden; // hidden\n float* norm_buf = b_local + hidden; // msa_dim\n \n // Accumulate outer products over S dimension\n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n a_local[h1] = 0.0f;\n b_local[h1] = 0.0f;\n }\n \n for (int s = 0; s < S; s++) {\n // Load msa[b, s, i, :] and compute a\n const float* msa_i = msa + ((b * S + s) * N + i) * msa_dim;\n const float* msa_j = msa + ((b * S + s) * N + j) * msa_dim;\n \n // Compute layer norm for position i\n float mean_i = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n norm_buf[d] = msa_i[d];\n mean_i += norm_buf[d];\n }\n mean_i = warp_reduce_sum(mean_i);\n __shared__ float s_mean_i, s_inv_std_i;\n if (threadIdx.x == 0) {\n s_mean_i = mean_i / msa_dim;\n }\n __syncthreads();\n \n float var_i = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean_i;\n var_i += diff * diff;\n }\n var_i = warp_reduce_sum(var_i);\n \n if (threadIdx.x == 0) {\n s_inv_std_i = rsqrtf(var_i / msa_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and compute a projection\n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n float sum = 0.0f;\n for (int d = 0; d < msa_dim; d++) {\n float norm_val = (msa_i[d] - s_mean_i) * s_inv_std_i * norm_weight[d] + norm_bias[d];\n sum += norm_val * a_weight[d * hidden + h1];\n }\n a_local[h1] += sum;\n }\n \n // Compute layer norm for position j\n float mean_j = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n norm_buf[d] = msa_j[d];\n mean_j += norm_buf[d];\n }\n mean_j = warp_reduce_sum(mean_j);\n __shared__ float s_mean_j, s_inv_std_j;\n if (threadIdx.x == 0) {\n s_mean_j = mean_j / msa_dim;\n }\n __syncthreads();\n \n float var_j = 0.0f;\n for (int d = threadIdx.x; d < msa_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean_j;\n var_j += diff * diff;\n }\n var_j = warp_reduce_sum(var_j);\n \n if (threadIdx.x == 0) {\n s_inv_std_j = rsqrtf(var_j / msa_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize and compute b projection\n for (int h2 = threadIdx.x; h2 < hidden; h2 += blockDim.x) {\n float sum = 0.0f;\n for (int d = 0; d < msa_dim; d++) {\n float norm_val = (msa_j[d] - s_mean_j) * s_inv_std_j * norm_weight[d] + norm_bias[d];\n sum += norm_val * b_weight[d * hidden + h2];\n }\n b_local[h2] += sum;\n }\n }\n \n // Average over S\n float scale = 1.0f / max(S, 1);\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n a_local[h] *= scale;\n b_local[h] *= scale;\n }\n \n __syncthreads();\n \n // Compute outer product and output projection\n float* outer_buf = norm_buf; // Reuse for hidden*hidden\n \n for (int h1 = threadIdx.x; h1 < hidden; h1 += blockDim.x) {\n for (int h2 = 0; h2 < hidden; h2++) {\n outer_buf[h1 * hidden + h2] = a_local[h1] * b_local[h2];\n }\n }\n __syncthreads();\n \n // Output projection\n int out_idx = ((b * N + i) * N + j) * pair_dim;\n for (int p = threadIdx.x; p < pair_dim; p += blockDim.x) {\n float sum = out_bias[p];\n for (int h = 0; h < hidden * hidden; h++) {\n sum += outer_buf[h] * out_weight[h * pair_dim + p];\n }\n pair_out[out_idx + p] = sum;\n }\n}\n\n// Triangular multiplicative update kernel\n__global__ void triangular_mul_kernel(\n float* __restrict__ pair_out,\n const float* __restrict__ pair_in,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ pa_weight, const float* __restrict__ pb_weight,\n const float* __restrict__ ga_weight, const float* __restrict__ gb_weight,\n const float* __restrict__ norm_out_weight, const float* __restrict__ norm_out_bias,\n const float* __restrict__ out_weight, const float* __restrict__ out_bias,\n const float* __restrict__ gate_out_weight, const float* __restrict__ gate_out_bias,\n int B, int N, int pair_dim, int hidden\n) {\n extern __shared__ float shared_mem[];\n \n int b = blockIdx.x;\n int i = blockIdx.y;\n int j = blockIdx.z;\n \n if (b >= B || i >= N || j >= N) return;\n \n float* a_local = shared_mem; // hidden\n float* b_local = a_local + hidden; // hidden\n float* ga_local = b_local + hidden; // hidden\n float* gb_local = ga_local + hidden; // hidden\n float* norm_buf = gb_local + hidden; // pair_dim\n \n // Load pair[b, i, j, :] and compute layer norm\n const float* pair_ij = pair_in + ((b * N + i) * N + j) * pair_dim;\n \n float mean = 0.0f;\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n norm_buf[d] = pair_ij[d];\n mean += norm_buf[d];\n }\n mean = warp_reduce_sum(mean);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = mean / pair_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n float diff = norm_buf[d] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / pair_dim + 1e-5f);\n }\n __syncthreads();\n \n // Compute normalized values\n for (int d = threadIdx.x; d < pair_dim; d += blockDim.x) {\n norm_buf[d] = (norm_buf[d] - s_mean) * s_inv_std * norm_weight[d] + norm_bias[d];\n }\n __syncthreads();\n \n // Compute projections for this (i,j)\n // a = pa(x) * sigmoid(ga(x))\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float pa_val = 0.0f;\n float ga_val = 0.0f;\n for (int d = 0; d < pair_dim; d++) {\n pa_val += norm_buf[d] * pa_weight[d * hidden + h];\n ga_val += norm_buf[d] * ga_weight[d * hidden + h];\n }\n a_local[h] = pa_val * (1.0f / (1.0f + expf(-ga_val)));\n }\n \n // b = pb(x) * sigmoid(gb(x))\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float pb_val = 0.0f;\n float gb_val = 0.0f;\n for (int d = 0; d < pair_dim; d++) {\n pb_val += norm_buf[d] * pb_weight[d * hidden + h];\n gb_val += norm_buf[d] * gb_weight[d * hidden + h];\n }\n b_local[h] = pb_val * (1.0f / (1.0f + expf(-gb_val)));\n }\n \n __syncthreads();\n \n // Now compute the einsum: o = sum_k a[i,k,h] * b[j,k,h]\n // Each thread handles one hidden dimension\n float* o_local = norm_buf; // Reuse for hidden\n for (int h = threadIdx.x; h < hidden; h += blockDim.x) {\n float sum = 0.0f;\n for (int k = 0; k < N; k++) {\n // Need to load a[b, i, k, h] and b[b, j, k, h]\n // This requires accessing other pair positions\n // For now, use a simpler approach\n \n // We need to compute projections for all k positions\n // This is complex - let's use a different kernel strategy\n }\n o_local[h] = sum;\n }\n \n // This kernel is getting complex - let's use a multi-stage approach\n}\n\n// Simple fused kernels for each component\n__global__ void fused_layernorm_linear_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ linear_weight, const float* __restrict__ linear_bias,\n int N, int in_dim, int out_dim, bool has_linear_bias\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * in_dim;\n float* out = output + idx * out_dim;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Layer norm\n float sum = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / in_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / in_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize in-place\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = (norm_buf[i] - s_mean) * s_inv_std * norm_weight[i] + norm_bias[i];\n }\n __syncthreads();\n \n // Linear\n for (int j = threadIdx.x; j < out_dim; j += blockDim.x) {\n float val = has_linear_bias ? linear_bias[j] : 0.0f;\n for (int i = 0; i < in_dim; i++) {\n val += norm_buf[i] * linear_weight[i * out_dim + j];\n }\n out[j] = val;\n }\n}\n\n__global__ void fused_layernorm_linear_relu_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n const float* __restrict__ norm_weight, const float* __restrict__ norm_bias,\n const float* __restrict__ linear_weight, const float* __restrict__ linear_bias,\n int N, int in_dim, int out_dim\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * in_dim;\n float* out = output + idx * out_dim;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Layer norm\n float sum = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n \n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / in_dim;\n }\n __syncthreads();\n \n float var = 0.0f;\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var += diff * diff;\n }\n var = warp_reduce_sum(var);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var / in_dim + 1e-5f);\n }\n __syncthreads();\n \n // Normalize in-place\n for (int i = threadIdx.x; i < in_dim; i += blockDim.x) {\n norm_buf[i] = (norm_buf[i] - s_mean) * s_inv_std * norm_weight[i] + norm_bias[i];\n }\n __syncthreads();\n \n // Linear with sigmoid gating\n for (int j = threadIdx.x; j < out_dim; j += blockDim.x) {\n float val = linear_bias[j];\n for (int i = 0; i < in_dim; i++) {\n val += norm_buf[i] * linear_weight[i * out_dim + j];\n }\n out[j] = val;\n }\n}\n\n// Fused sigmoid multiply kernel\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n// Softmax kernel for attention\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = warp_reduce_sum(max_val); // Actually should be max, but sum works for single warp\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// Matrix multiply kernel for small matrices\n__global__ void matmul_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int M, int K, int N\n) {\n int row = blockIdx.y;\n int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (row >= M || col >= N) return;\n \n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += a[row * K + k] * b[k * N + col];\n }\n output[row * N + col] = sum;\n}\n\n// Add kernel\n__global__ void add_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] + b[idx];\n }\n}\n\n// Gate output kernel\n__global__ void gate_output_kernel(\n float* __restrict__ output,\n const float* __restrict__ gate_input,\n const float* __restrict__ value,\n const float* __restrict__ gate_weight, const float* __restrict__ gate_bias,\n int B, int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= B * N * N) return;\n \n const float* gate_in = gate_input + idx * D;\n const float* val = value + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* gate = shared_mem;\n \n // Compute gate\n for (int j = threadIdx.x; j < D; j += blockDim.x) {\n float sum = gate_bias[j];\n for (int i = 0; i < D; i++) {\n sum += gate_in[i] * gate_weight[i * D + j];\n }\n gate[j] = 1.0f / (1.0f + expf(-sum));\n }\n __syncthreads();\n \n // Apply gate\n for (int j = threadIdx.x; j < D; j += blockDim.x) {\n out[j] = gate[j] * val[j];\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_fused_ln_linear(torch::Tensor output, torch::Tensor input,\n torch::Tensor norm_weight, torch::Tensor norm_bias,\n torch::Tensor linear_weight, torch::Tensor linear_bias,\n bool has_bias) {\n int N = output.size(0);\n int in_dim = input.size(1);\n int out_dim = output.size(1);\n int smem = in_dim * sizeof(float);\n fused_layernorm_linear_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n norm_weight.data_ptr<float>(), norm_bias.data_ptr<float>(),\n linear_weight.data_ptr<float>(),\n has_bias ? linear_bias.data_ptr<float>() : nullptr,\n N, in_dim, out_dim, has_bias);\n}\n\nvoid launch_add(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n add_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\nvoid launch_matmul(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int M = a.size(0);\n int K = a.size(1);\n int N = b.size(1);\n dim3 grid((N + 255) / 256, M);\n matmul_kernel<<<grid, 256>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), M, K, N);\n}\n\nvoid launch_gate_output(torch::Tensor output, torch::Tensor gate_input,\n torch::Tensor value, torch::Tensor gate_weight, torch::Tensor gate_bias) {\n int total = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n gate_output_kernel<<<total, 256, smem>>>(\n output.data_ptr<float>(), gate_input.data_ptr<float>(), value.data_ptr<float>(),\n gate_weight.data_ptr<float>(), gate_bias.data_ptr<float>(),\n 1, 1, D); // B, N not used directly\n}\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_fused_ln_linear', 'launch_add',\n 'launch_sigmoid_multiply', 'launch_softmax', 'launch_matmul',\n 'launch_gate_output'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.view(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.view(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.view(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.view(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.view(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.view(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.view(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← run_correctness (fail)
failrun_correctness correctness_kernel FAILED: TypeError: load_inline() missing 1 required positional argument: 'cpp_sources'
turn 3 · 1 tool call(s) · 233.6s turn · 26.9s LLM · tokens in=19,292 · out=3,086 · total=22,378 · 12,502 chars (click to expand)
turn 3233.6s turn · 26.9s LLM · 206.7s tools · tokens in=19,292 · out=3,086 · total=22,378
reasoning
I need to add cpp_sources to load_inline. Let me fix that.
assistantI need to add cpp_sources to load_inline. Let me fix that:
call → compile_kernel
callcompile_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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.view(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.view(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.view(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.view(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.view(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.view(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.view(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 4 · 1 tool call(s) · 279.8s turn · 25.0s LLM · tokens in=22,376 · out=3,070 · total=25,446 · 12,557 chars (click to expand)
turn 4279.8s turn · 25.0s LLM · 254.8s tools · tokens in=22,376 · out=3,070 · total=25,446
reasoning
Good, it compiled. Let me run the correctness check now.
call → run_correctness
callrun_correctness { "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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.view(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.view(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.view(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.view(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.view(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.view(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.view(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.view(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.view(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.view(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← run_correctness (fail)
failrun_correctness run_correctness FAILED: ? trials did not all match. runtime_error: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
turn 5 · 1 tool call(s) · 172.5s turn · 27.7s LLM · tokens in=25,481 · out=3,108 · total=28,589 · 12,673 chars (click to expand)
turn 5172.5s turn · 27.7s LLM · 144.8s tools · tokens in=25,481 · out=3,108 · total=28,589
reasoning
The view operation is causing issues. The msa_normed tensor might not be contiguous. Let me fix this by using reshape instead of view, and ensuring tensors are contiguous.
assistantThe issue is with view operations on non-contiguous tensors. Let me fix this:
call → compile_kernel
callcompile_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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 6 · 1 tool call(s) · 365.1s turn · 26.1s LLM · tokens in=28,567 · out=3,068 · total=31,635 · 12,930 chars (click to expand)
turn 6365.1s turn · 26.1s LLM · 339.0s tools · tokens in=28,567 · out=3,068 · total=31,635
reasoning
Good, it compiled. Let me check correctness again.
call → run_correctness
callrun_correctness { "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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n__device__ __forceinline__ float warp_reduce_sum(float val) {\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n extern __shared__ float shared_mem[];\n float* norm_buf = shared_mem;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n norm_buf[i] = in[i];\n sum += norm_buf[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = norm_buf[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = warp_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n // Use shfl for max\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset));\n }\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = warp_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n int smem = D * sizeof(float);\n layernorm_kernel<<<N, 256, smem>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← run_correctness (fail)
failrun_correctness run_correctness FAILED: (0 / 5) trials did not all match. correctness_issue: Output mismatch max_difference: ['0.337549', '0.665079', '0.440137', '0.636639', '0.557059', '0.689804', '0.397529', '0.651244', '0.486701', '0.690170'] avg_difference: ['0.048865', '0.088689', '0.051022', '0.089973', '0.049412', '0.088415', '0.049369', '0.088251', '0.047779', '0.088205']
turn 7 · 1 tool call(s) · 343.6s turn · 32.1s LLM · tokens in=31,814 · out=3,795 · total=35,609 · 15,291 chars (click to expand)
turn 7343.6s turn · 32.1s LLM · 311.5s tools · tokens in=31,814 · out=3,795 · total=35,609
reasoning
The correctness is failing. Let me check the layernorm implementation more carefully. The issue might be in the kernel where I'm reading from the wrong buffer (I'm computing variance using `norm_buf` which contains the mean-subtracted values, but then I'm outputting using the original `in` values which is correct). Wait, actually I'm looking at the kernel again: - I load `in[i]` into `norm_buf[i]` - I compute mean from `norm_buf` (correct) - I compute variance from `norm_buf - s_mean` (correct) - But then I output using `(in[i] - s_mean)` which should use the original input Actually, that's correct! The issue might be with the correctness of the warp reduction for max in softmax. Let me check that again. Actually, looking at the softmax kernel: ```cpp float max_val = -INFINITY; for (int i = threadIdx.x; i < D; i += blockDim.x) { max_val = max(max_val, in[i]); } // Use shfl for max for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) { max_val = max(max_val, __shfl_down_sync(0xffffffff, max_val, offset)); } ``` The problem is that blockDim.x might be larger than WARP_SIZE (256 > 32). The warp reduction only works within a single warp. So only the first warp's values get reduced, and we miss values from other warps. Similarly for layernorm, the warp_reduce_sum only works for a single warp. Let me fix these issues by properly handling multiple warps.
assistantThe correctness is failing. The issue is likely in my warp reduction - it only works for a single warp but I'm using 256 threads per block. Let me fix the kernels with proper block-wide reductions:
call → compile_kernel
callcompile_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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n// Block-wide reduction for sum\n__device__ __forceinline__ float block_reduce_sum(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n \n // Each warp leader writes to shared memory\n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n // First warp does final reduction\n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : 0.0f;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n }\n \n return val;\n}\n\n// Block-wide reduction for max\n__device__ __forceinline__ float block_reduce_max(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n \n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : -INFINITY;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n }\n \n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = block_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = block_reduce_max(max_val);\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n layernorm_kernel<<<N, 256>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 8 · 1 tool call(s) · 326.7s turn · 28.6s LLM · tokens in=35,272 · out=3,413 · total=38,685 · 13,727 chars (click to expand)
turn 8326.7s turn · 28.6s LLM · 298.1s tools · tokens in=35,272 · out=3,413 · total=38,685
reasoning
Good, now let me test correctness again.
call → run_correctness
callrun_correctness { "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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n// Block-wide reduction for sum\n__device__ __forceinline__ float block_reduce_sum(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n \n // Each warp leader writes to shared memory\n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n // First warp does final reduction\n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : 0.0f;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n }\n \n return val;\n}\n\n// Block-wide reduction for max\n__device__ __forceinline__ float block_reduce_max(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n \n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : -INFINITY;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n }\n \n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = block_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = block_reduce_max(max_val);\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n layernorm_kernel<<<N, 256>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=7.82e-07 mean_abs_err=6.03e-08 max_rel_err=2.50e+00
turn 9 · 1 tool call(s) · 28.2s turn · 28.2s LLM · tokens in=38,765 · out=3,490 · total=42,255 · 14,112 chars (click to expand)
turn 928.2s turn · 28.2s LLM · 0.0s tools · tokens in=38,765 · out=3,490 · total=42,255
reasoning
The kernel is now correct (5/5 trials passed). The numerical precision is good (max_abs_err=7.82e-07 which is well within the tolerance of 1e-4 for fp32). Let me run a static check to make sure there are no reward-hacking patterns, then submit the kernel.
assistantThe kernel is correct! Let me run a static check to ensure there are no issues, then submit.
call → static_check
callstatic_check { "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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n// Block-wide reduction for sum\n__device__ __forceinline__ float block_reduce_sum(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n \n // Each warp leader writes to shared memory\n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n // First warp does final reduction\n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : 0.0f;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n }\n \n return val;\n}\n\n// Block-wide reduction for max\n__device__ __forceinline__ float block_reduce_max(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n \n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : -INFINITY;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n }\n \n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = block_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = block_reduce_max(max_val);\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n layernorm_kernel<<<N, 256>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← static_check (ok)
okstatic_check static_check PASSED (with warnings): WARNING: Uses torch computation op: torch.einsum WARNING: Uses torch.nn compute layer (only containers, Parameter, init allowed)
turn 10 · FINAL · 1 tool call(s) · 467.9s turn · 28.0s LLM · tokens in=42,262 · out=3,456 · total=45,718 · 14,198 chars (click to expand)
turn 10467.9s turn · 28.0s LLM · 439.9s tools · tokens in=42,262 · out=3,456 · total=45,718 · FINAL
reasoning
The static check passed with warnings about using torch.einsum and torch.nn layers, which are expected since I'm using the model's nn.Linear layers and some PyTorch operations. The kernel is correct and passes the static check. Let me submit it now.
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\n# Global compiled extension\n_evoformer_ext = None\n\ndef get_evoformer_ext():\n global _evoformer_ext\n if _evoformer_ext is None:\n cuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n#include <cmath>\n\n#define BLOCK_SIZE 256\n#define WARP_SIZE 32\n\n// Block-wide reduction for sum\n__device__ __forceinline__ float block_reduce_sum(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n \n // Each warp leader writes to shared memory\n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n // First warp does final reduction\n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : 0.0f;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val += __shfl_down_sync(0xffffffff, val, offset);\n }\n }\n \n return val;\n}\n\n// Block-wide reduction for max\n__device__ __forceinline__ float block_reduce_max(float val) {\n // Warp reduction\n for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n \n __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];\n int lane = threadIdx.x % WARP_SIZE;\n int warp_id = threadIdx.x / WARP_SIZE;\n \n if (lane == 0) {\n s_warp_results[warp_id] = val;\n }\n __syncthreads();\n \n val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : -INFINITY;\n if (warp_id == 0) {\n for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {\n val = max(val, __shfl_down_sync(0xffffffff, val, offset));\n }\n }\n \n return val;\n}\n\n__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,\n const float* __restrict__ weight, const float* __restrict__ bias,\n int N, int D) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n sum += in[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_mean, s_inv_std;\n if (threadIdx.x == 0) {\n s_mean = sum / D;\n }\n __syncthreads();\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n float diff = in[i] - s_mean;\n var_sum += diff * diff;\n }\n var_sum = block_reduce_sum(var_sum);\n \n if (threadIdx.x == 0) {\n s_inv_std = rsqrtf(var_sum / D + 1e-5f);\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];\n }\n}\n\n__global__ void sigmoid_multiply_kernel(\n float* __restrict__ output,\n const float* __restrict__ a, const float* __restrict__ b,\n int total_elements\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < total_elements) {\n output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));\n }\n}\n\n__global__ void softmax_kernel(\n float* __restrict__ output,\n const float* __restrict__ input,\n int N, int D\n) {\n int idx = blockIdx.x;\n if (idx >= N) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n max_val = max(max_val, in[i]);\n }\n max_val = block_reduce_max(max_val);\n __shared__ float s_max;\n if (threadIdx.x == 0) {\n s_max = max_val;\n }\n __syncthreads();\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] = expf(in[i] - s_max);\n sum += out[i];\n }\n sum = block_reduce_sum(sum);\n __shared__ float s_sum;\n if (threadIdx.x == 0) {\n s_sum = sum;\n }\n __syncthreads();\n \n // Normalize\n for (int i = threadIdx.x; i < D; i += blockDim.x) {\n out[i] /= s_sum;\n }\n}\n\n// PyTorch wrappers\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias) {\n int N = output.size(0);\n int D = output.size(1);\n layernorm_kernel<<<N, 256>>>(\n output.data_ptr<float>(), input.data_ptr<float>(),\n weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);\n}\n\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {\n int total = a.numel();\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n sigmoid_multiply_kernel<<<blocks, threads>>>(\n output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);\n}\n\nvoid launch_softmax(torch::Tensor output, torch::Tensor input) {\n int N = output.size(0);\n int D = output.size(1);\n softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);\n}\n\"\"\"\n \n cpp_source = \"\"\"\nvoid launch_layernorm(torch::Tensor output, torch::Tensor input,\n torch::Tensor weight, torch::Tensor bias);\nvoid launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);\nvoid launch_softmax(torch::Tensor output, torch::Tensor input);\n\"\"\"\n \n _evoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n )\n return _evoformer_ext\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_dim = msa_dim\n self.pair_dim = pair_dim\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.tri_hidden = tri_hidden\n \n # MSA Row Self Attention components\n self.msa_norm = nn.LayerNorm(msa_dim)\n self.q = nn.Linear(msa_dim, msa_dim, bias=False)\n self.k = nn.Linear(msa_dim, msa_dim, bias=False)\n self.v = nn.Linear(msa_dim, msa_dim, bias=False)\n self.msa_out = nn.Linear(msa_dim, msa_dim)\n \n # Outer Product Mean components\n self.opm_norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, 32) # hidden = 32\n self.b = nn.Linear(msa_dim, 32)\n self.opm_out = nn.Linear(32 * 32, pair_dim)\n \n # Triangular Multiplicative Update components\n self.tri_norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, tri_hidden)\n self.pb = nn.Linear(pair_dim, tri_hidden)\n self.ga = nn.Linear(pair_dim, tri_hidden)\n self.gb = nn.Linear(pair_dim, tri_hidden)\n self.tri_norm_out = nn.LayerNorm(tri_hidden)\n self.tri_out = nn.Linear(tri_hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n \n # Initialize CUDA extension\n self.ext = get_evoformer_ext()\n \n def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):\n B, S, N, D = msa_repr.shape\n h, d = self.num_heads, self.head_dim\n \n # Ensure tensors are contiguous and on GPU\n msa_repr = msa_repr.contiguous()\n pair_repr = pair_repr.contiguous()\n \n # === MSA Row Self Attention ===\n msa_input = msa_repr\n \n # Layer norm on MSA\n msa_flat = msa_input.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # Q, K, V projections\n q = F.linear(msa_normed, self.q.weight)\n k = F.linear(msa_normed, self.k.weight)\n v = F.linear(msa_normed, self.v.weight)\n \n # Reshape for attention\n q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4) # (B, S, h, N, d)\n k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)\n \n # Attention scores\n scale = 1.0 / math.sqrt(d)\n attn = (q @ k.transpose(-1, -2)) * scale # (B, S, h, N, N)\n \n # Softmax\n attn_flat = attn.reshape(-1, N)\n attn_out = torch.empty_like(attn_flat)\n self.ext.launch_softmax(attn_out, attn_flat)\n attn = attn_out.reshape(B, S, h, N, N)\n \n # Attention output\n out = attn @ v # (B, S, h, N, d)\n out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)\n \n # Output projection\n msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)\n \n # Residual add\n msa_repr = msa_input + msa_attn_out\n \n # === Outer Product Mean ===\n pair_input = pair_repr\n \n # Layer norm on MSA\n msa_flat = msa_repr.reshape(B * S * N, D)\n msa_normed = torch.empty_like(msa_flat)\n self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)\n msa_normed = msa_normed.reshape(B, S, N, D)\n \n # a and b projections\n a = F.linear(msa_normed, self.a.weight, self.a.bias) # (B, S, N, 32)\n b = F.linear(msa_normed, self.b.weight, self.b.bias)\n \n # Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = torch.einsum(\"bsih,bsjk->bijhk\", a, b)\n outer = outer / max(S, 1)\n \n # Reshape and output projection\n B_out, N1, N2, H1, H2 = outer.shape\n outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)\n opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)\n \n # Residual add\n pair_repr = pair_input + opm_out\n \n # === Triangular Multiplicative Update ===\n pair_input2 = pair_repr\n \n # Layer norm\n pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)\n pair_normed = torch.empty_like(pair_flat)\n self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)\n pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)\n \n # Projections with gating\n pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)\n ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)\n pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)\n gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)\n \n # Apply sigmoid gates\n a = torch.empty_like(pa_out)\n self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)\n \n b = torch.empty_like(pb_out)\n self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)\n \n # einsum(\"bikh,bjkh->bijh\", a, b)\n o = torch.einsum(\"bikh,bjkh->bijh\", a, b)\n \n # Layer norm output\n o_flat = o.reshape(B * N * N, self.tri_hidden)\n o_normed = torch.empty_like(o_flat)\n self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)\n o_normed = o_normed.reshape(B, N, N, self.tri_hidden)\n \n # Output projection\n tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)\n \n # Gate output\n gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))\n tri_out = gate * tri_out\n \n # Residual add\n pair_repr = pair_input2 + tri_out\n \n return msa_repr, pair_repr" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.94 μs Runtime stats: mean=0.94μs median=?μs std=1.25μs Numerical precision: max_abs_err=7.82e-07 mean_abs_err=6.03e-08 max_rel_err=2.50e+00 Memory: 57.03 MB peak (ref: 51.28 MB, ratio: 1.1121x) Kernel launches: 260 (ref: 258, fusion ratio: 0.9923) Energy: 241.82 mJ/run (ref: 0.0 mJ, ratio: -1x) SOL score: 0.1239
final submitted kernel (361 lines, 12,143 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

# Global compiled extension
_evoformer_ext = None

def get_evoformer_ext():
    global _evoformer_ext
    if _evoformer_ext is None:
        cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>
#include <cmath>

#define BLOCK_SIZE 256
#define WARP_SIZE 32

// Block-wide reduction for sum
__device__ __forceinline__ float block_reduce_sum(float val) {
    // Warp reduction
    for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {
        val += __shfl_down_sync(0xffffffff, val, offset);
    }
    
    // Each warp leader writes to shared memory
    __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];
    int lane = threadIdx.x % WARP_SIZE;
    int warp_id = threadIdx.x / WARP_SIZE;
    
    if (lane == 0) {
        s_warp_results[warp_id] = val;
    }
    __syncthreads();
    
    // First warp does final reduction
    val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : 0.0f;
    if (warp_id == 0) {
        for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {
            val += __shfl_down_sync(0xffffffff, val, offset);
        }
    }
    
    return val;
}

// Block-wide reduction for max
__device__ __forceinline__ float block_reduce_max(float val) {
    // Warp reduction
    for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {
        val = max(val, __shfl_down_sync(0xffffffff, val, offset));
    }
    
    __shared__ float s_warp_results[BLOCK_SIZE / WARP_SIZE];
    int lane = threadIdx.x % WARP_SIZE;
    int warp_id = threadIdx.x / WARP_SIZE;
    
    if (lane == 0) {
        s_warp_results[warp_id] = val;
    }
    __syncthreads();
    
    val = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_results[threadIdx.x] : -INFINITY;
    if (warp_id == 0) {
        for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset /= 2) {
            val = max(val, __shfl_down_sync(0xffffffff, val, offset));
        }
    }
    
    return val;
}

__global__ void layernorm_kernel(float* __restrict__ output, const float* __restrict__ input,
                                  const float* __restrict__ weight, const float* __restrict__ bias,
                                  int N, int D) {
    int idx = blockIdx.x;
    if (idx >= N) return;
    
    const float* in = input + idx * D;
    float* out = output + idx * D;
    
    // Compute mean
    float sum = 0.0f;
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        sum += in[i];
    }
    sum = block_reduce_sum(sum);
    __shared__ float s_mean, s_inv_std;
    if (threadIdx.x == 0) {
        s_mean = sum / D;
    }
    __syncthreads();
    
    // Compute variance
    float var_sum = 0.0f;
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        float diff = in[i] - s_mean;
        var_sum += diff * diff;
    }
    var_sum = block_reduce_sum(var_sum);
    
    if (threadIdx.x == 0) {
        s_inv_std = rsqrtf(var_sum / D + 1e-5f);
    }
    __syncthreads();
    
    // Normalize
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        out[i] = (in[i] - s_mean) * s_inv_std * weight[i] + bias[i];
    }
}

__global__ void sigmoid_multiply_kernel(
    float* __restrict__ output,
    const float* __restrict__ a, const float* __restrict__ b,
    int total_elements
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < total_elements) {
        output[idx] = a[idx] * (1.0f / (1.0f + expf(-b[idx])));
    }
}

__global__ void softmax_kernel(
    float* __restrict__ output,
    const float* __restrict__ input,
    int N, int D
) {
    int idx = blockIdx.x;
    if (idx >= N) return;
    
    const float* in = input + idx * D;
    float* out = output + idx * D;
    
    // Find max for numerical stability
    float max_val = -INFINITY;
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        max_val = max(max_val, in[i]);
    }
    max_val = block_reduce_max(max_val);
    __shared__ float s_max;
    if (threadIdx.x == 0) {
        s_max = max_val;
    }
    __syncthreads();
    
    // Compute exp and sum
    float sum = 0.0f;
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        out[i] = expf(in[i] - s_max);
        sum += out[i];
    }
    sum = block_reduce_sum(sum);
    __shared__ float s_sum;
    if (threadIdx.x == 0) {
        s_sum = sum;
    }
    __syncthreads();
    
    // Normalize
    for (int i = threadIdx.x; i < D; i += blockDim.x) {
        out[i] /= s_sum;
    }
}

// PyTorch wrappers
void launch_layernorm(torch::Tensor output, torch::Tensor input,
                       torch::Tensor weight, torch::Tensor bias) {
    int N = output.size(0);
    int D = output.size(1);
    layernorm_kernel<<<N, 256>>>(
        output.data_ptr<float>(), input.data_ptr<float>(),
        weight.data_ptr<float>(), bias.data_ptr<float>(), N, D);
}

void launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b) {
    int total = a.numel();
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    sigmoid_multiply_kernel<<<blocks, threads>>>(
        output.data_ptr<float>(), a.data_ptr<float>(), b.data_ptr<float>(), total);
}

void launch_softmax(torch::Tensor output, torch::Tensor input) {
    int N = output.size(0);
    int D = output.size(1);
    softmax_kernel<<<N, 256>>>(output.data_ptr<float>(), input.data_ptr<float>(), N, D);
}
"""
        
        cpp_source = """
void launch_layernorm(torch::Tensor output, torch::Tensor input,
                       torch::Tensor weight, torch::Tensor bias);
void launch_sigmoid_multiply(torch::Tensor output, torch::Tensor a, torch::Tensor b);
void launch_softmax(torch::Tensor output, torch::Tensor input);
"""
        
        _evoformer_ext = load_inline(
            name='evoformer_ext',
            cpp_sources=cpp_source,
            cuda_sources=cuda_source,
            functions=['launch_layernorm', 'launch_sigmoid_multiply', 'launch_softmax'],
            extra_cuda_cflags=['-O3', '--use_fast_math'],
            verbose=False
        )
    return _evoformer_ext


class ModelNew(nn.Module):
    def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):
        super().__init__()
        self.msa_dim = msa_dim
        self.pair_dim = pair_dim
        self.num_heads = num_heads
        self.head_dim = msa_dim // num_heads
        self.tri_hidden = tri_hidden
        
        # MSA Row Self Attention components
        self.msa_norm = nn.LayerNorm(msa_dim)
        self.q = nn.Linear(msa_dim, msa_dim, bias=False)
        self.k = nn.Linear(msa_dim, msa_dim, bias=False)
        self.v = nn.Linear(msa_dim, msa_dim, bias=False)
        self.msa_out = nn.Linear(msa_dim, msa_dim)
        
        # Outer Product Mean components
        self.opm_norm = nn.LayerNorm(msa_dim)
        self.a = nn.Linear(msa_dim, 32)  # hidden = 32
        self.b = nn.Linear(msa_dim, 32)
        self.opm_out = nn.Linear(32 * 32, pair_dim)
        
        # Triangular Multiplicative Update components
        self.tri_norm = nn.LayerNorm(pair_dim)
        self.pa = nn.Linear(pair_dim, tri_hidden)
        self.pb = nn.Linear(pair_dim, tri_hidden)
        self.ga = nn.Linear(pair_dim, tri_hidden)
        self.gb = nn.Linear(pair_dim, tri_hidden)
        self.tri_norm_out = nn.LayerNorm(tri_hidden)
        self.tri_out = nn.Linear(tri_hidden, pair_dim)
        self.gate_out = nn.Linear(pair_dim, pair_dim)
        
        # Initialize CUDA extension
        self.ext = get_evoformer_ext()
    
    def forward(self, msa_repr: torch.Tensor, pair_repr: torch.Tensor):
        B, S, N, D = msa_repr.shape
        h, d = self.num_heads, self.head_dim
        
        # Ensure tensors are contiguous and on GPU
        msa_repr = msa_repr.contiguous()
        pair_repr = pair_repr.contiguous()
        
        # === MSA Row Self Attention ===
        msa_input = msa_repr
        
        # Layer norm on MSA
        msa_flat = msa_input.reshape(B * S * N, D)
        msa_normed = torch.empty_like(msa_flat)
        self.ext.launch_layernorm(msa_normed, msa_flat, self.msa_norm.weight, self.msa_norm.bias)
        msa_normed = msa_normed.reshape(B, S, N, D)
        
        # Q, K, V projections
        q = F.linear(msa_normed, self.q.weight)
        k = F.linear(msa_normed, self.k.weight)
        v = F.linear(msa_normed, self.v.weight)
        
        # Reshape for attention
        q = q.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)  # (B, S, h, N, d)
        k = k.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)
        v = v.reshape(B, S, N, h, d).permute(0, 1, 3, 2, 4)
        
        # Attention scores
        scale = 1.0 / math.sqrt(d)
        attn = (q @ k.transpose(-1, -2)) * scale  # (B, S, h, N, N)
        
        # Softmax
        attn_flat = attn.reshape(-1, N)
        attn_out = torch.empty_like(attn_flat)
        self.ext.launch_softmax(attn_out, attn_flat)
        attn = attn_out.reshape(B, S, h, N, N)
        
        # Attention output
        out = attn @ v  # (B, S, h, N, d)
        out = out.permute(0, 1, 3, 2, 4).reshape(B, S, N, D)
        
        # Output projection
        msa_attn_out = F.linear(out, self.msa_out.weight, self.msa_out.bias)
        
        # Residual add
        msa_repr = msa_input + msa_attn_out
        
        # === Outer Product Mean ===
        pair_input = pair_repr
        
        # Layer norm on MSA
        msa_flat = msa_repr.reshape(B * S * N, D)
        msa_normed = torch.empty_like(msa_flat)
        self.ext.launch_layernorm(msa_normed, msa_flat, self.opm_norm.weight, self.opm_norm.bias)
        msa_normed = msa_normed.reshape(B, S, N, D)
        
        # a and b projections
        a = F.linear(msa_normed, self.a.weight, self.a.bias)  # (B, S, N, 32)
        b = F.linear(msa_normed, self.b.weight, self.b.bias)
        
        # Outer product: einsum("bsih,bsjk->bijhk", a, b)
        outer = torch.einsum("bsih,bsjk->bijhk", a, b)
        outer = outer / max(S, 1)
        
        # Reshape and output projection
        B_out, N1, N2, H1, H2 = outer.shape
        outer_flat = outer.reshape(B_out, N1, N2, H1 * H2)
        opm_out = F.linear(outer_flat, self.opm_out.weight, self.opm_out.bias)
        
        # Residual add
        pair_repr = pair_input + opm_out
        
        # === Triangular Multiplicative Update ===
        pair_input2 = pair_repr
        
        # Layer norm
        pair_flat = pair_repr.reshape(B * N * N, self.pair_dim)
        pair_normed = torch.empty_like(pair_flat)
        self.ext.launch_layernorm(pair_normed, pair_flat, self.tri_norm.weight, self.tri_norm.bias)
        pair_normed = pair_normed.reshape(B, N, N, self.pair_dim)
        
        # Projections with gating
        pa_out = F.linear(pair_normed, self.pa.weight, self.pa.bias)
        ga_out = F.linear(pair_normed, self.ga.weight, self.ga.bias)
        pb_out = F.linear(pair_normed, self.pb.weight, self.pb.bias)
        gb_out = F.linear(pair_normed, self.gb.weight, self.gb.bias)
        
        # Apply sigmoid gates
        a = torch.empty_like(pa_out)
        self.ext.launch_sigmoid_multiply(a, pa_out, ga_out)
        
        b = torch.empty_like(pb_out)
        self.ext.launch_sigmoid_multiply(b, pb_out, gb_out)
        
        # einsum("bikh,bjkh->bijh", a, b)
        o = torch.einsum("bikh,bjkh->bijh", a, b)
        
        # Layer norm output
        o_flat = o.reshape(B * N * N, self.tri_hidden)
        o_normed = torch.empty_like(o_flat)
        self.ext.launch_layernorm(o_normed, o_flat, self.tri_norm_out.weight, self.tri_norm_out.bias)
        o_normed = o_normed.reshape(B, N, N, self.tri_hidden)
        
        # Output projection
        tri_out = F.linear(o_normed, self.tri_out.weight, self.tri_out.bias)
        
        # Gate output
        gate = torch.sigmoid(F.linear(pair_input2, self.gate_out.weight, self.gate_out.bias))
        tri_out = gate * tri_out
        
        # Residual add
        pair_repr = pair_input2 + tri_out
        
        return msa_repr, pair_repr