FW-GLM-5-1 · popcorn · pop_l123_st_gpt
incorrect 9_BayesianLinearRegression.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
6765
wall clock
575.5s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
—
ref runtime
0.30 μs
speedup
—
started
2026-05-11T08:53:47Z
correctness issue:
timingkernelbench.timing
kernel runtime
—
ref runtime
0.30 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
0.30 / — / 0.04 μs
roofline / occupancyheuristic
dram bandwidth
—
dram util %
—
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
—
bottleneck
—
dominant pipe %
—
arithmetic intensity
—
ridge point
—
L1 hit %
—
L2 hit %
—
ld sectors / req
—
st sectors / req
—
regs / thread
—
smem / block
—
block size
—
peak bw
—
peak fp32 tflops
—
peak fp16 tflops
—
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 0 · FINAL · 1 tool call(s) · 575.5s turn · 93.9s LLM · tokens in=1,363 · out=5,402 · total=6,765 · 20,933 chars (click to expand)
turn 0575.5s turn · 93.9s LLM · 481.6s tools · tokens in=1,363 · out=5,402 · total=6,765 · FINAL
reasoning
Let me analyze this task. I need to implement a custom CUDA kernel for Bayesian linear regression with a conjugate Gaussian prior. The reference implementation does: 1. Compute `Lambda_post = prior_precision * I + noise_precision * X^T X` 2. Cholesky decomposition: `L = cholesky(Lambda_post)` 3. Compute `mu_post = Lambda_post^{-1} * noise_precision * X^T y` using `cholesky_solve` 4. Predictive mean: `pred_mean = X_test @ mu_post` 5. Predictive variance: `pred_var = sum(X_test @ Sigma_post * X_test, dim=-1) + 1/noise_precision` The shapes are: - X: (B, N, D) = (16, 256, 16) - y: (B, N, 1) = (16, 256, 1) - X_test: (B, M, D) = (16, 64, 16) - Lambda_post: (B, D, D) = (16, 16, 16) - L: (B, D, D) = (16, 16, 16) - mu_post: (B, D, 1) = (16, 16, 1) - pred_mean: (B, M, 1) = (16, 64, 1) - Sigma_post: (B, D, D) = (16, 16, 16) - pred_var: (B, M) = (16, 64) The key operations are: 1. Batched matrix multiplication: X^T @ X (DxD matrices) 2. Cholesky decomposition (small matrices, D=16) 3. Cholesky solve 4. More matrix multiplications Since D=16 is small, we could potentially optimize this well with custom kernels. I could fuse multiple operations together for efficiency. The Cholesky decomposition and related operations on these small 16x16 matrices could be handled efficiently with custom kernels, possibly leveraging shared memory and cooperative thread groups. For larger dimensions, cuBLAS would be more appropriate, but here I have an opportunity to optimize specifically for this size. I'll start by building a working implementation that uses batched operations, then look for optimization opportunities through kernel fusion. The main computational flow involves computing the posterior precision matrix through batched matrix multiplication, decomposing it via Cholesky, solving for the posterior mean, then computing the predictive distribution. I can use `torch.linalg.cholesky` for the decomposition and `torch.cholesky_solve` for the linear system, with the inverse computed through `torch.cholesky_inverse`. I could create a fused kernel to combine some of these operations and reduce memory transfers between them. The computational bottleneck centers on three main operations: computing X^T @ X as a (D, D) matrix, performing Cholesky decomposition and solve on the small D×D system, and calculating the predictive variance. Since D=16 is quite small, a custom kernel approach might be viable—I'd launch B×D×D threads with each thread block handling one batch element, using shared memory for the matrix operations and computing the Cholesky decomposition collaboratively within the block. A more realistic path forward would start with PyTorch's optimized batched operations as a baseline, then selectively fuse expensive operations like the variance computation. The X^T @ X computation itself could benefit from a custom kernel that pre-scales X and accumulates results directly in shared memory rather than through multiple kernel launches. s = self.noise_precision * X^T @ y mu_post = torch.cholesky_solve(Xty_scaled, L) pred_mean = X_test @ mu_post Sigma_post = torch.cholesky_inverse(L) # Compute variance with einsum for efficiency tmp = torch.einsum('bmd,bdk,bmk->bm', X_test, Sigma_post, X_test) pred_var = tmp + 1.0 / self.noise_precision return pred_mean, pred_var I can streamline this by computing X^T @ y directly as a single operation, then scale it by noise_precision before solving. The variance calculation uses einsum to efficiently compute the quadratic form across all batches at once, avoiding explicit matrix multiplications.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile custom CUDA kernels at module load time\ncuda_source = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\n// Constants for our problem\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int D = 16; // Input dimension\n\n// Fused kernel: X^T @ X with scaling and adding diagonal\n// Each block handles one batch element, computes DxD matrix\n__global__ void compute_lambda_post_kernel(\n const float* __restrict__ X, // (B, N, D)\n float* __restrict__ Lambda, // (B, D, D)\n int B, int N,\n float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tid = threadIdx.x;\n \n // Shared memory for X tile and accumulation\n __shared__ float X_tile[256][16]; // Store X[b, :, :] transposed\n __shared__ float Lambda_shared[16][16];\n \n // Initialize Lambda_shared to zero\n for (int i = tid; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n Lambda_shared[row][col] = 0.0f;\n }\n __syncthreads();\n \n // Load X[b, :, :] into shared memory (transposed for coalesced access)\n const float* X_b = X + b * N * D;\n \n // Each thread processes multiple rows\n for (int n = tid; n < N; n += blockDim.x) {\n for (int d = 0; d < D; d++) {\n X_tile[n][d] = X_b[n * D + d];\n }\n }\n __syncthreads();\n \n // Compute X^T @ X using all threads\n // Each thread computes part of the result\n for (int row = 0; row < D; row++) {\n for (int col = row; col < D; col++) {\n float sum = 0.0f;\n for (int n = tid; n < N; n += blockDim.x) {\n sum += X_tile[n][row] * X_tile[n][col];\n }\n // Block-wide reduction\n for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {\n sum += __shfl_down_sync(0xffffffff, sum, offset);\n }\n if (tid == 0) {\n Lambda_shared[row][col] = sum * noise_prec;\n Lambda_shared[col][row] = sum * noise_prec; // Symmetric\n }\n __syncthreads();\n }\n }\n \n // Add diagonal prior\n if (tid < D) {\n Lambda_shared[tid][tid] += prior_prec;\n }\n __syncthreads();\n \n // Write result to global memory\n float* Lambda_b = Lambda + b * D * D;\n for (int i = tid; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n Lambda_b[row * D + col] = Lambda_shared[row][col];\n }\n}\n\n// Fused kernel: compute X^T @ y\n__global__ void compute_xt_y_kernel(\n const float* __restrict__ X, // (B, N, D)\n const float* __restrict__ y, // (B, N, 1)\n float* __restrict__ Xty, // (B, D, 1)\n int B, int N, float noise_prec\n) {\n int b = blockIdx.x;\n int d = threadIdx.x;\n \n if (d >= D) return;\n \n const float* X_b = X + b * N * D;\n const float* y_b = y + b * N;\n \n float sum = 0.0f;\n for (int n = 0; n < N; n++) {\n sum += X_b[n * D + d] * y_b[n];\n }\n \n Xty[b * D + d] = sum * noise_prec;\n}\n\n// Fused kernel: compute predictive variance\n// pred_var = sum_d sum_k (X_test * Sigma_post * X_test^T) + 1/noise_precision\n__global__ void compute_pred_var_kernel(\n const float* __restrict__ X_test, // (B, M, D)\n const float* __restrict__ Sigma_post, // (B, D, D)\n float* __restrict__ pred_var, // (B, M)\n int B, int M, float inv_noise_prec\n) {\n int b = blockIdx.y;\n int m = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (m >= M) return;\n \n const float* X_test_m = X_test + b * M * D + m * D;\n const float* Sigma = Sigma_post + b * D * D;\n \n float var = 0.0f;\n for (int d1 = 0; d1 < D; d1++) {\n float sum = 0.0f;\n for (int d2 = 0; d2 < D; d2++) {\n sum += Sigma[d1 * D + d2] * X_test_m[d2];\n }\n var += X_test_m[d1] * sum;\n }\n \n pred_var[b * M + m] = var + inv_noise_prec;\n}\n\n// Fused kernel for X_test @ mu_post (predictive mean)\n__global__ void compute_pred_mean_kernel(\n const float* __restrict__ X_test, // (B, M, D)\n const float* __restrict__ mu_post, // (B, D, 1)\n float* __restrict__ pred_mean, // (B, M, 1)\n int B, int M\n) {\n int b = blockIdx.y;\n int m = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (m >= M) return;\n \n const float* X_test_m = X_test + b * M * D + m * D;\n const float* mu = mu_post + b * D;\n \n float sum = 0.0f;\n for (int d = 0; d < D; d++) {\n sum += X_test_m[d] * mu[d];\n }\n \n pred_mean[b * M + m] = sum;\n}\n\n// Host functions\ntorch::Tensor compute_lambda_post_cuda(torch::Tensor X, float prior_prec, float noise_prec) {\n int B = X.size(0);\n int N = X.size(1);\n // int D = X.size(2); // Known to be 16\n \n auto Lambda = torch::empty({B, D, D}, X.options());\n \n compute_lambda_post_kernel<<<B, BLOCK_SIZE>>>(\n X.data_ptr<float>(),\n Lambda.data_ptr<float>(),\n B, N, prior_prec, noise_prec\n );\n \n return Lambda;\n}\n\ntorch::Tensor compute_xt_y_cuda(torch::Tensor X, torch::Tensor y, float noise_prec) {\n int B = X.size(0);\n int N = X.size(1);\n \n auto Xty = torch::empty({B, D, 1}, X.options());\n \n compute_xt_y_kernel<<<B, 32>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n Xty.data_ptr<float>(),\n B, N, noise_prec\n );\n \n return Xty;\n}\n\ntorch::Tensor compute_pred_mean_cuda(torch::Tensor X_test, torch::Tensor mu_post) {\n int B = X_test.size(0);\n int M = X_test.size(1);\n \n auto pred_mean = torch::empty({B, M, 1}, X_test.options());\n \n int threads = 64;\n int blocks_m = (M + threads - 1) / threads;\n dim3 grid(blocks_m, B);\n \n compute_pred_mean_kernel<<<grid, threads>>>(\n X_test.data_ptr<float>(),\n mu_post.data_ptr<float>(),\n pred_mean.data_ptr<float>(),\n B, M\n );\n \n return pred_mean;\n}\n\ntorch::Tensor compute_pred_var_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec) {\n int B = X_test.size(0);\n int M = X_test.size(1);\n \n auto pred_var = torch::empty({B, M}, X_test.options());\n \n int threads = 64;\n int blocks_m = (M + threads - 1) / threads;\n dim3 grid(blocks_m, B);\n \n compute_pred_var_kernel<<<grid, threads>>>(\n X_test.data_ptr<float>(),\n Sigma_post.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n B, M, inv_noise_prec\n );\n \n return pred_var;\n}\n\n// Fused kernel: X_test @ Sigma_post, stores result for variance computation\n// Then compute variance directly\n__global__ void compute_pred_var_fused_kernel(\n const float* __restrict__ X_test, // (B, M, D)\n const float* __restrict__ Sigma_post, // (B, D, D)\n float* __restrict__ pred_var, // (B, M)\n int B, int M, float inv_noise_prec\n) {\n // Each block handles one (b, m) pair\n int b = blockIdx.y;\n int m = blockIdx.x;\n \n __shared__ float X_shared[16]; // One row of X_test\n __shared__ float S_shared[16][16]; // Sigma_post\n __shared__ float XS_shared[16]; // X_test @ Sigma_post result for row m\n \n // Collaborative load of X_test[b, m, :]\n for (int d = threadIdx.x; d < D; d += blockDim.x) {\n X_shared[d] = X_test[b * M * D + m * D + d];\n }\n \n // Collaborative load of Sigma_post[b, :, :]\n const float* Sigma = Sigma_post + b * D * D;\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n S_shared[row][col] = Sigma[i];\n }\n __syncthreads();\n \n // Compute X_test[m, :] @ Sigma_post\n // Each thread computes one element\n float local_xs[4] = {0.0f, 0.0f, 0.0f, 0.0f};\n for (int d = threadIdx.x; d < D; d += blockDim.x) {\n float sum = 0.0f;\n for (int k = 0; k < D; k++) {\n sum += X_shared[k] * S_shared[k][d];\n }\n if (threadIdx.x < D) {\n XS_shared[d] = sum;\n }\n }\n __syncthreads();\n \n // Compute sum over d of X_shared[d] * XS_shared[d]\n // This is (X @ Sigma @ X^T) for row m\n float dot = 0.0f;\n for (int d = threadIdx.x; d < D; d += blockDim.x) {\n dot += X_shared[d] * XS_shared[d];\n }\n \n // Reduce across threads\n for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {\n dot += __shfl_down_sync(0xffffffff, dot, offset);\n }\n \n if (threadIdx.x == 0) {\n pred_var[b * M + m] = dot + inv_noise_prec;\n }\n}\n\ntorch::Tensor compute_pred_var_fused_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec) {\n int B = X_test.size(0);\n int M = X_test.size(1);\n \n auto pred_var = torch::empty({B, M}, X_test.options());\n \n dim3 grid(M, B); // One block per (b, m) pair\n int threads = 32; // Enough threads for D=16\n \n compute_pred_var_fused_kernel<<<grid, threads>>>(\n X_test.data_ptr<float>(),\n Sigma_post.data_ptr<float>(),\n pred_var.data_ptr<float>(),\n B, M, inv_noise_prec\n );\n \n return pred_var;\n}\n\n// Combined kernel that does X^T @ X and X^T @ y together to reuse X in shared memory\n__global__ void compute_xtx_and_xty_kernel(\n const float* __restrict__ X, // (B, N, D)\n const float* __restrict__ y, // (B, N, 1)\n float* __restrict__ Lambda, // (B, D, D)\n float* __restrict__ Xty, // (B, D, 1)\n int B, int N,\n float prior_prec, float noise_prec\n) {\n int b = blockIdx.x;\n int tid = threadIdx.x;\n \n __shared__ float X_shared[256][16]; // X transposed in shared memory\n __shared__ float y_shared[256]; // y values\n __shared__ float Lambda_s[16][16];\n __shared__ float Xty_s[16];\n \n const float* X_b = X + b * N * D;\n const float* y_b = y + b * N;\n \n // Initialize outputs\n for (int i = tid; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n Lambda_s[row][col] = 0.0f;\n }\n if (tid < D) {\n Xty_s[tid] = 0.0f;\n }\n __syncthreads();\n \n // Load X and y into shared memory\n for (int n = tid; n < N; n += blockDim.x) {\n y_shared[n] = y_b[n];\n for (int d = 0; d < D; d++) {\n X_shared[n][d] = X_b[n * D + d];\n }\n }\n __syncthreads();\n \n // Compute X^T @ X and X^T @ y\n for (int row = 0; row < D; row++) {\n // Compute X^T @ y for this row\n float xty_sum = 0.0f;\n for (int n = tid; n < N; n += blockDim.x) {\n xty_sum += X_shared[n][row] * y_shared[n];\n }\n for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {\n xty_sum += __shfl_down_sync(0xffffffff, xty_sum, offset);\n }\n if (tid == 0) {\n Xty_s[row] = xty_sum * noise_prec;\n }\n \n // Compute X^T @ X for columns >= row (upper triangle)\n for (int col = row; col < D; col++) {\n float xtx_sum = 0.0f;\n for (int n = tid; n < N; n += blockDim.x) {\n xtx_sum += X_shared[n][row] * X_shared[n][col];\n }\n for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {\n xtx_sum += __shfl_down_sync(0xffffffff, xtx_sum, offset);\n }\n if (tid == 0) {\n float val = xtx_sum * noise_prec;\n Lambda_s[row][col] = val;\n Lambda_s[col][row] = val;\n }\n }\n __syncthreads();\n }\n \n // Add diagonal prior\n if (tid < D) {\n Lambda_s[tid][tid] += prior_prec;\n }\n __syncthreads();\n \n // Write Lambda to global memory\n float* Lambda_b = Lambda + b * D * D;\n for (int i = tid; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n Lambda_b[row * D + col] = Lambda_s[row][col];\n }\n \n // Write Xty to global memory\n float* Xty_b = Xty + b * D;\n if (tid < D) {\n Xty_b[tid] = Xty_s[tid];\n }\n}\n\nstd::tuple<torch::Tensor, torch::Tensor> compute_xtx_and_xty_cuda(\n torch::Tensor X, torch::Tensor y, float prior_prec, float noise_prec\n) {\n int B = X.size(0);\n int N = X.size(1);\n \n auto Lambda = torch::empty({B, D, D}, X.options());\n auto Xty = torch::empty({B, D, 1}, X.options());\n \n compute_xtx_and_xty_kernel<<<B, BLOCK_SIZE>>>(\n X.data_ptr<float>(),\n y.data_ptr<float>(),\n Lambda.data_ptr<float>(),\n Xty.data_ptr<float>(),\n B, N, prior_prec, noise_prec\n );\n \n return std::make_tuple(Lambda, Xty);\n}\n\"\"\"\n\ncpp_source = r\"\"\"\nstd::tuple<torch::Tensor, torch::Tensor> compute_xtx_and_xty_cuda(torch::Tensor X, torch::Tensor y, float prior_prec, float noise_prec);\ntorch::Tensor compute_pred_mean_cuda(torch::Tensor X_test, torch::Tensor mu_post);\ntorch::Tensor compute_pred_var_fused_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec);\n\"\"\"\n\n# Load the extension\nbayesian_ops = load_inline(\n name='bayesian_linear_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['compute_xtx_and_xty_cuda', 'compute_pred_mean_cuda', 'compute_pred_var_fused_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3', '--use_fast_math']\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Bayesian linear regression with a conjugate Gaussian prior - optimized CUDA version.\n Uses custom kernels for the key operations:\n 1. Fused X^T@X and X^T@y computation (shares X in shared memory)\n 2. Fused predictive variance computation\n \"\"\"\n\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(\n self, X: torch.Tensor, y: torch.Tensor, X_test: torch.Tensor\n ) -> tuple:\n \"\"\"\n Args:\n X: (B, N, D) - training features\n y: (B, N, 1) - training targets\n X_test: (B, M, D) - test features\n Returns:\n (pred_mean, pred_var):\n pred_mean: (B, M, 1)\n pred_var: (B, M)\n \"\"\"\n D = X.shape[-1]\n \n # Ensure contiguous float32 tensors\n X = X.contiguous().float()\n y = y.contiguous().float()\n X_test = X_test.contiguous().float()\n \n # Fused kernel: compute X^T@X (Lambda_post scaled) and X^T@y (scaled)\n # Returns Lambda_post and Xty both scaled appropriately\n Lambda_post, rhs = bayesian_ops.compute_xtx_and_xty_cuda(\n X, y.squeeze(-1), self.prior_precision, self.noise_precision\n )\n rhs = rhs.unsqueeze(-1) # (B, D, 1)\n \n # Cholesky decomposition and solve (use PyTorch optimized routines)\n L = torch.linalg.cholesky(Lambda_post)\n mu_post = torch.cholesky_solve(rhs, L)\n \n # Predictive mean using custom kernel\n pred_mean = bayesian_ops.compute_pred_mean_cuda(X_test, mu_post.squeeze(-1))\n pred_mean = pred_mean.unsqueeze(-1) # (B, M, 1)\n \n # Predictive variance using fused kernel\n Sigma_post = torch.cholesky_inverse(L)\n pred_var = bayesian_ops.compute_pred_var_fused_cuda(\n X_test, Sigma_post, 1.0 / self.noise_precision\n )\n \n return pred_mean, pred_var" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: correctness check did not pass (? trials). runtime_error: Incompatible matrix sizes for cholesky_solve: each A matrix is 16 by 16 but each b matrix is 1 by 1
final submitted kernel (498 lines, 15,336 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

# Compile custom CUDA kernels at module load time
cuda_source = r"""
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>

// Constants for our problem
constexpr int BLOCK_SIZE = 256;
constexpr int D = 16;  // Input dimension

// Fused kernel: X^T @ X with scaling and adding diagonal
// Each block handles one batch element, computes DxD matrix
__global__ void compute_lambda_post_kernel(
    const float* __restrict__ X,  // (B, N, D)
    float* __restrict__ Lambda,    // (B, D, D)
    int B, int N,
    float prior_prec, float noise_prec
) {
    int b = blockIdx.x;
    int tid = threadIdx.x;
    
    // Shared memory for X tile and accumulation
    __shared__ float X_tile[256][16];  // Store X[b, :, :] transposed
    __shared__ float Lambda_shared[16][16];
    
    // Initialize Lambda_shared to zero
    for (int i = tid; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        Lambda_shared[row][col] = 0.0f;
    }
    __syncthreads();
    
    // Load X[b, :, :] into shared memory (transposed for coalesced access)
    const float* X_b = X + b * N * D;
    
    // Each thread processes multiple rows
    for (int n = tid; n < N; n += blockDim.x) {
        for (int d = 0; d < D; d++) {
            X_tile[n][d] = X_b[n * D + d];
        }
    }
    __syncthreads();
    
    // Compute X^T @ X using all threads
    // Each thread computes part of the result
    for (int row = 0; row < D; row++) {
        for (int col = row; col < D; col++) {
            float sum = 0.0f;
            for (int n = tid; n < N; n += blockDim.x) {
                sum += X_tile[n][row] * X_tile[n][col];
            }
            // Block-wide reduction
            for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {
                sum += __shfl_down_sync(0xffffffff, sum, offset);
            }
            if (tid == 0) {
                Lambda_shared[row][col] = sum * noise_prec;
                Lambda_shared[col][row] = sum * noise_prec;  // Symmetric
            }
            __syncthreads();
        }
    }
    
    // Add diagonal prior
    if (tid < D) {
        Lambda_shared[tid][tid] += prior_prec;
    }
    __syncthreads();
    
    // Write result to global memory
    float* Lambda_b = Lambda + b * D * D;
    for (int i = tid; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        Lambda_b[row * D + col] = Lambda_shared[row][col];
    }
}

// Fused kernel: compute X^T @ y
__global__ void compute_xt_y_kernel(
    const float* __restrict__ X,  // (B, N, D)
    const float* __restrict__ y,  // (B, N, 1)
    float* __restrict__ Xty,       // (B, D, 1)
    int B, int N, float noise_prec
) {
    int b = blockIdx.x;
    int d = threadIdx.x;
    
    if (d >= D) return;
    
    const float* X_b = X + b * N * D;
    const float* y_b = y + b * N;
    
    float sum = 0.0f;
    for (int n = 0; n < N; n++) {
        sum += X_b[n * D + d] * y_b[n];
    }
    
    Xty[b * D + d] = sum * noise_prec;
}

// Fused kernel: compute predictive variance
// pred_var = sum_d sum_k (X_test * Sigma_post * X_test^T) + 1/noise_precision
__global__ void compute_pred_var_kernel(
    const float* __restrict__ X_test,     // (B, M, D)
    const float* __restrict__ Sigma_post, // (B, D, D)
    float* __restrict__ pred_var,          // (B, M)
    int B, int M, float inv_noise_prec
) {
    int b = blockIdx.y;
    int m = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (m >= M) return;
    
    const float* X_test_m = X_test + b * M * D + m * D;
    const float* Sigma = Sigma_post + b * D * D;
    
    float var = 0.0f;
    for (int d1 = 0; d1 < D; d1++) {
        float sum = 0.0f;
        for (int d2 = 0; d2 < D; d2++) {
            sum += Sigma[d1 * D + d2] * X_test_m[d2];
        }
        var += X_test_m[d1] * sum;
    }
    
    pred_var[b * M + m] = var + inv_noise_prec;
}

// Fused kernel for X_test @ mu_post (predictive mean)
__global__ void compute_pred_mean_kernel(
    const float* __restrict__ X_test,  // (B, M, D)
    const float* __restrict__ mu_post, // (B, D, 1)
    float* __restrict__ pred_mean,      // (B, M, 1)
    int B, int M
) {
    int b = blockIdx.y;
    int m = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (m >= M) return;
    
    const float* X_test_m = X_test + b * M * D + m * D;
    const float* mu = mu_post + b * D;
    
    float sum = 0.0f;
    for (int d = 0; d < D; d++) {
        sum += X_test_m[d] * mu[d];
    }
    
    pred_mean[b * M + m] = sum;
}

// Host functions
torch::Tensor compute_lambda_post_cuda(torch::Tensor X, float prior_prec, float noise_prec) {
    int B = X.size(0);
    int N = X.size(1);
    // int D = X.size(2);  // Known to be 16
    
    auto Lambda = torch::empty({B, D, D}, X.options());
    
    compute_lambda_post_kernel<<<B, BLOCK_SIZE>>>(
        X.data_ptr<float>(),
        Lambda.data_ptr<float>(),
        B, N, prior_prec, noise_prec
    );
    
    return Lambda;
}

torch::Tensor compute_xt_y_cuda(torch::Tensor X, torch::Tensor y, float noise_prec) {
    int B = X.size(0);
    int N = X.size(1);
    
    auto Xty = torch::empty({B, D, 1}, X.options());
    
    compute_xt_y_kernel<<<B, 32>>>(
        X.data_ptr<float>(),
        y.data_ptr<float>(),
        Xty.data_ptr<float>(),
        B, N, noise_prec
    );
    
    return Xty;
}

torch::Tensor compute_pred_mean_cuda(torch::Tensor X_test, torch::Tensor mu_post) {
    int B = X_test.size(0);
    int M = X_test.size(1);
    
    auto pred_mean = torch::empty({B, M, 1}, X_test.options());
    
    int threads = 64;
    int blocks_m = (M + threads - 1) / threads;
    dim3 grid(blocks_m, B);
    
    compute_pred_mean_kernel<<<grid, threads>>>(
        X_test.data_ptr<float>(),
        mu_post.data_ptr<float>(),
        pred_mean.data_ptr<float>(),
        B, M
    );
    
    return pred_mean;
}

torch::Tensor compute_pred_var_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec) {
    int B = X_test.size(0);
    int M = X_test.size(1);
    
    auto pred_var = torch::empty({B, M}, X_test.options());
    
    int threads = 64;
    int blocks_m = (M + threads - 1) / threads;
    dim3 grid(blocks_m, B);
    
    compute_pred_var_kernel<<<grid, threads>>>(
        X_test.data_ptr<float>(),
        Sigma_post.data_ptr<float>(),
        pred_var.data_ptr<float>(),
        B, M, inv_noise_prec
    );
    
    return pred_var;
}

// Fused kernel: X_test @ Sigma_post, stores result for variance computation
// Then compute variance directly
__global__ void compute_pred_var_fused_kernel(
    const float* __restrict__ X_test,     // (B, M, D)
    const float* __restrict__ Sigma_post, // (B, D, D)
    float* __restrict__ pred_var,          // (B, M)
    int B, int M, float inv_noise_prec
) {
    // Each block handles one (b, m) pair
    int b = blockIdx.y;
    int m = blockIdx.x;
    
    __shared__ float X_shared[16];  // One row of X_test
    __shared__ float S_shared[16][16];  // Sigma_post
    __shared__ float XS_shared[16];  // X_test @ Sigma_post result for row m
    
    // Collaborative load of X_test[b, m, :]
    for (int d = threadIdx.x; d < D; d += blockDim.x) {
        X_shared[d] = X_test[b * M * D + m * D + d];
    }
    
    // Collaborative load of Sigma_post[b, :, :]
    const float* Sigma = Sigma_post + b * D * D;
    for (int i = threadIdx.x; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        S_shared[row][col] = Sigma[i];
    }
    __syncthreads();
    
    // Compute X_test[m, :] @ Sigma_post
    // Each thread computes one element
    float local_xs[4] = {0.0f, 0.0f, 0.0f, 0.0f};
    for (int d = threadIdx.x; d < D; d += blockDim.x) {
        float sum = 0.0f;
        for (int k = 0; k < D; k++) {
            sum += X_shared[k] * S_shared[k][d];
        }
        if (threadIdx.x < D) {
            XS_shared[d] = sum;
        }
    }
    __syncthreads();
    
    // Compute sum over d of X_shared[d] * XS_shared[d]
    // This is (X @ Sigma @ X^T) for row m
    float dot = 0.0f;
    for (int d = threadIdx.x; d < D; d += blockDim.x) {
        dot += X_shared[d] * XS_shared[d];
    }
    
    // Reduce across threads
    for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {
        dot += __shfl_down_sync(0xffffffff, dot, offset);
    }
    
    if (threadIdx.x == 0) {
        pred_var[b * M + m] = dot + inv_noise_prec;
    }
}

torch::Tensor compute_pred_var_fused_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec) {
    int B = X_test.size(0);
    int M = X_test.size(1);
    
    auto pred_var = torch::empty({B, M}, X_test.options());
    
    dim3 grid(M, B);  // One block per (b, m) pair
    int threads = 32;  // Enough threads for D=16
    
    compute_pred_var_fused_kernel<<<grid, threads>>>(
        X_test.data_ptr<float>(),
        Sigma_post.data_ptr<float>(),
        pred_var.data_ptr<float>(),
        B, M, inv_noise_prec
    );
    
    return pred_var;
}

// Combined kernel that does X^T @ X and X^T @ y together to reuse X in shared memory
__global__ void compute_xtx_and_xty_kernel(
    const float* __restrict__ X,  // (B, N, D)
    const float* __restrict__ y,  // (B, N, 1)
    float* __restrict__ Lambda,    // (B, D, D)
    float* __restrict__ Xty,       // (B, D, 1)
    int B, int N,
    float prior_prec, float noise_prec
) {
    int b = blockIdx.x;
    int tid = threadIdx.x;
    
    __shared__ float X_shared[256][16];  // X transposed in shared memory
    __shared__ float y_shared[256];       // y values
    __shared__ float Lambda_s[16][16];
    __shared__ float Xty_s[16];
    
    const float* X_b = X + b * N * D;
    const float* y_b = y + b * N;
    
    // Initialize outputs
    for (int i = tid; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        Lambda_s[row][col] = 0.0f;
    }
    if (tid < D) {
        Xty_s[tid] = 0.0f;
    }
    __syncthreads();
    
    // Load X and y into shared memory
    for (int n = tid; n < N; n += blockDim.x) {
        y_shared[n] = y_b[n];
        for (int d = 0; d < D; d++) {
            X_shared[n][d] = X_b[n * D + d];
        }
    }
    __syncthreads();
    
    // Compute X^T @ X and X^T @ y
    for (int row = 0; row < D; row++) {
        // Compute X^T @ y for this row
        float xty_sum = 0.0f;
        for (int n = tid; n < N; n += blockDim.x) {
            xty_sum += X_shared[n][row] * y_shared[n];
        }
        for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {
            xty_sum += __shfl_down_sync(0xffffffff, xty_sum, offset);
        }
        if (tid == 0) {
            Xty_s[row] = xty_sum * noise_prec;
        }
        
        // Compute X^T @ X for columns >= row (upper triangle)
        for (int col = row; col < D; col++) {
            float xtx_sum = 0.0f;
            for (int n = tid; n < N; n += blockDim.x) {
                xtx_sum += X_shared[n][row] * X_shared[n][col];
            }
            for (int offset = blockDim.x / 2; offset > 0; offset >>= 1) {
                xtx_sum += __shfl_down_sync(0xffffffff, xtx_sum, offset);
            }
            if (tid == 0) {
                float val = xtx_sum * noise_prec;
                Lambda_s[row][col] = val;
                Lambda_s[col][row] = val;
            }
        }
        __syncthreads();
    }
    
    // Add diagonal prior
    if (tid < D) {
        Lambda_s[tid][tid] += prior_prec;
    }
    __syncthreads();
    
    // Write Lambda to global memory
    float* Lambda_b = Lambda + b * D * D;
    for (int i = tid; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        Lambda_b[row * D + col] = Lambda_s[row][col];
    }
    
    // Write Xty to global memory
    float* Xty_b = Xty + b * D;
    if (tid < D) {
        Xty_b[tid] = Xty_s[tid];
    }
}

std::tuple<torch::Tensor, torch::Tensor> compute_xtx_and_xty_cuda(
    torch::Tensor X, torch::Tensor y, float prior_prec, float noise_prec
) {
    int B = X.size(0);
    int N = X.size(1);
    
    auto Lambda = torch::empty({B, D, D}, X.options());
    auto Xty = torch::empty({B, D, 1}, X.options());
    
    compute_xtx_and_xty_kernel<<<B, BLOCK_SIZE>>>(
        X.data_ptr<float>(),
        y.data_ptr<float>(),
        Lambda.data_ptr<float>(),
        Xty.data_ptr<float>(),
        B, N, prior_prec, noise_prec
    );
    
    return std::make_tuple(Lambda, Xty);
}
"""

cpp_source = r"""
std::tuple<torch::Tensor, torch::Tensor> compute_xtx_and_xty_cuda(torch::Tensor X, torch::Tensor y, float prior_prec, float noise_prec);
torch::Tensor compute_pred_mean_cuda(torch::Tensor X_test, torch::Tensor mu_post);
torch::Tensor compute_pred_var_fused_cuda(torch::Tensor X_test, torch::Tensor Sigma_post, float inv_noise_prec);
"""

# Load the extension
bayesian_ops = load_inline(
    name='bayesian_linear_cuda',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['compute_xtx_and_xty_cuda', 'compute_pred_mean_cuda', 'compute_pred_var_fused_cuda'],
    verbose=False,
    extra_cuda_cflags=['-O3', '--use_fast_math']
)


class ModelNew(nn.Module):
    """
    Bayesian linear regression with a conjugate Gaussian prior - optimized CUDA version.
    Uses custom kernels for the key operations:
    1. Fused X^T@X and X^T@y computation (shares X in shared memory)
    2. Fused predictive variance computation
    """

    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]
        
        # Ensure contiguous float32 tensors
        X = X.contiguous().float()
        y = y.contiguous().float()
        X_test = X_test.contiguous().float()
        
        # Fused kernel: compute X^T@X (Lambda_post scaled) and X^T@y (scaled)
        # Returns Lambda_post and Xty both scaled appropriately
        Lambda_post, rhs = bayesian_ops.compute_xtx_and_xty_cuda(
            X, y.squeeze(-1), self.prior_precision, self.noise_precision
        )
        rhs = rhs.unsqueeze(-1)  # (B, D, 1)
        
        # Cholesky decomposition and solve (use PyTorch optimized routines)
        L = torch.linalg.cholesky(Lambda_post)
        mu_post = torch.cholesky_solve(rhs, L)
        
        # Predictive mean using custom kernel
        pred_mean = bayesian_ops.compute_pred_mean_cuda(X_test, mu_post.squeeze(-1))
        pred_mean = pred_mean.unsqueeze(-1)  # (B, M, 1)
        
        # Predictive variance using fused kernel
        Sigma_post = torch.cholesky_inverse(L)
        pred_var = bayesian_ops.compute_pred_var_fused_cuda(
            X_test, Sigma_post, 1.0 / self.noise_precision
        )
        
        return pred_mean, pred_var