← 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(__ARM_NEON) || defined(__aarch64__)
28#include <arm_neon.h>
29#endif
30
31#if defined(__AVX2__)
32static inline __m256 rmsnorm_square_avx2_no_contract(__m256 values)
33{
34#if defined(__GNUC__) || defined(__clang__)
35 __m256 squared;
36 __asm__ volatile ("vmulps %1, %1, %0" : "=x"(squared) : "x"(values));
37 return squared;
38#else
39 /* Preserve the materialized pow(2) boundary on compilers where inline
40 * assembly is unavailable. */
41 _Alignas(32) volatile float materialized[8];
42 _mm256_store_ps((float *)materialized, _mm256_mul_ps(values, values));
43 return _mm256_load_ps((const float *)materialized);
44#endif
45}
46
47static inline __m256 rmsnorm_add_avx2_ordered(__m256 left, __m256 right)
48{
49#if defined(__GNUC__) || defined(__clang__)
50 __m256 sum;
51 __asm__ volatile ("vaddps %2, %1, %0" : "=x"(sum) : "x"(left), "x"(right));
52 return sum;
53#else
54 _Alignas(32) volatile float materialized[8];
55 _mm256_store_ps((float *)materialized, _mm256_add_ps(left, right));
56 return _mm256_load_ps((const float *)materialized);
57#endif
58}
59
60static inline __m256 rmsnorm_load_bf16_values_avx2(const float *values)
61{
62 _Alignas(32) float rounded[8];
63 for (int lane = 0; lane < 8; ++lane) {
64 rounded[lane] = bf16_to_float(float_to_bf16(values[lane]));
65 }
66 return _mm256_load_ps(rounded);
67}
68#endif
69
70#if defined(__i386__) || defined(__x86_64__)
71static inline float rmsnorm_div_f32_ordered(float numerator, float denominator)
72{
73#if defined(__GNUC__) || defined(__clang__)
74 float quotient;
75 __asm__ volatile ("vdivss %2, %1, %0"
76 : "=x"(quotient)
77 : "x"(numerator), "x"(denominator));
78 return quotient;
79#else
80 volatile float ordered_numerator = numerator;
81 volatile float ordered_denominator = denominator;
82 return ordered_numerator / ordered_denominator;
83#endif
84}
85#endif
86
87/* Portable NEON horizontal sum (no AArch64-only vaddvq_f32): folds the four
88 * lanes pairwise as (l0+l2) + (l1+l3). This is one specific reassociation of
89 * the scalar ascending sum; the numerical contract is tolerance-based, the
90 * same as the SSE/AVX branches above. */
91#if defined(__ARM_NEON) || defined(__aarch64__)
92static inline float hsum128_ps_rmsnorm(float32x4_t v) {
93 const float32x2_t lo = vget_low_f32(v);
94 const float32x2_t hi = vget_high_f32(v);
95 const float32x2_t sum2 = vadd_f32(lo, hi);
96 return vget_lane_f32(vpadd_f32(sum2, sum2), 0);
97}
98#endif
99
100/* AVX1 horizontal sum helper (no _mm256_reduce_add_ps in AVX1) */
101#if defined(__AVX__) && !defined(__AVX512F__)
102static inline float hsum256_ps_rmsnorm(__m256 v) {
103 // Sum upper and lower 128-bit lanes
104 __m128 hi = _mm256_extractf128_ps(v, 1);
105 __m128 lo = _mm256_castps256_ps128(v);
106 __m128 sum128 = _mm_add_ps(lo, hi);
107 // Horizontal add within 128-bit lane
108 sum128 = _mm_hadd_ps(sum128, sum128);
109 sum128 = _mm_hadd_ps(sum128, sum128);
110 return _mm_cvtss_f32(sum128);
111}
112#endif
113static void rmsnorm_forward_strict_scalar(const float *input,
114 const float *gamma,
115 float *output,
116 float *rstd_cache,
117 int tokens,
118 int d_model,
119 int input_stride,
120 int output_stride,
121 float eps)
122{
123 const float inv_d = 1.0f / (float)d_model;
124 for (int t = 0; t < tokens; ++t) {
125 const float *x = input + (size_t)t * (size_t)input_stride;
126 float *y = output + (size_t)t * (size_t)output_stride;
127
128 float sum_sq = 0.0f;
129 for (int d = 0; d < d_model; ++d) {
130 const float v = x[d];
131 sum_sq += v * v;
132 }
133 const float mean_sq = sum_sq * inv_d;
134 const float rstd = 1.0f / sqrtf(mean_sq + eps);
135 if (rstd_cache) {
136 rstd_cache[t] = rstd;
137 }
138
139 for (int d = 0; d < d_model; ++d) {
140 const float x_hat = x[d] * rstd;
141 y[d] = x_hat * gamma[d];
142 }
143 for (int d = d_model; d < output_stride; ++d) {
144 y[d] = 0.0f;
145 }
146 }
147}
148
149#if defined(__clang__)
150__attribute__((optnone, noinline))
151#elif defined(__GNUC__)
152__attribute__((optimize("O1,no-tree-vectorize,no-tree-slp-vectorize"), noinline))
153#endif
154void rmsnorm_forward_fp64_sum(const float *input,
155 const float *gamma,
156 float *output,
157 float *rstd_cache,
158 int tokens,
159 int d_model,
160 int aligned_embed_dim,
161 float eps)
162{
163 for (int t = 0; t < tokens; ++t) {
164 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
165 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
166 /* This provider's contract requires an ascending scalar reduction.
167 * Keep the accumulator volatile so whole-program optimization cannot
168 * reassociate the sum or replace it with SIMD partial reductions. */
169 volatile double sum_sq = 0.0;
170 for (int d = 0; d < d_model; ++d) {
171 const float square = x[d] * x[d];
172 sum_sq = sum_sq + (double)square;
173 }
174 const float mean_sq = (float)(sum_sq / (double)d_model);
175 const float rstd = 1.0f / sqrtf(mean_sq + eps);
176 if (rstd_cache) {
177 rstd_cache[t] = rstd;
178 }
179 for (int d = 0; d < d_model; ++d) {
180 const float normalized = x[d] * rstd;
181 y[d] = normalized * gamma[d];
182 }
183 for (int d = d_model; d < aligned_embed_dim; ++d) {
184 y[d] = 0.0f;
185 }
186 }
187}
188
189static inline float rmsnorm_llama_production_rstd(float mean_eps)
190{
191 /*
192 * ggml's production CPU RMSNorm emits scalar sqrt followed by scalar
193 * division. With -fno-math-errno ICX otherwise strength-reduces the C
194 * expression to vrsqrt14ss plus one Newton step. That estimate differs by
195 * one ULP for some rows and the error is amplified by quantized
196 * projections in deep recurrent models.
197 */
198#if defined(CK_TARGET_X86)
199 const __m128 value = _mm_set_ss(mean_eps);
200 const __m128 root = _mm_sqrt_ss(value);
201 return _mm_cvtss_f32(_mm_div_ss(_mm_set_ss(1.0f), root));
202#else
203 const volatile float root = sqrtf(mean_eps);
204 return 1.0f / root;
205#endif
206}
207
208#if defined(__clang__)
209__attribute__((optnone, noinline))
210#elif defined(__GNUC__)
211__attribute__((optimize("O1,no-tree-vectorize,no-tree-slp-vectorize"), noinline))
212#endif
213void rmsnorm_forward_llama_production(const float *input,
214 const float *gamma,
215 float *output,
216 float *rstd_cache,
217 int tokens,
218 int d_model,
219 int aligned_embed_dim,
220 float eps)
221{
222 for (int t = 0; t < tokens; ++t) {
223 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
224 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
225 volatile double sum_sq = 0.0;
226 for (int d = 0; d < d_model; ++d) {
227 const float square = x[d] * x[d];
228 sum_sq = sum_sq + (double)square;
229 }
230 const float mean_sq = (float)(sum_sq / (double)d_model);
231 const float rstd = rmsnorm_llama_production_rstd(mean_sq + eps);
232 if (rstd_cache) {
233 rstd_cache[t] = rstd;
234 }
235 for (int d = 0; d < d_model; ++d) {
236 /*
237 * Keep the RMSNorm + scale expression fused at the source level.
238 * llama.cpp's CPU graph fuses GGML_OP_RMS_NORM followed by
239 * GGML_OP_MUL and evaluates this left-associative expression in
240 * one kernel. Materializing the normalized value as a named
241 * float introduces a store/load rounding boundary under ICX and
242 * differs by one ULP for otherwise identical inputs.
243 */
244 y[d] = x[d] * rstd * gamma[d];
245 }
246 for (int d = d_model; d < aligned_embed_dim; ++d) {
247 y[d] = 0.0f;
248 }
249 }
250}
251
252/*
253 * Backend-matched PyTorch BF16 RMSNorm contract.
254 *
255 * This is intentionally separate from the generic FP32 and strict/FP64
256 * RMSNorm providers. FP64 accumulation lowers mathematical error, but it does
257 * not reproduce PyTorch's BF16 CPU boundary values. The installed PyTorch 2.8
258 * binary dispatches mean(sum(pow(x, 2))) through ATen's AVX2 float reduction:
259 *
260 * BF16 load -> FP32 materialized square -> four-stream/four-level AVX2
261 * cascade -> ordered lane fold -> FP32 mean/div/sqrt -> BF16 normalized
262 * value -> BF16 gamma multiply -> BF16 output.
263 *
264 * ICX may otherwise contract the square with accumulation, tree-reduce the
265 * scalar lane fold, or replace scalar division. The ordered intrinsic helpers
266 * above prevent those transformations. This implementation follows PyTorch
267 * aten/src/ATen/native/cpu/SumKernel.cpp (cascade_sum) at commit
268 * a1cb3cc05d46d198467bebbb6e8fba50a325d4e7. oneDNN is not the RMSNorm
269 * oracle; it is used by adjacent BF16 linear projections.
270 */
271static void rmsnorm_forward_pytorch_bf16_storage_impl(const float *input,
272 const float *gamma,
273 float *output,
274 float *rstd_cache,
275 int tokens,
276 int d_model,
277 int input_stride,
278 int output_stride,
279 float eps,
280 int weight_mode,
281 int use_pow_inverse)
282{
283 for (int t = 0; t < tokens; ++t) {
284 const float *x = input + (size_t)t * (size_t)input_stride;
285 float *y = output + (size_t)t * (size_t)output_stride;
286 float sum_sq = 0.0f;
287
288#if defined(__AVX2__)
289 /* Match the installed ATen build's AVX2 floating-point cascade_sum
290 * contract. It treats the
291 * contiguous row as four interleaved vector streams, accumulates
292 * 16-item blocks through four hierarchy levels, then folds the four
293 * streams and vector lanes left-to-right. PyTorch materializes pow(2)
294 * before mean(), so keep the multiply separate from accumulation.
295 * The host supports AVX-512, but this PyTorch build has AVX2 reduction
296 * providers only; provider ISA is part of the numerical contract. */
297 __m256 level[4][4];
298 for (int hierarchy = 0; hierarchy < 4; ++hierarchy) {
299 for (int stream = 0; stream < 4; ++stream) {
300 level[hierarchy][stream] = _mm256_setzero_ps();
301 }
302 }
303 int d = 0;
304 const int vector_count = d_model / 8;
305 const int cascade_items = vector_count / 4;
306 int level_power = 4;
307 if (cascade_items > 1) {
308 int ceil_log2 = 0;
309 unsigned int value = (unsigned int)(cascade_items - 1);
310 while (value != 0) {
311 value >>= 1;
312 ++ceil_log2;
313 }
314 const int candidate = ceil_log2 / 4;
315 if (candidate > level_power) level_power = candidate;
316 }
317 const int level_step = 1 << level_power;
318 const int level_mask = level_step - 1;
319 int item = 0;
320 for (; item + level_step <= cascade_items;) {
321 for (int block = 0; block < level_step; ++block, ++item) {
322 for (int stream = 0; stream < 4; ++stream) {
323 const int offset = (item * 4 + stream) * 8;
324 const __m256 values = rmsnorm_load_bf16_values_avx2(x + offset);
325 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
326 level[0][stream] = rmsnorm_add_avx2_ordered(
327 level[0][stream], squared
328 );
329 }
330 }
331 for (int hierarchy = 1; hierarchy < 4; ++hierarchy) {
332 for (int stream = 0; stream < 4; ++stream) {
333 level[hierarchy][stream] = rmsnorm_add_avx2_ordered(
334 level[hierarchy][stream], level[hierarchy - 1][stream]
335 );
336 level[hierarchy - 1][stream] = _mm256_setzero_ps();
337 }
338 const int mask = level_mask << (hierarchy * level_power);
339 if ((item & mask) != 0) break;
340 }
341 }
342 for (; item < cascade_items; ++item) {
343 for (int stream = 0; stream < 4; ++stream) {
344 const int offset = (item * 4 + stream) * 8;
345 const __m256 values = rmsnorm_load_bf16_values_avx2(x + offset);
346 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
347 level[0][stream] = rmsnorm_add_avx2_ordered(
348 level[0][stream], squared
349 );
350 }
351 }
352 for (int hierarchy = 1; hierarchy < 4; ++hierarchy) {
353 for (int stream = 0; stream < 4; ++stream) {
354 level[0][stream] = rmsnorm_add_avx2_ordered(
355 level[0][stream], level[hierarchy][stream]
356 );
357 }
358 }
359 __m256 reduced = level[0][0];
360 for (int stream = 1; stream < 4; ++stream) {
361 reduced = rmsnorm_add_avx2_ordered(reduced, level[0][stream]);
362 }
363 d = cascade_items * 4 * 8;
364 for (; d + 8 <= d_model; d += 8) {
365 const __m256 values = rmsnorm_load_bf16_values_avx2(x + d);
366 const __m256 squared = rmsnorm_square_avx2_no_contract(values);
367 reduced = rmsnorm_add_avx2_ordered(reduced, squared);
368 }
369 _Alignas(32) float lanes[8];
370 _mm256_store_ps(lanes, reduced);
371 volatile float ordered_sum = 0.0f;
372 for (int lane = 0; lane < 8; ++lane) {
373 ordered_sum = ordered_sum + lanes[lane];
374 }
375 sum_sq = ordered_sum;
376 for (; d < d_model; ++d) {
377 const float value = bf16_to_float(float_to_bf16(x[d]));
378 sum_sq += value * value;
379 }
380#else
381 for (int d = 0; d < d_model; ++d) {
382 const float value = bf16_to_float(float_to_bf16(x[d]));
383 sum_sq += value * value;
384 }
385#endif
386
387#if defined(__i386__) || defined(__x86_64__)
388 const float variance = rmsnorm_div_f32_ordered(sum_sq, (float)d_model);
389 const float rstd = use_pow_inverse
390 ? powf(variance + eps, -0.5f)
391 : rmsnorm_div_f32_ordered(1.0f, sqrtf(variance + eps));
392#else
393 const float variance = sum_sq / (float)d_model;
394 const float rstd = use_pow_inverse
395 ? powf(variance + eps, -0.5f)
396 : 1.0f / sqrtf(variance + eps);
397#endif
398 if (rstd_cache) rstd_cache[t] = rstd;
399 for (int d = 0; d < d_model; ++d) {
400 const float value = bf16_to_float(float_to_bf16(x[d]));
401 if (weight_mode == 3) {
402 /* Muse unweighted normalization rounds only when the FP32
403 * normalized value returns to the BF16 hidden-state dtype. */
404 y[d] = bf16_to_float(float_to_bf16(value * rstd));
405 } else if (weight_mode == 2) {
406 /* Keep the checkpoint weight zero-centered. Form 1 + weight
407 * in FP32 at execution instead of pre-folding into BF16. */
409 (value * rstd) * (1.0f + gamma[d])));
410 } else if (weight_mode == 1) {
411 /* Qwen3Next: (FP32 normalized * FP32 weight).to(BF16). */
413 (value * rstd) * gamma[d]));
414 } else {
415 const float weight =
416 bf16_to_float(float_to_bf16(gamma[d]));
417 const float normalized =
418 bf16_to_float(float_to_bf16(value * rstd));
419 y[d] = bf16_to_float(float_to_bf16(normalized * weight));
420 }
421 }
422 for (int d = d_model; d < output_stride; ++d) y[d] = 0.0f;
423 }
424}
425
427 const float *gamma,
428 float *output,
429 float *rstd_cache,
430 int tokens,
431 int d_model,
432 int aligned_embed_dim,
433 float eps)
434{
436 input, gamma, output, rstd_cache, tokens, d_model,
437 aligned_embed_dim, aligned_embed_dim, eps, 0, 0);
438}
439
441 const float *gamma,
442 float *output,
443 float *rstd_cache,
444 int tokens,
445 int d_model,
446 int input_stride,
447 int output_stride,
448 float eps)
449{
451 input, gamma, output, rstd_cache, tokens, d_model,
452 input_stride, output_stride, eps, 0, 0);
453}
454
456 const float *input,
457 const float *gamma,
458 float *output,
459 float *rstd_cache,
460 int tokens,
461 int d_model,
462 int aligned_embed_dim,
463 float eps)
464{
466 input, gamma, output, rstd_cache, tokens, d_model,
467 aligned_embed_dim, aligned_embed_dim, eps, 1, 0);
468}
469
471 const float *input,
472 const float *zero_centered_weight,
473 float *output,
474 float *rstd_cache,
475 int tokens,
476 int d_model,
477 int aligned_embed_dim,
478 float eps)
479{
481 input, zero_centered_weight, output, rstd_cache, tokens, d_model,
482 aligned_embed_dim, aligned_embed_dim, eps, 2, 0);
483}
484
486 const float *input,
487 const float *weight,
488 float *output,
489 float *rstd_cache,
490 int tokens,
491 int d_model,
492 int aligned_embed_dim,
493 float eps)
494{
496 input, weight, output, rstd_cache, tokens, d_model,
497 aligned_embed_dim, aligned_embed_dim, eps, 1, 0);
498}
499
501 const float *input,
502 float *output,
503 float *rstd_cache,
504 int tokens,
505 int d_model,
506 int aligned_embed_dim,
507 float eps)
508{
510 input, NULL, output, rstd_cache, tokens, d_model,
511 aligned_embed_dim, aligned_embed_dim, eps, 3, 0);
512}
513
514static void rmsnorm_backward_strict_scalar(const float *d_output,
515 const float *input,
516 const float *gamma,
517 const float *rstd_cache,
518 float *d_input,
519 float *d_gamma,
520 int tokens,
521 int d_model,
522 int aligned_embed_dim)
523{
524 const float inv_d = 1.0f / (float)d_model;
525 for (int d = 0; d < d_model; ++d) {
526 d_gamma[d] = 0.0f;
527 }
528
529 for (int t = 0; t < tokens; ++t) {
530 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
531 const float *dY = d_output + (size_t)t * (size_t)aligned_embed_dim;
532 float *dX = d_input + (size_t)t * (size_t)aligned_embed_dim;
533 const float rstd = rstd_cache[t];
534
535 float sum_dY_g_xhat = 0.0f;
536 for (int d = 0; d < d_model; ++d) {
537 const float x_hat = x[d] * rstd;
538 const float grad_x_hat = dY[d] * gamma[d];
539 sum_dY_g_xhat += x_hat * grad_x_hat;
540 }
541
542 for (int d = 0; d < d_model; ++d) {
543 const float x_hat = x[d] * rstd;
544 const float grad_x_hat = dY[d] * gamma[d];
545 dX[d] = (grad_x_hat - (x_hat * inv_d) * sum_dY_g_xhat) * rstd;
546 d_gamma[d] += dY[d] * x_hat;
547 }
548 for (int d = d_model; d < aligned_embed_dim; ++d) {
549 dX[d] = 0.0f;
550 }
551 }
552}
553
554
555/**
556 * RMSNorm forward pass
557 * @test test_rmsnorm.py::TestRMSNormForward::test_fp32_tokens
558 * @test test_rmsnorm.py::TestRMSNormForward::test_fp32_single
559 * @test test_rmsnorm.py::TestRMSNormForward::test_perf_rolled
560 * @test test_layernorm.py::TestLayerNormForward::test_rmsnorm_compat
561 * @test test_parity.py::test_rmsnorm_parity
562 *
563 * RMSNorm: y[i] = gamma[i] * x[i] / sqrt(mean(x^2) + eps)
564 *
565 * After changes: make test && make llamacpp-parity-full
566 */
567void rmsnorm_forward_strided_f32(const float *input,
568 const float *gamma,
569 float *output,
570 float *rstd_cache,
571 int tokens,
572 int d_model,
573 int input_stride,
574 int output_stride,
575 float eps)
576{
577 int T = tokens;
578 int D = d_model;
579
580 const char *exact_env = getenv("CK_RMSNORM_EXACT");
581 if (ck_strict_parity_enabled() || (exact_env && atoi(exact_env) != 0)) {
583 input, gamma, output, rstd_cache, T, D, input_stride, output_stride, eps
584 );
585 return;
586 }
587
588 for (int t = 0; t < T; ++t) {
589 const float *x = input + (size_t)t * (size_t)input_stride;
590 float *y = output + (size_t)t * (size_t)output_stride;
591
592#if defined(__AVX512F__)
593 // AVX-512: Process 16 floats at a time
594 __m512 sum_sq_vec = _mm512_setzero_ps();
595 int d = 0;
596
597 // Vectorized sum of squares
598 for (; d + 16 <= D; d += 16) {
599 __m512 xv = _mm512_loadu_ps(&x[d]);
600 sum_sq_vec = _mm512_fmadd_ps(xv, xv, sum_sq_vec);
601 }
602 float sum_sq = _mm512_reduce_add_ps(sum_sq_vec);
603
604 // Handle remaining elements
605 for (; d < D; ++d) {
606 sum_sq += x[d] * x[d];
607 }
608
609 float mean_sq = sum_sq / (float)D;
610 float rstd = 1.0f / sqrtf(mean_sq + eps);
611 if (rstd_cache) {
612 rstd_cache[t] = rstd;
613 }
614
615 // Apply normalization and scale (vectorized)
616 __m512 rstd_vec = _mm512_set1_ps(rstd);
617 d = 0;
618 for (; d + 16 <= D; d += 16) {
619 __m512 xv = _mm512_loadu_ps(&x[d]);
620 __m512 gv = _mm512_loadu_ps(&gamma[d]);
621 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
622 __m512 yv = _mm512_mul_ps(x_hat, gv);
623 _mm512_storeu_ps(&y[d], yv);
624 }
625 // Handle remaining elements
626 for (; d < D; ++d) {
627 y[d] = x[d] * rstd * gamma[d];
628 }
629
630#elif defined(__AVX__)
631 // AVX: Process 8 floats at a time
632 __m256 sum_sq_vec = _mm256_setzero_ps();
633 int d = 0;
634
635 // Vectorized sum of squares (no FMA in AVX1, use mul + add)
636 for (; d + 8 <= D; d += 8) {
637 __m256 xv = _mm256_loadu_ps(&x[d]);
638 __m256 xv_sq = _mm256_mul_ps(xv, xv);
639 sum_sq_vec = _mm256_add_ps(sum_sq_vec, xv_sq);
640 }
641 float sum_sq = hsum256_ps_rmsnorm(sum_sq_vec);
642
643 // Handle remaining elements
644 for (; d < D; ++d) {
645 sum_sq += x[d] * x[d];
646 }
647
648 float mean_sq = sum_sq / (float)D;
649 float rstd = 1.0f / sqrtf(mean_sq + eps);
650 if (rstd_cache) {
651 rstd_cache[t] = rstd;
652 }
653
654 // Apply normalization and scale (vectorized)
655 __m256 rstd_vec = _mm256_set1_ps(rstd);
656 d = 0;
657 for (; d + 8 <= D; d += 8) {
658 __m256 xv = _mm256_loadu_ps(&x[d]);
659 __m256 gv = _mm256_loadu_ps(&gamma[d]);
660 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
661 __m256 yv = _mm256_mul_ps(x_hat, gv);
662 _mm256_storeu_ps(&y[d], yv);
663 }
664 // Handle remaining elements
665 for (; d < D; ++d) {
666 y[d] = x[d] * rstd * gamma[d];
667 }
668
669#elif defined(__ARM_NEON) || defined(__aarch64__)
670 /* NEON: 4 floats at a time. The sum-of-squares is reassociated
671 * through four lanes (FMA accumulation, like the AVX-512 branch),
672 * so results differ from the ascending scalar order by FP32
673 * reassociation noise only; CK_RMSNORM_EXACT / strict parity mode
674 * above still selects the scalar reference. */
675 float32x4_t sum_sq_vec = vdupq_n_f32(0.0f);
676 int d = 0;
677
678 for (; d + 4 <= D; d += 4) {
679 const float32x4_t xv = vld1q_f32(&x[d]);
680 sum_sq_vec = vfmaq_f32(sum_sq_vec, xv, xv);
681 }
682 float sum_sq = hsum128_ps_rmsnorm(sum_sq_vec);
683
684 for (; d < D; ++d) {
685 sum_sq += x[d] * x[d];
686 }
687
688 float mean_sq = sum_sq / (float)D;
689 float rstd = 1.0f / sqrtf(mean_sq + eps);
690 if (rstd_cache) {
691 rstd_cache[t] = rstd;
692 }
693
694 const float32x4_t rstd_vec = vdupq_n_f32(rstd);
695 d = 0;
696 for (; d + 4 <= D; d += 4) {
697 const float32x4_t xv = vld1q_f32(&x[d]);
698 const float32x4_t gv = vld1q_f32(&gamma[d]);
699 /* Keep the two separate rounding boundaries of the scalar tail:
700 * x_hat = x * rstd, then y = x_hat * gamma (no FMA fusion). */
701 const float32x4_t x_hat = vmulq_f32(xv, rstd_vec);
702 vst1q_f32(&y[d], vmulq_f32(x_hat, gv));
703 }
704 for (; d < D; ++d) {
705 y[d] = x[d] * rstd * gamma[d];
706 }
707
708#else
709 // Scalar fallback
710 float sum_sq = 0.0f;
711 for (int d = 0; d < D; ++d) {
712 float v = x[d];
713 sum_sq += v * v;
714 }
715 float mean_sq = sum_sq / (float)D;
716 float rstd = 1.0f / sqrtf(mean_sq + eps);
717 if (rstd_cache) {
718 rstd_cache[t] = rstd;
719 }
720
721 // Apply normalization and scale
722 for (int d = 0; d < D; ++d) {
723 float x_hat = x[d] * rstd;
724 y[d] = x_hat * gamma[d];
725 }
726#endif
727
728 // Zero padding (if any)
729 for (int d = D; d < output_stride; ++d) {
730 y[d] = 0.0f;
731 }
732 }
733}
734
735void rmsnorm_forward(const float *input,
736 const float *gamma,
737 float *output,
738 float *rstd_cache,
739 int tokens,
740 int d_model,
741 int aligned_embed_dim,
742 float eps)
743{
745 input,
746 gamma,
747 output,
748 rstd_cache,
749 tokens,
750 d_model,
751 aligned_embed_dim,
752 aligned_embed_dim,
753 eps
754 );
755}
756
757void rmsnorm_forward_kv_lora(const float *input,
758 const float *gamma,
759 float *output,
760 float *rstd_cache,
761 int tokens,
762 int d_model,
763 int aligned_embed_dim,
764 float eps)
765{
766 rmsnorm_forward(input, gamma, output, rstd_cache, tokens, d_model, aligned_embed_dim, eps);
767}
768
769void rmsnorm_forward_no_weight(const float *input,
770 float *output,
771 float *rstd_cache,
772 int tokens,
773 int d_model,
774 int aligned_embed_dim,
775 float eps)
776{
777 if (!input || !output || tokens <= 0 || d_model <= 0 || aligned_embed_dim <= 0) {
778 return;
779 }
780 const float inv_d = 1.0f / (float)d_model;
781 for (int t = 0; t < tokens; ++t) {
782 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
783 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
784 double sum_sq = 0.0;
785 for (int d = 0; d < d_model; ++d) {
786 sum_sq += (double)x[d] * (double)x[d];
787 }
788 const float rstd = 1.0f / sqrtf((float)(sum_sq * (double)inv_d) + eps);
789 if (rstd_cache) {
790 rstd_cache[t] = rstd;
791 }
792 for (int d = 0; d < d_model; ++d) {
793 y[d] = x[d] * rstd;
794 }
795 for (int d = d_model; d < aligned_embed_dim; ++d) {
796 y[d] = 0.0f;
797 }
798 }
799}
800
801#if defined(__clang__)
802__attribute__((optnone, noinline))
803#elif defined(__GNUC__)
804__attribute__((optimize("O1,no-tree-vectorize,no-tree-slp-vectorize"), noinline))
805#endif
807 float *output,
808 float *rstd_cache,
809 int tokens,
810 int d_model,
811 int aligned_embed_dim,
812 float eps)
813{
814 if (!input || !output || tokens <= 0 || d_model <= 0 || aligned_embed_dim < d_model) {
815 return;
816 }
817 for (int t = 0; t < tokens; ++t) {
818 const float *x = input + (size_t)t * (size_t)aligned_embed_dim;
819 float *y = output + (size_t)t * (size_t)aligned_embed_dim;
820 volatile double sum_sq = 0.0;
821 for (int d = 0; d < d_model; ++d) {
822 const float square = x[d] * x[d];
823 sum_sq = sum_sq + (double)square;
824 }
825 const float mean_sq = (float)(sum_sq / (double)d_model);
826 const float rstd = rmsnorm_llama_production_rstd(mean_sq + eps);
827 if (rstd_cache) {
828 rstd_cache[t] = rstd;
829 }
830 for (int d = 0; d < d_model; ++d) {
831 y[d] = x[d] * rstd;
832 }
833 for (int d = d_model; d < aligned_embed_dim; ++d) {
834 y[d] = 0.0f;
835 }
836 }
837}
838
839void gemma4_v_norm_forward(const float *input,
840 float *output,
841 float *rstd_cache,
842 int tokens,
843 int num_kv_heads,
844 int head_dim,
845 float eps)
846{
847 if (!input || !output || tokens <= 0 || num_kv_heads <= 0 || head_dim <= 0) {
848 return;
849 }
851 input, output, rstd_cache,
852 tokens * num_kv_heads, head_dim, head_dim, eps);
853}
854
855
856/* Truthful provider-name helper (same pattern as ck_q4_k_q8_k_provider_name).
857 * Reports the ISA path rmsnorm_forward / rmsnorm_forward_strided_f32 will
858 * actually execute, including the strict/exact escape hatches. */
860{
861 const char *exact_env = getenv("CK_RMSNORM_EXACT");
862 if (ck_strict_parity_enabled() || (exact_env && atoi(exact_env) != 0)) {
863 return "rmsnorm_strict_scalar";
864 }
865#if defined(__AVX512F__)
866 return "rmsnorm_avx512";
867#elif defined(__AVX__)
868 return "rmsnorm_avx";
869#elif defined(__ARM_NEON) || defined(__aarch64__)
870 return "rmsnorm_neon";
871#else
872 return "rmsnorm_scalar";
873#endif
874}
875
876/**
877 * RMSNorm backward pass
878 * @test test_rmsnorm.py::TestRMSNormBackward::test_backward_tokens
879 * @test test_rmsnorm.py::TestRMSNormBackward::test_backward_single
880 * @test test_parity.py::test_rmsnorm_backward_parity
881 *
882 * Computes dX and dGamma given dY, X, gamma, and cached rstd.
883 * dX_i = rstd * (dY_i * gamma_i - x_hat_i * m)
884 * dGamma_i = sum_t (dY_i * x_hat_i)
885 *
886 * After changes: make test && make llamacpp-parity-full
887 */
888void rmsnorm_backward(const float *d_output,
889 const float *input,
890 const float *gamma,
891 const float *rstd_cache,
892 float *d_input,
893 float *d_gamma,
894 int tokens,
895 int d_model,
896 int aligned_embed_dim)
897{
898 int T = tokens;
899 int D = d_model;
900 int aligned = aligned_embed_dim;
901
903 rmsnorm_backward_strict_scalar(d_output, input, gamma, rstd_cache, d_input, d_gamma, T, D, aligned);
904 return;
905 }
906
907 // Zero parameter gradients
908#if defined(__AVX512F__)
909 {
910 int d = 0;
911 for (; d + 16 <= D; d += 16) {
912 _mm512_storeu_ps(&d_gamma[d], _mm512_setzero_ps());
913 }
914 for (; d < D; ++d) {
915 d_gamma[d] = 0.0f;
916 }
917 }
918#elif defined(__AVX__)
919 {
920 int d = 0;
921 for (; d + 8 <= D; d += 8) {
922 _mm256_storeu_ps(&d_gamma[d], _mm256_setzero_ps());
923 }
924 for (; d < D; ++d) {
925 d_gamma[d] = 0.0f;
926 }
927 }
928#else
929 for (int d = 0; d < D; ++d) {
930 d_gamma[d] = 0.0f;
931 }
932#endif
933
934 for (int t = 0; t < T; ++t) {
935 const float *x = input + (size_t)t * aligned;
936 const float *dY = d_output + (size_t)t * aligned;
937 float *dX = d_input + (size_t)t * aligned;
938
939 float rstd = rstd_cache[t];
940
941#if defined(__AVX512F__)
942 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
943 __m512 rstd_vec = _mm512_set1_ps(rstd);
944 __m512 sum_vec = _mm512_setzero_ps();
945 int d = 0;
946
947 for (; d + 16 <= D; d += 16) {
948 __m512 xv = _mm512_loadu_ps(&x[d]);
949 __m512 dyv = _mm512_loadu_ps(&dY[d]);
950 __m512 gv = _mm512_loadu_ps(&gamma[d]);
951 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
952 // sum += dY * gamma * x_hat
953 __m512 prod = _mm512_mul_ps(dyv, gv);
954 sum_vec = _mm512_fmadd_ps(prod, x_hat, sum_vec);
955 }
956 float sum_dY_g_xhat = _mm512_reduce_add_ps(sum_vec);
957
958 // Handle remaining elements
959 for (; d < D; ++d) {
960 float x_hat = x[d] * rstd;
961 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
962 }
963 float m = sum_dY_g_xhat / (float)D;
964
965 // Compute dX and accumulate dGamma (vectorized)
966 __m512 m_vec = _mm512_set1_ps(m);
967 d = 0;
968 for (; d + 16 <= D; d += 16) {
969 __m512 xv = _mm512_loadu_ps(&x[d]);
970 __m512 dyv = _mm512_loadu_ps(&dY[d]);
971 __m512 gv = _mm512_loadu_ps(&gamma[d]);
972 __m512 dgv = _mm512_loadu_ps(&d_gamma[d]);
973
974 __m512 x_hat = _mm512_mul_ps(xv, rstd_vec);
975
976 // dX = rstd * (dY * gamma - x_hat * m)
977 __m512 dy_g = _mm512_mul_ps(dyv, gv);
978 __m512 xhat_m = _mm512_mul_ps(x_hat, m_vec);
979 __m512 diff = _mm512_sub_ps(dy_g, xhat_m);
980 __m512 dxv = _mm512_mul_ps(rstd_vec, diff);
981 _mm512_storeu_ps(&dX[d], dxv);
982
983 // d_gamma += dY * x_hat
984 dgv = _mm512_fmadd_ps(dyv, x_hat, dgv);
985 _mm512_storeu_ps(&d_gamma[d], dgv);
986 }
987 // Handle remaining elements
988 for (; d < D; ++d) {
989 float x_hat = x[d] * rstd;
990 float dy = dY[d];
991 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
992 d_gamma[d] += dy * x_hat;
993 }
994
995#elif defined(__AVX__)
996 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
997 __m256 rstd_vec = _mm256_set1_ps(rstd);
998 __m256 sum_vec = _mm256_setzero_ps();
999 int d = 0;
1000
1001 for (; d + 8 <= D; d += 8) {
1002 __m256 xv = _mm256_loadu_ps(&x[d]);
1003 __m256 dyv = _mm256_loadu_ps(&dY[d]);
1004 __m256 gv = _mm256_loadu_ps(&gamma[d]);
1005 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
1006 // sum += dY * gamma * x_hat (no FMA, use mul + mul + add)
1007 __m256 prod = _mm256_mul_ps(dyv, gv);
1008 __m256 prod2 = _mm256_mul_ps(prod, x_hat);
1009 sum_vec = _mm256_add_ps(sum_vec, prod2);
1010 }
1011 float sum_dY_g_xhat = hsum256_ps_rmsnorm(sum_vec);
1012
1013 // Handle remaining elements
1014 for (; d < D; ++d) {
1015 float x_hat = x[d] * rstd;
1016 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
1017 }
1018 float m = sum_dY_g_xhat / (float)D;
1019
1020 // Compute dX and accumulate dGamma (vectorized)
1021 __m256 m_vec = _mm256_set1_ps(m);
1022 d = 0;
1023 for (; d + 8 <= D; d += 8) {
1024 __m256 xv = _mm256_loadu_ps(&x[d]);
1025 __m256 dyv = _mm256_loadu_ps(&dY[d]);
1026 __m256 gv = _mm256_loadu_ps(&gamma[d]);
1027 __m256 dgv = _mm256_loadu_ps(&d_gamma[d]);
1028
1029 __m256 x_hat = _mm256_mul_ps(xv, rstd_vec);
1030
1031 // dX = rstd * (dY * gamma - x_hat * m)
1032 __m256 dy_g = _mm256_mul_ps(dyv, gv);
1033 __m256 xhat_m = _mm256_mul_ps(x_hat, m_vec);
1034 __m256 diff = _mm256_sub_ps(dy_g, xhat_m);
1035 __m256 dxv = _mm256_mul_ps(rstd_vec, diff);
1036 _mm256_storeu_ps(&dX[d], dxv);
1037
1038 // d_gamma += dY * x_hat
1039 __m256 dy_xhat = _mm256_mul_ps(dyv, x_hat);
1040 dgv = _mm256_add_ps(dgv, dy_xhat);
1041 _mm256_storeu_ps(&d_gamma[d], dgv);
1042 }
1043 // Handle remaining elements
1044 for (; d < D; ++d) {
1045 float x_hat = x[d] * rstd;
1046 float dy = dY[d];
1047 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
1048 d_gamma[d] += dy * x_hat;
1049 }
1050
1051#else
1052 // Scalar fallback
1053 // Compute m = (1/D) * sum_j (dY_j * gamma_j * x_hat_j)
1054 float sum_dY_g_xhat = 0.0f;
1055 for (int d = 0; d < D; ++d) {
1056 float x_hat = x[d] * rstd;
1057 sum_dY_g_xhat += dY[d] * gamma[d] * x_hat;
1058 }
1059 float m = sum_dY_g_xhat / (float)D;
1060
1061 // Compute dX and accumulate dGamma
1062 for (int d = 0; d < D; ++d) {
1063 float x_hat = x[d] * rstd;
1064 float dy = dY[d];
1065 dX[d] = rstd * (dy * gamma[d] - x_hat * m);
1066 d_gamma[d] += dy * x_hat;
1067 }
1068#endif
1069
1070 // Zero padding gradients (if any)
1071 for (int d = D; d < aligned; ++d) {
1072 dX[d] = 0.0f;
1073 }
1074 }
1075}
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)
void rmsnorm_forward_muse_weighted_pytorch_bf16_storage(const float *input, const float *weight, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward_muse_unweighted_pytorch_bf16_storage(const float *input, 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)
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)
const char * ck_rmsnorm_provider_name(void)
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 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 weight_mode, int use_pow_inverse)
static float rmsnorm_llama_production_rstd(float mean_eps)
void rmsnorm_forward_muse_centered_pytorch_bf16_storage(const float *input, const float *zero_centered_weight, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void rmsnorm_forward_no_weight_llama_production(const float *input, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float 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