/home/noah/src/realizar/src/inference/norm.rs
Line | Count | Source |
1 | | //! Normalization and position encoding operations |
2 | | //! |
3 | | //! Provides layer normalization, RMS normalization, and rotary position embeddings |
4 | | //! used in transformer inference. |
5 | | //! |
6 | | //! ## Normalization Functions |
7 | | //! |
8 | | //! - [`simd_layer_norm`] - Standard layer normalization with mean and variance |
9 | | //! - [`simd_rms_norm`] - RMS normalization (faster, used in LLaMA/Mistral) |
10 | | //! |
11 | | //! ## Position Encoding |
12 | | //! |
13 | | //! - [`apply_rope`] - Rotary Position Embeddings (RoPE) |
14 | | |
15 | | /// SIMD-accelerated layer normalization |
16 | | /// |
17 | | /// LayerNorm(x) = (x - mean) / sqrt(var + eps) * weight + bias |
18 | | /// |
19 | | /// # Arguments |
20 | | /// |
21 | | /// * `input` - Input vector to normalize |
22 | | /// * `weight` - Scale parameters (gamma) |
23 | | /// * `bias` - Optional shift parameters (beta) |
24 | | /// * `eps` - Small constant for numerical stability (typically 1e-5) |
25 | | /// |
26 | | /// # Example |
27 | | /// |
28 | | /// ``` |
29 | | /// use realizar::inference::simd_layer_norm; |
30 | | /// |
31 | | /// let input = vec![1.0, 2.0, 3.0, 4.0]; |
32 | | /// let weight = vec![1.0, 1.0, 1.0, 1.0]; |
33 | | /// let output = simd_layer_norm(&input, &weight, None, 1e-5); |
34 | | /// |
35 | | /// // Output should have mean ≈ 0 and std ≈ 1 |
36 | | /// let mean: f32 = output.iter().sum::<f32>() / output.len() as f32; |
37 | | /// assert!(mean.abs() < 1e-5); |
38 | | /// ``` |
39 | | #[must_use] |
40 | 11 | pub fn simd_layer_norm(input: &[f32], weight: &[f32], bias: Option<&[f32]>, eps: f32) -> Vec<f32> { |
41 | 11 | let n = input.len(); |
42 | 11 | if n == 0 { |
43 | 1 | return Vec::new(); |
44 | 10 | } |
45 | | |
46 | | // Compute mean |
47 | 10 | let mean: f32 = input.iter().sum::<f32>() / n as f32; |
48 | | |
49 | | // Compute variance |
50 | 37 | let var10 : f3210 = input10 .iter10 ().map10 (|x| (x - mean).powi(2)).sum10 ::<f32>() / n as f3210 ; |
51 | | |
52 | | // Normalize |
53 | 10 | let inv_std = 1.0 / (var + eps).sqrt(); |
54 | 37 | let mut output10 : Vec<f32>10 = input10 .iter10 ().map10 (|x| (x - mean) * inv_std).collect10 (); |
55 | | |
56 | | // Apply affine transformation |
57 | 37 | for (i, out) in output.iter_mut()10 .enumerate10 () { |
58 | 37 | *out *= weight[i]; |
59 | 37 | if let Some(b4 ) = bias { |
60 | 4 | *out += b[i]; |
61 | 33 | } |
62 | | } |
63 | | |
64 | 10 | output |
65 | 11 | } |
66 | | |
67 | | /// SIMD-accelerated RMS normalization |
68 | | /// |
69 | | /// RMSNorm(x) = x / sqrt(mean(x^2) + eps) * weight |
70 | | /// |
71 | | /// RMS normalization is faster than LayerNorm as it doesn't require |
72 | | /// computing the mean. Used in LLaMA, Mistral, and other modern LLMs. |
73 | | /// |
74 | | /// # Arguments |
75 | | /// |
76 | | /// * `input` - Input vector to normalize |
77 | | /// * `weight` - Scale parameters |
78 | | /// * `eps` - Small constant for numerical stability (typically 1e-5) |
79 | | /// |
80 | | /// # Example |
81 | | /// |
82 | | /// ``` |
83 | | /// use realizar::inference::simd_rms_norm; |
84 | | /// |
85 | | /// let input = vec![1.0, 2.0, 3.0]; |
86 | | /// let weight = vec![1.0, 1.0, 1.0]; |
87 | | /// let output = simd_rms_norm(&input, &weight, 1e-5); |
88 | | /// |
89 | | /// // RMS of [1,2,3] ≈ 2.16, so normalized ≈ [0.46, 0.93, 1.39] |
90 | | /// assert!((output[0] - 0.4629).abs() < 0.01); |
91 | | /// ``` |
92 | | #[must_use] |
93 | 12 | pub fn simd_rms_norm(input: &[f32], weight: &[f32], eps: f32) -> Vec<f32> { |
94 | 12 | let n = input.len(); |
95 | 12 | if n == 0 { |
96 | 1 | return Vec::new(); |
97 | 11 | } |
98 | | |
99 | | // Compute RMS |
100 | 33 | let sum_sq11 : f3211 = input11 .iter11 ().map11 (|x| x * x).sum11 (); |
101 | 11 | let rms = (sum_sq / n as f32 + eps).sqrt(); |
102 | 11 | let inv_rms = 1.0 / rms; |
103 | | |
104 | | // Normalize and scale |
105 | 11 | input |
106 | 11 | .iter() |
107 | 11 | .zip(weight.iter()) |
108 | 33 | .map11 (|(x, w)| x * inv_rms * w) |
109 | 11 | .collect() |
110 | 12 | } |
111 | | |
112 | | /// Apply rotary position embeddings (RoPE) |
113 | | /// |
114 | | /// RoPE encodes position information by rotating pairs of dimensions. |
115 | | /// This enables relative position encoding that generalizes to longer sequences. |
116 | | /// |
117 | | /// # Arguments |
118 | | /// |
119 | | /// * `x` - Mutable slice to apply RoPE to [hidden_dim] |
120 | | /// * `hidden_dim` - Total hidden dimension (must equal x.len()) |
121 | | /// * `num_heads` - Number of attention heads |
122 | | /// * `position` - Token position in sequence (0-indexed) |
123 | | /// * `theta` - Base frequency (typically 10000.0) |
124 | | /// |
125 | | /// # Algorithm |
126 | | /// |
127 | | /// For each head and each pair of dimensions (i, i + d/2): |
128 | | /// ```text |
129 | | /// freq = 1 / theta^(2i/d) |
130 | | /// angle = position * freq |
131 | | /// x[i] = x[i] * cos(angle) - x[i+d/2] * sin(angle) |
132 | | /// x[i+d/2] = x[i] * sin(angle) + x[i+d/2] * cos(angle) |
133 | | /// ``` |
134 | | /// |
135 | | /// # Example |
136 | | /// |
137 | | /// ``` |
138 | | /// use realizar::inference::apply_rope; |
139 | | /// |
140 | | /// let mut x = vec![1.0; 64]; // 64 hidden dim |
141 | | /// apply_rope(&mut x, 64, 4, 0, 10000.0); // Position 0 |
142 | | /// |
143 | | /// // At position 0, rotations are identity (angle = 0) |
144 | | /// assert!((x[0] - 1.0).abs() < 1e-5); |
145 | | /// ``` |
146 | 17 | pub fn apply_rope(x: &mut [f32], hidden_dim: usize, num_heads: usize, position: usize, theta: f32) { |
147 | 17 | let head_dim = hidden_dim / num_heads; |
148 | 17 | let half_dim = head_dim / 2; |
149 | | |
150 | 52 | for h in 0..num_heads17 { |
151 | 52 | let head_offset = h * head_dim; |
152 | | |
153 | 120 | for i in 0..half_dim52 { |
154 | 120 | let freq = 1.0 / theta.powf(2.0 * i as f32 / head_dim as f32); |
155 | 120 | let angle = position as f32 * freq; |
156 | 120 | let cos_val = angle.cos(); |
157 | 120 | let sin_val = angle.sin(); |
158 | 120 | |
159 | 120 | let idx0 = head_offset + i; |
160 | 120 | let idx1 = head_offset + i + half_dim; |
161 | 120 | |
162 | 120 | let x0 = x[idx0]; |
163 | 120 | let x1 = x[idx1]; |
164 | 120 | |
165 | 120 | x[idx0] = x0 * cos_val - x1 * sin_val; |
166 | 120 | x[idx1] = x0 * sin_val + x1 * cos_val; |
167 | 120 | } |
168 | | } |
169 | 17 | } |
170 | | |
171 | | // ============================================================================ |
172 | | // EXTREME TDD: Comprehensive Tests |
173 | | // ============================================================================ |
174 | | |
175 | | #[cfg(test)] |
176 | | mod tests { |
177 | | use super::*; |
178 | | |
179 | | // ------------------------------------------------------------------------ |
180 | | // simd_layer_norm Tests |
181 | | // ------------------------------------------------------------------------ |
182 | | |
183 | | #[test] |
184 | 1 | fn test_layer_norm_basic() { |
185 | 1 | let input = vec![1.0, 2.0, 3.0, 4.0]; |
186 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
187 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
188 | | |
189 | | // Output should have mean ≈ 0 |
190 | 1 | let mean: f32 = output.iter().sum::<f32>() / output.len() as f32; |
191 | 1 | assert!(mean.abs() < 1e-5, "Mean should be ~0, got {}"0 , mean); |
192 | | |
193 | | // Output should have std ≈ 1 |
194 | 4 | let var1 : f321 = output.iter()1 .map1 (|x| (x - mean).powi(2)).sum1 ::<f32>() / output.len() as f321 ; |
195 | 1 | let std = var.sqrt(); |
196 | 1 | assert!((std - 1.0).abs() < 0.01, "Std should be ~1, got {}"0 , std); |
197 | 1 | } |
198 | | |
199 | | #[test] |
200 | 1 | fn test_layer_norm_with_scale() { |
201 | 1 | let input = vec![1.0, 2.0, 3.0, 4.0]; |
202 | 1 | let weight = vec![2.0, 2.0, 2.0, 2.0]; |
203 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
204 | | |
205 | | // With scale=2, std should be ~2 |
206 | 1 | let mean: f32 = output.iter().sum::<f32>() / output.len() as f32; |
207 | 4 | let var1 : f321 = output.iter()1 .map1 (|x| (x - mean).powi(2)).sum1 ::<f32>() / output.len() as f321 ; |
208 | 1 | let std = var.sqrt(); |
209 | 1 | assert!((std - 2.0).abs() < 0.01, "Std should be ~2, got {}"0 , std); |
210 | 1 | } |
211 | | |
212 | | #[test] |
213 | 1 | fn test_layer_norm_with_bias() { |
214 | 1 | let input = vec![1.0, 2.0, 3.0, 4.0]; |
215 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
216 | 1 | let bias = vec![5.0, 5.0, 5.0, 5.0]; |
217 | 1 | let output = simd_layer_norm(&input, &weight, Some(&bias), 1e-5); |
218 | | |
219 | | // With bias=5, mean should be ~5 |
220 | 1 | let mean: f32 = output.iter().sum::<f32>() / output.len() as f32; |
221 | 1 | assert!((mean - 5.0).abs() < 0.01, "Mean should be ~5, got {}"0 , mean); |
222 | 1 | } |
223 | | |
224 | | #[test] |
225 | 1 | fn test_layer_norm_empty() { |
226 | 1 | let input: Vec<f32> = vec![]; |
227 | 1 | let weight: Vec<f32> = vec![]; |
228 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
229 | 1 | assert!(output.is_empty()); |
230 | 1 | } |
231 | | |
232 | | #[test] |
233 | 1 | fn test_layer_norm_single_element() { |
234 | 1 | let input = vec![5.0]; |
235 | 1 | let weight = vec![1.0]; |
236 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
237 | | // Single element: mean=5, var=0, so normalized = 0 |
238 | 1 | assert!((output[0]).abs() < 1e-3); |
239 | 1 | } |
240 | | |
241 | | #[test] |
242 | 1 | fn test_layer_norm_uniform_input() { |
243 | 1 | let input = vec![3.0, 3.0, 3.0, 3.0]; |
244 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
245 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
246 | | // Uniform input: mean=3, var=0+eps, normalized ≈ 0 |
247 | 5 | for &x4 in &output { |
248 | 4 | assert!(x.abs() < 0.1); |
249 | | } |
250 | 1 | } |
251 | | |
252 | | #[test] |
253 | 1 | fn test_layer_norm_negative_values() { |
254 | 1 | let input = vec![-2.0, -1.0, 1.0, 2.0]; |
255 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
256 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
257 | | |
258 | | // Mean should be 0, values should preserve sign relationship |
259 | 1 | assert!(output[0] < output[1]); |
260 | 1 | assert!(output[1] < output[2]); |
261 | 1 | assert!(output[2] < output[3]); |
262 | 1 | } |
263 | | |
264 | | #[test] |
265 | 1 | fn test_layer_norm_large_values() { |
266 | 1 | let input = vec![1000.0, 2000.0, 3000.0, 4000.0]; |
267 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
268 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
269 | | |
270 | | // Should still have mean ≈ 0 and std ≈ 1 |
271 | 1 | let mean: f32 = output.iter().sum::<f32>() / output.len() as f32; |
272 | 1 | assert!(mean.abs() < 1e-4); |
273 | 1 | } |
274 | | |
275 | | // ------------------------------------------------------------------------ |
276 | | // simd_rms_norm Tests |
277 | | // ------------------------------------------------------------------------ |
278 | | |
279 | | #[test] |
280 | 1 | fn test_rms_norm_basic() { |
281 | 1 | let input = vec![1.0, 2.0, 3.0]; |
282 | 1 | let weight = vec![1.0, 1.0, 1.0]; |
283 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
284 | | |
285 | | // RMS = sqrt((1 + 4 + 9) / 3) = sqrt(14/3) ≈ 2.16 |
286 | 1 | let rms = (14.0_f32 / 3.0).sqrt(); |
287 | 3 | let expected1 : Vec<f32>1 = input.iter()1 .map1 (|x| x / rms).collect1 (); |
288 | | |
289 | 3 | for (out, exp) in output.iter()1 .zip1 (expected.iter()1 ) { |
290 | 3 | assert!((out - exp).abs() < 1e-5); |
291 | | } |
292 | 1 | } |
293 | | |
294 | | #[test] |
295 | 1 | fn test_rms_norm_with_scale() { |
296 | 1 | let input = vec![1.0, 2.0, 3.0]; |
297 | 1 | let weight = vec![2.0, 2.0, 2.0]; |
298 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
299 | | |
300 | 1 | let rms = (14.0_f32 / 3.0).sqrt(); |
301 | 3 | let expected1 : Vec<f32>1 = input.iter()1 .map1 (|x| x / rms * 2.0).collect1 (); |
302 | | |
303 | 3 | for (out, exp) in output.iter()1 .zip1 (expected.iter()1 ) { |
304 | 3 | assert!((out - exp).abs() < 1e-5); |
305 | | } |
306 | 1 | } |
307 | | |
308 | | #[test] |
309 | 1 | fn test_rms_norm_empty() { |
310 | 1 | let input: Vec<f32> = vec![]; |
311 | 1 | let weight: Vec<f32> = vec![]; |
312 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
313 | 1 | assert!(output.is_empty()); |
314 | 1 | } |
315 | | |
316 | | #[test] |
317 | 1 | fn test_rms_norm_single_element() { |
318 | 1 | let input = vec![5.0]; |
319 | 1 | let weight = vec![1.0]; |
320 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
321 | | // RMS of [5] = 5, so output = 5/5 = 1 |
322 | 1 | assert!((output[0] - 1.0).abs() < 1e-5); |
323 | 1 | } |
324 | | |
325 | | #[test] |
326 | 1 | fn test_rms_norm_unit_vector() { |
327 | | // For input [1, 0, 0] with weight [1, 1, 1] |
328 | | // RMS = sqrt(mean(x^2)) = sqrt(1/3) |
329 | | // output = input / RMS * weight = [sqrt(3), 0, 0] |
330 | 1 | let input = vec![1.0, 0.0, 0.0]; |
331 | 1 | let weight = vec![1.0, 1.0, 1.0]; |
332 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
333 | | |
334 | 1 | let expected = 3.0_f32.sqrt(); // sqrt(3) ≈ 1.732 |
335 | 1 | assert!( |
336 | 1 | (output[0] - expected).abs() < 1e-4, |
337 | 0 | "Expected {}, got {}", |
338 | | expected, |
339 | 0 | output[0] |
340 | | ); |
341 | 1 | assert!(output[1].abs() < 1e-5); |
342 | 1 | assert!(output[2].abs() < 1e-5); |
343 | 1 | } |
344 | | |
345 | | #[test] |
346 | 1 | fn test_rms_norm_zeros() { |
347 | 1 | let input = vec![0.0, 0.0, 0.0]; |
348 | 1 | let weight = vec![1.0, 1.0, 1.0]; |
349 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
350 | | |
351 | | // RMS = sqrt(eps), output = 0 / sqrt(eps) = 0 |
352 | 4 | for &x3 in &output { |
353 | 3 | assert!(x.abs() < 1e-2); |
354 | | } |
355 | 1 | } |
356 | | |
357 | | #[test] |
358 | 1 | fn test_rms_norm_negative_values() { |
359 | 1 | let input = vec![-3.0, 4.0]; |
360 | 1 | let weight = vec![1.0, 1.0]; |
361 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
362 | | |
363 | | // RMS = sqrt((9 + 16) / 2) = sqrt(12.5) ≈ 3.54 |
364 | 1 | let rms = (12.5_f32).sqrt(); |
365 | 1 | assert!((output[0] - (-3.0 / rms)).abs() < 1e-5); |
366 | 1 | assert!((output[1] - (4.0 / rms)).abs() < 1e-5); |
367 | 1 | } |
368 | | |
369 | | #[test] |
370 | 1 | fn test_rms_norm_preserves_direction() { |
371 | 1 | let input = vec![3.0, 4.0]; // 3-4-5 right triangle |
372 | 1 | let weight = vec![1.0, 1.0]; |
373 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
374 | | |
375 | | // Direction should be preserved: output[1]/output[0] = 4/3 |
376 | 1 | let ratio = output[1] / output[0]; |
377 | 1 | assert!((ratio - 4.0 / 3.0).abs() < 1e-5); |
378 | 1 | } |
379 | | |
380 | | // ------------------------------------------------------------------------ |
381 | | // apply_rope Tests |
382 | | // ------------------------------------------------------------------------ |
383 | | |
384 | | #[test] |
385 | 1 | fn test_rope_position_zero() { |
386 | 1 | let mut x = vec![1.0, 2.0, 3.0, 4.0]; // 4 hidden, 1 head, head_dim=4 |
387 | 1 | let original = x.clone(); |
388 | 1 | apply_rope(&mut x, 4, 1, 0, 10000.0); |
389 | | |
390 | | // At position 0, angle = 0, cos(0) = 1, sin(0) = 0 |
391 | | // So output should equal input |
392 | 4 | for (out, orig) in x.iter()1 .zip1 (original.iter()1 ) { |
393 | 4 | assert!((out - orig).abs() < 1e-5); |
394 | | } |
395 | 1 | } |
396 | | |
397 | | #[test] |
398 | 1 | fn test_rope_rotation_property() { |
399 | 1 | let mut x = vec![1.0, 0.0, 0.0, 1.0]; // 4 hidden, 1 head |
400 | 1 | apply_rope(&mut x, 4, 1, 1, 10000.0); |
401 | | |
402 | | // After rotation, magnitude should be preserved for each pair |
403 | 1 | let mag0 = (x[0] * x[0] + x[2] * x[2]).sqrt(); |
404 | 1 | let mag1 = (x[1] * x[1] + x[3] * x[3]).sqrt(); |
405 | | |
406 | 1 | assert!((mag0 - 1.0).abs() < 1e-5, "Magnitude of pair 0 should be 1"0 ); |
407 | 1 | assert!((mag1 - 1.0).abs() < 1e-5, "Magnitude of pair 1 should be 1"0 ); |
408 | 1 | } |
409 | | |
410 | | #[test] |
411 | 1 | fn test_rope_multiple_heads() { |
412 | 1 | let mut x = vec![1.0; 8]; // 8 hidden, 2 heads, head_dim = 4 |
413 | 1 | let original = x.clone(); |
414 | 1 | apply_rope(&mut x, 8, 2, 0, 10000.0); |
415 | | |
416 | | // At position 0, should be unchanged |
417 | 8 | for (out, orig) in x.iter()1 .zip1 (original.iter()1 ) { |
418 | 8 | assert!((out - orig).abs() < 1e-5); |
419 | | } |
420 | 1 | } |
421 | | |
422 | | #[test] |
423 | 1 | fn test_rope_different_positions() { |
424 | 1 | let mut x1 = vec![1.0; 4]; |
425 | 1 | let mut x2 = vec![1.0; 4]; |
426 | | |
427 | 1 | apply_rope(&mut x1, 4, 1, 0, 10000.0); |
428 | 1 | apply_rope(&mut x2, 4, 1, 1, 10000.0); |
429 | | |
430 | | // Different positions should give different results |
431 | 1 | assert!((x1[0] - x2[0]).abs() > 1e-6 || (0 x1[1]0 - x2[1]).abs() > 1e-6); |
432 | 1 | } |
433 | | |
434 | | #[test] |
435 | 1 | fn test_rope_theta_scaling() { |
436 | 1 | let mut x1 = vec![1.0; 4]; |
437 | 1 | let mut x2 = vec![1.0; 4]; |
438 | | |
439 | 1 | apply_rope(&mut x1, 4, 1, 10, 10000.0); |
440 | 1 | apply_rope(&mut x2, 4, 1, 10, 1000.0); |
441 | | |
442 | | // Different theta affects higher frequency dimensions (i > 0) |
443 | | // For i=0, freq = 1/theta^0 = 1 (same regardless of theta) |
444 | | // For i=1, freq = 1/theta^(2/head_dim) which differs by theta |
445 | | // So check x[1] or x[3] (the second pair uses i=1) |
446 | 1 | assert!( |
447 | 1 | (x1[1] - x2[1]).abs() > 1e-5 || (0 x1[3]0 - x2[3]).abs() > 1e-5, |
448 | 0 | "Different theta should give different results for non-zero frequency indices" |
449 | | ); |
450 | 1 | } |
451 | | |
452 | | #[test] |
453 | 1 | fn test_rope_large_position() { |
454 | 1 | let mut x = vec![1.0, 2.0, 3.0, 4.0]; |
455 | 1 | apply_rope(&mut x, 4, 1, 1000, 10000.0); |
456 | | |
457 | | // Results should be finite |
458 | 5 | for &val4 in &x { |
459 | 4 | assert!(val.is_finite()); |
460 | | } |
461 | 1 | } |
462 | | |
463 | | #[test] |
464 | 1 | fn test_rope_eight_heads() { |
465 | 1 | let hidden_dim = 64; |
466 | 1 | let num_heads = 8; |
467 | 1 | let mut x = vec![0.5; hidden_dim]; |
468 | | |
469 | 1 | apply_rope(&mut x, hidden_dim, num_heads, 5, 10000.0); |
470 | | |
471 | | // All values should be finite |
472 | 65 | for &val64 in &x { |
473 | 64 | assert!(val.is_finite()); |
474 | | } |
475 | 1 | } |
476 | | |
477 | | #[test] |
478 | 1 | fn test_rope_preserves_length() { |
479 | 1 | let mut x = vec![3.0, 4.0, 0.0, 0.0]; // pairs: (3,0), (4,0) |
480 | 1 | apply_rope(&mut x, 4, 1, 1, 10000.0); |
481 | | |
482 | 1 | assert_eq!(x.len(), 4); |
483 | 1 | } |
484 | | |
485 | | // ------------------------------------------------------------------------ |
486 | | // Integration Tests |
487 | | // ------------------------------------------------------------------------ |
488 | | |
489 | | #[test] |
490 | 1 | fn test_norm_then_rope() { |
491 | 1 | let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; |
492 | 1 | let weight = vec![1.0; 8]; |
493 | | |
494 | | // First normalize |
495 | 1 | let normalized = simd_rms_norm(&input, &weight, 1e-5); |
496 | | |
497 | | // Then apply RoPE |
498 | 1 | let mut output = normalized; |
499 | 1 | apply_rope(&mut output, 8, 2, 5, 10000.0); |
500 | | |
501 | | // Results should be finite |
502 | 9 | for &val8 in &output { |
503 | 8 | assert!(val.is_finite()); |
504 | | } |
505 | 1 | } |
506 | | |
507 | | #[test] |
508 | 1 | fn test_layer_norm_vs_rms_norm() { |
509 | 1 | let input = vec![1.0, 2.0, 3.0, 4.0]; |
510 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
511 | | |
512 | 1 | let ln_output = simd_layer_norm(&input, &weight, None, 1e-5); |
513 | 1 | let rms_output = simd_rms_norm(&input, &weight, 1e-5); |
514 | | |
515 | | // LayerNorm centers (mean=0), RMSNorm doesn't |
516 | 1 | let ln_mean: f32 = ln_output.iter().sum::<f32>() / 4.0; |
517 | 1 | let rms_mean: f32 = rms_output.iter().sum::<f32>() / 4.0; |
518 | | |
519 | 1 | assert!(ln_mean.abs() < 1e-5, "LayerNorm should have mean ~0"0 ); |
520 | 1 | assert!(rms_mean.abs() > 0.1, "RMSNorm should not center"0 ); |
521 | 1 | } |
522 | | |
523 | | // ------------------------------------------------------------------------ |
524 | | // Edge Cases |
525 | | // ------------------------------------------------------------------------ |
526 | | |
527 | | #[test] |
528 | 1 | fn test_layer_norm_eps_impact() { |
529 | 1 | let input = vec![0.0, 0.0, 0.0, 0.0]; |
530 | 1 | let weight = vec![1.0, 1.0, 1.0, 1.0]; |
531 | | |
532 | | // With var=0, eps prevents division by zero |
533 | 1 | let output = simd_layer_norm(&input, &weight, None, 1e-5); |
534 | 5 | for &val4 in &output { |
535 | 4 | assert!(val.is_finite()); |
536 | | } |
537 | 1 | } |
538 | | |
539 | | #[test] |
540 | 1 | fn test_rms_norm_eps_impact() { |
541 | 1 | let input = vec![0.0, 0.0]; |
542 | 1 | let weight = vec![1.0, 1.0]; |
543 | | |
544 | | // With sum_sq=0, eps prevents division by zero |
545 | 1 | let output = simd_rms_norm(&input, &weight, 1e-5); |
546 | 3 | for &val2 in &output { |
547 | 2 | assert!(val.is_finite()); |
548 | | } |
549 | 1 | } |
550 | | |
551 | | #[test] |
552 | 1 | fn test_rope_half_dim_calculation() { |
553 | | // Test with various head dimensions |
554 | 4 | for (hidden_dim, num_heads) in [(8, 2)1 , (16, 4)1 , (32, 8)1 , (64, 16)1 ] { |
555 | 4 | let mut x = vec![1.0; hidden_dim]; |
556 | 4 | apply_rope(&mut x, hidden_dim, num_heads, 1, 10000.0); |
557 | | |
558 | | // Should not panic and should produce finite values |
559 | 124 | for &val120 in &x { |
560 | 120 | assert!(val.is_finite()); |
561 | | } |
562 | | } |
563 | 1 | } |
564 | | } |