1use std::path::{Path, PathBuf};
4
5use kime_tensor::Blob;
6use serde_json::{Map, Value};
7
8use crate::error::{Error, Result};
9use crate::laya::{LayaGraph, LayaSpec};
10use crate::tensors::Tensors;
11use crate::{pack, safetensors};
12
13pub const LAYA_FILES: [&str; 4] = [
16 "rl_agent_config.json",
17 "encoder/config.json",
18 "tokenizer/tokenizer.json",
19 "tokenizer/tokenizer_config.json",
20];
21
22#[derive(Debug)]
23enum Files {
24 Loose(Vec<(String, Vec<u8>)>),
25 Packed(Vec<pack::FileEntry>),
26}
27
28#[derive(Debug)]
30pub struct Model {
31 pub spec: LayaSpec,
33 pub tensors: Tensors,
35 pub graph: LayaGraph,
37 pub index: Option<pack::Index>,
39 pub metadata: Option<Map<String, Value>>,
41 files: Files,
42}
43
44impl Model {
45 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
53 let path = path.as_ref();
54 if path.is_dir() { Self::open_dir(path) } else { Self::open_kime(path) }
55 }
56
57 fn open_dir(dir: &Path) -> Result<Self> {
58 let mut files = Vec::new();
59 for name in LAYA_FILES {
60 let p = dir.join(name);
61 match std::fs::read(&p) {
62 Ok(b) => files.push((name.to_string(), b)),
63 Err(e)
64 if e.kind() == std::io::ErrorKind::NotFound
65 && name.ends_with("_config.json") => {}
66 Err(e) => return Err(Error::Io(p, e)),
67 }
68 }
69 let st = dir.join("model.safetensors");
70 let blob = Blob::map(&st).map_err(|e| Error::Io(st.clone(), e))?;
71 let (tensors, metadata) =
72 safetensors::load(blob).map_err(|e| Error::Format(format!("{}: {e}", st.display())))?;
73 let get = |n: &str| files.iter().find(|(k, _)| k == n).map(|(_, b)| b.as_slice());
74 let spec = spec_from(get("rl_agent_config.json"), get("encoder/config.json"), dir)?;
75 let graph = LayaGraph::bind(&spec, &tensors)?;
76 Ok(Self { spec, tensors, graph, index: None, metadata, files: Files::Loose(files) })
77 }
78
79 fn open_kime(path: &Path) -> Result<Self> {
80 let blob = Blob::map(path).map_err(|e| Error::Io(path.to_path_buf(), e))?;
81 let (index, tensors) =
82 pack::load(blob).map_err(|e| Error::Format(format!("{}: {e}", path.display())))?;
83 if index.family != "laya" {
84 return Err(Error::Format(format!(
85 "{}: family {:?} is not supported by this build",
86 path.display(),
87 index.family
88 )));
89 }
90 let get = |n: &str| {
91 index.files.iter().find(|f| f.name == n).map(|f| &tensors.blob()[f.start..f.end])
92 };
93 let spec = spec_from(get("rl_agent_config.json"), get("encoder/config.json"), path)?;
94 let graph = LayaGraph::bind(&spec, &tensors)?;
95 let metadata = index.json.get("safetensors_metadata").and_then(Value::as_object).cloned();
96 let files = Files::Packed(index.files.clone());
97 Ok(Self { spec, tensors, graph, index: Some(index), metadata, files })
98 }
99
100 #[must_use]
102 pub fn file(&self, name: &str) -> Option<&[u8]> {
103 match &self.files {
104 Files::Loose(v) => v.iter().find(|(k, _)| k == name).map(|(_, b)| b.as_slice()),
105 Files::Packed(v) => {
106 v.iter().find(|f| f.name == name).map(|f| &self.tensors.blob()[f.start..f.end])
107 }
108 }
109 }
110
111 #[must_use]
113 pub fn file_names(&self) -> Vec<&str> {
114 match &self.files {
115 Files::Loose(v) => v.iter().map(|(k, _)| k.as_str()).collect(),
116 Files::Packed(v) => v.iter().map(|f| f.name.as_str()).collect(),
117 }
118 }
119
120 pub fn verify(&self) -> Result<()> {
126 match &self.index {
127 Some(index) => pack::verify(self.tensors.blob(), index),
128 None => Ok(()),
129 }
130 }
131
132 pub fn pack(&self, out: &mut impl std::io::Write) -> Result<String> {
142 let mut extra = Map::new();
143 if let Some(m) = &self.metadata {
144 extra.insert("safetensors_metadata".into(), Value::Object(m.clone()));
145 }
146 let files =
147 self.file_names().into_iter().map(|n| (n, self.file(n).expect("listed"))).collect();
148 pack::write(
149 &pack::Contents {
150 family: "laya",
151 id: &self.spec.id,
152 tensors: &self.tensors,
153 files,
154 extra,
155 },
156 out,
157 )
158 }
159
160 pub fn unpack(&self, dir: &Path) -> Result<()> {
171 let io = |p: PathBuf| move |e| Error::Io(p, e);
172 let st = dir.join("model.safetensors");
173 if st.exists() {
174 return Err(Error::Io(st, std::io::Error::from(std::io::ErrorKind::AlreadyExists)));
175 }
176 for name in self.file_names() {
177 let p = dir.join(name);
178 if let Some(parent) = p.parent() {
179 std::fs::create_dir_all(parent).map_err(io(parent.to_path_buf()))?;
180 }
181 std::fs::write(&p, self.file(name).expect("listed")).map_err(io(p.clone()))?;
182 }
183 let f = std::fs::File::create(&st).map_err(io(st.clone()))?;
184 let mut w = std::io::BufWriter::with_capacity(1 << 20, f);
185 safetensors::write(&self.tensors, self.metadata.as_ref(), &mut w)
186 .map_err(io(st.clone()))?;
187 std::io::Write::flush(&mut w).map_err(io(st))
188 }
189}
190
191fn spec_from(agent: Option<&[u8]>, encoder: Option<&[u8]>, at: &Path) -> Result<LayaSpec> {
192 let parse = |b: Option<&[u8]>, name: &str| -> Result<Value> {
193 let b = b.ok_or_else(|| Error::Format(format!("{}: no {name}", at.display())))?;
194 serde_json::from_slice(b)
195 .map_err(|e| Error::Format(format!("{}: {name}: {e}", at.display())))
196 };
197 LayaSpec::from_json(
198 &parse(agent, "rl_agent_config.json")?,
199 &parse(encoder, "encoder/config.json")?,
200 )
201}