Skip to main content

singe_kernel/cpu/
fused.rs

1//! Inference-oriented fused operations that combine common model steps.
2
3#[cfg(feature = "dtype-bf16")]
4use half::bf16;
5#[cfg(feature = "dtype-f16")]
6use half::f16;
7
8pub fn rms_norm_gated_silu(
9    input: &[f32],
10    gate: &[f32],
11    weight: &[f32],
12    rows: usize,
13    cols: usize,
14    eps: f32,
15    weight_offset: f32,
16) -> Vec<f32> {
17    let mut out = vec![0.0f32; rows * cols];
18    for row in 0..rows {
19        let row_start = row * cols;
20        let variance = input[row_start..row_start + cols]
21            .iter()
22            .map(|value| value * value)
23            .sum::<f32>()
24            / cols as f32;
25        let inv_rms = 1.0 / (variance + eps).sqrt();
26        for (column, weight_value) in weight.iter().copied().enumerate().take(cols) {
27            let offset = row_start + column;
28            let gate_value = gate[offset];
29            let silu_gate = gate_value / (1.0 + (-gate_value).exp());
30            out[offset] = input[offset] * inv_rms * (weight_value + weight_offset) * silu_gate;
31        }
32    }
33    out
34}
35
36pub fn rms_norm(input: &[f32], weight: &[f32], rows: usize, cols: usize, eps: f32) -> Vec<f32> {
37    let mut out = vec![0.0f32; rows * cols];
38    for row in 0..rows {
39        let row_start = row * cols;
40        let variance = input[row_start..row_start + cols]
41            .iter()
42            .map(|value| value * value)
43            .sum::<f32>()
44            / cols as f32;
45        let inv_rms = 1.0 / (variance + eps).sqrt();
46        for (column, weight_value) in weight.iter().copied().enumerate().take(cols) {
47            let offset = row_start + column;
48            out[offset] = input[offset] * inv_rms * weight_value;
49        }
50    }
51    out
52}
53
54pub fn rms_norm_weight_offset(
55    input: &[f32],
56    weight: &[f32],
57    rows: usize,
58    cols: usize,
59    eps: f32,
60    weight_offset: f32,
61) -> Vec<f32> {
62    let mut out = vec![0.0f32; rows * cols];
63    for row in 0..rows {
64        let row_start = row * cols;
65        let variance = input[row_start..row_start + cols]
66            .iter()
67            .map(|value| value * value)
68            .sum::<f32>()
69            / cols as f32;
70        let inv_rms = 1.0 / (variance + eps).sqrt();
71        for (column, weight_value) in weight.iter().copied().enumerate().take(cols) {
72            let offset = row_start + column;
73            out[offset] = input[offset] * inv_rms * (weight_value + weight_offset);
74        }
75    }
76    out
77}
78
79pub fn silu_and_mul_packed(input: &[f32], rows: usize, hidden: usize) -> Vec<f32> {
80    let mut out = vec![0.0f32; rows * hidden];
81    for row in 0..rows {
82        let input_row = row * hidden * 2;
83        let output_row = row * hidden;
84        for column in 0..hidden {
85            let gate = input[input_row + column];
86            let up = input[input_row + hidden + column];
87            out[output_row + column] = gate / (1.0 + (-gate).exp()) * up;
88        }
89    }
90    out
91}
92
93pub fn mhc_apply_residual_f32(
94    x: &[f32],
95    f_out: &[f32],
96    y: &[f32],
97    batch: usize,
98    n: usize,
99    channels: usize,
100) -> Vec<f32> {
101    let mut out = vec![0.0f32; batch * n * channels];
102    let y_row = n * (n + 2);
103    for batch_index in 0..batch {
104        for token in 0..n {
105            for channel in 0..channels {
106                let mut sum =
107                    y[batch_index * y_row + n + token] * f_out[batch_index * channels + channel];
108                for source_token in 0..n {
109                    let y_res = y[batch_index * y_row + 2 * n + token * n + source_token];
110                    let x_value = x[batch_index * n * channels + source_token * channels + channel];
111                    sum += y_res * x_value;
112                }
113                out[batch_index * n * channels + token * channels + channel] = sum;
114            }
115        }
116    }
117    out
118}
119
120pub fn mhc_sinkhorn_f32(y: &[f32], batch: usize, n: usize) -> Vec<f32> {
121    let mut out = y.to_vec();
122    let y_row = n * (n + 2);
123    for batch_index in 0..batch {
124        let base = batch_index * y_row + 2 * n;
125        let mut matrix = vec![0.0f32; n * n];
126        for index in 0..n * n {
127            matrix[index] = out[base + index].exp();
128        }
129        for _ in 0..20 {
130            for row in 0..n {
131                let row_start = row * n;
132                let row_sum = matrix[row_start..row_start + n].iter().sum::<f32>();
133                for column in 0..n {
134                    matrix[row_start + column] /= row_sum;
135                }
136            }
137            for column in 0..n {
138                let mut column_sum = 0.0f32;
139                for row in 0..n {
140                    column_sum += matrix[row * n + column];
141                }
142                for row in 0..n {
143                    matrix[row * n + column] /= column_sum;
144                }
145            }
146        }
147        out[base..base + n * n].copy_from_slice(&matrix);
148    }
149    out
150}
151
152pub fn mhc_gemm_rms_scale_f32(
153    x: &[f32],
154    w: &[f32],
155    bias: &[f32],
156    rows: usize,
157    columns: usize,
158    reduction: usize,
159    n: usize,
160    alpha_pre: f32,
161    alpha_post: f32,
162    alpha_res: f32,
163) -> (Vec<f32>, Vec<f32>) {
164    let mut y = vec![0.0f32; rows * columns];
165    let mut r = vec![0.0f32; rows];
166    for row in 0..rows {
167        let mut rms_sum = 0.0f32;
168        for k in 0..reduction {
169            let value = x[row * reduction + k];
170            rms_sum += value * value;
171        }
172        let rms = (rms_sum / reduction as f32).sqrt();
173        r[row] = rms;
174        for column in 0..columns {
175            let mut dot = 0.0f32;
176            for k in 0..reduction {
177                dot += x[row * reduction + k] * w[k * columns + column];
178            }
179            let scale = if column < n {
180                alpha_pre
181            } else if column < 2 * n {
182                alpha_post
183            } else {
184                alpha_res
185            };
186            let linear = dot * scale / rms + bias[column];
187            y[row * columns + column] = if column < n {
188                1.0 / (1.0 + (-linear).exp())
189            } else if column < 2 * n {
190                2.0 / (1.0 + (-linear).exp())
191            } else {
192                linear
193            };
194        }
195    }
196    (y, r)
197}
198
199#[cfg(feature = "dtype-f16")]
200pub fn half_vec(values: &[f32]) -> Vec<f16> {
201    values.iter().copied().map(f16::from_f32).collect()
202}
203
204#[cfg(feature = "dtype-f16")]
205pub fn half_to_f32(values: &[f16]) -> Vec<f32> {
206    values.iter().map(|value| value.to_f32()).collect()
207}
208
209#[cfg(feature = "dtype-bf16")]
210pub fn bfloat_vec(values: &[f32]) -> Vec<bf16> {
211    values.iter().copied().map(bf16::from_f32).collect()
212}
213
214#[cfg(feature = "dtype-bf16")]
215pub fn bfloat_to_f32(values: &[bf16]) -> Vec<f32> {
216    values.iter().map(|value| value.to_f32()).collect()
217}
218
219#[cfg(feature = "dtype-bf16")]
220pub fn round_bfloat_vec(values: &[f32]) -> Vec<f32> {
221    values
222        .iter()
223        .copied()
224        .map(bf16::from_f32)
225        .map(|value| value.to_f32())
226        .collect()
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn rms_norm_weight_offset_changes_outputs() {
235        let input = vec![0.5f32, -1.0, 2.0, -0.25, 1.5, -0.75];
236        let weight = vec![0.25f32, -0.5, 0.75];
237        let rows = 2usize;
238        let cols = 3usize;
239        let eps = 1e-5f32;
240
241        let standard = rms_norm_weight_offset(&input, &weight, rows, cols, eps, 0.0);
242        let offset_output = rms_norm_weight_offset(&input, &weight, rows, cols, eps, 1.0);
243
244        assert_ne!(standard, offset_output);
245        for row in 0..rows {
246            let row_start = row * cols;
247            let variance = input[row_start..row_start + cols]
248                .iter()
249                .map(|value| value * value)
250                .sum::<f32>()
251                / cols as f32;
252            let inv_rms = 1.0 / (variance + eps).sqrt();
253            for column in 0..cols {
254                let offset = row_start + column;
255                let expected_offset_contribution = input[offset] * inv_rms;
256                singe_core::assert_close!(
257                    &[offset_output[offset] - standard[offset]],
258                    &[expected_offset_contribution],
259                    1e-6,
260                );
261            }
262        }
263    }
264
265    #[test]
266    fn gated_rms_norm_zero_offset_is_distinct_from_offset_one() {
267        let input = vec![0.5f32, -1.0, 2.0];
268        let gate = vec![1.0f32, -0.5, 0.25];
269        let weight = vec![0.25f32, -0.5, 0.75];
270        let rows = 1usize;
271        let cols = 3usize;
272        let eps = 1e-5f32;
273
274        let gated_zero = rms_norm_gated_silu(&input, &gate, &weight, rows, cols, eps, 0.0);
275        let gated_offset = rms_norm_gated_silu(&input, &gate, &weight, rows, cols, eps, 1.0);
276
277        assert_ne!(gated_zero, gated_offset);
278        let variance = input.iter().map(|value| value * value).sum::<f32>() / cols as f32;
279        let inv_rms = 1.0 / (variance + eps).sqrt();
280        for column in 0..cols {
281            let gate_value = gate[column];
282            let silu_gate = gate_value / (1.0 + (-gate_value).exp());
283            let expected_offset_contribution = input[column] * inv_rms * silu_gate;
284            singe_core::assert_close!(
285                &[gated_offset[column] - gated_zero[column]],
286                &[expected_offset_contribution],
287                1e-6,
288            );
289        }
290    }
291}