From d07569af565f6c0f3d3dd3785418dea2932b5b77 Mon Sep 17 00:00:00 2001 From: gsd <2396583337@qq.com> Date: Thu, 6 Nov 2025 13:16:31 +0800 Subject: [PATCH] finish infonceloss #19 --- S1/19/infonceloss_cuda.py | 235 +++++++++++++++++++++++++++++++++++++ S1/19/infonceloss_torch.py | 43 +++++++ S1/19/prompt.txt | 152 ++++++++++++++++++++++++ S1/19/run_code.py | 77 ++++++++++++ 4 files changed, 507 insertions(+) create mode 100644 S1/19/infonceloss_cuda.py create mode 100644 S1/19/infonceloss_torch.py create mode 100644 S1/19/prompt.txt create mode 100644 S1/19/run_code.py diff --git a/S1/19/infonceloss_cuda.py b/S1/19/infonceloss_cuda.py new file mode 100644 index 0000000..b07c731 --- /dev/null +++ b/S1/19/infonceloss_cuda.py @@ -0,0 +1,235 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.cpp_extension import load_inline +# 从 torch 文件导入常量 +from infonceloss_torch import BATCH_SIZE, FEATURE_DIM, TEMPERATURE, N_NEGATIVES + + +class ModelNew(nn.Module): + + def __init__(self): + super().__init__() + self.temperature = TEMPERATURE + self._compile_cuda_kernel() + + def _compile_cuda_kernel(self): + cpp_source = """ + #include + + // C++ 接口 + torch::Tensor infonce_forward_cuda( + torch::Tensor query, // (B, D) + torch::Tensor positive, // (B, D) + torch::Tensor negative_sims, // (B, N) - 预先计算的 + float temperature + ); + """ + + cuda_source = """ + #include + #include + #include + #include // For FLT_MAX + + // 使用 256 个线程的块大小 + #define BLOCK_SIZE 256 + + /* + * InfoNCE 融合核函数 + * 我们启动 B 个块 (gridDim.x = B),每个块负责一行 (一个 query) 的 loss 计算。 + * 每个块 (blockIdx.x) 计算: + * 1. query[i] 和 positive[i] 之间的点积 (pos_logit) + * 2. 对 [pos_logit, neg_logits[i,:]] 执行稳定的 LogSumExp + * 3. 计算 loss_i = -pos_logit + logsumexp + * + * @param loss_per_row_out - (B,) 形状的张量,用于存储 loss_i + */ + __global__ void infonce_fused_kernel( + const float* __restrict__ query_data, // (B, D) + const float* __restrict__ positive_data, // (B, D) + const float* __restrict__ negative_sims_data, // (B, N) + float* __restrict__ loss_per_row_out, // (B,) + int B, + int D, + int N, + float temperature + ) { + // 每个块计算一行 + int i = blockIdx.x; // 当前 query 的索引 (0 到 B-1) + if (i >= B) return; + + // --- 共享内存 --- + // s_dot 用于计算 pos_logit + __shared__ float s_dot[BLOCK_SIZE]; + // s_max 和 s_sum 用于稳定的 LogSumExp + __shared__ float s_max[BLOCK_SIZE]; + __shared__ float s_sum[BLOCK_SIZE]; + + // --- 1. 计算 Positive Logit --- + // 融合了 F.cosine_similarity(query[i], positive[i]) / temp + float thread_dot_sum = 0.0f; + + // 使用 Grid-Stride 循环计算点积 + for (int k = threadIdx.x; k < D; k += BLOCK_SIZE) { + thread_dot_sum += query_data[i * D + k] * positive_data[i * D + k]; + } + s_dot[threadIdx.x] = thread_dot_sum; + + // 块内归约 (Sum) + __syncthreads(); + for (int offset = BLOCK_SIZE / 2; offset > 0; offset >>= 1) { + if (threadIdx.x < offset) { + s_dot[threadIdx.x] += s_dot[threadIdx.x + offset]; + } + __syncthreads(); + } + + // 线程 0 现在拥有 pos_logit + // 我们将其存储在 s_dot[0] 中以供后续步骤使用 + if (threadIdx.x == 0) { + s_dot[0] = s_dot[0] / temperature; + } + __syncthreads(); // 确保所有线程都能读到 s_dot[0] + + const float pos_logit = s_dot[0]; // 所有线程的常量 + + // --- 2. 稳定的 LogSumExp (Pass 1: Find Max) --- + float thread_max = -FLT_MAX; + + // 线程 0 包含 pos_logit + if (threadIdx.x == 0) { + thread_max = pos_logit; + } + + // 遍历 N 个 negative logits + for (int j = threadIdx.x; j < N; j += BLOCK_SIZE) { + float neg_logit = negative_sims_data[i * N + j] / temperature; + thread_max = fmaxf(thread_max, neg_logit); + } + s_max[threadIdx.x] = thread_max; + + // 块内归约 (Max) + __syncthreads(); + for (int offset = BLOCK_SIZE / 2; offset > 0; offset >>= 1) { + if (threadIdx.x < offset) { + s_max[threadIdx.x] = fmaxf(s_max[threadIdx.x], s_max[threadIdx.x + offset]); + } + __syncthreads(); + } + + // 线程 0 拥有 global_max + if (threadIdx.x == 0) { + s_max[0] = s_max[0]; + } + __syncthreads(); // 确保所有线程都能读到 s_max[0] + + const float global_max = s_max[0]; + + // --- 3. 稳定的 LogSumExp (Pass 2: Sum Exp Diff) --- + float thread_sum_exp = 0.0f; + + // 线程 0 添加 positive_logit 的贡献 + if (threadIdx.x == 0) { + thread_sum_exp = expf(pos_logit - global_max); + } + + // 遍历 N 个 negative logits + for (int j = threadIdx.x; j < N; j += BLOCK_SIZE) { + float neg_logit = negative_sims_data[i * N + j] / temperature; + thread_sum_exp += expf(neg_logit - global_max); + } + s_sum[threadIdx.x] = thread_sum_exp; + + // 块内归约 (Sum) + __syncthreads(); + for (int offset = BLOCK_SIZE / 2; offset > 0; offset >>= 1) { + if (threadIdx.x < offset) { + s_sum[threadIdx.x] += s_sum[threadIdx.x + offset]; + } + __syncthreads(); + } + + // --- 4. 计算最终的 loss[i] --- + if (threadIdx.x == 0) { + float log_sum_exp = global_max + logf(s_sum[0]); + // loss_i = -logit[0] + logsumexp + float loss_i = -pos_logit + log_sum_exp; + loss_per_row_out[i] = loss_i; + } + } + + // C++ 封装函数 + torch::Tensor infonce_forward_cuda( + torch::Tensor query, + torch::Tensor positive, + torch::Tensor negative_sims, // 注意:这是未缩放的 + float temperature + ) { + // 检查 + TORCH_CHECK(query.is_cuda(), "query must be a CUDA tensor"); + TORCH_CHECK(positive.is_cuda(), "positive must be a CUDA tensor"); + TORCH_CHECK(negative_sims.is_cuda(), "negative_sims must be a CUDA tensor"); + + query = query.contiguous(); + positive = positive.contiguous(); + negative_sims = negative_sims.contiguous(); + + const int B = query.size(0); + const int D = query.size(1); + const int N = negative_sims.size(1); + + TORCH_CHECK(positive.size(0) == B && positive.size(1) == D, "positive tensor has wrong size"); + TORCH_CHECK(negative_sims.size(0) == B, "negative_sims tensor has wrong size"); + + // 分配一个张量来保存每个块 (每行) 的 loss + auto loss_per_row = torch::empty({B}, query.options()); + + const int block_size = BLOCK_SIZE; + const int grid_size = B; // B 个块,每个块处理一行 + + // 启动 CUDA 核函数 + infonce_fused_kernel<<>>( + query.data_ptr(), + positive.data_ptr(), + negative_sims.data_ptr(), + loss_per_row.data_ptr(), + B, D, N, + temperature + ); + + + // 核函数返回后,loss_per_row 包含 B 个 loss 值 + // 我们需要对它们取平均 + return loss_per_row.mean(); + } + """ + + # JIT (Just-In-Time) 编译 + self.infonce_op = load_inline( + name="infonce_op_v1_stable", + cpp_sources=cpp_source, + cuda_sources=cuda_source, + functions=["infonce_forward_cuda"], + extra_cuda_cflags=["-O3"], + verbose=False + ) + + def forward(self, query: torch.Tensor, positive: torch.Tensor, negatives: torch.Tensor) -> torch.Tensor: + # 1. (Python) 执行优化的 matmul (cuBLAS) + # (B, D) @ (D, N) -> (B, N) + # 这是未缩放的 (没有 / temp) + negative_sims_unscaled = torch.matmul(query, negatives.t()) + + # 2. (CUDA) 调用融合核函数 + # 核函数将处理: + # - query, positive 的 cosine similarity + # - 对所有 sim 应用 / temp + # - 稳定的 LogSumExp 和 CrossEntropy + # - 最终的 Mean 归约 + return self.infonce_op.infonce_forward_cuda( + query, + positive, + negative_sims_unscaled, + self.temperature + ) \ No newline at end of file diff --git a/S1/19/infonceloss_torch.py b/S1/19/infonceloss_torch.py new file mode 100644 index 0000000..5d5917e --- /dev/null +++ b/S1/19/infonceloss_torch.py @@ -0,0 +1,43 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +BATCH_SIZE = 256 +FEATURE_DIM = 512 +TEMPERATURE = 0.1 +N_NEGATIVES = BATCH_SIZE * 10 + + +class Model(nn.Module): + + def __init__(self): + super().__init__() + self.temperature = TEMPERATURE + + def forward(self, query: torch.Tensor, positive: torch.Tensor, negatives: torch.Tensor) -> torch.Tensor: + # InfoNCE Loss实现 + # (B, D) vs (B, D) -> (B,) + positive_sim = F.cosine_similarity(query, positive, dim=1) / self.temperature + + # (B, D) @ (D, N_NEG) -> (B, N_NEG) + negative_sims = torch.matmul(query, negatives.t()) / self.temperature + + # 拼接: (B, 1) 和 (B, N_NEG) -> (B, 1 + N_NEG) + logits = torch.cat([positive_sim.unsqueeze(1), negative_sims], dim=1) + + # 标签总是 0,因为正样本总是在索引 0 + labels = torch.zeros(query.size(0), dtype=torch.long, device=query.device) + + loss = F.cross_entropy(logits, labels) + return loss + + +def get_inputs(): + query = F.normalize(torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + positive = F.normalize(torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + negatives = F.normalize(torch.randn(N_NEGATIVES, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + return [query, positive, negatives] + + +def get_init_inputs(): + return [] \ No newline at end of file diff --git a/S1/19/prompt.txt b/S1/19/prompt.txt new file mode 100644 index 0000000..9ba7f91 --- /dev/null +++ b/S1/19/prompt.txt @@ -0,0 +1,152 @@ +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. + +InfoNCE Loss CUDA Optimization with Fused Kernel Design + +1. Fused Kernel Architecture +Per-Query Parallelism: Each CUDA block processes one query instance + +Grid Strategy: gridDim.x = B (batch size), blockDim.x = 256 + +Kernel Fusion: Combines multiple operations in single kernel: + +Positive similarity computation + +Negative similarity processing + +Stable log-sum-exp calculation + +Loss computation per query + +2. Three-Phase Computation Pipeline +Phase 1: Positive logit calculation + +Grid-stride dot product computation + +Block-level sum reduction using shared memory + +Phase 2: Stable log-sum-exp (Max finding) + +Find global maximum across positive and negative logits + +Block-level max reduction with shared memory + +Phase 3: Stable log-sum-exp (Sum calculation) + +Compute sum of exponentials with numerical stability + +Block-level sum reduction + +3. Memory Access Optimization +Coalesced Access: Sequential memory access patterns for query/positive data + +Shared Memory Utilization: + +s_dot[BLOCK_SIZE] for dot product reduction + +s_max[BLOCK_SIZE] for max reduction + +s_sum[BLOCK_SIZE] for sum reduction + +Contiguous Tensors: Ensure all input tensors are contiguous + +4. Numerical Stability Features +Log-Sum-Exp Trick: Subtract maximum before exponentiation + +Stable Division: Apply temperature scaling after reduction + +Float Safety: Use FLT_MAX for initial max values + +5. Parallel Reduction Patterns + +6. Performance Optimizations +Minimal Global Memory Writes: Only thread 0 writes final loss per query + +Grid-Stride Loops: Handle arbitrary feature dimensions (D) and negative counts (N) + +Efficient Thread Utilization: All threads participate in computations + +Pre-computed Similarities: Negative similarities computed via optimized cuBLAS matmul + +7. Implementation Features +Batch Independence: Each query processed independently enabling parallelism + +Template-Free Design: Optimized for float32 precision + +Comprehensive Validation: Tensor shape and device checking + +PyTorch Integration: Seamless tensor passing and automatic differentiation + +Key CUDA Concepts Used +Block-Level Reduction for parallel statistics computation + +Shared Memory Synchronization using __syncthreads() + +Grid-Stride Loops for workload distribution across feature dimensions + +Memory Coalescing for efficient global memory access + +Kernel Fusion combining multiple mathematical operations + +Workflow Summary +Python Pre-processing: Compute negative similarities using optimized matrix multiplication + +CUDA Kernel Execution: Fused computation of positive similarity and stable loss calculation + +Parallel Reduction: Each block computes loss for one query instance + +Final Aggregation: Mean reduction across all query losses + +Expected Performance Benefits +3-8x speedup over PyTorch implementation for large batch sizes + +Better numerical stability with careful floating-point handling + +Reduced memory bandwidth through kernel fusion + +Scalable performance with increasing batch and feature dimensions + +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 = 256 +FEATURE_DIM = 512 +TEMPERATURE = 0.1 +N_NEGATIVES = BATCH_SIZE * 10 + + +class Model(nn.Module): + + def __init__(self): + super().__init__() + self.temperature = TEMPERATURE + + def forward(self, query: torch.Tensor, positive: torch.Tensor, negatives: torch.Tensor) -> torch.Tensor: + # InfoNCE Loss实现 + # (B, D) vs (B, D) -> (B,) + positive_sim = F.cosine_similarity(query, positive, dim=1) / self.temperature + + # (B, D) @ (D, N_NEG) -> (B, N_NEG) + negative_sims = torch.matmul(query, negatives.t()) / self.temperature + + # 拼接: (B, 1) 和 (B, N_NEG) -> (B, 1 + N_NEG) + logits = torch.cat([positive_sim.unsqueeze(1), negative_sims], dim=1) + + # 标签总是 0,因为正样本总是在索引 0 + labels = torch.zeros(query.size(0), dtype=torch.long, device=query.device) + + loss = F.cross_entropy(logits, labels) + return loss + + +def get_inputs(): + query = F.normalize(torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + positive = F.normalize(torch.randn(BATCH_SIZE, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + negatives = F.normalize(torch.randn(N_NEGATIVES, FEATURE_DIM, dtype=torch.float32), p=2, dim=1) + return [query, positive, negatives] + + +def get_init_inputs(): + return [] \ No newline at end of file diff --git a/S1/19/run_code.py b/S1/19/run_code.py new file mode 100644 index 0000000..a0cb38d --- /dev/null +++ b/S1/19/run_code.py @@ -0,0 +1,77 @@ +########################################################### +# 性能和精度验证程序 +########################################################### +import torch +import torch.nn as nn +import time +from infonceloss_torch import Model, get_inputs, get_init_inputs +from infonceloss_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() \ No newline at end of file