1use std::collections::{BTreeMap, BTreeSet, HashMap};
36use std::io;
37use std::path::{Path, PathBuf};
38use std::sync::OnceLock;
39
40use serde_json::{Map, Value};
41
42use super::gguf::MetaValue;
43use super::quant::{e4m3fn_to_f32, e5m2_to_f32, f16_to_f32, f32_to_f16, GgmlType, ScaleGrid};
44use super::reference::WeightSource;
45use super::safetensors::{self, Dtype, Entry, Shard};
46use super::spec::SpecSource;
47
48mod encode;
49mod onig;
50mod tiktoken;
51mod unicode_data;
52mod vocab;
53
54pub use encode::{nfc, HfPreTokenizer, HfTokenizer};
55pub use tiktoken::{split as tiktoken_split, Tiktoken};
56pub use unicode_data::{NORMAL_FORM_VERSION, PATTERN_VERSION};
57pub use vocab::{pre_tokenizer_named, PRE_TOKENIZER_DIGESTS, PRE_TOKENIZER_PROBE};
58
59fn invalid(msg: impl Into<String>) -> io::Error {
60 io::Error::new(io::ErrorKind::InvalidData, msg.into())
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum Op {
66 AddOne,
68 NegExp,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
74pub enum Recompute {
75 #[default]
78 AsConverted,
79 AsForward,
81}
82
83#[derive(Clone, Debug, PartialEq)]
85pub enum Form {
86 Gather {
89 src: String,
90 outer: usize,
91 m: usize,
92 n: usize,
93 rows: Vec<usize>,
94 cols: Vec<usize>,
95 transpose: bool,
96 },
97 Concat(Vec<Form>),
99 Beside(Vec<Form>),
101 Map { inner: Box<Form>, op: Op },
103 Values(Vec<f32>),
105}
106
107impl Form {
108 fn whole(src: &str, entry: &Entry) -> Form {
110 let n = entry.shape.last().copied().unwrap_or(1) as usize;
111 let m = (entry.elements() as usize).checked_div(n).unwrap_or(0);
112 Form::Gather {
113 src: src.to_string(),
114 outer: 1,
115 m,
116 n,
117 rows: (0..m).collect(),
118 cols: (0..n).collect(),
119 transpose: false,
120 }
121 }
122
123 fn elements(&self) -> usize {
124 match self {
125 Form::Gather { outer, rows, cols, .. } => outer * rows.len() * cols.len(),
126 Form::Concat(parts) | Form::Beside(parts) => parts.iter().map(Form::elements).sum(),
127 Form::Map { inner, .. } => inner.elements(),
128 Form::Values(v) => v.len(),
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
135struct View {
136 name: String,
137 dims: Vec<u64>,
139 form: Form,
140}
141
142pub struct HfCheckpoint {
144 dir: PathBuf,
145 recompute: Recompute,
146 family: Family,
147 hparams: Hparams,
148 tokenizer: OnceLock<BTreeMap<String, MetaValue>>,
149 arch: String,
150 meta: BTreeMap<String, MetaValue>,
151 views: Vec<View>,
152 index: HashMap<String, usize>,
153 shards: Vec<Shard>,
154 located: HashMap<String, usize>,
155 scaled: HashMap<String, Scaled>,
157 consumed: BTreeSet<String>,
159 unread: Vec<String>,
160 skipped: Vec<String>,
161}
162
163struct Scaled {
165 scale: String,
166 block: Option<Vec<usize>>,
167 grid: OnceLock<Result<ScaleGrid, String>>,
168}
169
170fn fp8_scales(h: &Hparams, located: &HashMap<String, usize>) -> io::Result<(HashMap<String, Scaled>, BTreeSet<String>)> {
174 let mut scaled = HashMap::new();
175 let mut consumed = BTreeSet::new();
176 let Some(q) = h.map.get("quantization_config").filter(|q| q.get("quant_method").and_then(Value::as_str) == Some("fp8")) else {
177 return Ok((scaled, consumed));
178 };
179 let block = match q.get("weight_block_size") {
180 None | Some(Value::Null) => None,
181 Some(Value::Array(b)) => Some(
182 b.iter()
183 .map(|x| x.as_u64().map(|x| x as usize).ok_or_else(|| invalid(format!("config.json: weight_block_size {b:?}"))))
184 .collect::<io::Result<Vec<usize>>>()?,
185 ),
186 Some(other) => return Err(invalid(format!("config.json: weight_block_size is {other}"))),
187 };
188 let mut names: Vec<&String> = located.keys().collect();
189 names.sort();
190 for name in names {
191 let weight = if let Some(w) = name.strip_suffix("_scale_inv") {
192 Some(w.to_string())
193 } else {
194 name.strip_suffix(".qscale_weight").map(|stem| format!("{stem}.weight"))
195 };
196 if let Some(weight) = weight {
197 if !located.contains_key(&weight) {
198 return Err(invalid(format!("{name} scales {weight}, which the checkpoint does not hold")));
199 }
200 scaled.insert(weight, Scaled { scale: name.clone(), block: block.clone(), grid: OnceLock::new() });
201 consumed.insert(name.clone());
202 } else if name.ends_with(".activation_scale") || name.ends_with("_activation_scale") || name.ends_with(".qscale_act") {
203 consumed.insert(name.clone());
204 }
205 }
206 Ok((scaled, consumed))
207}
208
209struct Hparams {
212 map: Map<String, Value>,
213 architecture: String,
215}
216
217impl Hparams {
218 fn find(&self, keys: &[&str]) -> Option<&Value> {
219 keys.iter().find_map(|k| self.map.get(*k)).filter(|v| !v.is_null())
220 }
221 fn u64(&self, keys: &[&str]) -> Option<u64> {
222 self.find(keys).and_then(Value::as_u64)
223 }
224 fn f64(&self, keys: &[&str]) -> Option<f64> {
225 self.find(keys).and_then(Value::as_f64)
226 }
227 fn need_u64(&self, key: &str) -> io::Result<u64> {
228 self.u64(&[key]).ok_or_else(|| invalid(format!("config.json: no `{key}`")))
229 }
230}
231
232struct Writer {
234 arch: String,
235 meta: BTreeMap<String, MetaValue>,
236}
237
238impl Writer {
239 fn put(&mut self, suffix: &str, v: MetaValue) {
240 self.meta.insert(format!("{}.{suffix}", self.arch), v);
241 }
242 fn u32(&mut self, suffix: &str, v: u64) {
243 self.put(suffix, MetaValue::U32(v as u32));
244 }
245 fn f32(&mut self, suffix: &str, v: f64) {
246 self.put(suffix, MetaValue::F32(v as f32));
247 }
248 fn bool(&mut self, suffix: &str, v: bool) {
249 self.put(suffix, MetaValue::Bool(v));
250 }
251 fn str(&mut self, suffix: &str, v: &str) {
252 self.put(suffix, MetaValue::Str(v.as_bytes().to_vec()));
253 }
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258enum Family {
259 Llama,
260 Qwen2,
261 Qwen3,
262 Qwen3Moe,
263 Qwen2Moe,
264 Qwen35,
265 Qwen35Moe,
266 Qwen3Next,
267 Gemma,
268 Gemma2,
269 Gemma3,
270 GptOss,
271 Deepseek2,
272 Llama4,
273 MinimaxM2,
274 Phi3,
275 Glm4,
276 Glm4Moe,
277 Qwen4Exp,
278}
279
280impl Family {
281 fn of(architecture: &str) -> Option<Family> {
282 Some(match architecture {
283 "LlamaForCausalLM" | "MistralForCausalLM" | "MixtralForCausalLM" => Family::Llama,
284 "Qwen2ForCausalLM" => Family::Qwen2,
285 "Qwen3ForCausalLM" => Family::Qwen3,
286 "Qwen3MoeForCausalLM" => Family::Qwen3Moe,
287 "Qwen2MoeForCausalLM" => Family::Qwen2Moe,
288 "Qwen3_5ForConditionalGeneration" | "Qwen3_5ForCausalLM" => Family::Qwen35,
289 "Qwen3_5MoeForConditionalGeneration" | "Qwen3_5MoeForCausalLM" => Family::Qwen35Moe,
290 "Qwen3NextForCausalLM" => Family::Qwen3Next,
291 "GemmaForCausalLM" => Family::Gemma,
292 "Gemma2ForCausalLM" => Family::Gemma2,
293 "Gemma3ForCausalLM" | "Gemma3ForConditionalGeneration" => Family::Gemma3,
294 "GptOssForCausalLM" => Family::GptOss,
295 "DeepseekV2ForCausalLM" | "DeepseekV3ForCausalLM" => Family::Deepseek2,
296 "Llama4ForCausalLM" | "Llama4ForConditionalGeneration" => Family::Llama4,
297 "MiniMaxM2ForCausalLM" => Family::MinimaxM2,
298 "Phi3ForCausalLM" => Family::Phi3,
299 "Glm4ForCausalLM" => Family::Glm4,
300 "Glm4MoeForCausalLM" => Family::Glm4Moe,
301 "Qwen4ExpForConditionalGeneration" | "Qwen4ExpForCausalLM" => Family::Qwen4Exp,
302 _ => return None,
303 })
304 }
305
306 fn arch(self) -> &'static str {
307 match self {
308 Family::Llama => "llama",
309 Family::Qwen2 => "qwen2",
310 Family::Qwen3 => "qwen3",
311 Family::Qwen3Moe => "qwen3moe",
312 Family::Qwen2Moe => "qwen2moe",
313 Family::Qwen35 => "qwen35",
314 Family::Qwen35Moe => "qwen35moe",
315 Family::Qwen3Next => "qwen3next",
316 Family::Gemma => "gemma",
317 Family::Gemma2 => "gemma2",
318 Family::Gemma3 => "gemma3",
319 Family::GptOss => "gpt-oss",
320 Family::Deepseek2 => "deepseek2",
321 Family::Llama4 => "llama4",
322 Family::MinimaxM2 => "minimax-m2",
323 Family::Phi3 => "phi3",
324 Family::Glm4 => "glm4",
325 Family::Glm4Moe => "glm4moe",
326 Family::Qwen4Exp => "qwen4exp",
327 }
328 }
329
330 fn hybrid(self) -> bool {
332 matches!(self, Family::Qwen35 | Family::Qwen35Moe | Family::Qwen3Next | Family::Qwen4Exp)
333 }
334
335 fn qwen_mixture(self) -> bool {
337 matches!(self, Family::Qwen2Moe | Family::Qwen35Moe | Family::Qwen3Next | Family::Qwen4Exp)
338 }
339}
340
341const INERT: &[&str] = &[
345 "_name_or_path",
346 "architectures",
347 "model_type",
348 "transformers_version",
349 "torch_dtype",
350 "dtype",
351 "use_cache",
352 "initializer_range",
353 "bos_token_id",
354 "eos_token_id",
355 "pad_token_id",
356 "attention_dropout",
357 "output_router_logits",
358 "router_aux_loss_coef",
359 "router_jitter_noise",
360 "pretraining_tp",
361 "unsloth_fixed",
362 "unsloth_version",
363 "boi_token_index",
364 "eoi_token_index",
365 "image_token_index",
366 "mm_tokens_per_image",
367 "cache_implementation",
368 "image_token_id",
369 "video_token_id",
370 "vision_start_token_id",
371 "vision_end_token_id",
372 "vision_config",
373 "text_config",
374 "language_model_only",
376 "tie_word_embeddings",
377 "max_window_layers",
378 "_sliding_window_pattern",
379 "mamba_ssm_dtype",
380 "mtp_use_dedicated_embeddings",
381 "num_nextn_predict_layers",
382 "attention_bias",
383 "qkv_bias",
384 "auto_map",
386 "aux_loss_alpha",
387 "seq_aux",
388 "ep_size",
389 "_attn_implementation_autoset",
393 "add_cross_attention",
394 "bad_words_ids",
395 "begin_suppress_tokens",
396 "chunk_size_feed_forward",
397 "cross_attention_hidden_size",
398 "decoder_start_token_id",
399 "diversity_penalty",
400 "do_sample",
401 "early_stopping",
402 "encoder_no_repeat_ngram_size",
403 "exponential_decay_length_penalty",
404 "finetuning_task",
405 "forced_bos_token_id",
406 "forced_eos_token_id",
407 "id2label",
408 "is_decoder",
409 "is_encoder_decoder",
410 "label2id",
411 "length_penalty",
412 "max_length",
413 "min_length",
414 "no_repeat_ngram_size",
415 "num_beam_groups",
416 "num_beams",
417 "num_return_sequences",
418 "output_attentions",
419 "output_hidden_states",
420 "output_scores",
421 "prefix",
422 "problem_type",
423 "pruned_heads",
424 "remove_invalid_values",
425 "repetition_penalty",
426 "return_dict",
427 "return_dict_in_generate",
428 "sep_token_id",
429 "suppress_tokens",
430 "task_specific_params",
431 "temperature",
432 "tf_legacy_loss",
433 "tie_encoder_decoder",
434 "tokenizer_class",
435 "top_k",
436 "top_p",
437 "torchscript",
438 "typical_p",
439 "use_bfloat16",
440];
441
442fn read_keys(f: Family) -> &'static [&'static str] {
444 const DECODER: &[&str] = &[
445 "hidden_size",
446 "intermediate_size",
447 "num_hidden_layers",
448 "num_attention_heads",
449 "num_key_value_heads",
450 "head_dim",
451 "rms_norm_eps",
452 "max_position_embeddings",
453 "vocab_size",
454 "rope_theta",
455 "rope_scaling",
456 "rope_parameters",
457 "hidden_act",
458 ];
459 let own: &[&str] = match f {
460 Family::Llama => &["mlp_bias", "num_local_experts", "num_experts_per_tok", "sliding_window"],
461 Family::Qwen2 => &["use_sliding_window", "sliding_window", "layer_types"],
462 Family::Qwen3 => &["use_sliding_window", "sliding_window", "layer_types"],
463 Family::Qwen3Moe => &[
464 "use_sliding_window",
465 "sliding_window",
466 "layer_types",
467 "moe_intermediate_size",
468 "num_experts",
469 "num_local_experts",
470 "num_experts_per_tok",
471 "norm_topk_prob",
472 "decoder_sparse_step",
473 "mlp_only_layers",
474 ],
475 Family::Qwen2Moe => &[
476 "use_sliding_window",
477 "sliding_window",
478 "layer_types",
479 "moe_intermediate_size",
480 "shared_expert_intermediate_size",
481 "num_experts",
482 "num_experts_per_tok",
483 "norm_topk_prob",
484 "decoder_sparse_step",
485 "mlp_only_layers",
486 ],
487 Family::Qwen3Next => &[
488 "decoder_sparse_step",
489 "full_attention_interval",
490 "layer_types",
491 "linear_conv_kernel_dim",
492 "linear_key_head_dim",
493 "linear_num_key_heads",
494 "linear_num_value_heads",
495 "linear_value_head_dim",
496 "mlp_only_layers",
497 "moe_intermediate_size",
498 "norm_topk_prob",
499 "num_experts",
500 "num_experts_per_tok",
501 "partial_rotary_factor",
502 "shared_expert_intermediate_size",
503 "use_sliding_window",
504 "output_gate_type",
505 ],
506 Family::Qwen4Exp => &[
507 "moe_intermediate_size",
508 "shared_expert_intermediate_size",
509 "num_experts",
510 "num_experts_per_tok",
511 "norm_topk_prob",
512 "full_attention_interval",
513 "layer_types",
514 "linear_conv_kernel_dim",
515 "linear_key_head_dim",
516 "linear_num_key_heads",
517 "linear_num_value_heads",
518 "linear_value_head_dim",
519 "mtp_num_hidden_layers",
520 "mtp",
521 "partial_rotary_factor",
522 "output_gate_type",
523 "hc_count",
524 "hc_lowrank",
525 "indexer_n_heads",
526 "indexer_kv_heads",
527 "indexer_head_dim",
528 "indexer_budget",
529 "indexer_compress_ratio",
530 "ple_layer_ids",
531 "ple_embed_dim",
532 "ple_conv_kernel_size",
533 "ngram_size",
534 "heads_per_ngram",
535 "ngram_vocab_size_base",
536 "make_ngram_vocab_size_divisible_by",
537 "seed",
538 "split_ngram_parts",
539 "number_of_conv_states",
540 ],
541 Family::Qwen35 | Family::Qwen35Moe => &[
542 "moe_intermediate_size",
543 "shared_expert_intermediate_size",
544 "num_experts",
545 "num_experts_per_tok",
546 "attn_output_gate",
547 "full_attention_interval",
548 "layer_types",
549 "linear_conv_kernel_dim",
550 "linear_key_head_dim",
551 "linear_num_key_heads",
552 "linear_num_value_heads",
553 "linear_value_head_dim",
554 "mtp_num_hidden_layers",
555 "mlp_only_layers",
556 "partial_rotary_factor",
557 "output_gate_type",
558 ],
559 Family::Gemma => &["hidden_activation", "use_bidirectional_attention"],
560 Family::Gemma2 => &[
561 "hidden_activation",
562 "use_bidirectional_attention",
563 "query_pre_attn_scalar",
564 "sliding_window",
565 "sliding_window_size",
566 "layer_types",
567 "attn_logit_softcapping",
568 "final_logit_softcapping",
569 ],
570 Family::Gemma3 => &[
571 "hidden_activation",
572 "query_pre_attn_scalar",
573 "sliding_window",
574 "sliding_window_pattern",
575 "layer_types",
576 "rope_local_base_freq",
577 "attn_logit_softcapping",
578 "final_logit_softcapping",
579 "use_bidirectional_attention",
580 ],
581 Family::GptOss => &[
582 "num_local_experts",
583 "num_experts_per_tok",
584 "experts_per_token",
585 "initial_context_length",
586 "sliding_window",
587 "layer_types",
588 "swiglu_limit",
589 "swiglu_alpha",
590 ],
591 Family::Deepseek2 => &[
592 "moe_intermediate_size",
593 "n_routed_experts",
594 "n_shared_experts",
595 "num_experts_per_tok",
596 "n_group",
597 "topk_group",
598 "first_k_dense_replace",
599 "kv_lora_rank",
600 "q_lora_rank",
601 "qk_nope_head_dim",
602 "qk_rope_head_dim",
603 "qk_head_dim",
604 "v_head_dim",
605 "routed_scaling_factor",
606 "norm_topk_prob",
607 "scoring_func",
608 "topk_method",
609 "rope_interleave",
610 "moe_layer_freq",
611 ],
612 Family::Llama4 => &[
613 "intermediate_size_mlp",
614 "num_local_experts",
615 "num_experts_per_tok",
616 "interleave_moe_layer_step",
617 "moe_layers",
618 "use_qk_norm",
619 "no_rope_layers",
620 "no_rope_layer_interval",
621 "attention_chunk_size",
622 "layer_types",
623 "attn_temperature_tuning",
624 "floor_scale",
625 "attn_scale",
626 "attention_bias",
627 "for_llm_compressor",
628 ],
629 Family::Glm4 => &["partial_rotary_factor"],
630 Family::Glm4Moe => &[
631 "partial_rotary_factor",
632 "moe_intermediate_size",
633 "n_routed_experts",
634 "n_shared_experts",
635 "num_experts_per_tok",
636 "first_k_dense_replace",
637 "routed_scaling_factor",
638 "norm_topk_prob",
639 "n_group",
640 "topk_group",
641 "use_qk_norm",
642 ],
643 Family::Phi3 => &[
644 "partial_rotary_factor",
645 "original_max_position_embeddings",
646 "sliding_window",
647 "resid_pdrop",
648 "embd_pdrop",
649 "lm_head_bias",
650 "mlp_bias",
651 "full_attn_mod",
652 "interpolate_factor",
653 ],
654 Family::MinimaxM2 => &[
655 "num_local_experts",
656 "num_experts_per_tok",
657 "rotary_dim",
658 "partial_rotary_factor",
659 "use_qk_norm",
660 "qk_norm_type",
661 "scoring_func",
662 "use_routing_bias",
663 "shared_intermediate_size",
664 "shared_moe_mode",
665 "mlp_intermediate_size",
666 "attn_type_list",
667 "layernorm_full_attention_beta",
668 "layernorm_linear_attention_beta",
669 "layernorm_mlp_beta",
670 "sliding_window",
671 "use_mtp",
672 "num_mtp_modules",
673 "mtp_transformer_layers",
674 ],
675 };
676 own.iter().copied().chain(DECODER.iter().copied()).collect::<Vec<_>>().leak()
677}
678
679fn validate(f: Family, h: &Hparams) -> io::Result<()> {
682 let known: BTreeSet<&str> =
683 INERT.iter().copied().chain(read_keys(f).iter().copied()).chain(["quantization_config"]).collect();
684 let mut unknown: Vec<&str> = h.map.keys().map(String::as_str).filter(|k| !known.contains(k)).collect();
685 unknown.sort();
686 if let Some(k) = unknown.first() {
687 return Err(invalid(format!("config.json: `{k}` is a key the {} intake does not read; unknown keys: {unknown:?}", f.arch())));
688 }
689 if let Some(q) = h.map.get("quantization_config").filter(|q| !q.is_null()) {
691 let method = q.get("quant_method").and_then(Value::as_str).unwrap_or("unnamed");
692 if method != "fp8" {
693 return Err(invalid(format!(
694 "config.json: `quantization_config` names a `{method}` checkpoint, a format the quantized-format intake does not read yet"
695 )));
696 }
697 }
698 let refuse = |k: &str, why: &str| Err(invalid(format!("config.json: `{k}` {why}")));
699 let get = |k: &str| h.map.get(k).filter(|v| !v.is_null());
700 let act = h.find(&["hidden_act", "hidden_activation"]).and_then(Value::as_str);
701 let gemma_scale = |deep: u64| -> io::Result<()> {
704 let Some(scalar) = get("query_pre_attn_scalar").and_then(Value::as_u64) else { return Ok(()) };
705 let layers = h.need_u64("num_hidden_layers")?;
706 let fixed = if layers == deep {
707 h.need_u64("hidden_size")? / h.need_u64("num_attention_heads")?
708 } else {
709 h.u64(&["head_dim"]).unwrap_or(256)
710 };
711 if scalar != fixed {
712 return refuse(
713 "query_pre_attn_scalar",
714 &format!("is {scalar}; llama.cpp scales this stack's scores by 1/√{fixed}, and its GGUF says no other"),
715 );
716 }
717 Ok(())
718 };
719 if matches!(f, Family::Gemma | Family::Gemma2 | Family::Gemma3) && get("use_bidirectional_attention").and_then(Value::as_bool) == Some(true) {
720 return refuse("use_bidirectional_attention", "is true; a causal decoder is read here");
721 }
722 match f {
723 Family::Gemma => {
725 for key in ["hidden_act", "hidden_activation"] {
726 if get(key).and_then(Value::as_str).is_some_and(|a| a != "gelu_pytorch_tanh" && a != "gelu") {
727 return refuse(key, "is not gelu_pytorch_tanh (or its legacy name gelu), the activation the gemma spec reads");
728 }
729 }
730 h.need_u64("head_dim")?;
731 }
732 Family::Gemma2 => {
733 for key in ["hidden_act", "hidden_activation"] {
734 if get(key).and_then(Value::as_str).is_some_and(|a| a != "gelu_pytorch_tanh") {
735 return refuse(key, "is not gelu_pytorch_tanh, the activation the gemma2 spec reads");
736 }
737 }
738 gemma_scale(46)?;
739 h.need_u64("head_dim")?;
740 for key in ["attn_logit_softcapping", "final_logit_softcapping"] {
741 if !get(key).and_then(Value::as_f64).is_some_and(|c| c > 0.0) {
742 return refuse(key, "is not a positive number; llama.cpp caps every Gemma 2, and its GGUF says no other");
743 }
744 }
745 let window = h.need_u64("sliding_window")?;
746 if get("sliding_window_size").and_then(Value::as_u64).is_some_and(|w| w != window) {
747 return refuse("sliding_window_size", &format!("is not sliding_window ({window}), the one window the GGUF carries"));
748 }
749 if let Some(Value::Array(types)) = h.map.get("layer_types") {
750 let alternating = types.len() as u64 == h.need_u64("num_hidden_layers")?
751 && types.iter().enumerate().all(|(l, t)| t.as_str() == Some(if l % 2 == 0 { "sliding_attention" } else { "full_attention" }));
752 if !alternating {
753 return refuse("layer_types", "is not a window on every other layer from the first, the pattern llama.cpp fixes for Gemma 2");
754 }
755 }
756 }
757 Family::Gemma3 => {
758 if act.is_some_and(|a| a != "gelu_pytorch_tanh") {
759 return refuse("hidden_activation", "is not gelu_pytorch_tanh, the activation the gemma3 spec reads");
760 }
761 gemma_scale(62)?;
762 if get("attn_logit_softcapping").is_some() {
763 return refuse("attn_logit_softcapping", "is set; Gemma 3 attention has no cap");
764 }
765 }
766 Family::Llama4 => {
768 if get("for_llm_compressor").and_then(Value::as_bool) == Some(true) {
769 return refuse("for_llm_compressor", "is true; experts stored one linear each are a layout the llama4 intake does not read");
770 }
771 }
772 Family::GptOss => {
773 if act.is_some_and(|a| a != "silu") {
774 return refuse("hidden_act", "is not silu");
775 }
776 if get("swiglu_limit").and_then(Value::as_f64).is_some_and(|v| v != 7.0) {
777 return refuse("swiglu_limit", "is not 7.0, the clamp the gpt-oss spec reads");
778 }
779 if get("swiglu_alpha").and_then(Value::as_f64).is_some_and(|v| v != 1.702) {
780 return refuse("swiglu_alpha", "is not 1.702, the slope the gpt-oss spec reads");
781 }
782 if get("experts_per_token").is_some_and(|v| Some(v) != get("num_experts_per_tok")) {
784 return refuse("experts_per_token", "differs from num_experts_per_tok, which the converter reads");
785 }
786 let original = rope_parameters(h).get("original_max_position_embeddings").cloned();
787 if get("initial_context_length").is_some_and(|v| original.as_ref().is_some_and(|o| o != v)) {
788 return refuse("initial_context_length", "differs from the rotation's original_max_position_embeddings, which the converter reads");
789 }
790 }
791 _ => {
792 if act.is_some_and(|a| a != "silu") {
793 return refuse("hidden_act", "is not silu, the activation this family's spec reads");
794 }
795 }
796 }
797 if get("use_sliding_window").and_then(Value::as_bool) == Some(true) {
798 return refuse("use_sliding_window", "is true; this family's GGUF carries no window");
799 }
800 if get("pruned_heads").is_some_and(|v| v.as_object().is_none_or(|o| !o.is_empty())) {
801 return refuse("pruned_heads", "names pruned heads, which the converter's GGUF does not carry");
802 }
803 for k in ["add_cross_attention", "is_encoder_decoder"] {
804 if get(k).and_then(Value::as_bool) == Some(true) {
805 return refuse(k, "is true; a decoder alone is read here");
806 }
807 }
808 if f == Family::Deepseek2 && get("moe_layer_freq").is_some_and(|v| v.as_u64() != Some(1)) {
809 return refuse("moe_layer_freq", "is not 1; llama.cpp's deepseek2 reads every block past the dense lead as a mixture");
810 }
811 if get("mlp_bias").and_then(Value::as_bool) == Some(true) {
812 return refuse("mlp_bias", "is true; the spec reads no FFN biases");
813 }
814 if let Some(a) = get("mlp_only_layers").and_then(Value::as_array) {
815 if !a.is_empty() {
816 return refuse("mlp_only_layers", "names dense layers inside a mixture, which the spec does not read");
817 }
818 }
819 if get("decoder_sparse_step").and_then(Value::as_u64).is_some_and(|s| s != 1) {
820 return refuse("decoder_sparse_step", "is not 1; every layer is read as a mixture");
821 }
822 let renormalized = get("norm_topk_prob").and_then(Value::as_bool).unwrap_or(f == Family::Qwen4Exp);
825 match f {
826 Family::Qwen2Moe if renormalized => {
827 return refuse("norm_topk_prob", "is true; llama.cpp's qwen2moe keeps the top-k softmax weights as they are")
828 }
829 Family::Qwen3Moe | Family::Qwen3Next | Family::Qwen4Exp if !renormalized => {
830 return refuse("norm_topk_prob", "is not true; llama.cpp's qwen3moe renormalizes the top-k softmax weights")
831 }
832 _ => {}
833 }
834 if f.hybrid() && get("attn_output_gate").and_then(Value::as_bool) == Some(false) {
835 return refuse("attn_output_gate", "is false; Qwen3.5's attention is read with its output gate");
836 }
837 if f == Family::Qwen4Exp {
841 match get("output_gate_type").and_then(Value::as_str) {
842 Some("sigmoid") => {}
843 Some(other) => return refuse("output_gate_type", &format!("is `{other}`; llama.cpp's qwen4exp gates Gated DeltaNet by the sigmoid")),
844 None => return refuse("output_gate_type", "is unset; transformers gates by `hidden_act`, llama.cpp's qwen4exp by the sigmoid"),
845 }
846 if get("indexer_kv_heads").and_then(Value::as_u64).is_some_and(|k| k != 1) {
847 return refuse("indexer_kv_heads", "is not 1; QSA scores every index query against one key head");
848 }
849 if get("ple_layer_ids").and_then(Value::as_array).is_some_and(|l| l.len() > 1) {
850 return refuse("ple_layer_ids", "names more than one layer; the converter's GGUF holds one n-gram table");
851 }
852 let ple = get("ple_layer_ids").and_then(Value::as_array).is_some_and(|l| !l.is_empty());
855 if get("number_of_conv_states").and_then(Value::as_u64).is_some_and(|n| n != if ple { 3 } else { 1 }) {
856 return refuse("number_of_conv_states", "is not the count the n-gram layers give (3 with them, 1 without)");
857 }
858 if let Some(mtp) = get("mtp") {
859 let stated = get("mtp_num_hidden_layers").and_then(Value::as_u64);
860 if mtp.get("num_hidden_layers").and_then(Value::as_u64).is_some_and(|n| Some(n) != stated) {
861 return refuse("mtp", "counts draft layers other than `mtp_num_hidden_layers`");
862 }
863 }
864 } else if let Some(gate) = get("output_gate_type").filter(|g| !matches!(g.as_str(), Some("silu" | "swish"))) {
865 return refuse("output_gate_type", &format!("is {gate}; Gated DeltaNet's norm is read gated by SiLU (swish)"));
866 }
867 if f == Family::Llama && get("sliding_window").is_some() {
868 return refuse("sliding_window", "is set; the llama GGUF carries no window, and llama.cpp attends every position");
869 }
870 if matches!(f, Family::Qwen2 | Family::Qwen3 | Family::Qwen3Moe | Family::Qwen2Moe) {
871 if let Some(types) = get("layer_types").and_then(Value::as_array) {
872 if types.iter().any(|t| t.as_str() != Some("full_attention")) {
873 return refuse("layer_types", "names a layer that is not full attention");
874 }
875 }
876 }
877 if f == Family::Llama4 {
878 let n = |k: &str| get(k).and_then(Value::as_f64);
881 if n("attention_chunk_size").is_some_and(|c| c != 8192.0) {
882 return refuse("attention_chunk_size", "is not 8192, the chunk llama.cpp's llama4 attends within");
883 }
884 if n("floor_scale").is_some_and(|c| c != 8192.0) {
885 return refuse("floor_scale", "is not 8192, the attention temperature's floor llama.cpp fixes");
886 }
887 if n("attn_scale").is_some_and(|c| c != 0.1) {
888 return refuse("attn_scale", "is not 0.1, the attention temperature's scale llama.cpp fixes");
889 }
890 if get("attn_temperature_tuning").and_then(Value::as_bool) == Some(false) {
891 return refuse("attn_temperature_tuning", "is false; llama.cpp tunes every rotation-free layer's temperature");
892 }
893 if get("attention_bias").and_then(Value::as_bool) == Some(true) {
894 return refuse("attention_bias", "is true; the spec reads no attention biases");
895 }
896 let experts = get("num_local_experts").and_then(Value::as_u64).unwrap_or(0);
897 if get("use_qk_norm").and_then(Value::as_bool).is_some_and(|q| q != (experts != 128)) {
898 return refuse("use_qk_norm", "disagrees with llama.cpp's rule: a QK norm unless the model has 128 experts");
899 }
900 let layers = get("num_hidden_layers").and_then(Value::as_u64).unwrap_or(0) as usize;
901 if let Some(a) = get("no_rope_layers").and_then(Value::as_array) {
902 let every_fourth: Vec<u64> = (0..layers).map(|l| u64::from((l + 1) % 4 != 0)).collect();
903 if a.iter().map(Value::as_u64).collect::<Option<Vec<u64>>>() != Some(every_fourth) {
904 return refuse("no_rope_layers", "is not every fourth layer unrotated, the pattern llama.cpp fixes");
905 }
906 }
907 let step = get("interleave_moe_layer_step").and_then(Value::as_u64).unwrap_or(1) as usize;
908 if let Some(a) = get("moe_layers").and_then(Value::as_array) {
909 let stepped: Vec<u64> = (0..layers).filter(|l| step > 0 && (l + 1) % step == 0).map(|l| l as u64).collect();
910 if a.iter().map(Value::as_u64).collect::<Option<Vec<u64>>>() != Some(stepped) {
911 return refuse("moe_layers", "is not every interleave_moe_layer_step-th layer, the layers the GGUF names");
912 }
913 }
914 }
915 if f == Family::Deepseek2 && get("rope_interleave").and_then(Value::as_bool) == Some(false) {
916 return refuse("rope_interleave", "is false; latent attention is read with interleaved rotation");
917 }
918 if matches!(f, Family::Glm4 | Family::Glm4Moe) && rope_parameters(h).contains_key("mrope_section") {
919 return refuse("rope_parameters", "names `mrope_section`: a GLM vision model's multimodal rotation is not read yet");
920 }
921 if f == Family::Phi3 {
922 if get("lm_head_bias").and_then(Value::as_bool) == Some(true) {
923 return refuse("lm_head_bias", "is true; the spec reads no head bias");
924 }
925 for (k, one) in [("full_attn_mod", 1.0), ("interpolate_factor", 1.0)] {
926 if get(k).and_then(Value::as_f64).is_some_and(|v| v != one) {
927 return refuse(k, "is not 1; the converter's Phi-3 class reads no such setting");
928 }
929 }
930 let longrope = h.map.get("rope_scaling").or_else(|| h.map.get("rope_parameters")).and_then(|r| {
931 r.get("rope_type").or_else(|| r.get("type")).and_then(Value::as_str).map(|t| t == "longrope" || t == "su")
932 });
933 if longrope == Some(true) {
934 return refuse("rope_scaling", "is LongRoPE (Phi-3.5, Phi-4-mini), whose factor tables are not read yet");
935 }
936 }
937 if f == Family::MinimaxM2 {
938 if let Some(types) = get("attn_type_list").and_then(Value::as_array) {
941 if types.iter().any(|t| t.as_u64() != Some(1)) {
942 return refuse("attn_type_list", "names a linear-attention layer (MiniMax-01's); every MiniMax-M2 layer is full attention");
943 }
944 }
945 for beta in ["layernorm_full_attention_beta", "layernorm_linear_attention_beta", "layernorm_mlp_beta"] {
946 if get(beta).and_then(Value::as_f64).is_some_and(|b| b != 1.0) {
947 return refuse(beta, "is not 1.0; the residual adds each sublayer's output unscaled");
948 }
949 }
950 if get("shared_intermediate_size").and_then(Value::as_u64).is_some_and(|w| w > 0) {
951 return refuse("shared_intermediate_size", "is not 0; the converter's MiniMax-M2 writes no shared expert");
952 }
953 if get("sliding_window").is_some() {
954 return refuse("sliding_window", "is set; MiniMax-M2 attends every position");
955 }
956 let rotary = get("rotary_dim").and_then(Value::as_u64);
957 let head = get("head_dim").and_then(Value::as_u64);
958 let factor = h.map.get("partial_rotary_factor").or_else(|| h.map.get("rope_parameters").and_then(|r| r.get("partial_rotary_factor")));
959 if let (Some(r), Some(hd), Some(f)) = (rotary, head, factor.and_then(Value::as_f64)) {
960 if (r as f64 - f * hd as f64).abs() > 0.5 {
961 return refuse("rotary_dim", "disagrees with partial_rotary_factor of head_dim");
962 }
963 }
964 }
965 Ok(())
966}
967
968impl HfCheckpoint {
969 pub fn open(dir: &Path) -> io::Result<HfCheckpoint> {
971 HfCheckpoint::open_with(dir, Recompute::AsConverted)
972 }
973
974 pub fn read_configuration(dir: &Path) -> io::Result<(String, BTreeMap<String, MetaValue>)> {
978 let (family, h) = configuration(dir)?;
979 let mut w = Writer { arch: family.arch().to_string(), meta: BTreeMap::new() };
980 let rope = rope_parameters(&h);
981 let block_count = h.need_u64("num_hidden_layers")? + stated_draft_blocks(family, &h).unwrap_or(0);
982 base_parameters(&h, &rope, &mut w, block_count, family)?;
983 family_parameters(family, &h, &rope, &mut w, block_count)?;
984 Ok((family.arch().to_string(), w.meta))
985 }
986
987 pub fn read_tokenizer(dir: &Path) -> io::Result<BTreeMap<String, MetaValue>> {
990 let (family, h) = configuration(dir)?;
991 vocab::tokenizer_meta(dir, &h, family)
992 }
993
994 pub fn open_with(dir: &Path, recompute: Recompute) -> io::Result<HfCheckpoint> {
996 let (family, h) = configuration(dir)?;
997
998 let paths = safetensors::shard_paths(dir)?;
999 let shards: Vec<Shard> = paths.iter().map(|p| Shard::open(p)).collect::<io::Result<_>>()?;
1000 let mut located = HashMap::new();
1001 for (i, s) in shards.iter().enumerate() {
1002 for name in s.tensors.keys() {
1003 if located.insert(name.clone(), i).is_some() {
1004 return Err(invalid(format!("{}: tensor {name} is in two shards", dir.display())));
1005 }
1006 }
1007 }
1008 let (scaled, consumed) = fp8_scales(&h, &located)?;
1009 let mut c = HfCheckpoint {
1010 dir: dir.to_path_buf(),
1011 recompute,
1012 family,
1013 hparams: Hparams { map: Map::new(), architecture: String::new() },
1014 tokenizer: OnceLock::new(),
1015 arch: family.arch().to_string(),
1016 meta: BTreeMap::new(),
1017 views: Vec::new(),
1018 index: HashMap::new(),
1019 shards,
1020 located,
1021 scaled,
1022 consumed,
1023 unread: Vec::new(),
1024 skipped: Vec::new(),
1025 };
1026 let mut w = Writer { arch: c.arch.clone(), meta: BTreeMap::new() };
1027 let rope = rope_parameters(&h);
1028 let block_count = h.need_u64("num_hidden_layers")? + stated_draft_blocks(family, &h).unwrap_or_else(|| draft_layers(&c));
1029 base_parameters(&h, &rope, &mut w, block_count, family)?;
1030 family_parameters(family, &h, &rope, &mut w, block_count)?;
1031 c.ple_parameters(&mut w)?;
1032 c.meta = w.meta;
1033 c.build_views(family, &h, &rope)?;
1034 c.hparams = h;
1035 Ok(c)
1036 }
1037
1038 pub fn tokenizer_meta(&self) -> io::Result<&BTreeMap<String, MetaValue>> {
1041 if let Some(m) = self.tokenizer.get() {
1042 return Ok(m);
1043 }
1044 let m = vocab::tokenizer_meta(&self.dir, &self.hparams, self.family)?;
1045 Ok(self.tokenizer.get_or_init(|| m))
1046 }
1047
1048 pub fn unread(&self) -> &[String] {
1050 &self.unread
1051 }
1052
1053 pub fn skipped(&self) -> &[String] {
1055 &self.skipped
1056 }
1057
1058 pub fn recomputed(&self) -> Vec<&str> {
1060 self.views.iter().filter(|v| matches!(v.form, Form::Map { .. })).map(|v| v.name.as_str()).collect()
1061 }
1062
1063 pub fn shard_count(&self) -> usize {
1065 self.shards.len()
1066 }
1067
1068 pub fn dir(&self) -> &Path {
1069 &self.dir
1070 }
1071
1072 fn entry(&self, name: &str) -> Option<&Entry> {
1073 self.located.get(name).map(|&i| &self.shards[i].tensors[name])
1074 }
1075
1076 fn need(&self, name: &str) -> io::Result<&Entry> {
1077 self.entry(name).ok_or_else(|| invalid(format!("{}: no tensor {name}", self.dir.display())))
1078 }
1079
1080 fn present(&mut self, name: String, form: Form) -> io::Result<()> {
1081 let dims = self.dims_of(&form)?;
1082 self.push(name, dims, form)
1083 }
1084
1085 fn push(&mut self, name: String, dims: Vec<u64>, form: Form) -> io::Result<()> {
1086 let n: u64 = dims.iter().product();
1087 if n as usize != form.elements() {
1088 return Err(invalid(format!("{name}: dims {dims:?} hold {n} elements, its view {}", form.elements())));
1089 }
1090 self.index.insert(name.clone(), self.views.len());
1091 self.views.push(View { name, dims, form });
1092 Ok(())
1093 }
1094
1095 fn dims_of(&self, form: &Form) -> io::Result<Vec<u64>> {
1099 Ok(match form {
1100 Form::Gather { src, outer, m, n, rows, cols, transpose } => {
1101 let e = self.need(src)?;
1102 let identity = *outer == 1
1103 && !*transpose
1104 && rows.len() == *m
1105 && cols.len() == *n
1106 && rows.iter().enumerate().all(|(i, &r)| i == r)
1107 && cols.iter().enumerate().all(|(i, &c)| i == c);
1108 if e.shape.len() <= 1 {
1109 vec![form.elements() as u64]
1110 } else if identity {
1111 e.shape.iter().rev().copied().collect()
1112 } else {
1113 let (r, c) = if *transpose { (cols.len(), rows.len()) } else { (rows.len(), cols.len()) };
1114 let mut torch = Vec::new();
1115 if *outer > 1 {
1116 torch.push(*outer as u64);
1117 }
1118 if r > 1 || *outer == 1 {
1119 torch.push(r as u64);
1120 }
1121 torch.push(c as u64);
1122 torch.into_iter().rev().collect()
1123 }
1124 }
1125 Form::Concat(parts) => {
1126 let mut d = self.dims_of(&parts[0])?;
1127 d.push(parts.len() as u64);
1128 d
1129 }
1130 Form::Beside(parts) => {
1131 let dims = parts.iter().map(|p| self.dims_of(p)).collect::<io::Result<Vec<_>>>()?;
1132 if dims.iter().any(|d| d.len() != 2 || d[1] != dims[0][1]) {
1133 return Err(invalid(format!("parts side by side of different rows: {dims:?}")));
1134 }
1135 vec![dims.iter().map(|d| d[0]).sum(), dims[0][1]]
1136 }
1137 Form::Map { inner, .. } => self.dims_of(inner)?,
1138 Form::Values(v) => vec![v.len() as u64],
1139 })
1140 }
1141
1142 fn ty_of(&self, form: &Form) -> io::Result<GgmlType> {
1144 Ok(match form {
1145 Form::Gather { src, .. } => self.presented(src)?,
1146 Form::Concat(parts) | Form::Beside(parts) => {
1147 let t = self.ty_of(&parts[0])?;
1148 for p in &parts[1..] {
1149 if self.ty_of(p)? != t {
1150 return Err(invalid("a stack of tensors of different dtypes"));
1151 }
1152 }
1153 t
1154 }
1155 Form::Map { .. } | Form::Values(_) => GgmlType::F32,
1156 })
1157 }
1158
1159 fn presented(&self, src: &str) -> io::Result<GgmlType> {
1161 Ok(if self.scaled.contains_key(src) { GgmlType::F32 } else { ggml_type(self.need(src)?.dtype) })
1162 }
1163
1164 fn source(&self, src: &str, first: usize, count: usize) -> io::Result<Vec<u8>> {
1168 let entry = self.need(src)?;
1169 let es = entry.dtype.bytes();
1170 let raw = self.shards[self.located[src]].read(src, (first * es) as u64, count * es)?;
1171 let Some(scaled) = self.scaled.get(src) else { return Ok(widen(entry.dtype, raw)) };
1172 let grid = scaled.grid.get_or_init(|| self.scale_grid(src, scaled)).as_ref().map_err(|e| invalid(e.clone()))?;
1173 let mut values = Vec::with_capacity(count);
1174 ggml_type(entry.dtype).dequantize(&widen(entry.dtype, raw), &mut values);
1175 grid.apply(first, &mut values);
1176 Ok(values.iter().flat_map(|v| v.to_le_bytes()).collect())
1177 }
1178
1179 fn scale_grid(&self, src: &str, scaled: &Scaled) -> Result<ScaleGrid, String> {
1181 let (weight, scale) = (self.need(src).map_err(|e| e.to_string())?, self.need(&scaled.scale).map_err(|e| e.to_string())?);
1182 let raw = self.shards[self.located[scaled.scale.as_str()]]
1183 .read(&scaled.scale, 0, scale.elements() as usize * scale.dtype.bytes())
1184 .map_err(|e| e.to_string())?;
1185 let mut values = Vec::with_capacity(scale.elements() as usize);
1186 ggml_type(scale.dtype).dequantize(&widen(scale.dtype, raw), &mut values);
1187 let axes = |e: &Entry| e.shape.iter().map(|&d| d as usize).collect::<Vec<usize>>();
1188 ScaleGrid::new(&axes(weight), &axes(scale), values, scaled.block.as_deref()).map_err(|e| format!("{src} scaled by {}: {e}", scaled.scale))
1189 }
1190
1191 fn read(&self, form: &Form, start: usize, count: usize) -> io::Result<Vec<u8>> {
1193 match form {
1194 Form::Gather { src, outer, m, n, rows, cols, transpose } => {
1195 let es = self.presented(src)?.block_bytes();
1196 let all_cols = cols.len() == *n && cols.iter().enumerate().all(|(i, &c)| i == c);
1197 let identity = !*transpose && all_cols && rows.len() == *m && rows.iter().enumerate().all(|(i, &r)| i == r);
1198 if identity {
1199 return self.source(src, start, count);
1200 }
1201 let mut out = Vec::with_capacity(count * es);
1202 if !*transpose {
1203 let (out_c, per) = (cols.len(), rows.len() * cols.len());
1205 let mut e = start;
1206 while e < start + count {
1207 let (b, rem) = (e / per, e % per);
1208 let (i, j0) = (rem / out_c, rem % out_c);
1209 let take = (out_c - j0).min(start + count - e);
1210 let source_row = b * m + rows[i];
1211 if all_cols {
1212 out.extend(self.source(src, source_row * n + j0, take)?);
1213 } else {
1214 let row = self.source(src, source_row * n, *n)?;
1215 for &c in &cols[j0..j0 + take] {
1216 out.extend_from_slice(&row[c * es..(c + 1) * es]);
1217 }
1218 }
1219 e += take;
1220 }
1221 return Ok(out);
1222 }
1223 let (out_r, out_c) = (cols.len(), rows.len());
1225 let per = out_r * out_c;
1226 let mut block: Option<(usize, Vec<u8>)> = None;
1227 for e in start..start + count {
1228 let b = e / per;
1229 let rem = e % per;
1230 let (i, j) = (rem / out_c, rem % out_c);
1231 let (r, c) = (rows[j], cols[i]);
1232 if block.as_ref().map(|(bb, _)| *bb) != Some(b) {
1233 debug_assert!(b < *outer);
1234 block = Some((b, self.source(src, b * m * n, m * n)?));
1235 }
1236 let at = (r * n + c) * es;
1237 out.extend_from_slice(&block.as_ref().unwrap().1[at..at + es]);
1238 }
1239 Ok(out)
1240 }
1241 Form::Concat(parts) => {
1242 let mut out = Vec::new();
1243 let mut base = 0;
1244 for p in parts {
1245 let len = p.elements();
1246 let lo = start.max(base);
1247 let hi = (start + count).min(base + len);
1248 if lo < hi {
1249 out.extend(self.read(p, lo - base, hi - lo)?);
1250 }
1251 base += len;
1252 }
1253 Ok(out)
1254 }
1255 Form::Beside(parts) => {
1256 let widths =
1257 parts.iter().map(|p| Ok(self.dims_of(p)?[0] as usize)).collect::<io::Result<Vec<usize>>>()?;
1258 let row = widths.iter().sum::<usize>();
1259 let mut out = Vec::new();
1260 let mut e = start;
1261 while e < start + count {
1262 let (r, mut c) = (e / row, e % row);
1263 let mut part = 0;
1264 while c >= widths[part] {
1265 c -= widths[part];
1266 part += 1;
1267 }
1268 let take = (widths[part] - c).min(start + count - e);
1269 out.extend(self.read(&parts[part], r * widths[part] + c, take)?);
1270 e += take;
1271 }
1272 Ok(out)
1273 }
1274 Form::Values(v) => Ok(v[start..start + count].iter().flat_map(|x| x.to_le_bytes()).collect()),
1275 Form::Map { inner, op } => {
1276 let ty = self.ty_of(inner)?;
1277 let raw = self.read(inner, start, count)?;
1278 let mut vals = Vec::with_capacity(count);
1279 ty.dequantize(&raw, &mut vals);
1280 let round = |x: f32| match (self.recompute, ty) {
1281 (Recompute::AsConverted, GgmlType::F16) => f16_to_f32(f32_to_f16(x)),
1282 _ => x,
1283 };
1284 Ok(vals
1285 .into_iter()
1286 .map(|x| match op {
1287 Op::AddOne => 1.0f32 + x,
1288 Op::NegExp => -((x as f64).exp() as f32),
1290 })
1291 .map(round)
1292 .flat_map(f32::to_le_bytes)
1293 .collect())
1294 }
1295 }
1296 }
1297
1298 fn build_views(&mut self, f: Family, h: &Hparams, rope: &Map<String, Value>) -> io::Result<()> {
1300 let names: Vec<String> = {
1301 let mut v: Vec<String> = self.located.keys().cloned().collect();
1302 v.sort();
1303 v
1304 };
1305 let mut read: BTreeSet<String> = self.consumed.clone();
1307 let p = decoder_prefix(&h.architecture, &names);
1308 let head = if p == "language_model.model." { "language_model.lm_head.weight" } else { "lm_head.weight" };
1309 let n_layers = h.need_u64("num_hidden_layers")? as usize;
1310 let tied = h.find(&["tie_word_embeddings"]).and_then(Value::as_bool).unwrap_or(false);
1311
1312 for name in &names {
1314 let s = name.strip_prefix("model.").unwrap_or(name);
1315 let tower = s.contains("visual.") || s.contains("vision.") || s.starts_with("vision_") || s.starts_with("multi_modal_projector.");
1316 let deepseek_draft = f == Family::Deepseek2
1317 && layer_of(name, "model.layers.").is_some_and(|(l, _)| l >= n_layers);
1318 let tied_head = name == head && (tied && f == Family::Deepseek2 || matches!(f, Family::Gemma | Family::Gemma2));
1320 if tower || deepseek_draft || tied_head {
1321 self.skipped.push(name.clone());
1322 read.insert(name.clone());
1323 }
1324 }
1325
1326 if f == Family::Llama || f == Family::Llama4 {
1327 if let Some(factors) = llama3_rope_factors(h, rope) {
1328 self.present("rope_freqs.weight".into(), Form::Values(factors))?;
1329 }
1330 }
1331
1332 let embed = format!("{p}embed_tokens.weight");
1334 let e = self.need(&embed)?.clone();
1335 let mut embed_form = Form::whole(&embed, &e);
1336 let mut embed_dims: Vec<u64> = e.shape.iter().rev().copied().collect();
1337 if f == Family::Gemma3 {
1338 let real = gemma_vocab(&self.dir, h)?;
1340 if let Form::Gather { rows, .. } = &mut embed_form {
1341 if real < rows.len() {
1342 rows.truncate(real);
1343 embed_dims[1] = real as u64;
1344 }
1345 }
1346 }
1347 self.push("token_embd.weight".into(), embed_dims, embed_form)?;
1348 read.insert(embed);
1349 if f == Family::Qwen4Exp {
1350 for (stored, gguf) in [
1352 ("hc_norm.weight", "output_hc_norm.weight"),
1353 ("input_mix_weight_down.weight", "output_hc_down.weight"),
1354 ("input_mix_weight_up.weight", "output_hc_up.weight"),
1355 ] {
1356 let name = format!("{p}hyper_connection_mixer.{stored}");
1357 let form = if stored == "hc_norm.weight" { self.norm_form(f, &name)? } else { Form::whole(&name, self.need(&name)?) };
1358 self.present(gguf.into(), form)?;
1359 read.insert(name);
1360 }
1361 } else {
1362 let norm = format!("{p}norm.weight");
1363 let nf = self.norm_form(f, &norm)?;
1364 self.present("output_norm.weight".into(), nf)?;
1365 read.insert(norm);
1366 }
1367 if !self.skipped.iter().any(|s| s == head) {
1368 if let Some(entry) = self.entry(head).cloned() {
1369 self.present("output.weight".into(), Form::whole(head, &entry))?;
1370 read.insert(head.into());
1371 }
1372 }
1373
1374 let mut experts: BTreeMap<(usize, &'static str), BTreeMap<usize, String>> = BTreeMap::new();
1376 let mut ngram_shards: BTreeMap<usize, String> = BTreeMap::new();
1378 for name in &names {
1379 if read.contains(name) {
1380 continue;
1381 }
1382 let (l, rest) = if let Some((l, rest)) = layer_of(name, &format!("{p}layers.")) {
1383 (l, rest.to_string())
1384 } else if f.hybrid() && name.starts_with("mtp.") {
1385 let r = &name["mtp.".len()..];
1388 if let Some((0, rest)) = layer_of(name, "mtp.layers.") {
1389 (n_layers, rest.to_string())
1390 } else if f == Family::Qwen4Exp && matches!(r, "fc_embedding.weight" | "fc_hidden.weight") {
1391 if r == "fc_embedding.weight" {
1393 let (e, hd) = ("mtp.fc_embedding.weight".to_string(), "mtp.fc_hidden.weight".to_string());
1394 let form = Form::Beside(vec![Form::whole(&e, self.need(&e)?), Form::whole(&hd, self.need(&hd)?)]);
1395 self.present(format!("blk.{n_layers}.nextn.eh_proj.weight"), form)?;
1396 read.insert(e);
1397 read.insert(hd);
1398 }
1399 continue;
1400 } else {
1401 let nextn = match r {
1402 "hyper_connection_mixer.hc_norm.weight" if f == Family::Qwen4Exp => "nextn.hc_head_norm.weight",
1403 "hyper_connection_mixer.input_mix_weight_down.weight" if f == Family::Qwen4Exp => "nextn.hc_head_down.weight",
1404 "hyper_connection_mixer.input_mix_weight_up.weight" if f == Family::Qwen4Exp => "nextn.hc_head_up.weight",
1405 "fc.weight" => "nextn.eh_proj.weight",
1406 "norm.weight" => "nextn.shared_head_norm.weight",
1407 "pre_fc_norm_embedding.weight" => "nextn.enorm.weight",
1408 "pre_fc_norm_hidden.weight" => "nextn.hnorm.weight",
1409 _ => continue,
1410 };
1411 let form = if nextn.ends_with("norm.weight") {
1412 self.norm_form(f, name)?
1413 } else {
1414 Form::whole(name, &self.need(name)?.clone())
1415 };
1416 self.present(format!("blk.{n_layers}.{nextn}"), form)?;
1417 read.insert(name.clone());
1418 continue;
1419 }
1420 } else {
1421 continue;
1422 };
1423 let blk = |s: &str| format!("blk.{l}.{s}");
1424 if let Some(proj) = expert_of(&rest) {
1425 experts.entry((l, proj.1)).or_default().insert(proj.0, name.clone());
1427 read.insert(name.clone());
1428 continue;
1429 }
1430 let entry = self.need(name)?.clone();
1431 let whole = Form::whole(name, &entry);
1432 let gguf: Option<(String, Form)> = match (f, rest.as_str()) {
1433 (_, "input_layernorm.weight") => Some((blk("attn_norm.weight"), self.norm_form(f, name)?)),
1434 (Family::Gemma2 | Family::Gemma3 | Family::Qwen35 | Family::Qwen35Moe | Family::Qwen3Next | Family::GptOss | Family::Glm4Moe, "post_attention_layernorm.weight") => {
1435 Some((blk("post_attention_norm.weight"), self.norm_form(f, name)?))
1436 }
1437 (_, "post_attention_layernorm.weight") => Some((blk("ffn_norm.weight"), self.norm_form(f, name)?)),
1438 (Family::Gemma2 | Family::Gemma3, "pre_feedforward_layernorm.weight") => Some((blk("ffn_norm.weight"), self.norm_form(f, name)?)),
1439 (Family::Gemma2 | Family::Gemma3, "post_feedforward_layernorm.weight") => Some((blk("post_ffw_norm.weight"), self.norm_form(f, name)?)),
1440 (_, "self_attn.q_norm.weight") => Some((blk("attn_q_norm.weight"), self.norm_form(f, name)?)),
1441 (_, "self_attn.k_norm.weight") => Some((blk("attn_k_norm.weight"), self.norm_form(f, name)?)),
1442 (Family::Llama, "self_attn.q_proj.weight" | "self_attn.q_proj.bias") => {
1443 let heads = h.need_u64("num_attention_heads")? as usize;
1444 let suffix = if rest.ends_with("bias") { "attn_q.bias" } else { "attn_q.weight" };
1445 Some((blk(suffix), llama_permute(name, &entry, heads)))
1446 }
1447 (Family::Llama, "self_attn.k_proj.weight" | "self_attn.k_proj.bias") => {
1448 let kv = h.u64(&["num_key_value_heads"]).unwrap_or(h.need_u64("num_attention_heads")?) as usize;
1449 let suffix = if rest.ends_with("bias") { "attn_k.bias" } else { "attn_k.weight" };
1450 Some((blk(suffix), llama_permute(name, &entry, kv)))
1451 }
1452 (_, "self_attn.q_proj.weight") => Some((blk("attn_q.weight"), whole)),
1453 (_, "self_attn.k_proj.weight") => Some((blk("attn_k.weight"), whole)),
1454 (_, "self_attn.v_proj.weight") => Some((blk("attn_v.weight"), whole)),
1455 (_, "self_attn.o_proj.weight") => Some((blk("attn_output.weight"), whole)),
1456 (_, "self_attn.q_proj.bias") => Some((blk("attn_q.bias"), whole)),
1457 (_, "self_attn.k_proj.bias") => Some((blk("attn_k.bias"), whole)),
1458 (_, "self_attn.v_proj.bias") => Some((blk("attn_v.bias"), whole)),
1459 (_, "self_attn.o_proj.bias") => Some((blk("attn_output.bias"), whole)),
1460 (Family::GptOss, "self_attn.sinks") => Some((blk("attn_sinks.weight"), whole)),
1461 (_, "mlp.gate_proj.weight") => Some((blk("ffn_gate.weight"), whole)),
1462 (_, "mlp.up_proj.weight") => Some((blk("ffn_up.weight"), whole)),
1463 (_, "mlp.down_proj.weight") => Some((blk("ffn_down.weight"), whole)),
1464 (
1465 Family::Qwen3Moe
1466 | Family::Qwen2Moe
1467 | Family::Qwen35Moe
1468 | Family::Qwen3Next
1469 | Family::Qwen4Exp
1470 | Family::Deepseek2
1471 | Family::Glm4Moe,
1472 "mlp.gate.weight",
1473 ) => {
1474 Some((blk("ffn_gate_inp.weight"), whole))
1475 }
1476 (f, "mlp.shared_expert.gate_proj.weight") if f.qwen_mixture() => Some((blk("ffn_gate_shexp.weight"), whole)),
1477 (f, "mlp.shared_expert.up_proj.weight") if f.qwen_mixture() => Some((blk("ffn_up_shexp.weight"), whole)),
1478 (f, "mlp.shared_expert.down_proj.weight") if f.qwen_mixture() => Some((blk("ffn_down_shexp.weight"), whole)),
1479 (f, "mlp.shared_expert_gate.weight") if f.qwen_mixture() => Some((blk("ffn_gate_inp_shexp.weight"), whole)),
1481 (Family::Qwen3Moe | Family::Qwen2Moe | Family::Qwen35Moe | Family::Qwen3Next | Family::Qwen4Exp, "mlp.experts.gate_up_proj") => {
1484 let (e, two, d) = dims3(&entry, name)?;
1485 let ff = two / 2;
1486 let half = |from: usize| Form::Gather {
1487 src: name.clone(),
1488 outer: e,
1489 m: two,
1490 n: d,
1491 rows: (from..from + ff).collect(),
1492 cols: (0..d).collect(),
1493 transpose: false,
1494 };
1495 self.present(blk("ffn_gate_exps.weight"), half(0))?;
1496 Some((blk("ffn_up_exps.weight"), half(ff)))
1497 }
1498 (Family::Qwen3Moe | Family::Qwen2Moe | Family::Qwen35Moe | Family::Qwen3Next | Family::Qwen4Exp, "mlp.experts.down_proj") => {
1499 Some((blk("ffn_down_exps.weight"), whole))
1500 }
1501 (Family::Llama | Family::MinimaxM2, "block_sparse_moe.gate.weight") => Some((blk("ffn_gate_inp.weight"), whole)),
1502 (Family::MinimaxM2, "block_sparse_moe.e_score_correction_bias") => Some((blk("exp_probs_b.bias"), whole)),
1503 (Family::Phi3, "self_attn.qkv_proj.weight") => Some((blk("attn_qkv.weight"), whole)),
1504 (Family::Phi3 | Family::Glm4, "mlp.gate_up_proj.weight") => Some((blk("ffn_up.weight"), whole)),
1505 (Family::Glm4, "post_self_attn_layernorm.weight") => Some((blk("post_attention_norm.weight"), self.norm_form(f, name)?)),
1506 (Family::Glm4, "post_mlp_layernorm.weight") => Some((blk("post_ffw_norm.weight"), self.norm_form(f, name)?)),
1507 (Family::Deepseek2 | Family::Glm4Moe, "mlp.gate.e_score_correction_bias") => Some((blk("exp_probs_b.bias"), whole)),
1508 (Family::Deepseek2 | Family::Glm4Moe, "mlp.shared_experts.gate_proj.weight") => Some((blk("ffn_gate_shexp.weight"), whole)),
1509 (Family::Deepseek2 | Family::Glm4Moe, "mlp.shared_experts.up_proj.weight") => Some((blk("ffn_up_shexp.weight"), whole)),
1510 (Family::Deepseek2 | Family::Glm4Moe, "mlp.shared_experts.down_proj.weight") => Some((blk("ffn_down_shexp.weight"), whole)),
1511 (Family::Glm4Moe, "eh_proj.weight") => Some((blk("nextn.eh_proj.weight"), whole)),
1513 (Family::Glm4Moe, "embed_tokens.weight") => Some((blk("nextn.embed_tokens.weight"), whole)),
1514 (Family::Glm4Moe, "shared_head.head.weight") => Some((blk("nextn.shared_head_head.weight"), whole)),
1515 (Family::Glm4Moe, "enorm.weight") => Some((blk("nextn.enorm.weight"), self.norm_form(f, name)?)),
1516 (Family::Glm4Moe, "hnorm.weight") => Some((blk("nextn.hnorm.weight"), self.norm_form(f, name)?)),
1517 (Family::Glm4Moe, "shared_head.norm.weight") => Some((blk("nextn.shared_head_norm.weight"), self.norm_form(f, name)?)),
1518 (Family::Deepseek2, "self_attn.q_a_proj.weight") => Some((blk("attn_q_a.weight"), whole)),
1519 (Family::Deepseek2, "self_attn.q_a_layernorm.weight") => Some((blk("attn_q_a_norm.weight"), whole)),
1520 (Family::Deepseek2, "self_attn.q_b_proj.weight") => Some((blk("attn_q_b.weight"), whole)),
1521 (Family::Deepseek2, "self_attn.kv_a_proj_with_mqa.weight") => Some((blk("attn_kv_a_mqa.weight"), whole)),
1522 (Family::Deepseek2, "self_attn.kv_a_layernorm.weight") => Some((blk("attn_kv_a_norm.weight"), whole)),
1523 (Family::Deepseek2, "self_attn.kv_b_proj.weight") => {
1524 let heads = h.u64(&["num_key_value_heads"]).unwrap_or(h.need_u64("num_attention_heads")?) as usize;
1525 let nope = h.need_u64("qk_nope_head_dim")? as usize;
1526 let v = h.need_u64("v_head_dim")? as usize;
1527 let lora = *entry.shape.last().unwrap_or(&0) as usize;
1528 if entry.elements() as usize != heads * (nope + v) * lora {
1529 return Err(invalid(format!("{name}: {:?} is not {heads} heads of {nope}+{v} by {lora}", entry.shape)));
1530 }
1531 let k_b = Form::Gather {
1532 src: name.clone(),
1533 outer: heads,
1534 m: nope + v,
1535 n: lora,
1536 rows: (0..nope).collect(),
1537 cols: (0..lora).collect(),
1538 transpose: true,
1539 };
1540 let v_b = Form::Gather {
1541 src: name.clone(),
1542 outer: heads,
1543 m: nope + v,
1544 n: lora,
1545 rows: (nope..nope + v).collect(),
1546 cols: (0..lora).collect(),
1547 transpose: false,
1548 };
1549 self.present(blk("attn_k_b.weight"), k_b)?;
1550 Some((blk("attn_v_b.weight"), v_b))
1551 }
1552 (Family::Llama4, "feed_forward.gate_proj.weight") => Some((blk("ffn_gate.weight"), whole)),
1553 (Family::Llama4, "feed_forward.up_proj.weight") => Some((blk("ffn_up.weight"), whole)),
1554 (Family::Llama4, "feed_forward.down_proj.weight") => Some((blk("ffn_down.weight"), whole)),
1555 (Family::Llama4, "feed_forward.router.weight") => Some((blk("ffn_gate_inp.weight"), whole)),
1556 (Family::Llama4, "feed_forward.shared_expert.gate_proj.weight") => Some((blk("ffn_gate_shexp.weight"), whole)),
1557 (Family::Llama4, "feed_forward.shared_expert.up_proj.weight") => Some((blk("ffn_up_shexp.weight"), whole)),
1558 (Family::Llama4, "feed_forward.shared_expert.down_proj.weight") => Some((blk("ffn_down_shexp.weight"), whole)),
1559 (Family::Llama4, "feed_forward.experts.gate_up_proj") => {
1562 let (e, d, two) = dims3(&entry, name)?;
1563 let ff = two / 2;
1564 let half = |from: usize| Form::Gather {
1565 src: name.clone(),
1566 outer: e,
1567 m: d,
1568 n: two,
1569 rows: (0..d).collect(),
1570 cols: (from..from + ff).collect(),
1571 transpose: true,
1572 };
1573 self.present(blk("ffn_gate_exps.weight"), half(0))?;
1574 Some((blk("ffn_up_exps.weight"), half(ff)))
1575 }
1576 (Family::Llama4, "feed_forward.experts.down_proj") => {
1577 let (e, ff, d) = dims3(&entry, name)?;
1578 Some((
1579 blk("ffn_down_exps.weight"),
1580 Form::Gather {
1581 src: name.clone(),
1582 outer: e,
1583 m: ff,
1584 n: d,
1585 rows: (0..ff).collect(),
1586 cols: (0..d).collect(),
1587 transpose: true,
1588 },
1589 ))
1590 }
1591 (Family::GptOss, "mlp.router.weight") => Some((blk("ffn_gate_inp.weight"), whole)),
1592 (Family::GptOss, "mlp.router.bias") => Some((blk("ffn_gate_inp.bias"), whole)),
1593 (Family::GptOss, "mlp.experts.gate_up_proj") => {
1594 let (e, d, two) = dims3(&entry, name)?;
1595 let ff = two / 2;
1596 let pick = |o: usize| Form::Gather {
1597 src: name.clone(),
1598 outer: e,
1599 m: d,
1600 n: two,
1601 rows: (0..d).collect(),
1602 cols: (0..ff).map(|j| 2 * j + o).collect(),
1603 transpose: true,
1604 };
1605 self.present(blk("ffn_gate_exps.weight"), pick(0))?;
1606 Some((blk("ffn_up_exps.weight"), pick(1)))
1607 }
1608 (Family::GptOss, "mlp.experts.gate_up_proj_bias") => {
1609 let (e, two) = (entry.shape[0] as usize, entry.shape[1] as usize);
1610 let pick = |o: usize| Form::Gather {
1611 src: name.clone(),
1612 outer: e,
1613 m: 1,
1614 n: two,
1615 rows: vec![0],
1616 cols: (0..two / 2).map(|j| 2 * j + o).collect(),
1617 transpose: false,
1618 };
1619 self.present(blk("ffn_gate_exps.bias"), pick(0))?;
1620 Some((blk("ffn_up_exps.bias"), pick(1)))
1621 }
1622 (Family::GptOss, "mlp.experts.down_proj") => {
1623 let (e, ff, d) = dims3(&entry, name)?;
1624 Some((
1625 blk("ffn_down_exps.weight"),
1626 Form::Gather {
1627 src: name.clone(),
1628 outer: e,
1629 m: ff,
1630 n: d,
1631 rows: (0..ff).collect(),
1632 cols: (0..d).collect(),
1633 transpose: true,
1634 },
1635 ))
1636 }
1637 (Family::GptOss, "mlp.experts.down_proj_bias") => Some((blk("ffn_down_exps.bias"), whole)),
1638 (Family::Qwen4Exp, r) if r.starts_with("attn_hyper_connection.") || r.starts_with("mlp_hyper_connection.") => {
1639 let (sub, part) = r.split_once('.').unwrap();
1640 let which = if sub == "attn_hyper_connection" { "hc_attn" } else { "hc_ffn" };
1641 let (suffix, form) = match part {
1642 "hc_norm.weight" => ("norm", self.norm_form(f, name)?),
1643 "input_mix_weight_down.weight" => ("down", whole),
1644 "input_mix_weight_up.weight" => ("up", whole),
1645 "block_inject_weight.weight" => ("inject", whole),
1646 other => return Err(invalid(format!("{name}: a hyper-connection tensor `{other}` this reader does not present"))),
1647 };
1648 Some((blk(&format!("{which}_{suffix}.weight")), form))
1649 }
1650 (Family::Qwen4Exp, "self_attn.indexer.index_qk_proj.weight") => {
1651 let heads = h.need_u64("indexer_n_heads")? as usize;
1653 let hd = h.need_u64("indexer_head_dim")? as usize;
1654 let n = *entry.shape.last().unwrap() as usize;
1655 let rows = |rows: std::ops::Range<usize>| Form::Gather {
1656 src: name.clone(),
1657 outer: 1,
1658 m: (heads + 1) * hd,
1659 n,
1660 rows: rows.collect(),
1661 cols: (0..n).collect(),
1662 transpose: false,
1663 };
1664 if entry.elements() as usize != (heads + 1) * hd * n {
1665 return Err(invalid(format!("{name}: {:?} is not {heads} index queries and a key of {hd}", entry.shape)));
1666 }
1667 self.present(blk("indexer.q_proj.weight"), rows(0..heads * hd))?;
1668 Some((blk("indexer.k_proj.weight"), rows(heads * hd..(heads + 1) * hd)))
1669 }
1670 (Family::Qwen4Exp, "self_attn.indexer.q_layernorm.weight") => Some((blk("indexer.q_norm.weight"), self.norm_form(f, name)?)),
1671 (Family::Qwen4Exp, "self_attn.indexer.k_layernorm.weight") => Some((blk("indexer.k_norm.weight"), self.norm_form(f, name)?)),
1672 (Family::Qwen4Exp, "ple.key_proj.weight") => Some((blk("ple_key.weight"), whole)),
1673 (Family::Qwen4Exp, "ple.value_proj.weight") => Some((blk("ple_value.weight"), whole)),
1674 (Family::Qwen4Exp, "ple.norm_key.weight") => Some((blk("ple_norm_key.weight"), self.norm_form(f, name)?)),
1675 (Family::Qwen4Exp, "ple.norm_query.weight") => Some((blk("ple_norm_query.weight"), self.norm_form(f, name)?)),
1676 (Family::Qwen4Exp, "ple.norm_conv.weight") => Some((blk("ple_norm_conv.weight"), self.norm_form(f, name)?)),
1677 (Family::Qwen4Exp, "ple.conv1d.weight") => Some((blk("ple_conv1d.weight"), whole)),
1678 (Family::Qwen4Exp, r) if r.starts_with("ple.ple_embedding.ngram_embedding.shard_") => {
1679 let index = r["ple.ple_embedding.ngram_embedding.shard_".len()..]
1680 .strip_suffix(".weight")
1681 .and_then(|i| i.parse::<usize>().ok())
1682 .ok_or_else(|| invalid(format!("{name}: an n-gram shard without its index")))?;
1683 ngram_shards.insert(index, name.clone());
1684 read.insert(name.clone());
1685 None
1686 }
1687 (
1689 Family::Qwen4Exp,
1690 "ple.ple_embedding.layer_multipliers" | "ple.ple_embedding.ngram_heads_offsets" | "ple.ple_embedding.ngram_heads_vocab_sizes",
1691 ) => {
1692 read.insert(name.clone());
1693 None
1694 }
1695 (Family::Qwen3Next, r) if r.starts_with("linear_attn.") => {
1696 Some(self.grouped_gated_delta(h, name, &entry, &r["linear_attn.".len()..], l)?)
1697 }
1698 (f, r) if f.hybrid() && r.starts_with("linear_attn.") => {
1699 Some(self.gated_delta(h, name, &entry, &r["linear_attn.".len()..], l)?)
1700 }
1701 _ => None,
1702 };
1703 if let Some((g, form)) = gguf {
1704 if g.ends_with("ssm_conv1d.weight") || g.ends_with("ple_conv1d.weight") {
1705 let taps = *entry.shape.last().unwrap();
1707 let channels = entry.elements() / taps.max(1);
1708 self.push(g, vec![taps, channels], form)?;
1709 } else {
1710 self.present(g, form)?;
1711 }
1712 read.insert(name.clone());
1713 }
1714 }
1715
1716 let expert_count = h.u64(&["num_local_experts", "num_experts", "n_routed_experts"]).unwrap_or(0) as usize;
1718 for ((l, proj), by_expert) in experts {
1719 if by_expert.len() != expert_count || by_expert.keys().copied().ne(0..expert_count) {
1720 return Err(invalid(format!(
1721 "block {l}: {} experts' {proj} of {expert_count}",
1722 by_expert.len()
1723 )));
1724 }
1725 let parts: Vec<Form> = by_expert
1726 .values()
1727 .map(|n| Ok(Form::whole(n, self.need(n)?)))
1728 .collect::<io::Result<_>>()?;
1729 let g = match proj {
1730 "gate_proj" => "ffn_gate_exps.weight",
1731 "up_proj" => "ffn_up_exps.weight",
1732 _ => "ffn_down_exps.weight",
1733 };
1734 self.present(format!("blk.{l}.{g}"), Form::Concat(parts))?;
1735 }
1736
1737 if !ngram_shards.is_empty() {
1739 if ngram_shards.keys().copied().ne(0..ngram_shards.len()) {
1740 return Err(invalid(format!("the n-gram table's shards are not 0 to {}", ngram_shards.len() - 1)));
1741 }
1742 let mut rows = 0u64;
1743 let mut width = None;
1744 let mut parts = Vec::with_capacity(ngram_shards.len());
1745 for shard in ngram_shards.values() {
1746 let e = self.need(shard)?.clone();
1747 let (r, w) = (e.shape[0], *e.shape.last().unwrap());
1748 if *width.get_or_insert(w) != w {
1749 return Err(invalid(format!("{shard}: rows of {w}, where the table's are {}", width.unwrap())));
1750 }
1751 rows += r;
1752 parts.push(Form::whole(shard, &e));
1753 }
1754 self.push("per_layer_token_embd.weight".into(), vec![width.unwrap(), rows], Form::Concat(parts))?;
1755 }
1756
1757 self.unread = names.into_iter().filter(|n| !read.contains(n)).collect();
1758 Ok(())
1759 }
1760
1761 fn ple_parameters(&self, w: &mut Writer) -> io::Result<()> {
1764 let find = |suffix: &str| self.located.keys().find(|n| n.ends_with(suffix)).cloned();
1765 let Some(multipliers) = find("ple.ple_embedding.layer_multipliers") else { return Ok(()) };
1766 let ints = |name: &str| -> io::Result<Vec<MetaValue>> {
1767 let e = self.need(name)?;
1768 if e.dtype != Dtype::I64 {
1769 return Err(invalid(format!("{name}: stored {:?}, where the hash constants are int64", e.dtype)));
1770 }
1771 let raw = self.shards[self.located[name]].read(name, 0, e.elements() as usize * 8)?;
1772 Ok(raw.chunks(8).map(|c| MetaValue::I64(i64::from_le_bytes(c.try_into().unwrap()))).collect())
1773 };
1774 let array = |items: Vec<MetaValue>| MetaValue::Array { ty: 11, items };
1775 w.put("ple.layer_multipliers", array(ints(&multipliers)?));
1776 let offsets = find("ple.ple_embedding.ngram_heads_offsets").ok_or_else(|| invalid("the n-gram heads' offsets are missing"))?;
1777 w.put("ple.head_offsets", array(ints(&offsets)?));
1778 let sizes = find("ple.ple_embedding.ngram_heads_vocab_sizes").ok_or_else(|| invalid("the n-gram heads' moduli are missing"))?;
1779 w.put("ple.head_vocab_sizes", array(ints(&sizes)?));
1780 let shard = find("ple.ple_embedding.ngram_embedding.shard_0.weight").ok_or_else(|| invalid("the n-gram table's first shard is missing"))?;
1781 w.u32("embedding_length_per_layer_input", *self.need(&shard)?.shape.last().unwrap());
1782 Ok(())
1783 }
1784
1785 fn norm_form(&self, f: Family, name: &str) -> io::Result<Form> {
1788 let whole = Form::whole(name, self.need(name)?);
1789 let offset = (matches!(f, Family::Gemma | Family::Gemma2 | Family::Gemma3) || f.hybrid()) && !name.ends_with("linear_attn.norm.weight");
1790 Ok(if offset { Form::Map { inner: Box::new(whole), op: Op::AddOne } } else { whole })
1791 }
1792
1793 fn grouped_gated_delta(&mut self, h: &Hparams, name: &str, entry: &Entry, rest: &str, l: usize) -> io::Result<(String, Form)> {
1796 let kh = h.need_u64("linear_num_key_heads")? as usize;
1797 let vh = h.need_u64("linear_num_value_heads")? as usize;
1798 let kd = h.need_u64("linear_key_head_dim")? as usize;
1799 let vd = h.need_u64("linear_value_head_dim")? as usize;
1800 let blk = |s: &str| format!("blk.{l}.{s}");
1801 let whole = Form::whole(name, entry);
1802 let group = 2 * kd + 2 * (vh / kh.max(1)) * vd;
1804 let vz = (vh / kh.max(1)) * vd;
1805 let rows_with = |rows: Vec<usize>, n: usize| Form::Gather {
1806 src: name.to_string(),
1807 outer: 1,
1808 m: rows.len(),
1809 n,
1810 rows,
1811 cols: (0..n).collect(),
1812 transpose: false,
1813 };
1814 let part = |off: usize, len: usize| -> Vec<usize> { (0..kh).flat_map(|k| (0..len).map(move |e| k * group + off + e)).collect() };
1815 Ok(match rest {
1816 "in_proj_qkvz.weight" => {
1817 let n = *entry.shape.last().unwrap() as usize;
1818 let qkv: Vec<usize> = part(0, kd).into_iter().chain(part(kd, kd)).chain(part(2 * kd, vz)).collect();
1819 self.present(blk("attn_gate.weight"), rows_with(part(2 * kd + vz, vz), n))?;
1820 (blk("attn_qkv.weight"), rows_with(qkv, n))
1821 }
1822 "in_proj_ba.weight" => (blk("ssm_ba.weight"), whole),
1823 "A_log" => (blk("ssm_a"), Form::Map { inner: Box::new(whole), op: Op::NegExp }),
1824 "dt_bias" => (blk("ssm_dt.bias"), whole),
1825 "conv1d.weight" => (blk("ssm_conv1d.weight"), whole),
1826 "norm.weight" => (blk("ssm_norm.weight"), whole),
1827 "out_proj.weight" => (blk("ssm_out.weight"), whole),
1828 other => return Err(invalid(format!("{name}: a Gated DeltaNet tensor `{other}` this reader does not present"))),
1829 })
1830 }
1831
1832 fn gated_delta(&self, h: &Hparams, name: &str, entry: &Entry, rest: &str, l: usize) -> io::Result<(String, Form)> {
1833 let kh = h.need_u64("linear_num_key_heads")? as usize;
1834 let vh = h.need_u64("linear_num_value_heads")? as usize;
1835 let kd = h.need_u64("linear_key_head_dim")? as usize;
1836 let vd = h.need_u64("linear_value_head_dim")? as usize;
1837 let reorder = kh != vh;
1838 let per_k = vh / kh.max(1);
1839 let tiled = |o: usize, d: usize| -> usize {
1841 let r = o / (kh * d);
1842 let k = (o / d) % kh;
1843 k * per_k * d + r * d + o % d
1844 };
1845 let perm = |len: usize, d: usize| -> Vec<usize> {
1846 (0..len).map(|o| if reorder { tiled(o, d) } else { o }).collect()
1847 };
1848 let blk = |s: &str| format!("blk.{l}.{s}");
1849 let whole = Form::whole(name, entry);
1850 let rows_with = |rows: Vec<usize>, n: usize| Form::Gather {
1851 src: name.to_string(),
1852 outer: 1,
1853 m: rows.len(),
1854 n,
1855 rows,
1856 cols: (0..n).collect(),
1857 transpose: false,
1858 };
1859 Ok(match rest {
1860 "in_proj_qkv.weight" => {
1861 let n = *entry.shape.last().unwrap() as usize;
1862 let qk = 2 * kh * kd;
1863 let rows: Vec<usize> = (0..qk).chain(perm(vh * vd, vd).into_iter().map(|o| qk + o)).collect();
1864 (blk("attn_qkv.weight"), rows_with(rows, n))
1865 }
1866 "in_proj_z.weight" => {
1867 let n = *entry.shape.last().unwrap() as usize;
1868 (blk("attn_gate.weight"), rows_with(perm(vh * vd, vd), n))
1869 }
1870 "in_proj_a.weight" | "in_proj_b.weight" => {
1871 let n = *entry.shape.last().unwrap() as usize;
1872 let g = if rest == "in_proj_a.weight" { "ssm_alpha.weight" } else { "ssm_beta.weight" };
1873 (blk(g), rows_with(perm(vh, 1), n))
1874 }
1875 "A_log" => (
1876 blk("ssm_a"),
1877 Form::Map { inner: Box::new(rows_with(perm(vh, 1), 1)), op: Op::NegExp },
1878 ),
1879 "dt_bias" => (blk("ssm_dt.bias"), rows_with(perm(vh, 1), 1)),
1880 "conv1d.weight" => {
1881 let taps = *entry.shape.last().unwrap() as usize;
1883 let qk = 2 * kh * kd;
1884 let rows: Vec<usize> = (0..qk).chain(perm(vh * vd, vd).into_iter().map(|o| qk + o)).collect();
1885 (blk("ssm_conv1d.weight"), rows_with(rows, taps))
1886 }
1887 "norm.weight" => (blk("ssm_norm.weight"), whole),
1888 "out_proj.weight" => {
1889 let m = entry.shape[0] as usize;
1890 (
1891 blk("ssm_out.weight"),
1892 Form::Gather {
1893 src: name.to_string(),
1894 outer: 1,
1895 m,
1896 n: vh * vd,
1897 rows: (0..m).collect(),
1898 cols: perm(vh * vd, vd),
1899 transpose: false,
1900 },
1901 )
1902 }
1903 other => return Err(invalid(format!("{name}: a Gated DeltaNet tensor `{other}` this reader does not present"))),
1904 })
1905 }
1906}
1907
1908fn decoder_prefix(architecture: &str, names: &[String]) -> &'static str {
1914 if !architecture.ends_with("ForConditionalGeneration") {
1915 "model."
1916 } else if names.iter().any(|n| n.starts_with("language_model.model.")) {
1917 "language_model.model."
1918 } else {
1919 "model.language_model."
1920 }
1921}
1922
1923fn configuration(dir: &Path) -> io::Result<(Family, Hparams)> {
1925 let config_path = dir.join("config.json");
1926 let root: Value = serde_json::from_str(
1927 &std::fs::read_to_string(&config_path).map_err(|e| io::Error::new(e.kind(), format!("{}: {e}", config_path.display())))?,
1928 )
1929 .map_err(|e| invalid(format!("{}: {e}", config_path.display())))?;
1930 let mut map = root.as_object().cloned().ok_or_else(|| invalid("config.json is not an object"))?;
1931 let architecture =
1932 map.get("architectures").and_then(Value::as_array).and_then(|a| a.first()).and_then(Value::as_str).unwrap_or_default().to_string();
1933 if let Some(Value::Object(text)) = map.get("text_config").cloned() {
1934 for (k, v) in text {
1935 map.insert(k, v);
1936 }
1937 }
1938 let family =
1939 Family::of(&architecture).ok_or_else(|| invalid(format!("config.json: architecture `{architecture}` is not one this reader presents")))?;
1940 let h = Hparams { map, architecture };
1941 validate(family, &h)?;
1942 Ok((family, h))
1943}
1944
1945fn stated_draft_blocks(family: Family, h: &Hparams) -> Option<u64> {
1949 match family {
1950 f if f.hybrid() => h.u64(&["mtp_num_hidden_layers"]),
1951 Family::Glm4Moe => Some(h.u64(&["num_nextn_predict_layers"]).unwrap_or(0)),
1952 _ => Some(0),
1953 }
1954}
1955
1956fn draft_layers(c: &HfCheckpoint) -> u64 {
1959 let layers: BTreeSet<usize> = c.located.keys().filter_map(|n| layer_of(n, "mtp.layers.").map(|(l, _)| l)).collect();
1960 layers.len() as u64
1961}
1962
1963fn ggml_type(d: Dtype) -> GgmlType {
1966 match d {
1967 Dtype::F16 => GgmlType::F16,
1968 Dtype::Bf16 => GgmlType::Bf16,
1969 _ => GgmlType::F32,
1970 }
1971}
1972
1973fn widen(d: Dtype, raw: Vec<u8>) -> Vec<u8> {
1976 let f32s = |it: &mut dyn Iterator<Item = f32>| it.flat_map(f32::to_le_bytes).collect::<Vec<u8>>();
1977 match d {
1978 Dtype::F32 | Dtype::F16 | Dtype::Bf16 => raw,
1979 Dtype::F64 => f32s(&mut raw.chunks_exact(8).map(|c| f64::from_le_bytes(c.try_into().unwrap()) as f32)),
1980 Dtype::F8E4m3 => f32s(&mut raw.iter().map(|&b| e4m3fn_to_f32(b))),
1981 Dtype::F8E5m2 => f32s(&mut raw.iter().map(|&b| e5m2_to_f32(b))),
1982 Dtype::I64 => f32s(&mut raw.chunks_exact(8).map(|c| i64::from_le_bytes(c.try_into().unwrap()) as f32)),
1983 Dtype::I32 => f32s(&mut raw.chunks_exact(4).map(|c| i32::from_le_bytes(c.try_into().unwrap()) as f32)),
1984 Dtype::I16 => f32s(&mut raw.chunks_exact(2).map(|c| i16::from_le_bytes(c.try_into().unwrap()) as f32)),
1985 Dtype::I8 => f32s(&mut raw.iter().map(|&b| b as i8 as f32)),
1986 Dtype::U8 => f32s(&mut raw.iter().map(|&b| b as f32)),
1987 Dtype::Bool => f32s(&mut raw.iter().map(|&b| f32::from(b != 0))),
1988 }
1989}
1990
1991fn layer_of<'a>(name: &'a str, prefix: &str) -> Option<(usize, &'a str)> {
1993 let rest = name.strip_prefix(prefix)?;
1994 let (l, tail) = rest.split_once('.')?;
1995 Some((l.parse().ok()?, tail))
1996}
1997
1998fn expert_of(rest: &str) -> Option<(usize, &'static str)> {
2000 let r = rest.strip_prefix("mlp.experts.").or_else(|| rest.strip_prefix("block_sparse_moe.experts."))?;
2001 let (x, tail) = r.split_once('.')?;
2002 let x: usize = x.parse().ok()?;
2003 let proj = match tail {
2005 "gate_proj.weight" | "w1.weight" => "gate_proj",
2006 "up_proj.weight" | "w3.weight" => "up_proj",
2007 "down_proj.weight" | "w2.weight" => "down_proj",
2008 _ => return None,
2009 };
2010 Some((x, proj))
2011}
2012
2013fn dims3(e: &Entry, name: &str) -> io::Result<(usize, usize, usize)> {
2015 match e.shape[..] {
2016 [a, b, c] => Ok((a as usize, b as usize, c as usize)),
2017 _ => Err(invalid(format!("{name}: {:?} is not three-dimensional", e.shape))),
2018 }
2019}
2020
2021fn llama_permute(name: &str, e: &Entry, heads: usize) -> Form {
2024 let rows = e.shape[0] as usize;
2025 let n = if e.shape.len() > 1 { (e.elements() as usize) / rows } else { 1 };
2026 let per = rows / heads.max(1);
2027 let half = per / 2;
2028 let src_row = |o: usize| (o / per) * per + (o % 2) * half + (o % per) / 2;
2029 Form::Gather {
2030 src: name.to_string(),
2031 outer: 1,
2032 m: rows,
2033 n,
2034 rows: (0..rows).map(src_row).collect(),
2035 cols: (0..n).collect(),
2036 transpose: false,
2037 }
2038}
2039
2040fn rope_parameters(h: &Hparams) -> Map<String, Value> {
2044 let mut rope = match h.map.get("rope_parameters").or_else(|| h.map.get("rope_scaling")) {
2045 Some(Value::Object(o)) => o.clone(),
2046 _ => Map::new(),
2047 };
2048 if !rope.contains_key("full_attention") && !rope.contains_key("sliding_attention") {
2049 let local = h.find(&["local_rope_theta", "rope_local_theta", "rope_theta_local", "swa_rope_theta", "rope_local_base_freq"]);
2050 if let Some(local) = local {
2051 let mut sliding = Map::new();
2052 sliding.insert("rope_theta".into(), local.clone());
2053 rope.insert("sliding_attention".into(), Value::Object(sliding));
2054 }
2055 let theta = h.find(&["global_rope_theta", "rope_global_theta", "rope_theta_global", "rope_theta", "rotary_emb_base"]);
2056 if !rope.contains_key("rope_theta") {
2057 if let Some(t) = theta {
2058 rope.insert("rope_theta".into(), t.clone());
2059 }
2060 }
2061 if !rope.contains_key("rope_type") {
2062 if let Some(t) = rope.get("type").cloned() {
2063 rope.insert("rope_type".into(), t);
2064 }
2065 }
2066 if !rope.contains_key("partial_rotary_factor") {
2067 if let Some(p) = h.find(&["partial_rotary_factor", "rope_pct", "rope_percent"]) {
2068 rope.insert("partial_rotary_factor".into(), p.clone());
2069 }
2070 }
2071 if !rope.contains_key("original_max_position_embeddings") {
2072 if let Some(o) = h.find(&["original_max_position_embeddings"]) {
2073 rope.insert("original_max_position_embeddings".into(), o.clone());
2074 }
2075 }
2076 }
2077 rope
2078}
2079
2080fn full_rope(rope: &Map<String, Value>) -> Map<String, Value> {
2082 match rope.get("full_attention") {
2083 Some(Value::Object(o)) => o.clone(),
2084 _ => rope.clone(),
2085 }
2086}
2087
2088fn base_parameters(h: &Hparams, rope: &Map<String, Value>, w: &mut Writer, block_count: u64, f: Family) -> io::Result<()> {
2090 w.u32("block_count", block_count);
2091 if let Some(n) = h.u64(&["max_position_embeddings", "n_ctx", "n_positions", "max_length", "max_sequence_length", "model_max_length"]) {
2092 w.u32("context_length", n);
2093 }
2094 if let Some(n) = h.u64(&["hidden_size", "n_embd", "dim"]) {
2095 w.u32("embedding_length", n);
2096 }
2097 if let Some(n) = h.u64(&["prefix_dense_intermediate_size", "dense_intermediate_size", "intermediate_size", "n_inner", "hidden_dim"]) {
2098 w.u32("feed_forward_length", n);
2099 }
2100 if let Some(n) = h.u64(&["num_attention_heads", "n_head", "n_heads"]) {
2101 w.u32("attention.head_count", n);
2102 }
2103 let kv = if f == Family::Deepseek2 { Some(1) } else { h.u64(&["num_key_value_heads", "n_kv_heads"]) };
2105 if let Some(n) = kv {
2106 w.u32("attention.head_count_kv", n);
2107 }
2108 if h.map.get("is_causal").and_then(Value::as_bool) == Some(false) {
2109 w.bool("attention.causal", false);
2110 }
2111 let rp = full_rope(rope);
2112 if let Some(kind) = rp.get("rope_type").and_then(Value::as_str) {
2113 let factor = rp.get("factor").and_then(Value::as_f64);
2114 match (kind, factor) {
2115 ("linear", Some(factor)) => {
2116 w.str("rope.scaling.type", "linear");
2117 w.f32("rope.scaling.factor", factor);
2118 }
2119 ("yarn", Some(factor)) => {
2120 w.str("rope.scaling.type", "yarn");
2121 w.f32("rope.scaling.factor", factor);
2122 let orig = rp
2123 .get("original_max_position_embeddings")
2124 .and_then(Value::as_u64)
2125 .ok_or_else(|| invalid("config.json: a YaRN rotation without original_max_position_embeddings"))?;
2126 w.u32("rope.scaling.original_context_length", orig);
2127 if let Some(t) = rp.get("truncate").and_then(Value::as_bool) {
2128 w.bool("rope.scaling.yarn_truncate", t);
2129 }
2130 if let Some(x) = rp.get("extrapolation_factor").and_then(Value::as_f64) {
2131 w.f32("rope.scaling.yarn_ext_factor", x);
2132 }
2133 if let Some(x) = rp.get("attention_factor").or_else(|| rp.get("attn_factor")).and_then(Value::as_f64) {
2134 w.f32("rope.scaling.yarn_attn_factor", x);
2135 }
2136 if let Some(x) = rp.get("beta_fast").and_then(Value::as_f64) {
2137 w.f32("rope.scaling.yarn_beta_fast", x);
2138 }
2139 if let Some(x) = rp.get("beta_slow").and_then(Value::as_f64) {
2140 w.f32("rope.scaling.yarn_beta_slow", x);
2141 }
2142 }
2143 ("su" | "longrope", _) => w.str("rope.scaling.type", "longrope"),
2144 _ => {}
2145 }
2146 }
2147 if let Some(Value::Array(sections)) = rope.get("mrope_section") {
2148 let mut v: Vec<i64> = sections.iter().filter_map(Value::as_i64).collect();
2149 while v.len() < 4 {
2150 v.push(0);
2151 }
2152 w.put(
2153 "rope.dimension_sections",
2154 MetaValue::Array { ty: 5, items: v[..4].iter().map(|&x| MetaValue::I32(x as i32)).collect() },
2155 );
2156 }
2157 if let Some(t) = rp.get("rope_theta").and_then(Value::as_f64) {
2158 w.f32("rope.freq_base", t);
2159 }
2160 if let Some(t) = rope.get("sliding_attention").and_then(|s| s.get("rope_theta")).and_then(Value::as_f64) {
2161 w.f32("rope.freq_base_swa", t);
2162 }
2163 let rms = if f == Family::Deepseek2 { Some(h.f64(&["rms_norm_eps"]).unwrap_or(1e-6)) } else { h.f64(&["rms_norm_eps", "norm_eps"]) };
2164 if let Some(e) = rms {
2165 w.f32("attention.layer_norm_rms_epsilon", e);
2166 }
2167 if let Some(e) = h.f64(&["layer_norm_eps", "layer_norm_epsilon", "norm_epsilon"]) {
2168 w.f32("attention.layer_norm_epsilon", e);
2169 }
2170 if let Some(n) = h.u64(&["num_local_experts", "num_experts", "n_routed_experts"]) {
2171 w.u32("expert_count", n);
2172 }
2173 if let Some(n) = h.u64(&["num_experts_per_tok", "num_experts_per_token", "top_k_experts"]) {
2174 w.u32("expert_used_count", n);
2175 }
2176 if let Some(n) = h.map.get("n_group").and_then(Value::as_u64) {
2177 w.u32("expert_group_count", n);
2178 }
2179 if let Some(n) = h.map.get("topk_group").and_then(Value::as_u64) {
2180 w.u32("expert_group_used_count", n);
2181 }
2182 let score = h.find(&[
2183 "score_function",
2184 "scoring_func",
2185 "score_func",
2186 "moe_router_activation",
2187 "moe_router_activation_func",
2188 "expert_selection_fn",
2189 "router_score_func",
2190 ]);
2191 if let Some(s) = score.and_then(Value::as_str) {
2192 let id = match s {
2193 "softmax" => 1,
2194 "sigmoid" => 2,
2195 "sqrtsoftplus" => 4,
2196 other => return Err(invalid(format!("config.json: expert score function `{other}` is not one the converter writes"))),
2197 };
2198 w.u32("expert_gating_func", id);
2199 }
2200 if let Some(hd) = h.map.get("head_dim").and_then(Value::as_u64) {
2201 w.u32("attention.key_length", hd);
2202 w.u32("attention.value_length", hd);
2203 }
2204 Ok(())
2205}
2206
2207fn qwen4exp_parameters(h: &Hparams, w: &mut Writer, block_count: u64, n_layers: u64) -> io::Result<()> {
2212 w.u32("hyper_connection.count", h.need_u64("hc_count")?);
2213 w.u32("hyper_connection.low_rank", h.need_u64("hc_lowrank")?);
2214 w.u32("attention.indexer.head_count", h.need_u64("indexer_n_heads")?);
2215 w.u32("attention.indexer.key_length", h.need_u64("indexer_head_dim")?);
2216 w.u32("attention.indexer.top_k", h.need_u64("indexer_budget")?);
2217 let ratio = h.need_u64("indexer_compress_ratio")?;
2218 let full: Vec<bool> = match h.map.get("layer_types") {
2222 Some(Value::Array(types)) => {
2223 types.iter().map(|t| matches!(t.as_str(), Some("full_attention" | "indexed_attention"))).collect()
2224 }
2225 _ => {
2226 let every = h.u64(&["full_attention_interval"]).unwrap_or(4);
2227 (0..n_layers).map(|l| (l + 1) % every == 0).collect()
2228 }
2229 };
2230 let ratios: Vec<MetaValue> = (0..block_count as usize)
2231 .map(|l| MetaValue::U32(if l >= n_layers as usize || full[l] { ratio as u32 } else { 0 }))
2232 .collect();
2233 w.put("attention.compress_ratios", MetaValue::Array { ty: 4, items: ratios });
2234 let layers: Vec<u64> = match h.map.get("ple_layer_ids") {
2235 Some(Value::Array(ids)) => ids.iter().filter_map(Value::as_u64).map(|l| l - 1).collect(),
2236 _ => Vec::new(),
2237 };
2238 if layers.is_empty() {
2239 return Ok(());
2240 }
2241 w.put("ple.layers", MetaValue::Array { ty: 4, items: layers.iter().map(|&l| MetaValue::U32(l as u32)).collect() });
2242 w.u32("ple.ngram_size", h.need_u64("ngram_size")?);
2243 w.u32("ple.heads_per_ngram", h.need_u64("heads_per_ngram")?);
2244 w.u32("ple.conv_kernel", h.need_u64("ple_conv_kernel_size")?);
2245 let eos = match h.map.get("eos_token_id") {
2247 Some(Value::Array(ids)) => ids.last().and_then(Value::as_u64),
2248 Some(v) => v.as_u64(),
2249 None => None,
2250 }
2251 .ok_or_else(|| invalid("config.json: eos_token_id is required: the n-gram hash resets on it"))?;
2252 w.u32("ple.eos_token_id", eos);
2253 if let Some(image) = h.map.get("image_token_id").and_then(Value::as_u64) {
2254 w.u32("ple.image_token_id", image);
2255 }
2256 Ok(())
2257}
2258
2259fn family_parameters(f: Family, h: &Hparams, rope: &Map<String, Value>, w: &mut Writer, block_count: u64) -> io::Result<()> {
2261 let head_dim = || -> io::Result<u64> {
2262 Ok(match h.u64(&["head_dim"]) {
2263 Some(d) => d,
2264 None => h.need_u64("hidden_size")? / h.need_u64("num_attention_heads")?,
2265 })
2266 };
2267 match f {
2268 Family::Llama => {
2269 w.u32("vocab_size", h.need_u64("vocab_size")?);
2270 w.u32("rope.dimension_count", head_dim()?);
2271 }
2272 Family::Qwen2 | Family::Qwen3 => {}
2273 Family::Qwen3Moe | Family::Qwen2Moe => {
2274 if let Some(n) = h.u64(&["moe_intermediate_size"]) {
2275 w.u32("expert_feed_forward_length", n);
2276 }
2277 if let Some(n) = h.u64(&["shared_expert_intermediate_size"]) {
2278 w.u32("expert_shared_feed_forward_length", n);
2279 }
2280 }
2281 Family::Qwen35 | Family::Qwen35Moe | Family::Qwen3Next | Family::Qwen4Exp => {
2282 if let Some(n) = h.u64(&["moe_intermediate_size"]) {
2283 w.u32("expert_feed_forward_length", n);
2284 }
2285 if let Some(n) = h.u64(&["shared_expert_intermediate_size"]) {
2286 w.u32("expert_shared_feed_forward_length", n);
2287 }
2288 let n_layers = h.need_u64("num_hidden_layers")?;
2289 if block_count > n_layers {
2290 w.u32("nextn_predict_layers", block_count - n_layers);
2291 }
2292 w.u32("ssm.conv_kernel", h.need_u64("linear_conv_kernel_dim")?);
2293 w.u32("ssm.state_size", h.need_u64("linear_key_head_dim")?);
2294 w.u32("ssm.group_count", h.need_u64("linear_num_key_heads")?);
2295 w.u32("ssm.time_step_rank", h.need_u64("linear_num_value_heads")?);
2296 w.u32("ssm.inner_size", h.need_u64("linear_value_head_dim")? * h.need_u64("linear_num_value_heads")?);
2297 if let Some(Value::Array(types)) = h.map.get("layer_types") {
2298 if types.len() as u64 != n_layers {
2299 return Err(invalid(format!("config.json: layer_types has {} entries for {n_layers} layers", types.len())));
2300 }
2301 let mut recurrent: Vec<MetaValue> =
2302 types.iter().map(|t| MetaValue::Bool(t.as_str() == Some("linear_attention"))).collect();
2303 recurrent.resize(block_count as usize, MetaValue::Bool(false));
2304 w.put("attention.recurrent_layers", MetaValue::Array { ty: 7, items: recurrent });
2305 }
2306 w.u32("full_attention_interval", h.u64(&["full_attention_interval"]).unwrap_or(4));
2307 let factor = rope
2308 .get("partial_rotary_factor")
2309 .or_else(|| h.map.get("partial_rotary_factor"))
2310 .and_then(Value::as_f64)
2311 .unwrap_or(0.25);
2312 w.u32("rope.dimension_count", (head_dim()? as f64 * factor) as u64);
2313 if f != Family::Qwen3Next && !rope.contains_key("mrope_section") {
2315 w.put(
2316 "rope.dimension_sections",
2317 MetaValue::Array { ty: 5, items: [11, 11, 10, 0].iter().map(|&x| MetaValue::I32(x)).collect() },
2318 );
2319 }
2320 if f == Family::Qwen4Exp {
2321 qwen4exp_parameters(h, w, block_count, n_layers)?;
2322 }
2323 }
2324 Family::Gemma => {}
2326 Family::Gemma2 => {
2329 for key in ["attn_logit_softcapping", "final_logit_softcapping"] {
2330 w.f32(key, h.f64(&[key]).ok_or_else(|| invalid(format!("config.json: no `{key}`")))?);
2331 }
2332 w.u32("attention.sliding_window", h.need_u64("sliding_window")?);
2333 }
2334 Family::Gemma3 => {
2335 let get = |k: &str| h.map.get(k).filter(|v| !v.is_null());
2336 w.u32("context_length", get("max_position_embeddings").and_then(Value::as_u64).unwrap_or(131072));
2337 w.u32("attention.head_count", get("num_attention_heads").and_then(Value::as_u64).unwrap_or(8));
2338 w.f32("attention.layer_norm_rms_epsilon", get("rms_norm_eps").and_then(Value::as_f64).unwrap_or(1e-6));
2339 let hd = get("head_dim").and_then(Value::as_u64).unwrap_or(256);
2340 w.u32("attention.key_length", hd);
2341 w.u32("attention.value_length", hd);
2342 let theta = full_rope(rope).get("rope_theta").and_then(Value::as_f64).unwrap_or(1_000_000.0);
2343 w.f32("rope.freq_base", theta);
2344 if let Some(cap) = get("final_logit_softcapping").and_then(Value::as_f64).filter(|c| *c != 0.0) {
2345 w.f32("final_logit_softcapping", cap);
2346 }
2347 if get("sliding_window_pattern").and_then(Value::as_u64) != Some(1) {
2348 let window = get("sliding_window")
2349 .and_then(Value::as_u64)
2350 .ok_or_else(|| invalid("config.json: gemma3 without sliding_window"))?;
2351 w.u32("attention.sliding_window", window);
2352 }
2353 w.u32("attention.head_count_kv", get("num_key_value_heads").and_then(Value::as_u64).unwrap_or(4));
2354 }
2355 Family::GptOss => {
2356 w.u32("attention.sliding_window", h.need_u64("sliding_window")?);
2357 w.u32("expert_feed_forward_length", h.need_u64("intermediate_size")?);
2358 }
2359 Family::Glm4 => {
2360 let partial = rope.get("partial_rotary_factor").and_then(Value::as_f64).unwrap_or(0.5);
2362 w.u32("rope.dimension_count", (head_dim()? as f64 * partial) as u64);
2363 }
2364 Family::Glm4Moe => {
2365 let partial = rope.get("partial_rotary_factor").and_then(Value::as_f64).unwrap_or(0.5);
2368 w.u32("rope.dimension_count", (head_dim()? as f64 * partial) as u64);
2369 if let Some(n) = h.u64(&["moe_intermediate_size"]) {
2370 w.u32("expert_feed_forward_length", n);
2371 }
2372 if let Some(n) = h.u64(&["n_shared_experts"]) {
2373 w.u32("expert_shared_count", n);
2374 }
2375 if let Some(n) = h.u64(&["first_k_dense_replace"]) {
2376 w.u32("leading_dense_block_count", n);
2377 }
2378 w.u32("expert_gating_func", 2);
2379 if let Some(s) = h.f64(&["routed_scaling_factor"]) {
2380 w.f32("expert_weights_scale", s);
2381 }
2382 if let Some(b) = h.map.get("norm_topk_prob").and_then(Value::as_bool) {
2383 w.bool("expert_weights_norm", b);
2384 }
2385 if let Some(n) = h.u64(&["num_nextn_predict_layers"]) {
2386 w.u32("nextn_predict_layers", n);
2387 }
2388 }
2389 Family::Phi3 => {
2390 let (d, heads) = (h.need_u64("hidden_size")?, h.need_u64("num_attention_heads")?);
2394 let partial = rope.get("partial_rotary_factor").and_then(Value::as_f64).unwrap_or(1.0);
2395 w.u32("rope.dimension_count", (partial * d as f64) as u64 / heads);
2396 let original = rope
2397 .get("original_max_position_embeddings")
2398 .and_then(Value::as_u64)
2399 .ok_or_else(|| invalid("config.json: no original_max_position_embeddings, which the converter's Phi-3 class reads"))?;
2400 w.u32("rope.scaling.original_context_length", original);
2401 w.u32("attention.sliding_window", h.u64(&["sliding_window"]).unwrap_or(0));
2402 }
2403 Family::MinimaxM2 => {
2404 w.u32("expert_feed_forward_length", h.need_u64("intermediate_size")?);
2405 w.u32("rope.dimension_count", h.need_u64("rotary_dim")?);
2406 }
2407 Family::Llama4 => {
2408 w.u32("vocab_size", h.need_u64("vocab_size")?);
2410 w.u32("rope.dimension_count", head_dim()?);
2411 w.u32("feed_forward_length", h.need_u64("intermediate_size_mlp")?);
2412 w.u32("interleave_moe_layer_step", h.need_u64("interleave_moe_layer_step")?);
2413 w.u32("expert_feed_forward_length", h.need_u64("intermediate_size")?);
2414 if let Some(Value::Array(types)) = h.map.get("layer_types") {
2415 if types.iter().all(|t| t.as_str() == Some("full_attention")) {
2416 w.u32("attention.sliding_window", 0);
2417 }
2418 }
2419 }
2420 Family::Deepseek2 => {
2421 let n_layers = h.need_u64("num_hidden_layers")?;
2422 let moe = h.map.get("n_routed_experts").is_some_and(|v| !v.is_null());
2423 let dense = h.u64(&["first_k_dense_replace"]).unwrap_or(if moe { 0 } else { n_layers });
2424 w.u32("leading_dense_block_count", dense);
2425 let kv_lora = h.u64(&["kv_lora_rank"]).unwrap_or(512);
2426 w.u32("vocab_size", h.need_u64("vocab_size")?);
2427 if let Some(q) = h.map.get("q_lora_rank").and_then(Value::as_u64) {
2428 w.u32("attention.q_lora_rank", q);
2429 }
2430 let rope_dim = h.need_u64("qk_rope_head_dim")?;
2431 let nope = h.need_u64("qk_nope_head_dim")?;
2432 w.u32("attention.kv_lora_rank", kv_lora);
2433 w.u32("attention.key_length", kv_lora + rope_dim);
2434 w.u32("attention.value_length", kv_lora);
2435 w.u32("attention.key_length_mla", nope + rope_dim);
2436 w.u32("attention.value_length_mla", h.need_u64("v_head_dim")?);
2437 let expert_ff = h
2438 .u64(&["moe_intermediate_size", "intermediate_size"])
2439 .ok_or_else(|| invalid("config.json: no moe_intermediate_size or intermediate_size"))?;
2440 w.u32("expert_feed_forward_length", expert_ff);
2441 if let Some(n) = h.map.get("n_routed_experts").and_then(Value::as_u64) {
2442 w.u32("expert_count", n);
2443 }
2444 w.u32("expert_shared_count", h.map.get("n_shared_experts").and_then(Value::as_u64).unwrap_or(0));
2445 if let Some(s) = h.map.get("routed_scaling_factor").and_then(Value::as_f64) {
2446 w.f32("expert_weights_scale", s);
2447 }
2448 if h.map.get("norm_topk_prob").and_then(Value::as_bool) == Some(true) {
2449 w.bool("expert_weights_norm", true);
2450 }
2451 w.u32("rope.dimension_count", rope_dim);
2452 if let Some(m) = rope.get("mscale_all_dim").and_then(Value::as_f64) {
2453 w.f32("rope.scaling.yarn_log_multiplier", 0.1 * m);
2454 }
2455 }
2456 }
2457 Ok(())
2458}
2459
2460fn llama3_rope_factors(h: &Hparams, rope: &Map<String, Value>) -> Option<Vec<f32>> {
2463 let rp = full_rope(rope);
2464 if !rp.get("rope_type").and_then(Value::as_str).is_some_and(|t| t.eq_ignore_ascii_case("llama3")) {
2465 return None;
2466 }
2467 let get = |k: &str, d: f64| rp.get(k).and_then(Value::as_f64).unwrap_or(d);
2468 let base = get("rope_theta", 10000.0) as f32;
2469 let dim = match h.u64(&["head_dim"]) {
2470 Some(d) => d,
2471 None => h.u64(&["hidden_size"])? / h.u64(&["num_attention_heads"])?,
2472 } as f32;
2473 let factor = get("factor", 8.0);
2474 let low = get("low_freq_factor", 1.0);
2475 let high = get("high_freq_factor", 4.0);
2476 let old = rp.get("original_max_position_embeddings").and_then(Value::as_f64).unwrap_or(8192.0);
2477 let low_wavelen = old / low;
2478 let high_wavelen = old / high;
2479 let two_pi = (2.0 * std::f64::consts::PI) as f32;
2480 Some(
2481 (0..(dim as usize).div_ceil(2))
2482 .map(|i| {
2483 let freq = 1.0f32 / base.powf((2 * i) as f32 / dim);
2484 let wavelen = two_pi / freq;
2485 if (wavelen as f64) < high_wavelen {
2486 1.0
2487 } else if (wavelen as f64) > low_wavelen {
2488 factor as f32
2489 } else {
2490 let smooth = ((old as f32 / wavelen) - low as f32) / ((high - low) as f32);
2491 1.0f32 / ((1.0f32 - smooth) / factor as f32 + smooth)
2492 }
2493 })
2494 .collect(),
2495 )
2496}
2497
2498fn gemma_vocab(dir: &Path, h: &Hparams) -> io::Result<usize> {
2501 if dir.join("tokenizer.model").is_file() {
2502 if let Some(n) = h.u64(&["vocab_size_per_layer_input", "vocab_size"]) {
2503 return Ok(n as usize);
2504 }
2505 return sentencepiece_pieces(&std::fs::read(dir.join("tokenizer.model"))?);
2506 }
2507 let path = dir.join("tokenizer.json");
2508 let json: Value = serde_json::from_str(&std::fs::read_to_string(&path)?).map_err(|e| invalid(format!("{}: {e}", path.display())))?;
2509 let vocab = json["model"]["vocab"].as_object().map_or(0, Map::len);
2510 let added = json["added_tokens"].as_array().map_or(0, Vec::len);
2511 Ok(vocab + added)
2512}
2513
2514fn sentencepiece_pieces(bytes: &[u8]) -> io::Result<usize> {
2516 let varint = |at: &mut usize| -> io::Result<u64> {
2517 let mut v = 0u64;
2518 for shift in (0..64).step_by(7) {
2519 let b = *bytes.get(*at).ok_or_else(|| invalid("tokenizer.model ends inside a varint"))?;
2520 *at += 1;
2521 v |= u64::from(b & 0x7f) << shift;
2522 if b & 0x80 == 0 {
2523 return Ok(v);
2524 }
2525 }
2526 Err(invalid("tokenizer.model holds an overlong varint"))
2527 };
2528 let (mut at, mut pieces) = (0usize, 0usize);
2529 while at < bytes.len() {
2530 let key = varint(&mut at)?;
2531 match key & 7 {
2532 0 => {
2533 varint(&mut at)?;
2534 }
2535 1 => at += 8,
2536 2 => {
2537 let len = varint(&mut at)? as usize;
2538 at += len;
2539 }
2540 5 => at += 4,
2541 w => return Err(invalid(format!("tokenizer.model: wire type {w}"))),
2542 }
2543 if key >> 3 == 1 {
2544 pieces += 1;
2545 }
2546 }
2547 Ok(pieces)
2548}
2549
2550impl SpecSource for HfCheckpoint {
2551 fn arch(&self) -> &str {
2552 &self.arch
2553 }
2554 fn arch_meta(&self, suffix: &str) -> Option<&MetaValue> {
2555 self.meta.get(&format!("{}.{suffix}", self.arch))
2556 }
2557 fn dims(&self, name: &str) -> Option<&[u64]> {
2558 self.index.get(name).map(|&i| self.views[i].dims.as_slice())
2559 }
2560 fn tensor_names(&self) -> Vec<&str> {
2561 self.views.iter().map(|v| v.name.as_str()).collect()
2562 }
2563}
2564
2565impl WeightSource for HfCheckpoint {
2566 fn describe(&self, name: &str) -> io::Result<(GgmlType, Vec<u64>)> {
2567 let v = self.index.get(name).map(|&i| &self.views[i]).ok_or_else(|| invalid(format!("no tensor {name}")))?;
2568 Ok((self.ty_of(&v.form)?, v.dims.clone()))
2569 }
2570 fn row_bytes(&mut self, name: &str, first: usize, count: usize) -> io::Result<Vec<u8>> {
2571 let v = self.index.get(name).map(|&i| &self.views[i]).ok_or_else(|| invalid(format!("no tensor {name}")))?;
2572 let row = v.dims.first().copied().unwrap_or(1) as usize;
2573 self.read(&v.form, first * row, count * row)
2574 }
2575 fn has(&self, name: &str) -> bool {
2576 self.index.contains_key(name)
2577 }
2578}