forked from ccf-ai-infra/GPUCodeForces
243 lines
7.6 KiB
Python
243 lines
7.6 KiB
Python
# LayerNorm 向量化Welford
|
|
|
|
import torch
|
|
from torch.utils.cpp_extension import load_inline
|
|
|
|
# --- 核心 CUDA C++ 代码 ---
|
|
layernorm_source = """
|
|
#include <torch/extension.h>
|
|
#include <cuda_runtime.h>
|
|
#include <math.h>
|
|
|
|
#define WARP_SIZE 32
|
|
|
|
struct WelfordData {
|
|
float mean;
|
|
float m2; // Sum of squares of differences from the current mean
|
|
int count;
|
|
};
|
|
|
|
// Warp 级合并 WelfordData
|
|
__device__ __forceinline__ WelfordData warp_reduce_welford(WelfordData data) {
|
|
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
|
|
WelfordData other;
|
|
other.mean = __shfl_down_sync(0xffffffff, data.mean, offset);
|
|
other.m2 = __shfl_down_sync(0xffffffff, data.m2, offset);
|
|
other.count = __shfl_down_sync(0xffffffff, data.count, offset);
|
|
|
|
if (other.count > 0) {
|
|
float delta = other.mean - data.mean;
|
|
float new_count = data.count + other.count;
|
|
data.m2 += other.m2 + delta * delta * data.count * other.count / new_count;
|
|
data.mean += delta * other.count / new_count;
|
|
data.count = new_count;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
__global__ void layernorm_kernel_welford_vectorized(
|
|
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__ WelfordData s_welford_results[32];
|
|
|
|
const float* x_row = x + row * features;
|
|
float* y_row = y + row * features;
|
|
|
|
// 向量化单次遍历计算全局均值和方差
|
|
WelfordData local_data;
|
|
local_data.mean = 0.0f;
|
|
local_data.m2 = 0.0f;
|
|
local_data.count = 0;
|
|
|
|
// 使用向量化处理以提升性能
|
|
if (features % 4 == 0) {
|
|
for (int i = tid * 4; i < features; i += blockDim.x * 4) {
|
|
float4 vec = *reinterpret_cast<const float4*>(x_row + i);
|
|
local_data.count++;
|
|
float delta = vec.x - local_data.mean;
|
|
local_data.mean += delta / local_data.count;
|
|
float delta2 = vec.x - local_data.mean;
|
|
local_data.m2 += delta * delta2;
|
|
|
|
local_data.count++;
|
|
delta = vec.y - local_data.mean;
|
|
local_data.mean += delta / local_data.count;
|
|
delta2 = vec.y - local_data.mean;
|
|
local_data.m2 += delta * delta2;
|
|
|
|
local_data.count++;
|
|
delta = vec.z - local_data.mean;
|
|
local_data.mean += delta / local_data.count;
|
|
delta2 = vec.z - local_data.mean;
|
|
local_data.m2 += delta * delta2;
|
|
|
|
local_data.count++;
|
|
delta = vec.w - local_data.mean;
|
|
local_data.mean += delta / local_data.count;
|
|
delta2 = vec.w - local_data.mean;
|
|
local_data.m2 += delta * delta2;
|
|
}
|
|
} else {
|
|
for (int i = tid; i < features; i += blockDim.x) {
|
|
float val = x_row[i];
|
|
local_data.count++;
|
|
float delta = val - local_data.mean;
|
|
local_data.mean += delta / local_data.count;
|
|
float delta2 = val - local_data.mean;
|
|
local_data.m2 += delta * delta2;
|
|
}
|
|
}
|
|
|
|
// Warp 级归约
|
|
WelfordData warp_data = warp_reduce_welford(local_data);
|
|
|
|
// 将 Warp 结果写入共享内存
|
|
if (lane_id == 0) {
|
|
s_welford_results[warp_id] = warp_data;
|
|
}
|
|
__syncthreads();
|
|
|
|
// Block 级归约(在第一个 warp 中完成)
|
|
if (warp_id == 0) {
|
|
WelfordData block_data;
|
|
block_data.mean = 0.0f;
|
|
block_data.m2 = 0.0f;
|
|
block_data.count = 0;
|
|
|
|
if (lane_id < num_warps) {
|
|
block_data = s_welford_results[lane_id];
|
|
}
|
|
|
|
block_data = warp_reduce_welford(block_data);
|
|
|
|
if (lane_id == 0) {
|
|
float mean = block_data.mean;
|
|
float var = block_data.m2 / features;
|
|
s_mean = mean;
|
|
s_inv_std = rsqrtf(var + 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;
|
|
}
|
|
|
|
threads = (threads + WARP_SIZE - 1) / WARP_SIZE * WARP_SIZE;
|
|
threads = min(threads, features);
|
|
|
|
size_t shared_mem = sizeof(float) * 2 + sizeof(WelfordData) * ((threads + WARP_SIZE - 1) / WARP_SIZE);
|
|
|
|
layernorm_kernel_welford_vectorized<<<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);
|
|
"""
|
|
|
|
layernorm = load_inline(
|
|
name="layernorm_welford_vectorized_final",
|
|
cpp_sources=layernorm_cpp_source,
|
|
cuda_sources=layernorm_source,
|
|
functions=["layernorm_cuda"],
|
|
extra_cuda_cflags=["-O3", "--use_fast_math"],
|
|
verbose=True
|
|
)
|
|
|
|
|
|
class ModelNew(torch.nn.Module):
|
|
def __init__(self, eps: float = 1e-5):
|
|
super(ModelNew, self).__init__()
|
|
self.eps = eps
|
|
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 = torch.nn.Parameter(torch.ones(feature_dim, device=x.device))
|
|
self.bias = torch.nn.Parameter(torch.zeros(feature_dim, device=x.device))
|
|
self._initialized = True
|
|
|
|
return self.layernorm.layernorm_cuda(x, self.weight, self.bias, self.eps)
|