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>,
multiplier: Option<f32>,
) -> Self {
if let Some(m) = multiplier {
for v in raw.iter_mut() {
*v *= m;
}
}
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,
final_norm: &crate::norm::NormOp,
launch: Option<L>,
) -> Option<Self> {
if !greedy_argmax || final_norm.rms_weights().is_none() {
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>,
multiplier: Option<f32>,
) -> Vec<f32> {
debug_assert!(
multiplier.is_none_or(|m| m > 0.0),
"a non-positive logit multiplier would reorder the vocabulary, so a folded \
argmax id could not be passed through"
);
if out.len() == vocab_size {
Logits::from_output_head(out, softcap, multiplier).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), None).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, None).into_vec(),
vec![100.0, -100.0],
"no cap configured must leave the head's output exactly alone"
);
}
#[test]
fn the_logit_multiplier_multiplies_and_runs_before_the_cap() {
assert_eq!(
Logits::from_output_head(vec![8.0, -4.0, 1.0], None, Some(0.25)).into_vec(),
vec![2.0, -1.0, 0.25]
);
let got = Logits::from_output_head(vec![100.0], Some(3.0), Some(0.25)).into_vec();
assert!(
(got[0] - capped(25.0, 3.0)).abs() < 1e-5,
"got {got:?}, want cap applied to the SCALED logit"
);
assert!(
(got[0] - 0.25 * capped(100.0, 3.0)).abs() > 1.0,
"the two orders must be distinguishable here"
);
}
fn rms() -> crate::norm::NormOp {
crate::norm::NormOp::Rms(vec![1.0; 4])
}
#[test]
fn lm_head_folds_into_the_stack_only_under_greedy_argmax() {
assert!(
FoldedLmHead::permit(false, &rms(), Some(())).is_none(),
"without greedy argmax the stack would return uncapped logits"
);
assert!(FoldedLmHead::permit(true, &rms(), None::<u32>).is_none());
let folded = FoldedLmHead::permit(true, &rms(), 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, &rms(), 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), None);
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, &rms(), Some(())).unwrap();
assert_eq!(
folded.interpret(vec![100.0], 32_000, Some(30.0), None),
vec![100.0],
"a 1-element argmax id must not be softcapped"
);
}
#[test]
fn a_non_rms_final_norm_cannot_fold_however_greedy_the_caller_is() {
for norm in [
crate::norm::NormOp::LayerNormNoParams,
crate::norm::NormOp::None,
] {
assert!(
FoldedLmHead::permit(true, &norm, Some(())).is_none(),
"{norm:?}: the stack has no weights to bake the final norm from"
);
}
assert!(
FoldedLmHead::permit(true, &rms(), Some(())).is_some(),
"an RMSNorm final norm still folds, or this test proves nothing"
);
}
}