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 presence_seen: std::collections::HashSet<u32>,
24 probs: Vec<f32>,
28 topk: Vec<f32>,
33 cand: Vec<(u32, f32)>,
35 cand_parts: Vec<Vec<(u32, f32)>>,
36 sum_parts: Vec<f32>,
37 sparse: Sparse,
38}
39
40impl SamplerScratch {
41 fn begin_seen(&mut self, vocab_size: usize) -> u32 {
42 if self.seen_epoch.len() < vocab_size {
43 self.seen_epoch.resize(vocab_size, 0);
44 }
45 self.epoch = self.epoch.wrapping_add(1);
46 if self.epoch == 0 {
47 self.seen_epoch.fill(0);
48 self.epoch = 1;
49 }
50 self.epoch
51 }
52}
53
54impl SplitMix64 {
55 pub fn new(seed: u64) -> Self {
56 Self { state: seed }
57 }
58
59 pub fn from_entropy() -> Self {
61 let t = std::time::SystemTime::now()
62 .duration_since(std::time::UNIX_EPOCH)
63 .unwrap_or_default();
64 let addr = Box::into_raw(Box::new(0u8)) as u64;
65 unsafe { drop(Box::from_raw(addr as *mut u8)) };
67 Self::new(t.as_nanos() as u64 ^ addr.rotate_left(17) ^ 0x9E3779B97F4A7C15)
68 }
69
70 #[inline]
71 pub fn next_u64(&mut self) -> u64 {
72 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
73 let mut z = self.state;
74 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
75 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
76 z ^ (z >> 31)
77 }
78
79 #[inline]
81 pub fn next_f32(&mut self) -> f32 {
82 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct SamplerConfig {
89 pub temperature: f32,
90 pub top_p: f32,
91 pub top_k: u32,
92 pub repetition_penalty: f32,
93 pub min_p: f32,
94 #[serde(default)]
99 pub presence_penalty: f32,
100 #[serde(default)]
102 pub seed: Option<u64>,
103 #[serde(default)]
105 pub suppress_tokens: Vec<u32>,
106}
107
108impl Default for SamplerConfig {
109 fn default() -> Self {
110 Self {
111 temperature: 0.7,
112 top_p: 0.9,
113 top_k: 40,
114 repetition_penalty: 1.1,
115 presence_penalty: 0.0,
116 min_p: 0.05,
117 seed: None,
118 suppress_tokens: Vec::new(),
119 }
120 }
121}
122
123pub fn sample(
126 logits: &[f32],
127 config: &SamplerConfig,
128 past_tokens: &[u32],
129 rng: &mut SplitMix64,
130) -> u32 {
131 let mut scratch = SamplerScratch::default();
132 sample_with_scratch(logits, config, past_tokens, rng, &mut scratch)
133}
134
135pub fn sample_with_scratch(
137 logits: &[f32],
138 config: &SamplerConfig,
139 past_tokens: &[u32],
140 rng: &mut SplitMix64,
141 scratch: &mut SamplerScratch,
142) -> u32 {
143 sample_with_scratch_pool(logits, config, past_tokens, rng, scratch, None)
144}
145
146pub fn sample_with_scratch_pool(
162 logits: &[f32],
163 config: &SamplerConfig,
164 past_tokens: &[u32],
165 rng: &mut SplitMix64,
166 scratch: &mut SamplerScratch,
167 pool: Option<&crate::pool::Pool>,
168) -> u32 {
169 if config.temperature < 1e-6
170 && config.repetition_penalty == 1.0
171 && config.presence_penalty == 0.0
172 && config.suppress_tokens.is_empty()
173 {
174 return argmax(logits);
175 }
176 if config.temperature < 1e-6 {
182 return argmax_penalized(logits, config, past_tokens, scratch, pool);
184 }
185 if sparse_ok(config) {
186 let mut sp = std::mem::take(&mut scratch.sparse);
188 let ok = sparse_distribution_into(logits, config, past_tokens, scratch, pool, &mut sp);
189 let t = if ok {
190 draw_sparse(&sp, rng)
191 } else {
192 argmax(logits)
193 };
194 scratch.sparse = sp;
195 return t;
196 }
197 let mut probs = std::mem::take(&mut scratch.probs);
198 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
199
200 let mut done = |probs: Vec<f32>, tok: u32| -> u32 {
201 scratch.probs = probs;
202 tok
203 };
204
205 if !normalized {
206 let t = argmax(logits);
208 return done(probs, t);
209 }
210 let t = categorical_sample(&probs, rng.next_f32());
211 done(probs, t)
212}
213
214pub fn argmax_penalized(
224 logits: &[f32],
225 config: &SamplerConfig,
226 past_tokens: &[u32],
227 scratch: &mut SamplerScratch,
228 pool: Option<&crate::pool::Pool>,
229) -> u32 {
230 let n = logits.len();
231 if config.repetition_penalty == 1.0
232 && config.presence_penalty == 0.0
233 && config.suppress_tokens.is_empty()
234 {
235 return argmax(logits);
236 }
237 let epoch = scratch.begin_seen(n);
239 for &tok in past_tokens {
240 let idx = tok as usize;
241 if idx < n {
242 scratch.seen_epoch[idx] = epoch;
243 }
244 }
245 let rep = config.repetition_penalty;
246 let pres = config.presence_penalty;
247 let suppress = &config.suppress_tokens;
249 let seen = &scratch.seen_epoch;
250 let value = |i: usize| -> f32 {
251 let mut v = logits[i];
252 if suppress.iter().any(|&t| t as usize == i) {
253 return f32::NEG_INFINITY;
254 }
255 if seen[i] == epoch {
256 if rep != 1.0 {
257 if v > 0.0 {
258 v /= rep;
259 } else {
260 v *= rep;
261 }
262 }
263 if pres != 0.0 {
264 v -= pres;
265 }
266 }
267 v
268 };
269 let best_in = |s: usize, e: usize| -> (usize, f32) {
272 let mut bi = s;
273 let mut bv = f32::NEG_INFINITY;
274 for i in s..e {
275 let v = value(i);
276 if v >= bv {
277 bv = v;
278 bi = i;
279 }
280 }
281 (bi, bv)
282 };
283 match pool {
284 Some(p) if n >= PAR_MIN && suppress.is_empty() => {
285 let m = std::sync::Mutex::new(Vec::<(usize, f32)>::new());
286 p.run_rows(n, &|s, e| {
287 let r = best_in(s, e);
288 m.lock().unwrap().push(r);
289 });
290 let mut parts = m.into_inner().unwrap();
291 parts.sort_by_key(|(i, _)| *i);
295 let mut bi = 0usize;
296 let mut bv = f32::NEG_INFINITY;
297 for (i, v) in parts {
298 if v >= bv {
299 bv = v;
300 bi = i;
301 }
302 }
303 bi as u32
304 }
305 _ => best_in(0, n).0 as u32,
306 }
307}
308
309fn chain(
315 logits: &[f32],
316 config: &SamplerConfig,
317 past_tokens: &[u32],
318 scratch: &mut SamplerScratch,
319 pool: Option<&crate::pool::Pool>,
320 probs: &mut Vec<f32>,
321) -> bool {
322 probs.clear();
323 probs.extend_from_slice(logits);
324 apply_penalties(probs, config, past_tokens, scratch);
325
326 if config.temperature < 1e-6 {
327 return true;
328 }
329 if config.temperature != 1.0 {
330 let t = config.temperature;
331 par_map(pool, probs, &move |p| p / t);
332 }
333
334 softmax_inplace_pool(pool, probs);
335
336 if config.min_p > 0.0 {
337 let max_prob = par_max(pool, probs, 0.0);
338 let threshold = max_prob * config.min_p;
339 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
340 }
341
342 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
343 apply_top_k_pool(pool, probs, config.top_k as usize);
344 }
345
346 if config.top_p < 1.0 && config.top_p > 0.0 {
347 apply_top_p(probs, config.top_p);
348 }
349
350 let sum: f32 = probs.iter().sum();
351 if sum > 0.0 {
352 par_map(pool, probs, &move |p| p / sum);
353 true
354 } else {
355 false
356 }
357}
358
359fn apply_penalties(
364 probs: &mut [f32],
365 config: &SamplerConfig,
366 past_tokens: &[u32],
367 scratch: &mut SamplerScratch,
368) {
369 for &tok in &config.suppress_tokens {
370 if (tok as usize) < probs.len() {
371 probs[tok as usize] = f32::NEG_INFINITY;
372 }
373 }
374 if config.repetition_penalty != 1.0 {
375 apply_repetition_penalty(probs, past_tokens, config.repetition_penalty, scratch);
376 }
377 if config.presence_penalty != 0.0 {
378 let mut seen = std::mem::take(&mut scratch.presence_seen);
383 seen.clear();
384 seen.extend(past_tokens.iter().copied());
385 for &tok in &seen {
386 if (tok as usize) < probs.len() {
387 probs[tok as usize] -= config.presence_penalty;
388 }
389 }
390 scratch.presence_seen = seen;
391 }
392}
393
394fn config_penalized(config: &SamplerConfig) -> bool {
395 config.repetition_penalty != 1.0
396 || config.presence_penalty != 0.0
397 || !config.suppress_tokens.is_empty()
398}
399
400pub const SPARSE_TOPK_MAX: usize = 256;
403
404pub fn sparse_ok(config: &SamplerConfig) -> bool {
408 config.temperature >= 1e-6 && config.top_k > 0 && (config.top_k as usize) <= SPARSE_TOPK_MAX
409}
410
411pub type Sparse = Vec<(u32, f32)>;
414
415pub fn sparse_distribution_into(
441 logits: &[f32],
442 config: &SamplerConfig,
443 past_tokens: &[u32],
444 scratch: &mut SamplerScratch,
445 pool: Option<&crate::pool::Pool>,
446 out: &mut Sparse,
447) -> bool {
448 debug_assert!(sparse_ok(config));
449 out.clear();
450 let k = (config.top_k as usize).min(logits.len());
451 if k == 0 {
452 return false;
453 }
454 let penalized = config_penalized(config);
455 let mut probs = std::mem::take(&mut scratch.probs);
456 if penalized {
457 probs.clear();
458 probs.extend_from_slice(logits);
459 apply_penalties(&mut probs, config, past_tokens, scratch);
460 }
461 let src: &[f32] = if penalized { &probs } else { logits };
462 let t = if config.temperature > 0.0 {
463 config.temperature
464 } else {
465 1.0
466 };
467 let mut cand = std::mem::take(&mut scratch.cand);
468 par_topk(pool, src, k, &mut cand, &mut scratch.cand_parts);
469 let ok = if let Some(&(_, lmax)) = cand.first().filter(|c| c.1.is_finite()) {
472 let sum_all = par_sum_exp(pool, src, lmax, t, &mut scratch.sum_parts);
473 let min_p = config.min_p;
476 let mut cum = 0.0f32;
477 let mut cut = false;
478 for &(id, l) in cand.iter() {
479 if cut {
480 break;
481 }
482 let e = ((l - lmax) / t).exp();
483 if min_p > 0.0 && e < min_p {
484 continue;
485 }
486 let pr = e / sum_all;
487 if pr <= 0.0 {
488 continue;
489 }
490 out.push((id, pr));
491 cum += pr;
492 if config.top_p < 1.0 && config.top_p > 0.0 && cum >= config.top_p {
493 cut = true;
494 }
495 }
496 let sum: f32 = out.iter().map(|c| c.1).sum();
498 if sum > 0.0 {
499 for c in out.iter_mut() {
500 c.1 /= sum;
501 }
502 out.sort_unstable_by_key(|c| c.0);
503 true
504 } else {
505 out.clear();
506 false
507 }
508 } else {
509 false
510 };
511 scratch.cand = cand;
512 scratch.probs = probs;
513 ok
514}
515
516fn par_topk(
523 pool: Option<&crate::pool::Pool>,
524 src: &[f32],
525 k: usize,
526 out: &mut Vec<(u32, f32)>,
527 parts: &mut Vec<Vec<(u32, f32)>>,
528) {
529 let better = |a: (u32, f32), b: (u32, f32)| a.1 > b.1 || (a.1 == b.1 && a.0 < b.0);
530 let scan = |s: usize, e: usize, best: &mut Vec<(u32, f32)>| {
533 best.clear();
534 for i in s..e {
535 let c = (i as u32, src[i]);
536 if best.len() < k {
537 let pos = best.iter().position(|&b| better(c, b)).unwrap_or(best.len());
538 best.insert(pos, c);
539 } else if better(c, best[k - 1]) {
540 let pos = best.iter().position(|&b| better(c, b)).unwrap_or(k - 1);
541 best.pop();
542 best.insert(pos, c);
543 }
544 }
545 };
546 let by_value_desc = |a: &(u32, f32), b: &(u32, f32)| {
547 b.1.partial_cmp(&a.1)
548 .unwrap_or(std::cmp::Ordering::Equal)
549 .then(a.0.cmp(&b.0))
550 };
551 let cap = k * 4 + 64;
555 let gather = |s: usize, e: usize, kth: f32, slot: &mut Vec<(u32, f32)>| {
557 slot.clear();
558 for i in s..e {
559 let v = src[i];
560 if v >= kth {
561 slot.push((i as u32, v));
562 if slot.len() >= cap {
563 break;
564 }
565 }
566 }
567 };
568 out.clear();
569 match pool {
570 Some(p) if src.len() >= PAR_MIN => {
571 let n = src.len();
572 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
573 let ng = n.div_ceil(grain);
574 parts.resize_with(ng, Vec::new);
575 let pp = crate::pool::SendMutT::new(parts.as_mut_ptr());
576 p.run_rows(n, &|s, e| {
577 let slot = unsafe { &mut *pp.at(s / grain) };
580 scan(s, e, slot);
581 });
582 for g in 0..ng {
583 out.extend_from_slice(&parts[g]);
584 }
585 out.sort_unstable_by(by_value_desc);
586 out.truncate(k);
587 let Some(&(_, kth)) = out.last() else {
588 return;
589 };
590 if !kth.is_finite() {
591 return; }
593 p.run_rows(n, &|s, e| {
594 let slot = unsafe { &mut *pp.at(s / grain) };
595 gather(s, e, kth, slot);
596 });
597 out.clear();
598 for g in 0..ng {
599 out.extend_from_slice(&parts[g]);
600 if out.len() >= cap {
601 break;
602 }
603 }
604 out.sort_unstable_by(by_value_desc);
605 out.truncate(cap);
606 }
607 _ => {
608 scan(0, src.len(), out);
609 let Some(&(_, kth)) = out.last() else {
610 return;
611 };
612 if !kth.is_finite() {
613 return;
614 }
615 let mut all = std::mem::take(out);
616 gather(0, src.len(), kth, &mut all);
617 all.sort_unstable_by(by_value_desc);
618 all.truncate(cap);
619 *out = all;
620 }
621 }
622}
623
624fn par_sum_exp(
627 pool: Option<&crate::pool::Pool>,
628 src: &[f32],
629 lmax: f32,
630 t: f32,
631 parts: &mut Vec<f32>,
632) -> f32 {
633 let term = |s: usize, e: usize| -> f32 {
634 let mut acc = 0.0f32;
635 for &l in &src[s..e] {
636 acc += ((l - lmax) / t).exp();
637 }
638 acc
639 };
640 match pool {
641 Some(p) if src.len() >= PAR_MIN => {
642 let n = src.len();
643 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
644 let ng = n.div_ceil(grain);
645 parts.clear();
646 parts.resize(ng, 0.0);
647 let pp = crate::pool::SendMut::new(parts.as_mut_ptr());
648 p.run_rows(n, &|s, e| {
649 unsafe { *pp.at(s / grain) = term(s, e) };
651 });
652 parts.iter().sum()
653 }
654 _ => term(0, src.len()),
655 }
656}
657
658pub fn draw_sparse(p: &[(u32, f32)], rng: &mut SplitMix64) -> u32 {
662 let r = rng.next_f32();
663 let mut cum = 0.0f32;
664 for &(id, pr) in p {
665 cum += pr;
666 if r < cum {
667 return id;
668 }
669 }
670 p.iter().rev().find(|c| c.1 > 0.0).map(|c| c.0).unwrap_or(0)
671}
672
673fn sparse_get(p: &[(u32, f32)], id: u32) -> f32 {
674 p.binary_search_by_key(&id, |c| c.0)
675 .map(|i| p[i].1)
676 .unwrap_or(0.0)
677}
678
679pub fn spec_accept_or_correct_sparse(
684 p: &[(u32, f32)],
685 q: &[(u32, f32)],
686 d: u32,
687 rng: &mut SplitMix64,
688 res: &mut Sparse,
689) -> Option<u32> {
690 let (pd, qd) = (sparse_get(p, d), sparse_get(q, d));
691 let r = rng.next_f32();
692 if qd > 0.0 && r * qd < pd {
693 return None;
694 }
695 res.clear();
696 let mut total = 0.0f32;
697 for &(id, pi) in p {
698 let ri = pi - sparse_get(q, id);
699 if ri > 0.0 {
700 res.push((id, ri));
701 total += ri;
702 }
703 }
704 if total <= 0.0 {
705 return Some(draw_sparse(p, rng));
706 }
707 for c in res.iter_mut() {
708 c.1 /= total;
709 }
710 Some(draw_sparse(res, rng))
711}
712
713pub fn distribution_into(
720 logits: &[f32],
721 config: &SamplerConfig,
722 past_tokens: &[u32],
723 scratch: &mut SamplerScratch,
724 pool: Option<&crate::pool::Pool>,
725 out: &mut Vec<f32>,
726) {
727 let one_hot = |out: &mut Vec<f32>, t: usize, n: usize| {
728 out.clear();
729 out.resize(n, 0.0);
730 if t < n {
731 out[t] = 1.0;
732 }
733 };
734 if config.temperature < 1e-6
735 && config.repetition_penalty == 1.0
736 && config.presence_penalty == 0.0
737 && config.suppress_tokens.is_empty()
738 {
739 return one_hot(out, argmax(logits) as usize, logits.len());
740 }
741 let mut probs = std::mem::take(&mut scratch.probs);
742 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
743 if config.temperature < 1e-6 {
744 let t = argmax(&probs) as usize;
745 scratch.probs = probs;
746 return one_hot(out, t, logits.len());
747 }
748 if !normalized {
749 scratch.probs = probs;
750 return one_hot(out, argmax(logits) as usize, logits.len());
751 }
752 out.clear();
753 out.extend_from_slice(&probs);
754 scratch.probs = probs;
755}
756
757pub fn draw(probs: &[f32], rng: &mut SplitMix64) -> u32 {
759 categorical_sample(probs, rng.next_f32())
760}
761
762pub fn spec_accept_or_correct(
772 p: &[f32],
773 q: &[f32],
774 d: u32,
775 rng: &mut SplitMix64,
776 scratch: &mut Vec<f32>,
777 pool: Option<&crate::pool::Pool>,
778) -> Option<u32> {
779 let di = d as usize;
780 let (pd, qd) = (
781 p.get(di).copied().unwrap_or(0.0),
782 q.get(di).copied().unwrap_or(0.0),
783 );
784 let r = rng.next_f32();
785 if qd > 0.0 && r * qd < pd {
787 return None;
788 }
789 let n = p.len().min(q.len());
790 scratch.clear();
791 scratch.extend_from_slice(&p[..n]);
792 {
794 let qp = q.as_ptr() as usize;
795 let sm = crate::pool::SendMut::new(scratch.as_mut_ptr());
796 let body = move |s: usize, e: usize| {
797 let qs = unsafe { std::slice::from_raw_parts(qp as *const f32, n) };
799 for i in s..e {
800 unsafe {
801 let x = sm.at(i);
802 *x = (*x - qs[i]).max(0.0);
803 }
804 }
805 };
806 match pool {
807 Some(pl) if n >= PAR_MIN => pl.run_rows(n, &body),
808 _ => body(0, n),
809 }
810 }
811 let sum: f32 = scratch.iter().sum();
812 if sum > 0.0 {
813 let inv = 1.0 / sum;
814 par_map(pool, scratch, &move |v| v * inv);
815 Some(categorical_sample(scratch, rng.next_f32()))
816 } else {
817 Some(categorical_sample(&p[..n], rng.next_f32()))
818 }
819}
820
821pub fn argmax(values: &[f32]) -> u32 {
833 if values.is_empty() {
834 return 0;
835 }
836 let n = values.len();
837 let mut best = [(0usize, f32::NEG_INFINITY); 4];
838 for (l, b) in best.iter_mut().enumerate() {
839 b.0 = l.min(n - 1);
840 }
841 let mut i = 0;
842 while i + 4 <= n {
843 for l in 0..4 {
844 let v = values[i + l];
845 if v >= best[l].1 {
846 best[l] = (i + l, v);
847 }
848 }
849 i += 4;
850 }
851 let mut bi = best[0].0;
852 let mut bv = best[0].1;
853 for b in &best[1..] {
854 if b.1 > bv || (b.1 == bv && b.0 > bi) {
855 bi = b.0;
856 bv = b.1;
857 }
858 }
859 while i < n {
860 if values[i] >= bv {
861 bv = values[i];
862 bi = i;
863 }
864 i += 1;
865 }
866 bi as u32
867}
868
869fn softmax_inplace(logits: &mut [f32]) {
870 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
871 let mut sum = 0.0f32;
872 for v in logits.iter_mut() {
873 *v = (*v - max_val).exp();
874 sum += *v;
875 }
876 if sum > 0.0 {
877 for v in logits.iter_mut() {
878 *v /= sum;
879 }
880 }
881}
882
883const PAR_MIN: usize = 1 << 14;
885
886fn par_map(pool: Option<&crate::pool::Pool>, buf: &mut [f32], f: &(dyn Fn(f32) -> f32 + Sync)) {
890 match pool {
891 Some(p) if buf.len() >= PAR_MIN => {
892 let out = crate::pool::SendMut::new(buf.as_mut_ptr());
893 p.run_rows(buf.len(), &move |s, e| {
894 for i in s..e {
895 unsafe {
898 let q = out.at(i);
899 *q = f(*q);
900 }
901 }
902 });
903 }
904 _ => {
905 for v in buf.iter_mut() {
906 *v = f(*v);
907 }
908 }
909 }
910}
911
912fn par_max(pool: Option<&crate::pool::Pool>, buf: &[f32], init: f32) -> f32 {
915 match pool {
916 Some(p) if buf.len() >= PAR_MIN => {
917 let m = std::sync::Mutex::new(init);
918 p.run_rows(buf.len(), &|s, e| {
919 let local = buf[s..e].iter().cloned().fold(init, f32::max);
920 let mut g = m.lock().unwrap();
921 *g = g.max(local);
922 });
923 m.into_inner().unwrap()
924 }
925 _ => buf.iter().cloned().fold(init, f32::max),
926 }
927}
928
929fn softmax_inplace_pool(pool: Option<&crate::pool::Pool>, logits: &mut [f32]) {
934 if pool.is_none() || logits.len() < PAR_MIN {
935 return softmax_inplace(logits);
936 }
937 let max_val = par_max(pool, logits, f32::NEG_INFINITY);
938 par_map(pool, logits, &move |v| (v - max_val).exp());
939 let sum: f32 = logits.iter().sum();
940 if sum > 0.0 {
941 par_map(pool, logits, &move |v| v / sum);
942 }
943}
944
945fn kth_largest(probs: &[f32], k: usize) -> f32 {
950 use std::cmp::Ordering;
951 let mut heap: Vec<f32> = Vec::with_capacity(k);
954 let desc = |a: f32, b: f32| b.partial_cmp(&a).unwrap_or(Ordering::Equal);
955 let sift_down = |h: &mut [f32], mut i: usize| {
956 let n = h.len();
957 loop {
958 let (l, r) = (2 * i + 1, 2 * i + 2);
959 let mut m = i;
960 if l < n && desc(h[l], h[m]) == Ordering::Greater {
962 m = l;
963 }
964 if r < n && desc(h[r], h[m]) == Ordering::Greater {
965 m = r;
966 }
967 if m == i {
968 break;
969 }
970 h.swap(i, m);
971 i = m;
972 }
973 };
974 let sift_up = |h: &mut [f32], mut i: usize| {
975 while i > 0 {
976 let parent = (i - 1) / 2;
977 if desc(h[i], h[parent]) == Ordering::Greater {
978 h.swap(i, parent);
979 i = parent;
980 } else {
981 break;
982 }
983 }
984 };
985 for &v in probs {
986 if heap.len() < k {
987 heap.push(v);
988 let n = heap.len();
989 sift_up(&mut heap, n - 1);
990 } else if desc(v, heap[0]) == Ordering::Less {
991 heap[0] = v;
993 sift_down(&mut heap, 0);
994 }
995 }
996 heap[0]
997}
998
999fn apply_top_k_pool(pool: Option<&crate::pool::Pool>, probs: &mut [f32], k: usize) {
1003 if k == 0 || k >= probs.len() {
1004 return;
1005 }
1006 let threshold = kth_largest(probs, k);
1007 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
1008}
1009
1010pub fn top1_prob_pool(
1015 pool: Option<&crate::pool::Pool>,
1016 scratch: &mut SamplerScratch,
1017 logits: &[f32],
1018 id: u32,
1019 temp: f32,
1020) -> f32 {
1021 let t = if temp > 1e-3 { temp } else { 1.0 };
1022 let max = par_max(pool, logits, f32::NEG_INFINITY);
1023 let mut e = std::mem::take(&mut scratch.topk);
1024 e.clear();
1025 e.extend_from_slice(logits);
1026 par_map(pool, &mut e, &move |v| ((v - max) / t).exp());
1027 let sum: f32 = e.iter().sum();
1028 let out = if sum > 0.0 {
1029 (((logits[id as usize] - max) / t).exp()) / sum
1030 } else {
1031 0.0
1032 };
1033 scratch.topk = e;
1034 out
1035}
1036
1037fn apply_repetition_penalty(
1038 logits: &mut [f32],
1039 past_tokens: &[u32],
1040 penalty: f32,
1041 scratch: &mut SamplerScratch,
1042) {
1043 let epoch = scratch.begin_seen(logits.len());
1044 for &tok in past_tokens {
1045 let idx = tok as usize;
1046 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
1047 scratch.seen_epoch[idx] = epoch;
1048 if logits[idx] > 0.0 {
1049 logits[idx] /= penalty;
1050 } else {
1051 logits[idx] *= penalty;
1052 }
1053 }
1054 }
1055}
1056
1057fn apply_top_k(probs: &mut [f32], k: usize, sel: &mut Vec<f32>) {
1062 if k == 0 || k >= probs.len() {
1063 return;
1064 }
1065 sel.clear();
1068 sel.extend_from_slice(probs);
1069 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1071 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1072 });
1073 let threshold = *kth;
1074 for p in probs.iter_mut() {
1075 if *p < threshold {
1076 *p = 0.0;
1077 }
1078 }
1079}
1080
1081fn apply_top_p(probs: &mut [f32], top_p: f32) {
1086 let mut indexed: Vec<(usize, f32)> = probs
1087 .iter()
1088 .copied()
1089 .enumerate()
1090 .filter(|&(_, p)| p > 0.0)
1091 .collect();
1092 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1093
1094 let mut cumsum = 0.0f32;
1095 let mut cutoff_idx = indexed.len();
1096 for (i, &(_, prob)) in indexed.iter().enumerate() {
1097 cumsum += prob;
1098 if cumsum >= top_p {
1099 cutoff_idx = i + 1;
1100 break;
1101 }
1102 }
1103
1104 for &(i, _) in &indexed[cutoff_idx..] {
1106 probs[i] = 0.0;
1107 }
1108}
1109
1110fn categorical_sample(probs: &[f32], r: f32) -> u32 {
1112 let mut cumsum = 0.0f32;
1113 for (i, &p) in probs.iter().enumerate() {
1114 cumsum += p;
1115 if r < cumsum {
1116 return i as u32;
1117 }
1118 }
1119 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
1120}
1121
1122#[cfg(test)]
1123mod tests {
1124 #[test]
1129 fn argmax_lanes_match_the_scalar_one_ties_and_all() {
1130 let scalar = |v: &[f32]| -> u32 {
1133 v.iter()
1134 .enumerate()
1135 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1136 .map(|(i, _)| i as u32)
1137 .unwrap_or(0)
1138 };
1139 for n in 0..40usize {
1140 for seed in 0..8u64 {
1141 let mut r = super::SplitMix64::new(seed * 7 + n as u64);
1142 let v: Vec<f32> = (0..n).map(|_| ((r.next_u64() % 5) as f32) - 2.0).collect();
1145 assert_eq!(super::argmax(&v), scalar(&v), "n={n} seed={seed} {v:?}");
1146 }
1147 }
1148 let flat = vec![f32::NEG_INFINITY; 13];
1149 assert_eq!(super::argmax(&flat), scalar(&flat));
1150 }
1151
1152 use super::*;
1153
1154 #[test]
1155 fn test_argmax() {
1156 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
1157 assert_eq!(argmax(&logits), 3);
1158 }
1159
1160 #[test]
1161 fn test_greedy_sampling() {
1162 let logits = vec![1.0, 5.0, 2.0, 3.0];
1163 let config = SamplerConfig {
1164 temperature: 0.0,
1165 ..Default::default()
1166 };
1167 let mut rng = SplitMix64::new(1);
1168 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
1169 }
1170
1171 #[test]
1174 fn argmax_penalized_matches_chain_argmax() {
1175 let pool = crate::pool::Pool::new(3);
1176 let n = 40_000usize;
1177 for seed in 0..8u64 {
1178 let mut r = SplitMix64::new(seed + 3);
1179 let logits: Vec<f32> = (0..n)
1181 .map(|_| ((r.next_u64() % 41) as f32 - 20.0) / 4.0)
1182 .collect();
1183 let past: Vec<u32> = (0..2000)
1184 .map(|_| (r.next_u64() % n as u64) as u32)
1185 .collect();
1186 for cfg in [
1187 SamplerConfig {
1188 temperature: 0.0,
1189 repetition_penalty: 1.1,
1190 ..Default::default()
1191 },
1192 SamplerConfig {
1193 temperature: 0.0,
1194 repetition_penalty: 1.0,
1195 presence_penalty: 1.5,
1196 ..Default::default()
1197 },
1198 SamplerConfig {
1199 temperature: 0.0,
1200 repetition_penalty: 1.3,
1201 presence_penalty: 0.7,
1202 suppress_tokens: vec![5, 77, 3000],
1203 ..Default::default()
1204 },
1205 ] {
1206 let mut s1 = SamplerScratch::default();
1207 let mut probs = Vec::new();
1208 chain(&logits, &cfg, &past, &mut s1, None, &mut probs);
1209 let want = argmax(&probs);
1210 let mut s2 = SamplerScratch::default();
1211 let got_serial = argmax_penalized(&logits, &cfg, &past, &mut s2, None);
1212 let got_pool = argmax_penalized(&logits, &cfg, &past, &mut s2, Some(&pool));
1213 assert_eq!(want, got_serial, "serial, seed {seed} cfg {cfg:?}");
1214 assert_eq!(want, got_pool, "pool, seed {seed} cfg {cfg:?}");
1215 }
1216 }
1217 }
1218
1219 #[test]
1222 fn pool_chain_matches_serial_bit_for_bit() {
1223 let pool = crate::pool::Pool::new(3);
1224 let n = 40_000usize; for seed in 0..6u64 {
1226 let mut r = SplitMix64::new(seed + 11);
1227 let logits: Vec<f32> = (0..n)
1228 .map(|i| ((r.next_u64() % 2001) as f32 - 1000.0) / 90.0 + (i % 7) as f32 * 0.01)
1229 .collect();
1230 let past: Vec<u32> = (0..500).map(|_| (r.next_u64() % n as u64) as u32).collect();
1231 for cfg in [
1232 SamplerConfig::default(),
1233 SamplerConfig {
1234 temperature: 0.7,
1235 top_p: 0.8,
1236 top_k: 20,
1237 min_p: 0.0,
1238 presence_penalty: 1.5,
1239 repetition_penalty: 1.0,
1240 ..Default::default()
1241 },
1242 SamplerConfig {
1243 temperature: 1.0,
1244 top_p: 0.95,
1245 top_k: 20,
1246 min_p: 0.0,
1247 ..Default::default()
1248 },
1249 SamplerConfig {
1250 temperature: 1.3,
1251 top_p: 1.0,
1252 top_k: 0,
1253 min_p: 0.02,
1254 ..Default::default()
1255 },
1256 ] {
1257 let mut s1 = SamplerScratch::default();
1258 let mut s2 = SamplerScratch::default();
1259 for step in 0..5u64 {
1260 let mut r1 = SplitMix64::new(seed * 100 + step);
1261 let mut r2 = r1.clone();
1262 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1263 let b = sample_with_scratch_pool(
1264 &logits,
1265 &cfg,
1266 &past,
1267 &mut r2,
1268 &mut s2,
1269 Some(&pool),
1270 );
1271 assert_eq!(a, b, "seed {seed} step {step} cfg {cfg:?}");
1272 assert_eq!(s1.probs, s2.probs, "probs differ seed {seed} step {step}");
1274 }
1275 }
1276 }
1277 }
1278
1279 #[test]
1284 fn spec_accept_or_correct_reproduces_the_target() {
1285 let n = 40usize;
1286 let mk = |seed: u64, sharp: f32| -> Vec<f32> {
1287 let mut r = SplitMix64::new(seed);
1288 let mut v: Vec<f32> = (0..n)
1289 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1290 .collect();
1291 for i in 0..n {
1293 if (i * 7 + seed as usize) % 5 == 0 {
1294 v[i] = 0.0;
1295 }
1296 }
1297 let s: f32 = v.iter().sum();
1298 v.iter().map(|x| x / s).collect()
1299 };
1300 let p = mk(3, 3.0);
1301 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1302 .into_iter()
1303 .enumerate()
1304 {
1305 let mut rng = SplitMix64::new(77 + qi as u64);
1306 let mut counts = vec![0u64; n];
1307 let mut scratch = Vec::new();
1308 let trials = 400_000u64;
1309 for _ in 0..trials {
1310 let d = categorical_sample(&q, rng.next_f32());
1311 let t = match spec_accept_or_correct(&p, &q, d, &mut rng, &mut scratch, None) {
1312 None => d,
1313 Some(c) => c,
1314 };
1315 counts[t as usize] += 1;
1316 }
1317 let l1: f64 = (0..n)
1318 .map(|i| (counts[i] as f64 / trials as f64 - p[i] as f64).abs())
1319 .sum();
1320 eprintln!("spec q#{qi}: L1(empirical, p) = {l1:.4}");
1321 assert!(
1322 l1 < 0.01,
1323 "q#{qi}: empirical distribution drifted from p, L1 {l1}"
1324 );
1325 for i in 0..n {
1327 if p[i] == 0.0 {
1328 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1329 }
1330 }
1331 }
1332 }
1333
1334 #[test]
1338 fn sparse_chain_matches_the_dense_chain() {
1339 let pool = crate::pool::Pool::new(3);
1340 let n = 40_000usize; for seed in 0..5u64 {
1342 let mut r = SplitMix64::new(100 + seed);
1343 let logits: Vec<f32> = (0..n)
1344 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1345 .collect();
1346 let past: Vec<u32> = (0..400).map(|_| (r.next_u64() % n as u64) as u32).collect();
1347 for cfg in [
1348 SamplerConfig {
1349 temperature: 0.7,
1350 top_p: 0.8,
1351 top_k: 20,
1352 min_p: 0.0,
1353 presence_penalty: 1.5,
1354 repetition_penalty: 1.0,
1355 ..Default::default()
1356 },
1357 SamplerConfig {
1358 temperature: 1.0,
1359 top_p: 0.95,
1360 top_k: 40,
1361 min_p: 0.05,
1362 presence_penalty: 0.0,
1363 repetition_penalty: 1.1,
1364 ..Default::default()
1365 },
1366 SamplerConfig {
1367 temperature: 0.6,
1368 top_p: 1.0,
1369 top_k: 3,
1370 min_p: 0.0,
1371 presence_penalty: 0.0,
1372 repetition_penalty: 1.0,
1373 suppress_tokens: vec![5, 6, 7],
1374 ..Default::default()
1375 },
1376 ] {
1377 assert!(sparse_ok(&cfg));
1378 let mut sd = SamplerScratch::default();
1379 let mut dense = Vec::new();
1380 distribution_into(&logits, &cfg, &past, &mut sd, None, &mut dense);
1381 for pl in [None, Some(&pool)] {
1382 let mut ss = SamplerScratch::default();
1383 let mut sp = Vec::new();
1384 let ok = sparse_distribution_into(&logits, &cfg, &past, &mut ss, pl, &mut sp);
1385 assert!(ok, "seed {seed} cfg {cfg:?}");
1386 let dense_nz: Vec<(u32, f32)> = dense
1387 .iter()
1388 .enumerate()
1389 .filter(|&(_, &v)| v > 0.0)
1390 .map(|(i, &v)| (i as u32, v))
1391 .collect();
1392 assert_eq!(
1393 dense_nz.len(),
1394 sp.len(),
1395 "seed {seed} pool {} cfg {cfg:?}: support {:?} vs {:?}",
1396 pl.is_some(),
1397 dense_nz,
1398 sp
1399 );
1400 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1401 assert_eq!(a.0, b.0, "seed {seed} cfg {cfg:?}: ids differ");
1402 assert!(
1403 (a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3),
1404 "seed {seed} cfg {cfg:?}: prob {} vs {}",
1405 a.1,
1406 b.1
1407 );
1408 }
1409 let mut agree = 0usize;
1412 let trials = 400usize;
1413 for k in 0..trials as u64 {
1414 let mut r1 = SplitMix64::new(500 + k);
1415 let mut r2 = SplitMix64::new(500 + k);
1416 let a = categorical_sample(&dense, r1.next_f32());
1417 let b = draw_sparse(&sp, &mut r2);
1418 agree += (a == b) as usize;
1419 }
1420 assert!(agree >= trials - 2, "seed {seed} cfg {cfg:?}: agree {agree}/{trials}");
1421 let mut r1 = SplitMix64::new(9);
1423 let mut r2 = SplitMix64::new(9);
1424 let a = categorical_sample(&dense, r1.next_f32());
1425 let mut s3 = SamplerScratch::default();
1426 let b = sample_with_scratch_pool(&logits, &cfg, &past, &mut r2, &mut s3, pl);
1427 assert_eq!(a, b, "seed {seed} cfg {cfg:?}: entry draw");
1428 }
1429 }
1430 }
1431 }
1432
1433 #[test]
1436 fn spec_accept_or_correct_sparse_reproduces_the_target() {
1437 let n = 40usize;
1438 let mk = |seed: u64, sharp: f32| -> Vec<(u32, f32)> {
1439 let mut r = SplitMix64::new(seed);
1440 let mut v: Vec<f32> = (0..n)
1441 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1442 .collect();
1443 for i in 0..n {
1444 if (i * 7 + seed as usize) % 5 == 0 {
1445 v[i] = 0.0;
1446 }
1447 }
1448 let s: f32 = v.iter().sum();
1449 v.iter()
1450 .enumerate()
1451 .filter(|&(_, &x)| x > 0.0)
1452 .map(|(i, &x)| (i as u32, x / s))
1453 .collect()
1454 };
1455 let p = mk(3, 3.0);
1456 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1457 .into_iter()
1458 .enumerate()
1459 {
1460 let mut rng = SplitMix64::new(77 + qi as u64);
1461 let mut counts = vec![0u64; n];
1462 let mut res = Vec::new();
1463 let trials = 400_000u64;
1464 for _ in 0..trials {
1465 let d = draw_sparse(&q, &mut rng);
1466 let t = match spec_accept_or_correct_sparse(&p, &q, d, &mut rng, &mut res) {
1467 None => d,
1468 Some(c) => c,
1469 };
1470 counts[t as usize] += 1;
1471 }
1472 let l1: f64 = (0..n)
1473 .map(|i| (counts[i] as f64 / trials as f64 - sparse_get(&p, i as u32) as f64).abs())
1474 .sum();
1475 eprintln!("sparse spec q#{qi}: L1(empirical, p) = {l1:.4}");
1476 assert!(l1 < 0.01, "q#{qi}: drifted, L1 {l1}");
1477 for i in 0..n {
1478 if sparse_get(&p, i as u32) == 0.0 {
1479 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1480 }
1481 }
1482 }
1483 }
1484
1485 #[test]
1488 fn distribution_matches_the_sampler_draw() {
1489 let n = 20_000usize;
1490 let mut r = SplitMix64::new(9);
1491 let logits: Vec<f32> = (0..n)
1492 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1493 .collect();
1494 let past: Vec<u32> = (0..300).map(|_| (r.next_u64() % n as u64) as u32).collect();
1495 for cfg in [
1496 SamplerConfig::default(),
1497 SamplerConfig {
1498 temperature: 0.7,
1499 top_p: 0.8,
1500 top_k: 20,
1501 min_p: 0.0,
1502 presence_penalty: 1.5,
1503 repetition_penalty: 1.0,
1504 ..Default::default()
1505 },
1506 SamplerConfig {
1507 temperature: 0.0,
1508 repetition_penalty: 1.1,
1509 ..Default::default()
1510 },
1511 SamplerConfig {
1512 temperature: 0.0,
1513 repetition_penalty: 1.0,
1514 presence_penalty: 0.0,
1515 ..Default::default()
1516 },
1517 ] {
1518 let mut s1 = SamplerScratch::default();
1519 let mut s2 = SamplerScratch::default();
1520 for step in 0..4u64 {
1521 let mut r1 = SplitMix64::new(step + 1);
1522 let mut r2 = r1.clone();
1523 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1524 let mut dist = Vec::new();
1525 distribution_into(&logits, &cfg, &past, &mut s2, None, &mut dist);
1526 assert!(
1527 (dist.iter().sum::<f32>() - 1.0).abs() < 1e-3,
1528 "not normalized: {}",
1529 dist.iter().sum::<f32>()
1530 );
1531 let b = if cfg.temperature < 1e-6 {
1532 argmax(&dist)
1533 } else {
1534 draw(&dist, &mut r2)
1535 };
1536 assert_eq!(a, b, "cfg {cfg:?} step {step}");
1537 }
1538 }
1539 }
1540
1541 #[test]
1544 fn kth_largest_equals_select_nth() {
1545 for seed in 0..20u64 {
1546 let mut r = SplitMix64::new(seed);
1547 let n = 50 + (r.next_u64() % 3000) as usize;
1548 let v: Vec<f32> = (0..n)
1549 .map(|_| {
1550 if r.next_u64() % 3 == 0 {
1551 0.0
1552 } else {
1553 (r.next_u64() % 97) as f32 / 97.0
1554 }
1555 })
1556 .collect();
1557 for k in [1usize, 2, 5, 20, 40, n / 2, n - 1] {
1558 let mut sel = v.clone();
1559 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1560 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1561 });
1562 assert_eq!(kth_largest(&v, k), *kth, "seed {seed} k {k}");
1563 }
1564 }
1565 }
1566
1567 #[test]
1569 fn top1_prob_pool_matches_serial() {
1570 let pool = crate::pool::Pool::new(2);
1571 let n = 30_000usize;
1572 let mut r = SplitMix64::new(5);
1573 let logits: Vec<f32> = (0..n)
1574 .map(|_| ((r.next_u64() % 1000) as f32) / 37.0)
1575 .collect();
1576 let serial = |id: u32, temp: f32| -> f32 {
1577 let t = if temp > 1e-3 { temp } else { 1.0 };
1578 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
1579 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
1580 (((logits[id as usize] - max) / t).exp()) / sum
1581 };
1582 let mut sc = SamplerScratch::default();
1583 for (id, t) in [(3u32, 1.0f32), (777, 0.7), (29_999, 2.0), (12, 0.0)] {
1584 let a = serial(id, t);
1585 let b = top1_prob_pool(Some(&pool), &mut sc, &logits, id, t);
1586 assert_eq!(a.to_bits(), b.to_bits(), "id {id} t {t}: {a} vs {b}");
1587 }
1588 }
1589
1590 #[test]
1591 fn test_softmax() {
1592 let mut logits = vec![1.0, 2.0, 3.0];
1593 softmax_inplace(&mut logits);
1594 let sum: f32 = logits.iter().sum();
1595 assert!((sum - 1.0).abs() < 1e-5);
1596 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
1597 }
1598
1599 #[test]
1600 fn test_repetition_penalty() {
1601 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
1602 let mut scratch = SamplerScratch::default();
1603 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
1604 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
1605 }
1606
1607 #[test]
1608 fn repetition_penalty_applies_once_per_unique_token() {
1609 let mut logits = vec![1.0, 4.0, -6.0];
1610 let mut scratch = SamplerScratch::default();
1611 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
1612 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
1613 }
1614
1615 #[test]
1616 fn top_k_keeps_exactly_k() {
1617 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
1618 apply_top_k(&mut probs, 2, &mut Vec::new());
1619 let kept = probs.iter().filter(|&&p| p > 0.0).count();
1620 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
1621 assert!(probs[1] > 0.0 && probs[3] > 0.0);
1622 }
1623
1624 #[test]
1625 fn rng_reaches_full_cdf() {
1626 let probs = vec![0.25f32; 4];
1629 let mut rng = SplitMix64::new(42);
1630 let mut hits = [0usize; 4];
1631 for _ in 0..4000 {
1632 let i = categorical_sample(&probs, rng.next_f32()) as usize;
1633 hits[i] += 1;
1634 }
1635 for (i, &h) in hits.iter().enumerate() {
1636 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
1637 }
1638 }
1639
1640 #[test]
1641 fn same_seed_same_sequence() {
1642 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
1643 let config = SamplerConfig {
1644 temperature: 1.0,
1645 seed: Some(7),
1646 ..Default::default()
1647 };
1648 let run = |seed: u64| -> Vec<u32> {
1649 let mut rng = SplitMix64::new(seed);
1650 (0..16)
1651 .map(|_| sample(&logits, &config, &[], &mut rng))
1652 .collect()
1653 };
1654 assert_eq!(run(7), run(7), "same seed must reproduce");
1655 assert_ne!(run(7), run(8), "different seed must differ");
1656 }
1657}