1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone)]
11pub struct SplitMix64 {
12 state: u64,
13}
14
15#[derive(Debug, Default)]
19pub struct SamplerScratch {
20 seen_epoch: Vec<u32>,
21 epoch: u32,
22 probs: Vec<f32>,
26 topk: Vec<f32>,
31}
32
33impl SamplerScratch {
34 fn begin_seen(&mut self, vocab_size: usize) -> u32 {
35 if self.seen_epoch.len() < vocab_size {
36 self.seen_epoch.resize(vocab_size, 0);
37 }
38 self.epoch = self.epoch.wrapping_add(1);
39 if self.epoch == 0 {
40 self.seen_epoch.fill(0);
41 self.epoch = 1;
42 }
43 self.epoch
44 }
45}
46
47impl SplitMix64 {
48 pub fn new(seed: u64) -> Self {
49 Self { state: seed }
50 }
51
52 pub fn from_entropy() -> Self {
54 let t = std::time::SystemTime::now()
55 .duration_since(std::time::UNIX_EPOCH)
56 .unwrap_or_default();
57 let addr = Box::into_raw(Box::new(0u8)) as u64;
58 unsafe { drop(Box::from_raw(addr as *mut u8)) };
60 Self::new(t.as_nanos() as u64 ^ addr.rotate_left(17) ^ 0x9E3779B97F4A7C15)
61 }
62
63 #[inline]
64 pub fn next_u64(&mut self) -> u64 {
65 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
66 let mut z = self.state;
67 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
68 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
69 z ^ (z >> 31)
70 }
71
72 #[inline]
74 pub fn next_f32(&mut self) -> f32 {
75 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct SamplerConfig {
82 pub temperature: f32,
83 pub top_p: f32,
84 pub top_k: u32,
85 pub repetition_penalty: f32,
86 pub min_p: f32,
87 #[serde(default)]
89 pub seed: Option<u64>,
90 #[serde(default)]
92 pub suppress_tokens: Vec<u32>,
93}
94
95impl Default for SamplerConfig {
96 fn default() -> Self {
97 Self {
98 temperature: 0.7,
99 top_p: 0.9,
100 top_k: 40,
101 repetition_penalty: 1.1,
102 min_p: 0.05,
103 seed: None,
104 suppress_tokens: Vec::new(),
105 }
106 }
107}
108
109pub fn sample(
112 logits: &[f32],
113 config: &SamplerConfig,
114 past_tokens: &[u32],
115 rng: &mut SplitMix64,
116) -> u32 {
117 let mut scratch = SamplerScratch::default();
118 sample_with_scratch(logits, config, past_tokens, rng, &mut scratch)
119}
120
121pub fn sample_with_scratch(
123 logits: &[f32],
124 config: &SamplerConfig,
125 past_tokens: &[u32],
126 rng: &mut SplitMix64,
127 scratch: &mut SamplerScratch,
128) -> u32 {
129 if config.temperature < 1e-6
130 && config.repetition_penalty == 1.0
131 && config.suppress_tokens.is_empty()
132 {
133 return argmax(logits);
134 }
135 let mut probs = std::mem::take(&mut scratch.probs);
141 probs.clear();
142 probs.extend_from_slice(logits);
143
144 for &tok in &config.suppress_tokens {
145 if (tok as usize) < probs.len() {
146 probs[tok as usize] = f32::NEG_INFINITY;
147 }
148 }
149
150 if config.repetition_penalty != 1.0 {
151 apply_repetition_penalty(&mut probs, past_tokens, config.repetition_penalty, scratch);
152 }
153
154 let mut done = |probs: Vec<f32>, tok: u32| -> u32 {
155 scratch.probs = probs;
156 tok
157 };
158
159 if config.temperature < 1e-6 {
160 let t = argmax(&probs); return done(probs, t);
162 }
163 if config.temperature != 1.0 {
164 for p in probs.iter_mut() {
165 *p /= config.temperature;
166 }
167 }
168
169 softmax_inplace(&mut probs);
170
171 if config.min_p > 0.0 {
172 let max_prob = probs.iter().cloned().fold(0.0f32, f32::max);
173 let threshold = max_prob * config.min_p;
174 for p in probs.iter_mut() {
175 if *p < threshold {
176 *p = 0.0;
177 }
178 }
179 }
180
181 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
182 apply_top_k(&mut probs, config.top_k as usize, &mut scratch.topk);
183 }
184
185 if config.top_p < 1.0 && config.top_p > 0.0 {
186 apply_top_p(&mut probs, config.top_p);
187 }
188
189 let sum: f32 = probs.iter().sum();
190 if sum > 0.0 {
191 for p in probs.iter_mut() {
192 *p /= sum;
193 }
194 } else {
195 let t = argmax(logits);
197 return done(probs, t);
198 }
199
200 let t = categorical_sample(&probs, rng.next_f32());
201 done(probs, t)
202}
203
204pub fn argmax(values: &[f32]) -> u32 {
216 if values.is_empty() {
217 return 0;
218 }
219 let n = values.len();
220 let mut best = [(0usize, f32::NEG_INFINITY); 4];
221 for (l, b) in best.iter_mut().enumerate() {
222 b.0 = l.min(n - 1);
223 }
224 let mut i = 0;
225 while i + 4 <= n {
226 for l in 0..4 {
227 let v = values[i + l];
228 if v >= best[l].1 {
229 best[l] = (i + l, v);
230 }
231 }
232 i += 4;
233 }
234 let mut bi = best[0].0;
235 let mut bv = best[0].1;
236 for b in &best[1..] {
237 if b.1 > bv || (b.1 == bv && b.0 > bi) {
238 bi = b.0;
239 bv = b.1;
240 }
241 }
242 while i < n {
243 if values[i] >= bv {
244 bv = values[i];
245 bi = i;
246 }
247 i += 1;
248 }
249 bi as u32
250}
251
252fn softmax_inplace(logits: &mut [f32]) {
253 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
254 let mut sum = 0.0f32;
255 for v in logits.iter_mut() {
256 *v = (*v - max_val).exp();
257 sum += *v;
258 }
259 if sum > 0.0 {
260 for v in logits.iter_mut() {
261 *v /= sum;
262 }
263 }
264}
265
266fn apply_repetition_penalty(
267 logits: &mut [f32],
268 past_tokens: &[u32],
269 penalty: f32,
270 scratch: &mut SamplerScratch,
271) {
272 let epoch = scratch.begin_seen(logits.len());
273 for &tok in past_tokens {
274 let idx = tok as usize;
275 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
276 scratch.seen_epoch[idx] = epoch;
277 if logits[idx] > 0.0 {
278 logits[idx] /= penalty;
279 } else {
280 logits[idx] *= penalty;
281 }
282 }
283 }
284}
285
286fn apply_top_k(probs: &mut [f32], k: usize, sel: &mut Vec<f32>) {
291 if k == 0 || k >= probs.len() {
292 return;
293 }
294 sel.clear();
297 sel.extend_from_slice(probs);
298 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
300 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
301 });
302 let threshold = *kth;
303 for p in probs.iter_mut() {
304 if *p < threshold {
305 *p = 0.0;
306 }
307 }
308}
309
310fn apply_top_p(probs: &mut [f32], top_p: f32) {
315 let mut indexed: Vec<(usize, f32)> = probs
316 .iter()
317 .copied()
318 .enumerate()
319 .filter(|&(_, p)| p > 0.0)
320 .collect();
321 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
322
323 let mut cumsum = 0.0f32;
324 let mut cutoff_idx = indexed.len();
325 for (i, &(_, prob)) in indexed.iter().enumerate() {
326 cumsum += prob;
327 if cumsum >= top_p {
328 cutoff_idx = i + 1;
329 break;
330 }
331 }
332
333 for &(i, _) in &indexed[cutoff_idx..] {
335 probs[i] = 0.0;
336 }
337}
338
339fn categorical_sample(probs: &[f32], r: f32) -> u32 {
341 let mut cumsum = 0.0f32;
342 for (i, &p) in probs.iter().enumerate() {
343 cumsum += p;
344 if r < cumsum {
345 return i as u32;
346 }
347 }
348 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
349}
350
351#[cfg(test)]
352mod tests {
353 #[test]
358 fn argmax_lanes_match_the_scalar_one_ties_and_all() {
359 let scalar = |v: &[f32]| -> u32 {
362 v.iter()
363 .enumerate()
364 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
365 .map(|(i, _)| i as u32)
366 .unwrap_or(0)
367 };
368 for n in 0..40usize {
369 for seed in 0..8u64 {
370 let mut r = super::SplitMix64::new(seed * 7 + n as u64);
371 let v: Vec<f32> = (0..n)
374 .map(|_| ((r.next_u64() % 5) as f32) - 2.0)
375 .collect();
376 assert_eq!(super::argmax(&v), scalar(&v), "n={n} seed={seed} {v:?}");
377 }
378 }
379 let flat = vec![f32::NEG_INFINITY; 13];
380 assert_eq!(super::argmax(&flat), scalar(&flat));
381 }
382
383 use super::*;
384
385 #[test]
386 fn test_argmax() {
387 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
388 assert_eq!(argmax(&logits), 3);
389 }
390
391 #[test]
392 fn test_greedy_sampling() {
393 let logits = vec![1.0, 5.0, 2.0, 3.0];
394 let config = SamplerConfig {
395 temperature: 0.0,
396 ..Default::default()
397 };
398 let mut rng = SplitMix64::new(1);
399 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
400 }
401
402 #[test]
403 fn test_softmax() {
404 let mut logits = vec![1.0, 2.0, 3.0];
405 softmax_inplace(&mut logits);
406 let sum: f32 = logits.iter().sum();
407 assert!((sum - 1.0).abs() < 1e-5);
408 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
409 }
410
411 #[test]
412 fn test_repetition_penalty() {
413 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
414 let mut scratch = SamplerScratch::default();
415 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
416 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
417 }
418
419 #[test]
420 fn repetition_penalty_applies_once_per_unique_token() {
421 let mut logits = vec![1.0, 4.0, -6.0];
422 let mut scratch = SamplerScratch::default();
423 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
424 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
425 }
426
427 #[test]
428 fn top_k_keeps_exactly_k() {
429 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
430 apply_top_k(&mut probs, 2, &mut Vec::new());
431 let kept = probs.iter().filter(|&&p| p > 0.0).count();
432 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
433 assert!(probs[1] > 0.0 && probs[3] > 0.0);
434 }
435
436 #[test]
437 fn rng_reaches_full_cdf() {
438 let probs = vec![0.25f32; 4];
441 let mut rng = SplitMix64::new(42);
442 let mut hits = [0usize; 4];
443 for _ in 0..4000 {
444 let i = categorical_sample(&probs, rng.next_f32()) as usize;
445 hits[i] += 1;
446 }
447 for (i, &h) in hits.iter().enumerate() {
448 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
449 }
450 }
451
452 #[test]
453 fn same_seed_same_sequence() {
454 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
455 let config = SamplerConfig {
456 temperature: 1.0,
457 seed: Some(7),
458 ..Default::default()
459 };
460 let run = |seed: u64| -> Vec<u32> {
461 let mut rng = SplitMix64::new(seed);
462 (0..16)
463 .map(|_| sample(&logits, &config, &[], &mut rng))
464 .collect()
465 };
466 assert_eq!(run(7), run(7), "same seed must reproduce");
467 assert_ne!(run(7), run(8), "different seed must differ");
468 }
469}