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\ncpp_source = r\"\"\"\n#include <torch/extension.h>\n#include <vector>\n\n// MSA Row Attention fused kernel\nstd::vector<torch::Tensor> msa_row_attn_cuda(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor q_weight,\n torch::Tensor k_weight,\n torch::Tensor v_weight,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n int num_heads,\n double eps);\n\n// Outer product mean fused kernel\ntorch::Tensor outer_product_mean_cuda(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor a_weight,\n torch::Tensor a_bias,\n torch::Tensor b_weight,\n torch::Tensor b_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n double eps);\n\n// Triangular multiplication fused kernel\ntorch::Tensor tri_mul_out_cuda(\n torch::Tensor pair,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor pa_weight,\n torch::Tensor pa_bias,\n torch::Tensor pb_weight,\n torch::Tensor pb_bias,\n torch::Tensor ga_weight,\n torch::Tensor ga_bias,\n torch::Tensor gb_weight,\n torch::Tensor gb_bias,\n torch::Tensor norm_out_weight,\n torch::Tensor norm_out_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n torch::Tensor gate_weight,\n torch::Tensor gate_bias,\n double eps);\n\n// Wrappers\nstd::vector<torch::Tensor> msa_row_attn(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor q_weight,\n torch::Tensor k_weight,\n torch::Tensor v_weight,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n int num_heads,\n double eps) {\n return msa_row_attn_cuda(msa, ln_weight, ln_bias, q_weight, k_weight, v_weight, out_weight, out_bias, num_heads, eps);\n}\n\ntorch::Tensor outer_product_mean(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor a_weight,\n torch::Tensor a_bias,\n torch::Tensor b_weight,\n torch::Tensor b_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n double eps) {\n return outer_product_mean_cuda(msa, ln_weight, ln_bias, a_weight, a_bias, b_weight, b_bias, out_weight, out_bias, eps);\n}\n\ntorch::Tensor tri_mul_out(\n torch::Tensor pair,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor pa_weight,\n torch::Tensor pa_bias,\n torch::Tensor pb_weight,\n torch::Tensor pb_bias,\n torch::Tensor ga_weight,\n torch::Tensor ga_bias,\n torch::Tensor gb_weight,\n torch::Tensor gb_bias,\n torch::Tensor norm_out_weight,\n torch::Tensor norm_out_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n torch::Tensor gate_weight,\n torch::Tensor gate_bias,\n double eps) {\n return tri_mul_out_cuda(pair, ln_weight, ln_bias, pa_weight, pa_bias, pb_weight, pb_bias, \n ga_weight, ga_bias, gb_weight, gb_bias, norm_out_weight, norm_out_bias,\n out_weight, out_bias, gate_weight, gate_bias, eps);\n}\n\"\"\"\n\ncuda_source = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int TILE_SIZE = 16;\n\n// Fused LayerNorm + Linear projection for multiple outputs\n__global__ void layernorm_multi_linear_kernel(\n const float* __restrict__ input,\n const float* __restrict__ ln_weight,\n const float* __restrict__ ln_bias,\n float* __restrict__ q_out,\n float* __restrict__ k_out,\n float* __restrict__ v_out,\n const float* __restrict__ q_weight,\n const float* __restrict__ k_weight,\n const float* __restrict__ v_weight,\n int num_groups,\n int in_features,\n int out_features,\n float eps) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (idx < num_groups) {\n int start = idx * in_features;\n int out_start = idx * out_features;\n \n // Compute mean\n float mean = 0.0f;\n for (int i = 0; i < in_features; i++) {\n mean += input[start + i];\n }\n mean /= static_cast<float>(in_features);\n \n // Compute variance\n float var = 0.0f;\n for (int i = 0; i < in_features; i++) {\n float diff = input[start + i] - mean;\n var += diff * diff;\n }\n var /= static_cast<float>(in_features);\n \n float inv_std = rsqrtf(var + eps);\n \n // Compute q, k, v projections\n for (int o = 0; o < out_features; o++) {\n float q_sum = 0.0f, k_sum = 0.0f, v_sum = 0.0f;\n for (int i = 0; i < in_features; i++) {\n float normalized = (input[start + i] - mean) * inv_std * ln_weight[i] + ln_bias[i];\n q_sum += normalized * q_weight[o * in_features + i];\n k_sum += normalized * k_weight[o * in_features + i];\n v_sum += normalized * v_weight[o * in_features + i];\n }\n q_out[out_start + o] = q_sum;\n k_out[out_start + o] = k_sum;\n v_out[out_start + o] = v_sum;\n }\n }\n}\n\n// Softmax kernel\n__device__ void warp_softmax(float* data, int len) {\n // Find max\n float max_val = data[0];\n for (int i = 1; i < len; i++) {\n max_val = fmaxf(max_val, data[i]);\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int i = 0; i < len; i++) {\n data[i] = expf(data[i] - max_val);\n sum += data[i];\n }\n \n // Normalize\n for (int i = 0; i < len; i++) {\n data[i] /= sum;\n }\n}\n\n// Attention computation kernel\n__global__ void attention_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n const float* __restrict__ v,\n float* __restrict__ out,\n int B, int S, int N, int h, int d,\n float scale) {\n \n // Each thread block handles one (batch, seq_pos, head) combination\n int b = blockIdx.x / (S * h);\n int s = (blockIdx.x / h) % S;\n int head = blockIdx.x % h;\n \n if (b >= B) return;\n \n int seq_len = N;\n int head_idx = b * S * h * N * d + s * h * N * d + head * N * d;\n \n // Shared memory for q, k transposed, and attention scores\n extern __shared__ float smem[];\n float* q_smem = smem; // d elements\n float* attn_smem = smem + d; // N elements\n float* out_smem = smem + d + N; // d elements\n \n // Load q for this position\n for (int i = threadIdx.x; i < d; i += blockDim.x) {\n q_smem[i] = q[head_idx + i];\n }\n __syncthreads();\n \n // Compute attention scores and accumulate output\n for (int i = threadIdx.x; i < d; i += blockDim.x) {\n out_smem[i] = 0.0f;\n }\n __syncthreads();\n \n // For each key position\n for (int key_pos = 0; key_pos < N; key_pos++) {\n // Compute attention score: q @ k^T\n float score = 0.0f;\n for (int i = 0; i < d; i++) {\n int k_idx = b * S * h * N * d + s * h * N * d + head * N * d + key_pos * d + i;\n score += q_smem[i] * k[k_idx];\n }\n attn_smem[key_pos] = score * scale;\n }\n __syncthreads();\n \n // Softmax\n if (threadIdx.x == 0) {\n warp_softmax(attn_smem, N);\n }\n __syncthreads();\n \n // Weighted sum of values\n for (int i = threadIdx.x; i < d; i += blockDim.x) {\n float val = 0.0f;\n for (int key_pos = 0; key_pos < N; key_pos++) {\n int v_idx = b * S * h * N * d + s * h * N * d + head * N * d + key_pos * d + i;\n val += attn_smem[key_pos] * v[v_idx];\n }\n out_smem[i] = val;\n }\n __syncthreads();\n \n // Write output\n for (int i = threadIdx.x; i < d; i += blockDim.x) {\n out[head_idx + i] = out_smem[i];\n }\n}\n\n// Final output projection kernel\n__global__ void output_proj_kernel(\n const float* __restrict__ input,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n float* __restrict__ output,\n int num_groups,\n int in_features,\n int out_features) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (idx < num_groups) {\n int in_start = idx * in_features;\n int out_start = idx * out_features;\n \n for (int o = 0; o < out_features; o++) {\n float sum = bias[o];\n for (int i = 0; i < in_features; i++) {\n sum += input[in_start + i] * weight[o * in_features + i];\n }\n output[out_start + o] = sum;\n }\n }\n}\n\nstd::vector<torch::Tensor> msa_row_attn_cuda(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor q_weight,\n torch::Tensor k_weight,\n torch::Tensor v_weight,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n int num_heads,\n double eps) {\n \n TORCH_CHECK(msa.is_cuda(), \"MSA must be a CUDA tensor\");\n \n auto sizes = msa.sizes();\n int64_t B = sizes[0], S = sizes[1], N = sizes[2], D = sizes[3];\n int64_t d = D / num_heads;\n int64_t h = num_heads;\n \n int64_t num_groups = B * S * N;\n \n auto q_out = torch::empty({B, S, N, D}, msa.options());\n auto k_out = torch::empty({B, S, N, D}, msa.options());\n auto v_out = torch::empty({B, S, N, D}, msa.options());\n \n // Fused LayerNorm + QKV projections\n int blocks = (static_cast<int>(num_groups) + BLOCK_SIZE - 1) / BLOCK_SIZE;\n \n layernorm_multi_linear_kernel<<<blocks, BLOCK_SIZE>>>(\n msa.data_ptr<float>(),\n ln_weight.data_ptr<float>(),\n ln_bias.data_ptr<float>(),\n q_out.data_ptr<float>(),\n k_out.data_ptr<float>(),\n v_out.data_ptr<float>(),\n q_weight.data_ptr<float>(),\n k_weight.data_ptr<float>(),\n v_weight.data_ptr<float>(),\n static_cast<int>(num_groups),\n static_cast<int>(D),\n static_cast<int>(D),\n static_cast<float>(eps));\n \n // Reshape for attention: [B, S, N, D] -> [B, S, h, N, d]\n auto q_reshaped = q_out.view({B, S, N, h, d}).permute({0, 1, 3, 2, 4}).contiguous();\n auto k_reshaped = k_out.view({B, S, N, h, d}).permute({0, 1, 3, 2, 4}).contiguous();\n auto v_reshaped = v_out.view({B, S, N, h, d}).permute({0, 1, 3, 2, 4}).contiguous();\n \n // Attention output\n auto attn_out = torch::empty({B, S, h, N, d}, msa.options());\n \n float scale = 1.0f / sqrtf(static_cast<float>(d));\n int attn_blocks = B * S * h;\n int smem_size = (d + N + d) * sizeof(float);\n \n attention_kernel<<<attn_blocks, 64, smem_size>>>(\n q_reshaped.data_ptr<float>(),\n k_reshaped.data_ptr<float>(),\n v_reshaped.data_ptr<float>(),\n attn_out.data_ptr<float>(),\n B, S, N, h, d, scale);\n \n // Reshape back: [B, S, h, N, d] -> [B, S, N, D]\n auto attn_out_reshaped = attn_out.permute({0, 1, 3, 2, 4}).contiguous().view({B, S, N, D});\n \n // Output projection\n auto final_out = torch::empty({B, S, N, D}, msa.options());\n blocks = (static_cast<int>(num_groups) + BLOCK_SIZE - 1) / BLOCK_SIZE;\n \n output_proj_kernel<<<blocks, BLOCK_SIZE>>>(\n attn_out_reshaped.data_ptr<float>(),\n out_weight.data_ptr<float>(),\n out_bias.data_ptr<float>(),\n final_out.data_ptr<float>(),\n static_cast<int>(num_groups),\n static_cast<int>(D),\n static_cast<int>(D));\n \n return {final_out, q_out, k_out, v_out};\n}\n\n// Outer product mean kernel\n__global__ void outer_product_kernel(\n const float* __restrict__ input,\n const float* __restrict__ ln_weight,\n const float* __restrict__ ln_bias,\n const float* __restrict__ a_weight,\n const float* __restrict__ a_bias,\n const float* __restrict__ b_weight,\n const float* __restrict__ b_bias,\n float* __restrict__ a_out,\n float* __restrict__ b_out,\n int num_groups,\n int in_features,\n int hidden,\n float eps) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (idx < num_groups) {\n int in_start = idx * in_features;\n int out_start = idx * hidden;\n \n // Compute mean\n float mean = 0.0f;\n for (int i = 0; i < in_features; i++) {\n mean += input[in_start + i];\n }\n mean /= static_cast<float>(in_features);\n \n // Compute variance\n float var = 0.0f;\n for (int i = 0; i < in_features; i++) {\n float diff = input[in_start + i] - mean;\n var += diff * diff;\n }\n var /= static_cast<float>(in_features);\n \n float inv_std = rsqrtf(var + eps);\n \n // Compute a and b projections\n for (int o = 0; o < hidden; o++) {\n float a_sum = a_bias[o];\n float b_sum = b_bias[o];\n for (int i = 0; i < in_features; i++) {\n float normalized = (input[in_start + i] - mean) * inv_std * ln_weight[i] + ln_bias[i];\n a_sum += normalized * a_weight[o * in_features + i];\n b_sum += normalized * b_weight[o * in_features + i];\n }\n a_out[out_start + o] = a_sum;\n b_out[out_start + o] = b_sum;\n }\n }\n}\n\ntorch::Tensor outer_product_mean_cuda(\n torch::Tensor msa,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor a_weight,\n torch::Tensor a_bias,\n torch::Tensor b_weight,\n torch::Tensor b_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n double eps) {\n \n TORCH_CHECK(msa.is_cuda(), \"MSA must be a CUDA tensor\");\n \n auto sizes = msa.sizes();\n int64_t B = sizes[0], S = sizes[1], N = sizes[2], D = sizes[3];\n int64_t hidden = a_weight.size(0);\n \n int64_t num_groups = B * S * N;\n \n auto a_out = torch::empty({B, S, N, hidden}, msa.options());\n auto b_out = torch::empty({B, S, N, hidden}, msa.options());\n \n int blocks = (static_cast<int>(num_groups) + BLOCK_SIZE - 1) / BLOCK_SIZE;\n \n outer_product_kernel<<<blocks, BLOCK_SIZE>>>(\n msa.data_ptr<float>(),\n ln_weight.data_ptr<float>(),\n ln_bias.data_ptr<float>(),\n a_weight.data_ptr<float>(),\n a_bias.data_ptr<float>(),\n b_weight.data_ptr<float>(),\n b_bias.data_ptr<float>(),\n a_out.data_ptr<float>(),\n b_out.data_ptr<float>(),\n static_cast<int>(num_groups),\n static_cast<int>(D),\n static_cast<int>(hidden),\n static_cast<float>(eps));\n \n // Outer product: einsum(\"bsih,bsjk->bijhk\", a, b)\n // a: [B, S, N, H], b: [B, S, N, H] -> [B, N, N, H, H]\n // Use PyTorch for the einsum since it's well optimized\n auto outer = torch::einsum(\"bsih,bsjk->bijhk\", {a_out, b_out});\n \n // Scale by sequence depth\n outer = outer / static_cast<float>(S);\n \n // Output projection\n int64_t N1 = outer.size(1), N2 = outer.size(2), H1 = outer.size(3), H2 = outer.size(4);\n auto outer_flat = outer.reshape({B, N1, N2, H1 * H2});\n \n return torch::linear(outer_flat, out_weight, out_bias);\n}\n\n// TriMulOut kernel\n__global__ void tri_mul_layernorm_linear_kernel(\n const float* __restrict__ input,\n const float* __restrict__ ln_weight,\n const float* __restrict__ ln_bias,\n const float* __restrict__ pa_weight,\n const float* __restrict__ pa_bias,\n const float* __restrict__ pb_weight,\n const float* __restrict__ pb_bias,\n const float* __restrict__ ga_weight,\n const float* __restrict__ ga_bias,\n const float* __restrict__ gb_weight,\n const float* __restrict__ gb_bias,\n float* __restrict__ pa_out,\n float* __restrict__ pb_out,\n int num_groups,\n int in_features,\n int hidden,\n float eps) {\n \n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (idx < num_groups) {\n int in_start = idx * in_features;\n int out_start = idx * hidden;\n \n // Compute mean\n float mean = 0.0f;\n for (int i = 0; i < in_features; i++) {\n mean += input[in_start + i];\n }\n mean /= static_cast<float>(in_features);\n \n // Compute variance\n float var = 0.0f;\n for (int i = 0; i < in_features; i++) {\n float diff = input[in_start + i] - mean;\n var += diff * diff;\n }\n var /= static_cast<float>(in_features);\n \n float inv_std = rsqrtf(var + eps);\n \n // Compute projections with gating\n for (int o = 0; o < hidden; o++) {\n float pa_sum = pa_bias[o];\n float pb_sum = pb_bias[o];\n float ga_sum = ga_bias[o];\n float gb_sum = gb_bias[o];\n \n for (int i = 0; i < in_features; i++) {\n float normalized = (input[in_start + i] - mean) * inv_std * ln_weight[i] + ln_bias[i];\n pa_sum += normalized * pa_weight[o * in_features + i];\n pb_sum += normalized * pb_weight[o * in_features + i];\n ga_sum += normalized * ga_weight[o * in_features + i];\n gb_sum += normalized * gb_weight[o * in_features + i];\n }\n \n // Apply sigmoid gating\n pa_out[out_start + o] = pa_sum * (1.0f / (1.0f + expf(-ga_sum)));\n pb_out[out_start + o] = pb_sum * (1.0f / (1.0f + expf(-gb_sum)));\n }\n }\n}\n\ntorch::Tensor tri_mul_out_cuda(\n torch::Tensor pair,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor pa_weight,\n torch::Tensor pa_bias,\n torch::Tensor pb_weight,\n torch::Tensor pb_bias,\n torch::Tensor ga_weight,\n torch::Tensor ga_bias,\n torch::Tensor gb_weight,\n torch::Tensor gb_bias,\n torch::Tensor norm_out_weight,\n torch::Tensor norm_out_bias,\n torch::Tensor out_weight,\n torch::Tensor out_bias,\n torch::Tensor gate_weight,\n torch::Tensor gate_bias,\n double eps) {\n \n TORCH_CHECK(pair.is_cuda(), \"Pair must be a CUDA tensor\");\n \n auto sizes = pair.sizes();\n int64_t B = sizes[0], N1 = sizes[1], N2 = sizes[2], D = sizes[3];\n int64_t hidden = pa_weight.size(0);\n \n int64_t num_groups = B * N1 * N2;\n \n auto pa_out = torch::empty({B, N1, N2, hidden}, pair.options());\n auto pb_out = torch::empty({B, N1, N2, hidden}, pair.options());\n \n int blocks = (static_cast<int>(num_groups) + BLOCK_SIZE - 1) / BLOCK_SIZE;\n \n tri_mul_layernorm_linear_kernel<<<blocks, BLOCK_SIZE>>>(\n pair.data_ptr<float>(),\n ln_weight.data_ptr<float>(),\n ln_bias.data_ptr<float>(),\n pa_weight.data_ptr<float>(),\n pa_bias.data_ptr<float>(),\n pb_weight.data_ptr<float>(),\n pb_bias.data_ptr<float>(),\n ga_weight.data_ptr<float>(),\n ga_bias.data_ptr<float>(),\n gb_weight.data_ptr<float>(),\n gb_bias.data_ptr<float>(),\n pa_out.data_ptr<float>(),\n pb_out.data_ptr<float>(),\n static_cast<int>(num_groups),\n static_cast<int>(D),\n static_cast<int>(hidden),\n static_cast<float>(eps));\n \n // einsum(\"bikh,bjkh->bijh\", a, b)\n auto o = torch::einsum(\"bikh,bjkh->bijh\", {pa_out, pb_out});\n \n // Norm out\n o = torch::layer_norm(o, {hidden}, norm_out_weight, norm_out_bias, eps);\n \n // Output projection\n o = torch::linear(o, out_weight, out_bias);\n \n // Gating\n auto gate = torch::sigmoid(torch::linear(pair, gate_weight, gate_bias));\n \n return gate * o;\n}\n\"\"\"\n\nevoformer_ext = load_inline(\n name='evoformer_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['msa_row_attn', 'outer_product_mean', 'tri_mul_out'],\n verbose=True\n)\n\n\nclass MSARowSelfAttention(nn.Module):\n def __init__(self, msa_dim, num_heads):\n super().__init__()\n self.num_heads = num_heads\n self.head_dim = msa_dim // num_heads\n self.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.out = nn.Linear(msa_dim, msa_dim)\n\n def forward(self, msa):\n B, S, N, D = msa.shape\n \n results = evoformer_ext.msa_row_attn(\n msa.contiguous(),\n self.norm.weight,\n self.norm.bias,\n self.q.weight,\n self.k.weight,\n self.v.weight,\n self.out.weight,\n self.out.bias,\n self.num_heads,\n 1e-5\n )\n return results[0]\n\n\nclass OuterProductMeanSmall(nn.Module):\n def __init__(self, msa_dim, pair_dim, hidden=32):\n super().__init__()\n self.norm = nn.LayerNorm(msa_dim)\n self.a = nn.Linear(msa_dim, hidden)\n self.b = nn.Linear(msa_dim, hidden)\n self.out = nn.Linear(hidden * hidden, pair_dim)\n\n def forward(self, msa):\n return evoformer_ext.outer_product_mean(\n msa.contiguous(),\n self.norm.weight,\n self.norm.bias,\n self.a.weight,\n self.a.bias,\n self.b.weight,\n self.b.bias,\n self.out.weight,\n self.out.bias,\n 1e-5\n )\n\n\nclass TriMulOut(nn.Module):\n def __init__(self, pair_dim, hidden):\n super().__init__()\n self.norm = nn.LayerNorm(pair_dim)\n self.pa = nn.Linear(pair_dim, hidden)\n self.pb = nn.Linear(pair_dim, hidden)\n self.ga = nn.Linear(pair_dim, hidden)\n self.gb = nn.Linear(pair_dim, hidden)\n self.norm_out = nn.LayerNorm(hidden)\n self.out = nn.Linear(hidden, pair_dim)\n self.gate_out = nn.Linear(pair_dim, pair_dim)\n\n def forward(self, pair):\n return evoformer_ext.tri_mul_out(\n pair.contiguous(),\n self.norm.weight,\n self.norm.bias,\n self.pa.weight,\n self.pa.bias,\n self.pb.weight,\n self.pb.bias,\n self.ga.weight,\n self.ga.bias,\n self.gb.weight,\n self.gb.bias,\n self.norm_out.weight,\n self.norm_out.bias,\n self.out.weight,\n self.out.bias,\n self.gate_out.weight,\n self.gate_out.bias,\n 1e-5\n )\n\n\nclass ModelNew(nn.Module):\n def __init__(self, msa_dim, pair_dim, num_heads, tri_hidden):\n super().__init__()\n self.msa_attn = MSARowSelfAttention(msa_dim, num_heads)\n self.opm = OuterProductMeanSmall(msa_dim, pair_dim)\n self.tri_mul = TriMulOut(pair_dim, tri_hidden)\n\n def forward(\n self, msa_repr: torch.Tensor, pair_repr: torch.Tensor\n ) -> tuple:\n msa_repr = msa_repr + self.msa_attn(msa_repr)\n pair_repr = pair_repr + self.opm(msa_repr)\n pair_repr = pair_repr + self.tri_mul(pair_repr)\n return msa_repr, pair_repr"
}