Merge pull request 'finish poissonnllloss #20' (#43) from gsd123/GPUCodeForces:poissonnllloss into main

This commit is contained in:
Kuohais 2025-11-06 22:04:20 +08:00
commit 28641e7bd3
4 changed files with 440 additions and 0 deletions

View File

@ -0,0 +1,179 @@
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
BATCH_SIZE = 4096
FEATURE_DIM = 512
# --- 损失函数的参数 ---
LOG_INPUT = True
FULL = False
EPS = 1e-8
# -------------------------------------------------------------
class ModelNew(nn.Module):
def __init__(self):
super().__init__()
# 将 Python 端的常量存储为实例属性
self.log_input = LOG_INPUT
self.full = FULL
self.eps = EPS
self._compile_cuda_kernel()
def _compile_cuda_kernel(self):
cpp_source = """
#include <torch/extension.h>
// C++ 接口
torch::Tensor poisson_nll_forward_cuda(
torch::Tensor input,
torch::Tensor target,
bool log_input,
bool full,
float eps_val
);
"""
cuda_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cmath> // for logf, expf
#include <float.h>
// 块大小
#define BLOCK_SIZE 256
// 定义 PI
const float PI = 3.141592653589793f;
/*
* PoissonNLLLoss 融合核函数
* 这是一个标准的并行归约核函数
* 每个线程处理多个元素 (Grid-Stride Loop)计算它们的 loss 并累加
* 然后执行一个块内归约 (Block-level reduction)
* C++ host 端对所有块的和再次求和然后除以 N 得到 'mean'
*/
__global__ void poisson_nll_fused_kernel(
const float* __restrict__ input_data,
const float* __restrict__ target_data,
float* __restrict__ block_loss_sums_out, // (grid_size,)
int n_elements,
bool log_input,
bool full,
float eps_val
) {
__shared__ float s_data[BLOCK_SIZE];
float thread_loss_sum = 0.0f;
int grid_stride = gridDim.x * blockDim.x;
// Grid-Stride Loop 遍历所有元素
for (int idx = blockIdx.x * blockDim.x + threadIdx.x;
idx < n_elements;
idx += grid_stride)
{
float x = input_data[idx]; // 'input'
float y = target_data[idx]; // 'target'
float loss_val = 0.0f;
// --- 核心 Loss 计算 ---
if (log_input) {
// loss = exp(input) - target * input
loss_val = expf(x) - y * x;
} else {
// loss = input - target * log(input + eps)
loss_val = x - y * logf(x + eps_val);
}
// --- 'full' 模式的附加项 ---
// (target * log(target) - target + 0.5 * log(2 * pi * target))
if (full && y > 1.0f) {
float stirling_term = y * logf(y) - y + 0.5f * logf(2.0f * PI * y);
loss_val += stirling_term;
}
// 累加该线程处理的所有元素的 loss
thread_loss_sum += loss_val;
}
// --- 块内归约 (Sum) ---
s_data[threadIdx.x] = thread_loss_sum;
__syncthreads();
for (int offset = BLOCK_SIZE / 2; offset > 0; offset >>= 1) {
if (threadIdx.x < offset) {
s_data[threadIdx.x] += s_data[threadIdx.x + offset];
}
__syncthreads();
}
// 块中的第一个线程将块的总和写入全局内存
if (threadIdx.x == 0) {
block_loss_sums_out[blockIdx.x] = s_data[0];
}
}
// C++ 封装函数
torch::Tensor poisson_nll_forward_cuda(
torch::Tensor input,
torch::Tensor target,
bool log_input,
bool full,
float eps_val
) {
// 检查
TORCH_CHECK(input.is_cuda(), "input must be a CUDA tensor");
TORCH_CHECK(target.is_cuda(), "target must be a CUDA tensor");
input = input.contiguous();
target = target.contiguous();
const int n_elements = input.numel();
TORCH_CHECK(target.numel() == n_elements, "input and target must have the same number of elements");
if (n_elements == 0) {
return torch::tensor(0.0f, input.options());
}
// 分配一个张量来保存每个块的部分和
const int block_size = BLOCK_SIZE;
const int grid_size = std::max(1, (n_elements + block_size - 1) / block_size);
auto block_loss_sums = torch::empty({grid_size}, input.options());
// 启动 CUDA 核函数
poisson_nll_fused_kernel<<<grid_size, block_size>>>(
input.data_ptr<float>(),
target.data_ptr<float>(),
block_loss_sums.data_ptr<float>(),
n_elements,
log_input,
full,
eps_val
);
// 核函数返回后对所有块的和进行求和然后除以总元素数
// (reduction='mean')
return block_loss_sums.sum() / n_elements;
}
"""
# JIT (Just-In-Time) 编译
self.pnl_op = load_inline(
name="poisson_nll_op_v1",
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=["poisson_nll_forward_cuda"],
extra_cuda_cflags=["-O3"],
verbose=False
)
def forward(self, input_tensor: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
# 调用我们编译好的 CUDA C++ 函数
return self.pnl_op.poisson_nll_forward_cuda(
input_tensor,
target,
self.log_input,
self.full,
self.eps
)

View File

@ -0,0 +1,45 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
BATCH_SIZE = 4096
FEATURE_DIM = 512
# --- 损失函数的参数 ---
# log_input=True: loss = exp(input) - target * input
# log_input=False: loss = input - target * log(input + eps)
LOG_INPUT = True
# full=True: 添加 Stirling's approximation
FULL = False
EPS = 1e-8
class Model(nn.Module):
def __init__(self):
super().__init__()
self.criterion = nn.PoissonNLLLoss(
log_input=LOG_INPUT,
full=FULL,
eps=EPS,
reduction='mean'
)
def forward(self, input_tensor: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
# 在 PyTorch 中input_tensor 是文档中的 'input'
return self.criterion(input_tensor, target)
def get_inputs():
# Input (log_input=True 时) 可以是任意实数
input_tensor = torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32)
# Target 在 Poisson 分布中代表计数,且在 'full' 模式下会计算 log(target)
# 因此 target 必须是 >= 0 的。我们使用 rand 来确保
target = torch.rand(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32) * 10 # 乘以 10 以便有一些 > 1
return [input_tensor, target]
def get_init_inputs():
return []

139
S1/20/prompt.txt Normal file
View File

@ -0,0 +1,139 @@
You write custom CUDA kernels to replace the PyTorch operators in the given EvoNorm architecture to get speedups.
You have complete freedom to choose the set of operators you want to replace. You may make the decision to replace some operators with custom CUDA kernels and leave others unchanged. You may replace multiple operators with custom implementations, consider operator fusion opportunities (combining multiple operators into a single kernel, for example, combining normalization+affine_transform+nonlinear_gating), or algorithmic changes (such as optimized memory access patterns). You are only limited by your imagination.
Poisson Negative Log-Likelihood Loss CUDA Optimization with Fused Kernel
CUDA Optimization Techniques
1. Parallel Reduction Architecture
Grid-Stride Loop Pattern: Each thread processes multiple elements with stride gridDim.x * blockDim.x
Block-Level Reduction: Partial sums computed in shared memory
Dynamic Grid Sizing: grid_size = (n_elements + BLOCK_SIZE - 1) / BLOCK_SIZE
2. Fused Kernel Design
Single Kernel Execution: Combines all loss computations in one kernel launch
Branch Handling: Efficiently handles log_input and full mode conditions
Mathematical Fusion: Integrates exponential, logarithmic, and conditional operations
3. Memory Access Optimization
Coalesced Memory Access: Sequential reading of input and target tensors
Shared Memory Utilization: s_data[BLOCK_SIZE] for block-level sum reduction
Contiguous Tensors: Ensures input and target are contiguous in memory
4. Numerical Stability Features
Epsilon Protection: eps_val prevents log(0) in non-log mode
Stirling's Approximation: Conditional Stirling term for full mode when y > 1.0f
Float Safety: Proper handling of edge cases and special values
5. Mathematical Operations
7. Performance Optimizations
Minimal Global Memory Writes: Only thread 0 writes block sum to global memory
Efficient Thread Utilization: All threads participate in computation and reduction
Load Balancing: Grid-stride loops handle arbitrary tensor sizes
Constant Propagation: PI and configuration parameters as compile-time constants
8. Implementation Features
Configuration Flexibility: Supports log_input, full, and eps parameters
Comprehensive Validation: Tensor device, contiguity, and size checking
Edge Case Handling: Empty tensor detection and proper zero handling
PyTorch Integration: Seamless tensor passing and automatic differentiation support
Key CUDA Concepts Used
Grid-Stride Loops for workload distribution across all elements
Shared Memory Reduction for parallel sum computation
Conditional Execution for handling different mathematical modes
Memory Coalescing for efficient global memory access
Kernel Fusion combining multiple mathematical operations
Workflow Summary
Configuration Setup: Parse log_input, full, and eps parameters
Memory Preparation: Ensure contiguous tensor layouts
Kernel Launch: Execute fused Poisson NLL computation with parallel reduction
Final Reduction: Sum block partial sums and compute mean loss
Mathematical Components
Exponential Computation: expf() for log-input mode
Logarithmic Computation: logf() for non-log mode and Stirling term
Stirling's Approximation: Complete term for Poisson distribution normalization
Element-wise Operations: Parallel computation across all tensor elements
Expected Performance Benefits
2-4x speedup over PyTorch implementation for large tensors
Reduced kernel launches through operation fusion
Better memory efficiency through coalesced access patterns
Scalable performance with increasing tensor sizes
This implementation provides a production-ready Poisson NLL Loss with significant performance improvements through careful CUDA optimization, parallel reduction patterns, and numerical stability considerations.
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:
import torch
import torch.nn as nn
import torch.nn.functional as F
BATCH_SIZE = 4096
FEATURE_DIM = 512
# --- 损失函数的参数 ---
# log_input=True: loss = exp(input) - target * input
# log_input=False: loss = input - target * log(input + eps)
LOG_INPUT = True
# full=True: 添加 Stirling's approximation
FULL = False
EPS = 1e-8
class Model(nn.Module):
def __init__(self):
super().__init__()
self.criterion = nn.PoissonNLLLoss(
log_input=LOG_INPUT,
full=FULL,
eps=EPS,
reduction='mean'
)
def forward(self, input_tensor: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
# 在 PyTorch 中input_tensor 是文档中的 'input'
return self.criterion(input_tensor, target)
def get_inputs():
# Input (log_input=True 时) 可以是任意实数
input_tensor = torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32)
# Target 在 Poisson 分布中代表计数,且在 'full' 模式下会计算 log(target)
# 因此 target 必须是 >= 0 的。我们使用 rand 来确保
target = torch.rand(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32) * 10 # 乘以 10 以便有一些 > 1
return [input_tensor, target]
def get_init_inputs():
return []

77
S1/20/run_code.py Normal file
View File

@ -0,0 +1,77 @@
###########################################################
# 性能和精度验证程序
###########################################################
import torch
import torch.nn as nn
import time
from poissonnllloss_torch import Model, get_inputs, get_init_inputs
from poissonnllloss_cuda import ModelNew
def run_benchmark():
# 检查 CUDA 是否可用
if not torch.cuda.is_available():
print("CUDA 不可用,请确保您有可用的 NVIDIA GPU 并已正确安装 PyTorch CUDA 版本。")
return
else:
device = torch.device("cuda")
# 初始化模型
init_inputs = get_init_inputs()
init_inputs = [
x.cuda(device=device) if isinstance(x, torch.Tensor) else x for x in init_inputs
]
inputs = get_inputs()
inputs = [
x.cuda(device=device) if isinstance(x, torch.Tensor) else x for x in inputs
]
torch_model = Model(*init_inputs).cuda()
cuda_model = ModelNew(*init_inputs).cuda()
torch_model.eval()
cuda_model.eval()
print("-------------------- 精度对齐验证 --------------------")
with torch.no_grad():
output_torch = torch_model(*inputs)
output_cuda = cuda_model(*inputs)
precision_flag = torch.allclose(output_torch, output_cuda, rtol=1e-03)
if precision_flag:
print("✅ 精度对齐:两个模型的输出结果非常接近。")
else:
print("❌ 精度不一致!")
print("\n-------------------- 性能加速比测试 --------------------")
num_iterations = 100
# PyTorch 模型计时
torch.cuda.synchronize()
start_time = time.time()
for _ in range(num_iterations):
_ = torch_model(*inputs)
torch.cuda.synchronize()
torch_time = (time.time() - start_time) / num_iterations
# 自定义 CUDA 内核计时
torch.cuda.synchronize()
start_time = time.time()
for _ in range(num_iterations):
_ = cuda_model(*inputs)
torch.cuda.synchronize()
cuda_time = (time.time() - start_time) / num_iterations
print(f"PyTorch torch.relu 平均执行时间: {torch_time:.6f}")
print(f"自定义 CUDA 内核 平均执行时间: {cuda_time:.6f}")
speedup = 0
if cuda_time > 0:
speedup = torch_time / cuda_time
print(f"加速比 (Speedup): {speedup:.2f}x")
else:
print("CUDA 内核执行时间为0无法计算加速比。")
return precision_flag, speedup
if __name__ == "__main__":
precision_flag, speedup = run_benchmark()