use frink_core::matmul::softcap_inplace;
use frink_core::weight_matrix::WeightMatrix;
pub(crate) struct Logits(Vec<f32>);
impl Logits {
pub(crate) fn from_output_head(
mut raw: Vec<f32>,
bias: Option<&[f32]>,
softcap: Option<f32>,
multiplier: Option<f32>,
) -> Self {
if let Some(b) = bias {
debug_assert!(!b.is_empty() && raw.len().is_multiple_of(b.len()));
for row in raw.chunks_mut(b.len()) {
for (v, bv) in row.iter_mut().zip(b) {
*v += bv;
}
}
}
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 project(
head: &WeightMatrix,
x: &[f32],
bias: Option<&[f32]>,
softcap: Option<f32>,
multiplier: Option<f32>,
) -> Self {
match (bias, softcap, multiplier) {
(None, Some(cap), None) => Logits(head.apply_softcapped(x, cap)),
_ => Self::from_output_head(head.apply(x), bias, softcap, multiplier),
}
}
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,
output_bias: Option<&[f32]>,
launch: Option<L>,
) -> Option<Self> {
if !greedy_argmax || final_norm.rms_weights().is_none() || output_bias.is_some() {
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, None, 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], None, 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, None).into_vec(),
vec![100.0, -100.0],
"no cap configured must leave the head's output exactly alone"
);
}
#[test]
fn project_with_a_cap_agrees_with_the_raw_projection_capped_afterwards() {
use frink_core::tensor::Tensor;
let (rows, cols) = (5usize, 4usize);
let data: Vec<f32> = (0..rows * cols)
.map(|i| (i as f32 * 0.7).sin() * 40.0)
.collect();
let head = WeightMatrix::F32(Tensor::new(data, vec![rows, cols]));
let x = [1.0f32, -2.0, 0.5, 3.0];
let via_project = Logits::project(&head, &x, None, Some(30.0), None).into_vec();
let via_raw = Logits::from_output_head(head.apply(&x), None, Some(30.0), None).into_vec();
assert_eq!(via_project, via_raw);
for v in &via_project {
assert!(v.abs() < 30.0, "a capped logit is inside (-cap, cap): {v}");
}
assert_eq!(
Logits::project(&head, &x, None, None, None).into_vec(),
head.apply(&x)
);
}
#[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, None, Some(0.25)).into_vec(),
vec![2.0, -1.0, 0.25]
);
let got = Logits::from_output_head(vec![100.0], None, 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(), None, Some(())).is_none(),
"without greedy argmax the stack would return uncapped logits"
);
assert!(FoldedLmHead::permit(true, &rms(), None, None::<u32>).is_none());
let folded = FoldedLmHead::permit(true, &rms(), None, 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(), None, 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(), None, 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_head_with_an_output_bias_cannot_fold() {
let bias = [0.0f32, 3.0];
assert!(FoldedLmHead::permit(true, &rms(), Some(&bias), Some(())).is_none());
assert!(FoldedLmHead::permit(true, &rms(), None, Some(())).is_some());
}
#[test]
fn the_output_bias_is_added_per_row_before_the_other_transforms() {
let bias = [1.0f32, -1.0];
let out = Logits::from_output_head(vec![1.0, 1.0, 2.0, 2.0], Some(&bias), None, Some(2.0));
assert_eq!(out.as_slice(), &[4.0, 0.0, 6.0, 2.0]);
}
#[test]
fn a_non_rms_final_norm_cannot_fold_however_greedy_the_caller_is() {
for norm in [
crate::norm::NormOp::LayerNormNoParams,
crate::norm::NormOp::RmsNoParams,
crate::norm::NormOp::None,
] {
assert!(
FoldedLmHead::permit(true, &norm, None, Some(())).is_none(),
"{norm:?}: the stack has no weights to bake the final norm from"
);
}
assert!(
FoldedLmHead::permit(true, &rms(), None, Some(())).is_some(),
"an RMSNorm final norm still folds, or this test proves nothing"
);
}
}