finish SupConLoss #132

This commit is contained in:
gsd 2025-12-09 15:10:42 +08:00
parent f1636af7f9
commit cb0d009dd3
6 changed files with 321 additions and 154 deletions

View File

@ -0,0 +1,227 @@
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cuda_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cmath>
#define EPSILON 1e-12f
__inline__ __device__ float warpReduceSum(float val) {
for (int offset = 16; offset > 0; offset /= 2)
val += __shfl_down_sync(0xffffffff, val, offset);
return val;
}
__global__ void normalize_kernel(const float* __restrict__ input, float* output, int rows, int cols) {
int bid = blockIdx.x; // row index
if (bid >= rows) return;
int tid = threadIdx.x;
const float* row_in = input + bid * cols;
float* row_out = output + bid * cols;
// Sum squares
float sum_sq = 0.0f;
for (int i = tid; i < cols; i += blockDim.x) {
float val = row_in[i];
sum_sq += val * val;
}
// Block Reduction
__shared__ float s_sum[32];
int lane = tid % 32;
int wid = tid / 32;
sum_sq = warpReduceSum(sum_sq);
if (lane == 0) s_sum[wid] = sum_sq;
__syncthreads();
float total_sum_sq = (tid < (blockDim.x / 32)) ? s_sum[tid] : 0.0f;
if (tid < 32) total_sum_sq = warpReduceSum(total_sum_sq);
// Broadcast norm factor
__shared__ float norm_factor;
if (tid == 0) {
float norm = sqrtf(total_sum_sq);
norm_factor = 1.0f / fmaxf(norm, EPSILON);
}
__syncthreads();
// Write output
float scale = norm_factor;
for (int i = tid; i < cols; i += blockDim.x) {
row_out[i] = row_in[i] * scale;
}
}
__global__ void supcon_loss_kernel(
const float* __restrict__ scores, // [N, N]
const int64_t* __restrict__ labels,
float* loss_out,
int batch_size
) {
int row = blockIdx.x;
if (row >= batch_size) return;
int tid = threadIdx.x;
int64_t my_label = labels[row];
const float* row_ptr = scores + row * batch_size;
// -- Local Registers --
// Softmax stats: M (max), S (sum_exp)
// Init: max = -inf, sum = 0
float max_val = -1e38f;
float sum_exp = 0.0f;
// Positive stats
float sum_pos = 0.0f;
float cnt_pos = 0.0f;
// Loop over columns
for (int col = tid; col < batch_size; col += blockDim.x) {
float val = row_ptr[col];
// 1. Update LogSumExp stats (Denominator: j != i)
if (col != row) {
if (val > max_val) {
sum_exp = sum_exp * expf(max_val - val) + 1.0f;
max_val = val;
} else {
sum_exp += expf(val - max_val);
}
}
// 2. Update Positive stats (Numerator: label[j] == label[i])
if (labels[col] == my_label) {
sum_pos += val;
cnt_pos += 1.0f;
}
}
// -- Warp Reduction --
for (int offset = 16; offset > 0; offset /= 2) {
// Simple sum reduction
sum_pos += __shfl_down_sync(0xffffffff, sum_pos, offset);
cnt_pos += __shfl_down_sync(0xffffffff, cnt_pos, offset);
// Softmax reduction (merge two (M, S) pairs)
float other_max = __shfl_down_sync(0xffffffff, max_val, offset);
float other_sum = __shfl_down_sync(0xffffffff, sum_exp, offset);
float new_max = fmaxf(max_val, other_max);
float scale_self = expf(max_val - new_max);
float scale_other = expf(other_max - new_max);
sum_exp = sum_exp * scale_self + other_sum * scale_other;
max_val = new_max;
}
// -- Block Reduction via Shared Memory --
__shared__ float s_max[32];
__shared__ float s_sum_e[32];
__shared__ float s_sum_p[32];
__shared__ float s_cnt[32];
int lane = tid % 32;
int wid = tid / 32;
if (lane == 0) {
s_max[wid] = max_val;
s_sum_e[wid] = sum_exp;
s_sum_p[wid] = sum_pos;
s_cnt[wid] = cnt_pos;
}
__syncthreads();
// Final reduction by first warp
if (tid < 32) {
int num_warps = (blockDim.x + 31) / 32;
float l_max = (tid < num_warps) ? s_max[tid] : -1e38f;
float l_sum_e = (tid < num_warps) ? s_sum_e[tid] : 0.0f;
float l_sum_p = (tid < num_warps) ? s_sum_p[tid] : 0.0f;
float l_cnt = (tid < num_warps) ? s_cnt[tid] : 0.0f;
for (int offset = 16; offset > 0; offset /= 2) {
l_sum_p += __shfl_down_sync(0xffffffff, l_sum_p, offset);
l_cnt += __shfl_down_sync(0xffffffff, l_cnt, offset);
float other_max = __shfl_down_sync(0xffffffff, l_max, offset);
float other_sum = __shfl_down_sync(0xffffffff, l_sum_e, offset);
float new_max = fmaxf(l_max, other_max);
float scale_self = expf(l_max - new_max);
float scale_other = expf(other_max - new_max);
l_sum_e = l_sum_e * scale_self + other_sum * scale_other;
l_max = new_max;
}
if (tid == 0) {
// Formula: Loss = LogSumExp(logits_{j!=i}) - Mean(logits_{pos})
float log_sum_exp = l_max + logf(l_sum_e);
if (l_cnt > 0.5f) {
float mean_pos = l_sum_p / l_cnt;
loss_out[row] = log_sum_exp - mean_pos;
} else {
loss_out[row] = 0.0f; // Should not happen if i is in batch
}
}
}
}
torch::Tensor supcon_cuda_forward(torch::Tensor features, torch::Tensor labels, float temperature) {
auto f_c = features.contiguous();
auto l_c = labels.contiguous();
int batch_size = f_c.size(0);
int dim = f_c.size(1);
// 1. Normalize
auto f_norm = torch::empty_like(f_c);
normalize_kernel<<<batch_size, 256>>>(f_c.data_ptr<float>(), f_norm.data_ptr<float>(), batch_size, dim);
// 2. Similarity Matrix
auto scores = torch::matmul(f_norm, f_norm.transpose(0, 1));
scores.div_(temperature);
// 3. Loss Calculation
auto loss = torch::empty({batch_size}, f_c.options());
supcon_loss_kernel<<<batch_size, 256>>>(
scores.data_ptr<float>(),
l_c.data_ptr<int64_t>(),
loss.data_ptr<float>(),
batch_size
);
return loss.mean();
}
"""
cpp_source = """
torch::Tensor supcon_cuda_forward(torch::Tensor features, torch::Tensor labels, float temperature);
"""
supcon_loss_module = load_inline(
name="supcon_loss_opt_precise",
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=["supcon_cuda_forward"],
verbose=False
)
class ModelNew(nn.Module):
def __init__(self, temperature):
super(ModelNew, self).__init__()
self.temperature = temperature
def forward(self, features, labels):
return supcon_loss_module.supcon_cuda_forward(features, labels, self.temperature)

View File

@ -0,0 +1,46 @@
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, temperature):
super(Model, self).__init__()
self.temperature = temperature
def forward(self, features: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
batch_size = features.shape[0]
features = torch.nn.functional.normalize(features, dim=1)
similarity_matrix = torch.matmul(features, features.T) / self.temperature
mask = labels.unsqueeze(0) == labels.unsqueeze(1)
mask = mask.float()
logits_mask = torch.ones_like(mask)
logits_mask.fill_diagonal_(0)
exp_logits = torch.exp(similarity_matrix) * logits_mask
log_prob = similarity_matrix - torch.log(exp_logits.sum(1, keepdim=True))
mean_log_prob_pos = (mask * log_prob).sum(1) / mask.sum(1)
loss = -mean_log_prob_pos.mean()
return loss
batch_size = 16
dim = 128
num_classes = 10
def get_inputs():
features = torch.randn(batch_size, dim)
labels = torch.randint(0, num_classes, (batch_size,))
return [features, labels]
def get_init_inputs():
temperature = 0.07
return [temperature]

View File

@ -1,56 +1,28 @@
You write custom CUDA kernels to replace the pytorch operators in the given GeGLU 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 chunk+gelu+elementwise_mul), or algorithmic changes (such as optimized memory access patterns). You are only limited by your imagination.
PyTorch C++/CUDA Extension API
Runtime compilation of CUDA code via load_inline
Custom CUDA kernel extension via torch.utils.cpp_extension.load_inline
Seamless integration of custom kernels into PyTorch's autograd system
Supervised Contrastive (SupCon) loss computation
CUDA Parallel Programming
Multi-kernel design: normalization + similarity + loss computation
Single CUDA kernel: segment_sum_kernel
Row-wise L2 normalization with warp-level reduction
Grid-stride loop pattern: blockIdx.x * blockDim.x + threadIdx.x
Similarity matrix computation via torch::matmul with temperature scaling
Block size: 256 threads (common optimization for memory-bound ops)
Advanced log-sum-exp reduction with warp-shuffle merging
Atomic Operations for Reduction
Label-based positive/negative separation for supervised contrastive learning
atomicAdd ensures thread-safe accumulation when multiple source rows map to the same segment
Warp-shuffle primitives (__shfl_down_sync) for efficient reductions
Critical for correctness in scatter/gather patterns with index collisions
Two-phase reduction: warp-level → shared memory → final warp
Efficient Memory Access
Coalesced global memory reads via __restrict__ pointers
2D→1D index mapping: row * channels + col
Avoids bank conflicts and maximizes memory throughput
PyTorch Tensor Integration
Direct pointer access: data_ptr<float>() and data_ptr<long>()
Zero-initialized output tensor: torch::zeros with preserved dtype/device
Stream-safe kernel launches (PyTorch-managed CUDA streams)
Module Abstraction
nn.Module wrapper for reusability and parameter management
Maintains dim_size as a configurable attribute
Segment-Based Computation Pattern
Groups input rows by segment_id from index tensor
Sums features channel-wise within each segment
Output shape: (dim_size, channels)
Numerical stability with EPSILON protection
Contiguous tensor handling for memory coalescing
@ -58,25 +30,46 @@ Here's an example to show you the syntax of inline embedding custom CUDA operato
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, dim_size):
def __init__(self, temperature):
super(Model, self).__init__()
self.dim_size = dim_size
self.temperature = temperature
def forward(self, src, index):
out = torch.zeros(self.dim_size, src.size(1), device=src.device, dtype=src.dtype)
index_expanded = index.unsqueeze(1).expand_as(src)
out.scatter_add_(0, index_expanded, src)
return out
def forward(self, features: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
batch_size = features.shape[0]
features = torch.nn.functional.normalize(features, dim=1)
similarity_matrix = torch.matmul(features, features.T) / self.temperature
mask = labels.unsqueeze(0) == labels.unsqueeze(1)
mask = mask.float()
logits_mask = torch.ones_like(mask)
logits_mask.fill_diagonal_(0)
exp_logits = torch.exp(similarity_matrix) * logits_mask
log_prob = similarity_matrix - torch.log(exp_logits.sum(1, keepdim=True))
mean_log_prob_pos = (mask * log_prob).sum(1) / mask.sum(1)
loss = -mean_log_prob_pos.mean()
return loss
batch_size = 16
dim = 128
num_classes = 10
batch_size = 1024
features = 64
dim_size = 128
def get_inputs():
src = torch.randn(batch_size, features)
index = torch.randint(0, dim_size, (batch_size,))
return [src, index]
features = torch.randn(batch_size, dim)
labels = torch.randint(0, num_classes, (batch_size,))
return [features, labels]
def get_init_inputs():
return [dim_size]
temperature = 0.07
return [temperature]

View File

@ -4,8 +4,8 @@
import torch
import torch.nn as nn
import time
from segmentsum_torch import Model, get_inputs, get_init_inputs
from segmentsum_cuda import ModelNew
from SupConLoss_torch import Model, get_inputs, get_init_inputs
from SupConLoss_cuda import ModelNew
def run_benchmark():

View File

@ -1,74 +0,0 @@
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cuda_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
__global__ void segment_sum_kernel(
const float* __restrict__ src,
const long* __restrict__ index,
float* __restrict__ out,
int num_elements,
int channels,
int dim_size
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < num_elements) {
int row = idx / channels;
int col = idx % channels;
long segment_id = index[row];
if (segment_id >= 0 && segment_id < dim_size) {
int out_idx = segment_id * channels + col;
atomicAdd(&out[out_idx], src[idx]);
}
}
}
torch::Tensor segment_sum_cuda(torch::Tensor src, torch::Tensor index, int dim_size) {
int N = src.size(0);
int C = src.size(1);
auto out = torch::zeros({dim_size, C}, src.options());
int num_elements = N * C;
const int block_size = 256;
int num_blocks = (num_elements + block_size - 1) / block_size;
segment_sum_kernel<<<num_blocks, block_size>>>(
src.data_ptr<float>(),
index.data_ptr<long>(),
out.data_ptr<float>(),
num_elements,
C,
dim_size
);
return out;
}
"""
cpp_source = """
torch::Tensor segment_sum_cuda(torch::Tensor src, torch::Tensor index, int dim_size);
"""
segment_sum_lib = load_inline(
name="segment_sum",
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=["segment_sum_cuda"],
verbose=True
)
class ModelNew(nn.Module):
def __init__(self, dim_size):
super(ModelNew, self).__init__()
self.dim_size = dim_size
self.lib = segment_sum_lib
def forward(self, src, index):
return self.lib.segment_sum_cuda(src, index, self.dim_size)

View File

@ -1,25 +0,0 @@
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, dim_size):
super(Model, self).__init__()
self.dim_size = dim_size
def forward(self, src, index):
out = torch.zeros(self.dim_size, src.size(1), device=src.device, dtype=src.dtype)
index_expanded = index.unsqueeze(1).expand_as(src)
out.scatter_add_(0, index_expanded, src)
return out
batch_size = 1024
features = 64
dim_size = 128
def get_inputs():
src = torch.randn(batch_size, features)
index = torch.randint(0, dim_size, (batch_size,))
return [src, index]
def get_init_inputs():
return [dim_size]