use minidx_core::optimizers::{TrainInfo, TrainParams};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
use std::time::Instant;
#[derive(Clone, Debug)]
pub enum Every {
Steps(usize),
Seconds(u64),
}
impl Every {
fn fires(&self, steps: usize, now: Instant, last_steps: usize, last_time: Instant) -> bool {
match self {
Every::Steps(s) => steps >= last_steps + s,
Every::Seconds(s) => now.duration_since(last_time).as_secs() > *s,
}
}
}
#[derive(Clone, Debug)]
pub struct RecorderBuilder {
to_path: Option<String>,
batch: Option<Every>,
info: Option<Every>,
snapshot: Option<Every>,
}
impl Default for RecorderBuilder {
fn default() -> Self {
Self {
to_path: None,
batch: Some(Every::Steps(250)),
info: Some(Every::Steps(2000)),
snapshot: Some(Every::Seconds(2 * 60)),
}
}
}
impl RecorderBuilder {
pub fn build(self) -> std::io::Result<Recorder> {
let now = Instant::now();
Ok(Recorder {
file: match self.to_path.map(|p| File::create(p)) {
Some(r) => Some(r?),
None => None,
},
sent_params: false,
batch: self.batch.map(|e| (e, 0, now)),
info: self.info.map(|e| (e, 0, now)),
snapshot: self.snapshot.map(|e| (e, 0, now)),
})
}
pub fn save_to<S: Into<String>>(mut self, p: S) -> Self {
self.to_path = Some(p.into());
self
}
pub fn training_params_freq(mut self, e: Every) -> Self {
self.info = Some(e);
self
}
pub fn batch_freq(mut self, e: Every) -> Self {
self.batch = Some(e);
self
}
pub fn snapshot_freq(mut self, e: Every) -> Self {
self.snapshot = Some(e);
self
}
}
#[derive(Debug)]
pub struct Recorder {
file: Option<File>,
sent_params: bool,
batch: Option<(Every, usize, Instant)>,
info: Option<(Every, usize, Instant)>,
snapshot: Option<(Every, usize, Instant)>,
}
impl Recorder {
pub fn new() -> RecorderBuilder {
RecorderBuilder::default()
}
pub fn flush(self) -> std::io::Result<()> {
if let Some(file) = self.file {
file.sync_all()
} else {
Ok(())
}
}
fn write(&mut self, record: Record) -> std::io::Result<()> {
if let Some(f) = &self.file {
serde_json::to_writer(f, &record)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e)))
} else {
Ok(())
}
}
pub fn record_batch<N: minidx_core::LoadableModule>(
&mut self,
info: BatchInfo,
params: &TrainParams,
nn: &N,
) -> std::io::Result<()> {
let now = Instant::now();
if !self.sent_params {
self.write(Record::Params(params.clone()))?;
self.sent_params = true;
}
let write_info = self
.info
.as_mut()
.map(|(e, last_steps, last_time)| {
if e.fires(info.step, now, *last_steps, *last_time) {
*last_steps = info.step;
*last_time = now;
true
} else {
false
}
})
.unwrap_or(false);
let write_batch = self
.batch
.as_mut()
.map(|(e, last_steps, last_time)| {
if e.fires(info.step, now, *last_steps, *last_time) {
*last_steps = info.step;
*last_time = now;
true
} else {
false
}
})
.unwrap_or(false);
let write_snapshot = self
.snapshot
.as_mut()
.map(|(e, last_steps, last_time)| {
if e.fires(info.step, now, *last_steps, *last_time) {
*last_steps = info.step;
*last_time = now;
true
} else {
false
}
})
.unwrap_or(false);
if write_info {
self.write(Record::TrainInfo(params.into()))?;
}
if write_batch {
self.write(Record::Batch(info.clone()))?;
}
if write_snapshot {
let mut params = HashMap::new();
nn.save("".into(), &mut params)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e)))?;
self.write(Record::Snapshot {
step: info.step,
params: params,
})?;
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BatchInfo {
pub step: usize,
pub size: usize,
pub loss: f64,
pub time_us: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Record {
Batch(BatchInfo),
Params(TrainParams),
TrainInfo(TrainInfo),
Snapshot {
step: usize,
params: HashMap<String, Vec<f64>>,
},
}