use anyhow::Result;
use serde_json::Value;
use std::fs::File;
use std::io::Read;
use std::path::Path;
pub fn parse_pytorch_model(file_path: &Path) -> Result<Value> {
let file = File::open(file_path)?;
let mut reader = std::io::BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)?;
let mut result = serde_json::Map::new();
result.insert(
"model_type".to_string(),
Value::String("pytorch".to_string()),
);
result.insert("file_size".to_string(), Value::Number(buffer.len().into()));
result.insert("format".to_string(), Value::String("pickle".to_string()));
let model_info = extract_pytorch_model_info(&buffer);
for (key, value) in model_info {
result.insert(key, value);
}
Ok(Value::Object(result))
}
fn extract_pytorch_model_info(buffer: &[u8]) -> serde_json::Map<String, Value> {
let mut info = serde_json::Map::new();
let searchable_content = String::from_utf8_lossy(buffer);
let weight_count = searchable_content.matches("weight").count();
let bias_count = searchable_content.matches("bias").count();
let conv_count = searchable_content.matches("conv").count();
let linear_count =
searchable_content.matches("linear").count() + searchable_content.matches("fc.").count();
let bn_count =
searchable_content.matches("bn").count() + searchable_content.matches("batch_norm").count();
let mut detected_layers = Vec::new();
if conv_count > 0 {
detected_layers.push(format!("convolution: {conv_count}"));
}
if linear_count > 0 {
detected_layers.push(format!("linear: {linear_count}"));
}
if bn_count > 0 {
detected_layers.push(format!("batch_norm: {bn_count}"));
}
if weight_count > 0 {
detected_layers.push(format!("weight_params: {weight_count}"));
}
if bias_count > 0 {
detected_layers.push(format!("bias_params: {bias_count}"));
}
if !detected_layers.is_empty() {
info.insert(
"detected_components".to_string(),
Value::String(detected_layers.join(", ")),
);
}
let layer_count = weight_count.max(bias_count / 2); if layer_count > 0 {
info.insert(
"estimated_layers".to_string(),
Value::Number(layer_count.into()),
);
}
let architectures = [
("resnet", "ResNet"),
("vgg", "VGG"),
("densenet", "DenseNet"),
("mobilenet", "MobileNet"),
("efficientnet", "EfficientNet"),
("transformer", "Transformer"),
("bert", "BERT"),
("gpt", "GPT"),
];
for (pattern, arch_name) in &architectures {
if searchable_content.to_lowercase().contains(pattern) {
info.insert(
"detected_architecture".to_string(),
Value::String(arch_name.to_string()),
);
break;
}
}
if searchable_content.contains("optimizer") {
info.insert("has_optimizer_state".to_string(), Value::Bool(true));
}
if searchable_content.contains("epoch") {
info.insert("has_training_metadata".to_string(), Value::Bool(true));
}
if searchable_content.contains("lr") || searchable_content.contains("learning_rate") {
info.insert("has_learning_rate".to_string(), Value::Bool(true));
}
info.insert(
"binary_size".to_string(),
Value::Number(buffer.len().into()),
);
if buffer.len() > 2 {
let protocol_byte = buffer[1];
if protocol_byte <= 5 {
info.insert(
"pickle_protocol".to_string(),
Value::Number(protocol_byte.into()),
);
}
}
let structure_hash = calculate_simple_hash(&searchable_content);
info.insert(
"structure_fingerprint".to_string(),
Value::String(format!("{structure_hash:x}")),
);
info
}
fn calculate_simple_hash(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
let structure_parts: Vec<&str> = content
.matches(|c: char| c.is_alphanumeric() || c == '.')
.take(1000) .collect();
structure_parts.hash(&mut hasher);
hasher.finish()
}