← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
mlp_kernels_bf16.c
Go to the documentation of this file.
1/**
2 * @file mlp_kernels_bf16.c
3 * @brief Optimized BF16 MLP Kernels
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 * Uses direct BF16 GEMM instead of converting to FP32.
15 * Layout: input[T,D] -> fc1[T,4D] -> GELU -> fc2[T,D]
16 *
17 * All functions use caller-provided scratch buffers (no internal malloc).
18 */
19
20#include <stddef.h>
21#include <stdint.h>
22#include <math.h>
23
24#if defined(__AVX512F__)
25#include <immintrin.h>
26#endif
27
28#ifdef _OPENMP
29#include <omp.h>
30#endif
31
32#include "bf16_utils.h"
33#include "ckernel_engine.h"
34
35// Suppress false positive warnings about uninitialized variables
36#pragma GCC diagnostic push
37#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
38
39/* Forward declaration of optimized BF16 GEMM */
40extern void gemm_bf16_fp32out(const uint16_t *A, const uint16_t *B,
41 const float *bias, float *C,
42 int M, int N, int K);
43
44/* GELU activation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) */
45static inline float gelu_scalar(float x)
46{
47 const float c = 0.7978845608f; /* sqrt(2/pi) */
48 const float k = 0.044715f;
49 float x3 = x * x * x;
50 return 0.5f * x * (1.0f + tanhf(c * (x + k * x3)));
51}
52
53static inline float gelu_derivative_scalar(float x)
54{
55 const float c = 0.7978845608f; /* sqrt(2/pi) */
56 const float k = 0.044715f;
57 const float x2 = x * x;
58 const float x3 = x2 * x;
59 const float g = c * (x + k * x3);
60 const float tanh_g = tanhf(g);
61 const float sech2_g = 1.0f - tanh_g * tanh_g;
62 const float g_prime = c * (1.0f + 3.0f * k * x2);
63 return 0.5f * (1.0f + tanh_g) + 0.5f * x * sech2_g * g_prime;
64}
65
66#if defined(__AVX512F__)
67/* Vectorized GELU using polynomial approximation of tanh */
68static inline __m512 gelu_avx512(__m512 x)
69{
70 const __m512 c = _mm512_set1_ps(0.7978845608f);
71 const __m512 k = _mm512_set1_ps(0.044715f);
72 const __m512 half = _mm512_set1_ps(0.5f);
73 const __m512 one = _mm512_set1_ps(1.0f);
74
75 __m512 x2 = _mm512_mul_ps(x, x);
76 __m512 x3 = _mm512_mul_ps(x2, x);
77
78 __m512 inner = _mm512_fmadd_ps(k, x3, x);
79 inner = _mm512_mul_ps(c, inner);
80
81 __m512 inner2 = _mm512_mul_ps(inner, inner);
82 __m512 num = _mm512_add_ps(_mm512_set1_ps(27.0f), inner2);
83 __m512 den = _mm512_fmadd_ps(_mm512_set1_ps(9.0f), inner2, _mm512_set1_ps(27.0f));
84 __m512 tanh_approx = _mm512_mul_ps(inner, _mm512_div_ps(num, den));
85
86 tanh_approx = _mm512_min_ps(tanh_approx, one);
87 tanh_approx = _mm512_max_ps(tanh_approx, _mm512_set1_ps(-1.0f));
88
89 __m512 result = _mm512_add_ps(one, tanh_approx);
90 result = _mm512_mul_ps(half, _mm512_mul_ps(x, result));
91
92 return result;
93}
94#endif
95
96/**
97 * Optimized MLP Forward (BF16 weights, FP32 activations)
98 *
99 * Caller-provided scratch buffers:
100 * scratch_bias1_f: [4*D] floats
101 * scratch_bias2_f: [D] floats
102 * scratch_fc1_bf16: [T * 4*D] uint16_t (BF16)
103 */
104void mlp_token_parallel_bf16(const uint16_t *input,
105 const uint16_t *W_fc1,
106 const uint16_t *b_fc1,
107 const uint16_t *W_fc2,
108 const uint16_t *b_fc2,
109 float *fc1_output,
110 float *output,
111 int T,
112 int aligned_dim,
113 int num_threads,
114 float *scratch_bias1_f,
115 float *scratch_bias2_f,
116 uint16_t *scratch_fc1_bf16)
117{
118 if (!input || !W_fc1 || !b_fc1 || !W_fc2 || !b_fc2 || !fc1_output || !output) return;
119 if (!scratch_bias1_f || !scratch_bias2_f || !scratch_fc1_bf16) return;
120
121 (void)num_threads;
122 const int D = aligned_dim;
123 const int fourD = 4 * D;
124
125 /* Convert biases to FP32 */
126 for (int i = 0; i < fourD; ++i) {
127 scratch_bias1_f[i] = bf16_to_float(b_fc1[i]);
128 }
129 for (int i = 0; i < D; ++i) {
130 scratch_bias2_f[i] = bf16_to_float(b_fc2[i]);
131 }
132
133 /* FC1: [T, D] x [4D, D].T -> [T, 4D] */
134 gemm_bf16_fp32out(input, W_fc1, scratch_bias1_f, fc1_output, T, fourD, D);
135
136 /* GELU activation */
137#if defined(__AVX512F__)
138 #pragma omp parallel for
139 for (int t = 0; t < T; ++t) {
140 float *row = fc1_output + (size_t)t * fourD;
141 int j = 0;
142 for (; j <= fourD - 16; j += 16) {
143 __m512 x = _mm512_loadu_ps(row + j);
144 _mm512_storeu_ps(row + j, gelu_avx512(x));
145 }
146 for (; j < fourD; ++j) {
147 row[j] = gelu_scalar(row[j]);
148 }
149 }
150#else
151 for (int t = 0; t < T; ++t) {
152 for (int j = 0; j < fourD; ++j) {
153 fc1_output[t * fourD + j] = gelu_scalar(fc1_output[t * fourD + j]);
154 }
155 }
156#endif
157
158 /* Convert FP32 activations to BF16 */
159#if defined(__AVX512F__)
160 #pragma omp parallel for
161 for (int t = 0; t < T; ++t) {
162 float *src = fc1_output + (size_t)t * fourD;
163 uint16_t *dst = scratch_fc1_bf16 + (size_t)t * fourD;
164 int j = 0;
165 for (; j <= fourD - 16; j += 16) {
166 __m512 fp32 = _mm512_loadu_ps(src + j);
167 __m512i as_int = _mm512_castps_si512(fp32);
168 __m512i lsb = _mm512_srli_epi32(as_int, 16);
169 lsb = _mm512_and_si512(lsb, _mm512_set1_epi32(1));
170 __m512i rounding = _mm512_add_epi32(_mm512_set1_epi32(0x7FFF), lsb);
171 __m512i rounded = _mm512_add_epi32(as_int, rounding);
172 __m512i shifted = _mm512_srli_epi32(rounded, 16);
173 __m256i bf16 = _mm512_cvtepi32_epi16(shifted);
174 _mm256_storeu_si256((__m256i *)(dst + j), bf16);
175 }
176 for (; j < fourD; ++j) {
177 dst[j] = float_to_bf16(src[j]);
178 }
179 }
180#else
181 for (size_t i = 0; i < (size_t)T * fourD; ++i) {
182 scratch_fc1_bf16[i] = float_to_bf16(fc1_output[i]);
183 }
184#endif
185
186 /* FC2: BF16 GEMM with FP32 output */
187 gemm_bf16_fp32out(scratch_fc1_bf16, W_fc2, scratch_bias2_f, output, T, D, fourD);
188}
189
190/**
191 * Alternative: Fully FP32 activations throughout
192 *
193 * Caller-provided scratch buffers:
194 * scratch_input_f: [T * D] floats
195 * scratch_bias1_f: [4*D] floats
196 * scratch_bias2_f: [D] floats
197 * scratch_fc1_bf16: [T * 4*D] uint16_t (BF16)
198 */
199void mlp_token_parallel_bf16_fp32act(const uint16_t *input,
200 const uint16_t *W_fc1,
201 const uint16_t *b_fc1,
202 const uint16_t *W_fc2,
203 const uint16_t *b_fc2,
204 float *fc1_output,
205 float *output,
206 int T,
207 int aligned_dim,
208 int num_threads,
209 float *scratch_input_f,
210 float *scratch_bias1_f,
211 float *scratch_bias2_f,
212 uint16_t *scratch_fc1_bf16)
213{
214 if (!input || !W_fc1 || !b_fc1 || !W_fc2 || !b_fc2 || !fc1_output || !output) return;
215 if (!scratch_input_f || !scratch_bias1_f || !scratch_bias2_f || !scratch_fc1_bf16) return;
216
217 (void)num_threads;
218 const int D = aligned_dim;
219 const int fourD = 4 * D;
220
221 /* Convert input and biases to FP32 */
222 bf16_tensor_to_float(input, scratch_input_f, (size_t)T * D);
223 bf16_tensor_to_float(b_fc1, scratch_bias1_f, fourD);
224 bf16_tensor_to_float(b_fc2, scratch_bias2_f, D);
225
226 /* FC1 */
227 gemm_bf16_fp32out(input, W_fc1, scratch_bias1_f, fc1_output, T, fourD, D);
228
229 /* GELU */
230#if defined(__AVX512F__)
231 #pragma omp parallel for
232 for (int t = 0; t < T; ++t) {
233 float *row = fc1_output + (size_t)t * fourD;
234 int j = 0;
235 for (; j <= fourD - 16; j += 16) {
236 __m512 x = _mm512_loadu_ps(row + j);
237 _mm512_storeu_ps(row + j, gelu_avx512(x));
238 }
239 for (; j < fourD; ++j) {
240 row[j] = gelu_scalar(row[j]);
241 }
242 }
243#else
244 for (size_t i = 0; i < (size_t)T * fourD; ++i) {
245 fc1_output[i] = gelu_scalar(fc1_output[i]);
246 }
247#endif
248
249 /* Convert fc1_output to BF16 for FC2 */
250 float_tensor_to_bf16(fc1_output, scratch_fc1_bf16, (size_t)T * fourD);
251 gemm_bf16_fp32out(scratch_fc1_bf16, W_fc2, scratch_bias2_f, output, T, D, fourD);
252}
253
254
255/**
256 * BF16 MLP backward with FP32 gradient accumulation.
257 *
258 * Forward contract matched here:
259 * z1 = input_bf16 @ W_fc1_bf16.T + b_fc1_bf16
260 * h = GELU(z1)
261 * hq = round_to_bf16(h)
262 * y = hq @ W_fc2_bf16.T + b_fc2_bf16
263 *
264 * Gradients are accumulated and written as FP32. The BF16 activation cast is
265 * treated like PyTorch's mixed-precision cast: gradient flows through to h,
266 * while d_W_fc2 uses the rounded hq values that FC2 actually consumed.
267 */
268void mlp_token_parallel_bf16_backward_mixed(const uint16_t *input,
269 const uint16_t *W_fc1,
270 const uint16_t *b_fc1,
271 const uint16_t *W_fc2,
272 const uint16_t *d_output,
273 float *d_input,
274 float *d_W_fc1,
275 float *d_b_fc1,
276 float *d_W_fc2,
277 float *d_b_fc2,
278 int T,
279 int aligned_dim,
280 int num_threads,
281 float *scratch_fc1_pre,
282 uint16_t *scratch_fc1_act_bf16,
283 float *scratch_d_fc1)
284{
285 if (!input || !W_fc1 || !b_fc1 || !W_fc2 || !d_output) return;
286 if (!scratch_fc1_pre || !scratch_fc1_act_bf16 || !scratch_d_fc1) return;
287 if (T <= 0 || aligned_dim <= 0) return;
288
289 (void)num_threads;
290 const int D = aligned_dim;
291 const int fourD = 4 * D;
292
293 /* Recompute FC1 pre-activation and the rounded activation consumed by FC2. */
294 for (int t = 0; t < T; ++t) {
295 for (int j = 0; j < fourD; ++j) {
296 float sum = bf16_to_float(b_fc1[j]);
297 for (int i = 0; i < D; ++i) {
298 const float x = bf16_to_float(input[(size_t)t * (size_t)D + (size_t)i]);
299 const float w = bf16_to_float(W_fc1[(size_t)j * (size_t)D + (size_t)i]);
300 sum += x * w;
301 }
302 scratch_fc1_pre[(size_t)t * (size_t)fourD + (size_t)j] = sum;
303 scratch_fc1_act_bf16[(size_t)t * (size_t)fourD + (size_t)j] = float_to_bf16(gelu_scalar(sum));
304 }
305 }
306
307 if (d_input) {
308 for (int t = 0; t < T; ++t) {
309 for (int i = 0; i < D; ++i) {
310 d_input[(size_t)t * (size_t)D + (size_t)i] = 0.0f;
311 }
312 }
313 }
314 if (d_W_fc1) {
315 for (size_t i = 0; i < (size_t)fourD * (size_t)D; ++i) d_W_fc1[i] = 0.0f;
316 }
317 if (d_b_fc1) {
318 for (int j = 0; j < fourD; ++j) d_b_fc1[j] = 0.0f;
319 }
320 if (d_W_fc2) {
321 for (size_t i = 0; i < (size_t)D * (size_t)fourD; ++i) d_W_fc2[i] = 0.0f;
322 }
323 if (d_b_fc2) {
324 for (int o = 0; o < D; ++o) d_b_fc2[o] = 0.0f;
325 }
326
327 /* d_W_fc2, d_b_fc2, and d_h = d_output @ W_fc2. */
328 for (int t = 0; t < T; ++t) {
329 for (int j = 0; j < fourD; ++j) {
330 float dh = 0.0f;
331 const float hq = bf16_to_float(scratch_fc1_act_bf16[(size_t)t * (size_t)fourD + (size_t)j]);
332 for (int o = 0; o < D; ++o) {
333 const float dy = bf16_to_float(d_output[(size_t)t * (size_t)D + (size_t)o]);
334 const float w2 = bf16_to_float(W_fc2[(size_t)o * (size_t)fourD + (size_t)j]);
335 dh += dy * w2;
336 if (d_W_fc2) {
337 d_W_fc2[(size_t)o * (size_t)fourD + (size_t)j] += dy * hq;
338 }
339 }
340 const float z = scratch_fc1_pre[(size_t)t * (size_t)fourD + (size_t)j];
341 scratch_d_fc1[(size_t)t * (size_t)fourD + (size_t)j] = dh * gelu_derivative_scalar(z);
342 }
343 if (d_b_fc2) {
344 for (int o = 0; o < D; ++o) {
345 d_b_fc2[o] += bf16_to_float(d_output[(size_t)t * (size_t)D + (size_t)o]);
346 }
347 }
348 }
349
350 /* Backprop through FC1. */
351 for (int t = 0; t < T; ++t) {
352 for (int j = 0; j < fourD; ++j) {
353 const float dz = scratch_d_fc1[(size_t)t * (size_t)fourD + (size_t)j];
354 if (d_b_fc1) d_b_fc1[j] += dz;
355 for (int i = 0; i < D; ++i) {
356 const float x = bf16_to_float(input[(size_t)t * (size_t)D + (size_t)i]);
357 const float w1 = bf16_to_float(W_fc1[(size_t)j * (size_t)D + (size_t)i]);
358 if (d_W_fc1) d_W_fc1[(size_t)j * (size_t)D + (size_t)i] += dz * x;
359 if (d_input) d_input[(size_t)t * (size_t)D + (size_t)i] += dz * w1;
360 }
361 }
362 }
363}
364
365#pragma GCC diagnostic pop
static void float_tensor_to_bf16(const float *src, uint16_t *dst, size_t count)
Definition bf16_utils.h:271
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
static void bf16_tensor_to_float(const uint16_t *src, float *dst, size_t count)
Definition bf16_utils.h:250
static float gelu_scalar(float x)
void mlp_token_parallel_bf16(const uint16_t *input, const uint16_t *W_fc1, const uint16_t *b_fc1, const uint16_t *W_fc2, const uint16_t *b_fc2, float *fc1_output, float *output, int T, int aligned_dim, int num_threads, float *scratch_bias1_f, float *scratch_bias2_f, uint16_t *scratch_fc1_bf16)
void mlp_token_parallel_bf16_fp32act(const uint16_t *input, const uint16_t *W_fc1, const uint16_t *b_fc1, const uint16_t *W_fc2, const uint16_t *b_fc2, float *fc1_output, float *output, int T, int aligned_dim, int num_threads, float *scratch_input_f, float *scratch_bias1_f, float *scratch_bias2_f, uint16_t *scratch_fc1_bf16)
void gemm_bf16_fp32out(const uint16_t *A, const uint16_t *B, const float *bias, float *C, int M, int N, int K)
void mlp_token_parallel_bf16_backward_mixed(const uint16_t *input, const uint16_t *W_fc1, const uint16_t *b_fc1, const uint16_t *W_fc2, const uint16_t *d_output, float *d_input, float *d_W_fc1, float *d_b_fc1, float *d_W_fc2, float *d_b_fc2, int T, int aligned_dim, int num_threads, float *scratch_fc1_pre, uint16_t *scratch_fc1_act_bf16, float *scratch_d_fc1)
static float gelu_derivative_scalar(float x)
#define C(color)
Definition show_config.c:39