Merge pull request 'finish GroupNorm Operator #2' (#19) from ZZZJ/GPUCodeForces:groupnorm into main

This commit is contained in:
Kuohais 2025-10-30 17:07:10 +08:00
commit a4dad99413
4 changed files with 293 additions and 0 deletions

152
S1/2/groupnorm_cuda.py Normal file
View File

@ -0,0 +1,152 @@
# groupnorm_cuda.py
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
from groupnorm_torch import NUM_GROUPS, C, EPS, W
assert W % 4 == 0, "Width (W) must be a multiple of 4 for float4 vectorization"
class ModelNew(nn.Module):
def __init__(self, num_groups, num_channels, eps):
super().__init__()
self.weight = nn.Parameter(torch.ones(num_channels))
self.bias = nn.Parameter(torch.zeros(num_channels))
self.num_groups = num_groups
self.eps = eps
self._compile_cuda_kernel()
def _compile_cuda_kernel(self):
cpp_source = """
#include <torch/extension.h>
torch::Tensor groupnorm_forward_cuda(
torch::Tensor input, torch::Tensor weight, torch::Tensor bias,
int num_groups, float eps);
"""
cuda_source = """
#include <cuda_runtime.h>
#include <float.h>
#define BLOCK_SIZE 512
#define WARP_SIZE 32
__device__ __forceinline__ float warp_reduce_sum(float val) {
for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2)
val += __shfl_down_sync(0xffffffff, val, offset);
return val;
}
__global__ void groupnorm_fused_vectorized_kernel(
const float* __restrict__ input, const float* __restrict__ weight,
const float* __restrict__ bias, float* __restrict__ output,
int N, int C, int H, int W, int num_groups, float eps
) {
__shared__ float s_warp_sums[BLOCK_SIZE / WARP_SIZE];
__shared__ float s_warp_sum_sqs[BLOCK_SIZE / WARP_SIZE];
int group_idx = blockIdx.x;
if (group_idx >= N * num_groups) return;
int sample_idx = group_idx / num_groups;
int group_in_sample = group_idx % num_groups;
int C_per_group = C / num_groups;
int group_size = C_per_group * H * W;
float thread_sum = 0.0f;
float thread_sum_sq = 0.0f;
for (int i = threadIdx.x; i < group_size / 4; i += BLOCK_SIZE) {
int c_local = i / (H * (W / 4));
int remainder = i % (H * (W / 4));
int h_local = remainder / (W / 4);
int w4_local = remainder % (W / 4);
int c_global = group_in_sample * C_per_group + c_local;
int offset = (sample_idx * C + c_global) * H * W + h_local * W + w4_local * 4;
float4 val4 = *reinterpret_cast<const float4*>(input + offset);
thread_sum += val4.x + val4.y + val4.z + val4.w;
thread_sum_sq += val4.x * val4.x + val4.y * val4.y + val4.z * val4.z + val4.w * val4.w;
}
float warp_sum = warp_reduce_sum(thread_sum);
float warp_sum_sq = warp_reduce_sum(thread_sum_sq);
int warp_id = threadIdx.x / WARP_SIZE;
int lane_id = threadIdx.x % WARP_SIZE;
if (lane_id == 0) { s_warp_sums[warp_id] = warp_sum; s_warp_sum_sqs[warp_id] = warp_sum_sq; }
__syncthreads();
warp_sum = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_sums[lane_id] : 0.0f;
warp_sum_sq = (threadIdx.x < BLOCK_SIZE / WARP_SIZE) ? s_warp_sum_sqs[lane_id] : 0.0f;
if (warp_id == 0) { warp_sum = warp_reduce_sum(warp_sum); warp_sum_sq = warp_reduce_sum(warp_sum_sq); }
if (threadIdx.x == 0) {
s_warp_sums[0] = warp_sum / group_size; // mean
float variance = (warp_sum_sq / group_size) - (s_warp_sums[0] * s_warp_sums[0]);
s_warp_sum_sqs[0] = rsqrtf(variance + eps); // inv_stddev
}
__syncthreads();
float mean = s_warp_sums[0];
float inv_stddev = s_warp_sum_sqs[0];
for (int i = threadIdx.x; i < group_size / 4; i += BLOCK_SIZE) {
int c_local = i / (H * (W / 4));
int remainder = i % (H * (W / 4));
int h_local = remainder / (W / 4);
int w4_local = remainder % (W / 4);
int c_global = group_in_sample * C_per_group + c_local;
int offset = (sample_idx * C + c_global) * H * W + h_local * W + w4_local * 4;
float4 val4 = *reinterpret_cast<const float4*>(input + offset);
float w0 = weight[c_global], b0 = bias[c_global];
val4.x = (val4.x - mean) * inv_stddev * w0 + b0;
val4.y = (val4.y - mean) * inv_stddev * w0 + b0;
val4.z = (val4.z - mean) * inv_stddev * w0 + b0;
val4.w = (val4.w - mean) * inv_stddev * w0 + b0;
*reinterpret_cast<float4*>(output + offset) = val4;
}
}
torch::Tensor groupnorm_forward_cuda(
torch::Tensor input, torch::Tensor weight, torch::Tensor bias,
int num_groups, float eps) {
const int N = input.size(0);
const int C = input.size(1);
const int H = input.size(2);
const int W = input.size(3);
auto output = torch::empty_like(input);
const int blocks = N * num_groups;
const int threads = BLOCK_SIZE;
size_t shared_mem_size = (threads / WARP_SIZE) * 2 * sizeof(float);
groupnorm_fused_vectorized_kernel<<<blocks, threads, shared_mem_size>>>(
input.data_ptr<float>(), weight.data_ptr<float>(), bias.data_ptr<float>(),
output.data_ptr<float>(), N, C, H, W, num_groups, eps);
return output;
}
"""
self.groupnorm_op = load_inline(
name="groupnorm_fused_vectorized_op",
cpp_sources=cpp_source, cuda_sources=cuda_source,
functions=["groupnorm_forward_cuda"],
extra_cuda_cflags=["-O3", "--use_fast_math"], verbose=True
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.groupnorm_op.groupnorm_forward_cuda(
x.contiguous(), self.weight, self.bias, self.num_groups, self.eps
)

23
S1/2/groupnorm_torch.py Normal file
View File

@ -0,0 +1,23 @@
# groupnorm_torch.py
import torch
import torch.nn as nn
N, C, H, W = 64, 256, 56, 56
NUM_GROUPS = 32
EPS = 1e-5
class Model(nn.Module):
"""使用 PyTorch 内置 nn.GroupNorm 的基准实现。"""
def __init__(self, num_groups, num_channels, eps):
super().__init__()
self.groupnorm = nn.GroupNorm(num_groups, num_channels, eps=eps, affine=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.groupnorm(x)
def get_inputs():
x = torch.randn(N, C, H, W, dtype=torch.float32)
return [x]
def get_init_inputs():
return [NUM_GROUPS, C, EPS]

30
S1/2/prompt.txt Normal file
View File

@ -0,0 +1,30 @@
You write custom CUDA kernels to replace the pytorch operators in the given 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 matmul+relu), or algorithmic changes (such as online softmax). You are only limited by your imagination.
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:
```python
# groupnorm_torch.py
import torch
import torch.nn as nn
N, C, H, W = 64, 256, 56, 56
NUM_GROUPS = 32
EPS = 1e-5
class Model(nn.Module):
"""使用 PyTorch 内置 nn.GroupNorm 的基准实现。"""
def __init__(self, num_groups, num_channels, eps):
super().__init__()
self.groupnorm = nn.GroupNorm(num_groups, num_channels, eps=eps, affine=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.groupnorm(x)
def get_inputs():
x = torch.randn(N, C, H, W, dtype=torch.float32)
return [x]
def get_init_inputs():
return [NUM_GROUPS, C, EPS]

88
S1/2/run_code.py Normal file
View File

@ -0,0 +1,88 @@
###########################################################
# 性能和精度验证程序
###########################################################
import torch
import torch.nn as nn
import time
from groupnorm_torch import Model, get_inputs, get_init_inputs
from groupnorm_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)
# 更严格的精度检查
abs_diff = (output_torch - output_cuda).abs()
max_diff = abs_diff.max().item()
mean_diff = abs_diff.mean().item()
print(f"最大差异: {max_diff:.6f}")
print(f"平均差异: {mean_diff:.6f}")
precision_flag = torch.allclose(output_torch, output_cuda, rtol=1e-05, atol=1e-05)
if precision_flag:
print("✅ 精度对齐:两个模型的输出结果非常接近。")
else:
print("❌ 精度不一致!")
print("\n-------------------- 性能加速比测试 --------------------")
num_iterations = 1000 # 增加迭代次数以获得更准确的时间测量
# Warm up
for _ in range(100):
_ = torch_model(*inputs)
_ = cuda_model(*inputs)
# 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 (matmul + relu) 平均执行时间: {torch_time:.6f}")
print(f"自定义 CUDA ReLU 平均执行时间: {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()