1#[derive(Debug, Clone)]
18pub struct SamplingParams {
19 pub temperature: f32,
20 pub top_p: f32,
23 pub top_k: usize,
26 pub repetition_penalty: f32,
31 pub presence_penalty: f32,
34 pub frequency_penalty: f32,
37}
38
39impl Default for SamplingParams {
40 fn default() -> Self {
43 SamplingParams {
44 temperature: 0.0,
45 top_p: 1.0,
46 top_k: 0,
47 repetition_penalty: 1.0,
48 presence_penalty: 0.0,
49 frequency_penalty: 0.0,
50 }
51 }
52}
53
54pub type LogitMask<'a> = &'a mut dyn FnMut(&mut [f32]);
58
59pub struct Sampler {
63 state: u64,
64}
65
66impl Sampler {
67 pub fn new(seed: u64) -> Self {
68 Sampler {
70 state: if seed == 0 { 0x9E3779B97F4A7C15 } else { seed },
71 }
72 }
73
74 fn next_u64(&mut self) -> u64 {
75 self.state ^= self.state << 13;
76 self.state ^= self.state >> 7;
77 self.state ^= self.state << 17;
78 self.state
79 }
80
81 fn next_f32(&mut self) -> f32 {
83 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
84 }
85
86 pub fn sample(&mut self, logits: &[f32], params: &SamplingParams, history: &[usize]) -> usize {
94 self.sample_with_mask(logits, params, history, None)
95 }
96
97 pub fn sample_with_mask(
100 &mut self,
101 logits: &[f32],
102 params: &SamplingParams,
103 history: &[usize],
104 mut mask: Option<LogitMask<'_>>,
105 ) -> usize {
106 if params.temperature <= 0.0 && mask.is_none() {
107 if logits.len() == 1 {
108 return logits[0] as usize;
109 }
110 let mut scores = logits.to_vec();
111 apply_history_penalties(&mut scores, params, history);
112 return argmax(&scores);
113 }
114
115 let mut scores: Vec<f32> = logits.to_vec();
116 apply_history_penalties(&mut scores, params, history);
117
118 if let Some(m) = mask.as_mut() {
119 m(&mut scores);
120 }
121
122 if params.temperature <= 0.0 {
123 if scores.len() == 1 {
124 return scores[0] as usize;
125 }
126 return argmax(&scores);
127 }
128
129 for s in scores.iter_mut() {
130 *s /= params.temperature;
131 }
132
133 let mut probs = softmax(&scores);
134
135 if params.top_k > 0 && params.top_k < probs.len() {
136 let mut idx: Vec<usize> = (0..probs.len()).collect();
137 idx.sort_unstable_by(|&a, &b| probs[b].partial_cmp(&probs[a]).unwrap());
138 for &i in idx.iter().skip(params.top_k) {
139 probs[i] = 0.0;
140 }
141 }
142
143 if params.top_p < 1.0 {
144 let mut idx: Vec<usize> = (0..probs.len()).collect();
145 idx.sort_unstable_by(|&a, &b| probs[b].partial_cmp(&probs[a]).unwrap());
146 let mut cumulative = 0.0f32;
147 let mut cutoff = idx.len();
148 for (rank, &i) in idx.iter().enumerate() {
149 cumulative += probs[i];
150 if cumulative >= params.top_p {
151 cutoff = rank + 1;
152 break;
153 }
154 }
155 for &i in idx.iter().skip(cutoff) {
156 probs[i] = 0.0;
157 }
158 }
159
160 let total: f32 = probs.iter().sum();
161 if total <= 0.0 {
162 return argmax(logits);
165 }
166 for p in probs.iter_mut() {
167 *p /= total;
168 }
169
170 let draw = self.next_f32();
171 let mut cumulative = 0.0f32;
172 for (i, &p) in probs.iter().enumerate() {
173 cumulative += p;
174 if draw < cumulative {
175 return i;
176 }
177 }
178 probs
182 .iter()
183 .enumerate()
184 .rev()
185 .find(|&(_, &p)| p > 0.0)
186 .map(|(i, _)| i)
187 .unwrap_or(0)
188 }
189}
190
191fn apply_history_penalties(scores: &mut [f32], params: &SamplingParams, history: &[usize]) {
192 if params.repetition_penalty != 1.0 {
193 for &tok in history {
194 if let Some(s) = scores.get_mut(tok) {
195 *s = if *s > 0.0 {
196 *s / params.repetition_penalty
197 } else {
198 *s * params.repetition_penalty
199 };
200 }
201 }
202 }
203 if params.presence_penalty != 0.0 || params.frequency_penalty != 0.0 {
204 let mut counts = std::collections::HashMap::<usize, usize>::new();
205 for &tok in history {
206 *counts.entry(tok).or_insert(0) += 1;
207 }
208 for (tok, count) in counts {
209 if let Some(s) = scores.get_mut(tok) {
210 *s -= params.frequency_penalty * count as f32;
211 *s -= params.presence_penalty;
212 }
213 }
214 }
215}
216
217fn argmax(logits: &[f32]) -> usize {
218 logits
219 .iter()
220 .enumerate()
221 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
222 .map(|(i, _)| i)
223 .unwrap_or(0)
224}
225
226fn softmax(logits: &[f32]) -> Vec<f32> {
227 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
228 let exps: Vec<f32> = logits.iter().map(|&l| (l - max).exp()).collect();
229 let sum: f32 = exps.iter().sum();
230 if sum <= 0.0 {
231 vec![1.0 / logits.len().max(1) as f32; logits.len()]
232 } else {
233 exps.into_iter().map(|e| e / sum).collect()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn temperature_zero_accepts_precomputed_argmax_singleton() {
243 let mut sampler = Sampler::new(1);
244 let params = SamplingParams::default();
245 assert_eq!(sampler.sample(&[42.0], ¶ms, &[]), 42);
246 let sampled = SamplingParams {
248 temperature: 0.8,
249 ..SamplingParams::default()
250 };
251 assert_eq!(sampler.sample(&[42.0], &sampled, &[]), 0);
253 }
254
255 #[test]
256 fn temperature_zero_is_deterministic_greedy_argmax() {
257 let logits = vec![0.1, 0.9, 0.3, -0.2];
258 let params = SamplingParams::default();
259 let mut sampler = Sampler::new(42);
260 assert_eq!(sampler.sample(&logits, ¶ms, &[]), 1);
261 assert_eq!(sampler.sample(&logits, ¶ms, &[]), 1);
263 }
264
265 #[test]
266 fn high_temperature_can_pick_a_non_argmax_token_over_many_draws() {
267 let logits = vec![1.0, 1.0, 1.0, 1.0];
268 let params = SamplingParams {
269 temperature: 1.0,
270 ..SamplingParams::default()
271 };
272 let mut sampler = Sampler::new(7);
273 let mut seen = std::collections::HashSet::new();
274 for _ in 0..200 {
275 seen.insert(sampler.sample(&logits, ¶ms, &[]));
276 }
277 assert!(
278 seen.len() > 1,
279 "uniform logits at temperature=1.0 must produce more than one distinct token across 200 draws"
280 );
281 }
282
283 #[test]
284 fn top_k_one_is_equivalent_to_greedy() {
285 let logits = vec![0.1, 0.9, 0.3, -0.2];
286 let params = SamplingParams {
287 temperature: 1.0,
288 top_k: 1,
289 ..SamplingParams::default()
290 };
291 let mut sampler = Sampler::new(123);
292 for _ in 0..20 {
293 assert_eq!(sampler.sample(&logits, ¶ms, &[]), 1);
294 }
295 }
296
297 #[test]
298 fn top_p_near_zero_is_equivalent_to_greedy() {
299 let logits = vec![0.1, 5.0, 0.3, -0.2];
300 let params = SamplingParams {
301 temperature: 1.0,
302 top_p: 0.001,
303 ..SamplingParams::default()
304 };
305 let mut sampler = Sampler::new(9);
306 for _ in 0..20 {
307 assert_eq!(sampler.sample(&logits, ¶ms, &[]), 1);
308 }
309 }
310
311 #[test]
312 fn presence_and_frequency_penalties_reduce_seen_token_logits() {
313 let logits = vec![0.0, 5.0, 0.0];
314 let params = SamplingParams {
315 temperature: 1.0,
316 presence_penalty: 10.0,
317 frequency_penalty: 0.0,
318 ..SamplingParams::default()
319 };
320 let mut sampler = Sampler::new(1);
321 let mut counts = [0usize; 3];
322 for _ in 0..500 {
323 counts[sampler.sample(&logits, ¶ms, &[1])] += 1;
324 }
325 assert!(
326 counts[1] < 250,
327 "presence_penalty should discourage token 1; counts={counts:?}"
328 );
329
330 let params = SamplingParams {
331 temperature: 1.0,
332 presence_penalty: 0.0,
333 frequency_penalty: 10.0,
334 ..SamplingParams::default()
335 };
336 let mut sampler = Sampler::new(2);
337 counts = [0; 3];
338 for _ in 0..500 {
339 counts[sampler.sample(&logits, ¶ms, &[1, 1, 1])] += 1;
340 }
341 assert!(
342 counts[1] < 250,
343 "frequency_penalty should discourage repeated token 1; counts={counts:?}"
344 );
345 }
346
347 #[test]
348 fn repetition_penalty_reduces_probability_of_recently_seen_token() {
349 let logits = vec![0.0, 5.0, 0.0];
350 let params = SamplingParams {
351 temperature: 1.0,
352 repetition_penalty: 1000.0,
353 ..SamplingParams::default()
354 };
355 let mut sampler = Sampler::new(3);
356 let mut counts = [0usize; 3];
357 for _ in 0..500 {
358 counts[sampler.sample(&logits, ¶ms, &[1])] += 1;
359 }
360 assert!(
361 counts[1] < 250,
362 "heavily penalizing token 1 (already in history) should make it far less likely than its raw logit alone would suggest; got counts={counts:?}"
363 );
364 }
365
366 #[test]
367 fn degenerate_all_zero_probability_falls_back_to_greedy() {
368 let logits = vec![0.1, 0.9, 0.3, -0.2];
372 let params = SamplingParams {
373 temperature: 1.0,
374 top_k: 1,
375 top_p: 1.0,
376 ..SamplingParams::default()
377 };
378 let mut sampler = Sampler::new(1);
379 assert_eq!(sampler.sample(&logits, ¶ms, &[]), 1);
380 }
381}