Merge pull request 'finish ABReLU #123' (#856) from hli28146/GPUCodeForces:h123 into main

This commit is contained in:
wawahejun 2025-12-14 20:42:53 +08:00
commit 50820eaa46
4 changed files with 320 additions and 0 deletions

View File

@ -0,0 +1,147 @@
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cpp_source = """
#include <torch/extension.h>
torch::Tensor abrelu_cuda_forward(const torch::Tensor& input);
"""
cuda_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <math.h>
#define BLOCK_SIZE 256
#define WARP_SIZE 32
struct __align__(16) Float4 {
float x, y, z, w;
};
template<typename T>
__device__ __forceinline__ T warp_reduce_sum(T val) {
#pragma unroll
for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {
val += __shfl_down_sync(0xffffffff, val, offset);
}
return val;
}
__device__ __forceinline__ float block_reduce_sum(float val) {
static __shared__ float shared[32];
int lane = threadIdx.x % WARP_SIZE;
int wid = threadIdx.x / WARP_SIZE;
val = warp_reduce_sum(val);
if (lane == 0) shared[wid] = val;
__syncthreads();
val = (threadIdx.x < blockDim.x / WARP_SIZE) ? shared[lane] : 0.0f;
if (wid == 0) val = warp_reduce_sum(val);
return val;
}
// --- Fused ABReLU Kernel ---
__global__ void abrelu_kernel(
float* __restrict__ output,
const float* __restrict__ input,
int N, int C, int HW)
{
// Grid: N * C
int gid = blockIdx.x;
int plane_offset = gid * HW;
const float* input_ptr = input + plane_offset;
float* output_ptr = output + plane_offset;
// Compute Mean
float local_sum = 0.0f;
int tid = threadIdx.x;
int i = tid * 4;
while (i < HW) {
if (i + 4 <= HW) {
Float4 val = reinterpret_cast<const Float4*>(&input_ptr[i])[0];
local_sum += val.x + val.y + val.z + val.w;
} else {
for (int k = 0; k < 4 && i+k < HW; ++k) {
local_sum += input_ptr[i+k];
}
}
i += blockDim.x * 4;
}
float plane_sum = block_reduce_sum(local_sum);
__shared__ float s_mean;
if (tid == 0) {
s_mean = plane_sum / HW;
}
__syncthreads();
float mean = s_mean;
// Apply Bias & ReLU
i = tid * 4;
while (i < HW) {
if (i + 4 <= HW) {
Float4 val = reinterpret_cast<const Float4*>(&input_ptr[i])[0];
Float4 res;
res.x = fmaxf(val.x + mean, 0.0f);
res.y = fmaxf(val.y + mean, 0.0f);
res.z = fmaxf(val.z + mean, 0.0f);
res.w = fmaxf(val.w + mean, 0.0f);
reinterpret_cast<Float4*>(&output_ptr[i])[0] = res;
} else {
for (int k = 0; k < 4 && i+k < HW; ++k) {
float v = input_ptr[i+k];
output_ptr[i+k] = fmaxf(v + mean, 0.0f);
}
}
i += blockDim.x * 4;
}
}
torch::Tensor abrelu_cuda_forward(const torch::Tensor& input) {
TORCH_CHECK(input.is_cuda(), "Input must be CUDA");
TORCH_CHECK(input.is_contiguous(), "Input must be contiguous");
int N = input.size(0);
int C = input.size(1);
int H = input.size(2);
int W = input.size(3);
int HW = H * W;
auto output = torch::empty_like(input);
dim3 grid(N * C);
dim3 block(BLOCK_SIZE);
abrelu_kernel<<<grid, block>>>(
output.data_ptr<float>(),
input.data_ptr<float>(),
N, C, HW
);
return output;
}
"""
abrelu_op_module = load_inline(
name='abrelu_op',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['abrelu_cuda_forward'],
verbose=False,
extra_cuda_cflags=['-O3']
)
class ModelNew(nn.Module):
def __init__(self):
super(ModelNew, self).__init__()
self.op = abrelu_op_module
def forward(self, input_tensor: torch.Tensor) -> torch.Tensor:
return self.op.abrelu_cuda_forward(input_tensor.contiguous())

View File

@ -0,0 +1,38 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
BATCH_SIZE = 64
CHANNELS = 256
HEIGHT = 64
WIDTH = 64
SHAPE = (BATCH_SIZE, CHANNELS, HEIGHT, WIDTH)
class ABReLU(nn.Module):
'''
"Average biased ReLU based CNN descriptor for improved face retrieval" (arXiv, 2018)
'''
def __init__(self):
super(ABReLU, self).__init__()
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Reduce over spatial dimensions (H, W)
mean = x.mean(dim=[2, 3], keepdim=True)
# Add bias and apply ReLU
return F.relu(x + mean)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.act = ABReLU()
def forward(self, x):
return self.act(x)
def get_inputs():
input_tensor = torch.randn(SHAPE, dtype=torch.float32)
return [input_tensor.contiguous()]
def get_init_inputs():
return []

View File

@ -0,0 +1,61 @@
Write a custom CUDA kernel to optimize `ABReLU` (Average Biased ReLU).
Formula (per channel `c`):
1. mu_c = mean(X[:, c, :, :])
2. Y[:, c, :, :] = ReLU(X[:, c, :, :] + mu_c)
Problem Analysis:
1. Memory Bound: The standard PyTorch implementation requires two full passes over the data per channel: one to compute the mean (reduction), and a second to apply the bias and ReLU. This is inefficient.
2. Kernel Overhead: Multiple kernel launches for reduction and element-wise ops.
Optimization Strategy: Fused Two-Pass Reduction Kernel
1. One-Block-per-Channel: Launch a grid of `N * C` blocks. Each block is responsible for processing one channel of one sample.
2. Fused Two-Pass Algorithm:
- Pass 1 (Statistics): Threads within a block cooperatively iterate over the `H * W` elements of their assigned channel. Each thread computes a partial sum. These are then aggregated using a fast parallel reduction in Shared Memory to compute the channel mean.
- Pass 2 (Apply): After the mean is computed and broadcasted within the block (via Shared Memory), threads iterate over the channel elements again. They read the original value, add the mean, apply ReLU, and write the result to the output tensor.
3. Vectorization: Use `float4` to maximize memory throughput.
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
BATCH_SIZE = 64
CHANNELS = 256
HEIGHT = 64
WIDTH = 64
SHAPE = (BATCH_SIZE, CHANNELS, HEIGHT, WIDTH)
class ABReLU(nn.Module):
'''
"Average biased ReLU based CNN descriptor for improved face retrieval" (arXiv, 2018)
'''
def __init__(self):
super(ABReLU, self).__init__()
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Reduce over spatial dimensions (H, W)
mean = x.mean(dim=[2, 3], keepdim=True)
# Add bias and apply ReLU
return F.relu(x + mean)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.act = ABReLU()
def forward(self, x):
return self.act(x)
def get_inputs():
input_tensor = torch.randn(SHAPE, dtype=torch.float32)
return [input_tensor.contiguous()]
def get_init_inputs():
return []

View File

@ -0,0 +1,74 @@
###########################################################
# 性能和精度验证程序
###########################################################
import torch
import torch.nn as nn
import time
from ABReLU_torch import Model,get_inputs,get_init_inputs
from ABReLU_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()