← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
qk_norm_kernels.c File Reference

Per-head RMSNorm on Q and K (Qwen3-style QK norm) More...

#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

Go to the source code of this file.

Enumerations

enum  QKNormISA {
  QK_NORM_ISA_SCALAR = 0 , QK_NORM_ISA_AVX = 1 , QK_NORM_ISA_AVX2 = 2 , QK_NORM_ISA_AVX_VNNI = 3 ,
  QK_NORM_ISA_AUTO = -1
}
 

Functions

void q_norm_forward (float *q, const float *q_gamma, int num_heads, int num_tokens, int head_dim, float eps)
 
void qk_norm_backward (const float *d_q_out, const float *d_k_out, const float *q_in, const float *k_in, const float *q_gamma, const float *k_gamma, float *d_q_in, float *d_k_in, float *d_q_gamma, float *d_k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
int qk_norm_backward_last_isa (void)
 
static void qk_norm_compute_rstd (const float *input, float *rstd_cache, int rows, int head_dim, float eps)
 
static void qk_norm_compute_rstd_scalar (const float *input, float *rstd_cache, int rows, int head_dim, float eps)
 
void qk_norm_forward (float *q, float *k, const float *q_gamma, const float *k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
void qk_norm_forward_fp64_sum (float *q, float *k, const float *q_gamma, const float *k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
void qk_norm_forward_llama_production (float *q, float *k, const float *q_gamma, const float *k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
void qk_norm_forward_pytorch_bf16_storage (float *q, float *k, const float *q_gamma, const float *k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
void qk_norm_forward_qwen4_pytorch_bf16_storage (float *q, float *k, const float *q_gamma, const float *k_gamma, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps)
 
static int qk_norm_isa_compiled (QKNormISA isa)
 
static QKNormISA qk_norm_parse_forced_isa (void)
 
static QKNormISA qk_norm_select_isa (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)
 
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_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_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_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_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)
 

Variables

static int g_qk_norm_last_isa = QK_NORM_ISA_SCALAR
 

Detailed Description

Per-head RMSNorm on Q and K (Qwen3-style QK norm)

CK-ENGINE KERNEL RULES:

  1. NO malloc/free - memory via bump allocator, pointers passed in
  2. NO OpenMP - parallelization at orchestrator/codegen layer
  3. API must define: inputs, outputs, workspace, and memory layouts
  4. Pure computation - deterministic, no side effects

After changes: make v7-qk-norm-backward-parity-isa python unittest/test_qk_norm.py

QK Norm normalizes each head's query/key vectors independently before RoPE. This stabilizes Q*K^T dot products before softmax, preventing attention collapse from large magnitude vectors.

Why only Q and K, not V? V does not participate in the attention score computation (Q*K^T). The softmax saturation problem comes from large Q*K^T values, so only Q and K magnitudes matter. V is linearly combined after softmax weights are computed – normalizing it would change output scale but not fix attention stability.

Data layout after QKV projection (head-major): Q: [num_heads, num_tokens, head_dim] contiguous K: [num_kv_heads, num_tokens, head_dim] contiguous

We treat Q as [num_heads * num_tokens] rows of [head_dim] elements. rmsnorm_forward normalizes each row independently. The gamma weight [head_dim] is shared across all heads (Qwen3 design: one gamma per Q, one per K).

Definition in file qk_norm_kernels.c.

Enumeration Type Documentation

◆ QKNormISA

enum QKNormISA
Enumerator
QK_NORM_ISA_SCALAR 
QK_NORM_ISA_AVX 
QK_NORM_ISA_AVX2 
QK_NORM_ISA_AVX_VNNI 
QK_NORM_ISA_AUTO 

Definition at line 101 of file qk_norm_kernels.c.

101 {
103 QK_NORM_ISA_AVX = 1,
107} QKNormISA;
QKNormISA
@ QK_NORM_ISA_AVX_VNNI
@ QK_NORM_ISA_AVX2
@ QK_NORM_ISA_AUTO
@ QK_NORM_ISA_SCALAR
@ QK_NORM_ISA_AVX

Function Documentation

◆ q_norm_forward()

void q_norm_forward ( float *  q,
const float *  q_gamma,
int  num_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Forward pass for Gemma4-assistant q-only per-head RMSNorm.

Some Gemma4 assistant/drafter checkpoints project only Q and then reuse Q as the shared K/V stream. This wrapper keeps that public kernel contract explicit while reusing the same row-wise RMSNorm implementation as qk_norm_forward.

Definition at line 405 of file qk_norm_kernels.c.

411{
412 rmsnorm_forward(q, q_gamma, q, NULL,
413 num_heads * num_tokens, head_dim, head_dim, eps);
414}
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)

References rmsnorm_forward().

◆ qk_norm_backward()

void qk_norm_backward ( const float *  d_q_out,
const float *  d_k_out,
const float *  q_in,
const float *  k_in,
const float *  q_gamma,
const float *  k_gamma,
float *  d_q_in,
float *  d_k_in,
float *  d_q_gamma,
float *  d_k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Backward pass for per-head QK RMSNorm.

This computes:

  • d_q / d_k for the Q and K activations
  • d_q_gamma / d_k_gamma for shared per-head gamma vectors

Implementation is reference-first and deterministic: 1) recompute row rstd values from saved q/k inputs 2) call rmsnorm_backward on flattened [rows, head_dim] views

Definition at line 427 of file qk_norm_kernels.c.

434{
435 int q_rows = num_heads * num_tokens;
436 int k_rows = num_kv_heads * num_tokens;
437
438 if (q_rows > 0) {
439 float q_rstd_cache[q_rows];
440 qk_norm_compute_rstd(q_in, q_rstd_cache, q_rows, head_dim, eps);
441 rmsnorm_backward(d_q_out, q_in, q_gamma, q_rstd_cache,
442 d_q_in, d_q_gamma, q_rows, head_dim, head_dim);
443 }
444
445 if (k_rows > 0) {
446 float k_rstd_cache[k_rows];
447 qk_norm_compute_rstd(k_in, k_rstd_cache, k_rows, head_dim, eps);
448 rmsnorm_backward(d_k_out, k_in, k_gamma, k_rstd_cache,
449 d_k_in, d_k_gamma, k_rows, head_dim, head_dim);
450 }
451}
static void qk_norm_compute_rstd(const float *input, float *rstd_cache, int rows, int head_dim, 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)

References qk_norm_compute_rstd(), and rmsnorm_backward().

◆ qk_norm_backward_last_isa()

int qk_norm_backward_last_isa ( void  )

Definition at line 111 of file qk_norm_kernels.c.

112{
113 return g_qk_norm_last_isa;
114}
static int g_qk_norm_last_isa

References g_qk_norm_last_isa.

◆ qk_norm_compute_rstd()

static void qk_norm_compute_rstd ( const float *  input,
float *  rstd_cache,
int  rows,
int  head_dim,
float  eps 
)
static

Definition at line 279 of file qk_norm_kernels.c.

284{
285 QKNormISA selected = qk_norm_select_isa();
286 g_qk_norm_last_isa = (int)selected;
287 switch (selected) {
288#if defined(__AVXVNNI__)
290 qk_norm_compute_rstd_avx_vnni(input, rstd_cache, rows, head_dim, eps);
291 return;
292#endif
293#if defined(__AVX2__)
294 case QK_NORM_ISA_AVX2:
295 qk_norm_compute_rstd_avx2(input, rstd_cache, rows, head_dim, eps);
296 return;
297#endif
298#if defined(__AVX__)
299 case QK_NORM_ISA_AVX:
300 qk_norm_compute_rstd_avx(input, rstd_cache, rows, head_dim, eps);
301 return;
302#endif
304 case QK_NORM_ISA_AUTO:
305 default:
306 qk_norm_compute_rstd_scalar(input, rstd_cache, rows, head_dim, eps);
307 return;
308 }
309}
static void qk_norm_compute_rstd_scalar(const float *input, float *rstd_cache, int rows, int head_dim, float eps)
static QKNormISA qk_norm_select_isa(void)

References g_qk_norm_last_isa, qk_norm_compute_rstd_scalar(), QK_NORM_ISA_AUTO, QK_NORM_ISA_AVX, QK_NORM_ISA_AVX2, QK_NORM_ISA_AVX_VNNI, QK_NORM_ISA_SCALAR, and qk_norm_select_isa().

Referenced by qk_norm_backward().

◆ qk_norm_compute_rstd_scalar()

static void qk_norm_compute_rstd_scalar ( const float *  input,
float *  rstd_cache,
int  rows,
int  head_dim,
float  eps 
)
static

Definition at line 178 of file qk_norm_kernels.c.

183{
184 for (int r = 0; r < rows; ++r) {
185 const float *x = input + (size_t)r * (size_t)head_dim;
186 double sum_sq = 0.0;
187 for (int d = 0; d < head_dim; ++d) {
188 double v = (double)x[d];
189 sum_sq += v * v;
190 }
191 float mean_sq = (float)(sum_sq / (double)head_dim);
192 rstd_cache[r] = 1.0f / sqrtf(mean_sq + eps);
193 }
194}

Referenced by qk_norm_compute_rstd().

◆ qk_norm_forward()

void qk_norm_forward ( float *  q,
float *  k,
const float *  q_gamma,
const float *  k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Per-head RMSNorm on Q and K.

Parameters
qQ scratch buffer [num_heads * num_tokens * head_dim], in-place
kK scratch buffer [num_kv_heads * num_tokens * head_dim], in-place
q_gammaQ norm gamma weights [head_dim]
k_gammaK norm gamma weights [head_dim]
num_headsNumber of query heads (e.g. 32 for Qwen3-8B)
num_kv_headsNumber of KV heads (e.g. 8 for Qwen3-8B with GQA)
num_tokensNumber of tokens (1 for decode, T for prefill)
head_dimDimension per head (e.g. 128)
epsRMSNorm epsilon (e.g. 1e-6)
Test:
unittest/test_qk_norm.py

Definition at line 326 of file qk_norm_kernels.c.

330{
331 /* Q norm: [num_heads * num_tokens] rows of [head_dim]
332 * Each row is one head's vector for one token. */
333 rmsnorm_forward(q, q_gamma, q, NULL,
334 num_heads * num_tokens, head_dim, head_dim, eps);
335
336 /* K norm: [num_kv_heads * num_tokens] rows of [head_dim]
337 * Same logic, fewer rows when using GQA. */
338 rmsnorm_forward(k, k_gamma, k, NULL,
339 num_kv_heads * num_tokens, head_dim, head_dim, eps);
340}

References rmsnorm_forward().

◆ qk_norm_forward_fp64_sum()

void qk_norm_forward_fp64_sum ( float *  q,
float *  k,
const float *  q_gamma,
const float *  k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Definition at line 342 of file qk_norm_kernels.c.

346{
347 rmsnorm_forward_fp64_sum(q, q_gamma, q, NULL,
348 num_heads * num_tokens, head_dim, head_dim, eps);
349 rmsnorm_forward_fp64_sum(k, k_gamma, k, NULL,
350 num_kv_heads * num_tokens, head_dim, head_dim, eps);
351}
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)

References rmsnorm_forward_fp64_sum().

◆ qk_norm_forward_llama_production()

void qk_norm_forward_llama_production ( float *  q,
float *  k,
const float *  q_gamma,
const float *  k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Definition at line 353 of file qk_norm_kernels.c.

357{
359 q, q_gamma, q, NULL,
360 num_heads * num_tokens, head_dim, head_dim, eps);
362 k, k_gamma, k, NULL,
363 num_kv_heads * num_tokens, head_dim, head_dim, eps);
364}
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)

References rmsnorm_forward_llama_production().

◆ qk_norm_forward_pytorch_bf16_storage()

void qk_norm_forward_pytorch_bf16_storage ( float *  q,
float *  k,
const float *  q_gamma,
const float *  k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Definition at line 366 of file qk_norm_kernels.c.

372{
374 q, q_gamma, q, NULL,
375 num_heads * num_tokens, head_dim, head_dim, eps);
377 k, k_gamma, k, NULL,
378 num_kv_heads * num_tokens, head_dim, head_dim, eps);
379}
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)

References rmsnorm_forward_pytorch_bf16_storage().

◆ qk_norm_forward_qwen4_pytorch_bf16_storage()

void qk_norm_forward_qwen4_pytorch_bf16_storage ( float *  q,
float *  k,
const float *  q_gamma,
const float *  k_gamma,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps 
)

Definition at line 381 of file qk_norm_kernels.c.

389{
391 q, q_gamma, q, NULL,
392 num_heads * num_tokens, head_dim, head_dim, eps);
394 k, k_gamma, k, NULL,
395 num_kv_heads * num_tokens, head_dim, head_dim, eps);
396}
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)

References rmsnorm_forward_qwen3next_pytorch_bf16_storage().

◆ qk_norm_isa_compiled()

static int qk_norm_isa_compiled ( QKNormISA  isa)
static

Definition at line 138 of file qk_norm_kernels.c.

139{
140 switch (isa) {
142 return 1;
143#if defined(__AVX__)
144 case QK_NORM_ISA_AVX:
145 return 1;
146#endif
147#if defined(__AVX2__)
148 case QK_NORM_ISA_AVX2:
149 return 1;
150#endif
151#if defined(__AVXVNNI__)
153 return 1;
154#endif
155 default:
156 return 0;
157 }
158}

References QK_NORM_ISA_AVX, QK_NORM_ISA_AVX2, QK_NORM_ISA_AVX_VNNI, and QK_NORM_ISA_SCALAR.

Referenced by qk_norm_select_isa().

◆ qk_norm_parse_forced_isa()

static QKNormISA qk_norm_parse_forced_isa ( void  )
static

Definition at line 116 of file qk_norm_kernels.c.

117{
118 const char *forced = getenv("CK_QK_NORM_BACKWARD_ISA");
119 if (!forced || forced[0] == '\0' || strcmp(forced, "auto") == 0) {
120 return QK_NORM_ISA_AUTO;
121 }
122 if (strcmp(forced, "scalar") == 0) {
123 return QK_NORM_ISA_SCALAR;
124 }
125 if (strcmp(forced, "avx") == 0) {
126 return QK_NORM_ISA_AVX;
127 }
128 if (strcmp(forced, "avx2") == 0) {
129 return QK_NORM_ISA_AVX2;
130 }
131 if (strcmp(forced, "avx_vnni") == 0) {
133 }
134 /* Unknown value -> keep behavior deterministic by falling back. */
135 return QK_NORM_ISA_SCALAR;
136}

References QK_NORM_ISA_AUTO, QK_NORM_ISA_AVX, QK_NORM_ISA_AVX2, QK_NORM_ISA_AVX_VNNI, and QK_NORM_ISA_SCALAR.

Referenced by qk_norm_select_isa().

◆ qk_norm_select_isa()

static QKNormISA qk_norm_select_isa ( void  )
static

Definition at line 160 of file qk_norm_kernels.c.

161{
163 if (forced != QK_NORM_ISA_AUTO) {
164 return qk_norm_isa_compiled(forced) ? forced : QK_NORM_ISA_SCALAR;
165 }
166
167#if defined(__AVXVNNI__)
169#elif defined(__AVX2__)
170 return QK_NORM_ISA_AVX2;
171#elif defined(__AVX__)
172 return QK_NORM_ISA_AVX;
173#else
174 return QK_NORM_ISA_SCALAR;
175#endif
176}
static int qk_norm_isa_compiled(QKNormISA isa)
static QKNormISA qk_norm_parse_forced_isa(void)

References QK_NORM_ISA_AUTO, QK_NORM_ISA_AVX, QK_NORM_ISA_AVX2, QK_NORM_ISA_AVX_VNNI, qk_norm_isa_compiled(), QK_NORM_ISA_SCALAR, and qk_norm_parse_forced_isa().

Referenced by qk_norm_compute_rstd().

◆ rmsnorm_backward()

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 
)

RMSNorm backward pass

Test:

test_rmsnorm.py::TestRMSNormBackward::test_backward_tokens

test_rmsnorm.py::TestRMSNormBackward::test_backward_single

test_parity.py::test_rmsnorm_backward_parity

Computes dX and dGamma given dY, X, gamma, and cached rstd. dX_i = rstd * (dY_i * gamma_i - x_hat_i * m) dGamma_i = sum_t (dY_i * x_hat_i)

After changes: make test && make llamacpp-parity-full

Definition at line 715 of file rmsnorm_kernels.c.

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}
int ck_strict_parity_enabled(void)
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)

Referenced by qk_norm_backward().

◆ rmsnorm_forward()

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 
)

Definition at line 621 of file rmsnorm_kernels.c.

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}
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)

Referenced by q_norm_forward(), qk_norm_forward(), and rmsnorm_forward_kv_lora().

◆ rmsnorm_forward_fp64_sum()

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 
)

Definition at line 137 of file rmsnorm_kernels.c.

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}

Referenced by qk_norm_forward_fp64_sum().

◆ rmsnorm_forward_llama_production()

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 
)

Definition at line 196 of file rmsnorm_kernels.c.

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}
static float rmsnorm_llama_production_rstd(float mean_eps)

Referenced by qk_norm_forward_llama_production().

◆ rmsnorm_forward_pytorch_bf16_storage()

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 
)

Definition at line 395 of file rmsnorm_kernels.c.

403{
405 input, gamma, output, rstd_cache, tokens, d_model,
406 aligned_embed_dim, aligned_embed_dim, eps, 0);
407}
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)

Referenced by qk_norm_forward_pytorch_bf16_storage().

◆ rmsnorm_forward_qwen3next_pytorch_bf16_storage()

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 
)

Definition at line 424 of file rmsnorm_kernels.c.

433{
435 input, gamma, output, rstd_cache, tokens, d_model,
436 aligned_embed_dim, aligned_embed_dim, eps, 1);
437}

Referenced by qk_norm_forward_qwen4_pytorch_bf16_storage().

Variable Documentation

◆ g_qk_norm_last_isa

int g_qk_norm_last_isa = QK_NORM_ISA_SCALAR
static

Definition at line 109 of file qk_norm_kernels.c.

Referenced by qk_norm_backward_last_isa(), and qk_norm_compute_rstd().