Hard refactor — no shims, no compat layers. Candle removed from Cargo.toml and all source files in 6 crates: - ml-core: MlDevice enum, checkpoint.rs (safetensors direct), cudarc imports fixed from candle re-export to direct, AdamWConfig lr_decay, cuda_compat gutted. Net -7,341 lines. - ml-ppo: All 16 files rewritten. LSTM→CudaLSTM, VarMap→GpuVarStore, PPOAgent 2306→700 lines, checkpoint→binary format. - ml-ensemble: GPU-resident sigmoid via custom CUDA kernel. - ml-explainability: Integrated gradients via GPU finite-difference kernels. - ml-labeling: Device→MlDevice. - ml-hyperopt: Cargo.toml only. Remaining: ml-dqn (24 files), ml-supervised (4 files), ml crate (104 files). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
211 lines
6.8 KiB
Rust
211 lines
6.8 KiB
Rust
//! 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<f64>,
|
|
/// Accuracy metric (model-specific)
|
|
pub accuracy: Option<f64>,
|
|
/// Learning rate used in this step
|
|
pub learning_rate: f64,
|
|
/// Gradient norm (for monitoring gradient explosion)
|
|
pub grad_norm: Option<f64>,
|
|
/// Custom model-specific metrics
|
|
pub custom_metrics: HashMap<String, f64>,
|
|
}
|
|
|
|
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<f64, MLError>;
|
|
|
|
/// Backward pass to compute gradients
|
|
fn backward(&mut self, loss_value: f64) -> Result<f64, MLError>;
|
|
|
|
/// 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<String, MLError>;
|
|
|
|
/// Load model checkpoint from standardized format
|
|
fn load_checkpoint(&mut self, checkpoint_path: &str) -> Result<CheckpointMetadata, MLError>;
|
|
}
|
|
|
|
/// 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<CheckpointMetadata, MLError> {
|
|
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(())
|
|
}
|
|
}
|