← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
swiglu_kernels.c
Go to the documentation of this file.
1/**
2 * @file swiglu_kernels.c
3 * @brief SwiGLU activation 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 * SwiGLU: y = silu(gate) * up = (gate * sigmoid(gate)) * up
15 */
16
17#ifndef _GNU_SOURCE
18#define _GNU_SOURCE
19#endif
20
21#include "bf16_utils.h"
22#include "ck_threadpool.h"
23#include "ckernel_engine.h"
24#include "ckernel_quant.h"
25#include <dlfcn.h>
26#include <math.h>
27#include <pthread.h>
28#include <stddef.h>
29#include <stdio.h>
30#include <stdlib.h>
31
32#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
33#include <immintrin.h>
34#endif
35
36#if defined(__ARM_NEON) || defined(__aarch64__)
37#include <arm_neon.h>
38#endif
39
40/*
41 * PyTorch-parity sigmoid for strict/exact SwiGLU paths.
42 * Keep this in fp32 to match ATen CPU opmath more closely.
43 */
44static inline float sigmoid_scalar_parity(float x)
45{
46 return 1.0f / (1.0f + expf(-x));
47}
48
49/* ========================================================================== */
50/* Fast exp approximation for SIMD */
51/* ========================================================================== */
52
53#if defined(__AVX512F__)
54// AVX-512 fast exp approximation
55static inline __m512 exp512_fast(__m512 x) {
56 // Clamp to avoid overflow/underflow
57 x = _mm512_max_ps(x, _mm512_set1_ps(-88.0f));
58 x = _mm512_min_ps(x, _mm512_set1_ps(88.0f));
59
60 // exp(x) = 2^(x * log2(e))
61 const __m512 log2e = _mm512_set1_ps(1.4426950408889634f);
62 __m512 z = _mm512_mul_ps(x, log2e);
63
64 // Split into integer and fractional parts
65 __m512 zf = _mm512_roundscale_ps(z, _MM_FROUND_TO_NEAREST_INT);
66 __m512 f = _mm512_sub_ps(z, zf);
67
68 // Polynomial for 2^f, f in [-0.5, 0.5]
69 const __m512 c0 = _mm512_set1_ps(1.0f);
70 const __m512 c1 = _mm512_set1_ps(0.6931471805599453f);
71 const __m512 c2 = _mm512_set1_ps(0.2402265069591007f);
72 const __m512 c3 = _mm512_set1_ps(0.05550410866482158f);
73 const __m512 c4 = _mm512_set1_ps(0.009618129107628478f);
74
75 __m512 poly = _mm512_fmadd_ps(f, c4, c3);
76 poly = _mm512_fmadd_ps(f, poly, c2);
77 poly = _mm512_fmadd_ps(f, poly, c1);
78 poly = _mm512_fmadd_ps(f, poly, c0);
79
80 // Scale by 2^n
81 __m512i zi = _mm512_cvtps_epi32(zf);
82 zi = _mm512_add_epi32(zi, _mm512_set1_epi32(127));
83 zi = _mm512_slli_epi32(zi, 23);
84 __m512 scale = _mm512_castsi512_ps(zi);
85
86 return _mm512_mul_ps(poly, scale);
87}
88
89// AVX-512 sigmoid: 1 / (1 + exp(-x))
90static inline __m512 sigmoid512_fast(__m512 x) {
91 __m512 neg_x = _mm512_sub_ps(_mm512_setzero_ps(), x);
92 __m512 exp_neg = exp512_fast(neg_x);
93 __m512 one = _mm512_set1_ps(1.0f);
94 return _mm512_div_ps(one, _mm512_add_ps(one, exp_neg));
95}
96#endif
97
98#if defined(__AVX2__)
99// AVX2 fast exp approximation (needs FMA and integer ops)
100static inline __m256 exp256_fast(__m256 x) {
101 // Clamp
102 x = _mm256_max_ps(x, _mm256_set1_ps(-88.0f));
103 x = _mm256_min_ps(x, _mm256_set1_ps(88.0f));
104
105 // exp(x) = 2^(x * log2(e))
106 const __m256 log2e = _mm256_set1_ps(1.4426950408889634f);
107 __m256 z = _mm256_mul_ps(x, log2e);
108
109 // Round to nearest integer
110 __m256 zf = _mm256_round_ps(z, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
111 __m256 f = _mm256_sub_ps(z, zf);
112
113 // Polynomial for 2^f
114 const __m256 c0 = _mm256_set1_ps(1.0f);
115 const __m256 c1 = _mm256_set1_ps(0.6931471805599453f);
116 const __m256 c2 = _mm256_set1_ps(0.2402265069591007f);
117 const __m256 c3 = _mm256_set1_ps(0.05550410866482158f);
118 const __m256 c4 = _mm256_set1_ps(0.009618129107628478f);
119
120 __m256 poly = _mm256_fmadd_ps(f, c4, c3);
121 poly = _mm256_fmadd_ps(f, poly, c2);
122 poly = _mm256_fmadd_ps(f, poly, c1);
123 poly = _mm256_fmadd_ps(f, poly, c0);
124
125 // Scale by 2^n
126 __m256i zi = _mm256_cvtps_epi32(zf);
127 zi = _mm256_add_epi32(zi, _mm256_set1_epi32(127));
128 zi = _mm256_slli_epi32(zi, 23);
129 __m256 scale = _mm256_castsi256_ps(zi);
130
131 return _mm256_mul_ps(poly, scale);
132}
133
134// AVX2 sigmoid
135static inline __m256 sigmoid256_fast(__m256 x) {
136 __m256 neg_x = _mm256_sub_ps(_mm256_setzero_ps(), x);
137 __m256 exp_neg = exp256_fast(neg_x);
138 __m256 one = _mm256_set1_ps(1.0f);
139 return _mm256_div_ps(one, _mm256_add_ps(one, exp_neg));
140}
141
142/* GGML's parity path uses this specific vector exponential approximation. */
143static inline __m256 ck_ggml_expf_avx2(__m256 x) {
144 const __m256 r = _mm256_set1_ps(0x1.8p23f);
145 const __m256 z = _mm256_fmadd_ps(x, _mm256_set1_ps(0x1.715476p+0f), r);
146 const __m256 n = _mm256_sub_ps(z, r);
147 const __m256 b = _mm256_fnmadd_ps(
148 n,
149 _mm256_set1_ps(0x1.7f7d1cp-20f),
150 _mm256_fnmadd_ps(n, _mm256_set1_ps(0x1.62e4p-1f), x));
151 const __m256i e = _mm256_slli_epi32(_mm256_castps_si256(z), 23);
152 const __m256 k = _mm256_castsi256_ps(
153 _mm256_add_epi32(e, _mm256_castps_si256(_mm256_set1_ps(1))));
154 const __m256i c = _mm256_castps_si256(
155 _mm256_cmp_ps(_mm256_andnot_ps(_mm256_set1_ps(-0.0f), n),
156 _mm256_set1_ps(126), _CMP_GT_OQ));
157 const __m256 u = _mm256_mul_ps(b, b);
158 const __m256 j = _mm256_fmadd_ps(
159 _mm256_fmadd_ps(
160 _mm256_fmadd_ps(_mm256_set1_ps(0x1.0e4020p-7f), b,
161 _mm256_set1_ps(0x1.573e2ep-5f)),
162 u,
163 _mm256_fmadd_ps(_mm256_set1_ps(0x1.555e66p-3f), b,
164 _mm256_set1_ps(0x1.fffdb6p-2f))),
165 u,
166 _mm256_mul_ps(_mm256_set1_ps(0x1.ffffecp-1f), b));
167 if (!_mm256_movemask_ps(_mm256_castsi256_ps(c))) {
168 return _mm256_fmadd_ps(j, k, k);
169 }
170 const __m256i g = _mm256_and_si256(
171 _mm256_castps_si256(_mm256_cmp_ps(n, _mm256_setzero_ps(), _CMP_LE_OQ)),
172 _mm256_set1_epi32((int)0x82000000u));
173 const __m256 s1 = _mm256_castsi256_ps(
174 _mm256_add_epi32(g, _mm256_set1_epi32(0x7f000000u)));
175 const __m256 s2 = _mm256_castsi256_ps(_mm256_sub_epi32(e, g));
176 const __m256i d = _mm256_castps_si256(
177 _mm256_cmp_ps(_mm256_andnot_ps(_mm256_set1_ps(-0.0f), n),
178 _mm256_set1_ps(192), _CMP_GT_OQ));
179 return _mm256_or_ps(
180 _mm256_and_ps(_mm256_castsi256_ps(d), _mm256_mul_ps(s1, s1)),
181 _mm256_andnot_ps(
182 _mm256_castsi256_ps(d),
183 _mm256_or_ps(
184 _mm256_and_ps(_mm256_castsi256_ps(c),
185 _mm256_mul_ps(_mm256_fmadd_ps(s2, j, s2), s1)),
186 _mm256_andnot_ps(_mm256_castsi256_ps(c),
187 _mm256_fmadd_ps(k, j, k)))));
188}
189#endif
190
191#if defined(__AVX512F__) && defined(__AVX512DQ__)
192/* Keep the production parity provider aligned with llama.cpp's AVX-512
193 * exponential approximation and instruction grouping. */
194static inline __m512 ck_ggml_expf_avx512(__m512 x) {
195 const __m512 r = _mm512_set1_ps(0x1.8p23f);
196 const __m512 z = _mm512_fmadd_ps(x, _mm512_set1_ps(0x1.715476p+0f), r);
197 const __m512 n = _mm512_sub_ps(z, r);
198 const __m512 b = _mm512_fnmadd_ps(
199 n, _mm512_set1_ps(0x1.7f7d1cp-20f),
200 _mm512_fnmadd_ps(n, _mm512_set1_ps(0x1.62e4p-1f), x));
201 const __mmask16 d = _mm512_cmp_ps_mask(
202 _mm512_abs_ps(n), _mm512_set1_ps(192.0f), _CMP_GT_OQ);
203 const __m512 u = _mm512_mul_ps(b, b);
204 const __m512 j = _mm512_fmadd_ps(
205 _mm512_fmadd_ps(
206 _mm512_fmadd_ps(
207 _mm512_set1_ps(0x1.0e4020p-7f), b,
208 _mm512_set1_ps(0x1.573e2ep-5f)),
209 u,
210 _mm512_fmadd_ps(
211 _mm512_set1_ps(0x1.555e66p-3f), b,
212 _mm512_set1_ps(0x1.fffdb6p-2f))),
213 u,
214 _mm512_fmadd_ps(
215 _mm512_set1_ps(0x1.ffffecp-1f), b,
216 _mm512_set1_ps(1.0f)));
217 const __m512 res = _mm512_scalef_ps(j, n);
218 if (_mm512_kortestz(d, d)) {
219 return res;
220 }
221 const __m512 zero = _mm512_setzero_ps();
222 const __m512 alt = _mm512_mask_blend_ps(
223 _mm512_cmp_ps_mask(n, zero, _CMP_LE_OQ),
224 _mm512_set1_ps(INFINITY),
225 zero);
226 return _mm512_mask_blend_ps(d, res, alt);
227}
228#endif
229
230#if defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
231/* NEON port of the same ggml vector exponential the AVX2 provider above
232 * implements; this is llama.cpp's own AArch64 ggml_v_expf (arm limited
233 * optimized-routines polynomial, max error 1.45358 + 0.5 ulps, flushes above
234 * 88.38 / below -103.97). On AArch64 this is the instruction-level oracle
235 * ggml itself uses, so the numerical contract is identical to the AVX2
236 * branch: approximation, not libm expf. Uses vfmaq_f32 (fused), so it
237 * requires FP FMA (baseline on AArch64); 32-bit NEON without FMA keeps the
238 * scalar expf tail. The any-lane predicate uses vgetq_lane_u64 instead of
239 * the AArch64-only vpaddd_u64 so the source stays portable NEON. */
240static inline float32x4_t ck_ggml_expf_neon(float32x4_t x) {
241 const float32x4_t r = vdupq_n_f32(0x1.8p23f);
242 const float32x4_t z = vfmaq_f32(r, x, vdupq_n_f32(0x1.715476p+0f));
243 const float32x4_t n = vsubq_f32(z, r);
244 const float32x4_t b = vfmsq_f32(vfmsq_f32(x, n, vdupq_n_f32(0x1.62e4p-1f)), n,
245 vdupq_n_f32(0x1.7f7d1cp-20f));
246 const uint32x4_t e = vshlq_n_u32(vreinterpretq_u32_f32(z), 23);
247 const float32x4_t k = vreinterpretq_f32_u32(vaddq_u32(e, vreinterpretq_u32_f32(vdupq_n_f32(1))));
248 const uint32x4_t c = vcagtq_f32(n, vdupq_n_f32(126));
249 const float32x4_t u = vmulq_f32(b, b);
250 const float32x4_t j = vfmaq_f32(
251 vmulq_f32(vdupq_n_f32(0x1.ffffecp-1f), b),
252 vfmaq_f32(vfmaq_f32(vdupq_n_f32(0x1.fffdb6p-2f), vdupq_n_f32(0x1.555e66p-3f), b),
253 vfmaq_f32(vdupq_n_f32(0x1.573e2ep-5f), vdupq_n_f32(0x1.0e4020p-7f), b), u), u);
254 const uint64x2_t c64 = vreinterpretq_u64_u32(c);
255 if (!(vgetq_lane_u64(c64, 0) | vgetq_lane_u64(c64, 1))) {
256 return vfmaq_f32(k, j, k);
257 }
258 const uint32x4_t d = vandq_u32(vclezq_f32(n), vdupq_n_u32(0x82000000));
259 const float32x4_t s1 = vreinterpretq_f32_u32(vaddq_u32(d, vdupq_n_u32(0x7f000000)));
260 const float32x4_t s2 = vreinterpretq_f32_u32(vsubq_u32(e, d));
261 return vbslq_f32(vcagtq_f32(n, vdupq_n_f32(192)), vmulq_f32(s1, s1),
262 vbslq_f32(c, vmulq_f32(vfmaq_f32(s2, s2, j), s1), vfmaq_f32(k, k, j)));
263}
264
265static inline int ck_swiglu_ggml_ref_forced(void) {
266 const char *env = getenv("CK_DEBUG_SWIGLU_GGML_REF");
267 return env && atoi(env) != 0;
268}
269#endif
270
271/**
272 * SwiGLU forward pass
273 * @test test_swiglu.py::TestSwiGLUForward::test_forward_tokens
274 * @test test_swiglu.py::TestSwiGLUForward::test_forward_single
275 * @test test_mlp.py::TestMLPForward::test_swiglu_mlp
276 * @test test_fused_swiglu_decode.py::TestFusedSwiGLUDecode::test_fused_swiglu_decode
277 * @test test_parity.py::test_swiglu_parity
278 *
279 * SwiGLU: y = silu(gate) * up where silu(x) = x * sigmoid(x)
280 *
281 * After changes: make test && make llamacpp-parity-full
282 */
283void swiglu_forward(const float *input,
284 float *output,
285 int tokens,
286 int dim)
287{
288 const char *fast_env = getenv("CK_SWIGLU_FAST");
289 const char *exact_env = getenv("CK_SWIGLU_EXACT");
291 !(fast_env && atoi(fast_env) != 0) ||
292 (exact_env && atoi(exact_env) != 0)) {
293 swiglu_forward_exact(input, output, tokens, dim);
294 return;
295 }
296
297 int T = tokens;
298 int D = dim;
299
300 for (int t = 0; t < T; ++t) {
301 const float *row = input + (size_t)t * (2 * D);
302 float *out_row = output + (size_t)t * D;
303 int d = 0;
304
305#if defined(__AVX512F__)
306 // AVX-512: Process 16 floats at a time
307 for (; d + 16 <= D; d += 16) {
308 __m512 a = _mm512_loadu_ps(&row[d]); // gate
309 __m512 b = _mm512_loadu_ps(&row[D + d]); // value
310
311 __m512 s = sigmoid512_fast(a); // sigmoid(a)
312 __m512 silu = _mm512_mul_ps(a, s); // silu(a) = a * sigmoid(a)
313 __m512 y = _mm512_mul_ps(silu, b); // y = silu(a) * b
314
315 _mm512_storeu_ps(&out_row[d], y);
316 }
317#elif defined(__AVX2__)
318 // AVX2: Process 8 floats at a time
319 for (; d + 8 <= D; d += 8) {
320 __m256 a = _mm256_loadu_ps(&row[d]); // gate
321 __m256 b = _mm256_loadu_ps(&row[D + d]); // value
322
323 __m256 s = sigmoid256_fast(a); // sigmoid(a)
324 __m256 silu = _mm256_mul_ps(a, s); // silu(a) = a * sigmoid(a)
325 __m256 y = _mm256_mul_ps(silu, b); // y = silu(a) * b
326
327 _mm256_storeu_ps(&out_row[d], y);
328 }
329#elif defined(__AVX__)
330 // AVX1: Vectorize arithmetic, use scalar sigmoid
331 float a_arr[8] __attribute__((aligned(32)));
332 float s_arr[8] __attribute__((aligned(32)));
333
334 for (; d + 8 <= D; d += 8) {
335 __m256 a = _mm256_loadu_ps(&row[d]); // gate
336 __m256 b = _mm256_loadu_ps(&row[D + d]); // value
337
338 // Compute sigmoid scalarly
339 _mm256_store_ps(a_arr, a);
340 for (int j = 0; j < 8; ++j) {
341 s_arr[j] = sigmoid_scalar(a_arr[j]);
342 }
343 __m256 s = _mm256_load_ps(s_arr);
344
345 __m256 silu = _mm256_mul_ps(a, s); // silu(a) = a * sigmoid(a)
346 __m256 y = _mm256_mul_ps(silu, b); // y = silu(a) * b
347
348 _mm256_storeu_ps(&out_row[d], y);
349 }
350#endif
351
352 // Scalar fallback for remaining elements
353 for (; d < D; ++d) {
354 float a = row[d]; // gate
355 float b = row[D + d]; // value
356
357 float s = sigmoid_scalar(a); // sigmoid(a)
358 float silu = a * s; // silu(a) = a * sigmoid(a)
359
360 out_row[d] = silu * b;
361 }
362 }
363}
364
365void swiglu_forward_q8_k(const float *input,
366 void *output_q8,
367 int tokens,
368 int dim)
369{
370 if (!input || !output_q8 || tokens <= 0 || dim <= 0) {
371 return;
372 }
373 if ((dim % QK_K) != 0) {
374 return;
375 }
376
377 const char *fast_env = getenv("CK_SWIGLU_FAST");
378 const char *exact_env = getenv("CK_SWIGLU_EXACT");
379 const int use_fast = !ck_strict_parity_enabled() &&
380 (fast_env && atoi(fast_env) != 0) &&
381 !(exact_env && atoi(exact_env) != 0);
382
383 const int blocks_per_row = dim / QK_K;
384 block_q8_K *q8 = (block_q8_K *)output_q8;
385 float tmp[QK_K];
386
387 for (int t = 0; t < tokens; ++t) {
388 const float *row = input + (size_t)t * (size_t)(2 * dim);
389 block_q8_K *q8_row = q8 + (size_t)t * (size_t)blocks_per_row;
390
391 for (int block = 0; block < blocks_per_row; ++block) {
392 const int base = block * QK_K;
393 int d = 0;
394
395#if defined(__AVX2__)
396 if (use_fast) {
397 for (; d + 8 <= QK_K; d += 8) {
398 const __m256 a = _mm256_loadu_ps(row + base + d);
399 const __m256 b = _mm256_loadu_ps(row + dim + base + d);
400 const __m256 s = sigmoid256_fast(a);
401 const __m256 y = _mm256_mul_ps(_mm256_mul_ps(a, s), b);
402 _mm256_storeu_ps(tmp + d, y);
403 }
404 }
405#else
406 (void)use_fast;
407#endif
408
409 for (; d < QK_K; ++d) {
410 const float a = row[base + d];
411 const float b = row[dim + base + d];
412 const float s = use_fast ? sigmoid_scalar(a) : sigmoid_scalar_parity(a);
413 tmp[d] = (a * s) * b;
414 }
415
416 quantize_row_q8_k(tmp, (void *)&q8_row[block], QK_K);
417 }
418 }
419}
420
421/**
422 * SwiGLU backward pass
423 * @test test_swiglu.py::TestSwiGLUBackward::test_backward_tokens
424 * @test test_swiglu.py::TestSwiGLUBackward::test_backward_single
425 * @test test_parity.py::test_swiglu_backward_parity
426 *
427 * Computes dGate and dUp given dY.
428 * dGate = dy * b * silu'(a), dUp = dy * silu(a)
429 *
430 * After changes: make test && make llamacpp-parity-full
431 */
432void swiglu_backward(const float *input,
433 const float *d_output,
434 float *d_input,
435 int tokens,
436 int dim)
437{
439 swiglu_backward_exact(input, d_output, d_input, tokens, dim);
440 return;
441 }
442
443 int T = tokens;
444 int D = dim;
445
446 for (int t = 0; t < T; ++t) {
447 const float *row = input + (size_t)t * (2 * D);
448 const float *dy_row = d_output + (size_t)t * D;
449 float *dx_row = d_input + (size_t)t * (2 * D);
450 int d = 0;
451
452#if defined(__AVX512F__)
453 // AVX-512: Process 16 floats at a time
454 __m512 one = _mm512_set1_ps(1.0f);
455 for (; d + 16 <= D; d += 16) {
456 __m512 a = _mm512_loadu_ps(&row[d]); // gate
457 __m512 b = _mm512_loadu_ps(&row[D + d]); // value
458 __m512 dy = _mm512_loadu_ps(&dy_row[d]);
459
460 __m512 s = sigmoid512_fast(a); // sigmoid(a)
461 __m512 silu = _mm512_mul_ps(a, s); // silu(a) = a * s
462 __m512 one_minus_s = _mm512_sub_ps(one, s);
463 __m512 inner = _mm512_fmadd_ps(a, one_minus_s, one); // 1 + a * (1 - s)
464 __m512 silu_prime = _mm512_mul_ps(s, inner); // s * (1 + a * (1 - s))
465
466 // dA = dy * b * silu_prime
467 __m512 dA = _mm512_mul_ps(dy, _mm512_mul_ps(b, silu_prime));
468 // dB = dy * silu
469 __m512 dB = _mm512_mul_ps(dy, silu);
470
471 _mm512_storeu_ps(&dx_row[d], dA);
472 _mm512_storeu_ps(&dx_row[D + d], dB);
473 }
474#elif defined(__AVX2__)
475 // AVX2: Process 8 floats at a time
476 __m256 one = _mm256_set1_ps(1.0f);
477 for (; d + 8 <= D; d += 8) {
478 __m256 a = _mm256_loadu_ps(&row[d]); // gate
479 __m256 b = _mm256_loadu_ps(&row[D + d]); // value
480 __m256 dy = _mm256_loadu_ps(&dy_row[d]);
481
482 __m256 s = sigmoid256_fast(a); // sigmoid(a)
483 __m256 silu = _mm256_mul_ps(a, s); // silu(a) = a * s
484 __m256 one_minus_s = _mm256_sub_ps(one, s);
485 __m256 inner = _mm256_fmadd_ps(a, one_minus_s, one); // 1 + a * (1 - s)
486 __m256 silu_prime = _mm256_mul_ps(s, inner); // s * (1 + a * (1 - s))
487
488 // dA = dy * b * silu_prime
489 __m256 dA = _mm256_mul_ps(dy, _mm256_mul_ps(b, silu_prime));
490 // dB = dy * silu
491 __m256 dB = _mm256_mul_ps(dy, silu);
492
493 _mm256_storeu_ps(&dx_row[d], dA);
494 _mm256_storeu_ps(&dx_row[D + d], dB);
495 }
496#elif defined(__AVX__)
497 // AVX1: Vectorize arithmetic, use scalar sigmoid
498 __m256 one = _mm256_set1_ps(1.0f);
499 float a_arr[8] __attribute__((aligned(32)));
500 float s_arr[8] __attribute__((aligned(32)));
501
502 for (; d + 8 <= D; d += 8) {
503 __m256 a = _mm256_loadu_ps(&row[d]); // gate
504 __m256 b = _mm256_loadu_ps(&row[D + d]); // value
505 __m256 dy = _mm256_loadu_ps(&dy_row[d]);
506
507 // Compute sigmoid scalarly
508 _mm256_store_ps(a_arr, a);
509 for (int j = 0; j < 8; ++j) {
510 s_arr[j] = sigmoid_scalar(a_arr[j]);
511 }
512 __m256 s = _mm256_load_ps(s_arr);
513
514 __m256 silu = _mm256_mul_ps(a, s); // silu(a) = a * s
515 __m256 one_minus_s = _mm256_sub_ps(one, s);
516 __m256 a_one_minus_s = _mm256_mul_ps(a, one_minus_s);
517 __m256 inner = _mm256_add_ps(one, a_one_minus_s); // 1 + a * (1 - s)
518 __m256 silu_prime = _mm256_mul_ps(s, inner); // s * (1 + a * (1 - s))
519
520 // dA = dy * b * silu_prime
521 __m256 dA = _mm256_mul_ps(dy, _mm256_mul_ps(b, silu_prime));
522 // dB = dy * silu
523 __m256 dB = _mm256_mul_ps(dy, silu);
524
525 _mm256_storeu_ps(&dx_row[d], dA);
526 _mm256_storeu_ps(&dx_row[D + d], dB);
527 }
528#endif
529
530 // Scalar fallback for remaining elements
531 for (; d < D; ++d) {
532 float a = row[d]; // gate
533 float b = row[D + d]; // value
534 float dy = dy_row[d];
535
536 float s = sigmoid_scalar(a); // sigmoid(a)
537 float silu = a * s; // silu(a)
538 float silu_prime = s * (1.0f + a * (1.0f - s)); // silu'(a), PyTorch form
539
540 float dA = dy * b * silu_prime;
541 float dB = dy * silu;
542
543 dx_row[d] = dA;
544 dx_row[D + d] = dB;
545 }
546 }
547}
548// ============================================================================
549// Exact versions using standard library expf (slower but accurate)
550// ============================================================================
551
552/**
553 * SwiGLU forward pass (exact version using stdlib sigmoid)
554 * @test test_swiglu.py::TestSwiGLUForward::test_exact_vs_fast
555 * @test test_swiglu.py::TestSwiGLUForward::test_exact_single
556 *
557 * Uses standard library expf for numerical accuracy reference.
558 *
559 * After changes: make test
560 */
561typedef struct {
562 const float *input;
563 float *output;
564 int dim;
565} ck_swiglu_exact_args_t;
566
567static int ck_swiglu_ranges_overlap(const float *input,
568 const float *output,
569 int tokens,
570 int dim)
571{
572 const uintptr_t input_begin = (uintptr_t)input;
573 const uintptr_t input_end = input_begin +
574 (size_t)tokens * (size_t)(2 * dim) * sizeof(float);
575 const uintptr_t output_begin = (uintptr_t)output;
576 const uintptr_t output_end = output_begin +
577 (size_t)tokens * (size_t)dim * sizeof(float);
578 return input_begin < output_end && output_begin < input_end;
579}
580
581static void ck_swiglu_dispatch_rows(ck_threadpool_t *pool,
582 int active,
583 int tokens,
584 int dim,
585 ck_range_fn_t fn,
586 void *args,
587 const float *input,
588 float *output)
589{
590 const size_t minimum_elements_per_thread = 65536u;
591 const size_t elems = (size_t)tokens * (size_t)dim;
592 if (!pool || active <= 1 || tokens < 2 ||
593 elems < 2u * minimum_elements_per_thread ||
594 ck_threadpool_thread_id(pool) > 0) {
595 fn(0, tokens, args);
596 return;
597 }
598
599 const size_t useful_threads =
600 (elems + minimum_elements_per_thread - 1u) /
601 minimum_elements_per_thread;
602 if ((size_t)active > useful_threads) active = (int)useful_threads;
603 if (active > tokens) active = tokens;
604
605 if (!ck_swiglu_ranges_overlap(input, output, tokens, dim)) {
607 pool, active, 0, tokens, 1, fn, args);
608 return;
609 }
610 if (input != output) {
611 fn(0, tokens, args);
612 return;
613 }
614
615 /*
616 * In-place [T, 2D] -> [T, D] compaction has a binary dependency:
617 * output row t overlaps input row floor(t / 2). Process each parent
618 * wave before its children; rows within a wave remain independent.
619 */
620 fn(0, 1, args);
621 for (int begin = 1; begin < tokens;) {
622 int end = begin > tokens - begin ? tokens : begin * 2;
623 int wave_threads = active;
624 const int wave_rows = end - begin;
625 if (wave_threads > wave_rows) wave_threads = wave_rows;
627 pool, wave_threads, begin, end, 1, fn, args);
628 begin = end;
629 }
630}
631
632static void ck_swiglu_exact_rows(int begin, int end, void *opaque)
633{
634 const ck_swiglu_exact_args_t *args =
635 (const ck_swiglu_exact_args_t *)opaque;
636
637 for (int t = begin; t < end; ++t) {
638 const float *row = args->input +
639 (size_t)t * (size_t)(2 * args->dim);
640 float *out_row = args->output + (size_t)t * (size_t)args->dim;
641
642 for (int d = 0; d < args->dim; ++d) {
643 float a = row[d];
644 float b = row[args->dim + d];
645 float s = sigmoid_scalar_parity(a);
646 float silu = a * s;
647 out_row[d] = silu * b;
648 }
649 }
650}
651
652void swiglu_forward_exact(const float *input,
653 float *output,
654 int tokens,
655 int dim)
656{
657 if (!input || !output || tokens <= 0 || dim <= 0) {
658 return;
659 }
660
661 ck_swiglu_exact_args_t args = {input, output, dim};
662 ck_threadpool_t *pool = ck_threadpool_global();
663 int active = pool ? ck_threadpool_n_threads(pool) : 1;
664 ck_swiglu_dispatch_rows(pool, active, tokens, dim,
665 ck_swiglu_exact_rows, &args, input, output);
666}
667
668typedef struct {
669 const float *input;
670 float *output;
671 int dim;
672} ck_swiglu_ggml_args_t;
673
674static void ck_swiglu_ggml_rows(int begin, int end, void *opaque)
675{
676 const ck_swiglu_ggml_args_t *args =
677 (const ck_swiglu_ggml_args_t *)opaque;
678 const int dim = args->dim;
679#if defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
680 const int force_ref = ck_swiglu_ggml_ref_forced();
681#endif
682 for (int t = begin; t < end; ++t) {
683 const float *row = args->input + (size_t)t * (size_t)(2 * dim);
684 float *out_row = args->output + (size_t)t * (size_t)dim;
685 int d = 0;
686
687#if defined(__AVX512F__) && defined(__AVX512DQ__)
688 for (; d + 16 <= dim; d += 16) {
689 const __m512 gate = _mm512_loadu_ps(row + d);
690 const __m512 up = _mm512_loadu_ps(row + dim + d);
691 const __m512 neg_gate = _mm512_sub_ps(_mm512_setzero_ps(), gate);
692 const __m512 denom = _mm512_add_ps(
693 _mm512_set1_ps(1.0f), ck_ggml_expf_avx512(neg_gate));
694 const __m512 silu = _mm512_div_ps(gate, denom);
695 _mm512_storeu_ps(out_row + d, _mm512_mul_ps(silu, up));
696 }
697#elif defined(__AVX2__) && defined(__FMA__)
698 for (; d + 8 <= dim; d += 8) {
699 const __m256 gate = _mm256_loadu_ps(row + d);
700 const __m256 up = _mm256_loadu_ps(row + dim + d);
701 const __m256 neg_gate = _mm256_sub_ps(_mm256_setzero_ps(), gate);
702 const __m256 denom = _mm256_add_ps(
703 _mm256_set1_ps(1.0f), ck_ggml_expf_avx2(neg_gate));
704 const __m256 silu = _mm256_div_ps(gate, denom);
705 _mm256_storeu_ps(out_row + d, _mm256_mul_ps(silu, up));
706 }
707#elif defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
708 if (!force_ref) {
709 for (; d + 4 <= dim; d += 4) {
710 const float32x4_t gate = vld1q_f32(row + d);
711 const float32x4_t up = vld1q_f32(row + dim + d);
712 const float32x4_t neg_gate = vsubq_f32(vdupq_n_f32(0.0f), gate);
713 const float32x4_t denom = vaddq_f32(
714 vdupq_n_f32(1.0f), ck_ggml_expf_neon(neg_gate));
715 const float32x4_t silu = vdivq_f32(gate, denom);
716 vst1q_f32(out_row + d, vmulq_f32(silu, up));
717 }
718 }
719#endif
720 for (; d < dim; ++d) {
721 const float gate = row[d];
722 out_row[d] = (gate / (1.0f + expf(-gate))) * row[dim + d];
723 }
724 }
725}
726
727void swiglu_forward_ggml(const float *input,
728 float *output,
729 int tokens,
730 int dim)
731{
732 if (!input || !output || tokens <= 0 || dim <= 0) {
733 return;
734 }
735
736 ck_swiglu_ggml_args_t args = {input, output, dim};
737 ck_threadpool_t *pool = ck_threadpool_global();
738 int active = pool ? ck_threadpool_n_threads(pool) : 1;
739 ck_swiglu_dispatch_rows(pool, active, tokens, dim,
740 ck_swiglu_ggml_rows, &args, input, output);
741}
742
743void swiglu_forward_ggml_split(const float *gate,
744 const float *up,
745 float *output,
746 int tokens,
747 int dim)
748{
749 if (!gate || !up || !output || tokens <= 0 || dim <= 0) {
750 return;
751 }
752#if defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
753 const int force_ref = ck_swiglu_ggml_ref_forced();
754#endif
755 for (int t = 0; t < tokens; ++t) {
756 const float *gate_row = gate + (size_t)t * (size_t)dim;
757 const float *up_row = up + (size_t)t * (size_t)dim;
758 float *out_row = output + (size_t)t * (size_t)dim;
759 int d = 0;
760
761#if defined(__AVX512F__) && defined(__AVX512DQ__)
762 for (; d + 16 <= dim; d += 16) {
763 const __m512 gate_v = _mm512_loadu_ps(gate_row + d);
764 const __m512 up_v = _mm512_loadu_ps(up_row + d);
765 const __m512 neg_gate = _mm512_sub_ps(_mm512_setzero_ps(), gate_v);
766 const __m512 denom = _mm512_add_ps(
767 _mm512_set1_ps(1.0f), ck_ggml_expf_avx512(neg_gate));
768 const __m512 silu = _mm512_div_ps(gate_v, denom);
769 _mm512_storeu_ps(out_row + d, _mm512_mul_ps(silu, up_v));
770 }
771#elif defined(__AVX2__) && defined(__FMA__)
772 for (; d + 8 <= dim; d += 8) {
773 const __m256 gate_v = _mm256_loadu_ps(gate_row + d);
774 const __m256 up_v = _mm256_loadu_ps(up_row + d);
775 const __m256 neg_gate = _mm256_sub_ps(_mm256_setzero_ps(), gate_v);
776 const __m256 denom = _mm256_add_ps(
777 _mm256_set1_ps(1.0f), ck_ggml_expf_avx2(neg_gate));
778 const __m256 silu = _mm256_div_ps(gate_v, denom);
779 _mm256_storeu_ps(out_row + d, _mm256_mul_ps(silu, up_v));
780 }
781#elif defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
782 if (!force_ref) {
783 for (; d + 4 <= dim; d += 4) {
784 const float32x4_t gate_v = vld1q_f32(gate_row + d);
785 const float32x4_t up_v = vld1q_f32(up_row + d);
786 const float32x4_t neg_gate = vsubq_f32(vdupq_n_f32(0.0f), gate_v);
787 const float32x4_t denom = vaddq_f32(
788 vdupq_n_f32(1.0f), ck_ggml_expf_neon(neg_gate));
789 const float32x4_t silu = vdivq_f32(gate_v, denom);
790 vst1q_f32(out_row + d, vmulq_f32(silu, up_v));
791 }
792 }
793#endif
794 for (; d < dim; ++d) {
795 const float gate_v = gate_row[d];
796 out_row[d] = (gate_v / (1.0f + expf(-gate_v))) * up_row[d];
797 }
798 }
799}
800
801/* Truthful provider-name helper for the ggml SwiGLU providers
802 * (swiglu_forward_ggml / swiglu_forward_ggml_split), same pattern as
803 * ck_q4_k_q8_k_provider_name. */
805{
806#if defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
807 if (ck_swiglu_ggml_ref_forced()) {
808 return "swiglu_ggml_ref";
809 }
810#endif
811#if defined(__AVX512F__) && defined(__AVX512DQ__)
812 return "swiglu_ggml_avx512";
813#elif defined(__AVX2__) && defined(__FMA__)
814 return "swiglu_ggml_avx2";
815#elif defined(__ARM_NEON) && (defined(__aarch64__) || defined(__ARM_FEATURE_FMA))
816 return "swiglu_ggml_neon";
817#else
818 return "swiglu_ggml_ref";
819#endif
820}
821
822#if defined(__AVX512F__)
823typedef __m512 (*ck_sleef_expf16_fn)(__m512);
824
825static ck_sleef_expf16_fn ck_pytorch_swiglu_expf16 = NULL;
826static void *ck_pytorch_swiglu_sleef_handle = NULL;
827static pthread_once_t ck_pytorch_swiglu_once = PTHREAD_ONCE_INIT;
828
829static void ck_bind_pytorch_swiglu_sleef(void)
830{
831 const char *library = getenv("CK_SLEEF_LIBRARY");
832 if (library && *library) {
833 ck_pytorch_swiglu_sleef_handle = dlopen(library, RTLD_NOW | RTLD_LOCAL);
834 if (ck_pytorch_swiglu_sleef_handle) {
835 ck_pytorch_swiglu_expf16 = (ck_sleef_expf16_fn)dlsym(
836 ck_pytorch_swiglu_sleef_handle, "Sleef_expf16_u10");
837 }
838 } else {
839 ck_pytorch_swiglu_expf16 =
840 (ck_sleef_expf16_fn)dlsym(RTLD_DEFAULT, "Sleef_expf16_u10");
841 }
842}
843#endif
844
845/*
846 * Match the ATen BF16 Qwen MLP expression, not the GGML FP32 expression:
847 *
848 * silu_bf16 = aten::silu(gate_bf16)
849 * out_bf16 = silu_bf16 * up_bf16
850 *
851 * PyTorch 2.8's reduced-floating CPU SiLU converts BF16 lanes to FP32, uses
852 * its SLEEF vector exponential, evaluates x / (1 + exp(-x)), and rounds the
853 * SiLU result to BF16. The following TensorIterator multiplication converts
854 * both BF16 operands to FP32 and performs a second BF16 store. Keeping the
855 * SiLU intermediate in FP32 is close, but is a different storage contract and
856 * changes long teacher-forced generation after repeated MLP blocks.
857 *
858 * Source oracle: PyTorch aten/src/ATen/native/cpu/Activation.cpp,
859 * silu_kernel(), commit a1cb3cc05d46d198467bebbb6e8fba50a325d4e7.
860 */
862 float *output,
863 int tokens,
864 int dim)
865{
866 if (!input || !output || tokens < 0 || dim < 0) {
867 fprintf(stderr, "HARD KERNEL CONTRACT FAULT: invalid PyTorch BF16 SwiGLU arguments\n");
868 abort();
869 }
870
871#if defined(__AVX512F__)
872 pthread_once(&ck_pytorch_swiglu_once, ck_bind_pytorch_swiglu_sleef);
873 if (!ck_pytorch_swiglu_expf16) {
874 fprintf(stderr,
875 "HARD KERNEL CONTRACT FAULT: PyTorch BF16 SwiGLU requires "
876 "SLEEF Sleef_expf16_u10; set CK_SLEEF_LIBRARY\n");
877 abort();
878 }
879#endif
880
881 for (int t = 0; t < tokens; ++t) {
882 const float *row = input + (size_t)t * (size_t)(2 * dim);
883 float *out_row = output + (size_t)t * (size_t)dim;
884 int d = 0;
885
886#if defined(__AVX512F__)
887 for (; d + 16 <= dim; d += 16) {
888 const __m512 gate = _mm512_loadu_ps(row + d);
889 const __m512 denominator = _mm512_add_ps(
890 _mm512_set1_ps(1.0f),
891 ck_pytorch_swiglu_expf16(_mm512_sub_ps(_mm512_setzero_ps(), gate)));
892 const __m512 silu = _mm512_div_ps(gate, denominator);
893 float silu_lanes[16] __attribute__((aligned(64)));
894 _mm512_store_ps(silu_lanes, silu);
895 for (int lane = 0; lane < 16; ++lane) {
896 const float silu_bf16 = bf16_to_float(float_to_bf16(silu_lanes[lane]));
897 const float up_bf16 = bf16_to_float(float_to_bf16(row[dim + d + lane]));
898 out_row[d + lane] = bf16_to_float(
899 float_to_bf16(silu_bf16 * up_bf16));
900 }
901 }
902#endif
903 for (; d < dim; ++d) {
904 const float gate_bf16 = bf16_to_float(float_to_bf16(row[d]));
905 const float up_bf16 = bf16_to_float(float_to_bf16(row[dim + d]));
906 const float silu = gate_bf16 / (1.0f + expf(-gate_bf16));
907 const float silu_bf16 = bf16_to_float(float_to_bf16(silu));
908 out_row[d] = bf16_to_float(float_to_bf16(silu_bf16 * up_bf16));
909 }
910 }
911}
912
913/**
914 * SwiGLU backward pass (exact version using stdlib sigmoid)
915 * @test test_swiglu.py::TestSwiGLUBackward::test_exact_vs_fast
916 * @test test_swiglu.py::TestSwiGLUBackward::test_exact_single
917 *
918 * Uses standard library expf for numerical accuracy reference.
919 *
920 * After changes: make test
921 */
922void swiglu_backward_exact(const float *input,
923 const float *d_output,
924 float *d_input,
925 int tokens,
926 int dim)
927{
928 int T = tokens;
929 int D = dim;
930
931 for (int t = 0; t < T; ++t) {
932 const float *row = input + (size_t)t * (2 * D);
933 const float *dy_row = d_output + (size_t)t * D;
934 float *dx_row = d_input + (size_t)t * (2 * D);
935
936 for (int d = 0; d < D; ++d) {
937 float a = row[d]; // gate
938 float b = row[D + d]; // value
939 float dy = dy_row[d];
940
941 float s = sigmoid_scalar_parity(a); // sigmoid(a)
942 float silu = a * s; // silu(a)
943 float silu_prime = s * (1.0f + a * (1.0f - s)); // silu'(a), PyTorch form
944
945 float dA = dy * b * silu_prime;
946 float dB = dy * silu;
947
948 dx_row[d] = dA;
949 dx_row[D + d] = dB;
950 }
951 }
952}
#define RTLD_DEFAULT
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
Persistent pthread thread pool for CK-Engine inference.
void ck_threadpool_parallel_for_n(ck_threadpool_t *pool, int active_threads, int begin, int end, int grain_size, ck_range_fn_t fn, void *args)
ck_threadpool_t * ck_threadpool_global(void)
void(* ck_range_fn_t)(int begin, int end, void *args)
int ck_threadpool_thread_id(const ck_threadpool_t *pool)
int ck_threadpool_n_threads(const ck_threadpool_t *pool)
void quantize_row_q8_k(const float *x, void *y, int k)
float sigmoid_scalar(float x)
int ck_strict_parity_enabled(void)
Quantization block structures for weight-only quantization.
#define QK_K
const char * ck_swiglu_ggml_provider_name(void)
static void ck_swiglu_dispatch_rows(ck_threadpool_t *pool, int active, int tokens, int dim, ck_range_fn_t fn, void *args, const float *input, float *output)
void swiglu_forward_exact(const float *input, float *output, int tokens, int dim)
void swiglu_forward_pytorch_bf16_storage(const float *input, float *output, int tokens, int dim)
void swiglu_forward(const float *input, float *output, int tokens, int dim)
void swiglu_backward(const float *input, const float *d_output, float *d_input, int tokens, int dim)
static void ck_swiglu_exact_rows(int begin, int end, void *opaque)
static void ck_swiglu_ggml_rows(int begin, int end, void *opaque)
void swiglu_backward_exact(const float *input, const float *d_output, float *d_input, int tokens, int dim)
void swiglu_forward_q8_k(const float *input, void *output_q8, int tokens, int dim)
static float sigmoid_scalar_parity(float x)
void swiglu_forward_ggml(const float *input, float *output, int tokens, int dim)
void swiglu_forward_ggml_split(const float *gate, const float *up, float *output, int tokens, int dim)
static int ck_swiglu_ranges_overlap(const float *input, const float *output, int tokens, int dim)
__attribute__((visibility("default"))) CKTokenizer *ck_tokenizer_create(CKTokenizerType type)
uint32_t end
Definition utf8.c:215
static void silu(float *x, int n)