← 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 "bf16_utils.h"
#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_muse_unweighted_scaled_pytorch_bf16_storage (float *q, float *k, int num_heads, int num_kv_heads, int num_tokens, int head_dim, float eps, float q_scale)
 
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_muse_unweighted_pytorch_bf16_storage (const float *input, 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 110 of file qk_norm_kernels.c.

110 {
112 QK_NORM_ISA_AVX = 1,
116} 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 434 of file qk_norm_kernels.c.

440{
441 rmsnorm_forward(q, q_gamma, q, NULL,
442 num_heads * num_tokens, head_dim, head_dim, eps);
443}
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 456 of file qk_norm_kernels.c.

463{
464 int q_rows = num_heads * num_tokens;
465 int k_rows = num_kv_heads * num_tokens;
466
467 if (q_rows > 0) {
468 float q_rstd_cache[q_rows];
469 qk_norm_compute_rstd(q_in, q_rstd_cache, q_rows, head_dim, eps);
470 rmsnorm_backward(d_q_out, q_in, q_gamma, q_rstd_cache,
471 d_q_in, d_q_gamma, q_rows, head_dim, head_dim);
472 }
473
474 if (k_rows > 0) {
475 float k_rstd_cache[k_rows];
476 qk_norm_compute_rstd(k_in, k_rstd_cache, k_rows, head_dim, eps);
477 rmsnorm_backward(d_k_out, k_in, k_gamma, k_rstd_cache,
478 d_k_in, d_k_gamma, k_rows, head_dim, head_dim);
479 }
480}
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 120 of file qk_norm_kernels.c.

121{
122 return g_qk_norm_last_isa;
123}
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 288 of file qk_norm_kernels.c.

293{
294 QKNormISA selected = qk_norm_select_isa();
295 g_qk_norm_last_isa = (int)selected;
296 switch (selected) {
297#if defined(__AVXVNNI__)
299 qk_norm_compute_rstd_avx_vnni(input, rstd_cache, rows, head_dim, eps);
300 return;
301#endif
302#if defined(__AVX2__)
303 case QK_NORM_ISA_AVX2:
304 qk_norm_compute_rstd_avx2(input, rstd_cache, rows, head_dim, eps);
305 return;
306#endif
307#if defined(__AVX__)
308 case QK_NORM_ISA_AVX:
309 qk_norm_compute_rstd_avx(input, rstd_cache, rows, head_dim, eps);
310 return;
311#endif
313 case QK_NORM_ISA_AUTO:
314 default:
315 qk_norm_compute_rstd_scalar(input, rstd_cache, rows, head_dim, eps);
316 return;
317 }
318}
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 187 of file qk_norm_kernels.c.

192{
193 for (int r = 0; r < rows; ++r) {
194 const float *x = input + (size_t)r * (size_t)head_dim;
195 double sum_sq = 0.0;
196 for (int d = 0; d < head_dim; ++d) {
197 double v = (double)x[d];
198 sum_sq += v * v;
199 }
200 float mean_sq = (float)(sum_sq / (double)head_dim);
201 rstd_cache[r] = 1.0f / sqrtf(mean_sq + eps);
202 }
203}

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 335 of file qk_norm_kernels.c.

339{
340 /* Q norm: [num_heads * num_tokens] rows of [head_dim]
341 * Each row is one head's vector for one token. */
342 rmsnorm_forward(q, q_gamma, q, NULL,
343 num_heads * num_tokens, head_dim, head_dim, eps);
344
345 /* K norm: [num_kv_heads * num_tokens] rows of [head_dim]
346 * Same logic, fewer rows when using GQA. */
347 rmsnorm_forward(k, k_gamma, k, NULL,
348 num_kv_heads * num_tokens, head_dim, head_dim, eps);
349}

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 351 of file qk_norm_kernels.c.

355{
356 rmsnorm_forward_fp64_sum(q, q_gamma, q, NULL,
357 num_heads * num_tokens, head_dim, head_dim, eps);
358 rmsnorm_forward_fp64_sum(k, k_gamma, k, NULL,
359 num_kv_heads * num_tokens, head_dim, head_dim, eps);
360}
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 362 of file qk_norm_kernels.c.

366{
368 q, q_gamma, q, NULL,
369 num_heads * num_tokens, head_dim, head_dim, eps);
371 k, k_gamma, k, NULL,
372 num_kv_heads * num_tokens, head_dim, head_dim, eps);
373}
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_muse_unweighted_scaled_pytorch_bf16_storage()

void qk_norm_forward_muse_unweighted_scaled_pytorch_bf16_storage ( float *  q,
float *  k,
int  num_heads,
int  num_kv_heads,
int  num_tokens,
int  head_dim,
float  eps,
float  q_scale 
)

Definition at line 407 of file qk_norm_kernels.c.

416{
417 const int q_elements = num_heads * num_tokens * head_dim;
419 q, q, NULL, num_heads * num_tokens, head_dim, head_dim, eps);
421 k, k, NULL, num_kv_heads * num_tokens, head_dim, head_dim, eps);
422 for (int index = 0; index < q_elements; ++index) {
423 q[index] = bf16_to_float(float_to_bf16(q[index] * q_scale));
424 }
425}
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
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)

References bf16_to_float(), float_to_bf16(), and rmsnorm_forward_muse_unweighted_pytorch_bf16_storage().

◆ 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 375 of file qk_norm_kernels.c.

381{
383 q, q_gamma, q, NULL,
384 num_heads * num_tokens, head_dim, head_dim, eps);
386 k, k_gamma, k, NULL,
387 num_kv_heads * num_tokens, head_dim, head_dim, eps);
388}
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 390 of file qk_norm_kernels.c.

398{
400 q, q_gamma, q, NULL,
401 num_heads * num_tokens, head_dim, head_dim, eps);
403 k, k_gamma, k, NULL,
404 num_kv_heads * num_tokens, head_dim, head_dim, eps);
405}
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 147 of file qk_norm_kernels.c.

148{
149 switch (isa) {
151 return 1;
152#if defined(__AVX__)
153 case QK_NORM_ISA_AVX:
154 return 1;
155#endif
156#if defined(__AVX2__)
157 case QK_NORM_ISA_AVX2:
158 return 1;
159#endif
160#if defined(__AVXVNNI__)
162 return 1;
163#endif
164 default:
165 return 0;
166 }
167}

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 125 of file qk_norm_kernels.c.

126{
127 const char *forced = getenv("CK_QK_NORM_BACKWARD_ISA");
128 if (!forced || forced[0] == '\0' || strcmp(forced, "auto") == 0) {
129 return QK_NORM_ISA_AUTO;
130 }
131 if (strcmp(forced, "scalar") == 0) {
132 return QK_NORM_ISA_SCALAR;
133 }
134 if (strcmp(forced, "avx") == 0) {
135 return QK_NORM_ISA_AVX;
136 }
137 if (strcmp(forced, "avx2") == 0) {
138 return QK_NORM_ISA_AVX2;
139 }
140 if (strcmp(forced, "avx_vnni") == 0) {
142 }
143 /* Unknown value -> keep behavior deterministic by falling back. */
144 return QK_NORM_ISA_SCALAR;
145}

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 169 of file qk_norm_kernels.c.

170{
172 if (forced != QK_NORM_ISA_AUTO) {
173 return qk_norm_isa_compiled(forced) ? forced : QK_NORM_ISA_SCALAR;
174 }
175
176#if defined(__AVXVNNI__)
178#elif defined(__AVX2__)
179 return QK_NORM_ISA_AVX2;
180#elif defined(__AVX__)
181 return QK_NORM_ISA_AVX;
182#else
183 return QK_NORM_ISA_SCALAR;
184#endif
185}
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 888 of file rmsnorm_kernels.c.

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}
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 735 of file rmsnorm_kernels.c.

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}
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 154 of file rmsnorm_kernels.c.

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}

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 213 of file rmsnorm_kernels.c.

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

Referenced by qk_norm_forward_llama_production().

◆ rmsnorm_forward_muse_unweighted_pytorch_bf16_storage()

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 
)

Definition at line 500 of file rmsnorm_kernels.c.

508{
510 input, NULL, output, rstd_cache, tokens, d_model,
511 aligned_embed_dim, aligned_embed_dim, eps, 3, 0);
512}
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)

Referenced by qk_norm_forward_muse_unweighted_scaled_pytorch_bf16_storage().

◆ 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 426 of file rmsnorm_kernels.c.

434{
436 input, gamma, output, rstd_cache, tokens, d_model,
437 aligned_embed_dim, aligned_embed_dim, eps, 0, 0);
438}

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 455 of file rmsnorm_kernels.c.

464{
466 input, gamma, output, rstd_cache, tokens, d_model,
467 aligned_embed_dim, aligned_embed_dim, eps, 1, 0);
468}

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 118 of file qk_norm_kernels.c.

Referenced by qk_norm_backward_last_isa(), and qk_norm_compute_rstd().