turn 01476.2s turn · 29.7s LLM · 1446.5s tools · tokens in=1,099 · out=2,567 · total=3,666 · FINAL
reasoning
Let me analyze this task:
1. The model is a grounded top-2 MoE router
2. It computes: `grounded = router_logits + alpha * (token_hidden @ expert_ground.t())`
3. Then it finds top-2 values and indices
4. Then applies softmax to those top-2 values
5. Returns a stacked tensor of shape (num_tokens, 2, 2) containing indices and weights
Let me trace through the shapes:
- token_hidden: (num_tokens, hidden_dim) = (4096, 128)
- router_logits: (num_tokens, num_experts) = (4096, 16)
- expert_ground: (num_experts, hidden_dim) = (16, 128)
- alpha: scalar float
Operations:
1. expert_ground.t(): (hidden_dim, num_experts) = (128, 16)
2. token_hidden @ expert_ground.t(): (num_tokens, num_experts) = (4096, 16)
3. alpha * that result: (4096, 16)
4. router_logits + that: (4096, 16) - this is grounded
5. topk(grounded, k=2, dim=-1): top_vals (4096, 2), top_idx (4096, 2)
6. softmax(top_vals, dim=-1): (4096, 2) - this is top_weights
7. top_idx.to(float32): (4096, 2)
8. stack((top_idx_float, top_weights), dim=-1): (4096, 2, 2)
So the output is (num_tokens, 2, 2) where:
- output[i, :, 0] = top indices as float
- output[i, :, 1] = softmax weights
Now I need to write a CUDA kernel for this. Let me think about the optimal approach:
1. The matrix multiplication is the main computational work: (4096, 128) @ (128, 16) = (4096, 16)
2. After that, element-wise addition with router_logits occurs.
3. topk along the expert dimension requires finding the two largest values per token.
4. Softmax is then applied to those two values.
5. Finally, the indices and weights get stacked together.
For performance, I could leverage cuBLAS for the matmul, then write a fused kernel to handle the bias addition, topk, softmax, and stacking operations all at once. This would reduce memory transfers between steps.
The optimal strategy combines cuBLAS for the GEMM with a custom kernel for the subsequent operations. I'll use cublasGemm for the matrix multiplication, then implement a kernel that fuses the remaining steps together.
For the fused component, I can have each thread handle one token row, computing the bias addition inline, then performing top-k selection and softmax normalization before outputting the results. This approach minimizes kernel launch overhead and memory bandwidth by keeping intermediate results in registers or shared memory.
Here's the implementation structure: set up cuBLAS handle during initialization, compute the grounded matrix by scaling the similarity scores and adding them to router logits, then launch a kernel where each thread processes one token to find the top-k experts and compute their weights.
The kernel iterates through all experts to find the two highest-scoring ones, then applies softmax to normalize their values. Finally, it writes the expert indices and their corresponding weights to the output tensor, packing them as (index, weight) pairs for each of the top-2 experts.
The forward pass computes grounded scores by scaling the matrix multiplication result and adding it to the router logits, then launches the kernel with one thread per token to extract and weight the top experts. The output is transposed to match the expected shape of (num_tokens, 2, 2) where the two experts' indices and weights are stacked along the last dimension. For each token, we're selecting the top 2 experts with their corresponding weights, so the structure makes sense as expert_0_idx, expert_0_weight, expert_1_idx, expert_1_weight arranged across the final dimension. The PyTorch implementation achieves this by stacking the indices and weights tensors along dim=-1, which interleaves them correctly for the desired output format. For the CUDA kernel, I need to output in the same shape: (num_tokens, 2, 2). I'll use cuBLAS for the matmul, then apply topk with softmax. The output tensor gets arranged so position [i, j, 0] holds the expert index and [i, j, 1] holds its weight. I'll structure the kernel to compute this directly without intermediate transposes.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile CUDA kernel at module load time\ncuda_source = r\"\"\"\n#include <torch/extension.h>\n#include <cublas_v2.h>\n#include <cuda_runtime.h>\n#include <cfloat>\n\nstatic cublasHandle_t get_cublas_handle() {\n static cublasHandle_t handle = nullptr;\n if (handle == nullptr) {\n cublasCreate(&handle);\n }\n return handle;\n}\n\n__global__ void top2_softmax_kernel(\n const float* __restrict__ grounded, // (num_tokens, num_experts)\n float* __restrict__ output, // (num_tokens, 2, 2)\n int num_tokens,\n int num_experts\n) {\n int token_idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (token_idx >= num_tokens) return;\n \n const float* row = grounded + token_idx * num_experts;\n \n // Find top-2 values and indices\n float max1 = -FLT_MAX, max2 = -FLT_MAX;\n int idx1 = 0, idx2 = 0;\n \n // First pass: find max1\n for (int e = 0; e < num_experts; e++) {\n float val = row[e];\n if (val > max1) {\n max2 = max1;\n idx2 = idx1;\n max1 = val;\n idx1 = e;\n } else if (val > max2) {\n max2 = val;\n idx2 = e;\n }\n }\n \n // Softmax on top-2 values\n float max_val = fmaxf(max1, max2);\n float e1 = expf(max1 - max_val);\n float e2 = expf(max2 - max_val);\n float sum = e1 + e2;\n float w1 = e1 / sum;\n float w2 = e2 / sum;\n \n // Write output: (num_tokens, 2, 2)\n // output[token_idx, :, :] = [[idx1, w1], [idx2, w2]]\n output[token_idx * 4 + 0] = static_cast<float>(idx1); // [token_idx, 0, 0]\n output[token_idx * 4 + 1] = w1; // [token_idx, 0, 1]\n output[token_idx * 4 + 2] = static_cast<float>(idx2); // [token_idx, 1, 0]\n output[token_idx * 4 + 3] = w2; // [token_idx, 1, 1]\n}\n\n// Fused kernel: computes grounded = router_logits + alpha * (H @ G^T), then top2+softmax\n// Each thread handles one token, computes dot product with all experts\n__global__ void fused_top2_kernel(\n const float* __restrict__ token_hidden, // (num_tokens, hidden_dim)\n const float* __restrict__ router_logits, // (num_tokens, num_experts)\n const float* __restrict__ expert_ground, // (num_experts, hidden_dim)\n float* __restrict__ output, // (num_tokens, 2, 2)\n int num_tokens,\n int hidden_dim,\n int num_experts,\n float alpha\n) {\n int token_idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (token_idx >= num_tokens) return;\n \n const float* hidden_row = token_hidden + token_idx * hidden_dim;\n const float* logits_row = router_logits + token_idx * num_experts;\n \n // Find top-2 grounded values and indices\n // grounded[i,e] = router_logits[i,e] + alpha * dot(hidden[i], ground[e])\n float max1 = -FLT_MAX, max2 = -FLT_MAX;\n int idx1 = 0, idx2 = 0;\n \n for (int e = 0; e < num_experts; e++) {\n // Compute dot product with expert e\n float dot = 0.0f;\n const float* ground_row = expert_ground + e * hidden_dim;\n for (int h = 0; h < hidden_dim; h++) {\n dot += hidden_row[h] * ground_row[h];\n }\n \n float grounded_val = logits_row[e] + alpha * dot;\n \n if (grounded_val > max1) {\n max2 = max1;\n idx2 = idx1;\n max1 = grounded_val;\n idx1 = e;\n } else if (grounded_val > max2) {\n max2 = grounded_val;\n idx2 = e;\n }\n }\n \n // Softmax on top-2 values (numerically stable)\n float max_val = fmaxf(max1, max2);\n float e1 = expf(max1 - max_val);\n float e2 = expf(max2 - max_val);\n float sum = e1 + e2;\n float w1 = e1 / sum;\n float w2 = e2 / sum;\n \n // Write output\n output[token_idx * 4 + 0] = static_cast<float>(idx1);\n output[token_idx * 4 + 1] = w1;\n output[token_idx * 4 + 2] = static_cast<float>(idx2);\n output[token_idx * 4 + 3] = w2;\n}\n\ntorch::Tensor grounded_top2_cuda(\n torch::Tensor token_hidden,\n torch::Tensor router_logits,\n torch::Tensor expert_ground,\n float alpha\n) {\n int num_tokens = token_hidden.size(0);\n int hidden_dim = token_hidden.size(1);\n int num_experts = router_logits.size(1);\n \n auto output = torch::empty({num_tokens, 2, 2}, \n torch::dtype(torch::kFloat32)\n .device(token_hidden.device()));\n \n int threads = 256;\n int blocks = (num_tokens + threads - 1) / threads;\n \n fused_top2_kernel<<<blocks, threads>>>(\n token_hidden.data_ptr<float>(),\n router_logits.data_ptr<float>(),\n expert_ground.data_ptr<float>(),\n output.data_ptr<float>(),\n num_tokens,\n hidden_dim,\n num_experts,\n alpha\n );\n \n return output;\n}\n\"\"\"\n\n# Load the CUDA extension\ngrounded_top2_ext = load_inline(\n name='grounded_top2_ext',\n cpp_sources=\"torch::Tensor grounded_top2_cuda(torch::Tensor, torch::Tensor, torch::Tensor, float);\",\n cuda_sources=cuda_source,\n functions=['grounded_top2_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Grounded top-2 MoE router with custom CUDA kernel.\n \"\"\"\n \n def __init__(self):\n super().__init__()\n \n def forward(\n self,\n token_hidden: torch.Tensor,\n router_logits: torch.Tensor,\n expert_ground: torch.Tensor,\n alpha: float,\n ) -> torch.Tensor:\n return grounded_top2_ext.grounded_top2_cuda(\n token_hidden, router_logits, expert_ground, alpha\n )"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 0.29 μs
Runtime stats: mean=0.29μs median=?μs std=0.00μs
Numerical precision: max_abs_err=5.36e-07 mean_abs_err=1.83e-08 max_rel_err=2.43e-06
Memory: 35.32 MB peak (ref: 35.76 MB, ratio: 0.9878x)
Kernel launches: 1 (ref: 20, fusion ratio: 20.0)
SOL score: 0.0039