← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
mlp_kernels.c
Go to the documentation of this file.
1/**
2 * @file mlp_kernels.c
3 * @brief MLP (feed-forward) 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 * LEGACY EXCEPTION: This file contains OpenMP for backward compatibility.
15 * New kernels should NOT use OpenMP internally.
16 *
17 * OpenMP removal note:
18 * The bias-reduction loops in the fallback FP32 backward kernels were made
19 * serial so the training hot path does not bounce between the CK threadpool
20 * and libiomp barriers. These kernels now serve as compatibility fallbacks
21 * while threaded training backward is migrated into ck_parallel_train.c.
22 *
23 * MLP: out = FC2(GELU(FC1(x)))
24 */
25
26#include "ckernel_engine.h"
27#if defined(__AVX512F__) || defined(__AVX2__) || defined(__AVX__)
28#include <immintrin.h>
29#endif
30#ifdef _OPENMP
31#include <omp.h>
32#endif
33#include <stdlib.h>
34
35/* Forward MLP kernel (FC1 -> GELU -> FC2) adapted from C-Transformer's */
36// mlp_token_parallel but expressed in a model-agnostic form. We keep the
37// familiar name `mlp_token_parallel` for reuse during decode/inference.
38//
39// Shapes:
40// input: [T × D] (row-major, stride = aligned_dim)
41// W_fc1: [4D × D] (row-major, stored as [out × in])
42// b_fc1: [4D]
43// W_fc2: [D × 4D]
44// b_fc2: [D]
45// fc1_output: [T × 4D] (workspace, also becomes GELU input/output)
46// output: [T × D]
47//
48// D is typically `aligned_dim` in your transformer; pass that value here.
49void mlp_token_parallel(const float *input,
50 const float *W_fc1,
51 const float *b_fc1,
52 const float *W_fc2,
53 const float *b_fc2,
54 float *fc1_output,
55 float *output,
56 int T,
57 int aligned_dim,
58 int num_threads)
59{
60 int D = aligned_dim;
61 int fourD = 4 * D;
62
63 // FC1: [T × D] · [D × 4D] -> [T × 4D]
64 // Our GEMM layout: A[M×K], B[N×K], so B is [4D × D].
65 gemm_blocked_serial(input, W_fc1, b_fc1,
66 fc1_output,
67 T, // M
68 fourD, // N
69 D); // K
70
71 // GELU in-place on FC1 output
72 gelu_fast_inplace(fc1_output, (size_t)T * (size_t)fourD);
73
74 // FC2: [T × 4D] · [4D × D] -> [T × D]
75 gemm_blocked_serial(fc1_output, W_fc2, b_fc2,
76 output,
77 T, // M
78 D, // N
79 fourD); // K
80}
81
82// Exact version of MLP forward using scalar GELU with standard library tanhf.
83// Slower but provides maximum accuracy. Used for correctness testing.
84void mlp_token_parallel_exact(const float *input,
85 const float *W_fc1,
86 const float *b_fc1,
87 const float *W_fc2,
88 const float *b_fc2,
89 float *fc1_output,
90 float *output,
91 int T,
92 int aligned_dim,
93 int num_threads)
94{
95 (void)num_threads;
96 int D = aligned_dim;
97 int fourD = 4 * D;
98
99 // FC1: [T × D] · [D × 4D] -> [T × 4D]
100 gemm_blocked_serial(input, W_fc1, b_fc1,
101 fc1_output,
102 T, // M
103 fourD, // N
104 D); // K
105
106 // Exact GELU using standard library tanhf
107 gelu_exact_inplace(fc1_output, (size_t)T * (size_t)fourD);
108
109 // FC2: [T × 4D] · [4D × D] -> [T × D]
110 gemm_blocked_serial(fc1_output, W_fc2, b_fc2,
111 output,
112 T, // M
113 D, // N
114 fourD); // K
115}
116
117// Generic FC2 backward kernel adapted from C-Transformer's backward_fc2_feature_parallel.
118// Now uses shared GEMM kernels for d_input and d_W computation.
119// Shapes:
120// d_output: [T × aligned_out]
121// fc2_input: [T × aligned_in]
122// W_fc2: [aligned_out × aligned_in] (row-major)
123// d_input: [T × aligned_in]
124// d_W_fc2: [aligned_out × aligned_in] (accumulated)
125// d_b_fc2: [aligned_out] (accumulated)
126void fc2_backward_kernel(const float *d_output,
127 const float *fc2_input,
128 const float *W_fc2,
129 float *d_input,
130 float *d_W_fc2,
131 float *d_b_fc2,
132 int T,
133 int aligned_in,
134 int aligned_out,
135 int num_threads)
136{
137 (void)num_threads; // Threading handled by GEMM kernels
138
139 // 1. d_input[T, in] = d_output[T, out] @ W[out, in]
140 // Using gemm_nn: C[M,N] = A[M,K] @ B[K,N]
141 // A = d_output [T, out], B = W [out, in], C = d_input [T, in]
142 // M = T, N = aligned_in, K = aligned_out
143 gemm_nn_simd(d_output, W_fc2, NULL, d_input,
144 T, aligned_in, aligned_out);
145
146 // 2. d_W[out, in] = d_output[T, out].T @ fc2_input[T, in]
147 // Using gemm_tn: C[M,N] = A[K,M].T @ B[K,N]
148 // A = d_output [T, out] (stored as [K=T, M=out]), B = fc2_input [T, in]
149 // C = d_W [out, in], M = aligned_out, N = aligned_in, K = T
150 // Note: gemm_tn overwrites, so we need to save and add if accumulating
151 // For now, assume d_W starts zeroed (gradient accumulation handled at higher level)
152 gemm_tn_parallel(d_output, fc2_input, NULL, d_W_fc2,
153 aligned_out, aligned_in, T);
154
155 // 3. d_b_fc2 = sum_over_T(d_output)
156 for (int out_idx = 0; out_idx < aligned_out; ++out_idx) {
157 float bias_grad = 0.0f;
158 for (int t = 0; t < T; ++t) {
159 bias_grad += d_output[(size_t)t * aligned_out + out_idx];
160 }
161 d_b_fc2[out_idx] += bias_grad;
162 }
163}
164
165// Generic FC1 backward kernel adapted from C-Transformer's backward_fc1_feature_parallel.
166// Now uses shared GEMM kernels for d_input and d_W computation.
167// Shapes:
168// d_output: [T × aligned_out]
169// fc1_input: [T × aligned_in]
170// W_fc1: [aligned_out × aligned_in] (row-major)
171// d_input: [T × aligned_in]
172// d_W_fc1: [aligned_out × aligned_in] (accumulated)
173// d_b_fc1: [aligned_out] (accumulated)
174void fc1_backward_kernel(const float *d_output,
175 const float *fc1_input,
176 const float *W_fc1,
177 float *d_input,
178 float *d_W_fc1,
179 float *d_b_fc1,
180 int T,
181 int aligned_in,
182 int aligned_out,
183 int num_threads)
184{
185 (void)num_threads; // Threading handled by GEMM kernels
186
187 // 1. d_input[T, in] = d_output[T, out] @ W[out, in]
188 // Using gemm_nn: C[M,N] = A[M,K] @ B[K,N]
189 // A = d_output [T, out], B = W [out, in], C = d_input [T, in]
190 // M = T, N = aligned_in, K = aligned_out
191 gemm_nn_simd(d_output, W_fc1, NULL, d_input,
192 T, aligned_in, aligned_out);
193
194 // 2. d_W[out, in] = d_output[T, out].T @ fc1_input[T, in]
195 // Using gemm_tn: C[M,N] = A[K,M].T @ B[K,N]
196 // A = d_output [T, out] (stored as [K=T, M=out]), B = fc1_input [T, in]
197 // C = d_W [out, in], M = aligned_out, N = aligned_in, K = T
198 gemm_tn_parallel(d_output, fc1_input, NULL, d_W_fc1,
199 aligned_out, aligned_in, T);
200
201 // 3. d_b_fc1 = sum_over_T(d_output)
202 for (int out_idx = 0; out_idx < aligned_out; ++out_idx) {
203 float bias_grad = 0.0f;
204 for (int t = 0; t < T; ++t) {
205 bias_grad += d_output[(size_t)t * aligned_out + out_idx];
206 }
207 d_b_fc1[out_idx] += bias_grad;
208 }
209}
void gelu_exact_inplace(float *data, size_t n)
void gemm_tn_parallel(const float *A, const float *B, const float *bias, float *C, int M, int N, int K)
void gemm_nn_simd(const float *A, const float *B, const float *bias, float *C, int M, int N, int K)
void gelu_fast_inplace(float *data, size_t n)
void gemm_blocked_serial(const float *A, const float *B, const float *bias, float *C, int M, int N, int K)
void mlp_token_parallel(const float *input, const float *W_fc1, const float *b_fc1, const float *W_fc2, const float *b_fc2, float *fc1_output, float *output, int T, int aligned_dim, int num_threads)
Definition mlp_kernels.c:49
void fc1_backward_kernel(const float *d_output, const float *fc1_input, const float *W_fc1, float *d_input, float *d_W_fc1, float *d_b_fc1, int T, int aligned_in, int aligned_out, int num_threads)
void mlp_token_parallel_exact(const float *input, const float *W_fc1, const float *b_fc1, const float *W_fc2, const float *b_fc2, float *fc1_output, float *output, int T, int aligned_dim, int num_threads)
Definition mlp_kernels.c:84
void fc2_backward_kernel(const float *d_output, const float *fc2_input, const float *W_fc2, float *d_input, float *d_W_fc2, float *d_b_fc2, int T, int aligned_in, int aligned_out, int num_threads)