1use std::collections::HashMap;
22use std::path::{Path, PathBuf};
23
24use memmap2::Mmap;
25
26use crate::metadata::ModelMetadata;
27use crate::source::{ModelSource, SamplerConfig, TensorDtype, TensorReader};
28use crate::tokenizer::TokenizerSpec;
29use crate::{FormatError, Result};
30
31const GGUF_MAGIC: u32 = 0x4655_4747; #[derive(Debug, Clone)]
34enum MetaValue {
35 U32(u32),
36 I32(i32),
37 U64(u64),
38 F32(f32),
39 Bool(bool),
40 String(String),
41 Strings(Vec<String>),
42 F32s(Vec<f32>),
43 I32s(Vec<i32>),
44}
45
46#[derive(Debug, Clone)]
47struct TensorInfo {
48 name: String,
49 dims: Vec<usize>, ggml_type: u32,
51 offset: usize, }
53
54pub struct GgufSource {
56 path: PathBuf,
57 mmap: Mmap,
58 metadata: ModelMetadata,
59 kv: HashMap<String, MetaValue>,
60 tensors: HashMap<String, TensorInfo>,
61 data_start: usize,
62 tokenizer_json: PathBuf,
63 added_tokens: HashMap<u32, String>,
64 eos_ids: Vec<u32>,
65 bos_id: Option<u32>,
66}
67
68struct Cursor<'a> {
72 buf: &'a [u8],
73 pos: usize,
74}
75
76impl<'a> Cursor<'a> {
77 fn new(buf: &'a [u8]) -> Self {
78 Cursor { buf, pos: 0 }
79 }
80 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
81 if self.pos + n > self.buf.len() {
82 return Err(FormatError::Safetensors("gguf: unexpected end of file".into()));
83 }
84 let out = &self.buf[self.pos..self.pos + n];
85 self.pos += n;
86 Ok(out)
87 }
88 fn u8(&mut self) -> Result<u8> {
89 Ok(self.take(1)?[0])
90 }
91 fn u16(&mut self) -> Result<u16> {
92 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
93 }
94 fn u32(&mut self) -> Result<u32> {
95 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
96 }
97 fn i32(&mut self) -> Result<i32> {
98 Ok(i32::from_le_bytes(self.take(4)?.try_into().unwrap()))
99 }
100 fn u64(&mut self) -> Result<u64> {
101 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
102 }
103 fn i64(&mut self) -> Result<i64> {
104 Ok(i64::from_le_bytes(self.take(8)?.try_into().unwrap()))
105 }
106 fn f32(&mut self) -> Result<f32> {
107 Ok(f32::from_le_bytes(self.take(4)?.try_into().unwrap()))
108 }
109 fn string(&mut self) -> Result<String> {
110 let len = self.u64()? as usize;
111 let bytes = self.take(len)?;
112 String::from_utf8(bytes.to_vec())
113 .map_err(|e| FormatError::Safetensors(format!("gguf: bad utf8 string: {e}")))
114 }
115}
116
117const META_U8: u32 = 0;
118const META_I8: u32 = 1;
119const META_U16: u32 = 2;
120const META_I16: u32 = 3;
121const META_U32: u32 = 4;
122const META_I32: u32 = 5;
123const META_F32: u32 = 6;
124const META_BOOL: u32 = 7;
125const META_STRING: u32 = 8;
126const META_ARRAY: u32 = 9;
127const META_U64: u32 = 10;
128const META_I64: u32 = 11;
129const META_F64: u32 = 12;
130
131fn read_meta_value(c: &mut Cursor, ty: u32) -> Result<MetaValue> {
132 Ok(match ty {
133 META_U8 => MetaValue::U32(c.u8()? as u32),
134 META_I8 => MetaValue::I32(c.u8()? as i8 as i32),
135 META_U16 => MetaValue::U32(c.u16()? as u32),
136 META_I16 => MetaValue::I32(c.u16()? as i16 as i32),
137 META_U32 => MetaValue::U32(c.u32()?),
138 META_I32 => MetaValue::I32(c.i32()?),
139 META_U64 => MetaValue::U64(c.u64()?),
140 META_I64 => MetaValue::U64(c.i64()? as u64),
141 META_F32 => MetaValue::F32(c.f32()?),
142 META_F64 => MetaValue::F32(f64::from_le_bytes(c.take(8)?.try_into().unwrap()) as f32),
143 META_BOOL => MetaValue::Bool(c.u8()? != 0),
144 META_STRING => MetaValue::String(c.string()?),
145 META_ARRAY => {
146 let elem_ty = c.u32()?;
147 let len = c.u64()? as usize;
148 match elem_ty {
149 META_STRING => {
150 let mut out = Vec::with_capacity(len);
151 for _ in 0..len {
152 out.push(c.string()?);
153 }
154 MetaValue::Strings(out)
155 }
156 META_F32 => {
157 let mut out = Vec::with_capacity(len);
158 for _ in 0..len {
159 out.push(c.f32()?);
160 }
161 MetaValue::F32s(out)
162 }
163 META_I32 | META_I16 | META_I8 => {
164 let mut out = Vec::with_capacity(len);
165 for _ in 0..len {
166 out.push(read_meta_value(c, elem_ty).map(|v| match v {
167 MetaValue::I32(i) => i,
168 _ => 0,
169 })?);
170 }
171 MetaValue::I32s(out)
172 }
173 META_U32 | META_U16 | META_U8 => {
174 let mut out = Vec::with_capacity(len);
175 for _ in 0..len {
176 out.push(read_meta_value(c, elem_ty).map(|v| match v {
177 MetaValue::U32(u) => u as i32,
178 _ => 0,
179 })?);
180 }
181 MetaValue::I32s(out)
182 }
183 other => {
184 return Err(FormatError::Safetensors(format!(
185 "gguf: unsupported metadata array element type {other}"
186 )));
187 }
188 }
189 }
190 other => {
191 return Err(FormatError::Safetensors(format!(
192 "gguf: unsupported metadata type {other}"
193 )));
194 }
195 })
196}
197
198const GGML_F32: u32 = 0;
200const GGML_F16: u32 = 1;
201const GGML_Q4_0: u32 = 2;
202const GGML_Q4_1: u32 = 3;
203const GGML_Q5_0: u32 = 6;
204const GGML_Q5_1: u32 = 7;
205const GGML_Q8_0: u32 = 8;
206const GGML_Q4_K: u32 = 12;
207const GGML_Q5_K: u32 = 13;
208const GGML_Q6_K: u32 = 14;
209const GGML_BF16: u32 = 30;
210
211impl GgufSource {
212 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
214 let path = path.as_ref().to_path_buf();
215 let file = std::fs::File::open(&path)?;
216 let mmap = unsafe { Mmap::map(&file)? };
217 let mut c = Cursor::new(&mmap);
218
219 if c.u32()? != GGUF_MAGIC {
220 return Err(FormatError::Safetensors("gguf: bad magic".into()));
221 }
222 let version = c.u32()?;
223 if !(2..=3).contains(&version) {
224 return Err(FormatError::Safetensors(format!(
225 "gguf: unsupported version {version}"
226 )));
227 }
228 let tensor_count = c.u64()? as usize;
229 let kv_count = c.u64()? as usize;
230
231 let mut kv = HashMap::with_capacity(kv_count);
232 for _ in 0..kv_count {
233 let key = c.string()?;
234 let ty = c.u32()?;
235 let value = read_meta_value(&mut c, ty)?;
236 kv.insert(key, value);
237 }
238
239 let mut tensors = HashMap::with_capacity(tensor_count);
240 for _ in 0..tensor_count {
241 let name = c.string()?;
242 let n_dims = c.u32()? as usize;
243 let mut dims = Vec::with_capacity(n_dims);
244 for _ in 0..n_dims {
245 dims.push(c.u64()? as usize);
246 }
247 let ggml_type = c.u32()?;
248 let offset = c.u64()? as usize;
249 tensors.insert(name.clone(), TensorInfo { name, dims, ggml_type, offset });
250 }
251
252 let alignment = match kv.get("general.alignment") {
254 Some(MetaValue::U32(a)) => *a as usize,
255 _ => 32,
256 };
257 let data_start = c.pos.div_ceil(alignment) * alignment;
258
259 if let Some(MetaValue::U32(count)) = kv.get("split.count") {
263 if *count > 1 {
264 let no = match kv.get("split.no") {
265 Some(MetaValue::U32(n)) => *n + 1,
266 _ => 1,
267 };
268 return Err(FormatError::Safetensors(format!(
269 "split GGUF: this file is shard {no} of {count} — \
270 multi-file GGUF loading is not supported yet; pull a \
271 single-file quant or merge the shards with llama.cpp's \
272 `llama-gguf-split --merge`"
273 )));
274 }
275 }
276
277 let metadata = build_model_metadata(&kv)?;
278 let (eos_ids, bos_id, added_tokens) = tokenizer_ids(&kv);
279 let tokenizer_json = ensure_tokenizer_json(&path, &kv)?;
280
281 let mut source = GgufSource {
282 path,
283 mmap,
284 metadata,
285 kv,
286 tensors,
287 data_start,
288 tokenizer_json,
289 added_tokens,
290 eos_ids,
291 bos_id,
292 };
293 source.metadata.tie_word_embeddings = !source.tensors.contains_key("output.weight");
296
297 let arch = source.metadata.architecture.clone();
300 let mut unmapped: Vec<&str> = source
301 .tensors
302 .keys()
303 .filter(|k| {
304 map_tensor_name(k, &arch).is_none()
305 && !KNOWN_UNMAPPED.contains(&k.as_str())
306 && !is_fused_source(k, &arch)
307 })
308 .map(String::as_str)
309 .collect();
310 if !unmapped.is_empty() {
311 unmapped.sort();
312 eprintln!(
313 "[gguf] {} unmapped tensors (arch {arch}); first: {}",
314 unmapped.len(),
315 unmapped[0]
316 );
317 }
318 Ok(source)
319 }
320
321 fn kv_u64(&self, key: &str) -> Option<u64> {
322 match self.kv.get(key) {
323 Some(MetaValue::U32(v)) => Some(*v as u64),
324 Some(MetaValue::U64(v)) => Some(*v),
325 Some(MetaValue::I32(v)) => Some(*v as u64),
326 _ => None,
327 }
328 }
329}
330
331fn build_model_metadata(kv: &HashMap<String, MetaValue>) -> Result<ModelMetadata> {
332 let get_u64 = |key: &str| -> Option<u64> {
333 match kv.get(key) {
334 Some(MetaValue::U32(v)) => Some(*v as u64),
335 Some(MetaValue::U64(v)) => Some(*v),
336 Some(MetaValue::I32(v)) => Some(*v as u64),
337 _ => None,
338 }
339 };
340 let get_f32 = |key: &str| -> Option<f32> {
341 match kv.get(key) {
342 Some(MetaValue::F32(v)) => Some(*v),
343 Some(MetaValue::U32(v)) => Some(*v as f32),
344 _ => None,
345 }
346 };
347 let get_str = |key: &str| -> Option<String> {
348 match kv.get(key) {
349 Some(MetaValue::String(s)) => Some(s.clone()),
350 _ => None,
351 }
352 };
353
354 let arch = get_str("general.architecture")
355 .ok_or_else(|| FormatError::MissingField("general.architecture".into()))?;
356 let prefix = arch.clone();
357 let field = |name: &str| get_u64(&format!("{prefix}.{name}"));
358
359 let hidden = field("embedding_length")
360 .ok_or_else(|| FormatError::MissingField("embedding_length".into()))? as usize;
361 let heads = field("attention.head_count")
362 .ok_or_else(|| FormatError::MissingField("attention.head_count".into()))?
363 as usize;
364 let kv_heads = field("attention.head_count_kv").unwrap_or(heads as u64) as usize;
365 let layers = field("block_count")
366 .ok_or_else(|| FormatError::MissingField("block_count".into()))? as usize;
367 let ctx = field("context_length").unwrap_or(2048) as usize;
368 let ffn = field("feed_forward_length").unwrap_or((hidden * 4) as u64) as usize;
369 let vocab = match kv.get(&format!("{prefix}.vocab_size")) {
370 Some(MetaValue::U32(v)) => *v as usize,
371 Some(MetaValue::U64(v)) => *v as usize,
372 _ => match kv.get("tokenizer.ggml.tokens") {
373 Some(MetaValue::Strings(t)) => t.len(),
374 _ => 0,
375 },
376 };
377 let (eos_ids, bos_id, _) = tokenizer_ids(kv);
378
379 Ok(ModelMetadata {
380 architecture: arch,
381 hidden_size: hidden,
382 intermediate_size: ffn,
383 num_hidden_layers: layers,
384 num_attention_heads: heads,
385 num_key_value_heads: kv_heads,
386 vocab_size: vocab,
387 max_position_embeddings: ctx,
388 rms_norm_eps: get_f32(&format!("{prefix}.attention.layer_norm_rms_epsilon"))
389 .unwrap_or(1e-5) as f64,
390 rope_theta: get_f32(&format!("{prefix}.rope.freq_base")).unwrap_or(10000.0) as f64,
391 tie_word_embeddings: false, head_dim: field("attention.key_length")
396 .map(|v| v as usize)
397 .unwrap_or(hidden / heads),
398 attention_bias: false,
401 bos_token_id: bos_id,
402 eos_token_ids: eos_ids,
403 vision: None,
404 attention_pattern: crate::metadata::AttentionPattern {
408 sliding_window: field("attention.sliding_window").map(|v| v as usize),
409 ..Default::default()
410 },
411 activation: crate::metadata::Activation::default(),
413 rope_scaling: gguf_rope_scaling(kv, &prefix)?,
414 })
415}
416
417fn gguf_rope_scaling(
421 kv: &HashMap<String, MetaValue>,
422 prefix: &str,
423) -> Result<crate::metadata::RopeScaling> {
424 use crate::metadata::RopeScaling;
425 let get_f32 = |key: String| match kv.get(&key) {
426 Some(MetaValue::F32(v)) => Some(*v as f64),
427 _ => None,
428 };
429 let kind = match kv.get(&format!("{prefix}.rope.scaling.type")) {
430 Some(MetaValue::String(s)) => s.clone(),
431 _ => return Ok(RopeScaling::None),
432 };
433 let factor = get_f32(format!("{prefix}.rope.scaling.factor")).unwrap_or(1.0);
434 let orig = match kv.get(&format!("{prefix}.rope.scaling.original_context_length")) {
435 Some(MetaValue::U32(v)) => *v as usize,
436 Some(MetaValue::U64(v)) => *v as usize,
437 _ => 32768,
438 };
439 match kind.as_str() {
440 "none" => Ok(RopeScaling::None),
441 "linear" => Ok(RopeScaling::Linear { factor }),
442 "yarn" => Ok(RopeScaling::Yarn {
443 factor,
444 original_max_position_embeddings: orig,
445 beta_fast: get_f32(format!("{prefix}.rope.scaling.yarn_beta_fast")).unwrap_or(32.0),
446 beta_slow: get_f32(format!("{prefix}.rope.scaling.yarn_beta_slow")).unwrap_or(1.0),
447 attention_factor: None,
448 }),
449 other => Err(FormatError::MissingField(format!(
450 "unsupported GGUF rope scaling type {other:?}"
451 ))),
452 }
453}
454
455const EOG_TOKENS: &[&str] = &[
460 "<|end|>",
461 "<|eot_id|>",
462 "<|eom_id|>",
463 "<|im_end|>",
464 "<end_of_turn>",
465 "<|end_of_text|>",
466 "<|endoftext|>",
467 "<EOT>",
468];
469
470fn tokenizer_ids(kv: &HashMap<String, MetaValue>) -> (Vec<u32>, Option<u32>, HashMap<u32, String>) {
471 let mut eos = Vec::new();
472 let mut bos = None;
473 let mut added = HashMap::new();
474 if let Some(MetaValue::U32(v)) = kv.get("tokenizer.ggml.eos_token_id") {
475 eos.push(*v);
476 }
477 if let Some(MetaValue::U32(v)) = kv.get("tokenizer.ggml.bos_token_id") {
478 bos = Some(*v);
479 }
480 if let (Some(MetaValue::Strings(tokens)), Some(MetaValue::I32s(types))) =
482 (kv.get("tokenizer.ggml.tokens"), kv.get("tokenizer.ggml.token_type"))
483 {
484 for (i, (tok, ty)) in tokens.iter().zip(types.iter()).enumerate() {
485 if *ty == 3 {
486 added.insert(i as u32, tok.clone());
487 }
488 if *ty == 3 && EOG_TOKENS.contains(&tok.as_str()) && !eos.contains(&(i as u32)) {
489 eos.push(i as u32);
490 }
491 }
492 }
493 (eos, bos, added)
494}
495
496fn special_token_count(kv: &HashMap<String, MetaValue>) -> usize {
498 match kv.get("tokenizer.ggml.token_type") {
499 Some(MetaValue::I32s(types)) => types.iter().filter(|t| **t == 3 || **t == 4).count(),
500 _ => 0,
501 }
502}
503
504fn pretokenizer_regex(kv: &HashMap<String, MetaValue>) -> &'static str {
509 const DEFAULT: &str = "(?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+";
510 const QWEN2: &str = "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+";
511 match kv.get("tokenizer.ggml.pre") {
512 Some(MetaValue::String(pre)) if pre == "qwen2" => QWEN2,
513 _ => DEFAULT,
514 }
515}
516
517fn ensure_tokenizer_json(path: &Path, kv: &HashMap<String, MetaValue>) -> Result<PathBuf> {
527 let sibling = path.with_file_name("tokenizer.json");
528 if sibling.exists() {
529 return Ok(sibling);
530 }
531 let cached = path.with_extension("tokenizer.json");
532 if cached.exists() {
533 let parsed = std::fs::read_to_string(&cached)
534 .ok()
535 .and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok());
536 let have_added = parsed
537 .as_ref()
538 .and_then(|v| v.get("added_tokens")?.as_array().map(Vec::len));
539 let have_regex = parsed.as_ref().and_then(|v| {
542 v.get("pre_tokenizer")?
543 .get("pretokenizers")?
544 .get(0)?
545 .get("pattern")?
546 .get("Regex")?
547 .as_str()
548 .map(str::to_string)
549 });
550 if have_added == Some(special_token_count(kv))
551 && have_regex.as_deref() == Some(pretokenizer_regex(kv))
552 {
553 return Ok(cached);
554 }
555 }
557
558 let tokens = match kv.get("tokenizer.ggml.tokens") {
559 Some(MetaValue::Strings(t)) => t,
560 _ => {
561 return Err(FormatError::MissingField(
562 "tokenizer.ggml.tokens (and no sibling tokenizer.json)".into(),
563 ));
564 }
565 };
566 let scores: Vec<f32> = match kv.get("tokenizer.ggml.scores") {
567 Some(MetaValue::F32s(s)) => s.clone(),
568 _ => vec![0.0; tokens.len()],
569 };
570 let merges: Vec<String> = match kv.get("tokenizer.ggml.merges") {
571 Some(MetaValue::Strings(m)) => m.clone(),
572 _ => vec![],
573 };
574
575 let mut vocab = serde_json::Map::new();
576 for (i, tok) in tokens.iter().enumerate() {
577 vocab.insert(tok.clone(), serde_json::Value::from(i));
578 }
579 let mut ordered: Vec<usize> = (0..tokens.len()).collect();
580 ordered.sort_by(|&a, &b| {
581 scores[a].partial_cmp(&scores[b]).unwrap_or(std::cmp::Ordering::Equal)
582 });
583 let mut ranks = serde_json::Map::new();
584 for (rank, id) in ordered.iter().enumerate() {
585 ranks.insert(id.to_string(), serde_json::Value::from(rank));
586 }
587
588 let mut added_tokens = Vec::new();
592 if let Some(MetaValue::I32s(types)) = kv.get("tokenizer.ggml.token_type") {
593 for (i, (tok, ty)) in tokens.iter().zip(types.iter()).enumerate() {
594 if *ty == 3 || *ty == 4 {
595 added_tokens.push(serde_json::json!({
596 "id": i,
597 "content": tok,
598 "single_word": false,
599 "lstrip": false,
600 "rstrip": false,
601 "normalized": false,
602 "special": true,
603 }));
604 }
605 }
606 }
607
608 let json = serde_json::json!({
609 "version": "1.0",
610 "truncation": null,
611 "padding": null,
612 "added_tokens": added_tokens,
613 "normalizer": null,
614 "pre_tokenizer": {
615 "type": "Sequence",
616 "pretokenizers": [
617 {"type": "Split", "pattern": {"Regex": pretokenizer_regex(kv)}, "behavior": "Isolated", "invert": false},
618 {"type": "ByteLevel", "add_prefix_space": false, "trim_offsets": true, "use_regex": false}
619 ]
620 },
621 "post_processor": null,
622 "decoder": {"type": "ByteLevel", "add_prefix_space": true, "trim_offsets": true, "use_regex": true},
623 "model": {
624 "type": "BPE",
625 "dropout": null,
626 "unk_token": null,
627 "continuing_subword_prefix": null,
628 "end_of_word_suffix": null,
629 "fuse_unk": false,
630 "byte_fallback": false,
631 "vocab": vocab,
632 "merges": merges,
633 }
634 });
635 let serialized = serde_json::to_string(&json).map_err(|e| FormatError::Safetensors(format!("tokenizer json: {e}")))?;
636 std::fs::write(&cached, serialized)?;
637 Ok(cached)
638}
639
640fn map_tensor_name(ggml: &str, arch: &str) -> Option<String> {
646 if ggml == "token_embd.weight" {
647 return Some("model.embed_tokens.weight".into());
648 }
649 if ggml == "output.weight" {
650 return Some("lm_head.weight".into());
651 }
652 if ggml == "output_norm.weight" {
653 return Some("model.norm.weight".into());
654 }
655 let rest = ggml.strip_prefix("blk.")?;
656 let (layer, rest) = rest.split_once('.')?;
657 let gemma = matches!(arch, "gemma3" | "gemma3_text");
658 let hf = match rest {
659 "attn_norm.weight" => "input_layernorm.weight",
660 "ffn_norm.weight" if gemma => "pre_feedforward_layernorm.weight",
661 "post_attention_norm.weight" if gemma => "post_attention_layernorm.weight",
662 "post_ffw_norm.weight" if gemma => "post_feedforward_layernorm.weight",
663 "ffn_norm.weight" => "post_attention_layernorm.weight",
664 "attn_q_norm.weight" => "self_attn.q_norm.weight",
667 "attn_k_norm.weight" => "self_attn.k_norm.weight",
668 "attn_q.weight" => "self_attn.q_proj.weight",
669 "attn_k.weight" => "self_attn.k_proj.weight",
670 "attn_v.weight" => "self_attn.v_proj.weight",
671 "attn_output.weight" => "self_attn.o_proj.weight",
672 "attn_q.bias" => "self_attn.q_proj.bias",
673 "attn_k.bias" => "self_attn.k_proj.bias",
674 "attn_v.bias" => "self_attn.v_proj.bias",
675 "attn_output.bias" => "self_attn.o_proj.bias",
676 "ffn_gate.weight" => "mlp.gate_proj.weight",
677 "ffn_up.weight" => "mlp.up_proj.weight",
678 "ffn_down.weight" => "mlp.down_proj.weight",
679 _ => return None,
680 };
681 Some(format!("model.layers.{layer}.{hf}"))
682}
683
684const KNOWN_UNMAPPED: &[&str] = &[
687 "rope_freqs.weight",
690];
691
692fn is_fused_source(ggml: &str, arch: &str) -> bool {
696 arch == "phi3"
697 && ggml
698 .strip_prefix("blk.")
699 .and_then(|r| r.split_once('.'))
700 .is_some_and(|(_, rest)| rest == "attn_qkv.weight" || rest == "attn_qkv.bias")
701}
702
703fn num_elements(dims: &[usize]) -> usize {
705 dims.iter().product()
706}
707
708fn tensor_byte_size(info: &TensorInfo) -> Result<usize> {
710 let n = num_elements(&info.dims);
711 let block = |bs: usize| -> Result<usize> {
712 if n % 32 != 0 {
713 return Err(FormatError::Safetensors(format!(
714 "gguf tensor {}: {n} elements not divisible by block size 32",
715 info.name
716 )));
717 }
718 Ok(n / 32 * bs)
719 };
720 let superblock = |bs: usize| -> Result<usize> {
721 if n % 256 != 0 {
722 return Err(FormatError::Safetensors(format!(
723 "gguf tensor {}: {n} elements not divisible by K-quant superblock 256",
724 info.name
725 )));
726 }
727 Ok(n / 256 * bs)
728 };
729 Ok(match info.ggml_type {
730 GGML_F32 => n * 4,
731 GGML_F16 | GGML_BF16 => n * 2,
732 GGML_Q4_0 => block(18)?, GGML_Q4_1 => block(20)?, GGML_Q5_0 => block(22)?, GGML_Q5_1 => block(24)?, GGML_Q8_0 => block(34)?, GGML_Q4_K => superblock(144)?, GGML_Q5_K => superblock(176)?, GGML_Q6_K => superblock(210)?, other => {
742 return Err(FormatError::UnsupportedDtype {
743 tensor: info.name.clone(),
744 dtype: format!("ggml_type {other}"),
745 });
746 }
747 })
748}
749
750pub fn dequantize_q4_0(data: &[u8], n: usize) -> Result<Vec<f32>> {
756 let mut fixed = Vec::with_capacity(n);
757 for block in data.chunks_exact(18) {
758 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
759 let mut vals = [0f32; 32];
760 for j in 0..16 {
761 let byte = block[2 + j];
762 vals[j] = ((byte & 0x0F) as i32 - 8) as f32 * d;
763 vals[j + 16] = ((byte >> 4) as i32 - 8) as f32 * d;
764 }
765 fixed.extend_from_slice(&vals);
766 }
767 if fixed.len() != n {
768 return Err(FormatError::Safetensors(format!(
769 "q4_0 dequant size mismatch: {} != {n}",
770 fixed.len()
771 )));
772 }
773 Ok(fixed)
774}
775
776pub fn dequantize_q8_0(data: &[u8], n: usize) -> Result<Vec<f32>> {
780 let mut out = Vec::with_capacity(n);
781 for block in data.chunks_exact(34) {
782 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
783 for &b in &block[2..34] {
784 out.push((b as i8) as f32 * d);
785 }
786 }
787 if out.len() != n {
788 return Err(FormatError::Safetensors(format!(
789 "q8_0 dequant size mismatch: {} != {n}",
790 out.len()
791 )));
792 }
793 Ok(out)
794}
795
796fn dequantize_q4_1(data: &[u8], n: usize) -> Result<Vec<f32>> {
799 let mut out = Vec::with_capacity(n);
800 for block in data.chunks_exact(20) {
801 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
802 let m = half::f16::from_le_bytes([block[2], block[3]]).to_f32();
803 let mut vals = [0f32; 32];
804 for j in 0..16 {
805 let byte = block[4 + j];
806 vals[j] = (byte & 0x0F) as f32 * d + m;
807 vals[j + 16] = (byte >> 4) as f32 * d + m;
808 }
809 out.extend_from_slice(&vals);
810 }
811 if out.len() != n {
812 return Err(FormatError::Safetensors(format!(
813 "q4_1 dequant size mismatch: {} != {n}",
814 out.len()
815 )));
816 }
817 Ok(out)
818}
819
820pub fn dequantize_q5_0(data: &[u8], n: usize) -> Result<Vec<f32>> {
826 let mut out = Vec::with_capacity(n);
827 for block in data.chunks_exact(22) {
828 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
829 let qh = u32::from_le_bytes([block[2], block[3], block[4], block[5]]);
830 let mut vals = [0f32; 32];
831 for j in 0..16 {
832 let byte = block[6 + j];
833 let lo = ((byte & 0x0F) as u32 | (((qh >> j) & 1) << 4)) as i32 - 16;
834 let hi = ((byte >> 4) as u32 | (((qh >> (j + 16)) & 1) << 4)) as i32 - 16;
835 vals[j] = lo as f32 * d;
836 vals[j + 16] = hi as f32 * d;
837 }
838 out.extend_from_slice(&vals);
839 }
840 if out.len() != n {
841 return Err(FormatError::Safetensors(format!(
842 "q5_0 dequant size mismatch: {} != {n}",
843 out.len()
844 )));
845 }
846 Ok(out)
847}
848
849fn dequantize_q5_1(data: &[u8], n: usize) -> Result<Vec<f32>> {
852 let mut out = Vec::with_capacity(n);
853 for block in data.chunks_exact(24) {
854 let d = half::f16::from_le_bytes([block[0], block[1]]).to_f32();
855 let m = half::f16::from_le_bytes([block[2], block[3]]).to_f32();
856 let qh = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
857 let mut vals = [0f32; 32];
858 for j in 0..16 {
859 let byte = block[8 + j];
860 let lo = (byte & 0x0F) as u32 | (((qh >> j) & 1) << 4);
861 let hi = (byte >> 4) as u32 | (((qh >> (j + 16)) & 1) << 4);
862 vals[j] = lo as f32 * d + m;
863 vals[j + 16] = hi as f32 * d + m;
864 }
865 out.extend_from_slice(&vals);
866 }
867 if out.len() != n {
868 return Err(FormatError::Safetensors(format!(
869 "q5_1 dequant size mismatch: {} != {n}",
870 out.len()
871 )));
872 }
873 Ok(out)
874}
875
876fn scale_min_k4(j: usize, q: &[u8]) -> (u8, u8) {
880 if j < 4 {
881 (q[j] & 63, q[j + 4] & 63)
882 } else {
883 (
884 (q[j + 4] & 0x0F) | ((q[j - 4] >> 6) << 4),
885 (q[j + 4] >> 4) | ((q[j] >> 6) << 4),
886 )
887 }
888}
889
890pub fn dequantize_q4_k(data: &[u8], n: usize) -> Result<Vec<f32>> {
895 let mut out = Vec::with_capacity(n);
896 for sb in data.chunks_exact(144) {
897 let d = half::f16::from_le_bytes([sb[0], sb[1]]).to_f32();
898 let dmin = half::f16::from_le_bytes([sb[2], sb[3]]).to_f32();
899 let scales = &sb[4..16];
900 let qs = &sb[16..144];
901 for j in 0..4 {
902 let (sc1, m1) = scale_min_k4(2 * j, scales);
905 let (sc2, m2) = scale_min_k4(2 * j + 1, scales);
906 let (d1, fmin1) = (d * sc1 as f32, dmin * m1 as f32);
907 let (d2, fmin2) = (d * sc2 as f32, dmin * m2 as f32);
908 for l in 0..32 {
909 let byte = qs[32 * j + l];
910 out.push(d1 * (byte & 0x0F) as f32 - fmin1);
911 }
912 for l in 0..32 {
913 let byte = qs[32 * j + l];
914 out.push(d2 * (byte >> 4) as f32 - fmin2);
915 }
916 }
917 }
918 if out.len() != n {
919 return Err(FormatError::Safetensors(format!(
920 "q4_k dequant size mismatch: {} != {n}",
921 out.len()
922 )));
923 }
924 Ok(out)
925}
926
927pub fn dequantize_q5_k(data: &[u8], n: usize) -> Result<Vec<f32>> {
930 let mut out = Vec::with_capacity(n);
931 for sb in data.chunks_exact(176) {
932 let d = half::f16::from_le_bytes([sb[0], sb[1]]).to_f32();
933 let dmin = half::f16::from_le_bytes([sb[2], sb[3]]).to_f32();
934 let scales = &sb[4..16];
935 let qh = &sb[16..48];
936 let qs = &sb[48..176];
937 for j in 0..4 {
938 let (sc1, m1) = scale_min_k4(2 * j, scales);
939 let (sc2, m2) = scale_min_k4(2 * j + 1, scales);
940 let (d1, fmin1) = (d * sc1 as f32, dmin * m1 as f32);
941 let (d2, fmin2) = (d * sc2 as f32, dmin * m2 as f32);
942 for l in 0..32 {
943 let lo = (qs[32 * j + l] & 0x0F) as u32;
944 let hi = ((qh[l] >> (2 * j)) & 1) as u32;
945 out.push(d1 * ((lo | (hi << 4)) as f32) - fmin1);
946 }
947 for l in 0..32 {
948 let lo = (qs[32 * j + l] >> 4) as u32;
949 let hi = ((qh[l] >> (2 * j + 1)) & 1) as u32;
950 out.push(d2 * ((lo | (hi << 4)) as f32) - fmin2);
951 }
952 }
953 }
954 if out.len() != n {
955 return Err(FormatError::Safetensors(format!(
956 "q5_k dequant size mismatch: {} != {n}",
957 out.len()
958 )));
959 }
960 Ok(out)
961}
962
963pub fn dequantize_q6_k(data: &[u8], n: usize) -> Result<Vec<f32>> {
969 let mut out = Vec::with_capacity(n);
970 for sb in data.chunks_exact(210) {
971 let d = half::f16::from_le_bytes([sb[208], sb[209]]).to_f32();
972 for half_idx in 0..2 {
974 let ql = &sb[64 * half_idx..64 * half_idx + 64];
975 let qh = &sb[128 + 32 * half_idx..128 + 32 * half_idx + 32];
976 let scales = &sb[192 + 8 * half_idx..192 + 8 * half_idx + 8];
977 let mut vals = [0f32; 128];
978 for l in 0..32 {
979 let is = l / 16;
980 let q1 = ((ql[l] & 0x0F) | ((qh[l] & 0x03) << 4)) as i8 as i32 - 32;
981 let q2 = ((ql[l + 32] & 0x0F) | ((qh[l] & 0x0C) >> 2 << 4)) as i8 as i32 - 32;
982 let q3 = ((ql[l] >> 4) | ((qh[l] & 0x30) >> 4 << 4)) as i8 as i32 - 32;
983 let q4 = ((ql[l + 32] >> 4) | ((qh[l] & 0xC0) >> 6 << 4)) as i8 as i32 - 32;
984 vals[l] = d * (scales[is] as i8) as f32 * q1 as f32;
985 vals[l + 32] = d * (scales[is + 2] as i8) as f32 * q2 as f32;
986 vals[l + 64] = d * (scales[is + 4] as i8) as f32 * q3 as f32;
987 vals[l + 96] = d * (scales[is + 6] as i8) as f32 * q4 as f32;
988 }
989 out.extend_from_slice(&vals);
990 }
991 }
992 if out.len() != n {
993 return Err(FormatError::Safetensors(format!(
994 "q6_k dequant size mismatch: {} != {n}",
995 out.len()
996 )));
997 }
998 Ok(out)
999}
1000
1001fn rope_depermute_src_rows(rows: usize, n_head: usize) -> Vec<usize> {
1010 let d = rows / n_head;
1011 let half = d / 2;
1012 let mut map = Vec::with_capacity(rows);
1013 for h in 0..n_head {
1014 let base = h * d;
1015 for j in 0..d {
1016 let src = if j < half { 2 * j } else { 2 * (j - half) + 1 };
1017 map.push(base + src);
1018 }
1019 }
1020 map
1021}
1022
1023fn depermute_rows_f32(values: Vec<f32>, rows: usize, n_head: usize) -> Vec<f32> {
1025 let row_len = values.len() / rows;
1026 let map = rope_depermute_src_rows(rows, n_head);
1027 let mut out = Vec::with_capacity(values.len());
1028 for src in map {
1029 out.extend_from_slice(&values[src * row_len..(src + 1) * row_len]);
1030 }
1031 out
1032}
1033
1034impl GgufSource {
1035 fn fused_slice(&self, name: &str) -> Option<(String, usize, usize)> {
1043 if self.metadata.architecture != "phi3" {
1044 return None;
1045 }
1046 let m = &self.metadata;
1047 let rest = name.strip_prefix("model.layers.")?;
1048 let (layer, rest) = rest.split_once('.')?;
1049 let q_rows = m.num_attention_heads * m.head_dim;
1050 let kv_rows = m.num_key_value_heads * m.head_dim;
1051 let ffn = m.intermediate_size;
1052 let (fused, start, len) = match rest {
1053 "self_attn.q_proj.weight" => ("attn_qkv.weight", 0, q_rows),
1054 "self_attn.k_proj.weight" => ("attn_qkv.weight", q_rows, kv_rows),
1055 "self_attn.v_proj.weight" => ("attn_qkv.weight", q_rows + kv_rows, kv_rows),
1056 "mlp.gate_proj.weight" => ("ffn_up.weight", 0, ffn),
1057 "mlp.up_proj.weight" => ("ffn_up.weight", ffn, ffn),
1058 _ => return None,
1059 };
1060 Some((format!("blk.{layer}.{fused}"), start, len))
1061 }
1062
1063 fn resolve_tensor(&self, name: &str) -> Option<(&String, &TensorInfo, Option<(usize, usize)>)> {
1067 if let Some((fused, start, len)) = self.fused_slice(name) {
1068 if let Some((k, info)) = self.tensors.get_key_value(&fused) {
1069 return Some((k, info, Some((start, len))));
1070 }
1071 }
1072 let arch = self.metadata.architecture.as_str();
1073 self.tensors
1074 .iter()
1075 .find(|(k, _)| map_tensor_name(k, arch).as_deref() == Some(name))
1076 .map(|(k, info)| (k, info, None))
1077 }
1078
1079 fn depermute_heads(&self, ggml_name: &str) -> Option<usize> {
1083 if !matches!(self.metadata.architecture.as_str(), "llama" | "mistral") {
1084 return None;
1085 }
1086 let rest = ggml_name.strip_prefix("blk.")?;
1087 let (_, rest) = rest.split_once('.')?;
1088 match rest {
1089 "attn_q.weight" | "attn_q.bias" => Some(self.metadata.num_attention_heads),
1090 "attn_k.weight" | "attn_k.bias" => Some(self.metadata.num_key_value_heads),
1091 _ => None,
1092 }
1093 }
1094}
1095
1096impl ModelSource for GgufSource {
1097 fn metadata(&self) -> &ModelMetadata {
1098 &self.metadata
1099 }
1100
1101 fn tensor_names(&self) -> Vec<String> {
1102 let arch = self.metadata.architecture.as_str();
1103 self.tensors
1104 .keys()
1105 .filter_map(|k| map_tensor_name(k, arch))
1106 .collect()
1107 }
1108
1109 fn open_tensor(&self, name: &str) -> Result<TensorReader<'_>> {
1110 let (ggml_name, info, slice) = self
1113 .resolve_tensor(name)
1114 .ok_or_else(|| FormatError::TensorNotFound(name.to_string()))?;
1115
1116 let size = tensor_byte_size(info)?;
1117 let start = self.data_start + info.offset;
1118 let data = self
1119 .mmap
1120 .get(start..start + size)
1121 .ok_or_else(|| FormatError::Safetensors(format!("gguf tensor {} out of bounds", info.name)))?;
1122
1123 let mut shape: Vec<usize> = info.dims.iter().rev().copied().collect();
1125 let data = match slice {
1126 None => data,
1127 Some((row_start, row_len)) => {
1128 let rows_total = shape.first().copied().unwrap_or(1).max(1);
1129 if size % rows_total != 0 {
1130 return Err(FormatError::Safetensors(format!(
1131 "gguf tensor {}: rows not byte-addressable for fused split",
1132 info.name
1133 )));
1134 }
1135 let row_bytes = size / rows_total;
1136 shape[0] = row_len;
1137 &data[row_start * row_bytes..(row_start + row_len) * row_bytes]
1138 }
1139 };
1140 let n: usize = shape.iter().product();
1141 let rows = shape.first().copied().unwrap_or(1).max(1);
1142 let permute = self.depermute_heads(ggml_name);
1145 let gemma_norm_offset = matches!(
1152 self.metadata.architecture.as_str(),
1153 "gemma3" | "gemma3_text"
1154 ) && ggml_name.ends_with("norm.weight");
1155
1156 if gemma_norm_offset {
1159 if info.ggml_type != GGML_F32 {
1160 return Err(FormatError::UnsupportedDtype {
1161 tensor: info.name.clone(),
1162 dtype: format!("gemma norm must be F32, got ggml_type {}", info.ggml_type),
1163 });
1164 }
1165 let values: Vec<f32> = data
1166 .chunks_exact(4)
1167 .map(|b| f32::from_le_bytes(b.try_into().unwrap()) - 1.0)
1168 .collect();
1169 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
1170 return Ok(TensorReader::owned(name.to_string(), shape, bytes));
1171 }
1172 if let GGML_F32 | GGML_F16 | GGML_BF16 = info.ggml_type {
1173 let dtype = match info.ggml_type {
1174 GGML_F32 => TensorDtype::F32,
1175 GGML_F16 => TensorDtype::F16,
1176 _ => TensorDtype::BF16,
1177 };
1178 return Ok(match permute {
1179 None => TensorReader::new(name.to_string(), shape, dtype, data),
1180 Some(n_head) => {
1181 let row_bytes = size / rows;
1182 let map = rope_depermute_src_rows(rows, n_head);
1183 let mut out = Vec::with_capacity(size);
1184 for src in map {
1185 out.extend_from_slice(&data[src * row_bytes..(src + 1) * row_bytes]);
1186 }
1187 TensorReader::owned_with_dtype(name.to_string(), shape, dtype, out)
1188 }
1189 });
1190 }
1191
1192 let values = match info.ggml_type {
1193 GGML_Q4_0 => dequantize_q4_0(data, n)?,
1194 GGML_Q4_1 => dequantize_q4_1(data, n)?,
1195 GGML_Q5_0 => dequantize_q5_0(data, n)?,
1196 GGML_Q5_1 => dequantize_q5_1(data, n)?,
1197 GGML_Q8_0 => dequantize_q8_0(data, n)?,
1198 GGML_Q4_K => dequantize_q4_k(data, n)?,
1199 GGML_Q5_K => dequantize_q5_k(data, n)?,
1200 GGML_Q6_K => dequantize_q6_k(data, n)?,
1201 other => {
1202 return Err(FormatError::UnsupportedDtype {
1203 tensor: info.name.clone(),
1204 dtype: format!("ggml_type {other}"),
1205 });
1206 }
1207 };
1208 let values = match permute {
1209 Some(n_head) => depermute_rows_f32(values, rows, n_head),
1210 None => values,
1211 };
1212 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
1213 Ok(TensorReader::owned(name.to_string(), shape, bytes))
1214 }
1215
1216 fn tokenizer(&self) -> Result<TokenizerSpec> {
1217 let add_bos = match self.kv.get("tokenizer.ggml.add_bos_token") {
1218 Some(MetaValue::Bool(b)) => Some(*b),
1219 _ => None,
1220 };
1221 let chat_template = match self.kv.get("tokenizer.chat_template") {
1222 Some(MetaValue::String(t)) => Some(t.clone()),
1223 _ => None,
1224 };
1225 Ok(TokenizerSpec {
1226 tokenizer_json: self.tokenizer_json.clone(),
1227 added_tokens: self.added_tokens.clone(),
1228 chat_template,
1229 add_bos,
1230 })
1231 }
1232
1233 fn open_tensor_quant(&self, name: &str) -> Result<Option<crate::QuantTensor<'_>>> {
1234 let Some((ggml_name, info, slice)) = self.resolve_tensor(name) else {
1235 return Ok(None);
1236 };
1237 let format = match info.ggml_type {
1238 GGML_Q4_0 => crate::QuantFormat::Q4_0,
1239 GGML_Q5_0 => crate::QuantFormat::Q5_0,
1240 GGML_Q8_0 => crate::QuantFormat::Q8_0,
1241 GGML_Q4_K => crate::QuantFormat::Q4K,
1242 GGML_Q5_K => crate::QuantFormat::Q5K,
1243 GGML_Q6_K => crate::QuantFormat::Q6K,
1244 _ => return Ok(None),
1245 };
1246 let size = tensor_byte_size(info)?;
1247 let start = self.data_start + info.offset;
1248 let data = self.mmap.get(start..start + size).ok_or_else(|| {
1249 FormatError::Safetensors(format!("gguf tensor {} out of bounds", info.name))
1250 })?;
1251 let mut shape: Vec<usize> = info.dims.iter().rev().copied().collect();
1252 let data = match slice {
1255 None => data,
1256 Some((row_start, row_len)) => {
1257 let rows_total = shape.first().copied().unwrap_or(1).max(1);
1258 if size % rows_total != 0 {
1259 return Ok(None);
1260 }
1261 let row_bytes = size / rows_total;
1262 shape[0] = row_len;
1263 &data[row_start * row_bytes..(row_start + row_len) * row_bytes]
1264 }
1265 };
1266
1267 let data = match self.depermute_heads(ggml_name) {
1273 None => std::borrow::Cow::Borrowed(data),
1274 Some(n_head) => {
1275 let rows = shape.first().copied().unwrap_or(1).max(1);
1276 if size % rows != 0 {
1277 return Ok(None);
1278 }
1279 let row_bytes = size / rows;
1280 let map = rope_depermute_src_rows(rows, n_head);
1281 let mut out = Vec::with_capacity(size);
1282 for src in map {
1283 out.extend_from_slice(&data[src * row_bytes..(src + 1) * row_bytes]);
1284 }
1285 std::borrow::Cow::Owned(out)
1286 }
1287 };
1288 Ok(Some(crate::QuantTensor { format, shape, data }))
1289 }
1290
1291 fn sampler_defaults(&self) -> Option<SamplerConfig> {
1292 None
1293 }
1294}
1295
1296impl GgufSource {
1297 pub fn eos_token_ids(&self) -> &[u32] {
1299 &self.eos_ids
1300 }
1301
1302 pub fn path(&self) -> &Path {
1304 &self.path
1305 }
1306}
1307
1308#[cfg(test)]
1309mod depermute_tests {
1310 use super::rope_depermute_src_rows;
1311
1312 fn forward_permute(rows: &[Vec<u32>], n_head: usize) -> Vec<Vec<u32>> {
1315 let d = rows.len() / n_head;
1316 let mut out = Vec::with_capacity(rows.len());
1317 for h in 0..n_head {
1318 let head = &rows[h * d..(h + 1) * d];
1319 for i in 0..d / 2 {
1320 out.push(head[i].clone());
1321 out.push(head[d / 2 + i].clone());
1322 }
1323 }
1324 out
1325 }
1326
1327 #[test]
1328 fn depermute_inverts_llama_cpp_permute() {
1329 let hf: Vec<Vec<u32>> = (0..16).map(|i| vec![i, 100 + i]).collect();
1331 let ggml = forward_permute(&hf, 2);
1332 assert_ne!(hf, ggml, "permute must actually move rows");
1333 let map = rope_depermute_src_rows(16, 2);
1334 let recovered: Vec<Vec<u32>> = map.iter().map(|&src| ggml[src].clone()).collect();
1335 assert_eq!(recovered, hf, "de-permute must invert llama.cpp's layout");
1336 }
1337}
1338
1339#[cfg(test)]
1340mod quant_access_tests {
1341 use super::*;
1342 use crate::ModelSource;
1343
1344 #[test]
1346 #[ignore]
1347 fn real_gguf_quant_access() {
1348 let path = dirs_home().join(".cache/combs/models/llama-3.2-1b-instruct-gguf/model.gguf");
1349 let src = GgufSource::load(&path).unwrap();
1350 for name in [
1351 "model.layers.0.mlp.gate_proj.weight",
1352 "model.layers.0.self_attn.q_proj.weight",
1353 ] {
1354 let qt = src.open_tensor_quant(name).unwrap();
1355 println!("{name}: {:?}", qt.map(|q| (q.format, q.shape, q.data.len())));
1356 }
1357 }
1358
1359 fn dirs_home() -> std::path::PathBuf {
1360 std::path::PathBuf::from(std::env::var("HOME").unwrap())
1361 }
1362}