1use std::collections::HashMap;
20use std::path::{Path, PathBuf};
21
22use memmap2::Mmap;
23
24use crate::metadata::ModelMetadata;
25use crate::source::{ModelSource, SamplerConfig, TensorDtype, TensorReader};
26use crate::tokenizer::TokenizerSpec;
27use crate::{FormatError, Result};
28
29const GGUF_MAGIC: u32 = 0x4655_4747; #[derive(Debug, Clone)]
32enum MetaValue {
33 U32(u32),
34 I32(i32),
35 U64(u64),
36 F32(f32),
37 Bool(bool),
38 String(String),
39 Strings(Vec<String>),
40 F32s(Vec<f32>),
41 I32s(Vec<i32>),
42}
43
44#[derive(Debug, Clone)]
45struct TensorInfo {
46 name: String,
47 dims: Vec<usize>, ggml_type: u32,
49 offset: usize, }
51
52pub struct GgufSource {
54 path: PathBuf,
55 mmap: Mmap,
56 metadata: ModelMetadata,
57 kv: HashMap<String, MetaValue>,
58 tensors: HashMap<String, TensorInfo>,
59 data_start: usize,
60 tokenizer_json: PathBuf,
61 added_tokens: HashMap<u32, String>,
62 eos_ids: Vec<u32>,
63 bos_id: Option<u32>,
64}
65
66struct Cursor<'a> {
70 buf: &'a [u8],
71 pos: usize,
72}
73
74impl<'a> Cursor<'a> {
75 fn new(buf: &'a [u8]) -> Self {
76 Cursor { buf, pos: 0 }
77 }
78 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
79 if self.pos + n > self.buf.len() {
80 return Err(FormatError::Safetensors("gguf: unexpected end of file".into()));
81 }
82 let out = &self.buf[self.pos..self.pos + n];
83 self.pos += n;
84 Ok(out)
85 }
86 fn u8(&mut self) -> Result<u8> {
87 Ok(self.take(1)?[0])
88 }
89 fn u16(&mut self) -> Result<u16> {
90 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
91 }
92 fn u32(&mut self) -> Result<u32> {
93 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
94 }
95 fn i32(&mut self) -> Result<i32> {
96 Ok(i32::from_le_bytes(self.take(4)?.try_into().unwrap()))
97 }
98 fn u64(&mut self) -> Result<u64> {
99 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
100 }
101 fn i64(&mut self) -> Result<i64> {
102 Ok(i64::from_le_bytes(self.take(8)?.try_into().unwrap()))
103 }
104 fn f32(&mut self) -> Result<f32> {
105 Ok(f32::from_le_bytes(self.take(4)?.try_into().unwrap()))
106 }
107 fn string(&mut self) -> Result<String> {
108 let len = self.u64()? as usize;
109 let bytes = self.take(len)?;
110 String::from_utf8(bytes.to_vec())
111 .map_err(|e| FormatError::Safetensors(format!("gguf: bad utf8 string: {e}")))
112 }
113}
114
115const META_U8: u32 = 0;
116const META_I8: u32 = 1;
117const META_U16: u32 = 2;
118const META_I16: u32 = 3;
119const META_U32: u32 = 4;
120const META_I32: u32 = 5;
121const META_F32: u32 = 6;
122const META_BOOL: u32 = 7;
123const META_STRING: u32 = 8;
124const META_ARRAY: u32 = 9;
125const META_U64: u32 = 10;
126const META_I64: u32 = 11;
127const META_F64: u32 = 12;
128
129fn read_meta_value(c: &mut Cursor, ty: u32) -> Result<MetaValue> {
130 Ok(match ty {
131 META_U8 => MetaValue::U32(c.u8()? as u32),
132 META_I8 => MetaValue::I32(c.u8()? as i8 as i32),
133 META_U16 => MetaValue::U32(c.u16()? as u32),
134 META_I16 => MetaValue::I32(c.u16()? as i16 as i32),
135 META_U32 => MetaValue::U32(c.u32()?),
136 META_I32 => MetaValue::I32(c.i32()?),
137 META_U64 => MetaValue::U64(c.u64()?),
138 META_I64 => MetaValue::U64(c.i64()? as u64),
139 META_F32 => MetaValue::F32(c.f32()?),
140 META_F64 => MetaValue::F32(f64::from_le_bytes(c.take(8)?.try_into().unwrap()) as f32),
141 META_BOOL => MetaValue::Bool(c.u8()? != 0),
142 META_STRING => MetaValue::String(c.string()?),
143 META_ARRAY => {
144 let elem_ty = c.u32()?;
145 let len = c.u64()? as usize;
146 match elem_ty {
147 META_STRING => {
148 let mut out = Vec::with_capacity(len);
149 for _ in 0..len {
150 out.push(c.string()?);
151 }
152 MetaValue::Strings(out)
153 }
154 META_F32 => {
155 let mut out = Vec::with_capacity(len);
156 for _ in 0..len {
157 out.push(c.f32()?);
158 }
159 MetaValue::F32s(out)
160 }
161 META_I32 | META_I16 | META_I8 => {
162 let mut out = Vec::with_capacity(len);
163 for _ in 0..len {
164 out.push(read_meta_value(c, elem_ty).map(|v| match v {
165 MetaValue::I32(i) => i,
166 _ => 0,
167 })?);
168 }
169 MetaValue::I32s(out)
170 }
171 META_U32 | META_U16 | META_U8 => {
172 let mut out = Vec::with_capacity(len);
173 for _ in 0..len {
174 out.push(read_meta_value(c, elem_ty).map(|v| match v {
175 MetaValue::U32(u) => u as i32,
176 _ => 0,
177 })?);
178 }
179 MetaValue::I32s(out)
180 }
181 other => {
182 return Err(FormatError::Safetensors(format!(
183 "gguf: unsupported metadata array element type {other}"
184 )));
185 }
186 }
187 }
188 other => {
189 return Err(FormatError::Safetensors(format!(
190 "gguf: unsupported metadata type {other}"
191 )));
192 }
193 })
194}
195
196const GGML_F32: u32 = 0;
198const GGML_F16: u32 = 1;
199const GGML_Q4_0: u32 = 2;
200const GGML_Q8_0: u32 = 8;
201const GGML_BF16: u32 = 30;
202
203impl GgufSource {
204 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
206 let path = path.as_ref().to_path_buf();
207 let file = std::fs::File::open(&path)?;
208 let mmap = unsafe { Mmap::map(&file)? };
209 let mut c = Cursor::new(&mmap);
210
211 if c.u32()? != GGUF_MAGIC {
212 return Err(FormatError::Safetensors("gguf: bad magic".into()));
213 }
214 let version = c.u32()?;
215 if !(2..=3).contains(&version) {
216 return Err(FormatError::Safetensors(format!(
217 "gguf: unsupported version {version}"
218 )));
219 }
220 let tensor_count = c.u64()? as usize;
221 let kv_count = c.u64()? as usize;
222
223 let mut kv = HashMap::with_capacity(kv_count);
224 for _ in 0..kv_count {
225 let key = c.string()?;
226 let ty = c.u32()?;
227 let value = read_meta_value(&mut c, ty)?;
228 kv.insert(key, value);
229 }
230
231 let mut tensors = HashMap::with_capacity(tensor_count);
232 for _ in 0..tensor_count {
233 let name = c.string()?;
234 let n_dims = c.u32()? as usize;
235 let mut dims = Vec::with_capacity(n_dims);
236 for _ in 0..n_dims {
237 dims.push(c.u64()? as usize);
238 }
239 let ggml_type = c.u32()?;
240 let offset = c.u64()? as usize;
241 tensors.insert(name.clone(), TensorInfo { name, dims, ggml_type, offset });
242 }
243
244 let alignment = match kv.get("general.alignment") {
246 Some(MetaValue::U32(a)) => *a as usize,
247 _ => 32,
248 };
249 let data_start = c.pos.div_ceil(alignment) * alignment;
250
251 let metadata = build_model_metadata(&kv)?;
252 let (eos_ids, bos_id, added_tokens) = tokenizer_ids(&kv);
253 let tokenizer_json = ensure_tokenizer_json(&path, &kv)?;
254
255 let mut source = GgufSource {
256 path,
257 mmap,
258 metadata,
259 kv,
260 tensors,
261 data_start,
262 tokenizer_json,
263 added_tokens,
264 eos_ids,
265 bos_id,
266 };
267 source.metadata.tie_word_embeddings = !source.tensors.contains_key("output.weight");
270 Ok(source)
271 }
272
273 fn kv_u64(&self, key: &str) -> Option<u64> {
274 match self.kv.get(key) {
275 Some(MetaValue::U32(v)) => Some(*v as u64),
276 Some(MetaValue::U64(v)) => Some(*v),
277 Some(MetaValue::I32(v)) => Some(*v as u64),
278 _ => None,
279 }
280 }
281}
282
283fn build_model_metadata(kv: &HashMap<String, MetaValue>) -> Result<ModelMetadata> {
284 let get_u64 = |key: &str| -> Option<u64> {
285 match kv.get(key) {
286 Some(MetaValue::U32(v)) => Some(*v as u64),
287 Some(MetaValue::U64(v)) => Some(*v),
288 Some(MetaValue::I32(v)) => Some(*v as u64),
289 _ => None,
290 }
291 };
292 let get_f32 = |key: &str| -> Option<f32> {
293 match kv.get(key) {
294 Some(MetaValue::F32(v)) => Some(*v),
295 Some(MetaValue::U32(v)) => Some(*v as f32),
296 _ => None,
297 }
298 };
299 let get_str = |key: &str| -> Option<String> {
300 match kv.get(key) {
301 Some(MetaValue::String(s)) => Some(s.clone()),
302 _ => None,
303 }
304 };
305
306 let arch = get_str("general.architecture")
307 .ok_or_else(|| FormatError::MissingField("general.architecture".into()))?;
308 let prefix = arch.clone();
309 let field = |name: &str| get_u64(&format!("{prefix}.{name}"));
310
311 let hidden = field("embedding_length")
312 .ok_or_else(|| FormatError::MissingField("embedding_length".into()))? as usize;
313 let heads = field("attention.head_count")
314 .ok_or_else(|| FormatError::MissingField("attention.head_count".into()))?
315 as usize;
316 let kv_heads = field("attention.head_count_kv").unwrap_or(heads as u64) as usize;
317 let layers = field("block_count")
318 .ok_or_else(|| FormatError::MissingField("block_count".into()))? as usize;
319 let ctx = field("context_length").unwrap_or(2048) as usize;
320 let ffn = field("feed_forward_length").unwrap_or((hidden * 4) as u64) as usize;
321 let vocab = match kv.get(&format!("{prefix}.vocab_size")) {
322 Some(MetaValue::U32(v)) => *v as usize,
323 Some(MetaValue::U64(v)) => *v as usize,
324 _ => match kv.get("tokenizer.ggml.tokens") {
325 Some(MetaValue::Strings(t)) => t.len(),
326 _ => 0,
327 },
328 };
329 let (eos_ids, bos_id, _) = tokenizer_ids(kv);
330
331 Ok(ModelMetadata {
332 architecture: arch,
333 hidden_size: hidden,
334 intermediate_size: ffn,
335 num_hidden_layers: layers,
336 num_attention_heads: heads,
337 num_key_value_heads: kv_heads,
338 vocab_size: vocab,
339 max_position_embeddings: ctx,
340 rms_norm_eps: get_f32(&format!("{prefix}.attention.layer_norm_rms_epsilon"))
341 .unwrap_or(1e-5) as f64,
342 rope_theta: get_f32(&format!("{prefix}.rope.freq_base")).unwrap_or(10000.0) as f64,
343 tie_word_embeddings: false, head_dim: hidden / heads,
346 attention_bias: false,
347 bos_token_id: bos_id,
348 eos_token_ids: eos_ids,
349 vision: None,
350 })
351}
352
353fn tokenizer_ids(kv: &HashMap<String, MetaValue>) -> (Vec<u32>, Option<u32>, HashMap<u32, String>) {
354 let mut eos = Vec::new();
355 let mut bos = None;
356 let mut added = HashMap::new();
357 if let Some(MetaValue::U32(v)) = kv.get("tokenizer.ggml.eos_token_id") {
358 eos.push(*v);
359 }
360 if let Some(MetaValue::U32(v)) = kv.get("tokenizer.ggml.bos_token_id") {
361 bos = Some(*v);
362 }
363 if let (Some(MetaValue::Strings(tokens)), Some(MetaValue::I32s(types))) =
365 (kv.get("tokenizer.ggml.tokens"), kv.get("tokenizer.ggml.token_type"))
366 {
367 for (i, (tok, ty)) in tokens.iter().zip(types.iter()).enumerate() {
368 if *ty == 3 {
369 added.insert(i as u32, tok.clone());
370 }
371 }
372 }
373 (eos, bos, added)
374}
375
376fn ensure_tokenizer_json(path: &Path, kv: &HashMap<String, MetaValue>) -> Result<PathBuf> {
379 let sibling = path.with_file_name("tokenizer.json");
380 if sibling.exists() {
381 return Ok(sibling);
382 }
383 let cached = path.with_extension("tokenizer.json");
384 if cached.exists() {
385 return Ok(cached);
386 }
387
388 let tokens = match kv.get("tokenizer.ggml.tokens") {
389 Some(MetaValue::Strings(t)) => t,
390 _ => {
391 return Err(FormatError::MissingField(
392 "tokenizer.ggml.tokens (and no sibling tokenizer.json)".into(),
393 ));
394 }
395 };
396 let scores: Vec<f32> = match kv.get("tokenizer.ggml.scores") {
397 Some(MetaValue::F32s(s)) => s.clone(),
398 _ => vec![0.0; tokens.len()],
399 };
400 let merges: Vec<String> = match kv.get("tokenizer.ggml.merges") {
401 Some(MetaValue::Strings(m)) => m.clone(),
402 _ => vec![],
403 };
404
405 let mut vocab = serde_json::Map::new();
406 for (i, tok) in tokens.iter().enumerate() {
407 vocab.insert(tok.clone(), serde_json::Value::from(i));
408 }
409 let mut ordered: Vec<usize> = (0..tokens.len()).collect();
410 ordered.sort_by(|&a, &b| {
411 scores[a].partial_cmp(&scores[b]).unwrap_or(std::cmp::Ordering::Equal)
412 });
413 let mut ranks = serde_json::Map::new();
414 for (rank, id) in ordered.iter().enumerate() {
415 ranks.insert(id.to_string(), serde_json::Value::from(rank));
416 }
417
418 let json = serde_json::json!({
419 "version": "1.0",
420 "truncation": null,
421 "padding": null,
422 "added_tokens": [],
423 "normalizer": null,
424 "pre_tokenizer": {
425 "type": "Sequence",
426 "pretokenizers": [
427 {"type": "Split", "pattern": {"Regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+"}, "behavior": "Isolated", "invert": false},
428 {"type": "ByteLevel", "add_prefix_space": false, "trim_offsets": true, "use_regex": false}
429 ]
430 },
431 "post_processor": null,
432 "decoder": {"type": "ByteLevel", "add_prefix_space": true, "trim_offsets": true, "use_regex": true},
433 "model": {
434 "type": "BPE",
435 "dropout": null,
436 "unk_token": null,
437 "continuing_subword_prefix": null,
438 "end_of_word_suffix": null,
439 "fuse_unk": false,
440 "byte_fallback": false,
441 "vocab": vocab,
442 "merges": merges,
443 }
444 });
445 let serialized = serde_json::to_string(&json).map_err(|e| FormatError::Safetensors(format!("tokenizer json: {e}")))?;
446 std::fs::write(&cached, serialized)?;
447 Ok(cached)
448}
449
450fn map_tensor_name(ggml: &str) -> Option<String> {
452 if ggml == "token_embd.weight" {
453 return Some("model.embed_tokens.weight".into());
454 }
455 if ggml == "output.weight" {
456 return Some("lm_head.weight".into());
457 }
458 if ggml == "output_norm.weight" {
459 return Some("model.norm.weight".into());
460 }
461 let rest = ggml.strip_prefix("blk.")?;
462 let (layer, rest) = rest.split_once('.')?;
463 let hf = match rest {
464 "attn_norm.weight" => "input_layernorm.weight",
465 "ffn_norm.weight" => "post_attention_layernorm.weight",
466 "attn_q.weight" => "self_attn.q_proj.weight",
467 "attn_k.weight" => "self_attn.k_proj.weight",
468 "attn_v.weight" => "self_attn.v_proj.weight",
469 "attn_output.weight" => "self_attn.o_proj.weight",
470 "attn_q.bias" => "self_attn.q_proj.bias",
471 "attn_k.bias" => "self_attn.k_proj.bias",
472 "attn_v.bias" => "self_attn.v_proj.bias",
473 "attn_output.bias" => "self_attn.o_proj.bias",
474 "ffn_gate.weight" => "mlp.gate_proj.weight",
475 "ffn_up.weight" => "mlp.up_proj.weight",
476 "ffn_down.weight" => "mlp.down_proj.weight",
477 _ => return None,
478 };
479 Some(format!("model.layers.{layer}.{hf}"))
480}
481
482fn num_elements(dims: &[usize]) -> usize {
484 dims.iter().product()
485}
486
487fn tensor_byte_size(info: &TensorInfo) -> Result<usize> {
489 let n = num_elements(&info.dims);
490 let block = |bs: usize| -> Result<usize> {
491 if n % 32 != 0 {
492 return Err(FormatError::Safetensors(format!(
493 "gguf tensor {}: {n} elements not divisible by block size 32",
494 info.name
495 )));
496 }
497 Ok(n / 32 * bs)
498 };
499 Ok(match info.ggml_type {
500 GGML_F32 => n * 4,
501 GGML_F16 | GGML_BF16 => n * 2,
502 GGML_Q4_0 => block(18)?, GGML_Q8_0 => block(34)?, other => {
505 return Err(FormatError::UnsupportedDtype {
506 tensor: info.name.clone(),
507 dtype: format!("ggml_type {other} (Q4_K/Q5_K/Q6_K not yet supported)"),
508 });
509 }
510 })
511}
512
513fn dequantize_q4_0(data: &[u8], n: usize) -> Result<Vec<f32>> {
516 let mut fixed = Vec::with_capacity(n);
517 for block in data.chunks_exact(18) {
518 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
519 let mut vals = [0f32; 32];
520 for j in 0..16 {
521 let byte = block[2 + j];
522 vals[j] = ((byte & 0x0F) as i32 - 8) as f32 * d;
523 vals[j + 16] = ((byte >> 4) as i32 - 8) as f32 * d;
524 }
525 fixed.extend_from_slice(&vals);
526 }
527 if fixed.len() != n {
528 return Err(FormatError::Safetensors(format!(
529 "q4_0 dequant size mismatch: {} != {n}",
530 fixed.len()
531 )));
532 }
533 Ok(fixed)
534}
535
536fn dequantize_q8_0(data: &[u8], n: usize) -> Result<Vec<f32>> {
538 let mut out = Vec::with_capacity(n);
539 for block in data.chunks_exact(34) {
540 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
541 for &b in &block[2..34] {
542 out.push((b as i8) as f32 * d);
543 }
544 }
545 if out.len() != n {
546 return Err(FormatError::Safetensors(format!(
547 "q8_0 dequant size mismatch: {} != {n}",
548 out.len()
549 )));
550 }
551 Ok(out)
552}
553
554impl ModelSource for GgufSource {
555 fn metadata(&self) -> &ModelMetadata {
556 &self.metadata
557 }
558
559 fn tensor_names(&self) -> Vec<String> {
560 self.tensors
561 .keys()
562 .filter_map(|k| map_tensor_name(k))
563 .collect()
564 }
565
566 fn open_tensor(&self, name: &str) -> Result<TensorReader<'_>> {
567 let (ggml_name, info) = self
569 .tensors
570 .iter()
571 .find(|(k, _)| map_tensor_name(k).as_deref() == Some(name))
572 .ok_or_else(|| FormatError::TensorNotFound(name.to_string()))?;
573 let _ = ggml_name;
574
575 let size = tensor_byte_size(info)?;
576 let start = self.data_start + info.offset;
577 let data = self
578 .mmap
579 .get(start..start + size)
580 .ok_or_else(|| FormatError::Safetensors(format!("gguf tensor {} out of bounds", info.name)))?;
581
582 let shape: Vec<usize> = info.dims.iter().rev().copied().collect();
584 let n = num_elements(&info.dims);
585
586 match info.ggml_type {
587 GGML_F32 | GGML_F16 | GGML_BF16 => {
588 let dtype = match info.ggml_type {
589 GGML_F32 => TensorDtype::F32,
590 GGML_F16 => TensorDtype::F16,
591 _ => TensorDtype::BF16,
592 };
593 Ok(TensorReader::new(name.to_string(), shape, dtype, data))
594 }
595 GGML_Q4_0 => {
596 let values = dequantize_q4_0(data, n)?;
597 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
598 Ok(TensorReader::owned(name.to_string(), shape, bytes))
599 }
600 GGML_Q8_0 => {
601 let values = dequantize_q8_0(data, n)?;
602 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
603 Ok(TensorReader::owned(name.to_string(), shape, bytes))
604 }
605 other => Err(FormatError::UnsupportedDtype {
606 tensor: info.name.clone(),
607 dtype: format!("ggml_type {other}"),
608 }),
609 }
610 }
611
612 fn tokenizer(&self) -> Result<TokenizerSpec> {
613 Ok(TokenizerSpec {
614 tokenizer_json: self.tokenizer_json.clone(),
615 added_tokens: self.added_tokens.clone(),
616 chat_template: None,
617 })
618 }
619
620 fn sampler_defaults(&self) -> Option<SamplerConfig> {
621 None
622 }
623}
624
625impl GgufSource {
626 pub fn eos_token_ids(&self) -> &[u32] {
628 &self.eos_ids
629 }
630
631 pub fn path(&self) -> &Path {
633 &self.path
634 }
635}