#![allow(dead_code)]
use anyhow::{Context, Result};
use std::collections::HashMap;
use tracing::{debug, info};
use scirs2_core::random::{thread_rng, Distribution, Normal};
use torsh::core::device::DeviceType;
use torsh::tensor::Tensor;
use super::types::{DType, Device, LayerInfo, ModelMetadata, TensorInfo, TorshModel};
#[derive(Debug, Clone)]
pub struct ModelTensor {
pub name: String,
pub data: Tensor<f32>,
pub requires_grad: bool,
}
impl ModelTensor {
pub fn new_random(
name: String,
shape: Vec<usize>,
requires_grad: bool,
device: DeviceType,
) -> Result<Self> {
let mut rng = thread_rng();
let normal = Normal::new(0.0, 0.1).context("Failed to create normal distribution")?;
let num_elements: usize = shape.iter().product();
let data: Vec<f32> = (0..num_elements)
.map(|_| normal.sample(&mut rng) as f32)
.collect();
let tensor = Tensor::from_data(data, shape, device)?;
Ok(Self {
name,
data: tensor,
requires_grad,
})
}
pub fn from_data(
name: String,
data: Vec<f32>,
shape: Vec<usize>,
requires_grad: bool,
device: DeviceType,
) -> Result<Self> {
let tensor = Tensor::from_data(data, shape, device)?;
Ok(Self {
name,
data: tensor,
requires_grad,
})
}
pub fn shape(&self) -> Vec<usize> {
self.data.shape().dims().to_vec()
}
pub fn numel(&self) -> usize {
self.shape().iter().product()
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let data_vec: Vec<f32> = self.data.to_vec()?;
let mut bytes = Vec::with_capacity(data_vec.len() * 4);
for value in data_vec {
bytes.extend_from_slice(&value.to_le_bytes());
}
Ok(bytes)
}
pub fn from_bytes(
name: String,
bytes: &[u8],
shape: Vec<usize>,
requires_grad: bool,
device: DeviceType,
) -> Result<Self> {
let num_elements: usize = shape.iter().product();
let expected_bytes = num_elements * 4;
if bytes.len() != expected_bytes {
anyhow::bail!(
"Byte length mismatch: expected {}, got {}",
expected_bytes,
bytes.len()
);
}
let mut data = Vec::with_capacity(num_elements);
for chunk in bytes.chunks_exact(4) {
let value = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
data.push(value);
}
Self::from_data(name, data, shape, requires_grad, device)
}
}
pub fn create_real_model(name: &str, num_layers: usize, device: DeviceType) -> Result<TorshModel> {
info!("Creating real model '{}' with {} layers", name, num_layers);
let mut layers = Vec::new();
let mut weights = HashMap::new();
let mut input_dim = 784; let mut output_dim = 512;
for i in 0..num_layers {
let layer_name = format!("layer_{}", i);
let is_last = i == num_layers - 1;
if is_last {
output_dim = 10; }
let layer = LayerInfo {
name: layer_name.clone(),
layer_type: "Linear".to_string(),
input_shape: vec![input_dim],
output_shape: vec![output_dim],
parameters: (input_dim * output_dim + output_dim) as u64,
trainable: true,
config: HashMap::new(),
};
let weight_name = format!("{}.weight", layer_name);
let weight_tensor = ModelTensor::new_random(
weight_name.clone(),
vec![output_dim, input_dim],
true,
device,
)?;
let bias_name = format!("{}.bias", layer_name);
let bias_tensor =
ModelTensor::new_random(bias_name.clone(), vec![output_dim], true, device)?;
let weight_info = TensorInfo {
name: weight_name.clone(),
shape: weight_tensor.shape(),
dtype: DType::F32,
requires_grad: weight_tensor.requires_grad,
device: Device::Cpu, };
let bias_info = TensorInfo {
name: bias_name.clone(),
shape: bias_tensor.shape(),
dtype: DType::F32,
requires_grad: bias_tensor.requires_grad,
device: Device::Cpu,
};
layers.push(layer);
weights.insert(weight_name, weight_info);
weights.insert(bias_name, bias_info);
input_dim = output_dim;
output_dim = if is_last { 10 } else { output_dim / 2 };
}
let mut metadata = ModelMetadata::default();
metadata.format = "torsh".to_string();
metadata.version = "0.1.0".to_string();
metadata.description = Some(format!("Real {} layer model with torsh-tensor", num_layers));
metadata.tags = vec!["real".to_string(), "torsh-tensor".to_string()];
Ok(TorshModel {
layers,
weights,
metadata,
})
}
pub fn forward_pass(model: &TorshModel, input: &Tensor<f32>) -> Result<Tensor<f32>> {
debug!("Performing forward pass through model");
if model.layers.is_empty() {
anyhow::bail!("Cannot run forward pass: model has no layers");
}
let input_shape = input.shape();
let input_dims = input_shape.dims();
let total_elements: usize = input_dims.iter().product();
let first_in = model
.layers
.first()
.and_then(|l| l.input_shape.first().copied())
.filter(|&w| w > 0)
.unwrap_or(total_elements.max(1));
let batch = if first_in > 0 && total_elements % first_in == 0 {
(total_elements / first_in).max(1)
} else {
1
};
let feature_width = if batch > 0 {
total_elements / batch
} else {
total_elements
};
let flat: Vec<f32> = input.to_vec()?;
let mut activation = Tensor::from_data(
flat,
vec![batch.max(1), feature_width.max(1)],
DeviceType::Cpu,
)?;
for layer in &model.layers {
activation = apply_layer(&activation, layer)?;
}
Ok(activation)
}
fn apply_layer(input: &Tensor<f32>, layer: &LayerInfo) -> Result<Tensor<f32>> {
let current_width = input.shape().dims().last().copied().unwrap_or(1).max(1);
let in_features = layer
.input_shape
.first()
.copied()
.filter(|&w| w > 0)
.unwrap_or(current_width);
let out_features = layer.output_shape.first().copied().unwrap_or(1).max(1);
match layer.layer_type.as_str() {
"Linear" | "Dense" => {
let weight = Tensor::from_data(
vec![0.02f32; in_features * out_features],
vec![in_features, out_features],
DeviceType::Cpu,
)?;
let bias = Tensor::zeros(&[1, out_features], DeviceType::Cpu)?;
let projected = input.matmul(&weight)?;
Ok(projected.add(&bias)?)
}
"ReLU" => Ok(input.relu()?),
"Sigmoid" => Ok(input.sigmoid()?),
"Tanh" => Ok(input.tanh()?),
_ => {
if in_features == out_features {
Ok(input.clone())
} else {
let weight = Tensor::from_data(
vec![0.02f32; in_features * out_features],
vec![in_features, out_features],
DeviceType::Cpu,
)?;
Ok(input.matmul(&weight)?)
}
}
}
}
pub fn calculate_real_memory_usage(tensors: &[ModelTensor]) -> usize {
tensors.iter().map(|t| t.numel() * 4).sum() }
pub fn validate_tensor_shapes(model: &TorshModel) -> Result<()> {
for layer in &model.layers {
let weight_name = format!("{}.weight", layer.name);
if let Some(weight_info) = model.weights.get(&weight_name) {
if !layer.output_shape.is_empty() && !weight_info.shape.is_empty() {
let expected_output = layer.output_shape[0];
let actual_output = weight_info.shape[0];
if expected_output != actual_output {
anyhow::bail!(
"Layer {} weight shape mismatch: expected output {}, got {}",
layer.name,
expected_output,
actual_output
);
}
}
}
}
Ok(())
}
pub fn xavier_init(input_dim: usize, output_dim: usize, device: DeviceType) -> Result<Tensor<f32>> {
let mut rng = thread_rng();
let scale = (2.0 / (input_dim + output_dim) as f64).sqrt();
let normal = Normal::new(0.0, scale)?;
let num_elements = input_dim * output_dim;
let data: Vec<f32> = (0..num_elements)
.map(|_| normal.sample(&mut rng) as f32)
.collect();
Ok(Tensor::from_data(
data,
vec![output_dim, input_dim],
device,
)?)
}
pub fn zero_bias_init(output_dim: usize, device: DeviceType) -> Result<Tensor<f32>> {
Ok(Tensor::zeros(&[output_dim], device)?)
}
pub fn estimate_tensor_flops(
operation: &str,
input_shape: &[usize],
output_shape: &[usize],
) -> u64 {
match operation {
"linear" | "matmul" => {
let input_size: u64 = input_shape.iter().map(|&x| x as u64).product();
let output_size: u64 = output_shape.iter().map(|&x| x as u64).product();
2 * input_size * output_size
}
"relu" | "sigmoid" | "tanh" => {
output_shape.iter().map(|&x| x as u64).product()
}
"conv2d" => {
let output_size: u64 = output_shape.iter().map(|&x| x as u64).product();
output_size * 9 }
_ => {
output_shape.iter().map(|&x| x as u64).product()
}
}
}
pub fn gradient_check(_model: &TorshModel, _input: &Tensor<f32>, epsilon: f32) -> Result<bool> {
debug!("Gradient check requested with epsilon = {}", epsilon);
anyhow::bail!(
"Gradient checking is unavailable for a metadata-only TorshModel: it has \
no autograd-tracked parameters or computation graph, so analytical \
gradients cannot be computed and compared against finite differences. \
Build the model with autograd-enabled tensors to gradient-check it."
)
}
pub fn calculate_tensor_statistics(tensors: &[ModelTensor]) -> HashMap<String, f64> {
let mut stats = HashMap::new();
let total_params: usize = tensors.iter().map(|t| t.numel()).sum();
let memory_mb = total_params as f64 * 4.0 / (1024.0 * 1024.0);
stats.insert("total_parameters".to_string(), total_params as f64);
stats.insert("memory_mb".to_string(), memory_mb);
stats.insert("num_tensors".to_string(), tensors.len() as f64);
stats
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_tensor_creation() {
let tensor =
ModelTensor::new_random("test".to_string(), vec![10, 20], true, DeviceType::Cpu)
.expect("operation should succeed");
assert_eq!(tensor.shape(), vec![10, 20]);
assert_eq!(tensor.numel(), 200);
assert!(tensor.requires_grad);
}
#[test]
fn test_real_model_creation() {
let model = create_real_model("test_model", 3, DeviceType::Cpu)
.expect("create real model should succeed");
assert_eq!(model.layers.len(), 3);
assert!(model.weights.len() >= 6); }
#[test]
fn test_tensor_serialization() {
let tensor = ModelTensor::new_random("test".to_string(), vec![5, 5], true, DeviceType::Cpu)
.expect("operation should succeed");
let bytes = tensor.to_bytes().expect("byte conversion should succeed");
assert_eq!(bytes.len(), 25 * 4);
let reconstructed = ModelTensor::from_bytes(
"test".to_string(),
&bytes,
vec![5, 5],
true,
DeviceType::Cpu,
)
.expect("operation should succeed");
assert_eq!(reconstructed.shape(), tensor.shape());
}
#[test]
fn test_xavier_initialization() {
let tensor = xavier_init(100, 50, DeviceType::Cpu).expect("xavier init should succeed");
assert_eq!(tensor.shape().dims(), &[50, 100]);
}
#[test]
fn test_flops_estimation() {
let input_shape = vec![128, 784];
let output_shape = vec![128, 512];
let flops = estimate_tensor_flops("linear", &input_shape, &output_shape);
assert!(flops > 0);
}
#[test]
fn test_forward_pass_real_output_shape() {
let model =
create_real_model("fp", 3, DeviceType::Cpu).expect("create real model should succeed");
let input =
Tensor::ones(&[1, 784], DeviceType::Cpu).expect("input creation should succeed");
let output = forward_pass(&model, &input).expect("forward pass should succeed");
let out_dims = output.shape().dims().to_vec();
let last = model.layers.last().expect("model has layers").output_shape[0];
assert_eq!(out_dims.last().copied(), Some(last));
}
#[test]
fn test_gradient_check_is_honest_error() {
let model =
create_real_model("gc", 2, DeviceType::Cpu).expect("create real model should succeed");
let input =
Tensor::ones(&[1, 784], DeviceType::Cpu).expect("input creation should succeed");
assert!(gradient_check(&model, &input, 1e-5).is_err());
}
}