FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 43_ContactMapPrediction.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
13528
wall clock
368.6s
truncated
yes
backend
cuda fp32
variant
popcorn
runtime
0.67 μs
ref runtime
0.17 μs
speedup
0.25x
started
2026-05-11T09:48:07Z
timingkernelbench.timing
kernel runtime
0.67 μs
ref runtime
0.17 μs
speedup
0.25x
kernel mean / median / std
0.67 / — / 0.01 μs
ref mean / median / std
0.17 / — / 0.08 μs
numerical precisioneval_kernel_against_ref
max abs error
1.19e-07
mean abs error
1.23e-08
max rel error
2.37e-07
mean rel error
2.38e-08
gpu memoryextended_metrics.measure_memory
kernel peak
37.09 MB
ref peak
49.03 MB
kernel / ref
0.76x
kernel launches / fusiontorch.profiler
kernel launches
6
ref launches
41
fusion ratio (ref / kernel)
6.83x
total cuda time
336 μs
energyNVML (pynvml)
kernel mJ / run
241.86
ref mJ / run
0.00
ratio (ref / kernel)
—
avg power
369.64 W
ref avg power
0.00 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.002
dram util %
0.2%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
7.96 GB/s
dram util %
0.2%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
18.7%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
177.9%
ld sectors / req
12.40
st sectors / req
—
regs / thread
48
smem / block
2048 B
block size
384
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
2771.6%
short_scoreboard
395.9%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F


class Model(nn.Module):
    """
    Contact map prediction head.  Takes residue-pair embeddings and
    predicts a symmetric binary contact probability matrix (residue i
    within 8 Å of residue j).  Uses a small MLP on the symmetrised pair
    representation followed by a sigmoid.  Common final stage in protein
    structure prediction pipelines.
    """

    def __init__(self, pair_dim, hidden_dim):
        super().__init__()
        self.layer_norm = nn.LayerNorm(pair_dim)
        self.mlp = nn.Sequential(
            nn.Linear(pair_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1),
        )

    def forward(self, pair_repr: torch.Tensor) -> torch.Tensor:
        """
        Args:
            pair_repr: (B, N, N, pair_dim)
        Returns:
            contact_probs: (B, N, N) – symmetric contact probability
        """
        x = self.layer_norm(pair_repr)
        # Symmetrise
        x = (x + x.permute(0, 2, 1, 3)) / 2.0
        logits = self.mlp(x).squeeze(-1)  # (B, N, N)
        return torch.sigmoid(logits)


pair_dim = 64
hidden_dim = 32
seq_len = 64
batch_size = 4


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


def get_init_inputs():
    return [pair_dim, hidden_dim]
turn 0 · FINAL · 1 tool call(s) · 368.6s turn · 114.3s LLM · tokens in=1,167 · out=12,361 · total=13,528 · 26,111 chars (click to expand)
turn 0368.6s turn · 114.3s LLM · 254.3s tools · tokens in=1,167 · out=12,361 · total=13,528 · FINAL · reasoning clipped
reasoning
reasoning (truncated 23027 chars): Let me analyze the task: 1. We have a contact map prediction head that: - Takes pair_repr of shape (B, N, N, pair_dim) - Applies LayerNorm - Symmetrizes by averaging with transpose - Passes through MLP: Linear->ReLU->Linear->ReLU->Linear - Squeezes last dimension - Applies sigmoid 2. The MLP has: - Linear(pair_dim, hidden_dim) - ReLU - Linear(hidden_dim, hidden_dim) - ReLU - Linear(hidden_dim, 1) 3. Parameters to handle: - LayerNorm weights and biases (pair_dim) - Linear1: weight (hidden_dim, pair_dim), bias (hidden_dim) - Linear2: weight (hidden_dim, hidden_dim), bias (hidden_dim) - Linear3: weight (1, hidden_dim), bias (1) Let me design an efficient CUDA kernel that fuses all these operations: 1. Load weights from PyTorch model 2. Create a kernel that: - Loads pair_repr element - Applies LayerNorm (normalize across last dimension) - Symmetrizes - Applies MLP - Applies sigmoid For efficiency, I should: - Fuse LayerNorm with the symmetrization - Fuse the MLP operations - Use shared memory for LayerNorm statistics computation - Vectorize loads/stores where possible Let me write the kernel: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline cuda_source = """ #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <math.h> template <typename scalar_t> __global__ void contact_map_kernel( const scalar_t* __restrict__ pair_repr, const scalar_t* __restrict__ ln_weight, const scalar_t* __restrict__ ln_bias, const scalar_t* __restrict__ linear1_weight, const scalar_t* __restrict__ linear1_bias, const scalar_t* __restrict__ linear2_weight, const scalar_t* __restrict__ linear2_bias, const scalar_t* __restrict__ linear3_weight, const scalar_t* __restrict__ linear3_bias, scalar_t* __restrict__ output, int batch_size, int seq_len, int pair_dim, int hidden_dim) { // Each thread handles one (b, i, j) output element int b = blockIdx.z; int i = blockIdx.y; int j = blockIdx.x * blockDim.x + threadIdx.x; if (b >= batch_size || i >= seq_len || j >= seq_len) return; // Load pair representations for positions (i,j) and (j,i) // pair_repr[b, i, j, :] and pair_repr[b, j, i, :] extern __shared__ char shared_mem[]; scalar_t* mean_ij = (scalar_t*)shared_mem; scalar_t* mean_ji = mean_ij + pair_dim; scalar_t* var_ij = mean_ji + pair_dim; scalar_t* var_ji = var_ij + pair_dim; // Compute LayerNorm for (i,j) position scalar_t sum_ij = 0; scalar_t sum_sq_ij = 0; for (int d = 0; d < pair_dim; d++) { scalar_t val = pair_repr[b * seq_len * seq_len * pair_dim + i * seq_len * pair_dim + j * pair_dim + d]; sum_ij += val; sum_sq_ij += val * val; } scalar_t mean_val_ij = sum_ij / pair_dim; scalar_t var_val_ij = sum_sq_ij / pair_dim - mean_val_ij * mean_val_ij; scalar_t inv_std_ij = rsqrtf(var_val_ij + 1e-5f); // Compute LayerNorm for (j,i) position scalar_t sum_ji = 0; scalar_t sum_sq_ji = 0; for (int d = 0; d < pair_dim; d++) { scalar_t val = pair_repr[b * seq_len * seq_len * pair_dim + j * seq_len * pair_dim + i * pair_dim + d]; sum_ji += val; sum_sq_ji += val * val; } scalar_t mean_val_ji = sum_ji / pair_dim; scalar_t var_val_ji = sum_sq_ji / pair_dim - mean_val_ji * mean_val_ji; scalar_t inv_std_ji = rsqrtf(var_val_ji + 1e-5f); // Symmetrize after LayerNorm: (ln(i,j) + ln(j,i)) / 2 // Apply MLP on symmetrized representation // First linear: pair_dim -> hidden_dim scalar_t hidden1[32]; // Assuming hidden_dim <= 32 for (int h = 0; h < hidden_dim; h++) { scalar_t acc = linear1_bias[h]; for (int d = 0; d < pair_dim; d++) { scalar_t val_ij = (pair_repr[b * seq_len * seq_len * pair_dim + i * seq_len * pair_dim + j * pair_dim + d] - mean_val_ij) * inv_std_ij; val_ij = val_ij * ln_weight[d] + ln_bias[d]; scalar_t val_ji = (pair_repr[b * seq_len * seq_len * pair_dim + j * seq_len * pair_dim + i * pair_dim + d] - mean_val_ji) * inv_std_ji; val_ji = val_ji * ln_weight[d] + ln_bias[d]; scalar_t sym_val = (val_ij + val_ji) / 2.0f; acc += sym_val * linear1_weight[h * pair_dim + d]; } hidden1[h] = max(0.0f, acc); // ReLU } // Second linear: hidden_dim -> hidden_dim scalar_t hidden2[32]; for (int h = 0; h < hidden_dim; h++) { scalar_t acc = linear2_bias[h]; for (int h1 = 0; h1 < hidden_dim; h1++) { acc += hidden1[h1] * linear2_weight[h * hidden_dim + h1]; } hidden2[h] = max(0.0f, acc); // ReLU } // Third linear: hidden_dim -> 1 scalar_t logit = linear3_bias[0]; for (int h = 0; h < hidden_dim; h++) { logit += hidden2[h] * linear3_weight[h]; } // Sigmoid output[b * seq_len * seq_len + i * seq_len + j] = 1.0f / (1.0f + expf(-logit)); } torch::Tensor contact_map_forward( torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias) { int batch_size = pair_repr.size(0); int seq_len = pair_repr.size(1); int pair_dim = pair_repr.size(3); int hidden_dim = linear1_weight.size(0); auto output = torch::zeros({batch_size, seq_len, seq_len}, pair_repr.options()); dim3 blocks(seq_len, seq_len, batch_size); int threads = 1; AT_DISPATCH_FLOATING_TYPES(pair_repr.scalar_type(), "contact_map_kernel", ([&] { contact_map_kernel<scalar_t><<<blocks, threads>>>( pair_repr.data_ptr<scalar_t>(), ln_weight.data_ptr<scalar_t>(), ln_bias.data_ptr<scalar_t>(), linear1_weight.data_ptr<scalar_t>(), linear1_bias.data_ptr<scalar_t>(), linear2_weight.data_ptr<scalar_t>(), linear2_bias.data_ptr<scalar_t>(), linear3_weight.data_ptr<scalar_t>(), linear3_bias.data_ptr<scalar_t>(), output.data_ptr<scalar_t>(), batch_size, seq_len, pair_dim, hidden_dim); })); return output; } """ contact_map_ext = load_inline( name='contact_map_ext', cpp_sources=['torch::Tensor contact_map_forward(torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias);'], cuda_sources=[cuda_source], functions=['contact_map_forward'], with_cuda=True, extra_cuda_cflags=['-O3'], ) class ModelNew(nn.Module): def __init__(self, pair_dim, hidden_dim): super().__init__() self.layer_norm = nn.LayerNorm(pair_dim) self.mlp = nn.Sequential( nn.Linear(pair_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1), ) def forward(self, pair_repr: torch.Tensor) -> torch.Tensor: # Get weights ln_weight = self.layer_norm.weight ln_bias = self.layer_norm.bias linear1_weight = self.mlp[0].weight linear1_bias = self.mlp[0].bias linear2_weight = self.mlp[3].weight linear2_bias = self.mlp[3].bias linear3_weight = se ...[ omitted 23027 characters ]... scalar_t>(pair_dim) - mean_ij * mean_ij; scalar_t inv_std_ij = rsqrt_wrapper(var_ij + static_cast<scalar_t>(1e-5)); // Compute LayerNorm statistics for (j,i) scalar_t sum_ji = 0; scalar_t sum_sq_ji = 0; #pragma unroll 8 for (int d = 0; d < pair_dim; d++) { scalar_t val = ptr_ji[d]; sum_ji += val; sum_sq_ji += val * val; } scalar_t mean_ji = sum_ji / static_cast<scalar_t>(pair_dim); scalar_t var_ji = sum_sq_ji / static_cast<scalar_t>(pair_dim) - mean_ji * mean_ji; scalar_t inv_std_ji = rsqrt_wrapper(var_ji + static_cast<scalar_t>(1e-5)); // Allocate hidden arrays (assuming hidden_dim <= 64) scalar_t hidden1[64]; scalar_t hidden2[64]; // First linear layer with fused LayerNorm and symmetrization for (int h = 0; h < hidden_dim; h++) { scalar_t acc = linear1_bias[h]; const scalar_t* w_ptr = linear1_weight + h * pair_dim; #pragma unroll 8 for (int d = 0; d < pair_dim; d++) { // LayerNorm + affine for (i,j) scalar_t val_ij = (ptr_ij[d] - mean_ij) * inv_std_ij; val_ij = val_ij * ln_weight[d] + ln_bias[d]; // LayerNorm + affine for (j,i) scalar_t val_ji = (ptr_ji[d] - mean_ji) * inv_std_ji; val_ji = val_ji * ln_weight[d] + ln_bias[d]; // Symmetrize and apply weight scalar_t sym_val = (val_ij + val_ji) * static_cast<scalar_t>(0.5); acc += sym_val * w_ptr[d]; } hidden1[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0); // ReLU } // Second linear layer for (int h = 0; h < hidden_dim; h++) { scalar_t acc = linear2_bias[h]; const scalar_t* w_ptr = linear2_weight + h * hidden_dim; for (int h1 = 0; h1 < hidden_dim; h1++) { acc += hidden1[h1] * w_ptr[h1]; } hidden2[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0); // ReLU } // Third linear layer scalar_t logit = linear3_bias[0]; for (int h = 0; h < hidden_dim; h++) { logit += hidden2[h] * linear3_weight[h]; } // Sigmoid output[idx] = static_cast<scalar_t>(1) / (static_cast<scalar_t>(1) + exp(-logit)); } torch::Tensor contact_map_forward( torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias) { // Ensure inputs are contiguous pair_repr = pair_repr.contiguous(); ln_weight = ln_weight.contiguous(); ln_bias = ln_bias.contiguous(); linear1_weight = linear1_weight.contiguous(); linear1_bias = linear1_bias.contiguous(); linear2_weight = linear2_weight.contiguous(); linear2_bias = linear2_bias.contiguous(); linear3_weight = linear3_weight.contiguous(); linear3_bias = linear3_bias.contiguous(); int batch_size = pair_repr.size(0); int seq_len = pair_repr.size(1); int pair_dim = pair_repr.size(3); int hidden_dim = linear1_weight.size(0); auto output = torch::zeros({batch_size, seq_len, seq_len}, pair_repr.options()); int total = batch_size * seq_len * seq_len; int threads = min(total, 256); int blocks = (total + threads - 1) / threads; AT_DISPATCH_FLOATING_TYPES(pair_repr.scalar_type(), "contact_map_kernel", ([&] { contact_map_kernel<scalar_t><<<blocks, threads>>>( pair_repr.data_ptr<scalar_t>(), ln_weight.data_ptr<scalar_t>(), ln_bias.data_ptr<scalar_t>(), linear1_weight.data_ptr<scalar_t>(), linear1_bias.data_ptr<scalar_t>(), linear2_weight.data_ptr<scalar_t>(), linear2_bias.data_ptr<scalar_t>(), linear3_weight.data_ptr<scalar_t>(), linear3_bias.data_ptr<scalar_t>(), output.data_ptr<scalar_t>(), batch_size, seq_len, pair_dim, hidden_dim); })); return output; } """ contact_map_ext = load_inline( name='contact_map_ext_v1', cpp_sources=['torch::Tensor contact_map_forward(torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias);'], cuda_sources=[cuda_source], functions=['contact_map_forward'], with_cuda=True, extra_cuda_cflags=['-O3', '--use_fast_math'], ) class ModelNew(nn.Module): def __init__(self, pair_dim, hidden_dim): super().__init__() self.layer_norm = nn.LayerNorm(pair_dim) self.mlp = nn.Sequential( nn.Linear(pair_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1), ) def forward(self, pair_repr: torch.Tensor) -> torch.Tensor: # Get weights from the model ln_weight = self.layer_norm.weight ln_bias = self.layer_norm.bias linear1_weight = self.mlp[0].weight linear1_bias = self.mlp[0].bias linear2_weight = self.mlp[2].weight linear2_bias = self.mlp[2].bias linear3_weight = self.mlp[4].weight.squeeze(0) linear3_bias = self.mlp[4].bias return contact_map_ext.contact_map_forward( pair_repr, ln_weight, ln_bias, linear1_weight, linear1_bias, linear2_weight, linear2_bias, linear3_weight, linear3_bias ) pair_dim = 64 hidden_dim = 32 seq_len = 64 batch_size = 4 def get_inputs(): return [torch.randn(batch_size, seq_len, seq_len, pair_dim)] def get_init_inputs(): return [pair_dim, hidden_dim] ``` Wait, I should double-check if my layer norm implementation matches PyTorch's exactly. PyTorch's LayerNorm applies: ``` y = (x - mean) / sqrt(var + eps) * weight + bias ``` Where mean and var are computed over the normalized dimensions (in this case, just the last dimension). My implementation does the same, so it should match. Let me also make sure the epsilon is correct. PyTorch's default is 1e-5, which I'm using. One more thing to check: the layer norm in PyTorch normalizes over the last dimension by default, which is what I'm doing. I think the kernel is correct. Let me submit it. Actually, let me reconsider the performance. The current approach has each thread doing: 1. 2 * pair_dim reads for layer norm stats 2. hidden_dim * pair_dim reads for the first linear layer (with symmetrization) 3. hidden_dim * hidden_dim FLOPs for the second linear layer 4. hidden_dim FLOPs for the third linear layer The memory reads are the bottleneck. Since we're re-reading the same data multiple times, we could benefit from caching. One optimization is to use texture cache or __ldg. Modern GPUs automatically use the read-only cache for __restrict__ const pointers, so we should get this for free. Another optimization is to compute the layer norm output once and cache it. But this would require additional storage or shared memory. For now, let me stick with the current implementation and see the results. Actually, I just realized I can improve the kernel by: 1. Using vectorized loads (float4) to load multiple elements at once 2. Loop unrolling for the hidden dimension loops But for simplicity, let me first submit the straightforward implementation. Let me also add proper error handling in case the hidden_dim exceeds 64. But for the given task (hidden_dim=32), this should be fine. One final thing: I should make sure the kernel handles different hidden_dim values correctly. The current implementation uses fixed-size arrays of 64 elements, which should work for hidden_dim <= 64. Let me finalize and submit.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\ntemplate <typename scalar_t>\n__device__ __forceinline__ scalar_t rsqrt_wrapper(scalar_t x);\n\ntemplate<>\n__device__ __forceinline__ float rsqrt_wrapper<float>(float x) {\n return rsqrtf(x);\n}\n\ntemplate<>\n__device__ __forceinline__ double rsqrt_wrapper<double>(double x) {\n return rsqrt(x);\n}\n\ntemplate <typename scalar_t>\n__global__ void contact_map_kernel(\n const scalar_t* __restrict__ pair_repr,\n const scalar_t* __restrict__ ln_weight,\n const scalar_t* __restrict__ ln_bias,\n const scalar_t* __restrict__ linear1_weight,\n const scalar_t* __restrict__ linear1_bias,\n const scalar_t* __restrict__ linear2_weight,\n const scalar_t* __restrict__ linear2_bias,\n const scalar_t* __restrict__ linear3_weight,\n const scalar_t* __restrict__ linear3_bias,\n scalar_t* __restrict__ output,\n int batch_size, int seq_len, int pair_dim, int hidden_dim) {\n \n int total = batch_size * seq_len * seq_len;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (idx >= total) return;\n \n int b = idx / (seq_len * seq_len);\n int remainder = idx % (seq_len * seq_len);\n int i = remainder / seq_len;\n int j = remainder % seq_len;\n \n const scalar_t* ptr_ij = pair_repr + (b * seq_len * seq_len + i * seq_len + j) * pair_dim;\n const scalar_t* ptr_ji = pair_repr + (b * seq_len * seq_len + j * seq_len + i) * pair_dim;\n \n // Compute LayerNorm statistics for (i,j)\n scalar_t sum_ij = 0;\n scalar_t sum_sq_ij = 0;\n #pragma unroll 8\n for (int d = 0; d < pair_dim; d++) {\n scalar_t val = ptr_ij[d];\n sum_ij += val;\n sum_sq_ij += val * val;\n }\n scalar_t mean_ij = sum_ij / static_cast<scalar_t>(pair_dim);\n scalar_t var_ij = sum_sq_ij / static_cast<scalar_t>(pair_dim) - mean_ij * mean_ij;\n scalar_t inv_std_ij = rsqrt_wrapper(var_ij + static_cast<scalar_t>(1e-5));\n \n // Compute LayerNorm statistics for (j,i)\n scalar_t sum_ji = 0;\n scalar_t sum_sq_ji = 0;\n #pragma unroll 8\n for (int d = 0; d < pair_dim; d++) {\n scalar_t val = ptr_ji[d];\n sum_ji += val;\n sum_sq_ji += val * val;\n }\n scalar_t mean_ji = sum_ji / static_cast<scalar_t>(pair_dim);\n scalar_t var_ji = sum_sq_ji / static_cast<scalar_t>(pair_dim) - mean_ji * mean_ji;\n scalar_t inv_std_ji = rsqrt_wrapper(var_ji + static_cast<scalar_t>(1e-5));\n \n // Allocate hidden arrays\n scalar_t hidden1[64];\n scalar_t hidden2[64];\n \n // First linear layer with fused LayerNorm and symmetrization\n for (int h = 0; h < hidden_dim; h++) {\n scalar_t acc = linear1_bias[h];\n const scalar_t* w_ptr = linear1_weight + h * pair_dim;\n \n #pragma unroll 8\n for (int d = 0; d < pair_dim; d++) {\n // LayerNorm + affine for (i,j)\n scalar_t val_ij = (ptr_ij[d] - mean_ij) * inv_std_ij;\n val_ij = val_ij * ln_weight[d] + ln_bias[d];\n \n // LayerNorm + affine for (j,i)\n scalar_t val_ji = (ptr_ji[d] - mean_ji) * inv_std_ji;\n val_ji = val_ji * ln_weight[d] + ln_bias[d];\n \n // Symmetrize and apply weight\n scalar_t sym_val = (val_ij + val_ji) * static_cast<scalar_t>(0.5);\n acc += sym_val * w_ptr[d];\n }\n hidden1[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0);\n }\n \n // Second linear layer\n for (int h = 0; h < hidden_dim; h++) {\n scalar_t acc = linear2_bias[h];\n const scalar_t* w_ptr = linear2_weight + h * hidden_dim;\n \n #pragma unroll\n for (int h1 = 0; h1 < hidden_dim; h1++) {\n acc += hidden1[h1] * w_ptr[h1];\n }\n hidden2[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0);\n }\n \n // Third linear layer\n scalar_t logit = linear3_bias[0];\n for (int h = 0; h < hidden_dim; h++) {\n logit += hidden2[h] * linear3_weight[h];\n }\n \n // Sigmoid\n output[idx] = static_cast<scalar_t>(1) / (static_cast<scalar_t>(1) + exp(-logit));\n}\n\ntorch::Tensor contact_map_forward(\n torch::Tensor pair_repr,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor linear1_weight,\n torch::Tensor linear1_bias,\n torch::Tensor linear2_weight,\n torch::Tensor linear2_bias,\n torch::Tensor linear3_weight,\n torch::Tensor linear3_bias) {\n \n pair_repr = pair_repr.contiguous();\n ln_weight = ln_weight.contiguous();\n ln_bias = ln_bias.contiguous();\n linear1_weight = linear1_weight.contiguous();\n linear1_bias = linear1_bias.contiguous();\n linear2_weight = linear2_weight.contiguous();\n linear2_bias = linear2_bias.contiguous();\n linear3_weight = linear3_weight.contiguous();\n linear3_bias = linear3_bias.contiguous();\n \n int batch_size = pair_repr.size(0);\n int seq_len = pair_repr.size(1);\n int pair_dim = pair_repr.size(3);\n int hidden_dim = linear1_weight.size(0);\n \n auto output = torch::zeros({batch_size, seq_len, seq_len}, pair_repr.options());\n \n int total = batch_size * seq_len * seq_len;\n int threads = min(total, 256);\n int blocks = (total + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES(pair_repr.scalar_type(), \"contact_map_kernel\", ([&] {\n contact_map_kernel<scalar_t><<<blocks, threads>>>(\n pair_repr.data_ptr<scalar_t>(),\n ln_weight.data_ptr<scalar_t>(),\n ln_bias.data_ptr<scalar_t>(),\n linear1_weight.data_ptr<scalar_t>(),\n linear1_bias.data_ptr<scalar_t>(),\n linear2_weight.data_ptr<scalar_t>(),\n linear2_bias.data_ptr<scalar_t>(),\n linear3_weight.data_ptr<scalar_t>(),\n linear3_bias.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, seq_len, pair_dim, hidden_dim);\n }));\n \n return output;\n}\n\"\"\"\n\ncontact_map_ext = load_inline(\n name='contact_map_ext_v1',\n cpp_sources=['torch::Tensor contact_map_forward(torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias);'],\n cuda_sources=[cuda_source],\n functions=['contact_map_forward'],\n with_cuda=True,\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, hidden_dim):\n super().__init__()\n self.layer_norm = nn.LayerNorm(pair_dim)\n self.mlp = nn.Sequential(\n nn.Linear(pair_dim, hidden_dim),\n nn.ReLU(),\n nn.Linear(hidden_dim, hidden_dim),\n nn.ReLU(),\n nn.Linear(hidden_dim, 1),\n )\n \n def forward(self, pair_repr: torch.Tensor) -> torch.Tensor:\n ln_weight = self.layer_norm.weight\n ln_bias = self.layer_norm.bias\n linear1_weight = self.mlp[0].weight\n linear1_bias = self.mlp[0].bias\n linear2_weight = self.mlp[2].weight\n linear2_bias = self.mlp[2].bias\n linear3_weight = self.mlp[4].weight.squeeze(0)\n linear3_bias = self.mlp[4].bias\n \n return contact_map_ext.contact_map_forward(\n pair_repr, ln_weight, ln_bias,\n linear1_weight, linear1_bias,\n linear2_weight, linear2_bias,\n linear3_weight, linear3_bias\n )\n\n\npair_dim = 64\nhidden_dim = 32\nseq_len = 64\nbatch_size = 4\n\n\ndef get_inputs():\n return [torch.randn(batch_size, seq_len, seq_len, pair_dim)]\n\n\ndef get_init_inputs():\n return [pair_dim, hidden_dim]" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.67 μs Runtime stats: mean=0.67μs median=?μs std=0.01μs Numerical precision: max_abs_err=1.19e-07 mean_abs_err=1.23e-08 max_rel_err=2.37e-07 Memory: 37.09 MB peak (ref: 49.03 MB, ratio: 0.7565x) Kernel launches: 6 (ref: 41, fusion ratio: 6.8333) Energy: 241.86 mJ/run (ref: 0.0 mJ, ratio: -1x) SOL score: 0.0024
final submitted kernel (226 lines, 7,803 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

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

template <typename scalar_t>
__device__ __forceinline__ scalar_t rsqrt_wrapper(scalar_t x);

template<>
__device__ __forceinline__ float rsqrt_wrapper<float>(float x) {
    return rsqrtf(x);
}

template<>
__device__ __forceinline__ double rsqrt_wrapper<double>(double x) {
    return rsqrt(x);
}

template <typename scalar_t>
__global__ void contact_map_kernel(
    const scalar_t* __restrict__ pair_repr,
    const scalar_t* __restrict__ ln_weight,
    const scalar_t* __restrict__ ln_bias,
    const scalar_t* __restrict__ linear1_weight,
    const scalar_t* __restrict__ linear1_bias,
    const scalar_t* __restrict__ linear2_weight,
    const scalar_t* __restrict__ linear2_bias,
    const scalar_t* __restrict__ linear3_weight,
    const scalar_t* __restrict__ linear3_bias,
    scalar_t* __restrict__ output,
    int batch_size, int seq_len, int pair_dim, int hidden_dim) {
    
    int total = batch_size * seq_len * seq_len;
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (idx >= total) return;
    
    int b = idx / (seq_len * seq_len);
    int remainder = idx % (seq_len * seq_len);
    int i = remainder / seq_len;
    int j = remainder % seq_len;
    
    const scalar_t* ptr_ij = pair_repr + (b * seq_len * seq_len + i * seq_len + j) * pair_dim;
    const scalar_t* ptr_ji = pair_repr + (b * seq_len * seq_len + j * seq_len + i) * pair_dim;
    
    // Compute LayerNorm statistics for (i,j)
    scalar_t sum_ij = 0;
    scalar_t sum_sq_ij = 0;
    #pragma unroll 8
    for (int d = 0; d < pair_dim; d++) {
        scalar_t val = ptr_ij[d];
        sum_ij += val;
        sum_sq_ij += val * val;
    }
    scalar_t mean_ij = sum_ij / static_cast<scalar_t>(pair_dim);
    scalar_t var_ij = sum_sq_ij / static_cast<scalar_t>(pair_dim) - mean_ij * mean_ij;
    scalar_t inv_std_ij = rsqrt_wrapper(var_ij + static_cast<scalar_t>(1e-5));
    
    // Compute LayerNorm statistics for (j,i)
    scalar_t sum_ji = 0;
    scalar_t sum_sq_ji = 0;
    #pragma unroll 8
    for (int d = 0; d < pair_dim; d++) {
        scalar_t val = ptr_ji[d];
        sum_ji += val;
        sum_sq_ji += val * val;
    }
    scalar_t mean_ji = sum_ji / static_cast<scalar_t>(pair_dim);
    scalar_t var_ji = sum_sq_ji / static_cast<scalar_t>(pair_dim) - mean_ji * mean_ji;
    scalar_t inv_std_ji = rsqrt_wrapper(var_ji + static_cast<scalar_t>(1e-5));
    
    // Allocate hidden arrays
    scalar_t hidden1[64];
    scalar_t hidden2[64];
    
    // First linear layer with fused LayerNorm and symmetrization
    for (int h = 0; h < hidden_dim; h++) {
        scalar_t acc = linear1_bias[h];
        const scalar_t* w_ptr = linear1_weight + h * pair_dim;
        
        #pragma unroll 8
        for (int d = 0; d < pair_dim; d++) {
            // LayerNorm + affine for (i,j)
            scalar_t val_ij = (ptr_ij[d] - mean_ij) * inv_std_ij;
            val_ij = val_ij * ln_weight[d] + ln_bias[d];
            
            // LayerNorm + affine for (j,i)
            scalar_t val_ji = (ptr_ji[d] - mean_ji) * inv_std_ji;
            val_ji = val_ji * ln_weight[d] + ln_bias[d];
            
            // Symmetrize and apply weight
            scalar_t sym_val = (val_ij + val_ji) * static_cast<scalar_t>(0.5);
            acc += sym_val * w_ptr[d];
        }
        hidden1[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0);
    }
    
    // Second linear layer
    for (int h = 0; h < hidden_dim; h++) {
        scalar_t acc = linear2_bias[h];
        const scalar_t* w_ptr = linear2_weight + h * hidden_dim;
        
        #pragma unroll
        for (int h1 = 0; h1 < hidden_dim; h1++) {
            acc += hidden1[h1] * w_ptr[h1];
        }
        hidden2[h] = acc > static_cast<scalar_t>(0) ? acc : static_cast<scalar_t>(0);
    }
    
    // Third linear layer
    scalar_t logit = linear3_bias[0];
    for (int h = 0; h < hidden_dim; h++) {
        logit += hidden2[h] * linear3_weight[h];
    }
    
    // Sigmoid
    output[idx] = static_cast<scalar_t>(1) / (static_cast<scalar_t>(1) + exp(-logit));
}

torch::Tensor contact_map_forward(
    torch::Tensor pair_repr,
    torch::Tensor ln_weight,
    torch::Tensor ln_bias,
    torch::Tensor linear1_weight,
    torch::Tensor linear1_bias,
    torch::Tensor linear2_weight,
    torch::Tensor linear2_bias,
    torch::Tensor linear3_weight,
    torch::Tensor linear3_bias) {
    
    pair_repr = pair_repr.contiguous();
    ln_weight = ln_weight.contiguous();
    ln_bias = ln_bias.contiguous();
    linear1_weight = linear1_weight.contiguous();
    linear1_bias = linear1_bias.contiguous();
    linear2_weight = linear2_weight.contiguous();
    linear2_bias = linear2_bias.contiguous();
    linear3_weight = linear3_weight.contiguous();
    linear3_bias = linear3_bias.contiguous();
    
    int batch_size = pair_repr.size(0);
    int seq_len = pair_repr.size(1);
    int pair_dim = pair_repr.size(3);
    int hidden_dim = linear1_weight.size(0);
    
    auto output = torch::zeros({batch_size, seq_len, seq_len}, pair_repr.options());
    
    int total = batch_size * seq_len * seq_len;
    int threads = min(total, 256);
    int blocks = (total + threads - 1) / threads;
    
    AT_DISPATCH_FLOATING_TYPES(pair_repr.scalar_type(), "contact_map_kernel", ([&] {
        contact_map_kernel<scalar_t><<<blocks, threads>>>(
            pair_repr.data_ptr<scalar_t>(),
            ln_weight.data_ptr<scalar_t>(),
            ln_bias.data_ptr<scalar_t>(),
            linear1_weight.data_ptr<scalar_t>(),
            linear1_bias.data_ptr<scalar_t>(),
            linear2_weight.data_ptr<scalar_t>(),
            linear2_bias.data_ptr<scalar_t>(),
            linear3_weight.data_ptr<scalar_t>(),
            linear3_bias.data_ptr<scalar_t>(),
            output.data_ptr<scalar_t>(),
            batch_size, seq_len, pair_dim, hidden_dim);
    }));
    
    return output;
}
"""

contact_map_ext = load_inline(
    name='contact_map_ext_v1',
    cpp_sources=['torch::Tensor contact_map_forward(torch::Tensor pair_repr, torch::Tensor ln_weight, torch::Tensor ln_bias, torch::Tensor linear1_weight, torch::Tensor linear1_bias, torch::Tensor linear2_weight, torch::Tensor linear2_bias, torch::Tensor linear3_weight, torch::Tensor linear3_bias);'],
    cuda_sources=[cuda_source],
    functions=['contact_map_forward'],
    with_cuda=True,
    extra_cuda_cflags=['-O3', '--use_fast_math'],
)

class ModelNew(nn.Module):
    def __init__(self, pair_dim, hidden_dim):
        super().__init__()
        self.layer_norm = nn.LayerNorm(pair_dim)
        self.mlp = nn.Sequential(
            nn.Linear(pair_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1),
        )
    
    def forward(self, pair_repr: torch.Tensor) -> torch.Tensor:
        ln_weight = self.layer_norm.weight
        ln_bias = self.layer_norm.bias
        linear1_weight = self.mlp[0].weight
        linear1_bias = self.mlp[0].bias
        linear2_weight = self.mlp[2].weight
        linear2_bias = self.mlp[2].bias
        linear3_weight = self.mlp[4].weight.squeeze(0)
        linear3_bias = self.mlp[4].bias
        
        return contact_map_ext.contact_map_forward(
            pair_repr, ln_weight, ln_bias,
            linear1_weight, linear1_bias,
            linear2_weight, linear2_bias,
            linear3_weight, linear3_bias
        )


pair_dim = 64
hidden_dim = 32
seq_len = 64
batch_size = 4


def get_inputs():
    return [torch.randn(batch_size, seq_len, seq_len, pair_dim)]


def get_init_inputs():
    return [pair_dim, hidden_dim]