#![allow(dead_code)]
type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
use torsh_tensor::Tensor;
pub fn leaky_relu(input: &Tensor, negative_slope: f64) -> Result<Tensor> {
let zero_tensor = torsh_tensor::creation::zeros_like(input)?;
let positive_part = input.maximum(&zero_tensor)?;
let negative_part = input
.minimum(&zero_tensor)?
.mul_scalar(negative_slope as f32)?;
Ok(positive_part.add(&negative_part)?)
}
pub fn elu(input: &Tensor, alpha: f64) -> Result<Tensor> {
let zero_tensor = torsh_tensor::creation::zeros_like(input)?;
let positive_part = input.maximum(&zero_tensor)?;
let negative_part = input.minimum(&zero_tensor)?;
let exp_part = negative_part
.exp()?
.sub_scalar(1.0)?
.mul_scalar(alpha as f32)?;
Ok(positive_part.add(&exp_part)?)
}
pub fn swish(input: &Tensor) -> Result<Tensor> {
let one_tensor = torsh_tensor::creation::ones_like(input)?;
let neg_input = input.neg()?;
let exp_neg = neg_input.exp()?;
let denominator = one_tensor.add(&exp_neg)?;
let sigmoid = one_tensor.div(&denominator)?;
Ok(input.mul(&sigmoid)?)
}
pub fn gelu(input: &Tensor) -> Result<Tensor> {
let sqrt_2_over_pi = (2.0 / std::f64::consts::PI).sqrt() as f32;
let coefficient = 0.044715_f32;
let x_cubed = input.pow_scalar(3.0)?;
let coeff_term = x_cubed.mul_scalar(coefficient)?;
let sum_term = input.add(&coeff_term)?;
let inner = sum_term.mul_scalar(sqrt_2_over_pi)?;
let tanh_part = inner.tanh()?;
let one_tensor = torsh_tensor::creation::ones_like(input)?;
let one_plus_tanh = one_tensor.add(&tanh_part)?;
let half_tensor = torsh_tensor::creation::ones_like(input)?.mul_scalar(0.5)?;
Ok(half_tensor.mul(input)?.mul(&one_plus_tanh)?)
}
pub fn mish(input: &Tensor) -> Result<Tensor> {
let one_tensor = torsh_tensor::creation::ones_like(input)?;
let exp_input = input.exp()?;
let one_plus_exp = one_tensor.add(&exp_input)?;
let softplus = one_plus_exp.ln()?;
let tanh_softplus = softplus.tanh()?;
Ok(input.mul(&tanh_softplus)?)
}
pub mod normalization {
use super::*;
pub fn layer_norm(input: &Tensor, eps: f64) -> Result<Tensor> {
let shape = input.shape();
let dims = shape.dims();
let last_dim = dims.len() - 1;
let mean = input.mean(Some(&[last_dim]), false)?;
let input_shape = dims.to_vec();
let mut mean_shape = input_shape.clone();
mean_shape[last_dim] = 1;
let mean_expanded = mean.unsqueeze(-1)?;
let mean_broadcasted = mean_expanded.expand(&input_shape)?;
let diff = input.sub(&mean_broadcasted)?;
let variance = diff.pow_scalar(2.0)?.mean(Some(&[last_dim]), false)?;
let variance_expanded = variance.unsqueeze(-1)?;
let variance_broadcasted = variance_expanded.expand(&input_shape)?;
let eps_tensor =
torsh_tensor::creation::ones_like(&variance_broadcasted)?.mul_scalar(eps as f32)?;
let variance_plus_eps = variance_broadcasted.add(&eps_tensor)?;
let std = variance_plus_eps.sqrt()?;
Ok(diff.div(&std)?)
}
pub fn graph_norm(input: &Tensor, edge_index: &Tensor, num_nodes: usize) -> Result<Tensor> {
let edge_index_i32 = edge_index.to_i32_simd()?;
let edge_index_i64 = edge_index_i32.to_i64_simd()?;
let edge_data = crate::utils::tensor_to_vec2::<i64>(&edge_index_i64)?;
let mut degrees = vec![0.0_f32; num_nodes];
for j in 0..edge_data[0].len() {
let src = edge_data[0][j] as usize;
let dst = edge_data[1][j] as usize;
if src < num_nodes {
degrees[src] += 1.0;
}
if dst < num_nodes {
degrees[dst] += 1.0;
}
}
let degree_tensor = torsh_tensor::creation::from_vec(
degrees
.iter()
.map(|&d| if d > 0.0 { 1.0 / d.sqrt() } else { 0.0 })
.collect(),
&[num_nodes],
torsh_core::device::DeviceType::Cpu,
)?;
let input_shape_binding = input.shape();
let input_shape = input_shape_binding.dims();
let degree_expanded = degree_tensor.unsqueeze(-1)?.expand(input_shape)?;
Ok(input.mul(°ree_expanded)?)
}
pub fn batch_norm(input: &Tensor, eps: f64) -> Result<Tensor> {
let mean = input.mean(Some(&[0]), true)?;
let input_shape_binding = input.shape();
let input_shape = input_shape_binding.dims();
let mean_expanded = mean.expand(input_shape)?;
let diff = input.sub(&mean_expanded)?;
let variance = diff.pow_scalar(2.0)?.mean(Some(&[0]), true)?;
let eps_tensor = torsh_tensor::creation::ones_like(&variance)?.mul_scalar(eps as f32)?;
let variance_plus_eps = variance.add(&eps_tensor)?;
let std = variance_plus_eps.sqrt()?;
let std_expanded = std.expand(input_shape)?;
Ok(diff.div(&std_expanded)?)
}
}
pub fn dropout(input: &Tensor, p: f64, training: bool) -> Result<Tensor> {
if !training || p == 0.0 {
return Ok(input.clone());
}
if p == 1.0 {
return Ok(torsh_tensor::creation::zeros_like(input)?);
}
let keep_prob = 1.0 - p;
let random_tensor = torsh_tensor::creation::rand_like(input)?;
let keep_prob_tensor =
torsh_tensor::creation::ones_like(input)?.mul_scalar(keep_prob as f32)?;
let diff_tensor = random_tensor.sub(&keep_prob_tensor)?;
let mask_raw = diff_tensor.relu()?;
let zero_tensor = torsh_tensor::creation::zeros_like(input)?;
let _mask_binary = mask_raw.gt(&zero_tensor)?;
let inverted_prob = random_tensor.gt(&keep_prob_tensor)?;
let _ones_f32 = torsh_tensor::creation::ones_like(input)?;
let _zeros_f32 = torsh_tensor::creation::zeros_like(input)?;
let inverted_data = inverted_prob.to_vec()?;
let keep_data: Vec<f32> = inverted_data
.iter()
.map(|&drop| if drop { 0.0 } else { 1.0 })
.collect();
let keep_mask =
torsh_tensor::creation::from_vec(keep_data, input.shape().dims(), input.device())?;
let masked = input.mul(&keep_mask)?;
Ok(masked.div_scalar(keep_prob as f32)?)
}
pub mod attention {
use super::*;
pub fn scaled_dot_product_attention(
query: &Tensor,
key: &Tensor,
value: &Tensor,
mask: Option<&Tensor>,
) -> Result<Tensor> {
let binding = key.shape();
let d_k = binding.dims().last().ok_or_else(|| {
torsh_core::error::TorshError::InvalidArgument(
"key tensor must have at least one dimension".to_string(),
)
})?;
let scale = 1.0 / (*d_k as f64).sqrt();
let key_transposed = key.transpose(-2, -1)?;
let scores = query.matmul(&key_transposed)?.mul_scalar(scale as f32)?;
let masked_scores = if let Some(mask) = mask {
let large_neg = torsh_tensor::creation::ones_like(&scores)?.mul_scalar(-1e9_f32)?;
let mask_effect = mask.mul(&large_neg)?;
scores.add(&mask_effect)?
} else {
scores
};
let attention_weights = masked_scores.softmax(-1)?;
Ok(attention_weights.matmul(value)?)
}
pub fn multi_head_attention(
query: &Tensor,
key: &Tensor,
value: &Tensor,
num_heads: usize,
mask: Option<&Tensor>,
) -> Result<Tensor> {
let batch_size = query.shape().dims()[0];
let seq_len = query.shape().dims()[1];
let d_model = query.shape().dims()[2];
let d_k = d_model / num_heads;
let q = query
.view(&[
batch_size as i32,
seq_len as i32,
num_heads as i32,
d_k as i32,
])?
.transpose(1, 2)?;
let k = key
.view(&[
batch_size as i32,
seq_len as i32,
num_heads as i32,
d_k as i32,
])?
.transpose(1, 2)?;
let v = value
.view(&[
batch_size as i32,
seq_len as i32,
num_heads as i32,
d_k as i32,
])?
.transpose(1, 2)?;
let attention_output = scaled_dot_product_attention(&q, &k, &v, mask);
Ok(attention_output?.transpose(1, 2)?.contiguous()?.view(&[
batch_size as i32,
seq_len as i32,
d_model as i32,
])?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_leaky_relu() {
let input = torsh_tensor::creation::from_vec(
vec![-2.0, -1.0, 0.0, 1.0, 2.0],
&[5],
torsh_core::device::DeviceType::Cpu,
)
.unwrap();
let output = leaky_relu(&input, 0.01);
let expected = vec![-0.02, -0.01, 0.0, 1.0, 2.0];
let output_vec = output
.expect("operation should succeed")
.to_vec()
.expect("conversion should succeed");
for (actual, expected) in output_vec.iter().zip(expected.iter()) {
assert_relative_eq!(actual, expected, epsilon = 1e-6);
}
}
#[test]
fn test_elu() {
let input = torsh_tensor::creation::from_vec(
vec![-1.0, 0.0, 1.0],
&[3],
torsh_core::device::DeviceType::Cpu,
)
.unwrap();
let output = elu(&input, 1.0);
let output_vec = output
.expect("operation should succeed")
.to_vec()
.expect("conversion should succeed");
assert_relative_eq!(output_vec[2], 1.0, epsilon = 1e-6);
assert_relative_eq!(output_vec[1], 0.0, epsilon = 1e-6);
let expected_negative = 1.0 * ((-1.0_f32).exp() - 1.0);
assert_relative_eq!(output_vec[0], expected_negative, epsilon = 1e-6);
}
#[test]
fn test_swish() {
let input = torsh_tensor::creation::from_vec(
vec![0.0, 1.0, -1.0],
&[3],
torsh_core::device::DeviceType::Cpu,
)
.unwrap();
let output = swish(&input);
let output_vec = output
.expect("operation should succeed")
.to_vec()
.expect("conversion should succeed");
assert_relative_eq!(output_vec[0], 0.0, epsilon = 1e-6);
let sigmoid_1 = 1.0 / (1.0 + (-1.0_f32).exp());
assert_relative_eq!(output_vec[1], sigmoid_1, epsilon = 1e-6);
}
#[test]
fn test_layer_norm() {
let input = torsh_tensor::creation::from_vec(
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
&[2, 3],
torsh_core::device::DeviceType::Cpu,
)
.unwrap();
let output = normalization::layer_norm(&input, 1e-8).expect("operation should succeed");
let output_2d = crate::utils::tensor_to_vec2::<f32>(&output).unwrap();
for row in output_2d {
let mean: f32 = row.iter().sum::<f32>() / row.len() as f32;
let var: f32 = row.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / row.len() as f32;
assert_relative_eq!(mean, 0.0, epsilon = 1e-6);
assert_relative_eq!(var.sqrt(), 1.0, epsilon = 1e-6);
}
}
#[test]
fn test_dropout() {
let input = torsh_tensor::creation::ones(&[100]).unwrap();
let output_training = dropout(&input, 0.5, true);
let output_vec = output_training
.expect("operation should succeed")
.to_vec()
.expect("conversion should succeed");
let num_zeros = output_vec.iter().filter(|&&x| x == 0.0).count();
let num_nonzeros = output_vec.iter().filter(|&&x| x != 0.0).count();
assert!(num_zeros > 30 && num_zeros < 70); assert!(num_nonzeros > 30 && num_nonzeros < 70);
let output_eval = dropout(&input, 0.5, false);
let output_eval_vec = output_eval
.expect("operation should succeed")
.to_vec()
.expect("conversion should succeed");
assert!(output_eval_vec.iter().all(|&x| x == 1.0));
}
}