kernel/resolution/
gguf.rs1use std::collections::BTreeMap;
8use std::fs::File;
9use std::io::{BufReader, Read, Seek, SeekFrom};
10use std::path::Path;
11
12use crate::resolution::format::GgufFacts;
13
14const MAX_KV_PAIRS: u64 = 512;
15const MAX_STRING_LEN: u64 = 1 << 16;
16const MAX_STRING_ARRAY_LEN: u64 = 1 << 24;
17
18const TYPE_UINT32: u32 = 4;
19const TYPE_INT32: u32 = 5;
20const TYPE_STRING: u32 = 8;
21const TYPE_ARRAY: u32 = 9;
22const TYPE_UINT64: u32 = 10;
23const TYPE_INT64: u32 = 11;
24
25pub fn has_gguf_magic(path: &Path) -> bool {
27 first_four(path) == Some(*b"GGUF")
28}
29
30pub fn has_ggml_magic(path: &Path) -> bool {
33 first_four(path) == Some(*b"lmgg")
34}
35
36pub fn gguf_general_architecture(path: &Path) -> Option<String> {
38 gguf_facts(path)?.architecture
39}
40
41pub fn gguf_facts(path: &Path) -> Option<GgufFacts> {
45 let mut reader = Reader::open(path)?;
46 if reader.read_array::<4>()? != *b"GGUF" {
47 return None;
48 }
49 let version = reader.read_u32()?;
50 if version < 2 {
51 return None;
52 }
53 let _tensor_count = reader.read_u64()?;
54 let kv_count = reader.read_u64()?;
55
56 let mut architecture: Option<String> = None;
57 let mut context_lengths: BTreeMap<String, i64> = BTreeMap::new();
58 let mut has_chat_template = false;
59 let mut file_type: Option<i64> = None;
60
61 for _ in 0..kv_count.min(MAX_KV_PAIRS) {
62 let Some(key) = reader.read_string() else {
63 break;
64 };
65 let Some(value_type) = reader.read_u32() else {
66 break;
67 };
68
69 if key == "general.architecture" {
70 if value_type == TYPE_STRING {
71 let Some(value) = reader.read_string() else {
72 break;
73 };
74 architecture = Some(value);
75 } else if !reader.skip_value(value_type) {
76 break;
77 }
78 } else if key == "tokenizer.chat_template" {
79 has_chat_template = true;
80 if !reader.skip_value(value_type) {
81 break;
82 }
83 } else if key == "general.file_type" {
84 match read_integer(&mut reader, value_type) {
85 Some(value) => file_type = Some(value),
86 None => {
87 if !reader.skip_value(value_type) {
88 break;
89 }
90 }
91 }
92 } else if key.ends_with(".context_length") {
93 match read_integer(&mut reader, value_type) {
94 Some(value) => {
95 if value > 0 {
96 context_lengths.insert(key, value);
97 }
98 }
99 None => {
100 if !reader.skip_value(value_type) {
101 break;
102 }
103 }
104 }
105 } else if !reader.skip_value(value_type) {
106 break;
107 }
108 }
109
110 let context_length = architecture
111 .as_ref()
112 .and_then(|arch| {
113 context_lengths
114 .get(&format!("{arch}.context_length"))
115 .copied()
116 })
117 .or_else(|| {
118 if context_lengths.len() == 1 {
119 context_lengths.values().copied().next()
120 } else {
121 None
122 }
123 });
124
125 Some(GgufFacts {
126 architecture,
127 context_length,
128 has_chat_template,
129 quantization: file_type.and_then(file_type_name).map(str::to_owned),
130 })
131}
132
133const FILE_TYPE_GUESSED: i64 = 1024;
136
137fn file_type_name(value: i64) -> Option<&'static str> {
141 Some(match value & !FILE_TYPE_GUESSED {
142 0 => "F32",
143 1 => "F16",
144 2 => "Q4_0",
145 3 => "Q4_1",
146 7 => "Q8_0",
147 8 => "Q5_0",
148 9 => "Q5_1",
149 10 => "Q2_K",
150 11 => "Q3_K_S",
151 12 => "Q3_K_M",
152 13 => "Q3_K_L",
153 14 => "Q4_K_S",
154 15 => "Q4_K_M",
155 16 => "Q5_K_S",
156 17 => "Q5_K_M",
157 18 => "Q6_K",
158 19 => "IQ2_XXS",
159 20 => "IQ2_XS",
160 21 => "Q2_K_S",
161 22 => "IQ3_XS",
162 23 => "IQ3_XXS",
163 24 => "IQ1_S",
164 25 => "IQ4_NL",
165 26 => "IQ3_S",
166 27 => "IQ3_M",
167 28 => "IQ2_S",
168 29 => "IQ2_M",
169 30 => "IQ4_XS",
170 31 => "IQ1_M",
171 32 => "BF16",
172 36 => "TQ1_0",
173 37 => "TQ2_0",
174 38 => "MXFP4_MOE",
175 _ => return None,
176 })
177}
178
179fn first_four(path: &Path) -> Option<[u8; 4]> {
180 let mut file = File::open(path).ok()?;
181 let mut buffer = [0u8; 4];
182 file.read_exact(&mut buffer).ok()?;
183 Some(buffer)
184}
185
186fn read_integer(reader: &mut Reader, value_type: u32) -> Option<i64> {
187 match value_type {
188 TYPE_UINT32 => reader.read_u32().map(i64::from),
189 TYPE_INT32 => reader.read_i32().map(i64::from),
190 TYPE_UINT64 => reader
191 .read_u64()
192 .map(|value| value.min(i64::MAX as u64) as i64),
193 TYPE_INT64 => reader.read_i64(),
194 _ => None,
195 }
196}
197
198fn scalar_width(value_type: u32) -> Option<u64> {
199 match value_type {
200 0 | 1 | 7 => Some(1), 2..=3 => Some(2), 4..=6 => Some(4), 10..=12 => Some(8), _ => None,
205 }
206}
207
208struct Reader {
209 inner: BufReader<File>,
210}
211
212impl Reader {
213 fn open(path: &Path) -> Option<Self> {
214 Some(Self {
215 inner: BufReader::new(File::open(path).ok()?),
216 })
217 }
218
219 fn read_bytes(&mut self, count: usize) -> Option<Vec<u8>> {
220 let mut buffer = vec![0u8; count];
221 self.inner.read_exact(&mut buffer).ok()?;
222 Some(buffer)
223 }
224
225 fn read_array<const N: usize>(&mut self) -> Option<[u8; N]> {
226 let mut buffer = [0u8; N];
227 self.inner.read_exact(&mut buffer).ok()?;
228 Some(buffer)
229 }
230
231 fn read_u32(&mut self) -> Option<u32> {
232 self.read_array::<4>().map(u32::from_le_bytes)
233 }
234
235 fn read_i32(&mut self) -> Option<i32> {
236 self.read_array::<4>().map(i32::from_le_bytes)
237 }
238
239 fn read_u64(&mut self) -> Option<u64> {
240 self.read_array::<8>().map(u64::from_le_bytes)
241 }
242
243 fn read_i64(&mut self) -> Option<i64> {
244 self.read_array::<8>().map(i64::from_le_bytes)
245 }
246
247 fn read_string(&mut self) -> Option<String> {
248 let length = self.read_u64()?;
249 if length > MAX_STRING_LEN {
250 return None;
251 }
252 let bytes = self.read_bytes(length as usize)?;
253 Some(String::from_utf8_lossy(&bytes).into_owned())
254 }
255
256 fn skip(&mut self, count: u64) -> bool {
257 if count == 0 {
258 return true;
259 }
260 if count > i64::MAX as u64 {
261 return false;
262 }
263 self.inner.seek(SeekFrom::Current(count as i64)).is_ok()
264 }
265
266 fn skip_value(&mut self, value_type: u32) -> bool {
267 if let Some(width) = scalar_width(value_type) {
268 return self.skip(width);
269 }
270 match value_type {
271 TYPE_STRING => match self.read_u64() {
272 Some(length) => self.skip(length),
273 None => false,
274 },
275 TYPE_ARRAY => self.skip_array(),
276 _ => false,
277 }
278 }
279
280 fn skip_array(&mut self) -> bool {
281 let Some(element_type) = self.read_u32() else {
282 return false;
283 };
284 let Some(count) = self.read_u64() else {
285 return false;
286 };
287 if let Some(width) = scalar_width(element_type) {
288 return match count.checked_mul(width) {
289 Some(total) => self.skip(total),
290 None => false,
291 };
292 }
293 if element_type != TYPE_STRING || count > MAX_STRING_ARRAY_LEN {
294 return false;
295 }
296 for _ in 0..count {
297 let Some(length) = self.read_u64() else {
298 return false;
299 };
300 if !self.skip(length) {
301 return false;
302 }
303 }
304 true
305 }
306}