Merge pull request 'finish layernorm # 6' (#25) from gsd123/GPUCodeForces:layernorm into main

This commit is contained in:
Kuohais 2025-11-03 10:01:01 +08:00
commit f5550da4ba
4 changed files with 416 additions and 0 deletions

205
S1/6/layernorm_cuda.py Normal file
View File

@ -0,0 +1,205 @@
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
# LayerNorm CUDA 实现 - 增强优化版本
layernorm_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <math.h>
#define WARP_SIZE 32
// Warp级归约函数
__device__ __forceinline__ float warp_reduce_sum(float val) {
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
val += __shfl_down_sync(0xffffffff, val, offset);
}
return val;
}
__global__ void layernorm_kernel_optimized(
const float* __restrict__ x,
const float* __restrict__ weight,
const float* __restrict__ bias,
float* __restrict__ y,
int batch,
int features,
float eps
) {
int row = blockIdx.x;
if (row >= batch) return;
int tid = threadIdx.x;
int warp_id = tid / WARP_SIZE;
int lane_id = tid % WARP_SIZE;
int num_warps = (blockDim.x + WARP_SIZE - 1) / WARP_SIZE;
__shared__ float s_mean;
__shared__ float s_inv_std;
__shared__ float s_warp_sums[32]; // 支持最多1024个线程
__shared__ float s_warp_sum_sqs[32];
const float* x_row = x + row * features;
float* y_row = y + row * features;
// 第一步并行计算均值和方差
float thread_sum = 0.0f;
float thread_sum_sq = 0.0f;
// 使用向量化加载如果特征数是4的倍数
if (features % 4 == 0) {
for (int i = tid * 4; i < features; i += blockDim.x * 4) {
float4 vec = *reinterpret_cast<const float4*>(x_row + i);
thread_sum += vec.x + vec.y + vec.z + vec.w;
thread_sum_sq += vec.x * vec.x + vec.y * vec.y + vec.z * vec.z + vec.w * vec.w;
}
} else {
// 标量版本
for (int i = tid; i < features; i += blockDim.x) {
float v = x_row[i];
thread_sum += v;
thread_sum_sq += v * v;
}
}
// Warp级归约
float warp_sum = warp_reduce_sum(thread_sum);
float warp_sum_sq = warp_reduce_sum(thread_sum_sq);
// 将warp结果写入共享内存
if (lane_id == 0) {
s_warp_sums[warp_id] = warp_sum;
s_warp_sum_sqs[warp_id] = warp_sum_sq;
}
__syncthreads();
// Block级归约在第一个warp中完成
if (warp_id == 0) {
float block_sum = (lane_id < num_warps) ? s_warp_sums[lane_id] : 0.0f;
float block_sum_sq = (lane_id < num_warps) ? s_warp_sum_sqs[lane_id] : 0.0f;
block_sum = warp_reduce_sum(block_sum);
block_sum_sq = warp_reduce_sum(block_sum_sq);
if (lane_id == 0) {
float mean = block_sum / features;
float var = (block_sum_sq / features) - (mean * mean);
s_mean = mean;
s_inv_std = rsqrtf(fmaxf(var, 0.0f) + eps);
}
}
__syncthreads();
float mean = s_mean;
float inv_std = s_inv_std;
// 第二步应用归一化向量化存储
if (features % 4 == 0) {
for (int i = tid * 4; i < features; i += blockDim.x * 4) {
float4 vec = *reinterpret_cast<const float4*>(x_row + i);
float4 w_vec = *reinterpret_cast<const float4*>(weight + i);
float4 b_vec = *reinterpret_cast<const float4*>(bias + i);
vec.x = (vec.x - mean) * inv_std * w_vec.x + b_vec.x;
vec.y = (vec.y - mean) * inv_std * w_vec.y + b_vec.y;
vec.z = (vec.z - mean) * inv_std * w_vec.z + b_vec.z;
vec.w = (vec.w - mean) * inv_std * w_vec.w + b_vec.w;
*reinterpret_cast<float4*>(y_row + i) = vec;
}
} else {
// 标量版本
for (int i = tid; i < features; i += blockDim.x) {
float v = x_row[i];
float w = weight[i];
float b = bias[i];
y_row[i] = (v - mean) * inv_std * w + b;
}
}
}
torch::Tensor layernorm_cuda(torch::Tensor x, torch::Tensor weight, torch::Tensor bias, float eps) {
TORCH_CHECK(x.is_cuda(), "x 必须是 CUDA 张量");
TORCH_CHECK(weight.is_cuda(), "weight 必须是 CUDA 张量");
TORCH_CHECK(bias.is_cuda(), "bias 必须是 CUDA 张量");
TORCH_CHECK(x.dim() == 2, "当前内核仅支持二维输入张量");
TORCH_CHECK(weight.dim() == 1, "LayerNorm 权重必须是一维向量");
TORCH_CHECK(bias.dim() == 1, "LayerNorm 偏置必须是一维向量");
TORCH_CHECK(x.size(1) == weight.size(0), "输入最后一维与权重长度不匹配");
TORCH_CHECK(weight.size(0) == bias.size(0), "权重和偏置长度必须相同");
int batch = x.size(0);
int features = x.size(1);
auto y = torch::empty_like(x);
// 智能线程配置
int threads;
if (features <= 64) {
threads = 64;
} else if (features <= 256) {
threads = 128;
} else if (features <= 1024) {
threads = 256;
} else {
threads = 512;
}
// 确保线程数是warp大小的倍数
threads = (threads + WARP_SIZE - 1) / WARP_SIZE * WARP_SIZE;
threads = min(threads, features);
// 计算共享内存大小
size_t shared_mem = 2 * ((threads + WARP_SIZE - 1) / WARP_SIZE) * sizeof(float) + 2 * sizeof(float);
layernorm_kernel_optimized<<<batch, threads, shared_mem>>>(
x.data_ptr<float>(),
weight.data_ptr<float>(),
bias.data_ptr<float>(),
y.data_ptr<float>(),
batch,
features,
eps
);
return y;
}
"""
layernorm_cpp_source = """
torch::Tensor layernorm_cuda(torch::Tensor x, torch::Tensor weight, torch::Tensor bias, float eps);
"""
# 编译 CUDA 代码
layernorm = load_inline(
name="layernorm",
cpp_sources=layernorm_cpp_source,
cuda_sources=layernorm_source,
functions=["layernorm_cuda"],
extra_cuda_cflags=["-O3", "--use_fast_math"],
verbose=True
)
class ModelNew(nn.Module):
def __init__(self, eps: float = 1e-5):
super(ModelNew, self).__init__()
self.eps = eps
# 在forward中动态确定特征维度
self.weight = None
self.bias = None
self.layernorm = layernorm
self._initialized = False
def forward(self, x):
# 动态初始化权重和偏置(只初始化一次)
if self.weight is None:
feature_dim = x.size(1)
self.weight = nn.Parameter(torch.ones(feature_dim, device=x.device))
self.bias = nn.Parameter(torch.zeros(feature_dim, device=x.device))
self._initialized = True
return self.layernorm.layernorm_cuda(x, self.weight, self.bias, self.eps)

52
S1/6/layernorm_torch.py Normal file
View File

@ -0,0 +1,52 @@
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Simple model that performs LayerNorm normalization using PyTorch's built-in nn.LayerNorm.
"""
def __init__(self, normalized_shape=None, eps=1e-5, elementwise_affine=True):
super(Model, self).__init__()
# 如果未指定normalized_shape将在forward中动态设置
self.normalized_shape = normalized_shape
self.eps = eps
self.elementwise_affine = elementwise_affine
self.layernorm = None
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Applies LayerNorm to the input tensor.
Args:
x (torch.Tensor): Input tensor of any shape.
Returns:
torch.Tensor: Output tensor with LayerNorm applied, same shape as input.
"""
# 如果layernorm未初始化根据输入形状动态创建
if self.layernorm is None:
if self.normalized_shape is None:
# 默认对最后一个维度进行归一化
self.normalized_shape = x.shape[1:]
self.layernorm = nn.LayerNorm(
normalized_shape=self.normalized_shape,
eps=self.eps,
elementwise_affine=self.elementwise_affine
).to(x.device)
return self.layernorm(x)
batch_size = 16
dim = 16384
def get_inputs():
x = torch.randn(batch_size, dim)
return [x]
def get_init_inputs():
# 可以传入归一化形状、eps等参数保持向后兼容
return [] # 使用默认参数

82
S1/6/prompt.txt Normal file
View File

@ -0,0 +1,82 @@
LayerNorm CUDA Implementation - Enhanced Optimized Version
Key optimization techniques used in this implementation:
1.Warp-Level Parallel Reduction: Implements efficient warp-level reduction for mean and variance calculations using warp shuffle operations
2.Vectorized Memory Access: Utilizes float4 vector loads/stores for coalesced memory access when feature dimension is divisible by 4
3.Shared Memory Hierarchy: Employs multi-level shared memory for intermediate results between warp and block levels
4.Dynamic Thread Configuration: Automatically adjusts thread block size based on feature dimension for optimal occupancy
5.Numerical Stability: Maintains numerical precision with robust variance calculation and epsilon handling
Bank Conflict Avoidance: Carefully structures shared memory access patterns to minimize bank conflicts
The custom kernel eliminates multiple memory passes by computing mean, variance, and normalization in a single fused operation with optimized memory hierarchy usage across warp, shared, and global memory levels.
Technical Features:
1.Warp Reduction: Efficient 32-thread warp reduction using __shfl_down_sync
2.Vectorization: Automatic fallback between vectorized (float4) and scalar operations
3.Smart Block Sizing: Adaptive thread configuration (64-512 threads) based on feature dimension
4.Memory Coalescing: Organized memory access patterns for maximum bandwidth utilization
5.Fused Operations: Combines statistics computation and normalization in one kernel
Performance Benefits:
1.Reduces global memory traffic by processing entire LayerNorm operation in-place
2.Eliminates intermediate tensor allocations between mean/variance calculations
3.Optimizes for various feature dimensions through adaptive thread configuration
4.Leverages CUDA memory hierarchy for maximum data reuse
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
class Model(nn.Module):
"""
Simple model that performs LayerNorm normalization using PyTorch's built-in nn.LayerNorm.
"""
def __init__(self, normalized_shape=None, eps=1e-5, elementwise_affine=True):
super(Model, self).__init__()
# 如果未指定normalized_shape将在forward中动态设置
self.normalized_shape = normalized_shape
self.eps = eps
self.elementwise_affine = elementwise_affine
self.layernorm = None
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Applies LayerNorm to the input tensor.
Args:
x (torch.Tensor): Input tensor of any shape.
Returns:
torch.Tensor: Output tensor with LayerNorm applied, same shape as input.
"""
# 如果layernorm未初始化根据输入形状动态创建
if self.layernorm is None:
if self.normalized_shape is None:
# 默认对最后一个维度进行归一化
self.normalized_shape = x.shape[1:]
self.layernorm = nn.LayerNorm(
normalized_shape=self.normalized_shape,
eps=self.eps,
elementwise_affine=self.elementwise_affine
).to(x.device)
return self.layernorm(x)
batch_size = 16
dim = 16384
def get_inputs():
x = torch.randn(batch_size, dim)
return [x]
def get_init_inputs():
# 可以传入归一化形状、eps等参数保持向后兼容
return [] # 使用默认参数

77
S1/6/run_code.py Normal file
View File

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