//! Attention mechanisms for translation models use crate::Result; use rtx_tensor::Tensor; use serde::{Deserialize, Serialize}; /// Attention mechanism types #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AttentionType { /// Additive (Bahdanau) attention Additive, /// Multiplicative (Luong) attention Multiplicative, /// Scaled dot-product attention ScaledDotProduct, /// Multi-head attention MultiHead { num_heads: usize }, } /// Attention configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AttentionConfig { pub attention_type: AttentionType, pub hidden_size: usize, pub dropout_rate: f32, pub temperature: f32, } impl Default for AttentionConfig { fn default() -> Self { Self { attention_type: AttentionType::ScaledDotProduct, hidden_size: 512, dropout_rate: 0.1, temperature: 1.0, } } } /// Attention layer for sequence-to-sequence models pub struct AttentionLayer { config: AttentionConfig, } impl AttentionLayer { pub fn new(config: AttentionConfig) -> Self { Self { config } } /// Apply attention mechanism pub fn forward( &self, query: &Tensor, key: &Tensor, value: &Tensor, mask: Option<&Tensor>, ) -> Result<(Tensor, Tensor)> { match &self.config.attention_type { AttentionType::Additive => self.additive_attention(query, key, value, mask), AttentionType::Multiplicative => self.multiplicative_attention(query, key, value, mask), AttentionType::ScaledDotProduct => { self.scaled_dot_product_attention(query, key, value, mask) } AttentionType::MultiHead { num_heads } => { self.multi_head_attention(query, key, value, mask, *num_heads) } } } fn scaled_dot_product_attention( &self, query: &Tensor, key: &Tensor, value: &Tensor, _mask: Option<&Tensor>, ) -> Result<(Tensor, Tensor)> { // Compute attention scores: Q * K^T / sqrt(d_k) let key_shape = key.shape().dims(); let d_k = key_shape[key_shape.len() - 1] as f32; let scores = (query.matmul(&key.transpose(-2, -1)?)? / d_k.sqrt())?; // Apply mask if provided // Note: Stubbed - where_cond not available in rtx-tensor let masked_scores = scores; // Apply softmax to get attention weights let attention_weights = masked_scores.softmax(-1)?; // Apply attention to values let context = attention_weights.matmul(value)?; Ok((context, attention_weights)) } fn additive_attention( &self, query: &Tensor, key: &Tensor, value: &Tensor, _mask: Option<&Tensor>, ) -> Result<(Tensor, Tensor)> { // Mock implementation - in practice would use learned parameters let scores = query.matmul(&key.transpose(-2, -1)?)?; let attention_weights = scores.softmax(-1)?; let context = attention_weights.matmul(value)?; Ok((context, attention_weights)) } fn multiplicative_attention( &self, query: &Tensor, key: &Tensor, value: &Tensor, _mask: Option<&Tensor>, ) -> Result<(Tensor, Tensor)> { // Mock implementation let scores = query.matmul(&key.transpose(-2, -1)?)?; let attention_weights = scores.softmax(-1)?; let context = attention_weights.matmul(value)?; Ok((context, attention_weights)) } fn multi_head_attention( &self, query: &Tensor, key: &Tensor, value: &Tensor, mask: Option<&Tensor>, _num_heads: usize, ) -> Result<(Tensor, Tensor)> { // For simplicity, delegate to scaled dot-product self.scaled_dot_product_attention(query, key, value, mask) } } #[cfg(test)] mod tests { use super::*; use rtx_tensor::Device; #[test] fn test_attention_layer() -> Result<()> { let config = AttentionConfig::default(); let layer = AttentionLayer::new(config); let seq_len = 10; let hidden_size = 64; let device = Device::cuda(0).unwrap_or(Device::default()); let query = Tensor::randn(&[1, seq_len, hidden_size], &device)?; let key = Tensor::randn(&[1, seq_len, hidden_size], &device)?; let value = Tensor::randn(&[1, seq_len, hidden_size], &device)?; let (context, weights) = layer.forward(&query, &key, &value, None)?; assert_eq!(context.shape(), &[1, seq_len, hidden_size]); assert_eq!(weights.shape(), &[1, seq_len, seq_len]); Ok(()) } }