1use crate::{FormatError, Result};
4
5#[derive(Debug, Clone)]
10pub struct ModelMetadata {
11 pub architecture: String,
14 pub hidden_size: usize,
16 pub intermediate_size: usize,
18 pub num_hidden_layers: usize,
20 pub num_attention_heads: usize,
22 pub num_key_value_heads: usize,
24 pub vocab_size: usize,
26 pub max_position_embeddings: usize,
28 pub rms_norm_eps: f64,
30 pub rope_theta: f64,
32 pub tie_word_embeddings: bool,
34 pub head_dim: usize,
36 pub attention_bias: bool,
38 pub bos_token_id: Option<u32>,
40 pub eos_token_ids: Vec<u32>,
42 pub vision: Option<VisionConfig>,
45}
46
47#[derive(Debug, Clone)]
50pub struct VisionConfig {
51 pub image_size: usize,
53 pub patch_size: usize,
55 pub hidden_size: usize,
57 pub intermediate_size: usize,
59 pub num_hidden_layers: usize,
61 pub num_attention_heads: usize,
63 pub layer_norm_eps: f64,
65 pub scale_factor: usize,
68 pub image_token_id: u32,
70}
71
72impl VisionConfig {
73 pub fn image_seq_len(&self) -> usize {
76 let per_side = self.image_size / self.patch_size;
77 (per_side * per_side) / (self.scale_factor * self.scale_factor)
78 }
79
80 pub fn head_dim(&self) -> usize {
82 self.hidden_size / self.num_attention_heads
83 }
84
85 fn from_hf_config(config: &serde_json::Value) -> Result<Option<Self>> {
88 let Some(v) = config.get("vision_config").filter(|v| v.is_object()) else {
89 return Ok(None);
90 };
91 let get = |key: &str| v.get(key).and_then(|x| x.as_u64()).map(|x| x as usize);
92 let image_token_id = config
93 .get("image_token_id")
94 .and_then(|x| x.as_u64())
95 .ok_or_else(|| FormatError::MissingField("image_token_id".to_string()))?
96 as u32;
97 Ok(Some(VisionConfig {
98 image_size: get("image_size").unwrap_or(512),
99 patch_size: get("patch_size")
100 .ok_or_else(|| FormatError::MissingField("vision_config.patch_size".to_string()))?,
101 hidden_size: get("hidden_size")
102 .ok_or_else(|| FormatError::MissingField("vision_config.hidden_size".to_string()))?,
103 intermediate_size: get("intermediate_size")
104 .ok_or_else(|| FormatError::MissingField("vision_config.intermediate_size".to_string()))?,
105 num_hidden_layers: get("num_hidden_layers")
106 .ok_or_else(|| FormatError::MissingField("vision_config.num_hidden_layers".to_string()))?,
107 num_attention_heads: get("num_attention_heads")
108 .ok_or_else(|| FormatError::MissingField("vision_config.num_attention_heads".to_string()))?,
109 layer_norm_eps: v
110 .get("layer_norm_eps")
111 .and_then(|x| x.as_f64())
112 .unwrap_or(1e-12),
113 scale_factor: config
114 .get("scale_factor")
115 .and_then(|x| x.as_u64())
116 .unwrap_or(2) as usize,
117 image_token_id,
118 }))
119 }
120}
121
122fn get_u64(v: &serde_json::Value, key: &str) -> Result<u64> {
123 v.get(key)
124 .and_then(|x| x.as_u64())
125 .ok_or_else(|| FormatError::MissingField(key.to_string()))
126}
127
128fn get_f64(v: &serde_json::Value, key: &str, default: f64) -> f64 {
129 v.get(key).and_then(|x| x.as_f64()).unwrap_or(default)
130}
131
132fn token_ids(v: Option<&serde_json::Value>) -> Vec<u32> {
134 match v {
135 Some(serde_json::Value::Array(arr)) => arr
136 .iter()
137 .filter_map(|x| x.as_u64().map(|n| n as u32))
138 .collect(),
139 Some(x) => x.as_u64().map(|n| vec![n as u32]).unwrap_or_default(),
140 None => Vec::new(),
141 }
142}
143
144impl ModelMetadata {
145 pub fn from_hf_config(
149 config: &serde_json::Value,
150 generation_config: Option<&serde_json::Value>,
151 ) -> Result<Self> {
152 let text = config.get("text_config").unwrap_or(config);
155 let hidden_size = get_u64(text, "hidden_size")? as usize;
156 let num_attention_heads = get_u64(text, "num_attention_heads")? as usize;
157 let num_key_value_heads = text
158 .get("num_key_value_heads")
159 .and_then(|x| x.as_u64())
160 .map(|x| x as usize)
161 .unwrap_or(num_attention_heads);
162
163 let mut eos_token_ids = token_ids(text.get("eos_token_id"));
164 if let Some(gc) = generation_config {
165 for id in token_ids(gc.get("eos_token_id")) {
166 if !eos_token_ids.contains(&id) {
167 eos_token_ids.push(id);
168 }
169 }
170 }
171
172 let bos_token_id = generation_config
173 .and_then(|gc| gc.get("bos_token_id"))
174 .and_then(|x| x.as_u64())
175 .map(|x| x as u32)
176 .or_else(|| {
177 text.get("bos_token_id")
178 .and_then(|x| x.as_u64())
179 .map(|x| x as u32)
180 });
181
182 let architecture = config
183 .get("model_type")
184 .and_then(|x| x.as_str())
185 .ok_or_else(|| FormatError::MissingField("model_type".to_string()))?
186 .to_string();
187
188 if hidden_size % num_attention_heads != 0 {
189 return Err(FormatError::MissingField(format!(
190 "hidden_size ({hidden_size}) not divisible by num_attention_heads ({num_attention_heads})"
191 )));
192 }
193
194 Ok(ModelMetadata {
195 architecture,
196 hidden_size,
197 intermediate_size: get_u64(text, "intermediate_size")? as usize,
198 num_hidden_layers: get_u64(text, "num_hidden_layers")? as usize,
199 num_attention_heads,
200 num_key_value_heads,
201 vocab_size: get_u64(text, "vocab_size")? as usize,
202 max_position_embeddings: text
203 .get("max_position_embeddings")
204 .and_then(|x| x.as_u64())
205 .unwrap_or(2048) as usize,
206 rms_norm_eps: get_f64(text, "rms_norm_eps", 1e-5),
207 rope_theta: get_f64(text, "rope_theta", 10000.0),
208 tie_word_embeddings: config
209 .get("tie_word_embeddings")
210 .or_else(|| text.get("tie_word_embeddings"))
211 .and_then(|x| x.as_bool())
212 .unwrap_or(false),
213 head_dim: hidden_size / num_attention_heads,
214 attention_bias: text
215 .get("attention_bias")
216 .and_then(|x| x.as_bool())
217 .unwrap_or(false),
218 bos_token_id,
219 eos_token_ids,
220 vision: VisionConfig::from_hf_config(config)?,
221 })
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn parses_smollm2_style_config() {
231 let config = serde_json::json!({
232 "model_type": "llama",
233 "hidden_size": 576,
234 "intermediate_size": 1536,
235 "num_hidden_layers": 30,
236 "num_attention_heads": 9,
237 "num_key_value_heads": 3,
238 "vocab_size": 49152,
239 "max_position_embeddings": 8192,
240 "rms_norm_eps": 1e-5,
241 "rope_theta": 100000,
242 "tie_word_embeddings": true,
243 "eos_token_id": 0,
244 "bos_token_id": 0
245 });
246 let meta = ModelMetadata::from_hf_config(&config, None).unwrap();
247 assert_eq!(meta.architecture, "llama");
248 assert_eq!(meta.head_dim, 64);
249 assert_eq!(meta.num_key_value_heads, 3);
250 assert_eq!(meta.rope_theta, 100000.0);
251 assert!(meta.tie_word_embeddings);
252 assert_eq!(meta.eos_token_ids, vec![0]);
253 }
254
255 #[test]
256 fn merges_generation_config_eos_array() {
257 let config = serde_json::json!({
258 "model_type": "llama", "hidden_size": 8, "intermediate_size": 16,
259 "num_hidden_layers": 1, "num_attention_heads": 2, "vocab_size": 10,
260 "eos_token_id": 1
261 });
262 let gen = serde_json::json!({ "eos_token_id": [1, 2] });
263 let meta = ModelMetadata::from_hf_config(&config, Some(&gen)).unwrap();
264 assert_eq!(meta.eos_token_ids, vec![1, 2]);
265 assert_eq!(meta.num_key_value_heads, 2);
267 }
268
269 #[test]
270 fn parses_nested_idefics3_config() {
271 let config = serde_json::json!({
272 "model_type": "idefics3",
273 "image_token_id": 49190,
274 "scale_factor": 4,
275 "tie_word_embeddings": false,
276 "text_config": {
277 "hidden_size": 576,
278 "intermediate_size": 1536,
279 "num_hidden_layers": 30,
280 "num_attention_heads": 9,
281 "num_key_value_heads": 3,
282 "vocab_size": 49280,
283 "max_position_embeddings": 8192,
284 "rms_norm_eps": 1e-5,
285 "rope_theta": 100000,
286 "eos_token_id": 2
287 },
288 "vision_config": {
289 "hidden_size": 768,
290 "intermediate_size": 3072,
291 "num_hidden_layers": 12,
292 "num_attention_heads": 12,
293 "image_size": 512,
294 "patch_size": 16,
295 "layer_norm_eps": 1e-6
296 }
297 });
298 let meta = ModelMetadata::from_hf_config(&config, None).unwrap();
299 assert_eq!(meta.architecture, "idefics3");
300 assert_eq!(meta.hidden_size, 576);
301 assert_eq!(meta.eos_token_ids, vec![2]);
302 let v = meta.vision.expect("vision config parsed");
303 assert_eq!(v.hidden_size, 768);
304 assert_eq!(v.image_token_id, 49190);
305 assert_eq!(v.scale_factor, 4);
306 assert_eq!(v.head_dim(), 64);
307 assert_eq!(v.image_seq_len(), 64);
309 }
310}