#[derive(Debug, Clone)]
pub struct SamplingConfig {
pub temperature: f32,
pub top_k: usize,
pub top_p: f32,
pub repetition_penalty: f32,
}
impl Default for SamplingConfig {
fn default() -> Self {
Self {
temperature: 0.7,
top_k: 50,
top_p: 0.9,
repetition_penalty: 1.1,
}
}
}
impl SamplingConfig {
pub fn greedy() -> Self {
Self {
temperature: 0.0,
top_k: 1,
top_p: 1.0,
repetition_penalty: 1.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Candidate {
pub token_id: u32,
pub logit: f32,
}
pub struct CandidateSet {
candidates: Vec<Candidate>,
}
impl CandidateSet {
pub fn from_full_logits(logits: &[f32]) -> Self {
let candidates = logits
.iter()
.enumerate()
.map(|(i, &l)| Candidate {
token_id: i as u32,
logit: l,
})
.collect();
Self { candidates }
}
pub fn from_candidates(candidates: Vec<Candidate>) -> Self {
Self { candidates }
}
pub fn apply_repetition_penalty(&mut self, previous_ids: &[u32], penalty: f32) {
if penalty == 1.0 {
return;
}
for c in &mut self.candidates {
if previous_ids.contains(&c.token_id) {
if c.logit > 0.0 {
c.logit /= penalty;
} else {
c.logit *= penalty;
}
}
}
}
pub fn argmax(&self) -> u32 {
let mut best_id = 0u32;
let mut best_val = f32::NEG_INFINITY;
for c in &self.candidates {
if c.logit > best_val {
best_val = c.logit;
best_id = c.token_id;
}
}
best_id
}
pub fn apply_temperature(&mut self, temperature: f32) {
if temperature <= 0.0 || temperature == 1.0 {
return;
}
let inv = 1.0 / temperature;
for c in &mut self.candidates {
c.logit *= inv;
}
}
pub fn retain_top_k(&mut self, k: usize) {
if k == 0 || k >= self.candidates.len() {
return;
}
self.candidates
.select_nth_unstable_by(k - 1, candidate_order);
self.candidates.truncate(k);
}
pub fn sample_top_p(&mut self, top_p: f32, r: f32) -> u32 {
let mut probs = Vec::new();
self.sample_top_p_with_scratch(top_p, r, &mut probs)
}
fn sample_top_p_with_scratch(&mut self, top_p: f32, r: f32, probs: &mut Vec<f32>) -> u32 {
if self.candidates.is_empty() {
return 0;
}
self.candidates.sort_by(candidate_order);
let max_logit = self.candidates[0].logit;
probs.clear();
probs.extend(self.candidates.iter().map(|c| (c.logit - max_logit).exp()));
let sum: f32 = probs.iter().sum();
for p in probs.iter_mut() {
*p /= sum;
}
if top_p < 1.0 {
let mut cumsum = 0.0f32;
let mut cutoff = probs.len();
for (i, &p) in probs.iter().enumerate() {
cumsum += p;
if cumsum >= top_p {
cutoff = i + 1;
break;
}
}
probs.truncate(cutoff);
self.candidates.truncate(cutoff);
let sum: f32 = probs.iter().sum();
for p in probs.iter_mut() {
*p /= sum;
}
}
let mut cumsum = 0.0f32;
for (c, &p) in self.candidates.iter().zip(probs.iter()) {
cumsum += p;
if r < cumsum {
return c.token_id;
}
}
self.candidates.last().map(|c| c.token_id).unwrap_or(0)
}
}
struct Rng {
state: u64,
}
impl Rng {
fn new(seed: u64) -> Self {
Self {
state: if seed == 0 { 0x853c49e6748fea9b } else { seed },
}
}
fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x
}
fn next_f32(&mut self) -> f32 {
(self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
}
}
pub struct Sampler {
config: SamplingConfig,
rng: Rng,
recent_tokens: Vec<u32>,
max_recent: usize,
candidate_scratch: Vec<Candidate>,
prob_scratch: Vec<f32>,
logit_scratch: Vec<f32>,
}
impl Sampler {
pub fn new(config: SamplingConfig) -> Self {
let seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0x853c_49e6_748f_ea9b);
Self {
config,
rng: Rng::new(seed),
recent_tokens: Vec::new(),
max_recent: 64,
candidate_scratch: Vec::new(),
prob_scratch: Vec::new(),
logit_scratch: Vec::new(),
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.rng = Rng::new(seed);
self
}
pub fn sample(&mut self, logits: &[f32]) -> u32 {
if self.config.temperature <= 0.0 || self.config.top_k == 1 {
let raw_best = argmax_f32(logits);
if self.config.repetition_penalty == 1.0 || !self.recent_tokens.contains(&raw_best) {
self.push_token(raw_best);
return raw_best;
}
self.logit_scratch.clear();
self.logit_scratch.extend_from_slice(logits);
let penalty = self.config.repetition_penalty;
for &tok in &self.recent_tokens {
let idx = tok as usize;
if idx < self.logit_scratch.len() {
if self.logit_scratch[idx] > 0.0 {
self.logit_scratch[idx] /= penalty;
} else {
self.logit_scratch[idx] *= penalty;
}
}
}
let token = argmax_f32(&self.logit_scratch);
self.push_token(token);
return token;
}
self.logit_scratch.clear();
self.logit_scratch.extend_from_slice(logits);
let adj = &mut self.logit_scratch;
if self.config.repetition_penalty != 1.0 {
for &tok in &self.recent_tokens {
let idx = tok as usize;
if idx < adj.len() {
if adj[idx] > 0.0 {
adj[idx] /= self.config.repetition_penalty;
} else {
adj[idx] *= self.config.repetition_penalty;
}
}
}
}
let inv_temp = if self.config.temperature != 1.0 {
1.0 / self.config.temperature
} else {
1.0
};
select_top_k(
adj,
self.config.top_k,
inv_temp,
&mut self.candidate_scratch,
);
let mut cs = CandidateSet {
candidates: std::mem::take(&mut self.candidate_scratch),
};
let r = self.rng.next_f32();
let token = cs.sample_top_p_with_scratch(self.config.top_p, r, &mut self.prob_scratch);
self.candidate_scratch = cs.candidates; self.push_token(token);
token
}
fn push_token(&mut self, token: u32) {
self.recent_tokens.push(token);
if self.recent_tokens.len() > self.max_recent {
self.recent_tokens.remove(0);
}
}
pub fn reset(&mut self) {
self.recent_tokens.clear();
}
}
fn argmax_f32(logits: &[f32]) -> u32 {
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
return unsafe { argmax_f32_neon(logits) };
}
}
argmax_f32_scalar(logits)
}
fn argmax_f32_scalar(logits: &[f32]) -> u32 {
let mut best_idx = 0u32;
let mut best_val = f32::NEG_INFINITY;
for (i, &v) in logits.iter().enumerate() {
if v > best_val {
best_val = v;
best_idx = i as u32;
}
}
best_idx
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn argmax_f32_neon(logits: &[f32]) -> u32 {
use std::arch::aarch64::*;
let len = logits.len();
let mut i = 0usize;
let mut best_v = vdupq_n_f32(f32::NEG_INFINITY);
let mut best_i = vdupq_n_u32(0);
let idx_init = [0u32, 1, 2, 3];
let mut idx_v = vld1q_u32(idx_init.as_ptr());
let idx_step = vdupq_n_u32(4);
while i + 4 <= len {
let v = vld1q_f32(logits.as_ptr().add(i));
let mask = vcgtq_f32(v, best_v); best_v = vbslq_f32(mask, v, best_v);
best_i = vbslq_u32(mask, idx_v, best_i);
idx_v = vaddq_u32(idx_v, idx_step);
i += 4;
}
let mut best_idx = 0u32;
let mut best_val = f32::NEG_INFINITY;
macro_rules! reduce_lane {
($lane:literal) => {{
let lane_val = vgetq_lane_f32::<$lane>(best_v);
let lane_idx = vgetq_lane_u32::<$lane>(best_i);
if lane_val > best_val || (lane_val == best_val && lane_idx < best_idx) {
best_val = lane_val;
best_idx = lane_idx;
}
}};
}
reduce_lane!(0);
reduce_lane!(1);
reduce_lane!(2);
reduce_lane!(3);
while i < len {
let v = *logits.get_unchecked(i);
if v > best_val {
best_val = v;
best_idx = i as u32;
}
i += 1;
}
best_idx
}
#[inline(always)]
fn heap_less(a: &Candidate, b: &Candidate) -> bool {
match (a.logit.is_nan(), b.logit.is_nan()) {
(true, _) => true,
(_, true) => false,
_ => a.logit < b.logit || (a.logit == b.logit && a.token_id > b.token_id),
}
}
fn heap_sift_down(heap: &mut [Candidate], mut pos: usize) {
let n = heap.len();
loop {
let left = 2 * pos + 1;
let right = 2 * pos + 2;
let mut smallest = pos;
if left < n && heap_less(&heap[left], &heap[smallest]) {
smallest = left;
}
if right < n && heap_less(&heap[right], &heap[smallest]) {
smallest = right;
}
if smallest == pos {
break;
}
heap.swap(pos, smallest);
pos = smallest;
}
}
fn heap_build(heap: &mut [Candidate]) {
if heap.len() <= 1 {
return;
}
let mut i = heap.len() / 2;
while i > 0 {
i -= 1;
heap_sift_down(heap, i);
}
}
fn select_top_k(logits: &[f32], k: usize, inv_temp: f32, out: &mut Vec<Candidate>) {
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
unsafe { select_top_k_neon(logits, k, inv_temp, out) };
return;
}
}
select_top_k_scalar(logits, k, inv_temp, out);
}
fn select_top_k_scalar(logits: &[f32], k: usize, inv_temp: f32, out: &mut Vec<Candidate>) {
out.clear();
if k == 0 || logits.is_empty() {
return;
}
let k = k.min(logits.len());
out.extend(logits.iter().take(k).enumerate().map(|(i, &raw)| {
let scaled = raw * inv_temp;
Candidate {
token_id: i as u32,
logit: if scaled.is_nan() {
f32::NEG_INFINITY
} else {
scaled
},
}
}));
heap_build(out);
for (i, &raw) in logits.iter().enumerate().skip(k) {
let logit = raw * inv_temp;
let cand = Candidate {
token_id: i as u32,
logit,
};
if heap_less(&out[0], &cand) {
out[0] = cand;
heap_sift_down(out, 0);
}
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn select_top_k_neon(logits: &[f32], k: usize, inv_temp: f32, out: &mut Vec<Candidate>) {
use std::arch::aarch64::*;
out.clear();
if k == 0 || logits.is_empty() {
return;
}
let k = k.min(logits.len());
out.extend(logits.iter().take(k).enumerate().map(|(i, &raw)| {
let scaled = raw * inv_temp;
Candidate {
token_id: i as u32,
logit: if scaled.is_nan() {
f32::NEG_INFINITY
} else {
scaled
},
}
}));
heap_build(out);
let n = logits.len();
let mut i = k;
let inv_v = vdupq_n_f32(inv_temp);
while i + 4 <= n {
let thresh = out[0].logit;
let thresh_v = vdupq_n_f32(thresh);
let raw_v = vld1q_f32(logits.as_ptr().add(i));
let scaled_v = vmulq_f32(raw_v, inv_v);
let mask = vcgtq_f32(scaled_v, thresh_v);
let any = vgetq_lane_u32::<0>(mask)
| vgetq_lane_u32::<1>(mask)
| vgetq_lane_u32::<2>(mask)
| vgetq_lane_u32::<3>(mask);
if any != 0 {
for j in 0..4usize {
let logit = *logits.get_unchecked(i + j) * inv_temp;
let cand = Candidate {
token_id: (i + j) as u32,
logit,
};
if heap_less(&out[0], &cand) {
out[0] = cand;
heap_sift_down(out, 0);
}
}
}
i += 4;
}
while i < n {
let logit = *logits.get_unchecked(i) * inv_temp;
let cand = Candidate {
token_id: i as u32,
logit,
};
if heap_less(&out[0], &cand) {
out[0] = cand;
heap_sift_down(out, 0);
}
i += 1;
}
}
#[inline(always)]
fn candidate_order(a: &Candidate, b: &Candidate) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a.logit.is_nan(), b.logit.is_nan()) {
(true, _) => Ordering::Greater,
(_, true) => Ordering::Less,
_ => b
.logit
.partial_cmp(&a.logit)
.unwrap_or(Ordering::Equal)
.then_with(|| a.token_id.cmp(&b.token_id)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_greedy_picks_argmax() {
let config = SamplingConfig::greedy();
let mut sampler = Sampler::new(config);
let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
assert_eq!(sampler.sample(&logits), 3);
}
#[test]
fn test_temperature_zero_is_greedy() {
let config = SamplingConfig {
temperature: 0.0,
..Default::default()
};
let mut sampler = Sampler::new(config);
let logits = vec![1.0, 5.0, 2.0];
assert_eq!(sampler.sample(&logits), 1);
}
#[test]
fn test_top_k_limits_candidates() {
let config = SamplingConfig {
temperature: 1.0,
top_k: 2,
top_p: 1.0,
repetition_penalty: 1.0,
};
let mut sampler = Sampler::new(config).with_seed(123);
let logits = vec![0.0, 10.0, 0.0, 9.0, 0.0];
let mut counts = [0u32; 5];
for _ in 0..100 {
let tok = sampler.sample(&logits);
counts[tok as usize] += 1;
}
assert_eq!(counts[0], 0);
assert_eq!(counts[2], 0);
assert_eq!(counts[4], 0);
assert!(counts[1] > 0);
assert!(counts[3] > 0);
}
#[test]
fn test_repetition_penalty_reduces_probability() {
let config = SamplingConfig {
temperature: 0.0, top_k: 0,
top_p: 1.0,
repetition_penalty: 100.0, };
let mut sampler = Sampler::new(config);
let logits = vec![0.0, 5.0, 4.9];
let first = sampler.sample(&logits);
assert_eq!(first, 1);
let second = sampler.sample(&logits);
assert_eq!(second, 2);
}
#[test]
fn test_top_p_nucleus_sampling() {
let config = SamplingConfig {
temperature: 1.0,
top_k: 0,
top_p: 0.5,
repetition_penalty: 1.0,
};
let mut sampler = Sampler::new(config).with_seed(456);
let logits = vec![10.0, 1.0, 1.0, 1.0, 1.0];
let mut counts = [0u32; 5];
for _ in 0..100 {
counts[sampler.sample(&logits) as usize] += 1;
}
assert!(counts[0] > 90);
}
#[test]
fn test_sampler_reset() {
let config = SamplingConfig::greedy();
let mut sampler = Sampler::new(config);
sampler.sample(&[1.0, 2.0, 3.0]);
assert!(!sampler.recent_tokens.is_empty());
sampler.reset();
assert!(sampler.recent_tokens.is_empty());
}
fn check_argmax_parity(logits: &[f32]) {
let scalar = argmax_f32_scalar(logits);
let dispatch = argmax_f32(logits);
assert_eq!(
scalar,
dispatch,
"argmax_f32 dispatch differs from scalar for len={}",
logits.len()
);
}
#[test]
fn test_argmax_full_vocab() {
let n = 248_320usize;
let logits: Vec<f32> = (0..n)
.map(|i| {
let h = (i as u64)
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
(h as f32 / u64::MAX as f32) * 20.0 - 10.0
})
.collect();
check_argmax_parity(&logits);
}
#[test]
fn test_argmax_lower_id_tie_wins() {
let mut logits = vec![0.0f32; 10];
logits[2] = 5.0;
logits[5] = 5.0;
assert_eq!(argmax_f32_scalar(&logits), 2, "scalar tie");
assert_eq!(argmax_f32(&logits), 2, "dispatch tie");
}
#[test]
fn test_argmax_nan_loses_to_real() {
let logits = vec![f32::NAN, 1.0, 2.0, 9.0, 0.5];
assert_eq!(argmax_f32_scalar(&logits), 3, "scalar nan");
assert_eq!(argmax_f32(&logits), 3, "dispatch nan");
}
#[test]
fn test_argmax_all_nan_returns_0() {
let logits = vec![f32::NAN, f32::NAN, f32::NAN];
assert_eq!(argmax_f32_scalar(&logits), 0, "scalar all-nan");
assert_eq!(argmax_f32(&logits), 0, "dispatch all-nan");
}
#[test]
fn test_argmax_all_neg_inf_returns_0() {
let logits = vec![f32::NEG_INFINITY; 8];
assert_eq!(argmax_f32_scalar(&logits), 0, "scalar neg-inf");
assert_eq!(argmax_f32(&logits), 0, "dispatch neg-inf");
}
#[test]
fn test_argmax_partial_chunk_lengths() {
for tail in 1usize..=7 {
let mut logits = vec![0.0f32; 8 + tail];
logits[8 + tail - 1] = 99.0; check_argmax_parity(&logits);
assert_eq!(
argmax_f32(&logits),
(8 + tail - 1) as u32,
"tail len={tail}"
);
}
}
#[test]
fn test_candidate_order_higher_logit_wins() {
use std::cmp::Ordering;
let a = Candidate {
token_id: 10,
logit: 5.0,
};
let b = Candidate {
token_id: 20,
logit: 3.0,
};
assert_eq!(
candidate_order(&a, &b),
Ordering::Less,
"higher logit must sort first"
);
}
#[test]
fn test_candidate_order_tie_lower_token_id_wins() {
use std::cmp::Ordering;
let a = Candidate {
token_id: 5,
logit: 2.0,
};
let b = Candidate {
token_id: 9,
logit: 2.0,
};
assert_eq!(
candidate_order(&a, &b),
Ordering::Less,
"equal logit: lower token_id is first"
);
assert_eq!(candidate_order(&b, &a), Ordering::Greater);
}
#[test]
fn test_candidate_order_nan_loses() {
use std::cmp::Ordering;
let nan = Candidate {
token_id: 0,
logit: f32::NAN,
};
let real = Candidate {
token_id: 99,
logit: -1000.0,
};
assert_eq!(
candidate_order(&nan, &real),
Ordering::Greater,
"NaN must sort last"
);
assert_eq!(candidate_order(&real, &nan), Ordering::Less);
}
#[test]
fn test_retain_top_k_tie_breaking() {
let mut cs = CandidateSet {
candidates: vec![
Candidate {
token_id: 7,
logit: 1.0,
},
Candidate {
token_id: 2,
logit: 1.0,
},
Candidate {
token_id: 5,
logit: 1.0,
},
],
};
cs.retain_top_k(2);
cs.candidates.sort_by(candidate_order);
assert_eq!(cs.candidates[0].token_id, 2);
assert_eq!(cs.candidates[1].token_id, 5);
}
fn check_top_k_parity(logits: &[f32], k: usize) {
let mut scalar_out = Vec::new();
select_top_k_scalar(logits, k, 1.0, &mut scalar_out);
scalar_out.sort_by(candidate_order);
let mut dispatch_out = Vec::new();
select_top_k(logits, k, 1.0, &mut dispatch_out);
dispatch_out.sort_by(candidate_order);
assert_eq!(
scalar_out.len(),
dispatch_out.len(),
"k={k}: length mismatch"
);
for (i, (s, d)) in scalar_out.iter().zip(dispatch_out.iter()).enumerate() {
assert_eq!(
s.token_id, d.token_id,
"k={k}: position {i} token_id mismatch (scalar={} dispatch={})",
s.token_id, d.token_id
);
}
}
#[test]
fn test_select_top_k_basic() {
let logits = vec![1.0f32, 5.0, 3.0, 9.0, 2.0, 7.0];
let mut out = Vec::new();
select_top_k_scalar(&logits, 3, 1.0, &mut out);
out.sort_by(candidate_order);
let ids: Vec<u32> = out.iter().map(|c| c.token_id).collect();
assert_eq!(ids, vec![3, 5, 1], "top-3 from [1,5,3,9,2,7]");
}
#[test]
fn test_select_top_k_tie_breaking() {
let logits = vec![5.0f32, 5.0, 1.0];
let mut out = Vec::new();
select_top_k_scalar(&logits, 1, 1.0, &mut out);
assert_eq!(out[0].token_id, 0, "tie: lower id must win");
}
#[test]
fn test_select_top_k_nan_excluded() {
let logits = vec![f32::NAN, 2.0, 9.0, 3.0];
let mut out = Vec::new();
select_top_k_scalar(&logits, 2, 1.0, &mut out);
out.sort_by(candidate_order);
let ids: Vec<u32> = out.iter().map(|c| c.token_id).collect();
assert_eq!(ids, vec![2, 3], "NaN must not appear in top-2");
}
#[test]
fn test_select_top_k_full_vocab_parity() {
let n = 248_320usize;
let logits: Vec<f32> = (0..n)
.map(|i| {
let h = (i as u64)
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
(h as f32 / u64::MAX as f32) * 20.0 - 10.0
})
.collect();
check_top_k_parity(&logits, 50);
}
#[test]
fn test_select_top_k_dispatch_tie_breaking() {
let logits = vec![5.0f32, 5.0, 5.0, 1.0, 1.0];
let mut out = Vec::new();
select_top_k(&logits, 2, 1.0, &mut out);
out.sort_by(candidate_order);
let ids: Vec<u32> = out.iter().map(|c| c.token_id).collect();
assert_eq!(
ids,
vec![0, 1],
"dispatch: tie must break by lower token_id"
);
}
#[test]
fn test_select_top_k_dispatch_nan_excluded() {
let logits = vec![f32::NAN, 4.0, 9.0, f32::NAN, 7.0];
let mut out = Vec::new();
select_top_k(&logits, 2, 1.0, &mut out);
out.sort_by(candidate_order);
let ids: Vec<u32> = out.iter().map(|c| c.token_id).collect();
assert_eq!(ids, vec![2, 4], "dispatch: NaN must not appear in top-2");
}
#[test]
fn test_select_top_k_dispatch_nan_in_seed() {
let mut logits = vec![
f32::NAN,
2.0,
3.0,
f32::NAN,
5.0,
6.0,
7.0,
f32::NAN,
9.0,
10.0,
];
logits.extend((1..=990u32).map(|x| x as f32));
let mut scalar_out = Vec::new();
select_top_k_scalar(&logits, 10, 1.0, &mut scalar_out);
scalar_out.sort_by(candidate_order);
let mut dispatch_out = Vec::new();
select_top_k(&logits, 10, 1.0, &mut dispatch_out);
dispatch_out.sort_by(candidate_order);
assert_eq!(scalar_out.len(), dispatch_out.len());
for (i, (s, d)) in scalar_out.iter().zip(dispatch_out.iter()).enumerate() {
assert_eq!(
s.token_id, d.token_id,
"nan_in_seed: position {i} token_id mismatch (scalar={} dispatch={})",
s.token_id, d.token_id
);
}
}
#[test]
fn test_select_top_k_applies_inv_temp_to_candidate_logits() {
let logits = vec![1.0f32, 2.0, 3.0, 4.0];
let mut out = Vec::new();
select_top_k(&logits, 2, 0.5, &mut out);
out.sort_by(candidate_order);
assert_eq!(out[0].token_id, 3);
assert_eq!(out[1].token_id, 2);
assert!(
(out[0].logit - 2.0).abs() < 1e-6,
"logit[3] must be 4.0*0.5=2.0"
);
assert!(
(out[1].logit - 1.5).abs() < 1e-6,
"logit[2] must be 3.0*0.5=1.5"
);
}
}