reasoning
The issue is still the same. Let me look more carefully at the shapes in the reference:
Reference:
1. `x = self.layer_norm(pair_repr)` → (B, N, N, D)
2. `g = torch.sigmoid(self.gate(x))` → (B, N, N, D)
3. `bias = self.bias_proj(x).permute(0, 3, 1, 2)` → (B, h, N, N)
4. `x_flat = x.reshape(B * N, N, D)` → **(B*N, N, D)** - this is the key!
5. `q = self.query(x_flat).view(B * N, N, h, d).transpose(1, 2)` → **(B*N, h, N, d)**
Ah! The reshape is different! In the reference:
- `x_flat = x.reshape(B * N, N, D)` means we flatten (B, N, N, D) to (B*N, N, D)
- This means the first N in the shape is the row index, and the second N is the column index
Let me trace through this more carefully:
- x has shape (B, N, N, D) = (B, i, j, D)
- x.reshape(B*N, N, D) = (B*i, j, D) = (B*N, j, D)
- So `bn = b * N + i` is the batch-row index
- The attention is across columns j for each row i
So for query at position q_pos (column j):
- q = self.query(x_flat) projects x_flat to get (B*N, N, D)
- q.view(B*N, N, h, d) = (bn, j, h, d)
- q.transpose(1, 2) = (bn, h, j, d)
So the query for (bn, h, q_pos) is the query for row `bn = b*N + i`, column `q_pos = j`.
Now for the bias:
- bias = self.bias_proj(x).permute(0, 3, 1, 2)
- self.bias_proj(x) projects x from (B, N, N, D) to (B, N, N, h)
- permute(0, 3, 1, 2) = (B, h, N, N)
But wait, in the attention:
- attn = attn.view(B, N, h, N, N) → attn[b, i, h, j, k]
- bias.unsqueeze(1) → bias[b, 1, h, N, N] = bias[b, 1, h, j, k] broadcast
- attn = attn + bias.unsqueeze(1) → attn[b, i, h, j, k] + bias[b, 1, h, j, k]
- So bias[b, h, j, k] is added to all rows i for the same column j and key k
But wait, that doesn't match what I have. The bias has shape (B, h, N, N) where the two N dimensions are for what?
Looking at:
```python
bias = self.bias_proj(x).permute(0, 3, 1, 2) # (B, h, N, N)
```
x has shape (B, N, N, D). After bias_proj (which goes from D to h), we get (B, N, N, h). Then permute(0, 3, 1, 2) gives (B, h, N, N).
So bias[b, h, i, j] = bias_proj(x[b, i, j, :])[h]
In the attention:
- attn = attn.view(B, N, h, N, N) → attn[b, i, h, j, k]
- bias.unsqueeze(1) → (B, 1, h, N, N)
- So bias[b, :, h, j, k] is the same for all i, and it's bias[b, h, j, k]
So the bias is:
- bias[b, h, j, k] = bias_proj(x[b, j, k, :])[h]
Wait, but that means the bias at (j, k) uses x at (j, k), not at (i, something).
Actually looking at it again:
- bias = self.bias_proj(x) has shape (B, N, N, h) = (B, i, j, h) using the indices from x
- After permute: (B, h, N, N) = (B, h, i, j)
- But the attention uses bias at (b, h, j, k) where j is the query position and k is the key position
This is confusing. Let me look at the attention add again:
```python
attn = attn.view(B, N, h, N, N) # (B, i, h, j, k)
# Add pair bias: bias (B, h, N, N) → (B, 1, h, N, N) broadcast across rows
attn = attn + bias.unsqueeze(1) # bias[b, :, h, j, k] = bias[b, h, j, k]
```
So the bias added is bias[b, h, j, k]. With bias having shape (B, h, N, N), this is the element at position (j, k) in the last two dimensions.
But bias = bias_proj(x).permute(0, 3, 1, 2), where x has shape (B, i, j, D).
- bias_proj(x) has shape (B, i, j, h)
- permute(0, 3, 1, 2) has shape (B, h, i, j)
So bias[b, h, i, j] = bias_proj(x[b, i, j, :])[h]
But we're adding bias[b, h, j, k] to attn[b, i, h, j, k].
This means the bias index is (j, k) which corresponds to x[b, j, k, :].
OK so the bias uses the query position j as its "row" and the key position k as its "column". This makes sense - it's the pair representation at (j, k).
But wait, I'm computing the bias from x at all positions. Let me check my bias reshaping:
- I have bias_proj with shape (B*N*N, h) from flattening x to (B*N*N, D)
- Then I reshape to (B, N, N, h) and permute to (B, h, N, N)
So bias_reshaped[b, h, i, j] should be bias_proj for position (b, i, j).
In my kernel, I access:
```cpp
score += bias[b * h * N * N + head * N * N + q_pos * N + k_pos];
```
This is bias[b, head, q_pos, k_pos], which should be bias_proj(x[b, q_pos, k_pos, :])[head].
But wait, x is the layer norm output with shape (B, N, N, D). I'm computing bias from x at position (b, i, j) which should give bias_proj(x[b, i, j, :]).
But the attention uses bias at position (b, h, q_pos, k_pos), which would be bias_proj(x[b, q_pos, k_pos, :]).
So the issue is: in my implementation, I'm computing bias for each position (i, j), but I should be accessing it based on the query position q_pos and key position k_pos.
Actually wait, let me check again. In my code:
- I compute bias_proj for each position in x: bias_proj[b * N * N + i * N + j, head] = bias_proj(x[b, i, j, :])[head]
- Then I reshape and permute: bias_reshaped[b, head, i, j] = bias_proj[b * N * N + i * N + j, head]
So bias_reshaped[b, head, i, j] = bias_proj(x[b, i, j, :])[head].
In the attention kernel, I access:
```cpp
score += bias[b * h * N * N + head * N * N + q_pos * N + k_pos];
```
This is bias[b, head, q_pos, k_pos] = bias_proj(x[b, q_pos, k_pos, :])[head].
This looks correct! But wait, let me check the reference again:
In reference:
- bias = self.bias_proj(x).permute(0, 3, 1, 2) → (B, h, N, N)
- bias[b, h, i, j] = bias_proj(x[b, i, j, :])[h]
But wait, in the reference, x still has its original dimensions (B, N, N, D). So bias_proj(x) = bias_proj(x[b, i, j, :]) for each (i, j).
And then in the attention:
- attn = attn.view(B, N, h, N, N) → attn[b, i, h, j, k]
- attn = attn + bias.unsqueeze(1) → bias[b, h, j, k]
So the bias is bias[b, h, j, k] = bias_proj(x[b, j, k, :])[h].
Hmm, but my bias_reshaped[b, head, q_pos, k_pos] = bias_proj(x[b, q_pos, k_pos, :])[head].
This should be correct since q_pos is j and k_pos is k.
Let me check another thing - the mask. In reference:
```python
# Apply mask: pair_mask (B, N, N) → mask over the key dimension
mask = pair_mask.unsqueeze(1).unsqueeze(2) # (B, 1, 1, N, N) – last dim is keys
attn = attn.masked_fill(mask == 0, float("-inf"))
```
So mask is (B, 1, 1, N, N) which broadcasts to (B, N, h, N, N).
The mask at position [b, :, :, j, k] is mask[b, 0, 0, j, k] = pair_mask[b, j, k].
But wait, that doesn't seem right. Let me trace through:
- pair_mask has shape (B, N, N) = (B, i, j)
- mask = pair_mask.unsqueeze(1).unsqueeze(2) → (B, 1, 1, N, N)
- attn has shape (B, N, h, N, N) = (B, i, h, j, k)
- The mask broadcasts to (B, N, h, N, N), so mask[b, i, h, j, k] = mask[b, 0, 0, j, k]
- But mask[b, 0, 0, j, k] = pair_mask[b, j, k]?? That doesn't match the dimensions.
Actually wait, let me re-read:
```python
mask = pair_mask.unsqueeze(1).unsqueeze(2) # (B, 1, 1, N, N) – last dim is keys
```
pair_mask has shape (B, N, N). unsqueeze(1) gives (B, 1, N, N). unsqueeze(2) gives (B, 1, 1, N, N).
So mask[b, 0, 0, i, j] = pair_mask[b, i, j].
In the attention:
- attn has shape (B, N, h, N, N) = (B, i, h, j, k) where j is query and k is key
- mask broadcasts to (B, N, h, N, N), so mask[b, i, h, j, k] = mask[b, 0, 0, j, k]
- But wait, mask[b, 0, 0, j, k] should be pair_mask[b, j, k]
Hmm, but the comment says "last dim is keys". So the mask at (b, i, h, j, k) uses pair_mask at (b, ?, ?).
Actually, the broadcasting works like this:
- mask has shape (B, 1, 1, N, N)
- attn has shape (B, N, h, N, N)
- For element attn[b, i, h, j, k], the corresponding mask element is mask[b, 0, 0, j, k]
- But mask[b, 0, 0, j, k] = pair_mask[b, j, k]... no wait, pair_mask has shape (B, N, N)
Let me trace this more carefully:
- pair_mask has shape (B, N, N)
- pair_mask.unsqueeze(1) has shape (B, 1, N, N)
- pair_mask.unsqueeze(1).unsqueeze(2) has shape (B, 1, 1, N, N)
So the original indices (b, i, j) in pair_mask become (b, 0, 0, i, j) in mask.
Now for attn with shape (B, N, h, N, N) = (B, i, h, j, k):
- The mask broadcasts from (B, 1, 1, N, N) to (B, N, h, N, N)
- mask[b, i, h, j, k] = mask[b, 0, 0, j, k] = pair_mask[b, j, k]
But wait, that means the mask uses pair_mask[b, j, k] where j is the query position and k is the key position. But in my implementation, I use mask[b, row, k_pos] where row = i.
Hmm, but the reference uses:
```python
mask = pair_mask.unsqueeze(1).unsqueeze(2) # (B, 1, 1, N, N)
```
So mask[b, 0, 0, :, :] = pair_mask[b, :, :].
The indices in mask are (b, :, :, dim3, dim4) = (b, :, :, i, j) from the original pair_mask.
So mask[b, 0, 0, i, j] = pair_mask[b, i, j].
When broadcast to attn with shape (B, N, h, N, N) = (B, i, h, j, k):
- The third and fourth dimensions of mask (both N) correspond to j and k in attn
- So mask[b, 0, 0, j, k] is applied to attn[b, i, h, j, k]
This means the mask at position (b, i, h, j, k) uses pair_mask[b, j, k].
But in my kernel, I use:
```cpp
if (mask[b * N * N + row * N + k_pos] == 0.0f) {
```
This is mask[b, row, k_pos] = pair_mask[b, row, k_pos], which uses the row index (i) instead of the query position (j).
I need to fix this to use:
```cpp
if (mask[b * N * N + q_pos * N + k_pos] == 0.0f) {
```
Let me also double-check the bias indexing. The bias at (b, h, j, k) should be added to all rows i. So the bias is:
- bias[b, h, j, k] = bias_proj(x[b, j, k, :])[h]
And in my kernel, I should access:
```cpp
score += bias[b * h * N * N + head * N * N + q_pos * N + k_pos];
```
This looks correct.
But the mask is wrong. Let me fix it.
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\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <c10/cuda/CUDAException.h>\n\n// LayerNorm kernel for (B, N, N, D) tensor\n__global__ void layernorm_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int total_positions, int D, float eps\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= total_positions) return;\n \n const float* in = input + idx * D;\n float* out = output + idx * D;\n \n // Compute mean\n float mean = 0.0f;\n for (int d = 0; d < D; d++) {\n mean += in[d];\n }\n mean /= D;\n \n // Compute variance\n float var = 0.0f;\n for (int d = 0; d < D; d++) {\n float diff = in[d] - mean;\n var += diff * diff;\n }\n var /= D;\n \n float inv_std = rsqrtf(var + eps);\n \n // Normalize and apply weight/bias\n for (int d = 0; d < D; d++) {\n out[d] = (in[d] - mean) * inv_std * weight[d] + bias[d];\n }\n}\n\n// Linear projection kernel: output = input @ weight.T + bias\n__global__ void linear_kernel(\n const float* __restrict__ input,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n float* __restrict__ output,\n int M, int in_features, int out_features, bool has_bias\n) {\n int m = blockIdx.y;\n int out_f = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (m >= M || out_f >= out_features) return;\n \n float sum = has_bias ? bias[out_f] : 0.0f;\n for (int in_f = 0; in_f < in_features; in_f++) {\n sum += input[m * in_features + in_f] * weight[out_f * in_features + in_f];\n }\n output[m * out_features + out_f] = sum;\n}\n\n// Attention computation kernel\n// query, key, value: (B*N, h, N, d)\n// bias: (B, h, N, N)\n// mask: (B, N, N)\n// output: (B*N, h, N, d)\n__global__ void attention_kernel(\n const float* __restrict__ query,\n const float* __restrict__ key,\n const float* __restrict__ value,\n const float* __restrict__ bias,\n const float* __restrict__ mask,\n float* __restrict__ output,\n float scale,\n int B, int N, int h, int d, int BN\n) {\n // blockIdx.x: index over B*N rows\n // blockIdx.y: index over h heads \n // blockIdx.z: index over N query positions\n int bn = blockIdx.x;\n int head = blockIdx.y;\n int q_pos = blockIdx.z;\n \n if (bn >= BN || head >= h || q_pos >= N) return;\n \n int b = bn / N;\n int row = bn % N; // row index in pair representation (i)\n int tid = threadIdx.x;\n \n // Shared memory layout\n extern __shared__ float smem[];\n float* q_shared = smem; // [d] - Q for this query position\n float* scores = &smem[d]; // [N] - attention scores\n \n // Cooperatively load Q for this query position\n for (int dd = tid; dd < d; dd += blockDim.x) {\n q_shared[dd] = query[bn * h * N * d + head * N * d + q_pos * d + dd];\n }\n __syncthreads();\n \n // Compute attention scores for all key positions\n // Each thread handles a subset of key positions\n for (int k_pos = tid; k_pos < N; k_pos += blockDim.x) {\n float score = 0.0f;\n // Load K for this key position and compute dot product\n for (int dd = 0; dd < d; dd++) {\n float k_val = key[bn * h * N * d + head * N * d + k_pos * d + dd];\n score += q_shared[dd] * k_val;\n }\n score *= scale;\n \n // Add bias: bias is (B, h, N, N)\n // Reference: attn[b, i, h, j, k] += bias[b, h, j, k]\n // So bias index uses q_pos (j) and k_pos (k)\n score += bias[b * h * N * N + head * N * N + q_pos * N + k_pos];\n \n // Apply mask: mask is (B, N, N)\n // Reference: mask broadcasts from (B, 1, 1, N, N) to attn (B, N, h, N, N)\n // mask[b, i, h, j, k] = mask[b, 0, 0, j, k] = pair_mask[b, j, k]\n // So mask index uses q_pos (j) and k_pos (k)\n if (mask[b * N * N + q_pos * N + k_pos] == 0.0f) {\n score = -INFINITY;\n }\n \n scores[k_pos] = score;\n }\n __syncthreads();\n \n // Find max score for numerical stability\n __shared__ float smax;\n if (tid == 0) {\n smax = -INFINITY;\n for (int k_pos = 0; k_pos < N; k_pos++) {\n if (scores[k_pos] > smax) smax = scores[k_pos];\n }\n }\n __syncthreads();\n \n // Compute exp(score - max) and sum\n __shared__ float ssum;\n float local_sum = 0.0f;\n for (int k_pos = tid; k_pos < N; k_pos += blockDim.x) {\n float exp_val = expf(scores[k_pos] - smax);\n scores[k_pos] = exp_val;\n local_sum += exp_val;\n }\n \n // Sum reduction across threads\n for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {\n local_sum += __shfl_down_sync(0xffffffff, local_sum, offset);\n }\n if (tid == 0) ssum = local_sum;\n __syncthreads();\n \n // Normalize scores\n for (int k_pos = tid; k_pos < N; k_pos += blockDim.x) {\n scores[k_pos] /= ssum;\n }\n __syncthreads();\n \n // Compute weighted sum of V for each output dimension\n for (int dd = tid; dd < d; dd += blockDim.x) {\n float out_val = 0.0f;\n for (int k_pos = 0; k_pos < N; k_pos++) {\n float v_val = value[bn * h * N * d + head * N * d + k_pos * d + dd];\n out_val += scores[k_pos] * v_val;\n }\n output[bn * h * N * d + head * N * d + q_pos * d + dd] = out_val;\n }\n}\n\n// Output kernel: reshape, apply gate, output projection\n__global__ void output_kernel(\n const float* __restrict__ attn_out, // (B*N, h, N, d)\n const float* __restrict__ gate_proj, // (B, N, N, D) - before sigmoid\n const float* __restrict__ w_out, // (D, D)\n const float* __restrict__ b_out, // (D)\n float* __restrict__ output, // (B, N, N, D)\n int B, int N, int h, int d, int D\n) {\n // Each block handles one (b, i, j) position\n int idx = blockIdx.x;\n int total = B * N * N;\n if (idx >= total) return;\n \n int b = idx / (N * N);\n int ij = idx % (N * N);\n int i = ij / N;\n int j = ij % N;\n \n int bn = b * N + i;\n int out_dim = threadIdx.x;\n \n if (out_dim >= D) return;\n \n // Read attention output for column j\n // attn_out layout: (B*N, h, N, d)\n // We need attn_out[bn, head, j, dd]\n int head = out_dim / d;\n int dd = out_dim % d;\n float val = attn_out[bn * h * N * d + head * N * d + j * d + dd];\n \n // Compute sigmoid gate and apply\n float g = 1.0f / (1.0f + expf(-gate_proj[idx * D + out_dim]));\n val *= g;\n \n // Output projection\n float out_val = b_out[out_dim];\n for (int in_f = 0; in_f < D; in_f++) {\n out_val += val * w_out[out_dim * D + in_f];\n }\n \n output[idx * D + out_dim] = out_val;\n}\n\n// Host function\ntorch::Tensor triangular_attention_cuda(\n torch::Tensor pair_repr,\n torch::Tensor pair_mask,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor w_query,\n torch::Tensor w_key,\n torch::Tensor w_value,\n torch::Tensor w_gate,\n torch::Tensor b_gate,\n torch::Tensor w_bias,\n torch::Tensor w_out,\n torch::Tensor b_out,\n int num_heads,\n int head_dim\n) {\n int B = pair_repr.size(0);\n int N = pair_repr.size(1);\n int D = pair_repr.size(3);\n int h = num_heads;\n int d = head_dim;\n int BN = B * N;\n \n auto options = pair_repr.options();\n int threads = 256;\n \n // Step 1: LayerNorm\n auto x = torch::empty({B, N, N, D}, options);\n int total_positions = B * N * N;\n int blocks = (total_positions + threads - 1) / threads;\n layernorm_kernel<<<blocks, threads>>>(\n pair_repr.data_ptr<float>(),\n x.data_ptr<float>(),\n ln_weight.data_ptr<float>(),\n ln_bias.data_ptr<float>(),\n total_positions, D, 1e-5f\n );\n \n // Step 2: Reshape x for linear projections\n // x is (B, N, N, D), flatten to (B*N*N, D)\n auto x_flat = x.view({total_positions, D});\n \n // Step 3: Compute all linear projections\n auto query = torch::empty({total_positions, D}, options);\n auto key = torch::empty({total_positions, D}, options);\n auto value = torch::empty({total_positions, D}, options);\n auto gate_proj = torch::empty({total_positions, D}, options);\n auto bias_proj = torch::empty({total_positions, h}, options);\n \n dim3 grid_linear((D + threads - 1) / threads, total_positions);\n \n linear_kernel<<<grid_linear, threads>>>(\n x_flat.data_ptr<float>(), w_query.data_ptr<float>(), nullptr,\n query.data_ptr<float>(), total_positions, D, D, false\n );\n linear_kernel<<<grid_linear, threads>>>(\n x_flat.data_ptr<float>(), w_key.data_ptr<float>(), nullptr,\n key.data_ptr<float>(), total_positions, D, D, false\n );\n linear_kernel<<<grid_linear, threads>>>(\n x_flat.data_ptr<float>(), w_value.data_ptr<float>(), nullptr,\n value.data_ptr<float>(), total_positions, D, D, false\n );\n linear_kernel<<<grid_linear, threads>>>(\n x_flat.data_ptr<float>(), w_gate.data_ptr<float>(), b_gate.data_ptr<float>(),\n gate_proj.data_ptr<float>(), total_positions, D, D, true\n );\n \n dim3 grid_bias((h + threads - 1) / threads, total_positions);\n linear_kernel<<<grid_bias, threads>>>(\n x_flat.data_ptr<float>(), w_bias.data_ptr<float>(), nullptr,\n bias_proj.data_ptr<float>(), total_positions, D, h, false\n );\n \n // Step 4: Reshape for attention\n // Q, K, V: (B*N*N, D) -> (B*N, N, h, d) -> (B*N, h, N, d)\n auto q_reshaped = query.view({BN, N, h, d}).permute({0, 2, 1, 3}).contiguous();\n auto k_reshaped = key.view({BN, N, h, d}).permute({0, 2, 1, 3}).contiguous();\n auto v_reshaped = value.view({BN, N, h, d}).permute({0, 2, 1, 3}).contiguous();\n \n // bias: (B*N*N, h) -> (B, N, N, h) -> (B, h, N, N)\n auto bias_reshaped = bias_proj.view({B, N, N, h}).permute({0, 3, 1, 2}).contiguous();\n \n // Step 5: Compute attention\n auto attn_out = torch::empty({BN, h, N, d}, options);\n float scale = 1.0f / sqrtf((float)d);\n \n // Shared memory: Q (d) + scores (N)\n size_t smem_size = d * sizeof(float) + N * sizeof(float);\n dim3 grid_attn(BN, h, N); // B*N blocks, h heads, N query positions\n attention_kernel<<<grid_attn, d, smem_size>>>(\n q_reshaped.data_ptr<float>(),\n k_reshaped.data_ptr<float>(),\n v_reshaped.data_ptr<float>(),\n bias_reshaped.data_ptr<float>(),\n pair_mask.data_ptr<float>(),\n attn_out.data_ptr<float>(),\n scale,\n B, N, h, d, BN\n );\n \n // Step 6: Output projection with gate\n auto output = torch::empty({B, N, N, D}, options);\n blocks = (total_positions + threads - 1) / threads;\n output_kernel<<<blocks, threads>>>(\n attn_out.data_ptr<float>(),\n gate_proj.data_ptr<float>(), // pass pre-sigmoid gate values\n w_out.data_ptr<float>(),\n b_out.data_ptr<float>(),\n output.data_ptr<float>(),\n B, N, h, d, D\n );\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor triangular_attention_cuda(\n torch::Tensor pair_repr,\n torch::Tensor pair_mask,\n torch::Tensor ln_weight,\n torch::Tensor ln_bias,\n torch::Tensor w_query,\n torch::Tensor w_key,\n torch::Tensor w_value,\n torch::Tensor w_gate,\n torch::Tensor b_gate,\n torch::Tensor w_bias,\n torch::Tensor w_out,\n torch::Tensor b_out,\n int num_heads,\n int head_dim\n);\n\"\"\"\n\ntriangular_attn = load_inline(\n name='triangular_attention_v5',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['triangular_attention_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, pair_dim, num_heads, dropout=0.0):\n super().__init__()\n self.num_heads = num_heads\n self.head_dim = pair_dim // num_heads\n assert pair_dim % num_heads == 0\n\n self.layer_norm = nn.LayerNorm(pair_dim)\n self.query = nn.Linear(pair_dim, pair_dim, bias=False)\n self.key = nn.Linear(pair_dim, pair_dim, bias=False)\n self.value = nn.Linear(pair_dim, pair_dim, bias=False)\n self.gate = nn.Linear(pair_dim, pair_dim)\n self.bias_proj = nn.Linear(pair_dim, num_heads, bias=False)\n self.out_proj = nn.Linear(pair_dim, pair_dim)\n self.dropout = nn.Dropout(dropout)\n self.pair_dim = pair_dim\n\n def forward(self, pair_repr: torch.Tensor, pair_mask: torch.Tensor) -> torch.Tensor:\n B, N, _, D = pair_repr.shape\n h = self.num_heads\n d = self.head_dim\n\n pair_repr = pair_repr.contiguous().float()\n pair_mask = pair_mask.contiguous().float()\n\n output = triangular_attn.triangular_attention_cuda(\n pair_repr,\n pair_mask,\n self.layer_norm.weight.contiguous(),\n self.layer_norm.bias.contiguous(),\n self.query.weight.contiguous(),\n self.key.weight.contiguous(),\n self.value.weight.contiguous(),\n self.gate.weight.contiguous(),\n self.gate.bias.contiguous(),\n self.bias_proj.weight.contiguous(),\n self.out_proj.weight.contiguous(),\n self.out_proj.bias.contiguous(),\n self.num_heads,\n self.head_dim\n )\n \n return output"
}