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
538 .iter()
539 .position(|&b| better(c, b))
540 .unwrap_or(best.len());
541 best.insert(pos, c);
542 } else if better(c, best[k - 1]) {
543 let pos = best.iter().position(|&b| better(c, b)).unwrap_or(k - 1);
544 best.pop();
545 best.insert(pos, c);
546 }
547 }
548 };
549 let by_value_desc = |a: &(u32, f32), b: &(u32, f32)| {
550 b.1.partial_cmp(&a.1)
551 .unwrap_or(std::cmp::Ordering::Equal)
552 .then(a.0.cmp(&b.0))
553 };
554 let cap = k * 4 + 64;
558 let gather = |s: usize, e: usize, kth: f32, slot: &mut Vec<(u32, f32)>| {
560 slot.clear();
561 for i in s..e {
562 let v = src[i];
563 if v >= kth {
564 slot.push((i as u32, v));
565 if slot.len() >= cap {
566 break;
567 }
568 }
569 }
570 };
571 out.clear();
572 match pool {
573 Some(p) if src.len() >= PAR_MIN => {
574 let n = src.len();
575 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
576 let ng = n.div_ceil(grain);
577 parts.resize_with(ng, Vec::new);
578 let pp = crate::pool::SendMutT::new(parts.as_mut_ptr());
579 p.run_rows(n, &|s, e| {
580 let slot = unsafe { &mut *pp.at(s / grain) };
583 scan(s, e, slot);
584 });
585 for g in 0..ng {
586 out.extend_from_slice(&parts[g]);
587 }
588 out.sort_unstable_by(by_value_desc);
589 out.truncate(k);
590 let Some(&(_, kth)) = out.last() else {
591 return;
592 };
593 if !kth.is_finite() {
594 return; }
596 p.run_rows(n, &|s, e| {
597 let slot = unsafe { &mut *pp.at(s / grain) };
598 gather(s, e, kth, slot);
599 });
600 out.clear();
601 for g in 0..ng {
602 out.extend_from_slice(&parts[g]);
603 if out.len() >= cap {
604 break;
605 }
606 }
607 out.sort_unstable_by(by_value_desc);
608 out.truncate(cap);
609 }
610 _ => {
611 scan(0, src.len(), out);
612 let Some(&(_, kth)) = out.last() else {
613 return;
614 };
615 if !kth.is_finite() {
616 return;
617 }
618 let mut all = std::mem::take(out);
619 gather(0, src.len(), kth, &mut all);
620 all.sort_unstable_by(by_value_desc);
621 all.truncate(cap);
622 *out = all;
623 }
624 }
625}
626
627fn par_sum_exp(
630 pool: Option<&crate::pool::Pool>,
631 src: &[f32],
632 lmax: f32,
633 t: f32,
634 parts: &mut Vec<f32>,
635) -> f32 {
636 let term = |s: usize, e: usize| -> f32 {
637 let mut acc = 0.0f32;
638 for &l in &src[s..e] {
639 acc += ((l - lmax) / t).exp();
640 }
641 acc
642 };
643 match pool {
644 Some(p) if src.len() >= PAR_MIN => {
645 let n = src.len();
646 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
647 let ng = n.div_ceil(grain);
648 parts.clear();
649 parts.resize(ng, 0.0);
650 let pp = crate::pool::SendMut::new(parts.as_mut_ptr());
651 p.run_rows(n, &|s, e| {
652 unsafe { *pp.at(s / grain) = term(s, e) };
654 });
655 parts.iter().sum()
656 }
657 _ => term(0, src.len()),
658 }
659}
660
661pub fn draw_sparse(p: &[(u32, f32)], rng: &mut SplitMix64) -> u32 {
665 let r = rng.next_f32();
666 let mut cum = 0.0f32;
667 for &(id, pr) in p {
668 cum += pr;
669 if r < cum {
670 return id;
671 }
672 }
673 p.iter().rev().find(|c| c.1 > 0.0).map(|c| c.0).unwrap_or(0)
674}
675
676fn sparse_get(p: &[(u32, f32)], id: u32) -> f32 {
677 p.binary_search_by_key(&id, |c| c.0)
678 .map(|i| p[i].1)
679 .unwrap_or(0.0)
680}
681
682pub fn spec_accept_or_correct_sparse(
687 p: &[(u32, f32)],
688 q: &[(u32, f32)],
689 d: u32,
690 rng: &mut SplitMix64,
691 res: &mut Sparse,
692) -> Option<u32> {
693 let (pd, qd) = (sparse_get(p, d), sparse_get(q, d));
694 let r = rng.next_f32();
695 if qd > 0.0 && r * qd < pd {
696 return None;
697 }
698 res.clear();
699 let mut total = 0.0f32;
700 for &(id, pi) in p {
701 let ri = pi - sparse_get(q, id);
702 if ri > 0.0 {
703 res.push((id, ri));
704 total += ri;
705 }
706 }
707 if total <= 0.0 {
708 return Some(draw_sparse(p, rng));
709 }
710 for c in res.iter_mut() {
711 c.1 /= total;
712 }
713 Some(draw_sparse(res, rng))
714}
715
716pub fn distribution_into(
723 logits: &[f32],
724 config: &SamplerConfig,
725 past_tokens: &[u32],
726 scratch: &mut SamplerScratch,
727 pool: Option<&crate::pool::Pool>,
728 out: &mut Vec<f32>,
729) {
730 let one_hot = |out: &mut Vec<f32>, t: usize, n: usize| {
731 out.clear();
732 out.resize(n, 0.0);
733 if t < n {
734 out[t] = 1.0;
735 }
736 };
737 if config.temperature < 1e-6
738 && config.repetition_penalty == 1.0
739 && config.presence_penalty == 0.0
740 && config.suppress_tokens.is_empty()
741 {
742 return one_hot(out, argmax(logits) as usize, logits.len());
743 }
744 let mut probs = std::mem::take(&mut scratch.probs);
745 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
746 if config.temperature < 1e-6 {
747 let t = argmax(&probs) as usize;
748 scratch.probs = probs;
749 return one_hot(out, t, logits.len());
750 }
751 if !normalized {
752 scratch.probs = probs;
753 return one_hot(out, argmax(logits) as usize, logits.len());
754 }
755 out.clear();
756 out.extend_from_slice(&probs);
757 scratch.probs = probs;
758}
759
760pub fn draw(probs: &[f32], rng: &mut SplitMix64) -> u32 {
762 categorical_sample(probs, rng.next_f32())
763}
764
765pub fn spec_accept_or_correct(
775 p: &[f32],
776 q: &[f32],
777 d: u32,
778 rng: &mut SplitMix64,
779 scratch: &mut Vec<f32>,
780 pool: Option<&crate::pool::Pool>,
781) -> Option<u32> {
782 let di = d as usize;
783 let (pd, qd) = (
784 p.get(di).copied().unwrap_or(0.0),
785 q.get(di).copied().unwrap_or(0.0),
786 );
787 let r = rng.next_f32();
788 if qd > 0.0 && r * qd < pd {
790 return None;
791 }
792 let n = p.len().min(q.len());
793 scratch.clear();
794 scratch.extend_from_slice(&p[..n]);
795 {
797 let qp = q.as_ptr() as usize;
798 let sm = crate::pool::SendMut::new(scratch.as_mut_ptr());
799 let body = move |s: usize, e: usize| {
800 let qs = unsafe { std::slice::from_raw_parts(qp as *const f32, n) };
802 for i in s..e {
803 unsafe {
804 let x = sm.at(i);
805 *x = (*x - qs[i]).max(0.0);
806 }
807 }
808 };
809 match pool {
810 Some(pl) if n >= PAR_MIN => pl.run_rows(n, &body),
811 _ => body(0, n),
812 }
813 }
814 let sum: f32 = scratch.iter().sum();
815 if sum > 0.0 {
816 let inv = 1.0 / sum;
817 par_map(pool, scratch, &move |v| v * inv);
818 Some(categorical_sample(scratch, rng.next_f32()))
819 } else {
820 Some(categorical_sample(&p[..n], rng.next_f32()))
821 }
822}
823
824pub fn argmax(values: &[f32]) -> u32 {
836 if values.is_empty() {
837 return 0;
838 }
839 let n = values.len();
840 let mut best = [(0usize, f32::NEG_INFINITY); 4];
841 for (l, b) in best.iter_mut().enumerate() {
842 b.0 = l.min(n - 1);
843 }
844 let mut i = 0;
845 while i + 4 <= n {
846 for l in 0..4 {
847 let v = values[i + l];
848 if v >= best[l].1 {
849 best[l] = (i + l, v);
850 }
851 }
852 i += 4;
853 }
854 let mut bi = best[0].0;
855 let mut bv = best[0].1;
856 for b in &best[1..] {
857 if b.1 > bv || (b.1 == bv && b.0 > bi) {
858 bi = b.0;
859 bv = b.1;
860 }
861 }
862 while i < n {
863 if values[i] >= bv {
864 bv = values[i];
865 bi = i;
866 }
867 i += 1;
868 }
869 bi as u32
870}
871
872fn softmax_inplace(logits: &mut [f32]) {
873 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
874 let mut sum = 0.0f32;
875 for v in logits.iter_mut() {
876 *v = (*v - max_val).exp();
877 sum += *v;
878 }
879 if sum > 0.0 {
880 for v in logits.iter_mut() {
881 *v /= sum;
882 }
883 }
884}
885
886const PAR_MIN: usize = 1 << 14;
888
889fn par_map(pool: Option<&crate::pool::Pool>, buf: &mut [f32], f: &(dyn Fn(f32) -> f32 + Sync)) {
893 match pool {
894 Some(p) if buf.len() >= PAR_MIN => {
895 let out = crate::pool::SendMut::new(buf.as_mut_ptr());
896 p.run_rows(buf.len(), &move |s, e| {
897 for i in s..e {
898 unsafe {
901 let q = out.at(i);
902 *q = f(*q);
903 }
904 }
905 });
906 }
907 _ => {
908 for v in buf.iter_mut() {
909 *v = f(*v);
910 }
911 }
912 }
913}
914
915fn par_max(pool: Option<&crate::pool::Pool>, buf: &[f32], init: f32) -> f32 {
918 match pool {
919 Some(p) if buf.len() >= PAR_MIN => {
920 let m = std::sync::Mutex::new(init);
921 p.run_rows(buf.len(), &|s, e| {
922 let local = buf[s..e].iter().cloned().fold(init, f32::max);
923 let mut g = m.lock().unwrap();
924 *g = g.max(local);
925 });
926 m.into_inner().unwrap()
927 }
928 _ => buf.iter().cloned().fold(init, f32::max),
929 }
930}
931
932fn softmax_inplace_pool(pool: Option<&crate::pool::Pool>, logits: &mut [f32]) {
937 if pool.is_none() || logits.len() < PAR_MIN {
938 return softmax_inplace(logits);
939 }
940 let max_val = par_max(pool, logits, f32::NEG_INFINITY);
941 par_map(pool, logits, &move |v| (v - max_val).exp());
942 let sum: f32 = logits.iter().sum();
943 if sum > 0.0 {
944 par_map(pool, logits, &move |v| v / sum);
945 }
946}
947
948fn kth_largest(probs: &[f32], k: usize) -> f32 {
953 use std::cmp::Ordering;
954 let mut heap: Vec<f32> = Vec::with_capacity(k);
957 let desc = |a: f32, b: f32| b.partial_cmp(&a).unwrap_or(Ordering::Equal);
958 let sift_down = |h: &mut [f32], mut i: usize| {
959 let n = h.len();
960 loop {
961 let (l, r) = (2 * i + 1, 2 * i + 2);
962 let mut m = i;
963 if l < n && desc(h[l], h[m]) == Ordering::Greater {
965 m = l;
966 }
967 if r < n && desc(h[r], h[m]) == Ordering::Greater {
968 m = r;
969 }
970 if m == i {
971 break;
972 }
973 h.swap(i, m);
974 i = m;
975 }
976 };
977 let sift_up = |h: &mut [f32], mut i: usize| {
978 while i > 0 {
979 let parent = (i - 1) / 2;
980 if desc(h[i], h[parent]) == Ordering::Greater {
981 h.swap(i, parent);
982 i = parent;
983 } else {
984 break;
985 }
986 }
987 };
988 for &v in probs {
989 if heap.len() < k {
990 heap.push(v);
991 let n = heap.len();
992 sift_up(&mut heap, n - 1);
993 } else if desc(v, heap[0]) == Ordering::Less {
994 heap[0] = v;
996 sift_down(&mut heap, 0);
997 }
998 }
999 heap[0]
1000}
1001
1002fn apply_top_k_pool(pool: Option<&crate::pool::Pool>, probs: &mut [f32], k: usize) {
1006 if k == 0 || k >= probs.len() {
1007 return;
1008 }
1009 let threshold = kth_largest(probs, k);
1010 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
1011}
1012
1013pub fn top1_prob_pool(
1018 pool: Option<&crate::pool::Pool>,
1019 scratch: &mut SamplerScratch,
1020 logits: &[f32],
1021 id: u32,
1022 temp: f32,
1023) -> f32 {
1024 let t = if temp > 1e-3 { temp } else { 1.0 };
1025 let max = par_max(pool, logits, f32::NEG_INFINITY);
1026 let mut e = std::mem::take(&mut scratch.topk);
1027 e.clear();
1028 e.extend_from_slice(logits);
1029 par_map(pool, &mut e, &move |v| ((v - max) / t).exp());
1030 let sum: f32 = e.iter().sum();
1031 let out = if sum > 0.0 {
1032 (((logits[id as usize] - max) / t).exp()) / sum
1033 } else {
1034 0.0
1035 };
1036 scratch.topk = e;
1037 out
1038}
1039
1040fn apply_repetition_penalty(
1041 logits: &mut [f32],
1042 past_tokens: &[u32],
1043 penalty: f32,
1044 scratch: &mut SamplerScratch,
1045) {
1046 let epoch = scratch.begin_seen(logits.len());
1047 for &tok in past_tokens {
1048 let idx = tok as usize;
1049 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
1050 scratch.seen_epoch[idx] = epoch;
1051 if logits[idx] > 0.0 {
1052 logits[idx] /= penalty;
1053 } else {
1054 logits[idx] *= penalty;
1055 }
1056 }
1057 }
1058}
1059
1060fn apply_top_k(probs: &mut [f32], k: usize, sel: &mut Vec<f32>) {
1065 if k == 0 || k >= probs.len() {
1066 return;
1067 }
1068 sel.clear();
1071 sel.extend_from_slice(probs);
1072 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1074 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1075 });
1076 let threshold = *kth;
1077 for p in probs.iter_mut() {
1078 if *p < threshold {
1079 *p = 0.0;
1080 }
1081 }
1082}
1083
1084fn apply_top_p(probs: &mut [f32], top_p: f32) {
1089 let mut indexed: Vec<(usize, f32)> = probs
1090 .iter()
1091 .copied()
1092 .enumerate()
1093 .filter(|&(_, p)| p > 0.0)
1094 .collect();
1095 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1096
1097 let mut cumsum = 0.0f32;
1098 let mut cutoff_idx = indexed.len();
1099 for (i, &(_, prob)) in indexed.iter().enumerate() {
1100 cumsum += prob;
1101 if cumsum >= top_p {
1102 cutoff_idx = i + 1;
1103 break;
1104 }
1105 }
1106
1107 for &(i, _) in &indexed[cutoff_idx..] {
1109 probs[i] = 0.0;
1110 }
1111}
1112
1113fn categorical_sample(probs: &[f32], r: f32) -> u32 {
1115 let mut cumsum = 0.0f32;
1116 for (i, &p) in probs.iter().enumerate() {
1117 cumsum += p;
1118 if r < cumsum {
1119 return i as u32;
1120 }
1121 }
1122 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 #[test]
1132 fn argmax_lanes_match_the_scalar_one_ties_and_all() {
1133 let scalar = |v: &[f32]| -> u32 {
1136 v.iter()
1137 .enumerate()
1138 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1139 .map(|(i, _)| i as u32)
1140 .unwrap_or(0)
1141 };
1142 for n in 0..40usize {
1143 for seed in 0..8u64 {
1144 let mut r = super::SplitMix64::new(seed * 7 + n as u64);
1145 let v: Vec<f32> = (0..n).map(|_| ((r.next_u64() % 5) as f32) - 2.0).collect();
1148 assert_eq!(super::argmax(&v), scalar(&v), "n={n} seed={seed} {v:?}");
1149 }
1150 }
1151 let flat = vec![f32::NEG_INFINITY; 13];
1152 assert_eq!(super::argmax(&flat), scalar(&flat));
1153 }
1154
1155 use super::*;
1156
1157 #[test]
1158 fn test_argmax() {
1159 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
1160 assert_eq!(argmax(&logits), 3);
1161 }
1162
1163 #[test]
1164 fn test_greedy_sampling() {
1165 let logits = vec![1.0, 5.0, 2.0, 3.0];
1166 let config = SamplerConfig {
1167 temperature: 0.0,
1168 ..Default::default()
1169 };
1170 let mut rng = SplitMix64::new(1);
1171 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
1172 }
1173
1174 #[test]
1177 fn argmax_penalized_matches_chain_argmax() {
1178 let pool = crate::pool::Pool::new(3);
1179 let n = 40_000usize;
1180 for seed in 0..8u64 {
1181 let mut r = SplitMix64::new(seed + 3);
1182 let logits: Vec<f32> = (0..n)
1184 .map(|_| ((r.next_u64() % 41) as f32 - 20.0) / 4.0)
1185 .collect();
1186 let past: Vec<u32> = (0..2000)
1187 .map(|_| (r.next_u64() % n as u64) as u32)
1188 .collect();
1189 for cfg in [
1190 SamplerConfig {
1191 temperature: 0.0,
1192 repetition_penalty: 1.1,
1193 ..Default::default()
1194 },
1195 SamplerConfig {
1196 temperature: 0.0,
1197 repetition_penalty: 1.0,
1198 presence_penalty: 1.5,
1199 ..Default::default()
1200 },
1201 SamplerConfig {
1202 temperature: 0.0,
1203 repetition_penalty: 1.3,
1204 presence_penalty: 0.7,
1205 suppress_tokens: vec![5, 77, 3000],
1206 ..Default::default()
1207 },
1208 ] {
1209 let mut s1 = SamplerScratch::default();
1210 let mut probs = Vec::new();
1211 chain(&logits, &cfg, &past, &mut s1, None, &mut probs);
1212 let want = argmax(&probs);
1213 let mut s2 = SamplerScratch::default();
1214 let got_serial = argmax_penalized(&logits, &cfg, &past, &mut s2, None);
1215 let got_pool = argmax_penalized(&logits, &cfg, &past, &mut s2, Some(&pool));
1216 assert_eq!(want, got_serial, "serial, seed {seed} cfg {cfg:?}");
1217 assert_eq!(want, got_pool, "pool, seed {seed} cfg {cfg:?}");
1218 }
1219 }
1220 }
1221
1222 #[test]
1225 fn pool_chain_matches_serial_bit_for_bit() {
1226 let pool = crate::pool::Pool::new(3);
1227 let n = 40_000usize; for seed in 0..6u64 {
1229 let mut r = SplitMix64::new(seed + 11);
1230 let logits: Vec<f32> = (0..n)
1231 .map(|i| ((r.next_u64() % 2001) as f32 - 1000.0) / 90.0 + (i % 7) as f32 * 0.01)
1232 .collect();
1233 let past: Vec<u32> = (0..500).map(|_| (r.next_u64() % n as u64) as u32).collect();
1234 for cfg in [
1235 SamplerConfig::default(),
1236 SamplerConfig {
1237 temperature: 0.7,
1238 top_p: 0.8,
1239 top_k: 20,
1240 min_p: 0.0,
1241 presence_penalty: 1.5,
1242 repetition_penalty: 1.0,
1243 ..Default::default()
1244 },
1245 SamplerConfig {
1246 temperature: 1.0,
1247 top_p: 0.95,
1248 top_k: 20,
1249 min_p: 0.0,
1250 ..Default::default()
1251 },
1252 SamplerConfig {
1253 temperature: 1.3,
1254 top_p: 1.0,
1255 top_k: 0,
1256 min_p: 0.02,
1257 ..Default::default()
1258 },
1259 ] {
1260 let mut s1 = SamplerScratch::default();
1261 let mut s2 = SamplerScratch::default();
1262 for step in 0..5u64 {
1263 let mut r1 = SplitMix64::new(seed * 100 + step);
1264 let mut r2 = r1.clone();
1265 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1266 let b = sample_with_scratch_pool(
1267 &logits,
1268 &cfg,
1269 &past,
1270 &mut r2,
1271 &mut s2,
1272 Some(&pool),
1273 );
1274 assert_eq!(a, b, "seed {seed} step {step} cfg {cfg:?}");
1275 assert_eq!(s1.probs, s2.probs, "probs differ seed {seed} step {step}");
1277 }
1278 }
1279 }
1280 }
1281
1282 #[test]
1287 fn spec_accept_or_correct_reproduces_the_target() {
1288 let n = 40usize;
1289 let mk = |seed: u64, sharp: f32| -> Vec<f32> {
1290 let mut r = SplitMix64::new(seed);
1291 let mut v: Vec<f32> = (0..n)
1292 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1293 .collect();
1294 for i in 0..n {
1296 if (i * 7 + seed as usize) % 5 == 0 {
1297 v[i] = 0.0;
1298 }
1299 }
1300 let s: f32 = v.iter().sum();
1301 v.iter().map(|x| x / s).collect()
1302 };
1303 let p = mk(3, 3.0);
1304 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1305 .into_iter()
1306 .enumerate()
1307 {
1308 let mut rng = SplitMix64::new(77 + qi as u64);
1309 let mut counts = vec![0u64; n];
1310 let mut scratch = Vec::new();
1311 let trials = 400_000u64;
1312 for _ in 0..trials {
1313 let d = categorical_sample(&q, rng.next_f32());
1314 let t = match spec_accept_or_correct(&p, &q, d, &mut rng, &mut scratch, None) {
1315 None => d,
1316 Some(c) => c,
1317 };
1318 counts[t as usize] += 1;
1319 }
1320 let l1: f64 = (0..n)
1321 .map(|i| (counts[i] as f64 / trials as f64 - p[i] as f64).abs())
1322 .sum();
1323 eprintln!("spec q#{qi}: L1(empirical, p) = {l1:.4}");
1324 assert!(
1325 l1 < 0.01,
1326 "q#{qi}: empirical distribution drifted from p, L1 {l1}"
1327 );
1328 for i in 0..n {
1330 if p[i] == 0.0 {
1331 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1332 }
1333 }
1334 }
1335 }
1336
1337 #[test]
1341 fn sparse_chain_matches_the_dense_chain() {
1342 let pool = crate::pool::Pool::new(3);
1343 let n = 40_000usize; for seed in 0..5u64 {
1345 let mut r = SplitMix64::new(100 + seed);
1346 let logits: Vec<f32> = (0..n)
1347 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1348 .collect();
1349 let past: Vec<u32> = (0..400).map(|_| (r.next_u64() % n as u64) as u32).collect();
1350 for cfg in [
1351 SamplerConfig {
1352 temperature: 0.7,
1353 top_p: 0.8,
1354 top_k: 20,
1355 min_p: 0.0,
1356 presence_penalty: 1.5,
1357 repetition_penalty: 1.0,
1358 ..Default::default()
1359 },
1360 SamplerConfig {
1361 temperature: 1.0,
1362 top_p: 0.95,
1363 top_k: 40,
1364 min_p: 0.05,
1365 presence_penalty: 0.0,
1366 repetition_penalty: 1.1,
1367 ..Default::default()
1368 },
1369 SamplerConfig {
1370 temperature: 0.6,
1371 top_p: 1.0,
1372 top_k: 3,
1373 min_p: 0.0,
1374 presence_penalty: 0.0,
1375 repetition_penalty: 1.0,
1376 suppress_tokens: vec![5, 6, 7],
1377 ..Default::default()
1378 },
1379 ] {
1380 assert!(sparse_ok(&cfg));
1381 let mut sd = SamplerScratch::default();
1382 let mut dense = Vec::new();
1383 distribution_into(&logits, &cfg, &past, &mut sd, None, &mut dense);
1384 for pl in [None, Some(&pool)] {
1385 let mut ss = SamplerScratch::default();
1386 let mut sp = Vec::new();
1387 let ok = sparse_distribution_into(&logits, &cfg, &past, &mut ss, pl, &mut sp);
1388 assert!(ok, "seed {seed} cfg {cfg:?}");
1389 let dense_nz: Vec<(u32, f32)> = dense
1390 .iter()
1391 .enumerate()
1392 .filter(|&(_, &v)| v > 0.0)
1393 .map(|(i, &v)| (i as u32, v))
1394 .collect();
1395 assert_eq!(
1396 dense_nz.len(),
1397 sp.len(),
1398 "seed {seed} pool {} cfg {cfg:?}: support {:?} vs {:?}",
1399 pl.is_some(),
1400 dense_nz,
1401 sp
1402 );
1403 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1404 assert_eq!(a.0, b.0, "seed {seed} cfg {cfg:?}: ids differ");
1405 assert!(
1406 (a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3),
1407 "seed {seed} cfg {cfg:?}: prob {} vs {}",
1408 a.1,
1409 b.1
1410 );
1411 }
1412 let mut agree = 0usize;
1415 let trials = 400usize;
1416 for k in 0..trials as u64 {
1417 let mut r1 = SplitMix64::new(500 + k);
1418 let mut r2 = SplitMix64::new(500 + k);
1419 let a = categorical_sample(&dense, r1.next_f32());
1420 let b = draw_sparse(&sp, &mut r2);
1421 agree += (a == b) as usize;
1422 }
1423 assert!(
1424 agree >= trials - 2,
1425 "seed {seed} cfg {cfg:?}: agree {agree}/{trials}"
1426 );
1427 let mut r1 = SplitMix64::new(9);
1429 let mut r2 = SplitMix64::new(9);
1430 let a = categorical_sample(&dense, r1.next_f32());
1431 let mut s3 = SamplerScratch::default();
1432 let b = sample_with_scratch_pool(&logits, &cfg, &past, &mut r2, &mut s3, pl);
1433 assert_eq!(a, b, "seed {seed} cfg {cfg:?}: entry draw");
1434 }
1435 }
1436 }
1437 }
1438
1439 #[test]
1442 fn spec_accept_or_correct_sparse_reproduces_the_target() {
1443 let n = 40usize;
1444 let mk = |seed: u64, sharp: f32| -> Vec<(u32, f32)> {
1445 let mut r = SplitMix64::new(seed);
1446 let mut v: Vec<f32> = (0..n)
1447 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1448 .collect();
1449 for i in 0..n {
1450 if (i * 7 + seed as usize) % 5 == 0 {
1451 v[i] = 0.0;
1452 }
1453 }
1454 let s: f32 = v.iter().sum();
1455 v.iter()
1456 .enumerate()
1457 .filter(|&(_, &x)| x > 0.0)
1458 .map(|(i, &x)| (i as u32, x / s))
1459 .collect()
1460 };
1461 let p = mk(3, 3.0);
1462 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1463 .into_iter()
1464 .enumerate()
1465 {
1466 let mut rng = SplitMix64::new(77 + qi as u64);
1467 let mut counts = vec![0u64; n];
1468 let mut res = Vec::new();
1469 let trials = 400_000u64;
1470 for _ in 0..trials {
1471 let d = draw_sparse(&q, &mut rng);
1472 let t = match spec_accept_or_correct_sparse(&p, &q, d, &mut rng, &mut res) {
1473 None => d,
1474 Some(c) => c,
1475 };
1476 counts[t as usize] += 1;
1477 }
1478 let l1: f64 = (0..n)
1479 .map(|i| (counts[i] as f64 / trials as f64 - sparse_get(&p, i as u32) as f64).abs())
1480 .sum();
1481 eprintln!("sparse spec q#{qi}: L1(empirical, p) = {l1:.4}");
1482 assert!(l1 < 0.01, "q#{qi}: drifted, L1 {l1}");
1483 for i in 0..n {
1484 if sparse_get(&p, i as u32) == 0.0 {
1485 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1486 }
1487 }
1488 }
1489 }
1490
1491 #[test]
1494 fn distribution_matches_the_sampler_draw() {
1495 let n = 20_000usize;
1496 let mut r = SplitMix64::new(9);
1497 let logits: Vec<f32> = (0..n)
1498 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1499 .collect();
1500 let past: Vec<u32> = (0..300).map(|_| (r.next_u64() % n as u64) as u32).collect();
1501 for cfg in [
1502 SamplerConfig::default(),
1503 SamplerConfig {
1504 temperature: 0.7,
1505 top_p: 0.8,
1506 top_k: 20,
1507 min_p: 0.0,
1508 presence_penalty: 1.5,
1509 repetition_penalty: 1.0,
1510 ..Default::default()
1511 },
1512 SamplerConfig {
1513 temperature: 0.0,
1514 repetition_penalty: 1.1,
1515 ..Default::default()
1516 },
1517 SamplerConfig {
1518 temperature: 0.0,
1519 repetition_penalty: 1.0,
1520 presence_penalty: 0.0,
1521 ..Default::default()
1522 },
1523 ] {
1524 let mut s1 = SamplerScratch::default();
1525 let mut s2 = SamplerScratch::default();
1526 for step in 0..4u64 {
1527 let mut r1 = SplitMix64::new(step + 1);
1528 let mut r2 = r1.clone();
1529 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1530 let mut dist = Vec::new();
1531 distribution_into(&logits, &cfg, &past, &mut s2, None, &mut dist);
1532 assert!(
1533 (dist.iter().sum::<f32>() - 1.0).abs() < 1e-3,
1534 "not normalized: {}",
1535 dist.iter().sum::<f32>()
1536 );
1537 let b = if cfg.temperature < 1e-6 {
1538 argmax(&dist)
1539 } else {
1540 draw(&dist, &mut r2)
1541 };
1542 assert_eq!(a, b, "cfg {cfg:?} step {step}");
1543 }
1544 }
1545 }
1546
1547 #[test]
1550 fn kth_largest_equals_select_nth() {
1551 for seed in 0..20u64 {
1552 let mut r = SplitMix64::new(seed);
1553 let n = 50 + (r.next_u64() % 3000) as usize;
1554 let v: Vec<f32> = (0..n)
1555 .map(|_| {
1556 if r.next_u64() % 3 == 0 {
1557 0.0
1558 } else {
1559 (r.next_u64() % 97) as f32 / 97.0
1560 }
1561 })
1562 .collect();
1563 for k in [1usize, 2, 5, 20, 40, n / 2, n - 1] {
1564 let mut sel = v.clone();
1565 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1566 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1567 });
1568 assert_eq!(kth_largest(&v, k), *kth, "seed {seed} k {k}");
1569 }
1570 }
1571 }
1572
1573 #[test]
1575 fn top1_prob_pool_matches_serial() {
1576 let pool = crate::pool::Pool::new(2);
1577 let n = 30_000usize;
1578 let mut r = SplitMix64::new(5);
1579 let logits: Vec<f32> = (0..n)
1580 .map(|_| ((r.next_u64() % 1000) as f32) / 37.0)
1581 .collect();
1582 let serial = |id: u32, temp: f32| -> f32 {
1583 let t = if temp > 1e-3 { temp } else { 1.0 };
1584 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
1585 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
1586 (((logits[id as usize] - max) / t).exp()) / sum
1587 };
1588 let mut sc = SamplerScratch::default();
1589 for (id, t) in [(3u32, 1.0f32), (777, 0.7), (29_999, 2.0), (12, 0.0)] {
1590 let a = serial(id, t);
1591 let b = top1_prob_pool(Some(&pool), &mut sc, &logits, id, t);
1592 assert_eq!(a.to_bits(), b.to_bits(), "id {id} t {t}: {a} vs {b}");
1593 }
1594 }
1595
1596 #[test]
1597 fn test_softmax() {
1598 let mut logits = vec![1.0, 2.0, 3.0];
1599 softmax_inplace(&mut logits);
1600 let sum: f32 = logits.iter().sum();
1601 assert!((sum - 1.0).abs() < 1e-5);
1602 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
1603 }
1604
1605 #[test]
1606 fn test_repetition_penalty() {
1607 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
1608 let mut scratch = SamplerScratch::default();
1609 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
1610 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
1611 }
1612
1613 #[test]
1614 fn repetition_penalty_applies_once_per_unique_token() {
1615 let mut logits = vec![1.0, 4.0, -6.0];
1616 let mut scratch = SamplerScratch::default();
1617 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
1618 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
1619 }
1620
1621 #[test]
1622 fn top_k_keeps_exactly_k() {
1623 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
1624 apply_top_k(&mut probs, 2, &mut Vec::new());
1625 let kept = probs.iter().filter(|&&p| p > 0.0).count();
1626 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
1627 assert!(probs[1] > 0.0 && probs[3] > 0.0);
1628 }
1629
1630 #[test]
1631 fn rng_reaches_full_cdf() {
1632 let probs = vec![0.25f32; 4];
1635 let mut rng = SplitMix64::new(42);
1636 let mut hits = [0usize; 4];
1637 for _ in 0..4000 {
1638 let i = categorical_sample(&probs, rng.next_f32()) as usize;
1639 hits[i] += 1;
1640 }
1641 for (i, &h) in hits.iter().enumerate() {
1642 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
1643 }
1644 }
1645
1646 #[test]
1647 fn same_seed_same_sequence() {
1648 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
1649 let config = SamplerConfig {
1650 temperature: 1.0,
1651 seed: Some(7),
1652 ..Default::default()
1653 };
1654 let run = |seed: u64| -> Vec<u32> {
1655 let mut rng = SplitMix64::new(seed);
1656 (0..16)
1657 .map(|_| sample(&logits, &config, &[], &mut rng))
1658 .collect()
1659 };
1660 assert_eq!(run(7), run(7), "same seed must reproduce");
1661 assert_ne!(run(7), run(8), "different seed must differ");
1662 }
1663}