use crate::{
error::{RealizarError, Result},
tensor::Tensor,
};
mod position;
pub use position::{ALiBi, RoPE, RopeScalingType, ScaledRoPE};
mod model;
pub use model::{Embedding, KVCache, Model, ModelConfig, TransformerBlock};
mod attention;
pub use attention::{Attention, FusedQKVAttention, MultiHeadAttention, SlidingWindowAttention};
pub fn softmax(input: &Tensor<f32>) -> Result<Tensor<f32>> {
let data = input.data();
let shape = input.shape();
if data.is_empty() {
return Err(RealizarError::InvalidShape {
reason: "Cannot apply softmax to empty tensor".to_string(),
});
}
if shape.is_empty() {
return Err(RealizarError::InvalidShape {
reason: "Cannot apply softmax to tensor with empty shape".to_string(),
});
}
let last_dim = shape[shape.len() - 1];
let num_groups = data.len() / last_dim;
let mut output = Vec::with_capacity(data.len());
for group_idx in 0..num_groups {
let start = group_idx * last_dim;
let end = start + last_dim;
let group = &data[start..end];
let max_val = group.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let exp_vals: Vec<f32> = group.iter().map(|&x| (x - max_val).exp()).collect();
let sum_exp: f32 = exp_vals.iter().sum();
for &exp_val in &exp_vals {
output.push(exp_val / sum_exp);
}
}
Tensor::from_vec(shape.to_vec(), output)
}
pub fn gelu(input: &Tensor<f32>) -> Result<Tensor<f32>> {
let data = input.data();
if data.is_empty() {
return Err(RealizarError::InvalidShape {
reason: "Cannot apply GELU to empty tensor".to_string(),
});
}
let output: Vec<f32> = data.iter().map(|&x| trueno::gelu_scalar(x)).collect();
Tensor::from_vec(input.shape().to_vec(), output)
}
#[derive(Debug, Clone)]
pub struct LayerNorm {
normalized_shape: usize,
eps: f32,
weight: Vec<f32>,
bias: Vec<f32>,
}
impl LayerNorm {
pub fn new(normalized_shape: usize, eps: f32) -> Result<Self> {
if normalized_shape == 0 {
return Err(RealizarError::InvalidShape {
reason: "normalized_shape must be > 0".to_string(),
});
}
let weight = vec![1.0; normalized_shape];
let bias = vec![0.0; normalized_shape];
Ok(Self {
normalized_shape,
eps,
weight,
bias,
})
}
pub fn forward(&self, input: &Tensor<f32>) -> Result<Tensor<f32>> {
let shape = input.shape();
if shape.is_empty() {
return Err(RealizarError::InvalidShape {
reason: "Input tensor cannot be empty".to_string(),
});
}
let last_dim = shape[shape.len() - 1];
if last_dim != self.normalized_shape {
return Err(RealizarError::InvalidShape {
reason: format!(
"Last dimension {} doesn't match normalized_shape {}",
last_dim, self.normalized_shape
),
});
}
let data = input.data();
let total_size = data.len();
let num_groups = total_size / self.normalized_shape;
let mut output = Vec::with_capacity(total_size);
for group_idx in 0..num_groups {
let start = group_idx * self.normalized_shape;
let end = start + self.normalized_shape;
let group = &data[start..end];
#[allow(clippy::cast_precision_loss)]
let mean: f32 = group.iter().sum::<f32>() / self.normalized_shape as f32;
#[allow(clippy::cast_precision_loss)]
let variance: f32 = group
.iter()
.map(|&x| {
let diff = x - mean;
diff * diff
})
.sum::<f32>()
/ self.normalized_shape as f32;
for (i, &x) in group.iter().enumerate() {
let normalized = (x - mean) / (variance + self.eps).sqrt();
let transformed = normalized * self.weight[i] + self.bias[i];
output.push(transformed);
}
}
debug_assert!(
output.iter().all(|&x| x.is_finite()),
"LayerNorm produced NaN or Inf values - check input distribution"
);
Tensor::from_vec(shape.to_vec(), output)
}
#[must_use]
pub fn normalized_shape(&self) -> usize {
self.normalized_shape
}
#[must_use]
pub fn eps(&self) -> f32 {
self.eps
}
}
#[derive(Debug, Clone)]
pub struct Linear {
in_features: usize,
out_features: usize,
weight: Vec<f32>,
bias: Vec<f32>,
}
include!("linear.rs");
include!("fused_layer_norm_linear.rs");