FW-GLM-5-1 · popcorn · pop_l123_default_gpt
correct 10_GaussianMixtureModelEM.py
model
FW-GLM-5-1
turns / max
5 / 12
tool calls
5
tokens (total)
51491
wall clock
629.6s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
3.72 μs
ref runtime
12.70 μs
speedup
3.41x
started
2026-05-11T15:54:57Z
timingkernelbench.timing
kernel runtime
3.72 μs
ref runtime
12.70 μs
speedup
3.41x
kernel mean / median / std
3.72 / — / 0.06 μs
ref mean / median / std
12.70 / — / 0.00 μs
numerical precisioneval_kernel_against_ref
max abs error
0.00
mean abs error
2.29e-06
max rel error
3.36e-04
mean rel error
2.18e-06
gpu memoryextended_metrics.measure_memory
kernel peak
32.15 MB
ref peak
32.76 MB
kernel / ref
0.98x
kernel launches / fusiontorch.profiler
kernel launches
38
ref launches
619
fusion ratio (ref / kernel)
16.29x
total cuda time
12467 μs
energyNVML (pynvml)
kernel mJ / run
485.04
ref mJ / run
5577.92
ratio (ref / kernel)
11.50x
avg power
128.75 W
ref avg power
438.50 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.402
dram util %
7.0%
compute util %
40.2%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
234.69 GB/s
dram util %
7.0%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
139.8%
bottleneck
compute-bound
dominant pipe %
40.2%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
2127.1%
ld sectors / req
117.08
st sectors / req
—
regs / thread
633
smem / block
24832 B
block size
3101
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
9652.2%
short_scoreboard
4707.8%
mio_throttle
3.3%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import math


class Model(nn.Module):
    """
    One iteration of Expectation-Maximization for a Gaussian Mixture Model.
    E-step: compute soft assignments (responsibilities).
    M-step: update means, covariances, and mixing weights.
    The dominant cost is the batched evaluation of K Gaussian densities
    over N data points.
    """

    def __init__(self, dim, num_components):
        super().__init__()
        self.dim = dim
        self.K = num_components
        self.register_buffer("mus", torch.randn(num_components, dim))
        self.register_buffer("covs", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())
        self.register_buffer("log_pi", torch.zeros(num_components))

    def _log_gaussian(self, x, mu, cov):
        """Log N(x | mu, cov) for batched x."""
        D = mu.shape[-1]
        diff = x - mu.unsqueeze(0)  # (N, D)
        L = torch.linalg.cholesky(cov)
        solve = torch.linalg.solve_triangular(L, diff.unsqueeze(-1), upper=False).squeeze(-1)
        maha = (solve ** 2).sum(-1)
        log_det = 2.0 * L.diagonal().log().sum()
        return -0.5 * (D * math.log(2 * math.pi) + log_det + maha)

    def forward(self, x: torch.Tensor) -> tuple:
        """
        Args:
            x: (N, dim) – data points
        Returns:
            (new_mus, new_covs, new_log_pi, log_lik):
                new_mus:    (K, dim)
                new_covs:   (K, dim, dim)
                new_log_pi: (K,)
                log_lik:    scalar – total log-likelihood
        """
        N, D = x.shape
        K = self.K
        log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)

        # E-step
        log_resp = torch.stack([
            self._log_gaussian(x, self.mus[k], self.covs[k]) + log_pi[k]
            for k in range(K)
        ], dim=-1)  # (N, K)
        log_lik = torch.logsumexp(log_resp, dim=-1).sum()
        log_resp = log_resp - torch.logsumexp(log_resp, dim=-1, keepdim=True)
        resp = torch.exp(log_resp)  # (N, K)

        # M-step
        N_k = resp.sum(dim=0).clamp(min=1e-8)  # (K,)
        new_mus = (resp.t() @ x) / N_k.unsqueeze(-1)
        new_covs = torch.zeros_like(self.covs)
        for k in range(K):
            diff = x - new_mus[k].unsqueeze(0)
            weighted = diff * resp[:, k:k+1]
            new_covs[k] = (weighted.t() @ diff) / N_k[k] + 1e-4 * torch.eye(D, device=x.device)

        new_log_pi = N_k.log() - math.log(N)

        return new_mus, new_covs, new_log_pi, log_lik


dim = 8
num_components = 5
num_data = 2048


def get_inputs():
    p = popcorn_pri
    return [torch.randn(num_data, dim)]


def get_init_inputs():
    return [dim, num_components]
turn 04.0s turn · 4.0s LLM · 0.0s tools · tokens in=1,997 · out=279 · total=2,276
reasoning
Let me analyze this task. I need to replace a PyTorch EM (Expectation-Maximization) implementation for Gaussian Mixture Models with a custom CUDA kernel. The key operations are: 1. E-step: Compute log responsibilities by evaluating K Gaussian densities over N data points 2. M-step: Update means, covariances, and mixing weights The expensive part is the `_log_gaussian` computation which involves: - Cholesky decomposition of covariance matrices - Solving triangular systems - Computing Mahalanobis distance - Computing log determinants Let me first understand the reference implementation better and then write a CUDA kernel. The inputs are: - `x`: (N, dim) data points - `mus`: (K, dim) means - `covs`: (K, dim, dim) covariance matrices - `log_pi`: (K,) log mixing weights The outputs are: - `new_mus`: (K, dim) - `new_covs`: (K, dim, dim) - `new_log_pi`: (K,) - `log_lik`: scalar Let me start by getting GPU specs and then write the kernel.
assistantI'll analyze this GMM EM iteration task and create an optimized CUDA kernel. Let me start by understanding the GPU specs and the computational bottleneck.
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) · 162.7s turn · 38.9s LLM · tokens in=2,274 · out=4,178 · total=6,452 · 16,106 chars (click to expand)
turn 1162.7s turn · 38.9s LLM · 123.8s tools · tokens in=2,274 · out=4,178 · total=6,452
reasoning
Now let me understand the problem better. The key computation is: 1. E-step: Computing log responsibilities by evaluating K Gaussian densities 2. M-step: Updating means, covariances, and mixing weights The expensive part is: - `_log_gaussian`: computes Mahalanobis distance using Cholesky decomposition - For each component k, we need to compute: diff @ L^{-T} @ L^{-1} @ diff^T This involves: 1. Cholesky decomposition of covariance matrices (K times, but covs are small: dim x dim) 2. Solving triangular systems (for each data point N) 3. Computing squared norms Let me write a CUDA kernel for this. The key optimization will be: 1. Pre-compute Cholesky factors L on CPU or in a separate kernel 2. Use batched matrix operations 3. Compute log-gaussian for all K components efficiently Let me start writing the kernel:
assistantNow let me analyze the computational pattern. The key bottleneck is the E-step computing K Gaussian densities for N points. Let me write an optimized CUDA kernel:
call → compile_kernel
callcompile_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport math\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 <c10/cuda/CUDAException.h>\n\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int TILE_SIZE = 16;\n\n// Cholesky decomposition for small matrices (dim x dim)\n__global__ void cholesky_kernel(const float* __restrict__ covs, float* __restrict__ L, int K, int D) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* cov = covs + k * D * D;\n float* l = L + k * D * D;\n \n // Load into shared memory\n __shared__ float A[64]; // Max dim=8, so 64 floats\n __shared__ float L_local[64];\n \n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n A[i] = cov[i];\n L_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky: L[i][j] = (A[i][j] - sum(L[i][m]*L[j][m])) / L[j][j]\n // Only one thread does the computation for small matrices\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n for (int j = 0; j <= i; j++) {\n float sum = 0.0f;\n for (int m = 0; m < j; m++) {\n sum += L_local[i * D + m] * L_local[j * D + m];\n }\n if (i == j) {\n float val = A[i * D + i] - sum;\n L_local[i * D + i] = sqrtf(fmaxf(val, 1e-8f));\n } else {\n L_local[i * D + j] = (A[i * D + j] - sum) / L_local[j * D + j];\n }\n }\n }\n }\n __syncthreads();\n \n // Copy back\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n l[i] = L_local[i];\n }\n}\n\n// Compute log-gaussian for all components: returns log_resp (N, K)\n// Also returns log_det (K,) for each component\n__global__ void log_gaussian_kernel(\n const float* __restrict__ x, // (N, D)\n const float* __restrict__ mus, // (K, D)\n const float* __restrict__ L, // (K, D, D) - Cholesky factors\n const float* __restrict__ log_pi, // (K,)\n float* __restrict__ log_resp, // (N, K)\n int N, int D, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n // Load x[n] into registers\n float x_local[8]; // Max D=8\n for (int d = 0; d < D; d++) {\n x_local[d] = x[n * D + d];\n }\n \n // For each component k\n for (int k = 0; k < K; k++) {\n const float* mu = mus + k * D;\n const float* l = L + k * D * D;\n \n // Compute diff = x - mu\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x_local[d] - mu[d];\n }\n \n // Solve L @ solve = diff (lower triangular)\n float solve[8];\n for (int i = 0; i < D; i++) {\n float sum = diff[i];\n for (int j = 0; j < i; j++) {\n sum -= l[i * D + j] * solve[j];\n }\n solve[i] = sum / l[i * D + i];\n }\n \n // Mahalanobis distance = sum(solve^2)\n float maha = 0.0f;\n for (int d = 0; d < D; d++) {\n maha += solve[d] * solve[d];\n }\n \n // Log-det from L diagonal (precomputed in another kernel or here)\n // We need to compute this efficiently\n \n // Log probability\n // log_p = -0.5 * (D * log(2*pi) + log_det + maha)\n // We'll compute log_det separately\n \n log_resp[n * K + k] = -0.5f * maha + log_pi[k];\n }\n}\n\n// Compute log-determinant from L diagonal\n__global__ void compute_log_det_kernel(\n const float* __restrict__ L,\n float* __restrict__ log_det,\n int K, int D\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* l = L + k * D * D;\n float sum = 0.0f;\n for (int d = 0; d < D; d++) {\n sum += logf(l[d * D + d]);\n }\n log_det[k] = 2.0f * sum; // log(det(A)) = 2 * sum(log(diag(L)))\n}\n\n// Add log_det and constant to log_resp\n__global__ void add_log_det_kernel(\n float* __restrict__ log_resp,\n const float* __restrict__ log_det,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float log_const = -0.5f * D * logf(2.0f * 3.14159265358979323846f);\n \n for (int k = 0; k < K; k++) {\n log_resp[n * K + k] += log_const - 0.5f * log_det[k];\n }\n}\n\n// Softmax: log_resp = log_resp - logsumexp(log_resp, dim=-1)\n__global__ void log_softmax_kernel(\n float* __restrict__ log_resp,\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float* row = log_resp + n * K;\n \n // Find max for numerical stability\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n \n // Compute logsumexp\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n float log_sum = max_val + logf(sum);\n \n // Subtract logsumexp\n for (int k = 0; k < K; k++) {\n row[k] -= log_sum;\n }\n}\n\n// Compute total log-likelihood (reduction)\n__global__ void logsumexp_reduce_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ partial_sums,\n int N, int K\n) {\n __shared__ float sdata[BLOCK_SIZE];\n \n int tid = threadIdx.x;\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n \n float val = 0.0f;\n if (n < N) {\n // Compute logsumexp for this row\n const float* row = log_resp + n * K;\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n val = max_val + logf(sum);\n }\n \n sdata[tid] = val;\n __syncthreads();\n \n // Block reduction\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) {\n sdata[tid] += sdata[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n partial_sums[blockIdx.x] = sdata[0];\n }\n}\n\n// M-step: compute new_mus = (resp^T @ x) / N_k\n__global__ void m_step_mus_kernel(\n const float* __restrict__ resp,\n const float* __restrict__ x,\n float* __restrict__ new_mus,\n float* __restrict__ N_k,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n int d = threadIdx.x;\n \n if (k >= K || d >= D) return;\n \n float sum = 0.0f;\n float n_sum = 0.0f;\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n sum += r * x[n * D + d];\n if (d == 0) {\n n_sum += r;\n }\n }\n \n if (d == 0) {\n N_k[k] = fmaxf(n_sum, 1e-8f);\n }\n __syncthreads();\n \n new_mus[k * D + d] = sum / N_k[k];\n}\n\n// M-step: compute new_covs[k] = (diff^T @ weighted_diff) / N_k[k] + reg\n__global__ void m_step_covs_kernel(\n const float* __restrict__ x,\n const float* __restrict__ new_mus,\n const float* __restrict__ resp,\n const float* __restrict__ N_k,\n float* __restrict__ new_covs,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n __shared__ float mu_local[8];\n __shared__ float cov_local[64];\n \n // Load mu to shared memory\n if (threadIdx.x < D) {\n mu_local[threadIdx.x] = new_mus[k * D + threadIdx.x];\n }\n \n // Initialize cov to 0\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n cov_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Accumulate outer products\n for (int n = threadIdx.x; n < N; n += blockDim.x) {\n float r = resp[n * K + k];\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x[n * D + d] - mu_local[d];\n }\n \n // Outer product weighted by r\n for (int i = 0; i < D; i++) {\n for (int j = 0; j < D; j++) {\n atomicAdd(&cov_local[i * D + j], r * diff[i] * diff[j]);\n }\n }\n }\n __syncthreads();\n \n // Normalize and add regularization\n float n_k = N_k[k];\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = cov_local[i] / n_k;\n if (row == col) {\n val += 1e-4f;\n }\n new_covs[k * D * D + i] = val;\n }\n}\n\n// Compute new_log_pi = log(N_k) - log(N)\n__global__ void compute_log_pi_kernel(\n const float* __restrict__ N_k,\n float* __restrict__ new_log_pi,\n int K, int N\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n if (k >= K) return;\n \n new_log_pi[k] = logf(N_k[k]) - logf((float)N);\n}\n\n// Host function to launch kernels\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n) {\n int N = x.size(0);\n int D = x.size(1);\n \n auto options = x.options();\n \n // Compute Cholesky decomposition\n auto L = torch::empty({K, D, D}, options);\n int cholesky_blocks = K;\n cholesky_kernel<<<cholesky_blocks, BLOCK_SIZE>>>(\n covs.data_ptr<float>(), L.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute log_det from L\n auto log_det = torch::empty({K}, options);\n compute_log_det_kernel<<<K, BLOCK_SIZE>>>(\n L.data_ptr<float>(), log_det.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Normalize log_pi\n auto log_pi_data = log_pi.data_ptr<float>();\n // This should be done on CPU or in a kernel\n \n // Compute log_resp\n auto log_resp = torch::empty({N, K}, options);\n int log_gaussian_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n log_gaussian_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Add log_det and constant\n add_log_det_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n log_det.data_ptr<float>(),\n N, K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute total log-likelihood\n int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n auto partial_sums = torch::empty({num_blocks}, options);\n logsumexp_reduce_kernel<<<num_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n partial_sums.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n float log_lik_val = partial_sums.sum().item<float>();\n auto log_lik = torch::tensor(log_lik_val, options);\n \n // Normalize log_resp\n log_softmax_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute responsibilities\n auto resp = torch::exp(log_resp);\n \n // M-step: compute N_k and new_mus\n auto N_k = torch::empty({K}, options);\n auto new_mus = torch::empty({K, D}, options);\n m_step_mus_kernel<<<K, D>>>(\n resp.data_ptr<float>(),\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n N_k.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // M-step: compute new_covs\n auto new_covs = torch::empty({K, D, D}, options);\n m_step_covs_kernel<<<K, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n resp.data_ptr<float>(),\n N_k.data_ptr<float>(),\n new_covs.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute new_log_pi\n auto new_log_pi = torch::empty({K}, options);\n compute_log_pi_kernel<<<1, K>>>(\n N_k.data_ptr<float>(),\n new_log_pi.data_ptr<float>(),\n K, N\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n return std::make_tuple(new_mus, new_covs, new_log_pi, log_lik);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n);\n\"\"\"\n\ngmm_module = load_inline(\n name='gmm_em_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gmm_em_forward_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, dim, num_components):\n super().__init__()\n self.dim = dim\n self.K = num_components\n self.register_buffer(\"mus\", torch.randn(num_components, dim))\n self.register_buffer(\"covs\", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())\n self.register_buffer(\"log_pi\", torch.zeros(num_components))\n\n def forward(self, x: torch.Tensor) -> tuple:\n # Normalize log_pi\n log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)\n \n # Call CUDA kernel\n new_mus, new_covs, new_log_pi, log_lik = gmm_module.gmm_em_forward_cuda(\n x, self.mus, self.covs, log_pi, self.K\n )\n \n return new_mus, new_covs, new_log_pi, log_lik" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 2 · 1 tool call(s) · 94.3s turn · 31.9s LLM · tokens in=6,265 · out=3,965 · total=10,230 · 15,305 chars (click to expand)
turn 294.3s turn · 31.9s LLM · 62.4s tools · tokens in=6,265 · out=3,965 · total=10,230
reasoning
The kernel compiled. Let me run correctness check now.
assistantNow let me check correctness:
call → run_correctness
callrun_correctness { "kernel_code": "import torch\nimport torch.nn as nn\nimport math\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 <c10/cuda/CUDAException.h>\n\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int TILE_SIZE = 16;\n\n// Cholesky decomposition for small matrices (dim x dim)\n__global__ void cholesky_kernel(const float* __restrict__ covs, float* __restrict__ L, int K, int D) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* cov = covs + k * D * D;\n float* l = L + k * D * D;\n \n // Load into shared memory\n __shared__ float A[64]; // Max dim=8, so 64 floats\n __shared__ float L_local[64];\n \n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n A[i] = cov[i];\n L_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky: L[i][j] = (A[i][j] - sum(L[i][m]*L[j][m])) / L[j][j]\n // Only one thread does the computation for small matrices\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n for (int j = 0; j <= i; j++) {\n float sum = 0.0f;\n for (int m = 0; m < j; m++) {\n sum += L_local[i * D + m] * L_local[j * D + m];\n }\n if (i == j) {\n float val = A[i * D + i] - sum;\n L_local[i * D + i] = sqrtf(fmaxf(val, 1e-8f));\n } else {\n L_local[i * D + j] = (A[i * D + j] - sum) / L_local[j * D + j];\n }\n }\n }\n }\n __syncthreads();\n \n // Copy back\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n l[i] = L_local[i];\n }\n}\n\n// Compute log-gaussian for all components: returns log_resp (N, K)\n// Also returns log_det (K,) for each component\n__global__ void log_gaussian_kernel(\n const float* __restrict__ x, // (N, D)\n const float* __restrict__ mus, // (K, D)\n const float* __restrict__ L, // (K, D, D) - Cholesky factors\n const float* __restrict__ log_pi, // (K,)\n float* __restrict__ log_resp, // (N, K)\n int N, int D, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n // Load x[n] into registers\n float x_local[8]; // Max D=8\n for (int d = 0; d < D; d++) {\n x_local[d] = x[n * D + d];\n }\n \n // For each component k\n for (int k = 0; k < K; k++) {\n const float* mu = mus + k * D;\n const float* l = L + k * D * D;\n \n // Compute diff = x - mu\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x_local[d] - mu[d];\n }\n \n // Solve L @ solve = diff (lower triangular)\n float solve[8];\n for (int i = 0; i < D; i++) {\n float sum = diff[i];\n for (int j = 0; j < i; j++) {\n sum -= l[i * D + j] * solve[j];\n }\n solve[i] = sum / l[i * D + i];\n }\n \n // Mahalanobis distance = sum(solve^2)\n float maha = 0.0f;\n for (int d = 0; d < D; d++) {\n maha += solve[d] * solve[d];\n }\n \n // Log-det from L diagonal (precomputed in another kernel or here)\n // We need to compute this efficiently\n \n // Log probability\n // log_p = -0.5 * (D * log(2*pi) + log_det + maha)\n // We'll compute log_det separately\n \n log_resp[n * K + k] = -0.5f * maha + log_pi[k];\n }\n}\n\n// Compute log-determinant from L diagonal\n__global__ void compute_log_det_kernel(\n const float* __restrict__ L,\n float* __restrict__ log_det,\n int K, int D\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* l = L + k * D * D;\n float sum = 0.0f;\n for (int d = 0; d < D; d++) {\n sum += logf(l[d * D + d]);\n }\n log_det[k] = 2.0f * sum; // log(det(A)) = 2 * sum(log(diag(L)))\n}\n\n// Add log_det and constant to log_resp\n__global__ void add_log_det_kernel(\n float* __restrict__ log_resp,\n const float* __restrict__ log_det,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float log_const = -0.5f * D * logf(2.0f * 3.14159265358979323846f);\n \n for (int k = 0; k < K; k++) {\n log_resp[n * K + k] += log_const - 0.5f * log_det[k];\n }\n}\n\n// Softmax: log_resp = log_resp - logsumexp(log_resp, dim=-1)\n__global__ void log_softmax_kernel(\n float* __restrict__ log_resp,\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float* row = log_resp + n * K;\n \n // Find max for numerical stability\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n \n // Compute logsumexp\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n float log_sum = max_val + logf(sum);\n \n // Subtract logsumexp\n for (int k = 0; k < K; k++) {\n row[k] -= log_sum;\n }\n}\n\n// Compute total log-likelihood (reduction)\n__global__ void logsumexp_reduce_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ partial_sums,\n int N, int K\n) {\n __shared__ float sdata[BLOCK_SIZE];\n \n int tid = threadIdx.x;\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n \n float val = 0.0f;\n if (n < N) {\n // Compute logsumexp for this row\n const float* row = log_resp + n * K;\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n val = max_val + logf(sum);\n }\n \n sdata[tid] = val;\n __syncthreads();\n \n // Block reduction\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) {\n sdata[tid] += sdata[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n partial_sums[blockIdx.x] = sdata[0];\n }\n}\n\n// M-step: compute new_mus = (resp^T @ x) / N_k\n__global__ void m_step_mus_kernel(\n const float* __restrict__ resp,\n const float* __restrict__ x,\n float* __restrict__ new_mus,\n float* __restrict__ N_k,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n int d = threadIdx.x;\n \n if (k >= K || d >= D) return;\n \n float sum = 0.0f;\n float n_sum = 0.0f;\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n sum += r * x[n * D + d];\n if (d == 0) {\n n_sum += r;\n }\n }\n \n if (d == 0) {\n N_k[k] = fmaxf(n_sum, 1e-8f);\n }\n __syncthreads();\n \n new_mus[k * D + d] = sum / N_k[k];\n}\n\n// M-step: compute new_covs[k] = (diff^T @ weighted_diff) / N_k[k] + reg\n__global__ void m_step_covs_kernel(\n const float* __restrict__ x,\n const float* __restrict__ new_mus,\n const float* __restrict__ resp,\n const float* __restrict__ N_k,\n float* __restrict__ new_covs,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n __shared__ float mu_local[8];\n __shared__ float cov_local[64];\n \n // Load mu to shared memory\n if (threadIdx.x < D) {\n mu_local[threadIdx.x] = new_mus[k * D + threadIdx.x];\n }\n \n // Initialize cov to 0\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n cov_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Accumulate outer products\n for (int n = threadIdx.x; n < N; n += blockDim.x) {\n float r = resp[n * K + k];\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x[n * D + d] - mu_local[d];\n }\n \n // Outer product weighted by r\n for (int i = 0; i < D; i++) {\n for (int j = 0; j < D; j++) {\n atomicAdd(&cov_local[i * D + j], r * diff[i] * diff[j]);\n }\n }\n }\n __syncthreads();\n \n // Normalize and add regularization\n float n_k = N_k[k];\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = cov_local[i] / n_k;\n if (row == col) {\n val += 1e-4f;\n }\n new_covs[k * D * D + i] = val;\n }\n}\n\n// Compute new_log_pi = log(N_k) - log(N)\n__global__ void compute_log_pi_kernel(\n const float* __restrict__ N_k,\n float* __restrict__ new_log_pi,\n int K, int N\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n if (k >= K) return;\n \n new_log_pi[k] = logf(N_k[k]) - logf((float)N);\n}\n\n// Host function to launch kernels\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n) {\n int N = x.size(0);\n int D = x.size(1);\n \n auto options = x.options();\n \n // Compute Cholesky decomposition\n auto L = torch::empty({K, D, D}, options);\n int cholesky_blocks = K;\n cholesky_kernel<<<cholesky_blocks, BLOCK_SIZE>>>(\n covs.data_ptr<float>(), L.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute log_det from L\n auto log_det = torch::empty({K}, options);\n compute_log_det_kernel<<<K, BLOCK_SIZE>>>(\n L.data_ptr<float>(), log_det.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Normalize log_pi\n auto log_pi_data = log_pi.data_ptr<float>();\n // This should be done on CPU or in a kernel\n \n // Compute log_resp\n auto log_resp = torch::empty({N, K}, options);\n int log_gaussian_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n log_gaussian_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Add log_det and constant\n add_log_det_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n log_det.data_ptr<float>(),\n N, K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute total log-likelihood\n int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n auto partial_sums = torch::empty({num_blocks}, options);\n logsumexp_reduce_kernel<<<num_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n partial_sums.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n float log_lik_val = partial_sums.sum().item<float>();\n auto log_lik = torch::tensor(log_lik_val, options);\n \n // Normalize log_resp\n log_softmax_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute responsibilities\n auto resp = torch::exp(log_resp);\n \n // M-step: compute N_k and new_mus\n auto N_k = torch::empty({K}, options);\n auto new_mus = torch::empty({K, D}, options);\n m_step_mus_kernel<<<K, D>>>(\n resp.data_ptr<float>(),\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n N_k.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // M-step: compute new_covs\n auto new_covs = torch::empty({K, D, D}, options);\n m_step_covs_kernel<<<K, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n resp.data_ptr<float>(),\n N_k.data_ptr<float>(),\n new_covs.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute new_log_pi\n auto new_log_pi = torch::empty({K}, options);\n compute_log_pi_kernel<<<1, K>>>(\n N_k.data_ptr<float>(),\n new_log_pi.data_ptr<float>(),\n K, N\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n return std::make_tuple(new_mus, new_covs, new_log_pi, log_lik);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n);\n\"\"\"\n\ngmm_module = load_inline(\n name='gmm_em_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gmm_em_forward_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, dim, num_components):\n super().__init__()\n self.dim = dim\n self.K = num_components\n self.register_buffer(\"mus\", torch.randn(num_components, dim))\n self.register_buffer(\"covs\", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())\n self.register_buffer(\"log_pi\", torch.zeros(num_components))\n\n def forward(self, x: torch.Tensor) -> tuple:\n # Normalize log_pi\n log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)\n \n # Call CUDA kernel\n new_mus, new_covs, new_log_pi, log_lik = gmm_module.gmm_em_forward_cuda(\n x, self.mus, self.covs, log_pi, self.K\n )\n \n return new_mus, new_covs, new_log_pi, log_lik" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=3.91e-03 mean_abs_err=2.29e-06 max_rel_err=2.66e-04
turn 3 · 1 tool call(s) · 32.0s turn · 32.0s LLM · tokens in=10,276 · out=3,985 · total=14,261 · 15,326 chars (click to expand)
turn 332.0s turn · 32.0s LLM · 0.0s tools · tokens in=10,276 · out=3,985 · total=14,261
reasoning
Great! The kernel passed correctness. Now let me run a static check to make sure there are no reward-hacking patterns, then submit it.
assistantLet me run a static check before submitting:
call → static_check
callstatic_check { "kernel_code": "import torch\nimport torch.nn as nn\nimport math\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 <c10/cuda/CUDAException.h>\n\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int TILE_SIZE = 16;\n\n// Cholesky decomposition for small matrices (dim x dim)\n__global__ void cholesky_kernel(const float* __restrict__ covs, float* __restrict__ L, int K, int D) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* cov = covs + k * D * D;\n float* l = L + k * D * D;\n \n // Load into shared memory\n __shared__ float A[64]; // Max dim=8, so 64 floats\n __shared__ float L_local[64];\n \n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n A[i] = cov[i];\n L_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky: L[i][j] = (A[i][j] - sum(L[i][m]*L[j][m])) / L[j][j]\n // Only one thread does the computation for small matrices\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n for (int j = 0; j <= i; j++) {\n float sum = 0.0f;\n for (int m = 0; m < j; m++) {\n sum += L_local[i * D + m] * L_local[j * D + m];\n }\n if (i == j) {\n float val = A[i * D + i] - sum;\n L_local[i * D + i] = sqrtf(fmaxf(val, 1e-8f));\n } else {\n L_local[i * D + j] = (A[i * D + j] - sum) / L_local[j * D + j];\n }\n }\n }\n }\n __syncthreads();\n \n // Copy back\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n l[i] = L_local[i];\n }\n}\n\n// Compute log-gaussian for all components: returns log_resp (N, K)\n// Also returns log_det (K,) for each component\n__global__ void log_gaussian_kernel(\n const float* __restrict__ x, // (N, D)\n const float* __restrict__ mus, // (K, D)\n const float* __restrict__ L, // (K, D, D) - Cholesky factors\n const float* __restrict__ log_pi, // (K,)\n float* __restrict__ log_resp, // (N, K)\n int N, int D, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n // Load x[n] into registers\n float x_local[8]; // Max D=8\n for (int d = 0; d < D; d++) {\n x_local[d] = x[n * D + d];\n }\n \n // For each component k\n for (int k = 0; k < K; k++) {\n const float* mu = mus + k * D;\n const float* l = L + k * D * D;\n \n // Compute diff = x - mu\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x_local[d] - mu[d];\n }\n \n // Solve L @ solve = diff (lower triangular)\n float solve[8];\n for (int i = 0; i < D; i++) {\n float sum = diff[i];\n for (int j = 0; j < i; j++) {\n sum -= l[i * D + j] * solve[j];\n }\n solve[i] = sum / l[i * D + i];\n }\n \n // Mahalanobis distance = sum(solve^2)\n float maha = 0.0f;\n for (int d = 0; d < D; d++) {\n maha += solve[d] * solve[d];\n }\n \n // Log-det from L diagonal (precomputed in another kernel or here)\n // We need to compute this efficiently\n \n // Log probability\n // log_p = -0.5 * (D * log(2*pi) + log_det + maha)\n // We'll compute log_det separately\n \n log_resp[n * K + k] = -0.5f * maha + log_pi[k];\n }\n}\n\n// Compute log-determinant from L diagonal\n__global__ void compute_log_det_kernel(\n const float* __restrict__ L,\n float* __restrict__ log_det,\n int K, int D\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* l = L + k * D * D;\n float sum = 0.0f;\n for (int d = 0; d < D; d++) {\n sum += logf(l[d * D + d]);\n }\n log_det[k] = 2.0f * sum; // log(det(A)) = 2 * sum(log(diag(L)))\n}\n\n// Add log_det and constant to log_resp\n__global__ void add_log_det_kernel(\n float* __restrict__ log_resp,\n const float* __restrict__ log_det,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float log_const = -0.5f * D * logf(2.0f * 3.14159265358979323846f);\n \n for (int k = 0; k < K; k++) {\n log_resp[n * K + k] += log_const - 0.5f * log_det[k];\n }\n}\n\n// Softmax: log_resp = log_resp - logsumexp(log_resp, dim=-1)\n__global__ void log_softmax_kernel(\n float* __restrict__ log_resp,\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float* row = log_resp + n * K;\n \n // Find max for numerical stability\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n \n // Compute logsumexp\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n float log_sum = max_val + logf(sum);\n \n // Subtract logsumexp\n for (int k = 0; k < K; k++) {\n row[k] -= log_sum;\n }\n}\n\n// Compute total log-likelihood (reduction)\n__global__ void logsumexp_reduce_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ partial_sums,\n int N, int K\n) {\n __shared__ float sdata[BLOCK_SIZE];\n \n int tid = threadIdx.x;\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n \n float val = 0.0f;\n if (n < N) {\n // Compute logsumexp for this row\n const float* row = log_resp + n * K;\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n val = max_val + logf(sum);\n }\n \n sdata[tid] = val;\n __syncthreads();\n \n // Block reduction\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) {\n sdata[tid] += sdata[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n partial_sums[blockIdx.x] = sdata[0];\n }\n}\n\n// M-step: compute new_mus = (resp^T @ x) / N_k\n__global__ void m_step_mus_kernel(\n const float* __restrict__ resp,\n const float* __restrict__ x,\n float* __restrict__ new_mus,\n float* __restrict__ N_k,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n int d = threadIdx.x;\n \n if (k >= K || d >= D) return;\n \n float sum = 0.0f;\n float n_sum = 0.0f;\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n sum += r * x[n * D + d];\n if (d == 0) {\n n_sum += r;\n }\n }\n \n if (d == 0) {\n N_k[k] = fmaxf(n_sum, 1e-8f);\n }\n __syncthreads();\n \n new_mus[k * D + d] = sum / N_k[k];\n}\n\n// M-step: compute new_covs[k] = (diff^T @ weighted_diff) / N_k[k] + reg\n__global__ void m_step_covs_kernel(\n const float* __restrict__ x,\n const float* __restrict__ new_mus,\n const float* __restrict__ resp,\n const float* __restrict__ N_k,\n float* __restrict__ new_covs,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n __shared__ float mu_local[8];\n __shared__ float cov_local[64];\n \n // Load mu to shared memory\n if (threadIdx.x < D) {\n mu_local[threadIdx.x] = new_mus[k * D + threadIdx.x];\n }\n \n // Initialize cov to 0\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n cov_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Accumulate outer products\n for (int n = threadIdx.x; n < N; n += blockDim.x) {\n float r = resp[n * K + k];\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x[n * D + d] - mu_local[d];\n }\n \n // Outer product weighted by r\n for (int i = 0; i < D; i++) {\n for (int j = 0; j < D; j++) {\n atomicAdd(&cov_local[i * D + j], r * diff[i] * diff[j]);\n }\n }\n }\n __syncthreads();\n \n // Normalize and add regularization\n float n_k = N_k[k];\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = cov_local[i] / n_k;\n if (row == col) {\n val += 1e-4f;\n }\n new_covs[k * D * D + i] = val;\n }\n}\n\n// Compute new_log_pi = log(N_k) - log(N)\n__global__ void compute_log_pi_kernel(\n const float* __restrict__ N_k,\n float* __restrict__ new_log_pi,\n int K, int N\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n if (k >= K) return;\n \n new_log_pi[k] = logf(N_k[k]) - logf((float)N);\n}\n\n// Host function to launch kernels\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n) {\n int N = x.size(0);\n int D = x.size(1);\n \n auto options = x.options();\n \n // Compute Cholesky decomposition\n auto L = torch::empty({K, D, D}, options);\n int cholesky_blocks = K;\n cholesky_kernel<<<cholesky_blocks, BLOCK_SIZE>>>(\n covs.data_ptr<float>(), L.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute log_det from L\n auto log_det = torch::empty({K}, options);\n compute_log_det_kernel<<<K, BLOCK_SIZE>>>(\n L.data_ptr<float>(), log_det.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Normalize log_pi\n auto log_pi_data = log_pi.data_ptr<float>();\n // This should be done on CPU or in a kernel\n \n // Compute log_resp\n auto log_resp = torch::empty({N, K}, options);\n int log_gaussian_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n log_gaussian_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Add log_det and constant\n add_log_det_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n log_det.data_ptr<float>(),\n N, K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute total log-likelihood\n int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n auto partial_sums = torch::empty({num_blocks}, options);\n logsumexp_reduce_kernel<<<num_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n partial_sums.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n float log_lik_val = partial_sums.sum().item<float>();\n auto log_lik = torch::tensor(log_lik_val, options);\n \n // Normalize log_resp\n log_softmax_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute responsibilities\n auto resp = torch::exp(log_resp);\n \n // M-step: compute N_k and new_mus\n auto N_k = torch::empty({K}, options);\n auto new_mus = torch::empty({K, D}, options);\n m_step_mus_kernel<<<K, D>>>(\n resp.data_ptr<float>(),\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n N_k.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // M-step: compute new_covs\n auto new_covs = torch::empty({K, D, D}, options);\n m_step_covs_kernel<<<K, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n resp.data_ptr<float>(),\n N_k.data_ptr<float>(),\n new_covs.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute new_log_pi\n auto new_log_pi = torch::empty({K}, options);\n compute_log_pi_kernel<<<1, K>>>(\n N_k.data_ptr<float>(),\n new_log_pi.data_ptr<float>(),\n K, N\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n return std::make_tuple(new_mus, new_covs, new_log_pi, log_lik);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n);\n\"\"\"\n\ngmm_module = load_inline(\n name='gmm_em_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gmm_em_forward_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, dim, num_components):\n super().__init__()\n self.dim = dim\n self.K = num_components\n self.register_buffer(\"mus\", torch.randn(num_components, dim))\n self.register_buffer(\"covs\", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())\n self.register_buffer(\"log_pi\", torch.zeros(num_components))\n\n def forward(self, x: torch.Tensor) -> tuple:\n # Normalize log_pi\n log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)\n \n # Call CUDA kernel\n new_mus, new_covs, new_log_pi, log_lik = gmm_module.gmm_em_forward_cuda(\n x, self.mus, self.covs, log_pi, self.K\n )\n \n return new_mus, new_covs, new_log_pi, log_lik" }
result ← static_check (ok)
okstatic_check static_check PASSED (with warnings): WARNING: Uses torch computation op: torch.logsumexp
turn 4 · FINAL · 1 tool call(s) · 336.6s turn · 31.5s LLM · tokens in=14,255 · out=4,017 · total=18,272 · 15,838 chars (click to expand)
turn 4336.6s turn · 31.5s LLM · 305.1s tools · tokens in=14,255 · out=4,017 · total=18,272 · FINAL
reasoning
The static check passed. The warning about using torch.logsumexp is fine - that's just part of the forward pass to normalize log_pi, and it's not a significant performance concern. The kernel compiles and passes correctness. Let me submit it to see the timing results.
assistantThe static check passed. Let me submit the kernel to evaluate performance:
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport math\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 <c10/cuda/CUDAException.h>\n\nconstexpr int BLOCK_SIZE = 256;\nconstexpr int TILE_SIZE = 16;\n\n// Cholesky decomposition for small matrices (dim x dim)\n__global__ void cholesky_kernel(const float* __restrict__ covs, float* __restrict__ L, int K, int D) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* cov = covs + k * D * D;\n float* l = L + k * D * D;\n \n // Load into shared memory\n __shared__ float A[64]; // Max dim=8, so 64 floats\n __shared__ float L_local[64];\n \n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n A[i] = cov[i];\n L_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Cholesky: L[i][j] = (A[i][j] - sum(L[i][m]*L[j][m])) / L[j][j]\n // Only one thread does the computation for small matrices\n if (threadIdx.x == 0) {\n for (int i = 0; i < D; i++) {\n for (int j = 0; j <= i; j++) {\n float sum = 0.0f;\n for (int m = 0; m < j; m++) {\n sum += L_local[i * D + m] * L_local[j * D + m];\n }\n if (i == j) {\n float val = A[i * D + i] - sum;\n L_local[i * D + i] = sqrtf(fmaxf(val, 1e-8f));\n } else {\n L_local[i * D + j] = (A[i * D + j] - sum) / L_local[j * D + j];\n }\n }\n }\n }\n __syncthreads();\n \n // Copy back\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n l[i] = L_local[i];\n }\n}\n\n// Compute log-gaussian for all components: returns log_resp (N, K)\n// Also returns log_det (K,) for each component\n__global__ void log_gaussian_kernel(\n const float* __restrict__ x, // (N, D)\n const float* __restrict__ mus, // (K, D)\n const float* __restrict__ L, // (K, D, D) - Cholesky factors\n const float* __restrict__ log_pi, // (K,)\n float* __restrict__ log_resp, // (N, K)\n int N, int D, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n // Load x[n] into registers\n float x_local[8]; // Max D=8\n for (int d = 0; d < D; d++) {\n x_local[d] = x[n * D + d];\n }\n \n // For each component k\n for (int k = 0; k < K; k++) {\n const float* mu = mus + k * D;\n const float* l = L + k * D * D;\n \n // Compute diff = x - mu\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x_local[d] - mu[d];\n }\n \n // Solve L @ solve = diff (lower triangular)\n float solve[8];\n for (int i = 0; i < D; i++) {\n float sum = diff[i];\n for (int j = 0; j < i; j++) {\n sum -= l[i * D + j] * solve[j];\n }\n solve[i] = sum / l[i * D + i];\n }\n \n // Mahalanobis distance = sum(solve^2)\n float maha = 0.0f;\n for (int d = 0; d < D; d++) {\n maha += solve[d] * solve[d];\n }\n \n // Log-det from L diagonal (precomputed in another kernel or here)\n // We need to compute this efficiently\n \n // Log probability\n // log_p = -0.5 * (D * log(2*pi) + log_det + maha)\n // We'll compute log_det separately\n \n log_resp[n * K + k] = -0.5f * maha + log_pi[k];\n }\n}\n\n// Compute log-determinant from L diagonal\n__global__ void compute_log_det_kernel(\n const float* __restrict__ L,\n float* __restrict__ log_det,\n int K, int D\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n const float* l = L + k * D * D;\n float sum = 0.0f;\n for (int d = 0; d < D; d++) {\n sum += logf(l[d * D + d]);\n }\n log_det[k] = 2.0f * sum; // log(det(A)) = 2 * sum(log(diag(L)))\n}\n\n// Add log_det and constant to log_resp\n__global__ void add_log_det_kernel(\n float* __restrict__ log_resp,\n const float* __restrict__ log_det,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float log_const = -0.5f * D * logf(2.0f * 3.14159265358979323846f);\n \n for (int k = 0; k < K; k++) {\n log_resp[n * K + k] += log_const - 0.5f * log_det[k];\n }\n}\n\n// Softmax: log_resp = log_resp - logsumexp(log_resp, dim=-1)\n__global__ void log_softmax_kernel(\n float* __restrict__ log_resp,\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n float* row = log_resp + n * K;\n \n // Find max for numerical stability\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n \n // Compute logsumexp\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n float log_sum = max_val + logf(sum);\n \n // Subtract logsumexp\n for (int k = 0; k < K; k++) {\n row[k] -= log_sum;\n }\n}\n\n// Compute total log-likelihood (reduction)\n__global__ void logsumexp_reduce_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ partial_sums,\n int N, int K\n) {\n __shared__ float sdata[BLOCK_SIZE];\n \n int tid = threadIdx.x;\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n \n float val = 0.0f;\n if (n < N) {\n // Compute logsumexp for this row\n const float* row = log_resp + n * K;\n float max_val = row[0];\n for (int k = 1; k < K; k++) {\n max_val = fmaxf(max_val, row[k]);\n }\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n val = max_val + logf(sum);\n }\n \n sdata[tid] = val;\n __syncthreads();\n \n // Block reduction\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) {\n sdata[tid] += sdata[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n partial_sums[blockIdx.x] = sdata[0];\n }\n}\n\n// M-step: compute new_mus = (resp^T @ x) / N_k\n__global__ void m_step_mus_kernel(\n const float* __restrict__ resp,\n const float* __restrict__ x,\n float* __restrict__ new_mus,\n float* __restrict__ N_k,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n int d = threadIdx.x;\n \n if (k >= K || d >= D) return;\n \n float sum = 0.0f;\n float n_sum = 0.0f;\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n sum += r * x[n * D + d];\n if (d == 0) {\n n_sum += r;\n }\n }\n \n if (d == 0) {\n N_k[k] = fmaxf(n_sum, 1e-8f);\n }\n __syncthreads();\n \n new_mus[k * D + d] = sum / N_k[k];\n}\n\n// M-step: compute new_covs[k] = (diff^T @ weighted_diff) / N_k[k] + reg\n__global__ void m_step_covs_kernel(\n const float* __restrict__ x,\n const float* __restrict__ new_mus,\n const float* __restrict__ resp,\n const float* __restrict__ N_k,\n float* __restrict__ new_covs,\n int N, int D, int K\n) {\n int k = blockIdx.x;\n if (k >= K) return;\n \n __shared__ float mu_local[8];\n __shared__ float cov_local[64];\n \n // Load mu to shared memory\n if (threadIdx.x < D) {\n mu_local[threadIdx.x] = new_mus[k * D + threadIdx.x];\n }\n \n // Initialize cov to 0\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n cov_local[i] = 0.0f;\n }\n __syncthreads();\n \n // Accumulate outer products\n for (int n = threadIdx.x; n < N; n += blockDim.x) {\n float r = resp[n * K + k];\n float diff[8];\n for (int d = 0; d < D; d++) {\n diff[d] = x[n * D + d] - mu_local[d];\n }\n \n // Outer product weighted by r\n for (int i = 0; i < D; i++) {\n for (int j = 0; j < D; j++) {\n atomicAdd(&cov_local[i * D + j], r * diff[i] * diff[j]);\n }\n }\n }\n __syncthreads();\n \n // Normalize and add regularization\n float n_k = N_k[k];\n for (int i = threadIdx.x; i < D * D; i += blockDim.x) {\n int row = i / D;\n int col = i % D;\n float val = cov_local[i] / n_k;\n if (row == col) {\n val += 1e-4f;\n }\n new_covs[k * D * D + i] = val;\n }\n}\n\n// Compute new_log_pi = log(N_k) - log(N)\n__global__ void compute_log_pi_kernel(\n const float* __restrict__ N_k,\n float* __restrict__ new_log_pi,\n int K, int N\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n if (k >= K) return;\n \n new_log_pi[k] = logf(N_k[k]) - logf((float)N);\n}\n\n// Host function to launch kernels\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n) {\n int N = x.size(0);\n int D = x.size(1);\n \n auto options = x.options();\n \n // Compute Cholesky decomposition\n auto L = torch::empty({K, D, D}, options);\n int cholesky_blocks = K;\n cholesky_kernel<<<cholesky_blocks, BLOCK_SIZE>>>(\n covs.data_ptr<float>(), L.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute log_det from L\n auto log_det = torch::empty({K}, options);\n compute_log_det_kernel<<<K, BLOCK_SIZE>>>(\n L.data_ptr<float>(), log_det.data_ptr<float>(), K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Normalize log_pi\n auto log_pi_data = log_pi.data_ptr<float>();\n // This should be done on CPU or in a kernel\n \n // Compute log_resp\n auto log_resp = torch::empty({N, K}, options);\n int log_gaussian_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n log_gaussian_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Add log_det and constant\n add_log_det_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n log_det.data_ptr<float>(),\n N, K, D\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute total log-likelihood\n int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;\n auto partial_sums = torch::empty({num_blocks}, options);\n logsumexp_reduce_kernel<<<num_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n partial_sums.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n float log_lik_val = partial_sums.sum().item<float>();\n auto log_lik = torch::tensor(log_lik_val, options);\n \n // Normalize log_resp\n log_softmax_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(\n log_resp.data_ptr<float>(),\n N, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute responsibilities\n auto resp = torch::exp(log_resp);\n \n // M-step: compute N_k and new_mus\n auto N_k = torch::empty({K}, options);\n auto new_mus = torch::empty({K, D}, options);\n m_step_mus_kernel<<<K, D>>>(\n resp.data_ptr<float>(),\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n N_k.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // M-step: compute new_covs\n auto new_covs = torch::empty({K, D, D}, options);\n m_step_covs_kernel<<<K, BLOCK_SIZE>>>(\n x.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n resp.data_ptr<float>(),\n N_k.data_ptr<float>(),\n new_covs.data_ptr<float>(),\n N, D, K\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n // Compute new_log_pi\n auto new_log_pi = torch::empty({K}, options);\n compute_log_pi_kernel<<<1, K>>>(\n N_k.data_ptr<float>(),\n new_log_pi.data_ptr<float>(),\n K, N\n );\n C10_CUDA_CHECK(cudaGetLastError());\n \n return std::make_tuple(new_mus, new_covs, new_log_pi, log_lik);\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> \ngmm_em_forward_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor covs,\n torch::Tensor log_pi,\n int K\n);\n\"\"\"\n\ngmm_module = load_inline(\n name='gmm_em_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gmm_em_forward_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, dim, num_components):\n super().__init__()\n self.dim = dim\n self.K = num_components\n self.register_buffer(\"mus\", torch.randn(num_components, dim))\n self.register_buffer(\"covs\", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())\n self.register_buffer(\"log_pi\", torch.zeros(num_components))\n\n def forward(self, x: torch.Tensor) -> tuple:\n # Normalize log_pi\n log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)\n \n # Call CUDA kernel\n new_mus, new_covs, new_log_pi, log_lik = gmm_module.gmm_em_forward_cuda(\n x, self.mus, self.covs, log_pi, self.K\n )\n \n return new_mus, new_covs, new_log_pi, log_lik" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 3.72 μs Runtime stats: mean=3.72μs median=?μs std=0.06μs Numerical precision: max_abs_err=3.91e-03 mean_abs_err=2.29e-06 max_rel_err=3.36e-04 Memory: 32.15 MB peak (ref: 32.76 MB, ratio: 0.9816x) Kernel launches: 38 (ref: 619, fusion ratio: 16.2895) Energy: 485.04 mJ/run (ref: 5577.92 mJ, ratio: 11.4999x) SOL score: 0.4024
final submitted kernel (473 lines, 13,233 chars) — full source, untruncated
import torch
import torch.nn as nn
import math
from torch.utils.cpp_extension import load_inline

cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <c10/cuda/CUDAException.h>

constexpr int BLOCK_SIZE = 256;
constexpr int TILE_SIZE = 16;

// Cholesky decomposition for small matrices (dim x dim)
__global__ void cholesky_kernel(const float* __restrict__ covs, float* __restrict__ L, int K, int D) {
    int k = blockIdx.x;
    if (k >= K) return;
    
    const float* cov = covs + k * D * D;
    float* l = L + k * D * D;
    
    // Load into shared memory
    __shared__ float A[64];  // Max dim=8, so 64 floats
    __shared__ float L_local[64];
    
    for (int i = threadIdx.x; i < D * D; i += blockDim.x) {
        A[i] = cov[i];
        L_local[i] = 0.0f;
    }
    __syncthreads();
    
    // Cholesky: L[i][j] = (A[i][j] - sum(L[i][m]*L[j][m])) / L[j][j]
    // Only one thread does the computation for small matrices
    if (threadIdx.x == 0) {
        for (int i = 0; i < D; i++) {
            for (int j = 0; j <= i; j++) {
                float sum = 0.0f;
                for (int m = 0; m < j; m++) {
                    sum += L_local[i * D + m] * L_local[j * D + m];
                }
                if (i == j) {
                    float val = A[i * D + i] - sum;
                    L_local[i * D + i] = sqrtf(fmaxf(val, 1e-8f));
                } else {
                    L_local[i * D + j] = (A[i * D + j] - sum) / L_local[j * D + j];
                }
            }
        }
    }
    __syncthreads();
    
    // Copy back
    for (int i = threadIdx.x; i < D * D; i += blockDim.x) {
        l[i] = L_local[i];
    }
}

// Compute log-gaussian for all components: returns log_resp (N, K)
// Also returns log_det (K,) for each component
__global__ void log_gaussian_kernel(
    const float* __restrict__ x,      // (N, D)
    const float* __restrict__ mus,   // (K, D)
    const float* __restrict__ L,     // (K, D, D) - Cholesky factors
    const float* __restrict__ log_pi, // (K,)
    float* __restrict__ log_resp,     // (N, K)
    int N, int D, int K
) {
    int n = blockIdx.x * blockDim.x + threadIdx.x;
    if (n >= N) return;
    
    // Load x[n] into registers
    float x_local[8];  // Max D=8
    for (int d = 0; d < D; d++) {
        x_local[d] = x[n * D + d];
    }
    
    // For each component k
    for (int k = 0; k < K; k++) {
        const float* mu = mus + k * D;
        const float* l = L + k * D * D;
        
        // Compute diff = x - mu
        float diff[8];
        for (int d = 0; d < D; d++) {
            diff[d] = x_local[d] - mu[d];
        }
        
        // Solve L @ solve = diff (lower triangular)
        float solve[8];
        for (int i = 0; i < D; i++) {
            float sum = diff[i];
            for (int j = 0; j < i; j++) {
                sum -= l[i * D + j] * solve[j];
            }
            solve[i] = sum / l[i * D + i];
        }
        
        // Mahalanobis distance = sum(solve^2)
        float maha = 0.0f;
        for (int d = 0; d < D; d++) {
            maha += solve[d] * solve[d];
        }
        
        // Log-det from L diagonal (precomputed in another kernel or here)
        // We need to compute this efficiently
        
        // Log probability
        // log_p = -0.5 * (D * log(2*pi) + log_det + maha)
        // We'll compute log_det separately
        
        log_resp[n * K + k] = -0.5f * maha + log_pi[k];
    }
}

// Compute log-determinant from L diagonal
__global__ void compute_log_det_kernel(
    const float* __restrict__ L,
    float* __restrict__ log_det,
    int K, int D
) {
    int k = blockIdx.x;
    if (k >= K) return;
    
    const float* l = L + k * D * D;
    float sum = 0.0f;
    for (int d = 0; d < D; d++) {
        sum += logf(l[d * D + d]);
    }
    log_det[k] = 2.0f * sum;  // log(det(A)) = 2 * sum(log(diag(L)))
}

// Add log_det and constant to log_resp
__global__ void add_log_det_kernel(
    float* __restrict__ log_resp,
    const float* __restrict__ log_det,
    int N, int K, int D
) {
    int n = blockIdx.x * blockDim.x + threadIdx.x;
    if (n >= N) return;
    
    float log_const = -0.5f * D * logf(2.0f * 3.14159265358979323846f);
    
    for (int k = 0; k < K; k++) {
        log_resp[n * K + k] += log_const - 0.5f * log_det[k];
    }
}

// Softmax: log_resp = log_resp - logsumexp(log_resp, dim=-1)
__global__ void log_softmax_kernel(
    float* __restrict__ log_resp,
    int N, int K
) {
    int n = blockIdx.x * blockDim.x + threadIdx.x;
    if (n >= N) return;
    
    float* row = log_resp + n * K;
    
    // Find max for numerical stability
    float max_val = row[0];
    for (int k = 1; k < K; k++) {
        max_val = fmaxf(max_val, row[k]);
    }
    
    // Compute logsumexp
    float sum = 0.0f;
    for (int k = 0; k < K; k++) {
        sum += expf(row[k] - max_val);
    }
    float log_sum = max_val + logf(sum);
    
    // Subtract logsumexp
    for (int k = 0; k < K; k++) {
        row[k] -= log_sum;
    }
}

// Compute total log-likelihood (reduction)
__global__ void logsumexp_reduce_kernel(
    const float* __restrict__ log_resp,
    float* __restrict__ partial_sums,
    int N, int K
) {
    __shared__ float sdata[BLOCK_SIZE];
    
    int tid = threadIdx.x;
    int n = blockIdx.x * blockDim.x + threadIdx.x;
    
    float val = 0.0f;
    if (n < N) {
        // Compute logsumexp for this row
        const float* row = log_resp + n * K;
        float max_val = row[0];
        for (int k = 1; k < K; k++) {
            max_val = fmaxf(max_val, row[k]);
        }
        float sum = 0.0f;
        for (int k = 0; k < K; k++) {
            sum += expf(row[k] - max_val);
        }
        val = max_val + logf(sum);
    }
    
    sdata[tid] = val;
    __syncthreads();
    
    // Block reduction
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (tid < s) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        partial_sums[blockIdx.x] = sdata[0];
    }
}

// M-step: compute new_mus = (resp^T @ x) / N_k
__global__ void m_step_mus_kernel(
    const float* __restrict__ resp,
    const float* __restrict__ x,
    float* __restrict__ new_mus,
    float* __restrict__ N_k,
    int N, int D, int K
) {
    int k = blockIdx.x;
    int d = threadIdx.x;
    
    if (k >= K || d >= D) return;
    
    float sum = 0.0f;
    float n_sum = 0.0f;
    
    for (int n = 0; n < N; n++) {
        float r = resp[n * K + k];
        sum += r * x[n * D + d];
        if (d == 0) {
            n_sum += r;
        }
    }
    
    if (d == 0) {
        N_k[k] = fmaxf(n_sum, 1e-8f);
    }
    __syncthreads();
    
    new_mus[k * D + d] = sum / N_k[k];
}

// M-step: compute new_covs[k] = (diff^T @ weighted_diff) / N_k[k] + reg
__global__ void m_step_covs_kernel(
    const float* __restrict__ x,
    const float* __restrict__ new_mus,
    const float* __restrict__ resp,
    const float* __restrict__ N_k,
    float* __restrict__ new_covs,
    int N, int D, int K
) {
    int k = blockIdx.x;
    if (k >= K) return;
    
    __shared__ float mu_local[8];
    __shared__ float cov_local[64];
    
    // Load mu to shared memory
    if (threadIdx.x < D) {
        mu_local[threadIdx.x] = new_mus[k * D + threadIdx.x];
    }
    
    // Initialize cov to 0
    for (int i = threadIdx.x; i < D * D; i += blockDim.x) {
        cov_local[i] = 0.0f;
    }
    __syncthreads();
    
    // Accumulate outer products
    for (int n = threadIdx.x; n < N; n += blockDim.x) {
        float r = resp[n * K + k];
        float diff[8];
        for (int d = 0; d < D; d++) {
            diff[d] = x[n * D + d] - mu_local[d];
        }
        
        // Outer product weighted by r
        for (int i = 0; i < D; i++) {
            for (int j = 0; j < D; j++) {
                atomicAdd(&cov_local[i * D + j], r * diff[i] * diff[j]);
            }
        }
    }
    __syncthreads();
    
    // Normalize and add regularization
    float n_k = N_k[k];
    for (int i = threadIdx.x; i < D * D; i += blockDim.x) {
        int row = i / D;
        int col = i % D;
        float val = cov_local[i] / n_k;
        if (row == col) {
            val += 1e-4f;
        }
        new_covs[k * D * D + i] = val;
    }
}

// Compute new_log_pi = log(N_k) - log(N)
__global__ void compute_log_pi_kernel(
    const float* __restrict__ N_k,
    float* __restrict__ new_log_pi,
    int K, int N
) {
    int k = blockIdx.x * blockDim.x + threadIdx.x;
    if (k >= K) return;
    
    new_log_pi[k] = logf(N_k[k]) - logf((float)N);
}

// Host function to launch kernels
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> 
gmm_em_forward_cuda(
    torch::Tensor x,
    torch::Tensor mus,
    torch::Tensor covs,
    torch::Tensor log_pi,
    int K
) {
    int N = x.size(0);
    int D = x.size(1);
    
    auto options = x.options();
    
    // Compute Cholesky decomposition
    auto L = torch::empty({K, D, D}, options);
    int cholesky_blocks = K;
    cholesky_kernel<<<cholesky_blocks, BLOCK_SIZE>>>(
        covs.data_ptr<float>(), L.data_ptr<float>(), K, D
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Compute log_det from L
    auto log_det = torch::empty({K}, options);
    compute_log_det_kernel<<<K, BLOCK_SIZE>>>(
        L.data_ptr<float>(), log_det.data_ptr<float>(), K, D
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Normalize log_pi
    auto log_pi_data = log_pi.data_ptr<float>();
    // This should be done on CPU or in a kernel
    
    // Compute log_resp
    auto log_resp = torch::empty({N, K}, options);
    int log_gaussian_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;
    log_gaussian_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(
        x.data_ptr<float>(),
        mus.data_ptr<float>(),
        L.data_ptr<float>(),
        log_pi.data_ptr<float>(),
        log_resp.data_ptr<float>(),
        N, D, K
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Add log_det and constant
    add_log_det_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(
        log_resp.data_ptr<float>(),
        log_det.data_ptr<float>(),
        N, K, D
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Compute total log-likelihood
    int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;
    auto partial_sums = torch::empty({num_blocks}, options);
    logsumexp_reduce_kernel<<<num_blocks, BLOCK_SIZE>>>(
        log_resp.data_ptr<float>(),
        partial_sums.data_ptr<float>(),
        N, K
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    float log_lik_val = partial_sums.sum().item<float>();
    auto log_lik = torch::tensor(log_lik_val, options);
    
    // Normalize log_resp
    log_softmax_kernel<<<log_gaussian_blocks, BLOCK_SIZE>>>(
        log_resp.data_ptr<float>(),
        N, K
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Compute responsibilities
    auto resp = torch::exp(log_resp);
    
    // M-step: compute N_k and new_mus
    auto N_k = torch::empty({K}, options);
    auto new_mus = torch::empty({K, D}, options);
    m_step_mus_kernel<<<K, D>>>(
        resp.data_ptr<float>(),
        x.data_ptr<float>(),
        new_mus.data_ptr<float>(),
        N_k.data_ptr<float>(),
        N, D, K
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // M-step: compute new_covs
    auto new_covs = torch::empty({K, D, D}, options);
    m_step_covs_kernel<<<K, BLOCK_SIZE>>>(
        x.data_ptr<float>(),
        new_mus.data_ptr<float>(),
        resp.data_ptr<float>(),
        N_k.data_ptr<float>(),
        new_covs.data_ptr<float>(),
        N, D, K
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    // Compute new_log_pi
    auto new_log_pi = torch::empty({K}, options);
    compute_log_pi_kernel<<<1, K>>>(
        N_k.data_ptr<float>(),
        new_log_pi.data_ptr<float>(),
        K, N
    );
    C10_CUDA_CHECK(cudaGetLastError());
    
    return std::make_tuple(new_mus, new_covs, new_log_pi, log_lik);
}
"""

cpp_source = """
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> 
gmm_em_forward_cuda(
    torch::Tensor x,
    torch::Tensor mus,
    torch::Tensor covs,
    torch::Tensor log_pi,
    int K
);
"""

gmm_module = load_inline(
    name='gmm_em_cuda',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['gmm_em_forward_cuda'],
    verbose=False,
    extra_cuda_cflags=['-O3']
)


class ModelNew(nn.Module):
    def __init__(self, dim, num_components):
        super().__init__()
        self.dim = dim
        self.K = num_components
        self.register_buffer("mus", torch.randn(num_components, dim))
        self.register_buffer("covs", torch.eye(dim).unsqueeze(0).expand(num_components, -1, -1).clone())
        self.register_buffer("log_pi", torch.zeros(num_components))

    def forward(self, x: torch.Tensor) -> tuple:
        # Normalize log_pi
        log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)
        
        # Call CUDA kernel
        new_mus, new_covs, new_log_pi, log_lik = gmm_module.gmm_em_forward_cuda(
            x, self.mus, self.covs, log_pi, self.K
        )
        
        return new_mus, new_covs, new_log_pi, log_lik