← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
rmsnorm_kernels.c
Go to the documentation of this file.
1/**
2 * @file rmsnorm_kernels.c
3 * @brief RMSNorm forward/backward kernels with SIMD (SSE/AVX/AVX512)
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 *
14 * RMSNorm: y[i] = gamma[i] * x[i] / sqrt(mean(x^2) + eps)
15 */
16
17#include "bf16_utils.h"
18#include "ckernel_engine.h"
19#include <math.h>
20#include <stddef.h>
21#include <stdlib.h>
22
23#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
24#include <immintrin.h>
25#endif
26
27#if defined(__AVX2__)
28static inline __m256 rmsnorm_square_avx2_no_contract(__m256 values)
29{
30#if defined(__GNUC__) || defined(__clang__)
31 __m256 squared;
32 __asm__ volatile ("vmulps %1, %1, %0" : "=x"(squared) : "x"(values));
33 return squared;
34#else
35 /* Preserve the materialized pow(2) boundary on compilers where inline
36 * assembly is unavailable. */
37 _Alignas(32) volatile float materialized[8];
38 _mm256_store_ps((float *)materialized, _mm256_mul_ps(values, values));
39 return _mm256_load_ps((const float *)materialized);
40#endif
41}
42
43static inline __m256 rmsnorm_add_avx2_ordered(__m256 left, __m256 right)
44{
45#if defined(__GNUC__) || defined(__clang__)
46 __m256 sum;
47 __asm__ volatile ("vaddps %2, %1, %0" : "=x"(sum) : "x"(left), "x"(right));
48 return sum;
49#else
50 _Alignas(32) volatile float materialized[8];
51 _mm256_store_ps((float *)materialized, _mm256_add_ps(left, right));
52 return _mm256_load_ps((const float *)materialized);
53#endif
54}
55
56static inline __m256 rmsnorm_load_bf16_values_avx2(const float *values)
57{
58 _Alignas(32) float rounded[8];
59 for (int lane = 0; lane < 8; ++lane) {
60 rounded[lane] = bf16_to_float(float_to_bf16(values[lane]));
61 }
62 return _mm256_load_ps(rounded);
63}
64#endif
65
66#if defined(__i386__) || defined(__x86_64__)
67static inline float rmsnorm_div_f32_ordered(float numerator, float denominator)
68{
69#if defined(__GNUC__) || defined(__clang__)
70 float quotient;
71 __asm__ volatile ("vdivss %2, %1, %0"
72 : "=x"(quotient)
73 : "x"(numerator), "x"(denominator));
74 return quotient;
75#else
76 volatile float ordered_numerator = numerator;
77 volatile float ordered_denominator = denominator;
78 return ordered_numerator / ordered_denominator;
79#endif
80}
81#endif
82
83/* AVX1 horizontal sum helper (no _mm256_reduce_add_ps in AVX1) */
84#if defined(__AVX__) && !defined(__AVX512F__)
85static inline float hsum256_ps_rmsnorm(__m256 v) {
86 // Sum upper and lower 128-bit lanes
87 __m128 hi = _mm256_extractf128_ps(v, 1);
88 __m128 lo = _mm256_castps256_ps128(v);
89 __m128 sum128 = _mm_add_ps(lo, hi);
90 // Horizontal add within 128-bit lane
91 sum128 = _mm_hadd_ps(sum128, sum128);
92 sum128 = _mm_hadd_ps(sum128, sum128);
93 return _mm_cvtss_f32(sum128);
94}
95#endif
96static void rmsnorm_forward_strict_scalar(const float *input,
97 const float *gamma,
98 float *output,
99 float *rstd_cache,
100 int tokens,
101 int d_model,
102 int input_stride,
103 int output_stride,
104 float eps)
105{
106 const float inv_d = 1.0f / (float)d_model;
107 for (int t = 0; t < tokens; ++t) {
108 const float *x = input + (size_t)t * (size_t)input_stride;
109 float *y = output + (size_t)t * (size_t)output_stride;
110
111 float sum_sq = 0.0f;
112 for (int d = 0; d < d_model; ++d) {
113 const float v = x[d];
114 sum_sq += v * v;
115 }
116 const float mean_sq = sum_sq * inv_d;
117 const float rstd = 1.0f / sqrtf(mean_sq + eps);
118 if (rstd_cache) {
119 rstd_cache[t] = rstd;
120 }
121
122 for (int d = 0; d < d_model; ++d) {
123 const float x_hat = x[d] * rstd;
124 y[d] = x_hat * gamma[d];
125 }
126 for (int d = d_model; d < output_stride; ++d) {
127 y[d] = 0.0f;
128 }
129 }
130}
131
132#if defined(__clang__)
133__attribute__((optnone, noinline))
134#elif defined(__GNUC__)
135__attribute__((optimize("O1,no-tree-vectorize,no-tree-slp-vectorize"), noinline))
136#endif
137void rmsnorm_forward_fp64_sum(const float *input,
138 const float *gamma,
139 float *output,
140 float *rstd_cache,
141 int tokens,
142 int d_model,
143 int aligned_embed_dim,
144 float eps)
145{
146 for (int t = 0; t < tokens; ++t) {
147 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
148 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
149 /* This provider's contract requires an ascending scalar reduction.
150 * Keep the accumulator volatile so whole-program optimization cannot
151 * reassociate the sum or replace it with SIMD partial reductions. */
152 volatile double sum_sq = 0.0;
153 for (int d = 0; d < d_model; ++d) {
154 const float square = x[d] * x[d];
155 sum_sq = sum_sq + (double)square;
156 }
157 const float mean_sq = (float)(sum_sq / (double)d_model);
158 const float rstd = 1.0f / sqrtf(mean_sq + eps);
159 if (rstd_cache) {
160 rstd_cache[t] = rstd;
161 }
162 for (int d = 0; d < d_model; ++d) {
163 const float normalized = x[d] * rstd;
164 y[d] = normalized * gamma[d];
165 }
166 for (int d = d_model; d < aligned_embed_dim; ++d) {
167 y[d] = 0.0f;
168 }
169 }
170}
171
172static inline float rmsnorm_llama_production_rstd(float mean_eps)
173{
174 /*
175 * ggml's production CPU RMSNorm emits scalar sqrt followed by scalar
176 * division. With -fno-math-errno ICX otherwise strength-reduces the C
177 * expression to vrsqrt14ss plus one Newton step. That estimate differs by
178 * one ULP for some rows and the error is amplified by quantized
179 * projections in deep recurrent models.
180 */
181#if defined(CK_TARGET_X86)
182 const __m128 value = _mm_set_ss(mean_eps);
183 const __m128 root = _mm_sqrt_ss(value);
184 return _mm_cvtss_f32(_mm_div_ss(_mm_set_ss(1.0f), root));
185#else
186 const volatile float root = sqrtf(mean_eps);
187 return 1.0f / root;
188#endif
189}
190
191#if defined(__clang__)
192__attribute__((optnone, noinline))
193#elif defined(__GNUC__)
194__attribute__((optimize("O1,no-tree-vectorize,no-tree-slp-vectorize"), noinline))
195#endif
196void rmsnorm_forward_llama_production(const float *input,
197 const float *gamma,
198 float *output,
199 float *rstd_cache,
200 int tokens,
201 int d_model,
202 int aligned_embed_dim,
203 float eps)
204{
205 for (int t = 0; t < tokens; ++t) {
206 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
207 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
208 volatile double sum_sq = 0.0;
209 for (int d = 0; d < d_model; ++d) {
210 const float square = x[d] * x[d];
211 sum_sq = sum_sq + (double)square;
212 }
213 const float mean_sq = (float)(sum_sq / (double)d_model);
214 const float rstd = rmsnorm_llama_production_rstd(mean_sq + eps);
215 if (rstd_cache) {
216 rstd_cache[t] = rstd;
217 }
218 for (int d = 0; d < d_model; ++d) {
219 /*
220 * Keep the RMSNorm + scale expression fused at the source level.
221 * llama.cpp's CPU graph fuses GGML_OP_RMS_NORM followed by
222 * GGML_OP_MUL and evaluates this left-associative expression in
223 * one kernel. Materializing the normalized value as a named
224 * float introduces a store/load rounding boundary under ICX and
225 * differs by one ULP for otherwise identical inputs.
226 */
227 y[d] = x[d] * rstd * gamma[d];
228 }
229 for (int d = d_model; d < aligned_embed_dim; ++d) {
230 y[d] = 0.0f;
231 }
232 }
233}
234
235/*
236 * Backend-matched PyTorch BF16 RMSNorm contract.
237 *
238 * This is intentionally separate from the generic FP32 and strict/FP64
239 * RMSNorm providers. FP64 accumulation lowers mathematical error, but it does
240 * not reproduce PyTorch's BF16 CPU boundary values. The installed PyTorch 2.8
241 * binary dispatches mean(sum(pow(x, 2))) through ATen's AVX2 float reduction:
242 *
243 * BF16 load -> FP32 materialized square -> four-stream/four-level AVX2
244 * cascade -> ordered lane fold -> FP32 mean/div/sqrt -> BF16 normalized
245 * value -> BF16 gamma multiply -> BF16 output.
246 *
247 * ICX may otherwise contract the square with accumulation, tree-reduce the
248 * scalar lane fold, or replace scalar division. The ordered intrinsic helpers
249 * above prevent those transformations. This implementation follows PyTorch
250 * aten/src/ATen/native/cpu/SumKernel.cpp (cascade_sum) at commit
251 * a1cb3cc05d46d198467bebbb6e8fba50a325d4e7. oneDNN is not the RMSNorm
252 * oracle; it is used by adjacent BF16 linear projections.
253 */
254static void rmsnorm_forward_pytorch_bf16_storage_impl(const float *input,
255 const float *gamma,
256 float *output,
257 float *rstd_cache,
258 int tokens,
259 int d_model,
260 int input_stride,
261 int output_stride,
262 float eps,
263 int qwen3next_weight_order)
264{
265 for (int t = 0; t < tokens; ++t) {
266 const float *x = input + (size_t)t * (size_t)input_stride;
267 float *y = output + (size_t)t * (size_t)output_stride;
268 float sum_sq = 0.0f;
269
270#if defined(__AVX2__)
271 /* Match the installed ATen build's AVX2 floating-point cascade_sum
272 * contract. It treats the
273 * contiguous row as four interleaved vector streams, accumulates
274 * 16-item blocks through four hierarchy levels, then folds the four
275 * streams and vector lanes left-to-right. PyTorch materializes pow(2)
276 * before mean(), so keep the multiply separate from accumulation.
277 * The host supports AVX-512, but this PyTorch build has AVX2 reduction
278 * providers only; provider ISA is part of the numerical contract. */
279 __m256 level[4][4];
280 for (int hierarchy = 0; hierarchy < 4; ++hierarchy) {
281 for (int stream = 0; stream < 4; ++stream) {
282 level[hierarchy][stream] = _mm256_setzero_ps();
283 }
284 }
285 int d = 0;
286 const int vector_count = d_model / 8;
287 const int cascade_items = vector_count / 4;
288 int level_power = 4;
289 if (cascade_items > 1) {
290 int ceil_log2 = 0;
291 unsigned int value = (unsigned int)(cascade_items - 1);
292 while (value != 0) {
293 value >>= 1;
294 ++ceil_log2;
295 }
296 const int candidate = ceil_log2 / 4;
297 if (candidate > level_power) level_power = candidate;
298 }
299 const int level_step = 1 << level_power;
300 const int level_mask = level_step - 1;
301 int item = 0;
302 for (; item + level_step <= cascade_items;) {
303 for (int block = 0; block < level_step; ++block, ++item) {
304 for (int stream = 0; stream < 4; ++stream) {
305 const int offset = (item * 4 + stream) * 8;
306 const __m256 values = rmsnorm_load_bf16_values_avx2(x + offset);
307 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
308 level[0][stream] = rmsnorm_add_avx2_ordered(
309 level[0][stream], squared
310 );
311 }
312 }
313 for (int hierarchy = 1; hierarchy < 4; ++hierarchy) {
314 for (int stream = 0; stream < 4; ++stream) {
315 level[hierarchy][stream] = rmsnorm_add_avx2_ordered(
316 level[hierarchy][stream], level[hierarchy - 1][stream]
317 );
318 level[hierarchy - 1][stream] = _mm256_setzero_ps();
319 }
320 const int mask = level_mask << (hierarchy * level_power);
321 if ((item & mask) != 0) break;
322 }
323 }
324 for (; item < cascade_items; ++item) {
325 for (int stream = 0; stream < 4; ++stream) {
326 const int offset = (item * 4 + stream) * 8;
327 const __m256 values = rmsnorm_load_bf16_values_avx2(x + offset);
328 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
329 level[0][stream] = rmsnorm_add_avx2_ordered(
330 level[0][stream], squared
331 );
332 }
333 }
334 for (int hierarchy = 1; hierarchy < 4; ++hierarchy) {
335 for (int stream = 0; stream < 4; ++stream) {
336 level[0][stream] = rmsnorm_add_avx2_ordered(
337 level[0][stream], level[hierarchy][stream]
338 );
339 }
340 }
341 __m256 reduced = level[0][0];
342 for (int stream = 1; stream < 4; ++stream) {
343 reduced = rmsnorm_add_avx2_ordered(reduced, level[0][stream]);
344 }
345 d = cascade_items * 4 * 8;
346 for (; d + 8 <= d_model; d += 8) {
347 const __m256 values = rmsnorm_load_bf16_values_avx2(x + d);
348 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
349 reduced = rmsnorm_add_avx2_ordered(reduced, squared);
350 }
351 _Alignas(32) float lanes[8];
352 _mm256_store_ps(lanes, reduced);
353 volatile float ordered_sum = 0.0f;
354 for (int lane = 0; lane < 8; ++lane) {
355 ordered_sum = ordered_sum + lanes[lane];
356 }
357 sum_sq = ordered_sum;
358 for (; d < d_model; ++d) {
359 const float value = bf16_to_float(float_to_bf16(x[d]));
360 sum_sq += value * value;
361 }
362#else
363 for (int d = 0; d < d_model; ++d) {
364 const float value = bf16_to_float(float_to_bf16(x[d]));
365 sum_sq += value * value;
366 }
367#endif
368
369#if defined(__i386__) || defined(__x86_64__)
370 const float variance = rmsnorm_div_f32_ordered(sum_sq, (float)d_model);
371 const float rstd = rmsnorm_div_f32_ordered(1.0f, sqrtf(variance + eps));
372#else
373 const float variance = sum_sq / (float)d_model;
374 const float rstd = 1.0f / sqrtf(variance + eps);
375#endif
376 if (rstd_cache) rstd_cache[t] = rstd;
377 for (int d = 0; d < d_model; ++d) {
378 const float value = bf16_to_float(float_to_bf16(x[d]));
379 if (qwen3next_weight_order) {
380 /* Qwen3Next: (FP32 normalized * FP32 weight).to(BF16). */
382 (value * rstd) * gamma[d]));
383 } else {
384 const float weight =
385 bf16_to_float(float_to_bf16(gamma[d]));
386 const float normalized =
387 bf16_to_float(float_to_bf16(value * rstd));
388 y[d] = bf16_to_float(float_to_bf16(normalized * weight));
389 }
390 }
391 for (int d = d_model; d < output_stride; ++d) y[d] = 0.0f;
392 }
393}
394
396 const float *gamma,
397 float *output,
398 float *rstd_cache,
399 int tokens,
400 int d_model,
401 int aligned_embed_dim,
402 float eps)
403{
405 input, gamma, output, rstd_cache, tokens, d_model,
406 aligned_embed_dim, aligned_embed_dim, eps, 0);
407}
408
410 const float *gamma,
411 float *output,
412 float *rstd_cache,
413 int tokens,
414 int d_model,
415 int input_stride,
416 int output_stride,
417 float eps)
418{
420 input, gamma, output, rstd_cache, tokens, d_model,
421 input_stride, output_stride, eps, 0);
422}
423
425 const float *input,
426 const float *gamma,
427 float *output,
428 float *rstd_cache,
429 int tokens,
430 int d_model,
431 int aligned_embed_dim,
432 float eps)
433{
435 input, gamma, output, rstd_cache, tokens, d_model,
436 aligned_embed_dim, aligned_embed_dim, eps, 1);
437}
438
439static void rmsnorm_backward_strict_scalar(const float *d_output,
440 const float *input,
441 const float *gamma,
442 const float *rstd_cache,
443 float *d_input,
444 float *d_gamma,
445 int tokens,
446 int d_model,
447 int aligned_embed_dim)
448{
449 const float inv_d = 1.0f / (float)d_model;
450 for (int d = 0; d < d_model; ++d) {
451 d_gamma[d] = 0.0f;
452 }
453
454 for (int t = 0; t < tokens; ++t) {
455 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
456 const float *dY = d_output + (size_t)t * (size_t)aligned_embed_dim;
457 float *dX = d_input + (size_t)t * (size_t)aligned_embed_dim;
458 const float rstd = rstd_cache[t];
459
460 float sum_dY_g_xhat = 0.0f;
461 for (int d = 0; d < d_model; ++d) {
462 const float x_hat = x[d] * rstd;
463 const float grad_x_hat = dY[d] * gamma[d];
464 sum_dY_g_xhat += x_hat * grad_x_hat;
465 }
466
467 for (int d = 0; d < d_model; ++d) {
468 const float x_hat = x[d] * rstd;
469 const float grad_x_hat = dY[d] * gamma[d];
470 dX[d] = (grad_x_hat - (x_hat * inv_d) * sum_dY_g_xhat) * rstd;
471 d_gamma[d] += dY[d] * x_hat;
472 }
473 for (int d = d_model; d < aligned_embed_dim; ++d) {
474 dX[d] = 0.0f;
475 }
476 }
477}
478
479
480/**
481 * RMSNorm forward pass
482 * @test test_rmsnorm.py::TestRMSNormForward::test_fp32_tokens
483 * @test test_rmsnorm.py::TestRMSNormForward::test_fp32_single
484 * @test test_rmsnorm.py::TestRMSNormForward::test_perf_rolled
485 * @test test_layernorm.py::TestLayerNormForward::test_rmsnorm_compat
486 * @test test_parity.py::test_rmsnorm_parity
487 *
488 * RMSNorm: y[i] = gamma[i] * x[i] / sqrt(mean(x^2) + eps)
489 *
490 * After changes: make test && make llamacpp-parity-full
491 */
492void rmsnorm_forward_strided_f32(const float *input,
493 const float *gamma,
494 float *output,
495 float *rstd_cache,
496 int tokens,
497 int d_model,
498 int input_stride,
499 int output_stride,
500 float eps)
501{
502 int T = tokens;
503 int D = d_model;
504
505 const char *exact_env = getenv("CK_RMSNORM_EXACT");
506 if (ck_strict_parity_enabled() || (exact_env && atoi(exact_env) != 0)) {
508 input, gamma, output, rstd_cache, T, D, input_stride, output_stride, eps
509 );
510 return;
511 }
512
513 for (int t = 0; t < T; ++t) {
514 const float *x = input + (size_t)t * (size_t)input_stride;
515 float *y = output + (size_t)t * (size_t)output_stride;
516
517#if defined(__AVX512F__)
518 // AVX-512: Process 16 floats at a time
519 __m512 sum_sq_vec = _mm512_setzero_ps();
520 int d = 0;
521
522 // Vectorized sum of squares
523 for (; d + 16 <= D; d += 16) {
524 __m512 xv = _mm512_loadu_ps(&x[d]);
525 sum_sq_vec = _mm512_fmadd_ps(xv, xv, sum_sq_vec);
526 }
527 float sum_sq = _mm512_reduce_add_ps(sum_sq_vec);
528
529 // Handle remaining elements
530 for (; d < D; ++d) {
531 sum_sq += x[d] * x[d];
532 }
533
534 float mean_sq = sum_sq / (float)D;
535 float rstd = 1.0f / sqrtf(mean_sq + eps);
536 if (rstd_cache) {
537 rstd_cache[t] = rstd;
538 }
539
540 // Apply normalization and scale (vectorized)
541 __m512 rstd_vec = _mm512_set1_ps(rstd);
542 d = 0;
543 for (; d + 16 <= D; d += 16) {
544 __m512 xv = _mm512_loadu_ps(&x[d]);
545 __m512 gv = _mm512_loadu_ps(&gamma[d]);
546 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
547 __m512 yv = _mm512_mul_ps(x_hat, gv);
548 _mm512_storeu_ps(&y[d], yv);
549 }
550 // Handle remaining elements
551 for (; d < D; ++d) {
552 y[d] = x[d] * rstd * gamma[d];
553 }
554
555#elif defined(__AVX__)
556 // AVX: Process 8 floats at a time
557 __m256 sum_sq_vec = _mm256_setzero_ps();
558 int d = 0;
559
560 // Vectorized sum of squares (no FMA in AVX1, use mul + add)
561 for (; d + 8 <= D; d += 8) {
562 __m256 xv = _mm256_loadu_ps(&x[d]);
563 __m256 xv_sq = _mm256_mul_ps(xv, xv);
564 sum_sq_vec = _mm256_add_ps(sum_sq_vec, xv_sq);
565 }
566 float sum_sq = hsum256_ps_rmsnorm(sum_sq_vec);
567
568 // Handle remaining elements
569 for (; d < D; ++d) {
570 sum_sq += x[d] * x[d];
571 }
572
573 float mean_sq = sum_sq / (float)D;
574 float rstd = 1.0f / sqrtf(mean_sq + eps);
575 if (rstd_cache) {
576 rstd_cache[t] = rstd;
577 }
578
579 // Apply normalization and scale (vectorized)
580 __m256 rstd_vec = _mm256_set1_ps(rstd);
581 d = 0;
582 for (; d + 8 <= D; d += 8) {
583 __m256 xv = _mm256_loadu_ps(&x[d]);
584 __m256 gv = _mm256_loadu_ps(&gamma[d]);
585 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
586 __m256 yv = _mm256_mul_ps(x_hat, gv);
587 _mm256_storeu_ps(&y[d], yv);
588 }
589 // Handle remaining elements
590 for (; d < D; ++d) {
591 y[d] = x[d] * rstd * gamma[d];
592 }
593
594#else
595 // Scalar fallback
596 float sum_sq = 0.0f;
597 for (int d = 0; d < D; ++d) {
598 float v = x[d];
599 sum_sq += v * v;
600 }
601 float mean_sq = sum_sq / (float)D;
602 float rstd = 1.0f / sqrtf(mean_sq + eps);
603 if (rstd_cache) {
604 rstd_cache[t] = rstd;
605 }
606
607 // Apply normalization and scale
608 for (int d = 0; d < D; ++d) {
609 float x_hat = x[d] * rstd;
610 y[d] = x_hat * gamma[d];
611 }
612#endif
613
614 // Zero padding (if any)
615 for (int d = D; d < output_stride; ++d) {
616 y[d] = 0.0f;
617 }
618 }
619}
620
621void rmsnorm_forward(const float *input,
622 const float *gamma,
623 float *output,
624 float *rstd_cache,
625 int tokens,
626 int d_model,
627 int aligned_embed_dim,
628 float eps)
629{
631 input,
632 gamma,
633 output,
634 rstd_cache,
635 tokens,
636 d_model,
637 aligned_embed_dim,
638 aligned_embed_dim,
639 eps
640 );
641}
642
643void rmsnorm_forward_kv_lora(const float *input,
644 const float *gamma,
645 float *output,
646 float *rstd_cache,
647 int tokens,
648 int d_model,
649 int aligned_embed_dim,
650 float eps)
651{
652 rmsnorm_forward(input, gamma, output, rstd_cache, tokens, d_model, aligned_embed_dim, eps);
653}
654
655void rmsnorm_forward_no_weight(const float *input,
656 float *output,
657 float *rstd_cache,
658 int tokens,
659 int d_model,
660 int aligned_embed_dim,
661 float eps)
662{
663 if (!input || !output || tokens <= 0 || d_model <= 0 || aligned_embed_dim <= 0) {
664 return;
665 }
666 const float inv_d = 1.0f / (float)d_model;
667 for (int t = 0; t < tokens; ++t) {
668 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
669 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
670 double sum_sq = 0.0;
671 for (int d = 0; d < d_model; ++d) {
672 sum_sq += (double)x[d] * (double)x[d];
673 }
674 const float rstd = 1.0f / sqrtf((float)(sum_sq * (double)inv_d) + eps);
675 if (rstd_cache) {
676 rstd_cache[t] = rstd;
677 }
678 for (int d = 0; d < d_model; ++d) {
679 y[d] = x[d] * rstd;
680 }
681 for (int d = d_model; d < aligned_embed_dim; ++d) {
682 y[d] = 0.0f;
683 }
684 }
685}
686
687void gemma4_v_norm_forward(const float *input,
688 float *output,
689 float *rstd_cache,
690 int tokens,
691 int num_kv_heads,
692 int head_dim,
693 float eps)
694{
695 if (!input || !output || tokens <= 0 || num_kv_heads <= 0 || head_dim <= 0) {
696 return;
697 }
698 rmsnorm_forward_no_weight(input, output, rstd_cache,
699 tokens * num_kv_heads, head_dim, head_dim, eps);
700}
701
702
703/**
704 * RMSNorm backward pass
705 * @test test_rmsnorm.py::TestRMSNormBackward::test_backward_tokens
706 * @test test_rmsnorm.py::TestRMSNormBackward::test_backward_single
707 * @test test_parity.py::test_rmsnorm_backward_parity
708 *
709 * Computes dX and dGamma given dY, X, gamma, and cached rstd.
710 * dX_i = rstd * (dY_i * gamma_i - x_hat_i * m)
711 * dGamma_i = sum_t (dY_i * x_hat_i)
712 *
713 * After changes: make test && make llamacpp-parity-full
714 */
715void rmsnorm_backward(const float *d_output,
716 const float *input,
717 const float *gamma,
718 const float *rstd_cache,
719 float *d_input,
720 float *d_gamma,
721 int tokens,
722 int d_model,
723 int aligned_embed_dim)
724{
725 int T = tokens;
726 int D = d_model;
727 int aligned = aligned_embed_dim;
728
730 rmsnorm_backward_strict_scalar(d_output, input, gamma, rstd_cache, d_input, d_gamma, T, D, aligned);
731 return;
732 }
733
734 // Zero parameter gradients
735#if defined(__AVX512F__)
736 {
737 int d = 0;
738 for (; d + 16 <= D; d += 16) {
739 _mm512_storeu_ps(&d_gamma[d], _mm512_setzero_ps());
740 }
741 for (; d < D; ++d) {
742 d_gamma[d] = 0.0f;
743 }
744 }
745#elif defined(__AVX__)
746 {
747 int d = 0;
748 for (; d + 8 <= D; d += 8) {
749 _mm256_storeu_ps(&d_gamma[d], _mm256_setzero_ps());
750 }
751 for (; d < D; ++d) {
752 d_gamma[d] = 0.0f;
753 }
754 }
755#else
756 for (int d = 0; d < D; ++d) {
757 d_gamma[d] = 0.0f;
758 }
759#endif
760
761 for (int t = 0; t < T; ++t) {
762 const float *x = input + (size_t)t * aligned;
763 const float *dY = d_output + (size_t)t * aligned;
764 float *dX = d_input + (size_t)t * aligned;
765
766 float rstd = rstd_cache[t];
767
768#if defined(__AVX512F__)
769 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
770 __m512 rstd_vec = _mm512_set1_ps(rstd);
771 __m512 sum_vec = _mm512_setzero_ps();
772 int d = 0;
773
774 for (; d + 16 <= D; d += 16) {
775 __m512 xv = _mm512_loadu_ps(&x[d]);
776 __m512 dyv = _mm512_loadu_ps(&dY[d]);
777 __m512 gv = _mm512_loadu_ps(&gamma[d]);
778 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
779 // sum += dY * gamma * x_hat
780 __m512 prod = _mm512_mul_ps(dyv, gv);
781 sum_vec = _mm512_fmadd_ps(prod, x_hat, sum_vec);
782 }
783 float sum_dY_g_xhat = _mm512_reduce_add_ps(sum_vec);
784
785 // Handle remaining elements
786 for (; d < D; ++d) {
787 float x_hat = x[d] * rstd;
788 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
789 }
790 float m = sum_dY_g_xhat / (float)D;
791
792 // Compute dX and accumulate dGamma (vectorized)
793 __m512 m_vec = _mm512_set1_ps(m);
794 d = 0;
795 for (; d + 16 <= D; d += 16) {
796 __m512 xv = _mm512_loadu_ps(&x[d]);
797 __m512 dyv = _mm512_loadu_ps(&dY[d]);
798 __m512 gv = _mm512_loadu_ps(&gamma[d]);
799 __m512 dgv = _mm512_loadu_ps(&d_gamma[d]);
800
801 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
802
803 // dX = rstd * (dY * gamma - x_hat * m)
804 __m512 dy_g = _mm512_mul_ps(dyv, gv);
805 __m512 xhat_m = _mm512_mul_ps(x_hat, m_vec);
806 __m512 diff = _mm512_sub_ps(dy_g, xhat_m);
807 __m512 dxv = _mm512_mul_ps(rstd_vec, diff);
808 _mm512_storeu_ps(&dX[d], dxv);
809
810 // d_gamma += dY * x_hat
811 dgv = _mm512_fmadd_ps(dyv, x_hat, dgv);
812 _mm512_storeu_ps(&d_gamma[d], dgv);
813 }
814 // Handle remaining elements
815 for (; d < D; ++d) {
816 float x_hat = x[d] * rstd;
817 float dy = dY[d];
818 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
819 d_gamma[d] += dy * x_hat;
820 }
821
822#elif defined(__AVX__)
823 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
824 __m256 rstd_vec = _mm256_set1_ps(rstd);
825 __m256 sum_vec = _mm256_setzero_ps();
826 int d = 0;
827
828 for (; d + 8 <= D; d += 8) {
829 __m256 xv = _mm256_loadu_ps(&x[d]);
830 __m256 dyv = _mm256_loadu_ps(&dY[d]);
831 __m256 gv = _mm256_loadu_ps(&gamma[d]);
832 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
833 // sum += dY * gamma * x_hat (no FMA, use mul + mul + add)
834 __m256 prod = _mm256_mul_ps(dyv, gv);
835 __m256 prod2 = _mm256_mul_ps(prod, x_hat);
836 sum_vec = _mm256_add_ps(sum_vec, prod2);
837 }
838 float sum_dY_g_xhat = hsum256_ps_rmsnorm(sum_vec);
839
840 // Handle remaining elements
841 for (; d < D; ++d) {
842 float x_hat = x[d] * rstd;
843 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
844 }
845 float m = sum_dY_g_xhat / (float)D;
846
847 // Compute dX and accumulate dGamma (vectorized)
848 __m256 m_vec = _mm256_set1_ps(m);
849 d = 0;
850 for (; d + 8 <= D; d += 8) {
851 __m256 xv = _mm256_loadu_ps(&x[d]);
852 __m256 dyv = _mm256_loadu_ps(&dY[d]);
853 __m256 gv = _mm256_loadu_ps(&gamma[d]);
854 __m256 dgv = _mm256_loadu_ps(&d_gamma[d]);
855
856 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
857
858 // dX = rstd * (dY * gamma - x_hat * m)
859 __m256 dy_g = _mm256_mul_ps(dyv, gv);
860 __m256 xhat_m = _mm256_mul_ps(x_hat, m_vec);
861 __m256 diff = _mm256_sub_ps(dy_g, xhat_m);
862 __m256 dxv = _mm256_mul_ps(rstd_vec, diff);
863 _mm256_storeu_ps(&dX[d], dxv);
864
865 // d_gamma += dY * x_hat
866 __m256 dy_xhat = _mm256_mul_ps(dyv, x_hat);
867 dgv = _mm256_add_ps(dgv, dy_xhat);
868 _mm256_storeu_ps(&d_gamma[d], dgv);
869 }
870 // Handle remaining elements
871 for (; d < D; ++d) {
872 float x_hat = x[d] * rstd;
873 float dy = dY[d];
874 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
875 d_gamma[d] += dy * x_hat;
876 }
877
878#else
879 // Scalar fallback
880 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
881 float sum_dY_g_xhat = 0.0f;
882 for (int d = 0; d < D; ++d) {
883 float x_hat = x[d] * rstd;
884 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
885 }
886 float m = sum_dY_g_xhat / (float)D;
887
888 // Compute dX and accumulate dGamma
889 for (int d = 0; d < D; ++d) {
890 float x_hat = x[d] * rstd;
891 float dy = dY[d];
892 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
893 d_gamma[d] += dy * x_hat;
894 }
895#endif
896
897 // Zero padding gradients (if any)
898 for (int d = D; d < aligned; ++d) {
899 dX[d] = 0.0f;
900 }
901 }
902}
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
int ck_strict_parity_enabled(void)
void rmsnorm_forward_no_weight(const float *input, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward_fp64_sum(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward_qwen3next_pytorch_bf16_storage(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward_llama_production(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
static void rmsnorm_forward_strict_scalar(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int input_stride, int output_stride, float eps)
void rmsnorm_forward_pytorch_bf16_storage(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
static void rmsnorm_forward_pytorch_bf16_storage_impl(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int input_stride, int output_stride, float eps, int qwen3next_weight_order)
void rmsnorm_forward_strided_f32(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int input_stride, int output_stride, float eps)
void rmsnorm_forward_kv_lora(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void gemma4_v_norm_forward(const float *input, float *output, float *rstd_cache, int tokens, int num_kv_heads, int head_dim, float eps)
static void rmsnorm_backward_strict_scalar(const float *d_output, const float *input, const float *gamma, const float *rstd_cache, float *d_input, float *d_gamma, int tokens, int d_model, int aligned_embed_dim)
void rmsnorm_forward_strided_pytorch_bf16_storage(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int input_stride, int output_stride, float eps)
void rmsnorm_backward(const float *d_output, const float *input, const float *gamma, const float *rstd_cache, float *d_input, float *d_gamma, int tokens, int d_model, int aligned_embed_dim)
static float rmsnorm_llama_production_rstd(float mean_eps)
int32_t int32_t int32_t int32_t int32_t mask
Definition tokenizer.h:234
__attribute__((visibility("default"))) CKTokenizer *ck_tokenizer_create(CKTokenizerType type)
const char * left
Definition true_bpe.h:138
const char const char * right
Definition true_bpe.h:139