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 #[serde(default)]
120 pub loop_stop: bool,
121}
122
123pub fn tail_repeats(ids: &[u32]) -> bool {
127 tail_repeat_excess(ids).is_some()
128}
129
130pub fn tail_repeat_excess(ids: &[u32]) -> Option<usize> {
133 let n = ids.len();
134 (4..=64usize).find_map(|p| {
135 let copies = if p < 12 { 3 } else { 2 };
136 (n >= p * copies && (1..copies).all(|c| ids[n - p..] == ids[n - (c + 1) * p..n - c * p]))
137 .then_some(p * (copies - 1))
138 })
139}
140
141pub const BOUNDED_PENALTY_WINDOW: usize = 128;
144
145impl SamplerConfig {
146 pub fn penalty_past<'a>(&self, past: &'a [u32], bounded_native: bool) -> &'a [u32] {
150 let mut w = self.penalty_window;
151 if w == 0 && bounded_native {
152 w = std::env::var("CMF_PENALTY_WINDOW")
153 .ok()
154 .and_then(|v| v.parse::<usize>().ok())
155 .filter(|&v| v > 0)
156 .unwrap_or(BOUNDED_PENALTY_WINDOW);
157 }
158 if w == 0 {
159 past
160 } else {
161 &past[past.len().saturating_sub(w)..]
162 }
163 }
164}
165
166impl Default for SamplerConfig {
167 fn default() -> Self {
168 Self {
169 temperature: 0.7,
170 top_p: 0.9,
171 top_k: 40,
172 repetition_penalty: 1.1,
173 presence_penalty: 0.0,
174 min_p: 0.05,
175 seed: None,
176 suppress_tokens: Vec::new(),
177 penalty_window: 0,
178 loop_stop: false,
179 }
180 }
181}
182
183pub const FILE_SAMPLING_KEYS: [&str; 9] = [
187 "temperature",
188 "top_p",
189 "top_k",
190 "min_p",
191 "repetition_penalty",
192 "presence_penalty",
193 "max_tokens",
194 "loop_stop",
195 "history_turns",
196];
197
198pub fn file_history_turns(provenance: Option<&serde_json::Value>) -> Option<usize> {
201 provenance
202 .and_then(|p| p.get("sampling"))
203 .and_then(|s| s.get("history_turns"))
204 .and_then(|v| v.as_f64())
205 .filter(|&n| n >= 1.0)
206 .map(|n| n as usize)
207}
208
209pub fn last_turns_start<'a>(roles: impl DoubleEndedIterator<Item = &'a str> + ExactSizeIterator, n: usize) -> usize {
211 let mut seen = 0;
212 for (i, r) in roles.enumerate().rev() {
213 if r == "user" {
214 seen += 1;
215 if seen == n {
216 return i;
217 }
218 }
219 }
220 0
221}
222
223pub fn file_max_tokens(provenance: Option<&serde_json::Value>) -> Option<u32> {
226 provenance
227 .and_then(|p| p.get("sampling"))
228 .and_then(|s| s.get("max_tokens"))
229 .and_then(|v| v.as_u64())
230 .filter(|&n| n > 0)
231 .map(|n| n.min(u32::MAX as u64) as u32)
232}
233
234impl SamplerConfig {
235 pub fn from_provenance(provenance: Option<&serde_json::Value>) -> Self {
242 let mut c = Self::default();
243 let Some(s) = provenance.and_then(|p| p.get("sampling")) else {
244 return c;
245 };
246 let f = |k: &str| s.get(k).and_then(|v| v.as_f64()).filter(|v| v.is_finite());
247 if let Some(v) = f("temperature") {
248 c.temperature = v as f32;
249 }
250 if let Some(v) = f("top_p") {
251 c.top_p = v as f32;
252 }
253 if let Some(v) = s.get("top_k").and_then(|v| v.as_u64()) {
254 c.top_k = v.min(u32::MAX as u64) as u32;
255 }
256 if let Some(v) = f("min_p") {
257 c.min_p = v as f32;
258 }
259 if let Some(v) = f("repetition_penalty") {
260 c.repetition_penalty = v as f32;
261 }
262 if let Some(v) = f("presence_penalty") {
263 c.presence_penalty = v as f32;
264 }
265 if let Some(v) = f("loop_stop") {
266 c.loop_stop = v != 0.0;
267 }
268 c
269 }
270
271 pub fn file_has_defaults(provenance: Option<&serde_json::Value>) -> bool {
273 provenance
274 .and_then(|p| p.get("sampling"))
275 .is_some_and(|s| s.as_object().is_some_and(|o| !o.is_empty()))
276 }
277}
278
279pub fn sample(
282 logits: &[f32],
283 config: &SamplerConfig,
284 past_tokens: &[u32],
285 rng: &mut SplitMix64,
286) -> u32 {
287 let mut scratch = SamplerScratch::default();
288 sample_with_scratch(logits, config, past_tokens, rng, &mut scratch)
289}
290
291pub fn sample_with_scratch(
293 logits: &[f32],
294 config: &SamplerConfig,
295 past_tokens: &[u32],
296 rng: &mut SplitMix64,
297 scratch: &mut SamplerScratch,
298) -> u32 {
299 sample_with_scratch_pool(logits, config, past_tokens, rng, scratch, None)
300}
301
302const SAMPLER_SERIAL_MAX: usize = 1 << 16;
319
320pub fn sample_with_scratch_pool(
321 logits: &[f32],
322 config: &SamplerConfig,
323 past_tokens: &[u32],
324 rng: &mut SplitMix64,
325 scratch: &mut SamplerScratch,
326 pool: Option<&crate::pool::Pool>,
327) -> u32 {
328 let pool = if logits.len() <= SAMPLER_SERIAL_MAX { None } else { pool };
330 if config.temperature < 1e-6
331 && config.repetition_penalty == 1.0
332 && config.presence_penalty == 0.0
333 && config.suppress_tokens.is_empty()
334 {
335 return argmax(logits);
336 }
337 if config.temperature < 1e-6 {
343 return argmax_penalized(logits, config, past_tokens, scratch, pool);
345 }
346 if sparse_ok(config) {
347 let mut sp = std::mem::take(&mut scratch.sparse);
349 let ok = sparse_distribution_into(logits, config, past_tokens, scratch, pool, &mut sp);
350 let t = if ok {
351 draw_sparse(&sp, rng)
352 } else {
353 argmax(logits)
354 };
355 scratch.sparse = sp;
356 return t;
357 }
358 if nucleus_sparse_ok(config, logits.len()) {
365 let capped = SamplerConfig {
366 top_k: SPARSE_TOPK_MAX as u32,
367 ..config.clone()
368 };
369 let mut sp = std::mem::take(&mut scratch.sparse);
370 let ok = sparse_distribution_into(logits, &capped, past_tokens, scratch, pool, &mut sp);
371 if ok && scratch.sparse_cut {
372 let t = draw_sparse(&sp, rng);
373 scratch.sparse = sp;
374 return t;
375 }
376 scratch.sparse = sp;
377 }
378 let mut probs = std::mem::take(&mut scratch.probs);
379 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
380
381 let mut done = |probs: Vec<f32>, tok: u32| -> u32 {
382 scratch.probs = probs;
383 tok
384 };
385
386 if !normalized {
387 let t = argmax(logits);
389 return done(probs, t);
390 }
391 let t = categorical_sample(&probs, rng.next_f32());
392 done(probs, t)
393}
394
395pub fn argmax_penalized(
405 logits: &[f32],
406 config: &SamplerConfig,
407 past_tokens: &[u32],
408 scratch: &mut SamplerScratch,
409 pool: Option<&crate::pool::Pool>,
410) -> u32 {
411 let n = logits.len();
412 if config.repetition_penalty == 1.0
413 && config.presence_penalty == 0.0
414 && config.suppress_tokens.is_empty()
415 {
416 return argmax(logits);
417 }
418 let epoch = scratch.begin_seen(n);
420 for &tok in past_tokens {
421 let idx = tok as usize;
422 if idx < n {
423 scratch.seen_epoch[idx] = epoch;
424 }
425 }
426 let rep = config.repetition_penalty;
427 let pres = config.presence_penalty;
428 let suppress = &config.suppress_tokens;
430 let seen = &scratch.seen_epoch;
431 let value = |i: usize| -> f32 {
432 let mut v = logits[i];
433 if suppress.iter().any(|&t| t as usize == i) {
434 return f32::NEG_INFINITY;
435 }
436 if seen[i] == epoch {
437 if rep != 1.0 {
438 if v > 0.0 {
439 v /= rep;
440 } else {
441 v *= rep;
442 }
443 }
444 if pres != 0.0 {
445 v -= pres;
446 }
447 }
448 v
449 };
450 let best_in = |s: usize, e: usize| -> (usize, f32) {
453 let mut bi = s;
454 let mut bv = f32::NEG_INFINITY;
455 for i in s..e {
456 let v = value(i);
457 if v >= bv {
458 bv = v;
459 bi = i;
460 }
461 }
462 (bi, bv)
463 };
464 match pool {
465 Some(p) if n >= PAR_MIN && suppress.is_empty() => {
466 let m = std::sync::Mutex::new(Vec::<(usize, f32)>::new());
467 p.run_rows(n, &|s, e| {
468 let r = best_in(s, e);
469 m.lock().unwrap().push(r);
470 });
471 let mut parts = m.into_inner().unwrap();
472 parts.sort_by_key(|(i, _)| *i);
476 let mut bi = 0usize;
477 let mut bv = f32::NEG_INFINITY;
478 for (i, v) in parts {
479 if v >= bv {
480 bv = v;
481 bi = i;
482 }
483 }
484 bi as u32
485 }
486 _ => best_in(0, n).0 as u32,
487 }
488}
489
490fn chain(
496 logits: &[f32],
497 config: &SamplerConfig,
498 past_tokens: &[u32],
499 scratch: &mut SamplerScratch,
500 pool: Option<&crate::pool::Pool>,
501 probs: &mut Vec<f32>,
502) -> bool {
503 probs.clear();
504 probs.extend_from_slice(logits);
505 apply_penalties(probs, config, past_tokens, scratch);
506
507 if config.temperature < 1e-6 {
508 return true;
509 }
510 if config.temperature != 1.0 {
511 let t = config.temperature;
512 par_map(pool, probs, &move |p| p / t);
513 }
514
515 softmax_inplace_pool(pool, probs);
516
517 if config.min_p > 0.0 {
518 let max_prob = par_max(pool, probs, 0.0);
519 let threshold = max_prob * config.min_p;
520 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
521 }
522
523 if config.top_k > 0 && (config.top_k as usize) < probs.len() {
524 apply_top_k_pool(pool, probs, config.top_k as usize);
525 }
526
527 if config.top_p < 1.0 && config.top_p > 0.0 {
528 apply_top_p(probs, config.top_p);
529 }
530
531 let sum: f32 = probs.iter().sum();
532 if sum > 0.0 {
533 par_map(pool, probs, &move |p| p / sum);
534 true
535 } else {
536 false
537 }
538}
539
540fn apply_penalties(
545 probs: &mut [f32],
546 config: &SamplerConfig,
547 past_tokens: &[u32],
548 scratch: &mut SamplerScratch,
549) {
550 for &tok in &config.suppress_tokens {
551 if (tok as usize) < probs.len() {
552 probs[tok as usize] = f32::NEG_INFINITY;
553 }
554 }
555 if config.repetition_penalty != 1.0 {
556 apply_repetition_penalty(probs, past_tokens, config.repetition_penalty, scratch);
557 }
558 if config.presence_penalty != 0.0 {
559 let mut seen = std::mem::take(&mut scratch.presence_seen);
564 seen.clear();
565 seen.extend(past_tokens.iter().copied());
566 for &tok in &seen {
567 if (tok as usize) < probs.len() {
568 probs[tok as usize] -= config.presence_penalty;
569 }
570 }
571 scratch.presence_seen = seen;
572 }
573}
574
575fn config_penalized(config: &SamplerConfig) -> bool {
576 config.repetition_penalty != 1.0
577 || config.presence_penalty != 0.0
578 || !config.suppress_tokens.is_empty()
579}
580
581pub const SPARSE_TOPK_MAX: usize = 256;
584
585fn nucleus_sparse_ok(config: &SamplerConfig, vocab: usize) -> bool {
590 config.top_k == 0
591 && config.temperature >= 1e-6
592 && config.top_p > 0.0
593 && config.top_p < 1.0
594 && vocab > SPARSE_TOPK_MAX * 4
595 && {
596 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
598 *ON.get_or_init(|| std::env::var("CMF_NUCLEUS_SPARSE").as_deref() != Ok("0"))
599 }
600}
601
602pub fn sparse_ok(config: &SamplerConfig) -> bool {
603 config.temperature >= 1e-6 && config.top_k > 0 && (config.top_k as usize) <= SPARSE_TOPK_MAX
604}
605
606pub type Sparse = Vec<(u32, f32)>;
609
610pub fn sparse_distribution_into(
636 logits: &[f32],
637 config: &SamplerConfig,
638 past_tokens: &[u32],
639 scratch: &mut SamplerScratch,
640 pool: Option<&crate::pool::Pool>,
641 out: &mut Sparse,
642) -> bool {
643 debug_assert!(sparse_ok(config));
644 out.clear();
645 let k = (config.top_k as usize).min(logits.len());
646 if k == 0 {
647 return false;
648 }
649 let penalized = config_penalized(config);
650 let mut probs = std::mem::take(&mut scratch.probs);
651 if penalized {
652 probs.clear();
653 probs.extend_from_slice(logits);
654 apply_penalties(&mut probs, config, past_tokens, scratch);
655 }
656 let src: &[f32] = if penalized { &probs } else { logits };
657 let t = if config.temperature > 0.0 {
658 config.temperature
659 } else {
660 1.0
661 };
662 let mut cand = std::mem::take(&mut scratch.cand);
663 par_topk(pool, src, k, &mut cand, &mut scratch.cand_parts);
664 let ok = if let Some(&(_, lmax)) = cand.first().filter(|c| c.1.is_finite()) {
667 let sum_all = par_sum_exp(pool, src, lmax, t, &mut scratch.sum_parts);
668 let min_p = config.min_p;
671 let mut cum = 0.0f32;
672 let mut cut = false;
673 scratch.sparse_cut = false;
674 for &(id, l) in cand.iter() {
675 if cut {
676 break;
677 }
678 let e = ((l - lmax) / t).exp();
679 if min_p > 0.0 && e < min_p {
680 continue;
681 }
682 let pr = e / sum_all;
683 if pr <= 0.0 {
684 continue;
685 }
686 out.push((id, pr));
687 cum += pr;
688 if config.top_p < 1.0 && config.top_p > 0.0 && cum >= config.top_p {
689 cut = true;
690 }
691 }
692 scratch.sparse_cut = cut;
693 let sum: f32 = out.iter().map(|c| c.1).sum();
695 if sum > 0.0 {
696 for c in out.iter_mut() {
697 c.1 /= sum;
698 }
699 out.sort_unstable_by_key(|c| c.0);
700 true
701 } else {
702 out.clear();
703 false
704 }
705 } else {
706 false
707 };
708 scratch.cand = cand;
709 scratch.probs = probs;
710 ok
711}
712
713fn par_topk(
720 pool: Option<&crate::pool::Pool>,
721 src: &[f32],
722 k: usize,
723 out: &mut Vec<(u32, f32)>,
724 parts: &mut Vec<Vec<(u32, f32)>>,
725) {
726 let better = |a: (u32, f32), b: (u32, f32)| a.1 > b.1 || (a.1 == b.1 && a.0 < b.0);
727 let scan = |s: usize, e: usize, best: &mut Vec<(u32, f32)>| {
730 best.clear();
731 for i in s..e {
732 let c = (i as u32, src[i]);
733 if best.len() < k {
734 let pos = best
735 .iter()
736 .position(|&b| better(c, b))
737 .unwrap_or(best.len());
738 best.insert(pos, c);
739 } else if better(c, best[k - 1]) {
740 let pos = best.iter().position(|&b| better(c, b)).unwrap_or(k - 1);
741 best.pop();
742 best.insert(pos, c);
743 }
744 }
745 };
746 let by_value_desc = |a: &(u32, f32), b: &(u32, f32)| {
747 b.1.partial_cmp(&a.1)
748 .unwrap_or(std::cmp::Ordering::Equal)
749 .then(a.0.cmp(&b.0))
750 };
751 let cap = k * 4 + 64;
755 let gather = |s: usize, e: usize, kth: f32, slot: &mut Vec<(u32, f32)>| {
757 slot.clear();
758 for i in s..e {
759 let v = src[i];
760 if v >= kth {
761 slot.push((i as u32, v));
762 if slot.len() >= cap {
763 break;
764 }
765 }
766 }
767 };
768 out.clear();
769 match pool {
770 Some(p) if src.len() >= PAR_MIN => {
771 let n = src.len();
772 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
773 let ng = n.div_ceil(grain);
774 parts.resize_with(ng, Vec::new);
775 let pp = crate::pool::SendMutT::new(parts.as_mut_ptr());
776 p.run_rows(n, &|s, e| {
777 let slot = unsafe { &mut *pp.at(s / grain) };
780 scan(s, e, slot);
781 });
782 for g in 0..ng {
783 out.extend_from_slice(&parts[g]);
784 }
785 out.sort_unstable_by(by_value_desc);
786 out.truncate(k);
787 let Some(&(_, kth)) = out.last() else {
788 return;
789 };
790 if !kth.is_finite() {
791 return; }
793 p.run_rows(n, &|s, e| {
794 let slot = unsafe { &mut *pp.at(s / grain) };
795 gather(s, e, kth, slot);
796 });
797 out.clear();
798 for g in 0..ng {
799 out.extend_from_slice(&parts[g]);
800 if out.len() >= cap {
801 break;
802 }
803 }
804 out.sort_unstable_by(by_value_desc);
805 out.truncate(cap);
806 }
807 _ => {
808 scan(0, src.len(), out);
809 let Some(&(_, kth)) = out.last() else {
810 return;
811 };
812 if !kth.is_finite() {
813 return;
814 }
815 let mut all = std::mem::take(out);
816 gather(0, src.len(), kth, &mut all);
817 all.sort_unstable_by(by_value_desc);
818 all.truncate(cap);
819 *out = all;
820 }
821 }
822}
823
824fn par_sum_exp(
827 pool: Option<&crate::pool::Pool>,
828 src: &[f32],
829 lmax: f32,
830 t: f32,
831 parts: &mut Vec<f32>,
832) -> f32 {
833 let floor = lmax - 40.0 * t;
836 let term = |s: usize, e: usize| -> f32 {
837 let mut acc = 0.0f32;
838 for &l in &src[s..e] {
839 if l >= floor {
840 acc += ((l - lmax) / t).exp();
841 }
842 }
843 acc
844 };
845 match pool {
846 Some(p) if src.len() >= PAR_MIN => {
847 let n = src.len();
848 let grain = crate::pool::grain_for(n, p.n_workers() + 1);
849 let ng = n.div_ceil(grain);
850 parts.clear();
851 parts.resize(ng, 0.0);
852 let pp = crate::pool::SendMut::new(parts.as_mut_ptr());
853 p.run_rows(n, &|s, e| {
854 unsafe { *pp.at(s / grain) = term(s, e) };
856 });
857 parts.iter().sum()
858 }
859 _ => term(0, src.len()),
860 }
861}
862
863pub fn draw_sparse(p: &[(u32, f32)], rng: &mut SplitMix64) -> u32 {
867 let r = rng.next_f32();
868 let mut cum = 0.0f32;
869 for &(id, pr) in p {
870 cum += pr;
871 if r < cum {
872 return id;
873 }
874 }
875 p.iter().rev().find(|c| c.1 > 0.0).map(|c| c.0).unwrap_or(0)
876}
877
878fn sparse_get(p: &[(u32, f32)], id: u32) -> f32 {
879 p.binary_search_by_key(&id, |c| c.0)
880 .map(|i| p[i].1)
881 .unwrap_or(0.0)
882}
883
884pub fn spec_accept_or_correct_sparse(
889 p: &[(u32, f32)],
890 q: &[(u32, f32)],
891 d: u32,
892 rng: &mut SplitMix64,
893 res: &mut Sparse,
894) -> Option<u32> {
895 let (pd, qd) = (sparse_get(p, d), sparse_get(q, d));
896 let r = rng.next_f32();
897 if qd > 0.0 && r * qd < pd {
898 return None;
899 }
900 res.clear();
901 let mut total = 0.0f32;
902 for &(id, pi) in p {
903 let ri = pi - sparse_get(q, id);
904 if ri > 0.0 {
905 res.push((id, ri));
906 total += ri;
907 }
908 }
909 if total <= 0.0 {
910 return Some(draw_sparse(p, rng));
911 }
912 for c in res.iter_mut() {
913 c.1 /= total;
914 }
915 Some(draw_sparse(res, rng))
916}
917
918pub fn distribution_into(
925 logits: &[f32],
926 config: &SamplerConfig,
927 past_tokens: &[u32],
928 scratch: &mut SamplerScratch,
929 pool: Option<&crate::pool::Pool>,
930 out: &mut Vec<f32>,
931) {
932 let one_hot = |out: &mut Vec<f32>, t: usize, n: usize| {
933 out.clear();
934 out.resize(n, 0.0);
935 if t < n {
936 out[t] = 1.0;
937 }
938 };
939 if config.temperature < 1e-6
940 && config.repetition_penalty == 1.0
941 && config.presence_penalty == 0.0
942 && config.suppress_tokens.is_empty()
943 {
944 return one_hot(out, argmax(logits) as usize, logits.len());
945 }
946 let mut probs = std::mem::take(&mut scratch.probs);
947 let normalized = chain(logits, config, past_tokens, scratch, pool, &mut probs);
948 if config.temperature < 1e-6 {
949 let t = argmax(&probs) as usize;
950 scratch.probs = probs;
951 return one_hot(out, t, logits.len());
952 }
953 if !normalized {
954 scratch.probs = probs;
955 return one_hot(out, argmax(logits) as usize, logits.len());
956 }
957 out.clear();
958 out.extend_from_slice(&probs);
959 scratch.probs = probs;
960}
961
962pub fn draw(probs: &[f32], rng: &mut SplitMix64) -> u32 {
964 categorical_sample(probs, rng.next_f32())
965}
966
967pub fn spec_accept_or_correct(
977 p: &[f32],
978 q: &[f32],
979 d: u32,
980 rng: &mut SplitMix64,
981 scratch: &mut Vec<f32>,
982 pool: Option<&crate::pool::Pool>,
983) -> Option<u32> {
984 let di = d as usize;
985 let (pd, qd) = (
986 p.get(di).copied().unwrap_or(0.0),
987 q.get(di).copied().unwrap_or(0.0),
988 );
989 let r = rng.next_f32();
990 if qd > 0.0 && r * qd < pd {
992 return None;
993 }
994 let n = p.len().min(q.len());
995 scratch.clear();
996 scratch.extend_from_slice(&p[..n]);
997 {
999 let qp = q.as_ptr() as usize;
1000 let sm = crate::pool::SendMut::new(scratch.as_mut_ptr());
1001 let body = move |s: usize, e: usize| {
1002 let qs = unsafe { std::slice::from_raw_parts(qp as *const f32, n) };
1004 for i in s..e {
1005 unsafe {
1006 let x = sm.at(i);
1007 *x = (*x - qs[i]).max(0.0);
1008 }
1009 }
1010 };
1011 match pool {
1012 Some(pl) if n >= PAR_MIN => pl.run_rows(n, &body),
1013 _ => body(0, n),
1014 }
1015 }
1016 let sum: f32 = scratch.iter().sum();
1017 if sum > 0.0 {
1018 let inv = 1.0 / sum;
1019 par_map(pool, scratch, &move |v| v * inv);
1020 Some(categorical_sample(scratch, rng.next_f32()))
1021 } else {
1022 Some(categorical_sample(&p[..n], rng.next_f32()))
1023 }
1024}
1025
1026pub fn argmax(values: &[f32]) -> u32 {
1038 if values.is_empty() {
1039 return 0;
1040 }
1041 let n = values.len();
1042 let mut best = [(0usize, f32::NEG_INFINITY); 4];
1043 for (l, b) in best.iter_mut().enumerate() {
1044 b.0 = l.min(n - 1);
1045 }
1046 let mut i = 0;
1047 while i + 4 <= n {
1048 for l in 0..4 {
1049 let v = values[i + l];
1050 if v >= best[l].1 {
1051 best[l] = (i + l, v);
1052 }
1053 }
1054 i += 4;
1055 }
1056 let mut bi = best[0].0;
1057 let mut bv = best[0].1;
1058 for b in &best[1..] {
1059 if b.1 > bv || (b.1 == bv && b.0 > bi) {
1060 bi = b.0;
1061 bv = b.1;
1062 }
1063 }
1064 while i < n {
1065 if values[i] >= bv {
1066 bv = values[i];
1067 bi = i;
1068 }
1069 i += 1;
1070 }
1071 bi as u32
1072}
1073
1074fn softmax_inplace(logits: &mut [f32]) {
1075 let max_val = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1076 let mut sum = 0.0f32;
1077 for v in logits.iter_mut() {
1078 *v = (*v - max_val).exp();
1079 sum += *v;
1080 }
1081 if sum > 0.0 {
1082 for v in logits.iter_mut() {
1083 *v /= sum;
1084 }
1085 }
1086}
1087
1088const PAR_MIN: usize = 1 << 14;
1090
1091fn par_map(pool: Option<&crate::pool::Pool>, buf: &mut [f32], f: &(dyn Fn(f32) -> f32 + Sync)) {
1095 match pool {
1096 Some(p) if buf.len() >= PAR_MIN => {
1097 let out = crate::pool::SendMut::new(buf.as_mut_ptr());
1098 p.run_rows(buf.len(), &move |s, e| {
1099 for i in s..e {
1100 unsafe {
1103 let q = out.at(i);
1104 *q = f(*q);
1105 }
1106 }
1107 });
1108 }
1109 _ => {
1110 for v in buf.iter_mut() {
1111 *v = f(*v);
1112 }
1113 }
1114 }
1115}
1116
1117fn par_max(pool: Option<&crate::pool::Pool>, buf: &[f32], init: f32) -> f32 {
1120 match pool {
1121 Some(p) if buf.len() >= PAR_MIN => {
1122 let m = std::sync::Mutex::new(init);
1123 p.run_rows(buf.len(), &|s, e| {
1124 let local = buf[s..e].iter().cloned().fold(init, f32::max);
1125 let mut g = m.lock().unwrap();
1126 *g = g.max(local);
1127 });
1128 m.into_inner().unwrap()
1129 }
1130 _ => buf.iter().cloned().fold(init, f32::max),
1131 }
1132}
1133
1134fn softmax_inplace_pool(pool: Option<&crate::pool::Pool>, logits: &mut [f32]) {
1139 if pool.is_none() || logits.len() < PAR_MIN {
1140 return softmax_inplace(logits);
1141 }
1142 let max_val = par_max(pool, logits, f32::NEG_INFINITY);
1143 par_map(pool, logits, &move |v| (v - max_val).exp());
1144 let sum: f32 = logits.iter().sum();
1145 if sum > 0.0 {
1146 par_map(pool, logits, &move |v| v / sum);
1147 }
1148}
1149
1150fn kth_largest(probs: &[f32], k: usize) -> f32 {
1155 use std::cmp::Ordering;
1156 let mut heap: Vec<f32> = Vec::with_capacity(k);
1159 let desc = |a: f32, b: f32| b.partial_cmp(&a).unwrap_or(Ordering::Equal);
1160 let sift_down = |h: &mut [f32], mut i: usize| {
1161 let n = h.len();
1162 loop {
1163 let (l, r) = (2 * i + 1, 2 * i + 2);
1164 let mut m = i;
1165 if l < n && desc(h[l], h[m]) == Ordering::Greater {
1167 m = l;
1168 }
1169 if r < n && desc(h[r], h[m]) == Ordering::Greater {
1170 m = r;
1171 }
1172 if m == i {
1173 break;
1174 }
1175 h.swap(i, m);
1176 i = m;
1177 }
1178 };
1179 let sift_up = |h: &mut [f32], mut i: usize| {
1180 while i > 0 {
1181 let parent = (i - 1) / 2;
1182 if desc(h[i], h[parent]) == Ordering::Greater {
1183 h.swap(i, parent);
1184 i = parent;
1185 } else {
1186 break;
1187 }
1188 }
1189 };
1190 for &v in probs {
1191 if heap.len() < k {
1192 heap.push(v);
1193 let n = heap.len();
1194 sift_up(&mut heap, n - 1);
1195 } else if desc(v, heap[0]) == Ordering::Less {
1196 heap[0] = v;
1198 sift_down(&mut heap, 0);
1199 }
1200 }
1201 heap[0]
1202}
1203
1204fn apply_top_k_pool(pool: Option<&crate::pool::Pool>, probs: &mut [f32], k: usize) {
1208 if k == 0 || k >= probs.len() {
1209 return;
1210 }
1211 let threshold = kth_largest(probs, k);
1212 par_map(pool, probs, &move |p| if p < threshold { 0.0 } else { p });
1213}
1214
1215pub fn top1_prob_pool(
1220 pool: Option<&crate::pool::Pool>,
1221 scratch: &mut SamplerScratch,
1222 logits: &[f32],
1223 id: u32,
1224 temp: f32,
1225) -> f32 {
1226 let pool = if logits.len() <= SAMPLER_SERIAL_MAX { None } else { pool };
1227 let t = if temp > 1e-3 { temp } else { 1.0 };
1228 let max = par_max(pool, logits, f32::NEG_INFINITY);
1229 let sum = par_sum_exp(pool, logits, max, t, &mut scratch.sum_parts);
1230 if sum > 0.0 {
1231 (((logits[id as usize] - max) / t).exp()) / sum
1232 } else {
1233 0.0
1234 }
1235}
1236
1237fn apply_repetition_penalty(
1238 logits: &mut [f32],
1239 past_tokens: &[u32],
1240 penalty: f32,
1241 scratch: &mut SamplerScratch,
1242) {
1243 let epoch = scratch.begin_seen(logits.len());
1244 for &tok in past_tokens {
1245 let idx = tok as usize;
1246 if idx < logits.len() && scratch.seen_epoch[idx] != epoch {
1247 scratch.seen_epoch[idx] = epoch;
1248 if logits[idx] > 0.0 {
1249 logits[idx] /= penalty;
1250 } else {
1251 logits[idx] *= penalty;
1252 }
1253 }
1254 }
1255}
1256
1257fn apply_top_k(probs: &mut [f32], k: usize, sel: &mut Vec<f32>) {
1262 if k == 0 || k >= probs.len() {
1263 return;
1264 }
1265 sel.clear();
1268 sel.extend_from_slice(probs);
1269 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1271 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1272 });
1273 let threshold = *kth;
1274 for p in probs.iter_mut() {
1275 if *p < threshold {
1276 *p = 0.0;
1277 }
1278 }
1279}
1280
1281fn apply_top_p(probs: &mut [f32], top_p: f32) {
1286 if apply_top_p_head(probs, top_p) {
1287 return;
1288 }
1289 apply_top_p_full(probs, top_p);
1290}
1291
1292const TOP_P_HEAD: usize = 1024;
1302
1303fn apply_top_p_head(probs: &mut [f32], top_p: f32) -> bool {
1304 let n = probs.len();
1305 if n <= TOP_P_HEAD * 4 {
1306 return false;
1307 }
1308 let mut head: Vec<(usize, f32)> = probs
1309 .iter()
1310 .copied()
1311 .enumerate()
1312 .filter(|&(_, p)| p > 0.0)
1313 .collect();
1314 if head.len() <= TOP_P_HEAD {
1315 return false;
1316 }
1317 let by_desc = |a: &(usize, f32), b: &(usize, f32)| {
1318 b.1.partial_cmp(&a.1)
1319 .unwrap_or(std::cmp::Ordering::Equal)
1320 .then(a.0.cmp(&b.0))
1321 };
1322 head.select_nth_unstable_by(TOP_P_HEAD - 1, by_desc);
1323 head.truncate(TOP_P_HEAD);
1324 head.sort_by(by_desc);
1325 let mut cumsum = 0.0f32;
1326 let mut cut = None;
1327 for (i, &(_, prob)) in head.iter().enumerate() {
1328 cumsum += prob;
1329 if cumsum >= top_p {
1330 cut = Some(i);
1331 break;
1332 }
1333 }
1334 let Some(cut) = cut else {
1335 return false;
1336 };
1337 let v = head[cut].1;
1338 let keep_eq = head[..=cut].iter().filter(|&&(_, p)| p == v).count();
1341 if head[TOP_P_HEAD - 1].1 == v {
1344 return false;
1345 }
1346 let mut eq_seen = 0usize;
1347 for p in probs.iter_mut() {
1348 if *p > v {
1349 continue;
1350 }
1351 if *p == v && eq_seen < keep_eq {
1352 eq_seen += 1;
1353 continue;
1354 }
1355 *p = 0.0;
1356 }
1357 true
1358}
1359
1360fn apply_top_p_full(probs: &mut [f32], top_p: f32) {
1361 let mut indexed: Vec<(usize, f32)> = probs
1362 .iter()
1363 .copied()
1364 .enumerate()
1365 .filter(|&(_, p)| p > 0.0)
1366 .collect();
1367 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1368
1369 let mut cumsum = 0.0f32;
1370 let mut cutoff_idx = indexed.len();
1371 for (i, &(_, prob)) in indexed.iter().enumerate() {
1372 cumsum += prob;
1373 if cumsum >= top_p {
1374 cutoff_idx = i + 1;
1375 break;
1376 }
1377 }
1378
1379 for &(i, _) in &indexed[cutoff_idx..] {
1381 probs[i] = 0.0;
1382 }
1383}
1384
1385fn categorical_sample(probs: &[f32], r: f32) -> u32 {
1387 let mut cumsum = 0.0f32;
1388 for (i, &p) in probs.iter().enumerate() {
1389 cumsum += p;
1390 if r < cumsum {
1391 return i as u32;
1392 }
1393 }
1394 probs.iter().rposition(|&p| p > 0.0).unwrap_or(0) as u32
1395}
1396
1397#[cfg(test)]
1398mod tests {
1399 #[test]
1404 fn argmax_lanes_match_the_scalar_one_ties_and_all() {
1405 let scalar = |v: &[f32]| -> u32 {
1408 v.iter()
1409 .enumerate()
1410 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1411 .map(|(i, _)| i as u32)
1412 .unwrap_or(0)
1413 };
1414 for n in 0..40usize {
1415 for seed in 0..8u64 {
1416 let mut r = super::SplitMix64::new(seed * 7 + n as u64);
1417 let v: Vec<f32> = (0..n).map(|_| ((r.next_u64() % 5) as f32) - 2.0).collect();
1420 assert_eq!(super::argmax(&v), scalar(&v), "n={n} seed={seed} {v:?}");
1421 }
1422 }
1423 let flat = vec![f32::NEG_INFINITY; 13];
1424 assert_eq!(super::argmax(&flat), scalar(&flat));
1425 }
1426
1427 use super::*;
1428
1429 #[test]
1430 fn test_argmax() {
1431 let logits = vec![0.1, 0.5, 0.3, 0.9, 0.2];
1432 assert_eq!(argmax(&logits), 3);
1433 }
1434
1435 #[test]
1436 fn test_greedy_sampling() {
1437 let logits = vec![1.0, 5.0, 2.0, 3.0];
1438 let config = SamplerConfig {
1439 temperature: 0.0,
1440 ..Default::default()
1441 };
1442 let mut rng = SplitMix64::new(1);
1443 assert_eq!(sample(&logits, &config, &[], &mut rng), 1);
1444 }
1445
1446 #[test]
1449 fn argmax_penalized_matches_chain_argmax() {
1450 let pool = crate::pool::Pool::new(3);
1451 let n = 40_000usize;
1452 for seed in 0..8u64 {
1453 let mut r = SplitMix64::new(seed + 3);
1454 let logits: Vec<f32> = (0..n)
1456 .map(|_| ((r.next_u64() % 41) as f32 - 20.0) / 4.0)
1457 .collect();
1458 let past: Vec<u32> = (0..2000)
1459 .map(|_| (r.next_u64() % n as u64) as u32)
1460 .collect();
1461 for cfg in [
1462 SamplerConfig {
1463 temperature: 0.0,
1464 repetition_penalty: 1.1,
1465 ..Default::default()
1466 },
1467 SamplerConfig {
1468 temperature: 0.0,
1469 repetition_penalty: 1.0,
1470 presence_penalty: 1.5,
1471 ..Default::default()
1472 },
1473 SamplerConfig {
1474 temperature: 0.0,
1475 repetition_penalty: 1.3,
1476 presence_penalty: 0.7,
1477 suppress_tokens: vec![5, 77, 3000],
1478 ..Default::default()
1479 },
1480 ] {
1481 let mut s1 = SamplerScratch::default();
1482 let mut probs = Vec::new();
1483 chain(&logits, &cfg, &past, &mut s1, None, &mut probs);
1484 let want = argmax(&probs);
1485 let mut s2 = SamplerScratch::default();
1486 let got_serial = argmax_penalized(&logits, &cfg, &past, &mut s2, None);
1487 let got_pool = argmax_penalized(&logits, &cfg, &past, &mut s2, Some(&pool));
1488 assert_eq!(want, got_serial, "serial, seed {seed} cfg {cfg:?}");
1489 assert_eq!(want, got_pool, "pool, seed {seed} cfg {cfg:?}");
1490 }
1491 }
1492 }
1493
1494 #[test]
1497 fn pool_chain_matches_serial_bit_for_bit() {
1498 let pool = crate::pool::Pool::new(3);
1499 let n = 40_000usize; for seed in 0..6u64 {
1501 let mut r = SplitMix64::new(seed + 11);
1502 let logits: Vec<f32> = (0..n)
1503 .map(|i| ((r.next_u64() % 2001) as f32 - 1000.0) / 90.0 + (i % 7) as f32 * 0.01)
1504 .collect();
1505 let past: Vec<u32> = (0..500).map(|_| (r.next_u64() % n as u64) as u32).collect();
1506 for cfg in [
1507 SamplerConfig::default(),
1508 SamplerConfig {
1509 temperature: 0.7,
1510 top_p: 0.8,
1511 top_k: 20,
1512 min_p: 0.0,
1513 presence_penalty: 1.5,
1514 repetition_penalty: 1.0,
1515 ..Default::default()
1516 },
1517 SamplerConfig {
1518 temperature: 1.0,
1519 top_p: 0.95,
1520 top_k: 20,
1521 min_p: 0.0,
1522 ..Default::default()
1523 },
1524 SamplerConfig {
1525 temperature: 1.3,
1526 top_p: 1.0,
1527 top_k: 0,
1528 min_p: 0.02,
1529 ..Default::default()
1530 },
1531 ] {
1532 let mut s1 = SamplerScratch::default();
1533 let mut s2 = SamplerScratch::default();
1534 for step in 0..5u64 {
1535 let mut r1 = SplitMix64::new(seed * 100 + step);
1536 let mut r2 = r1.clone();
1537 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1538 let b = sample_with_scratch_pool(
1539 &logits,
1540 &cfg,
1541 &past,
1542 &mut r2,
1543 &mut s2,
1544 Some(&pool),
1545 );
1546 assert_eq!(a, b, "seed {seed} step {step} cfg {cfg:?}");
1547 assert_eq!(s1.probs, s2.probs, "probs differ seed {seed} step {step}");
1549 }
1550 }
1551 }
1552 }
1553
1554 #[test]
1559 fn spec_accept_or_correct_reproduces_the_target() {
1560 let n = 40usize;
1561 let mk = |seed: u64, sharp: f32| -> Vec<f32> {
1562 let mut r = SplitMix64::new(seed);
1563 let mut v: Vec<f32> = (0..n)
1564 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1565 .collect();
1566 for i in 0..n {
1568 if (i * 7 + seed as usize) % 5 == 0 {
1569 v[i] = 0.0;
1570 }
1571 }
1572 let s: f32 = v.iter().sum();
1573 v.iter().map(|x| x / s).collect()
1574 };
1575 let p = mk(3, 3.0);
1576 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1577 .into_iter()
1578 .enumerate()
1579 {
1580 let mut rng = SplitMix64::new(77 + qi as u64);
1581 let mut counts = vec![0u64; n];
1582 let mut scratch = Vec::new();
1583 let trials = 400_000u64;
1584 for _ in 0..trials {
1585 let d = categorical_sample(&q, rng.next_f32());
1586 let t = match spec_accept_or_correct(&p, &q, d, &mut rng, &mut scratch, None) {
1587 None => d,
1588 Some(c) => c,
1589 };
1590 counts[t as usize] += 1;
1591 }
1592 let l1: f64 = (0..n)
1593 .map(|i| (counts[i] as f64 / trials as f64 - p[i] as f64).abs())
1594 .sum();
1595 eprintln!("spec q#{qi}: L1(empirical, p) = {l1:.4}");
1596 assert!(
1597 l1 < 0.01,
1598 "q#{qi}: empirical distribution drifted from p, L1 {l1}"
1599 );
1600 for i in 0..n {
1602 if p[i] == 0.0 {
1603 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1604 }
1605 }
1606 }
1607 }
1608
1609 #[test]
1613 fn sparse_chain_matches_the_dense_chain() {
1614 let pool = crate::pool::Pool::new(3);
1615 let n = 40_000usize; for seed in 0..5u64 {
1617 let mut r = SplitMix64::new(100 + seed);
1618 let logits: Vec<f32> = (0..n)
1619 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1620 .collect();
1621 let past: Vec<u32> = (0..400).map(|_| (r.next_u64() % n as u64) as u32).collect();
1622 for cfg in [
1623 SamplerConfig {
1624 temperature: 0.7,
1625 top_p: 0.8,
1626 top_k: 20,
1627 min_p: 0.0,
1628 presence_penalty: 1.5,
1629 repetition_penalty: 1.0,
1630 ..Default::default()
1631 },
1632 SamplerConfig {
1633 temperature: 1.0,
1634 top_p: 0.95,
1635 top_k: 40,
1636 min_p: 0.05,
1637 presence_penalty: 0.0,
1638 repetition_penalty: 1.1,
1639 ..Default::default()
1640 },
1641 SamplerConfig {
1642 temperature: 0.6,
1643 top_p: 1.0,
1644 top_k: 3,
1645 min_p: 0.0,
1646 presence_penalty: 0.0,
1647 repetition_penalty: 1.0,
1648 suppress_tokens: vec![5, 6, 7],
1649 ..Default::default()
1650 },
1651 ] {
1652 assert!(sparse_ok(&cfg));
1653 let mut sd = SamplerScratch::default();
1654 let mut dense = Vec::new();
1655 distribution_into(&logits, &cfg, &past, &mut sd, None, &mut dense);
1656 for pl in [None, Some(&pool)] {
1657 let mut ss = SamplerScratch::default();
1658 let mut sp = Vec::new();
1659 let ok = sparse_distribution_into(&logits, &cfg, &past, &mut ss, pl, &mut sp);
1660 assert!(ok, "seed {seed} cfg {cfg:?}");
1661 let dense_nz: Vec<(u32, f32)> = dense
1662 .iter()
1663 .enumerate()
1664 .filter(|&(_, &v)| v > 0.0)
1665 .map(|(i, &v)| (i as u32, v))
1666 .collect();
1667 assert_eq!(
1668 dense_nz.len(),
1669 sp.len(),
1670 "seed {seed} pool {} cfg {cfg:?}: support {:?} vs {:?}",
1671 pl.is_some(),
1672 dense_nz,
1673 sp
1674 );
1675 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1676 assert_eq!(a.0, b.0, "seed {seed} cfg {cfg:?}: ids differ");
1677 assert!(
1678 (a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3),
1679 "seed {seed} cfg {cfg:?}: prob {} vs {}",
1680 a.1,
1681 b.1
1682 );
1683 }
1684 let mut agree = 0usize;
1687 let trials = 400usize;
1688 for k in 0..trials as u64 {
1689 let mut r1 = SplitMix64::new(500 + k);
1690 let mut r2 = SplitMix64::new(500 + k);
1691 let a = categorical_sample(&dense, r1.next_f32());
1692 let b = draw_sparse(&sp, &mut r2);
1693 agree += (a == b) as usize;
1694 }
1695 assert!(
1696 agree >= trials - 2,
1697 "seed {seed} cfg {cfg:?}: agree {agree}/{trials}"
1698 );
1699 let mut r1 = SplitMix64::new(9);
1701 let mut r2 = SplitMix64::new(9);
1702 let a = categorical_sample(&dense, r1.next_f32());
1703 let mut s3 = SamplerScratch::default();
1704 let b = sample_with_scratch_pool(&logits, &cfg, &past, &mut r2, &mut s3, pl);
1705 assert_eq!(a, b, "seed {seed} cfg {cfg:?}: entry draw");
1706 }
1707 }
1708 }
1709 }
1710
1711 #[test]
1714 fn spec_accept_or_correct_sparse_reproduces_the_target() {
1715 let n = 40usize;
1716 let mk = |seed: u64, sharp: f32| -> Vec<(u32, f32)> {
1717 let mut r = SplitMix64::new(seed);
1718 let mut v: Vec<f32> = (0..n)
1719 .map(|_| ((r.next_u64() % 1000) as f32 / 1000.0).powf(sharp))
1720 .collect();
1721 for i in 0..n {
1722 if (i * 7 + seed as usize) % 5 == 0 {
1723 v[i] = 0.0;
1724 }
1725 }
1726 let s: f32 = v.iter().sum();
1727 v.iter()
1728 .enumerate()
1729 .filter(|&(_, &x)| x > 0.0)
1730 .map(|(i, &x)| (i as u32, x / s))
1731 .collect()
1732 };
1733 let p = mk(3, 3.0);
1734 for (qi, q) in [mk(3, 3.0), mk(11, 1.0), mk(29, 6.0)]
1735 .into_iter()
1736 .enumerate()
1737 {
1738 let mut rng = SplitMix64::new(77 + qi as u64);
1739 let mut counts = vec![0u64; n];
1740 let mut res = Vec::new();
1741 let trials = 400_000u64;
1742 for _ in 0..trials {
1743 let d = draw_sparse(&q, &mut rng);
1744 let t = match spec_accept_or_correct_sparse(&p, &q, d, &mut rng, &mut res) {
1745 None => d,
1746 Some(c) => c,
1747 };
1748 counts[t as usize] += 1;
1749 }
1750 let l1: f64 = (0..n)
1751 .map(|i| (counts[i] as f64 / trials as f64 - sparse_get(&p, i as u32) as f64).abs())
1752 .sum();
1753 eprintln!("sparse spec q#{qi}: L1(empirical, p) = {l1:.4}");
1754 assert!(l1 < 0.01, "q#{qi}: drifted, L1 {l1}");
1755 for i in 0..n {
1756 if sparse_get(&p, i as u32) == 0.0 {
1757 assert_eq!(counts[i], 0, "q#{qi}: token {i} outside p emitted");
1758 }
1759 }
1760 }
1761 }
1762
1763 #[test]
1766 fn distribution_matches_the_sampler_draw() {
1767 let n = 20_000usize;
1768 let mut r = SplitMix64::new(9);
1769 let logits: Vec<f32> = (0..n)
1770 .map(|_| ((r.next_u64() % 3000) as f32 - 1500.0) / 120.0)
1771 .collect();
1772 let past: Vec<u32> = (0..300).map(|_| (r.next_u64() % n as u64) as u32).collect();
1773 for cfg in [
1774 SamplerConfig::default(),
1775 SamplerConfig {
1776 temperature: 0.7,
1777 top_p: 0.8,
1778 top_k: 20,
1779 min_p: 0.0,
1780 presence_penalty: 1.5,
1781 repetition_penalty: 1.0,
1782 ..Default::default()
1783 },
1784 SamplerConfig {
1785 temperature: 0.0,
1786 repetition_penalty: 1.1,
1787 ..Default::default()
1788 },
1789 SamplerConfig {
1790 temperature: 0.0,
1791 repetition_penalty: 1.0,
1792 presence_penalty: 0.0,
1793 ..Default::default()
1794 },
1795 ] {
1796 let mut s1 = SamplerScratch::default();
1797 let mut s2 = SamplerScratch::default();
1798 for step in 0..4u64 {
1799 let mut r1 = SplitMix64::new(step + 1);
1800 let mut r2 = r1.clone();
1801 let a = sample_with_scratch(&logits, &cfg, &past, &mut r1, &mut s1);
1802 let mut dist = Vec::new();
1803 distribution_into(&logits, &cfg, &past, &mut s2, None, &mut dist);
1804 assert!(
1805 (dist.iter().sum::<f32>() - 1.0).abs() < 1e-3,
1806 "not normalized: {}",
1807 dist.iter().sum::<f32>()
1808 );
1809 let b = if cfg.temperature < 1e-6 {
1810 argmax(&dist)
1811 } else {
1812 draw(&dist, &mut r2)
1813 };
1814 assert_eq!(a, b, "cfg {cfg:?} step {step}");
1815 }
1816 }
1817 }
1818
1819 #[test]
1822 fn kth_largest_equals_select_nth() {
1823 for seed in 0..20u64 {
1824 let mut r = SplitMix64::new(seed);
1825 let n = 50 + (r.next_u64() % 3000) as usize;
1826 let v: Vec<f32> = (0..n)
1827 .map(|_| {
1828 if r.next_u64() % 3 == 0 {
1829 0.0
1830 } else {
1831 (r.next_u64() % 97) as f32 / 97.0
1832 }
1833 })
1834 .collect();
1835 for k in [1usize, 2, 5, 20, 40, n / 2, n - 1] {
1836 let mut sel = v.clone();
1837 let (_, kth, _) = sel.select_nth_unstable_by(k - 1, |a, b| {
1838 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
1839 });
1840 assert_eq!(kth_largest(&v, k), *kth, "seed {seed} k {k}");
1841 }
1842 }
1843 }
1844
1845 #[test]
1847 fn top1_prob_pool_matches_serial() {
1848 let pool = crate::pool::Pool::new(2);
1849 let n = 30_000usize;
1850 let mut r = SplitMix64::new(5);
1851 let logits: Vec<f32> = (0..n)
1852 .map(|_| ((r.next_u64() % 1000) as f32) / 37.0)
1853 .collect();
1854 let serial = |id: u32, temp: f32| -> f32 {
1855 let t = if temp > 1e-3 { temp } else { 1.0 };
1856 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
1857 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
1858 (((logits[id as usize] - max) / t).exp()) / sum
1859 };
1860 let mut sc = SamplerScratch::default();
1861 for (id, t) in [(3u32, 1.0f32), (777, 0.7), (29_999, 2.0), (12, 0.0)] {
1862 let a = serial(id, t);
1863 let b = top1_prob_pool(Some(&pool), &mut sc, &logits, id, t);
1864 assert_eq!(a.to_bits(), b.to_bits(), "id {id} t {t}: {a} vs {b}");
1865 }
1866 }
1867
1868 #[test]
1869 fn test_softmax() {
1870 let mut logits = vec![1.0, 2.0, 3.0];
1871 softmax_inplace(&mut logits);
1872 let sum: f32 = logits.iter().sum();
1873 assert!((sum - 1.0).abs() < 1e-5);
1874 assert!(logits[2] > logits[1] && logits[1] > logits[0]);
1875 }
1876
1877 #[test]
1878 fn test_repetition_penalty() {
1879 let mut logits = vec![1.0, 2.0, 3.0, 4.0];
1880 let mut scratch = SamplerScratch::default();
1881 apply_repetition_penalty(&mut logits, &[1, 3], 2.0, &mut scratch);
1882 assert_eq!(logits, vec![1.0, 1.0, 3.0, 2.0]);
1883 }
1884
1885 #[test]
1886 fn repetition_penalty_applies_once_per_unique_token() {
1887 let mut logits = vec![1.0, 4.0, -6.0];
1888 let mut scratch = SamplerScratch::default();
1889 apply_repetition_penalty(&mut logits, &[1, 1, 2, 1, 2], 2.0, &mut scratch);
1890 assert_eq!(logits, vec![1.0, 2.0, -12.0]);
1891 }
1892
1893 #[test]
1897 fn nucleus_sparse_matches_the_dense_top_p() {
1898 let n = 50_000usize;
1899 let mut closed = 0;
1900 for seed in 0..8u64 {
1901 let mut r = SplitMix64::new(500 + seed);
1902 let logits: Vec<f32> = (0..n)
1903 .map(|i| {
1904 let base = ((r.next_u64() % 3000) as f32 - 1500.0) / 300.0;
1905 if i % 997 == 0 { base + 14.0 } else { base }
1906 })
1907 .collect();
1908 let cfg = SamplerConfig {
1909 temperature: 1.0,
1910 top_p: 0.95,
1911 top_k: 0,
1912 min_p: 0.0,
1913 repetition_penalty: 1.0,
1914 ..Default::default()
1915 };
1916 assert!(nucleus_sparse_ok(&cfg, n));
1917 let mut sd = SamplerScratch::default();
1918 let mut dense = Vec::new();
1919 distribution_into(&logits, &cfg, &[], &mut sd, None, &mut dense);
1920 let capped = SamplerConfig { top_k: SPARSE_TOPK_MAX as u32, ..cfg.clone() };
1921 let mut ss = SamplerScratch::default();
1922 let mut sp = Vec::new();
1923 let ok = sparse_distribution_into(&logits, &capped, &[], &mut ss, None, &mut sp);
1924 if !(ok && ss.sparse_cut) {
1925 continue;
1926 }
1927 closed += 1;
1928 let dense_nz: Vec<(u32, f32)> = dense
1929 .iter()
1930 .enumerate()
1931 .filter(|&(_, &v)| v > 0.0)
1932 .map(|(i, &v)| (i as u32, v))
1933 .collect();
1934 assert_eq!(dense_nz.len(), sp.len(), "seed {seed}");
1935 for (a, b) in dense_nz.iter().zip(sp.iter()) {
1936 assert_eq!(a.0, b.0, "seed {seed}: ids differ");
1937 assert!((a.1 - b.1).abs() <= 2e-5 * a.1.max(1e-3), "seed {seed}: {} vs {}", a.1, b.1);
1938 }
1939 }
1940 assert!(closed >= 4, "the nucleus closed inside the cap only {closed} times");
1941 }
1942
1943 #[test]
1946 fn top_p_head_matches_the_full_sort() {
1947 for seed in 0..40u64 {
1948 let mut r = SplitMix64::new(seed);
1949 let n = 20_000usize;
1950 let flat = seed % 3 == 0;
1951 let raw: Vec<f32> = (0..n)
1952 .map(|i| {
1953 let u = r.next_f32();
1954 if flat {
1955 ((r.next_u64() % 7) as f32) * 0.5
1956 } else if i % 97 == 0 {
1957 8.0 + 4.0 * u
1958 } else {
1959 u * 3.0
1960 }
1961 })
1962 .collect();
1963 let mut probs = raw.clone();
1964 softmax_inplace_pool(None, &mut probs);
1965 for &tp in &[0.5f32, 0.9, 0.95, 0.999] {
1966 let mut a = probs.clone();
1967 let mut b = probs.clone();
1968 apply_top_p(&mut a, tp);
1969 apply_top_p_full(&mut b, tp);
1970 assert_eq!(a, b, "seed {seed} top_p {tp}");
1971 }
1972 }
1973 }
1974
1975 #[test]
1976 fn file_sampling_defaults_override_only_what_they_set() {
1977 let none = SamplerConfig::from_provenance(None);
1978 assert_eq!(none.top_k, SamplerConfig::default().top_k);
1979 let p = serde_json::json!({"sampling": {
1980 "temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0.0,
1981 "repetition_penalty": 1.0
1982 }});
1983 let c = SamplerConfig::from_provenance(Some(&p));
1984 assert_eq!(
1985 (c.temperature, c.top_p, c.top_k, c.min_p, c.repetition_penalty),
1986 (1.0, 0.95, 0, 0.0, 1.0)
1987 );
1988 assert_eq!(c.presence_penalty, SamplerConfig::default().presence_penalty);
1989 assert!(SamplerConfig::file_has_defaults(Some(&p)));
1990 assert!(!SamplerConfig::file_has_defaults(Some(&serde_json::json!({}))));
1991 }
1992
1993 #[test]
1994 fn top_k_keeps_exactly_k() {
1995 let mut probs = vec![0.1, 0.4, 0.05, 0.3, 0.15];
1996 apply_top_k(&mut probs, 2, &mut Vec::new());
1997 let kept = probs.iter().filter(|&&p| p > 0.0).count();
1998 assert_eq!(kept, 2, "top-k must keep exactly k (was k+1 in v1)");
1999 assert!(probs[1] > 0.0 && probs[3] > 0.0);
2000 }
2001
2002 #[test]
2003 fn rng_reaches_full_cdf() {
2004 let probs = vec![0.25f32; 4];
2007 let mut rng = SplitMix64::new(42);
2008 let mut hits = [0usize; 4];
2009 for _ in 0..4000 {
2010 let i = categorical_sample(&probs, rng.next_f32()) as usize;
2011 hits[i] += 1;
2012 }
2013 for (i, &h) in hits.iter().enumerate() {
2014 assert!(h > 700, "index {i} sampled only {h}/4000 — biased RNG");
2015 }
2016 }
2017
2018 #[test]
2019 fn same_seed_same_sequence() {
2020 let logits: Vec<f32> = (0..32).map(|i| (i as f32 * 0.37).sin()).collect();
2021 let config = SamplerConfig {
2022 temperature: 1.0,
2023 seed: Some(7),
2024 ..Default::default()
2025 };
2026 let run = |seed: u64| -> Vec<u32> {
2027 let mut rng = SplitMix64::new(seed);
2028 (0..16)
2029 .map(|_| sample(&logits, &config, &[], &mut rng))
2030 .collect()
2031 };
2032 assert_eq!(run(7), run(7), "same seed must reproduce");
2033 assert_ne!(run(7), run(8), "different seed must differ");
2034 }
2035}
2036
2037#[cfg(test)]
2038mod loop_monitor_tests {
2039 use super::tail_repeats;
2040
2041 #[test]
2042 fn tail_repeats_needs_three_short_or_two_long_copies() {
2043 let short = [1u32, 2, 3, 4];
2044 let mut ids: Vec<u32> = short.iter().chain(&short).copied().collect();
2045 assert!(!tail_repeats(&ids), "two copies of a short block are rhetoric");
2046 ids.extend_from_slice(&short);
2047 assert!(tail_repeats(&ids));
2048 let long: Vec<u32> = (0..12).collect();
2049 let twice: Vec<u32> = long.iter().chain(&long).copied().collect();
2050 assert!(tail_repeats(&twice));
2051 let varied: Vec<u32> = (0..200).collect();
2052 assert!(!tail_repeats(&varied));
2053 assert_eq!(super::tail_repeat_excess(&twice), Some(12));
2054 let roles = ["system", "user", "assistant", "user", "assistant", "user"];
2055 assert_eq!(super::last_turns_start(roles.iter().copied(), 1), 5);
2056 assert_eq!(super::last_turns_start(roles.iter().copied(), 2), 3);
2057 assert_eq!(super::last_turns_start(roles.iter().copied(), 9), 0);
2058 }
2059}