correct
7_GaussianProcessRegression.py
started
2026-05-11T12:34:14Z
timingkernelbench.timing
kernel mean / median / std
10.10 / — / 0.00 μs
ref mean / median / std
5.45 / — / 0.95 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
1.59x
energyNVML (pynvml)
ratio (ref / kernel)
0.20x
SOL (speed-of-light)heuristic_from_speedup
roofline / occupancyheuristic
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Gaussian Process posterior predictive distribution via Cholesky
decomposition. Computes the predictive mean and variance at test
points given training data and an RBF kernel. The Cholesky solve
is the computational bottleneck in exact GP inference.
"""
def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):
super().__init__()
self.noise = noise
self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())
self.log_variance = nn.Parameter(torch.tensor(variance).log())
def _rbf_kernel(self, X1, X2):
l2 = self.log_lengthscale.exp() ** 2
var = self.log_variance.exp()
dist_sq = ((X1.unsqueeze(2) - X2.unsqueeze(1)) ** 2).sum(-1)
return var * torch.exp(-0.5 * dist_sq / l2)
def forward(
self, X_train: torch.Tensor, Y_train: torch.Tensor, X_test: torch.Tensor
) -> tuple:
"""
Args:
X_train: (B, N, D)
Y_train: (B, N, 1)
X_test: (B, M, D)
Returns:
(mean, var):
mean: (B, M, 1) – predictive mean
var: (B, M) – predictive variance
"""
K_nn = self._rbf_kernel(X_train, X_train)
K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)
K_nm = self._rbf_kernel(X_train, X_test)
K_mm = self._rbf_kernel(X_test, X_test)
L = torch.linalg.cholesky(K_nn)
alpha = torch.cholesky_solve(Y_train, L)
mean = K_nm.transpose(-1, -2) @ alpha
v = torch.linalg.solve_triangular(L, K_nm, upper=False)
var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)
var = var.clamp(min=1e-6)
return mean, var
input_dim = 4
num_train = 128
num_test = 32
batch_size = 8
def get_inputs():
p = popcorn_pri
X_train = torch.randn(p.jitter_int(batch_size), p.jitter_int(num_train), input_dim)
Y_train = torch.randn(p.jitter_int(batch_size), p.jitter_int(num_train), 1)
X_test = torch.randn(p.jitter_int(batch_size), p.jitter_int(num_test), input_dim)
return [X_train, Y_train, X_test]
def get_init_inputs():
return [input_dim]
turn 0468.0s turn · 2.3s LLM · 465.6s tools · tokens in=1,841 · out=104 · total=1,945
reasoning
Let me analyze this task. I need to implement a custom CUDA kernel for a Gaussian Process posterior predictive distribution computation.
The key operations are:
1. RBF kernel computation between X1 and X2
2. Cholesky decomposition of K_nn
3. Cholesky solve for alpha
4. Matrix multiplication for mean
5. Triangular solve for v
6. Variance computation
Let me first get the GPU specs to understand the hardware I'm working with.
call → get_gpu_specs
callget_gpu_specs
{}
result ← get_gpu_specs (ok)
okget_gpu_specs
GPU specs for NVIDIA H100 80GB HBM3:
total memory (runtime): 79.1 GB
spec entry: H100
GPU Architecture: Hopper
GPU Memory: 80GB
Memory Bandwidth: 3.35 TB/s
FP64 TFLOPS: 34
FP64 Tensor Core TFLOPS: 67
FP32 TFLOPS: 67
TF32 Tensor Core TFLOPS: 989 with sparsity
BFLOAT16 Tensore Core TFLOPS: 1979 with sparsity
FP16 Tensor Core TFLOPS: 1979 with sparsity
FP8 Tensor Core TFLOPS: 3958 with sparsity
INT8 Tensor Core TOPS: 3958 with sparsity
Register File Size: 64K 32-bit registers per SM
Maximum number of registers per thread: 255
Maximum number of thread blocks per SM: 32
Shared memory capacity per SM: 228 KB
Maximum shared memory per thread block: 227 KB
turn 1 · 1 tool call(s) · 526.8s turn · 30.3s LLM · tokens in=2,089 · out=3,060 · total=5,149 · 11,732 chars (click to expand)
turn 1526.8s turn · 30.3s LLM · 496.4s tools · tokens in=2,089 · out=3,060 · total=5,149
reasoning
Now let me understand the computation in detail:
1. `_rbf_kernel(X1, X2)`: Computes RBF kernel matrix
- X1: (B, N, D), X2: (B, M, D)
- Output: (B, N, M)
- dist_sq = ((X1.unsqueeze(2) - X2.unsqueeze(1)) ** 2).sum(-1)
- K = var * exp(-0.5 * dist_sq / l2)
2. Forward:
- K_nn = rbf_kernel(X_train, X_train) # (B, N, N)
- K_nn += noise * I
- K_nm = rbf_kernel(X_train, X_test) # (B, N, M)
- K_mm = rbf_kernel(X_test, X_test) # (B, M, M)
- L = cholesky(K_nn) # (B, N, N) lower triangular
- alpha = cholesky_solve(Y_train, L) # (B, N, 1)
- mean = K_nm.T @ alpha # (B, M, 1)
- v = solve_triangular(L, K_nm, upper=False) # (B, N, M)
- var = K_mm.diagonal() - (v ** 2).sum(dim=-2) # (B, M)
The main computational tasks are:
1. Kernel computation (O(N^2 * D + N * M * D))
2. Cholesky decomposition (O(N^3))
3. Cholesky solve (O(N^2))
4. Triangular solve (O(N^2 * M))
5. Matrix multiply for mean (O(M * N))
The main bottlenecks are:
1. Computing RBF kernels (especially K_nn which is N×N)
2. Cholesky decomposition
For the RBF kernel computation:
- dist_sq[i,j] = ||X1[i] - X2[j]||^2 = ||X1[i]||^2 + ||X2[j]||^2 - 2 * X1[i]·X2[j]
- This is more efficient using matrix multiplication
Let me write a kernel that computes:
1. RBF kernels efficiently
2. Uses cuBLAS/cuSolver for Cholesky and triangular solves
Actually, looking at the problem more carefully, the key operations that PyTorch is doing are:
1. Kernel computation - can be optimized with custom CUDA
2. Cholesky decomposition - PyTorch uses cuSolver under the hood
3. Cholesky solve / triangular solve - PyTorch uses cuBLAS
The main opportunities for optimization:
1. Kernel computation: The current implementation expands tensors and computes element-wise. We can use the identity ||x-y||^2 = ||x||^2 + ||y||^2 - 2*x·y to avoid the expansion.
2. Fusing operations where possible.
Let me start by writing a kernel that optimizes the RBF kernel computation:
```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
# Define the CUDA kernel
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <cmath>
template <typename scalar_t>
__global__ void rbf_kernel_forward_kernel(
const scalar_t* __restrict__ X1,
const scalar_t* __restrict__ X2,
const scalar_t* __restrict__ X1_norm_sq,
const scalar_t* __restrict__ X2_norm_sq,
scalar_t* __restrict__ output,
const scalar_t lengthscale_sq,
const scalar_t variance,
int batch_size,
int N,
int M,
int D) {
const int batch_idx = blockIdx.z;
const int row = blockIdx.y * blockDim.y + threadIdx.y;
const int col = blockIdx.x * blockDim.x + threadIdx.x;
if (batch_idx >= batch_size || row >= N || col >= M) return;
// Compute dot product
scalar_t dot = 0;
for (int d = 0; d < D; d++) {
dot += X1[batch_idx * N * D + row * D + d] *
X2[batch_idx * M * D + col * D + d];
}
// dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot
scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] +
X2_norm_sq[batch_idx * M + col] - 2 * dot;
// RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)
scalar_t val = variance * exp(-0.5 * dist_sq / lengthscale_sq);
output[batch_idx * N * M + row * M + col] = val;
}
torch::Tensor rbf_kernel_cuda(
torch::Tensor X1,
torch::Tensor X2,
double lengthscale,
double variance) {
const int batch_size = X1.size(0);
const int N = X1.size(1);
const int M = X2.size(1);
const int D = X1.size(2);
// Compute squared norms
auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)
auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)
auto output = torch::empty({batch_size, N, M}, X1.options());
const dim3 threads(16, 16);
const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);
AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), "rbf_kernel_forward_kernel", ([&] {
rbf_kernel_forward_kernel<scalar_t><<<blocks, threads>>>(
X1.data_ptr<scalar_t>(),
X2.data_ptr<scalar_t>(),
X1_norm_sq.data_ptr<scalar_t>(),
X2_norm_sq.data_ptr<scalar_t>(),
output.data_ptr<scalar_t>(),
lengthscale * lengthscale,
variance,
batch_size, N, M, D);
}));
return output;
}
"""
cpp_source = """
torch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);
"""
rbf_module = load_inline(
name='rbf_kernel_cuda',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['rbf_kernel_cuda'],
extra_cuda_cflags=['-O3'],
)
class ModelNew(nn.Module):
def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):
super().__init__()
self.noise = noise
self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())
self.log_variance = nn.Parameter(torch.tensor(variance).log())
def _rbf_kernel(self, X1, X2):
l = self.log_lengthscale.exp().item()
v = self.log_variance.exp().item()
return rbf_module.rbf_kernel_cuda(X1, X2, l, v)
def forward(self, X_train, Y_train, X_test):
K_nn = self._rbf_kernel(X_train, X_train)
K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)
K_nm = self._rbf_kernel(X_train, X_test)
K_mm = self._rbf_kernel(X_test, X_test)
L = torch.linalg.cholesky(K_nn)
alpha = torch.cholesky_solve(Y_train, L)
mean = K_nm.transpose(-1, -2) @ alpha
v = torch.linalg.solve_triangular(L, K_nm, upper=False)
var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)
var = var.clamp(min=1e-6)
return mean, var
```
This approach optimizes the RBF kernel computation. Let me compile and test this first.
assistantLooking at this problem, I need to optimize a Gaussian Process predictive distribution computation. The main components are:
1. RBF kernel computations (K_nn, K_nm, K_mm)
2. Cholesky decomposition
3. Triangular solves
The RBF kernel computation can be optimized significantly using the identity `||x-y||² = ||x||² + ||y||² - 2·x·y`, avoiding the expensive tensor expansion. Let me start by implementing an optimized RBF kernel in CUDA.
call → compile_kernel
callcompile_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__global__ void rbf_kernel_forward_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product\n scalar_t dot = 0;\n for (int d = 0; d < D; d++) {\n dot += X1[batch_idx * N * D + row * D + d] * \n X2[batch_idx * M * D + col * D + d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n \n output[batch_idx * N * M + row * M + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_forward_kernel\", ([&] {\n rbf_kernel_forward_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\n\"\"\"\n\nrbf_module = load_inline(\n name='rbf_kernel_cuda_v1',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return rbf_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n K_nn = self._rbf_kernel(X_train, X_train)\n K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)\n K_nm = self._rbf_kernel(X_train, X_test)\n K_mm = self._rbf_kernel(X_test, X_test)\n \n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n mean = K_nm.transpose(-1, -2) @ alpha\n \n v = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 2512.2s turn · 10.6s LLM · 501.7s tools · tokens in=3,350 · out=1,157 · total=4,507
reasoning
Good, it compiled. Now let me run the correctness test.
call → run_correctness
callrun_correctness
{
"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__global__ void rbf_kernel_forward_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product\n scalar_t dot = 0;\n for (int d = 0; d < D; d++) {\n dot += X1[batch_idx * N * D + row * D + d] * \n X2[batch_idx * M * D + col * D + d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n \n output[batch_idx * N * M + row * M + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_forward_kernel\", ([&] {\n rbf_kernel_forward_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\n\"\"\"\n\nrbf_module = load_inline(\n name='rbf_kernel_cuda_v1',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return rbf_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n K_nn = self._rbf_kernel(X_train, X_train)\n K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)\n K_nm = self._rbf_kernel(X_train, X_test)\n K_mm = self._rbf_kernel(X_test, X_test)\n \n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n mean = K_nm.transpose(-1, -2) @ alpha\n \n v = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (5 / 5) trials all matched the reference.
Numerical precision: max_abs_err=8.39e-05 mean_abs_err=7.59e-06 max_rel_err=4.03e-02
turn 3 · 1 tool call(s) · 433.6s turn · 13.4s LLM · tokens in=4,551 · out=1,447 · total=5,998 · 6,479 chars (click to expand)
turn 3433.6s turn · 13.4s LLM · 420.2s tools · tokens in=4,551 · out=1,447 · total=5,998
reasoning
The correctness test passed. Now let me optimize this further. The main bottleneck in the reference is likely the RBF kernel computation which involves many memory accesses. Let me think about further optimizations:
1. The RBF kernel can be improved by using shared memory for X1 and X2 data
2. We could potentially use cuBLAS for the matmul operations
3. The Cholesky decomposition and triangular solves could be fused or optimized
Let me first submit the current version to see what the baseline speedup is, then iterate.
Actually, let me try a few more optimizations first. The dot product computation can be optimized using shared memory tile-based approach similar to matrix multiplication. Also, I should consider using vectorized loads.
Let me try a tiled shared memory approach for the RBF kernel:
assistantGood, correctness passes. Now let me optimize the RBF kernel further using shared memory tiling for better memory access patterns:
call → compile_kernel
callcompile_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\n// Constants for shared memory tiling\nconstexpr int TILE_SIZE = 16;\nconstexpr int MAX_D = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_tiled_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n __shared__ scalar_t s_X1[TILE_SIZE][MAX_D];\n __shared__ scalar_t s_X2[TILE_SIZE][MAX_D];\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * TILE_SIZE + threadIdx.y;\n const int col = blockIdx.x * TILE_SIZE + threadIdx.x;\n \n const int ty = threadIdx.y;\n const int tx = threadIdx.x;\n \n scalar_t dot = 0;\n \n // Load X1 row into shared memory\n if (row < N && tx < D) {\n s_X1[ty][tx] = X1[batch_idx * N * D + row * D + tx];\n }\n \n // Load X2 col into shared memory\n if (col < M && ty < D) {\n s_X2[tx][ty] = X2[batch_idx * M * D + col * D + ty];\n }\n \n __syncthreads();\n \n // Compute dot product\n if (row < N && col < M) {\n for (int d = 0; d < D; d++) {\n dot += s_X1[ty][d] * s_X2[tx][d];\n }\n \n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n output[batch_idx * N * M + row * M + col] = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n }\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(TILE_SIZE, TILE_SIZE);\n const dim3 blocks((M + TILE_SIZE - 1) / TILE_SIZE, \n (N + TILE_SIZE - 1) / TILE_SIZE, \n batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_tiled_kernel\", ([&] {\n rbf_kernel_tiled_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\n\"\"\"\n\nrbf_module = load_inline(\n name='rbf_kernel_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return rbf_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n K_nn = self._rbf_kernel(X_train, X_train)\n K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)\n K_nm = self._rbf_kernel(X_train, X_test)\n K_mm = self._rbf_kernel(X_test, X_test)\n \n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n mean = K_nm.transpose(-1, -2) @ alpha\n \n v = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 4484.2s turn · 11.4s LLM · 472.8s tools · tokens in=5,849 · out=1,296 · total=7,145
reasoning
The tiled kernel with fixed-size shared memory has limitations with D up to 32. Let me run correctness to check if it works for D=4 in the test case.
call → run_correctness
callrun_correctness
{
"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\n// Constants for shared memory tiling\nconstexpr int TILE_SIZE = 16;\nconstexpr int MAX_D = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_tiled_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n __shared__ scalar_t s_X1[TILE_SIZE][MAX_D];\n __shared__ scalar_t s_X2[TILE_SIZE][MAX_D];\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * TILE_SIZE + threadIdx.y;\n const int col = blockIdx.x * TILE_SIZE + threadIdx.x;\n \n const int ty = threadIdx.y;\n const int tx = threadIdx.x;\n \n scalar_t dot = 0;\n \n // Load X1 row into shared memory\n if (row < N && tx < D) {\n s_X1[ty][tx] = X1[batch_idx * N * D + row * D + tx];\n }\n \n // Load X2 col into shared memory\n if (col < M && ty < D) {\n s_X2[tx][ty] = X2[batch_idx * M * D + col * D + ty];\n }\n \n __syncthreads();\n \n // Compute dot product\n if (row < N && col < M) {\n for (int d = 0; d < D; d++) {\n dot += s_X1[ty][d] * s_X2[tx][d];\n }\n \n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n output[batch_idx * N * M + row * M + col] = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n }\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(TILE_SIZE, TILE_SIZE);\n const dim3 blocks((M + TILE_SIZE - 1) / TILE_SIZE, \n (N + TILE_SIZE - 1) / TILE_SIZE, \n batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_tiled_kernel\", ([&] {\n rbf_kernel_tiled_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\n\"\"\"\n\nrbf_module = load_inline(\n name='rbf_kernel_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return rbf_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n K_nn = self._rbf_kernel(X_train, X_train)\n K_nn = K_nn + self.noise * torch.eye(K_nn.shape[-1], device=K_nn.device).unsqueeze(0)\n K_nm = self._rbf_kernel(X_train, X_test)\n K_mm = self._rbf_kernel(X_test, X_test)\n \n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n mean = K_nm.transpose(-1, -2) @ alpha\n \n v = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm.diagonal(dim1=-2, dim2=-1) - (v ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (5 / 5) trials all matched the reference.
Numerical precision: max_abs_err=8.39e-05 mean_abs_err=7.59e-06 max_rel_err=4.03e-02
turn 5 · 1 tool call(s) · 490.4s turn · 21.0s LLM · tokens in=7,167 · out=2,200 · total=9,367 · 12,710 chars (click to expand)
turn 5490.4s turn · 21.0s LLM · 469.4s tools · tokens in=7,167 · out=2,200 · total=9,367
reasoning
Good, correctness passes. But the shared memory approach has fixed size array which may not work for all D values. Let me also try a different approach - using a more efficient kernel that handles the matrix multiplication and exponential in one pass, and also optimizing the diagonal computation for K_mm.
Actually, let me think more about what the bottleneck is. The RBF kernel involves:
1. Computing squared norms: (X * X).sum(-1) - this is efficient
2. Matrix multiplication for dot products - this is O(N*M*D)
3. Exponential computation - this is O(N*M)
For K_nn (128x128) with D=4, we have 128*128*4 = 65536 multiply-adds per batch
For K_nm (128x32) with D=4, we have 128*32*4 = 16384 multiply-adds per batch
For K_mm (32x32) with D=4, we have 32*32*4 = 4096 multiply-adds per batch
The total is relatively small. The Cholesky decomposition and triangular solve on 128x128 matrices are probably the dominant costs.
Actually, I should consider fusing more operations. The current approach still uses PyTorch operations for:
- Cholesky decomposition
- Cholesky solve
- Matrix multiplication (K_nm.T @ alpha)
- Triangular solve
- Diagonal computation
One key optimization: we can compute K_mm more efficiently since we only need the diagonal. K_mm.diagonal() is just the variance (since dist_sq = 0 for self-pairs).
Actually wait - for K_mm diagonal, dist_sq = ||x_i - x_i||^2 = 0, so K_mm[i,i] = variance * exp(0) = variance.
So the diagonal of K_mm is always just variance! This is a nice simplification.
Let me implement a cleaner version with this optimization:
assistantLet me implement a cleaner version with the observation that K_mm's diagonal is always just `variance` (since the distance between a point and itself is 0):
call → compile_kernel
callcompile_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\nconstexpr int TILE_SIZE = 16;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n #pragma unroll 4\n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n}\n\n// Kernel to add noise diagonal to K_nn\ntemplate <typename scalar_t>\n__global__ void add_noise_diag_kernel(\n scalar_t* __restrict__ K,\n const scalar_t noise,\n int batch_size,\n int N) {\n \n const int batch_idx = blockIdx.x;\n const int idx = threadIdx.x;\n \n if (batch_idx >= batch_size || idx >= N) return;\n \n // Add noise to diagonal\n K[batch_idx * N * N + idx * N + idx] += noise;\n}\n\n// Kernel to compute diagonal of K_mm (which is just variance for RBF)\ntemplate <typename scalar_t>\n__global__ void fill_kmm_diag_kernel(\n scalar_t* __restrict__ K_mm_diag,\n const scalar_t variance,\n int batch_size,\n int M) {\n \n const int batch_idx = blockIdx.x;\n const int idx = threadIdx.x;\n \n if (batch_idx >= batch_size || idx >= M) return;\n \n // For RBF kernel, diagonal is always variance (dist_sq = 0)\n K_mm_diag[batch_idx * M + idx] = variance;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(TILE_SIZE, TILE_SIZE);\n const dim3 blocks((M + TILE_SIZE - 1) / TILE_SIZE, \n (N + TILE_SIZE - 1) / TILE_SIZE, \n batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\nvoid add_noise_diag_cuda(torch::Tensor K, double noise) {\n const int batch_size = K.size(0);\n const int N = K.size(1);\n \n const int threads = min(N, 256);\n const int blocks = batch_size;\n \n AT_DISPATCH_FLOATING_TYPES(K.scalar_type(), \"add_noise_diag_kernel\", ([&] {\n add_noise_diag_kernel<scalar_t><<<blocks, threads>>>(\n K.data_ptr<scalar_t>(),\n noise,\n batch_size, N);\n }));\n}\n\ntorch::Tensor get_kmm_diag_cuda(double variance, int batch_size, int M, torch::TensorOptions options) {\n auto output = torch::empty({batch_size, M}, options);\n \n const int threads = min(M, 256);\n const int blocks = batch_size;\n \n AT_DISPATCH_FLOATING_TYPES(options.dtype(), \"fill_kmm_diag_kernel\", ([&] {\n fill_kmm_diag_kernel<scalar_t><<<blocks, threads>>>(\n output.data_ptr<scalar_t>(),\n variance,\n batch_size, M);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\nvoid add_noise_diag_cuda(torch::Tensor K, double noise);\ntorch::Tensor get_kmm_diag_cuda(double variance, int batch_size, int M, torch::TensorOptions options);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v3',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'add_noise_diag_cuda', 'get_kmm_diag_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return gp_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n # Compute RBF kernels\n K_nn = self._rbf_kernel(X_train, X_train)\n gp_module.add_noise_diag_cuda(K_nn, self.noise)\n K_nm = self._rbf_kernel(X_train, X_test)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n K_mm_diag = gp_module.get_kmm_diag_cuda(v, X_test.shape[0], X_test.shape[1], X_test.options())\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← compile_kernel (fail)
failcompile_kernel
compile_kernel FAILED: RuntimeError.
RuntimeError: Error building extension 'gp_kernel_cuda_v3': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=gp_kernel_cuda_v3 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_3_problem_7_cache/k_94a96d3139d8/gp_kernel_cuda_v3/main.cpp -o main.o
[2/3] /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=gp_kernel_cuda_v3 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_3_problem_7_cache/k_94a96d3139d8/gp_kernel_cuda_v3/cuda.cu -o cuda.cuda.o
FAILED: [code=2] cuda.cuda.o
/usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=gp_kernel_cuda_v3 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_3_problem_7_cache/k_94a96d3139d8/gp_kernel_cuda_v3/cuda.cu -o cuda.cuda.o
nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used
/scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_3_problem_7_cache/k_94a96d3139d8/gp_kernel_cuda_v3/cuda.cu(142): error: no suitable conversion function from "const caffe2::TypeMeta" to "c10::ScalarType" exists
; torch::headeronly::ScalarType _st = ::detail::scalar_type(the_type); ;
^
1 error detected in the compilation of "/scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_3_problem_7_cache/k_94a96d3139d8/gp_kernel_cuda_v3/cuda.cu".
ninja: build stopped: subcommand failed.
turn 6 · 1 tool call(s) · 958.0s turn · 13.3s LLM · tokens in=9,984 · out=1,506 · total=11,490 · 6,595 chars (click to expand)
turn 6958.0s turn · 13.3s LLM · 944.7s tools · tokens in=9,984 · out=1,506 · total=11,490
reasoning
There's an issue with passing the torch::TensorOptions. Let me fix this.
call → compile_kernel
callcompile_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\nconstexpr int TILE_SIZE = 16;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n #pragma unroll 4\n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n}\n\n// Kernel to add noise diagonal to K_nn\ntemplate <typename scalar_t>\n__global__ void add_noise_diag_kernel(\n scalar_t* __restrict__ K,\n const scalar_t noise,\n int batch_size,\n int N) {\n \n const int batch_idx = blockIdx.x;\n const int idx = threadIdx.x;\n \n if (batch_idx >= batch_size || idx >= N) return;\n \n // Add noise to diagonal\n K[batch_idx * N * N + idx * N + idx] += noise;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(TILE_SIZE, TILE_SIZE);\n const dim3 blocks((M + TILE_SIZE - 1) / TILE_SIZE, \n (N + TILE_SIZE - 1) / TILE_SIZE, \n batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\nvoid add_noise_diag_cuda(torch::Tensor K, double noise) {\n const int batch_size = K.size(0);\n const int N = K.size(1);\n \n const int threads = min(N, 256);\n const int blocks = batch_size;\n \n AT_DISPATCH_FLOATING_TYPES(K.scalar_type(), \"add_noise_diag_kernel\", ([&] {\n add_noise_diag_kernel<scalar_t><<<blocks, threads>>>(\n K.data_ptr<scalar_t>(),\n static_cast<scalar_t>(noise),\n batch_size, N);\n }));\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\nvoid add_noise_diag_cuda(torch::Tensor K, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v3',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'add_noise_diag_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return gp_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n # Compute RBF kernels\n K_nn = self._rbf_kernel(X_train, X_train)\n gp_module.add_noise_diag_cuda(K_nn, self.noise)\n K_nm = self._rbf_kernel(X_train, X_test)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n v = self.log_variance.exp().item()\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 7 · 1 tool call(s) · 682.3s turn · 12.2s LLM · tokens in=11,486 · out=1,498 · total=12,984 · 6,656 chars (click to expand)
turn 7682.3s turn · 12.2s LLM · 670.0s tools · tokens in=11,486 · out=1,498 · total=12,984
reasoning
Good, let me check correctness now.
call → run_correctness
callrun_correctness
{
"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\nconstexpr int TILE_SIZE = 16;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n #pragma unroll 4\n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2 * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * exp(-0.5 * dist_sq / lengthscale_sq);\n}\n\n// Kernel to add noise diagonal to K_nn\ntemplate <typename scalar_t>\n__global__ void add_noise_diag_kernel(\n scalar_t* __restrict__ K,\n const scalar_t noise,\n int batch_size,\n int N) {\n \n const int batch_idx = blockIdx.x;\n const int idx = threadIdx.x;\n \n if (batch_idx >= batch_size || idx >= N) return;\n \n // Add noise to diagonal\n K[batch_idx * N * N + idx * N + idx] += noise;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(TILE_SIZE, TILE_SIZE);\n const dim3 blocks((M + TILE_SIZE - 1) / TILE_SIZE, \n (N + TILE_SIZE - 1) / TILE_SIZE, \n batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\nvoid add_noise_diag_cuda(torch::Tensor K, double noise) {\n const int batch_size = K.size(0);\n const int N = K.size(1);\n \n const int threads = min(N, 256);\n const int blocks = batch_size;\n \n AT_DISPATCH_FLOATING_TYPES(K.scalar_type(), \"add_noise_diag_kernel\", ([&] {\n add_noise_diag_kernel<scalar_t><<<blocks, threads>>>(\n K.data_ptr<scalar_t>(),\n static_cast<scalar_t>(noise),\n batch_size, N);\n }));\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\nvoid add_noise_diag_cuda(torch::Tensor K, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v3',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'add_noise_diag_cuda'],\n extra_cuda_cflags=['-O3'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def _rbf_kernel(self, X1, X2):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n return gp_module.rbf_kernel_cuda(X1, X2, l, v)\n \n def forward(self, X_train, Y_train, X_test):\n # Compute RBF kernels\n K_nn = self._rbf_kernel(X_train, X_train)\n gp_module.add_noise_diag_cuda(K_nn, self.noise)\n K_nm = self._rbf_kernel(X_train, X_test)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n v = self.log_variance.exp().item()\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (5 / 5) trials all matched the reference.
Numerical precision: max_abs_err=8.39e-05 mean_abs_err=7.58e-06 max_rel_err=4.03e-02
turn 8 · 1 tool call(s) · 384.9s turn · 18.6s LLM · tokens in=13,033 · out=2,139 · total=15,172 · 8,918 chars (click to expand)
turn 8384.9s turn · 18.6s LLM · 366.3s tools · tokens in=13,033 · out=2,139 · total=15,172
reasoning
Good, this passes correctness. Now let me try to optimize further. The biggest wins would be:
1. Avoid computing full K_mm (just the diagonal)
2. Fuse some operations
But the main bottleneck is still the Cholesky and triangular solve. Let me think about how to optimize the kernel further.
One thing I notice is that I'm computing `K_nm.transpose(-1, -2) @ alpha` separately from the Cholesky solve. Also, `torch.linalg.solve_triangular` followed by squaring and summing could be optimized.
Let me also see if I can avoid the separate kernel launch for adding noise by adding it inline in the RBF kernel computation.
Another optimization: since `K_nm.transpose(-1, -2) @ alpha` is `K_mn @ alpha`, I could compute `K_nm` with the transposed output directly (i.e., compute K_nm^T directly).
Let me also try to optimize the RBF kernel with better memory access patterns and vectorized loads.
assistantLet me try a more optimized version with fused operations and better memory access:
call → compile_kernel
callcompile_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\nconstexpr int TILE_SIZE = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product with unrolled loop\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n}\n\n// Kernel for K_nn with noise diagonal already added\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_knn_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X1_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n const scalar_t noise,\n int batch_size,\n int N,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= N) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X1 + batch_idx * N * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X1[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X1_norm_sq[batch_idx * N + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n \n // Add noise on diagonal\n if (row == col) {\n val += noise;\n }\n \n output[batch_idx * N * N + row * N + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\ntorch::Tensor rbf_kernel_knn_cuda(\n torch::Tensor X1, \n double lengthscale, \n double variance,\n double noise) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n \n auto output = torch::empty({batch_size, N, N}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((N + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_knn_kernel\", ([&] {\n rbf_kernel_knn_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n noise,\n batch_size, N, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\ntorch::Tensor rbf_kernel_knn_cuda(torch::Tensor X1, double lengthscale, double variance, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v4',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'rbf_kernel_knn_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def forward(self, X_train, Y_train, X_test):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n \n # Compute K_nn with noise diagonal fused\n K_nn = gp_module.rbf_kernel_knn_cuda(X_train, l, v, self.noise)\n \n # Compute K_nm (train x test)\n K_nm = gp_module.rbf_kernel_cuda(X_train, X_test, l, v)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean: K_nm.T @ alpha = K_mn @ alpha\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 9 · 1 tool call(s) · 519.7s turn · 16.3s LLM · tokens in=15,009 · out=1,930 · total=16,939 · 8,034 chars (click to expand)
turn 9519.7s turn · 16.3s LLM · 503.4s tools · tokens in=15,009 · out=1,930 · total=16,939
reasoning
The kernel compiles. Let me check correctness first before submitting.
call → run_correctness
callrun_correctness
{
"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\nconstexpr int TILE_SIZE = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product with unrolled loop\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n}\n\n// Kernel for K_nn with noise diagonal already added\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_knn_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X1_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n const scalar_t noise,\n int batch_size,\n int N,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= N) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X1 + batch_idx * N * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X1[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X1_norm_sq[batch_idx * N + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n \n // Add noise on diagonal\n if (row == col) {\n val += noise;\n }\n \n output[batch_idx * N * N + row * N + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\ntorch::Tensor rbf_kernel_knn_cuda(\n torch::Tensor X1, \n double lengthscale, \n double variance,\n double noise) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n \n auto output = torch::empty({batch_size, N, N}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((N + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_knn_kernel\", ([&] {\n rbf_kernel_knn_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n noise,\n batch_size, N, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\ntorch::Tensor rbf_kernel_knn_cuda(torch::Tensor X1, double lengthscale, double variance, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v4',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'rbf_kernel_knn_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def forward(self, X_train, Y_train, X_test):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n \n # Compute K_nn with noise diagonal fused\n K_nn = gp_module.rbf_kernel_knn_cuda(X_train, l, v, self.noise)\n \n # Compute K_nm (train x test)\n K_nm = gp_module.rbf_kernel_cuda(X_train, X_test, l, v)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean: K_nm.T @ alpha = K_mn @ alpha\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (5 / 5) trials all matched the reference.
Numerical precision: max_abs_err=8.01e-05 mean_abs_err=7.43e-06 max_rel_err=4.82e-02
turn 10 · 1 tool call(s) · 17.1s turn · 17.1s LLM · tokens in=17,015 · out=2,102 · total=19,117 · 8,713 chars (click to expand)
turn 1017.1s turn · 17.1s LLM · 0.0s tools · tokens in=17,015 · out=2,102 · total=19,117
reasoning
The kernel is passing correctness tests. I've made several optimizations:
1. Custom CUDA kernel for RBF kernel computation using the squared-norm trick
2. Fused noise diagonal addition into K_nn computation
3. Avoided computing full K_mm matrix since we only need its diagonal (which is just variance)
4. Using fast math compiler flag
The error is within tolerance (max_abs_err=8.01e-05, rtol/atol=1e-4 for fp32).
I should submit this now since:
1. Correctness passes
2. I've made meaningful optimizations to the RBF kernel computation
3. The remaining operations (Cholesky, solve_triangular, matmul) are already highly optimized in PyTorch's cuSOLVER/cuBLAS
Let me run static check first to make sure there are no issues.
assistantLet me run a static check before submitting:
call → static_check
callstatic_check
{
"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\nconstexpr int TILE_SIZE = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product with unrolled loop\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n}\n\n// Kernel for K_nn with noise diagonal already added\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_knn_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X1_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n const scalar_t noise,\n int batch_size,\n int N,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= N) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X1 + batch_idx * N * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X1[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X1_norm_sq[batch_idx * N + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n \n // Add noise on diagonal\n if (row == col) {\n val += noise;\n }\n \n output[batch_idx * N * N + row * N + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\ntorch::Tensor rbf_kernel_knn_cuda(\n torch::Tensor X1, \n double lengthscale, \n double variance,\n double noise) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n \n auto output = torch::empty({batch_size, N, N}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((N + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_knn_kernel\", ([&] {\n rbf_kernel_knn_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n noise,\n batch_size, N, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\ntorch::Tensor rbf_kernel_knn_cuda(torch::Tensor X1, double lengthscale, double variance, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v4',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'rbf_kernel_knn_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def forward(self, X_train, Y_train, X_test):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n \n # Compute K_nn with noise diagonal fused\n K_nn = gp_module.rbf_kernel_knn_cuda(X_train, l, v, self.noise)\n \n # Compute K_nm (train x test)\n K_nm = gp_module.rbf_kernel_cuda(X_train, X_test, l, v)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean: K_nm.T @ alpha = K_mn @ alpha\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← static_check (ok)
okstatic_check
static_check PASSED: no violations or warnings detected.
turn 11 · FINAL · 1 tool call(s) · 679.8s turn · 15.1s LLM · tokens in=19,043 · out=1,931 · total=20,974 · 8,299 chars (click to expand)
turn 11679.8s turn · 15.1s LLM · 664.7s tools · tokens in=19,043 · out=1,931 · total=20,974 · FINAL
reasoning
The kernel passes static check and correctness tests. I should submit it now.
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\nconstexpr int TILE_SIZE = 32;\n\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X2,\n const scalar_t* __restrict__ X1_norm_sq,\n const scalar_t* __restrict__ X2_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n int batch_size,\n int N,\n int M,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= M) return;\n \n // Compute dot product with unrolled loop\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X2_norm_sq[batch_idx * M + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n output[batch_idx * N * M + row * M + col] = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n}\n\n// Kernel for K_nn with noise diagonal already added\ntemplate <typename scalar_t>\n__global__ void rbf_kernel_knn_kernel(\n const scalar_t* __restrict__ X1,\n const scalar_t* __restrict__ X1_norm_sq,\n scalar_t* __restrict__ output,\n const scalar_t lengthscale_sq,\n const scalar_t variance,\n const scalar_t noise,\n int batch_size,\n int N,\n int D) {\n \n const int batch_idx = blockIdx.z;\n const int row = blockIdx.y * blockDim.y + threadIdx.y;\n const int col = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (batch_idx >= batch_size || row >= N || col >= N) return;\n \n // Compute dot product\n scalar_t dot = 0;\n const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;\n const scalar_t* x2_ptr = X1 + batch_idx * N * D + col * D;\n \n for (int d = 0; d < D; d++) {\n dot += x1_ptr[d] * x2_ptr[d];\n }\n \n // dist_sq = ||X1[row]||^2 + ||X1[col]||^2 - 2 * dot\n scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] + \n X1_norm_sq[batch_idx * N + col] - 2.0f * dot;\n \n // RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)\n scalar_t val = variance * expf(-0.5f * dist_sq / lengthscale_sq);\n \n // Add noise on diagonal\n if (row == col) {\n val += noise;\n }\n \n output[batch_idx * N * N + row * N + col] = val;\n}\n\ntorch::Tensor rbf_kernel_cuda(\n torch::Tensor X1, \n torch::Tensor X2, \n double lengthscale, \n double variance) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int M = X2.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)\n \n auto output = torch::empty({batch_size, N, M}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_kernel\", ([&] {\n rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X2.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n X2_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n batch_size, N, M, D);\n }));\n \n return output;\n}\n\ntorch::Tensor rbf_kernel_knn_cuda(\n torch::Tensor X1, \n double lengthscale, \n double variance,\n double noise) {\n \n const int batch_size = X1.size(0);\n const int N = X1.size(1);\n const int D = X1.size(2);\n \n // Compute squared norms\n auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)\n \n auto output = torch::empty({batch_size, N, N}, X1.options());\n \n const dim3 threads(16, 16);\n const dim3 blocks((N + 15) / 16, (N + 15) / 16, batch_size);\n \n AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), \"rbf_kernel_knn_kernel\", ([&] {\n rbf_kernel_knn_kernel<scalar_t><<<blocks, threads>>>(\n X1.data_ptr<scalar_t>(),\n X1_norm_sq.data_ptr<scalar_t>(),\n output.data_ptr<scalar_t>(),\n lengthscale * lengthscale,\n variance,\n noise,\n batch_size, N, D);\n }));\n \n return output;\n}\n\"\"\"\n\ncpp_source = \"\"\"\ntorch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);\ntorch::Tensor rbf_kernel_knn_cuda(torch::Tensor X1, double lengthscale, double variance, double noise);\n\"\"\"\n\ngp_module = load_inline(\n name='gp_kernel_cuda_v4',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rbf_kernel_cuda', 'rbf_kernel_knn_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):\n super().__init__()\n self.noise = noise\n self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())\n self.log_variance = nn.Parameter(torch.tensor(variance).log())\n \n def forward(self, X_train, Y_train, X_test):\n l = self.log_lengthscale.exp().item()\n v = self.log_variance.exp().item()\n \n # Compute K_nn with noise diagonal fused\n K_nn = gp_module.rbf_kernel_knn_cuda(X_train, l, v, self.noise)\n \n # Compute K_nm (train x test)\n K_nm = gp_module.rbf_kernel_cuda(X_train, X_test, l, v)\n \n # K_mm diagonal is just variance (dist_sq = 0 for self-pairs)\n batch_size, M = X_test.shape[0], X_test.shape[1]\n K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)\n \n # Cholesky decomposition and solve\n L = torch.linalg.cholesky(K_nn)\n alpha = torch.cholesky_solve(Y_train, L)\n \n # Predictive mean: K_nm.T @ alpha = K_mn @ alpha\n mean = K_nm.transpose(-1, -2) @ alpha\n \n # Predictive variance\n v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)\n var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)\n var = var.clamp(min=1e-6)\n \n return mean, var"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 10.10 μs
Runtime stats: mean=10.10μs median=?μs std=0.00μs
Numerical precision: max_abs_err=8.01e-05 mean_abs_err=7.43e-06 max_rel_err=4.82e-02
Memory: 34.66 MB peak (ref: 37.03 MB, ratio: 0.936x)
Kernel launches: 88 (ref: 140, fusion ratio: 1.5909)
Energy: 3542.7 mJ/run (ref: 707.76 mJ, ratio: 0.1998x)
SOL score: 0.054
final submitted kernel (207 lines, 6,639 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>
constexpr int TILE_SIZE = 32;
template <typename scalar_t>
__global__ void rbf_kernel_kernel(
const scalar_t* __restrict__ X1,
const scalar_t* __restrict__ X2,
const scalar_t* __restrict__ X1_norm_sq,
const scalar_t* __restrict__ X2_norm_sq,
scalar_t* __restrict__ output,
const scalar_t lengthscale_sq,
const scalar_t variance,
int batch_size,
int N,
int M,
int D) {
const int batch_idx = blockIdx.z;
const int row = blockIdx.y * blockDim.y + threadIdx.y;
const int col = blockIdx.x * blockDim.x + threadIdx.x;
if (batch_idx >= batch_size || row >= N || col >= M) return;
// Compute dot product with unrolled loop
scalar_t dot = 0;
const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;
const scalar_t* x2_ptr = X2 + batch_idx * M * D + col * D;
for (int d = 0; d < D; d++) {
dot += x1_ptr[d] * x2_ptr[d];
}
// dist_sq = ||X1[row]||^2 + ||X2[col]||^2 - 2 * dot
scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] +
X2_norm_sq[batch_idx * M + col] - 2.0f * dot;
// RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)
output[batch_idx * N * M + row * M + col] = variance * expf(-0.5f * dist_sq / lengthscale_sq);
}
// Kernel for K_nn with noise diagonal already added
template <typename scalar_t>
__global__ void rbf_kernel_knn_kernel(
const scalar_t* __restrict__ X1,
const scalar_t* __restrict__ X1_norm_sq,
scalar_t* __restrict__ output,
const scalar_t lengthscale_sq,
const scalar_t variance,
const scalar_t noise,
int batch_size,
int N,
int D) {
const int batch_idx = blockIdx.z;
const int row = blockIdx.y * blockDim.y + threadIdx.y;
const int col = blockIdx.x * blockDim.x + threadIdx.x;
if (batch_idx >= batch_size || row >= N || col >= N) return;
// Compute dot product
scalar_t dot = 0;
const scalar_t* x1_ptr = X1 + batch_idx * N * D + row * D;
const scalar_t* x2_ptr = X1 + batch_idx * N * D + col * D;
for (int d = 0; d < D; d++) {
dot += x1_ptr[d] * x2_ptr[d];
}
// dist_sq = ||X1[row]||^2 + ||X1[col]||^2 - 2 * dot
scalar_t dist_sq = X1_norm_sq[batch_idx * N + row] +
X1_norm_sq[batch_idx * N + col] - 2.0f * dot;
// RBF kernel: variance * exp(-0.5 * dist_sq / lengthscale_sq)
scalar_t val = variance * expf(-0.5f * dist_sq / lengthscale_sq);
// Add noise on diagonal
if (row == col) {
val += noise;
}
output[batch_idx * N * N + row * N + col] = val;
}
torch::Tensor rbf_kernel_cuda(
torch::Tensor X1,
torch::Tensor X2,
double lengthscale,
double variance) {
const int batch_size = X1.size(0);
const int N = X1.size(1);
const int M = X2.size(1);
const int D = X1.size(2);
// Compute squared norms
auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)
auto X2_norm_sq = (X2 * X2).sum(-1); // (B, M)
auto output = torch::empty({batch_size, N, M}, X1.options());
const dim3 threads(16, 16);
const dim3 blocks((M + 15) / 16, (N + 15) / 16, batch_size);
AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), "rbf_kernel_kernel", ([&] {
rbf_kernel_kernel<scalar_t><<<blocks, threads>>>(
X1.data_ptr<scalar_t>(),
X2.data_ptr<scalar_t>(),
X1_norm_sq.data_ptr<scalar_t>(),
X2_norm_sq.data_ptr<scalar_t>(),
output.data_ptr<scalar_t>(),
lengthscale * lengthscale,
variance,
batch_size, N, M, D);
}));
return output;
}
torch::Tensor rbf_kernel_knn_cuda(
torch::Tensor X1,
double lengthscale,
double variance,
double noise) {
const int batch_size = X1.size(0);
const int N = X1.size(1);
const int D = X1.size(2);
// Compute squared norms
auto X1_norm_sq = (X1 * X1).sum(-1); // (B, N)
auto output = torch::empty({batch_size, N, N}, X1.options());
const dim3 threads(16, 16);
const dim3 blocks((N + 15) / 16, (N + 15) / 16, batch_size);
AT_DISPATCH_FLOATING_TYPES(X1.scalar_type(), "rbf_kernel_knn_kernel", ([&] {
rbf_kernel_knn_kernel<scalar_t><<<blocks, threads>>>(
X1.data_ptr<scalar_t>(),
X1_norm_sq.data_ptr<scalar_t>(),
output.data_ptr<scalar_t>(),
lengthscale * lengthscale,
variance,
noise,
batch_size, N, D);
}));
return output;
}
"""
cpp_source = """
torch::Tensor rbf_kernel_cuda(torch::Tensor X1, torch::Tensor X2, double lengthscale, double variance);
torch::Tensor rbf_kernel_knn_cuda(torch::Tensor X1, double lengthscale, double variance, double noise);
"""
gp_module = load_inline(
name='gp_kernel_cuda_v4',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['rbf_kernel_cuda', 'rbf_kernel_knn_cuda'],
extra_cuda_cflags=['-O3', '--use_fast_math'],
)
class ModelNew(nn.Module):
def __init__(self, input_dim, noise=0.01, lengthscale=1.0, variance=1.0):
super().__init__()
self.noise = noise
self.log_lengthscale = nn.Parameter(torch.tensor(lengthscale).log())
self.log_variance = nn.Parameter(torch.tensor(variance).log())
def forward(self, X_train, Y_train, X_test):
l = self.log_lengthscale.exp().item()
v = self.log_variance.exp().item()
# Compute K_nn with noise diagonal fused
K_nn = gp_module.rbf_kernel_knn_cuda(X_train, l, v, self.noise)
# Compute K_nm (train x test)
K_nm = gp_module.rbf_kernel_cuda(X_train, X_test, l, v)
# K_mm diagonal is just variance (dist_sq = 0 for self-pairs)
batch_size, M = X_test.shape[0], X_test.shape[1]
K_mm_diag = torch.full((batch_size, M), v, dtype=X_test.dtype, device=X_test.device)
# Cholesky decomposition and solve
L = torch.linalg.cholesky(K_nn)
alpha = torch.cholesky_solve(Y_train, L)
# Predictive mean: K_nm.T @ alpha = K_mn @ alpha
mean = K_nm.transpose(-1, -2) @ alpha
# Predictive variance
v_matrix = torch.linalg.solve_triangular(L, K_nm, upper=False)
var = K_mm_diag - (v_matrix ** 2).sum(dim=-2)
var = var.clamp(min=1e-6)
return mean, var