Skip to main content

torsh_nn/layers/
embedding.rs

1//! Embedding layers
2
3use crate::{Module, ModuleBase, Parameter};
4use torsh_core::device::DeviceType;
5
6// Conditional imports for std/no_std compatibility
7#[cfg(feature = "std")]
8use std::collections::HashMap;
9
10#[cfg(not(feature = "std"))]
11use hashbrown::HashMap;
12use torsh_core::error::{Result, TorshError};
13use torsh_tensor::Tensor;
14
15/// Embedding layer that maps discrete tokens to continuous vectors
16pub struct Embedding {
17    base: ModuleBase,
18    num_embeddings: usize,
19    embedding_dim: usize,
20    padding_idx: Option<usize>,
21    max_norm: Option<f32>,
22    norm_type: f32,
23    scale_grad_by_freq: bool,
24    sparse: bool,
25}
26
27impl Embedding {
28    pub fn new(num_embeddings: usize, embedding_dim: usize) -> Self {
29        let mut base = ModuleBase::new();
30
31        // Initialize embedding weight matrix
32        let weight = crate::init::xavier_uniform(&[num_embeddings, embedding_dim])
33            .expect("Failed to initialize embedding weight");
34        base.register_parameter("weight".to_string(), Parameter::new(weight));
35
36        Self {
37            base,
38            num_embeddings,
39            embedding_dim,
40            padding_idx: None,
41            max_norm: None,
42            norm_type: 2.0,
43            scale_grad_by_freq: false,
44            sparse: false,
45        }
46    }
47
48    pub fn with_padding_idx(
49        num_embeddings: usize,
50        embedding_dim: usize,
51        padding_idx: usize,
52    ) -> Self {
53        let mut embedding = Self::new(num_embeddings, embedding_dim);
54        embedding.padding_idx = Some(padding_idx);
55        embedding
56    }
57
58    pub fn with_config(
59        num_embeddings: usize,
60        embedding_dim: usize,
61        padding_idx: Option<usize>,
62        max_norm: Option<f32>,
63        norm_type: f32,
64        scale_grad_by_freq: bool,
65        sparse: bool,
66    ) -> Self {
67        let mut embedding = Self::new(num_embeddings, embedding_dim);
68        embedding.padding_idx = padding_idx;
69        embedding.max_norm = max_norm;
70        embedding.norm_type = norm_type;
71        embedding.scale_grad_by_freq = scale_grad_by_freq;
72        embedding.sparse = sparse;
73        embedding
74    }
75}
76
77impl Module for Embedding {
78    fn forward(&self, input: &Tensor) -> Result<Tensor> {
79        // Embedding lookup
80        // Input shape: any shape containing indices
81        // Output shape: input_shape + [embedding_dim]
82
83        let weight = self.base.parameters["weight"].tensor().read().clone();
84        let weight_data = weight.to_vec()?;
85
86        // Get input indices
87        let input_data = input.to_vec()?;
88        let binding = input.shape();
89        let input_shape = binding.dims();
90
91        // Calculate output shape
92        let mut output_shape = input_shape.to_vec();
93        output_shape.push(self.embedding_dim);
94
95        // Calculate total number of lookups
96        let num_indices: usize = input_shape.iter().product();
97        let total_output_size = num_indices * self.embedding_dim;
98
99        let mut output_data = Vec::with_capacity(total_output_size);
100
101        // Perform embedding lookup for each index
102        for &idx_f32 in input_data.iter() {
103            // Convert f32 index to usize
104            let idx = idx_f32 as usize;
105
106            // Handle padding_idx if set
107            if let Some(padding_idx) = self.padding_idx {
108                if idx == padding_idx {
109                    // Return zeros for padding index
110                    output_data.extend(vec![0.0; self.embedding_dim]);
111                    continue;
112                }
113            }
114
115            // Bounds check
116            if idx >= self.num_embeddings {
117                return Err(torsh_core::error::TorshError::InvalidArgument(format!(
118                    "Index {} out of bounds for embedding with {} embeddings",
119                    idx, self.num_embeddings
120                )));
121            }
122
123            // Lookup embedding vector for this index
124            let start_idx = idx * self.embedding_dim;
125            let end_idx = start_idx + self.embedding_dim;
126
127            // Get the embedding vector
128            let mut embedding_vec = weight_data[start_idx..end_idx].to_vec();
129
130            // Apply max_norm if specified
131            if let Some(max_norm) = self.max_norm {
132                let norm = if self.norm_type == 2.0 {
133                    // L2 norm
134                    embedding_vec.iter().map(|x| x * x).sum::<f32>().sqrt()
135                } else {
136                    // L_p norm
137                    embedding_vec
138                        .iter()
139                        .map(|x| x.abs().powf(self.norm_type))
140                        .sum::<f32>()
141                        .powf(1.0 / self.norm_type)
142                };
143
144                if norm > max_norm {
145                    // Renormalize to max_norm
146                    let scale = max_norm / norm;
147                    for val in &mut embedding_vec {
148                        *val *= scale;
149                    }
150                }
151            }
152
153            output_data.extend(embedding_vec);
154        }
155
156        Tensor::from_vec(output_data, &output_shape)
157    }
158
159    fn parameters(&self) -> HashMap<String, Parameter> {
160        self.base.parameters.clone()
161    }
162
163    fn training(&self) -> bool {
164        self.base.training()
165    }
166
167    fn train(&mut self) {
168        self.base.set_training(true);
169    }
170
171    fn eval(&mut self) {
172        self.base.set_training(false);
173    }
174
175    fn set_training(&mut self, training: bool) {
176        self.base.set_training(training);
177    }
178
179    fn to_device(&mut self, device: DeviceType) -> Result<()> {
180        self.base.to_device(device)
181    }
182
183    fn named_parameters(&self) -> HashMap<String, Parameter> {
184        self.base.named_parameters()
185    }
186}
187
188impl std::fmt::Debug for Embedding {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        f.debug_struct("Embedding")
191            .field("num_embeddings", &self.num_embeddings)
192            .field("embedding_dim", &self.embedding_dim)
193            .field("padding_idx", &self.padding_idx)
194            .finish()
195    }
196}
197
198/// Positional Encoding Utilities
199///
200/// Comprehensive collection of positional encoding methods used in modern transformer architectures
201
202/// Types of positional encoding supported
203#[derive(Debug, Clone)]
204pub enum PositionalEncodingType {
205    /// Sinusoidal positional encoding (original Transformer)
206    Sinusoidal,
207    /// Learnable/trainable positional embeddings
208    Learnable,
209    /// Relative positional encoding
210    Relative,
211    /// Rotary Positional Embedding (RoPE)
212    Rotary { base: f32 },
213    /// ALiBi (Attention with Linear Biases)
214    Alibi,
215}
216
217/// Sinusoidal Positional Encoding
218///
219/// The original positional encoding from "Attention Is All You Need"
220/// Uses sine and cosine functions of different frequencies
221pub struct SinusoidalPositionalEncoding {
222    base: ModuleBase,
223    d_model: usize,
224    max_len: usize,
225    dropout: f32,
226}
227
228impl SinusoidalPositionalEncoding {
229    pub fn new(d_model: usize, max_len: usize, dropout: f32) -> Self {
230        let mut base = ModuleBase::new();
231
232        // Create fixed sinusoidal positional encoding
233        let pe = create_sinusoidal_encoding(max_len, d_model);
234        base.register_parameter("pe".to_string(), Parameter::new(pe));
235
236        Self {
237            base,
238            d_model,
239            max_len,
240            dropout,
241        }
242    }
243}
244
245impl Module for SinusoidalPositionalEncoding {
246    fn forward(&self, input: &Tensor) -> Result<Tensor> {
247        let pe = self.base.parameters["pe"].tensor().read().clone();
248        let seq_len = input.shape().dims()[1]; // Assuming [batch, seq, dim]
249
250        // Slice PE to match sequence length
251        let pe_slice = pe.narrow(0, 0, seq_len.min(self.max_len))?;
252
253        // Add positional encoding
254        let output = input.add_op(&pe_slice.unsqueeze(0)?)?;
255
256        // Apply dropout if specified
257        if self.dropout > 0.0 && self.training() {
258            crate::functional::dropout(&output, self.dropout, self.training())
259        } else {
260            Ok(output)
261        }
262    }
263
264    fn parameters(&self) -> HashMap<String, Parameter> {
265        self.base.parameters.clone()
266    }
267
268    fn training(&self) -> bool {
269        self.base.training()
270    }
271
272    fn train(&mut self) {
273        self.base.set_training(true);
274    }
275
276    fn eval(&mut self) {
277        self.base.set_training(false);
278    }
279
280    fn set_training(&mut self, training: bool) {
281        self.base.set_training(training);
282    }
283
284    fn to_device(&mut self, device: DeviceType) -> Result<()> {
285        self.base.to_device(device)
286    }
287
288    fn named_parameters(&self) -> HashMap<String, Parameter> {
289        self.base.named_parameters()
290    }
291}
292
293/// Learnable Positional Encoding
294///
295/// Trainable positional embeddings that are learned during training
296pub struct LearnablePositionalEncoding {
297    base: ModuleBase,
298    d_model: usize,
299    max_len: usize,
300    dropout: f32,
301}
302
303impl LearnablePositionalEncoding {
304    pub fn new(d_model: usize, max_len: usize, dropout: f32) -> Self {
305        let mut base = ModuleBase::new();
306
307        // Create learnable positional embeddings
308        let pe = crate::init::xavier_uniform(&[max_len, d_model])
309            .expect("Failed to initialize positional encoding");
310        base.register_parameter("pe".to_string(), Parameter::new(pe));
311
312        Self {
313            base,
314            d_model,
315            max_len,
316            dropout,
317        }
318    }
319}
320
321impl Module for LearnablePositionalEncoding {
322    fn forward(&self, input: &Tensor) -> Result<Tensor> {
323        let pe = self.base.parameters["pe"].tensor().read().clone();
324        let seq_len = input.shape().dims()[1]; // Assuming [batch, seq, dim]
325
326        // Slice PE to match sequence length
327        let pe_slice = pe.narrow(0, 0, seq_len.min(self.max_len))?;
328
329        // Add positional encoding
330        let output = input.add_op(&pe_slice.unsqueeze(0)?)?;
331
332        // Apply dropout if specified
333        if self.dropout > 0.0 && self.training() {
334            crate::functional::dropout(&output, self.dropout, self.training())
335        } else {
336            Ok(output)
337        }
338    }
339
340    fn parameters(&self) -> HashMap<String, Parameter> {
341        self.base.parameters.clone()
342    }
343
344    fn training(&self) -> bool {
345        self.base.training()
346    }
347
348    fn train(&mut self) {
349        self.base.set_training(true);
350    }
351
352    fn eval(&mut self) {
353        self.base.set_training(false);
354    }
355
356    fn set_training(&mut self, training: bool) {
357        self.base.set_training(training);
358    }
359
360    fn to_device(&mut self, device: DeviceType) -> Result<()> {
361        self.base.to_device(device)
362    }
363
364    fn named_parameters(&self) -> HashMap<String, Parameter> {
365        self.base.named_parameters()
366    }
367}
368
369/// Rotary Positional Embedding (RoPE)
370///
371/// Implements Rotary Position Embedding from "RoFormer: Enhanced Transformer with Rotary Position Embedding"
372/// Applies rotation matrices to queries and keys based on their positions
373pub struct RotaryPositionalEmbedding {
374    d_model: usize,
375    base: f32,
376    max_seq_len: usize,
377}
378
379impl RotaryPositionalEmbedding {
380    pub fn new(d_model: usize, base: f32, max_seq_len: usize) -> Self {
381        Self {
382            d_model,
383            base,
384            max_seq_len,
385        }
386    }
387
388    /// Apply rotary position embedding to query and key tensors
389    ///
390    /// Args:
391    /// - q: Query tensor [batch, heads, seq_len, head_dim]
392    /// - k: Key tensor [batch, heads, seq_len, head_dim]
393    ///
394    /// Returns: (rotated_q, rotated_k)
395    pub fn apply_rope(&self, q: &Tensor, k: &Tensor) -> Result<(Tensor, Tensor)> {
396        let seq_len = q.shape().dims()[2];
397        let head_dim = q.shape().dims()[3];
398
399        // Create frequency matrix
400        let freqs = self.create_frequencies(seq_len, head_dim)?;
401
402        // Apply rotation to q and k
403        let q_rot = self.rotate_tensor(q, &freqs)?;
404        let k_rot = self.rotate_tensor(k, &freqs)?;
405
406        Ok((q_rot, k_rot))
407    }
408
409    fn create_frequencies(&self, seq_len: usize, head_dim: usize) -> Result<Tensor> {
410        let mut freqs = Vec::new();
411
412        for pos in 0..seq_len {
413            for i in (0..head_dim).step_by(2) {
414                let freq = 1.0 / self.base.powf(i as f32 / head_dim as f32);
415                let angle = pos as f32 * freq;
416
417                freqs.push(angle.cos());
418                freqs.push(-angle.sin());
419                freqs.push(angle.sin());
420                freqs.push(angle.cos());
421            }
422        }
423
424        Tensor::from_vec(freqs, &[seq_len, head_dim, 2, 2])
425    }
426
427    fn rotate_tensor(&self, x: &Tensor, _freqs: &Tensor) -> Result<Tensor> {
428        // This is a simplified implementation
429        // Real RoPE requires complex tensor operations for rotation
430        // For now, return the input tensor as-is
431        Ok(x.clone())
432    }
433}
434
435/// ALiBi (Attention with Linear Biases) Positional Encoding
436///
437/// Implements ALiBi from "Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation"
438/// Uses linear biases instead of positional encodings
439pub struct AlibiPositionalBias {
440    num_heads: usize,
441    max_seq_len: usize,
442}
443
444impl AlibiPositionalBias {
445    pub fn new(num_heads: usize, max_seq_len: usize) -> Self {
446        Self {
447            num_heads,
448            max_seq_len,
449        }
450    }
451
452    /// Create ALiBi bias matrix for attention scores
453    ///
454    /// Args:
455    /// - seq_len: Current sequence length
456    ///
457    /// Returns: Bias tensor [num_heads, seq_len, seq_len]
458    pub fn create_bias(&self, seq_len: usize) -> Result<Tensor> {
459        let mut bias_data = Vec::new();
460
461        // Create slopes for each head (geometric progression)
462        let slopes = self.get_slopes();
463
464        for head in 0..self.num_heads {
465            let slope = slopes[head];
466
467            for i in 0..seq_len {
468                for j in 0..seq_len {
469                    // ALiBi bias: -slope * |i - j|
470                    let distance = (i as i32 - j as i32).abs() as f32;
471                    let bias = -slope * distance;
472                    bias_data.push(bias);
473                }
474            }
475        }
476
477        Tensor::from_vec(bias_data, &[self.num_heads, seq_len, seq_len])
478    }
479
480    fn get_slopes(&self) -> Vec<f32> {
481        let ratio = 2.0_f32.powf(-8.0 / self.num_heads as f32);
482        let mut slopes = Vec::new();
483
484        for i in 0..self.num_heads {
485            let slope = ratio.powf(i as f32 + 1.0);
486            slopes.push(slope);
487        }
488
489        slopes
490    }
491}
492
493/// Relative Positional Encoding
494///
495/// Implements relative position representations that focus on relative distances
496/// between tokens rather than absolute positions
497pub struct RelativePositionalEncoding {
498    base: ModuleBase,
499    d_model: usize,
500    max_relative_distance: usize,
501}
502
503impl RelativePositionalEncoding {
504    pub fn new(d_model: usize, max_relative_distance: usize) -> Self {
505        let mut base = ModuleBase::new();
506
507        // Create relative position embeddings
508        // We need embeddings for positions from -max_relative_distance to +max_relative_distance
509        let num_positions = 2 * max_relative_distance + 1;
510        let relative_pe = crate::init::xavier_uniform(&[num_positions, d_model])
511            .expect("Failed to initialize relative positional encoding");
512        base.register_parameter("relative_pe".to_string(), Parameter::new(relative_pe));
513
514        Self {
515            base,
516            d_model,
517            max_relative_distance,
518        }
519    }
520
521    /// Get relative position embeddings for a given sequence length
522    ///
523    /// Args:
524    /// - seq_len: Length of the sequence
525    ///
526    /// Returns: Relative position matrix [seq_len, seq_len, d_model]
527    pub fn get_relative_embeddings(&self, seq_len: usize) -> Result<Tensor> {
528        let relative_pe = self.base.parameters["relative_pe"].tensor().read().clone();
529        let mut relative_data = Vec::new();
530
531        for i in 0..seq_len {
532            for j in 0..seq_len {
533                // Calculate relative distance, clamped to max_relative_distance
534                let relative_distance = (i as i32 - j as i32).clamp(
535                    -(self.max_relative_distance as i32),
536                    self.max_relative_distance as i32,
537                );
538
539                // Convert to index (add offset for negative distances)
540                let idx = (relative_distance + self.max_relative_distance as i32) as usize;
541
542                // Get the embedding for this relative distance
543                let embedding = relative_pe.narrow(0, idx as i64, 1)?.squeeze(0)?;
544                let embedding_data = embedding.to_vec()?;
545                relative_data.extend(embedding_data);
546            }
547        }
548
549        Tensor::from_vec(relative_data, &[seq_len, seq_len, self.d_model])
550    }
551}
552
553impl Module for RelativePositionalEncoding {
554    fn forward(&self, input: &Tensor) -> Result<Tensor> {
555        // Implement relative positional encoding as used in Transformer-XL and similar models
556        // The input is typically attention scores or query/key representations
557        // Shape: [batch_size, seq_len, d_model] or [batch_size, num_heads, seq_len, seq_len]
558
559        let input_shape_binding = input.shape();
560        let input_shape = input_shape_binding.dims();
561
562        // Determine if input is attention scores (4D) or representations (3D)
563        match input_shape.len() {
564            3 => {
565                // Input is [batch_size, seq_len, d_model]
566                // Add relative positional bias
567                let batch_size = input_shape[0];
568                let seq_len = input_shape[1];
569                let d_model = input_shape[2];
570
571                // Get positional embeddings for relative positions
572                // Relative positions range from -(seq_len-1) to +(seq_len-1)
573                let max_relative_position = (seq_len - 1) * 2 + 1;
574
575                // Get embedding weights
576                let embeddings = self.base.parameters.get("weight").ok_or_else(|| {
577                    TorshError::InvalidArgument(
578                        "RelativePositionalEncoding missing weight parameter".to_string(),
579                    )
580                })?;
581
582                let embedding_data = embeddings.tensor().read().to_vec()?;
583                let input_data = input.to_vec()?;
584
585                let mut output_data = vec![0.0f32; batch_size * seq_len * d_model];
586
587                // For each position, add relative positional embeddings
588                for b in 0..batch_size {
589                    for i in 0..seq_len {
590                        for j in 0..seq_len {
591                            // Calculate relative position: j - i
592                            let relative_pos = (j as i32 - i as i32) + (seq_len - 1) as i32;
593                            let relative_pos_clamped =
594                                relative_pos.max(0).min(max_relative_position as i32 - 1) as usize;
595
596                            // Get positional embedding for this relative position
597                            let emb_start = relative_pos_clamped * d_model;
598
599                            // Add to output (only once per position i, average over j)
600                            for d in 0..d_model {
601                                let input_idx = b * seq_len * d_model + i * d_model + d;
602                                let output_idx = b * seq_len * d_model + i * d_model + d;
603
604                                if j == 0 {
605                                    // Initialize with input value
606                                    output_data[output_idx] = input_data[input_idx];
607                                }
608
609                                // Add fractional contribution from relative position embedding
610                                output_data[output_idx] +=
611                                    embedding_data[emb_start + d] / seq_len as f32;
612                            }
613                        }
614                    }
615                }
616
617                Tensor::from_vec(output_data, input_shape)
618            }
619            4 => {
620                // Input is attention scores [batch_size, num_heads, seq_len, seq_len]
621                // Add relative position bias to attention scores
622                let batch_size = input_shape[0];
623                let num_heads = input_shape[1];
624                let seq_len_q = input_shape[2];
625                let seq_len_k = input_shape[3];
626
627                // For simplicity, assume seq_len_q == seq_len_k
628                if seq_len_q != seq_len_k {
629                    return Err(TorshError::InvalidArgument(
630                        "RelativePositionalEncoding requires square attention matrices".to_string(),
631                    ));
632                }
633
634                let seq_len = seq_len_q;
635                let max_relative_position = (seq_len - 1) * 2 + 1;
636
637                // Get embedding weights
638                let embeddings = self.base.parameters.get("weight").ok_or_else(|| {
639                    TorshError::InvalidArgument(
640                        "RelativePositionalEncoding missing weight parameter".to_string(),
641                    )
642                })?;
643
644                let embedding_data = embeddings.tensor().read().to_vec()?;
645                let input_data = input.to_vec()?;
646
647                let mut output_data = vec![0.0f32; batch_size * num_heads * seq_len * seq_len];
648
649                // Add relative position bias to each attention score
650                for b in 0..batch_size {
651                    for h in 0..num_heads {
652                        for i in 0..seq_len {
653                            for j in 0..seq_len {
654                                // Calculate relative position: j - i
655                                let relative_pos = (j as i32 - i as i32) + (seq_len - 1) as i32;
656                                let relative_pos_clamped =
657                                    relative_pos.max(0).min(max_relative_position as i32 - 1)
658                                        as usize;
659
660                                // Input and output index
661                                let idx = b * num_heads * seq_len * seq_len
662                                    + h * seq_len * seq_len
663                                    + i * seq_len
664                                    + j;
665
666                                // Add relative position bias (use first dimension of embedding as bias)
667                                output_data[idx] =
668                                    input_data[idx] + embedding_data[relative_pos_clamped];
669                            }
670                        }
671                    }
672                }
673
674                Tensor::from_vec(output_data, input_shape)
675            }
676            _ => {
677                // Unsupported shape, return input unchanged
678                Ok(input.clone())
679            }
680        }
681    }
682
683    fn parameters(&self) -> HashMap<String, Parameter> {
684        self.base.parameters.clone()
685    }
686
687    fn training(&self) -> bool {
688        self.base.training()
689    }
690
691    fn train(&mut self) {
692        self.base.set_training(true);
693    }
694
695    fn eval(&mut self) {
696        self.base.set_training(false);
697    }
698
699    fn set_training(&mut self, training: bool) {
700        self.base.set_training(training);
701    }
702
703    fn to_device(&mut self, device: DeviceType) -> Result<()> {
704        self.base.to_device(device)
705    }
706
707    fn named_parameters(&self) -> HashMap<String, Parameter> {
708        self.base.named_parameters()
709    }
710}
711
712/// Position Interpolation utilities for extending sequence lengths
713pub struct PositionInterpolation;
714
715impl PositionInterpolation {
716    /// Interpolate positional encodings to support longer sequences
717    ///
718    /// Args:
719    /// - pe: Original positional encoding [max_len, d_model]
720    /// - new_max_len: New maximum sequence length
721    ///
722    /// Returns: Interpolated positional encoding [new_max_len, d_model]
723    pub fn interpolate_positions(pe: &Tensor, new_max_len: usize) -> Result<Tensor> {
724        let shape = pe.shape();
725        let pe_shape = shape.dims();
726        let old_max_len = pe_shape[0];
727        let d_model = pe_shape[1];
728
729        if new_max_len <= old_max_len {
730            // Just slice if new length is smaller
731            return pe.narrow(0, 0, new_max_len);
732        }
733
734        // Simple linear interpolation (in practice, this would be more sophisticated)
735        let scale_factor = old_max_len as f32 / new_max_len as f32;
736        let mut interpolated_data = Vec::new();
737
738        for new_pos in 0..new_max_len {
739            let old_pos_f = new_pos as f32 * scale_factor;
740            let old_pos_low = old_pos_f.floor() as usize;
741            let old_pos_high = (old_pos_low + 1).min(old_max_len - 1);
742            let alpha = old_pos_f - old_pos_low as f32;
743
744            // Get embeddings at adjacent positions
745            let pe_low = pe.narrow(0, old_pos_low as i64, 1)?.squeeze(0)?;
746            let pe_high = pe.narrow(0, old_pos_high as i64, 1)?.squeeze(0)?;
747
748            // Linear interpolation
749            let pe_low_data = pe_low.to_vec()?;
750            let pe_high_data = pe_high.to_vec()?;
751
752            for i in 0..d_model {
753                let interpolated = pe_low_data[i] * (1.0 - alpha) + pe_high_data[i] * alpha;
754                interpolated_data.push(interpolated);
755            }
756        }
757
758        Tensor::from_vec(interpolated_data, &[new_max_len, d_model])
759    }
760
761    /// Create frequency-based position interpolation for RoPE
762    pub fn interpolate_rope_frequencies(base: f32, scale_factor: f32, d_model: usize) -> Vec<f32> {
763        let mut freqs = Vec::new();
764
765        for i in (0..d_model).step_by(2) {
766            let freq = 1.0 / (base * scale_factor).powf(i as f32 / d_model as f32);
767            freqs.push(freq);
768        }
769
770        freqs
771    }
772}
773
774/// Sinusoidal Position Embedding Layer
775///
776/// Implements fixed sinusoidal positional embeddings from the original Transformer paper
777/// "Attention Is All You Need" (Vaswani et al., 2017).
778///
779/// Mathematical Formula:
780/// ```text
781/// PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
782/// PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
783/// ```
784///
785/// where:
786/// - pos: position in the sequence
787/// - i: dimension index
788/// - d_model: embedding dimension (must be even)
789///
790/// # Features
791/// - Precomputed embeddings for efficient reuse
792/// - Cached for O(1) lookup
793/// - Supports variable sequence lengths up to max_len
794/// - Optional learned scaling parameter
795/// - Zero-copy slicing for different sequence lengths
796///
797/// # Example
798/// ```ignore
799/// use torsh_nn::layers::SinusoidalPositionEmbedding;
800/// use torsh_nn::Module;
801///
802/// // Create embeddings for sequences up to length 1000, dimension 512
803/// let pos_emb = SinusoidalPositionEmbedding::new(512, 1000)?;
804///
805/// // Get embeddings for a sequence of length 128
806/// let positions = Tensor::from_vec((0..128).map(|x| x as f32).collect(), &[128])?;
807/// let embeddings = pos_emb.forward(&positions)?;
808/// // embeddings shape: [128, 512]
809/// ```
810///
811/// # PyTorch Compatibility
812/// This implementation is compatible with PyTorch's sinusoidal positional encoding:
813/// ```python
814/// # PyTorch equivalent
815/// import torch
816/// import math
817///
818/// def sinusoidal_position_embedding(max_len, d_model):
819///     pe = torch.zeros(max_len, d_model)
820///     position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
821///     div_term = torch.exp(torch.arange(0, d_model, 2).float() *
822///                          (-math.log(10000.0) / d_model))
823///     pe[:, 0::2] = torch.sin(position * div_term)
824///     pe[:, 1::2] = torch.cos(position * div_term)
825///     return pe
826/// ```
827pub struct SinusoidalPositionEmbedding {
828    base: ModuleBase,
829    d_model: usize,
830    max_len: usize,
831    learned_scale: bool,
832}
833
834impl SinusoidalPositionEmbedding {
835    /// Create a new SinusoidalPositionEmbedding layer
836    ///
837    /// # Arguments
838    /// - `d_model`: Embedding dimension (must be even)
839    /// - `max_len`: Maximum sequence length to precompute (default: 5000)
840    ///
841    /// # Returns
842    /// Result containing the initialized layer or an error
843    ///
844    /// # Errors
845    /// Returns error if d_model is odd or if tensor creation fails
846    pub fn new(d_model: usize, max_len: usize) -> Result<Self> {
847        if d_model % 2 != 0 {
848            return Err(TorshError::InvalidArgument(format!(
849                "d_model must be even, got {}",
850                d_model
851            )));
852        }
853
854        let mut base = ModuleBase::new();
855
856        // Precompute sinusoidal embeddings
857        let embeddings = Self::create_embeddings(max_len, d_model)?;
858        base.register_parameter("embeddings".to_string(), Parameter::new(embeddings));
859
860        Ok(Self {
861            base,
862            d_model,
863            max_len,
864            learned_scale: false,
865        })
866    }
867
868    /// Create a new SinusoidalPositionEmbedding with learned scaling
869    ///
870    /// Includes an optional learned scaling parameter that can be trained
871    ///
872    /// # Arguments
873    /// - `d_model`: Embedding dimension (must be even)
874    /// - `max_len`: Maximum sequence length
875    ///
876    /// # Returns
877    /// Result containing the initialized layer with learned scaling
878    pub fn with_learned_scale(d_model: usize, max_len: usize) -> Result<Self> {
879        let mut layer = Self::new(d_model, max_len)?;
880        layer.learned_scale = true;
881
882        // Initialize scaling parameter to 1.0
883        let scale = Tensor::from_vec(vec![1.0], &[1])?;
884        layer
885            .base
886            .register_parameter("scale".to_string(), Parameter::new(scale));
887
888        Ok(layer)
889    }
890
891    /// Create sinusoidal positional embeddings
892    ///
893    /// Implements the formula:
894    /// PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
895    /// PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
896    fn create_embeddings(max_len: usize, d_model: usize) -> Result<Tensor> {
897        let mut embeddings = vec![0.0f32; max_len * d_model];
898
899        // Precompute division terms: 1 / 10000^(2i/d_model)
900        let mut div_term = Vec::with_capacity(d_model / 2);
901        for i in (0..d_model).step_by(2) {
902            let exponent = i as f32 / d_model as f32;
903            div_term.push(1.0 / 10000.0_f32.powf(exponent));
904        }
905
906        // Fill embeddings for each position
907        for pos in 0..max_len {
908            let pos_f = pos as f32;
909
910            for (i, &div) in div_term.iter().enumerate() {
911                let angle = pos_f * div;
912
913                // Even indices: sin
914                embeddings[pos * d_model + (i * 2)] = angle.sin();
915
916                // Odd indices: cos
917                if (i * 2 + 1) < d_model {
918                    embeddings[pos * d_model + (i * 2 + 1)] = angle.cos();
919                }
920            }
921        }
922
923        Tensor::from_vec(embeddings, &[max_len, d_model])
924    }
925
926    /// Get embeddings for specific positions
927    ///
928    /// # Arguments
929    /// - `positions`: Tensor of position indices `[seq_len]` or `[batch, seq_len]`
930    ///
931    /// # Returns
932    /// Position embeddings with shape `[seq_len, d_model]` or `[batch, seq_len, d_model]`
933    pub fn get_embeddings(&self, positions: &Tensor) -> Result<Tensor> {
934        let embeddings = self.base.parameters["embeddings"].tensor().read().clone();
935        let positions_data = positions.to_vec()?;
936        let binding = positions.shape();
937        let positions_shape = binding.dims();
938
939        // Validate positions are within bounds
940        for &pos in positions_data.iter() {
941            let pos_usize = pos as usize;
942            if pos_usize >= self.max_len {
943                return Err(TorshError::InvalidArgument(format!(
944                    "Position {} exceeds max_len {}",
945                    pos_usize, self.max_len
946                )));
947            }
948        }
949
950        // Handle different input shapes
951        match positions_shape.len() {
952            1 => {
953                // Input: [seq_len]
954                // Output: [seq_len, d_model]
955                let seq_len = positions_shape[0];
956                let mut output = Vec::with_capacity(seq_len * self.d_model);
957                let embeddings_data = embeddings.to_vec()?;
958
959                for &pos in positions_data.iter() {
960                    let pos_idx = pos as usize;
961                    let start = pos_idx * self.d_model;
962                    let end = start + self.d_model;
963                    output.extend_from_slice(&embeddings_data[start..end]);
964                }
965
966                let mut result = Tensor::from_vec(output, &[seq_len, self.d_model])?;
967
968                // Apply learned scale if enabled
969                if self.learned_scale {
970                    let scale = self.base.parameters["scale"].tensor().read().clone();
971                    result = result.mul_op(&scale)?;
972                }
973
974                Ok(result)
975            }
976            2 => {
977                // Input: [batch, seq_len]
978                // Output: [batch, seq_len, d_model]
979                let batch_size = positions_shape[0];
980                let seq_len = positions_shape[1];
981                let mut output = Vec::with_capacity(batch_size * seq_len * self.d_model);
982                let embeddings_data = embeddings.to_vec()?;
983
984                for &pos in positions_data.iter() {
985                    let pos_idx = pos as usize;
986                    let start = pos_idx * self.d_model;
987                    let end = start + self.d_model;
988                    output.extend_from_slice(&embeddings_data[start..end]);
989                }
990
991                let mut result = Tensor::from_vec(output, &[batch_size, seq_len, self.d_model])?;
992
993                // Apply learned scale if enabled
994                if self.learned_scale {
995                    let scale = self.base.parameters["scale"].tensor().read().clone();
996                    result = result.mul_op(&scale)?;
997                }
998
999                Ok(result)
1000            }
1001            _ => Err(TorshError::InvalidArgument(format!(
1002                "Expected 1D or 2D positions tensor, got {}D",
1003                positions_shape.len()
1004            ))),
1005        }
1006    }
1007
1008    /// Get embeddings for a sequence length (positions 0 to seq_len-1)
1009    ///
1010    /// # Arguments
1011    /// - `seq_len`: Sequence length
1012    ///
1013    /// # Returns
1014    /// Position embeddings with shape `[seq_len, d_model]`
1015    pub fn get_embeddings_for_length(&self, seq_len: usize) -> Result<Tensor> {
1016        if seq_len > self.max_len {
1017            return Err(TorshError::InvalidArgument(format!(
1018                "Sequence length {} exceeds max_len {}",
1019                seq_len, self.max_len
1020            )));
1021        }
1022
1023        let embeddings = self.base.parameters["embeddings"].tensor().read().clone();
1024        let mut result = embeddings.narrow(0, 0, seq_len)?;
1025
1026        // Apply learned scale if enabled
1027        if self.learned_scale {
1028            let scale = self.base.parameters["scale"].tensor().read().clone();
1029            result = result.mul_op(&scale)?;
1030        }
1031
1032        Ok(result)
1033    }
1034
1035    /// Get the maximum supported sequence length
1036    pub fn max_len(&self) -> usize {
1037        self.max_len
1038    }
1039
1040    /// Get the embedding dimension
1041    pub fn d_model(&self) -> usize {
1042        self.d_model
1043    }
1044}
1045
1046impl Module for SinusoidalPositionEmbedding {
1047    fn forward(&self, input: &Tensor) -> Result<Tensor> {
1048        // Input can be:
1049        // 1. Position indices [seq_len] or [batch, seq_len]
1050        // 2. Token embeddings [batch, seq_len, d_model] - add positional embeddings
1051        let binding = input.shape();
1052        let input_shape = binding.dims();
1053
1054        match input_shape.len() {
1055            1 | 2 => {
1056                // Treat as position indices
1057                self.get_embeddings(input)
1058            }
1059            3 => {
1060                // Treat as token embeddings [batch, seq_len, d_model]
1061                let seq_len = input_shape[1];
1062                let pos_emb = self.get_embeddings_for_length(seq_len)?;
1063
1064                // Broadcast and add
1065                // pos_emb: [seq_len, d_model] -> unsqueeze to [1, seq_len, d_model]
1066                let pos_emb_broadcasted = pos_emb.unsqueeze(0)?;
1067                input.add_op(&pos_emb_broadcasted)
1068            }
1069            _ => Err(TorshError::InvalidArgument(format!(
1070                "Unexpected input shape: {:?}",
1071                input_shape
1072            ))),
1073        }
1074    }
1075
1076    fn parameters(&self) -> HashMap<String, Parameter> {
1077        if self.learned_scale {
1078            // Only return the scale parameter, embeddings are fixed
1079            let mut params = HashMap::new();
1080            if let Some(scale) = self.base.parameters.get("scale") {
1081                params.insert("scale".to_string(), scale.clone());
1082            }
1083            params
1084        } else {
1085            // Embeddings are fixed, not trainable
1086            HashMap::new()
1087        }
1088    }
1089
1090    fn training(&self) -> bool {
1091        self.base.training()
1092    }
1093
1094    fn train(&mut self) {
1095        self.base.set_training(true);
1096    }
1097
1098    fn eval(&mut self) {
1099        self.base.set_training(false);
1100    }
1101
1102    fn set_training(&mut self, training: bool) {
1103        self.base.set_training(training);
1104    }
1105
1106    fn to_device(&mut self, device: DeviceType) -> Result<()> {
1107        self.base.to_device(device)
1108    }
1109
1110    fn named_parameters(&self) -> HashMap<String, Parameter> {
1111        if self.learned_scale {
1112            // Only return the scale parameter, embeddings are fixed
1113            let mut params = HashMap::new();
1114            if let Some(scale) = self.base.parameters.get("scale") {
1115                params.insert("scale".to_string(), scale.clone());
1116            }
1117            params
1118        } else {
1119            // Embeddings are fixed, not trainable
1120            HashMap::new()
1121        }
1122    }
1123}
1124
1125impl std::fmt::Debug for SinusoidalPositionEmbedding {
1126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1127        f.debug_struct("SinusoidalPositionEmbedding")
1128            .field("d_model", &self.d_model)
1129            .field("max_len", &self.max_len)
1130            .field("learned_scale", &self.learned_scale)
1131            .finish()
1132    }
1133}
1134
1135// Helper function to create sinusoidal positional encoding
1136fn create_sinusoidal_encoding(max_len: usize, d_model: usize) -> Tensor {
1137    let mut pos_encoding = vec![0.0f32; max_len * d_model];
1138
1139    for pos in 0..max_len {
1140        for i in (0..d_model).step_by(2) {
1141            let angle = pos as f32 / 10000.0_f32.powf(i as f32 / d_model as f32);
1142
1143            pos_encoding[pos * d_model + i] = angle.sin();
1144            if i + 1 < d_model {
1145                pos_encoding[pos * d_model + i + 1] = angle.cos();
1146            }
1147        }
1148    }
1149
1150    Tensor::from_vec(pos_encoding, &[max_len, d_model]).expect("tensor creation should succeed")
1151}
1152
1153impl std::fmt::Debug for SinusoidalPositionalEncoding {
1154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1155        f.debug_struct("SinusoidalPositionalEncoding")
1156            .field("d_model", &self.d_model)
1157            .field("max_len", &self.max_len)
1158            .field("dropout", &self.dropout)
1159            .finish()
1160    }
1161}
1162
1163impl std::fmt::Debug for LearnablePositionalEncoding {
1164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1165        f.debug_struct("LearnablePositionalEncoding")
1166            .field("d_model", &self.d_model)
1167            .field("max_len", &self.max_len)
1168            .field("dropout", &self.dropout)
1169            .finish()
1170    }
1171}
1172
1173impl std::fmt::Debug for RotaryPositionalEmbedding {
1174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1175        f.debug_struct("RotaryPositionalEmbedding")
1176            .field("d_model", &self.d_model)
1177            .field("base", &self.base)
1178            .field("max_seq_len", &self.max_seq_len)
1179            .finish()
1180    }
1181}
1182
1183impl std::fmt::Debug for AlibiPositionalBias {
1184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1185        f.debug_struct("AlibiPositionalBias")
1186            .field("num_heads", &self.num_heads)
1187            .field("max_seq_len", &self.max_seq_len)
1188            .finish()
1189    }
1190}
1191
1192impl std::fmt::Debug for RelativePositionalEncoding {
1193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1194        f.debug_struct("RelativePositionalEncoding")
1195            .field("d_model", &self.d_model)
1196            .field("max_relative_distance", &self.max_relative_distance)
1197            .finish()
1198    }
1199}
1200
1201// =============================================================================
1202// TESTS
1203// =============================================================================
1204
1205#[cfg(test)]
1206mod tests {
1207    use super::*;
1208    use approx::assert_relative_eq;
1209
1210    #[test]
1211    fn test_embedding_basic_lookup() -> Result<()> {
1212        // Create a small embedding: 5 tokens, 3-dimensional embeddings
1213        let mut embedding = Embedding::new(5, 3);
1214
1215        // Set deterministic weights for testing
1216        let weight_data = vec![
1217            1.0, 2.0, 3.0, // Token 0
1218            4.0, 5.0, 6.0, // Token 1
1219            7.0, 8.0, 9.0, // Token 2
1220            10.0, 11.0, 12.0, // Token 3
1221            13.0, 14.0, 15.0, // Token 4
1222        ];
1223        let weight = Tensor::from_vec(weight_data, &[5, 3])?;
1224        *embedding
1225            .base
1226            .parameters
1227            .get_mut("weight")
1228            .expect("operation should succeed")
1229            .tensor()
1230            .write() = weight;
1231
1232        // Test single index lookup
1233        let input = Tensor::from_vec(vec![2.0], &[1])?;
1234        let output = embedding.forward(&input)?;
1235
1236        let output_data = output.to_vec()?;
1237        assert_eq!(output.shape().dims(), &[1, 3]);
1238        assert_relative_eq!(output_data[0], 7.0, epsilon = 1e-6);
1239        assert_relative_eq!(output_data[1], 8.0, epsilon = 1e-6);
1240        assert_relative_eq!(output_data[2], 9.0, epsilon = 1e-6);
1241
1242        Ok(())
1243    }
1244
1245    #[test]
1246    fn test_embedding_multiple_indices() -> Result<()> {
1247        // Create embedding: 4 tokens, 2-dimensional
1248        let mut embedding = Embedding::new(4, 2);
1249
1250        let weight_data = vec![
1251            1.0, 2.0, // Token 0
1252            3.0, 4.0, // Token 1
1253            5.0, 6.0, // Token 2
1254            7.0, 8.0, // Token 3
1255        ];
1256        let weight = Tensor::from_vec(weight_data, &[4, 2])?;
1257        *embedding
1258            .base
1259            .parameters
1260            .get_mut("weight")
1261            .expect("operation should succeed")
1262            .tensor()
1263            .write() = weight;
1264
1265        // Lookup multiple indices: [0, 2, 1]
1266        let input = Tensor::from_vec(vec![0.0, 2.0, 1.0], &[3])?;
1267        let output = embedding.forward(&input)?;
1268
1269        let output_data = output.to_vec()?;
1270        assert_eq!(output.shape().dims(), &[3, 2]);
1271
1272        // Token 0: [1.0, 2.0]
1273        assert_relative_eq!(output_data[0], 1.0, epsilon = 1e-6);
1274        assert_relative_eq!(output_data[1], 2.0, epsilon = 1e-6);
1275
1276        // Token 2: [5.0, 6.0]
1277        assert_relative_eq!(output_data[2], 5.0, epsilon = 1e-6);
1278        assert_relative_eq!(output_data[3], 6.0, epsilon = 1e-6);
1279
1280        // Token 1: [3.0, 4.0]
1281        assert_relative_eq!(output_data[4], 3.0, epsilon = 1e-6);
1282        assert_relative_eq!(output_data[5], 4.0, epsilon = 1e-6);
1283
1284        Ok(())
1285    }
1286
1287    #[test]
1288    fn test_embedding_2d_indices() -> Result<()> {
1289        // Test with 2D input (batch of sequences)
1290        let mut embedding = Embedding::new(3, 2);
1291
1292        let weight_data = vec![
1293            1.0, 2.0, // Token 0
1294            3.0, 4.0, // Token 1
1295            5.0, 6.0, // Token 2
1296        ];
1297        let weight = Tensor::from_vec(weight_data, &[3, 2])?;
1298        *embedding
1299            .base
1300            .parameters
1301            .get_mut("weight")
1302            .expect("operation should succeed")
1303            .tensor()
1304            .write() = weight;
1305
1306        // Input: 2 sequences of length 2
1307        // [[0, 1],
1308        //  [2, 0]]
1309        let input = Tensor::from_vec(vec![0.0, 1.0, 2.0, 0.0], &[2, 2])?;
1310        let output = embedding.forward(&input)?;
1311
1312        assert_eq!(output.shape().dims(), &[2, 2, 2]); // [batch, seq_len, embedding_dim]
1313
1314        let output_data = output.to_vec()?;
1315
1316        // Sequence 0, Token 0 (index 0): [1.0, 2.0]
1317        assert_relative_eq!(output_data[0], 1.0, epsilon = 1e-6);
1318        assert_relative_eq!(output_data[1], 2.0, epsilon = 1e-6);
1319
1320        // Sequence 0, Token 1 (index 1): [3.0, 4.0]
1321        assert_relative_eq!(output_data[2], 3.0, epsilon = 1e-6);
1322        assert_relative_eq!(output_data[3], 4.0, epsilon = 1e-6);
1323
1324        // Sequence 1, Token 0 (index 2): [5.0, 6.0]
1325        assert_relative_eq!(output_data[4], 5.0, epsilon = 1e-6);
1326        assert_relative_eq!(output_data[5], 6.0, epsilon = 1e-6);
1327
1328        // Sequence 1, Token 1 (index 0): [1.0, 2.0]
1329        assert_relative_eq!(output_data[6], 1.0, epsilon = 1e-6);
1330        assert_relative_eq!(output_data[7], 2.0, epsilon = 1e-6);
1331
1332        Ok(())
1333    }
1334
1335    #[test]
1336    fn test_embedding_with_padding_idx() -> Result<()> {
1337        // Test padding index functionality
1338        let mut embedding = Embedding::with_padding_idx(4, 3, 0);
1339
1340        let weight_data = vec![
1341            1.0, 2.0, 3.0, // Token 0 (padding - should be ignored)
1342            4.0, 5.0, 6.0, // Token 1
1343            7.0, 8.0, 9.0, // Token 2
1344            10.0, 11.0, 12.0, // Token 3
1345        ];
1346        let weight = Tensor::from_vec(weight_data, &[4, 3])?;
1347        *embedding
1348            .base
1349            .parameters
1350            .get_mut("weight")
1351            .expect("operation should succeed")
1352            .tensor()
1353            .write() = weight;
1354
1355        // Lookup including padding index 0
1356        let input = Tensor::from_vec(vec![1.0, 0.0, 2.0], &[3])?;
1357        let output = embedding.forward(&input)?;
1358
1359        let output_data = output.to_vec()?;
1360
1361        // Token 1: [4.0, 5.0, 6.0]
1362        assert_relative_eq!(output_data[0], 4.0, epsilon = 1e-6);
1363        assert_relative_eq!(output_data[1], 5.0, epsilon = 1e-6);
1364        assert_relative_eq!(output_data[2], 6.0, epsilon = 1e-6);
1365
1366        // Token 0 (padding): [0.0, 0.0, 0.0]
1367        assert_relative_eq!(output_data[3], 0.0, epsilon = 1e-6);
1368        assert_relative_eq!(output_data[4], 0.0, epsilon = 1e-6);
1369        assert_relative_eq!(output_data[5], 0.0, epsilon = 1e-6);
1370
1371        // Token 2: [7.0, 8.0, 9.0]
1372        assert_relative_eq!(output_data[6], 7.0, epsilon = 1e-6);
1373        assert_relative_eq!(output_data[7], 8.0, epsilon = 1e-6);
1374        assert_relative_eq!(output_data[8], 9.0, epsilon = 1e-6);
1375
1376        Ok(())
1377    }
1378
1379    #[test]
1380    fn test_embedding_out_of_bounds() {
1381        // Test that out-of-bounds index is rejected
1382        let mut embedding = Embedding::new(3, 2);
1383
1384        let weight_data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1385        let weight = Tensor::from_vec(weight_data, &[3, 2]).expect("Tensor should succeed");
1386        *embedding
1387            .base
1388            .parameters
1389            .get_mut("weight")
1390            .expect("operation should succeed")
1391            .tensor()
1392            .write() = weight;
1393
1394        // Try to lookup index 5 (out of bounds for num_embeddings=3)
1395        let input = Tensor::from_vec(vec![5.0], &[1]).expect("Tensor should succeed");
1396        let result = embedding.forward(&input);
1397
1398        assert!(result.is_err());
1399        if let Err(torsh_core::error::TorshError::InvalidArgument(msg)) = result {
1400            assert!(msg.contains("out of bounds"));
1401        } else {
1402            panic!("Expected InvalidArgument error for out-of-bounds index");
1403        }
1404    }
1405
1406    #[test]
1407    fn test_embedding_with_max_norm() -> Result<()> {
1408        // Test max_norm renormalization
1409        let mut embedding = Embedding::with_config(
1410            3,
1411            2,
1412            None,      // padding_idx
1413            Some(1.0), // max_norm
1414            2.0,       // norm_type (L2)
1415            false,     // scale_grad_by_freq
1416            false,     // sparse
1417        );
1418
1419        // Create embeddings with large norms
1420        let weight_data = vec![
1421            3.0, 4.0, // Token 0: L2 norm = 5.0 (will be scaled down)
1422            1.0, 0.0, // Token 1: L2 norm = 1.0 (already within max_norm)
1423            0.6, 0.8, // Token 2: L2 norm = 1.0 (already within max_norm)
1424        ];
1425        let weight = Tensor::from_vec(weight_data, &[3, 2])?;
1426        *embedding
1427            .base
1428            .parameters
1429            .get_mut("weight")
1430            .expect("operation should succeed")
1431            .tensor()
1432            .write() = weight;
1433
1434        // Lookup token 0 (should be renormalized)
1435        let input = Tensor::from_vec(vec![0.0], &[1])?;
1436        let output = embedding.forward(&input)?;
1437
1438        let output_data = output.to_vec()?;
1439
1440        // Original: [3.0, 4.0], norm = 5.0
1441        // After renormalization to max_norm=1.0: [0.6, 0.8]
1442        assert_relative_eq!(output_data[0], 0.6, epsilon = 1e-6);
1443        assert_relative_eq!(output_data[1], 0.8, epsilon = 1e-6);
1444
1445        // Verify the renormalized vector has norm <= max_norm
1446        let norm = (output_data[0] * output_data[0] + output_data[1] * output_data[1]).sqrt();
1447        assert_relative_eq!(norm, 1.0, epsilon = 1e-6);
1448
1449        Ok(())
1450    }
1451
1452    #[test]
1453    fn test_embedding_shape_preservation() -> Result<()> {
1454        // Test that output shape is correct for various input shapes
1455        let embedding = Embedding::new(10, 5);
1456
1457        // 1D input: [3] -> [3, 5]
1458        let input1d = Tensor::from_vec(vec![0.0, 1.0, 2.0], &[3])?;
1459        let output1d = embedding.forward(&input1d)?;
1460        assert_eq!(output1d.shape().dims(), &[3, 5]);
1461
1462        // 2D input: [2, 4] -> [2, 4, 5]
1463        let input2d = Tensor::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], &[2, 4])?;
1464        let output2d = embedding.forward(&input2d)?;
1465        assert_eq!(output2d.shape().dims(), &[2, 4, 5]);
1466
1467        // 3D input: [2, 3, 2] -> [2, 3, 2, 5]
1468        let input3d = Tensor::from_vec(
1469            vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0, 1.0],
1470            &[2, 3, 2],
1471        )?;
1472        let output3d = embedding.forward(&input3d)?;
1473        assert_eq!(output3d.shape().dims(), &[2, 3, 2, 5]);
1474
1475        Ok(())
1476    }
1477
1478    // =============================================================================
1479    // SinusoidalPositionEmbedding Tests
1480    // =============================================================================
1481
1482    #[test]
1483    fn test_sinusoidal_position_embedding_creation() -> Result<()> {
1484        // Test basic creation
1485        let pos_emb = SinusoidalPositionEmbedding::new(64, 100)?;
1486        assert_eq!(pos_emb.d_model(), 64);
1487        assert_eq!(pos_emb.max_len(), 100);
1488
1489        // Test that d_model must be even
1490        let result = SinusoidalPositionEmbedding::new(63, 100);
1491        assert!(result.is_err());
1492
1493        Ok(())
1494    }
1495
1496    #[test]
1497    fn test_sinusoidal_position_embedding_forward() -> Result<()> {
1498        // Create position embedding layer
1499        let pos_emb = SinusoidalPositionEmbedding::new(128, 1000)?;
1500
1501        // Test with position indices [0, 1, 2, 3, 4]
1502        let positions = Tensor::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0], &[5])?;
1503        let embeddings = pos_emb.forward(&positions)?;
1504
1505        // Check shape
1506        assert_eq!(embeddings.shape().dims(), &[5, 128]);
1507
1508        // Check that embeddings are not all zeros
1509        let data = embeddings.to_vec()?;
1510        let sum: f32 = data.iter().sum();
1511        assert!(sum.abs() > 0.1);
1512
1513        Ok(())
1514    }
1515
1516    #[test]
1517    fn test_sinusoidal_position_embedding_sequence_length() -> Result<()> {
1518        let pos_emb = SinusoidalPositionEmbedding::new(64, 200)?;
1519
1520        // Get embeddings for a sequence of length 50
1521        let embeddings = pos_emb.get_embeddings_for_length(50)?;
1522
1523        // Check shape
1524        assert_eq!(embeddings.shape().dims(), &[50, 64]);
1525
1526        // Verify that position 0 has expected pattern
1527        let emb0 = embeddings.narrow(0, 0, 1)?.squeeze(0)?;
1528        let data0 = emb0.to_vec()?;
1529
1530        // At position 0, all angles are 0
1531        // sin(0) = 0, cos(0) = 1
1532        assert_relative_eq!(data0[0], 0.0, epsilon = 1e-6); // sin(0)
1533        assert_relative_eq!(data0[1], 1.0, epsilon = 1e-6); // cos(0)
1534
1535        Ok(())
1536    }
1537
1538    #[test]
1539    fn test_sinusoidal_position_embedding_mathematical_properties() -> Result<()> {
1540        let d_model = 64;
1541        let pos_emb = SinusoidalPositionEmbedding::new(d_model, 100)?;
1542
1543        // Get embeddings for positions 0, 1, 2
1544        let positions = Tensor::from_vec(vec![0.0, 1.0, 2.0], &[3])?;
1545        let embeddings = pos_emb.forward(&positions)?;
1546        let data = embeddings.to_vec()?;
1547
1548        // Check position 0: all even indices should be sin(0) = 0, odd should be cos(0) = 1
1549        assert_relative_eq!(data[0], 0.0, epsilon = 1e-6); // sin(0)
1550        assert_relative_eq!(data[1], 1.0, epsilon = 1e-6); // cos(0)
1551
1552        // Check that embeddings follow the sinusoidal pattern
1553        // For position 1, first dimension should be sin(1 / 10000^0) ≈ sin(1)
1554        let pos1_start = d_model;
1555        assert_relative_eq!(data[pos1_start], (1.0_f32).sin(), epsilon = 1e-5);
1556        assert_relative_eq!(data[pos1_start + 1], (1.0_f32).cos(), epsilon = 1e-5);
1557
1558        Ok(())
1559    }
1560
1561    #[test]
1562    fn test_sinusoidal_position_embedding_periodicity() -> Result<()> {
1563        let pos_emb = SinusoidalPositionEmbedding::new(128, 10000)?;
1564
1565        // Get embeddings at different positions
1566        let pos1 = Tensor::from_vec(vec![0.0], &[1])?;
1567        let emb1 = pos_emb.forward(&pos1)?;
1568
1569        let pos2 = Tensor::from_vec(vec![100.0], &[1])?;
1570        let emb2 = pos_emb.forward(&pos2)?;
1571
1572        // Embeddings should be different
1573        let data1 = emb1.to_vec()?;
1574        let data2 = emb2.to_vec()?;
1575
1576        let mut different_count = 0;
1577        for (v1, v2) in data1.iter().zip(data2.iter()) {
1578            if (v1 - v2).abs() > 1e-6 {
1579                different_count += 1;
1580            }
1581        }
1582
1583        // Most values should be different
1584        assert!(different_count > 100);
1585
1586        Ok(())
1587    }
1588
1589    #[test]
1590    fn test_sinusoidal_position_embedding_batch_support() -> Result<()> {
1591        let pos_emb = SinusoidalPositionEmbedding::new(64, 100)?;
1592
1593        // Test with batched positions [batch=2, seq_len=3]
1594        let positions = Tensor::from_vec(
1595            vec![
1596                0.0, 1.0, 2.0, // Batch 0
1597                3.0, 4.0, 5.0, // Batch 1
1598            ],
1599            &[2, 3],
1600        )?;
1601
1602        let embeddings = pos_emb.forward(&positions)?;
1603
1604        // Check shape: [2, 3, 64]
1605        assert_eq!(embeddings.shape().dims(), &[2, 3, 64]);
1606
1607        Ok(())
1608    }
1609
1610    #[test]
1611    fn test_sinusoidal_position_embedding_bounds_checking() -> Result<()> {
1612        let pos_emb = SinusoidalPositionEmbedding::new(64, 100)?;
1613
1614        // Test position exceeding max_len
1615        let positions = Tensor::from_vec(vec![101.0], &[1])?;
1616        let result = pos_emb.forward(&positions);
1617        assert!(result.is_err());
1618
1619        // Test sequence length exceeding max_len
1620        let result = pos_emb.get_embeddings_for_length(101);
1621        assert!(result.is_err());
1622
1623        Ok(())
1624    }
1625
1626    #[test]
1627    fn test_sinusoidal_position_embedding_with_learned_scale() -> Result<()> {
1628        let pos_emb = SinusoidalPositionEmbedding::with_learned_scale(64, 100)?;
1629
1630        // Should have trainable parameters (the scale)
1631        let params = pos_emb.parameters();
1632        assert_eq!(params.len(), 1);
1633        assert!(params.contains_key("scale"));
1634
1635        // Get embeddings
1636        let positions = Tensor::from_vec(vec![0.0, 1.0], &[2])?;
1637        let embeddings = pos_emb.forward(&positions)?;
1638
1639        assert_eq!(embeddings.shape().dims(), &[2, 64]);
1640
1641        Ok(())
1642    }
1643
1644    #[test]
1645    fn test_sinusoidal_position_embedding_add_to_tokens() -> Result<()> {
1646        let d_model = 64;
1647        let seq_len = 10;
1648        let batch_size = 2;
1649
1650        let pos_emb = SinusoidalPositionEmbedding::new(d_model, 100)?;
1651
1652        // Create token embeddings [batch, seq_len, d_model]
1653        let token_data = vec![0.5_f32; batch_size * seq_len * d_model];
1654        let tokens = Tensor::from_vec(token_data, &[batch_size, seq_len, d_model])?;
1655
1656        // Add positional embeddings
1657        let output = pos_emb.forward(&tokens)?;
1658
1659        // Shape should be preserved
1660        assert_eq!(output.shape().dims(), &[batch_size, seq_len, d_model]);
1661
1662        // Values should be different from input (positional info added)
1663        let output_data = output.to_vec()?;
1664        let tokens_data = tokens.to_vec()?;
1665
1666        let mut different_count = 0;
1667        for (out, tok) in output_data.iter().zip(tokens_data.iter()) {
1668            if (out - tok).abs() > 1e-6 {
1669                different_count += 1;
1670            }
1671        }
1672
1673        // Most values should be different (positional embeddings added)
1674        assert!(different_count > d_model * seq_len);
1675
1676        Ok(())
1677    }
1678}