qualia_core_db/inference/
safetensor.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SourceFormat {
21 Safetensor,
23 Gguf,
25 Unknown,
27}
28
29pub fn detect_format(head: &[u8]) -> SourceFormat {
32 if head.len() >= 4 && &head[0..4] == b"GGUF" {
33 return SourceFormat::Gguf;
34 }
35 if head.len() >= 9 {
36 let hlen = u64::from_le_bytes(head[0..8].try_into().unwrap()) as usize;
37 if hlen > 0 && hlen < (1 << 30) && head[8] == b'{' {
39 return SourceFormat::Safetensor;
40 }
41 }
42 SourceFormat::Unknown
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SafeTensorEntry {
48 pub name: String,
49 pub dtype: String,
50 pub shape: Vec<usize>,
51 pub begin: usize,
53 pub end: usize,
54}
55
56impl SafeTensorEntry {
57 #[inline]
58 pub fn byte_len(&self) -> usize {
59 self.end - self.begin
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct SafeTensorPlan {
66 pub tensors: Vec<SafeTensorEntry>,
67 pub data_start: usize,
69 pub is_mlx: bool,
71}
72
73pub fn parse_safetensor_header(src: &[u8]) -> Result<SafeTensorPlan, String> {
76 if src.len() < 8 {
77 return Err("safetensor: too small for 8-byte header length".to_string());
78 }
79 let hlen = u64::from_le_bytes(src[0..8].try_into().unwrap()) as usize;
80 let data_start = 8usize
81 .checked_add(hlen)
82 .ok_or("safetensor: header length overflow")?;
83 if data_start > src.len() {
84 return Err("safetensor: header length exceeds file".to_string());
85 }
86 let json: serde_json::Value = serde_json::from_slice(&src[8..data_start])
87 .map_err(|e| format!("safetensor: header JSON parse error: {e}"))?;
88 let obj = json
89 .as_object()
90 .ok_or("safetensor: header is not a JSON object")?;
91
92 let is_mlx = obj
93 .get("__metadata__")
94 .and_then(|m| m.get("format"))
95 .and_then(|f| f.as_str())
96 .map(|s| s.eq_ignore_ascii_case("mlx"))
97 .unwrap_or(false);
98
99 let data_len = src.len() - data_start;
100 let mut tensors = Vec::new();
101 for (name, spec) in obj {
102 if name == "__metadata__" {
103 continue;
104 }
105 let dtype = spec
106 .get("dtype")
107 .and_then(|d| d.as_str())
108 .ok_or_else(|| format!("safetensor: tensor '{name}' missing dtype"))?
109 .to_string();
110 let shape = spec
111 .get("shape")
112 .and_then(|s| s.as_array())
113 .ok_or_else(|| format!("safetensor: tensor '{name}' missing shape"))?
114 .iter()
115 .map(|v| v.as_u64().unwrap_or(0) as usize)
116 .collect::<Vec<_>>();
117 let offs = spec
118 .get("data_offsets")
119 .and_then(|o| o.as_array())
120 .ok_or_else(|| format!("safetensor: tensor '{name}' missing data_offsets"))?;
121 if offs.len() != 2 {
122 return Err(format!(
123 "safetensor: tensor '{name}' data_offsets must be [begin,end]"
124 ));
125 }
126 let begin = offs[0].as_u64().unwrap_or(0) as usize;
127 let end = offs[1].as_u64().unwrap_or(0) as usize;
128 if end < begin || end > data_len {
129 return Err(format!("safetensor: tensor '{name}' offsets out of bounds"));
130 }
131 tensors.push(SafeTensorEntry {
132 name: name.clone(),
133 dtype,
134 shape,
135 begin,
136 end,
137 });
138 }
139 tensors.sort_by(|a, b| a.begin.cmp(&b.begin));
141 Ok(SafeTensorPlan {
142 tensors,
143 data_start,
144 is_mlx,
145 })
146}
147
148pub const GGML_F32: u32 = 0;
153pub const GGML_F16: u32 = 1;
154pub const GGML_Q8_0: u32 = 8;
155pub const GGML_BF16: u32 = 30;
156
157pub fn safetensor_dtype_to_ggml(dtype: &str) -> Option<u32> {
160 match dtype {
161 "F32" => Some(GGML_F32),
162 "F16" => Some(GGML_F16),
163 "BF16" => Some(GGML_BF16),
164 _ => None, }
166}
167
168pub fn is_high_fidelity_ggml(ggml_type: u32) -> bool {
172 matches!(ggml_type, GGML_F32 | GGML_F16 | GGML_Q8_0 | GGML_BF16)
173}
174
175pub fn ggml_elem_bytes(ggml_type: u32) -> Option<usize> {
177 match ggml_type {
178 GGML_F32 => Some(4),
179 GGML_F16 | GGML_BF16 => Some(2),
180 GGML_Q8_0 => Some(1), _ => None,
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 fn synth_safetensor(t: &[(&str, &str, Vec<usize>, usize)]) -> Vec<u8> {
191 let mut entries = serde_json::Map::new();
193 let mut cursor = 0usize;
194 for (name, dtype, shape, nbytes) in t {
195 let begin = cursor;
196 let end = cursor + nbytes;
197 cursor = end;
198 entries.insert(
199 (*name).to_string(),
200 serde_json::json!({ "dtype": dtype, "shape": shape, "data_offsets": [begin, end] }),
201 );
202 }
203 let header = serde_json::Value::Object(entries);
204 let header_bytes = serde_json::to_vec(&header).unwrap();
205 let mut out = Vec::new();
206 out.extend_from_slice(&(header_bytes.len() as u64).to_le_bytes());
207 out.extend_from_slice(&header_bytes);
208 out.resize(out.len() + cursor, 0u8); out
210 }
211
212 #[test]
213 fn detects_formats() {
214 let st = synth_safetensor(&[("w", "F16", vec![2, 2], 8)]);
215 assert_eq!(detect_format(&st), SourceFormat::Safetensor);
216 assert_eq!(detect_format(b"GGUF\0\0\0\0"), SourceFormat::Gguf);
217 assert_eq!(detect_format(b"not a model"), SourceFormat::Unknown);
218 }
219
220 #[test]
221 fn parses_header_and_offsets() {
222 let st = synth_safetensor(&[("a", "F16", vec![4], 8), ("b", "F32", vec![2, 2], 16)]);
223 let plan = parse_safetensor_header(&st).unwrap();
224 assert_eq!(plan.tensors.len(), 2);
225 assert_eq!(plan.tensors[0].name, "a");
226 assert_eq!(plan.tensors[0].byte_len(), 8);
227 assert_eq!(plan.tensors[1].dtype, "F32");
228 assert_eq!(plan.tensors[1].byte_len(), 16);
229 assert!(!plan.is_mlx);
230 }
231
232 #[test]
233 fn dtype_gate_accepts_high_fidelity_rejects_q4() {
234 assert_eq!(safetensor_dtype_to_ggml("F16"), Some(GGML_F16));
235 assert_eq!(safetensor_dtype_to_ggml("BF16"), Some(GGML_BF16));
236 assert_eq!(safetensor_dtype_to_ggml("U8"), None);
237
238 assert!(is_high_fidelity_ggml(GGML_F16));
239 assert!(is_high_fidelity_ggml(GGML_Q8_0));
240 assert!(!is_high_fidelity_ggml(12)); assert!(!is_high_fidelity_ggml(2)); }
243}