1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone)]
11pub struct SplitMix64 {
12 state: u64,
13}
14
15impl SplitMix64 {
16 pub fn new(seed: u64) -> Self {
17 Self { state: seed }
18 }
19
20 pub fn from_entropy() -> Self {
22 let t = std::time::SystemTime::now()
23 .duration_since(std::time::UNIX_EPOCH)
24 .unwrap_or_default();
25 let addr = Box::into_raw(Box::new(0u8)) as u64;
26 unsafe { drop(Box::from_raw(addr as *mut u8)) };
28 Self::new(t.as_nanos() as u64 ^ addr.rotate_left(17) ^ 0x9E3779B97F4A7C15)
29 }
30
31 #[inline]
32 pub fn next_u64(&mut self) -> u64 {
33 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
34 let mut z = self.state;
35 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
36 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
37 z ^ (z >> 31)
38 }
39
40 #[inline]
42 pub fn next_f32(&mut self) -> f32 {
43 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SamplerConfig {
50 pub temperature: f32,
51 pub top_p: f32,
52 pub top_k: u32,
53 pub repetition_penalty: f32,
54 pub min_p: f32,
55 #[serde(default)]
57 pub seed: Option<u64>,
58}
59
60impl Default for SamplerConfig {
61 fn default() -> Self {
62 Self {
63 temperature: 0.7,
64 top_p: 0.9,
65 top_k: 40,
66 repetition_penalty: 1.1,
67 min_p: 0.05,
68 seed: None,
69 }
70 }
71}
72
73pub fn sample(
76 logits: &[f32],
77 config: &SamplerConfig,
78 past_tokens: &[u32],
79 rng: &mut SplitMix64,
80) -> u32 {
81 if config.temperature < 1e-6 && config.repetition_penalty == 1.0 {
85 return argmax(logits);
86 }
87 let mut probs = logits.to_vec();
88
89 if config.repetition_penalty != 1.0 {
90 apply_repetition_penalty(&mut probs, past_tokens, config.repetition_penalty);
91 }
92
93 if config.temperature < 1e-6 {
94 return argmax(&probs); }
96 if config.temperature != 1.0 {
97 for p in probs.iter_mut() {
98 *p /= config.temperature;
99 }
100 }
101
102 softmax_inplace(&mut probs);
103
104 if config.min_p > 0.0 {
105 let max_prob = probs.iter().cloned().fold(0.0f32, f32::max);
106 let threshold = max_prob * config.min_p;
107 for p in probs.iter_mut() {
108 if *p < threshold {
109 *p = 0.0;
110 }
111 }
112 }
113
114 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
115 apply_top_k(&mut probs, config.top_k as usize);
116 }
117
118 if config.top_p < 1.0 && config.top_p > 0.0 {
119 apply_top_p(&mut probs, config.top_p);
120 }
121
122 let sum: f32 = probs.iter().sum();
123 if sum > 0.0 {
124 for p in probs.iter_mut() {
125 *p /= sum;
126 }
127 } else {
128 return argmax(logits);
130 }
131
132 categorical_sample(&probs, rng.next_f32())
133}
134
135pub fn argmax(values: &[f32]) -> u32 {
137 values
138 .iter()
139 .enumerate()
140 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
141 .map(|(i, _)| i as u32)
142 .unwrap_or(0)
143}
144
145fn softmax_inplace(logits: &mut [f32]) {
146 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
147 let mut sum = 0.0f32;
148 for v in logits.iter_mut() {
149 *v = (*v - max_val).exp();
150 sum += *v;
151 }
152 if sum > 0.0 {
153 for v in logits.iter_mut() {
154 *v /= sum;
155 }
156 }
157}
158
159fn apply_repetition_penalty(logits: &mut [f32], past_tokens: &[u32], penalty: f32) {
160 for &tok in past_tokens {
161 let idx = tok as usize;
162 if idx < logits.len() {
163 if logits[idx] > 0.0 {
164 logits[idx] /= penalty;
165 } else {
166 logits[idx] *= penalty;
167 }
168 }
169 }
170}
171
172fn apply_top_k(probs: &mut [f32], k: usize) {
177 if k == 0 || k >= probs.len() {
178 return;
179 }
180 let mut sel: Vec<f32> = probs.to_vec();
181 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
183 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
184 });
185 let threshold = *kth;
186 for p in probs.iter_mut() {
187 if *p < threshold {
188 *p = 0.0;
189 }
190 }
191}
192
193fn apply_top_p(probs: &mut [f32], top_p: f32) {
198 let mut indexed: Vec<(usize, f32)> = probs
199 .iter()
200 .copied()
201 .enumerate()
202 .filter(|&(_, p)| p > 0.0)
203 .collect();
204 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
205
206 let mut cumsum = 0.0f32;
207 let mut cutoff_idx = indexed.len();
208 for (i, &(_, prob)) in indexed.iter().enumerate() {
209 cumsum += prob;
210 if cumsum >= top_p {
211 cutoff_idx = i + 1;
212 break;
213 }
214 }
215
216 for &(i, _) in &indexed[cutoff_idx..] {
218 probs[i] = 0.0;
219 }
220}
221
222fn categorical_sample(probs: &[f32], r: f32) -> u32 {
224 let mut cumsum = 0.0f32;
225 for (i, &p) in probs.iter().enumerate() {
226 cumsum += p;
227 if r < cumsum {
228 return i as u32;
229 }
230 }
231 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_argmax() {
240 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
241 assert_eq!(argmax(&logits), 3);
242 }
243
244 #[test]
245 fn test_greedy_sampling() {
246 let logits = vec![1.0, 5.0, 2.0, 3.0];
247 let config = SamplerConfig {
248 temperature: 0.0,
249 ..Default::default()
250 };
251 let mut rng = SplitMix64::new(1);
252 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
253 }
254
255 #[test]
256 fn test_softmax() {
257 let mut logits = vec![1.0, 2.0, 3.0];
258 softmax_inplace(&mut logits);
259 let sum: f32 = logits.iter().sum();
260 assert!((sum - 1.0).abs() < 1e-5);
261 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
262 }
263
264 #[test]
265 fn test_repetition_penalty() {
266 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
267 apply_repetition_penalty(&mut logits, &[1, 3], 2.0);
268 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
269 }
270
271 #[test]
272 fn top_k_keeps_exactly_k() {
273 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
274 apply_top_k(&mut probs, 2);
275 let kept = probs.iter().filter(|&&p| p > 0.0).count();
276 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
277 assert!(probs[1] > 0.0 && probs[3] > 0.0);
278 }
279
280 #[test]
281 fn rng_reaches_full_cdf() {
282 let probs = vec![0.25f32; 4];
285 let mut rng = SplitMix64::new(42);
286 let mut hits = [0usize; 4];
287 for _ in 0..4000 {
288 let i = categorical_sample(&probs, rng.next_f32()) as usize;
289 hits[i] += 1;
290 }
291 for (i, &h) in hits.iter().enumerate() {
292 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
293 }
294 }
295
296 #[test]
297 fn same_seed_same_sequence() {
298 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
299 let config = SamplerConfig {
300 temperature: 1.0,
301 seed: Some(7),
302 ..Default::default()
303 };
304 let run = |seed: u64| -> Vec<u32> {
305 let mut rng = SplitMix64::new(seed);
306 (0..16).map(|_| sample(&logits, &config, &[], &mut rng)).collect()
307 };
308 assert_eq!(run(7), run(7), "same seed must reproduce");
309 assert_ne!(run(7), run(8), "different seed must differ");
310 }
311}