#![allow(dead_code)]
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::PathBuf;
use tracing::{info, warn};
use crate::config::Config;
use crate::utils::{output, progress, validation};
use super::args::{InspectArgs, ValidateArgs};
use super::types::{format_bytes, ModelInfo};
pub async fn analyze_model_file(input_path: &PathBuf) -> Result<ModelInfo> {
let format = match input_path.extension().and_then(|s| s.to_str()) {
Some("torsh") => "torsh",
Some("pth") | Some("pt") => "pytorch",
Some("onnx") => "onnx",
Some("pb") => "tensorflow",
Some("tflite") => "tflite",
_ => "unknown",
};
let file_size = tokio::fs::metadata(input_path).await?.len();
let size_str = format_bytes(file_size);
let name = input_path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let mut metadata = HashMap::new();
metadata.insert("file_size_bytes".to_string(), serde_json::json!(file_size));
metadata.insert("format".to_string(), serde_json::json!(format));
metadata.insert(
"analyzed_at".to_string(),
serde_json::json!(chrono::Utc::now().to_rfc3339()),
);
let (parameters, layers, input_shape, output_shape, precision, device) =
analyze_model_structure(input_path, format).await?;
Ok(ModelInfo {
name,
format: format.to_string(),
size: size_str,
parameters,
layers,
input_shape,
output_shape,
precision,
device,
metadata,
})
}
async fn analyze_model_structure(
input_path: &PathBuf,
format: &str,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
let start_time = std::time::Instant::now();
info!("Starting model structure analysis for format: {}", format);
let result = match format {
"torsh" => analyze_torsh_model(input_path).await,
"pytorch" => analyze_pytorch_model(input_path).await,
"onnx" => analyze_onnx_model(input_path).await,
"tensorflow" => analyze_tensorflow_model(input_path).await,
"tflite" => analyze_tflite_model(input_path).await,
_ => {
warn!("Unknown model format: {}, using basic analysis", format);
analyze_generic_model(input_path).await
}
};
let analysis_duration = start_time.elapsed();
info!(
"Model structure analysis completed in {:?}",
analysis_duration
);
result
}
pub async fn inspect_model(args: InspectArgs, _config: &Config, output_format: &str) -> Result<()> {
validation::validate_file_exists(&args.input)?;
info!("Inspecting model: {}", args.input.display());
let pb = progress::create_spinner("Analyzing model...");
let model_info = analyze_model_file(&args.input).await?;
pb.finish_with_message("Model analysis completed");
output::print_table("Model Information", &model_info, output_format)?;
if args.detailed {
output::print_info("=== Detailed Model Analysis ===");
if let Some(file_size_bytes) = model_info.metadata.get("file_size_bytes") {
output::print_info(&format!(
"File Size: {} bytes ({})",
file_size_bytes, model_info.size
));
}
output::print_info(&format!("Parameters: {}", model_info.parameters));
output::print_info(&format!("Layers: {}", model_info.layers));
output::print_info(&format!("Input Shape: {:?}", model_info.input_shape));
output::print_info(&format!("Output Shape: {:?}", model_info.output_shape));
output::print_info(&format!("Precision: {}", model_info.precision));
output::print_info(&format!("Device: {}", model_info.device));
}
if args.stats {
output::print_info("=== Model Statistics ===");
let param_mb = (model_info.parameters * 4) as f64 / (1024.0 * 1024.0); output::print_info(&format!(
"Estimated Memory (parameters): {:.1} MB",
param_mb
));
let total_elements: u64 = model_info.input_shape.iter().product::<usize>() as u64;
output::print_info(&format!("Input Elements: {}", total_elements));
let output_elements: u64 = model_info.output_shape.iter().product::<usize>() as u64;
output::print_info(&format!("Output Elements: {}", output_elements));
}
if args.memory {
output::print_info("=== Memory Analysis ===");
let param_memory = (model_info.parameters * 4) as f64 / (1024.0 * 1024.0);
let activation_memory =
(model_info.input_shape.iter().product::<usize>() * 4) as f64 / (1024.0 * 1024.0);
output::print_info(&format!("Parameter Memory: {:.1} MB", param_memory));
output::print_info(&format!(
"Estimated Activation Memory: {:.1} MB",
activation_memory
));
output::print_info(&format!(
"Total Estimated Memory: {:.1} MB",
param_memory + activation_memory
));
}
if args.complexity {
output::print_info("=== Complexity Analysis ===");
let input_elements: u64 = model_info.input_shape.iter().product::<usize>() as u64;
let flops_estimate = input_elements * model_info.parameters / 1000; output::print_info(&format!(
"Estimated FLOPs: {:.1}K",
flops_estimate as f64 / 1000.0
));
output::print_info(&format!(
"Model Complexity: {} parameters across {} layers",
model_info.parameters, model_info.layers
));
}
if model_info.format == "torsh" {
if let Ok(full_model) = super::serialization::load_model(&args.input).await {
output::print_info("\n=== Model Architecture Visualization ===");
let viz = super::types::visualize_model_ascii(&full_model);
println!("{}", viz);
}
}
if let Some(export_path) = args.export {
let export_content = output::format_output(&model_info, "json")?;
tokio::fs::write(&export_path, export_content).await?;
output::print_success(&format!(
"Model information exported to {}",
export_path.display()
));
}
Ok(())
}
pub async fn validate_model(
args: ValidateArgs,
_config: &Config,
_output_format: &str,
) -> Result<()> {
validation::validate_file_exists(&args.input)?;
validation::validate_directory_exists(&args.dataset)?;
validation::validate_device(&args.device)?;
info!("Validating model {}", args.input.display());
use super::serialization::load_model;
use super::types::calculate_model_statistics;
let model = load_model(&args.input)
.await
.with_context(|| format!("failed to load model `{}`", args.input.display()))?;
let stats = calculate_model_statistics(&model);
info!(
"Model loaded: {} parameters across {} layers ({:.2} MB)",
stats.total_parameters, stats.num_layers, stats.memory_footprint_mb
);
anyhow::bail!(
"Model `{}` loaded successfully ({} parameters, {} layers), but \
dataset-based accuracy validation is not implemented: decoding `{}` into \
labelled samples and running per-sample inference to measure accuracy \
against the {:.2} threshold is unsupported. This command will not \
fabricate an accuracy or pass/fail result.",
args.input.display(),
stats.total_parameters,
stats.num_layers,
args.dataset.display(),
args.accuracy_threshold
)
}
async fn analyze_torsh_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
info!("Analyzing ToRSh model: {}", input_path.display());
use super::serialization::load_model;
use super::types::calculate_model_statistics;
match load_model(input_path).await {
Ok(model) => {
let stats = calculate_model_statistics(&model);
let input_shape = model
.layers
.first()
.map(|l| l.input_shape.clone())
.unwrap_or_else(|| vec![3, 224, 224]);
let output_shape = model
.layers
.last()
.map(|l| l.output_shape.clone())
.unwrap_or_else(|| vec![1000]);
let precision = model
.weights
.values()
.next()
.map(|t| t.dtype.name())
.unwrap_or("f32")
.to_string();
let device = model
.weights
.values()
.next()
.map(|t| t.device.name())
.unwrap_or_else(|| "cpu".to_string());
info!(
"ToRSh model: {} parameters, {} layers, {:.2} MB",
stats.total_parameters, stats.num_layers, stats.memory_footprint_mb
);
Ok((
stats.total_parameters,
stats.num_layers,
input_shape,
output_shape,
precision,
device,
))
}
Err(e) => {
warn!("Failed to load ToRSh model: {}", e);
let file_size = tokio::fs::metadata(input_path).await?.len();
let estimated_params = (file_size / 4) as u64;
Ok((
estimated_params,
estimate_layers_from_size(file_size as usize),
vec![3, 224, 224],
vec![1000],
"f32".to_string(),
"cpu".to_string(),
))
}
}
}
async fn analyze_pytorch_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
info!("Analyzing PyTorch model: {}", input_path.display());
use super::pytorch_parser::parse_pytorch_model;
match parse_pytorch_model(input_path).await {
Ok(pytorch_info) => {
info!(
"PyTorch model: version {}, {} parameters, {} state dict keys",
pytorch_info.version_display(),
pytorch_info.num_parameters,
pytorch_info.state_dict_keys.len()
);
let num_layers = estimate_layers_from_keys(&pytorch_info.state_dict_keys);
Ok((
pytorch_info.num_parameters,
num_layers,
vec![3, 224, 224], vec![1000], "f32".to_string(),
"cpu".to_string(),
))
}
Err(e) => {
warn!("Failed to parse PyTorch model: {}", e);
let file_size = tokio::fs::metadata(input_path).await?.len();
let estimated_params = (file_size / 6) as u64;
Ok((
estimated_params,
estimate_layers_from_size(file_size as usize),
vec![3, 224, 224],
vec![1000],
"f32".to_string(),
"cpu".to_string(),
))
}
}
}
fn estimate_layers_from_keys(keys: &[String]) -> usize {
let mut layer_names = std::collections::HashSet::new();
for key in keys {
if let Some(layer_name) = key.split('.').next() {
layer_names.insert(layer_name);
}
}
layer_names.len().max(1)
}
async fn analyze_onnx_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
info!("Analyzing ONNX model: {}", input_path.display());
match tokio::fs::read(input_path).await {
Ok(model_data) => {
let file_size = model_data.len();
let estimated_params = (file_size / 5) as u64;
let size_mb = file_size as f64 / (1024.0 * 1024.0);
let complexity_score = (size_mb * 1.5) as usize;
Ok((
estimated_params,
complexity_score.min(500), vec![1, 3, 224, 224], vec![1, 1000], "f32".to_string(),
"cpu".to_string(),
))
}
Err(e) => {
warn!("Failed to analyze ONNX model: {}", e);
Ok((
0,
0,
vec![],
vec![],
"unknown".to_string(),
"cpu".to_string(),
))
}
}
}
async fn analyze_tensorflow_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
info!("Analyzing TensorFlow model: {}", input_path.display());
if input_path.is_dir() {
let mut total_size = 0u64;
let mut entries = tokio::fs::read_dir(input_path).await?;
while let Some(entry) = entries.next_entry().await? {
if let Ok(metadata) = entry.metadata().await {
total_size += metadata.len();
}
}
let estimated_params = total_size / 8;
Ok((
estimated_params,
estimate_layers_from_size(total_size as usize),
vec![224, 224, 3], vec![1000],
"f32".to_string(),
"cpu".to_string(),
))
} else {
match tokio::fs::metadata(input_path).await {
Ok(metadata) => {
let file_size = metadata.len() as usize;
Ok((
(file_size / 8) as u64,
estimate_layers_from_size(file_size),
vec![224, 224, 3],
vec![1000],
"f32".to_string(),
"cpu".to_string(),
))
}
Err(e) => {
warn!("Failed to analyze TensorFlow model: {}", e);
Ok((
0,
0,
vec![],
vec![],
"unknown".to_string(),
"cpu".to_string(),
))
}
}
}
}
async fn analyze_tflite_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
info!("Analyzing TensorFlow Lite model: {}", input_path.display());
match tokio::fs::read(input_path).await {
Ok(model_data) => {
let file_size = model_data.len();
let estimated_params = (file_size / 3) as u64;
info!("TFLite model size: {} KB", file_size / 1024);
Ok((
estimated_params,
estimate_layers_from_size(file_size),
vec![1, 224, 224, 3], vec![1, 1000],
"int8".to_string(), "cpu".to_string(),
))
}
Err(e) => {
warn!("Failed to analyze TFLite model: {}", e);
Ok((
0,
0,
vec![],
vec![],
"unknown".to_string(),
"cpu".to_string(),
))
}
}
}
async fn analyze_generic_model(
input_path: &PathBuf,
) -> Result<(u64, usize, Vec<usize>, Vec<usize>, String, String)> {
match tokio::fs::metadata(input_path).await {
Ok(metadata) => {
let file_size = metadata.len() as usize;
let estimated_params = (file_size / 10) as u64;
Ok((
estimated_params,
estimate_layers_from_size(file_size),
vec![1],
vec![1],
"unknown".to_string(),
"cpu".to_string(),
))
}
Err(e) => {
warn!("Failed to analyze generic model: {}", e);
Ok((
0,
0,
vec![],
vec![],
"unknown".to_string(),
"cpu".to_string(),
))
}
}
}
fn estimate_layers_from_size(file_size: usize) -> usize {
let size_mb = file_size as f64 / (1024.0 * 1024.0);
match size_mb {
s if s < 1.0 => 5, s if s < 10.0 => 25, s if s < 50.0 => 50, s if s < 200.0 => 100, s if s < 500.0 => 200, _ => 300, }
}
fn estimate_memory_usage(file_size: usize) -> usize {
(file_size as f64 * 2.5) as usize
}