reasoning
Now let me analyze the computation:
For each timestep t:
1. gate_t = gate[:, t].unsqueeze(-1).unsqueeze(-1) -> shape (batch, num_heads, 1, 1)
2. beta_t = beta[:, t].unsqueeze(-1).unsqueeze(-1) -> shape (batch, num_heads, 1, 1)
3. kv_outer = k[:, t].unsqueeze(-1) * v[:, t].unsqueeze(-2) -> outer product, shape (batch, num_heads, head_dim, head_dim)
4. state = gate_t * state + beta_t * kv_outer -> update state (batch, num_heads, head_dim, head_dim)
5. y_t = (q[:, t].unsqueeze(-1) * state).sum(dim=-2) -> compute output, shape (batch, num_heads, head_dim)
The state is a 4D tensor of shape (batch, num_heads, head_dim, head_dim). For the given dimensions:
- batch_size = 2
- seq_len = 128
- num_heads = 4
- head_dim = 24
State size: 2 * 4 * 24 * 24 = 4608 floats = 18KB, which easily fits in shared memory.
Key observations:
1. The computation for different batches and heads is independent - we can parallelize over (batch, heads).
2. The sequential dependency is across timesteps, so we need to iterate through them within each thread block.
3. We can use shared memory to store the state matrix.
4. We can have multiple threads cooperate on the matrix operations.
Let me think about parallelization:
- We could have one thread block per (batch, head) pair, or
- We could have warps working together within thread blocks
Given the small head_dim (24), each state matrix is 24x24 = 576 elements.
Strategy:
- Launch a kernel with thread blocks for each (batch, head) pair
- Each block has multiple threads that cooperate on the state
- Use shared memory for the state matrix
- Iterate through timesteps sequentially within each block
Let me design the kernel:
Option 1: One thread per (batch, head) handles all computations
- Simple but each thread does 24*24 work per timestep
- No parallelism within the state computation
Option 2: Warp per (batch, head)
- 32 threads for 24*24 = 576 elements
- Each thread handles 18 elements on average
- Can parallelize the outer product and the query-state multiplication
Option 3: Thread block per (batch, head)
- Similar to warp but more threads
For head_dim=24 and seq_len=128:
- Total elements in output: 2 * 128 * 4 * 24 = 24,576
- State updates per (batch, head): 128 * 24 * 24 = 73,728 operations
Let me write a first version:
```python
import torch
import torch.utils.cpp_extension as cpp_extension
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
template <typename scalar_t>
__global__ void linear_attention_kernel(
const scalar_t* __restrict__ q,
const scalar_t* __restrict__ k,
const scalar_t* __restrict__ v,
const scalar_t* __restrict__ gate,
const scalar_t* __restrict__ beta,
scalar_t* __restrict__ output,
int batch_size, int seq_len, int num_heads, int head_dim) {
// Map block to (batch, head)
int batch = blockIdx.x / num_heads;
int head = blockIdx.x % num_heads;
int tid = threadIdx.x;
int block_size = blockDim.x;
// Shared memory for state (head_dim x head_dim)
extern __shared__ char shared_mem[];
scalar_t* state = reinterpret_cast<scalar_t*>(shared_mem);
// Initialize state to zero
for (int i = tid; i < head_dim * head_dim; i += block_size) {
state[i] = scalar_t(0);
}
__syncthreads();
// Base indices for this (batch, head)
int bh_offset = batch * num_heads * seq_len + head * seq_len;
int bh_state_offset = batch * num_heads * head_dim * head_dim + head * head_dim * head_dim;
// Iterate through sequence
for (int t = 0; t < seq_len; t++) {
scalar_t gate_t = gate[batch * num_heads * seq_len + head * seq_len + t];
scalar_t beta_t = beta[batch * num_heads * seq_len + head * seq_len + t];
// Get k[t] and v[t] for this (batch, head)
const scalar_t* k_t = k + (batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + head * head_dim);
const scalar_t* v_t = v + (batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + head * head_dim);
const scalar_t* q_t = q + (batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + head * head_dim);
// Update state: state = gate_t * state + beta_t * k[t] * v[t]^T
// Each thread handles some elements of the state matrix
for (int i = tid; i < head_dim * head_dim; i += block_size) {
int row = i / head_dim;
int col = i % head_dim;
state[i] = gate_t * state[i] + beta_t * k_t[row] * v_t[col];
}
__syncthreads();
// Compute output: y[t] = q[t]^T @ state -> sum over rows
// y[j] = sum_i q[i] * state[i, j]
scalar_t* y_t = output + (batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + head * head_dim);
for (int j = tid; j < head_dim; j += block_size) {
scalar_t sum = scalar_t(0);
for (int i = 0; i < head_dim; i++) {
sum += q_t[i] * state[i * head_dim + j];
}
y_t[j] = sum;
}
__syncthreads();
}
}
torch::Tensor linear_attention_cuda(
torch::Tensor q,
torch::Tensor k,
torch::Tensor v,
torch::Tensor gate,
torch::Tensor beta) {
int batch_size = q.size(0);
int seq_len = q.size(1);
int num_heads = q.size(2);
int head_dim = q.size(3);
auto output = torch::zeros_like(q);
int blocks = batch_size * num_heads;
int threads = 128; // More than enough for head_dim=24
int shared_mem_size = head_dim * head_dim * sizeof(float);
AT_DISPATCH_FLOATING_TYPES(q.scalar_type(), "linear_attention_kernel", ([&] {
linear_attention_kernel<scalar_t><<<blocks, threads, shared_mem_size>>>(
q.data_ptr<scalar_t>(),
k.data_ptr<scalar_t>(),
v.data_ptr<scalar_t>(),
gate.data_ptr<scalar_t>(),
beta.data_ptr<scalar_t>(),
output.data_ptr<scalar_t>(),
batch_size, seq_len, num_heads, head_dim);
}));
return output;
}
"""
module = cpp_extension.load_inline(
name='linear_attention',
cpp_sources=['torch::Tensor linear_attention_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor gate, torch::Tensor beta);'],
cuda_sources=[cuda_source],
functions=['linear_attention_cuda'],
extra_cuda_cflags=['-O3'],
)
class ModelNew(nn.Module):
def __init__(self):
super().__init__()
self.module = module
def forward(self, q, k, v, gate, beta):
return self.module.linear_attention_cuda(q, k, v, gate, beta)
```
Wait, I need to be more careful about the tensor layout. Let me check:
q.shape = (batch_size, seq_len, num_heads, head_dim)
So for q[:, t, h, :], the index would be:
- batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + h * head_dim + d
Let me rewrite more carefully.
Actually, let me trace through more carefully. The reference does:
- k[:, t] has shape (batch, num_heads, head_dim)
- k[:, t].unsqueeze(-1) -> (batch, num_heads, head_dim, 1)
- v[:, t] has shape (batch, num_heads, head_dim)
- v[:, t].unsqueeze(-2) -> (batch, num_heads, 1, head_dim)
- kv_outer = k[:, t].unsqueeze(-1) * v[:, t].unsqueeze(-2) -> (batch, num_heads, head_dim, head_dim)
So kv_outer[b, h, i, j] = k[b, t, h, i] * v[b, t, h, j]
And the query computation:
- q[:, t] has shape (batch, num_heads, head_dim)
- q[:, t].unsqueeze(-1) -> (batch, num_heads, head_dim, 1)
- q[:, t].unsqueeze(-1) * state -> (batch, num_heads, head_dim, head_dim)
- sum(dim=-2) -> (batch, num_heads, head_dim)
So y[b, t, h, j] = sum_i q[b, t, h, i] * state[b, h, i, j]
Let me write the kernel more carefully.
call → compile_kernel
callcompile_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.utils.cpp_extension as cpp_extension\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\ntemplate <typename scalar_t>\n__global__ void linear_attention_kernel(\n const scalar_t* __restrict__ q,\n const scalar_t* __restrict__ k,\n const scalar_t* __restrict__ v,\n const scalar_t* __restrict__ gate,\n const scalar_t* __restrict__ beta,\n scalar_t* __restrict__ output,\n int batch_size, int seq_len, int num_heads, int head_dim) {\n \n // Map block to (batch, head)\n int batch = blockIdx.x / num_heads;\n int head = blockIdx.x % num_heads;\n \n int tid = threadIdx.x;\n int block_size = blockDim.x;\n \n // Shared memory for state (head_dim x head_dim)\n extern __shared__ char shared_mem[];\n scalar_t* state = reinterpret_cast<scalar_t*>(shared_mem);\n \n // Initialize state to zero\n for (int i = tid; i < head_dim * head_dim; i += block_size) {\n state[i] = scalar_t(0);\n }\n __syncthreads();\n \n // Iterate through sequence\n for (int t = 0; t < seq_len; t++) {\n // Load gate and beta scalars for this (batch, head, t)\n // gate shape: (batch, seq_len, num_heads)\n int gate_idx = batch * seq_len * num_heads + t * num_heads + head;\n scalar_t gate_t = gate[gate_idx];\n scalar_t beta_t = beta[gate_idx];\n \n // Get pointers to k[t], v[t], q[t] for this (batch, head)\n // q/k/v shape: (batch, seq_len, num_heads, head_dim)\n int kvq_base = batch * seq_len * num_heads * head_dim + t * num_heads * head_dim + head * head_dim;\n const scalar_t* k_t = k + kvq_base;\n const scalar_t* v_t = v + kvq_base;\n const scalar_t* q_t = q + kvq_base;\n \n // Update state: state = gate_t * state + beta_t * k[t] * v[t]^T\n // state[i, j] = gate_t * state[i, j] + beta_t * k[i] * v[j]\n for (int idx = tid; idx < head_dim * head_dim; idx += block_size) {\n int row = idx / head_dim;\n int col = idx % head_dim;\n state[idx] = gate_t * state[idx] + beta_t * k_t[row] * v_t[col];\n }\n __syncthreads();\n \n // Compute output: y[t] = q[t]^T @ state -> sum over rows\n // y[j] = sum_i q[i] * state[i, j]\n scalar_t* y_t = output + kvq_base;\n \n for (int j = tid; j < head_dim; j += block_size) {\n scalar_t sum = scalar_t(0);\n for (int i = 0; i < head_dim; i++) {\n sum += q_t[i] * state[i * head_dim + j];\n }\n y_t[j] = sum;\n }\n __syncthreads();\n }\n}\n\ntorch::Tensor linear_attention_cuda(\n torch::Tensor q,\n torch::Tensor k,\n torch::Tensor v,\n torch::Tensor gate,\n torch::Tensor beta) {\n \n int batch_size = q.size(0);\n int seq_len = q.size(1);\n int num_heads = q.size(2);\n int head_dim = q.size(3);\n \n auto output = torch::zeros_like(q);\n \n int blocks = batch_size * num_heads;\n int threads = 128;\n int shared_mem_size = head_dim * head_dim * sizeof(float);\n \n AT_DISPATCH_FLOATING_TYPES(q.scalar_type(), \"linear_attention_kernel\", ([&] {\n linear_attention_kernel<scalar_t><<<blocks, threads, shared_mem_size>>>(\n q.data_ptr<scalar_t>(),\n k.data_ptr<scalar_t>(),\n v.data_ptr<scalar_t>(),\n gate.data_ptr<scalar_t>(),\n beta.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n batch_size, seq_len, num_heads, head_dim);\n }));\n \n return output;\n}\n\"\"\"\n\nmodule = cpp_extension.load_inline(\n name='linear_attention_ext',\n cpp_sources=['torch::Tensor linear_attention_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor gate, torch::Tensor beta);'],\n cuda_sources=[cuda_source],\n functions=['linear_attention_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n self.module = module\n \n def forward(self, q, k, v, gate, beta):\n return self.module.linear_attention_cuda(q, k, v, gate, beta)"
}