use std::fmt::Debug;
use crate::common::cast_scalar;
use crate::error::Result;
use scirs2_core::ndarray::{Array2, Array3};
use scirs2_core::numeric::Float;
pub struct MultiHeadAttention<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
> {
num_heads: usize,
head_dimension: usize,
model_dimension: usize,
query_weights: Array2<T>,
key_weights: Array2<T>,
value_weights: Array2<T>,
output_weights: Array2<T>,
dropout_rate: f64,
attention_weights: Option<Array3<T>>,
scale_factor: T,
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync>
MultiHeadAttention<T>
{
pub fn new(num_heads: usize, model_dimension: usize, head_dimension: usize) -> Result<Self> {
if !model_dimension.is_multiple_of(num_heads) {
return Err(crate::error::OptimError::Other(
"Model dimension must be divisible by number of heads".to_string(),
));
}
if head_dimension == 0 {
return Err(crate::error::OptimError::InvalidConfig(
"head_dimension must be greater than 0".to_string(),
));
}
let scale_factor: T = cast_scalar(1.0 / (head_dimension as f64).sqrt())?;
let xavier_limit = Self::xavier_limit(model_dimension, model_dimension);
let query_weights =
Self::initialize_weights(model_dimension, model_dimension, xavier_limit);
let key_weights = Self::initialize_weights(model_dimension, model_dimension, xavier_limit);
let value_weights =
Self::initialize_weights(model_dimension, model_dimension, xavier_limit);
let output_weights =
Self::initialize_weights(model_dimension, model_dimension, xavier_limit);
Ok(Self {
num_heads,
head_dimension,
model_dimension,
query_weights,
key_weights,
value_weights,
output_weights,
dropout_rate: 0.1,
attention_weights: None,
scale_factor,
})
}
pub fn xavier_limit(fan_in: usize, fan_out: usize) -> f64 {
(6.0 / (fan_in + fan_out).max(1) as f64).sqrt()
}
fn initialize_weights(rows: usize, cols: usize, limit: f64) -> Array2<T> {
let mut weights = Array2::<T>::zeros((rows, cols));
let mut rng = scirs2_core::random::thread_rng();
for elem in weights.iter_mut() {
let random_val = rng.random::<f64>();
let scaled_val = (random_val - 0.5) * 2.0 * limit;
*elem = scirs2_core::numeric::NumCast::from(scaled_val).unwrap_or_else(|| T::zero());
}
weights
}
pub fn forward(
&mut self,
query: &Array2<T>,
key: &Array2<T>,
value: &Array2<T>,
) -> Result<Array2<T>> {
let total_seq_len = query.shape()[0];
let model_dim = query.shape()[1];
if model_dim != self.model_dimension {
return Err(crate::error::OptimError::InvalidConfig(format!(
"MultiHeadAttention was built for model dimension {} but the query \
has {model_dim} features",
self.model_dimension
)));
}
if key.shape() != query.shape() || value.shape() != query.shape() {
return Err(crate::error::OptimError::InvalidConfig(format!(
"MultiHeadAttention needs query, key and value of equal shape: \
query {:?}, key {:?}, value {:?}",
query.shape(),
key.shape(),
value.shape()
)));
}
if total_seq_len == 0 {
return Err(crate::error::OptimError::InvalidConfig(
"MultiHeadAttention cannot attend over an empty sequence".to_string(),
));
}
let batch_size = 1;
let seq_length = total_seq_len;
let q = query.dot(&self.query_weights);
let k = key.dot(&self.key_weights);
let v = value.dot(&self.value_weights);
let q_heads = self.reshape_for_heads(&q, batch_size, seq_length)?;
let k_heads = self.reshape_for_heads(&k, batch_size, seq_length)?;
let v_heads = self.reshape_for_heads(&v, batch_size, seq_length)?;
let attention_output = self.compute_attention(&q_heads, &k_heads, &v_heads)?;
let reshaped_output = self.reshape_from_heads(&attention_output, batch_size, seq_length)?;
let output = reshaped_output.dot(&self.output_weights);
Ok(output)
}
fn reshape_for_heads(
&self,
tensor: &Array2<T>,
batch_size: usize,
seq_length: usize,
) -> Result<Array3<T>> {
let mut reshaped =
Array3::<T>::zeros((batch_size, self.num_heads, seq_length * self.head_dimension));
for batch in 0..batch_size {
for head in 0..self.num_heads {
for seq in 0..seq_length {
for dim in 0..self.head_dimension {
let input_idx = batch * seq_length + seq;
let input_dim = head * self.head_dimension + dim;
let output_idx = seq * self.head_dimension + dim;
if input_idx < tensor.shape()[0] && input_dim < tensor.shape()[1] {
reshaped[[batch, head, output_idx]] = tensor[[input_idx, input_dim]];
}
}
}
}
}
Ok(reshaped)
}
fn reshape_from_heads(
&self,
tensor: &Array3<T>,
batch_size: usize,
seq_length: usize,
) -> Result<Array2<T>> {
let mut output = Array2::<T>::zeros((batch_size * seq_length, self.model_dimension));
for batch in 0..batch_size {
for head in 0..self.num_heads {
for seq in 0..seq_length {
for dim in 0..self.head_dimension {
let input_idx = seq * self.head_dimension + dim;
let output_row = batch * seq_length + seq;
let output_col = head * self.head_dimension + dim;
if input_idx < tensor.shape()[2]
&& output_row < output.shape()[0]
&& output_col < output.shape()[1]
{
output[[output_row, output_col]] = tensor[[batch, head, input_idx]];
}
}
}
}
}
Ok(output)
}
fn compute_attention(
&mut self,
query: &Array3<T>,
key: &Array3<T>,
value: &Array3<T>,
) -> Result<Array3<T>> {
let batch_size = query.shape()[0];
let num_heads = query.shape()[1];
let seq_length = query.shape()[2] / self.head_dimension;
let mut attention_output = Array3::<T>::zeros(query.raw_dim());
let mut attention_weights =
Array3::<T>::zeros((batch_size, num_heads, seq_length * seq_length));
for batch in 0..batch_size {
for head in 0..num_heads {
let q_slice = self.extract_head_slice(query, batch, head, seq_length)?;
let k_slice = self.extract_head_slice(key, batch, head, seq_length)?;
let v_slice = self.extract_head_slice(value, batch, head, seq_length)?;
let scores = self.compute_attention_scores(&q_slice, &k_slice)?;
let attention_probs = self.softmax(&scores)?;
for i in 0..seq_length {
for j in 0..seq_length {
if i * seq_length + j < attention_weights.shape()[2] {
attention_weights[[batch, head, i * seq_length + j]] =
attention_probs[[i, j]];
}
}
}
let attended_values = attention_probs.dot(&v_slice);
for i in 0..seq_length {
for j in 0..self.head_dimension {
let output_idx = i * self.head_dimension + j;
if output_idx < attention_output.shape()[2] {
attention_output[[batch, head, output_idx]] = attended_values[[i, j]];
}
}
}
}
}
self.attention_weights = Some(attention_weights);
Ok(attention_output)
}
fn extract_head_slice(
&self,
tensor: &Array3<T>,
batch: usize,
head: usize,
seq_length: usize,
) -> Result<Array2<T>> {
let mut slice = Array2::<T>::zeros((seq_length, self.head_dimension));
for seq in 0..seq_length {
for dim in 0..self.head_dimension {
let tensor_idx = seq * self.head_dimension + dim;
if tensor_idx < tensor.shape()[2] {
slice[[seq, dim]] = tensor[[batch, head, tensor_idx]];
}
}
}
Ok(slice)
}
fn compute_attention_scores(&self, query: &Array2<T>, key: &Array2<T>) -> Result<Array2<T>> {
let seq_length = query.shape()[0];
let mut scores = Array2::<T>::zeros((seq_length, seq_length));
for i in 0..seq_length {
for j in 0..seq_length {
let mut score = T::zero();
for k in 0..self.head_dimension {
score = score + query[[i, k]] * key[[j, k]];
}
scores[[i, j]] = score * self.scale_factor;
}
}
Ok(scores)
}
fn softmax(&self, scores: &Array2<T>) -> Result<Array2<T>> {
let seq_length = scores.shape()[0];
let mut probs = Array2::<T>::zeros((seq_length, seq_length));
for i in 0..seq_length {
let mut max_score = T::neg_infinity();
for j in 0..seq_length {
if scores[[i, j]] > max_score {
max_score = scores[[i, j]];
}
}
let mut exp_sum = T::zero();
let mut exp_scores = vec![T::zero(); seq_length];
for j in 0..seq_length {
exp_scores[j] = (scores[[i, j]] - max_score).exp();
exp_sum = exp_sum + exp_scores[j];
}
if exp_sum <= T::zero() || !exp_sum.is_finite() {
return Err(crate::error::OptimError::ComputationError(format!(
"attention row {i} has no finite scores to normalize"
)));
}
for j in 0..seq_length {
probs[[i, j]] = exp_scores[j] / exp_sum;
}
}
Ok(probs)
}
pub fn get_attention_weights(&self) -> Option<Array3<T>> {
self.attention_weights.clone()
}
pub fn reset(&mut self) -> Result<()> {
let xavier_limit = Self::xavier_limit(self.model_dimension, self.model_dimension);
self.query_weights =
Self::initialize_weights(self.model_dimension, self.model_dimension, xavier_limit);
self.key_weights =
Self::initialize_weights(self.model_dimension, self.model_dimension, xavier_limit);
self.value_weights =
Self::initialize_weights(self.model_dimension, self.model_dimension, xavier_limit);
self.output_weights =
Self::initialize_weights(self.model_dimension, self.model_dimension, xavier_limit);
self.attention_weights = None;
Ok(())
}
pub fn parameter_count(&self) -> usize {
self.query_weights.len()
+ self.key_weights.len()
+ self.value_weights.len()
+ self.output_weights.len()
}
pub fn projection_snapshots(&self) -> (&Array2<T>, &Array2<T>, &Array2<T>, &Array2<T>) {
(
&self.query_weights,
&self.key_weights,
&self.value_weights,
&self.output_weights,
)
}
pub fn set_dropout_rate(&mut self, rate: f64) {
self.dropout_rate = rate.clamp(0.0, 1.0);
}
}
pub trait AttentionMechanism<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
>
{
fn compute_attention(
&mut self,
query: &Array2<T>,
key: &Array2<T>,
value: &Array2<T>,
) -> Result<Array2<T>>;
fn get_attention_weights(&self) -> Option<Array3<T>>;
fn parameter_count(&self) -> usize;
fn reset(&mut self) -> Result<()>;
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync>
AttentionMechanism<T> for MultiHeadAttention<T>
{
fn compute_attention(
&mut self,
query: &Array2<T>,
key: &Array2<T>,
value: &Array2<T>,
) -> Result<Array2<T>> {
self.forward(query, key, value)
}
fn get_attention_weights(&self) -> Option<Array3<T>> {
self.attention_weights.clone()
}
fn parameter_count(&self) -> usize {
self.parameter_count()
}
fn reset(&mut self) -> Result<()> {
self.reset()
}
}
pub struct SelfAttention<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
> {
multi_head_attention: MultiHeadAttention<T>,
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync>
SelfAttention<T>
{
pub fn new(num_heads: usize, model_dimension: usize, head_dimension: usize) -> Result<Self> {
let multi_head_attention =
MultiHeadAttention::new(num_heads, model_dimension, head_dimension)?;
Ok(Self {
multi_head_attention,
})
}
pub fn forward(&mut self, input: &Array2<T>) -> Result<Array2<T>> {
self.multi_head_attention.forward(input, input, input)
}
}
pub struct CrossAttention<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
> {
multi_head_attention: MultiHeadAttention<T>,
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync>
CrossAttention<T>
{
pub fn new(num_heads: usize, model_dimension: usize, head_dimension: usize) -> Result<Self> {
let multi_head_attention =
MultiHeadAttention::new(num_heads, model_dimension, head_dimension)?;
Ok(Self {
multi_head_attention,
})
}
pub fn forward(&mut self, query: &Array2<T>, context: &Array2<T>) -> Result<Array2<T>> {
self.multi_head_attention.forward(query, context, context)
}
}
pub struct AttentionVisualizer<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync> Default
for AttentionVisualizer<T>
{
fn default() -> Self {
Self::new()
}
}
impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + 'static + Send + Sync>
AttentionVisualizer<T>
{
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
pub fn extract_attention_patterns(
&self,
attention_weights: &Array3<T>,
) -> AttentionPatterns<T> {
let batch_size = attention_weights.shape()[0];
let num_heads = attention_weights.shape()[1];
let seq_length_squared = attention_weights.shape()[2];
let seq_length = (seq_length_squared as f64).sqrt() as usize;
let mut head_entropies = vec![T::zero(); num_heads];
if batch_size == 0 || num_heads == 0 {
return AttentionPatterns {
head_entropies,
attention_diversity: T::zero(),
sequence_length: seq_length,
num_heads,
};
}
let batch_count: T =
scirs2_core::numeric::NumCast::from(batch_size).unwrap_or_else(|| T::one());
let head_count: T =
scirs2_core::numeric::NumCast::from(num_heads).unwrap_or_else(|| T::one());
let span = (seq_length * seq_length).min(seq_length_squared);
let mut attention_diversity = T::zero();
for head in 0..num_heads {
let mut entropy = T::zero();
for batch in 0..batch_size {
for idx in 0..span {
let prob = attention_weights[[batch, head, idx]];
if prob > T::zero() {
entropy = entropy - prob * prob.ln();
}
}
}
let mean_entropy = entropy / batch_count;
head_entropies[head] = mean_entropy;
attention_diversity = attention_diversity + mean_entropy;
}
AttentionPatterns {
head_entropies,
attention_diversity: attention_diversity / head_count,
sequence_length: seq_length,
num_heads,
}
}
}
#[derive(Debug, Clone)]
pub struct AttentionPatterns<
T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
> {
pub head_entropies: Vec<T>,
pub attention_diversity: T,
pub sequence_length: usize,
pub num_heads: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multi_head_attention_creation() {
let attention = MultiHeadAttention::<f32>::new(8, 512, 64);
assert!(attention.is_ok());
let mha = attention.expect("MultiHeadAttention::new should succeed");
assert_eq!(mha.num_heads, 8);
assert_eq!(mha.model_dimension, 512);
assert_eq!(mha.head_dimension, 64);
}
#[test]
fn test_attention_forward_pass() {
let mut attention = MultiHeadAttention::<f32>::new(4, 128, 32)
.expect("MultiHeadAttention::new should succeed");
let seq_length = 10;
let batch_size = 2;
let input = Array2::<f32>::zeros((batch_size * seq_length, 128));
let result = attention.forward(&input, &input, &input);
assert!(result.is_ok());
let output = result.expect("forward should succeed");
assert_eq!(output.shape(), &[batch_size * seq_length, 128]);
}
#[test]
fn test_self_attention() {
let mut self_attention =
SelfAttention::<f32>::new(4, 128, 32).expect("SelfAttention::new should succeed");
let input = Array2::<f32>::zeros((20, 128)); let result = self_attention.forward(&input);
assert!(result.is_ok());
let output = result.expect("forward should succeed");
assert_eq!(output.shape(), input.shape());
}
#[test]
fn test_parameter_count() {
let attention = MultiHeadAttention::<f32>::new(8, 512, 64)
.expect("MultiHeadAttention::new should succeed");
let param_count = attention.parameter_count();
let expected = 4 * 512 * 512;
assert_eq!(param_count, expected);
}
#[test]
fn test_attention_visualization() {
let visualizer = AttentionVisualizer::<f32>::new();
let attention_weights = Array3::<f32>::zeros((1, 4, 100));
let patterns = visualizer.extract_attention_patterns(&attention_weights);
assert_eq!(patterns.num_heads, 4);
assert_eq!(patterns.sequence_length, 10);
assert_eq!(patterns.head_entropies.len(), 4);
}
}