use std::collections::BTreeMap;
use std::path::PathBuf;
use ferrox_gguf::{GgmlType, GgufValue, GgufWriter, ShardedGguf, TensorPlan};
use ferrox_models::load_rank_head;
const N_EMBD: usize = 3;
const POOLER_W: [f32; 6] = [0.5, -0.25, 1.0, -1.5, 0.75, 0.25];
const POOLER_B: [f32; 2] = [0.1, -0.2];
const CLASSIFIER_W_POOLED: [f32; 2] = [2.0, -3.0];
const CLASSIFIER_W_DIRECT: [f32; 3] = [2.0, -3.0, 0.5];
const CLASSIFIER_B: [f32; 1] = [0.25];
const CLS_ROW: [f32; N_EMBD] = [1.0, -2.0, 0.5];
fn f32_bytes(v: &[f32]) -> Vec<u8> {
v.iter().flat_map(|x| x.to_le_bytes()).collect()
}
fn write_head_gguf(name: &str, tensors: &[(&str, Vec<u64>, Vec<f32>)]) -> PathBuf {
let mut metadata = BTreeMap::new();
metadata.insert(
"general.architecture".to_string(),
GgufValue::String("bert".to_string()),
);
metadata.insert(
"bert.classifier.output_labels".to_string(),
GgufValue::Array(vec![GgufValue::String("LABEL_0".to_string())]),
);
let plan: Vec<TensorPlan> = tensors
.iter()
.map(|(n, shape, data)| TensorPlan {
name: (*n).to_string(),
shape: shape.clone(),
dtype: GgmlType::F32,
byte_len: data.len() * 4,
})
.collect();
let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
std::fs::create_dir_all(&dir).expect("create the fixture directory");
let path = dir.join(format!("{name}-{}.gguf", std::process::id()));
let file = std::fs::File::create(&path).expect("create the fixture");
let mut w = GgufWriter::create(file, &metadata, plan).expect("write the header");
for (n, _, data) in tensors {
w.write_tensor(n, &f32_bytes(data)).expect("write a tensor");
}
w.finish().expect("finish the fixture");
path
}
fn expected_pooled() -> [f32; 2] {
let p0 = 0.5 * CLS_ROW[0] + -0.25 * CLS_ROW[1] + 1.0 * CLS_ROW[2] + POOLER_B[0];
let p1 = -1.5 * CLS_ROW[0] + 0.75 * CLS_ROW[1] + 0.25 * CLS_ROW[2] + POOLER_B[1];
[p0.tanh(), p1.tanh()]
}
#[test]
fn a_gguf_carrying_a_pooler_runs_classifier_tanh_pooler() {
let path = write_head_gguf(
"rerank-head-pooled",
&[
("cls.weight", vec![3, 2], POOLER_W.to_vec()),
("cls.bias", vec![2], POOLER_B.to_vec()),
("cls.output.weight", vec![2], CLASSIFIER_W_POOLED.to_vec()),
("cls.output.bias", vec![1], CLASSIFIER_B.to_vec()),
],
);
let file = ShardedGguf::open(&path).expect("open the fixture");
let head = load_rank_head(&file, "bert", N_EMBD, 1e-12)
.expect("the head loads")
.expect("the fixture carries a head");
assert!(head.has_pooler(), "cls.weight is in the file");
assert_eq!(head.graph(), "classifier(tanh(pooler(cls)))");
assert_eq!(head.n_cls_out(), 1);
assert_eq!(head.labels(), ["LABEL_0"]);
let t = expected_pooled();
let want = CLASSIFIER_W_POOLED[0] * t[0] + CLASSIFIER_W_POOLED[1] * t[1] + CLASSIFIER_B[0];
let got = head.score(&CLS_ROW);
assert!(
(got - want).abs() < 1e-6,
"head produced {got}, hand-computed classifier(tanh(pooler(cls))) is {want}"
);
let p0 = 0.5 * CLS_ROW[0] + -0.25 * CLS_ROW[1] + 1.0 * CLS_ROW[2] + POOLER_B[0];
let p1 = -1.5 * CLS_ROW[0] + 0.75 * CLS_ROW[1] + 0.25 * CLS_ROW[2] + POOLER_B[1];
let no_tanh = CLASSIFIER_W_POOLED[0] * p0 + CLASSIFIER_W_POOLED[1] * p1 + CLASSIFIER_B[0];
assert!(
(got - no_tanh).abs() > 1.0,
"a head with no tanh would have scored {no_tanh}, and {got} is indistinguishable"
);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_gguf_with_no_pooler_still_projects_the_cls_row_directly() {
let path = write_head_gguf(
"rerank-head-direct",
&[
("cls.output.weight", vec![3], CLASSIFIER_W_DIRECT.to_vec()),
("cls.output.bias", vec![1], CLASSIFIER_B.to_vec()),
],
);
let file = ShardedGguf::open(&path).expect("open the fixture");
let head = load_rank_head(&file, "bert", N_EMBD, 1e-12)
.expect("the head loads")
.expect("the fixture carries a head");
assert!(!head.has_pooler(), "cls.weight is NOT in the file");
assert_eq!(head.graph(), "classifier(cls)");
let want = CLASSIFIER_W_DIRECT[0] * CLS_ROW[0]
+ CLASSIFIER_W_DIRECT[1] * CLS_ROW[1]
+ CLASSIFIER_W_DIRECT[2] * CLS_ROW[2]
+ CLASSIFIER_B[0];
let got = head.score(&CLS_ROW);
assert!(
(got - want).abs() < 1e-6,
"head produced {got}, hand-computed classifier(cls) is {want}"
);
let doubled: Vec<f32> = CLS_ROW.iter().map(|v| v * 2.0).collect();
let unbiased = |s: f32| s - CLASSIFIER_B[0];
assert!(
(unbiased(head.score(&doubled)) - 2.0 * unbiased(got)).abs() < 1e-6,
"the head with no pooler is not linear, so something applied a non-linearity"
);
std::fs::remove_file(&path).ok();
}