Skip to content

Commit 6449864

Browse files
feat(kernels): add batch-invariant RMSNorm
1 parent e27f284 commit 6449864

10 files changed

Lines changed: 1291 additions & 0 deletions

File tree

‎benchmarks/benchmark_rmsnorm.py‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import argparse
2+
import time
3+
4+
import torch
5+
6+
from rl_engine.kernels.ops.pytorch.norm.rmsnorm_ref import rmsnorm_ref_custom
7+
from rl_engine.kernels.ops.triton.rmsnorm_triton import rmsnorm_triton
8+
9+
try:
10+
from rl_engine.kernels.ops.cuda.norm.rmsnorm import rmsnorm_cuda
11+
12+
HAS_CUDA_EXT = True
13+
except Exception:
14+
HAS_CUDA_EXT = False
15+
16+
17+
def bench(fn, x, w, dy, warmup=20, iters=100):
18+
for _ in range(warmup):
19+
x.grad = None
20+
w.grad = None
21+
y = fn(x, w)
22+
y.backward(dy)
23+
torch.cuda.synchronize()
24+
25+
start = time.time()
26+
for _ in range(iters):
27+
x.grad = None
28+
w.grad = None
29+
y = fn(x, w)
30+
y.backward(dy)
31+
torch.cuda.synchronize()
32+
return (time.time() - start) * 1000.0 / iters
33+
34+
35+
def main():
36+
parser = argparse.ArgumentParser()
37+
parser.add_argument("--T", type=int, default=1024)
38+
parser.add_argument("--H", type=int, default=4096)
39+
parser.add_argument("--dtype", choices=["fp16", "bf16"], default="bf16")
40+
args = parser.parse_args()
41+
42+
dtype = torch.float16 if args.dtype == "fp16" else torch.bfloat16
43+
device = "cuda"
44+
T, H = args.T, args.H
45+
46+
torch.manual_seed(0)
47+
x_base = torch.randn((T, H), device=device, dtype=dtype) * 0.2
48+
w_base = torch.randn((H,), device=device, dtype=dtype) * 0.2
49+
dy = torch.randn((T, H), device=device, dtype=dtype) * 0.2
50+
51+
def make_inputs():
52+
return (
53+
x_base.detach().clone().requires_grad_(True),
54+
w_base.detach().clone().requires_grad_(True),
55+
)
56+
57+
x, w = make_inputs()
58+
t_ref = bench(lambda a, b: rmsnorm_ref_custom(a, b), x, w, dy)
59+
print(f"pytorch ref : {t_ref:.4f} ms")
60+
61+
x, w = make_inputs()
62+
t_tri = bench(lambda a, b: rmsnorm_triton(a, b), x, w, dy)
63+
print(f"triton : {t_tri:.4f} ms | speedup vs ref: {t_ref / t_tri:.2f}x")
64+
65+
if HAS_CUDA_EXT:
66+
x, w = make_inputs()
67+
t_cuda = bench(lambda a, b: rmsnorm_cuda(a, b), x, w, dy)
68+
print(f"cuda : {t_cuda:.4f} ms | speedup vs ref: {t_ref / t_cuda:.2f}x")
69+
else:
70+
print("cuda : skipped, extension is not built")
71+
72+
73+
if __name__ == "__main__":
74+
main()

‎csrc/cuda/rmsnorm.cu‎

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
#include <torch/extension.h>
2+
#include <ATen/cuda/CUDAContext.h>
3+
#include <cuda.h>
4+
#include <cuda_runtime.h>
5+
#include <cuda_bf16.h>
6+
#include <cuda_fp16.h>
7+
8+
template <typename scalar_t>
9+
__device__ __forceinline__ float load_as_float(const scalar_t* ptr) {
10+
return static_cast<float>(*ptr);
11+
}
12+
13+
template <>
14+
__device__ __forceinline__ float load_as_float<at::Half>(const at::Half* ptr) {
15+
const __half* p = reinterpret_cast<const __half*>(ptr);
16+
return __half2float(*p);
17+
}
18+
19+
template <>
20+
__device__ __forceinline__ float load_as_float<at::BFloat16>(const at::BFloat16* ptr) {
21+
const __nv_bfloat16* p = reinterpret_cast<const __nv_bfloat16*>(ptr);
22+
return __bfloat162float(*p);
23+
}
24+
25+
26+
template <typename scalar_t>
27+
__device__ __forceinline__ void store_from_float(scalar_t* ptr, float v) {
28+
*ptr = static_cast<scalar_t>(v);
29+
}
30+
31+
template <>
32+
__device__ __forceinline__ void store_from_float<at::Half>(at::Half* ptr, float v) {
33+
__half* p = reinterpret_cast<__half*>(ptr);
34+
*p = __float2half(v);
35+
}
36+
37+
template <>
38+
__device__ __forceinline__ void store_from_float<at::BFloat16>(at::BFloat16* ptr, float v) {
39+
__nv_bfloat16* p = reinterpret_cast<__nv_bfloat16*>(ptr);
40+
*p = __float2bfloat16(v);
41+
}
42+
43+
44+
__device__ __forceinline__ float block_reduce_sum(float v) {
45+
extern __shared__ float smem[];
46+
int tid = threadIdx.x;
47+
48+
smem[tid] = v;
49+
__syncthreads();
50+
51+
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
52+
if (tid < stride) {
53+
smem[tid] += smem[tid + stride];
54+
}
55+
__syncthreads();
56+
}
57+
58+
return smem[0];
59+
}
60+
61+
62+
static int choose_threads(int H) {
63+
if (H <= 64) return 64;
64+
if (H <= 128) return 128;
65+
if (H <= 256) return 256;
66+
return 512;
67+
}
68+
69+
70+
template <typename scalar_t, typename weight_t>
71+
__global__ void rmsnorm_fwd_kernel(
72+
const scalar_t* __restrict__ x,
73+
const weight_t* __restrict__ weight,
74+
scalar_t* __restrict__ y,
75+
float* __restrict__ rstd,
76+
int T,
77+
int H,
78+
float eps
79+
) {
80+
int row = blockIdx.x;
81+
int tid = threadIdx.x;
82+
83+
const scalar_t* x_row = x + row * H;
84+
scalar_t* y_row = y + row * H;
85+
86+
float local_sum = 0.0f;
87+
88+
// 计算 sum(x^2),每个 thread 负责若干列。
89+
for (int col = tid; col < H; col += blockDim.x) {
90+
float xv = load_as_float<scalar_t>(x_row + col);
91+
local_sum += xv * xv;
92+
}
93+
94+
// 固定 block reduction。
95+
float sum = block_reduce_sum(local_sum);
96+
97+
float row_rstd = rsqrtf(sum / static_cast<float>(H) + eps);
98+
99+
if (tid == 0) {
100+
rstd[row] = row_rstd;
101+
}
102+
103+
__syncthreads();
104+
105+
// 写出 y = x * rstd * weight。
106+
for (int col = tid; col < H; col += blockDim.x) {
107+
float xv = load_as_float<scalar_t>(x_row + col);
108+
float wv = load_as_float<weight_t>(weight + col);
109+
float out = xv * row_rstd * wv;
110+
store_from_float<scalar_t>(y_row + col, out);
111+
}
112+
}
113+
114+
115+
template <typename scalar_t, typename weight_t>
116+
__global__ void rmsnorm_bwd_dx_kernel(
117+
const scalar_t* __restrict__ dy,
118+
const scalar_t* __restrict__ x,
119+
const weight_t* __restrict__ weight,
120+
const float* __restrict__ rstd,
121+
scalar_t* __restrict__ dx,
122+
int T,
123+
int H
124+
) {
125+
int row = blockIdx.x;
126+
int tid = threadIdx.x;
127+
128+
const scalar_t* dy_row = dy + row * H;
129+
const scalar_t* x_row = x + row * H;
130+
scalar_t* dx_row = dx + row * H;
131+
132+
float local_dot = 0.0f;
133+
134+
for (int col = tid; col < H; col += blockDim.x) {
135+
float dyv = load_as_float<scalar_t>(dy_row + col);
136+
float xv = load_as_float<scalar_t>(x_row + col);
137+
float wv = load_as_float<weight_t>(weight + col);
138+
local_dot += dyv * wv * xv;
139+
}
140+
141+
float dot = block_reduce_sum(local_dot);
142+
143+
float r = rstd[row];
144+
float coeff = dot * r * r * r / static_cast<float>(H);
145+
146+
for (int col = tid; col < H; col += blockDim.x) {
147+
float dyv = load_as_float<scalar_t>(dy_row + col);
148+
float xv = load_as_float<scalar_t>(x_row + col);
149+
float wv = load_as_float<weight_t>(weight + col);
150+
151+
float out = r * dyv * wv - xv * coeff;
152+
store_from_float<scalar_t>(dx_row + col, out);
153+
}
154+
}
155+
156+
157+
template <typename scalar_t>
158+
__global__ void rmsnorm_partial_dw_kernel(
159+
const scalar_t* __restrict__ dy,
160+
const scalar_t* __restrict__ x,
161+
const float* __restrict__ rstd,
162+
const bool* __restrict__ mask,
163+
float* __restrict__ partial_dw,
164+
int T,
165+
int H
166+
) {
167+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
168+
int total = T * H;
169+
170+
if (idx >= total) return;
171+
172+
int row = idx / H;
173+
174+
if (!mask[row]) {
175+
partial_dw[idx] = 0.0f;
176+
return;
177+
}
178+
179+
float dyv = load_as_float<scalar_t>(dy + idx);
180+
float xv = load_as_float<scalar_t>(x + idx);
181+
float r = rstd[row];
182+
183+
partial_dw[idx] = dyv * xv * r;
184+
}
185+
186+
187+
__global__ void rmsnorm_reduce_dw_kernel(
188+
const float* __restrict__ partial_dw,
189+
float* __restrict__ dw,
190+
int T,
191+
int H
192+
) {
193+
int h = blockIdx.x;
194+
int tid = threadIdx.x;
195+
196+
float local_sum = 0.0f;
197+
198+
for (int t = tid; t < T; t += blockDim.x) {
199+
local_sum += partial_dw[t * H + h];
200+
}
201+
202+
float sum = block_reduce_sum(local_sum);
203+
204+
if (tid == 0) {
205+
dw[h] = sum;
206+
}
207+
}
208+
209+
210+
void rmsnorm_forward_cuda(
211+
torch::Tensor x,
212+
torch::Tensor weight,
213+
torch::Tensor y,
214+
torch::Tensor rstd,
215+
double eps
216+
) {
217+
int T = x.size(0);
218+
int H = x.size(1);
219+
int threads = choose_threads(H);
220+
size_t smem = threads * sizeof(float);
221+
222+
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
223+
224+
AT_DISPATCH_FLOATING_TYPES_AND2(at::kHalf, at::kBFloat16, x.scalar_type(), "rmsnorm_forward_cuda", [&] {
225+
using x_t = scalar_t;
226+
AT_DISPATCH_FLOATING_TYPES_AND2(at::kHalf, at::kBFloat16, weight.scalar_type(), "rmsnorm_forward_weight_cuda", [&] {
227+
using w_t = scalar_t;
228+
rmsnorm_fwd_kernel<x_t, w_t><<<T, threads, smem, stream>>>(
229+
x.data_ptr<x_t>(),
230+
weight.data_ptr<w_t>(),
231+
y.data_ptr<x_t>(),
232+
rstd.data_ptr<float>(),
233+
T,
234+
H,
235+
static_cast<float>(eps)
236+
);
237+
});
238+
});
239+
}
240+
241+
242+
void rmsnorm_backward_dx_cuda(
243+
torch::Tensor dy,
244+
torch::Tensor x,
245+
torch::Tensor weight,
246+
torch::Tensor rstd,
247+
torch::Tensor dx
248+
) {
249+
int T = x.size(0);
250+
int H = x.size(1);
251+
int threads = choose_threads(H);
252+
size_t smem = threads * sizeof(float);
253+
254+
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
255+
256+
AT_DISPATCH_FLOATING_TYPES_AND2(at::kHalf, at::kBFloat16, x.scalar_type(), "rmsnorm_backward_dx_cuda", [&] {
257+
using x_t = scalar_t;
258+
AT_DISPATCH_FLOATING_TYPES_AND2(at::kHalf, at::kBFloat16, weight.scalar_type(), "rmsnorm_backward_dx_weight_cuda", [&] {
259+
using w_t = scalar_t;
260+
rmsnorm_bwd_dx_kernel<x_t, w_t><<<T, threads, smem, stream>>>(
261+
dy.data_ptr<x_t>(),
262+
x.data_ptr<x_t>(),
263+
weight.data_ptr<w_t>(),
264+
rstd.data_ptr<float>(),
265+
dx.data_ptr<x_t>(),
266+
T,
267+
H
268+
);
269+
});
270+
});
271+
}
272+
273+
274+
void rmsnorm_backward_partial_dw_cuda(
275+
torch::Tensor dy,
276+
torch::Tensor x,
277+
torch::Tensor rstd,
278+
torch::Tensor mask,
279+
torch::Tensor partial_dw
280+
) {
281+
int T = x.size(0);
282+
int H = x.size(1);
283+
int total = T * H;
284+
285+
int threads = 256;
286+
int blocks = (total + threads - 1) / threads;
287+
288+
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
289+
290+
AT_DISPATCH_FLOATING_TYPES_AND2(at::kHalf, at::kBFloat16, x.scalar_type(), "rmsnorm_partial_dw_cuda", [&] {
291+
rmsnorm_partial_dw_kernel<scalar_t><<<blocks, threads, 0, stream>>>(
292+
dy.data_ptr<scalar_t>(),
293+
x.data_ptr<scalar_t>(),
294+
rstd.data_ptr<float>(),
295+
mask.data_ptr<bool>(),
296+
partial_dw.data_ptr<float>(),
297+
T,
298+
H
299+
);
300+
});
301+
}
302+
303+
304+
void rmsnorm_backward_reduce_dw_cuda(
305+
torch::Tensor partial_dw,
306+
torch::Tensor dw
307+
) {
308+
int T = partial_dw.size(0);
309+
int H = partial_dw.size(1);
310+
311+
int threads = choose_threads(T);
312+
size_t smem = threads * sizeof(float);
313+
314+
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
315+
316+
rmsnorm_reduce_dw_kernel<<<H, threads, smem, stream>>>(
317+
partial_dw.data_ptr<float>(),
318+
dw.data_ptr<float>(),
319+
T,
320+
H
321+
);
322+
}

0 commit comments

Comments
 (0)