use std::path::Path;
use nuvec_core::tree::{LeafKind, Leaves, LoadMode, Tree, Visit, Visitor};
use nuvec_core::{Array, DType, Error, Result};
use crate::{load_safetensors, save_safetensors};
pub fn save<T: Leaves + ?Sized>(path: impl AsRef<Path>, tree: &T) -> Result<()> {
struct Named(Vec<(String, Array)>);
impl Visitor for Named {
fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
self.0.push((path.to_owned(), a.clone()));
}
}
let mut named = Named(Vec::new());
tree.visit(&mut Visit::new(&mut named));
let named = named.0;
let mut seen = std::collections::HashSet::new();
for (p, _) in &named {
if !seen.insert(p.as_str()) {
return Err(Error::InvalidArgument(format!(
"save: the tree has the path {p:?} twice"
)));
}
}
let mut metadata = vec![
("format".to_owned(), "nuvec-tree".to_owned()),
("root".to_owned(), std::any::type_name::<T>().to_owned()),
];
for key in ["step", "learning_rate"] {
let found = named
.iter()
.find(|(p, a)| a.ndim() == 0 && (p == key || p.ends_with(&format!(".{key}"))));
if let Some((_, a)) = found {
let value = match a.dtype() {
DType::F16 | DType::BF16 | DType::F32 | DType::F64 => {
a.astype(DType::F64).item::<f64>()?.to_string()
}
_ => a.astype(DType::I64).item::<i64>()?.to_string(),
};
metadata.push((key.to_owned(), value));
}
}
let tensors: Vec<(&str, &Array)> = named.iter().map(|(p, a)| (p.as_str(), a)).collect();
let metadata: Vec<(&str, &str)> = metadata
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
save_safetensors(path, &tensors, &metadata)
}
pub fn load_into<T: Tree>(path: impl AsRef<Path>, tree: &mut T) -> Result<()> {
let file = load_safetensors(path)?;
tree.load_flat(file.tensors, LoadMode::Strict)
.map_err(|e| match e {
Error::InvalidArgument(m) => {
Error::InvalidArgument(m.replacen("load_flat", "load_into", 1))
}
e => e,
})
}