singe_kernel/cpu/
fused.rs1#[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}