← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
recurrent_qk_norm_kernels.c
Go to the documentation of this file.
1#include "bf16_utils.h"
2#include "ckernel_engine.h"
3
4#include <math.h>
5
6#if defined(__AVX2__)
7#include <immintrin.h>
8#endif
9
11 int rows,
12 int dim,
13 int head_dim,
14 float eps) {
15 if (!x || rows <= 0 || dim <= 0 || head_dim <= 0) {
16 return;
17 }
18 const int num_heads = dim / head_dim;
19 if (num_heads <= 0 || num_heads * head_dim != dim) {
20 return;
21 }
22
23 for (int row = 0; row < rows; ++row) {
24 float *row_ptr = x + (size_t) row * (size_t) dim;
25 for (int head = 0; head < num_heads; ++head) {
26 float *head_ptr = row_ptr + (size_t) head * (size_t) head_dim;
27 double sum_sq = 0.0;
28 for (int col = 0; col < head_dim; ++col) {
29 sum_sq += (double) (head_ptr[col] * head_ptr[col]);
30 }
31 const float norm = sqrtf((float) sum_sq);
32 const float inv_norm = 1.0f / fmaxf(norm, eps);
33 for (int col = 0; col < head_dim; ++col) {
34 head_ptr[col] *= inv_norm;
35 }
36 }
37 }
38}
39
40static void recurrent_l2_norm_rows_backward_one(const float *d_out,
41 const float *x,
42 float *d_x,
43 int rows,
44 int dim,
45 int head_dim,
46 float eps) {
47 if (!d_out || !x || !d_x || rows <= 0 || dim <= 0 || head_dim <= 0) {
48 return;
49 }
50 const int num_heads = dim / head_dim;
51 if (num_heads <= 0 || num_heads * head_dim != dim) {
52 return;
53 }
54
55 for (int row = 0; row < rows; ++row) {
56 const float *d_row = d_out + (size_t) row * (size_t) dim;
57 const float *x_row = x + (size_t) row * (size_t) dim;
58 float *dx_row = d_x + (size_t) row * (size_t) dim;
59 for (int head = 0; head < num_heads; ++head) {
60 const float *d_head = d_row + (size_t) head * (size_t) head_dim;
61 const float *x_head = x_row + (size_t) head * (size_t) head_dim;
62 float *dx_head = dx_row + (size_t) head * (size_t) head_dim;
63
64 double sum_sq = 0.0;
65 double dot = 0.0;
66 for (int col = 0; col < head_dim; ++col) {
67 sum_sq += (double) (x_head[col] * x_head[col]);
68 dot += (double) (d_head[col] * x_head[col]);
69 }
70 const float norm = sqrtf((float) sum_sq);
71 const float inv_norm = 1.0f / fmaxf(norm, eps);
72 if (norm <= eps) {
73 for (int col = 0; col < head_dim; ++col) {
74 dx_head[col] = d_head[col] / eps;
75 }
76 continue;
77 }
78 const float proj_scale = inv_norm * inv_norm * inv_norm * (float) dot;
79 for (int col = 0; col < head_dim; ++col) {
80 dx_head[col] = inv_norm * d_head[col] - proj_scale * x_head[col];
81 }
82 }
83 }
84}
85
87 float *k,
88 int rows,
89 int q_dim,
90 int k_dim,
91 int head_dim,
92 float eps) {
93 recurrent_l2_norm_rows_forward_one(q, rows, q_dim, head_dim, eps);
94 recurrent_l2_norm_rows_forward_one(k, rows, k_dim, head_dim, eps);
95}
96
97static int recurrent_ceil_log2(int value)
98{
99 int result = 0;
100 int remaining = value - 1;
101 while (remaining > 0) {
102 ++result;
103 remaining >>= 1;
104 }
105 return result;
106}
107
108static float recurrent_pytorch_fp32_square_sum(const float *x, int dim)
109{
110#if defined(__AVX2__)
111 if (dim >= 8) {
112 enum { ilp_factor = 4, num_levels = 4, vector_width = 8 };
113 const int vector_count = dim / vector_width;
114 const int cascade_count = vector_count / ilp_factor;
115 const int level_power =
116 recurrent_ceil_log2(cascade_count) / num_levels > 4
117 ? recurrent_ceil_log2(cascade_count) / num_levels
118 : 4;
119 const int level_step = 1 << level_power;
120 const int level_mask = level_step - 1;
121 __m256 acc[num_levels][ilp_factor];
122 for (int level = 0; level < num_levels; ++level) {
123 for (int lane = 0; lane < ilp_factor; ++lane) {
124 acc[level][lane] = _mm256_setzero_ps();
125 }
126 }
127
128 int group = 0;
129 while (group + level_step <= cascade_count) {
130 for (int offset = 0; offset < level_step; ++offset, ++group) {
131 for (int lane = 0; lane < ilp_factor; ++lane) {
132 const int vector_index = group * ilp_factor + lane;
133 const __m256 value = _mm256_loadu_ps(
134 x + vector_index * vector_width);
135 const __m256 square = _mm256_mul_ps(value, value);
136 acc[0][lane] = _mm256_add_ps(acc[0][lane], square);
137 }
138 }
139 for (int level = 1; level < num_levels; ++level) {
140 for (int lane = 0; lane < ilp_factor; ++lane) {
141 acc[level][lane] =
142 _mm256_add_ps(acc[level][lane], acc[level - 1][lane]);
143 acc[level - 1][lane] = _mm256_setzero_ps();
144 }
145 const int mask = level_mask << (level * level_power);
146 if ((group & mask) != 0) {
147 break;
148 }
149 }
150 }
151 for (; group < cascade_count; ++group) {
152 for (int lane = 0; lane < ilp_factor; ++lane) {
153 const int vector_index = group * ilp_factor + lane;
154 const __m256 value = _mm256_loadu_ps(
155 x + vector_index * vector_width);
156 const __m256 square = _mm256_mul_ps(value, value);
157 acc[0][lane] = _mm256_add_ps(acc[0][lane], square);
158 }
159 }
160 for (int level = 1; level < num_levels; ++level) {
161 for (int lane = 0; lane < ilp_factor; ++lane) {
162 acc[0][lane] =
163 _mm256_add_ps(acc[0][lane], acc[level][lane]);
164 }
165 }
166
167 int vector_index = cascade_count * ilp_factor;
168 for (; vector_index < vector_count; ++vector_index) {
169 const __m256 value = _mm256_loadu_ps(
170 x + vector_index * vector_width);
171 const __m256 square = _mm256_mul_ps(value, value);
172 acc[0][0] = _mm256_add_ps(acc[0][0], square);
173 }
174 for (int lane = 1; lane < ilp_factor; ++lane) {
175 acc[0][0] = _mm256_add_ps(acc[0][0], acc[0][lane]);
176 }
177
178 _Alignas(32) float lanes[8];
179 _mm256_store_ps(lanes, acc[0][0]);
180 volatile float sum = 0.0f;
181 for (int lane = 0; lane < 8; ++lane) {
182 sum = sum + lanes[lane];
183 }
184 for (int d = vector_count * vector_width; d < dim; ++d) {
185 sum = sum + x[d] * x[d];
186 }
187 return sum;
188 }
189#endif
190 volatile float sum = 0.0f;
191 for (int d = 0; d < dim; ++d) {
192 sum = sum + x[d] * x[d];
193 }
194 return sum;
195}
196
198 int rows,
199 int dim,
200 int head_dim,
201 float eps)
202{
203 if (!x || rows <= 0 || dim <= 0 || head_dim <= 0 ||
204 dim % head_dim != 0) {
205 return;
206 }
207 const int num_heads = dim / head_dim;
208 for (int row = 0; row < rows; ++row) {
209 float *row_ptr = x + (size_t)row * (size_t)dim;
210 for (int head = 0; head < num_heads; ++head) {
211 float *head_ptr = row_ptr + (size_t)head * (size_t)head_dim;
212 const float sum = recurrent_pytorch_fp32_square_sum(
213 head_ptr, head_dim);
214 const float inverse = 1.0f / sqrtf(sum + eps);
215#if defined(__AVX512F__)
216 const __m512 inv = _mm512_set1_ps(inverse);
217 int d = 0;
218 for (; d + 16 <= head_dim; d += 16) {
219 _mm512_storeu_ps(
220 head_ptr + d,
221 _mm512_mul_ps(_mm512_loadu_ps(head_ptr + d), inv));
222 }
223 for (; d < head_dim; ++d) {
224 head_ptr[d] = head_ptr[d] * inverse;
225 }
226#elif defined(__AVX2__)
227 const __m256 inv = _mm256_set1_ps(inverse);
228 int d = 0;
229 for (; d + 8 <= head_dim; d += 8) {
230 _mm256_storeu_ps(
231 head_ptr + d,
232 _mm256_mul_ps(_mm256_loadu_ps(head_ptr + d), inv));
233 }
234 for (; d < head_dim; ++d) {
235 head_ptr[d] = head_ptr[d] * inverse;
236 }
237#else
238 for (int d = 0; d < head_dim; ++d) {
239 head_ptr[d] = head_ptr[d] * inverse;
240 }
241#endif
242 }
243 }
244}
245
247 float *k,
248 int rows,
249 int q_dim,
250 int k_dim,
251 int head_dim,
252 float eps)
253{
254 recurrent_pytorch_fp32_l2_rows(q, rows, q_dim, head_dim, eps);
255 recurrent_pytorch_fp32_l2_rows(k, rows, k_dim, head_dim, eps);
256}
257
258static float recurrent_pytorch_bf16_square_sum(const float *x, int dim)
259{
260#if defined(__AVX2__)
261 if (dim >= 32) {
262 __m256 streams[4] = {
263 _mm256_setzero_ps(), _mm256_setzero_ps(),
264 _mm256_setzero_ps(), _mm256_setzero_ps()
265 };
266 int d = 0;
267 for (; d + 32 <= dim; d += 32) {
268 for (int stream = 0; stream < 4; ++stream) {
269 _Alignas(32) float lanes[8];
270 const __m256 values = _mm256_loadu_ps(x + d + stream * 8);
271 _mm256_store_ps(lanes, _mm256_mul_ps(values, values));
272 for (int lane = 0; lane < 8; ++lane) {
273 lanes[lane] =
274 bf16_to_float(float_to_bf16(lanes[lane]));
275 }
276 streams[stream] =
277 _mm256_add_ps(streams[stream], _mm256_load_ps(lanes));
278 }
279 }
280 __m256 reduced = _mm256_add_ps(streams[0], streams[1]);
281 reduced = _mm256_add_ps(reduced, streams[2]);
282 reduced = _mm256_add_ps(reduced, streams[3]);
283 _Alignas(32) float lanes[8];
284 _mm256_store_ps(lanes, reduced);
285 volatile float sum = 0.0f;
286 for (int lane = 0; lane < 8; ++lane) {
287 sum = sum + lanes[lane];
288 }
289 for (; d < dim; ++d) {
290 const float value = bf16_to_float(float_to_bf16(x[d]));
291 const float square =
292 bf16_to_float(float_to_bf16(value * value));
293 sum = sum + square;
294 }
295 return sum;
296 }
297#endif
298 volatile float sum = 0.0f;
299 for (int d = 0; d < dim; ++d) {
300 const float value = bf16_to_float(float_to_bf16(x[d]));
301 const float square = bf16_to_float(float_to_bf16(value * value));
302 sum = sum + square;
303 }
304 return sum;
305}
306
308 int rows,
309 int dim,
310 int expanded_heads,
311 int head_dim,
312 float eps)
313{
314 if (!x || rows <= 0 || dim <= 0 || head_dim <= 0 ||
315 dim % head_dim != 0) {
316 return;
317 }
318 const int num_heads = dim / head_dim;
319 if (expanded_heads <= 0 || expanded_heads % num_heads != 0) {
320 return;
321 }
322 const int heads_per_group = expanded_heads / num_heads;
323 const int total_heads = rows * expanded_heads;
324 const int vector_limit = (total_heads / 32) * 32;
325 for (int row = 0; row < rows; ++row) {
326 float *row_ptr = x + (size_t)row * (size_t)dim;
327 for (int head = 0; head < num_heads; ++head) {
328 float *head_ptr =
329 row_ptr + (size_t)head * (size_t)head_dim;
331 head_ptr, head_dim);
332 sum = bf16_to_float(float_to_bf16(sum));
333 const float denominator =
334 bf16_to_float(float_to_bf16(sum + eps));
335 /*
336 * Qwen3Next repeat_interleave() expands compact Q/K groups to
337 * value heads before l2norm. TensorIterator's rsqrt tail is
338 * therefore determined by the expanded tensor, not this compact
339 * storage. Select the arithmetic used by the first repeated
340 * lane; all lanes are vector lanes for production prefill.
341 */
342 const int flat_head =
343 (row * expanded_heads) + head * heads_per_group;
344 float inverse;
345 if (flat_head < vector_limit) {
346 /* ATen Vec<BF16> evaluates 32 scalar outputs per vector
347 * iteration using FP32 rsqrt followed by BF16 storage. */
348 inverse = bf16_to_float(
349 float_to_bf16(1.0f / sqrtf(denominator)));
350 } else {
351 /* TensorIterator's BF16 scalar tail materializes sqrt to
352 * BF16 before applying the BF16 reciprocal. */
353 const float root =
354 bf16_to_float(float_to_bf16(sqrtf(denominator)));
355 inverse =
356 bf16_to_float(float_to_bf16(1.0f / root));
357 }
358 for (int col = 0; col < head_dim; ++col) {
359 const float value =
360 bf16_to_float(float_to_bf16(head_ptr[col]));
361 head_ptr[col] = bf16_to_float(
362 float_to_bf16(value * inverse));
363 }
364 }
365 }
366}
367
369 float *k,
370 int rows,
371 int q_dim,
372 int k_dim,
373 int expanded_heads,
374 int head_dim,
375 float eps)
376{
378 q, rows, q_dim, expanded_heads, head_dim, eps);
380 k, rows, k_dim, expanded_heads, head_dim, eps);
381}
382
383void recurrent_qk_l2_norm_backward(const float *d_q_out,
384 const float *d_k_out,
385 const float *q,
386 const float *k,
387 float *d_q,
388 float *d_k,
389 int rows,
390 int q_dim,
391 int k_dim,
392 int head_dim,
393 float eps) {
394 recurrent_l2_norm_rows_backward_one(d_q_out, q, d_q, rows, q_dim, head_dim, eps);
395 recurrent_l2_norm_rows_backward_one(d_k_out, k, d_k, rows, k_dim, head_dim, eps);
396}
static uint16_t float_to_bf16(float f)
Definition bf16_utils.h:90
static float bf16_to_float(uint16_t v)
Definition bf16_utils.h:38
static void recurrent_l2_norm_rows_backward_one(const float *d_out, const float *x, float *d_x, int rows, int dim, int head_dim, float eps)
void recurrent_qk_l2_norm_pytorch_fp32_output(float *q, float *k, int rows, int q_dim, int k_dim, int head_dim, float eps)
void recurrent_qk_l2_norm_pytorch_bf16_storage(float *q, float *k, int rows, int q_dim, int k_dim, int expanded_heads, int head_dim, float eps)
void recurrent_qk_l2_norm_forward(float *q, float *k, int rows, int q_dim, int k_dim, int head_dim, float eps)
static void recurrent_pytorch_bf16_l2_rows(float *x, int rows, int dim, int expanded_heads, int head_dim, float eps)
static float recurrent_pytorch_fp32_square_sum(const float *x, int dim)
static void recurrent_l2_norm_rows_forward_one(float *x, int rows, int dim, int head_dim, float eps)
void recurrent_qk_l2_norm_backward(const float *d_q_out, const float *d_k_out, const float *q, const float *k, float *d_q, float *d_k, int rows, int q_dim, int k_dim, int head_dim, float eps)
static void recurrent_pytorch_fp32_l2_rows(float *x, int rows, int dim, int head_dim, float eps)
static int recurrent_ceil_log2(int value)
static float recurrent_pytorch_bf16_square_sum(const float *x, int dim)
int32_t int32_t int32_t int32_t int32_t mask
Definition tokenizer.h:234