compile_fail
8_SegmentTopKCSR.py
started
2026-05-11T10:08:35Z
compile error: /scratch/abaner/KernelBench/runs/pop_l123_st_gpt/popcorn/FW-GLM-5-1/level_1_problem_8_cache/k_5c3a2353a923/topk_extension/topk_extension.so: undefined symbol: _ZNK2at10TensorBase8data_ptrIxEEPT_v
timingkernelbench.timing
kernel mean / median / std
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Per-row top-k selection over CSR edge segments.
Returns the top-k values and their original edge indices for each node's
neighborhood. Useful for graph sparsification, hard attention, and
candidate-pruning workloads.
"""
def __init__(self, k):
super().__init__()
self.k = k
def forward(
self,
row_ptr: torch.Tensor,
edge_scores: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
num_nodes = row_ptr.numel() - 1
topk_vals = torch.full((num_nodes, self.k), float("-inf"), dtype=edge_scores.dtype, device=edge_scores.device)
topk_idx = torch.full((num_nodes, self.k), -1, dtype=torch.int64, device=edge_scores.device)
for node in range(num_nodes):
start = int(row_ptr[node].item())
end = int(row_ptr[node + 1].item())
if end <= start:
continue
segment = edge_scores[start:end]
curr_k = min(self.k, end - start)
vals, idx = torch.topk(segment, k=curr_k, dim=0)
topk_vals[node, :curr_k] = vals
topk_idx[node, :curr_k] = idx.long() + start
return topk_vals, topk_idx
num_nodes = 320
avg_degree = 12
k = 4
def get_inputs():
return list(popcorn_pri.make_csr_scalar_edge_scores(num_nodes, avg_degree))
def get_init_inputs():
return [k]
turn 0 · FINAL · 1 tool call(s) · 47.8s turn · 25.1s LLM · tokens in=1,291 · out=2,226 · total=3,517 · 10,365 chars (click to expand)
turn 047.8s turn · 25.1s LLM · 22.7s tools · tokens in=1,291 · out=2,226 · total=3,517 · FINAL
reasoning
Let me analyze this task carefully:
1. The model does per-row top-k selection over CSR edge segments
2. For each node, it finds the top-k values from its neighborhood edges and returns both values and indices
3. The reference implementation uses a Python loop which is slow
I need to write a custom CUDA kernel that:
- Takes `row_ptr` (CSR format pointers) and `edge_scores` as inputs
- For each node, finds top-k values from its segment
- Returns topk_vals and topk_idx tensors
Key considerations:
- Each node can have different segment sizes
- k is fixed at construction time (k=4)
- Need to handle cases where segment size < k (fill remaining with -inf and -1)
- The output indices should be global edge indices (local index + start position)
Let me design the CUDA kernel:
1. Use one thread block per node (or possibly one warp per node for efficiency)
2. Each thread block processes one segment to find top-k values
3. Since k=4 is small, I can use a simple approach where each thread loads values and we do parallel reduction for top-k
For small k values like k=4, I can:
- Have each thread in a block help find the top-k
- Use shared memory for intermediate results
- Sort or select top-k in parallel
Actually, since the average degree is around 12 and k=4, a simple approach would be:
- Launch one thread block per node
- Each block processes its segment
- Use a min-heap-like approach in shared memory to track top-k
Let me think about the implementation more carefully:
Option 1: One thread per node
- Load elements sequentially
- Keep track of top-k values locally
- Simple but may be slow for larger segments
Option 2: One warp per node
- Parallelize the top-k finding within each warp
- Use warp-level primitives for efficiency
- Better for segments with many elements
Option 3: One block per node
- Maximum parallelism for each segment
- Use shared memory for coordination
- Could be overkill for small segments
Given that avg_degree = 12 and k = 4, one warp per node seems like a good balance. Each warp can cooperatively find top-k for its segment. But let me also consider a simpler approach: one thread per node, each thread processes its segment sequentially. With 320 nodes and average degree 12, this is actually quite simple and might be efficient enough.
I'll start with the one-thread-per-node approach for simplicity, then optimize if needed. The kernel design involves assigning each thread to a node, processing its edges, maintaining top-k values and indices locally, and writing results back.
For the CUDA implementation, I'm considering two main strategies. The first is a heap-based approach where I maintain a min-heap of k elements, inserting new elements only if they're larger than the minimum. The second is a simpler top-k selection approach where I start with -inf values and iteratively replace the minimum when finding larger elements.
Given the small k (4), even bubble-sort-like operations would be efficient. I'll use the iterative approach: initialize top-k arrays with -inf and -1, then for each edge, check if it exceeds the minimum value and update accordingly. __device__ void update_topk(float* vals, int* idxs, int k, float new_val, int new_idx) {
int min_pos = 0;
for (int i = 1; i < k; i++) {
if (vals[i] < vals[min_pos]) min_pos = i;
}
if (new_val > vals[min_pos]) {
vals[min_pos] = new_val;
idxs[min_pos] = new_idx;
}
}
extern "C" __global__ void topk_per_row_kernel(
const int* __restrict__ row_ptr,
const float* __restrict__ edge_scores,
float* __restrict__ topk_vals,
int64_t* __restrict__ topk_idx,
int num_nodes,
int k
) {
int node = blockIdx.x * blockDim.x + threadIdx.x;
if (node >= num_nodes) return;
int start = row_ptr[node];
int end = row_ptr[node + 1];
// Initialize with -inf
float local_vals[16]; // max k
int local_idx[16];
for (int i = 0; i < k; i++) {
local_vals[i] = -CUDART_INF_F;
local_idx[i] = -1;
}
// Process segment
for (int i = start; i < end; i++) {
update_topk(local_vals, local_idx, k, edge_scores[i], i);
}
// Write results
for (int i = 0; i < k; i++) {
topk_vals[node * k + i] = local_vals[i];
topk_idx[node * k + i] = local_idx[i];
}
}
The update function finds the minimum value in the current top-k and replaces it if a larger value comes in. The kernel processes each row independently, maintaining local arrays of the best k scores and their indices, then writes the final results back to global memory.
Since the output arrays are [num_nodes, k] with k=4, the flattened indexing node * k + i works correctly. Now I need to set up the PyTorch extension with proper C++ wrapper code that avoids including pybind11 headers since the build system handles that automatically.
The wrapper function converts the row pointers to integers, calls the CUDA kernel with enough threads to cover all nodes, then synchronizes. The kernel launch uses 256 threads per block with enough blocks to process every node in parallel.
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 = \"\"\"\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cfloat>\n\n__device__ inline void update_topk(float* vals, long long* idxs, int k, float new_val, long long new_idx) {\n // Find position of minimum value\n int min_pos = 0;\n float min_val = vals[0];\n for (int i = 1; i < k; i++) {\n if (vals[i] < min_val) {\n min_val = vals[i];\n min_pos = i;\n }\n }\n // Replace if new value is larger\n if (new_val > min_val) {\n vals[min_pos] = new_val;\n idxs[min_pos] = new_idx;\n }\n}\n\n__global__ void topk_per_row_kernel(\n const int* __restrict__ row_ptr,\n const float* __restrict__ edge_scores,\n float* __restrict__ topk_vals,\n long long* __restrict__ topk_idx,\n int num_nodes,\n int k\n) {\n int node = blockIdx.x * blockDim.x + threadIdx.x;\n if (node >= num_nodes) return;\n \n int start = row_ptr[node];\n int end = row_ptr[node + 1];\n \n // Initialize with -inf (register arrays for efficiency)\n float local_vals[16];\n long long local_idx[16];\n \n for (int i = 0; i < k; i++) {\n local_vals[i] = -FLT_MAX;\n local_idx[i] = -1;\n }\n \n // Process segment sequentially\n for (int i = start; i < end; i++) {\n update_topk(local_vals, local_idx, k, edge_scores[i], (long long)i);\n }\n \n // Write results (output is row-major [num_nodes, k])\n for (int i = 0; i < k; i++) {\n topk_vals[node * k + i] = local_vals[i];\n topk_idx[node * k + i] = local_idx[i];\n }\n}\n\ntorch::Tensor topk_per_row_cuda(\n torch::Tensor row_ptr,\n torch::Tensor edge_scores,\n torch::Tensor topk_vals,\n torch::Tensor topk_idx,\n int k\n) {\n int num_nodes = row_ptr.size(0) - 1;\n \n const int threads = 256;\n const int blocks = (num_nodes + threads - 1) / threads;\n \n topk_per_row_kernel<<<blocks, threads>>>(\n row_ptr.data_ptr<int>(),\n edge_scores.data_ptr<float>(),\n topk_vals.data_ptr<float>(),\n topk_idx.data_ptr<long long>(),\n num_nodes,\n k\n );\n \n return topk_vals;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor topk_per_row_cuda(\n torch::Tensor row_ptr,\n torch::Tensor edge_scores,\n torch::Tensor topk_vals,\n torch::Tensor topk_idx,\n int k);\n\"\"\"\n\n# Load the CUDA extension\ntopk_extension = load_inline(\n name='topk_extension',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['topk_per_row_cuda'],\n extra_cuda_cflags=['-O3', '-use_fast_math'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Per-row top-k selection over CSR edge segments using custom CUDA kernel.\n \n Returns the top-k values and their original edge indices for each node's\n neighborhood. Useful for graph sparsification, hard attention, and\n candidate-pruning workloads.\n \"\"\"\n\n def __init__(self, k):\n super().__init__()\n self.k = k\n\n def forward(\n self,\n row_ptr: torch.Tensor,\n edge_scores: torch.Tensor,\n ) -> tuple[torch.Tensor, torch.Tensor]:\n num_nodes = row_ptr.numel() - 1\n \n # Preallocate output tensors with default values\n topk_vals = torch.full(\n (num_nodes, self.k), \n float(\"-inf\"), \n dtype=edge_scores.dtype, \n device=edge_scores.device\n )\n topk_idx = torch.full(\n (num_nodes, self.k), \n -1, \n dtype=torch.int64, \n device=edge_scores.device\n )\n \n # Call custom CUDA kernel\n topk_extension.topk_per_row_cuda(\n row_ptr.int(),\n edge_scores,\n topk_vals,\n topk_idx,\n self.k\n )\n \n return topk_vals, topk_idx"
}
result ← submit_kernel (fail)
failsubmit_kernel
submit_kernel FAILED: kernel did not compile.
/scratch/abaner/KernelBench/runs/pop_l123_st_gpt/popcorn/FW-GLM-5-1/level_1_problem_8_cache/k_5c3a2353a923/topk_extension/topk_extension.so: undefined symbol: _ZNK2at10TensorBase8data_ptrIxEEPT_v
final submitted kernel (151 lines, 3,882 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
# Compile CUDA kernel at module load time
cuda_source = """
#include <cuda.h>
#include <cuda_runtime.h>
#include <cfloat>
__device__ inline void update_topk(float* vals, long long* idxs, int k, float new_val, long long new_idx) {
// Find position of minimum value
int min_pos = 0;
float min_val = vals[0];
for (int i = 1; i < k; i++) {
if (vals[i] < min_val) {
min_val = vals[i];
min_pos = i;
}
}
// Replace if new value is larger
if (new_val > min_val) {
vals[min_pos] = new_val;
idxs[min_pos] = new_idx;
}
}
__global__ void topk_per_row_kernel(
const int* __restrict__ row_ptr,
const float* __restrict__ edge_scores,
float* __restrict__ topk_vals,
long long* __restrict__ topk_idx,
int num_nodes,
int k
) {
int node = blockIdx.x * blockDim.x + threadIdx.x;
if (node >= num_nodes) return;
int start = row_ptr[node];
int end = row_ptr[node + 1];
// Initialize with -inf (register arrays for efficiency)
float local_vals[16];
long long local_idx[16];
for (int i = 0; i < k; i++) {
local_vals[i] = -FLT_MAX;
local_idx[i] = -1;
}
// Process segment sequentially
for (int i = start; i < end; i++) {
update_topk(local_vals, local_idx, k, edge_scores[i], (long long)i);
}
// Write results (output is row-major [num_nodes, k])
for (int i = 0; i < k; i++) {
topk_vals[node * k + i] = local_vals[i];
topk_idx[node * k + i] = local_idx[i];
}
}
torch::Tensor topk_per_row_cuda(
torch::Tensor row_ptr,
torch::Tensor edge_scores,
torch::Tensor topk_vals,
torch::Tensor topk_idx,
int k
) {
int num_nodes = row_ptr.size(0) - 1;
const int threads = 256;
const int blocks = (num_nodes + threads - 1) / threads;
topk_per_row_kernel<<<blocks, threads>>>(
row_ptr.data_ptr<int>(),
edge_scores.data_ptr<float>(),
topk_vals.data_ptr<float>(),
topk_idx.data_ptr<long long>(),
num_nodes,
k
);
return topk_vals;
}
"""
cpp_source = """
torch::Tensor topk_per_row_cuda(
torch::Tensor row_ptr,
torch::Tensor edge_scores,
torch::Tensor topk_vals,
torch::Tensor topk_idx,
int k);
"""
# Load the CUDA extension
topk_extension = load_inline(
name='topk_extension',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['topk_per_row_cuda'],
extra_cuda_cflags=['-O3', '-use_fast_math'],
verbose=False
)
class ModelNew(nn.Module):
"""
Per-row top-k selection over CSR edge segments using custom CUDA kernel.
Returns the top-k values and their original edge indices for each node's
neighborhood. Useful for graph sparsification, hard attention, and
candidate-pruning workloads.
"""
def __init__(self, k):
super().__init__()
self.k = k
def forward(
self,
row_ptr: torch.Tensor,
edge_scores: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
num_nodes = row_ptr.numel() - 1
# Preallocate output tensors with default values
topk_vals = torch.full(
(num_nodes, self.k),
float("-inf"),
dtype=edge_scores.dtype,
device=edge_scores.device
)
topk_idx = torch.full(
(num_nodes, self.k),
-1,
dtype=torch.int64,
device=edge_scores.device
)
# Call custom CUDA kernel
topk_extension.topk_per_row_cuda(
row_ptr.int(),
edge_scores,
topk_vals,
topk_idx,
self.k
)
return topk_vals, topk_idx