1#[derive(Clone, Debug)]
11pub struct SamplerConfig {
12 pub temperature: f32, pub top_k: usize, pub top_p: f32, pub min_p: f32, pub penalty_last_n: usize, pub penalty_repeat: f32, pub penalty_freq: f32, pub penalty_present: f32, pub seed: u64,
21}
22
23impl Default for SamplerConfig {
24 fn default() -> Self {
25 SamplerConfig {
26 temperature: 0.0,
27 top_k: 0,
28 top_p: 1.0,
29 min_p: 0.0,
30 penalty_last_n: 0,
31 penalty_repeat: 1.0,
32 penalty_freq: 0.0,
33 penalty_present: 0.0,
34 seed: 0,
35 }
36 }
37}
38
39pub struct Sampler {
41 cfg: SamplerConfig,
42 rng: SplitMix64,
43 history: Vec<u32>, }
45
46impl Sampler {
47 pub fn new(cfg: SamplerConfig) -> Self {
48 let rng = SplitMix64::new(cfg.seed);
49 Sampler {
50 cfg,
51 rng,
52 history: Vec::new(),
53 }
54 }
55
56 pub fn is_greedy(&self) -> bool {
57 self.cfg.temperature <= 0.0
58 }
59 pub fn is_spec_sampling(&self) -> bool {
69 self.cfg.temperature > 0.0
70 && self.cfg.penalty_repeat == 1.0
71 && self.cfg.penalty_freq == 0.0
72 && self.cfg.penalty_present == 0.0
73 && self.cfg.top_k == 0
74 && self.cfg.top_p >= 1.0
75 && self.cfg.min_p <= 0.0
76 }
77 pub fn top_k(&self) -> usize {
78 self.cfg.top_k
79 }
80 pub fn penalty_last_n(&self) -> usize {
81 self.cfg.penalty_last_n
82 }
83 pub fn penalty_repeat(&self) -> f32 {
84 self.cfg.penalty_repeat
85 }
86 pub fn penalty_freq(&self) -> f32 {
87 self.cfg.penalty_freq
88 }
89 pub fn penalty_present(&self) -> f32 {
90 self.cfg.penalty_present
91 }
92 pub fn top_p(&self) -> f32 {
93 self.cfg.top_p
94 }
95 pub fn min_p(&self) -> f32 {
96 self.cfg.min_p
97 }
98 pub fn temperature(&self) -> f32 {
99 self.cfg.temperature
100 }
101 pub fn seed(&self) -> u64 {
102 self.cfg.seed
103 }
104
105 pub fn accept(&mut self, token: u32) {
107 self.history.push(token);
108 }
109
110 pub fn sample(&mut self, logits: &[f32]) -> u32 {
113 if self.is_greedy()
117 && self.cfg.penalty_repeat == 1.0
118 && self.cfg.penalty_freq == 0.0
119 && self.cfg.penalty_present == 0.0
120 {
121 return argmax_u32(logits);
122 }
123
124 let mut cand: Vec<(u32, f32)> = logits
126 .iter()
127 .enumerate()
128 .map(|(i, &l)| (i as u32, l))
129 .collect();
130
131 self.apply_penalties(&mut cand);
133
134 if self.is_greedy() {
136 let mut best = cand[0];
137 for &c in &cand[1..] {
138 if c.1 > best.1 {
139 best = c;
140 }
141 }
142 return best.0;
143 }
144
145 if self.cfg.temperature > 0.0 && self.cfg.temperature != 1.0 {
147 let inv = 1.0 / self.cfg.temperature;
148 for c in cand.iter_mut() {
149 c.1 *= inv;
150 }
151 }
152
153 if self.cfg.top_k > 0 && self.cfg.top_k < cand.len() {
155 cand.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
156 cand.truncate(self.cfg.top_k);
157 }
158
159 softmax_inplace(&mut cand);
161
162 if self.cfg.top_p < 1.0 {
164 cand.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
165 let mut cum = 0.0f32;
166 let mut keep = 0usize;
167 for (i, c) in cand.iter().enumerate() {
168 cum += c.1;
169 keep = i + 1;
170 if cum >= self.cfg.top_p {
171 break;
172 }
173 }
174 cand.truncate(keep.max(1));
175 }
176
177 if self.cfg.min_p > 0.0 {
179 let maxp = cand.iter().map(|c| c.1).fold(0.0f32, f32::max);
180 let thresh = self.cfg.min_p * maxp;
181 cand.retain(|c| c.1 >= thresh);
182 if cand.is_empty() {
183 return argmax_u32(logits);
184 } }
186
187 let sum: f32 = cand.iter().map(|c| c.1).sum();
189 let r = self.rng.next_f32() * sum;
190 let mut acc = 0.0f32;
191 for c in &cand {
192 acc += c.1;
193 if acc >= r {
194 return c.0;
195 }
196 }
197 cand.last().unwrap().0
198 }
199
200 fn apply_penalties(&self, cand: &mut [(u32, f32)]) {
203 let n = self.cfg.penalty_last_n;
204 if n == 0 {
205 return;
206 }
207 if self.cfg.penalty_repeat == 1.0
208 && self.cfg.penalty_freq == 0.0
209 && self.cfg.penalty_present == 0.0
210 {
211 return;
212 }
213 let start = self.history.len().saturating_sub(n);
214 let window = &self.history[start..];
215 if window.is_empty() {
216 return;
217 }
218 use std::collections::HashMap;
220 let mut counts: HashMap<u32, i32> = HashMap::new();
221 for &t in window {
222 *counts.entry(t).or_insert(0) += 1;
223 }
224 for c in cand.iter_mut() {
225 if let Some(&cnt) = counts.get(&c.0) {
226 if self.cfg.penalty_repeat != 1.0 {
228 if c.1 > 0.0 {
229 c.1 /= self.cfg.penalty_repeat;
230 } else {
231 c.1 *= self.cfg.penalty_repeat;
232 }
233 }
234 c.1 -= cnt as f32 * self.cfg.penalty_freq;
235 c.1 -= self.cfg.penalty_present; }
237 }
238 }
239}
240
241fn argmax_u32(logits: &[f32]) -> u32 {
242 let mut best = 0u32;
243 let mut bv = f32::NEG_INFINITY;
244 for (i, &v) in logits.iter().enumerate() {
245 if v > bv {
246 bv = v;
247 best = i as u32;
248 }
249 }
250 best
251}
252
253fn softmax_inplace(cand: &mut [(u32, f32)]) {
255 let maxl = cand.iter().map(|c| c.1).fold(f32::NEG_INFINITY, f32::max);
256 let mut sum = 0.0f32;
257 for c in cand.iter_mut() {
258 let e = (c.1 - maxl).exp();
259 c.1 = e;
260 sum += e;
261 }
262 let inv = if sum > 0.0 { 1.0 / sum } else { 0.0 };
263 for c in cand.iter_mut() {
264 c.1 *= inv;
265 }
266}
267
268struct SplitMix64 {
271 state: u64,
272}
273impl SplitMix64 {
274 fn new(seed: u64) -> Self {
275 SplitMix64 {
276 state: seed.wrapping_add(0x9E3779B97F4A7C15),
277 }
278 }
279 fn next_u64(&mut self) -> u64 {
280 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
281 let mut z = self.state;
282 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
283 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
284 z ^ (z >> 31)
285 }
286 fn next_f32(&mut self) -> f32 {
288 ((self.next_u64() >> 40) as f32) / (1u32 << 24) as f32
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn greedy_is_argmax() {
299 let mut s = Sampler::new(SamplerConfig::default()); let logits = vec![0.1, 5.0, 2.0, -1.0];
301 assert_eq!(s.sample(&logits), 1);
302 }
303
304 #[test]
305 fn temp_sampling_deterministic_with_seed() {
306 let cfg = SamplerConfig {
307 temperature: 1.0,
308 seed: 42,
309 ..Default::default()
310 };
311 let logits = vec![1.0, 2.0, 3.0, 0.5];
312 let a = Sampler::new(cfg.clone()).sample(&logits);
313 let b = Sampler::new(cfg).sample(&logits);
314 assert_eq!(a, b, "same seed must reproduce the draw");
315 assert!(a < 4);
316 }
317
318 #[test]
319 fn top_k_one_is_argmax() {
320 let cfg = SamplerConfig {
321 temperature: 1.0,
322 top_k: 1,
323 seed: 7,
324 ..Default::default()
325 };
326 let logits = vec![0.1, 5.0, 2.0, -1.0];
327 assert_eq!(
328 Sampler::new(cfg).sample(&logits),
329 1,
330 "top_k=1 collapses to argmax"
331 );
332 }
333
334 #[test]
335 fn min_p_keeps_only_high_prob() {
336 let cfg = SamplerConfig {
338 temperature: 1.0,
339 min_p: 0.5,
340 seed: 3,
341 ..Default::default()
342 };
343 let logits = vec![0.0, 0.0, 10.0, 0.0];
344 for _ in 0..16 {
345 assert_eq!(Sampler::new(cfg.clone()).sample(&logits), 2);
346 }
347 }
348
349 #[test]
350 fn repeat_penalty_suppresses_recent() {
351 let mut cfg = SamplerConfig::default();
353 cfg.penalty_last_n = 8;
354 cfg.penalty_repeat = 100.0;
355 let mut s = Sampler::new(cfg);
356 s.accept(1); let logits = vec![4.0, 5.0, 4.5, 1.0]; let got = s.sample(&logits);
359 assert_ne!(
360 got, 1,
361 "recent token must be penalized out of greedy argmax"
362 );
363 assert_eq!(got, 2, "next-highest after penalizing 1");
364 }
365}