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 sparse_cut: bool,
41}
42
43impl SamplerScratch {
44 fn begin_seen(&mut self, vocab_size: usize) -> u32 {
45 if self.seen_epoch.len() < vocab_size {
46 self.seen_epoch.resize(vocab_size, 0);
47 }
48 self.epoch = self.epoch.wrapping_add(1);
49 if self.epoch == 0 {
50 self.seen_epoch.fill(0);
51 self.epoch = 1;
52 }
53 self.epoch
54 }
55}
56
57impl SplitMix64 {
58 pub fn new(seed: u64) -> Self {
59 Self { state: seed }
60 }
61
62 pub fn from_entropy() -> Self {
64 let t = std::time::SystemTime::now()
65 .duration_since(std::time::UNIX_EPOCH)
66 .unwrap_or_default();
67 let addr = Box::into_raw(Box::new(0u8)) as u64;
68 unsafe { drop(Box::from_raw(addr as *mut u8)) };
70 Self::new(t.as_nanos() as u64 ^ addr.rotate_left(17) ^ 0x9E3779B97F4A7C15)
71 }
72
73 #[inline]
74 pub fn next_u64(&mut self) -> u64 {
75 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
76 let mut z = self.state;
77 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
78 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
79 z ^ (z >> 31)
80 }
81
82 #[inline]
84 pub fn next_f32(&mut self) -> f32 {
85 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct SamplerConfig {
92 pub temperature: f32,
93 pub top_p: f32,
94 pub top_k: u32,
95 pub repetition_penalty: f32,
96 pub min_p: f32,
97 #[serde(default)]
102 pub presence_penalty: f32,
103 #[serde(default)]
105 pub seed: Option<u64>,
106 #[serde(default)]
108 pub suppress_tokens: Vec<u32>,
109 #[serde(default)]
115 pub penalty_window: usize,
116}
117
118pub const BOUNDED_PENALTY_WINDOW: usize = 128;
121
122impl SamplerConfig {
123 pub fn penalty_past<'a>(&self, past: &'a [u32], bounded_native: bool) -> &'a [u32] {
127 let mut w = self.penalty_window;
128 if w == 0 && bounded_native {
129 w = std::env::var("CMF_PENALTY_WINDOW")
130 .ok()
131 .and_then(|v| v.parse::<usize>().ok())
132 .filter(|&v| v > 0)
133 .unwrap_or(BOUNDED_PENALTY_WINDOW);
134 }
135 if w == 0 {
136 past
137 } else {
138 &past[past.len().saturating_sub(w)..]
139 }
140 }
141}
142
143impl Default for SamplerConfig {
144 fn default() -> Self {
145 Self {
146 temperature: 0.7,
147 top_p: 0.9,
148 top_k: 40,
149 repetition_penalty: 1.1,
150 presence_penalty: 0.0,
151 min_p: 0.05,
152 seed: None,
153 suppress_tokens: Vec::new(),
154 penalty_window: 0,
155 }
156 }
157}
158
159pub const FILE_SAMPLING_KEYS: [&str; 7] = [
163 "temperature",
164 "top_p",
165 "top_k",
166 "min_p",
167 "repetition_penalty",
168 "presence_penalty",
169 "max_tokens",
170];
171
172pub fn file_max_tokens(provenance: Option<&serde_json::Value>) -> Option<u32> {
175 provenance
176 .and_then(|p| p.get("sampling"))
177 .and_then(|s| s.get("max_tokens"))
178 .and_then(|v| v.as_u64())
179 .filter(|&n| n > 0)
180 .map(|n| n.min(u32::MAX as u64) as u32)
181}
182
183impl SamplerConfig {
184 pub fn from_provenance(provenance: Option<&serde_json::Value>) -> Self {
191 let mut c = Self::default();
192 let Some(s) = provenance.and_then(|p| p.get("sampling")) else {
193 return c;
194 };
195 let f = |k: &str| s.get(k).and_then(|v| v.as_f64()).filter(|v| v.is_finite());
196 if let Some(v) = f("temperature") {
197 c.temperature = v as f32;
198 }
199 if let Some(v) = f("top_p") {
200 c.top_p = v as f32;
201 }
202 if let Some(v) = s.get("top_k").and_then(|v| v.as_u64()) {
203 c.top_k = v.min(u32::MAX as u64) as u32;
204 }
205 if let Some(v) = f("min_p") {
206 c.min_p = v as f32;
207 }
208 if let Some(v) = f("repetition_penalty") {
209 c.repetition_penalty = v as f32;
210 }
211 if let Some(v) = f("presence_penalty") {
212 c.presence_penalty = v as f32;
213 }
214 c
215 }
216
217 pub fn file_has_defaults(provenance: Option<&serde_json::Value>) -> bool {
219 provenance
220 .and_then(|p| p.get("sampling"))
221 .is_some_and(|s| s.as_object().is_some_and(|o| !o.is_empty()))
222 }
223}
224
225pub fn sample(
228 logits: &[f32],
229 config: &SamplerConfig,
230 past_tokens: &[u32],
231 rng: &mut SplitMix64,
232) -> u32 {
233 let mut scratch = SamplerScratch::default();
234 sample_with_scratch(logits, config, past_tokens, rng, &mut scratch)
235}
236
237pub fn sample_with_scratch(
239 logits: &[f32],
240 config: &SamplerConfig,
241 past_tokens: &[u32],
242 rng: &mut SplitMix64,
243 scratch: &mut SamplerScratch,
244) -> u32 {
245 sample_with_scratch_pool(logits, config, past_tokens, rng, scratch, None)
246}
247
248pub fn sample_with_scratch_pool(
264 logits: &[f32],
265 config: &SamplerConfig,
266 past_tokens: &[u32],
267 rng: &mut SplitMix64,
268 scratch: &mut SamplerScratch,
269 pool: Option<&crate::pool::Pool>,
270) -> u32 {
271 if config.temperature < 1e-6
272 && config.repetition_penalty == 1.0
273 && config.presence_penalty == 0.0
274 && config.suppress_tokens.is_empty()
275 {
276 return argmax(logits);
277 }
278 if config.temperature < 1e-6 {
284 return argmax_penalized(logits, config, past_tokens, scratch, pool);
286 }
287 if sparse_ok(config) {
288 let mut sp = std::mem::take(&mut scratch.sparse);
290 let ok = sparse_distribution_into(logits, config, past_tokens, scratch, pool, &mut sp);
291 let t = if ok {
292 draw_sparse(&sp, rng)
293 } else {
294 argmax(logits)
295 };
296 scratch.sparse = sp;
297 return t;
298 }
299 if nucleus_sparse_ok(config, logits.len()) {
306 let capped = SamplerConfig {
307 top_k: SPARSE_TOPK_MAX as u32,
308 ..config.clone()
309 };
310 let mut sp = std::mem::take(&mut scratch.sparse);
311 let ok = sparse_distribution_into(logits, &capped, past_tokens, scratch, pool, &mut sp);
312 if ok && scratch.sparse_cut {
313 let t = draw_sparse(&sp, rng);
314 scratch.sparse = sp;
315 return t;
316 }
317 scratch.sparse = sp;
318 }
319 let mut probs = std::mem::take(&mut scratch.probs);
320 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
321
322 let mut done = |probs: Vec<f32>, tok: u32| -> u32 {
323 scratch.probs = probs;
324 tok
325 };
326
327 if !normalized {
328 let t = argmax(logits);
330 return done(probs, t);
331 }
332 let t = categorical_sample(&probs, rng.next_f32());
333 done(probs, t)
334}
335
336pub fn argmax_penalized(
346 logits: &[f32],
347 config: &SamplerConfig,
348 past_tokens: &[u32],
349 scratch: &mut SamplerScratch,
350 pool: Option<&crate::pool::Pool>,
351) -> u32 {
352 let n = logits.len();
353 if config.repetition_penalty == 1.0
354 && config.presence_penalty == 0.0
355 && config.suppress_tokens.is_empty()
356 {
357 return argmax(logits);
358 }
359 let epoch = scratch.begin_seen(n);
361 for &tok in past_tokens {
362 let idx = tok as usize;
363 if idx < n {
364 scratch.seen_epoch[idx] = epoch;
365 }
366 }
367 let rep = config.repetition_penalty;
368 let pres = config.presence_penalty;
369 let suppress = &config.suppress_tokens;
371 let seen = &scratch.seen_epoch;
372 let value = |i: usize| -> f32 {
373 let mut v = logits[i];
374 if suppress.iter().any(|&t| t as usize == i) {
375 return f32::NEG_INFINITY;
376 }
377 if seen[i] == epoch {
378 if rep != 1.0 {
379 if v > 0.0 {
380 v /= rep;
381 } else {
382 v *= rep;
383 }
384 }
385 if pres != 0.0 {
386 v -= pres;
387 }
388 }
389 v
390 };
391 let best_in = |s: usize, e: usize| -> (usize, f32) {
394 let mut bi = s;
395 let mut bv = f32::NEG_INFINITY;
396 for i in s..e {
397 let v = value(i);
398 if v >= bv {
399 bv = v;
400 bi = i;
401 }
402 }
403 (bi, bv)
404 };
405 match pool {
406 Some(p) if n >= PAR_MIN && suppress.is_empty() => {
407 let m = std::sync::Mutex::new(Vec::<(usize, f32)>::new());
408 p.run_rows(n, &|s, e| {
409 let r = best_in(s, e);
410 m.lock().unwrap().push(r);
411 });
412 let mut parts = m.into_inner().unwrap();
413 parts.sort_by_key(|(i, _)| *i);
417 let mut bi = 0usize;
418 let mut bv = f32::NEG_INFINITY;
419 for (i, v) in parts {
420 if v >= bv {
421 bv = v;
422 bi = i;
423 }
424 }
425 bi as u32
426 }
427 _ => best_in(0, n).0 as u32,
428 }
429}
430
431fn chain(
437 logits: &[f32],
438 config: &SamplerConfig,
439 past_tokens: &[u32],
440 scratch: &mut SamplerScratch,
441 pool: Option<&crate::pool::Pool>,
442 probs: &mut Vec<f32>,
443) -> bool {
444 probs.clear();
445 probs.extend_from_slice(logits);
446 apply_penalties(probs, config, past_tokens, scratch);
447
448 if config.temperature < 1e-6 {
449 return true;
450 }
451 if config.temperature != 1.0 {
452 let t = config.temperature;
453 par_map(pool, probs, &move |p| p / t);
454 }
455
456 softmax_inplace_pool(pool, probs);
457
458 if config.min_p > 0.0 {
459 let max_prob = par_max(pool, probs, 0.0);
460 let threshold = max_prob * config.min_p;
461 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
462 }
463
464 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
465 apply_top_k_pool(pool, probs, config.top_k as usize);
466 }
467
468 if config.top_p < 1.0 && config.top_p > 0.0 {
469 apply_top_p(probs, config.top_p);
470 }
471
472 let sum: f32 = probs.iter().sum();
473 if sum > 0.0 {
474 par_map(pool, probs, &move |p| p / sum);
475 true
476 } else {
477 false
478 }
479}
480
481fn apply_penalties(
486 probs: &mut [f32],
487 config: &SamplerConfig,
488 past_tokens: &[u32],
489 scratch: &mut SamplerScratch,
490) {
491 for &tok in &config.suppress_tokens {
492 if (tok as usize) < probs.len() {
493 probs[tok as usize] = f32::NEG_INFINITY;
494 }
495 }
496 if config.repetition_penalty != 1.0 {
497 apply_repetition_penalty(probs, past_tokens, config.repetition_penalty, scratch);
498 }
499 if config.presence_penalty != 0.0 {
500 let mut seen = std::mem::take(&mut scratch.presence_seen);
505 seen.clear();
506 seen.extend(past_tokens.iter().copied());
507 for &tok in &seen {
508 if (tok as usize) < probs.len() {
509 probs[tok as usize] -= config.presence_penalty;
510 }
511 }
512 scratch.presence_seen = seen;
513 }
514}
515
516fn config_penalized(config: &SamplerConfig) -> bool {
517 config.repetition_penalty != 1.0
518 || config.presence_penalty != 0.0
519 || !config.suppress_tokens.is_empty()
520}
521
522pub const SPARSE_TOPK_MAX: usize = 256;
525
526fn nucleus_sparse_ok(config: &SamplerConfig, vocab: usize) -> bool {
531 config.top_k == 0
532 && config.temperature >= 1e-6
533 && config.top_p > 0.0
534 && config.top_p < 1.0
535 && vocab > SPARSE_TOPK_MAX * 4
536 && {
537 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
539 *ON.get_or_init(|| std::env::var("CMF_NUCLEUS_SPARSE").as_deref() != Ok("0"))
540 }
541}
542
543pub fn sparse_ok(config: &SamplerConfig) -> bool {
544 config.temperature >= 1e-6 && config.top_k > 0 && (config.top_k as usize) <= SPARSE_TOPK_MAX
545}
546
547pub type Sparse = Vec<(u32, f32)>;
550
551pub fn sparse_distribution_into(
577 logits: &[f32],
578 config: &SamplerConfig,
579 past_tokens: &[u32],
580 scratch: &mut SamplerScratch,
581 pool: Option<&crate::pool::Pool>,
582 out: &mut Sparse,
583) -> bool {
584 debug_assert!(sparse_ok(config));
585 out.clear();
586 let k = (config.top_k as usize).min(logits.len());
587 if k == 0 {
588 return false;
589 }
590 let penalized = config_penalized(config);
591 let mut probs = std::mem::take(&mut scratch.probs);
592 if penalized {
593 probs.clear();
594 probs.extend_from_slice(logits);
595 apply_penalties(&mut probs, config, past_tokens, scratch);
596 }
597 let src: &[f32] = if penalized { &probs } else { logits };
598 let t = if config.temperature > 0.0 {
599 config.temperature
600 } else {
601 1.0
602 };
603 let mut cand = std::mem::take(&mut scratch.cand);
604 par_topk(pool, src, k, &mut cand, &mut scratch.cand_parts);
605 let ok = if let Some(&(_, lmax)) = cand.first().filter(|c| c.1.is_finite()) {
608 let sum_all = par_sum_exp(pool, src, lmax, t, &mut scratch.sum_parts);
609 let min_p = config.min_p;
612 let mut cum = 0.0f32;
613 let mut cut = false;
614 scratch.sparse_cut = false;
615 for &(id, l) in cand.iter() {
616 if cut {
617 break;
618 }
619 let e = ((l - lmax) / t).exp();
620 if min_p > 0.0 && e < min_p {
621 continue;
622 }
623 let pr = e / sum_all;
624 if pr <= 0.0 {
625 continue;
626 }
627 out.push((id, pr));
628 cum += pr;
629 if config.top_p < 1.0 && config.top_p > 0.0 && cum >= config.top_p {
630 cut = true;
631 }
632 }
633 scratch.sparse_cut = cut;
634 let sum: f32 = out.iter().map(|c| c.1).sum();
636 if sum > 0.0 {
637 for c in out.iter_mut() {
638 c.1 /= sum;
639 }
640 out.sort_unstable_by_key(|c| c.0);
641 true
642 } else {
643 out.clear();
644 false
645 }
646 } else {
647 false
648 };
649 scratch.cand = cand;
650 scratch.probs = probs;
651 ok
652}
653
654fn par_topk(
661 pool: Option<&crate::pool::Pool>,
662 src: &[f32],
663 k: usize,
664 out: &mut Vec<(u32, f32)>,
665 parts: &mut Vec<Vec<(u32, f32)>>,
666) {
667 let better = |a: (u32, f32), b: (u32, f32)| a.1 > b.1 || (a.1 == b.1 && a.0 < b.0);
668 let scan = |s: usize, e: usize, best: &mut Vec<(u32, f32)>| {
671 best.clear();
672 for i in s..e {
673 let c = (i as u32, src[i]);
674 if best.len() < k {
675 let pos = best
676 .iter()
677 .position(|&b| better(c, b))
678 .unwrap_or(best.len());
679 best.insert(pos, c);
680 } else if better(c, best[k - 1]) {
681 let pos = best.iter().position(|&b| better(c, b)).unwrap_or(k - 1);
682 best.pop();
683 best.insert(pos, c);
684 }
685 }
686 };
687 let by_value_desc = |a: &(u32, f32), b: &(u32, f32)| {
688 b.1.partial_cmp(&a.1)
689 .unwrap_or(std::cmp::Ordering::Equal)
690 .then(a.0.cmp(&b.0))
691 };
692 let cap = k * 4 + 64;
696 let gather = |s: usize, e: usize, kth: f32, slot: &mut Vec<(u32, f32)>| {
698 slot.clear();
699 for i in s..e {
700 let v = src[i];
701 if v >= kth {
702 slot.push((i as u32, v));
703 if slot.len() >= cap {
704 break;
705 }
706 }
707 }
708 };
709 out.clear();
710 match pool {
711 Some(p) if src.len() >= PAR_MIN => {
712 let n = src.len();
713 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
714 let ng = n.div_ceil(grain);
715 parts.resize_with(ng, Vec::new);
716 let pp = crate::pool::SendMutT::new(parts.as_mut_ptr());
717 p.run_rows(n, &|s, e| {
718 let slot = unsafe { &mut *pp.at(s / grain) };
721 scan(s, e, slot);
722 });
723 for g in 0..ng {
724 out.extend_from_slice(&parts[g]);
725 }
726 out.sort_unstable_by(by_value_desc);
727 out.truncate(k);
728 let Some(&(_, kth)) = out.last() else {
729 return;
730 };
731 if !kth.is_finite() {
732 return; }
734 p.run_rows(n, &|s, e| {
735 let slot = unsafe { &mut *pp.at(s / grain) };
736 gather(s, e, kth, slot);
737 });
738 out.clear();
739 for g in 0..ng {
740 out.extend_from_slice(&parts[g]);
741 if out.len() >= cap {
742 break;
743 }
744 }
745 out.sort_unstable_by(by_value_desc);
746 out.truncate(cap);
747 }
748 _ => {
749 scan(0, src.len(), out);
750 let Some(&(_, kth)) = out.last() else {
751 return;
752 };
753 if !kth.is_finite() {
754 return;
755 }
756 let mut all = std::mem::take(out);
757 gather(0, src.len(), kth, &mut all);
758 all.sort_unstable_by(by_value_desc);
759 all.truncate(cap);
760 *out = all;
761 }
762 }
763}
764
765fn par_sum_exp(
768 pool: Option<&crate::pool::Pool>,
769 src: &[f32],
770 lmax: f32,
771 t: f32,
772 parts: &mut Vec<f32>,
773) -> f32 {
774 let term = |s: usize, e: usize| -> f32 {
775 let mut acc = 0.0f32;
776 for &l in &src[s..e] {
777 acc += ((l - lmax) / t).exp();
778 }
779 acc
780 };
781 match pool {
782 Some(p) if src.len() >= PAR_MIN => {
783 let n = src.len();
784 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
785 let ng = n.div_ceil(grain);
786 parts.clear();
787 parts.resize(ng, 0.0);
788 let pp = crate::pool::SendMut::new(parts.as_mut_ptr());
789 p.run_rows(n, &|s, e| {
790 unsafe { *pp.at(s / grain) = term(s, e) };
792 });
793 parts.iter().sum()
794 }
795 _ => term(0, src.len()),
796 }
797}
798
799pub fn draw_sparse(p: &[(u32, f32)], rng: &mut SplitMix64) -> u32 {
803 let r = rng.next_f32();
804 let mut cum = 0.0f32;
805 for &(id, pr) in p {
806 cum += pr;
807 if r < cum {
808 return id;
809 }
810 }
811 p.iter().rev().find(|c| c.1 > 0.0).map(|c| c.0).unwrap_or(0)
812}
813
814fn sparse_get(p: &[(u32, f32)], id: u32) -> f32 {
815 p.binary_search_by_key(&id, |c| c.0)
816 .map(|i| p[i].1)
817 .unwrap_or(0.0)
818}
819
820pub fn spec_accept_or_correct_sparse(
825 p: &[(u32, f32)],
826 q: &[(u32, f32)],
827 d: u32,
828 rng: &mut SplitMix64,
829 res: &mut Sparse,
830) -> Option<u32> {
831 let (pd, qd) = (sparse_get(p, d), sparse_get(q, d));
832 let r = rng.next_f32();
833 if qd > 0.0 && r * qd < pd {
834 return None;
835 }
836 res.clear();
837 let mut total = 0.0f32;
838 for &(id, pi) in p {
839 let ri = pi - sparse_get(q, id);
840 if ri > 0.0 {
841 res.push((id, ri));
842 total += ri;
843 }
844 }
845 if total <= 0.0 {
846 return Some(draw_sparse(p, rng));
847 }
848 for c in res.iter_mut() {
849 c.1 /= total;
850 }
851 Some(draw_sparse(res, rng))
852}
853
854pub fn distribution_into(
861 logits: &[f32],
862 config: &SamplerConfig,
863 past_tokens: &[u32],
864 scratch: &mut SamplerScratch,
865 pool: Option<&crate::pool::Pool>,
866 out: &mut Vec<f32>,
867) {
868 let one_hot = |out: &mut Vec<f32>, t: usize, n: usize| {
869 out.clear();
870 out.resize(n, 0.0);
871 if t < n {
872 out[t] = 1.0;
873 }
874 };
875 if config.temperature < 1e-6
876 && config.repetition_penalty == 1.0
877 && config.presence_penalty == 0.0
878 && config.suppress_tokens.is_empty()
879 {
880 return one_hot(out, argmax(logits) as usize, logits.len());
881 }
882 let mut probs = std::mem::take(&mut scratch.probs);
883 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
884 if config.temperature < 1e-6 {
885 let t = argmax(&probs) as usize;
886 scratch.probs = probs;
887 return one_hot(out, t, logits.len());
888 }
889 if !normalized {
890 scratch.probs = probs;
891 return one_hot(out, argmax(logits) as usize, logits.len());
892 }
893 out.clear();
894 out.extend_from_slice(&probs);
895 scratch.probs = probs;
896}
897
898pub fn draw(probs: &[f32], rng: &mut SplitMix64) -> u32 {
900 categorical_sample(probs, rng.next_f32())
901}
902
903pub fn spec_accept_or_correct(
913 p: &[f32],
914 q: &[f32],
915 d: u32,
916 rng: &mut SplitMix64,
917 scratch: &mut Vec<f32>,
918 pool: Option<&crate::pool::Pool>,
919) -> Option<u32> {
920 let di = d as usize;
921 let (pd, qd) = (
922 p.get(di).copied().unwrap_or(0.0),
923 q.get(di).copied().unwrap_or(0.0),
924 );
925 let r = rng.next_f32();
926 if qd > 0.0 && r * qd < pd {
928 return None;
929 }
930 let n = p.len().min(q.len());
931 scratch.clear();
932 scratch.extend_from_slice(&p[..n]);
933 {
935 let qp = q.as_ptr() as usize;
936 let sm = crate::pool::SendMut::new(scratch.as_mut_ptr());
937 let body = move |s: usize, e: usize| {
938 let qs = unsafe { std::slice::from_raw_parts(qp as *const f32, n) };
940 for i in s..e {
941 unsafe {
942 let x = sm.at(i);
943 *x = (*x - qs[i]).max(0.0);
944 }
945 }
946 };
947 match pool {
948 Some(pl) if n >= PAR_MIN => pl.run_rows(n, &body),
949 _ => body(0, n),
950 }
951 }
952 let sum: f32 = scratch.iter().sum();
953 if sum > 0.0 {
954 let inv = 1.0 / sum;
955 par_map(pool, scratch, &move |v| v * inv);
956 Some(categorical_sample(scratch, rng.next_f32()))
957 } else {
958 Some(categorical_sample(&p[..n], rng.next_f32()))
959 }
960}
961
962pub fn argmax(values: &[f32]) -> u32 {
974 if values.is_empty() {
975 return 0;
976 }
977 let n = values.len();
978 let mut best = [(0usize, f32::NEG_INFINITY); 4];
979 for (l, b) in best.iter_mut().enumerate() {
980 b.0 = l.min(n - 1);
981 }
982 let mut i = 0;
983 while i + 4 <= n {
984 for l in 0..4 {
985 let v = values[i + l];
986 if v >= best[l].1 {
987 best[l] = (i + l, v);
988 }
989 }
990 i += 4;
991 }
992 let mut bi = best[0].0;
993 let mut bv = best[0].1;
994 for b in &best[1..] {
995 if b.1 > bv || (b.1 == bv && b.0 > bi) {
996 bi = b.0;
997 bv = b.1;
998 }
999 }
1000 while i < n {
1001 if values[i] >= bv {
1002 bv = values[i];
1003 bi = i;
1004 }
1005 i += 1;
1006 }
1007 bi as u32
1008}
1009
1010fn softmax_inplace(logits: &mut [f32]) {
1011 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1012 let mut sum = 0.0f32;
1013 for v in logits.iter_mut() {
1014 *v = (*v - max_val).exp();
1015 sum += *v;
1016 }
1017 if sum > 0.0 {
1018 for v in logits.iter_mut() {
1019 *v /= sum;
1020 }
1021 }
1022}
1023
1024const PAR_MIN: usize = 1 << 14;
1026
1027fn par_map(pool: Option<&crate::pool::Pool>, buf: &mut [f32], f: &(dyn Fn(f32) -> f32 + Sync)) {
1031 match pool {
1032 Some(p) if buf.len() >= PAR_MIN => {
1033 let out = crate::pool::SendMut::new(buf.as_mut_ptr());
1034 p.run_rows(buf.len(), &move |s, e| {
1035 for i in s..e {
1036 unsafe {
1039 let q = out.at(i);
1040 *q = f(*q);
1041 }
1042 }
1043 });
1044 }
1045 _ => {
1046 for v in buf.iter_mut() {
1047 *v = f(*v);
1048 }
1049 }
1050 }
1051}
1052
1053fn par_max(pool: Option<&crate::pool::Pool>, buf: &[f32], init: f32) -> f32 {
1056 match pool {
1057 Some(p) if buf.len() >= PAR_MIN => {
1058 let m = std::sync::Mutex::new(init);
1059 p.run_rows(buf.len(), &|s, e| {
1060 let local = buf[s..e].iter().cloned().fold(init, f32::max);
1061 let mut g = m.lock().unwrap();
1062 *g = g.max(local);
1063 });
1064 m.into_inner().unwrap()
1065 }
1066 _ => buf.iter().cloned().fold(init, f32::max),
1067 }
1068}
1069
1070fn softmax_inplace_pool(pool: Option<&crate::pool::Pool>, logits: &mut [f32]) {
1075 if pool.is_none() || logits.len() < PAR_MIN {
1076 return softmax_inplace(logits);
1077 }
1078 let max_val = par_max(pool, logits, f32::NEG_INFINITY);
1079 par_map(pool, logits, &move |v| (v - max_val).exp());
1080 let sum: f32 = logits.iter().sum();
1081 if sum > 0.0 {
1082 par_map(pool, logits, &move |v| v / sum);
1083 }
1084}
1085
1086fn kth_largest(probs: &[f32], k: usize) -> f32 {
1091 use std::cmp::Ordering;
1092 let mut heap: Vec<f32> = Vec::with_capacity(k);
1095 let desc = |a: f32, b: f32| b.partial_cmp(&a).unwrap_or(Ordering::Equal);
1096 let sift_down = |h: &mut [f32], mut i: usize| {
1097 let n = h.len();
1098 loop {
1099 let (l, r) = (2 * i + 1, 2 * i + 2);
1100 let mut m = i;
1101 if l < n && desc(h[l], h[m]) == Ordering::Greater {
1103 m = l;
1104 }
1105 if r < n && desc(h[r], h[m]) == Ordering::Greater {
1106 m = r;
1107 }
1108 if m == i {
1109 break;
1110 }
1111 h.swap(i, m);
1112 i = m;
1113 }
1114 };
1115 let sift_up = |h: &mut [f32], mut i: usize| {
1116 while i > 0 {
1117 let parent = (i - 1) / 2;
1118 if desc(h[i], h[parent]) == Ordering::Greater {
1119 h.swap(i, parent);
1120 i = parent;
1121 } else {
1122 break;
1123 }
1124 }
1125 };
1126 for &v in probs {
1127 if heap.len() < k {
1128 heap.push(v);
1129 let n = heap.len();
1130 sift_up(&mut heap, n - 1);
1131 } else if desc(v, heap[0]) == Ordering::Less {
1132 heap[0] = v;
1134 sift_down(&mut heap, 0);
1135 }
1136 }
1137 heap[0]
1138}
1139
1140fn apply_top_k_pool(pool: Option<&crate::pool::Pool>, probs: &mut [f32], k: usize) {
1144 if k == 0 || k >= probs.len() {
1145 return;
1146 }
1147 let threshold = kth_largest(probs, k);
1148 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
1149}
1150
1151pub fn top1_prob_pool(
1156 pool: Option<&crate::pool::Pool>,
1157 scratch: &mut SamplerScratch,
1158 logits: &[f32],
1159 id: u32,
1160 temp: f32,
1161) -> f32 {
1162 let t = if temp > 1e-3 { temp } else { 1.0 };
1163 let max = par_max(pool, logits, f32::NEG_INFINITY);
1164 let mut e = std::mem::take(&mut scratch.topk);
1165 e.clear();
1166 e.extend_from_slice(logits);
1167 par_map(pool, &mut e, &move |v| ((v - max) / t).exp());
1168 let sum: f32 = e.iter().sum();
1169 let out = if sum > 0.0 {
1170 (((logits[id as usize] - max) / t).exp()) / sum
1171 } else {
1172 0.0
1173 };
1174 scratch.topk = e;
1175 out
1176}
1177
1178fn apply_repetition_penalty(
1179 logits: &mut [f32],
1180 past_tokens: &[u32],
1181 penalty: f32,
1182 scratch: &mut SamplerScratch,
1183) {
1184 let epoch = scratch.begin_seen(logits.len());
1185 for &tok in past_tokens {
1186 let idx = tok as usize;
1187 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
1188 scratch.seen_epoch[idx] = epoch;
1189 if logits[idx] > 0.0 {
1190 logits[idx] /= penalty;
1191 } else {
1192 logits[idx] *= penalty;
1193 }
1194 }
1195 }
1196}
1197
1198fn apply_top_k(probs: &mut [f32], k: usize, sel: &mut Vec<f32>) {
1203 if k == 0 || k >= probs.len() {
1204 return;
1205 }
1206 sel.clear();
1209 sel.extend_from_slice(probs);
1210 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1212 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1213 });
1214 let threshold = *kth;
1215 for p in probs.iter_mut() {
1216 if *p < threshold {
1217 *p = 0.0;
1218 }
1219 }
1220}
1221
1222fn apply_top_p(probs: &mut [f32], top_p: f32) {
1227 if apply_top_p_head(probs, top_p) {
1228 return;
1229 }
1230 apply_top_p_full(probs, top_p);
1231}
1232
1233const TOP_P_HEAD: usize = 1024;
1243
1244fn apply_top_p_head(probs: &mut [f32], top_p: f32) -> bool {
1245 let n = probs.len();
1246 if n <= TOP_P_HEAD * 4 {
1247 return false;
1248 }
1249 let mut head: Vec<(usize, f32)> = probs
1250 .iter()
1251 .copied()
1252 .enumerate()
1253 .filter(|&(_, p)| p > 0.0)
1254 .collect();
1255 if head.len() <= TOP_P_HEAD {
1256 return false;
1257 }
1258 let by_desc = |a: &(usize, f32), b: &(usize, f32)| {
1259 b.1.partial_cmp(&a.1)
1260 .unwrap_or(std::cmp::Ordering::Equal)
1261 .then(a.0.cmp(&b.0))
1262 };
1263 head.select_nth_unstable_by(TOP_P_HEAD - 1, by_desc);
1264 head.truncate(TOP_P_HEAD);
1265 head.sort_by(by_desc);
1266 let mut cumsum = 0.0f32;
1267 let mut cut = None;
1268 for (i, &(_, prob)) in head.iter().enumerate() {
1269 cumsum += prob;
1270 if cumsum >= top_p {
1271 cut = Some(i);
1272 break;
1273 }
1274 }
1275 let Some(cut) = cut else {
1276 return false;
1277 };
1278 let v = head[cut].1;
1279 let keep_eq = head[..=cut].iter().filter(|&&(_, p)| p == v).count();
1282 if head[TOP_P_HEAD - 1].1 == v {
1285 return false;
1286 }
1287 let mut eq_seen = 0usize;
1288 for p in probs.iter_mut() {
1289 if *p > v {
1290 continue;
1291 }
1292 if *p == v && eq_seen < keep_eq {
1293 eq_seen += 1;
1294 continue;
1295 }
1296 *p = 0.0;
1297 }
1298 true
1299}
1300
1301fn apply_top_p_full(probs: &mut [f32], top_p: f32) {
1302 let mut indexed: Vec<(usize, f32)> = probs
1303 .iter()
1304 .copied()
1305 .enumerate()
1306 .filter(|&(_, p)| p > 0.0)
1307 .collect();
1308 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1309
1310 let mut cumsum = 0.0f32;
1311 let mut cutoff_idx = indexed.len();
1312 for (i, &(_, prob)) in indexed.iter().enumerate() {
1313 cumsum += prob;
1314 if cumsum >= top_p {
1315 cutoff_idx = i + 1;
1316 break;
1317 }
1318 }
1319
1320 for &(i, _) in &indexed[cutoff_idx..] {
1322 probs[i] = 0.0;
1323 }
1324}
1325
1326fn categorical_sample(probs: &[f32], r: f32) -> u32 {
1328 let mut cumsum = 0.0f32;
1329 for (i, &p) in probs.iter().enumerate() {
1330 cumsum += p;
1331 if r < cumsum {
1332 return i as u32;
1333 }
1334 }
1335 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 #[test]
1345 fn argmax_lanes_match_the_scalar_one_ties_and_all() {
1346 let scalar = |v: &[f32]| -> u32 {
1349 v.iter()
1350 .enumerate()
1351 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1352 .map(|(i, _)| i as u32)
1353 .unwrap_or(0)
1354 };
1355 for n in 0..40usize {
1356 for seed in 0..8u64 {
1357 let mut r = super::SplitMix64::new(seed * 7 + n as u64);
1358 let v: Vec<f32> = (0..n).map(|_| ((r.next_u64() % 5) as f32) - 2.0).collect();
1361 assert_eq!(super::argmax(&v), scalar(&v), "n={n} seed={seed} {v:?}");
1362 }
1363 }
1364 let flat = vec![f32::NEG_INFINITY; 13];
1365 assert_eq!(super::argmax(&flat), scalar(&flat));
1366 }
1367
1368 use super::*;
1369
1370 #[test]
1371 fn test_argmax() {
1372 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
1373 assert_eq!(argmax(&logits), 3);
1374 }
1375
1376 #[test]
1377 fn test_greedy_sampling() {
1378 let logits = vec![1.0, 5.0, 2.0, 3.0];
1379 let config = SamplerConfig {
1380 temperature: 0.0,
1381 ..Default::default()
1382 };
1383 let mut rng = SplitMix64::new(1);
1384 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
1385 }
1386
1387 #[test]
1390 fn argmax_penalized_matches_chain_argmax() {
1391 let pool = crate::pool::Pool::new(3);
1392 let n = 40_000usize;
1393 for seed in 0..8u64 {
1394 let mut r = SplitMix64::new(seed + 3);
1395 let logits: Vec<f32> = (0..n)
1397 .map(|_| ((r.next_u64() % 41) as f32 - 20.0) / 4.0)
1398 .collect();
1399 let past: Vec<u32> = (0..2000)
1400 .map(|_| (r.next_u64() % n as u64) as u32)
1401 .collect();
1402 for cfg in [
1403 SamplerConfig {
1404 temperature: 0.0,
1405 repetition_penalty: 1.1,
1406 ..Default::default()
1407 },
1408 SamplerConfig {
1409 temperature: 0.0,
1410 repetition_penalty: 1.0,
1411 presence_penalty: 1.5,
1412 ..Default::default()
1413 },
1414 SamplerConfig {
1415 temperature: 0.0,
1416 repetition_penalty: 1.3,
1417 presence_penalty: 0.7,
1418 suppress_tokens: vec![5, 77, 3000],
1419 ..Default::default()
1420 },
1421 ] {
1422 let mut s1 = SamplerScratch::default();
1423 let mut probs = Vec::new();
1424 chain(&logits, &cfg, &past, &mut s1, None, &mut probs);
1425 let want = argmax(&probs);
1426 let mut s2 = SamplerScratch::default();
1427 let got_serial = argmax_penalized(&logits, &cfg, &past, &mut s2, None);
1428 let got_pool = argmax_penalized(&logits, &cfg, &past, &mut s2, Some(&pool));
1429 assert_eq!(want, got_serial, "serial, seed {seed} cfg {cfg:?}");
1430 assert_eq!(want, got_pool, "pool, seed {seed} cfg {cfg:?}");
1431 }
1432 }
1433 }
1434
1435 #[test]
1438 fn pool_chain_matches_serial_bit_for_bit() {
1439 let pool = crate::pool::Pool::new(3);
1440 let n = 40_000usize; for seed in 0..6u64 {
1442 let mut r = SplitMix64::new(seed + 11);
1443 let logits: Vec<f32> = (0..n)
1444 .map(|i| ((r.next_u64() % 2001) as f32 - 1000.0) / 90.0 + (i % 7) as f32 * 0.01)
1445 .collect();
1446 let past: Vec<u32> = (0..500).map(|_| (r.next_u64() % n as u64) as u32).collect();
1447 for cfg in [
1448 SamplerConfig::default(),
1449 SamplerConfig {
1450 temperature: 0.7,
1451 top_p: 0.8,
1452 top_k: 20,
1453 min_p: 0.0,
1454 presence_penalty: 1.5,
1455 repetition_penalty: 1.0,
1456 ..Default::default()
1457 },
1458 SamplerConfig {
1459 temperature: 1.0,
1460 top_p: 0.95,
1461 top_k: 20,
1462 min_p: 0.0,
1463 ..Default::default()
1464 },
1465 SamplerConfig {
1466 temperature: 1.3,
1467 top_p: 1.0,
1468 top_k: 0,
1469 min_p: 0.02,
1470 ..Default::default()
1471 },
1472 ] {
1473 let mut s1 = SamplerScratch::default();
1474 let mut s2 = SamplerScratch::default();
1475 for step in 0..5u64 {
1476 let mut r1 = SplitMix64::new(seed * 100 + step);
1477 let mut r2 = r1.clone();
1478 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1479 let b = sample_with_scratch_pool(
1480 &logits,
1481 &cfg,
1482 &past,
1483 &mut r2,
1484 &mut s2,
1485 Some(&pool),
1486 );
1487 assert_eq!(a, b, "seed {seed} step {step} cfg {cfg:?}");
1488 assert_eq!(s1.probs, s2.probs, "probs differ seed {seed} step {step}");
1490 }
1491 }
1492 }
1493 }
1494
1495 #[test]
1500 fn spec_accept_or_correct_reproduces_the_target() {
1501 let n = 40usize;
1502 let mk = |seed: u64, sharp: f32| -> Vec<f32> {
1503 let mut r = SplitMix64::new(seed);
1504 let mut v: Vec<f32> = (0..n)
1505 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1506 .collect();
1507 for i in 0..n {
1509 if (i * 7 + seed as usize) % 5 == 0 {
1510 v[i] = 0.0;
1511 }
1512 }
1513 let s: f32 = v.iter().sum();
1514 v.iter().map(|x| x / s).collect()
1515 };
1516 let p = mk(3, 3.0);
1517 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1518 .into_iter()
1519 .enumerate()
1520 {
1521 let mut rng = SplitMix64::new(77 + qi as u64);
1522 let mut counts = vec![0u64; n];
1523 let mut scratch = Vec::new();
1524 let trials = 400_000u64;
1525 for _ in 0..trials {
1526 let d = categorical_sample(&q, rng.next_f32());
1527 let t = match spec_accept_or_correct(&p, &q, d, &mut rng, &mut scratch, None) {
1528 None => d,
1529 Some(c) => c,
1530 };
1531 counts[t as usize] += 1;
1532 }
1533 let l1: f64 = (0..n)
1534 .map(|i| (counts[i] as f64 / trials as f64 - p[i] as f64).abs())
1535 .sum();
1536 eprintln!("spec q#{qi}: L1(empirical, p) = {l1:.4}");
1537 assert!(
1538 l1 < 0.01,
1539 "q#{qi}: empirical distribution drifted from p, L1 {l1}"
1540 );
1541 for i in 0..n {
1543 if p[i] == 0.0 {
1544 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1545 }
1546 }
1547 }
1548 }
1549
1550 #[test]
1554 fn sparse_chain_matches_the_dense_chain() {
1555 let pool = crate::pool::Pool::new(3);
1556 let n = 40_000usize; for seed in 0..5u64 {
1558 let mut r = SplitMix64::new(100 + seed);
1559 let logits: Vec<f32> = (0..n)
1560 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1561 .collect();
1562 let past: Vec<u32> = (0..400).map(|_| (r.next_u64() % n as u64) as u32).collect();
1563 for cfg in [
1564 SamplerConfig {
1565 temperature: 0.7,
1566 top_p: 0.8,
1567 top_k: 20,
1568 min_p: 0.0,
1569 presence_penalty: 1.5,
1570 repetition_penalty: 1.0,
1571 ..Default::default()
1572 },
1573 SamplerConfig {
1574 temperature: 1.0,
1575 top_p: 0.95,
1576 top_k: 40,
1577 min_p: 0.05,
1578 presence_penalty: 0.0,
1579 repetition_penalty: 1.1,
1580 ..Default::default()
1581 },
1582 SamplerConfig {
1583 temperature: 0.6,
1584 top_p: 1.0,
1585 top_k: 3,
1586 min_p: 0.0,
1587 presence_penalty: 0.0,
1588 repetition_penalty: 1.0,
1589 suppress_tokens: vec![5, 6, 7],
1590 ..Default::default()
1591 },
1592 ] {
1593 assert!(sparse_ok(&cfg));
1594 let mut sd = SamplerScratch::default();
1595 let mut dense = Vec::new();
1596 distribution_into(&logits, &cfg, &past, &mut sd, None, &mut dense);
1597 for pl in [None, Some(&pool)] {
1598 let mut ss = SamplerScratch::default();
1599 let mut sp = Vec::new();
1600 let ok = sparse_distribution_into(&logits, &cfg, &past, &mut ss, pl, &mut sp);
1601 assert!(ok, "seed {seed} cfg {cfg:?}");
1602 let dense_nz: Vec<(u32, f32)> = dense
1603 .iter()
1604 .enumerate()
1605 .filter(|&(_, &v)| v > 0.0)
1606 .map(|(i, &v)| (i as u32, v))
1607 .collect();
1608 assert_eq!(
1609 dense_nz.len(),
1610 sp.len(),
1611 "seed {seed} pool {} cfg {cfg:?}: support {:?} vs {:?}",
1612 pl.is_some(),
1613 dense_nz,
1614 sp
1615 );
1616 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1617 assert_eq!(a.0, b.0, "seed {seed} cfg {cfg:?}: ids differ");
1618 assert!(
1619 (a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3),
1620 "seed {seed} cfg {cfg:?}: prob {} vs {}",
1621 a.1,
1622 b.1
1623 );
1624 }
1625 let mut agree = 0usize;
1628 let trials = 400usize;
1629 for k in 0..trials as u64 {
1630 let mut r1 = SplitMix64::new(500 + k);
1631 let mut r2 = SplitMix64::new(500 + k);
1632 let a = categorical_sample(&dense, r1.next_f32());
1633 let b = draw_sparse(&sp, &mut r2);
1634 agree += (a == b) as usize;
1635 }
1636 assert!(
1637 agree >= trials - 2,
1638 "seed {seed} cfg {cfg:?}: agree {agree}/{trials}"
1639 );
1640 let mut r1 = SplitMix64::new(9);
1642 let mut r2 = SplitMix64::new(9);
1643 let a = categorical_sample(&dense, r1.next_f32());
1644 let mut s3 = SamplerScratch::default();
1645 let b = sample_with_scratch_pool(&logits, &cfg, &past, &mut r2, &mut s3, pl);
1646 assert_eq!(a, b, "seed {seed} cfg {cfg:?}: entry draw");
1647 }
1648 }
1649 }
1650 }
1651
1652 #[test]
1655 fn spec_accept_or_correct_sparse_reproduces_the_target() {
1656 let n = 40usize;
1657 let mk = |seed: u64, sharp: f32| -> Vec<(u32, f32)> {
1658 let mut r = SplitMix64::new(seed);
1659 let mut v: Vec<f32> = (0..n)
1660 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1661 .collect();
1662 for i in 0..n {
1663 if (i * 7 + seed as usize) % 5 == 0 {
1664 v[i] = 0.0;
1665 }
1666 }
1667 let s: f32 = v.iter().sum();
1668 v.iter()
1669 .enumerate()
1670 .filter(|&(_, &x)| x > 0.0)
1671 .map(|(i, &x)| (i as u32, x / s))
1672 .collect()
1673 };
1674 let p = mk(3, 3.0);
1675 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1676 .into_iter()
1677 .enumerate()
1678 {
1679 let mut rng = SplitMix64::new(77 + qi as u64);
1680 let mut counts = vec![0u64; n];
1681 let mut res = Vec::new();
1682 let trials = 400_000u64;
1683 for _ in 0..trials {
1684 let d = draw_sparse(&q, &mut rng);
1685 let t = match spec_accept_or_correct_sparse(&p, &q, d, &mut rng, &mut res) {
1686 None => d,
1687 Some(c) => c,
1688 };
1689 counts[t as usize] += 1;
1690 }
1691 let l1: f64 = (0..n)
1692 .map(|i| (counts[i] as f64 / trials as f64 - sparse_get(&p, i as u32) as f64).abs())
1693 .sum();
1694 eprintln!("sparse spec q#{qi}: L1(empirical, p) = {l1:.4}");
1695 assert!(l1 < 0.01, "q#{qi}: drifted, L1 {l1}");
1696 for i in 0..n {
1697 if sparse_get(&p, i as u32) == 0.0 {
1698 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1699 }
1700 }
1701 }
1702 }
1703
1704 #[test]
1707 fn distribution_matches_the_sampler_draw() {
1708 let n = 20_000usize;
1709 let mut r = SplitMix64::new(9);
1710 let logits: Vec<f32> = (0..n)
1711 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1712 .collect();
1713 let past: Vec<u32> = (0..300).map(|_| (r.next_u64() % n as u64) as u32).collect();
1714 for cfg in [
1715 SamplerConfig::default(),
1716 SamplerConfig {
1717 temperature: 0.7,
1718 top_p: 0.8,
1719 top_k: 20,
1720 min_p: 0.0,
1721 presence_penalty: 1.5,
1722 repetition_penalty: 1.0,
1723 ..Default::default()
1724 },
1725 SamplerConfig {
1726 temperature: 0.0,
1727 repetition_penalty: 1.1,
1728 ..Default::default()
1729 },
1730 SamplerConfig {
1731 temperature: 0.0,
1732 repetition_penalty: 1.0,
1733 presence_penalty: 0.0,
1734 ..Default::default()
1735 },
1736 ] {
1737 let mut s1 = SamplerScratch::default();
1738 let mut s2 = SamplerScratch::default();
1739 for step in 0..4u64 {
1740 let mut r1 = SplitMix64::new(step + 1);
1741 let mut r2 = r1.clone();
1742 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1743 let mut dist = Vec::new();
1744 distribution_into(&logits, &cfg, &past, &mut s2, None, &mut dist);
1745 assert!(
1746 (dist.iter().sum::<f32>() - 1.0).abs() < 1e-3,
1747 "not normalized: {}",
1748 dist.iter().sum::<f32>()
1749 );
1750 let b = if cfg.temperature < 1e-6 {
1751 argmax(&dist)
1752 } else {
1753 draw(&dist, &mut r2)
1754 };
1755 assert_eq!(a, b, "cfg {cfg:?} step {step}");
1756 }
1757 }
1758 }
1759
1760 #[test]
1763 fn kth_largest_equals_select_nth() {
1764 for seed in 0..20u64 {
1765 let mut r = SplitMix64::new(seed);
1766 let n = 50 + (r.next_u64() % 3000) as usize;
1767 let v: Vec<f32> = (0..n)
1768 .map(|_| {
1769 if r.next_u64() % 3 == 0 {
1770 0.0
1771 } else {
1772 (r.next_u64() % 97) as f32 / 97.0
1773 }
1774 })
1775 .collect();
1776 for k in [1usize, 2, 5, 20, 40, n / 2, n - 1] {
1777 let mut sel = v.clone();
1778 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1779 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1780 });
1781 assert_eq!(kth_largest(&v, k), *kth, "seed {seed} k {k}");
1782 }
1783 }
1784 }
1785
1786 #[test]
1788 fn top1_prob_pool_matches_serial() {
1789 let pool = crate::pool::Pool::new(2);
1790 let n = 30_000usize;
1791 let mut r = SplitMix64::new(5);
1792 let logits: Vec<f32> = (0..n)
1793 .map(|_| ((r.next_u64() % 1000) as f32) / 37.0)
1794 .collect();
1795 let serial = |id: u32, temp: f32| -> f32 {
1796 let t = if temp > 1e-3 { temp } else { 1.0 };
1797 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
1798 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
1799 (((logits[id as usize] - max) / t).exp()) / sum
1800 };
1801 let mut sc = SamplerScratch::default();
1802 for (id, t) in [(3u32, 1.0f32), (777, 0.7), (29_999, 2.0), (12, 0.0)] {
1803 let a = serial(id, t);
1804 let b = top1_prob_pool(Some(&pool), &mut sc, &logits, id, t);
1805 assert_eq!(a.to_bits(), b.to_bits(), "id {id} t {t}: {a} vs {b}");
1806 }
1807 }
1808
1809 #[test]
1810 fn test_softmax() {
1811 let mut logits = vec![1.0, 2.0, 3.0];
1812 softmax_inplace(&mut logits);
1813 let sum: f32 = logits.iter().sum();
1814 assert!((sum - 1.0).abs() < 1e-5);
1815 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
1816 }
1817
1818 #[test]
1819 fn test_repetition_penalty() {
1820 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
1821 let mut scratch = SamplerScratch::default();
1822 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
1823 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
1824 }
1825
1826 #[test]
1827 fn repetition_penalty_applies_once_per_unique_token() {
1828 let mut logits = vec![1.0, 4.0, -6.0];
1829 let mut scratch = SamplerScratch::default();
1830 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
1831 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
1832 }
1833
1834 #[test]
1838 fn nucleus_sparse_matches_the_dense_top_p() {
1839 let n = 50_000usize;
1840 let mut closed = 0;
1841 for seed in 0..8u64 {
1842 let mut r = SplitMix64::new(500 + seed);
1843 let logits: Vec<f32> = (0..n)
1844 .map(|i| {
1845 let base = ((r.next_u64() % 3000) as f32 - 1500.0) / 300.0;
1846 if i % 997 == 0 { base + 14.0 } else { base }
1847 })
1848 .collect();
1849 let cfg = SamplerConfig {
1850 temperature: 1.0,
1851 top_p: 0.95,
1852 top_k: 0,
1853 min_p: 0.0,
1854 repetition_penalty: 1.0,
1855 ..Default::default()
1856 };
1857 assert!(nucleus_sparse_ok(&cfg, n));
1858 let mut sd = SamplerScratch::default();
1859 let mut dense = Vec::new();
1860 distribution_into(&logits, &cfg, &[], &mut sd, None, &mut dense);
1861 let capped = SamplerConfig { top_k: SPARSE_TOPK_MAX as u32, ..cfg.clone() };
1862 let mut ss = SamplerScratch::default();
1863 let mut sp = Vec::new();
1864 let ok = sparse_distribution_into(&logits, &capped, &[], &mut ss, None, &mut sp);
1865 if !(ok && ss.sparse_cut) {
1866 continue;
1867 }
1868 closed += 1;
1869 let dense_nz: Vec<(u32, f32)> = dense
1870 .iter()
1871 .enumerate()
1872 .filter(|&(_, &v)| v > 0.0)
1873 .map(|(i, &v)| (i as u32, v))
1874 .collect();
1875 assert_eq!(dense_nz.len(), sp.len(), "seed {seed}");
1876 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1877 assert_eq!(a.0, b.0, "seed {seed}: ids differ");
1878 assert!((a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3), "seed {seed}: {} vs {}", a.1, b.1);
1879 }
1880 }
1881 assert!(closed >= 4, "the nucleus closed inside the cap only {closed} times");
1882 }
1883
1884 #[test]
1887 fn top_p_head_matches_the_full_sort() {
1888 for seed in 0..40u64 {
1889 let mut r = SplitMix64::new(seed);
1890 let n = 20_000usize;
1891 let flat = seed % 3 == 0;
1892 let raw: Vec<f32> = (0..n)
1893 .map(|i| {
1894 let u = r.next_f32();
1895 if flat {
1896 ((r.next_u64() % 7) as f32) * 0.5
1897 } else if i % 97 == 0 {
1898 8.0 + 4.0 * u
1899 } else {
1900 u * 3.0
1901 }
1902 })
1903 .collect();
1904 let mut probs = raw.clone();
1905 softmax_inplace_pool(None, &mut probs);
1906 for &tp in &[0.5f32, 0.9, 0.95, 0.999] {
1907 let mut a = probs.clone();
1908 let mut b = probs.clone();
1909 apply_top_p(&mut a, tp);
1910 apply_top_p_full(&mut b, tp);
1911 assert_eq!(a, b, "seed {seed} top_p {tp}");
1912 }
1913 }
1914 }
1915
1916 #[test]
1917 fn file_sampling_defaults_override_only_what_they_set() {
1918 let none = SamplerConfig::from_provenance(None);
1919 assert_eq!(none.top_k, SamplerConfig::default().top_k);
1920 let p = serde_json::json!({"sampling": {
1921 "temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0.0,
1922 "repetition_penalty": 1.0
1923 }});
1924 let c = SamplerConfig::from_provenance(Some(&p));
1925 assert_eq!(
1926 (c.temperature, c.top_p, c.top_k, c.min_p, c.repetition_penalty),
1927 (1.0, 0.95, 0, 0.0, 1.0)
1928 );
1929 assert_eq!(c.presence_penalty, SamplerConfig::default().presence_penalty);
1930 assert!(SamplerConfig::file_has_defaults(Some(&p)));
1931 assert!(!SamplerConfig::file_has_defaults(Some(&serde_json::json!({}))));
1932 }
1933
1934 #[test]
1935 fn top_k_keeps_exactly_k() {
1936 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
1937 apply_top_k(&mut probs, 2, &mut Vec::new());
1938 let kept = probs.iter().filter(|&&p| p > 0.0).count();
1939 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
1940 assert!(probs[1] > 0.0 && probs[3] > 0.0);
1941 }
1942
1943 #[test]
1944 fn rng_reaches_full_cdf() {
1945 let probs = vec![0.25f32; 4];
1948 let mut rng = SplitMix64::new(42);
1949 let mut hits = [0usize; 4];
1950 for _ in 0..4000 {
1951 let i = categorical_sample(&probs, rng.next_f32()) as usize;
1952 hits[i] += 1;
1953 }
1954 for (i, &h) in hits.iter().enumerate() {
1955 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
1956 }
1957 }
1958
1959 #[test]
1960 fn same_seed_same_sequence() {
1961 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
1962 let config = SamplerConfig {
1963 temperature: 1.0,
1964 seed: Some(7),
1965 ..Default::default()
1966 };
1967 let run = |seed: u64| -> Vec<u32> {
1968 let mut rng = SplitMix64::new(seed);
1969 (0..16)
1970 .map(|_| sample(&logits, &config, &[], &mut rng))
1971 .collect()
1972 };
1973 assert_eq!(run(7), run(7), "same seed must reproduce");
1974 assert_ne!(run(7), run(8), "different seed must differ");
1975 }
1976}