use super::penalties::apply_history_penalties;
use super::{argmax, filtered_distribution, greedy_choice, SamplingParams};
use crate::penalty_window::PenaltyWindow;
pub type LogitMask<'a> = &'a mut dyn FnMut(&mut [f32]);
pub struct Sampler {
state: u64,
}
impl Sampler {
pub fn new(seed: u64) -> Self {
Sampler {
state: if seed == 0 { 0x9E3779B97F4A7C15 } else { seed },
}
}
fn next_u64(&mut self) -> u64 {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state.wrapping_mul(0x2545F491_4F6CDD1D)
}
fn next_f32(&mut self) -> f32 {
(self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
}
pub fn xtc_roll(&mut self, params: &SamplingParams) -> Option<f32> {
if params.xtc_can_fire() {
Some(self.next_f32())
} else {
None
}
}
pub fn sample(
&mut self,
logits: &[f32],
params: &SamplingParams,
history: PenaltyWindow<'_>,
) -> usize {
self.sample_with_mask(logits, params, history, None)
}
pub fn sample_with_mask(
&mut self,
logits: &[f32],
params: &SamplingParams,
history: PenaltyWindow<'_>,
mask: Option<LogitMask<'_>>,
) -> usize {
self.sample_inner(logits, params, history, mask, false).0
}
pub fn sample_reporting(
&mut self,
logits: &[f32],
params: &SamplingParams,
history: PenaltyWindow<'_>,
mask: Option<LogitMask<'_>>,
) -> (usize, Option<Vec<f32>>) {
self.sample_inner(logits, params, history, mask, true)
}
fn sample_inner(
&mut self,
logits: &[f32],
params: &SamplingParams,
history: PenaltyWindow<'_>,
mut mask: Option<LogitMask<'_>>,
want_probs: bool,
) -> (usize, Option<Vec<f32>>) {
let xtc_roll = self.xtc_roll(params);
if params.temperature <= 0.0 && mask.is_none() && logits.len() == 1 {
return (logits[0] as usize, None);
}
let mut scores: Vec<f32> = logits.to_vec();
apply_history_penalties(&mut scores, params, history);
if let Some(m) = mask.as_mut() {
m(&mut scores);
}
if params.temperature <= 0.0 {
if scores.len() == 1 {
return (scores[0] as usize, None);
}
if !want_probs {
return (greedy_choice(scores, params, history, xtc_roll), None);
}
let probs = filtered_distribution(scores, params, history, xtc_roll);
return (argmax(&probs), Some(probs));
}
let probs = filtered_distribution(scores, params, history, xtc_roll);
let chosen = self.sample_from(&probs);
(chosen, want_probs.then_some(probs))
}
pub fn uniform(&mut self) -> f32 {
self.next_f32()
}
pub fn sample_from(&mut self, probs: &[f32]) -> usize {
let draw = self.next_f32();
let mut cumulative = 0.0f32;
for (i, &p) in probs.iter().enumerate() {
cumulative += p;
if draw < cumulative {
return i;
}
}
probs
.iter()
.enumerate()
.rev()
.find(|&(_, &p)| p > 0.0)
.map(|(i, _)| i)
.unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sampling::{sampling_distribution, spread_logits};
#[test]
fn a_chain_without_xtc_does_not_consume_a_draw_for_it() {
let params = SamplingParams {
temperature: 1.0,
..SamplingParams::default()
};
let logits = spread_logits(32);
assert!(
Sampler::new(99).xtc_roll(¶ms).is_none(),
"the guard must refuse the draw, not merely ignore it"
);
let mut sampled_by_chain = Sampler::new(99);
let mut by_hand = Sampler::new(99);
for step in 0..16 {
let sampled = sampled_by_chain.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[]));
let probs = sampling_distribution(&logits, ¶ms, PenaltyWindow::new(&[], &[]), None);
assert_eq!(
sampled,
by_hand.sample_from(&probs),
"token {step} came off a different position in the stream"
);
}
assert_eq!(sampled_by_chain.uniform(), by_hand.uniform());
}
#[test]
fn the_reported_distribution_is_the_one_that_was_sampled() {
let logits: Vec<f32> = (0..64).map(|i| ((i * 7) % 13) as f32 * 0.4).collect();
let params = SamplingParams {
temperature: 0.9,
top_p: 0.95,
..SamplingParams::default()
};
let quiet = Sampler::new(7).sample(&logits, ¶ms, PenaltyWindow::new(&[], &[]));
let (loud, probs) =
Sampler::new(7).sample_reporting(&logits, ¶ms, PenaltyWindow::new(&[], &[]), None);
assert_eq!(
quiet, loud,
"asking for the probabilities changed which token was drawn"
);
let probs = probs.expect("a real vocabulary reports a distribution");
assert_eq!(probs.len(), logits.len(), "one entry per vocabulary slot");
let total: f32 = probs.iter().sum();
assert!(
(total - 1.0).abs() < 1e-4,
"must be normalised, got {total}"
);
assert!(
probs[loud] > 0.0,
"the chosen token has zero probability in the distribution it came from"
);
assert!(
probs.contains(&0.0),
"top_p 0.95 kept every candidate, so this proves nothing"
);
}
#[test]
fn greedy_reports_the_distribution_its_answer_is_the_argmax_of() {
let logits = vec![0.1f32, 3.0, 0.2, 2.9];
let params = SamplingParams::default();
assert!(params.temperature <= 0.0, "default is greedy");
let (chosen, probs) =
Sampler::new(1).sample_reporting(&logits, ¶ms, PenaltyWindow::new(&[], &[]), None);
let probs = probs.expect("a real vocabulary reports a distribution");
assert_eq!(chosen, 1, "the largest logit wins");
let best = probs
.iter()
.enumerate()
.max_by(|a, b| a.1.total_cmp(b.1))
.map(|(i, _)| i)
.unwrap();
assert_eq!(chosen, best, "the answer is not the argmax of the report");
assert_eq!(
Sampler::new(1).sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
chosen
);
}
#[test]
fn a_device_folded_argmax_reports_no_distribution() {
let params = SamplingParams::default();
let (chosen, probs) =
Sampler::new(1).sample_reporting(&[42.0], ¶ms, PenaltyWindow::new(&[], &[]), None);
assert_eq!(chosen, 42, "the singleton is the chosen id");
assert!(
probs.is_none(),
"a folded argmax must not fabricate a distribution"
);
}
#[test]
fn temperature_zero_accepts_precomputed_argmax_singleton() {
let mut sampler = Sampler::new(1);
let params = SamplingParams::default();
assert_eq!(
sampler.sample(&[42.0], ¶ms, PenaltyWindow::new(&[], &[])),
42
);
let sampled = SamplingParams {
temperature: 0.8,
..SamplingParams::default()
};
assert_eq!(
sampler.sample(&[42.0], &sampled, PenaltyWindow::new(&[], &[])),
0
);
}
#[test]
fn temperature_zero_is_deterministic_greedy_argmax() {
let logits = vec![0.1, 0.9, 0.3, -0.2];
let params = SamplingParams::default();
let mut sampler = Sampler::new(42);
assert_eq!(
sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
1
);
assert_eq!(
sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
1
);
}
#[test]
fn high_temperature_can_pick_a_non_argmax_token_over_many_draws() {
let logits = vec![1.0, 1.0, 1.0, 1.0];
let params = SamplingParams {
temperature: 1.0,
..SamplingParams::default()
};
let mut sampler = Sampler::new(7);
let mut seen = std::collections::HashSet::new();
for _ in 0..200 {
seen.insert(sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])));
}
assert!(
seen.len() > 1,
"uniform logits at temperature=1.0 must produce more than one distinct token across 200 draws"
);
}
#[test]
fn top_k_one_is_equivalent_to_greedy() {
let logits = vec![0.1, 0.9, 0.3, -0.2];
let params = SamplingParams {
temperature: 1.0,
top_k: 1,
..SamplingParams::default()
};
let mut sampler = Sampler::new(123);
for _ in 0..20 {
assert_eq!(
sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
1
);
}
}
#[test]
fn top_p_near_zero_is_equivalent_to_greedy() {
let logits = vec![0.1, 5.0, 0.3, -0.2];
let params = SamplingParams {
temperature: 1.0,
top_p: 0.001,
..SamplingParams::default()
};
let mut sampler = Sampler::new(9);
for _ in 0..20 {
assert_eq!(
sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
1
);
}
}
#[test]
fn presence_and_frequency_penalties_reduce_seen_token_logits() {
let logits = vec![0.0, 5.0, 0.0];
let params = SamplingParams {
temperature: 1.0,
presence_penalty: 10.0,
frequency_penalty: 0.0,
..SamplingParams::default()
};
let mut sampler = Sampler::new(1);
let mut counts = [0usize; 3];
for _ in 0..500 {
counts[sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[1]))] += 1;
}
assert!(
counts[1] < 250,
"presence_penalty should discourage token 1; counts={counts:?}"
);
let params = SamplingParams {
temperature: 1.0,
presence_penalty: 0.0,
frequency_penalty: 10.0,
..SamplingParams::default()
};
let mut sampler = Sampler::new(2);
counts = [0; 3];
for _ in 0..500 {
counts[sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[1, 1, 1]))] += 1;
}
assert!(
counts[1] < 250,
"frequency_penalty should discourage repeated token 1; counts={counts:?}"
);
}
#[test]
fn repetition_penalty_reduces_probability_of_recently_seen_token() {
let logits = vec![0.0, 5.0, 0.0];
let params = SamplingParams {
temperature: 1.0,
repetition_penalty: 1000.0,
..SamplingParams::default()
};
let mut sampler = Sampler::new(3);
let mut counts = [0usize; 3];
for _ in 0..500 {
counts[sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[1]))] += 1;
}
assert!(
counts[1] < 250,
"heavily penalizing token 1 (already in history) should make it far less likely than its raw logit alone would suggest; got counts={counts:?}"
);
}
#[test]
fn low_seeds_do_not_bias_the_first_draw() {
let vocab = 8;
let logits = vec![0.0f32; vocab];
let params = SamplingParams {
temperature: 1.0,
..SamplingParams::default()
};
let seeds = 4_000u64;
let mut counts = vec![0usize; vocab];
for seed in 1..=seeds {
counts[Sampler::new(seed).sample(&logits, ¶ms, PenaltyWindow::new(&[], &[]))] += 1;
}
let expected = seeds as f64 / vocab as f64;
for (token, &c) in counts.iter().enumerate() {
assert!(
(c as f64 - expected).abs() < expected * 0.25,
"uniform logits: token {token} came up {c} times across {seeds} seeds, \
expected about {expected:.0} (counts={counts:?})"
);
}
}
#[test]
fn the_published_distribution_is_the_one_sample_actually_draws_from() {
let logits = vec![0.4, 2.0, -1.0, 1.2, 0.9, -0.3];
let params = SamplingParams {
temperature: 0.8,
top_p: 0.9,
top_k: 4,
repetition_penalty: 1.3,
..SamplingParams::default()
};
let history = [1usize, 4];
let claimed =
sampling_distribution(&logits, ¶ms, PenaltyWindow::new(&[], &history), None);
assert!((claimed.iter().sum::<f32>() - 1.0).abs() < 1e-5);
let draws = 100_000;
let mut counts = vec![0usize; logits.len()];
let mut sampler = Sampler::new(0xC0FFEE);
for _ in 0..draws {
counts[sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &history))] += 1;
}
for (i, &c) in counts.iter().enumerate() {
let empirical = c as f64 / draws as f64;
assert!(
(empirical - claimed[i] as f64).abs() < 0.01,
"token {i}: sample() draws it {empirical:.4} of the time but \
sampling_distribution claims {:.4}",
claimed[i]
);
}
}
#[test]
fn degenerate_all_zero_probability_falls_back_to_greedy() {
let logits = vec![0.1, 0.9, 0.3, -0.2];
let params = SamplingParams {
temperature: 1.0,
top_k: 1,
top_p: 1.0,
..SamplingParams::default()
};
let mut sampler = Sampler::new(1);
assert_eq!(
sampler.sample(&logits, ¶ms, PenaltyWindow::new(&[], &[])),
1
);
}
}