1use std::collections::HashMap;
17use std::io::{self, Read, Seek};
18use std::path::Path;
19
20use crate::error::TruenoError;
21
22const GGUF_MAGIC: u32 = 0x4655_4747; pub use trueno_quant::GgmlType;
35
36const ADMITTED: [GgmlType; 15] = [
44 GgmlType::F32,
45 GgmlType::F16,
46 GgmlType::Q4_0,
47 GgmlType::Q4_1,
48 GgmlType::Q5_0,
49 GgmlType::Q5_1,
50 GgmlType::Q8_0,
51 GgmlType::Q8_1,
52 GgmlType::Q2K,
53 GgmlType::Q3K,
54 GgmlType::Q4K,
55 GgmlType::Q5K,
56 GgmlType::Q6K,
57 GgmlType::Q8K,
58 GgmlType::BF16,
59];
60
61fn from_u32(v: u32) -> Option<GgmlType> {
68 let t = GgmlType::from_id(v)?;
69 let mut i = 0;
70 while i < ADMITTED.len() {
71 if ADMITTED[i] as u32 == t as u32 {
72 return Some(t);
73 }
74 i += 1;
75 }
76 None
77}
78
79#[derive(Debug, Clone)]
81pub struct TensorInfo {
82 pub name: String,
83 pub dtype: GgmlType,
84 pub dims: Vec<u64>,
85 pub offset: u64,
87}
88
89impl TensorInfo {
90 pub fn n_elements(&self) -> u64 {
91 self.dims.iter().product::<u64>().max(1)
92 }
93
94 pub fn byte_size(&self) -> usize {
95 self.dtype.tensor_bytes(self.n_elements() as usize)
96 }
97}
98
99pub struct GgufFile {
101 pub tensor_count: u64,
102 pub metadata: HashMap<String, MetadataValue>,
103 pub tensors: Vec<TensorInfo>,
104 pub data_offset: u64,
106 data: Vec<u8>,
108}
109
110#[derive(Debug, Clone)]
111pub enum MetadataValue {
112 U8(u8),
113 I8(i8),
114 U16(u16),
115 I16(i16),
116 U32(u32),
117 I32(i32),
118 U64(u64),
119 I64(i64),
120 F32(f32),
121 F64(f64),
122 Bool(bool),
123 String(String),
124 Array(Vec<MetadataValue>),
125}
126
127impl MetadataValue {
128 pub fn as_u32(&self) -> Option<u32> {
129 match self {
130 Self::U32(v) => Some(*v),
131 Self::U64(v) => Some(*v as u32),
132 Self::I32(v) => Some(*v as u32),
133 _ => None,
134 }
135 }
136
137 pub fn as_f32(&self) -> Option<f32> {
138 match self {
139 Self::F32(v) => Some(*v),
140 Self::F64(v) => Some(*v as f32),
141 _ => None,
142 }
143 }
144
145 pub fn as_str(&self) -> Option<&str> {
146 match self {
147 Self::String(s) => Some(s),
148 _ => None,
149 }
150 }
151}
152
153impl GgufFile {
154 pub fn load(path: &Path) -> Result<Self, TruenoError> {
156 let data = std::fs::read(path).map_err(|e| {
157 TruenoError::InvalidInput(format!("Failed to read GGUF file {}: {e}", path.display()))
158 })?;
159
160 Self::parse(data)
161 }
162
163 pub fn parse(data: Vec<u8>) -> Result<Self, TruenoError> {
165 let mut cursor = io::Cursor::new(&data);
166
167 let magic = read_u32(&mut cursor)?;
169 if magic != GGUF_MAGIC {
170 return Err(TruenoError::InvalidInput(format!(
171 "Not a GGUF file: magic=0x{magic:08x}, expected 0x{GGUF_MAGIC:08x}"
172 )));
173 }
174 let version = read_u32(&mut cursor)?;
175 if !(2..=3).contains(&version) {
176 return Err(TruenoError::InvalidInput(format!(
177 "Unsupported GGUF version {version} (need 2 or 3)"
178 )));
179 }
180 let tensor_count = read_u64(&mut cursor)?;
181 let metadata_kv_count = read_u64(&mut cursor)?;
182
183 let mut metadata = HashMap::new();
185 for _ in 0..metadata_kv_count {
186 let key = read_gguf_string(&mut cursor)?;
187 let value = read_metadata_value(&mut cursor)?;
188 metadata.insert(key, value);
189 }
190
191 let mut tensors = Vec::with_capacity(tensor_count as usize);
193 for _ in 0..tensor_count {
194 let name = read_gguf_string(&mut cursor)?;
195 let n_dims = read_u32(&mut cursor)? as usize;
196 let mut dims = Vec::with_capacity(n_dims);
197 for _ in 0..n_dims {
198 dims.push(read_u64(&mut cursor)?);
199 }
200 let dtype_u32 = read_u32(&mut cursor)?;
201 let dtype = from_u32(dtype_u32).ok_or_else(|| {
202 TruenoError::InvalidInput(format!(
203 "Unknown GGML type {dtype_u32} for tensor '{name}'"
204 ))
205 })?;
206 let offset = read_u64(&mut cursor)?;
207 tensors.push(TensorInfo { name, dtype, dims, offset });
208 }
209
210 let alignment =
212 metadata.get("general.alignment").and_then(|v| v.as_u32()).unwrap_or(32) as u64;
213 let pos = cursor.position();
214 let data_offset = (pos + alignment - 1) / alignment * alignment;
215
216 Ok(Self { tensor_count, metadata, tensors, data_offset, data })
217 }
218
219 pub fn tensor_data(&self, name: &str) -> Option<&[u8]> {
221 let info = self.tensors.iter().find(|t| t.name == name)?;
222 let start = self.data_offset as usize + info.offset as usize;
223 let end = start + info.byte_size();
224 if end <= self.data.len() {
225 Some(&self.data[start..end])
226 } else {
227 None
228 }
229 }
230
231 pub fn tensor_info(&self, name: &str) -> Option<&TensorInfo> {
233 self.tensors.iter().find(|t| t.name == name)
234 }
235
236 pub fn meta_str(&self, key: &str) -> Option<&str> {
238 self.metadata.get(key)?.as_str()
239 }
240
241 pub fn meta_u32(&self, key: &str) -> Option<u32> {
243 self.metadata.get(key)?.as_u32()
244 }
245
246 pub fn meta_f32(&self, key: &str) -> Option<f32> {
248 self.metadata.get(key)?.as_f32()
249 }
250}
251
252fn read_u8<R: Read>(r: &mut R) -> Result<u8, TruenoError> {
255 let mut buf = [0u8; 1];
256 r.read_exact(&mut buf)
257 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
258 Ok(buf[0])
259}
260
261fn read_u16<R: Read>(r: &mut R) -> Result<u16, TruenoError> {
262 let mut buf = [0u8; 2];
263 r.read_exact(&mut buf)
264 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
265 Ok(u16::from_le_bytes(buf))
266}
267
268fn read_u32<R: Read>(r: &mut R) -> Result<u32, TruenoError> {
269 let mut buf = [0u8; 4];
270 r.read_exact(&mut buf)
271 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
272 Ok(u32::from_le_bytes(buf))
273}
274
275fn read_i32<R: Read>(r: &mut R) -> Result<i32, TruenoError> {
276 let mut buf = [0u8; 4];
277 r.read_exact(&mut buf)
278 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
279 Ok(i32::from_le_bytes(buf))
280}
281
282fn read_u64<R: Read>(r: &mut R) -> Result<u64, TruenoError> {
283 let mut buf = [0u8; 8];
284 r.read_exact(&mut buf)
285 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
286 Ok(u64::from_le_bytes(buf))
287}
288
289fn read_i64<R: Read>(r: &mut R) -> Result<i64, TruenoError> {
290 let mut buf = [0u8; 8];
291 r.read_exact(&mut buf)
292 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
293 Ok(i64::from_le_bytes(buf))
294}
295
296fn read_f32_val<R: Read>(r: &mut R) -> Result<f32, TruenoError> {
297 let mut buf = [0u8; 4];
298 r.read_exact(&mut buf)
299 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
300 Ok(f32::from_le_bytes(buf))
301}
302
303fn read_f64_val<R: Read>(r: &mut R) -> Result<f64, TruenoError> {
304 let mut buf = [0u8; 8];
305 r.read_exact(&mut buf)
306 .map_err(|e| TruenoError::InvalidInput(format!("GGUF read error: {e}")))?;
307 Ok(f64::from_le_bytes(buf))
308}
309
310fn read_gguf_string<R: Read>(r: &mut R) -> Result<String, TruenoError> {
311 let len = read_u64(r)? as usize;
312 if len > 1_000_000 {
313 return Err(TruenoError::InvalidInput(format!("GGUF string too long: {len}")));
314 }
315 let mut buf = vec![0u8; len];
316 r.read_exact(&mut buf)
317 .map_err(|e| TruenoError::InvalidInput(format!("GGUF string read error: {e}")))?;
318 String::from_utf8(buf)
319 .map_err(|e| TruenoError::InvalidInput(format!("GGUF string not UTF-8: {e}")))
320}
321
322fn read_metadata_value<R: Read + Seek>(r: &mut R) -> Result<MetadataValue, TruenoError> {
323 let value_type = read_u32(r)?;
324 match value_type {
325 0 => Ok(MetadataValue::U8(read_u8(r)?)),
326 1 => Ok(MetadataValue::I8(read_u8(r)? as i8)),
327 2 => Ok(MetadataValue::U16(read_u16(r)?)),
328 3 => Ok(MetadataValue::I16(read_u16(r)? as i16)),
329 4 => Ok(MetadataValue::U32(read_u32(r)?)),
330 5 => Ok(MetadataValue::I32(read_i32(r)?)),
331 6 => Ok(MetadataValue::F32(read_f32_val(r)?)),
332 7 => Ok(MetadataValue::Bool(read_u8(r)? != 0)),
333 8 => Ok(MetadataValue::String(read_gguf_string(r)?)),
334 9 => {
335 let elem_type = read_u32(r)?;
337 let count = read_u64(r)? as usize;
338 if count > 10_000_000 {
339 return Err(TruenoError::InvalidInput(format!("GGUF array too large: {count}")));
340 }
341 let mut items = Vec::with_capacity(count.min(1024));
342 for _ in 0..count {
343 let item = match elem_type {
345 0 => MetadataValue::U8(read_u8(r)?),
346 1 => MetadataValue::I8(read_u8(r)? as i8),
347 4 => MetadataValue::U32(read_u32(r)?),
348 5 => MetadataValue::I32(read_i32(r)?),
349 6 => MetadataValue::F32(read_f32_val(r)?),
350 8 => MetadataValue::String(read_gguf_string(r)?),
351 10 => MetadataValue::U64(read_u64(r)?),
352 11 => MetadataValue::I64(read_i64(r)?),
353 12 => MetadataValue::F64(read_f64_val(r)?),
354 _ => {
355 return Err(TruenoError::InvalidInput(format!(
356 "Unsupported GGUF array element type {elem_type}"
357 )))
358 }
359 };
360 items.push(item);
361 }
362 Ok(MetadataValue::Array(items))
363 }
364 10 => Ok(MetadataValue::U64(read_u64(r)?)),
365 11 => Ok(MetadataValue::I64(read_i64(r)?)),
366 12 => Ok(MetadataValue::F64(read_f64_val(r)?)),
367 _ => Err(TruenoError::InvalidInput(format!("Unknown GGUF metadata type {value_type}"))),
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_ggml_type_q4k_properties() {
377 let q4k = GgmlType::Q4K;
378 assert_eq!(q4k.block_size(), 256);
379 assert_eq!(q4k.block_bytes(), 144);
380 assert_eq!(q4k.tensor_bytes(4096), 2304);
382 }
383
384 #[test]
385 fn test_ggml_type_f32_properties() {
386 let f32t = GgmlType::F32;
387 assert_eq!(f32t.block_size(), 1);
388 assert_eq!(f32t.block_bytes(), 4);
389 assert_eq!(f32t.tensor_bytes(1024), 4096);
390 }
391
392 #[test]
393 fn test_gguf_magic_check() {
394 let bad_data = vec![0u8; 32];
395 let result = GgufFile::parse(bad_data);
396 assert!(result.is_err());
397 }
398
399 #[test]
400 fn test_minimal_gguf() {
401 let mut data = Vec::new();
403 data.extend_from_slice(&GGUF_MAGIC.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes()); data.resize(32, 0);
409
410 let file = GgufFile::parse(data).expect("valid minimal GGUF");
411 assert_eq!(file.tensor_count, 0);
412 assert_eq!(file.tensors.len(), 0);
413 }
414}
415
416#[cfg(test)]
420#[path = "gguf_characterization_tests.rs"]
421mod gguf_characterization_tests;