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