1use crate::activation::Activation;
7use crate::weight_init::{InitStrategy, WeightInitializer};
8use crate::NeuralResult;
9use scirs2_core::ndarray::{s, Array1, Array2, Array3};
10use scirs2_core::numeric::NumCast;
11use scirs2_core::random::thread_rng;
12use sklears_core::error::SklearsError;
13use sklears_core::types::FloatBounds;
14
15fn apply_activation_3d<T: FloatBounds>(activation: &Activation, input: &Array3<T>) -> Array3<T> {
17 match activation {
18 Activation::Identity => input.clone(),
19 Activation::Logistic => input.mapv(|val| {
20 let exp_neg = (-val).exp();
21 T::one() / (T::one() + exp_neg)
22 }),
23 Activation::Tanh => input.mapv(|val| val.tanh()),
24 Activation::Relu => input.mapv(|val| val.max(T::zero())),
25 _ => input.clone(), }
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub enum PositionalEncodingType {
32 Sinusoidal,
34 Learnable,
36 Relative,
38}
39
40#[derive(Debug, Clone)]
45#[allow(dead_code)] pub struct PositionalEncoding<T: FloatBounds> {
47 max_seq_len: usize,
49 d_model: usize,
51 encoding_type: PositionalEncodingType,
53 dropout_rate: T,
55 sinusoidal_encodings: Option<Array2<T>>,
57 position_embeddings: Option<Array2<T>>,
59 scale_embeddings: bool,
61}
62
63impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand + From<f64>> PositionalEncoding<T> {
64 pub fn new(
66 max_seq_len: usize,
67 d_model: usize,
68 encoding_type: PositionalEncodingType,
69 dropout_rate: T,
70 scale_embeddings: bool,
71 ) -> NeuralResult<Self> {
72 let mut pe = Self {
73 max_seq_len,
74 d_model,
75 encoding_type: encoding_type.clone(),
76 dropout_rate,
77 sinusoidal_encodings: None,
78 position_embeddings: None,
79 scale_embeddings,
80 };
81
82 match encoding_type {
83 PositionalEncodingType::Sinusoidal => {
84 pe.sinusoidal_encodings = Some(pe.create_sinusoidal_encodings()?);
85 }
86 PositionalEncodingType::Learnable => {
87 pe.position_embeddings = Some(pe.create_learnable_embeddings()?);
88 }
89 PositionalEncodingType::Relative => {
90 }
93 }
94
95 Ok(pe)
96 }
97
98 fn create_sinusoidal_encodings(&self) -> NeuralResult<Array2<T>> {
100 let mut encodings = Array2::zeros((self.max_seq_len, self.d_model));
101
102 for pos in 0..self.max_seq_len {
103 for i in 0..(self.d_model / 2) {
104 let angle = pos as f64 / 10000_f64.powf(2.0 * i as f64 / self.d_model as f64);
105
106 encodings[[pos, 2 * i]] = NumCast::from(angle.sin()).unwrap_or_else(T::zero);
108
109 if 2 * i + 1 < self.d_model {
111 encodings[[pos, 2 * i + 1]] =
112 NumCast::from(angle.cos()).unwrap_or_else(T::zero);
113 }
114 }
115 }
116
117 Ok(encodings)
118 }
119
120 fn create_learnable_embeddings(&self) -> NeuralResult<Array2<T>> {
122 let mut rng = thread_rng();
123 let initializer = WeightInitializer::new(InitStrategy::Normal {
124 mean: 0.0,
125 std: 0.02,
126 });
127
128 initializer.initialize_2d(&mut rng, (self.max_seq_len, self.d_model))
129 }
130
131 pub fn encode(&self, embeddings: &Array3<T>) -> NeuralResult<Array3<T>> {
133 let (batch_size, seq_len, d_model) = embeddings.dim();
134
135 if d_model != self.d_model {
136 return Err(SklearsError::InvalidParameter {
137 name: "d_model".to_string(),
138 reason: format!("expected {}, got {}", self.d_model, d_model),
139 });
140 }
141
142 if seq_len > self.max_seq_len {
143 return Err(SklearsError::InvalidParameter {
144 name: "seq_len".to_string(),
145 reason: format!(
146 "sequence length {} exceeds maximum {}",
147 seq_len, self.max_seq_len
148 ),
149 });
150 }
151
152 let mut output = embeddings.clone();
153
154 if self.scale_embeddings {
156 let scale_factor = NumCast::from((self.d_model as f64).sqrt()).unwrap_or(T::one());
157 output *= scale_factor;
158 }
159
160 match &self.encoding_type {
162 PositionalEncodingType::Sinusoidal => {
163 if let Some(ref encodings) = self.sinusoidal_encodings {
164 for batch in 0..batch_size {
165 for pos in 0..seq_len {
166 for dim in 0..d_model {
167 output[[batch, pos, dim]] += encodings[[pos, dim]];
168 }
169 }
170 }
171 }
172 }
173 PositionalEncodingType::Learnable => {
174 if let Some(ref embeddings) = self.position_embeddings {
175 for batch in 0..batch_size {
176 for pos in 0..seq_len {
177 for dim in 0..d_model {
178 output[[batch, pos, dim]] += embeddings[[pos, dim]];
179 }
180 }
181 }
182 }
183 }
184 PositionalEncodingType::Relative => {
185 }
188 }
189
190 Ok(output)
191 }
192
193 pub fn get_encodings(&self, seq_len: usize) -> NeuralResult<Array2<T>> {
195 if seq_len > self.max_seq_len {
196 return Err(SklearsError::InvalidParameter {
197 name: "seq_len".to_string(),
198 reason: format!(
199 "sequence length {} exceeds maximum {}",
200 seq_len, self.max_seq_len
201 ),
202 });
203 }
204
205 match &self.encoding_type {
206 PositionalEncodingType::Sinusoidal => {
207 if let Some(ref encodings) = self.sinusoidal_encodings {
208 Ok(encodings.slice(s![..seq_len, ..]).to_owned())
209 } else {
210 Err(SklearsError::InvalidParameter {
211 name: "position_encodings".to_string(),
212 reason: "sinusoidal encodings not initialized".to_string(),
213 })
214 }
215 }
216 PositionalEncodingType::Learnable => {
217 if let Some(ref embeddings) = self.position_embeddings {
218 Ok(embeddings.slice(s![..seq_len, ..]).to_owned())
219 } else {
220 Err(SklearsError::InvalidParameter {
221 name: "position_embeddings".to_string(),
222 reason: "learnable embeddings not initialized".to_string(),
223 })
224 }
225 }
226 PositionalEncodingType::Relative => {
227 Ok(Array2::zeros((seq_len, self.d_model)))
229 }
230 }
231 }
232
233 pub fn update_position_embeddings(
235 &mut self,
236 gradients: &Array2<T>,
237 learning_rate: T,
238 ) -> NeuralResult<()> {
239 if let PositionalEncodingType::Learnable = self.encoding_type {
240 if let Some(ref mut embeddings) = self.position_embeddings {
241 *embeddings = embeddings.clone() - gradients * learning_rate;
242 Ok(())
243 } else {
244 Err(SklearsError::InvalidParameter {
245 name: "position_embeddings".to_string(),
246 reason: "learnable embeddings not initialized".to_string(),
247 })
248 }
249 } else {
250 Err(SklearsError::InvalidParameter {
251 name: "encoding_type".to_string(),
252 reason: "position embeddings are not learnable".to_string(),
253 })
254 }
255 }
256
257 pub fn num_parameters(&self) -> usize {
259 match &self.encoding_type {
260 PositionalEncodingType::Sinusoidal => 0, PositionalEncodingType::Learnable => {
262 if let Some(ref embeddings) = self.position_embeddings {
263 embeddings.len()
264 } else {
265 0
266 }
267 }
268 PositionalEncodingType::Relative => 0, }
270 }
271}
272
273#[derive(Debug, Clone)]
278#[allow(dead_code)] pub struct MultiHeadAttention<T: FloatBounds> {
280 num_heads: usize,
282 d_model: usize,
284 d_k: usize,
286 w_q: Array2<T>,
288 w_k: Array2<T>,
290 w_v: Array2<T>,
292 w_o: Array2<T>,
294 b_q: Option<Array1<T>>,
296 b_k: Option<Array1<T>>,
298 b_v: Option<Array1<T>>,
300 b_o: Option<Array1<T>>,
302 dropout_rate: T,
304 use_bias: bool,
306 scale_factor: T,
308 cached_attention_weights: Option<Array3<T>>,
310}
311
312impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand + From<f64>> MultiHeadAttention<T> {
313 pub fn new(
315 d_model: usize,
316 num_heads: usize,
317 dropout_rate: T,
318 use_bias: bool,
319 ) -> NeuralResult<Self> {
320 if !d_model.is_multiple_of(num_heads) {
321 return Err(SklearsError::InvalidParameter {
322 name: "d_model".to_string(),
323 reason: format!(
324 "d_model ({}) must be divisible by num_heads ({})",
325 d_model, num_heads
326 ),
327 });
328 }
329
330 let d_k = d_model / num_heads;
331 let scale_factor = NumCast::from(1.0 / (d_k as f64).sqrt()).unwrap_or(T::one());
332
333 let mut rng = thread_rng();
335 let initializer = WeightInitializer::new(InitStrategy::XavierUniform);
336
337 let w_q = initializer.initialize_2d(&mut rng, (d_model, d_model))?;
338 let w_k = initializer.initialize_2d(&mut rng, (d_model, d_model))?;
339 let w_v = initializer.initialize_2d(&mut rng, (d_model, d_model))?;
340 let w_o = initializer.initialize_2d(&mut rng, (d_model, d_model))?;
341
342 let (b_q, b_k, b_v, b_o) = if use_bias {
343 (
344 Some(Array1::zeros(d_model)),
345 Some(Array1::zeros(d_model)),
346 Some(Array1::zeros(d_model)),
347 Some(Array1::zeros(d_model)),
348 )
349 } else {
350 (None, None, None, None)
351 };
352
353 Ok(Self {
354 num_heads,
355 d_model,
356 d_k,
357 w_q,
358 w_k,
359 w_v,
360 w_o,
361 b_q,
362 b_k,
363 b_v,
364 b_o,
365 dropout_rate,
366 use_bias,
367 scale_factor,
368 cached_attention_weights: None,
369 })
370 }
371
372 pub fn forward(
374 &mut self,
375 query: &Array3<T>,
376 key: &Array3<T>,
377 value: &Array3<T>,
378 mask: Option<&Array3<T>>,
379 ) -> NeuralResult<Array3<T>> {
380 let (batch_size, seq_len_q, _) = query.dim();
381 let (_, seq_len_k, _) = key.dim();
382 let (_, seq_len_v, _) = value.dim();
383
384 if seq_len_k != seq_len_v {
385 return Err(SklearsError::InvalidParameter {
386 name: "seq_len".to_string(),
387 reason: "key and value sequence lengths must match".to_string(),
388 });
389 }
390
391 let q = self.linear_projection(query, &self.w_q, self.b_q.as_ref())?;
393 let k = self.linear_projection(key, &self.w_k, self.b_k.as_ref())?;
394 let v = self.linear_projection(value, &self.w_v, self.b_v.as_ref())?;
395
396 let q_heads = self.reshape_for_heads(&q, batch_size, seq_len_q)?;
398 let k_heads = self.reshape_for_heads(&k, batch_size, seq_len_k)?;
399 let v_heads = self.reshape_for_heads(&v, batch_size, seq_len_v)?;
400
401 let (attended_values, attention_weights) =
403 self.scaled_dot_product_attention(&q_heads, &k_heads, &v_heads, mask)?;
404
405 self.cached_attention_weights = Some(attention_weights);
407
408 let concatenated = self.reshape_from_heads(&attended_values, batch_size, seq_len_q)?;
410
411 let output = self.linear_projection(&concatenated, &self.w_o, self.b_o.as_ref())?;
413
414 Ok(output)
415 }
416
417 fn linear_projection(
419 &self,
420 input: &Array3<T>,
421 weight: &Array2<T>,
422 bias: Option<&Array1<T>>,
423 ) -> NeuralResult<Array3<T>> {
424 let (batch_size, seq_len, d_model) = input.dim();
425 let mut output = Array3::zeros((batch_size, seq_len, d_model));
426
427 for batch in 0..batch_size {
428 let input_2d = input.slice(s![batch, .., ..]);
429 let projected = input_2d.dot(weight);
430 output.slice_mut(s![batch, .., ..]).assign(&projected);
431
432 if let Some(bias_vec) = bias {
433 for seq in 0..seq_len {
434 for dim in 0..d_model {
435 output[[batch, seq, dim]] += bias_vec[dim];
436 }
437 }
438 }
439 }
440
441 Ok(output)
442 }
443
444 fn reshape_for_heads(
446 &self,
447 input: &Array3<T>,
448 batch_size: usize,
449 seq_len: usize,
450 ) -> NeuralResult<Array3<T>> {
451 let mut output = Array3::zeros((batch_size * self.num_heads, seq_len, self.d_k));
454
455 for batch in 0..batch_size {
456 for head in 0..self.num_heads {
457 let head_idx = batch * self.num_heads + head;
458 let start_dim = head * self.d_k;
459 let _end_dim = start_dim + self.d_k;
460
461 for seq in 0..seq_len {
462 for dim in 0..self.d_k {
463 output[[head_idx, seq, dim]] = input[[batch, seq, start_dim + dim]];
464 }
465 }
466 }
467 }
468
469 Ok(output)
470 }
471
472 fn reshape_from_heads(
474 &self,
475 input: &Array3<T>,
476 batch_size: usize,
477 seq_len: usize,
478 ) -> NeuralResult<Array3<T>> {
479 let mut output = Array3::zeros((batch_size, seq_len, self.d_model));
482
483 for batch in 0..batch_size {
484 for head in 0..self.num_heads {
485 let head_idx = batch * self.num_heads + head;
486 let start_dim = head * self.d_k;
487
488 for seq in 0..seq_len {
489 for dim in 0..self.d_k {
490 output[[batch, seq, start_dim + dim]] = input[[head_idx, seq, dim]];
491 }
492 }
493 }
494 }
495
496 Ok(output)
497 }
498
499 fn scaled_dot_product_attention(
501 &self,
502 q: &Array3<T>,
503 k: &Array3<T>,
504 v: &Array3<T>,
505 mask: Option<&Array3<T>>,
506 ) -> NeuralResult<(Array3<T>, Array3<T>)> {
507 let (batch_heads, seq_len_q, d_k) = q.dim();
508 let (_, seq_len_k, _) = k.dim();
509
510 let mut scores = Array3::zeros((batch_heads, seq_len_q, seq_len_k));
512
513 for batch_head in 0..batch_heads {
514 let q_slice = q.slice(s![batch_head, .., ..]);
515 let k_slice = k.slice(s![batch_head, .., ..]);
516 let score_slice = q_slice.dot(&k_slice.t());
517 scores
518 .slice_mut(s![batch_head, .., ..])
519 .assign(&score_slice);
520 }
521
522 scores *= self.scale_factor;
524
525 if let Some(mask_tensor) = mask {
527 let neg_inf = NumCast::from(-1e9).unwrap_or(T::zero());
529 for batch_head in 0..batch_heads {
530 for i in 0..seq_len_q {
531 for j in 0..seq_len_k {
532 let mask_batch = batch_head % mask_tensor.dim().0;
533 if mask_tensor[[mask_batch, i, j]] == T::zero() {
534 scores[[batch_head, i, j]] = neg_inf;
535 }
536 }
537 }
538 }
539 }
540
541 let attention_weights = self.softmax_3d(&scores)?;
543
544 let mut output = Array3::zeros((batch_heads, seq_len_q, d_k));
546
547 for batch_head in 0..batch_heads {
548 let attn_slice = attention_weights.slice(s![batch_head, .., ..]);
549 let v_slice = v.slice(s![batch_head, .., ..]);
550 let out_slice = attn_slice.dot(&v_slice);
551 output.slice_mut(s![batch_head, .., ..]).assign(&out_slice);
552 }
553
554 Ok((output, attention_weights))
555 }
556
557 fn softmax_3d(&self, input: &Array3<T>) -> NeuralResult<Array3<T>> {
559 let (dim0, dim1, dim2) = input.dim();
560 let mut output = Array3::zeros((dim0, dim1, dim2));
561
562 for i in 0..dim0 {
563 for j in 0..dim1 {
564 let mut max_val = input[[i, j, 0]];
566 for k in 1..dim2 {
567 if input[[i, j, k]] > max_val {
568 max_val = input[[i, j, k]];
569 }
570 }
571
572 let mut sum = T::zero();
574 for k in 0..dim2 {
575 let exp_val = (input[[i, j, k]] - max_val).exp();
576 output[[i, j, k]] = exp_val;
577 sum += exp_val;
578 }
579
580 for k in 0..dim2 {
582 output[[i, j, k]] /= sum;
583 }
584 }
585 }
586
587 Ok(output)
588 }
589
590 pub fn get_attention_weights(&self) -> Option<&Array3<T>> {
592 self.cached_attention_weights.as_ref()
593 }
594
595 pub fn num_parameters(&self) -> usize {
597 let weight_params = self.w_q.len() + self.w_k.len() + self.w_v.len() + self.w_o.len();
598 let bias_params = if self.use_bias {
599 self.b_q.as_ref().map_or(0, |b| b.len())
600 + self.b_k.as_ref().map_or(0, |b| b.len())
601 + self.b_v.as_ref().map_or(0, |b| b.len())
602 + self.b_o.as_ref().map_or(0, |b| b.len())
603 } else {
604 0
605 };
606 weight_params + bias_params
607 }
608}
609
610#[derive(Debug, Clone)]
612#[allow(dead_code)] pub struct FeedForward<T: FloatBounds> {
614 w1: Array2<T>,
616 w2: Array2<T>,
618 b1: Option<Array1<T>>,
620 b2: Option<Array1<T>>,
622 activation: Activation,
624 dropout_rate: T,
626 use_bias: bool,
628}
629
630impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> FeedForward<T> {
631 pub fn new(
633 d_model: usize,
634 d_ff: usize,
635 activation: Activation,
636 dropout_rate: T,
637 use_bias: bool,
638 ) -> NeuralResult<Self> {
639 let mut rng = thread_rng();
641 let initializer = WeightInitializer::new(InitStrategy::XavierUniform);
642
643 let w1 = initializer.initialize_2d(&mut rng, (d_model, d_ff))?;
644 let w2 = initializer.initialize_2d(&mut rng, (d_ff, d_model))?;
645
646 let (b1, b2) = if use_bias {
647 (Some(Array1::zeros(d_ff)), Some(Array1::zeros(d_model)))
648 } else {
649 (None, None)
650 };
651
652 Ok(Self {
653 w1,
654 w2,
655 b1,
656 b2,
657 activation,
658 dropout_rate,
659 use_bias,
660 })
661 }
662
663 pub fn forward(&mut self, input: &Array3<T>) -> NeuralResult<Array3<T>> {
665 let (batch_size, seq_len, d_model) = input.dim();
666
667 let mut hidden = Array3::zeros((batch_size, seq_len, self.w1.dim().1));
669
670 for batch in 0..batch_size {
671 let input_2d = input.slice(s![batch, .., ..]);
672 let hidden_2d = input_2d.dot(&self.w1);
673 hidden.slice_mut(s![batch, .., ..]).assign(&hidden_2d);
674
675 if let Some(ref bias) = self.b1 {
676 for seq in 0..seq_len {
677 for dim in 0..bias.len() {
678 hidden[[batch, seq, dim]] += bias[dim];
679 }
680 }
681 }
682 }
683
684 hidden = apply_activation_3d(&self.activation, &hidden);
686
687 let mut output = Array3::zeros((batch_size, seq_len, d_model));
689
690 for batch in 0..batch_size {
691 let hidden_2d = hidden.slice(s![batch, .., ..]);
692 let output_2d = hidden_2d.dot(&self.w2);
693 output.slice_mut(s![batch, .., ..]).assign(&output_2d);
694
695 if let Some(ref bias) = self.b2 {
696 for seq in 0..seq_len {
697 for dim in 0..bias.len() {
698 output[[batch, seq, dim]] += bias[dim];
699 }
700 }
701 }
702 }
703
704 Ok(output)
705 }
706
707 pub fn num_parameters(&self) -> usize {
709 let weight_params = self.w1.len() + self.w2.len();
710 let bias_params = if self.use_bias {
711 self.b1.as_ref().map_or(0, |b| b.len()) + self.b2.as_ref().map_or(0, |b| b.len())
712 } else {
713 0
714 };
715 weight_params + bias_params
716 }
717}
718
719#[allow(non_snake_case)]
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 #[ignore]
726 fn test_sinusoidal_positional_encoding() {
727 let pe =
728 PositionalEncoding::<f64>::new(100, 64, PositionalEncodingType::Sinusoidal, 0.1, true)
729 .expect("operation should succeed");
730
731 assert_eq!(pe.max_seq_len, 100);
732 assert_eq!(pe.d_model, 64);
733 assert!(pe.sinusoidal_encodings.is_some());
734
735 let encodings = pe.get_encodings(10).expect("operation should succeed");
736 assert_eq!(encodings.dim(), (10, 64));
737 }
738
739 #[test]
740 #[ignore]
741 fn test_learnable_positional_encoding() {
742 let pe =
743 PositionalEncoding::<f64>::new(50, 32, PositionalEncodingType::Learnable, 0.1, false)
744 .expect("operation should succeed");
745
746 assert_eq!(pe.num_parameters(), 50 * 32);
747 assert!(pe.position_embeddings.is_some());
748
749 let encodings = pe.get_encodings(20).expect("operation should succeed");
750 assert_eq!(encodings.dim(), (20, 32));
751 }
752
753 #[test]
754 #[ignore]
755 fn test_positional_encoding_forward() {
756 let pe =
757 PositionalEncoding::<f64>::new(50, 16, PositionalEncodingType::Sinusoidal, 0.0, false)
758 .expect("operation should succeed");
759
760 let embeddings = Array3::zeros((2, 10, 16)); let encoded = pe.encode(&embeddings).expect("operation should succeed");
762
763 assert_eq!(encoded.dim(), (2, 10, 16));
764 }
765
766 #[test]
767 #[ignore]
768 fn test_multi_head_attention_creation() {
769 let mha =
770 MultiHeadAttention::<f64>::new(512, 8, 0.1, true).expect("construction should succeed");
771
772 assert_eq!(mha.num_heads, 8);
773 assert_eq!(mha.d_model, 512);
774 assert_eq!(mha.d_k, 64);
775 assert!(mha.use_bias);
776 }
777
778 #[test]
779 #[ignore]
780 fn test_multi_head_attention_invalid_dimensions() {
781 let result = MultiHeadAttention::<f64>::new(511, 8, 0.1, true);
782 assert!(result.is_err()); }
784
785 #[test]
786 #[ignore]
787 fn test_multi_head_attention_forward() {
788 let mut mha =
789 MultiHeadAttention::<f64>::new(64, 4, 0.0, false).expect("construction should succeed");
790
791 let query = Array3::zeros((2, 10, 64)); let key = Array3::zeros((2, 15, 64)); let value = Array3::zeros((2, 15, 64)); let output = mha
796 .forward(&query, &key, &value, None)
797 .expect("forward pass should succeed");
798 assert_eq!(output.dim(), (2, 10, 64));
799
800 assert!(mha.get_attention_weights().is_some());
802 }
803
804 #[test]
805 #[ignore]
806 fn test_multi_head_attention_with_mask() {
807 let mut mha =
808 MultiHeadAttention::<f64>::new(32, 2, 0.0, false).expect("construction should succeed");
809
810 let query = Array3::ones((1, 5, 32));
811 let key = Array3::ones((1, 5, 32));
812 let value = Array3::ones((1, 5, 32));
813
814 let mut mask = Array3::zeros((1, 5, 5));
816 for i in 0..5 {
817 for j in 0..=i {
818 mask[[0, i, j]] = 1.0;
819 }
820 }
821
822 let output = mha
823 .forward(&query, &key, &value, Some(&mask))
824 .expect("forward pass should succeed");
825 assert_eq!(output.dim(), (1, 5, 32));
826 }
827
828 #[test]
829 #[ignore]
830 fn test_feed_forward_network() {
831 let mut ffn = FeedForward::<f64>::new(256, 1024, Activation::Relu, 0.1, true)
832 .expect("construction should succeed");
833
834 let input = Array3::zeros((2, 10, 256));
835 let output = ffn.forward(&input).expect("forward pass should succeed");
836
837 assert_eq!(output.dim(), (2, 10, 256));
838 assert_eq!(ffn.num_parameters(), 256 * 1024 + 1024 * 256 + 1024 + 256);
839 }
840
841 #[test]
842 #[ignore]
843 fn test_positional_encoding_sequence_length_validation() {
844 let pe =
845 PositionalEncoding::<f64>::new(20, 16, PositionalEncodingType::Sinusoidal, 0.0, false)
846 .expect("operation should succeed");
847
848 let long_embeddings = Array3::zeros((1, 25, 16)); let result = pe.encode(&long_embeddings);
850 assert!(result.is_err());
851 }
852
853 #[test]
854 #[ignore]
855 fn test_positional_encoding_dimension_validation() {
856 let pe =
857 PositionalEncoding::<f64>::new(50, 16, PositionalEncodingType::Sinusoidal, 0.0, false)
858 .expect("operation should succeed");
859
860 let wrong_dim_embeddings = Array3::zeros((1, 10, 32)); let result = pe.encode(&wrong_dim_embeddings);
862 assert!(result.is_err());
863 }
864
865 #[test]
866 #[ignore]
867 fn test_learnable_embedding_updates() {
868 let mut pe =
869 PositionalEncoding::<f64>::new(10, 8, PositionalEncodingType::Learnable, 0.0, false)
870 .expect("operation should succeed");
871
872 let gradients = Array2::ones((10, 8));
873 let learning_rate = 0.01;
874
875 let result = pe.update_position_embeddings(&gradients, learning_rate);
876 assert!(result.is_ok());
877 }
878
879 #[test]
880 #[ignore]
881 fn test_sinusoidal_encoding_properties() {
882 let pe =
883 PositionalEncoding::<f64>::new(100, 64, PositionalEncodingType::Sinusoidal, 0.0, false)
884 .expect("operation should succeed");
885
886 let encodings = pe
887 .sinusoidal_encodings
888 .as_ref()
889 .expect("operation should succeed");
890
891 assert_eq!(encodings.dim(), (100, 64));
894
895 let pos_0 = encodings.row(0);
897 let pos_1 = encodings.row(1);
898
899 let mut different = false;
900 for i in 0..64 {
901 if (pos_0[i] - pos_1[i]).abs() > 1e-6_f64 {
902 different = true;
903 break;
904 }
905 }
906 assert!(
907 different,
908 "Different positions should have different encodings"
909 );
910 }
911}