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

Mega-Fused Attention + MLP Block. More...

#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "ckernel_quant.h"

Go to the source code of this file.

Functions

void attention_mlp_fused_fp32 (const float *q, const float *k_cache, const float *v_cache, int seq_len, int num_heads, int num_kv_heads, int head_dim, float attn_scale, const float *wo, const float *residual_1, const float *rms_weight, float eps, const float *w_gate, const float *w_up, const float *w_down, int embed_dim, int intermediate_dim, float *hidden_out)
 
void attention_mlp_fused_q4k (const float *q, const float *k_cache, const float *v_cache, int seq_len, int num_heads, int num_kv_heads, int head_dim, float attn_scale, const void *wo, const float *residual_1, const float *rms_weight, float eps, const void *w_gate, const void *w_up, const void *w_down, int embed_dim, int intermediate_dim, float *hidden_out)
 
void attention_mlp_separate_fp32 (const float *q, const float *k_cache, const float *v_cache, int seq_len, int num_heads, int num_kv_heads, int head_dim, float attn_scale, const float *wo, const float *residual_1, const float *rms_weight, float eps, const float *w_gate, const float *w_up, const float *w_down, int embed_dim, int intermediate_dim, float *attn_out_buf, float *hidden_after_attn_buf, float *normed_buf, float *gate_buf, float *up_buf, float *mlp_out_buf, float *hidden_out)
 
static float compute_rms_scale_internal (const float *x, int n, float eps)
 
void layer_fused_attn_mlp_qkv_q4k (const float *q, const float *k_cache, const float *v_cache, int seq_len, float attn_scale, const void *wo, const float *rms_weight_mlp, const void *w_gate, const void *w_up, const void *w_down, const float *rms_weight_attn, const void *wq_next, const void *wk_next, const void *wv_next, const float *residual_in, int embed_dim, int intermediate_dim, int num_heads, int num_kv_heads, int head_dim, float eps, float *q_next, float *k_next, float *v_next, float *hidden_out)
 
void mlp_fused_fp32_v2 (const float *hidden_in, const float *rms_weight, float eps, const float *w_gate, const float *w_up, const float *w_down, int embed_dim, int intermediate_dim, float *hidden_out)
 
void mlp_fused_fp32_v3 (const float *hidden_in, const float *rms_weight, float eps, const float *w_gate, const float *w_up, const float *w_down, int embed_dim, int intermediate_dim, float *hidden_out)
 
void mlp_separate_fp32 (const float *hidden_in, const float *rms_weight, float eps, const float *w_gate, const float *w_up, const float *w_down, float *normed_buf, float *gate_buf, float *up_buf, int embed_dim, int intermediate_dim, float *hidden_out)
 
static float silu_scalar (float x)
 
static void softmax_inplace (float *x, int n)
 

Detailed Description

Mega-Fused Attention + MLP Block.

CK-ENGINE KERNEL RULES:

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

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

VIOLATION: Uses memcpy for layout conversion. TODO: Use strided access.

Part of C-Kernel-Engine v6.6 Fusion Kernels

FUSES THE ENTIRE BLOCK from Attention output to next layer input:

Attention(Q, K_cache, V_cache) │ ▼ Output Projection (attn @ Wo) │ ▼

  • residual_1 │ ▼ RMSNorm │ ▼ MLP: gate ──► SwiGLU ◄── up │ ▼ down │ ▼
  • residual_2 │ ▼ hidden_out (ready for next layer)

NON-FUSED version writes these buffers to DRAM:

  • attn_output [embed_dim]
  • projected [embed_dim]
  • hidden_after_attn [embed_dim]
  • normed [embed_dim]
  • gate [intermediate_dim]
  • up [intermediate_dim]
  • swiglu [intermediate_dim]
  • mlp_out [embed_dim] = 8 DRAM round-trips!

FUSED version: ALL intermediates stay in L1/L2, ZERO DRAM writes

EXPECTED SPEEDUP: 2-3x for this block

Definition in file attention_mlp_fused.c.

Function Documentation

◆ attention_mlp_fused_fp32()

void attention_mlp_fused_fp32 ( const float *  q,
const float *  k_cache,
const float *  v_cache,
int  seq_len,
int  num_heads,
int  num_kv_heads,
int  head_dim,
float  attn_scale,
const float *  wo,
const float *  residual_1,
const float *  rms_weight,
float  eps,
const float *  w_gate,
const float *  w_up,
const float *  w_down,
int  embed_dim,
int  intermediate_dim,
float *  hidden_out 
)

Definition at line 153 of file attention_mlp_fused.c.

188 {
189 const int heads_per_kv = num_heads / num_kv_heads;
190 const int q_dim = num_heads * head_dim;
191 const int kv_dim = num_kv_heads * head_dim;
192
193 /* Stack buffers - all stay in L1/L2 */
194 float attn_out[4096]; /* Attention output per head, then combined */
195 float hidden_after_attn[4096];
196 float normed[4096];
197 float gate_out[16384]; /* Intermediate dim (e.g., 4864 for Qwen2) */
198 float up_out[16384];
199
200 if (embed_dim > 4096 || intermediate_dim > 16384) {
201 return; /* TODO: heap allocation for large models */
202 }
203
204 /* ═══════════════════════════════════════════════════════════════════════
205 * STEP 1: Multi-Head Attention (Q @ K^T -> softmax -> @ V)
206 * ═══════════════════════════════════════════════════════════════════════ */
207
208 memset(attn_out, 0, q_dim * sizeof(float));
209
210 for (int h = 0; h < num_heads; h++) {
211 int kv_h = h / heads_per_kv; /* GQA: map query head to KV head */
212
213 const float *q_head = q + h * head_dim;
214 float *out_head = attn_out + h * head_dim;
215
216 /* Compute attention scores: Q @ K^T */
217 float scores[8192]; /* Max seq_len */
218 if (seq_len > 8192) return;
219
220 for (int t = 0; t < seq_len; t++) {
221 const float *k_t = k_cache + t * kv_dim + kv_h * head_dim;
222 float score = 0.0f;
223 for (int d = 0; d < head_dim; d++) {
224 score += q_head[d] * k_t[d];
225 }
226 scores[t] = score * attn_scale;
227 }
228
229 /* Softmax */
230 softmax_inplace(scores, seq_len);
231
232 /* Weighted sum of V: scores @ V */
233 for (int t = 0; t < seq_len; t++) {
234 const float *v_t = v_cache + t * kv_dim + kv_h * head_dim;
235 float w = scores[t];
236 for (int d = 0; d < head_dim; d++) {
237 out_head[d] += w * v_t[d];
238 }
239 }
240 }
241
242 /* ═══════════════════════════════════════════════════════════════════════
243 * STEP 2: Output Projection (attn_out @ Wo) + Residual
244 * ═══════════════════════════════════════════════════════════════════════ */
245
246 for (int i = 0; i < embed_dim; i++) {
247 float sum = 0.0f;
248 const float *wo_row = wo + i * q_dim;
249 for (int j = 0; j < q_dim; j++) {
250 sum += wo_row[j] * attn_out[j];
251 }
252 hidden_after_attn[i] = sum + residual_1[i]; /* Residual add */
253 }
254
255 /* ═══════════════════════════════════════════════════════════════════════
256 * STEP 3: RMSNorm
257 * ═══════════════════════════════════════════════════════════════════════ */
258
259 float rms_scale = compute_rms_scale_internal(hidden_after_attn, embed_dim, eps);
260
261#ifdef __AVX2__
262 __m256 vscale = _mm256_set1_ps(rms_scale);
263 int i = 0;
264 for (; i + 7 < embed_dim; i += 8) {
265 __m256 vh = _mm256_loadu_ps(hidden_after_attn + i);
266 __m256 vw = _mm256_loadu_ps(rms_weight + i);
267 __m256 vn = _mm256_mul_ps(_mm256_mul_ps(vh, vw), vscale);
268 _mm256_storeu_ps(normed + i, vn);
269 }
270 for (; i < embed_dim; i++) {
271 normed[i] = hidden_after_attn[i] * rms_weight[i] * rms_scale;
272 }
273#else
274 for (int i = 0; i < embed_dim; i++) {
275 normed[i] = hidden_after_attn[i] * rms_weight[i] * rms_scale;
276 }
277#endif
278
279 /* ═══════════════════════════════════════════════════════════════════════
280 * STEP 4: MLP Gate + Up projections (can be parallelized)
281 * ═══════════════════════════════════════════════════════════════════════ */
282
283 /* Gate projection: gate_out = normed @ W_gate^T */
284 for (int i = 0; i < intermediate_dim; i++) {
285 float sum = 0.0f;
286 const float *wg_row = w_gate + i * embed_dim;
287 for (int j = 0; j < embed_dim; j++) {
288 sum += wg_row[j] * normed[j];
289 }
290 gate_out[i] = sum;
291 }
292
293 /* Up projection: up_out = normed @ W_up^T */
294 for (int i = 0; i < intermediate_dim; i++) {
295 float sum = 0.0f;
296 const float *wu_row = w_up + i * embed_dim;
297 for (int j = 0; j < embed_dim; j++) {
298 sum += wu_row[j] * normed[j];
299 }
300 up_out[i] = sum;
301 }
302
303 /* ═══════════════════════════════════════════════════════════════════════
304 * STEP 5: SwiGLU activation: silu(gate) * up
305 * ═══════════════════════════════════════════════════════════════════════ */
306
307#ifdef __AVX2__
308 i = 0;
309 for (; i + 7 < intermediate_dim; i += 8) {
310 __m256 vg = _mm256_loadu_ps(gate_out + i);
311 __m256 vu = _mm256_loadu_ps(up_out + i);
312 __m256 vsilu = silu_avx2(vg);
313 __m256 vswiglu = _mm256_mul_ps(vsilu, vu);
314 _mm256_storeu_ps(gate_out + i, vswiglu); /* Reuse gate_out buffer */
315 }
316 for (; i < intermediate_dim; i++) {
317 gate_out[i] = silu_scalar(gate_out[i]) * up_out[i];
318 }
319#else
320 for (int i = 0; i < intermediate_dim; i++) {
321 gate_out[i] = silu_scalar(gate_out[i]) * up_out[i];
322 }
323#endif
324
325 /* ═══════════════════════════════════════════════════════════════════════
326 * STEP 6: Down projection + Final Residual
327 * ═══════════════════════════════════════════════════════════════════════ */
328
329 for (int i = 0; i < embed_dim; i++) {
330 float sum = 0.0f;
331 const float *wd_row = w_down + i * intermediate_dim;
332 for (int j = 0; j < intermediate_dim; j++) {
333 sum += wd_row[j] * gate_out[j]; /* gate_out now holds SwiGLU output */
334 }
335 hidden_out[i] = sum + hidden_after_attn[i]; /* Final residual */
336 }
337}
static float silu_scalar(float x)
static float compute_rms_scale_internal(const float *x, int n, float eps)
static void softmax_inplace(float *x, int n)
int32_t float * score
Definition tokenizer.h:328

References compute_rms_scale_internal(), score, silu_scalar(), and softmax_inplace().

◆ attention_mlp_fused_q4k()

void attention_mlp_fused_q4k ( const float *  q,
const float *  k_cache,
const float *  v_cache,
int  seq_len,
int  num_heads,
int  num_kv_heads,
int  head_dim,
float  attn_scale,
const void *  wo,
const float *  residual_1,
const float *  rms_weight,
float  eps,
const void *  w_gate,
const void *  w_up,
const void *  w_down,
int  embed_dim,
int  intermediate_dim,
float *  hidden_out 
)

Definition at line 720 of file attention_mlp_fused.c.

752 {
753 const int heads_per_kv = num_heads / num_kv_heads;
754 const int q_dim = num_heads * head_dim;
755 const int kv_dim = num_kv_heads * head_dim;
756
757 /* Stack buffers */
758 float attn_out[4096];
759 float hidden_after_attn[4096];
760 float normed[4096];
761 float mlp_out[4096];
762
763 if (embed_dim > 4096) return;
764
765 /* ═══════════════════════════════════════════════════════════════════════
766 * STEP 1: Multi-Head Attention (same as FP32 version)
767 * ═══════════════════════════════════════════════════════════════════════ */
768
769 memset(attn_out, 0, q_dim * sizeof(float));
770
771 for (int h = 0; h < num_heads; h++) {
772 int kv_h = h / heads_per_kv;
773 const float *q_head = q + h * head_dim;
774 float *out_head = attn_out + h * head_dim;
775
776 float scores[8192];
777 if (seq_len > 8192) return;
778
779 for (int t = 0; t < seq_len; t++) {
780 const float *k_t = k_cache + t * kv_dim + kv_h * head_dim;
781 float score = 0.0f;
782 for (int d = 0; d < head_dim; d++) {
783 score += q_head[d] * k_t[d];
784 }
785 scores[t] = score * attn_scale;
786 }
787
788 softmax_inplace(scores, seq_len);
789
790 for (int t = 0; t < seq_len; t++) {
791 const float *v_t = v_cache + t * kv_dim + kv_h * head_dim;
792 float w = scores[t];
793 for (int d = 0; d < head_dim; d++) {
794 out_head[d] += w * v_t[d];
795 }
796 }
797 }
798
799 /* ═══════════════════════════════════════════════════════════════════════
800 * STEP 2: Output Projection (Q4_K) + Residual
801 * ═══════════════════════════════════════════════════════════════════════ */
802
803 extern void gemv_q4_k(float *y, const void *W, const float *x, int M, int K);
804
805 gemv_q4_k(hidden_after_attn, wo, attn_out, embed_dim, q_dim);
806
807 /* Add residual */
808 for (int i = 0; i < embed_dim; i++) {
809 hidden_after_attn[i] += residual_1[i];
810 }
811
812 /* ═══════════════════════════════════════════════════════════════════════
813 * STEP 3: RMSNorm (same as before)
814 * ═══════════════════════════════════════════════════════════════════════ */
815
816 float rms_scale = compute_rms_scale_internal(hidden_after_attn, embed_dim, eps);
817
818 for (int i = 0; i < embed_dim; i++) {
819 normed[i] = hidden_after_attn[i] * rms_weight[i] * rms_scale;
820 }
821
822 /* ═══════════════════════════════════════════════════════════════════════
823 * STEP 4-6: MLP with Q4_K weights (inline implementation)
824 *
825 * gate_out = normed @ W_gate
826 * up_out = normed @ W_up
827 * swiglu = silu(gate_out) * up_out
828 * mlp_out = swiglu @ W_down
829 * ═══════════════════════════════════════════════════════════════════════ */
830
831 float gate_out[16384];
832 float up_out[16384];
833
834 if (intermediate_dim > 16384) return;
835
836 /* Gate projection */
837 gemv_q4_k(gate_out, w_gate, normed, intermediate_dim, embed_dim);
838
839 /* Up projection */
840 gemv_q4_k(up_out, w_up, normed, intermediate_dim, embed_dim);
841
842 /* SwiGLU: silu(gate) * up */
843#ifdef __AVX2__
844 int i = 0;
845 for (; i + 7 < intermediate_dim; i += 8) {
846 __m256 vg = _mm256_loadu_ps(gate_out + i);
847 __m256 vu = _mm256_loadu_ps(up_out + i);
848 __m256 vsilu = silu_avx2(vg);
849 __m256 vswiglu = _mm256_mul_ps(vsilu, vu);
850 _mm256_storeu_ps(gate_out + i, vswiglu);
851 }
852 for (; i < intermediate_dim; i++) {
853 gate_out[i] = silu_scalar(gate_out[i]) * up_out[i];
854 }
855#else
856 for (int i = 0; i < intermediate_dim; i++) {
857 gate_out[i] = silu_scalar(gate_out[i]) * up_out[i];
858 }
859#endif
860
861 /* Down projection */
862 gemv_q4_k(mlp_out, w_down, gate_out, embed_dim, intermediate_dim);
863
864 /* Final residual add */
865 for (int i = 0; i < embed_dim; i++) {
866 hidden_out[i] = mlp_out[i] + hidden_after_attn[i];
867 }
868}
void gemv_q4_k(float *y, const void *W, const float *x, int M, int K)
Auto-dispatch GEMV based on available SIMD.

References compute_rms_scale_internal(), gemv_q4_k(), score, silu_scalar(), and softmax_inplace().

◆ attention_mlp_separate_fp32()

void attention_mlp_separate_fp32 ( const float *  q,
const float *  k_cache,
const float *  v_cache,
int  seq_len,
int  num_heads,
int  num_kv_heads,
int  head_dim,
float  attn_scale,
const float *  wo,
const float *  residual_1,
const float *  rms_weight,
float  eps,
const float *  w_gate,
const float *  w_up,
const float *  w_down,
int  embed_dim,
int  intermediate_dim,
float *  attn_out_buf,
float *  hidden_after_attn_buf,
float *  normed_buf,
float *  gate_buf,
float *  up_buf,
float *  mlp_out_buf,
float *  hidden_out 
)

Definition at line 1062 of file attention_mlp_fused.c.

1079 {
1080 /* This version writes all intermediates to the provided buffers,
1081 * simulating non-fused execution with DRAM traffic */
1082
1083 const int heads_per_kv = num_heads / num_kv_heads;
1084 const int q_dim = num_heads * head_dim;
1085 const int kv_dim = num_kv_heads * head_dim;
1086
1087 /* Step 1: Attention */
1088 memset(attn_out_buf, 0, q_dim * sizeof(float));
1089
1090 /* Stack-allocated scores buffer (no malloc!) */
1091 float scores[8192]; /* Max seq_len supported */
1092 if (seq_len > 8192) return;
1093
1094 for (int h = 0; h < num_heads; h++) {
1095 int kv_h = h / heads_per_kv;
1096 const float *q_head = q + h * head_dim;
1097 float *out_head = attn_out_buf + h * head_dim;
1098
1099 for (int t = 0; t < seq_len; t++) {
1100 const float *k_t = k_cache + t * kv_dim + kv_h * head_dim;
1101 float score = 0.0f;
1102 for (int d = 0; d < head_dim; d++) {
1103 score += q_head[d] * k_t[d];
1104 }
1105 scores[t] = score * attn_scale;
1106 }
1107
1108 softmax_inplace(scores, seq_len);
1109
1110 for (int t = 0; t < seq_len; t++) {
1111 const float *v_t = v_cache + t * kv_dim + kv_h * head_dim;
1112 float w = scores[t];
1113 for (int d = 0; d < head_dim; d++) {
1114 out_head[d] += w * v_t[d];
1115 }
1116 }
1117 }
1118
1119 /* Step 2: Output projection + residual -> DRAM write */
1120 for (int i = 0; i < embed_dim; i++) {
1121 float sum = 0.0f;
1122 const float *wo_row = wo + i * q_dim;
1123 for (int j = 0; j < q_dim; j++) {
1124 sum += wo_row[j] * attn_out_buf[j];
1125 }
1126 hidden_after_attn_buf[i] = sum + residual_1[i];
1127 }
1128
1129 /* Step 3: RMSNorm -> DRAM write */
1130 float rms_scale = compute_rms_scale_internal(hidden_after_attn_buf, embed_dim, eps);
1131 for (int i = 0; i < embed_dim; i++) {
1132 normed_buf[i] = hidden_after_attn_buf[i] * rms_weight[i] * rms_scale;
1133 }
1134
1135 /* Step 4: Gate projection -> DRAM write */
1136 for (int i = 0; i < intermediate_dim; i++) {
1137 float sum = 0.0f;
1138 const float *wg_row = w_gate + i * embed_dim;
1139 for (int j = 0; j < embed_dim; j++) {
1140 sum += wg_row[j] * normed_buf[j];
1141 }
1142 gate_buf[i] = sum;
1143 }
1144
1145 /* Step 5: Up projection -> DRAM write */
1146 for (int i = 0; i < intermediate_dim; i++) {
1147 float sum = 0.0f;
1148 const float *wu_row = w_up + i * embed_dim;
1149 for (int j = 0; j < embed_dim; j++) {
1150 sum += wu_row[j] * normed_buf[j];
1151 }
1152 up_buf[i] = sum;
1153 }
1154
1155 /* Step 6: SwiGLU (in-place in gate_buf) */
1156 for (int i = 0; i < intermediate_dim; i++) {
1157 gate_buf[i] = silu_scalar(gate_buf[i]) * up_buf[i];
1158 }
1159
1160 /* Step 7: Down projection -> DRAM write */
1161 for (int i = 0; i < embed_dim; i++) {
1162 float sum = 0.0f;
1163 const float *wd_row = w_down + i * intermediate_dim;
1164 for (int j = 0; j < intermediate_dim; j++) {
1165 sum += wd_row[j] * gate_buf[j];
1166 }
1167 mlp_out_buf[i] = sum;
1168 }
1169
1170 /* Step 8: Final residual */
1171 for (int i = 0; i < embed_dim; i++) {
1172 hidden_out[i] = mlp_out_buf[i] + hidden_after_attn_buf[i];
1173 }
1174}

References compute_rms_scale_internal(), score, silu_scalar(), and softmax_inplace().

◆ compute_rms_scale_internal()

static float compute_rms_scale_internal ( const float *  x,
int  n,
float  eps 
)
inlinestatic

Definition at line 76 of file attention_mlp_fused.c.

76 {
77 float sum_sq = 0.0f;
78
79#ifdef __AVX2__
80 __m256 vsum = _mm256_setzero_ps();
81 int i = 0;
82 for (; i + 7 < n; i += 8) {
83 __m256 vx = _mm256_loadu_ps(x + i);
84 vsum = _mm256_fmadd_ps(vx, vx, vsum);
85 }
86 __m128 vlow = _mm256_castps256_ps128(vsum);
87 __m128 vhigh = _mm256_extractf128_ps(vsum, 1);
88 vlow = _mm_add_ps(vlow, vhigh);
89 vlow = _mm_hadd_ps(vlow, vlow);
90 vlow = _mm_hadd_ps(vlow, vlow);
91 sum_sq = _mm_cvtss_f32(vlow);
92 for (; i < n; i++) {
93 sum_sq += x[i] * x[i];
94 }
95#else
96 for (int i = 0; i < n; i++) {
97 sum_sq += x[i] * x[i];
98 }
99#endif
100
101 float rms = sqrtf(sum_sq / (float)n + eps);
102 return 1.0f / rms;
103}

Referenced by attention_mlp_fused_fp32(), attention_mlp_fused_q4k(), attention_mlp_separate_fp32(), mlp_fused_fp32_v2(), mlp_fused_fp32_v3(), and mlp_separate_fp32().

◆ layer_fused_attn_mlp_qkv_q4k()

void layer_fused_attn_mlp_qkv_q4k ( const float *  q,
const float *  k_cache,
const float *  v_cache,
int  seq_len,
float  attn_scale,
const void *  wo,
const float *  rms_weight_mlp,
const void *  w_gate,
const void *  w_up,
const void *  w_down,
const float *  rms_weight_attn,
const void *  wq_next,
const void *  wk_next,
const void *  wv_next,
const float *  residual_in,
int  embed_dim,
int  intermediate_dim,
int  num_heads,
int  num_kv_heads,
int  head_dim,
float  eps,
float *  q_next,
float *  k_next,
float *  v_next,
float *  hidden_out 
)

Definition at line 879 of file attention_mlp_fused.c.

916 {
917 extern void gemv_q4_k(float *y, const void *W, const float *x, int M, int K);
918
919 const int heads_per_kv = num_heads / num_kv_heads;
920 const int q_dim = num_heads * head_dim;
921 const int kv_dim = num_kv_heads * head_dim;
922
923 /* All intermediate buffers on stack - stay in L1/L2
924 * hidden_out is the final output buffer - we write to it directly! */
925 float attn_out[4096];
926 float hidden_after_attn[4096];
927 float normed_mlp[4096];
928 float gate_out[16384];
929 float up_out[16384];
930 /* NOTE: No hidden_after_mlp buffer - we output directly to hidden_out */
931 float normed_attn[4096];
932
933 if (embed_dim > 4096 || intermediate_dim > 16384) return;
934
935 /* ═══════════════════════════════════════════════════════════════════════
936 * STEP 1: Multi-Head Attention
937 * ═══════════════════════════════════════════════════════════════════════ */
938
939 memset(attn_out, 0, q_dim * sizeof(float));
940
941 for (int h = 0; h < num_heads; h++) {
942 int kv_h = h / heads_per_kv;
943 const float *q_head = q + h * head_dim;
944 float *out_head = attn_out + h * head_dim;
945
946 float scores[8192];
947 if (seq_len > 8192) return;
948
949 for (int t = 0; t < seq_len; t++) {
950 const float *k_t = k_cache + t * kv_dim + kv_h * head_dim;
951 float score = 0.0f;
952 for (int d = 0; d < head_dim; d++) {
953 score += q_head[d] * k_t[d];
954 }
955 scores[t] = score * attn_scale;
956 }
957
958 /* Softmax */
959 float max_score = scores[0];
960 for (int t = 1; t < seq_len; t++) {
961 if (scores[t] > max_score) max_score = scores[t];
962 }
963 float sum_exp = 0.0f;
964 for (int t = 0; t < seq_len; t++) {
965 scores[t] = expf(scores[t] - max_score);
966 sum_exp += scores[t];
967 }
968 float inv_sum = 1.0f / sum_exp;
969 for (int t = 0; t < seq_len; t++) {
970 scores[t] *= inv_sum;
971 }
972
973 /* Weighted sum of V */
974 for (int t = 0; t < seq_len; t++) {
975 const float *v_t = v_cache + t * kv_dim + kv_h * head_dim;
976 float w = scores[t];
977 for (int d = 0; d < head_dim; d++) {
978 out_head[d] += w * v_t[d];
979 }
980 }
981 }
982
983 /* ═══════════════════════════════════════════════════════════════════════
984 * STEP 2: Output Projection (Q4_K) + Residual
985 * ═══════════════════════════════════════════════════════════════════════ */
986
987 gemv_q4_k(hidden_after_attn, wo, attn_out, embed_dim, q_dim);
988
989 for (int i = 0; i < embed_dim; i++) {
990 hidden_after_attn[i] += residual_in[i];
991 }
992
993 /* ═══════════════════════════════════════════════════════════════════════
994 * STEP 3: RMSNorm (for MLP)
995 * ═══════════════════════════════════════════════════════════════════════ */
996
997 float sum_sq = 0.0f;
998 for (int i = 0; i < embed_dim; i++) {
999 sum_sq += hidden_after_attn[i] * hidden_after_attn[i];
1000 }
1001 float rms_scale = 1.0f / sqrtf(sum_sq / embed_dim + eps);
1002
1003 for (int i = 0; i < embed_dim; i++) {
1004 normed_mlp[i] = hidden_after_attn[i] * rms_weight_mlp[i] * rms_scale;
1005 }
1006
1007 /* ═══════════════════════════════════════════════════════════════════════
1008 * STEP 4-6: MLP (gate + up + SwiGLU + down)
1009 * ═══════════════════════════════════════════════════════════════════════ */
1010
1011 gemv_q4_k(gate_out, w_gate, normed_mlp, intermediate_dim, embed_dim);
1012 gemv_q4_k(up_out, w_up, normed_mlp, intermediate_dim, embed_dim);
1013
1014 /* SwiGLU: silu(gate) * up */
1015 for (int i = 0; i < intermediate_dim; i++) {
1016 float g = gate_out[i];
1017 float silu_g = g / (1.0f + expf(-g));
1018 gate_out[i] = silu_g * up_out[i];
1019 }
1020
1021 /* Down projection - output DIRECTLY to hidden_out (no intermediate buffer!) */
1022 gemv_q4_k(hidden_out, w_down, gate_out, embed_dim, intermediate_dim);
1023
1024 /* MLP residual - hidden_out now contains the final hidden state */
1025 for (int i = 0; i < embed_dim; i++) {
1026 hidden_out[i] += hidden_after_attn[i];
1027 }
1028
1029 /* ═══════════════════════════════════════════════════════════════════════
1030 * STEP 7: RMSNorm (for NEXT layer's attention)
1031 * Read from hidden_out (already contains final hidden state)
1032 * ═══════════════════════════════════════════════════════════════════════ */
1033
1034 sum_sq = 0.0f;
1035 for (int i = 0; i < embed_dim; i++) {
1036 sum_sq += hidden_out[i] * hidden_out[i];
1037 }
1038 rms_scale = 1.0f / sqrtf(sum_sq / embed_dim + eps);
1039
1040 for (int i = 0; i < embed_dim; i++) {
1041 normed_attn[i] = hidden_out[i] * rms_weight_attn[i] * rms_scale;
1042 }
1043
1044 /* ═══════════════════════════════════════════════════════════════════════
1045 * STEP 8: NEXT LAYER's Q, K, V Projections
1046 *
1047 * Q goes to caller (for attention computation)
1048 * K, V go to KV cache (DRAM write - this is intentional!)
1049 * ═══════════════════════════════════════════════════════════════════════ */
1050
1051 gemv_q4_k(q_next, wq_next, normed_attn, q_dim, embed_dim);
1052 gemv_q4_k(k_next, wk_next, normed_attn, kv_dim, embed_dim);
1053 gemv_q4_k(v_next, wv_next, normed_attn, kv_dim, embed_dim);
1054
1055 /* hidden_out already contains the final hidden state - no memcpy needed! */
1056}

References gemv_q4_k(), and score.

◆ mlp_fused_fp32_v2()

void mlp_fused_fp32_v2 ( const float *  hidden_in,
const float *  rms_weight,
float  eps,
const float *  w_gate,
const float *  w_up,
const float *  w_down,
int  embed_dim,
int  intermediate_dim,
float *  hidden_out 
)

Definition at line 385 of file attention_mlp_fused.c.

404 {
405 /* Stack buffers - sized for typical models */
406 float normed[4096];
407 float swiglu[16384]; /* intermediate_dim */
408
409 if (embed_dim > 4096 || intermediate_dim > 16384) {
410 return; /* TODO: handle larger models */
411 }
412
413 /* ═══════════════════════════════════════════════════════════════════════
414 * STEP 1: RMSNorm (SIMD)
415 * ═══════════════════════════════════════════════════════════════════════ */
416
417 float rms_scale = compute_rms_scale_internal(hidden_in, embed_dim, eps);
418
419#ifdef __AVX2__
420 __m256 vscale = _mm256_set1_ps(rms_scale);
421 int i = 0;
422 for (; i + 7 < embed_dim; i += 8) {
423 __m256 vh = _mm256_loadu_ps(hidden_in + i);
424 __m256 vw = _mm256_loadu_ps(rms_weight + i);
425 __m256 vn = _mm256_mul_ps(_mm256_mul_ps(vh, vw), vscale);
426 _mm256_storeu_ps(normed + i, vn);
427 }
428 for (; i < embed_dim; i++) {
429 normed[i] = hidden_in[i] * rms_weight[i] * rms_scale;
430 }
431#else
432 for (int i = 0; i < embed_dim; i++) {
433 normed[i] = hidden_in[i] * rms_weight[i] * rms_scale;
434 }
435#endif
436
437 /* ═══════════════════════════════════════════════════════════════════════
438 * STEP 2: Gate + Up projections with TRUE FUSION + SwiGLU
439 *
440 * Key insight: Compute gate[i] and up[i] together, then immediately
441 * apply SwiGLU. This eliminates separate gate_out and up_out buffers.
442 * ═══════════════════════════════════════════════════════════════════════ */
443
444#ifdef __AVX2__
445 for (int j = 0; j < intermediate_dim; j++) {
446 /* Compute gate and up for output j using SIMD GEMV */
447 const float *wg_row = w_gate + j * embed_dim;
448 const float *wu_row = w_up + j * embed_dim;
449
450 __m256 gate_acc = _mm256_setzero_ps();
451 __m256 up_acc = _mm256_setzero_ps();
452
453 int k = 0;
454 for (; k + 7 < embed_dim; k += 8) {
455 __m256 vn = _mm256_loadu_ps(normed + k);
456 __m256 vwg = _mm256_loadu_ps(wg_row + k);
457 __m256 vwu = _mm256_loadu_ps(wu_row + k);
458
459 gate_acc = _mm256_fmadd_ps(vwg, vn, gate_acc);
460 up_acc = _mm256_fmadd_ps(vwu, vn, up_acc);
461 }
462
463 /* Horizontal sums */
464 __m128 glow = _mm256_castps256_ps128(gate_acc);
465 __m128 ghigh = _mm256_extractf128_ps(gate_acc, 1);
466 glow = _mm_add_ps(glow, ghigh);
467 __m128 gshuf = _mm_movehdup_ps(glow);
468 glow = _mm_add_ps(glow, gshuf);
469 gshuf = _mm_movehl_ps(gshuf, glow);
470 glow = _mm_add_ss(glow, gshuf);
471 float gate_val = _mm_cvtss_f32(glow);
472
473 __m128 ulow = _mm256_castps256_ps128(up_acc);
474 __m128 uhigh = _mm256_extractf128_ps(up_acc, 1);
475 ulow = _mm_add_ps(ulow, uhigh);
476 __m128 ushuf = _mm_movehdup_ps(ulow);
477 ulow = _mm_add_ps(ulow, ushuf);
478 ushuf = _mm_movehl_ps(ushuf, ulow);
479 ulow = _mm_add_ss(ulow, ushuf);
480 float up_val = _mm_cvtss_f32(ulow);
481
482 /* Remainder */
483 for (; k < embed_dim; k++) {
484 gate_val += wg_row[k] * normed[k];
485 up_val += wu_row[k] * normed[k];
486 }
487
488 /* Fused SwiGLU: silu(gate) * up */
489 swiglu[j] = silu_scalar(gate_val) * up_val;
490 }
491#else
492 for (int j = 0; j < intermediate_dim; j++) {
493 const float *wg_row = w_gate + j * embed_dim;
494 const float *wu_row = w_up + j * embed_dim;
495 float gate_val = 0.0f, up_val = 0.0f;
496
497 for (int k = 0; k < embed_dim; k++) {
498 gate_val += wg_row[k] * normed[k];
499 up_val += wu_row[k] * normed[k];
500 }
501
502 swiglu[j] = silu_scalar(gate_val) * up_val;
503 }
504#endif
505
506 /* ═══════════════════════════════════════════════════════════════════════
507 * STEP 3: Down projection + Residual (SIMD GEMV)
508 * ═══════════════════════════════════════════════════════════════════════ */
509
510#ifdef __AVX2__
511 for (int j = 0; j < embed_dim; j++) {
512 float sum = gemv_fp32_row_avx2(w_down + j * intermediate_dim, swiglu, intermediate_dim);
513 hidden_out[j] = sum + hidden_in[j]; /* Residual */
514 }
515#else
516 for (int j = 0; j < embed_dim; j++) {
517 float sum = 0.0f;
518 const float *wd_row = w_down + j * intermediate_dim;
519 for (int k = 0; k < intermediate_dim; k++) {
520 sum += wd_row[k] * swiglu[k];
521 }
522 hidden_out[j] = sum + hidden_in[j];
523 }
524#endif
525}

References compute_rms_scale_internal(), and silu_scalar().

◆ mlp_fused_fp32_v3()

void mlp_fused_fp32_v3 ( const float *  hidden_in,
const float *  rms_weight,
float  eps,
const float *  w_gate,
const float *  w_up,
const float *  w_down,
int  embed_dim,
int  intermediate_dim,
float *  hidden_out 
)

Definition at line 542 of file attention_mlp_fused.c.

552 {
553 /* Stack buffers */
554 float normed[4096];
555 float gate_out[16384];
556 float swiglu[16384];
557
558 if (embed_dim > 4096 || intermediate_dim > 16384) {
559 return;
560 }
561
562 /* ═══════════════════════════════════════════════════════════════════════
563 * STEP 1: RMSNorm (SIMD)
564 * ═══════════════════════════════════════════════════════════════════════ */
565
566 float rms_scale = compute_rms_scale_internal(hidden_in, embed_dim, eps);
567
568#ifdef __AVX2__
569 __m256 vscale = _mm256_set1_ps(rms_scale);
570 int i = 0;
571 for (; i + 7 < embed_dim; i += 8) {
572 __m256 vh = _mm256_loadu_ps(hidden_in + i);
573 __m256 vw = _mm256_loadu_ps(rms_weight + i);
574 __m256 vn = _mm256_mul_ps(_mm256_mul_ps(vh, vw), vscale);
575 _mm256_storeu_ps(normed + i, vn);
576 }
577 for (; i < embed_dim; i++) {
578 normed[i] = hidden_in[i] * rms_weight[i] * rms_scale;
579 }
580#else
581 for (int i = 0; i < embed_dim; i++) {
582 normed[i] = hidden_in[i] * rms_weight[i] * rms_scale;
583 }
584#endif
585
586 /* ═══════════════════════════════════════════════════════════════════════
587 * STEP 2: Gate projection (SIMD GEMV, sequential weight access)
588 * ═══════════════════════════════════════════════════════════════════════ */
589
590#ifdef __AVX2__
591 for (int j = 0; j < intermediate_dim; j++) {
592 gate_out[j] = gemv_fp32_row_avx2(w_gate + j * embed_dim, normed, embed_dim);
593 }
594#else
595 for (int j = 0; j < intermediate_dim; j++) {
596 float sum = 0.0f;
597 const float *wg_row = w_gate + j * embed_dim;
598 for (int k = 0; k < embed_dim; k++) {
599 sum += wg_row[k] * normed[k];
600 }
601 gate_out[j] = sum;
602 }
603#endif
604
605 /* ═══════════════════════════════════════════════════════════════════════
606 * STEP 3: Up projection + FUSED SwiGLU (SIMD GEMV, sequential access)
607 *
608 * Key: compute up[j], then immediately apply SwiGLU with gate[j].
609 * This avoids storing the full up_out buffer.
610 * ═══════════════════════════════════════════════════════════════════════ */
611
612#ifdef __AVX2__
613 for (int j = 0; j < intermediate_dim; j++) {
614 float up_val = gemv_fp32_row_avx2(w_up + j * embed_dim, normed, embed_dim);
615 /* Fused SwiGLU: silu(gate) * up */
616 swiglu[j] = silu_scalar(gate_out[j]) * up_val;
617 }
618#else
619 for (int j = 0; j < intermediate_dim; j++) {
620 float up_val = 0.0f;
621 const float *wu_row = w_up + j * embed_dim;
622 for (int k = 0; k < embed_dim; k++) {
623 up_val += wu_row[k] * normed[k];
624 }
625 swiglu[j] = silu_scalar(gate_out[j]) * up_val;
626 }
627#endif
628
629 /* ═══════════════════════════════════════════════════════════════════════
630 * STEP 4: Down projection + Residual (SIMD GEMV)
631 * ═══════════════════════════════════════════════════════════════════════ */
632
633#ifdef __AVX2__
634 for (int j = 0; j < embed_dim; j++) {
635 float sum = gemv_fp32_row_avx2(w_down + j * intermediate_dim, swiglu, intermediate_dim);
636 hidden_out[j] = sum + hidden_in[j];
637 }
638#else
639 for (int j = 0; j < embed_dim; j++) {
640 float sum = 0.0f;
641 const float *wd_row = w_down + j * intermediate_dim;
642 for (int k = 0; k < intermediate_dim; k++) {
643 sum += wd_row[k] * swiglu[k];
644 }
645 hidden_out[j] = sum + hidden_in[j];
646 }
647#endif
648}

References compute_rms_scale_internal(), and silu_scalar().

◆ mlp_separate_fp32()

void mlp_separate_fp32 ( const float *  hidden_in,
const float *  rms_weight,
float  eps,
const float *  w_gate,
const float *  w_up,
const float *  w_down,
float *  normed_buf,
float *  gate_buf,
float *  up_buf,
int  embed_dim,
int  intermediate_dim,
float *  hidden_out 
)

Definition at line 657 of file attention_mlp_fused.c.

670 {
671 /* Step 1: RMSNorm */
672 float rms_scale = compute_rms_scale_internal(hidden_in, embed_dim, eps);
673 for (int i = 0; i < embed_dim; i++) {
674 normed_buf[i] = hidden_in[i] * rms_weight[i] * rms_scale;
675 }
676
677 /* Step 2: Gate projection */
678 for (int j = 0; j < intermediate_dim; j++) {
679 float sum = 0.0f;
680 const float *wg_row = w_gate + j * embed_dim;
681 for (int k = 0; k < embed_dim; k++) {
682 sum += wg_row[k] * normed_buf[k];
683 }
684 gate_buf[j] = sum;
685 }
686
687 /* Step 3: Up projection */
688 for (int j = 0; j < intermediate_dim; j++) {
689 float sum = 0.0f;
690 const float *wu_row = w_up + j * embed_dim;
691 for (int k = 0; k < embed_dim; k++) {
692 sum += wu_row[k] * normed_buf[k];
693 }
694 up_buf[j] = sum;
695 }
696
697 /* Step 4: SwiGLU */
698 for (int j = 0; j < intermediate_dim; j++) {
699 gate_buf[j] = silu_scalar(gate_buf[j]) * up_buf[j];
700 }
701
702 /* Step 5: Down projection + Residual */
703 for (int j = 0; j < embed_dim; j++) {
704 float sum = 0.0f;
705 const float *wd_row = w_down + j * intermediate_dim;
706 for (int k = 0; k < intermediate_dim; k++) {
707 sum += wd_row[k] * gate_buf[k];
708 }
709 hidden_out[j] = sum + hidden_in[j];
710 }
711}

References compute_rms_scale_internal(), and silu_scalar().

◆ silu_scalar()

static float silu_scalar ( float  x)
inlinestatic

◆ softmax_inplace()

static void softmax_inplace ( float *  x,
int  n 
)
static

Definition at line 128 of file attention_mlp_fused.c.

128 {
129 float max_val = x[0];
130 for (int i = 1; i < n; i++) {
131 if (x[i] > max_val) max_val = x[i];
132 }
133
134 float sum = 0.0f;
135 for (int i = 0; i < n; i++) {
136 x[i] = expf(x[i] - max_val);
137 sum += x[i];
138 }
139
140 float inv_sum = 1.0f / sum;
141 for (int i = 0; i < n; i++) {
142 x[i] *= inv_sum;
143 }
144}

Referenced by attention_mlp_fused_fp32(), attention_mlp_fused_q4k(), and attention_mlp_separate_fp32().