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