//! Unified Training Trait for All ML Models //! //! Provides a common interface for training all models (MAMBA-2, DQN, PPO, TFT, etc.). //! Standardizes batch processing, gradient computation, optimizer steps, checkpointing, //! and metrics collection across all model types. //! //! Uses `GpuTensor` and `MlDevice` instead of candle types. use crate::MLError; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Training metrics collected during training #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrainingMetrics { /// Training loss pub loss: f64, /// Validation loss pub val_loss: Option, /// Accuracy metric (model-specific) pub accuracy: Option, /// Learning rate used in this step pub learning_rate: f64, /// Gradient norm (for monitoring gradient explosion) pub grad_norm: Option, /// Custom model-specific metrics pub custom_metrics: HashMap, } impl Default for TrainingMetrics { fn default() -> Self { Self { loss: 0.0, val_loss: None, accuracy: None, learning_rate: 1e-4, grad_norm: None, custom_metrics: HashMap::new(), } } } /// Checkpoint metadata for standardized format #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CheckpointMetadata { /// Model type (MAMBA-2, DQN, PPO, TFT) pub model_type: String, /// Model version pub version: String, /// Epoch number pub epoch: usize, /// Training step pub step: usize, /// Timestamp when checkpoint was created pub timestamp: std::time::SystemTime, /// Training configuration (serialized as JSON) pub config: serde_json::Value, /// Performance metrics at checkpoint time pub metrics: TrainingMetrics, } /// Unified training interface for all ML models /// /// Implementations must provide: /// - Forward pass (inference) /// - Backward pass (gradient computation) /// - Optimizer step (parameter updates) /// - Checkpoint save/load (safetensors + JSON metadata) /// - Metrics collection (loss, accuracy, custom metrics) /// /// This trait uses opaque types (f64 for loss, &str for device info) so that /// it does not depend on any specific tensor library. pub trait UnifiedTrainable { /// Get model type identifier (MAMBA-2, DQN, PPO, TFT) fn model_type(&self) -> &str; /// Get device description (e.g., "cpu", "cuda:0") fn device_name(&self) -> String; /// Forward pass through model (returns loss as f64) fn forward_loss(&mut self, input: &[f32], target: &[f32]) -> Result; /// Backward pass to compute gradients fn backward(&mut self, loss_value: f64) -> Result; /// Update model parameters using optimizer fn optimizer_step(&mut self) -> Result<(), MLError>; /// Zero gradients before next backward pass fn zero_grad(&mut self) -> Result<(), MLError>; /// Get current learning rate fn get_learning_rate(&self) -> f64; /// Set learning rate (for scheduling) fn set_learning_rate(&mut self, lr: f64) -> Result<(), MLError>; /// Get current training step count fn get_step(&self) -> usize; /// Collect current training metrics fn collect_metrics(&self) -> TrainingMetrics; /// Save model checkpoint in standardized format /// /// Format: safetensors for weights, JSON for metadata fn save_checkpoint(&self, checkpoint_path: &str) -> Result; /// Load model checkpoint from standardized format fn load_checkpoint(&mut self, checkpoint_path: &str) -> Result; } /// Helper functions for standardized checkpoint management pub mod checkpoint { use super::*; use std::path::Path; /// Generate checkpoint filename with epoch and step pub fn checkpoint_filename(model_type: &str, epoch: usize, step: usize) -> String { format!("{}_epoch{}_step{}", model_type, epoch, step) } /// Save checkpoint metadata to JSON pub fn save_metadata( metadata: &CheckpointMetadata, checkpoint_path: &str, ) -> Result<(), MLError> { let metadata_path = format!("{}.json", checkpoint_path); let json = serde_json::to_string_pretty(metadata).map_err(|e| { MLError::ModelError(format!("Failed to serialize checkpoint metadata: {}", e)) })?; std::fs::write(&metadata_path, json).map_err(|e| { MLError::ModelError(format!("Failed to write checkpoint metadata: {}", e)) })?; Ok(()) } /// Load checkpoint metadata from JSON pub fn load_metadata(checkpoint_path: &str) -> Result { let metadata_path = format!("{}.json", checkpoint_path); if !Path::new(&metadata_path).exists() { return Err(MLError::ModelError(format!( "Checkpoint metadata not found: {}", metadata_path ))); } let json = std::fs::read_to_string(&metadata_path).map_err(|e| { MLError::ModelError(format!("Failed to read checkpoint metadata: {}", e)) })?; let metadata: CheckpointMetadata = serde_json::from_str(&json).map_err(|e| { MLError::ModelError(format!("Failed to deserialize checkpoint metadata: {}", e)) })?; Ok(metadata) } /// Check if checkpoint exists pub fn checkpoint_exists(checkpoint_path: &str) -> bool { let safetensors_path = format!("{}.safetensors", checkpoint_path); let metadata_path = format!("{}.json", checkpoint_path); Path::new(&safetensors_path).exists() && Path::new(&metadata_path).exists() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_training_metrics_default() { let metrics = TrainingMetrics::default(); assert_eq!(metrics.loss, 0.0); assert_eq!(metrics.val_loss, None); assert_eq!(metrics.learning_rate, 1e-4); } #[test] fn test_checkpoint_filename() { let filename = checkpoint::checkpoint_filename("MAMBA-2", 10, 1000); assert_eq!(filename, "MAMBA-2_epoch10_step1000"); } #[test] fn test_checkpoint_metadata_serialization() -> anyhow::Result<()> { let metadata = CheckpointMetadata { model_type: "MAMBA-2".to_owned(), version: "2.0.0".to_owned(), epoch: 10, step: 1000, timestamp: std::time::SystemTime::now(), config: serde_json::json!({"d_model": 256}), metrics: TrainingMetrics::default(), }; let json = serde_json::to_string_pretty(&metadata)?; assert!(json.contains("MAMBA-2")); let deserialized: CheckpointMetadata = serde_json::from_str(&json)?; assert_eq!(deserialized.model_type, "MAMBA-2"); assert_eq!(deserialized.epoch, 10); Ok(()) } }