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}
23
24impl SamplerScratch {
25 fn begin_seen(&mut self, vocab_size: usize) -> u32 {
26 if self.seen_epoch.len() < vocab_size {
27 self.seen_epoch.resize(vocab_size, 0);
28 }
29 self.epoch = self.epoch.wrapping_add(1);
30 if self.epoch == 0 {
31 self.seen_epoch.fill(0);
32 self.epoch = 1;
33 }
34 self.epoch
35 }
36}
37
38impl SplitMix64 {
39 pub fn new(seed: u64) -> Self {
40 Self { state: seed }
41 }
42
43 pub fn from_entropy() -> Self {
45 let t = std::time::SystemTime::now()
46 .duration_since(std::time::UNIX_EPOCH)
47 .unwrap_or_default();
48 let addr = Box::into_raw(Box::new(0u8)) as u64;
49 unsafe { drop(Box::from_raw(addr as *mut u8)) };
51 Self::new(t.as_nanos() as u64 ^ addr.rotate_left(17) ^ 0x9E3779B97F4A7C15)
52 }
53
54 #[inline]
55 pub fn next_u64(&mut self) -> u64 {
56 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
57 let mut z = self.state;
58 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
59 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
60 z ^ (z >> 31)
61 }
62
63 #[inline]
65 pub fn next_f32(&mut self) -> f32 {
66 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct SamplerConfig {
73 pub temperature: f32,
74 pub top_p: f32,
75 pub top_k: u32,
76 pub repetition_penalty: f32,
77 pub min_p: f32,
78 #[serde(default)]
80 pub seed: Option<u64>,
81 #[serde(default)]
83 pub suppress_tokens: Vec<u32>,
84}
85
86impl Default for SamplerConfig {
87 fn default() -> Self {
88 Self {
89 temperature: 0.7,
90 top_p: 0.9,
91 top_k: 40,
92 repetition_penalty: 1.1,
93 min_p: 0.05,
94 seed: None,
95 suppress_tokens: Vec::new(),
96 }
97 }
98}
99
100pub fn sample(
103 logits: &[f32],
104 config: &SamplerConfig,
105 past_tokens: &[u32],
106 rng: &mut SplitMix64,
107) -> u32 {
108 let mut scratch = SamplerScratch::default();
109 sample_with_scratch(logits, config, past_tokens, rng, &mut scratch)
110}
111
112pub fn sample_with_scratch(
114 logits: &[f32],
115 config: &SamplerConfig,
116 past_tokens: &[u32],
117 rng: &mut SplitMix64,
118 scratch: &mut SamplerScratch,
119) -> u32 {
120 if config.temperature < 1e-6
121 && config.repetition_penalty == 1.0
122 && config.suppress_tokens.is_empty()
123 {
124 return argmax(logits);
125 }
126 let mut probs = logits.to_vec();
127
128 for &tok in &config.suppress_tokens {
129 if (tok as usize) < probs.len() {
130 probs[tok as usize] = f32::NEG_INFINITY;
131 }
132 }
133
134 if config.repetition_penalty != 1.0 {
135 apply_repetition_penalty(&mut probs, past_tokens, config.repetition_penalty, scratch);
136 }
137
138 if config.temperature < 1e-6 {
139 return argmax(&probs); }
141 if config.temperature != 1.0 {
142 for p in probs.iter_mut() {
143 *p /= config.temperature;
144 }
145 }
146
147 softmax_inplace(&mut probs);
148
149 if config.min_p > 0.0 {
150 let max_prob = probs.iter().cloned().fold(0.0f32, f32::max);
151 let threshold = max_prob * config.min_p;
152 for p in probs.iter_mut() {
153 if *p < threshold {
154 *p = 0.0;
155 }
156 }
157 }
158
159 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
160 apply_top_k(&mut probs, config.top_k as usize);
161 }
162
163 if config.top_p < 1.0 && config.top_p > 0.0 {
164 apply_top_p(&mut probs, config.top_p);
165 }
166
167 let sum: f32 = probs.iter().sum();
168 if sum > 0.0 {
169 for p in probs.iter_mut() {
170 *p /= sum;
171 }
172 } else {
173 return argmax(logits);
175 }
176
177 categorical_sample(&probs, rng.next_f32())
178}
179
180pub fn argmax(values: &[f32]) -> u32 {
182 values
183 .iter()
184 .enumerate()
185 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
186 .map(|(i, _)| i as u32)
187 .unwrap_or(0)
188}
189
190fn softmax_inplace(logits: &mut [f32]) {
191 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
192 let mut sum = 0.0f32;
193 for v in logits.iter_mut() {
194 *v = (*v - max_val).exp();
195 sum += *v;
196 }
197 if sum > 0.0 {
198 for v in logits.iter_mut() {
199 *v /= sum;
200 }
201 }
202}
203
204fn apply_repetition_penalty(
205 logits: &mut [f32],
206 past_tokens: &[u32],
207 penalty: f32,
208 scratch: &mut SamplerScratch,
209) {
210 let epoch = scratch.begin_seen(logits.len());
211 for &tok in past_tokens {
212 let idx = tok as usize;
213 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
214 scratch.seen_epoch[idx] = epoch;
215 if logits[idx] > 0.0 {
216 logits[idx] /= penalty;
217 } else {
218 logits[idx] *= penalty;
219 }
220 }
221 }
222}
223
224fn apply_top_k(probs: &mut [f32], k: usize) {
229 if k == 0 || k >= probs.len() {
230 return;
231 }
232 let mut sel: Vec<f32> = probs.to_vec();
233 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
235 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
236 });
237 let threshold = *kth;
238 for p in probs.iter_mut() {
239 if *p < threshold {
240 *p = 0.0;
241 }
242 }
243}
244
245fn apply_top_p(probs: &mut [f32], top_p: f32) {
250 let mut indexed: Vec<(usize, f32)> = probs
251 .iter()
252 .copied()
253 .enumerate()
254 .filter(|&(_, p)| p > 0.0)
255 .collect();
256 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
257
258 let mut cumsum = 0.0f32;
259 let mut cutoff_idx = indexed.len();
260 for (i, &(_, prob)) in indexed.iter().enumerate() {
261 cumsum += prob;
262 if cumsum >= top_p {
263 cutoff_idx = i + 1;
264 break;
265 }
266 }
267
268 for &(i, _) in &indexed[cutoff_idx..] {
270 probs[i] = 0.0;
271 }
272}
273
274fn categorical_sample(probs: &[f32], r: f32) -> u32 {
276 let mut cumsum = 0.0f32;
277 for (i, &p) in probs.iter().enumerate() {
278 cumsum += p;
279 if r < cumsum {
280 return i as u32;
281 }
282 }
283 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_argmax() {
292 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
293 assert_eq!(argmax(&logits), 3);
294 }
295
296 #[test]
297 fn test_greedy_sampling() {
298 let logits = vec![1.0, 5.0, 2.0, 3.0];
299 let config = SamplerConfig {
300 temperature: 0.0,
301 ..Default::default()
302 };
303 let mut rng = SplitMix64::new(1);
304 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
305 }
306
307 #[test]
308 fn test_softmax() {
309 let mut logits = vec![1.0, 2.0, 3.0];
310 softmax_inplace(&mut logits);
311 let sum: f32 = logits.iter().sum();
312 assert!((sum - 1.0).abs() < 1e-5);
313 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
314 }
315
316 #[test]
317 fn test_repetition_penalty() {
318 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
319 let mut scratch = SamplerScratch::default();
320 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
321 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
322 }
323
324 #[test]
325 fn repetition_penalty_applies_once_per_unique_token() {
326 let mut logits = vec![1.0, 4.0, -6.0];
327 let mut scratch = SamplerScratch::default();
328 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
329 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
330 }
331
332 #[test]
333 fn top_k_keeps_exactly_k() {
334 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
335 apply_top_k(&mut probs, 2);
336 let kept = probs.iter().filter(|&&p| p > 0.0).count();
337 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
338 assert!(probs[1] > 0.0 && probs[3] > 0.0);
339 }
340
341 #[test]
342 fn rng_reaches_full_cdf() {
343 let probs = vec![0.25f32; 4];
346 let mut rng = SplitMix64::new(42);
347 let mut hits = [0usize; 4];
348 for _ in 0..4000 {
349 let i = categorical_sample(&probs, rng.next_f32()) as usize;
350 hits[i] += 1;
351 }
352 for (i, &h) in hits.iter().enumerate() {
353 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
354 }
355 }
356
357 #[test]
358 fn same_seed_same_sequence() {
359 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
360 let config = SamplerConfig {
361 temperature: 1.0,
362 seed: Some(7),
363 ..Default::default()
364 };
365 let run = |seed: u64| -> Vec<u32> {
366 let mut rng = SplitMix64::new(seed);
367 (0..16)
368 .map(|_| sample(&logits, &config, &[], &mut rng))
369 .collect()
370 };
371 assert_eq!(run(7), run(7), "same seed must reproduce");
372 assert_ne!(run(7), run(8), "different seed must differ");
373 }
374}