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