1use std::io::{Read, Seek, SeekFrom};
10
11use anyhow::{bail, Context, Result};
12use byteorder::{LittleEndian, ReadBytesExt};
13
14use super::types::{GGMLType, GGUFMetaValue};
15
16const GGUF_MAGIC: &[u8; 4] = b"GGUF";
17const DEFAULT_ALIGNMENT: u64 = 32;
18
19pub const READ_BUF_CAPACITY: usize = 1 << 20;
26
27pub struct GGUFTensorInfo {
28 pub name: String,
29 pub shape: Vec<u64>,
31 pub dtype: GGMLType,
32 pub offset: u64,
34}
35
36impl GGUFTensorInfo {
37 pub fn n_elems(&self) -> u64 {
38 self.shape.iter().product()
39 }
40
41 pub fn n_bytes(&self) -> u64 {
42 match self.dtype {
43 GGMLType::F32 => self.n_elems() * 4,
44 GGMLType::F16 | GGMLType::BF16 => self.n_elems() * 2,
45 GGMLType::Q8_0 => self.n_elems() / 32 * 34,
46 }
47 }
48}
49
50pub struct GGUFFile {
51 pub metadata: Vec<(String, GGUFMetaValue)>,
52 pub tensors: Vec<GGUFTensorInfo>,
53 pub data_start: u64,
55}
56
57impl GGUFFile {
58 pub fn read(reader: &mut (impl Read + Seek)) -> Result<Self> {
59 let mut magic = [0u8; 4];
60 reader.read_exact(&mut magic).context("read GGUF magic")?;
61 if &magic != GGUF_MAGIC {
62 bail!("not a GGUF file (bad magic)");
63 }
64 let version = reader.read_u32::<LittleEndian>()?;
65 if !(2..=3).contains(&version) {
66 bail!("unsupported GGUF version {version}");
67 }
68 let tensor_count = reader.read_u64::<LittleEndian>()?;
69 let kv_count = reader.read_u64::<LittleEndian>()?;
70
71 let mut metadata = Vec::with_capacity(kv_count as usize);
72 for _ in 0..kv_count {
73 let key = read_string(reader)?;
74 let vtype = reader.read_u32::<LittleEndian>()?;
75 let value = read_value(reader, vtype)?;
76 if let Some(v) = value {
77 metadata.push((key, v));
78 }
79 }
80
81 let alignment = metadata
82 .iter()
83 .find(|(k, _)| k == "general.alignment")
84 .and_then(|(_, v)| match v {
85 GGUFMetaValue::Uint32(a) => Some(*a as u64),
86 GGUFMetaValue::Uint64(a) => Some(*a),
87 _ => None,
88 })
89 .unwrap_or(DEFAULT_ALIGNMENT);
90
91 let mut tensors = Vec::with_capacity(tensor_count as usize);
92 for _ in 0..tensor_count {
93 let name = read_string(reader)?;
94 let n_dims = reader.read_u32::<LittleEndian>()?;
95 let mut shape = Vec::with_capacity(n_dims as usize);
96 for _ in 0..n_dims {
97 shape.push(reader.read_u64::<LittleEndian>()?);
98 }
99 let dtype_raw = reader.read_u32::<LittleEndian>()?;
100 let dtype = match dtype_raw {
101 0 => GGMLType::F32,
102 1 => GGMLType::F16,
103 8 => GGMLType::Q8_0,
104 30 => GGMLType::BF16,
105 other => bail!("tensor {name}: ggml dtype {other} not supported by this reader"),
106 };
107 let offset = reader.read_u64::<LittleEndian>()?;
108 tensors.push(GGUFTensorInfo { name, shape, dtype, offset });
109 }
110
111 let pos = reader.stream_position()?;
112 let data_start = pos.div_ceil(alignment) * alignment;
113
114 Ok(GGUFFile { metadata, tensors, data_start })
115 }
116
117 pub fn tensor_bytes(
119 &self,
120 reader: &mut (impl Read + Seek),
121 info: &GGUFTensorInfo,
122 ) -> Result<Vec<u8>> {
123 reader.seek(SeekFrom::Start(self.data_start + info.offset))?;
124 let mut buf = vec![0u8; info.n_bytes() as usize];
125 reader
126 .read_exact(&mut buf)
127 .with_context(|| format!("read tensor {} data", info.name))?;
128 Ok(buf)
129 }
130
131 pub fn tensor_f32(
133 &self,
134 reader: &mut (impl Read + Seek),
135 info: &GGUFTensorInfo,
136 ) -> Result<Vec<f32>> {
137 let bytes = self.tensor_bytes(reader, info)?;
138 match info.dtype {
139 GGMLType::F32 => Ok(bytes
140 .chunks_exact(4)
141 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
142 .collect()),
143 GGMLType::F16 => Ok(bytes
144 .chunks_exact(2)
145 .map(|c| f16_bits_to_f32(u16::from_le_bytes([c[0], c[1]])))
146 .collect()),
147 GGMLType::BF16 => Ok(bytes
148 .chunks_exact(2)
149 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
150 .collect()),
151 GGMLType::Q8_0 => {
152 let mut out = Vec::with_capacity(info.n_elems() as usize);
153 for block in bytes.chunks_exact(34) {
154 let d = f16_bits_to_f32(u16::from_le_bytes([block[0], block[1]]));
155 for &q in &block[2..34] {
156 out.push(d * (q as i8) as f32);
157 }
158 }
159 Ok(out)
160 }
161 }
162 }
163}
164
165fn read_string(reader: &mut impl Read) -> Result<String> {
166 let len = reader.read_u64::<LittleEndian>()? as usize;
167 let mut buf = vec![0u8; len];
168 reader.read_exact(&mut buf)?;
169 String::from_utf8(buf).context("GGUF string is not UTF-8")
170}
171
172fn read_value(reader: &mut impl Read, vtype: u32) -> Result<Option<GGUFMetaValue>> {
175 Ok(match vtype {
176 0 => Some(GGUFMetaValue::Uint8(reader.read_u8()?)),
177 1 => Some(GGUFMetaValue::Int8(reader.read_i8()?)),
178 2 => Some(GGUFMetaValue::Uint16(reader.read_u16::<LittleEndian>()?)),
179 3 => Some(GGUFMetaValue::Int16(reader.read_i16::<LittleEndian>()?)),
180 4 => Some(GGUFMetaValue::Uint32(reader.read_u32::<LittleEndian>()?)),
181 5 => Some(GGUFMetaValue::Int32(reader.read_i32::<LittleEndian>()?)),
182 6 => Some(GGUFMetaValue::Float32(reader.read_f32::<LittleEndian>()?)),
183 7 => Some(GGUFMetaValue::Bool(reader.read_u8()? != 0)),
184 8 => Some(GGUFMetaValue::String(read_string(reader)?)),
185 9 => {
186 let elem_type = reader.read_u32::<LittleEndian>()?;
187 let count = reader.read_u64::<LittleEndian>()?;
188 match elem_type {
189 4 => {
190 let mut v = Vec::with_capacity(count as usize);
191 for _ in 0..count {
192 v.push(reader.read_u32::<LittleEndian>()?);
193 }
194 Some(GGUFMetaValue::ArrayUint32(v))
195 }
196 6 => {
197 let mut v = Vec::with_capacity(count as usize);
198 for _ in 0..count {
199 v.push(reader.read_f32::<LittleEndian>()?);
200 }
201 Some(GGUFMetaValue::ArrayFloat32(v))
202 }
203 8 => {
204 let mut v = Vec::with_capacity(count as usize);
205 for _ in 0..count {
206 v.push(read_string(reader)?);
207 }
208 Some(GGUFMetaValue::ArrayString(v))
209 }
210 other => {
211 for _ in 0..count {
212 read_value(reader, other)?;
213 }
214 None
215 }
216 }
217 }
218 10 => Some(GGUFMetaValue::Uint64(reader.read_u64::<LittleEndian>()?)),
219 11 => Some(GGUFMetaValue::Int64(reader.read_i64::<LittleEndian>()?)),
220 12 => Some(GGUFMetaValue::Float64(reader.read_f64::<LittleEndian>()?)),
221 other => bail!("unsupported GGUF metadata value type {other}"),
222 })
223}
224
225fn f16_bits_to_f32(bits: u16) -> f32 {
226 let sign = ((bits & 0x8000) as u32) << 16;
227 let exp = ((bits >> 10) & 0x1F) as i32;
228 let mantissa = (bits & 0x03FF) as u32;
229 let f32_bits = if exp == 0 {
230 if mantissa == 0 {
231 sign
232 } else {
233 let mut m = mantissa;
234 let mut e = 0i32;
235 while m & 0x0400 == 0 {
236 m <<= 1;
237 e += 1;
238 }
239 sign | ((127 - 15 - e + 1) as u32) << 23 | (m & 0x03FF) << 13
240 }
241 } else if exp == 31 {
242 sign | 0x7F80_0000 | (mantissa << 13)
243 } else {
244 sign | ((exp + 127 - 15) as u32) << 23 | (mantissa << 13)
245 };
246 f32::from_bits(f32_bits)
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::writer::GGUFWriter;
253 use std::io::Cursor;
254
255 #[test]
256 fn writer_reader_roundtrip_all_dtypes() {
257 let mut w = GGUFWriter::new();
258 w.add_metadata("general.architecture", GGUFMetaValue::String("test".into()));
259 w.add_metadata("test.count", GGUFMetaValue::Uint32(7));
260 w.add_metadata(
261 "test.floats",
262 GGUFMetaValue::ArrayFloat32(vec![0.5, 1.5]),
263 );
264
265 let f32_vals: Vec<f32> = (0..32).map(|i| i as f32 / 4.0).collect();
266 let f32_bytes: Vec<u8> = f32_vals.iter().flat_map(|v| v.to_le_bytes()).collect();
267 w.add_tensor("t.f32", vec![32], GGMLType::F32, f32_bytes);
268
269 let bf16_bytes: Vec<u8> = (0..16)
271 .flat_map(|_| ((1.25f32.to_bits() >> 16) as u16).to_le_bytes())
272 .collect();
273 w.add_tensor("t.bf16", vec![4, 4], GGMLType::BF16, bf16_bytes);
274
275 let mut buf = Cursor::new(Vec::new());
276 w.write_to(&mut buf).unwrap();
277
278 buf.set_position(0);
279 let f = GGUFFile::read(&mut buf).unwrap();
280 assert_eq!(f.metadata.len(), 3);
281 assert_eq!(f.tensors.len(), 2);
282
283 let tb = f.tensors.iter().find(|t| t.name == "t.bf16").unwrap();
284 assert_eq!(tb.dtype, GGMLType::BF16);
285 assert_eq!(tb.shape, vec![4, 4]);
286 let vals = f.tensor_f32(&mut buf, tb).unwrap();
287 assert!(vals.iter().all(|&v| v == 1.25));
288
289 let tf = f.tensors.iter().find(|t| t.name == "t.f32").unwrap();
290 let vals = f.tensor_f32(&mut buf, tf).unwrap();
291 assert_eq!(vals, f32_vals);
292 }
293
294 #[test]
295 fn q8_0_dequant_roundtrip() {
296 let mut block = vec![0u8; 34];
298 block[0..2].copy_from_slice(&0x3C00u16.to_le_bytes()); for i in 0..32 {
300 block[2 + i] = (-(127 - i as i8)) as u8;
301 }
302 let mut w = GGUFWriter::new();
303 w.add_tensor("q", vec![32], GGMLType::Q8_0, block);
304 let mut buf = Cursor::new(Vec::new());
305 w.write_to(&mut buf).unwrap();
306 buf.set_position(0);
307 let f = GGUFFile::read(&mut buf).unwrap();
308 let vals = f.tensor_f32(&mut buf, &f.tensors[0]).unwrap();
309 assert_eq!(vals[0], -127.0);
310 assert_eq!(vals[31], -96.0);
311 }
312}