1use crate::pool::Pool;
4use cortiq_core::types::NormStyle;
5
6#[inline(always)]
8#[inline]
13pub fn gelu_erf(x: f32) -> f32 {
14 0.5 * x * (1.0 + erf_f32(x * std::f32::consts::FRAC_1_SQRT_2))
15}
16
17#[inline]
19pub fn erf_f32(x: f32) -> f32 {
20 let a = x.abs();
21 let t = 1.0 / (1.0 + 0.327_591_1 * a);
22 let y = 1.0
23 - (((((1.061_405_4 * t - 1.453_152_1) * t + 1.421_413_8) * t - 0.284_496_74) * t
24 + 0.254_829_6)
25 * t)
26 * (-a * a).exp();
27 if x < 0.0 { -y } else { y }
28}
29
30pub fn gelu_tanh(x: f32) -> f32 {
31 const C: f32 = 0.797_884_6; 0.5 * x * (1.0 + (C * (x + 0.044_715 * x * x * x)).tanh())
33}
34
35pub fn silu(x: f32) -> f32 {
36 x / (1.0 + (-x).exp())
37}
38
39pub fn rms_norm(input: &[f32], weight: &[f32], eps: f64, style: NormStyle) -> Vec<f32> {
47 let mut out = vec![0.0f32; input.len()];
48 rms_norm_into(input, weight, eps, style, &mut out);
49 out
50}
51
52pub fn rms_norm_into(input: &[f32], weight: &[f32], eps: f64, style: NormStyle, out: &mut [f32]) {
55 let n = input.len();
56 debug_assert_eq!(out.len(), n);
57 let mean_sq: f64 = input.iter().map(|&x| (x as f64) * (x as f64)).sum::<f64>() / n as f64;
58 let inv_rms = 1.0 / (mean_sq + eps).sqrt() as f32;
59 match style {
60 NormStyle::Qwen => {
61 for (o, (&x, &w)) in out.iter_mut().zip(input.iter().zip(weight)) {
62 *o = x * inv_rms * w;
63 }
64 }
65 NormStyle::Gemma => {
66 for (o, (&x, &w)) in out.iter_mut().zip(input.iter().zip(weight)) {
67 *o = x * inv_rms * (1.0 + w);
68 }
69 }
70 }
71}
72
73pub fn add_rmsnorm_fused_into(
77 h: &mut [f32],
78 delta: &[f32],
79 weight: &[f32],
80 eps: f64,
81 style: NormStyle,
82 out: &mut [f32],
83) {
84 let n = h.len();
85 debug_assert_eq!(delta.len(), n);
86 debug_assert_eq!(out.len(), n);
87 let mut mean_sq = 0.0f64;
88 for (hv, &dv) in h.iter_mut().zip(delta) {
89 let val = *hv + dv;
90 *hv = val;
91 mean_sq += (val as f64) * (val as f64);
92 }
93 mean_sq /= n as f64;
94 let inv_rms = 1.0 / (mean_sq + eps).sqrt() as f32;
95 match style {
96 NormStyle::Qwen => {
97 for (o, (&x, &w)) in out.iter_mut().zip(h.iter().zip(weight)) {
98 *o = x * inv_rms * w;
99 }
100 }
101 NormStyle::Gemma => {
102 for (o, (&x, &w)) in out.iter_mut().zip(h.iter().zip(weight)) {
103 *o = x * inv_rms * (1.0 + w);
104 }
105 }
106 }
107}
108
109pub fn sparse_ffn_forward(
116 hidden_states: &[f32],
117 gate_proj_full: &[f32], up_proj_full: &[f32], down_proj_full: &[f32], hidden_size: usize,
121 intermediate_size: usize,
122 active_indices: &[u16],
123 pool: Option<&Pool>,
124) -> Vec<f32> {
125 let n_active = active_indices.len();
126 if n_active == 0 {
127 return vec![0.0; hidden_states.len()];
128 }
129
130 let seq_len = hidden_states.len() / hidden_size;
131 let mut output = vec![0.0f32; seq_len * hidden_size];
132
133 for s in 0..seq_len {
134 let h = &hidden_states[s * hidden_size..(s + 1) * hidden_size];
135
136 let mut act = vec![0.0f32; n_active];
139 let neuron_act = |ai: usize| -> f32 {
140 let idx = active_indices[ai];
141 let row = idx as usize * hidden_size;
142 if row + hidden_size > gate_proj_full.len() {
143 return 0.0;
144 }
145 let mut gate_sum = 0.0f32;
146 let mut up_sum = 0.0f32;
147 for k in 0..hidden_size {
148 gate_sum += gate_proj_full[row + k] * h[k];
149 up_sum += up_proj_full[row + k] * h[k];
150 }
151 silu(gate_sum) * up_sum
152 };
153 match pool {
154 Some(pool) if n_active >= 256 => {
155 let act_ptr = SendMut(act.as_mut_ptr());
156 pool.run(&move |widx, n| {
157 let chunk = n_active.div_ceil(n);
158 let start = widx * chunk;
159 let end = (start + chunk).min(n_active);
160 for ai in start..end {
161 unsafe { *act_ptr.at(ai) = neuron_act(ai) };
163 }
164 });
165 }
166 _ => {
167 for (ai, dst) in act.iter_mut().enumerate() {
168 *dst = neuron_act(ai);
169 }
170 }
171 }
172
173 let out = &mut output[s * hidden_size..(s + 1) * hidden_size];
175 for (ai, &idx) in active_indices.iter().enumerate() {
176 let val = act[ai];
177 if val.abs() < 1e-12 {
178 continue;
179 }
180 for k in 0..hidden_size {
181 out[k] += down_proj_full[k * intermediate_size + idx as usize] * val;
182 }
183 }
184 }
185
186 output
187}
188
189#[allow(clippy::too_many_arguments)]
193pub fn sparse_ffn_forward_pair(
194 h1: &[f32],
195 h2: &[f32],
196 gate_proj_full: &[f32],
197 up_proj_full: &[f32],
198 down_proj_full: &[f32],
199 hidden_size: usize,
200 intermediate_size: usize,
201 active_indices: &[u16],
202 pool: Option<&Pool>,
203) -> (Vec<f32>, Vec<f32>) {
204 let n_active = active_indices.len();
205 let mut out1 = vec![0.0f32; hidden_size];
206 let mut out2 = vec![0.0f32; hidden_size];
207 if n_active == 0 {
208 return (out1, out2);
209 }
210
211 let mut act1 = vec![0.0f32; n_active];
212 let mut act2 = vec![0.0f32; n_active];
213 let neuron_pair = |ai: usize| -> (f32, f32) {
214 let idx = active_indices[ai];
215 let row = idx as usize * hidden_size;
216 if row + hidden_size > gate_proj_full.len() {
217 return (0.0, 0.0);
218 }
219 let (mut g1, mut u1, mut g2, mut u2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
220 for k in 0..hidden_size {
221 let gw = gate_proj_full[row + k];
222 let uw = up_proj_full[row + k];
223 g1 += gw * h1[k];
224 g2 += gw * h2[k];
225 u1 += uw * h1[k];
226 u2 += uw * h2[k];
227 }
228 (silu(g1) * u1, silu(g2) * u2)
229 };
230 match pool {
231 Some(pool) if n_active >= 256 => {
232 let a1 = SendMut(act1.as_mut_ptr());
233 let a2 = SendMut(act2.as_mut_ptr());
234 pool.run(&move |widx, n| {
235 let chunk = n_active.div_ceil(n);
236 let start = widx * chunk;
237 let end = (start + chunk).min(n_active);
238 for ai in start..end {
239 let (v1, v2) = neuron_pair(ai);
240 unsafe {
242 *a1.at(ai) = v1;
243 *a2.at(ai) = v2;
244 }
245 }
246 });
247 }
248 _ => {
249 for ai in 0..n_active {
250 let (v1, v2) = neuron_pair(ai);
251 act1[ai] = v1;
252 act2[ai] = v2;
253 }
254 }
255 }
256
257 for (ai, &idx) in active_indices.iter().enumerate() {
259 let (v1, v2) = (act1[ai], act2[ai]);
260 if v1.abs() < 1e-12 && v2.abs() < 1e-12 {
261 continue;
262 }
263 for k in 0..hidden_size {
264 let dw = down_proj_full[k * intermediate_size + idx as usize];
265 out1[k] += dw * v1;
266 out2[k] += dw * v2;
267 }
268 }
269 (out1, out2)
270}
271
272#[derive(Clone, Copy)]
273struct SendMut(*mut f32);
274unsafe impl Send for SendMut {}
275unsafe impl Sync for SendMut {}
276
277impl SendMut {
278 #[inline]
280 fn at(self, i: usize) -> *mut f32 {
281 unsafe { self.0.add(i) }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_silu() {
291 assert!((silu(0.0) - 0.0).abs() < 1e-6);
292 assert!((silu(1.0) - 0.7310586).abs() < 1e-4);
293 }
294
295 #[test]
296 fn rms_norm_qwen_multiplies_by_w() {
297 let input = vec![1.0, 2.0, 3.0, 4.0];
298 let weight = vec![1.0; 4]; let out = rms_norm(&input, &weight, 1e-6, NormStyle::Qwen);
300 let rms = (30.0_f64 / 4.0).sqrt() as f32;
301 assert!((out[0] - 1.0 / rms).abs() < 1e-4);
302
303 let out2 = rms_norm(&input, &[2.0; 4], 1e-6, NormStyle::Qwen);
305 assert!((out2[0] - 2.0 / rms).abs() < 1e-4);
306 }
307
308 #[test]
309 fn rms_norm_gemma_adds_one() {
310 let input = vec![1.0, 2.0, 3.0, 4.0];
311 let weight = vec![0.0; 4]; let out = rms_norm(&input, &weight, 1e-6, NormStyle::Gemma);
313 let rms = (30.0_f64 / 4.0).sqrt() as f32;
314 assert!((out[0] - 1.0 / rms).abs() < 1e-4);
315 }
316
317 #[test]
318 fn test_sparse_ffn_full_active() {
319 let hidden = 4;
320 let inter = 8;
321 let h = vec![1.0f32; hidden];
322 let gate = vec![0.1f32; inter * hidden];
323 let up = vec![0.1f32; inter * hidden];
324 let down = vec![0.1f32; hidden * inter];
325 let active: Vec<u16> = (0..inter as u16).collect();
326
327 let out = sparse_ffn_forward(&h, &gate, &up, &down, hidden, inter, &active, None);
328 assert_eq!(out.len(), hidden);
329 assert!(out.iter().all(|&v| v.abs() > 1e-6));
330 }
331
332 #[test]
333 fn test_sparse_ffn_half_active() {
334 let hidden = 4;
335 let inter = 8;
336 let h = vec![1.0f32; hidden];
337 let gate = vec![0.1f32; inter * hidden];
338 let up = vec![0.1f32; inter * hidden];
339 let down = vec![0.1f32; hidden * inter];
340
341 let full: Vec<u16> = (0..inter as u16).collect();
342 let half: Vec<u16> = (0..inter as u16 / 2).collect();
343
344 let out_full = sparse_ffn_forward(&h, &gate, &up, &down, hidden, inter, &full, None);
345 let out_half = sparse_ffn_forward(&h, &gate, &up, &down, hidden, inter, &half, None);
346
347 let mag_full: f32 = out_full.iter().map(|v| v.abs()).sum();
348 let mag_half: f32 = out_half.iter().map(|v| v.abs()).sum();
349 assert!(mag_half < mag_full);
350 assert!(mag_half > 0.0);
351 }
352}