pub const ROPE_GATED_ON_FINETUNED: &[&str] = &["granite", "granitemoe", "granite-moe"];
pub fn unrotated_refusal(arch: &str, declared: Option<bool>) -> Option<String> {
if !ROPE_GATED_ON_FINETUNED.contains(&arch) || declared != Some(false) {
return None;
}
Some(format!(
"`{arch}.rope.scaling.finetuned` = false. src/models/granite.cpp:33-35 reads this key \
as a SWITCH FOR ROPE rather than as a note about the scaling, and :130-133,:206-219 \
then build no positions and skip both ggml_rope_ext calls, so llama.cpp runs this \
checkpoint with NO rotation at all. ferrox's generic decoder rotates every Q and K \
head of every layer and has no per-model way to express `no RoPE`, so it would \
answer fluently from positions this checkpoint never encodes -- the same failure \
gpt2, mpt, refact, bloom and jais were caught in. No Granite converter writes this \
key (conversion/granite.py:253 is GraniteHybridModel, a different architecture \
string), so a real Granite export never reaches this message"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_an_explicit_false_refuses() {
for arch in ROPE_GATED_ON_FINETUNED {
assert!(
unrotated_refusal(arch, Some(false)).is_some(),
"{arch} must refuse an explicit false"
);
assert!(
unrotated_refusal(arch, Some(true)).is_none(),
"{arch} rotates when the file says so"
);
assert!(
unrotated_refusal(arch, None).is_none(),
"{arch} must take llama.cpp's `true` default when the key is absent"
);
}
}
#[test]
fn an_architecture_that_does_not_gate_its_rope_is_unaffected() {
for arch in ["llama", "qwen3", "granitehybrid"] {
assert!(
unrotated_refusal(arch, Some(false)).is_none(),
"{arch} does not read this key as a RoPE switch"
);
}
}
#[test]
fn the_refusal_names_the_key_and_the_line_that_decides_it() {
let msg = unrotated_refusal("granite", Some(false)).expect("refused");
assert!(msg.contains("granite.rope.scaling.finetuned"), "{msg}");
assert!(msg.contains("granite.cpp:33-35"), "{msg}");
}
}