use super::SamplingParams;
use crate::penalty_window::PenaltyWindow;
pub(crate) fn apply_history_penalties(
scores: &mut [f32],
params: &SamplingParams,
history: PenaltyWindow<'_>,
) {
if !params.sampler_order.has_penalties() {
return;
}
if params.repetition_penalty == 1.0
&& params.presence_penalty == 0.0
&& params.frequency_penalty == 0.0
{
return;
}
if params.penalty_last_n == 0 {
return;
}
let mut counts = std::collections::HashMap::<usize, usize>::new();
for tok in history.recent(params.penalty_last_n) {
*counts.entry(tok).or_insert(0) += 1;
}
for (tok, count) in counts {
let Some(s) = scores.get_mut(tok) else {
continue;
};
if params.repetition_penalty != 1.0 {
*s = if *s > 0.0 {
*s / params.repetition_penalty
} else {
*s * params.repetition_penalty
};
}
*s -= params.frequency_penalty * count as f32;
*s -= params.presence_penalty;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_repetition_penalty_does_not_compound_with_repeats() {
let params = SamplingParams {
temperature: 1.0,
top_p: 1.0,
top_k: 0,
repetition_penalty: 2.0,
..SamplingParams::default()
};
let logits = vec![4.0f32, 1.0, 1.0];
let mut scores = logits.clone();
apply_history_penalties(
&mut scores,
¶ms,
PenaltyWindow::new(&[], &[0, 0, 0, 0, 0]),
);
assert!(
(scores[0] - 2.0).abs() < 1e-6,
"expected one division (2.0), got {} -- {} would be 2^5",
scores[0],
4.0f32 / 32.0
);
let mut once = logits.clone();
apply_history_penalties(&mut once, ¶ms, PenaltyWindow::new(&[], &[0]));
assert_eq!(once[0].to_bits(), scores[0].to_bits());
let mut negative = vec![-4.0f32];
apply_history_penalties(&mut negative, ¶ms, PenaltyWindow::new(&[], &[0, 0, 0]));
assert!((negative[0] + 8.0).abs() < 1e-6, "got {}", negative[0]);
}
#[test]
fn the_penalties_only_see_the_last_n_tokens() {
let params = SamplingParams {
repetition_penalty: 2.0,
penalty_last_n: 2,
..SamplingParams::default()
};
let mut scores = vec![8.0f32, 8.0, 8.0];
apply_history_penalties(&mut scores, ¶ms, PenaltyWindow::new(&[], &[0, 1, 2]));
assert_eq!(
scores[0].to_bits(),
8.0f32.to_bits(),
"token 0 is outside the window"
);
assert!((scores[1] - 4.0).abs() < 1e-6, "got {}", scores[1]);
assert!((scores[2] - 4.0).abs() < 1e-6, "got {}", scores[2]);
let off = SamplingParams {
penalty_last_n: 0,
..params.clone()
};
let mut untouched = vec![8.0f32; 3];
apply_history_penalties(&mut untouched, &off, PenaltyWindow::new(&[], &[0, 1, 2]));
assert_eq!(untouched, vec![8.0f32; 3]);
let wide = SamplingParams {
penalty_last_n: 1000,
..params
};
let mut short = vec![8.0f32];
apply_history_penalties(&mut short, &wide, PenaltyWindow::new(&[], &[0]));
assert!((short[0] - 4.0).abs() < 1e-6);
}
#[test]
fn the_frequency_penalty_still_counts_repeats() {
let params = SamplingParams {
frequency_penalty: 0.5,
presence_penalty: 0.25,
..SamplingParams::default()
};
let mut scores = vec![10.0f32];
apply_history_penalties(&mut scores, ¶ms, PenaltyWindow::new(&[], &[0, 0, 0, 0]));
assert!((scores[0] - 7.75).abs() < 1e-6, "got {}", scores[0]);
}
}