FW-GLM-5-1 · popcorn · pop_l123_default_gpt
correct 9_BayesianLinearRegression.py
model
FW-GLM-5-1
turns / max
7 / 12
tool calls
7
tokens (total)
94821
wall clock
1743.3s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
0.14 μs
ref runtime
0.40 μs
speedup
2.84x
started
2026-05-11T10:47:21Z
timingkernelbench.timing
kernel runtime
0.14 μs
ref runtime
0.40 μs
speedup
2.84x
kernel mean / median / std
0.14 / — / 5.57e-04 μs
ref mean / median / std
0.40 / — / 0.05 μs
numerical precisioneval_kernel_against_ref
max abs error
5.96e-07
mean abs error
4.00e-08
max rel error
0.00
mean rel error
1.01e-06
gpu memoryextended_metrics.measure_memory
kernel peak
33.37 MB
ref peak
33.51 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
7
ref launches
141
fusion ratio (ref / kernel)
20.14x
total cuda time
51 μs
energyNVML (pynvml)
kernel mJ / run
243.10
ref mJ / run
243.28
ratio (ref / kernel)
1.00x
avg power
2303.37 W
ref avg power
610.04 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.011
dram util %
1.1%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
35.23 GB/s
dram util %
1.1%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
58.1%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
440.6%
ld sectors / req
64.53
st sectors / req
—
regs / thread
294
smem / block
12160 B
block size
1280
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
10718.1%
short_scoreboard
487.9%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn


class Model(nn.Module):
    """
    Bayesian linear regression with a conjugate Gaussian prior.
    Given features X and targets y, computes the posterior distribution
    over weights: p(w | X, y) = N(mu_post, Sigma_post) in closed form
    via the matrix inversion lemma / Cholesky decomposition.
    """

    def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):
        super().__init__()
        self.input_dim = input_dim
        self.prior_precision = prior_precision
        self.noise_precision = noise_precision

    def forward(
        self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor
    ) -> tuple:
        """
        Args:
            X:      (B, N, D) – training features
            y:      (B, N, 1) – training targets
            X_test: (B, M, D) – test features
        Returns:
            (pred_mean, pred_var):
                pred_mean: (B, M, 1)
                pred_var:  (B, M)
        """
        D = X.shape[-1]
        eye = torch.eye(D, device=X.device, dtype=X.dtype).unsqueeze(0)

        # Posterior precision: Lambda_post = alpha * I + beta * X^T X
        Lambda_post = self.prior_precision * eye + self.noise_precision * (X.transpose(-1, -2) @ X)
        L = torch.linalg.cholesky(Lambda_post)

        # Posterior mean: mu_post = Lambda_post^{-1} * beta * X^T y
        rhs = self.noise_precision * (X.transpose(-1, -2) @ y)
        mu_post = torch.cholesky_solve(rhs, L)

        # Predictive distribution
        pred_mean = X_test @ mu_post
        Sigma_post = torch.cholesky_inverse(L)
        pred_var = (X_test @ Sigma_post * X_test).sum(-1) + 1.0 / self.noise_precision

        return pred_mean, pred_var


input_dim = 16
num_train = 256
num_test = 64
batch_size = 16


def get_inputs():
    p = popcorn_pri
    X = torch.randn(p.jitter_int(batch_size), p.jitter_int(num_train), input_dim)
    y = 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, y, X_test]


def get_init_inputs():
    return [input_dim]
turn 04.2s turn · 4.2s LLM · 0.0s tools · tokens in=1,784 · out=212 · total=1,996
reasoning
Let me analyze this task. I need to write a custom CUDA kernel to replace a Bayesian linear regression model that: 1. Computes posterior precision: `Lambda_post = alpha * I + beta * X^T X` 2. Computes Cholesky decomposition of Lambda_post 3. Computes posterior mean: `mu_post = Lambda_post^{-1} * beta * X^T y` 4. Computes predictive mean: `pred_mean = X_test @ mu_post` 5. Computes predictive variance: `pred_var = (X_test @ Sigma_post * X_test).sum(-1) + 1.0 / noise_precision` The dimensions are: - D = input_dim = 16 - N = num_train = 256 - M = num_test = 64 - B = batch_size = 16 Let me first check the GPU specs to understand what hardware I'm working with.
assistantI'll start by understanding the GPU specs and then develop a custom CUDA kernel for this Bayesian linear regression model.
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) · 321.7s turn · 51.5s LLM · tokens in=2,054 · out=4,101 · total=6,155 · 15,503 chars (click to expand)
turn 1321.7s turn · 51.5s LLM · 270.2s tools · tokens in=2,054 · out=4,101 · total=6,155
reasoning
Now let me analyze the computational bottlenecks: 1. X.transpose(-1, -2) @ X: This is a batched matmul producing (B, D, D) matrices 2. Lambda_post = prior_precision * I + noise_precision * XtX 3. Cholesky decomposition of Lambda_post (B, D, D) 4. XtY = X.transpose(-1, -2) @ y: (B, D, 1) 5. rhs = noise_precision * XtY: (B, D, 1) 6. mu_post = cholesky_solve(rhs, L): (B, D, 1) 7. pred_mean = X_test @ mu_post: (B, M, 1) 8. Sigma_post = cholesky_inverse(L): (B, D, D) 9. pred_var = sum(X_test @ Sigma_post * X_test, dim=-1) + 1/noise_precision: (B, M) The key dimensions: - D = 16 (input_dim) - N = 256 (num_train) - M = 64 (num_test) - B = 16 (batch_size) D is very small (16), which means: - Lambda_post is (16, 16) per batch - Small matrices are inefficient on GPU with standard batched operations For small matrices like (16, 16), we should: 1. Pack multiple batches into shared memory 2. Use custom kernels for matrix operations 3. Avoid overhead of launching many small kernels Let me write an optimized kernel that fuses operations and uses shared memory efficiently.
assistantNow let me analyze the computation and write an optimized kernel. The key insight is that D=16 is small, so we can efficiently process multiple batches together using shared memory.
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\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_X[D][256]; // Store up to 256 rows of X\n __shared__ float s_XtX[D][D];\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread processes multiple elements of XtX\n // XtX = X^T * X\n int rows_per_iter = min(256, N);\n \n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Load X into shared memory\n for (int i = tx; i < valid_rows * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n s_X[col][row] = Xb[(base_row + row) * D + col];\n }\n __syncthreads();\n \n // Compute partial XtX\n for (int i = tx; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += s_X[row][k] * s_X[col][k];\n }\n atomicAdd(&s_XtX[row][col], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int i = tx; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = noise_prec * s_XtX[row][col];\n if (row == col) {\n val += prior_prec;\n }\n Lb[row * D + col] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n float* __restrict__ A,\n float* __restrict__ L,\n int B\n) {\n int b = blockIdx.x;\n float* Ab = A + b * D * D;\n float* Lb = L + b * D * D;\n \n __shared__ float s_A[D][D];\n __shared__ float s_L[D][D];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_X[D][256];\n __shared__ float s_y[256];\n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D];\n __shared__ float s_rhs[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n __syncthreads();\n \n // Compute XtY = X^T * y\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Load X and y\n for (int i = tx; i < valid_rows; i += blockDim.x) {\n s_y[i] = yb[base_row + i];\n }\n for (int i = tx; i < valid_rows * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n s_X[col][row] = Xb[(base_row + row) * D + col];\n }\n __syncthreads();\n \n // Compute partial XtY\n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += s_X[tx][k] * s_y[k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // rhs = noise_prec * XtY\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs using forward and backward substitution\n // First solve L * z = rhs (forward substitution)\n __shared__ float s_z[D];\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Then solve L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * mb[j]; // L[j][i] = L^T[i][j]\n }\n mb[i] = sum / s_L[i][i];\n }\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n __shared__ float s_Xt[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n // Load one row of X_test\n for (int j = 0; j < D; j++) {\n s_Xt[j] = Xtb[i * D + j];\n }\n \n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += s_Xt[j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D];\n __shared__ float s_inv[D][D]; // Cholesky inverse\n \n // Load L and compute L^{-1}\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_inv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution on identity\n // L * L_inv = I\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n if (i == col) {\n float sum = 1.0f;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_inv[j][col];\n }\n s_inv[i][col] = sum / s_L[i][i];\n } else if (i > col) {\n float sum = 0.0f;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_inv[j][col];\n }\n s_inv[i][col] = sum / s_L[i][i];\n }\n }\n }\n }\n __syncthreads();\n \n // Compute Sigma = L^{-T} * L^{-1}\n // pred_var[i] = X_test[i] @ Sigma @ X_test[i]^T\n // = ||L^{-1} @ X_test[i]^T||^2\n __shared__ float s_LinvX[D];\n \n for (int i = tx; i < M; i += blockDim.x) {\n // Load X_test[i]\n for (int j = 0; j < D; j++) {\n s_LinvX[j] = Xtb[i * D + j];\n }\n \n // Compute L^{-1} @ x (forward substitution style using precomputed L_inv)\n // We need L^{-1} @ x, which is stored column-wise in s_inv\n // Actually, s_inv[col][row] represents element at (row, col) of L_inv\n // L_inv @ x = sum over rows\n float result[D];\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = row; col < D; col++) { // L_inv is lower triangular\n sum += s_inv[col][row] * s_LinvX[col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D, 1}, options);\n auto pred_mean = torch::empty({B, M, 1}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n return std::make_tuple(pred_mean, pred_var);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 2 · 1 tool call(s) · 276.6s turn · 32.0s LLM · tokens in=5,831 · out=3,752 · total=9,583 · 14,588 chars (click to expand)
turn 2276.6s turn · 32.0s LLM · 244.5s tools · tokens in=5,831 · out=3,752 · total=9,583
reasoning
Great, it compiled! Now let's run the correctness check.
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\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_X[D][256]; // Store up to 256 rows of X\n __shared__ float s_XtX[D][D];\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread processes multiple elements of XtX\n // XtX = X^T * X\n int rows_per_iter = min(256, N);\n \n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Load X into shared memory\n for (int i = tx; i < valid_rows * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n s_X[col][row] = Xb[(base_row + row) * D + col];\n }\n __syncthreads();\n \n // Compute partial XtX\n for (int i = tx; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += s_X[row][k] * s_X[col][k];\n }\n atomicAdd(&s_XtX[row][col], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int i = tx; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = noise_prec * s_XtX[row][col];\n if (row == col) {\n val += prior_prec;\n }\n Lb[row * D + col] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n float* __restrict__ A,\n float* __restrict__ L,\n int B\n) {\n int b = blockIdx.x;\n float* Ab = A + b * D * D;\n float* Lb = L + b * D * D;\n \n __shared__ float s_A[D][D];\n __shared__ float s_L[D][D];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_X[D][256];\n __shared__ float s_y[256];\n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D];\n __shared__ float s_rhs[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n __syncthreads();\n \n // Compute XtY = X^T * y\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Load X and y\n for (int i = tx; i < valid_rows; i += blockDim.x) {\n s_y[i] = yb[base_row + i];\n }\n for (int i = tx; i < valid_rows * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n s_X[col][row] = Xb[(base_row + row) * D + col];\n }\n __syncthreads();\n \n // Compute partial XtY\n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += s_X[tx][k] * s_y[k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // rhs = noise_prec * XtY\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs using forward and backward substitution\n // First solve L * z = rhs (forward substitution)\n __shared__ float s_z[D];\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Then solve L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * mb[j]; // L[j][i] = L^T[i][j]\n }\n mb[i] = sum / s_L[i][i];\n }\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n __shared__ float s_Xt[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n // Load one row of X_test\n for (int j = 0; j < D; j++) {\n s_Xt[j] = Xtb[i * D + j];\n }\n \n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += s_Xt[j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D];\n __shared__ float s_inv[D][D]; // Cholesky inverse\n \n // Load L and compute L^{-1}\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_inv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution on identity\n // L * L_inv = I\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n if (i == col) {\n float sum = 1.0f;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_inv[j][col];\n }\n s_inv[i][col] = sum / s_L[i][i];\n } else if (i > col) {\n float sum = 0.0f;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_inv[j][col];\n }\n s_inv[i][col] = sum / s_L[i][i];\n }\n }\n }\n }\n __syncthreads();\n \n // Compute Sigma = L^{-T} * L^{-1}\n // pred_var[i] = X_test[i] @ Sigma @ X_test[i]^T\n // = ||L^{-1} @ X_test[i]^T||^2\n __shared__ float s_LinvX[D];\n \n for (int i = tx; i < M; i += blockDim.x) {\n // Load X_test[i]\n for (int j = 0; j < D; j++) {\n s_LinvX[j] = Xtb[i * D + j];\n }\n \n // Compute L^{-1} @ x (forward substitution style using precomputed L_inv)\n // We need L^{-1} @ x, which is stored column-wise in s_inv\n // Actually, s_inv[col][row] represents element at (row, col) of L_inv\n // L_inv @ x = sum over rows\n float result[D];\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = row; col < D; col++) { // L_inv is lower triangular\n sum += s_inv[col][row] * s_LinvX[col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D, 1}, options);\n auto pred_mean = torch::empty({B, M, 1}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n return std::make_tuple(pred_mean, pred_var);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← run_correctness (fail)
failrun_correctness run_correctness FAILED: (0 / 5) trials did not all match. correctness_issue: Output mismatch max_difference: ['0.030214', '0.025511', '0.036983', '0.025696', '0.047474'] avg_difference: ['0.004298', '0.004011', '0.004189', '0.004113', '0.004357']
turn 3 · 1 tool call(s) · 482.2s turn · 59.9s LLM · tokens in=9,680 · out=6,649 · total=16,329 · 22,147 chars (click to expand)
turn 3482.2s turn · 59.9s LLM · 422.4s tools · tokens in=9,680 · out=6,649 · total=16,329
reasoning
There's a correctness issue. Let me think about what might be wrong: 1. The Cholesky decomposition expects a symmetric positive definite matrix but Lambda_post is symmetric by construction (X^T @ X is symmetric). 2. The cholesky_solve and forward/backward substitution should be correct, but let me double-check. 3. The predictive variance computation: I'm trying to compute ||L^{-1} @ x||^2, but my implementation might have issues. Let me rewrite this more carefully, using a cleaner approach. The key insight is: - pred_var = x @ Sigma @ x^T + 1/noise_precision - where Sigma = (L @ L^T)^{-1} = L^{-T} @ L^{-1} - So pred_var = ||L^{-1} @ x||^2 + 1/noise_precision But computing L^{-1} is tricky. Let me instead use cholesky_solve to solve L @ L^T @ z = x, then pred_var = z @ x^T... wait, that's not quite right either. Actually, let me think again: - Sigma = (Lambda_post)^{-1} = (L @ L^T)^{-1} - pred_var = x @ Sigma @ x^T = x @ (L @ L^T)^{-1} @ x^T - Let y = L^{-1} @ x^T, then y^T @ y = ||y||^2 = ||L^{-1} @ x^T||^2 = pred_var So I need to solve L @ y = x^T (lower triangular), then pred_var = ||y||^2. Let me also check my L^{-1} computation. I was computing L @ L_inv = I, which is forward substitution. Actually, I think the issue is in how I'm using s_inv. Let me trace through: - s_inv[row][col] stores L_inv at (row, col) - L is lower triangular, so L_inv should also be lower triangular - L @ L_inv = I means L[i,:] @ L_inv[:,j] = I[i,j] For column j of L_inv: - L[0,0] * L_inv[0,j] = I[0,j], so L_inv[0,j] = I[0,j] / L[0,0] - L[1,0] * L_inv[0,j] + L[1,1] * L_inv[1,j] = I[1,j] - etc. If j=0: I[0,0]=1, I[1,0]=0, etc. So L_inv[0,0] = 1/L[0,0] L_inv[1,0] = -L[1,0]/L[0,0] * 1/L[1,1] = -L[1,0]/(L[0,0]*L[1,1]) Wait, let me redo this more carefully. For L_inv such that L @ L_inv = I: For each column j, we have L @ (column j of L_inv) = e_j (j-th column of I) So for j=0: L[0,0] * L_inv[0,0] = 1 => L_inv[0,0] = 1/L[0,0] L[1,0] * L_inv[0,0] + L[1,1] * L_inv[1,0] = 0 => L_inv[1,0] = -L[1,0] * L_inv[0,0] / L[1,1] For j=1: L[0,0] * L_inv[0,1] = 0 => L_inv[0,1] = 0 L[1,0] * L_inv[0,1] + L[1,1] * L_inv[1,1] = 1 => L_inv[1,1] = 1/L[1,1] L[2,0] * L_inv[0,1] + L[2,1] * L_inv[1,1] + L[2,2] * L_inv[2,1] = 0 ... So L_inv is also lower triangular, and only L_inv[j,j] on the diagonal depends on 1/L[j,j], while off-diagonal elements are computed from forward substitution. Looking at my code: ```cpp for (int col = 0; col < D; col++) { for (int i = 0; i < D; i++) { if (i == col) { float sum = 1.0f; // This should be I[i,col] = 1 for i==col for (int j = 0; j < i; j++) { sum -= s_L[i][j] * s_inv[j][col]; } s_inv[i][col] = sum / s_L[i][i]; } else if (i > col) { float sum = 0.0f; // This should be I[i,col] = 0 for i>col for (int j = 0; j < i; j++) { sum -= s_L[i][j] * s_inv[j][col]; } s_inv[i][col] = sum / s_L[i][i]; } // else: i < col, s_inv[i][col] stays 0 (upper triangular part) } } ``` Wait, this looks wrong. The forward substitution should be: ``` s_inv[i][col] = (I[i,col] - sum_{j < i} L[i,j] * L_inv[j,col]) / L[i,i] ``` For i == col: I[i,col] = I[i,i] = 1 For i > col: I[i,col] = 0 For i < col: We're in the upper triangle, but L_inv should be lower triangular, so this stays 0. Actually wait, I think there's a bug. For column col: - When i == col, the RHS is I[i,col] = I[col,col] = 1 - When i > col, the RHS is I[i,col] = 0 - When i < col, L_inv[i,col] = 0 because L_inv is lower triangular But my loop has i < D, so for i < col, we skip setting anything, which keeps s_inv[i][col] = 0 from initialization. Actually, the issue might be that L_inv is NOT lower triangular! Let me reconsider. If L is lower triangular, then L^{-1} is also lower triangular. This is a standard result. So L_inv should be lower triangular. Let me trace through for D=2: L = [[a, 0], [b, c]] L_inv = [[x, y], [z, w]] L @ L_inv = [[a*x, a*y], [b*x + c*z, b*y + c*w]] = I = [[1, 0], [0, 1]] So: a*x = 1 => x = 1/a a*y = 0 => y = 0 b*x + c*z = 0 => z = -b*x/c = -b/(a*c) b*y + c*w = 1 => w = 1/c So L_inv = [[1/a, 0], [-b/(ac), 1/c]], which is indeed lower triangular. My code should compute this correctly... let me check again. For col=0: - i=0, col=0: sum = 1.0, no j < 0, so s_inv[0][0] = 1/L[0][0] = 1/a ✓ - i=1, col=0: i > col, sum = 0, j=0: sum -= L[1][0] * s_inv[0][0] = -b * 1/a = -b/a s_inv[1][0] = sum / L[1][1] = (-b/a) / c = -b/(ac) ✓ For col=1: - i=0, col=1: i < col, skip => s_inv[0][1] = 0 ✓ - i=1, col=1: i == col, sum = 1.0, j=0: sum -= L[1][0] * s_inv[0][1] = -b * 0 = 0 s_inv[1][1] = 1 / L[1][1] = 1/c ✓ So my L_inv computation looks correct. Now for the variance computation: pred_var = ||L^{-1} @ x||^2 Where x is a row of X_test. Let me compute L_inv @ x^T: L_inv[i,:] @ x^T = sum_j L_inv[i,j] * x[j] = sum_{j=0}^{i} L_inv[i,j] * x[j] (since L_inv is lower triangular) In my code: ```cpp for (int row = 0; row < D; row++) { float sum = 0.0f; for (int col = row; col < D; col++) { // L_inv is lower triangular sum += s_inv[col][row] * s_LinvX[col]; } result[row] = sum; } ``` Wait, this is wrong! I'm accessing s_inv[col][row], but the loop should be: - For row i of L_inv, we need sum over col j from 0 to i: L_inv[i,j] * x[j] - But I'm looping col from row to D, which gives elements L_inv[col,row] = L_inv^T[row,col] Actually, s_inv[col][row] = L_inv[col][row], not L_inv^T[row,col]. Let me think again. s_inv[row][col] = L_inv[row][col]. So for L_inv @ x: result[i] = sum_j L_inv[i,j] * x[j] Since L_inv is lower triangular, L_inv[i,j] = 0 for j > i. So result[i] = sum_{j=0}^{i} L_inv[i,j] * x[j] = sum_{j=0}^{i} s_inv[i][j] * x[j] My code does: ```cpp for (int col = row; col < D; col++) { // L_inv is lower triangular sum += s_inv[col][row] * s_LinvX[col]; } ``` This is summing s_inv[col][row] * x[col] for col from row to D. But s_inv[col][row] is L_inv[col][row], which is accessing the upper triangular part (col >= row means we're above the diagonal). This is completely wrong! I should be accessing: ```cpp for (int col = 0; col <= row; col++) { // Lower triangular sum += s_inv[row][col] * s_LinvX[col]; } ``` Let me fix this. Actually wait, there's another issue. I need to be careful about the matrix layout. Let me think about what I'm storing: s_inv[row][col] stores the element at row 'row' and column 'col' of L_inv. L_inv is lower triangular, so L_inv[row][col] = 0 for col > row. So L_inv @ x gives: result[i] = sum_{j=0}^{D-1} L_inv[i][j] * x[j] = sum_{j=0}^{i} L_inv[i][j] * x[j] In terms of s_inv: result[i] = sum_{j=0}^{i} s_inv[i][j] * x[j] My buggy code accesses s_inv[col][row] which is L_inv[col][row]. For col >= row, this is the upper triangle or diagonal, not lower triangle. The fix is: ```cpp for (int j = 0; j <= row; j++) { sum += s_inv[row][j] * s_LinvX[j]; } ``` Let me also check other parts of the code for similar issues. Also, I notice that I'm computing cholesky_solve using forward and backward substitution, which should be correct. Let me verify: Given L @ L^T @ mu = rhs: 1. Solve L @ z = rhs (forward substitution) 2. Solve L^T @ mu = z (backward substitution) For step 1: z[i] = (rhs[i] - sum_{j<i} L[i,j] * z[j]) / L[i,i] For step 2: mu[i] = (z[i] - sum_{j>i} L[j,i] * mu[j]) / L[i,i] // L^T[i,j] = L[j,i] Actually, I need to double-check the backward substitution. For L^T @ mu = z where L^T is upper triangular: L^T[i,:] @ mu = z[i] sum_j L^T[i,j] * mu[j] = z[i] Since L^T is upper triangular, L^T[i,j] = 0 for j < i. So sum_{j=i}^{D-1} L^T[i,j] * mu[j] = z[i] L^T[i,i] * mu[i] + sum_{j>i} L^T[i,j] * mu[j] = z[i] L^T[i,i] = L[i,i] L^T[i,j] = L[j,i] for j > i So: mu[i] = (z[i] - sum_{j>i} L[j,i] * mu[j]) / L[i,i] This should be done from i = D-1 down to 0. In my code: ```cpp for (int i = D - 1; i >= 0; i--) { float sum = s_z[i]; for (int j = i + 1; j < D; j++) { sum -= s_L[j][i] * mb[j]; // L[j][i] = L^T[i][j] } mb[i] = sum / s_L[i][i]; } ``` This looks correct. Let me fix the variance computation and also double-check other parts. Actually, I think there might also be issues with my choice of using atomics for accumulation. The atomics can cause race conditions and aren't necessary since each thread processes different elements. Let me rewrite the code more carefully.
assistantThe correctness check failed. Let me analyze and fix the issues. The main problem is likely in the variance computation where I'm incorrectly indexing the L inverse matrix.
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\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_XtX[D][D + 1]; // +1 to avoid bank conflicts\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread accumulates part of XtX\n // Process rows of X in chunks\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Each thread computes one or more entries of XtX\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + i] * Xb[(base_row + k) * D + j];\n }\n atomicAdd(&s_XtX[i][j], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float val = noise_prec * s_XtX[i][j];\n if (i == j) {\n val += prior_prec;\n }\n Lb[i * D + j] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n const float* __restrict__ A,\n float* __restrict__ L_out,\n int B\n) {\n int b = blockIdx.x;\n const float* Ab = A + b * D * D;\n float* Lb = L_out + b * D * D;\n \n __shared__ float s_A[D][D + 1];\n __shared__ float s_L[D][D + 1];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L (copy entire matrix including upper triangle zeros)\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D + 1];\n __shared__ float s_rhs[D];\n __shared__ float s_z[D];\n __shared__ float s_mu[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n __syncthreads();\n \n // Compute XtY = X^T * y by accumulation\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + tx] * yb[base_row + k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // Load L and compute rhs\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs\n // Step 1: L * z = rhs (forward substitution)\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Step 2: L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * s_mu[j];\n }\n s_mu[i] = sum / s_L[i][i];\n }\n }\n __syncthreads();\n \n // Store mu_post\n if (tx < D) {\n mb[tx] = s_mu[tx];\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += Xtb[i * D + j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance using L^{-1}\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D + 1];\n __shared__ float s_Linv[D][D + 1];\n \n // Load L\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_Linv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution: L * L_inv = I\n // For each column col of L_inv, solve L * (L_inv[:,col]) = e_col\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n // We need L_inv[i,col], but L_inv is lower triangular so only compute for i >= col\n if (i >= col) {\n float rhs = (i == col) ? 1.0f : 0.0f;\n float sum = rhs;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_Linv[j][col];\n }\n s_Linv[i][col] = sum / s_L[i][i];\n }\n // For i < col, s_Linv[i][col] stays 0 (upper triangular part)\n }\n }\n }\n __syncthreads();\n \n // pred_var[i] = x_i @ Sigma @ x_i^T = ||L^{-1} @ x_i^T||^2\n // where x_i is the i-th row of X_test\n for (int i = tx; i < M; i += blockDim.x) {\n float result[D];\n \n // Compute L_inv @ x^T\n // result[row] = sum_{col=0}^{row} s_Linv[row][col] * x[col]\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = 0; col <= row; col++) { // L_inv is lower triangular\n sum += s_Linv[row][col] * Xtb[i * D + col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D}, options);\n auto pred_mean = torch::empty({B, M}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n // Reshape outputs to match expected shapes\n return std::make_tuple(\n pred_mean.unsqueeze(-1), // (B, M, 1)\n pred_var // (B, M)\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 4 · 1 tool call(s) · 243.9s turn · 27.2s LLM · tokens in=13,199 · out=3,482 · total=16,681 · 13,355 chars (click to expand)
turn 4243.9s turn · 27.2s LLM · 216.6s tools · tokens in=13,199 · out=3,482 · total=16,681
reasoning
Let me test for correctness again.
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\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_XtX[D][D + 1]; // +1 to avoid bank conflicts\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread accumulates part of XtX\n // Process rows of X in chunks\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Each thread computes one or more entries of XtX\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + i] * Xb[(base_row + k) * D + j];\n }\n atomicAdd(&s_XtX[i][j], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float val = noise_prec * s_XtX[i][j];\n if (i == j) {\n val += prior_prec;\n }\n Lb[i * D + j] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n const float* __restrict__ A,\n float* __restrict__ L_out,\n int B\n) {\n int b = blockIdx.x;\n const float* Ab = A + b * D * D;\n float* Lb = L_out + b * D * D;\n \n __shared__ float s_A[D][D + 1];\n __shared__ float s_L[D][D + 1];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L (copy entire matrix including upper triangle zeros)\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D + 1];\n __shared__ float s_rhs[D];\n __shared__ float s_z[D];\n __shared__ float s_mu[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n __syncthreads();\n \n // Compute XtY = X^T * y by accumulation\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + tx] * yb[base_row + k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // Load L and compute rhs\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs\n // Step 1: L * z = rhs (forward substitution)\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Step 2: L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * s_mu[j];\n }\n s_mu[i] = sum / s_L[i][i];\n }\n }\n __syncthreads();\n \n // Store mu_post\n if (tx < D) {\n mb[tx] = s_mu[tx];\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += Xtb[i * D + j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance using L^{-1}\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D + 1];\n __shared__ float s_Linv[D][D + 1];\n \n // Load L\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_Linv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution: L * L_inv = I\n // For each column col of L_inv, solve L * (L_inv[:,col]) = e_col\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n // We need L_inv[i,col], but L_inv is lower triangular so only compute for i >= col\n if (i >= col) {\n float rhs = (i == col) ? 1.0f : 0.0f;\n float sum = rhs;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_Linv[j][col];\n }\n s_Linv[i][col] = sum / s_L[i][i];\n }\n // For i < col, s_Linv[i][col] stays 0 (upper triangular part)\n }\n }\n }\n __syncthreads();\n \n // pred_var[i] = x_i @ Sigma @ x_i^T = ||L^{-1} @ x_i^T||^2\n // where x_i is the i-th row of X_test\n for (int i = tx; i < M; i += blockDim.x) {\n float result[D];\n \n // Compute L_inv @ x^T\n // result[row] = sum_{col=0}^{row} s_Linv[row][col] * x[col]\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = 0; col <= row; col++) { // L_inv is lower triangular\n sum += s_Linv[row][col] * Xtb[i * D + col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D}, options);\n auto pred_mean = torch::empty({B, M}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n // Reshape outputs to match expected shapes\n return std::make_tuple(\n pred_mean.unsqueeze(-1), // (B, M, 1)\n pred_var // (B, M)\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=5.96e-07 mean_abs_err=4.00e-08 max_rel_err=1.32e-03
turn 5 · 1 tool call(s) · 30.5s turn · 30.5s LLM · tokens in=16,731 · out=3,505 · total=20,236 · 13,446 chars (click to expand)
turn 530.5s turn · 30.5s LLM · 0.0s tools · tokens in=16,731 · out=3,505 · total=20,236
reasoning
The correctness test passed. Now let me run a static check before submitting.
assistantExcellent! The correctness test passed. Let 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\n// Constants\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_XtX[D][D + 1]; // +1 to avoid bank conflicts\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread accumulates part of XtX\n // Process rows of X in chunks\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Each thread computes one or more entries of XtX\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + i] * Xb[(base_row + k) * D + j];\n }\n atomicAdd(&s_XtX[i][j], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float val = noise_prec * s_XtX[i][j];\n if (i == j) {\n val += prior_prec;\n }\n Lb[i * D + j] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n const float* __restrict__ A,\n float* __restrict__ L_out,\n int B\n) {\n int b = blockIdx.x;\n const float* Ab = A + b * D * D;\n float* Lb = L_out + b * D * D;\n \n __shared__ float s_A[D][D + 1];\n __shared__ float s_L[D][D + 1];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L (copy entire matrix including upper triangle zeros)\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D + 1];\n __shared__ float s_rhs[D];\n __shared__ float s_z[D];\n __shared__ float s_mu[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n __syncthreads();\n \n // Compute XtY = X^T * y by accumulation\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + tx] * yb[base_row + k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // Load L and compute rhs\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs\n // Step 1: L * z = rhs (forward substitution)\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Step 2: L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * s_mu[j];\n }\n s_mu[i] = sum / s_L[i][i];\n }\n }\n __syncthreads();\n \n // Store mu_post\n if (tx < D) {\n mb[tx] = s_mu[tx];\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += Xtb[i * D + j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance using L^{-1}\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D + 1];\n __shared__ float s_Linv[D][D + 1];\n \n // Load L\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_Linv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution: L * L_inv = I\n // For each column col of L_inv, solve L * (L_inv[:,col]) = e_col\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n // We need L_inv[i,col], but L_inv is lower triangular so only compute for i >= col\n if (i >= col) {\n float rhs = (i == col) ? 1.0f : 0.0f;\n float sum = rhs;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_Linv[j][col];\n }\n s_Linv[i][col] = sum / s_L[i][i];\n }\n // For i < col, s_Linv[i][col] stays 0 (upper triangular part)\n }\n }\n }\n __syncthreads();\n \n // pred_var[i] = x_i @ Sigma @ x_i^T = ||L^{-1} @ x_i^T||^2\n // where x_i is the i-th row of X_test\n for (int i = tx; i < M; i += blockDim.x) {\n float result[D];\n \n // Compute L_inv @ x^T\n // result[row] = sum_{col=0}^{row} s_Linv[row][col] * x[col]\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = 0; col <= row; col++) { // L_inv is lower triangular\n sum += s_Linv[row][col] * Xtb[i * D + col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D}, options);\n auto pred_mean = torch::empty({B, M}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n // Reshape outputs to match expected shapes\n return std::make_tuple(\n pred_mean.unsqueeze(-1), // (B, M, 1)\n pred_var // (B, M)\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← static_check (ok)
okstatic_check static_check PASSED: no violations or warnings detected.
turn 6 · FINAL · 1 tool call(s) · 384.2s turn · 30.0s LLM · tokens in=20,235 · out=3,606 · total=23,841 · 14,337 chars (click to expand)
turn 6384.2s turn · 30.0s LLM · 354.2s tools · tokens in=20,235 · out=3,606 · total=23,841 · FINAL
reasoning
Good, the static check passed. Now let me submit the kernel for performance evaluation. But before I do, let me try to improve the kernel performance a bit more by considering a few optimizations: 1. The current implementation has multiple kernel launches, which adds overhead. I could try to fuse more operations. 2. The variance computation could potentially use register-private arrays instead of shared memory for the result vector. Actually, let me just submit the current version and see how it performs. The correctness check passed with excellent precision (max error ~6e-7), and the static check passed.
assistantThe static check passed. Let me submit the kernel for final evaluation.
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\n// Constants\nconstexpr int D = 16;\nconstexpr int THREADS_PER_BLOCK = 256;\n\n// Fused kernel for computing XtX and adding prior\n// Each block handles one batch\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X,\n float* __restrict__ Lambda_post,\n int N, int B, float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n // X is (B, N, D), Lambda_post is (B, D, D)\n const float* Xb = X + b * N * D;\n float* Lb = Lambda_post + b * D * D;\n \n // Shared memory for partial sums\n __shared__ float s_XtX[D][D + 1]; // +1 to avoid bank conflicts\n \n // Initialize XtX to zero\n if (tx < D * D) {\n s_XtX[tx / D][tx % D] = 0.0f;\n }\n __syncthreads();\n \n // Each thread accumulates part of XtX\n // Process rows of X in chunks\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n // Each thread computes one or more entries of XtX\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + i] * Xb[(base_row + k) * D + j];\n }\n atomicAdd(&s_XtX[i][j], sum);\n }\n __syncthreads();\n }\n \n // Store result with prior\n for (int idx = tx; idx < D * D; idx += blockDim.x) {\n int i = idx / D;\n int j = idx % D;\n float val = noise_prec * s_XtX[i][j];\n if (i == j) {\n val += prior_prec;\n }\n Lb[i * D + j] = val;\n }\n}\n\n// Cholesky decomposition for small matrices (D=16)\n__global__ void cholesky_kernel(\n const float* __restrict__ A,\n float* __restrict__ L_out,\n int B\n) {\n int b = blockIdx.x;\n const float* Ab = A + b * D * D;\n float* Lb = L_out + b * D * D;\n \n __shared__ float s_A[D][D + 1];\n __shared__ float s_L[D][D + 1];\n \n // Load A into shared memory\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n s_A[row][col] = Ab[row * D + col];\n s_L[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky decomposition (single thread for correctness)\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))\n float diag = s_A[i][i];\n for (int k = 0; k < i; k++) {\n diag -= s_L[i][k] * s_L[i][k];\n }\n s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));\n \n // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]\n for (int j = i + 1; j < D; j++) {\n float val = s_A[j][i];\n for (int k = 0; k < i; k++) {\n val -= s_L[j][k] * s_L[i][k];\n }\n s_L[j][i] = val / s_L[i][i];\n }\n }\n }\n __syncthreads();\n \n // Store L (copy entire matrix including upper triangle zeros)\n if (threadIdx.x < D * D) {\n int row = threadIdx.x / D;\n int col = threadIdx.x % D;\n Lb[row * D + col] = s_L[row][col];\n }\n}\n\n// Fused kernel: compute XtY and solve with Cholesky\n__global__ void solve_mu_post_kernel(\n const float* __restrict__ X,\n const float* __restrict__ y,\n const float* __restrict__ L,\n float* __restrict__ mu_post,\n int N, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xb = X + b * N * D;\n const float* yb = y + b * N;\n const float* Lb = L + b * D * D;\n float* mb = mu_post + b * D;\n \n __shared__ float s_XtY[D];\n __shared__ float s_L[D][D + 1];\n __shared__ float s_rhs[D];\n __shared__ float s_z[D];\n __shared__ float s_mu[D];\n \n // Initialize\n if (tx < D) s_XtY[tx] = 0.0f;\n __syncthreads();\n \n // Compute XtY = X^T * y by accumulation\n for (int iter = 0; iter < (N + 255) / 256; iter++) {\n int base_row = iter * 256;\n int valid_rows = min(256, N - base_row);\n \n if (tx < D) {\n float sum = 0.0f;\n for (int k = 0; k < valid_rows; k++) {\n sum += Xb[(base_row + k) * D + tx] * yb[base_row + k];\n }\n atomicAdd(&s_XtY[tx], sum);\n }\n __syncthreads();\n }\n \n // Load L and compute rhs\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n }\n if (tx < D) {\n s_rhs[tx] = noise_prec * s_XtY[tx];\n }\n __syncthreads();\n \n // Solve L * L^T * mu = rhs\n // Step 1: L * z = rhs (forward substitution)\n if (tx == 0) {\n for (int i = 0; i < D; i++) {\n float sum = s_rhs[i];\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_z[j];\n }\n s_z[i] = sum / s_L[i][i];\n }\n \n // Step 2: L^T * mu = z (backward substitution)\n for (int i = D - 1; i >= 0; i--) {\n float sum = s_z[i];\n for (int j = i + 1; j < D; j++) {\n sum -= s_L[j][i] * s_mu[j];\n }\n s_mu[i] = sum / s_L[i][i];\n }\n }\n __syncthreads();\n \n // Store mu_post\n if (tx < D) {\n mb[tx] = s_mu[tx];\n }\n}\n\n// Predictive mean: pred_mean = X_test @ mu_post\n__global__ void predict_mean_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ mu_post,\n float* __restrict__ pred_mean,\n int M, int B\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* mb = mu_post + b * D;\n float* pb = pred_mean + b * M;\n \n __shared__ float s_mu[D];\n \n // Load mu_post into shared memory\n if (tx < D) {\n s_mu[tx] = mb[tx];\n }\n __syncthreads();\n \n // Each thread computes one or more predictions\n for (int i = tx; i < M; i += blockDim.x) {\n float sum = 0.0f;\n for (int j = 0; j < D; j++) {\n sum += Xtb[i * D + j] * s_mu[j];\n }\n pb[i] = sum;\n }\n}\n\n// Predictive variance using L^{-1}\n__global__ void predict_var_kernel(\n const float* __restrict__ X_test,\n const float* __restrict__ L,\n float* __restrict__ pred_var,\n int M, int B, float noise_prec\n) {\n int b = blockIdx.x;\n int tx = threadIdx.x;\n \n const float* Xtb = X_test + b * M * D;\n const float* Lb = L + b * D * D;\n float* vb = pred_var + b * M;\n \n __shared__ float s_L[D][D + 1];\n __shared__ float s_Linv[D][D + 1];\n \n // Load L\n if (tx < D * D) {\n int row = tx / D;\n int col = tx % D;\n s_L[row][col] = Lb[row * D + col];\n s_Linv[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Compute L^{-1} by forward substitution: L * L_inv = I\n // For each column col of L_inv, solve L * (L_inv[:,col]) = e_col\n if (tx == 0) {\n for (int col = 0; col < D; col++) {\n for (int i = 0; i < D; i++) {\n // We need L_inv[i,col], but L_inv is lower triangular so only compute for i >= col\n if (i >= col) {\n float rhs = (i == col) ? 1.0f : 0.0f;\n float sum = rhs;\n for (int j = 0; j < i; j++) {\n sum -= s_L[i][j] * s_Linv[j][col];\n }\n s_Linv[i][col] = sum / s_L[i][i];\n }\n // For i < col, s_Linv[i][col] stays 0 (upper triangular part)\n }\n }\n }\n __syncthreads();\n \n // pred_var[i] = x_i @ Sigma @ x_i^T = ||L^{-1} @ x_i^T||^2\n // where x_i is the i-th row of X_test\n for (int i = tx; i < M; i += blockDim.x) {\n float result[D];\n \n // Compute L_inv @ x^T\n // result[row] = sum_{col=0}^{row} s_Linv[row][col] * x[col]\n for (int row = 0; row < D; row++) {\n float sum = 0.0f;\n for (int col = 0; col <= row; col++) { // L_inv is lower triangular\n sum += s_Linv[row][col] * Xtb[i * D + col];\n }\n result[row] = sum;\n }\n \n // Compute squared norm\n float var = 0.0f;\n for (int j = 0; j < D; j++) {\n var += result[j] * result[j];\n }\n vb[i] = var + 1.0f / noise_prec;\n }\n}\n\n// Host function\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n) {\n int B = X.size(0);\n int N = X.size(1);\n int D = X.size(2);\n int M = X_test.size(1);\n \n auto options = X.options();\n \n // Allocate intermediate tensors\n auto Lambda_post = torch::empty({B, D, D}, options);\n auto L = torch::empty({B, D, D}, options);\n auto mu_post = torch::empty({B, D}, options);\n auto pred_mean = torch::empty({B, M}, options);\n auto pred_var = torch::empty({B, M}, options);\n \n // Launch kernels\n int blocks = B;\n \n // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X\n compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n Lambda_post.data_ptr<float>(),\n N, B, prior_precision, noise_precision\n );\n \n // Cholesky decomposition\n cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n Lambda_post.data_ptr<float>(),\n L.data_ptr<float>(),\n B\n );\n \n // Solve for mu_post\n solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n L.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n N, B, noise_precision\n );\n \n // Predictive mean\n predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n M, B\n );\n \n // Predictive variance\n predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(\n X_test.data_ptr<float>(),\n L.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n M, B, noise_precision\n );\n \n // Reshape outputs to match expected shapes\n return std::make_tuple(\n pred_mean.unsqueeze(-1), // (B, M, 1)\n pred_var // (B, M)\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(\n torch::Tensor X,\n torch::Tensor y,\n torch::Tensor X_test,\n float prior_precision,\n float noise_precision\n);\n\"\"\"\n\nbayesian_extension = load_inline(\n name='bayesian_linear_cuda_v2',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['bayesian_linear_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):\n super().__init__()\n self.input_dim = input_dim\n self.prior_precision = prior_precision\n self.noise_precision = noise_precision\n \n def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):\n pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(\n X.contiguous(),\n y.contiguous(),\n X_test.contiguous(),\n self.prior_precision,\n self.noise_precision\n )\n return pred_mean, pred_var" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.14 μs Runtime stats: mean=0.14μs median=?μs std=0.00μs Numerical precision: max_abs_err=5.96e-07 mean_abs_err=4.00e-08 max_rel_err=1.32e-03 Memory: 33.37 MB peak (ref: 33.51 MB, ratio: 0.9959x) Kernel launches: 7 (ref: 141, fusion ratio: 20.1429) Energy: 243.1 mJ/run (ref: 243.28 mJ, ratio: 1.0007x) SOL score: 0.0105
final submitted kernel (409 lines, 11,666 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>

// Constants
constexpr int D = 16;
constexpr int THREADS_PER_BLOCK = 256;

// Fused kernel for computing XtX and adding prior
// Each block handles one batch
__global__ void compute_lambda_post_kernel(
    const float* __restrict__ X,
    float* __restrict__ Lambda_post,
    int N, int B, float prior_prec, float noise_prec
) {
    int b = blockIdx.x;
    int tx = threadIdx.x;
    
    // X is (B, N, D), Lambda_post is (B, D, D)
    const float* Xb = X + b * N * D;
    float* Lb = Lambda_post + b * D * D;
    
    // Shared memory for partial sums
    __shared__ float s_XtX[D][D + 1];  // +1 to avoid bank conflicts
    
    // Initialize XtX to zero
    if (tx < D * D) {
        s_XtX[tx / D][tx % D] = 0.0f;
    }
    __syncthreads();
    
    // Each thread accumulates part of XtX
    // Process rows of X in chunks
    for (int iter = 0; iter < (N + 255) / 256; iter++) {
        int base_row = iter * 256;
        int valid_rows = min(256, N - base_row);
        
        // Each thread computes one or more entries of XtX
        for (int idx = tx; idx < D * D; idx += blockDim.x) {
            int i = idx / D;
            int j = idx % D;
            float sum = 0.0f;
            for (int k = 0; k < valid_rows; k++) {
                sum += Xb[(base_row + k) * D + i] * Xb[(base_row + k) * D + j];
            }
            atomicAdd(&s_XtX[i][j], sum);
        }
        __syncthreads();
    }
    
    // Store result with prior
    for (int idx = tx; idx < D * D; idx += blockDim.x) {
        int i = idx / D;
        int j = idx % D;
        float val = noise_prec * s_XtX[i][j];
        if (i == j) {
            val += prior_prec;
        }
        Lb[i * D + j] = val;
    }
}

// Cholesky decomposition for small matrices (D=16)
__global__ void cholesky_kernel(
    const float* __restrict__ A,
    float* __restrict__ L_out,
    int B
) {
    int b = blockIdx.x;
    const float* Ab = A + b * D * D;
    float* Lb = L_out + b * D * D;
    
    __shared__ float s_A[D][D + 1];
    __shared__ float s_L[D][D + 1];
    
    // Load A into shared memory
    if (threadIdx.x < D * D) {
        int row = threadIdx.x / D;
        int col = threadIdx.x % D;
        s_A[row][col] = Ab[row * D + col];
        s_L[row][col] = 0.0f;
    }
    __syncthreads();
    
    // Cholesky decomposition (single thread for correctness)
    if (threadIdx.x == 0) {
        for (int i = 0; i < D; i++) {
            // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2))
            float diag = s_A[i][i];
            for (int k = 0; k < i; k++) {
                diag -= s_L[i][k] * s_L[i][k];
            }
            s_L[i][i] = sqrtf(fmaxf(diag, 1e-10f));
            
            // L[j][i] = (A[j][i] - sum(L[j][k]*L[i][k])) / L[i][i]
            for (int j = i + 1; j < D; j++) {
                float val = s_A[j][i];
                for (int k = 0; k < i; k++) {
                    val -= s_L[j][k] * s_L[i][k];
                }
                s_L[j][i] = val / s_L[i][i];
            }
        }
    }
    __syncthreads();
    
    // Store L (copy entire matrix including upper triangle zeros)
    if (threadIdx.x < D * D) {
        int row = threadIdx.x / D;
        int col = threadIdx.x % D;
        Lb[row * D + col] = s_L[row][col];
    }
}

// Fused kernel: compute XtY and solve with Cholesky
__global__ void solve_mu_post_kernel(
    const float* __restrict__ X,
    const float* __restrict__ y,
    const float* __restrict__ L,
    float* __restrict__ mu_post,
    int N, int B, float noise_prec
) {
    int b = blockIdx.x;
    int tx = threadIdx.x;
    
    const float* Xb = X + b * N * D;
    const float* yb = y + b * N;
    const float* Lb = L + b * D * D;
    float* mb = mu_post + b * D;
    
    __shared__ float s_XtY[D];
    __shared__ float s_L[D][D + 1];
    __shared__ float s_rhs[D];
    __shared__ float s_z[D];
    __shared__ float s_mu[D];
    
    // Initialize
    if (tx < D) s_XtY[tx] = 0.0f;
    __syncthreads();
    
    // Compute XtY = X^T * y by accumulation
    for (int iter = 0; iter < (N + 255) / 256; iter++) {
        int base_row = iter * 256;
        int valid_rows = min(256, N - base_row);
        
        if (tx < D) {
            float sum = 0.0f;
            for (int k = 0; k < valid_rows; k++) {
                sum += Xb[(base_row + k) * D + tx] * yb[base_row + k];
            }
            atomicAdd(&s_XtY[tx], sum);
        }
        __syncthreads();
    }
    
    // Load L and compute rhs
    if (tx < D * D) {
        int row = tx / D;
        int col = tx % D;
        s_L[row][col] = Lb[row * D + col];
    }
    if (tx < D) {
        s_rhs[tx] = noise_prec * s_XtY[tx];
    }
    __syncthreads();
    
    // Solve L * L^T * mu = rhs
    // Step 1: L * z = rhs (forward substitution)
    if (tx == 0) {
        for (int i = 0; i < D; i++) {
            float sum = s_rhs[i];
            for (int j = 0; j < i; j++) {
                sum -= s_L[i][j] * s_z[j];
            }
            s_z[i] = sum / s_L[i][i];
        }
        
        // Step 2: L^T * mu = z (backward substitution)
        for (int i = D - 1; i >= 0; i--) {
            float sum = s_z[i];
            for (int j = i + 1; j < D; j++) {
                sum -= s_L[j][i] * s_mu[j];
            }
            s_mu[i] = sum / s_L[i][i];
        }
    }
    __syncthreads();
    
    // Store mu_post
    if (tx < D) {
        mb[tx] = s_mu[tx];
    }
}

// Predictive mean: pred_mean = X_test @ mu_post
__global__ void predict_mean_kernel(
    const float* __restrict__ X_test,
    const float* __restrict__ mu_post,
    float* __restrict__ pred_mean,
    int M, int B
) {
    int b = blockIdx.x;
    int tx = threadIdx.x;
    
    const float* Xtb = X_test + b * M * D;
    const float* mb = mu_post + b * D;
    float* pb = pred_mean + b * M;
    
    __shared__ float s_mu[D];
    
    // Load mu_post into shared memory
    if (tx < D) {
        s_mu[tx] = mb[tx];
    }
    __syncthreads();
    
    // Each thread computes one or more predictions
    for (int i = tx; i < M; i += blockDim.x) {
        float sum = 0.0f;
        for (int j = 0; j < D; j++) {
            sum += Xtb[i * D + j] * s_mu[j];
        }
        pb[i] = sum;
    }
}

// Predictive variance using L^{-1}
__global__ void predict_var_kernel(
    const float* __restrict__ X_test,
    const float* __restrict__ L,
    float* __restrict__ pred_var,
    int M, int B, float noise_prec
) {
    int b = blockIdx.x;
    int tx = threadIdx.x;
    
    const float* Xtb = X_test + b * M * D;
    const float* Lb = L + b * D * D;
    float* vb = pred_var + b * M;
    
    __shared__ float s_L[D][D + 1];
    __shared__ float s_Linv[D][D + 1];
    
    // Load L
    if (tx < D * D) {
        int row = tx / D;
        int col = tx % D;
        s_L[row][col] = Lb[row * D + col];
        s_Linv[row][col] = 0.0f;
    }
    __syncthreads();
    
    // Compute L^{-1} by forward substitution: L * L_inv = I
    // For each column col of L_inv, solve L * (L_inv[:,col]) = e_col
    if (tx == 0) {
        for (int col = 0; col < D; col++) {
            for (int i = 0; i < D; i++) {
                // We need L_inv[i,col], but L_inv is lower triangular so only compute for i >= col
                if (i >= col) {
                    float rhs = (i == col) ? 1.0f : 0.0f;
                    float sum = rhs;
                    for (int j = 0; j < i; j++) {
                        sum -= s_L[i][j] * s_Linv[j][col];
                    }
                    s_Linv[i][col] = sum / s_L[i][i];
                }
                // For i < col, s_Linv[i][col] stays 0 (upper triangular part)
            }
        }
    }
    __syncthreads();
    
    // pred_var[i] = x_i @ Sigma @ x_i^T = ||L^{-1} @ x_i^T||^2
    // where x_i is the i-th row of X_test
    for (int i = tx; i < M; i += blockDim.x) {
        float result[D];
        
        // Compute L_inv @ x^T
        // result[row] = sum_{col=0}^{row} s_Linv[row][col] * x[col]
        for (int row = 0; row < D; row++) {
            float sum = 0.0f;
            for (int col = 0; col <= row; col++) {  // L_inv is lower triangular
                sum += s_Linv[row][col] * Xtb[i * D + col];
            }
            result[row] = sum;
        }
        
        // Compute squared norm
        float var = 0.0f;
        for (int j = 0; j < D; j++) {
            var += result[j] * result[j];
        }
        vb[i] = var + 1.0f / noise_prec;
    }
}

// Host function
std::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(
    torch::Tensor X,
    torch::Tensor y,
    torch::Tensor X_test,
    float prior_precision,
    float noise_precision
) {
    int B = X.size(0);
    int N = X.size(1);
    int D = X.size(2);
    int M = X_test.size(1);
    
    auto options = X.options();
    
    // Allocate intermediate tensors
    auto Lambda_post = torch::empty({B, D, D}, options);
    auto L = torch::empty({B, D, D}, options);
    auto mu_post = torch::empty({B, D}, options);
    auto pred_mean = torch::empty({B, M}, options);
    auto pred_var = torch::empty({B, M}, options);
    
    // Launch kernels
    int blocks = B;
    
    // Compute Lambda_post = prior_precision * I + noise_precision * X^T @ X
    compute_lambda_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(
        X.data_ptr<float>(),
        Lambda_post.data_ptr<float>(),
        N, B, prior_precision, noise_precision
    );
    
    // Cholesky decomposition
    cholesky_kernel<<<blocks, THREADS_PER_BLOCK>>>(
        Lambda_post.data_ptr<float>(),
        L.data_ptr<float>(),
        B
    );
    
    // Solve for mu_post
    solve_mu_post_kernel<<<blocks, THREADS_PER_BLOCK>>>(
        X.data_ptr<float>(),
        y.data_ptr<float>(),
        L.data_ptr<float>(),
        mu_post.data_ptr<float>(),
        N, B, noise_precision
    );
    
    // Predictive mean
    predict_mean_kernel<<<blocks, THREADS_PER_BLOCK>>>(
        X_test.data_ptr<float>(),
        mu_post.data_ptr<float>(),
        pred_mean.data_ptr<float>(),
        M, B
    );
    
    // Predictive variance
    predict_var_kernel<<<blocks, THREADS_PER_BLOCK>>>(
        X_test.data_ptr<float>(),
        L.data_ptr<float>(),
        pred_var.data_ptr<float>(),
        M, B, noise_precision
    );
    
    // Reshape outputs to match expected shapes
    return std::make_tuple(
        pred_mean.unsqueeze(-1),  // (B, M, 1)
        pred_var  // (B, M)
    );
}
"""

cpp_source = """
std::tuple<torch::Tensor, torch::Tensor> bayesian_linear_forward(
    torch::Tensor X,
    torch::Tensor y,
    torch::Tensor X_test,
    float prior_precision,
    float noise_precision
);
"""

bayesian_extension = load_inline(
    name='bayesian_linear_cuda_v2',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['bayesian_linear_forward'],
    extra_cuda_cflags=['-O3', '--use_fast_math'],
    verbose=True
)

class ModelNew(nn.Module):
    def __init__(self, input_dim, prior_precision=1.0, noise_precision=1.0):
        super().__init__()
        self.input_dim = input_dim
        self.prior_precision = prior_precision
        self.noise_precision = noise_precision
    
    def forward(self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor):
        pred_mean, pred_var = bayesian_extension.bayesian_linear_forward(
            X.contiguous(),
            y.contiguous(),
            X_test.contiguous(),
            self.prior_precision,
            self.noise_precision
        )
        return pred_mean, pred_var