use candle_core::DType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CalibrationMethod {
MinMax,
Percentile(f32),
Entropy,
KLDivergence,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModalitySpec {
pub input_tensors: Vec<String>,
pub output_tensors: Vec<String>,
pub input_shape: Vec<Option<i64>>, pub output_shape: Vec<Option<i64>>,
pub dtype: String,
pub preprocessing: Option<String>,
pub postprocessing: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShardingInfo {
pub total_shards: u32,
pub shard_index: Option<u32>,
pub strategy: String,
pub shard_boundaries: HashMap<String, Vec<usize>>,
pub topology: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevicePlacement {
pub preferred_device: String,
pub layer_placement: HashMap<String, String>,
pub memory_strategy: String,
pub gradient_checkpointing: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryRequirement {
pub min_memory_mb: u64,
pub recommended_memory_mb: u64,
pub peak_memory_mb: Option<u64>,
pub breakdown: HashMap<String, u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceBenchmark {
pub timestamp: DateTime<Utc>,
pub device: String,
pub hardware_info: HashMap<String, String>,
pub throughput: Option<f64>,
pub latency_ms: HashMap<String, f64>,
pub memory_usage_mb: Option<u64>,
pub batch_size: u32,
pub sequence_length: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelMetadata {
pub format_version: String,
pub mlmf_version: String,
pub created_at: DateTime<Utc>,
pub modified_at: DateTime<Utc>,
pub size_bytes: u64,
pub parameter_count: u64,
pub is_quantized: bool,
pub last_modified: DateTime<Utc>,
pub architecture: Option<String>,
pub model_family: Option<String>,
pub version: Option<String>,
pub weights_hash: Option<String>,
pub config_hash: Option<String>,
pub modalities: Vec<String>,
pub input_specs: HashMap<String, ModalitySpec>,
pub output_specs: HashMap<String, ModalitySpec>,
pub sharding_info: Option<ShardingInfo>,
pub device_placement: Option<DevicePlacement>,
pub memory_requirements: HashMap<String, MemoryRequirement>,
pub benchmarks: HashMap<String, PerformanceBenchmark>,
pub custom: HashMap<String, serde_json::Value>,
}
impl ModelMetadata {
pub fn new() -> Self {
let now = Utc::now();
Self {
format_version: "1.0".to_string(),
mlmf_version: env!("CARGO_PKG_VERSION").to_string(),
created_at: now,
modified_at: now,
size_bytes: 0,
parameter_count: 0,
is_quantized: false,
last_modified: now,
architecture: None,
model_family: None,
version: None,
weights_hash: None,
config_hash: None,
modalities: Vec::new(),
input_specs: HashMap::new(),
output_specs: HashMap::new(),
sharding_info: None,
device_placement: None,
memory_requirements: HashMap::new(),
benchmarks: HashMap::new(),
custom: HashMap::new(),
}
}
pub fn add_custom(&mut self, key: String, value: serde_json::Value) {
self.custom.insert(key, value);
let now = Utc::now();
self.modified_at = now;
self.last_modified = now;
}
pub fn touch(&mut self) {
let now = Utc::now();
self.modified_at = now;
self.last_modified = now;
}
pub fn set_architecture(&mut self, architecture: &str, model_family: Option<&str>) {
self.architecture = Some(architecture.to_string());
if let Some(family) = model_family {
self.model_family = Some(family.to_string());
}
self.touch();
}
pub fn add_modality(
&mut self,
modality: &str,
input_spec: ModalitySpec,
output_spec: ModalitySpec,
) {
if !self.modalities.contains(&modality.to_string()) {
self.modalities.push(modality.to_string());
}
self.input_specs.insert(modality.to_string(), input_spec);
self.output_specs.insert(modality.to_string(), output_spec);
self.touch();
}
pub fn set_memory_requirements(&mut self, device: &str, requirements: MemoryRequirement) {
self.memory_requirements
.insert(device.to_string(), requirements);
self.touch();
}
pub fn add_benchmark(&mut self, benchmark_name: &str, benchmark: PerformanceBenchmark) {
self.benchmarks
.insert(benchmark_name.to_string(), benchmark);
self.touch();
}
pub fn update_hashes(&mut self, weights_hash: String, config_hash: Option<String>) {
self.weights_hash = Some(weights_hash);
if let Some(config) = config_hash {
self.config_hash = Some(config);
}
self.touch();
}
}
impl Default for ModelMetadata {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorInfo {
pub original_name: String,
pub mapped_name: String,
pub dtype: String, pub shape: Vec<usize>,
pub size_bytes: u64,
pub quantization: Option<TensorQuantizationInfo>,
pub statistics: Option<TensorStatistics>,
pub layer_type: Option<String>,
pub parameter_type: Option<String>,
}
impl TensorInfo {
pub fn new(name: &str, dtype: DType, shape: Vec<usize>, original_name: Option<&str>) -> Self {
let size_bytes = shape.iter().product::<usize>() as u64 * dtype_size_bytes(dtype);
Self {
original_name: original_name.unwrap_or(name).to_string(),
mapped_name: name.to_string(),
dtype: dtype_to_string(dtype),
shape,
size_bytes,
quantization: None,
statistics: None,
layer_type: None,
parameter_type: None,
}
}
pub fn get_dtype(&self) -> DType {
string_to_dtype(&self.dtype)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorQuantizationInfo {
pub bit_depth: u8,
pub method: CalibrationMethod,
pub scale: f32,
pub zero_point: f32,
pub block_size: Option<usize>,
pub min_val: f32,
pub max_val: f32,
pub activation_stats: TensorStatistics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantizationErrorMetrics {
pub mae: f32,
pub mse: f32,
pub psnr: f32,
pub snr: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorStatistics {
pub min: f32,
pub max: f32,
pub mean: f32,
pub std: f32,
pub median: f32,
pub percentile_1: f32,
pub percentile_99: f32,
pub zero_ratio: f32,
pub outlier_ratio: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelQuantizationInfo {
pub bit_depth: u8,
pub method: CalibrationMethod,
pub block_size: Option<usize>,
pub layer_overrides: HashMap<String, (u8, CalibrationMethod)>,
pub tensor_info: HashMap<String, TensorQuantizationInfo>,
pub calibration_info: Option<CalibrationInfo>,
pub quantized_at: Option<DateTime<Utc>>,
pub error_metrics: Option<QuantizationErrorMetrics>,
}
impl ModelQuantizationInfo {
pub fn new(bit_depth: u8, method: CalibrationMethod, block_size: Option<usize>) -> Self {
Self {
bit_depth,
method,
block_size,
layer_overrides: HashMap::new(),
tensor_info: HashMap::new(),
calibration_info: None,
quantized_at: None,
error_metrics: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalibrationInfo {
pub sample_count: usize,
pub dataset_description: Option<String>,
pub distribution_stats: Option<HashMap<String, f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelProvenance {
pub training_data: Option<DatasetInfo>,
pub validation_data: Option<DatasetInfo>,
pub test_data: Option<DatasetInfo>,
pub training_config: Option<TrainingConfig>,
pub training_metrics: Option<TrainingMetrics>,
pub lineage: Vec<ModelLineage>,
pub citations: Vec<Citation>,
pub creation_method: Option<String>,
pub base_model: Option<BaseModelInfo>,
pub training_environment: Option<TrainingEnvironment>,
pub reproducibility: Option<ReproducibilityInfo>,
pub compliance: Option<ComplianceInfo>,
pub modification_history: Vec<ModificationRecord>,
pub quality_checkpoints: Vec<QualityCheckpoint>,
}
impl ModelProvenance {
pub fn new() -> Self {
Self {
training_data: None,
validation_data: None,
test_data: None,
training_config: None,
training_metrics: None,
lineage: Vec::new(),
citations: Vec::new(),
creation_method: None,
base_model: None,
training_environment: None,
reproducibility: None,
compliance: None,
modification_history: Vec::new(),
quality_checkpoints: Vec::new(),
}
}
pub fn set_creation_method(&mut self, method: &str, base_model: Option<BaseModelInfo>) {
self.creation_method = Some(method.to_string());
self.base_model = base_model;
}
pub fn add_lineage(
&mut self,
parent_model: &str,
relationship: &str,
description: Option<&str>,
) {
self.lineage.push(ModelLineage {
parent_model: parent_model.to_string(),
relationship: relationship.to_string(),
created_at: Utc::now(),
description: description.map(|s| s.to_string()),
});
}
pub fn add_modification(
&mut self,
modification_type: &str,
description: &str,
author: &str,
version_before: &str,
version_after: &str,
parameters_changed: Vec<String>,
validation_results: HashMap<String, f32>,
) {
self.modification_history.push(ModificationRecord {
timestamp: Utc::now(),
modification_type: modification_type.to_string(),
description: description.to_string(),
author: author.to_string(),
version_before: version_before.to_string(),
version_after: version_after.to_string(),
parameters_changed,
validation_results,
});
}
pub fn add_quality_checkpoint(
&mut self,
step: u64,
epoch: f32,
metrics: HashMap<String, f32>,
validation_metrics: HashMap<String, f32>,
model_hash: &str,
notes: Option<&str>,
) {
self.quality_checkpoints.push(QualityCheckpoint {
step,
epoch,
metrics,
validation_metrics,
model_hash: model_hash.to_string(),
timestamp: Utc::now(),
notes: notes.map(|s| s.to_string()),
});
}
pub fn add_citation(&mut self, citation: Citation) {
self.citations.push(citation);
}
}
impl Default for ModelProvenance {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatasetInfo {
pub name: String,
pub version: Option<String>,
pub source: Option<String>,
pub sample_count: Option<u64>,
pub description: Option<String>,
pub license: Option<String>,
pub statistics: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingConfig {
pub optimizer: String,
pub learning_rate: f64,
pub batch_size: u32,
pub epochs: u32,
pub loss_function: String,
pub hardware: Option<String>,
pub training_duration: Option<String>,
pub hyperparameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingMetrics {
pub final_train_loss: f64,
pub final_val_loss: Option<f64>,
pub train_accuracy: Option<f64>,
pub val_accuracy: Option<f64>,
pub loss_history: Vec<(u32, f64)>,
pub accuracy_history: Vec<(u32, f64)>,
pub custom_metrics: HashMap<String, Vec<(u32, f64)>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelLineage {
pub parent_model: String,
pub relationship: String,
pub created_at: DateTime<Utc>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Citation {
pub citation_type: String,
pub title: String,
pub authors: Vec<String>,
pub venue: Option<String>,
pub year: Option<u32>,
pub url: Option<String>,
pub doi: Option<String>,
}
fn dtype_to_string(dtype: DType) -> String {
match dtype {
DType::F32 => "f32".to_string(),
DType::F16 => "f16".to_string(),
DType::BF16 => "bf16".to_string(),
DType::F64 => "f64".to_string(),
DType::U8 => "u8".to_string(),
DType::U32 => "u32".to_string(),
DType::I64 => "i64".to_string(),
_ => format!("{:?}", dtype),
}
}
fn string_to_dtype(s: &str) -> DType {
match s {
"f32" => DType::F32,
"f16" => DType::F16,
"bf16" => DType::BF16,
"f64" => DType::F64,
"u8" => DType::U8,
"u32" => DType::U32,
"i64" => DType::I64,
_ => DType::F32, }
}
fn dtype_size_bytes(dtype: DType) -> u64 {
match dtype {
DType::F32 => 4,
DType::F16 => 2,
DType::BF16 => 2,
DType::F64 => 8,
DType::U8 => 1,
DType::U32 => 4,
DType::I64 => 8,
_ => 4, }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseModelInfo {
pub name: String,
pub version: String,
pub repository: String,
pub license: String,
pub architecture: String,
pub parameters: u64,
pub modifications: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingEnvironment {
pub framework: String,
pub framework_version: String,
pub hardware: Vec<String>,
pub cuda_version: Option<String>,
pub python_version: String,
pub os: String,
pub total_gpus: Option<u32>,
pub total_memory_gb: Option<f32>,
pub distributed_setup: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityInfo {
pub random_seed: Option<u64>,
pub environment_hash: String,
pub data_hash: String,
pub code_version: String,
pub config_hash: String,
pub deterministic: bool,
pub reproduction_command: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceInfo {
pub data_sources: Vec<String>,
pub license_requirements: Vec<String>,
pub ethical_approvals: Vec<String>,
pub gdpr_compliant: bool,
pub data_retention_policy: Option<String>,
pub usage_restrictions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModificationRecord {
pub timestamp: DateTime<Utc>,
pub modification_type: String,
pub description: String,
pub author: String,
pub version_before: String,
pub version_after: String,
pub parameters_changed: Vec<String>,
pub validation_results: HashMap<String, f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityCheckpoint {
pub step: u64,
pub epoch: f32,
pub metrics: HashMap<String, f32>,
pub validation_metrics: HashMap<String, f32>,
pub model_hash: String,
pub timestamp: DateTime<Utc>,
pub notes: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_metadata_creation() {
let metadata = ModelMetadata::new();
assert_eq!(metadata.format_version, "1.0");
assert!(!metadata.custom.is_empty() || metadata.custom.is_empty()); }
#[test]
fn test_tensor_info_creation() {
let info = TensorInfo::new(
"mapped.weight",
DType::F32,
vec![1024, 768],
Some("original.weight"),
);
assert_eq!(info.original_name, "original.weight");
assert_eq!(info.dtype, "f32");
assert_eq!(info.shape, vec![1024, 768]);
assert_eq!(info.size_bytes, 1024 * 768 * 4); }
#[test]
fn test_dtype_conversion() {
assert_eq!(dtype_to_string(DType::F32), "f32");
assert_eq!(string_to_dtype("f32"), DType::F32);
assert_eq!(dtype_size_bytes(DType::F32), 4);
}
}