pub fn slopes(n_heads: usize, max_bias: f32) -> Option<Vec<f32>> {
if max_bias.partial_cmp(&0.0) != Some(std::cmp::Ordering::Greater) || n_heads == 0 {
return None;
}
let n_head_log2 = 1usize << (usize::BITS - 1 - n_heads.leading_zeros());
let m0 = 2f32.powf(-max_bias / n_head_log2 as f32);
let m1 = 2f32.powf(-(max_bias / 2.0) / n_head_log2 as f32);
Some(
(0..n_heads)
.map(|h| {
if h < n_head_log2 {
m0.powi(h as i32 + 1)
} else {
m1.powi(2 * (h - n_head_log2) as i32 + 1)
}
})
.collect(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_power_of_two_head_count_is_the_geometric_sequence() {
let s = slopes(8, 8.0).unwrap();
let want = [
0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625, 0.0078125, 0.00390625,
];
for (g, w) in s.iter().zip(want) {
assert!((g - w).abs() < 1e-7, "{g} vs {w}");
}
}
#[test]
fn a_non_power_of_two_head_count_takes_the_second_base_for_the_tail() {
let s = slopes(12, 8.0).unwrap();
let m1 = 2f32.powf(-0.5);
for (h, got) in s.iter().enumerate() {
let want = if h < 8 {
0.5f32.powi(h as i32 + 1)
} else {
m1.powi(2 * (h as i32 - 8) + 1)
};
assert!((got - want).abs() < 1e-7, "head {h}: {got} vs {want}");
}
}
#[test]
fn a_zero_max_bias_is_no_alibi() {
assert!(slopes(8, 0.0).is_none());
assert!(slopes(8, -1.0).is_none());
assert!(slopes(0, 8.0).is_none());
}
}