#[derive(Clone, Debug)]
pub struct SamplerConfig {
pub temperature: f32, pub top_k: usize, pub top_p: f32, pub min_p: f32, pub penalty_last_n: usize, pub penalty_repeat: f32, pub penalty_freq: f32, pub penalty_present: f32, pub seed: u64,
}
impl Default for SamplerConfig {
fn default() -> Self {
SamplerConfig {
temperature: 0.0,
top_k: 0,
top_p: 1.0,
min_p: 0.0,
penalty_last_n: 0,
penalty_repeat: 1.0,
penalty_freq: 0.0,
penalty_present: 0.0,
seed: 0,
}
}
}
pub struct Sampler {
cfg: SamplerConfig,
rng: SplitMix64,
history: Vec<u32>, }
impl Sampler {
pub fn new(cfg: SamplerConfig) -> Self {
let rng = SplitMix64::new(cfg.seed);
Sampler {
cfg,
rng,
history: Vec::new(),
}
}
pub fn is_greedy(&self) -> bool {
self.cfg.temperature <= 0.0
}
pub fn is_spec_sampling(&self) -> bool {
self.cfg.temperature > 0.0
&& self.cfg.penalty_repeat == 1.0
&& self.cfg.penalty_freq == 0.0
&& self.cfg.penalty_present == 0.0
&& self.cfg.top_k == 0
&& self.cfg.top_p >= 1.0
&& self.cfg.min_p <= 0.0
}
pub fn top_k(&self) -> usize {
self.cfg.top_k
}
pub fn penalty_last_n(&self) -> usize {
self.cfg.penalty_last_n
}
pub fn penalty_repeat(&self) -> f32 {
self.cfg.penalty_repeat
}
pub fn penalty_freq(&self) -> f32 {
self.cfg.penalty_freq
}
pub fn penalty_present(&self) -> f32 {
self.cfg.penalty_present
}
pub fn top_p(&self) -> f32 {
self.cfg.top_p
}
pub fn min_p(&self) -> f32 {
self.cfg.min_p
}
pub fn temperature(&self) -> f32 {
self.cfg.temperature
}
pub fn seed(&self) -> u64 {
self.cfg.seed
}
pub fn accept(&mut self, token: u32) {
self.history.push(token);
}
pub fn sample(&mut self, logits: &[f32]) -> u32 {
if self.is_greedy()
&& self.cfg.penalty_repeat == 1.0
&& self.cfg.penalty_freq == 0.0
&& self.cfg.penalty_present == 0.0
{
return argmax_u32(logits);
}
let mut cand: Vec<(u32, f32)> = logits
.iter()
.enumerate()
.map(|(i, &l)| (i as u32, l))
.collect();
self.apply_penalties(&mut cand);
if self.is_greedy() {
let mut best = cand[0];
for &c in &cand[1..] {
if c.1 > best.1 {
best = c;
}
}
return best.0;
}
if self.cfg.temperature > 0.0 && self.cfg.temperature != 1.0 {
let inv = 1.0 / self.cfg.temperature;
for c in cand.iter_mut() {
c.1 *= inv;
}
}
if self.cfg.top_k > 0 && self.cfg.top_k < cand.len() {
cand.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
cand.truncate(self.cfg.top_k);
}
softmax_inplace(&mut cand);
if self.cfg.top_p < 1.0 {
cand.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
let mut cum = 0.0f32;
let mut keep = 0usize;
for (i, c) in cand.iter().enumerate() {
cum += c.1;
keep = i + 1;
if cum >= self.cfg.top_p {
break;
}
}
cand.truncate(keep.max(1));
}
if self.cfg.min_p > 0.0 {
let maxp = cand.iter().map(|c| c.1).fold(0.0f32, f32::max);
let thresh = self.cfg.min_p * maxp;
cand.retain(|c| c.1 >= thresh);
if cand.is_empty() {
return argmax_u32(logits);
} }
let sum: f32 = cand.iter().map(|c| c.1).sum();
let r = self.rng.next_f32() * sum;
let mut acc = 0.0f32;
for c in &cand {
acc += c.1;
if acc >= r {
return c.0;
}
}
cand.last().unwrap().0
}
fn apply_penalties(&self, cand: &mut [(u32, f32)]) {
let n = self.cfg.penalty_last_n;
if n == 0 {
return;
}
if self.cfg.penalty_repeat == 1.0
&& self.cfg.penalty_freq == 0.0
&& self.cfg.penalty_present == 0.0
{
return;
}
let start = self.history.len().saturating_sub(n);
let window = &self.history[start..];
if window.is_empty() {
return;
}
use std::collections::HashMap;
let mut counts: HashMap<u32, i32> = HashMap::new();
for &t in window {
*counts.entry(t).or_insert(0) += 1;
}
for c in cand.iter_mut() {
if let Some(&cnt) = counts.get(&c.0) {
if self.cfg.penalty_repeat != 1.0 {
if c.1 > 0.0 {
c.1 /= self.cfg.penalty_repeat;
} else {
c.1 *= self.cfg.penalty_repeat;
}
}
c.1 -= cnt as f32 * self.cfg.penalty_freq;
c.1 -= self.cfg.penalty_present; }
}
}
}
fn argmax_u32(logits: &[f32]) -> u32 {
let mut best = 0u32;
let mut bv = f32::NEG_INFINITY;
for (i, &v) in logits.iter().enumerate() {
if v > bv {
bv = v;
best = i as u32;
}
}
best
}
fn softmax_inplace(cand: &mut [(u32, f32)]) {
let maxl = cand.iter().map(|c| c.1).fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0.0f32;
for c in cand.iter_mut() {
let e = (c.1 - maxl).exp();
c.1 = e;
sum += e;
}
let inv = if sum > 0.0 { 1.0 / sum } else { 0.0 };
for c in cand.iter_mut() {
c.1 *= inv;
}
}
struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
fn new(seed: u64) -> Self {
SplitMix64 {
state: seed.wrapping_add(0x9E3779B97F4A7C15),
}
}
fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
z ^ (z >> 31)
}
fn next_f32(&mut self) -> f32 {
((self.next_u64() >> 40) as f32) / (1u32 << 24) as f32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greedy_is_argmax() {
let mut s = Sampler::new(SamplerConfig::default()); let logits = vec![0.1, 5.0, 2.0, -1.0];
assert_eq!(s.sample(&logits), 1);
}
#[test]
fn temp_sampling_deterministic_with_seed() {
let cfg = SamplerConfig {
temperature: 1.0,
seed: 42,
..Default::default()
};
let logits = vec![1.0, 2.0, 3.0, 0.5];
let a = Sampler::new(cfg.clone()).sample(&logits);
let b = Sampler::new(cfg).sample(&logits);
assert_eq!(a, b, "same seed must reproduce the draw");
assert!(a < 4);
}
#[test]
fn top_k_one_is_argmax() {
let cfg = SamplerConfig {
temperature: 1.0,
top_k: 1,
seed: 7,
..Default::default()
};
let logits = vec![0.1, 5.0, 2.0, -1.0];
assert_eq!(
Sampler::new(cfg).sample(&logits),
1,
"top_k=1 collapses to argmax"
);
}
#[test]
fn min_p_keeps_only_high_prob() {
let cfg = SamplerConfig {
temperature: 1.0,
min_p: 0.5,
seed: 3,
..Default::default()
};
let logits = vec![0.0, 0.0, 10.0, 0.0];
for _ in 0..16 {
assert_eq!(Sampler::new(cfg.clone()).sample(&logits), 2);
}
}
#[test]
fn repeat_penalty_suppresses_recent() {
let mut cfg = SamplerConfig::default();
cfg.penalty_last_n = 8;
cfg.penalty_repeat = 100.0;
let mut s = Sampler::new(cfg);
s.accept(1); let logits = vec![4.0, 5.0, 4.5, 1.0]; let got = s.sample(&logits);
assert_ne!(
got, 1,
"recent token must be penalized out of greedy argmax"
);
assert_eq!(got, 2, "next-highest after penalizing 1");
}
}