FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 28_DirichletMultinomialLogLikelihood.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
18637
wall clock
628.6s
truncated
yes
backend
cuda fp32
variant
popcorn
runtime
0.01 μs
ref runtime
0.05 μs
speedup
3.92x
started
2026-05-11T08:58:29Z
timingkernelbench.timing
kernel runtime
0.01 μs
ref runtime
0.05 μs
speedup
3.92x
kernel mean / median / std
0.01 / — / 5.58e-04 μs
ref mean / median / std
0.05 / — / 4.05e-04 μs
numerical precisioneval_kernel_against_ref
max abs error
4.88e-04
mean abs error
3.87e-05
max rel error
8.58e-08
mean rel error
6.07e-09
gpu memoryextended_metrics.measure_memory
kernel peak
0.51 MB
ref peak
1.51 MB
kernel / ref
0.34x
kernel launches / fusiontorch.profiler
kernel launches
5
ref launches
18
fusion ratio (ref / kernel)
3.60x
total cuda time
15 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.125
dram util %
5.0%
compute util %
12.5%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
167.10 GB/s
dram util %
5.0%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
56.6%
bottleneck
compute-bound
dominant pipe %
12.5%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
163.7%
ld sectors / req
7.77
st sectors / req
—
regs / thread
52
smem / block
5248 B
block size
512
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
910.8%
short_scoreboard
288.3%
mio_throttle
23.5%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn


class Model(nn.Module):
    """
    Log-likelihood under the Dirichlet-Multinomial (Polya) distribution.
    Given count vectors n and concentration parameters alpha, computes
    log p(n | alpha) using the log-gamma formulation.  Core computation
    in Bayesian topic models and mixed-membership models.
    """

    def __init__(self, vocab_size):
        super().__init__()
        self.vocab_size = vocab_size
        self.log_alpha = nn.Parameter(torch.zeros(vocab_size))

    def forward(self, counts: torch.Tensor) -> torch.Tensor:
        """
        Args:
            counts: (B, V) – integer count vectors (as float for differentiability)
        Returns:
            log_lik: (B,) – log p(counts | alpha) for each sample
        """
        alpha = torch.exp(self.log_alpha).unsqueeze(0)  # (1, V)
        N = counts.sum(-1)  # (B,)
        alpha_sum = alpha.sum(-1)  # (1,)

        # log p(n | alpha) = log Gamma(alpha_sum) - log Gamma(N + alpha_sum)
        #                   + sum_v [ log Gamma(n_v + alpha_v) - log Gamma(alpha_v) ]
        log_lik = (
            torch.lgamma(alpha_sum)
            - torch.lgamma(N + alpha_sum)
            + (torch.lgamma(counts + alpha) - torch.lgamma(alpha)).sum(-1)
        )
        return log_lik


vocab_size = 256
batch_size = 512


def get_inputs():
    bs = popcorn_pri.jitter_int(batch_size)
    counts = torch.randint(0, 10, (bs, vocab_size)).float()
    return [counts]


def get_init_inputs():
    return [vocab_size]
turn 0 · FINAL · 1 tool call(s) · 628.6s turn · 167.6s LLM · tokens in=1,189 · out=17,448 · total=18,637 · 23,022 chars (click to expand)
turn 0628.6s turn · 167.6s LLM · 461.0s tools · tokens in=1,189 · out=17,448 · total=18,637 · FINAL · reasoning clipped
reasoning
reasoning (truncated 49056 chars): Let me analyze the reference implementation to understand what needs to be done: 1. The model computes log-likelihood under Dirichlet-Multinomial distribution 2. Key operations: - `alpha = torch.exp(self.log_alpha).unsqueeze(0)` - exponential and unsqueeze - `N = counts.sum(-1)` - sum reduction across vocab dimension - `alpha_sum = alpha.sum(-1)` - sum of alpha values - Main computation involves `torch.lgamma` operations The forward pass computes: ``` log_lik = lgamma(alpha_sum) - lgamma(N + alpha_sum) + sum_v[lgamma(n_v + alpha_v) - lgamma(alpha_v)] ``` For a custom CUDA kernel, I need to: 1. Compute alpha = exp(log_alpha) - this is element-wise 2. Compute N = sum of counts for each batch element - reduction 3. Compute alpha_sum = sum of alpha values - reduction 4. For each batch element, compute the log-likelihood using lgamma The main challenge is that CUDA doesn't have a built-in lgamma function that's easily accessible. However, we can use CUDA's math functions - CUDA does provide `lgamma` in device code. Let me write a CUDA kernel that: 1. Each thread handles one batch element 2. Computes the sum of counts for that batch element 3. Computes lgamma terms for all vocab elements and accumulates 4. Uses shared memory for the alpha values (since they're shared across batch) Actually, let me think about parallelization more carefully: - Batch size = 512, vocab_size = 256 - We need to compute lgamma for each (batch, vocab) pair in the main sum term - We need reductions for N and alpha_sum One approach: - Use one thread block per batch element - Each thread in the block handles multiple vocab elements - Use warp reduction or block reduction for sums Let me write this kernel: ```python import torch from torch.utils.cpp_extension import load_inline cuda_source = """ #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <cmath> __global__ void dirichlet_multinomial_ll_kernel( const float* __restrict__ counts, const float* __restrict__ log_alpha, float* __restrict__ output, int batch_size, int vocab_size ) { int batch_idx = blockIdx.x; if (batch_idx >= batch_size) return; int tid = threadIdx.x; int block_size = blockDim.x; // Shared memory for alpha and partial sums extern __shared__ float shared_mem[]; float* alpha_shared = shared_mem; // vocab_size elements float* partial_sums = &alpha_shared[vocab_size]; // blockDim.x elements // Load alpha values to shared memory for (int v = tid; v < vocab_size; v += block_size) { alpha_shared[v] = expf(log_alpha[v]); } __syncthreads(); // Compute alpha_sum (same for all batches) float alpha_sum_local = 0.0f; for (int v = tid; v < vocab_size; v += block_size) { alpha_sum_local += alpha_shared[v]; } partial_sums[tid] = alpha_sum_local; __syncthreads(); // Block reduction for alpha_sum for (int stride = block_size / 2; stride > 0; stride >>= 1) { if (tid < stride) { partial_sums[tid] += partial_sums[tid + stride]; } __syncthreads(); } float alpha_sum = partial_sums[0]; // Compute N (sum of counts) for this batch const float* counts_batch = counts + batch_idx * vocab_size; float N_local = 0.0f; for (int v = tid; v < vocab_size; v += block_size) { N_local += counts_batch[v]; } partial_sums[tid] = N_local; __syncthreads(); // Block reduction for N for (int stride = block_size / 2; stride > 0; stride >>= 1) { if (tid < stride) { partial_sums[tid] += partial_sums[tid + stride]; } __syncthreads(); } float N = partial_sums[0]; // Compute the main sum term float sum_term_local = 0.0f; for (int v = tid; v < vocab_size; v += block_size) { float alpha_v = alpha_shared[v]; float n_v = counts_batch[v]; sum_term_local += lgammaf(n_v + alpha_v) - lgammaf(alpha_v); } partial_sums[tid] = sum_term_local; __syncthreads(); // Block reduction for sum_term for (int stride = block_size / 2; stride > 0; stride >>= 1) { if (tid < stride) { partial_sums[tid] += partial_sums[tid + stride]; } __syncthreads(); } float sum_term = partial_sums[0]; // Final result if (tid == 0) { output[batch_idx] = lgammaf(alpha_sum) - lgammaf(N + alpha_sum) + sum_term; } } torch::Tensor dirichlet_multinomial_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha) { int batch_size = counts.size(0); int vocab_size = counts.size(1); auto output = torch::empty({batch_size}, counts.options()); int block_size = 256; int shared_mem_size = vocab_size * sizeof(float) + block_size * sizeof(float); dirichlet_multinomial_ll_kernel<<<batch_size, block_size, shared_mem_size>>>( counts.data_ptr<float>(), log_alpha.data_ptr<float>(), output.data_ptr<float>(), batch_size, vocab_size ); return output; } """ module = load_inline( name='dirichlet_multinomial_ll', cpp_sources=['torch::Tensor dirichlet_multinomial_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha);'], cuda_sources=[cuda_source], functions=['dirichlet_multinomial_ll_cuda'], verbose=True ) class ModelNew(nn.Module): def __init__(self, vocab_size): super().__init__() self.vocab_size = vocab_size self.log_alpha = nn.Parameter(torch.zeros(vocab_size)) def forward(self, counts: torch.Tensor) -> torch.Tensor: return module.dirichlet_multinomial_ll_cuda(counts, self.log_alpha) ``` Wait, I need to check the block size. vocab_size is 256, and I'm using 256 threads. That should work well. Actually, let me reconsider the shared memory layout. I'm using vocab_size (256) floats for alpha and blockDim.x (256) floats for partial sums. That's 256 * 4 * 2 = 2048 bytes, which is fine. Let me also think about optimization: 1. The lgamma function is expensive - this will be the bottleneck 2. We're computing lgamma(alpha_sum) once per batch, but alpha_sum is the same for all batches 3. We're computing lgamma(alpha_v) for each v, but these are also the same across batches So we could precompute lgamma(alpha_sum) and lgamma(alpha_v) values and pass them to the kernel. But for now, let's see if the basic kernel works. Actually, let me optimize a bit more: - Precompute alpha and lgamma(alpha) values - Precompute alpha_sum and lgamma(alpha_sum) But wait, log_alpha is a parameter that gets updated during training, so we can't really precompute these once. We could compute them once per forward pass though. Let me refine the approach: 1. Compute alpha = exp(log_alpha) - once 2. Compute lgamma_alpha = lgamma(alpha) - once 3. Compute alpha_sum = sum(alpha) - once 4. Compute lgamma_alpha_sum = lgamma(alpha_sum) - once 5. For each batch, compute N and the sum term, then combine This reduces redundant lgamma calculations across batches. Let me rewrite: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline cuda_source = """ #include <torch/extension.h> #include <cuda.h> #include <cuda_runtime.h> #include <cmath> __global__ void dm_ll_precompute_kernel( const float* __restrict__ log_alpha, float* __restrict__ alpha, float* __restrict__ lgamma_alpha, float* __restrict__ alpha_sum, float* __restrict__ lgamma_alpha_sum, int vocab_size ) { int tid = threadIdx.x; int block_size = blockDim.x; // Shared memory for reduction extern __shared__ float shared_mem[]; float* partial_sums = shared_mem; // Compute alpha and partial sums float alpha_local = 0.0f; for (int v = tid; v < vocab_size; v += block_size) { float a = expf(log_alpha[v]); alpha[v] = a; lgamma_alpha[v] = lgammaf(a); alpha_local + ...[ omitted 49056 characters ]... rn; int tid = threadIdx.x; int block_size = blockDim.x; extern __shared__ float shared_mem[]; float* shared_scalars = shared_mem; float* partial_sums = &shared_scalars[2]; if (tid == 0) { shared_scalars[0] = alpha_sum_ptr[0]; shared_scalars[1] = lgamma_alpha_sum_ptr[0]; } __syncthreads(); float alpha_sum = shared_scalars[0]; float lgamma_alpha_sum = shared_scalars[1]; const float* counts_batch = counts + batch_idx * vocab_size; float N_local = 0.0f; float sum_term_local = 0.0f; for (int v = tid; v < vocab_size; v += block_size) { float n_v = counts_batch[v]; float a_v = alpha[v]; N_local += n_v; sum_term_local += lgammaf(n_v + a_v) - lgamma_alpha[v]; } partial_sums[tid] = N_local; partial_sums[block_size + tid] = sum_term_local; __syncthreads(); for (int stride = block_size / 2; stride > 0; stride >>= 1) { if (tid < stride) { partial_sums[tid] += partial_sums[tid + stride]; partial_sums[block_size + tid] += partial_sums[block_size + tid + stride]; } __syncthreads(); } if (tid == 0) { float N = partial_sums[0]; float sum_term = partial_sums[block_size]; output[batch_idx] = lgamma_alpha_sum - lgammaf(N + alpha_sum) + sum_term; } } torch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha) { int batch_size = counts.size(0); int vocab_size = counts.size(1); auto alpha = torch::empty({vocab_size}, log_alpha.options()); auto lgamma_alpha = torch::empty({vocab_size}, log_alpha.options()); auto alpha_sum_tensor = torch::empty({1}, log_alpha.options()); auto lgamma_alpha_sum_tensor = torch::empty({1}, log_alpha.options()); auto output = torch::empty({batch_size}, counts.options()); int block_size = 256; int shared_mem_precompute = block_size * sizeof(float); dm_precompute_kernel<<<1, block_size, shared_mem_precompute>>>( log_alpha.data_ptr<float>(), alpha.data_ptr<float>(), lgamma_alpha.data_ptr<float>(), alpha_sum_tensor.data_ptr<float>(), lgamma_alpha_sum_tensor.data_ptr<float>(), vocab_size ); int shared_mem_main = (2 + 2 * block_size) * sizeof(float); dm_main_kernel<<<batch_size, block_size, shared_mem_main>>>( counts.data_ptr<float>(), alpha.data_ptr<float>(), lgamma_alpha.data_ptr<float>(), alpha_sum_tensor.data_ptr<float>(), lgamma_alpha_sum_tensor.data_ptr<float>(), output.data_ptr<float>(), batch_size, vocab_size ); return output; } """ module = load_inline( name='dm_ll_v3', cpp_sources=['torch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha);'], cuda_sources=[cuda_source], functions=['dm_ll_cuda'], verbose=True ) class ModelNew(nn.Module): def __init__(self, vocab_size): super().__init__() self.vocab_size = vocab_size self.log_alpha = nn.Parameter(torch.zeros(vocab_size)) def forward(self, counts: torch.Tensor) -> torch.Tensor: return module.dm_ll_cuda(counts, self.log_alpha) ``` Actually, I realize there's one more potential issue. The second kernel uses values computed by the first kernel (alpha_sum_tensor, lgamma_alpha_sum_tensor). Between the two kernel launches, I'm not explicitly synchronizing. In CUDA, kernel launches are asynchronous, and subsequent kernels can start before previous ones finish, as long as there are no dependencies. But in this case, there IS a dependency: dm_main_kernel reads from alpha_sum_tensor and lgamma_alpha_sum_tensor, which are written by dm_precompute_kernel. CUDA's stream semantics ensure that kernels in the same stream are executed in order, and the second kernel will not start until the first one completes (for the data it depends on). Actually, CUDA's dependency checking is done at the memory level. If dm_main_kernel reads from a memory location that dm_precompute_kernel writes to, CUDA will automatically insert a dependency. But the way I'm passing pointers might not trigger this automatic dependency. To be safe, let me add an explicit synchronization or use CUDA events: Actually, PyTorch handles this. The kernel launches go through PyTorch's CUDA stream management, which ensures proper ordering. But wait, I'm not using PyTorch's CUDA stream for the kernel launches. I'm using the raw CUDA kernel launch syntax `<<<...>>>(...)`. This goes through CUDA's default stream, which should still work correctly because CUDA 7+ uses per-thread default streams, and operations in the same stream are ordered. Let me verify: when I call `dm_precompute_kernel<<<...>>>(...)` and then `dm_main_kernel<<<...>>>(...)`, both go into the same default stream, so the second kernel won't start until the first one completes. This should be correct. Actually, I should make sure PyTorch and my kernels use the same CUDA stream. PyTorch uses its own streams for tensor operations. If I launch a kernel in the default stream and PyTorch is using a different stream, there could be race conditions. Let me use PyTorch's CUDA stream for the kernel launches: Actually, `load_inline` handles this. The generated Python bindings use PyTorch's CUDA stream management. So my kernels should be launched in the correct stream. OK, I think the implementation is correct. Let me finalize it. Actually, let me reconsider the algorithm one more time: The reference computes: ```python alpha = torch.exp(self.log_alpha).unsqueeze(0) # (1, V) N = counts.sum(-1) # (B,) alpha_sum = alpha.sum(-1) # (1,) log_lik = ( torch.lgamma(alpha_sum) - torch.lgamma(N + alpha_sum) + (torch.lgamma(counts + alpha) - torch.lgamma(alpha)).sum(-1) ) ``` My kernel computes: 1. Precompute: alpha = exp(log_alpha), lgamma_alpha = lgamma(alpha), alpha_sum = sum(alpha), lgamma_alpha_sum = lgamma(alpha_sum) 2. For each batch: - N = sum(counts) - sum_term = sum(lgamma(n_v + alpha_v) - lgamma_alpha_v) - output = lgamma_alpha_sum - lgamma(N + alpha_sum) + sum_term This matches the reference. Good. One thing I'm not sure about: the reference uses `counts + alpha` where alpha has shape (1, V) and counts has shape (B, V). This broadcasts alpha to shape (B, V) and adds element-wise. My kernel handles this correctly by iterating over vocab for each batch and using the same alpha values. Let me also double-check the indexing: - `log_alpha` has shape (V,) - 1D tensor - `counts` has shape (B, V) - 2D tensor - `alpha_sum` is a scalar In my kernel: - `log_alpha.data_ptr<float>()` gives a pointer to the first element of log_alpha (which is a 1D tensor) - `counts.data_ptr<float>()` gives a pointer to the first element of counts (which is a 2D tensor, but in memory it's a contiguous 1D array with B*V elements) When I access `counts_batch[v]`, I'm accessing `counts + batch_idx * vocab_size + v`, which is correct. OK, I'm confident the implementation is correct. Let me submit it. One final thought: should I try to optimize further? The current implementation should be faster than PyTorch because: 1. It avoids Python overhead 2. It uses a single pass over the data (well, two kernel passes, but no intermediate tensors for most operations) 3. It precomputes lgamma(alpha) once instead of for each batch Let me submit this version and see how it performs. Wait, I should double check one thing. In the reference: ```python alpha = torch.exp(self.log_alpha).unsqueeze(0) # (1, V) ``` The unsqueeze adds a dimension at position 0, making alpha shape (1, V). This is just for broadcasting purposes. The actual values are the same as exp(log_alpha). In my kernel, I compute `alpha = exp(log_alpha)` which gives shape (V,). I don't need to unsqueeze because I'm handling the iteration manually. OK, the implementation is correct. Let me submit.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n__global__ void dm_precompute_kernel(\n const float* __restrict__ log_alpha,\n float* __restrict__ alpha,\n float* __restrict__ lgamma_alpha,\n float* __restrict__ alpha_sum_out,\n float* __restrict__ lgamma_alpha_sum_out,\n int vocab_size\n) {\n int tid = threadIdx.x;\n int block_size = blockDim.x;\n \n extern __shared__ float shared_mem[];\n float* partial_sums = shared_mem;\n \n float alpha_sum_local = 0.0f;\n for (int v = tid; v < vocab_size; v += block_size) {\n float a = expf(log_alpha[v]);\n alpha[v] = a;\n lgamma_alpha[v] = lgammaf(a);\n alpha_sum_local += a;\n }\n partial_sums[tid] = alpha_sum_local;\n __syncthreads();\n \n for (int stride = block_size / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n partial_sums[tid] += partial_sums[tid + stride];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n float as = partial_sums[0];\n alpha_sum_out[0] = as;\n lgamma_alpha_sum_out[0] = lgammaf(as);\n }\n}\n\n__global__ void dm_main_kernel(\n const float* __restrict__ counts,\n const float* __restrict__ alpha,\n const float* __restrict__ lgamma_alpha,\n const float* __restrict__ alpha_sum_ptr,\n const float* __restrict__ lgamma_alpha_sum_ptr,\n float* __restrict__ output,\n int batch_size,\n int vocab_size\n) {\n int batch_idx = blockIdx.x;\n if (batch_idx >= batch_size) return;\n \n int tid = threadIdx.x;\n int block_size = blockDim.x;\n \n extern __shared__ float shared_mem[];\n float* shared_scalars = shared_mem;\n float* partial_sums = &shared_scalars[2];\n \n if (tid == 0) {\n shared_scalars[0] = alpha_sum_ptr[0];\n shared_scalars[1] = lgamma_alpha_sum_ptr[0];\n }\n __syncthreads();\n \n float alpha_sum = shared_scalars[0];\n float lgamma_alpha_sum = shared_scalars[1];\n \n const float* counts_batch = counts + batch_idx * vocab_size;\n \n float N_local = 0.0f;\n float sum_term_local = 0.0f;\n for (int v = tid; v < vocab_size; v += block_size) {\n float n_v = counts_batch[v];\n float a_v = alpha[v];\n N_local += n_v;\n sum_term_local += lgammaf(n_v + a_v) - lgamma_alpha[v];\n }\n \n partial_sums[tid] = N_local;\n partial_sums[block_size + tid] = sum_term_local;\n __syncthreads();\n \n for (int stride = block_size / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n partial_sums[tid] += partial_sums[tid + stride];\n partial_sums[block_size + tid] += partial_sums[block_size + tid + stride];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n float N = partial_sums[0];\n float sum_term = partial_sums[block_size];\n output[batch_idx] = lgamma_alpha_sum - lgammaf(N + alpha_sum) + sum_term;\n }\n}\n\ntorch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha) {\n int batch_size = counts.size(0);\n int vocab_size = counts.size(1);\n \n auto alpha = torch::empty({vocab_size}, log_alpha.options());\n auto lgamma_alpha = torch::empty({vocab_size}, log_alpha.options());\n auto alpha_sum_tensor = torch::empty({1}, log_alpha.options());\n auto lgamma_alpha_sum_tensor = torch::empty({1}, log_alpha.options());\n auto output = torch::empty({batch_size}, counts.options());\n \n int block_size = 256;\n \n int shared_mem_precompute = block_size * sizeof(float);\n dm_precompute_kernel<<<1, block_size, shared_mem_precompute>>>(\n log_alpha.data_ptr<float>(),\n alpha.data_ptr<float>(),\n lgamma_alpha.data_ptr<float>(),\n alpha_sum_tensor.data_ptr<float>(),\n lgamma_alpha_sum_tensor.data_ptr<float>(),\n vocab_size\n );\n \n int shared_mem_main = (2 + 2 * block_size) * sizeof(float);\n dm_main_kernel<<<batch_size, block_size, shared_mem_main>>>(\n counts.data_ptr<float>(),\n alpha.data_ptr<float>(),\n lgamma_alpha.data_ptr<float>(),\n alpha_sum_tensor.data_ptr<float>(),\n lgamma_alpha_sum_tensor.data_ptr<float>(),\n output.data_ptr<float>(),\n batch_size,\n vocab_size\n );\n \n return output;\n}\n\"\"\"\n\nmodule = load_inline(\n name='dm_ll_v3',\n cpp_sources=['torch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha);'],\n cuda_sources=[cuda_source],\n functions=['dm_ll_cuda'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, vocab_size):\n super().__init__()\n self.vocab_size = vocab_size\n self.log_alpha = nn.Parameter(torch.zeros(vocab_size))\n \n def forward(self, counts: torch.Tensor) -> torch.Tensor:\n return module.dm_ll_cuda(counts, self.log_alpha)" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.01 μs Runtime stats: mean=0.01μs median=?μs std=0.00μs Numerical precision: max_abs_err=4.88e-04 mean_abs_err=3.87e-05 max_rel_err=8.58e-08 Memory: 0.51 MB peak (ref: 1.51 MB, ratio: 0.336x) Kernel launches: 5 (ref: 18, fusion ratio: 3.6) SOL score: 0.1251
final submitted kernel (161 lines, 4,903 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

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

__global__ void dm_precompute_kernel(
    const float* __restrict__ log_alpha,
    float* __restrict__ alpha,
    float* __restrict__ lgamma_alpha,
    float* __restrict__ alpha_sum_out,
    float* __restrict__ lgamma_alpha_sum_out,
    int vocab_size
) {
    int tid = threadIdx.x;
    int block_size = blockDim.x;
    
    extern __shared__ float shared_mem[];
    float* partial_sums = shared_mem;
    
    float alpha_sum_local = 0.0f;
    for (int v = tid; v < vocab_size; v += block_size) {
        float a = expf(log_alpha[v]);
        alpha[v] = a;
        lgamma_alpha[v] = lgammaf(a);
        alpha_sum_local += a;
    }
    partial_sums[tid] = alpha_sum_local;
    __syncthreads();
    
    for (int stride = block_size / 2; stride > 0; stride >>= 1) {
        if (tid < stride) {
            partial_sums[tid] += partial_sums[tid + stride];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        float as = partial_sums[0];
        alpha_sum_out[0] = as;
        lgamma_alpha_sum_out[0] = lgammaf(as);
    }
}

__global__ void dm_main_kernel(
    const float* __restrict__ counts,
    const float* __restrict__ alpha,
    const float* __restrict__ lgamma_alpha,
    const float* __restrict__ alpha_sum_ptr,
    const float* __restrict__ lgamma_alpha_sum_ptr,
    float* __restrict__ output,
    int batch_size,
    int vocab_size
) {
    int batch_idx = blockIdx.x;
    if (batch_idx >= batch_size) return;
    
    int tid = threadIdx.x;
    int block_size = blockDim.x;
    
    extern __shared__ float shared_mem[];
    float* shared_scalars = shared_mem;
    float* partial_sums = &shared_scalars[2];
    
    if (tid == 0) {
        shared_scalars[0] = alpha_sum_ptr[0];
        shared_scalars[1] = lgamma_alpha_sum_ptr[0];
    }
    __syncthreads();
    
    float alpha_sum = shared_scalars[0];
    float lgamma_alpha_sum = shared_scalars[1];
    
    const float* counts_batch = counts + batch_idx * vocab_size;
    
    float N_local = 0.0f;
    float sum_term_local = 0.0f;
    for (int v = tid; v < vocab_size; v += block_size) {
        float n_v = counts_batch[v];
        float a_v = alpha[v];
        N_local += n_v;
        sum_term_local += lgammaf(n_v + a_v) - lgamma_alpha[v];
    }
    
    partial_sums[tid] = N_local;
    partial_sums[block_size + tid] = sum_term_local;
    __syncthreads();
    
    for (int stride = block_size / 2; stride > 0; stride >>= 1) {
        if (tid < stride) {
            partial_sums[tid] += partial_sums[tid + stride];
            partial_sums[block_size + tid] += partial_sums[block_size + tid + stride];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        float N = partial_sums[0];
        float sum_term = partial_sums[block_size];
        output[batch_idx] = lgamma_alpha_sum - lgammaf(N + alpha_sum) + sum_term;
    }
}

torch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha) {
    int batch_size = counts.size(0);
    int vocab_size = counts.size(1);
    
    auto alpha = torch::empty({vocab_size}, log_alpha.options());
    auto lgamma_alpha = torch::empty({vocab_size}, log_alpha.options());
    auto alpha_sum_tensor = torch::empty({1}, log_alpha.options());
    auto lgamma_alpha_sum_tensor = torch::empty({1}, log_alpha.options());
    auto output = torch::empty({batch_size}, counts.options());
    
    int block_size = 256;
    
    int shared_mem_precompute = block_size * sizeof(float);
    dm_precompute_kernel<<<1, block_size, shared_mem_precompute>>>(
        log_alpha.data_ptr<float>(),
        alpha.data_ptr<float>(),
        lgamma_alpha.data_ptr<float>(),
        alpha_sum_tensor.data_ptr<float>(),
        lgamma_alpha_sum_tensor.data_ptr<float>(),
        vocab_size
    );
    
    int shared_mem_main = (2 + 2 * block_size) * sizeof(float);
    dm_main_kernel<<<batch_size, block_size, shared_mem_main>>>(
        counts.data_ptr<float>(),
        alpha.data_ptr<float>(),
        lgamma_alpha.data_ptr<float>(),
        alpha_sum_tensor.data_ptr<float>(),
        lgamma_alpha_sum_tensor.data_ptr<float>(),
        output.data_ptr<float>(),
        batch_size,
        vocab_size
    );
    
    return output;
}
"""

module = load_inline(
    name='dm_ll_v3',
    cpp_sources=['torch::Tensor dm_ll_cuda(torch::Tensor counts, torch::Tensor log_alpha);'],
    cuda_sources=[cuda_source],
    functions=['dm_ll_cuda'],
    verbose=False
)

class ModelNew(nn.Module):
    def __init__(self, vocab_size):
        super().__init__()
        self.vocab_size = vocab_size
        self.log_alpha = nn.Parameter(torch.zeros(vocab_size))
    
    def forward(self, counts: torch.Tensor) -> torch.Tensor:
        return module.dm_ll_cuda(counts, self.log_alpha)