use crate::layers::attention::MultiHeadAttention;
use crate::layers::transformer::{PositionalEncoding, PositionalEncodingType};
use crate::weight_init::{InitStrategy, WeightInitializer};
use crate::NeuralResult;
use scirs2_core::ndarray::{s, Array1, Array2, Array3};
use scirs2_core::random::ChaCha8Rng;
use scirs2_core::random::SeedableRng;
use sklears_core::types::FloatBounds;
#[derive(Debug, Clone)]
pub struct TransformerConfig<T: FloatBounds> {
pub d_model: usize,
pub num_heads: usize,
pub d_ff: usize,
pub num_encoder_layers: usize,
pub num_decoder_layers: usize,
pub max_seq_len: usize,
pub vocab_size: usize,
pub dropout_rate: T,
pub label_smoothing: T,
pub pre_norm: bool,
pub activation: String,
pub share_embeddings: bool,
}
impl<T: FloatBounds> Default for TransformerConfig<T> {
fn default() -> Self {
Self {
d_model: 512,
num_heads: 8,
d_ff: 2048,
num_encoder_layers: 6,
num_decoder_layers: 6,
max_seq_len: 512,
vocab_size: 30000,
dropout_rate: T::from(0.1).unwrap_or_else(|| T::zero()),
label_smoothing: T::from(0.1).unwrap_or_else(|| T::zero()),
pre_norm: false,
activation: "relu".to_string(),
share_embeddings: false,
}
}
}
#[derive(Debug, Clone)]
pub struct FeedForwardNetwork<T: FloatBounds> {
linear1: Array2<T>,
bias1: Array1<T>,
linear2: Array2<T>,
bias2: Array1<T>,
activation: String,
dropout_rate: T,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> FeedForwardNetwork<T> {
pub fn new(
d_model: usize,
d_ff: usize,
dropout_rate: T,
activation: String,
) -> NeuralResult<Self> {
let mut rng = ChaCha8Rng::seed_from_u64(42);
let initializer = WeightInitializer::new(InitStrategy::XavierUniform);
let linear1 = initializer.initialize_2d(&mut rng, (d_model, d_ff))?;
let bias1 = Array1::zeros(d_ff);
let linear2 = initializer.initialize_2d(&mut rng, (d_ff, d_model))?;
let bias2 = Array1::zeros(d_model);
Ok(Self {
linear1,
bias1,
linear2,
bias2,
activation,
dropout_rate,
})
}
pub fn forward(&self, input: &Array3<T>, training: bool) -> NeuralResult<Array3<T>> {
let (batch_size, seq_len, _d_model) = input.dim();
let mut output = Array3::zeros((batch_size, seq_len, self.linear2.ncols()));
for b in 0..batch_size {
for s in 0..seq_len {
let x = input.slice(s![b, s, ..]);
let hidden = x.dot(&self.linear1) + &self.bias1;
let activated = match self.activation.as_str() {
"relu" => hidden.mapv(|x| x.max(T::zero())),
"gelu" => hidden.mapv(|x| {
let x_f64 = x.to_f64().unwrap_or(0.0);
let gelu_val = 0.5 * x_f64 * (1.0 + (x_f64 * 0.7978845608028654).tanh());
T::from(gelu_val).unwrap_or(T::zero())
}),
_ => hidden, };
let dropout_applied = if training && self.dropout_rate > T::zero() {
let mut rng = scirs2_core::random::thread_rng();
activated.mapv(|x| {
if rng.random::<f64>() < self.dropout_rate.to_f64().unwrap_or(0.0) {
T::zero()
} else {
x / (T::one() - self.dropout_rate)
}
})
} else {
activated
};
let final_output = dropout_applied.dot(&self.linear2) + &self.bias2;
output.slice_mut(s![b, s, ..]).assign(&final_output);
}
}
Ok(output)
}
}
#[derive(Debug, Clone)]
pub struct LayerNorm<T: FloatBounds> {
weight: Array1<T>,
bias: Array1<T>,
eps: T,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> LayerNorm<T> {
pub fn new(d_model: usize) -> Self {
Self {
weight: Array1::ones(d_model),
bias: Array1::zeros(d_model),
eps: T::from(1e-5).unwrap_or_else(|| T::zero()),
}
}
pub fn forward(&self, input: &Array3<T>) -> Array3<T> {
let (batch_size, seq_len, d_model) = input.dim();
let mut output = Array3::zeros((batch_size, seq_len, d_model));
for b in 0..batch_size {
for s in 0..seq_len {
let x = input.slice(s![b, s, ..]);
let mean = x.mean().unwrap_or(T::zero());
let variance = x
.mapv(|v| (v - mean) * (v - mean))
.mean()
.unwrap_or(T::zero());
let std = (variance + self.eps).sqrt();
let normalized = x.mapv(|v| (v - mean) / std);
let scaled = &normalized * &self.weight + &self.bias;
output.slice_mut(s![b, s, ..]).assign(&scaled);
}
}
output
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct TransformerEncoderLayer<T: FloatBounds> {
self_attention: MultiHeadAttention<T>,
ffn: FeedForwardNetwork<T>,
norm1: LayerNorm<T>,
norm2: LayerNorm<T>,
dropout_rate: T,
pre_norm: bool,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> TransformerEncoderLayer<T> {
pub fn new(config: &TransformerConfig<T>) -> NeuralResult<Self> {
let self_attention =
MultiHeadAttention::new(config.num_heads, config.d_model, Some(config.dropout_rate))?;
let ffn = FeedForwardNetwork::new(
config.d_model,
config.d_ff,
config.dropout_rate,
config.activation.clone(),
)?;
let norm1 = LayerNorm::new(config.d_model);
let norm2 = LayerNorm::new(config.d_model);
Ok(Self {
self_attention,
ffn,
norm1,
norm2,
dropout_rate: config.dropout_rate,
pre_norm: config.pre_norm,
})
}
pub fn forward(
&mut self,
input: &Array3<T>,
mask: Option<&Array2<bool>>,
training: bool,
) -> NeuralResult<Array3<T>> {
if self.pre_norm {
let norm_input = self.norm1.forward(input);
let attn_output =
self.self_attention
.apply(&norm_input, &norm_input, &norm_input, mask, training)?;
let residual1 = input + &attn_output;
let norm_residual1 = self.norm2.forward(&residual1);
let ffn_output = self.ffn.forward(&norm_residual1, training)?;
let residual2 = &residual1 + &ffn_output;
Ok(residual2)
} else {
let attn_output = self
.self_attention
.apply(input, input, input, mask, training)?;
let residual1 = input + &attn_output;
let norm1_output = self.norm1.forward(&residual1);
let ffn_output = self.ffn.forward(&norm1_output, training)?;
let residual2 = &norm1_output + &ffn_output;
let norm2_output = self.norm2.forward(&residual2);
Ok(norm2_output)
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct TransformerDecoderLayer<T: FloatBounds> {
self_attention: MultiHeadAttention<T>,
cross_attention: MultiHeadAttention<T>,
ffn: FeedForwardNetwork<T>,
norm1: LayerNorm<T>,
norm2: LayerNorm<T>,
norm3: LayerNorm<T>,
dropout_rate: T,
pre_norm: bool,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> TransformerDecoderLayer<T> {
pub fn new(config: &TransformerConfig<T>) -> NeuralResult<Self> {
let self_attention =
MultiHeadAttention::new(config.num_heads, config.d_model, Some(config.dropout_rate))?;
let cross_attention =
MultiHeadAttention::new(config.num_heads, config.d_model, Some(config.dropout_rate))?;
let ffn = FeedForwardNetwork::new(
config.d_model,
config.d_ff,
config.dropout_rate,
config.activation.clone(),
)?;
let norm1 = LayerNorm::new(config.d_model);
let norm2 = LayerNorm::new(config.d_model);
let norm3 = LayerNorm::new(config.d_model);
Ok(Self {
self_attention,
cross_attention,
ffn,
norm1,
norm2,
norm3,
dropout_rate: config.dropout_rate,
pre_norm: config.pre_norm,
})
}
pub fn forward(
&mut self,
input: &Array3<T>,
encoder_output: &Array3<T>,
self_mask: Option<&Array2<bool>>,
cross_mask: Option<&Array2<bool>>,
training: bool,
) -> NeuralResult<Array3<T>> {
if self.pre_norm {
let norm_input = self.norm1.forward(input);
let self_attn_output = self.self_attention.apply(
&norm_input,
&norm_input,
&norm_input,
self_mask,
training,
)?;
let residual1 = input + &self_attn_output;
let norm_residual1 = self.norm2.forward(&residual1);
let cross_attn_output = self.cross_attention.apply(
&norm_residual1,
encoder_output,
encoder_output,
cross_mask,
training,
)?;
let residual2 = &residual1 + &cross_attn_output;
let norm_residual2 = self.norm3.forward(&residual2);
let ffn_output = self.ffn.forward(&norm_residual2, training)?;
let residual3 = &residual2 + &ffn_output;
Ok(residual3)
} else {
let self_attn_output = self
.self_attention
.apply(input, input, input, self_mask, training)?;
let residual1 = input + &self_attn_output;
let norm1_output = self.norm1.forward(&residual1);
let cross_attn_output = self.cross_attention.apply(
&norm1_output,
encoder_output,
encoder_output,
cross_mask,
training,
)?;
let residual2 = &norm1_output + &cross_attn_output;
let norm2_output = self.norm2.forward(&residual2);
let ffn_output = self.ffn.forward(&norm2_output, training)?;
let residual3 = &norm2_output + &ffn_output;
let norm3_output = self.norm3.forward(&residual3);
Ok(norm3_output)
}
}
}
#[derive(Debug, Clone)]
pub struct TransformerEncoder<T: FloatBounds> {
layers: Vec<TransformerEncoderLayer<T>>,
embeddings: Array2<T>,
positional_encoding: PositionalEncoding<T>,
final_norm: Option<LayerNorm<T>>,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand + From<f64>> TransformerEncoder<T> {
pub fn new(config: &TransformerConfig<T>) -> NeuralResult<Self> {
let mut rng = ChaCha8Rng::seed_from_u64(42);
let initializer = WeightInitializer::new(InitStrategy::XavierUniform);
let mut layers = Vec::new();
for _ in 0..config.num_encoder_layers {
layers.push(TransformerEncoderLayer::new(config)?);
}
let embeddings =
initializer.initialize_2d(&mut rng, (config.vocab_size, config.d_model))?;
let positional_encoding = PositionalEncoding::new(
config.max_seq_len,
config.d_model,
PositionalEncodingType::Sinusoidal,
config.dropout_rate,
true,
)?;
let final_norm = if config.pre_norm {
Some(LayerNorm::new(config.d_model))
} else {
None
};
Ok(Self {
layers,
embeddings,
positional_encoding,
final_norm,
})
}
pub fn forward(
&mut self,
input_ids: &Array2<usize>,
mask: Option<&Array2<bool>>,
training: bool,
) -> NeuralResult<Array3<T>> {
let (batch_size, seq_len) = input_ids.dim();
let d_model = self.embeddings.ncols();
let mut embedded = Array3::zeros((batch_size, seq_len, d_model));
for b in 0..batch_size {
for s in 0..seq_len {
let token_id = input_ids[[b, s]];
if token_id < self.embeddings.nrows() {
embedded
.slice_mut(s![b, s, ..])
.assign(&self.embeddings.row(token_id));
}
}
}
let mut x = self.positional_encoding.encode(&embedded)?;
for layer in &mut self.layers {
x = layer.forward(&x, mask, training)?;
}
if let Some(ref final_norm) = self.final_norm {
x = final_norm.forward(&x);
}
Ok(x)
}
}
#[derive(Debug, Clone)]
pub struct TransformerDecoder<T: FloatBounds> {
layers: Vec<TransformerDecoderLayer<T>>,
embeddings: Array2<T>,
positional_encoding: PositionalEncoding<T>,
final_norm: Option<LayerNorm<T>>,
output_projection: Array2<T>,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand + From<f64>> TransformerDecoder<T> {
pub fn new(config: &TransformerConfig<T>) -> NeuralResult<Self> {
let mut rng = ChaCha8Rng::seed_from_u64(42);
let initializer = WeightInitializer::new(InitStrategy::XavierUniform);
let mut layers = Vec::new();
for _ in 0..config.num_decoder_layers {
layers.push(TransformerDecoderLayer::new(config)?);
}
let embeddings =
initializer.initialize_2d(&mut rng, (config.vocab_size, config.d_model))?;
let positional_encoding = PositionalEncoding::new(
config.max_seq_len,
config.d_model,
PositionalEncodingType::Sinusoidal,
config.dropout_rate,
true,
)?;
let final_norm = if config.pre_norm {
Some(LayerNorm::new(config.d_model))
} else {
None
};
let output_projection =
initializer.initialize_2d(&mut rng, (config.d_model, config.vocab_size))?;
Ok(Self {
layers,
embeddings,
positional_encoding,
final_norm,
output_projection,
})
}
pub fn forward(
&mut self,
input_ids: &Array2<usize>,
encoder_output: &Array3<T>,
self_mask: Option<&Array2<bool>>,
cross_mask: Option<&Array2<bool>>,
training: bool,
) -> NeuralResult<Array3<T>> {
let (batch_size, seq_len) = input_ids.dim();
let d_model = self.embeddings.ncols();
let mut embedded = Array3::zeros((batch_size, seq_len, d_model));
for b in 0..batch_size {
for s in 0..seq_len {
let token_id = input_ids[[b, s]];
if token_id < self.embeddings.nrows() {
embedded
.slice_mut(s![b, s, ..])
.assign(&self.embeddings.row(token_id));
}
}
}
let mut x = self.positional_encoding.encode(&embedded)?;
for layer in &mut self.layers {
x = layer.forward(&x, encoder_output, self_mask, cross_mask, training)?;
}
if let Some(ref final_norm) = self.final_norm {
x = final_norm.forward(&x);
}
let (batch_size, seq_len, _d_model) = x.dim();
let mut logits = Array3::zeros((batch_size, seq_len, self.output_projection.ncols()));
for b in 0..batch_size {
for s in 0..seq_len {
let hidden = x.slice(s![b, s, ..]);
let output = hidden.dot(&self.output_projection);
logits.slice_mut(s![b, s, ..]).assign(&output);
}
}
Ok(logits)
}
}
#[derive(Debug, Clone)]
pub struct EncoderDecoderTransformer<T: FloatBounds> {
encoder: TransformerEncoder<T>,
decoder: TransformerDecoder<T>,
config: TransformerConfig<T>,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand + From<f64>>
EncoderDecoderTransformer<T>
{
pub fn new(config: TransformerConfig<T>) -> NeuralResult<Self> {
let encoder = TransformerEncoder::new(&config)?;
let decoder = TransformerDecoder::new(&config)?;
Ok(Self {
encoder,
decoder,
config,
})
}
pub fn forward(
&mut self,
src_ids: &Array2<usize>,
tgt_ids: &Array2<usize>,
src_mask: Option<&Array2<bool>>,
tgt_mask: Option<&Array2<bool>>,
cross_mask: Option<&Array2<bool>>,
training: bool,
) -> NeuralResult<Array3<T>> {
let encoder_output = self.encoder.forward(src_ids, src_mask, training)?;
let decoder_output =
self.decoder
.forward(tgt_ids, &encoder_output, tgt_mask, cross_mask, training)?;
Ok(decoder_output)
}
pub fn generate(
&mut self,
src_ids: &Array2<usize>,
max_length: usize,
start_token: usize,
end_token: usize,
src_mask: Option<&Array2<bool>>,
) -> NeuralResult<Array2<usize>> {
let batch_size = src_ids.nrows();
let encoder_output = self.encoder.forward(src_ids, src_mask, false)?;
let mut generated = Array2::from_elem((batch_size, 1), start_token);
for _ in 1..max_length {
let seq_len = generated.ncols();
let mut tgt_mask = Array2::from_elem((seq_len, seq_len), true);
for i in 0..seq_len {
for j in i + 1..seq_len {
tgt_mask[[i, j]] = false;
}
}
let logits =
self.decoder
.forward(&generated, &encoder_output, Some(&tgt_mask), None, false)?;
let last_logits = logits.slice(s![.., -1, ..]);
let mut next_tokens = Array1::zeros(batch_size);
for b in 0..batch_size {
let logits_row = last_logits.slice(s![b, ..]);
let mut max_idx = 0;
let mut max_val = *logits_row.iter().next().expect("empty iterator");
for (idx, &val) in logits_row.iter().enumerate() {
if val > max_val {
max_val = val;
max_idx = idx;
}
}
next_tokens[b] = max_idx;
}
let mut new_generated = Array2::zeros((batch_size, generated.ncols() + 1));
new_generated
.slice_mut(s![.., ..generated.ncols()])
.assign(&generated);
for b in 0..batch_size {
new_generated[[b, generated.ncols()]] = next_tokens[b];
}
generated = new_generated;
let mut all_ended = true;
for b in 0..batch_size {
if next_tokens[b] != end_token {
all_ended = false;
break;
}
}
if all_ended {
break;
}
}
Ok(generated)
}
pub fn config(&self) -> &TransformerConfig<T> {
&self.config
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::essentials::Normal;
use scirs2_core::ndarray::Array3;
use scirs2_core::random::thread_rng;
#[test]
#[ignore]
fn test_transformer_config() {
let config: TransformerConfig<f64> = TransformerConfig::default();
assert_eq!(config.d_model, 512);
assert_eq!(config.num_heads, 8);
assert_eq!(config.d_ff, 2048);
}
#[test]
#[ignore]
fn test_feed_forward_network() {
let ffn = FeedForwardNetwork::new(512, 2048, 0.1, "relu".to_string())
.expect("construction should succeed");
let input = Array3::from_shape_fn((2, 10, 512), |_| {
let mut rng = thread_rng();
rng.sample(Normal::new(0.0, 1.0).expect("construction should succeed"))
});
let output = ffn
.forward(&input, false)
.expect("forward pass should succeed");
assert_eq!(output.dim(), (2, 10, 512));
}
#[test]
#[ignore]
fn test_layer_norm() {
let layer_norm = LayerNorm::<f64>::new(512);
let input = Array3::from_shape_fn((2, 10, 512), |_| {
let mut rng = thread_rng();
rng.sample(Normal::new(0.0, 1.0).expect("construction should succeed"))
});
let output = layer_norm.forward(&input);
assert_eq!(output.dim(), (2, 10, 512));
}
#[test]
#[ignore]
fn test_transformer_encoder_layer() {
let config: TransformerConfig<f64> = TransformerConfig::default();
let mut encoder_layer =
TransformerEncoderLayer::new(&config).expect("construction should succeed");
let input = Array3::from_shape_fn((2, 10, 512), |_| {
let mut rng = thread_rng();
rng.sample(Normal::new(0.0, 1.0).expect("construction should succeed"))
});
let output = encoder_layer
.forward(&input, None, false)
.expect("forward pass should succeed");
assert_eq!(output.dim(), (2, 10, 512));
}
#[test]
#[ignore]
fn test_transformer_decoder_layer() {
let config: TransformerConfig<f64> = TransformerConfig::default();
let mut decoder_layer =
TransformerDecoderLayer::new(&config).expect("construction should succeed");
let input = Array3::from_shape_fn((2, 10, 512), |_| {
let mut rng = thread_rng();
rng.sample(Normal::new(0.0, 1.0).expect("construction should succeed"))
});
let encoder_output = Array3::from_shape_fn((2, 15, 512), |_| {
let mut rng = thread_rng();
rng.sample(Normal::new(0.0, 1.0).expect("construction should succeed"))
});
let output = decoder_layer
.forward(&input, &encoder_output, None, None, false)
.expect("operation should succeed");
assert_eq!(output.dim(), (2, 10, 512));
}
#[test]
#[ignore]
fn test_encoder_decoder_transformer() {
let config: TransformerConfig<f64> = TransformerConfig {
vocab_size: 1000,
max_seq_len: 20,
d_model: 128,
num_heads: 4,
d_ff: 256,
num_encoder_layers: 2,
num_decoder_layers: 2,
..Default::default()
};
let mut transformer =
EncoderDecoderTransformer::new(config).expect("construction should succeed");
let src_ids = Array2::from_elem((2, 10), 1); let tgt_ids = Array2::from_elem((2, 8), 2);
let output = transformer
.forward(&src_ids, &tgt_ids, None, None, None, false)
.expect("operation should succeed");
assert_eq!(output.dim(), (2, 8, 1000)); }
#[test]
#[ignore]
fn test_transformer_generation() {
let config: TransformerConfig<f64> = TransformerConfig {
vocab_size: 100,
max_seq_len: 20,
d_model: 64,
num_heads: 2,
d_ff: 128,
num_encoder_layers: 1,
num_decoder_layers: 1,
..Default::default()
};
let mut transformer =
EncoderDecoderTransformer::new(config).expect("construction should succeed");
let src_ids = Array2::from_elem((1, 5), 1);
let generated = transformer
.generate(&src_ids, 10, 2, 3, None)
.expect("operation should succeed");
assert_eq!(generated.nrows(), 1);
assert!(generated.ncols() <= 10);
assert_eq!(generated[[0, 0]], 2); }
}