pub fn ferrum_to_gguf(name: &str) -> Option<String> {
if let Some(out) = map_top_level(name) {
return Some(out);
}
let rest = name.strip_prefix("model.layers.")?;
let (idx_str, after_idx) = rest.split_once('.')?;
let idx: usize = idx_str.parse().ok()?;
let mapped = map_layer_scoped(after_idx)?;
Some(format!("blk.{idx}.{mapped}"))
}
fn map_top_level(name: &str) -> Option<String> {
let mapped = match name {
"model.embed_tokens" => "token_embd",
"model.embed_tokens.weight" => "token_embd.weight",
"model.norm" => "output_norm",
"model.norm.weight" => "output_norm.weight",
"lm_head" => "output",
"lm_head.weight" => "output.weight",
_ => return None,
};
Some(mapped.to_string())
}
fn map_layer_scoped(rest: &str) -> Option<String> {
let (stem, suffix) = if let Some(s) = rest.strip_suffix(".weight") {
(s, ".weight")
} else if let Some(s) = rest.strip_suffix(".bias") {
(s, ".bias")
} else {
(rest, "")
};
let mapped_stem = match stem {
"input_layernorm" => "attn_norm",
"post_attention_layernorm" => "ffn_norm",
"self_attn.q_proj" => "attn_q",
"self_attn.k_proj" => "attn_k",
"self_attn.v_proj" => "attn_v",
"self_attn.o_proj" => "attn_output",
"self_attn.q_norm" => "attn_q_norm",
"self_attn.k_norm" => "attn_k_norm",
"mlp.gate_proj" => "ffn_gate",
"mlp.up_proj" => "ffn_up",
"mlp.down_proj" => "ffn_down",
"mlp.router" => "ffn_gate_inp",
"mlp.gate_exps" => "ffn_gate_exps",
"mlp.up_exps" => "ffn_up_exps",
"mlp.down_exps" => "ffn_down_exps",
_ => return None,
};
Some(format!("{mapped_stem}{suffix}"))
}
pub fn qkv_split_parts(layer_prefix: &str) -> [String; 3] {
[
format!("{layer_prefix}self_attn.q_proj"),
format!("{layer_prefix}self_attn.k_proj"),
format!("{layer_prefix}self_attn.v_proj"),
]
}
pub fn gate_up_split_parts(layer_prefix: &str) -> [String; 2] {
[
format!("{layer_prefix}mlp.gate_proj"),
format!("{layer_prefix}mlp.up_proj"),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_top_level_tensors() {
assert_eq!(
ferrum_to_gguf("model.embed_tokens.weight"),
Some("token_embd.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.embed_tokens"),
Some("token_embd".into())
);
assert_eq!(
ferrum_to_gguf("model.norm.weight"),
Some("output_norm.weight".into())
);
assert_eq!(ferrum_to_gguf("lm_head"), Some("output".into()));
assert_eq!(
ferrum_to_gguf("lm_head.weight"),
Some("output.weight".into())
);
}
#[test]
fn maps_layer_attention_weights() {
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.q_proj.weight"),
Some("blk.0.attn_q.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.27.self_attn.k_proj.weight"),
Some("blk.27.attn_k.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.5.self_attn.v_proj.weight"),
Some("blk.5.attn_v.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.o_proj.weight"),
Some("blk.0.attn_output.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.o_proj"),
Some("blk.0.attn_output".into())
);
}
#[test]
fn maps_qwen3_qk_norm() {
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.q_norm.weight"),
Some("blk.0.attn_q_norm.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.k_norm.weight"),
Some("blk.0.attn_k_norm.weight".into())
);
}
#[test]
fn maps_attention_bias() {
assert_eq!(
ferrum_to_gguf("model.layers.0.self_attn.q_proj.bias"),
Some("blk.0.attn_q.bias".into())
);
}
#[test]
fn maps_layer_norms() {
assert_eq!(
ferrum_to_gguf("model.layers.0.input_layernorm.weight"),
Some("blk.0.attn_norm.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.post_attention_layernorm.weight"),
Some("blk.0.ffn_norm.weight".into())
);
}
#[test]
fn maps_mlp_projections() {
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.gate_proj.weight"),
Some("blk.0.ffn_gate.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.up_proj.weight"),
Some("blk.0.ffn_up.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.down_proj.weight"),
Some("blk.0.ffn_down.weight".into())
);
}
#[test]
fn maps_moe_router_and_stacked_experts() {
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.router.weight"),
Some("blk.0.ffn_gate_inp.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.gate_exps.weight"),
Some("blk.0.ffn_gate_exps.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.27.mlp.up_exps.weight"),
Some("blk.27.ffn_up_exps.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.down_exps.weight"),
Some("blk.0.ffn_down_exps.weight".into())
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.router"),
Some("blk.0.ffn_gate_inp".into())
);
}
#[test]
fn rejects_unknown_names() {
assert_eq!(ferrum_to_gguf("totally_made_up"), None);
assert_eq!(ferrum_to_gguf("model.layers.0.unknown_part.weight"), None);
assert_eq!(
ferrum_to_gguf("model.layers.bad_idx.input_layernorm.weight"),
None
);
assert_eq!(
ferrum_to_gguf("model.layers.0.mlp.experts.0.gate_proj.weight"),
None
);
}
#[test]
fn split_parts_helpers() {
assert_eq!(
qkv_split_parts("model.layers.3."),
[
"model.layers.3.self_attn.q_proj".to_string(),
"model.layers.3.self_attn.k_proj".into(),
"model.layers.3.self_attn.v_proj".into(),
]
);
assert_eq!(
gate_up_split_parts("model.layers.3."),
[
"model.layers.3.mlp.gate_proj".to_string(),
"model.layers.3.mlp.up_proj".into(),
]
);
}
}