pub type ProgressFn = Box<dyn Fn(ProgressEvent) + Send + Sync>;
#[derive(Debug, Clone)]
pub enum ProgressEvent {
LoadingConfig {
path: String,
},
ScanningFiles {
count: usize,
},
DetectingArchitecture,
LoadingTensors {
current: usize,
total: usize,
file_name: Option<String>,
},
MappingNames {
count: usize,
},
BuildingModel,
ValidatingModel,
LoadingFile {
file: std::path::PathBuf,
format: String,
},
LoadingTensorsFromFiles {
count: usize,
format: String,
},
SavingFile {
file: std::path::PathBuf,
format: String,
},
SavingTensors {
count: usize,
format: String,
},
Complete {
tensor_count: usize,
format: String,
},
Status {
message: String,
},
SavingCheckpoint,
CheckpointSaved,
ParsingMetadata,
PrefetchingTensors {
count: usize,
},
LoadingCheckpoint {
path: String,
},
CheckpointLoaded,
}
impl ProgressEvent {
pub fn description(&self) -> String {
match self {
ProgressEvent::LoadingConfig { path } => {
format!("Loading config from {}", path)
}
ProgressEvent::ScanningFiles { count } => {
if *count == 0 {
"Scanning for model files...".to_string()
} else {
format!("Found {} model file(s)", count)
}
}
ProgressEvent::DetectingArchitecture => "Detecting model architecture...".to_string(),
ProgressEvent::LoadingTensors {
current,
total,
file_name,
} => {
if let Some(name) = file_name {
format!("Loading tensors [{}/{}]: {}", current, total, name)
} else {
format!("Loading tensors [{}/{}]", current, total)
}
}
ProgressEvent::MappingNames { count } => {
format!("Mapped {} tensor names", count)
}
ProgressEvent::BuildingModel => "Building model from tensors...".to_string(),
ProgressEvent::ValidatingModel => "Validating model configuration...".to_string(),
ProgressEvent::LoadingFile { file, format } => {
format!("Loading {} file: {}", format, file.display())
}
ProgressEvent::LoadingTensorsFromFiles { count, format } => {
format!("Loading {} tensors from {} format", count, format)
}
ProgressEvent::SavingFile { file, format } => {
format!("Saving {} file: {}", format, file.display())
}
ProgressEvent::SavingTensors { count, format } => {
format!("Saving {} tensors to {} format", count, format)
}
ProgressEvent::Complete {
tensor_count,
format,
} => {
format!(
"✓ {} format: {} tensors processed successfully",
format, tensor_count
)
}
ProgressEvent::Status { message } => message.clone(),
ProgressEvent::SavingCheckpoint => "Saving checkpoint...".to_string(),
ProgressEvent::CheckpointSaved => "Checkpoint saved successfully".to_string(),
ProgressEvent::LoadingCheckpoint { path } => {
format!("Loading checkpoint from {}", path)
}
ProgressEvent::CheckpointLoaded => "Checkpoint loaded successfully".to_string(),
ProgressEvent::ParsingMetadata => {
"Parsing tensor metadata from memory-mapped file...".to_string()
}
ProgressEvent::PrefetchingTensors { count } => {
format!("Prefetching {} tensors into cache...", count)
}
}
}
pub fn is_complete(&self) -> bool {
matches!(self, ProgressEvent::Complete { .. })
}
pub fn is_error(&self) -> bool {
false
}
}
pub fn default_progress() -> ProgressFn {
Box::new(|event: ProgressEvent| {
let description = event.description();
if event.is_complete() {
println!("{}", description);
} else {
println!("📦 {}", description);
}
})
}
pub fn silent_progress() -> ProgressFn {
Box::new(|_event: ProgressEvent| {
})
}
pub fn timestamped_progress() -> ProgressFn {
Box::new(|event: ProgressEvent| {
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S");
let description = event.description();
if event.is_complete() {
println!("[{}] {}", timestamp, description);
} else {
println!("[{}] 📦 {}", timestamp, description);
}
})
}
pub fn prefixed_progress(prefix: String) -> ProgressFn {
Box::new(move |event: ProgressEvent| {
let description = event.description();
if event.is_complete() {
println!("[{}] {}", prefix, description);
} else {
println!("[{}] 📦 {}", prefix, description);
}
})
}
#[cfg(feature = "progress")]
pub fn progress_bar() -> ProgressFn {
use indicatif::{ProgressBar, ProgressStyle};
use std::sync::{Arc, Mutex};
let pb = Arc::new(Mutex::new(None::<ProgressBar>));
Box::new(move |event: ProgressEvent| {
let mut pb_guard = pb.lock().unwrap();
match event {
ProgressEvent::LoadingTensors { current, total, .. } => {
if pb_guard.is_none() {
let new_pb = ProgressBar::new(total as u64);
new_pb.set_style(
ProgressStyle::default_bar()
.template("📦 Loading tensors [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.unwrap()
.progress_chars("█▉▊▋▌▍▎▏ "),
);
*pb_guard = Some(new_pb);
}
if let Some(ref pb) = *pb_guard {
pb.set_position(current as u64);
if let Some(file_name) = event.description().split(": ").nth(1) {
pb.set_message(file_name.to_string());
}
}
}
ProgressEvent::Complete { .. } => {
if let Some(ref pb) = *pb_guard {
pb.finish_with_message("✓ Complete");
}
*pb_guard = None;
println!("{}", event.description());
}
_ => {
println!("📦 {}", event.description());
}
}
})
}
#[cfg(not(feature = "progress"))]
pub fn progress_bar() -> ProgressFn {
default_progress()
}
pub fn custom_progress<F>(f: F) -> ProgressFn
where
F: Fn(ProgressEvent) + Send + Sync + 'static,
{
Box::new(f)
}
pub struct ProgressTimer {
start_time: std::time::Instant,
progress_fn: Option<ProgressFn>,
}
impl ProgressTimer {
pub fn new(progress_fn: Option<ProgressFn>) -> Self {
Self {
start_time: std::time::Instant::now(),
progress_fn,
}
}
pub fn report(&self, event: ProgressEvent) {
if let Some(ref progress_fn) = self.progress_fn {
progress_fn(event);
}
}
pub fn complete(&self) -> f64 {
let _elapsed_secs = self.start_time.elapsed().as_secs_f64();
self.report(ProgressEvent::Complete {
tensor_count: 0, format: "Generic".to_string(),
});
_elapsed_secs
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn test_progress_event_descriptions() {
let event = ProgressEvent::LoadingConfig {
path: "/path/to/config.json".to_string(),
};
assert_eq!(
event.description(),
"Loading config from /path/to/config.json"
);
let event = ProgressEvent::ScanningFiles { count: 3 };
assert_eq!(event.description(), "Found 3 model file(s)");
let event = ProgressEvent::Complete {
tensor_count: 1234,
format: "SafeTensors".to_string(),
};
assert_eq!(
event.description(),
"✓ SafeTensors format: 1234 tensors processed successfully"
);
assert!(event.is_complete());
}
#[test]
fn test_custom_progress() {
let events = Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
let progress_fn = custom_progress(move |event: ProgressEvent| {
events_clone.lock().unwrap().push(event);
});
progress_fn(ProgressEvent::DetectingArchitecture);
progress_fn(ProgressEvent::Complete {
tensor_count: 100,
format: "SafeTensors".to_string(),
});
let captured_events = events.lock().unwrap();
assert_eq!(captured_events.len(), 2);
assert!(matches!(
captured_events[0],
ProgressEvent::DetectingArchitecture
));
assert!(matches!(captured_events[1], ProgressEvent::Complete { .. }));
}
#[test]
fn test_progress_timer() {
let events = Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
let progress_fn = custom_progress(move |event: ProgressEvent| {
events_clone.lock().unwrap().push(event);
});
let timer = ProgressTimer::new(Some(progress_fn));
timer.report(ProgressEvent::DetectingArchitecture);
let elapsed = timer.complete();
assert!(elapsed >= 0.0);
let captured_events = events.lock().unwrap();
assert_eq!(captured_events.len(), 2);
assert!(matches!(captured_events[1], ProgressEvent::Complete { .. }));
}
}