reasoning
Let me understand the task:
1. I need to implement a CUDA kernel for SVGD (Stein Variational Gradient Descent) update step
2. The forward method takes particles (N, dim) and returns phi (N, dim)
3. I need to compute:
- score = -particles (gradient of log p(x) for N(0,I))
- Pairwise squared distances dist_sq (N, N)
- Median of dist_sq for bandwidth h
- RBF kernel K = exp(-dist_sq / (2*h))
- Gradient of kernel grad_K = -diff / h * K.unsqueeze(-1)
- phi = (K @ score + grad_K.sum(dim=0)) / N
The main computational challenges are:
1. Computing pairwise squared distances (N x N matrix)
2. Finding the median of dist_sq
3. Computing the kernel matrix and its gradient
4. The final matrix-vector product and summation
Let me write a CUDA kernel that efficiently handles this:
Key operations:
1. `diff = particles.unsqueeze(0) - particles.unsqueeze(1)` -> (N, N, dim)
- This is computing diff[i,j,k] = particles[j,k] - particles[i,k]
2. `dist_sq = (diff ** 2).sum(-1)` -> (N, N)
- dist_sq[i,j] = sum_k (particles[j,k] - particles[i,k])^2
3. Find median of dist_sq (N*N elements)
4. `K = torch.exp(-dist_sq / (2.0 * h))` -> (N, N)
5. `grad_K = -diff / h * K.unsqueeze(-1)` -> (N, N, dim)
- grad_K[i,j,k] = -diff[i,j,k] / h * K[i,j]
6. `phi = (K @ score + grad_K.sum(dim=0)) / N`
- K @ score: (N, N) @ (N, dim) -> (N, dim)
- phi1[i,k] = sum_j K[i,j] * score[j,k]
- grad_K.sum(dim=0): sum over first axis -> (N, dim)
- phi2[j,k] = sum_i grad_K[i,j,k]
- phi = (phi1 + phi2) / N
Now I'll implement an optimized CUDA kernel. The median computation on GPU is tricky—I'll use a Thrust-based approach for efficiency, but I need to ensure the kernel structure is correct and handles all edge cases properly.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\nimport math\n\ncuda_source = r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <thrust/device_vector.h>\n#include <thrust/sort.h>\n#include <c10/cuda/CUDAStream.h>\n\n// Block sizes\n#define BLOCK_SIZE 256\n#define TILE_DIM 32\n\n// Compute pairwise squared distances and store in row-major\n__global__ void compute_dist_sq_kernel(\n const float* __restrict__ particles,\n float* __restrict__ dist_sq,\n int N, int dim\n) {\n int i = blockIdx.y;\n int j = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (i >= N || j >= N) return;\n \n float sum = 0.0f;\n for (int k = 0; k < dim; k++) {\n float diff = particles[j * dim + k] - particles[i * dim + k];\n sum += diff * diff;\n }\n dist_sq[i * N + j] = sum;\n}\n\n// Compute score = -particles\n__global__ void compute_score_kernel(\n const float* __restrict__ particles,\n float* __restrict__ score,\n int N, int dim\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = N * dim;\n \n if (idx < total) {\n score[idx] = -particles[idx];\n }\n}\n\n// Compute kernel matrix K = exp(-dist_sq / (2*h))\n__global__ void compute_kernel_kernel(\n const float* __restrict__ dist_sq,\n float* __restrict__ K,\n float inv_2h,\n int N\n) {\n int i = blockIdx.y;\n int j = blockIdx.x * blockDim.x + threadIdx.x;\n \n if (i >= N || j >= N) return;\n \n float d = dist_sq[i * N + j];\n K[i * N + j] = expf(-d * inv_2h);\n}\n\n// Compute phi = (K @ score + grad_K.sum(dim=0)) / N\n// grad_K[i,j,k] = -(particles[j,k] - particles[i,k]) / h * K[i,j]\n// phi1[j,k] = sum_i K[i,j] * score[j,k] = score[j,k] * sum_i K[i,j]\n// phi2[j,k] = sum_i grad_K[i,j,k] = sum_i -(particles[j,k] - particles[i,k]) / h * K[i,j]\n// = (1/h) * sum_i (particles[i,k] - particles[j,k]) * K[i,j]\n// = (1/h) * (sum_i particles[i,k] * K[i,j] - particles[j,k] * sum_i K[i,j])\n\n__global__ void compute_phi_kernel(\n const float* __restrict__ particles,\n const float* __restrict__ K,\n const float* __restrict__ score,\n float* __restrict__ phi,\n float inv_h,\n int N, int dim\n) {\n int j = blockIdx.x;\n int k = blockIdx.y;\n \n if (j >= N || k >= dim) return;\n \n float sum_K_col = 0.0f;\n float sum_pK = 0.0f;\n float K_score_sum = 0.0f;\n \n float score_jk = score[j * dim + k];\n float p_jk = particles[j * dim + k];\n \n // Iterate over i (rows of K)\n // K[i,j] is at K[i*N + j]\n for (int i = 0; i < N; i++) {\n float Kij = K[i * N + j];\n sum_K_col += Kij;\n sum_pK += particles[i * dim + k] * Kij;\n K_score_sum += Kij * score[i * dim + k];\n }\n \n // phi1[j,k] = sum_i K[i,j] * score[i,k] = K_score_sum\n // phi2[j,k] = (1/h) * (sum_pK - p_jk * sum_K_col)\n float phi1 = K_score_sum;\n float phi2 = inv_h * (sum_pK - p_jk * sum_K_col);\n \n phi[j * dim + k] = (phi1 + phi2) / (float)N;\n}\n\n// Optimized version using shared memory for reduction\n__global__ void compute_phi_optimized_kernel(\n const float* __restrict__ particles,\n const float* __restrict__ K,\n const float* __restrict__ score,\n float* __restrict__ phi,\n float inv_h,\n int N, int dim\n) {\n extern __shared__ float shared_data[];\n \n int j = blockIdx.x;\n int k = blockIdx.y;\n \n if (j >= N || k >= dim) return;\n \n int tid = threadIdx.x;\n int blockSize = blockDim.x;\n \n float score_jk = score[j * dim + k];\n float p_jk = particles[j * dim + k];\n \n float local_K_score = 0.0f;\n float local_sum_K_col = 0.0f;\n float local_sum_pK = 0.0f;\n \n // Strided loop over i\n for (int i = tid; i < N; i += blockSize) {\n float Kij = K[i * N + j];\n float pik = particles[i * dim + k];\n float sik = score[i * dim + k];\n \n local_K_score += Kij * sik;\n local_sum_K_col += Kij;\n local_sum_pK += pik * Kij;\n }\n \n // Shared memory for reduction\n float* s_K_score = shared_data;\n float* s_sum_K = shared_data + blockSize;\n float* s_sum_pK = shared_data + 2 * blockSize;\n \n s_K_score[tid] = local_K_score;\n s_sum_K[tid] = local_sum_K_col;\n s_sum_pK[tid] = local_sum_pK;\n \n __syncthreads();\n \n // Block reduction\n for (int s = blockSize / 2; s > 0; s >>= 1) {\n if (tid < s) {\n s_K_score[tid] += s_K_score[tid + s];\n s_sum_K[tid] += s_sum_K[tid + s];\n s_sum_pK[tid] += s_sum_pK[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n float phi1 = s_K_score[0];\n float phi2 = inv_h * (s_sum_pK[0] - p_jk * s_sum_K[0]);\n phi[j * dim + k] = (phi1 + phi2) / (float)N;\n }\n}\n\ntorch::Tensor svgd_forward_cuda(torch::Tensor particles) {\n int N = particles.size(0);\n int dim = particles.size(1);\n \n auto options = particles.options();\n \n // Allocate intermediate tensors\n auto dist_sq = torch::empty({N, N}, options);\n auto K = torch::empty({N, N}, options);\n auto score = torch::empty({N, dim}, options);\n auto phi = torch::empty({N, dim}, options);\n \n // Compute score = -particles\n int total_threads = N * dim;\n int blocks = (total_threads + BLOCK_SIZE - 1) / BLOCK_SIZE;\n compute_score_kernel<<<blocks, BLOCK_SIZE>>>(\n particles.data_ptr<float>(),\n score.data_ptr<float>(),\n N, dim\n );\n \n // Compute pairwise squared distances\n dim3 grid((N + BLOCK_SIZE - 1) / BLOCK_SIZE, N);\n compute_dist_sq_kernel<<<grid, BLOCK_SIZE>>>(\n particles.data_ptr<float>(),\n dist_sq.data_ptr<float>(),\n N, dim\n );\n \n // Find median using Thrust sort\n auto dist_sq_flat = dist_sq.view(-1);\n thrust::device_ptr<float> d_ptr(dist_sq_flat.data_ptr<float>());\n thrust::sort(d_ptr, d_ptr + N * N);\n \n float median_sq;\n cudaMemcpy(&median_sq, dist_sq_flat.data_ptr<float>() + (N * N) / 2, sizeof(float), cudaMemcpyDeviceToHost);\n \n // Clamp median_sq to minimum\n if (median_sq < 1e-5f) median_sq = 1e-5f;\n \n // Compute h\n float log_N = logf((float)N);\n if (log_N < 1.0f) log_N = 1.0f;\n float h = median_sq / log_N;\n float inv_2h = 0.5f / h;\n float inv_h = 1.0f / h;\n \n // Compute kernel matrix\n compute_kernel_kernel<<<grid, BLOCK_SIZE>>>(\n dist_sq.data_ptr<float>(),\n K.data_ptr<float>(),\n inv_2h,\n N\n );\n \n // Compute phi using optimized kernel\n dim3 grid_phi(N, dim);\n int block_size = min(256, N);\n int shared_mem = block_size * 3 * sizeof(float);\n compute_phi_optimized_kernel<<<grid_phi, block_size, shared_mem>>>(\n particles.data_ptr<float>(),\n K.data_ptr<float>(),\n score.data_ptr<float>(),\n phi.data_ptr<float>(),\n inv_h,\n N, dim\n );\n \n return phi;\n}\n\nstd::vector<torch::Tensor> svgd_forward_batch_cuda(torch::Tensor particles) {\n auto phi = svgd_forward_cuda(particles);\n return {phi};\n}\n'''\n\ncpp_source = r'''\nstd::vector<torch::Tensor> svgd_forward_batch_cuda(torch::Tensor particles);\n'''\n\nsvgd_module = load_inline(\n name='svgd_cuda',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['svgd_forward_batch_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Stein Variational Gradient Descent (SVGD) update step.\n CUDA-optimized implementation.\n \"\"\"\n\n def __init__(self, dim):\n super().__init__()\n self.dim = dim\n self.register_buffer(\"target_mean\", torch.zeros(dim))\n\n def forward(self, particles: torch.Tensor) -> torch.Tensor:\n # Ensure contiguous float32 input on same device\n particles = particles.contiguous().float()\n \n # Call CUDA kernel\n results = svgd_module.svgd_forward_batch_cuda(particles)\n return results[0]"
}