use ferrox_core::matmul::softcap_inplace;
pub(crate) struct Logits(Vec<f32>);
impl Logits {
pub(crate) fn from_output_head(mut raw: Vec<f32>, softcap: Option<f32>) -> Self {
if let Some(sc) = softcap {
softcap_inplace(&mut raw, sc);
}
Logits(raw)
}
pub(crate) fn as_slice(&self) -> &[f32] {
&self.0
}
pub(crate) fn into_vec(self) -> Vec<f32> {
self.0
}
}
#[cfg(any(feature = "metal", test))]
pub(crate) struct FoldedLmHead<L> {
launch: L,
}
#[cfg(any(feature = "metal", test))]
impl<L> FoldedLmHead<L> {
pub(crate) fn permit(greedy_argmax: bool, launch: Option<L>) -> Option<Self> {
if !greedy_argmax {
return None;
}
launch.map(|launch| FoldedLmHead { launch })
}
pub(crate) fn launch(&self) -> &L {
&self.launch
}
pub(crate) fn argmax_only(&self) -> bool {
true
}
pub(crate) fn interpret(
&self,
out: Vec<f32>,
vocab_size: usize,
softcap: Option<f32>,
) -> Vec<f32> {
if out.len() == vocab_size {
Logits::from_output_head(out, softcap).into_vec()
} else {
out
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn capped(x: f32, sc: f32) -> f32 {
sc * (x / sc).tanh()
}
#[test]
fn logits_cannot_be_built_without_the_cap_being_applied() {
let out = Logits::from_output_head(vec![100.0, -100.0, 0.5], Some(30.0)).into_vec();
for (got, &raw) in out.iter().zip([100.0f32, -100.0, 0.5].iter()) {
assert!(
(got - capped(raw, 30.0)).abs() < 1e-5,
"got {got} for raw {raw}"
);
}
assert_eq!(
Logits::from_output_head(vec![100.0, -100.0], None).into_vec(),
vec![100.0, -100.0],
"no cap configured must leave the head's output exactly alone"
);
}
#[test]
fn lm_head_folds_into_the_stack_only_under_greedy_argmax() {
assert!(
FoldedLmHead::permit(false, Some(())).is_none(),
"without greedy argmax the stack would return uncapped logits"
);
assert!(FoldedLmHead::permit(true, None::<u32>).is_none());
let folded =
FoldedLmHead::permit(true, Some(7u32)).expect("greedy + launch permits folding");
assert_eq!(
*folded.launch(),
7,
"the permission must carry the launch it was granted for"
);
assert!(
folded.argmax_only(),
"a permitted fold is an argmax fold; anything else returns raw logits"
);
}
#[test]
fn a_folded_stack_returning_logits_gets_them_softcapped() {
let folded = FoldedLmHead::permit(true, Some(())).unwrap();
let vocab = 4;
let raw = vec![100.0f32, -100.0, 31.0, 0.25];
let got = folded.interpret(raw.clone(), vocab, Some(30.0));
for (i, (g, r)) in got.iter().zip(raw.iter()).enumerate() {
assert!(
(g - capped(*r, 30.0)).abs() < 1e-4,
"logit {i}: got {g}, expected {} (raw {r})",
capped(*r, 30.0)
);
}
assert!(
got.iter().zip(raw.iter()).any(|(g, r)| (g - r).abs() > 1.0),
"the cap must actually bite at these magnitudes, or this test \
cannot tell a capped path from a raw one"
);
}
#[test]
fn a_folded_stack_returning_an_argmax_id_is_passed_through_untouched() {
let folded = FoldedLmHead::permit(true, Some(())).unwrap();
assert_eq!(
folded.interpret(vec![100.0], 32_000, Some(30.0)),
vec![100.0],
"a 1-element argmax id must not be softcapped"
);
}
}