-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrmsnorm.metal
More file actions
217 lines (191 loc) · 6.64 KB
/
Copy pathrmsnorm.metal
File metadata and controls
217 lines (191 loc) · 6.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/**
* @file rmsnorm.metal
* @brief Fused RMSNorm (Root Mean Square Layer Normalization) kernel
*
* Fuses the following operations into a single kernel:
* 1. x^2 (square)
* 2. mean(x^2) (mean reduction)
* 3. rsqrt(mean + eps) (root mean square)
* 4. x * rms (normalize)
* 5. normalized * weight (scale by learned weight)
*
* This reduces 5 separate kernel launches to 1, improving performance
* and reducing memory bandwidth.
*/
#include <metal_stdlib>
using namespace metal;
/**
* @brief Fused RMSNorm kernel
*
* Computes: output = (x / rms(x)) * weight
* where rms(x) = sqrt(mean(x^2) + eps)
*
* Uses a two-pass algorithm:
* Pass 1: Compute sum of squares using threadgroup reduction
* Pass 2: Normalize and scale by weight
*
* @param input Input tensor [batch * seq_len, hidden_size]
* @param weight Learned weight parameter [hidden_size]
* @param output Output tensor [batch * seq_len, hidden_size]
* @param batch_seq_len Number of sequences (batch * seq_len)
* @param hidden_size Hidden dimension size
* @param eps Epsilon for numerical stability
* @param gid Thread position in grid (sequence index)
* @param lid Thread position in threadgroup
* @param local_sum Threadgroup shared memory for reduction
*/
kernel void rmsnorm_fused(
device const float* input [[buffer(0)]],
device const float* weight [[buffer(1)]],
device float* output [[buffer(2)]],
constant uint& batch_seq_len [[buffer(3)]],
constant uint& hidden_size [[buffer(4)]],
constant float& eps [[buffer(5)]],
uint gid [[threadgroup_position_in_grid]],
uint lid [[thread_position_in_threadgroup]],
uint threadgroup_size [[threads_per_threadgroup]],
threadgroup float* local_sum [[threadgroup(0)]]) {
// Each threadgroup processes one sequence
if (gid >= batch_seq_len) {
return;
}
// Pointer to this sequence's input
device const float* x = input + gid * hidden_size;
device float* y = output + gid * hidden_size;
// Pass 1: Compute sum of squares using parallel reduction
float sum_sq = 0.0f;
// Each thread processes multiple elements if hidden_size > threadgroup_size
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float val = x[i];
sum_sq += val * val;
}
// Store partial sum in threadgroup memory
local_sum[lid] = sum_sq;
threadgroup_barrier(mem_flags::mem_threadgroup);
// Parallel reduction in threadgroup memory
// Use FP32 accumulation for numerical stability
for (uint stride = threadgroup_size / 2; stride > 0; stride >>= 1) {
if (lid < stride) {
local_sum[lid] += local_sum[lid + stride];
}
threadgroup_barrier(mem_flags::mem_threadgroup);
}
// Thread 0 computes the final RMS value
float rms;
if (lid == 0) {
float mean_sq = local_sum[0] / float(hidden_size);
rms = rsqrt(mean_sq + eps); // 1 / sqrt(mean_sq + eps)
local_sum[0] = rms; // Store for all threads to read
}
threadgroup_barrier(mem_flags::mem_threadgroup);
rms = local_sum[0];
// Pass 2: Normalize and scale by weight
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float normalized = x[i] * rms;
y[i] = normalized * weight[i];
}
}
/**
* @brief Fused RMSNorm kernel with FP16 input/output
*
* Same as rmsnorm_fused but uses half precision for input/output
* while maintaining FP32 accumulation for stability.
*/
kernel void rmsnorm_fused_fp16(
device const half* input [[buffer(0)]],
device const half* weight [[buffer(1)]],
device half* output [[buffer(2)]],
constant uint& batch_seq_len [[buffer(3)]],
constant uint& hidden_size [[buffer(4)]],
constant float& eps [[buffer(5)]],
uint gid [[threadgroup_position_in_grid]],
uint lid [[thread_position_in_threadgroup]],
uint threadgroup_size [[threads_per_threadgroup]],
threadgroup float* local_sum [[threadgroup(0)]]) {
if (gid >= batch_seq_len) {
return;
}
device const half* x = input + gid * hidden_size;
device half* y = output + gid * hidden_size;
// Pass 1: Compute sum of squares with FP32 accumulation
float sum_sq = 0.0f;
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float val = float(x[i]); // Convert to FP32 for accuracy
sum_sq += val * val;
}
local_sum[lid] = sum_sq;
threadgroup_barrier(mem_flags::mem_threadgroup);
// Parallel reduction
for (uint stride = threadgroup_size / 2; stride > 0; stride >>= 1) {
if (lid < stride) {
local_sum[lid] += local_sum[lid + stride];
}
threadgroup_barrier(mem_flags::mem_threadgroup);
}
float rms;
if (lid == 0) {
float mean_sq = local_sum[0] / float(hidden_size);
rms = rsqrt(mean_sq + eps);
local_sum[0] = rms;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
rms = local_sum[0];
// Pass 2: Normalize and scale (FP16 output)
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float normalized = float(x[i]) * rms;
y[i] = half(normalized * float(weight[i]));
}
}
/**
* @brief Fused RMSNorm with residual add
*
* Computes: output = (x / rms(x)) * weight + residual
*
* Fuses residual connection into the normalization kernel.
*/
kernel void rmsnorm_fused_residual(
device const float* input [[buffer(0)]],
device const float* weight [[buffer(1)]],
device const float* residual [[buffer(2)]],
device float* output [[buffer(3)]],
constant uint& batch_seq_len [[buffer(4)]],
constant uint& hidden_size [[buffer(5)]],
constant float& eps [[buffer(6)]],
uint gid [[threadgroup_position_in_grid]],
uint lid [[thread_position_in_threadgroup]],
uint threadgroup_size [[threads_per_threadgroup]],
threadgroup float* local_sum [[threadgroup(0)]]) {
if (gid >= batch_seq_len) {
return;
}
device const float* x = input + gid * hidden_size;
device const float* res = residual + gid * hidden_size;
device float* y = output + gid * hidden_size;
// Pass 1: Sum of squares
float sum_sq = 0.0f;
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float val = x[i];
sum_sq += val * val;
}
local_sum[lid] = sum_sq;
threadgroup_barrier(mem_flags::mem_threadgroup);
for (uint stride = threadgroup_size / 2; stride > 0; stride >>= 1) {
if (lid < stride) {
local_sum[lid] += local_sum[lid + stride];
}
threadgroup_barrier(mem_flags::mem_threadgroup);
}
float rms;
if (lid == 0) {
float mean_sq = local_sum[0] / float(hidden_size);
rms = rsqrt(mean_sq + eps);
local_sum[0] = rms;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
rms = local_sum[0];
// Pass 2: Normalize, scale, and add residual
for (uint i = lid; i < hidden_size; i += threadgroup_size) {
float normalized = x[i] * rms;
y[i] = normalized * weight[i] + res[i];
}
}