use serde::{Deserialize, Serialize};
pub const METRICS_PLUGIN: &str = include_str!("overbrainer_metrics.py");
pub const PLUGIN_FILE: &str = "overbrainer_metrics.py";
pub const PLUGIN_CLASS: &str = "overbrainer_metrics.OverbrainerMetricsPlugin";
pub const METRICS_ENV: &str = "OVERBRAINER_METRICS";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TrainMetric {
pub time: f64,
pub step: u64,
#[serde(default)]
pub epoch: Option<f64>,
#[serde(default)]
pub max_steps: Option<u64>,
#[serde(default)]
pub loss: Option<f64>,
#[serde(default)]
pub eval_loss: Option<f64>,
#[serde(default)]
pub learning_rate: Option<f64>,
#[serde(default)]
pub grad_norm: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "event", rename_all = "lowercase")]
pub enum MetricLine {
Begin {
time: f64,
#[serde(default)]
max_steps: Option<u64>,
},
Log(TrainMetric),
}
pub fn parse_line(line: &str) -> Result<MetricLine, serde_json::Error> {
serde_json::from_str(line)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_lines_parse_with_missing_values() -> Result<(), serde_json::Error> {
let line = r#"{"event": "log", "time": 1.5, "step": 3, "epoch": 0.25, "max_steps": 12, "loss": 1.2, "grad_norm": null, "extra": 1}"#;
assert_eq!(
parse_line(line)?,
MetricLine::Log(TrainMetric {
time: 1.5,
step: 3,
epoch: Some(0.25),
max_steps: Some(12),
loss: Some(1.2),
eval_loss: None,
learning_rate: None,
grad_norm: None,
})
);
Ok(())
}
#[test]
fn begin_lines_parse() -> Result<(), serde_json::Error> {
assert_eq!(
parse_line(r#"{"event": "begin", "time": 1.0, "max_steps": 40}"#)?,
MetricLine::Begin {
time: 1.0,
max_steps: Some(40)
}
);
Ok(())
}
#[test]
fn other_lines_are_errors() {
assert!(parse_line(r#"{"event": "other"}"#).is_err());
assert!(parse_line("not json").is_err());
}
}