mod common;
use common::{
assert_all_three_paths_match, graph_caches, graph_fixture_path, load_graph_fixture, worst_vs,
GRAPH_PROMPT,
};
use ferrox_models::capability::QkNormStyle;
use ferrox_models::{Decoder, ModelConfig, RopeLayout};
use ferrox_moe::GatingFunction;
const DENSE_ROWS: [&str; 5] = ["internlm2", "xverse", "ernie4_5", "baichuan", "exaone"];
const ALL_ROWS: [&str; 7] = [
"internlm2",
"xverse",
"ernie4_5",
"baichuan",
"exaone",
"bailingmoe2",
"plamo3",
];
const NO_WINDOW_NO_POST_NORM_ROWS: [&str; 6] = [
"internlm2",
"xverse",
"ernie4_5",
"baichuan",
"exaone",
"bailingmoe2",
];
const INTERNLM2_GOLDEN: [f32; 48] = [
-0.38497463,
-0.411406,
-0.23690121,
-0.079736516,
-0.086446024,
0.034069862,
-0.18947236,
0.31578812,
-0.009138606,
0.23619087,
0.29375088,
0.09236469,
0.46713042,
-0.38761902,
0.8017329,
-0.24398606,
-0.7430083,
-0.08430417,
-0.11138179,
0.09992017,
0.2612124,
-0.4456048,
0.19681671,
-0.09016396,
0.044498637,
-0.27157593,
-0.62290406,
-0.017267626,
0.33660623,
-0.19604379,
0.28323877,
-0.5600808,
0.15880197,
0.097796604,
-0.31987217,
-0.17884886,
-0.11307311,
-0.035523556,
-0.37471962,
-0.2398659,
0.051130064,
-0.28694645,
0.17326327,
-0.12214558,
-0.06344788,
-0.07479449,
-0.2589051,
0.2516519,
];
#[test]
fn internlm2_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("internlm2", &INTERNLM2_GOLDEN);
}
#[test]
fn internlm2_loads_the_optional_qkv_biases_its_file_carries() {
let d = load_graph_fixture("internlm2");
for (il, layer) in d.layers.iter().enumerate() {
for (what, bias) in [
("q", &layer.attn.q_bias),
("k", &layer.attn.k_bias),
("v", &layer.attn.v_bias),
] {
let bias = bias
.as_ref()
.unwrap_or_else(|| panic!("blk.{il}: attn_{what}.bias must be loaded"));
assert!(
bias.iter().any(|b| *b != 0.0),
"blk.{il}: attn_{what}.bias is all zeros; it could not fail"
);
}
}
}
const XVERSE_GOLDEN: [f32; 48] = [
-0.06578407,
0.35991532,
0.09296507,
0.0500691,
0.013582745,
0.50010747,
-0.20849259,
0.053211644,
0.057681076,
0.1302229,
-0.409114,
0.24452712,
0.39229798,
-0.24814555,
-0.19971883,
-0.10270727,
0.36186606,
0.24045944,
0.1772546,
0.38434425,
0.116239354,
-0.1490037,
-0.16259417,
0.07176717,
0.27208632,
-0.1671407,
0.032589324,
-0.13619582,
0.006233629,
0.29767945,
0.16670844,
-0.28568843,
-0.07653904,
0.18598174,
-0.036290076,
-0.3572907,
-0.10366354,
0.472095,
-0.385141,
0.06120664,
0.05274259,
-0.39883983,
-0.22407724,
0.3536656,
0.004656989,
-0.14421564,
0.42171967,
-0.18553367,
];
#[test]
fn xverse_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("xverse", &XVERSE_GOLDEN);
}
const ERNIE4_5_GOLDEN: [f32; 48] = [
0.03883054,
-0.22302249,
-0.17906801,
0.15218028,
-0.02055931,
-0.070127405,
0.1165026,
-0.034907892,
-0.12018872,
-0.048992664,
0.016463999,
0.08155338,
-0.07699711,
-0.12543437,
0.28277662,
0.38459194,
0.13750306,
-0.41823462,
-0.043292582,
-0.12194319,
-0.09083214,
0.071103305,
0.01813967,
0.2717319,
0.031989187,
-0.024477273,
0.18046162,
-0.09042182,
-0.22470418,
0.2138165,
-0.22854619,
0.008390563,
-0.12621851,
-0.4525338,
0.47459865,
-0.22544149,
0.48321664,
0.0048647877,
-0.038895264,
-0.11535422,
-0.048806466,
0.031679325,
0.103515804,
0.15141746,
-0.08237043,
0.10312017,
-0.1163362,
-0.32099646,
];
#[test]
fn ernie4_5_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("ernie4_5", &ERNIE4_5_GOLDEN);
}
#[test]
fn ernie4_5_reads_its_head_dim_from_the_file_rather_than_deriving_it() {
let d = load_graph_fixture("ernie4_5");
assert_eq!(d.config.hidden_dim, 24);
assert_eq!(d.config.n_heads, 4);
assert_eq!(d.config.head_dim, 8);
}
const BAICHUAN_GOLDEN: [f32; 48] = [
0.056164384,
0.05335981,
0.034717236,
-0.016637973,
-0.1681825,
-0.19657308,
-0.093710594,
0.12201875,
0.202757,
0.061601378,
0.0852424,
0.017296217,
-0.13683479,
0.019249436,
-0.0067887288,
0.030473292,
-0.0056031533,
0.019746449,
0.040556442,
-0.13934176,
-0.00078091025,
0.067291364,
0.04678838,
-0.04069045,
-0.07528572,
0.09840066,
0.027413199,
-0.012988582,
-0.0071283206,
0.10830741,
0.046693385,
0.23345774,
0.10834331,
-0.017043814,
0.04738696,
-0.09828674,
-0.018817,
0.037317395,
0.10740893,
-0.086651094,
0.16450927,
0.11803734,
0.07888082,
0.051779855,
-0.087531194,
-0.07406292,
0.008599423,
0.0709973,
];
#[test]
fn baichuan_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("baichuan", &BAICHUAN_GOLDEN);
}
#[test]
fn the_baichuan_fixture_has_the_32_layers_that_select_the_rotating_variant() {
let d = load_graph_fixture("baichuan");
assert_eq!(
d.config.n_layers, 32,
"baichuan.cpp:5-14 reads the variant off this number; 32 is the 7B, which is the \
only one that RoPEs"
);
}
const EXAONE_GOLDEN: [f32; 48] = [
-0.2249429,
0.34571093,
0.028062485,
-0.3856704,
0.08937423,
0.18980339,
-0.052521326,
0.014981142,
0.036303222,
-0.11778174,
0.24545757,
-0.19239902,
0.14795516,
0.0996446,
0.0015762504,
-0.054319553,
0.40219936,
-0.35769135,
0.22361766,
0.07902548,
0.18331085,
0.21348247,
0.25453204,
-0.16258636,
0.42788702,
0.2037906,
-0.018451544,
0.5553448,
0.26972133,
0.13789096,
-0.30589244,
0.06253724,
-0.023459988,
0.16139778,
0.26396036,
0.2515811,
0.53262925,
0.1445617,
-0.4598862,
0.12606835,
-0.1691545,
0.17235437,
-0.09227791,
-0.11130126,
0.046939783,
-0.2966211,
-0.31041434,
-0.3674787,
];
#[test]
fn exaone_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("exaone", &EXAONE_GOLDEN);
}
#[test]
fn the_exaone_fixture_ships_no_output_weight_and_ties_the_lm_head() {
let path = graph_fixture_path("exaone");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
assert!(
file.find_tensor("output.weight").is_none(),
"the fixture must be the tied case or it pins nothing about exaone.cpp:22-24"
);
assert!(file.find_tensor("token_embd.weight").is_some());
}
const BAILINGMOE2_GOLDEN: [f32; 48] = [
-0.09195735,
0.124805875,
0.02403112,
0.33854562,
-0.3279288,
0.0605181,
0.34822923,
-0.00276571,
0.106965765,
-0.1772596,
-0.25497413,
0.32685977,
0.37302768,
-0.13560869,
-0.096370846,
-0.44192657,
0.15269074,
0.32532498,
-0.11355774,
-0.008084873,
0.3309023,
-0.3946235,
0.018904123,
-0.018048527,
-0.1964149,
-0.05105628,
0.42756924,
-0.18849637,
0.40864584,
-0.11983519,
-0.103815354,
0.21823177,
0.26624107,
0.18969972,
0.38225642,
-0.048707068,
0.9169096,
-0.49677095,
-0.0034501795,
0.261436,
0.08845875,
-0.449533,
-0.19524488,
0.17076917,
-0.31562522,
-0.54570425,
0.36975017,
-0.4732991,
];
#[test]
fn bailingmoe2_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("bailingmoe2", &BAILINGMOE2_GOLDEN);
}
#[test]
fn bailingmoe2_reads_its_routing_out_of_the_file_rather_than_guessing() {
let path = graph_fixture_path("bailingmoe2");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
assert!(
file.metadata_u64("bailingmoe2.expert_gating_func")
.is_some(),
"the fixture must carry the key it is being read from"
);
let d = load_graph_fixture("bailingmoe2");
assert_eq!(
d.config.moe.gating,
GatingFunction::Sigmoid,
"bailingmoe2.cpp:11 reads this from the file; ferrox's name-based fallback would \
have said softmax"
);
assert!(
d.config.moe.norm_topk_prob,
"expert_weights_norm is true in this file (bailingmoe2.cpp:10 reads it)"
);
assert_eq!(d.config.moe.expert_weights_scale, 2.5);
assert_eq!(d.config.moe.n_experts, 6);
assert_eq!(d.config.moe.n_experts_active, 2);
assert_eq!(d.config.moe.n_shared_experts, 2);
assert!(d.config.layer_is_dense(0));
assert!(!d.config.layer_is_dense(1));
assert!(!d.config.layer_is_dense(2));
assert_eq!(d.config.qk_norm_style, QkNormStyle::PerHead);
for (il, layer) in d.layers.iter().enumerate() {
assert_eq!(
layer.attn.q_norm.as_ref().map(Vec::len),
Some(d.config.head_dim),
"blk.{il}: per-head Q norm"
);
if il == 0 {
continue;
}
assert_eq!(layer.moe.shared_experts.len(), 1, "blk.{il}");
assert_eq!(layer.moe.shared_experts[0].gate.rows(), 16, "blk.{il}");
assert!(layer.moe.exp_probs_bias.is_some(), "blk.{il}");
}
}
#[test]
fn routing_bailingmoe2_through_softmax_instead_of_sigmoid_diverges_from_llama_cpp() {
let path = graph_fixture_path("bailingmoe2");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
let mut config = ModelConfig::from_gguf(&file).expect("parses");
config.moe.gating = GatingFunction::Softmax;
let d = Decoder::from_gguf(&path, config).expect("loads");
let mut kv = graph_caches(&d);
let worst = worst_vs(
&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
&BAILINGMOE2_GOLDEN,
);
assert!(
worst > 1e-3,
"softmax gating changed the output by only {worst}; the fixture cannot see this"
);
}
#[test]
fn norming_bailingmoe2_after_rope_instead_of_before_diverges_from_llama_cpp() {
let mut d = load_graph_fixture("bailingmoe2");
assert!(
!d.qk_norm_after_rope,
"bailingmoe2.cpp:123-135 norms Q and K and only then rotates them"
);
d.qk_norm_after_rope = true;
let mut kv = graph_caches(&d);
let worst = worst_vs(
&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
&BAILINGMOE2_GOLDEN,
);
assert!(
worst > 1e-2,
"swapping the QK-norm order moved the output by only {worst}; \
the fixture cannot see this arm"
);
}
const PLAMO3_GOLDEN: [f32; 48] = [
-0.13417868,
0.5886667,
0.43671453,
0.47216994,
0.0437923,
0.6202583,
-0.33917707,
-0.15624414,
-0.4340622,
0.08551871,
-0.28434193,
-0.6546696,
-0.33789676,
0.06587186,
0.3138425,
0.7828187,
-0.2447187,
0.75487375,
-0.6003437,
0.070651375,
-0.27455223,
0.5503286,
1.041208,
-1.1511995,
0.37486026,
0.3923036,
-0.086244255,
-0.47600126,
-0.25178447,
0.8037309,
0.19178456,
-0.6201743,
0.41236115,
0.654441,
-0.09785151,
0.095623925,
-0.27044287,
-0.5235145,
-0.57836926,
0.32180697,
-0.3484251,
0.004385993,
-0.17752947,
-0.032321587,
0.11161679,
0.20395917,
0.15744714,
0.037524372,
];
#[test]
fn plamo3_matches_llama_cpp_on_all_three_paths() {
assert_all_three_paths_match("plamo3", &PLAMO3_GOLDEN);
}
#[test]
fn the_plamo3_fixture_spells_its_post_norms_without_a_weight_suffix() {
let path = graph_fixture_path("plamo3");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
for base in ["post_attention_norm", "post_ffw_norm"] {
assert!(
file.find_tensor(&format!("blk.0.{base}")).is_some(),
"blk.0.{base} is the name plamo3.cpp:52,55 asks for"
);
assert!(
file.find_tensor(&format!("blk.0.{base}.weight")).is_none(),
"blk.0.{base}.weight is the spelling every OTHER architecture uses; carrying \
both would make this fixture prove nothing about plamo3"
);
}
let d = load_graph_fixture("plamo3");
for (il, layer) in d.layers.iter().enumerate() {
let post_attn = layer
.attn
.post_attn_norm
.as_ref()
.unwrap_or_else(|| panic!("blk.{il}: attn_post_norm must be loaded"));
let post_ffn = layer
.attn
.post_ffn_norm
.as_ref()
.unwrap_or_else(|| panic!("blk.{il}: ffn_post_norm must be loaded"));
assert!(post_attn.iter().any(|w| *w != 0.0), "blk.{il}");
assert!(post_ffn.iter().any(|w| *w != 0.0), "blk.{il}");
}
}
#[test]
fn dropping_either_of_plamo3s_post_norms_diverges_from_llama_cpp() {
for which in ["attn", "ffn"] {
let mut d = load_graph_fixture("plamo3");
for layer in d.layers.iter_mut() {
if which == "attn" {
layer.attn.post_attn_norm = None;
} else {
layer.attn.post_ffn_norm = None;
}
}
let mut kv = graph_caches(&d);
let worst = worst_vs(
&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
&PLAMO3_GOLDEN,
);
assert!(
worst > 1e-2,
"dropping post_{which}_norm moved the output by only {worst}; \
the fixture cannot see this slot"
);
}
}
#[test]
fn plamo3_reads_its_window_period_and_phase_and_the_window_actually_bites() {
let d = load_graph_fixture("plamo3");
assert_eq!(d.config.sliding_window, Some(3));
assert_eq!(d.config.swa_pattern, Some(2));
assert!(
!d.config.swa_dense_first,
"set_swa_pattern's dense_first defaults to false and plamo3.cpp:11 does not pass it"
);
assert_eq!(d.config.layer_sliding_window(0), Some(3));
assert_eq!(d.config.layer_sliding_window(1), None);
assert_eq!(d.config.layer_sliding_window(2), Some(3));
assert_eq!(d.config.layer_sliding_window(3), None);
assert!(GRAPH_PROMPT.len() > 3);
}
#[test]
fn removing_plamo3s_sliding_window_diverges_from_llama_cpp() {
let path = graph_fixture_path("plamo3");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
let mut config = ModelConfig::from_gguf(&file).expect("parses");
config.sliding_window = None;
let d = Decoder::from_gguf(&path, config).expect("loads");
let mut kv = graph_caches(&d);
let worst = worst_vs(
&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
&PLAMO3_GOLDEN,
);
assert!(
worst > 1e-2,
"dropping the window moved the output by only {worst}; it never masked anything"
);
}
#[test]
fn inverting_plamo3s_swa_phase_diverges_from_llama_cpp() {
let path = graph_fixture_path("plamo3");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
let mut config = ModelConfig::from_gguf(&file).expect("parses");
config.swa_dense_first = true;
let d = Decoder::from_gguf(&path, config).expect("loads");
assert_eq!(
d.config.layer_sliding_window(0),
None,
"phase really flipped"
);
let mut kv = graph_caches(&d);
let worst = worst_vs(
&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
&PLAMO3_GOLDEN,
);
assert!(
worst > 1e-2,
"inverting the SWA phase moved the output by only {worst}; \
the fixture cannot see which layers slide"
);
}
#[test]
fn the_plamo3_fixture_has_equal_key_and_value_head_dims() {
let path = graph_fixture_path("plamo3");
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
assert_eq!(file.metadata_u64("plamo3.attention.key_length"), Some(8));
assert_eq!(file.metadata_u64("plamo3.attention.value_length"), Some(8));
let d = load_graph_fixture("plamo3");
assert_eq!(d.config.head_dim, 8);
assert_eq!(d.config.hidden_dim, 24);
assert_eq!(d.config.n_heads, 4);
}
#[test]
fn the_rope_variant_each_architecture_uses_is_the_one_llama_cpp_uses() {
for (name, want) in [
("internlm2", RopeLayout::Norm),
("xverse", RopeLayout::Norm),
("ernie4_5", RopeLayout::Norm),
("baichuan", RopeLayout::Norm),
("exaone", RopeLayout::Neox),
("bailingmoe2", RopeLayout::Neox),
("plamo3", RopeLayout::Neox),
] {
assert_eq!(
load_graph_fixture(name).config.rope_layout,
want,
"{name}: rope layout"
);
}
}
#[test]
fn rotating_the_wrong_pairs_diverges_from_llama_cpp() {
for (name, golden) in [
("internlm2", &INTERNLM2_GOLDEN),
("xverse", &XVERSE_GOLDEN),
("ernie4_5", &ERNIE4_5_GOLDEN),
("baichuan", &BAICHUAN_GOLDEN),
("exaone", &EXAONE_GOLDEN),
("bailingmoe2", &BAILINGMOE2_GOLDEN),
("plamo3", &PLAMO3_GOLDEN),
] {
let path = graph_fixture_path(name);
let file = ferrox_gguf::GgufFile::open(&path).expect("opens");
let mut config = ModelConfig::from_gguf(&file).expect("parses");
config.rope_layout = match config.rope_layout {
RopeLayout::Norm => RopeLayout::Neox,
_ => RopeLayout::Norm,
};
let d = Decoder::from_gguf(&path, config).expect("loads");
let mut kv = graph_caches(&d);
let worst = worst_vs(&d.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv), golden);
assert!(
worst > 1e-2,
"{name}: flipping the RoPE variant moved the output by only {worst}; \
the fixture cannot see this"
);
}
}
#[test]
fn no_row_here_overrides_the_attention_scale() {
for name in ALL_ROWS {
let d = load_graph_fixture(name);
assert!(
d.config.attention_scale.is_none(),
"{name}: attention_scale must stay unset"
);
}
}
#[test]
fn six_of_the_rows_have_no_window_and_no_post_norm() {
for name in NO_WINDOW_NO_POST_NORM_ROWS {
let d = load_graph_fixture(name);
assert!(d.config.sliding_window.is_none(), "{name}: sliding window");
for (il, layer) in d.layers.iter().enumerate() {
assert!(
layer.attn.post_attn_norm.is_none(),
"{name} blk.{il}: no LLM_TENSOR_ATTN_POST_NORM upstream"
);
assert!(
layer.attn.post_ffn_norm.is_none(),
"{name} blk.{il}: no LLM_TENSOR_FFN_POST_NORM upstream"
);
}
}
}
#[test]
fn the_dense_rows_have_no_qk_norm_and_therefore_no_ordering_question() {
for name in DENSE_ROWS {
let d = load_graph_fixture(name);
for (il, layer) in d.layers.iter().enumerate() {
assert!(
layer.attn.q_norm.is_none() && layer.attn.k_norm.is_none(),
"{name} blk.{il}: no attn_q_norm/attn_k_norm upstream, so no ordering \
question either"
);
}
}
}
#[test]
fn only_bailingmoe2_is_moe() {
for name in DENSE_ROWS {
let d = load_graph_fixture(name);
assert_eq!(
d.config.moe.n_experts, 1,
"{name}: dense, so gating and top-k renormalisation do not arise"
);
assert_eq!(d.config.moe.n_shared_experts, 0, "{name}");
}
let plamo3 = load_graph_fixture("plamo3");
assert_eq!(
plamo3.config.moe.n_experts, 1,
"plamo3 is dense too, it just is not in DENSE_ROWS because it has post-norms"
);
let moe = load_graph_fixture("bailingmoe2");
assert!(
moe.config.moe.n_experts > 1,
"bailingmoe2 is the row the MoE questions are asked of"
);
}