1use std::path::Path;
4
5use nuvec_core::tree::{LeafKind, Leaves, LoadMode, Tree, Visit, Visitor};
6use nuvec_core::{Array, DType, Error, Result};
7
8use crate::{load_safetensors, save_safetensors};
9
10pub fn save<T: Leaves + ?Sized>(path: impl AsRef<Path>, tree: &T) -> Result<()> {
21 struct Named(Vec<(String, Array)>);
22 impl Visitor for Named {
23 fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
24 self.0.push((path.to_owned(), a.clone()));
25 }
26 }
27 let mut named = Named(Vec::new());
28 tree.visit(&mut Visit::new(&mut named));
29 let named = named.0;
30 let mut seen = std::collections::HashSet::new();
31 for (p, _) in &named {
32 if !seen.insert(p.as_str()) {
33 return Err(Error::InvalidArgument(format!(
34 "save: the tree has the path {p:?} twice"
35 )));
36 }
37 }
38 let mut metadata = vec![
39 ("format".to_owned(), "nuvec-tree".to_owned()),
40 ("root".to_owned(), std::any::type_name::<T>().to_owned()),
41 ];
42 for key in ["step", "learning_rate"] {
43 let found = named
44 .iter()
45 .find(|(p, a)| a.ndim() == 0 && (p == key || p.ends_with(&format!(".{key}"))));
46 if let Some((_, a)) = found {
47 let value = match a.dtype() {
48 DType::F16 | DType::BF16 | DType::F32 | DType::F64 => {
49 a.astype(DType::F64).item::<f64>()?.to_string()
50 }
51 _ => a.astype(DType::I64).item::<i64>()?.to_string(),
52 };
53 metadata.push((key.to_owned(), value));
54 }
55 }
56 let tensors: Vec<(&str, &Array)> = named.iter().map(|(p, a)| (p.as_str(), a)).collect();
57 let metadata: Vec<(&str, &str)> = metadata
58 .iter()
59 .map(|(k, v)| (k.as_str(), v.as_str()))
60 .collect();
61 save_safetensors(path, &tensors, &metadata)
62}
63
64pub fn load_into<T: Tree>(path: impl AsRef<Path>, tree: &mut T) -> Result<()> {
68 let file = load_safetensors(path)?;
69 tree.load_flat(file.tensors, LoadMode::Strict)
70 .map_err(|e| match e {
71 Error::InvalidArgument(m) => {
72 Error::InvalidArgument(m.replacen("load_flat", "load_into", 1))
73 }
74 e => e,
75 })
76}