reasoning
Let me analyze this task. I need to implement a custom CUDA kernel for one iteration of Expectation-Maximization for a Gaussian Mixture Model.
The key operations are:
1. E-step: Compute log-responsibilities for each data point to each component
2. M-step: Update means, covariances, and mixing weights
The most expensive part is computing the log-gaussian for each (data point, component) pair.
Let me break down the computation:
1. **Log Gaussian computation** for each component k:
- diff = x - mu_k (broadcasted)
- Cholesky decomposition L of covariance
- solve = L^{-1} @ diff (triangular solve)
- mahalanobis distance = sum(solve^2)
- log_det = 2 * sum(log(diag(L)))
- result = -0.5 * (D * log(2π) + log_det + maha)
2. **E-step**:
- log_resp[n,k] = log_gaussian(x[n], mu[k], cov[k]) + log_pi[k]
- log_lik = sum(logsumexp(log_resp, dim=-1))
- log_resp = log_resp - logsumexp(log_resp, dim=-1, keepdim=True)
- resp = exp(log_resp)
3. **M-step**:
- N_k = sum(resp[:, k])
- new_mus[k] = sum(resp[n,k] * x[n]) / N_k
- new_covs[k] = sum(resp[n,k] * (x[n] - new_mus[k]) * (x[n] - new_mus[k])^T) / N_k + regularization
- new_log_pi[k] = log(N_k) - log(N)
For the CUDA kernel, I need to think about how to parallelize this efficiently.
The Cholesky decomposition presents a challenge since each covariance matrix requires its own decomposition, but these are independent across components. I could either decompose them on the CPU beforehand or implement batched Cholesky on the GPU. Looking at the reference implementation, the covariance matrices start as identity, so I can leverage that. The forward pass updates these covariances after each E-step, which means the decomposition would need to happen during the M-step.
For the Cholesky factors specifically, I could precompute them in Python using torch.linalg.cholesky and pass them to CUDA, or implement a batched decomposition kernel. The main computational bottleneck is the E-step's Gaussian density calculations over all N data points and K components, along with the M-step's covariance updates—both of these could benefit significantly from GPU parallelization. Since the covariances are identity matrices initially, I can handle the Cholesky decomposition on the host side before launching kernels for the heavier computations. I'll use PyTorch's C++ extensions to build this out, making sure to decompose the problem into manageable kernel launches rather than trying to write everything in a single kernel. The key is avoiding manual PYBIND11 binding and instead relying on torch.utils.cpp_extension.load_inline to handle the interface automatically. (x - mu). This means I need diff = x - mu_k for each k, then solve the triangular system L_k \ diff, compute the squared norm, and combine with log_det.
So the kernel computes log_resp values for all (n, k) pairs, handles the logsumexp operations, and returns resp, log_lik, plus M-step intermediates. The covariance update requires matrix multiplication with the weighted differences, which I'll tackle as a separate kernel.
I need to implement batched log-gaussian computation across K components and N data points, where each data point has dimension D. The Cholesky decomposition gives me L matrices with shape (K, D, D), and I'm computing responsibilities by solving L_k \ (x_n - mu_k), squaring and summing, then combining with the log determinant and mixing weights.
The core computation loops through each (n, k) pair: diff = x[n] - mus[k], then solves the triangular system L[k] * y = diff, computes the squared norm of y, and combines this with log_det[k] and log_pi[k] to get log_resp[n, k]. From there, logsumexp operations normalize these responsibilities and the final output is exp(log_resp).
I'm considering whether to implement this as a single comprehensive kernel or break it into separate launches. A single kernel has the advantage of keeping intermediate results like log_resp in GPU memory without transferring back to the host, but requires handling logsumexp—which involves parallel reduction—within CUDA itself, which adds complexity.
Given the time constraints, I think the cleaner approach is to let PyTorch handle the reduction operations like logsumexp while using custom CUDA for the compute-intensive batched log-gaussian calculations. I'd launch a kernel for computing log_resp, then keep the reductions and M-step updates in PyTorch where they're already optimized. This balances performance gains where they matter most with implementation simplicity.
I'm realizing the computational bottleneck is actually distributed across several operations: the Mahalanobis distance computations for each (n, k) pair, the logsumexp reductions, and the matrix multiplications in the M-step. The most expensive parts are the Mahalanobis distance calculation with its triangular solve (O(N*K*D^2) operations) and the covariance matrix updates (O(N*K*D^2) through weighted outer products). The M-step's covariance computation in particular requires N outer products per component, each scaled by the responsibility weights.
I should focus CUDA kernels on the parallelizable heavy lifting—the triangular solves for the Mahalanobis distance and the weighted covariance accumulation—while keeping simpler operations like logsumexp in PyTorch. For the implementation, I'd start with a basic kernel computing log_resp, then check correctness before optimizing the M-step covariance updates.
The core kernel needs to handle N*K work items, where each computes the Mahalanobis distance for one (data_point, component) pair. The challenge is that triangular solves are inherently sequential, so I'd implement a straightforward row-by-row approach rather than trying to force parallelism where it doesn't naturally exist.
}
// Compute squared sum
T sum = 0;
for (int d = 0; d < D; d++) {
sum += y[d] * y[d];
}
log_resp[idx] = -0.5 * (D * log_2pi + log_det[k] + sum) + log_pi[k];
}
```
I need to bind the scalar values properly and ensure the indexing matches between the kernel parameters and the actual data layout. Let me write out the complete kernel implementation with the correct parameter passing and memory alignment.
The kernel should handle float inputs and compute the log-responsibilities for each data point and component pair. I'll need to pass the data matrix, means, lower triangular Cholesky factors, log determinants, log priors, and output buffer along with the dimension sizes. Each thread computes one responsibility value by solving a triangular system and then computing the log-probability.
For each component, I extract the corresponding mean and Cholesky factor, then solve the triangular system L * y = diff using forward substitution. The log-responsibility combines the squared norm of the solution, the log-determinant term, and the log-prior weight. The forward substitution works through the lower triangular matrix column by column. For each row index i, I subtract the dot product of already-computed solution elements with the corresponding row of L, then divide by the diagonal element. Since the Cholesky factor is lower triangular, I should use a 1D kernel where each thread handles one (data_point, component) pair, keeping the indexing straightforward.
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\n# Compile CUDA kernel at module load time\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n__global__ void compute_log_resp_kernel(\n const float* __restrict__ x,\n const float* __restrict__ mus,\n const float* __restrict__ L,\n const float* __restrict__ log_det,\n const float* __restrict__ log_pi,\n float* __restrict__ log_resp,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n int k = blockIdx.y * blockDim.y + threadIdx.y;\n \n if (n >= N || k >= K) return;\n \n const float log_2pi = 1.8378770664093453f; // log(2*pi)\n \n int idx = n * K + k;\n int x_offset = n * D;\n int mu_offset = k * D;\n int L_offset = k * D * D;\n \n // Allocate diff and y in registers/local memory\n extern __shared__ float shared_mem[];\n float* diff = shared_mem; // Not used in this simple version\n \n // Compute diff and triangular solve on-the-fly\n // Solve L @ y = diff, where L is lower triangular\n // Then compute y^T @ y = ||y||^2\n \n float y_sq_sum = 0.0f;\n \n // Forward substitution: solve row by row\n for (int i = 0; i < D; i++) {\n // diff[i] = x[n, i] - mus[k, i]\n float diff_i = x[x_offset + i] - mus[mu_offset + i];\n \n // Subtract L[k, i, j] * y[j] for j < i\n float sum = diff_i;\n for (int j = 0; j < i; j++) {\n // L[k, i, j] is at L_offset + i * D + j\n sum -= L[L_offset + i * D + j] * y_sq_sum; // This is wrong, need separate y array\n }\n \n // Actually, let me redo this correctly with a local y array\n // The above is incorrect\n }\n}\n\n__global__ void log_gaussian_kernel(\n const float* __restrict__ x,\n const float* __restrict__ mus,\n const float* __restrict__ L,\n const float* __restrict__ log_det,\n const float* __restrict__ log_pi,\n float* __restrict__ log_resp,\n int N, int K, int D\n) {\n // Each thread handles one (n, k) pair\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n int k = blockIdx.y * blockDim.y + threadIdx.y;\n \n if (n >= N || k >= K) return;\n \n const float log_2pi = 1.8378770664093453f;\n \n int idx = n * K + k;\n \n // Compute diff = x[n] - mus[k] and solve L[k] @ y = diff\n // Using forward substitution (L is lower triangular)\n \n // We'll compute maha = ||y||^2 directly\n \n float maha = 0.0f;\n \n for (int i = 0; i < D; i++) {\n // diff[i] = x[n, i] - mus[k, i]\n float diff_i = x[n * D + i] - mus[k * D + i];\n \n // y[i] = (diff[i] - sum_{j<i} L[k,i,j] * y[j]) / L[k,i,i]\n // We need to track y values...\n \n // Let's use a different approach: compute y incrementally\n }\n \n // Actually, let me write this more carefully with a local y buffer\n // For small D (like 8), this fits in registers\n}\n\n__global__ void compute_mahalanobis_kernel(\n const float* __restrict__ x,\n const float* __restrict__ mus,\n const float* __restrict__ L,\n const float* __restrict__ log_det,\n const float* __restrict__ log_pi,\n float* __restrict__ log_resp,\n int N, int K, int D\n) {\n // Each thread handles one (n, k) pair\n // D is expected to be small (e.g., 8)\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n int k = blockIdx.y * blockDim.y + threadIdx.y;\n \n if (n >= N || k >= K) return;\n \n const float log_2pi = 1.8378770664093453f; // log(2*pi)\n \n // Pointers to the relevant data\n const float* x_ptr = x + n * D;\n const float* mu_ptr = mus + k * D;\n const float* L_ptr = L + k * D * D;\n \n // Compute diff = x - mu and solve L @ y = diff using forward substitution\n // L is lower triangular, stored row-major\n // We compute ||y||^2 directly\n \n float y[32]; // Assume D <= 32\n float maha = 0.0f;\n \n for (int i = 0; i < D; i++) {\n // diff[i] = x[i] - mu[i]\n float diff_i = x_ptr[i] - mu_ptr[i];\n \n // y[i] = (diff_i - sum_{j < i} L[i,j] * y[j]) / L[i,i]\n float sum = diff_i;\n for (int j = 0; j < i; j++) {\n sum -= L_ptr[i * D + j] * y[j];\n }\n y[i] = sum / L_ptr[i * D + i];\n maha += y[i] * y[i];\n }\n \n // log_resp[n,k] = -0.5 * (D * log(2*pi) + log_det[k] + maha) + log_pi[k]\n float log_gauss = -0.5f * (D * log_2pi + log_det[k] + maha);\n log_resp[n * K + k] = log_gauss + log_pi[k];\n}\n\n// Kernel for M-step covariance computation\n// Each thread computes one element of new_covs[k]\n__global__ void compute_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 K, int D\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n int i = blockIdx.y * blockDim.y + threadIdx.y;\n int j = blockIdx.z * blockDim.z + threadIdx.z;\n \n if (k >= K || i >= D || j >= D) return;\n \n // new_covs[k, i, j] = sum_n resp[n,k] * (x[n,i] - new_mus[k,i]) * (x[n,j] - new_mus[k,j]) / N_k[k]\n // Only compute lower triangular part (cov is symmetric)\n \n float sum = 0.0f;\n float mu_i = new_mus[k * D + i];\n float mu_j = new_mus[k * D + j];\n \n for (int n = 0; n < N; n++) {\n float diff_i = x[n * D + i] - mu_i;\n float diff_j = x[n * D + j] - mu_j;\n sum += resp[n * K + k] * diff_i * diff_j;\n }\n \n new_covs[k * D * D + i * D + j] = sum / N_k[k];\n}\n\n// Optimized covariance kernel using shared memory for partial sums\n__global__ void compute_covs_fast_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 K, int D\n) {\n // One block per (k, i, j) output element\n // Threads in block cooperate to sum over N\n extern __shared__ float sdata[];\n \n int k = blockIdx.x / (D * D);\n int ij = blockIdx.x % (D * D);\n int i = ij / D;\n int j = ij % D;\n \n int tid = threadIdx.x;\n int blockSize = blockDim.x;\n \n // Initialize shared memory for reduction\n sdata[tid] = 0.0f;\n \n float mu_i = new_mus[k * D + i];\n float mu_j = new_mus[k * D + j];\n \n // Each thread sums over a subset of N\n for (int n = tid; n < N; n += blockSize) {\n float diff_i = x[n * D + i] - mu_i;\n float diff_j = x[n * D + j] - mu_j;\n sdata[tid] += resp[n * K + k] * diff_i * diff_j;\n }\n __syncthreads();\n \n // Block reduction\n for (int s = blockSize / 2; s > 0; s >>= 1) {\n if (tid < s) {\n sdata[tid] += sdata[tid + s];\n }\n __syncthreads();\n }\n \n // Write result\n if (tid == 0) {\n new_covs[k * D * D + i * D + j] = sdata[0] / N_k[k];\n }\n}\n\n// Log-sum-exp kernel: compute logsumexp along K dimension\n__global__ void logsumexp_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ result,\n float* __restrict__ normalized, // optional: if not null, store normalized values\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n // Find max for numerical stability\n float max_val = log_resp[n * K];\n for (int k = 1; k < K; k++) {\n float val = log_resp[n * K + k];\n if (val > max_val) max_val = val;\n }\n \n // Compute sum of exp\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(log_resp[n * K + k] - max_val);\n }\n \n // logsumexp = max + log(sum)\n float lse = max_val + logf(sum);\n result[n] = lse;\n \n // Optionally normalize\n if (normalized != nullptr) {\n for (int k = 0; k < K; k++) {\n normalized[n * K + k] = log_resp[n * K + k] - lse;\n }\n }\n}\n\"\"\"\n\ncpp_source = \"\"\"\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor> gmm_e_step_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor L, // Cholesky factors\n torch::Tensor log_det,\n torch::Tensor log_pi\n);\n\ntorch::Tensor gmm_compute_covs_cuda(\n torch::Tensor x,\n torch::Tensor new_mus,\n torch::Tensor resp,\n torch::Tensor N_k\n);\n\"\"\"\n\n# Actually let me rewrite this more cleanly\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Compute log_resp for all (n, k) pairs\n__global__ void log_gaussian_batched_kernel(\n const float* __restrict__ x,\n const float* __restrict__ mus,\n const float* __restrict__ L,\n const float* __restrict__ log_det,\n const float* __restrict__ log_pi,\n float* __restrict__ log_resp,\n int N, int K, int D\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n int k = blockIdx.y * blockDim.y + threadIdx.y;\n \n if (n >= N || k >= K) return;\n \n const float log_2pi = 1.8378770664093453f;\n \n const float* x_ptr = x + n * D;\n const float* mu_ptr = mus + k * D;\n const float* L_ptr = L + k * D * D;\n \n // Forward substitution to solve L @ y = (x - mu)\n // and compute ||y||^2\n float y_sq_sum = 0.0f;\n \n // For small D, use register array\n float y[16]; // Assume D <= 16\n \n for (int i = 0; i < D; i++) {\n float diff_i = x_ptr[i] - mu_ptr[i];\n float sum = diff_i;\n for (int j = 0; j < i; j++) {\n sum -= L_ptr[i * D + j] * y[j];\n }\n y[i] = sum / L_ptr[i * D + i];\n y_sq_sum += y[i] * y[i];\n }\n \n float log_gauss = -0.5f * (D * log_2pi + log_det[k] + y_sq_sum);\n log_resp[n * K + k] = log_gauss + log_pi[k];\n}\n\n// Logsumexp and normalization kernel\n__global__ void logsumexp_norm_kernel(\n const float* __restrict__ log_resp,\n float* __restrict__ lse_out,\n float* __restrict__ log_resp_norm,\n int N, int K\n) {\n int n = blockIdx.x * blockDim.x + threadIdx.x;\n if (n >= N) return;\n \n const float* row = log_resp + n * K;\n float* out_row = log_resp_norm + n * K;\n \n // Find max\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 // Sum of exp(x - max)\n float sum = 0.0f;\n for (int k = 0; k < K; k++) {\n sum += expf(row[k] - max_val);\n }\n \n float lse = max_val + logf(sum);\n lse_out[n] = lse;\n \n // Normalize\n for (int k = 0; k < K; k++) {\n out_row[k] = row[k] - lse;\n }\n}\n\n// Compute N_k and new_mus\n__global__ void compute_stats_kernel(\n const float* __restrict__ resp,\n const float* __restrict__ x,\n float* __restrict__ N_k,\n float* __restrict__ new_mus,\n int N, int K, int D\n) {\n int k = blockIdx.x * blockDim.x + threadIdx.x;\n if (k >= K) return;\n \n float n_k = 0.0f;\n float mu[16]; // Assume D <= 16\n for (int d = 0; d < D; d++) mu[d] = 0.0f;\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n n_k += r;\n for (int d = 0; d < D; d++) {\n mu[d] += r * x[n * D + d];\n }\n }\n \n N_k[k] = fmaxf(n_k, 1e-8f);\n for (int d = 0; d < D; d++) {\n new_mus[k * D + d] = mu[d] / N_k[k];\n }\n}\n\n// Compute weighted covariance contributions\n// Each thread handles one (k, i, j) element\n__global__ void compute_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 K, int D\n) {\n int k = blockIdx.x;\n int i = threadIdx.x;\n int j = threadIdx.y;\n \n if (k >= K || i >= D || j >= D) return;\n \n float sum = 0.0f;\n float mu_i = new_mus[k * D + i];\n float mu_j = new_mus[k * D + j];\n \n for (int n = 0; n < N; n++) {\n float r = resp[n * K + k];\n float diff_i = x[n * D + i] - mu_i;\n float diff_j = x[n * D + j] - mu_j;\n sum += r * diff_i * diff_j;\n }\n \n new_covs[k * D * D + i * D + j] = sum / N_k[k];\n}\n\n// Host functions\ntorch::Tensor compute_log_resp_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor L,\n torch::Tensor log_det,\n torch::Tensor log_pi\n) {\n int N = x.size(0);\n int K = mus.size(0);\n int D = x.size(1);\n \n auto log_resp = torch::empty({N, K}, x.options());\n \n dim3 block(32, 4); // 128 threads per block\n dim3 grid((N + block.x - 1) / block.x, (K + block.y - 1) / block.y);\n \n log_gaussian_batched_kernel<<<grid, block>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_det.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, K, D\n );\n \n return log_resp;\n}\n\nstd::tuple<torch::Tensor, torch::Tensor, torch::Tensor> e_step_cuda(\n torch::Tensor x,\n torch::Tensor mus,\n torch::Tensor L,\n torch::Tensor log_det,\n torch::Tensor log_pi\n) {\n int N = x.size(0);\n int K = mus.size(0);\n int D = x.size(1);\n \n auto log_resp = torch::empty({N, K}, x.options());\n auto lse = torch::empty({N}, x.options());\n auto log_resp_norm = torch::empty({N, K}, x.options());\n \n dim3 block1(32, 4);\n dim3 grid1((N + block1.x - 1) / block1.x, (K + block1.y - 1) / block1.y);\n \n log_gaussian_batched_kernel<<<grid1, block1>>>(\n x.data_ptr<float>(),\n mus.data_ptr<float>(),\n L.data_ptr<float>(),\n log_det.data_ptr<float>(),\n log_pi.data_ptr<float>(),\n log_resp.data_ptr<float>(),\n N, K, D\n );\n \n // Logsumexp and normalization\n int block2 = 256;\n int grid2 = (N + block2 - 1) / block2;\n \n logsumexp_norm_kernel<<<grid2, block2>>>(\n log_resp.data_ptr<float>(),\n lse.data_ptr<float>(),\n log_resp_norm.data_ptr<float>(),\n N, K\n );\n \n return std::make_tuple(log_resp_norm, lse, log_resp);\n}\n\nstd::tuple<torch::Tensor, torch::Tensor> m_step_stats_cuda(\n torch::Tensor resp,\n torch::Tensor x\n) {\n int N = x.size(0);\n int K = resp.size(1);\n int D = x.size(1);\n \n auto N_k = torch::empty({K}, x.options());\n auto new_mus = torch::empty({K, D}, x.options());\n \n int block = 256;\n int grid = (K + block - 1) / block;\n \n compute_stats_kernel<<<grid, block>>>(\n resp.data_ptr<float>(),\n x.data_ptr<float>(),\n N_k.data_ptr<float>(),\n new_mus.data_ptr<float>(),\n N, K, D\n );\n \n return std::make_tuple(N_k, new_mus);\n}\n\ntorch::Tensor compute_covs_cuda(\n torch::Tensor x,\n torch::Tensor new_mus,\n torch::Tensor resp,\n torch::Tensor N_k\n) {\n int N = x.size(0);\n int K = new_mus.size(0);\n int D = x.size(1);\n \n auto new_covs = torch::zeros({K, D, D}, x.options());\n \n // One block per k\n // 2D thread block for (i, j)\n dim3 block(D, D);\n dim3 grid(K);\n \n compute_covs_kernel<<<grid, block>>>(\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, K, D\n );\n \n return new_covs;\n}\n\"\"\"\n\ngmm_module = load_inline(\n name='gmm_cuda',\n cpp_sources='',\n cuda_sources=cuda_source,\n functions=['e_step_cuda', 'm_step_stats_cuda', 'compute_covs_cuda'],\n verbose=False,\n extra_cuda_cflags=['-O3']\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n One iteration of Expectation-Maximization for a Gaussian Mixture Model.\n E-step: compute soft assignments (responsibilities).\n M-step: update means, covariances, and mixing weights.\n Using custom CUDA kernels for performance.\n \"\"\"\n\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 \"\"\"\n Args:\n x: (N, dim) - data points\n Returns:\n (new_mus, new_covs, new_log_pi, log_lik):\n new_mus: (K, dim)\n new_covs: (K, dim, dim)\n new_log_pi: (K,)\n log_lik: scalar - total log-likelihood\n \"\"\"\n N, D = x.shape\n K = self.K\n \n # Normalize log_pi\n log_pi = self.log_pi - torch.logsumexp(self.log_pi, dim=0)\n \n # Compute Cholesky decomposition of covariances\n L = torch.linalg.cholesky(self.covs) # (K, D, D)\n log_det = 2.0 * L.diagonal(dim1=-2, dim2=-1).log().sum(-1) # (K,)\n \n # E-step using CUDA kernel\n log_resp_norm, lse, _ = gmm_module.e_step_cuda(\n x.contiguous(), \n self.mus.contiguous(),\n L.contiguous(),\n log_det.contiguous(),\n log_pi.contiguous()\n )\n \n log_lik = lse.sum()\n resp = torch.exp(log_resp_norm) # (N, K)\n \n # M-step using CUDA kernels\n N_k, new_mus = gmm_module.m_step_stats_cuda(\n resp.contiguous(),\n x.contiguous()\n )\n \n new_covs = gmm_module.compute_covs_cuda(\n x.contiguous(),\n new_mus.contiguous(),\n resp.contiguous(),\n N_k.contiguous()\n )\n \n # Add regularization to diagonal\n new_covs = new_covs + 1e-4 * torch.eye(D, device=x.device, dtype=x.dtype).unsqueeze(0)\n \n new_log_pi = N_k.log() - math.log(N)\n \n return new_mus, new_covs, new_log_pi, log_lik\n\n\ndim = 8\nnum_components = 5\nnum_data = 2048\n\n\ndef get_inputs():\n return [torch.randn(num_data, dim)]\n\n\ndef get_init_inputs():\n return [dim, num_components]"
}