1#![allow(unused_imports)]
11
12use anyhow::{Context, Result};
13
14use super::{
15 AttnPositionEncoding, LayerType, ModelConfig, default_conv_kernel, default_partial_rotary,
16 default_rms_eps, default_rope_theta, finalize_config, parse_deepseek_v4, parse_gemma4_params,
17 parse_glm5_next, parse_kimi_k3, parse_laguna, parse_longcat_ngram, parse_minimax_m2,
18 parse_mistral_params, parse_quantization_config, parse_qwen4_exp, parse_step3p7,
19 parse_vision_config, resolve_attn_position_encoding, sanitize_kimi_k3_eos, validate_config,
20};
21
22fn required_u64(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<u64> {
23 let value = raw
24 .get(key)
25 .with_context(|| format!("{model_type} config missing required field `{key}`"))?;
26 value
27 .as_u64()
28 .with_context(|| format!("{model_type} config field `{key}` must be an unsigned integer"))
29}
30
31fn required_nonzero_usize(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<usize> {
32 let value = required_u64(raw, key, model_type)? as usize;
33 if value == 0 {
34 anyhow::bail!("{model_type} config field `{key}` must be greater than zero");
35 }
36 Ok(value)
37}
38
39fn required_u32(raw: &serde_json::Value, key: &str, model_type: &str) -> Result<u32> {
40 let value = required_u64(raw, key, model_type)?;
41 u32::try_from(value)
42 .with_context(|| format!("{model_type} config field `{key}` does not fit in u32"))
43}
44
45pub fn parse_config(json: &str) -> Result<ModelConfig> {
48 let mut config = parse_config_dispatch(json)?;
49 resolve_attn_position_encoding(&mut config)?;
50 populate_eos_token_ids(&mut config, json);
51 sanitize_kimi_k3_eos(&mut config);
52 Ok(config)
53}
54
55#[cfg(test)]
56pub(super) fn populate_eos_token_ids_for_test(config: &mut ModelConfig, json: &str) {
57 populate_eos_token_ids(config, json);
58}
59
60fn 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 =
100 serde_json::from_str(json).context("Invalid JSON in config.json")?;
101
102 let top_model_type = raw
106 .get("model_type")
107 .and_then(serde_json::Value::as_str)
108 .filter(|s| !s.is_empty())
109 .or_else(|| {
110 raw.get("architectures")
111 .and_then(serde_json::Value::as_array)
112 .and_then(|a| a.first())
113 .and_then(serde_json::Value::as_str)
114 .and_then(|arch| match arch {
115 "LongcatFlashNgramForCausalLM" => Some("longcat_flash_ngram"),
116 "LongcatFlashForCausalLM" => Some("longcat_flash"),
117 "Qwen4ExpForConditionalGeneration"
118 | "Qwen3_8FlashNextForConditionalGeneration" => Some("qwen4_exp"),
119 _ => None,
120 })
121 })
122 .unwrap_or("");
123
124 match top_model_type {
125 "qwen3_vl_moe" | "qwen3_5_moe" | "qwen3_5" => {
126 let text_config = raw
127 .get("text_config")
128 .context("qwen3_5_moe config missing text_config")?;
129 let mut config: ModelConfig = serde_json::from_value(text_config.clone())
130 .context("Failed to parse text_config")?;
131 config.model_type = top_model_type.to_string();
132 if config.eos_token_id == 0 {
133 config.eos_token_id = text_config
134 .get("eos_token_id")
135 .and_then(serde_json::Value::as_u64)
136 .unwrap_or(0) as u32;
137 }
138 if config.vocab_size == 0 {
139 config.vocab_size = raw
140 .get("vocab_size")
141 .and_then(serde_json::Value::as_u64)
142 .unwrap_or(0) as usize;
143 }
144 if let Some(rope_params) = text_config.get("rope_parameters") {
145 if config.rope_theta == default_rope_theta()
146 && let Some(theta) = rope_params
147 .get("rope_theta")
148 .and_then(serde_json::Value::as_f64)
149 {
150 config.rope_theta = theta;
151 }
152 if config.partial_rotary_factor == default_partial_rotary()
153 && let Some(prf) = rope_params
154 .get("partial_rotary_factor")
155 .and_then(serde_json::Value::as_f64)
156 {
157 config.partial_rotary_factor = prf;
158 }
159 }
160 config.norm_topk_prob = true;
161 config.nested_config = true;
162 config.attn_gated = top_model_type != "qwen3_vl_moe";
163 if top_model_type == "qwen3_vl_moe" || raw.get("vision_config").is_some() {
164 config.vision = parse_vision_config(&raw);
165 }
166 if let Some(rope_params) = text_config.get("rope_parameters") {
170 if let Some(ms) = rope_params.get("mrope_section").and_then(|v| v.as_array())
171 && ms.len() == 3
172 {
173 config.mrope_section = [
174 ms[0].as_u64().unwrap_or(0) as usize,
175 ms[1].as_u64().unwrap_or(0) as usize,
176 ms[2].as_u64().unwrap_or(0) as usize,
177 ];
178 }
179 config.mrope_interleaved = rope_params
180 .get("mrope_interleaved")
181 .and_then(|v| v.as_bool())
182 .unwrap_or(false);
183 let is_moe = top_model_type == "qwen3_5_moe" || top_model_type == "qwen3_vl_moe";
184 if is_moe
185 && config.mrope_interleaved
186 && config.mrope_section.iter().sum::<usize>() > 0
187 {
188 config.model_type = "qwen3_6_moe".to_string();
189 }
190 }
191 if top_model_type == "qwen3_5_moe"
196 && config.vision.is_some()
197 && config.mtp_num_hidden_layers == 0
198 && raw
199 .get("image_token_id")
200 .and_then(serde_json::Value::as_u64)
201 == Some(248_056)
202 {
203 config.model_type = "holo3_1_moe".to_string();
204 }
205 finalize_config(&mut config, &raw)?;
206 Ok(config)
207 }
208 "nemotron_h" | "nemotron_h_puzzle" => {
209 let mut raw_mut = raw.clone();
213 if top_model_type == "nemotron_h_puzzle" {
214 apply_nemotron_puzzle_json(&mut raw_mut)?;
215 }
216 let mut config: ModelConfig = serde_json::from_value(raw_mut.clone())
217 .context("Failed to parse nemotron_h config.json")?;
218 if config.num_experts == 0 && config.n_routed_experts > 0 {
219 config.num_experts = config.n_routed_experts;
220 }
221 if config.rms_norm_eps == default_rms_eps() && config.norm_eps > 0.0 {
222 config.rms_norm_eps = config.norm_eps;
223 }
224 if config.linear_conv_kernel_dim == default_conv_kernel() && config.conv_kernel > 0 {
225 config.linear_conv_kernel_dim = config.conv_kernel;
226 }
227 if config.shared_expert_intermediate_size == 0
228 && config.moe_shared_expert_intermediate_size > 0
229 {
230 config.shared_expert_intermediate_size = config.moe_shared_expert_intermediate_size;
231 }
232 config.attn_gated = false;
233 config.attn_position_encoding = Some(AttnPositionEncoding::None);
238 config.weight_prefix = "backbone".to_string();
239 if !config.hybrid_override_pattern.is_empty() && config.layer_types.is_empty() {
240 config.layer_types = config
241 .hybrid_override_pattern
242 .chars()
243 .map(|c| match c {
244 'M' => LayerType::LinearAttention,
245 'E' => LayerType::Moe,
246 '*' => LayerType::FullAttention,
247 other => panic!("Unknown hybrid_override_pattern char: '{other}'"),
248 })
249 .collect();
250 }
251 if top_model_type == "nemotron_h_puzzle" {
252 apply_nemotron_puzzle_config(&mut config, &raw_mut)?;
253 }
254 finalize_config(&mut config, &raw_mut)?;
255 Ok(config)
256 }
257 "gemma4" => parse_gemma4_params(&raw),
258 "laguna" => parse_laguna(&raw),
259 "longcat_flash_ngram" | "longcat_flash" => parse_longcat_ngram(&raw),
260 "qwen4_exp" | "qwen3_8_flash_next" => parse_qwen4_exp(&raw),
263 "m2m_100" | "nllb" => {
264 let mut config = ModelConfig::qwen3_next_80b_nvfp4();
265 config.model_type = "m2m_100".to_string();
266 config.hidden_size = required_nonzero_usize(&raw, "d_model", top_model_type)?;
267 config.num_hidden_layers =
268 required_nonzero_usize(&raw, "decoder_layers", top_model_type)?;
269 config.intermediate_size =
270 required_nonzero_usize(&raw, "decoder_ffn_dim", top_model_type)?;
271 config.vocab_size = required_nonzero_usize(&raw, "vocab_size", top_model_type)?;
272 config.num_attention_heads =
273 required_nonzero_usize(&raw, "decoder_attention_heads", top_model_type)?;
274 config.num_key_value_heads = config.num_attention_heads;
275 if !config
276 .hidden_size
277 .is_multiple_of(config.num_attention_heads)
278 {
279 anyhow::bail!(
280 "{} config has d_model ({}) not divisible by decoder_attention_heads ({})",
281 top_model_type,
282 config.hidden_size,
283 config.num_attention_heads,
284 );
285 }
286 config.head_dim = config.hidden_size / config.num_attention_heads;
287 config.max_position_embeddings =
288 required_nonzero_usize(&raw, "max_position_embeddings", top_model_type)?;
289 config.bos_token_id = required_u32(&raw, "bos_token_id", top_model_type)?;
290 config.eos_token_id = required_u32(&raw, "eos_token_id", top_model_type)?;
291 config.tie_word_embeddings = true;
292 config.attn_gated = false;
293 config.weight_prefix = "model.decoder".to_string();
294 config.num_experts = 0;
295 config.num_experts_per_tok = 1;
296 config.moe_intermediate_size = 0;
297 config.shared_expert_intermediate_size = 0;
298 config.layer_types.clear();
299 config.full_attention_interval = 1;
300 config.linear_num_key_heads = 0;
301 config.linear_key_head_dim = 0;
302 config.linear_num_value_heads = 0;
303 config.linear_value_head_dim = 0;
304 config.mtp_num_hidden_layers = 0;
305 config.vision = None;
306 config.quantization_config = parse_quantization_config(&raw)?;
307 validate_config(&config)?;
308 Ok(config)
309 }
310 "minimax_m2" => parse_minimax_m2(&raw),
311 "step3p7" => parse_step3p7(&raw),
312 "deepseek_v4" => parse_deepseek_v4(json),
313 "glm5_next" | "glm5_next_text" => parse_glm5_next(json),
315 "kimi_k3" | "kimi_linear" => parse_kimi_k3(json),
318 _ => {
319 let mut config: ModelConfig =
320 serde_json::from_str(json).context("Failed to parse config.json")?;
321 config.attn_gated = true;
322 finalize_config(&mut config, &raw)?;
323 Ok(config)
324 }
325 }
326}
327
328fn apply_nemotron_puzzle_json(raw: &mut serde_json::Value) -> Result<()> {
335 let obj = raw
336 .as_object_mut()
337 .context("nemotron_h_puzzle config.json is not an object")?;
338 let n_layers = obj
339 .get("layers_block_type")
340 .and_then(|v| v.as_array())
341 .map(|a| a.len())
342 .or_else(|| {
343 obj.get("block_configs")
344 .and_then(|v| v.as_array())
345 .map(|a| a.len())
346 })
347 .context("nemotron_h_puzzle missing layers_block_type / block_configs")?;
348 if obj
349 .get("num_hidden_layers")
350 .map(|v| v.is_null() || v.as_u64() == Some(0))
351 .unwrap_or(true)
352 {
353 obj.insert("num_hidden_layers".into(), serde_json::json!(n_layers));
354 }
355 let mut max_inter = 0usize;
356 let mut max_topk = 0usize;
357 if let Some(blocks) = obj.get("block_configs").and_then(|v| v.as_array()) {
358 for b in blocks {
359 if let Some(mi) = b.get("moe_intermediate_size").and_then(|v| v.as_u64()) {
360 max_inter = max_inter.max(mi as usize);
361 }
362 if let Some(tk) = b.get("num_experts_per_tok").and_then(|v| v.as_u64()) {
363 max_topk = max_topk.max(tk as usize);
364 }
365 }
366 }
367 if obj
368 .get("moe_intermediate_size")
369 .and_then(|v| v.as_u64())
370 .unwrap_or(0)
371 == 0
372 && max_inter > 0
373 {
374 obj.insert("moe_intermediate_size".into(), serde_json::json!(max_inter));
375 }
376 if obj
377 .get("num_experts_per_tok")
378 .and_then(|v| v.as_u64())
379 .unwrap_or(0)
380 == 0
381 && max_topk > 0
382 {
383 obj.insert("num_experts_per_tok".into(), serde_json::json!(max_topk));
384 }
385 Ok(())
386}
387
388fn apply_nemotron_puzzle_config(config: &mut ModelConfig, raw: &serde_json::Value) -> Result<()> {
391 let block_types = raw
392 .get("layers_block_type")
393 .and_then(|v| v.as_array())
394 .context("nemotron_h_puzzle missing layers_block_type")?;
395 config.layer_types = block_types
396 .iter()
397 .map(|v| {
398 let s = v.as_str().unwrap_or("");
399 Ok(match s {
400 "mamba" => LayerType::LinearAttention,
401 "moe" => LayerType::Moe,
402 "attention" => LayerType::FullAttention,
403 other => anyhow::bail!("unknown layers_block_type entry: '{other}'"),
404 })
405 })
406 .collect::<Result<Vec<_>>>()?;
407 if config.num_hidden_layers == 0 {
408 config.num_hidden_layers = config.layer_types.len();
409 }
410 let n = config.num_hidden_layers;
411 let mut inters = vec![0usize; n];
412 let mut topks = vec![0usize; n];
413 if let Some(blocks) = raw.get("block_configs").and_then(|v| v.as_array()) {
414 for (i, b) in blocks.iter().enumerate().take(n) {
415 if b.get("block_type").and_then(|v| v.as_str()) != Some("moe") {
416 continue;
417 }
418 inters[i] = b
419 .get("moe_intermediate_size")
420 .and_then(|v| v.as_u64())
421 .unwrap_or(0) as usize;
422 topks[i] = b
423 .get("num_experts_per_tok")
424 .and_then(|v| v.as_u64())
425 .unwrap_or(0) as usize;
426 }
427 }
428 config.moe_intermediate_sizes = inters;
429 config.num_experts_per_toks = topks;
430 if let Some(m) = config
431 .moe_intermediate_sizes
432 .iter()
433 .copied()
434 .filter(|&s| s > 0)
435 .max()
436 {
437 config.moe_intermediate_size = m;
438 }
439 if let Some(m) = config
440 .num_experts_per_toks
441 .iter()
442 .copied()
443 .filter(|&k| k > 0)
444 .max()
445 {
446 config.num_experts_per_tok = m;
447 }
448 Ok(())
449}