forked from ccf-ai-infra/GPUCodeForces
210 lines
7.9 KiB
Python
210 lines
7.9 KiB
Python
# kldivloss_cuda_ultra.py
|
|
import torch
|
|
from torch.utils.cpp_extension import load_inline
|
|
from kldivloss_torch import BATCH_SIZE, DIM
|
|
|
|
class ModelNew(torch.nn.Module):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._compile_cuda_kernel()
|
|
|
|
def _compile_cuda_kernel(self):
|
|
cpp_source = """
|
|
#include <torch/extension.h>
|
|
torch::Tensor kldiv_forward_cuda(torch::Tensor input_logits, torch::Tensor target_prob);
|
|
"""
|
|
|
|
cuda_source = """
|
|
#include <cuda_runtime.h>
|
|
#include <cooperative_groups.h>
|
|
namespace cg = cooperative_groups;
|
|
|
|
#define BLOCK_SIZE 256
|
|
#define VEC_SIZE 4
|
|
#define WARP_SIZE 32
|
|
|
|
// Ultra-fast warp reduction using cooperative groups
|
|
__device__ __forceinline__ double warp_reduce_sum_cg(double val) {
|
|
#pragma unroll
|
|
for (int offset = 16; offset > 0; offset >>= 1) {
|
|
val += __shfl_down_sync(0xffffffff, val, offset);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
// Optimized block reduction
|
|
__device__ __forceinline__ double block_reduce_sum(double val) {
|
|
__shared__ double warp_sums[8];
|
|
|
|
int lane = threadIdx.x & 31;
|
|
int warp_id = threadIdx.x >> 5;
|
|
|
|
val = warp_reduce_sum_cg(val);
|
|
|
|
if (lane == 0) {
|
|
warp_sums[warp_id] = val;
|
|
}
|
|
__syncthreads();
|
|
|
|
if (warp_id == 0) {
|
|
val = (lane < 8) ? warp_sums[lane] : 0.0;
|
|
val = warp_reduce_sum_cg(val);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
// Safe and fast KL term computation
|
|
__device__ __forceinline__ double safe_kl_term(float p, float log_q) {
|
|
// Avoid NaN: when p < eps, contribution is 0
|
|
return (p > 1e-8f) ? ((double)p * (__logf(p) - log_q)) : 0.0;
|
|
}
|
|
|
|
// Version 1: Maximally unrolled with 8x vectorization
|
|
__global__ void kldiv_kernel_v1(
|
|
const float* __restrict__ input_logits,
|
|
const float* __restrict__ target_prob,
|
|
double* __restrict__ output_sum,
|
|
int N_elements
|
|
) {
|
|
double sum = 0.0;
|
|
|
|
int N_vec = N_elements >> 2; // / 4
|
|
int tid = (blockIdx.x * blockDim.x + threadIdx.x);
|
|
int stride = gridDim.x * blockDim.x;
|
|
|
|
const float4* __restrict__ in4 = (const float4*)input_logits;
|
|
const float4* __restrict__ tgt4 = (const float4*)target_prob;
|
|
|
|
// 8x unrolled loop
|
|
for (int i = tid; i < N_vec; i += stride << 3) {
|
|
#pragma unroll
|
|
for (int u = 0; u < 8; u++) {
|
|
int idx = i + (u * stride);
|
|
if (idx < N_vec) {
|
|
float4 lq = in4[idx];
|
|
float4 p = tgt4[idx];
|
|
|
|
sum += safe_kl_term(p.x, lq.x);
|
|
sum += safe_kl_term(p.y, lq.y);
|
|
sum += safe_kl_term(p.z, lq.z);
|
|
sum += safe_kl_term(p.w, lq.w);
|
|
}
|
|
}
|
|
}
|
|
|
|
sum = block_reduce_sum(sum);
|
|
|
|
if (threadIdx.x == 0) {
|
|
output_sum[blockIdx.x] = sum;
|
|
}
|
|
}
|
|
|
|
// Version 2: Register-tiled with manual prefetching
|
|
__global__ void kldiv_kernel_v2(
|
|
const float* __restrict__ input_logits,
|
|
const float* __restrict__ target_prob,
|
|
double* __restrict__ output_sum,
|
|
int N_elements
|
|
) {
|
|
double sum = 0.0;
|
|
|
|
int N_vec = N_elements >> 2;
|
|
int tid = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int stride = gridDim.x * blockDim.x;
|
|
|
|
const float4* __restrict__ in4 = (const float4*)input_logits;
|
|
const float4* __restrict__ tgt4 = (const float4*)target_prob;
|
|
|
|
// Process 4 vectors per iteration (register tiling)
|
|
for (int i = tid; i < N_vec; i += stride * 4) {
|
|
float4 lq0, lq1, lq2, lq3;
|
|
float4 p0, p1, p2, p3;
|
|
|
|
// Load 4 vectors
|
|
if (i < N_vec) { lq0 = in4[i]; p0 = tgt4[i]; }
|
|
if (i + stride < N_vec) { lq1 = in4[i + stride]; p1 = tgt4[i + stride]; }
|
|
if (i + stride*2 < N_vec) { lq2 = in4[i + stride*2]; p2 = tgt4[i + stride*2]; }
|
|
if (i + stride*3 < N_vec) { lq3 = in4[i + stride*3]; p3 = tgt4[i + stride*3]; }
|
|
|
|
// Compute
|
|
if (i < N_vec) {
|
|
sum += safe_kl_term(p0.x, lq0.x) + safe_kl_term(p0.y, lq0.y);
|
|
sum += safe_kl_term(p0.z, lq0.z) + safe_kl_term(p0.w, lq0.w);
|
|
}
|
|
if (i + stride < N_vec) {
|
|
sum += safe_kl_term(p1.x, lq1.x) + safe_kl_term(p1.y, lq1.y);
|
|
sum += safe_kl_term(p1.z, lq1.z) + safe_kl_term(p1.w, lq1.w);
|
|
}
|
|
if (i + stride*2 < N_vec) {
|
|
sum += safe_kl_term(p2.x, lq2.x) + safe_kl_term(p2.y, lq2.y);
|
|
sum += safe_kl_term(p2.z, lq2.z) + safe_kl_term(p2.w, lq2.w);
|
|
}
|
|
if (i + stride*3 < N_vec) {
|
|
sum += safe_kl_term(p3.x, lq3.x) + safe_kl_term(p3.y, lq3.y);
|
|
sum += safe_kl_term(p3.z, lq3.z) + safe_kl_term(p3.w, lq3.w);
|
|
}
|
|
}
|
|
|
|
sum = block_reduce_sum(sum);
|
|
|
|
if (threadIdx.x == 0) {
|
|
output_sum[blockIdx.x] = sum;
|
|
}
|
|
}
|
|
|
|
torch::Tensor kldiv_forward_cuda(torch::Tensor input_logits, torch::Tensor target_prob) {
|
|
TORCH_CHECK(input_logits.is_cuda() && target_prob.is_cuda(), "Inputs must be CUDA tensors");
|
|
|
|
input_logits = input_logits.contiguous();
|
|
target_prob = target_prob.contiguous();
|
|
|
|
int N = input_logits.numel();
|
|
|
|
if (N % VEC_SIZE != 0) {
|
|
TORCH_CHECK(false, "Total elements must be divisible by 4");
|
|
}
|
|
|
|
const int block_size = BLOCK_SIZE;
|
|
// Optimal grid size: balance between parallelism and reduction overhead
|
|
const int grid_size = min(1024, (N / VEC_SIZE + block_size - 1) / block_size);
|
|
|
|
auto partial_sum = torch::empty({grid_size}, input_logits.options().dtype(torch::kFloat64));
|
|
|
|
// Choose best kernel based on problem size
|
|
if (N >= 1048576) { // >= 1M elements, use v2 (register tiling)
|
|
kldiv_kernel_v2<<<grid_size, block_size>>>(
|
|
input_logits.data_ptr<float>(),
|
|
target_prob.data_ptr<float>(),
|
|
partial_sum.data_ptr<double>(),
|
|
N
|
|
);
|
|
} else { // Use v1 (maximally unrolled)
|
|
kldiv_kernel_v1<<<grid_size, block_size>>>(
|
|
input_logits.data_ptr<float>(),
|
|
target_prob.data_ptr<float>(),
|
|
partial_sum.data_ptr<double>(),
|
|
N
|
|
);
|
|
}
|
|
|
|
// Final reduction on device
|
|
double total = partial_sum.sum().item<double>();
|
|
float result = (float)(total / N);
|
|
|
|
return torch::tensor(result, input_logits.options());
|
|
}
|
|
"""
|
|
|
|
self.kldiv_op = load_inline(
|
|
name="kldiv_ultra_op",
|
|
cpp_sources=cpp_source,
|
|
cuda_sources=cuda_source,
|
|
functions=["kldiv_forward_cuda"],
|
|
extra_cuda_cflags=["-O3", "--use_fast_math", "-lineinfo", "--maxrregcount=64"],
|
|
verbose=True
|
|
)
|
|
|
|
def forward(self, input_logits: torch.Tensor, target_prob: torch.Tensor) -> torch.Tensor:
|
|
return self.kldiv_op.kldiv_forward_cuda(input_logits, target_prob) |