metrale_model_weights/weights/gguf/
config.rs1use std::path::Path;
15
16use anyhow::{Context, Result};
17use metrale_config::{GgufConfigInputs, GgufMeta, ModelConfig, config_from_gguf};
18
19use super::container::GgufFile;
20use super::find_gguf;
21
22impl GgufMeta for GgufFile {
23 fn get_u64(&self, key: &str) -> Option<u64> {
24 GgufFile::get_u64(self, key)
25 }
26 fn get_f64(&self, key: &str) -> Option<f64> {
27 GgufFile::get_f64(self, key)
28 }
29 fn get_str(&self, key: &str) -> Option<&str> {
30 GgufFile::get_str(self, key)
31 }
32 fn get_arr_len(&self, key: &str) -> Option<usize> {
33 GgufFile::arr_len(self, key)
34 }
35 fn get_u64_arr(&self, key: &str) -> Option<Vec<u64>> {
40 let arr = GgufFile::get(self, key)?.as_array()?;
41 arr.iter()
42 .map(|v| {
43 v.as_u64()
44 .or_else(|| v.as_i64().and_then(|i| u64::try_from(i).ok()))
45 })
46 .collect()
47 }
48 fn get_f64_arr(&self, key: &str) -> Option<Vec<f64>> {
53 let arr = GgufFile::get(self, key)?.as_array()?;
54 arr.iter().map(|v| v.as_f64()).collect()
55 }
56}
57
58pub fn config_from_gguf_dir(model_dir: &Path) -> Result<ModelConfig> {
66 let path = find_gguf(model_dir)
67 .with_context(|| format!("no .gguf file in {}", model_dir.display()))?;
68 let file =
69 std::fs::File::open(&path).with_context(|| format!("failed to open {}", path.display()))?;
70 let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
74 let gguf = GgufFile::parse(&mmap)
75 .with_context(|| format!("failed to parse GGUF metadata: {}", path.display()))?;
76
77 let token_embd_vocab = gguf
78 .tensor("token_embd.weight")
79 .and_then(|t| t.dims.last().copied());
80 let has_output_weight = gguf.tensor("output.weight").is_some();
81
82 let inputs = GgufConfigInputs {
83 meta: &gguf,
84 token_embd_vocab,
85 has_output_weight,
86 };
87 config_from_gguf(&inputs).context("failed to build ModelConfig from GGUF metadata")
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
98 fn gguf_meta_bridge_forwards() {
99 let mut b: Vec<u8> = Vec::new();
102 let push_u32 = |b: &mut Vec<u8>, v: u32| b.extend_from_slice(&v.to_le_bytes());
103 let push_u64 = |b: &mut Vec<u8>, v: u64| b.extend_from_slice(&v.to_le_bytes());
104 let push_str = |b: &mut Vec<u8>, s: &str| {
105 b.extend_from_slice(&(s.len() as u64).to_le_bytes());
106 b.extend_from_slice(s.as_bytes());
107 };
108 push_u32(&mut b, 0x4655_4747);
109 push_u32(&mut b, 3);
110 push_u64(&mut b, 0);
111 push_u64(&mut b, 2);
112 push_str(&mut b, "qwen3.block_count");
113 push_u32(&mut b, 4);
114 push_u32(&mut b, 28);
115 push_str(&mut b, "tokenizer.ggml.tokens");
116 push_u32(&mut b, 9);
117 push_u32(&mut b, 8);
118 push_u64(&mut b, 3);
119 for s in ["a", "bb", "ccc"] {
120 push_str(&mut b, s);
121 }
122 while !b.len().is_multiple_of(32) {
126 b.push(0);
127 }
128 let gguf = GgufFile::parse(&b).unwrap();
129 let m: &dyn GgufMeta = &gguf;
130 assert_eq!(m.get_u64("qwen3.block_count"), Some(28));
131 assert_eq!(m.get_arr_len("tokenizer.ggml.tokens"), Some(3));
132 assert_eq!(m.get_str("nonexistent"), None);
133 }
134}