1#![allow(unused_imports)]
8
9use anyhow::{Context, Result};
10
11use super::{
12 LayerType, ModelConfig, default_conv_kernel, default_partial_rotary, default_rms_eps,
13 default_rope_theta, finalize_config, parse_deepseek_v4, parse_gemma4_params, parse_glm5_next,
14 parse_laguna, parse_longcat_ngram, parse_minimax_m2, parse_mistral_params,
15 parse_quantization_config, parse_qwen4_exp, parse_step3p7, parse_vision_config,
16 validate_config,
17};
18
19fn required_u64(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<u64> {
20 let value = raw
21 .get(key)
22 .with_context(|| format!("{model_type} config missing required field `{key}`"))?;
23 value
24 .as_u64()
25 .with_context(|| format!("{model_type} config field `{key}` must be an unsigned integer"))
26}
27
28fn required_nonzero_usize(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<usize> {
29 let value = required_u64(raw, key, model_type)? as usize;
30 if value == 0 {
31 anyhow::bail!("{model_type} config field `{key}` must be greater than zero");
32 }
33 Ok(value)
34}
35
36fn required_u32(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<u32> {
37 let value = required_u64(raw, key, model_type)?;
38 u32::try_from(value)
39 .with_context(|| format!("{model_type} config field `{key}` does not fit in u32"))
40}
41
42pub fn parse_config(json: &str) -> Result<ModelConfig> {
48 let mut config = parse_config_dispatch(json)?;
49 populate_eos_token_ids(&mut config, json);
50 Ok(config)
51}
52
53#[cfg(test)]
63pub(super) fn populate_eos_token_ids_for_test(config: &mut ModelConfig, json: &str) {
64 populate_eos_token_ids(config, json);
65}
66
67fn populate_eos_token_ids(config: &mut ModelConfig, json: &str) {
68 let Ok(raw) = serde_json::from_str::<serde_json::Value>(json) else {
69 return;
70 };
71 let mut ids: Vec<u32> = vec![config.eos_token_id];
72 let mut push = |v: &serde_json::Value| match v {
73 serde_json::Value::Number(n) => {
74 if let Some(x) = n.as_u64() {
75 ids.push(x as u32);
76 }
77 }
78 serde_json::Value::Array(a) => {
79 for e in a {
80 if let Some(x) = e.as_u64() {
81 ids.push(x as u32);
82 }
83 }
84 }
85 _ => {}
86 };
87 if let Some(v) = raw.get("eos_token_id") {
88 push(v);
89 }
90 if let Some(v) = raw.get("text_config").and_then(|t| t.get("eos_token_id")) {
91 push(v);
92 }
93 let mut seen = std::collections::BTreeSet::new();
94 ids.retain(|id| seen.insert(*id));
95 config.eos_token_ids = ids;
96}
97
98fn parse_config_dispatch(json: &str) -> Result<ModelConfig> {
99 let raw: serde_json::Value =
101 serde_json::from_str(json).context("Invalid JSON in config.json")?;
102
103 let top_model_type = raw
111 .get("model_type")
112 .and_then(serde_json::Value::as_str)
113 .filter(|s| !s.is_empty())
114 .or_else(|| {
115 raw.get("architectures")
116 .and_then(serde_json::Value::as_array)
117 .and_then(|a| a.first())
118 .and_then(serde_json::Value::as_str)
119 .and_then(|arch| match arch {
120 "LongcatFlashNgramForCausalLM" => Some("longcat_flash_ngram"),
121 "LongcatFlashForCausalLM" => Some("longcat_flash"),
122 "Qwen4ExpForConditionalGeneration"
123 | "Qwen3_8FlashNextForConditionalGeneration" => Some("qwen4_exp"),
124 _ => None,
125 })
126 })
127 .unwrap_or("");
128
129 match top_model_type {
130 "qwen3_vl_moe" | "qwen3_5_moe" | "qwen3_5" => {
131 let text_config = raw
132 .get("text_config")
133 .context("qwen3_5_moe config missing text_config")?;
134 let mut config: ModelConfig = serde_json::from_value(text_config.clone())
135 .context("Failed to parse text_config")?;
136 config.model_type = top_model_type.to_string();
138 if config.eos_token_id == 0 {
142 config.eos_token_id = text_config
143 .get("eos_token_id")
144 .and_then(serde_json::Value::as_u64)
145 .unwrap_or(0) as u32;
146 }
147 if config.vocab_size == 0 {
149 config.vocab_size = raw
150 .get("vocab_size")
151 .and_then(serde_json::Value::as_u64)
152 .unwrap_or(0) as usize;
153 }
154 if let Some(rope_params) = text_config.get("rope_parameters") {
156 if config.rope_theta == default_rope_theta()
157 && let Some(theta) = rope_params
158 .get("rope_theta")
159 .and_then(serde_json::Value::as_f64)
160 {
161 config.rope_theta = theta;
162 }
163 if config.partial_rotary_factor == default_partial_rotary()
165 && let Some(prf) = rope_params
166 .get("partial_rotary_factor")
167 .and_then(serde_json::Value::as_f64)
168 {
169 config.partial_rotary_factor = prf;
170 }
171 }
172 config.norm_topk_prob = true;
175 config.nested_config = true;
177 config.attn_gated = top_model_type != "qwen3_vl_moe";
178 if top_model_type == "qwen3_vl_moe" || raw.get("vision_config").is_some() {
183 config.vision = parse_vision_config(&raw);
184 }
185 if let Some(rope_params) = text_config.get("rope_parameters") {
200 if let Some(ms) = rope_params.get("mrope_section").and_then(|v| v.as_array())
201 && ms.len() == 3
202 {
203 config.mrope_section = [
204 ms[0].as_u64().unwrap_or(0) as usize,
205 ms[1].as_u64().unwrap_or(0) as usize,
206 ms[2].as_u64().unwrap_or(0) as usize,
207 ];
208 }
209 config.mrope_interleaved = rope_params
210 .get("mrope_interleaved")
211 .and_then(|v| v.as_bool())
212 .unwrap_or(false);
213 let is_moe = top_model_type == "qwen3_5_moe" || top_model_type == "qwen3_vl_moe";
214 if is_moe
215 && config.mrope_interleaved
216 && config.mrope_section.iter().sum::<usize>() > 0
217 {
218 config.model_type = "qwen3_6_moe".to_string();
219 }
220 }
221 if top_model_type == "qwen3_5_moe"
231 && config.vision.is_some()
232 && config.mtp_num_hidden_layers == 0
233 && raw
234 .get("image_token_id")
235 .and_then(serde_json::Value::as_u64)
236 == Some(248_056)
237 {
238 config.model_type = "holo3_1_moe".to_string();
239 }
240 finalize_config(&mut config, &raw)?;
241 Ok(config)
242 }
243 "nemotron_h" | "nemotron_h_puzzle" => {
244 let mut raw_mut = raw.clone();
248 if top_model_type == "nemotron_h_puzzle" {
249 apply_nemotron_puzzle_json(&mut raw_mut)?;
250 }
251 let mut config: ModelConfig = serde_json::from_value(raw_mut.clone())
252 .context("Failed to parse nemotron_h config.json")?;
253 if config.num_experts == 0 && config.n_routed_experts > 0 {
255 config.num_experts = config.n_routed_experts;
256 }
257 if config.rms_norm_eps == default_rms_eps() && config.norm_eps > 0.0 {
258 config.rms_norm_eps = config.norm_eps;
259 }
260 if config.linear_conv_kernel_dim == default_conv_kernel() && config.conv_kernel > 0 {
261 config.linear_conv_kernel_dim = config.conv_kernel;
262 }
263 if config.shared_expert_intermediate_size == 0
264 && config.moe_shared_expert_intermediate_size > 0
265 {
266 config.shared_expert_intermediate_size = config.moe_shared_expert_intermediate_size;
267 }
268 config.attn_gated = false;
270 config.weight_prefix = "backbone".to_string();
271 if !config.hybrid_override_pattern.is_empty() && config.layer_types.is_empty() {
273 config.layer_types = config
274 .hybrid_override_pattern
275 .chars()
276 .map(|c| match c {
277 'M' => LayerType::LinearAttention,
278 'E' => LayerType::Moe,
279 '*' => LayerType::FullAttention,
280 other => panic!("Unknown hybrid_override_pattern char: '{other}'"),
281 })
282 .collect();
283 }
284 if top_model_type == "nemotron_h_puzzle" {
286 apply_nemotron_puzzle_config(&mut config, &raw_mut)?;
287 }
288 finalize_config(&mut config, &raw_mut)?;
289 Ok(config)
290 }
291 "gemma4" => parse_gemma4_params(&raw),
292 "laguna" => parse_laguna(&raw),
293 "longcat_flash_ngram" | "longcat_flash" => parse_longcat_ngram(&raw),
294 "qwen4_exp" | "qwen3_8_flash_next" => parse_qwen4_exp(&raw),
304 "m2m_100" | "nllb" => {
305 let mut config = ModelConfig::qwen3_next_80b_nvfp4();
306 config.model_type = "m2m_100".to_string();
307 config.hidden_size = required_nonzero_usize(&raw, "d_model", top_model_type)?;
308 config.num_hidden_layers =
309 required_nonzero_usize(&raw, "decoder_layers", top_model_type)?;
310 config.intermediate_size =
311 required_nonzero_usize(&raw, "decoder_ffn_dim", top_model_type)?;
312 config.vocab_size = required_nonzero_usize(&raw, "vocab_size", top_model_type)?;
313 config.num_attention_heads =
314 required_nonzero_usize(&raw, "decoder_attention_heads", top_model_type)?;
315 config.num_key_value_heads = config.num_attention_heads;
316 if !config
317 .hidden_size
318 .is_multiple_of(config.num_attention_heads)
319 {
320 anyhow::bail!(
321 "{} config has d_model ({}) not divisible by decoder_attention_heads ({})",
322 top_model_type,
323 config.hidden_size,
324 config.num_attention_heads,
325 );
326 }
327 config.head_dim = config.hidden_size / config.num_attention_heads;
328 config.max_position_embeddings =
329 required_nonzero_usize(&raw, "max_position_embeddings", top_model_type)?;
330 config.bos_token_id = required_u32(&raw, "bos_token_id", top_model_type)?;
331 config.eos_token_id = required_u32(&raw, "eos_token_id", top_model_type)?;
332 config.tie_word_embeddings = true;
333 config.attn_gated = false;
334 config.weight_prefix = "model.decoder".to_string();
335 config.num_experts = 0;
336 config.num_experts_per_tok = 1;
337 config.moe_intermediate_size = 0;
338 config.shared_expert_intermediate_size = 0;
339 config.layer_types.clear();
340 config.full_attention_interval = 1;
341 config.linear_num_key_heads = 0;
342 config.linear_key_head_dim = 0;
343 config.linear_num_value_heads = 0;
344 config.linear_value_head_dim = 0;
345 config.mtp_num_hidden_layers = 0;
346 config.vision = None;
347 config.quantization_config = parse_quantization_config(&raw);
348 validate_config(&config)?;
349 Ok(config)
350 }
351 "minimax_m2" => parse_minimax_m2(&raw),
352 "step3p7" => parse_step3p7(&raw),
353 "deepseek_v4" => parse_deepseek_v4(json),
354 "glm5_next" | "glm5_next_text" => parse_glm5_next(json),
358 _ => {
359 let mut config: ModelConfig =
361 serde_json::from_str(json).context("Failed to parse config.json")?;
362 config.attn_gated = true;
363 finalize_config(&mut config, &raw)?;
364 Ok(config)
365 }
366 }
367}
368
369fn apply_nemotron_puzzle_json(raw: &mut serde_json::Value) -> Result<()> {
375 let obj = raw
376 .as_object_mut()
377 .context("nemotron_h_puzzle config.json is not an object")?;
378 let n_layers = obj
379 .get("layers_block_type")
380 .and_then(|v| v.as_array())
381 .map(|a| a.len())
382 .or_else(|| {
383 obj.get("block_configs")
384 .and_then(|v| v.as_array())
385 .map(|a| a.len())
386 })
387 .context("nemotron_h_puzzle missing layers_block_type / block_configs")?;
388 if obj
389 .get("num_hidden_layers")
390 .map(|v| v.is_null() || v.as_u64() == Some(0))
391 .unwrap_or(true)
392 {
393 obj.insert("num_hidden_layers".into(), serde_json::json!(n_layers));
394 }
395 let mut max_inter = 0usize;
397 let mut max_topk = 0usize;
398 if let Some(blocks) = obj.get("block_configs").and_then(|v| v.as_array()) {
399 for b in blocks {
400 if let Some(mi) = b.get("moe_intermediate_size").and_then(|v| v.as_u64()) {
401 max_inter = max_inter.max(mi as usize);
402 }
403 if let Some(tk) = b.get("num_experts_per_tok").and_then(|v| v.as_u64()) {
404 max_topk = max_topk.max(tk as usize);
405 }
406 }
407 }
408 if obj
409 .get("moe_intermediate_size")
410 .and_then(|v| v.as_u64())
411 .unwrap_or(0)
412 == 0
413 && max_inter > 0
414 {
415 obj.insert("moe_intermediate_size".into(), serde_json::json!(max_inter));
416 }
417 if obj
418 .get("num_experts_per_tok")
419 .and_then(|v| v.as_u64())
420 .unwrap_or(0)
421 == 0
422 && max_topk > 0
423 {
424 obj.insert("num_experts_per_tok".into(), serde_json::json!(max_topk));
425 }
426 Ok(())
427}
428
429fn apply_nemotron_puzzle_config(config: &mut ModelConfig, raw: &serde_json::Value) -> Result<()> {
431 let block_types = raw
432 .get("layers_block_type")
433 .and_then(|v| v.as_array())
434 .context("nemotron_h_puzzle missing layers_block_type")?;
435 config.layer_types = block_types
436 .iter()
437 .map(|v| {
438 let s = v.as_str().unwrap_or("");
439 Ok(match s {
440 "mamba" => LayerType::LinearAttention,
441 "moe" => LayerType::Moe,
442 "attention" => LayerType::FullAttention,
443 other => anyhow::bail!("unknown layers_block_type entry: '{other}'"),
444 })
445 })
446 .collect::<Result<Vec<_>>>()?;
447 if config.num_hidden_layers == 0 {
448 config.num_hidden_layers = config.layer_types.len();
449 }
450 let n = config.num_hidden_layers;
452 let mut inters = vec![0usize; n];
453 let mut topks = vec![0usize; n];
454 if let Some(blocks) = raw.get("block_configs").and_then(|v| v.as_array()) {
455 for (i, b) in blocks.iter().enumerate().take(n) {
456 if b.get("block_type").and_then(|v| v.as_str()) != Some("moe") {
457 continue;
458 }
459 inters[i] = b
460 .get("moe_intermediate_size")
461 .and_then(|v| v.as_u64())
462 .unwrap_or(0) as usize;
463 topks[i] = b
464 .get("num_experts_per_tok")
465 .and_then(|v| v.as_u64())
466 .unwrap_or(0) as usize;
467 }
468 }
469 config.moe_intermediate_sizes = inters;
470 config.num_experts_per_toks = topks;
471 if let Some(m) = config
473 .moe_intermediate_sizes
474 .iter()
475 .copied()
476 .filter(|&s| s > 0)
477 .max()
478 {
479 config.moe_intermediate_size = m;
480 }
481 if let Some(m) = config
482 .num_experts_per_toks
483 .iter()
484 .copied()
485 .filter(|&k| k > 0)
486 .max()
487 {
488 config.num_experts_per_tok = m;
489 }
490 Ok(())
491}