Skip to main content

superfluid_executor/
sampling.rs

1use superfluid_abi::SamplingParams;
2
3pub fn argmax(row: &[f32]) -> u32 {
4    if row.first().is_none_or(|v| v.is_nan()) {
5        return 0;
6    }
7    let best = max_of(row);
8    const BLOCK: usize = 64;
9    for (b, block) in row.chunks(BLOCK).enumerate() {
10        if max_of(block) == best {
11            let at = block.iter().position(|&v| v == best).unwrap_or(0);
12            return (b * BLOCK + at) as u32;
13        }
14    }
15    0
16}
17
18fn max_of(row: &[f32]) -> f32 {
19    #[cfg(target_arch = "aarch64")]
20    {
21        use std::arch::aarch64::{vdupq_n_f32, vld1q_f32, vmaxnmq_f32, vmaxnmvq_f32};
22        let (chunks, rest) = row.as_chunks::<16>();
23        // SAFETY: NEON is part of the aarch64 baseline; each load reads
24        // four f32 inside a 16-element chunk.
25        let mut best = unsafe {
26            let low = vdupq_n_f32(f32::NEG_INFINITY);
27            let (mut a, mut b, mut c, mut d) = (low, low, low, low);
28            for chunk in chunks {
29                let p = chunk.as_ptr();
30                a = vmaxnmq_f32(a, vld1q_f32(p));
31                b = vmaxnmq_f32(b, vld1q_f32(p.add(4)));
32                c = vmaxnmq_f32(c, vld1q_f32(p.add(8)));
33                d = vmaxnmq_f32(d, vld1q_f32(p.add(12)));
34            }
35            vmaxnmvq_f32(vmaxnmq_f32(vmaxnmq_f32(a, b), vmaxnmq_f32(c, d)))
36        };
37        for &v in rest {
38            if v > best {
39                best = v;
40            }
41        }
42        best
43    }
44    #[cfg(not(target_arch = "aarch64"))]
45    {
46        let mut best = f32::NEG_INFINITY;
47        for &v in row {
48            if v > best {
49                best = v;
50            }
51        }
52        best
53    }
54}
55
56pub fn penalizes(p: &SamplingParams) -> bool {
57    let repeat = if p.repeat_penalty == 0.0 { 1.0 } else { p.repeat_penalty };
58    repeat != 1.0 || p.presence_penalty != 0.0 || p.freq_penalty != 0.0
59}
60
61pub fn apply_penalties(row: &mut [f32], recent: &[u32], p: &SamplingParams, exempt: &std::collections::HashSet<u32>) {
62    if !penalizes(p) {
63        return;
64    }
65    let repeat = if p.repeat_penalty == 0.0 { 1.0 } else { p.repeat_penalty };
66    let mut counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
67    for &t in recent {
68        if !exempt.contains(&t) {
69            *counts.entry(t).or_insert(0) += 1;
70        }
71    }
72    for (t, n) in counts {
73        let Some(l) = row.get_mut(t as usize) else { continue };
74        if repeat != 1.0 {
75            *l = if *l > 0.0 { *l / repeat } else { *l * repeat };
76        }
77        *l -= p.presence_penalty + p.freq_penalty * n as f32;
78    }
79}
80
81pub fn apply_logit_bias(row: &mut [f32], bias: &[(u32, f32)]) {
82    for &(t, b) in bias {
83        if let Some(l) = row.get_mut(t as usize) {
84            *l += b;
85        }
86    }
87}
88
89pub fn uniform(position: u64) -> f64 {
90    let mut z = position.wrapping_add(0x9E37_79B9_7F4A_7C15);
91    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
92    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
93    z ^= z >> 31;
94    (z >> 11) as f64 / (1u64 << 53) as f64
95}
96
97fn by_prob_desc(a: &(u32, f32), b: &(u32, f32)) -> std::cmp::Ordering {
98    b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal).then(a.0.cmp(&b.0))
99}
100
101fn sort_head(cands: &mut [(u32, f32)], head: usize) {
102    if head < cands.len() {
103        cands.select_nth_unstable_by(head - 1, by_prob_desc);
104    }
105    cands[..head].sort_unstable_by(by_prob_desc);
106}
107
108const INITIAL_HEAD: usize = 128;
109
110fn grow(head: usize, n: usize) -> usize {
111    head.saturating_mul(8).clamp(head + 1, n)
112}
113
114const DEAD_BELOW: f32 = -36.736_8;
115
116#[inline(always)]
117fn exp_neg(x: f32) -> f32 {
118    const LOG2E: f32 = std::f32::consts::LOG2_E;
119    const LN2_HI: f32 = 0.693_359_4;
120    const LN2_LO: f32 = -2.121_944_4e-4;
121    let live = x > DEAD_BELOW;
122    let x = if live { x } else { 0.0 };
123    let n = (x * LOG2E - 0.5) as i32;
124    let nf = n as f32;
125    let r = (x - nf * LN2_HI) - nf * LN2_LO;
126    let poly = ((((1.987_569_1e-4 * r + 1.398_2e-3) * r + 8.333_452e-3) * r + 4.166_579_6e-2) * r + 1.666_666_5e-1) * r + 0.5;
127    let y = poly * (r * r) + r + 1.0;
128    let scale = f32::from_bits(((n + 127) as u32) << 23);
129    if live {
130        y * scale
131    } else {
132        0.0
133    }
134}
135
136fn softmax_weights(row: &mut [f32], inv_t: f32) -> f32 {
137    for l in row.iter_mut() {
138        *l *= inv_t;
139    }
140    let max = max_of(row);
141    let mut acc = [0.0f32; 16];
142    let (chunks, rest) = row.as_chunks_mut::<16>();
143    #[cfg(target_arch = "aarch64")]
144    // SAFETY: NEON is part of the aarch64 baseline; every load and store
145    // is four f32 inside a 16-element chunk or the 16-element `acc`.
146    unsafe {
147        use std::arch::aarch64::*;
148        let sums = acc.as_mut_ptr();
149        let top = vdupq_n_f32(max);
150        for chunk in chunks.iter_mut() {
151            let p = chunk.as_mut_ptr();
152            let x = [
153                vsubq_f32(vld1q_f32(p), top),
154                vsubq_f32(vld1q_f32(p.add(4)), top),
155                vsubq_f32(vld1q_f32(p.add(8)), top),
156                vsubq_f32(vld1q_f32(p.add(12)), top),
157            ];
158            if vmaxnmvq_f32(vmaxnmq_f32(vmaxnmq_f32(x[0], x[1]), vmaxnmq_f32(x[2], x[3]))) <= DEAD_BELOW {
159                for (q, _) in x.iter().enumerate() {
160                    vst1q_f32(p.add(4 * q), vdupq_n_f32(0.0));
161                }
162                continue;
163            }
164            for (q, xq) in x.iter().enumerate() {
165                let e = exp_neg_x4(*xq);
166                vst1q_f32(p.add(4 * q), e);
167                vst1q_f32(sums.add(4 * q), vaddq_f32(vld1q_f32(sums.add(4 * q)), e));
168            }
169        }
170    }
171    #[cfg(not(target_arch = "aarch64"))]
172    for chunk in chunks.iter_mut() {
173        for j in 0..16 {
174            let e = exp_neg(chunk[j] - max);
175            chunk[j] = e;
176            acc[j] += e;
177        }
178    }
179    let quarter = |q: usize| (acc[q] + acc[q + 4]) + (acc[q + 8] + acc[q + 12]);
180    let mut sum = (quarter(0) + quarter(1)) + (quarter(2) + quarter(3));
181    for l in rest {
182        let e = exp_neg(*l - max);
183        *l = e;
184        sum += e;
185    }
186    sum
187}
188
189#[cfg(target_arch = "aarch64")]
190#[inline(always)]
191unsafe fn exp_neg_x4(x: std::arch::aarch64::float32x4_t) -> std::arch::aarch64::float32x4_t {
192    use std::arch::aarch64::*;
193    let zero = vdupq_n_f32(0.0);
194    let live = vcgtq_f32(x, vdupq_n_f32(DEAD_BELOW));
195    let x = vbslq_f32(live, x, zero);
196    let n = vcvtq_s32_f32(vsubq_f32(vmulq_f32(x, vdupq_n_f32(std::f32::consts::LOG2_E)), vdupq_n_f32(0.5)));
197    let nf = vcvtq_f32_s32(n);
198    let r = vsubq_f32(vsubq_f32(x, vmulq_f32(nf, vdupq_n_f32(0.693_359_4))), vmulq_f32(nf, vdupq_n_f32(-2.121_944_4e-4)));
199    let mut poly = vaddq_f32(vmulq_f32(vdupq_n_f32(1.987_569_1e-4), r), vdupq_n_f32(1.398_2e-3));
200    poly = vaddq_f32(vmulq_f32(poly, r), vdupq_n_f32(8.333_452e-3));
201    poly = vaddq_f32(vmulq_f32(poly, r), vdupq_n_f32(4.166_579_6e-2));
202    poly = vaddq_f32(vmulq_f32(poly, r), vdupq_n_f32(1.666_666_5e-1));
203    poly = vaddq_f32(vmulq_f32(poly, r), vdupq_n_f32(0.5));
204    let y = vaddq_f32(vaddq_f32(vmulq_f32(poly, vmulq_f32(r, r)), r), vdupq_n_f32(1.0));
205    let scale = vreinterpretq_f32_s32(vshlq_n_s32::<23>(vaddq_s32(n, vdupq_n_s32(127))));
206    vbslq_f32(live, vmulq_f32(y, scale), zero)
207}
208
209fn top_head(probs: &[f32], head: usize, out: &mut Vec<(u32, f32)>) {
210    const BLOCK: usize = 16;
211    out.clear();
212    let n = probs.len();
213    if head >= n / 4 {
214        out.extend(probs.iter().enumerate().map(|(i, &p)| (i as u32, p)));
215        sort_head(out, head);
216        return;
217    }
218    let mut floor = f32::NEG_INFINITY;
219    for (b, block) in probs.chunks(BLOCK).enumerate() {
220        if max_of(block) <= floor {
221            continue;
222        }
223        for (j, &p) in block.iter().enumerate() {
224            if p > floor {
225                out.push(((b * BLOCK + j) as u32, p));
226                if out.len() == 2 * head {
227                    out.select_nth_unstable_by(head - 1, by_prob_desc);
228                    out.truncate(head);
229                    floor = out[head - 1].1;
230                }
231            }
232        }
233    }
234    sort_head(out, head);
235}
236
237pub fn sample(row: &mut [f32], p: &SamplingParams, u: f64) -> u32 {
238    if p.temperature <= 0.0 {
239        return argmax(row);
240    }
241    if row.is_empty() {
242        return 0;
243    }
244    let sum = softmax_weights(row, 1.0 / p.temperature);
245    if sum > 0.0 {
246        for w in row.iter_mut() {
247            *w /= sum;
248        }
249    }
250    let full_total: f32 = if sum > 0.0 { 1.0 } else { 0.0 };
251    let n = row.len();
252    let mut cands: Vec<(u32, f32)> = Vec::new();
253    let k_cap = if p.top_k == 0 { n } else { (p.top_k as usize).clamp(1, n) };
254    let mut head = if p.top_k == 0 { INITIAL_HEAD.min(n) } else { k_cap };
255    loop {
256        top_head(row, head, &mut cands);
257        let front = &cands[..head];
258        let mut keep = head.min(k_cap);
259        let mut cut = p.top_k != 0;
260        if p.min_p > 0.0 {
261            let floor = p.min_p * front[0].1;
262            let c = front[..keep].iter().take_while(|c| c.1 >= floor).count();
263            if c < keep {
264                keep = c.max(1);
265                cut = true;
266            } else if !cut && head < n {
267                head = grow(head, n);
268                continue;
269            }
270        }
271        if p.top_p < 1.0 {
272            let mut acc = 0.0f32;
273            let mut found = None;
274            for (i, c) in front[..keep].iter().enumerate() {
275                acc += c.1;
276                if acc >= p.top_p {
277                    found = Some(i + 1);
278                    break;
279                }
280            }
281            match found {
282                Some(k) => {
283                    keep = k.max(1);
284                    cut = true;
285                }
286                None if !cut && head < n => {
287                    head = grow(head, n);
288                    continue;
289                }
290                None => {}
291            }
292        }
293        let total: f32 = if cut { front[..keep].iter().map(|c| c.1).sum() } else { full_total };
294        let mut target = u as f32 * total;
295        let mut last_with_mass = front[0].0;
296        for c in &front[..keep] {
297            if target < c.1 {
298                return c.0;
299            }
300            target -= c.1;
301            if c.1 > 0.0 {
302                last_with_mass = c.0;
303            }
304        }
305        if cut || head == n {
306            return last_with_mass;
307        }
308        head = grow(head, n);
309    }
310}
311
312pub fn logprobs(row: &[f32], chosen: u32, k: usize) -> (f32, Vec<(u32, f32)>) {
313    let max = row.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
314    let lse = max + row.iter().map(|&l| (l - max).exp()).sum::<f32>().ln();
315    let lp = row.get(chosen as usize).map(|&l| l - lse).unwrap_or(f32::NEG_INFINITY);
316    let k = k.min(row.len());
317    if k == 0 {
318        return (lp, Vec::new());
319    }
320    let mut top: Vec<(u32, f32)> = row.iter().enumerate().map(|(i, &l)| (i as u32, l - lse)).collect();
321    sort_head(&mut top, k);
322    top.truncate(k);
323    (lp, top)
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn uniform_is_a_pure_function_of_position() {
332        assert_eq!(uniform(7), uniform(7));
333        assert_ne!(uniform(7), uniform(8));
334        assert!((0.0..1.0).contains(&uniform(u64::MAX)));
335    }
336
337    #[test]
338    fn greedy_is_lowest_index_argmax() {
339        assert_eq!(argmax(&[1.0, 3.0, 3.0, 2.0]), 1);
340        assert_eq!(argmax(&[]), 0);
341        let plain = |row: &[f32]| -> u32 {
342            let mut best = 0usize;
343            for (i, &v) in row.iter().enumerate() {
344                if v > row[best] {
345                    best = i;
346                }
347            }
348            best as u32
349        };
350        let mut st = 0xA11CE_u64;
351        for n in 1..40usize {
352            for case in 0..40 {
353                let mut row: Vec<f32> = (0..n).map(|_| (lcg(&mut st) * 6.0).floor()).collect();
354                match case % 5 {
355                    1 => row[n / 2] = f32::NAN,
356                    2 => row[0] = f32::NAN,
357                    3 => row[n - 1] = f32::INFINITY,
358                    4 => row.iter_mut().for_each(|v| *v = f32::NEG_INFINITY),
359                    _ => {}
360                }
361                assert_eq!(argmax(&row), plain(&row), "{row:?}");
362            }
363        }
364    }
365
366    #[test]
367    fn top_k_one_is_greedy_at_any_temperature() {
368        let p = SamplingParams { temperature: 1.0, top_k: 1, top_p: 1.0, ..Default::default() };
369        let mut row = vec![0.1, 0.5, 0.2];
370        assert_eq!(sample(&mut row, &p, 0.99), 1);
371    }
372
373    #[test]
374    fn top_p_cuts_on_the_original_mass_after_top_k() {
375        let row: Vec<f32> = [0.4f32, 0.3, 0.3].iter().map(|p| p.ln()).collect();
376        let p = SamplingParams { temperature: 1.0, top_k: 2, top_p: 0.5, ..Default::default() };
377        assert_eq!(sample(&mut row.clone(), &p, 0.95), 1, "the second candidate survives the cut");
378        assert_eq!(sample(&mut row.clone(), &p, 0.1), 0);
379        let p1 = SamplingParams { temperature: 1.0, top_k: 2, top_p: 0.39, ..Default::default() };
380        assert_eq!(sample(&mut row.clone(), &p1, 0.95), 0, "a threshold under the best probability keeps only it");
381    }
382
383    #[test]
384    fn a_draw_at_the_top_of_the_range_stays_on_a_token_with_mass() {
385        let top = 1.0 - f64::EPSILON / 2.0;
386        assert_eq!(top as f32, 1.0, "the draw this is about");
387        let mut row = vec![f32::NEG_INFINITY; 64];
388        (row[5], row[9]) = (1.0, 1.0);
389        let p = SamplingParams { temperature: 1.0, top_k: 40, top_p: 1.0, ..Default::default() };
390        assert_eq!(sample(&mut row.clone(), &p, top), 9, "the last token with any mass");
391        let p = SamplingParams { temperature: 1.0, top_k: 0, top_p: 1.0, ..Default::default() };
392        assert_eq!(sample(&mut row.clone(), &p, top), 9);
393        assert_eq!(sample(&mut row.clone(), &p, 0.0), 5);
394    }
395
396    #[test]
397    fn top_p_zero_keeps_the_top_token() {
398        let row: Vec<f32> = [0.4f32, 0.3, 0.3].iter().map(|p| p.ln()).collect();
399        for top_p in [0.0, -1.0] {
400            let p = SamplingParams { temperature: 1.0, top_p, ..Default::default() };
401            for u in [0.0, 0.5, 0.99] {
402                assert_eq!(sample(&mut row.clone(), &p, u), 0, "top_p {top_p}, draw {u}");
403            }
404        }
405        let off = SamplingParams { temperature: 1.0, top_p: 1.0, ..Default::default() };
406        assert_eq!(sample(&mut row.clone(), &off, 0.99), 2, "1.0 is the whole distribution");
407    }
408
409    #[test]
410    fn repeat_penalty_demotes_a_seen_token() {
411        let p = SamplingParams { repeat_penalty: 100.0, ..Default::default() };
412        let mut row = vec![0.5, 10.0, 0.7];
413        apply_penalties(&mut row, &[1], &p, &Default::default());
414        assert_eq!(argmax(&row), 2);
415    }
416
417    fn reference_sample(row: &[f32], p: &SamplingParams, u: f64) -> u32 {
418        let mut weights = row.to_vec();
419        let sum = softmax_weights(&mut weights, 1.0 / p.temperature);
420        let mut cands: Vec<(u32, f32)> = weights.iter().enumerate().map(|(i, &w)| (i as u32, w / sum)).collect();
421        let full_total: f32 = 1.0;
422        cands.sort_by(by_prob_desc);
423        let mut bounded = false;
424        if p.top_k != 0 {
425            cands.truncate((p.top_k as usize).clamp(1, cands.len()));
426            bounded = true;
427        }
428        if p.min_p > 0.0 {
429            let floor = p.min_p * cands[0].1;
430            let keep = cands.iter().take_while(|c| c.1 >= floor).count().max(1);
431            if keep < cands.len() {
432                bounded = true;
433            }
434            cands.truncate(keep);
435        }
436        if p.top_p < 1.0 {
437            let mut acc = 0.0f32;
438            let mut keep = 0usize;
439            for c in &cands {
440                acc += c.1;
441                keep += 1;
442                if acc >= p.top_p {
443                    bounded = true;
444                    break;
445                }
446            }
447            cands.truncate(keep.max(1));
448        }
449        let total: f32 = if bounded { cands.iter().map(|c| c.1).sum() } else { full_total };
450        let mut target = u as f32 * total;
451        for c in &cands {
452            if target < c.1 {
453                return c.0;
454            }
455            target -= c.1;
456        }
457        cands.last().map(|c| c.0).unwrap_or(0)
458    }
459
460    fn lcg(state: &mut u64) -> f32 {
461        *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
462        ((*state >> 40) as f32) / ((1u64 << 24) as f32)
463    }
464
465    #[test]
466    fn head_sorting_matches_the_full_sort_reference() {
467        let n = 3001usize;
468        let mut st = 0x5EED_u64;
469        for shape in 0..3 {
470            let mut row: Vec<f32> = (0..n).map(|_| lcg(&mut st) * [12.0, 3.0, 0.02][shape]).collect();
471            row[17] += [6.0, 1.0, 0.0][shape];
472            for &top_k in &[0u32, 1, 7, 50, 5000] {
473                for &top_p in &[1.0f32, 0.9, 0.3] {
474                    for &min_p in &[0.0f32, 0.1] {
475                        for &temperature in &[0.7f32, 1.5] {
476                            let p = SamplingParams { temperature, top_k, top_p, min_p, ..Default::default() };
477                            let mut us = vec![0.0, 0.001, 0.25, 0.5, 0.75, 0.999, 0.999_999];
478                            us.extend((0..8).map(|_| lcg(&mut st) as f64));
479                            for u in us {
480                                let want = reference_sample(&row, &p, u);
481                                let got = sample(&mut row.clone(), &p, u);
482                                assert_eq!(got, want, "shape {shape} top_k {top_k} top_p {top_p} min_p {min_p} t {temperature} u {u}");
483                            }
484                        }
485                    }
486                }
487            }
488        }
489    }
490
491    #[test]
492    fn exp_neg_is_the_exponential_to_two_ulp() {
493        let mut worst = 0.0f64;
494        let mut x = 0.0f32;
495        while x > DEAD_BELOW {
496            let (got, want) = (exp_neg(x) as f64, (x as f64).exp());
497            worst = worst.max(((got - want) / want).abs());
498            x -= 0.000_37;
499        }
500        assert!(worst < 2.5e-7, "relative error {worst:e}");
501        assert_eq!(exp_neg(0.0), 1.0);
502        for dead in [DEAD_BELOW, -37.0, -1000.0, f32::NEG_INFINITY, f32::NAN] {
503            assert_eq!(exp_neg(dead), 0.0, "{dead}");
504        }
505    }
506
507    #[test]
508    fn the_row_pass_is_exp_neg_element_for_element() {
509        let mut st = 0xE4B_u64;
510        for n in [1usize, 15, 16, 17, 31, 32, 33, 100, 1000] {
511            let mut row: Vec<f32> = (0..n).map(|_| lcg(&mut st) * 120.0 - 60.0).collect();
512            if n > 20 {
513                (row[3], row[18]) = (f32::NAN, f32::NEG_INFINITY);
514            }
515            let inv_t = 1.3f32;
516            let scaled: Vec<f32> = row.iter().map(|l| l * inv_t).collect();
517            let max = scaled.iter().copied().filter(|v| !v.is_nan()).fold(f32::NEG_INFINITY, f32::max);
518            let want: Vec<f32> = scaled.iter().map(|l| exp_neg(l - max)).collect();
519            let mut acc = [0.0f32; 16];
520            let whole = n / 16 * 16;
521            for (i, e) in want[..whole].iter().enumerate() {
522                acc[i % 16] += e;
523            }
524            let quarter = |q: usize| (acc[q] + acc[q + 4]) + (acc[q + 8] + acc[q + 12]);
525            let mut want_sum = (quarter(0) + quarter(1)) + (quarter(2) + quarter(3));
526            for e in &want[whole..] {
527                want_sum += e;
528            }
529            let sum = softmax_weights(&mut row, inv_t);
530            assert_eq!(row.iter().map(|v| v.to_bits()).collect::<Vec<_>>(), want.iter().map(|v| v.to_bits()).collect::<Vec<_>>(), "n {n}");
531            assert_eq!(sum.to_bits(), want_sum.to_bits(), "n {n}");
532        }
533    }
534
535    #[test]
536    fn top_head_is_the_head_of_the_sorted_row() {
537        let mut st = 0xBEEF_u64;
538        for &n in &[1usize, 15, 16, 17, 400, 5000] {
539            for &levels in &[2.0f32, 37.0, 1.0e6] {
540                let row: Vec<f32> = (0..n).map(|_| (lcg(&mut st) * levels).floor() / levels).collect();
541                let mut whole: Vec<(u32, f32)> = row.iter().enumerate().map(|(i, &p)| (i as u32, p)).collect();
542                whole.sort_by(by_prob_desc);
543                for &head in &[1usize, 2, 7, 128, 1000] {
544                    let head = head.min(n);
545                    let mut got = Vec::new();
546                    top_head(&row, head, &mut got);
547                    assert_eq!(&got[..head], &whole[..head], "n {n} levels {levels} head {head}");
548                }
549            }
550        }
551    }
552
553    #[test]
554    fn a_row_with_no_mass_answers_the_first_token() {
555        let p = SamplingParams { temperature: 0.8, top_k: 20, top_p: 0.9, ..Default::default() };
556        for u in [0.0, 0.5, 0.999] {
557            assert_eq!(sample(&mut vec![f32::NEG_INFINITY; 500], &p, u), 0);
558            assert_eq!(sample(&mut vec![f32::NAN; 500], &p, u), 0);
559        }
560    }
561
562    #[test]
563    #[ignore]
564    fn sampler_cost() {
565        let n = 151_936usize;
566        let mut st = 0xC057_u64;
567        let rows: Vec<Vec<f32>> = (0..3)
568            .map(|shape| {
569                let mut row: Vec<f32> = (0..n).map(|_| lcg(&mut st) * [30.0, 12.0, 45.0][shape]).collect();
570                row[4242] += 8.0;
571                row
572            })
573            .collect();
574        let time = |what: &str, f: &mut dyn FnMut(&[f32]) -> u32| {
575            let mut sink = 0u32;
576            let t = std::time::Instant::now();
577            for i in 0..300 {
578                sink ^= f(&rows[i % 3]);
579            }
580            eprintln!("{what}: {:.0} us ({sink})", t.elapsed().as_secs_f64() * 1e6 / 300.0);
581        };
582        let p = SamplingParams { temperature: 0.7, top_k: 20, top_p: 0.8, ..Default::default() };
583        let open = SamplingParams { temperature: 1.0, top_k: 0, top_p: 0.95, ..Default::default() };
584        time("copy the row", &mut |r| r.to_vec().len() as u32);
585        time("argmax", &mut |r| argmax(r));
586        time("sample top-k 20 top-p 0.8 (with the copy)", &mut |r| sample(&mut r.to_vec(), &p, 0.37));
587        time("sample top-p 0.95, no top-k (with the copy)", &mut |r| sample(&mut r.to_vec(), &open, 0.37));
588        time("reference: exp per element and a full sort (with the copy)", &mut |r| reference_sample(r, &p, 0.37));
589        let mut w = rows[0].clone();
590        let t = std::time::Instant::now();
591        for _ in 0..300 {
592            w.copy_from_slice(&rows[0]);
593            std::hint::black_box(softmax_weights(&mut w, 1.0 / 0.7));
594        }
595        eprintln!("softmax_weights alone (with the copy): {:.0} us", t.elapsed().as_secs_f64() * 1e6 / 300.0);
596        if let Some(path) = std::env::var_os("SUPERFLUID_SAMPLER_ROWS") {
597            let bytes = std::fs::read(path).unwrap();
598            let all: Vec<f32> = bytes.as_chunks::<4>().0.iter().map(|b| f32::from_ne_bytes(*b)).collect();
599            let real: Vec<&[f32]> = all.chunks_exact(n).collect();
600            let mut w = vec![0.0f32; n];
601            let mut cands = Vec::new();
602            let (mut a, mut b, mut c) = (0.0, 0.0, 0.0);
603            for i in 0..300 {
604                w.copy_from_slice(real[i % real.len()]);
605                let t = std::time::Instant::now();
606                let sum = softmax_weights(&mut w, 1.0 / 0.7);
607                a += t.elapsed().as_secs_f64();
608                let t = std::time::Instant::now();
609                for x in w.iter_mut() {
610                    *x /= sum;
611                }
612                b += t.elapsed().as_secs_f64();
613                let t = std::time::Instant::now();
614                top_head(&w, 20, &mut cands);
615                c += t.elapsed().as_secs_f64();
616            }
617            eprintln!("real rows: weights {:.0} us, division {:.0} us, head {:.0} us", a * 1e6 / 300.0, b * 1e6 / 300.0, c * 1e6 / 300.0);
618        }
619    }
620
621    #[test]
622    fn flat_row_draws_deep_in_the_id_order() {
623        let row = vec![0.0f32; 4000];
624        let p = SamplingParams { temperature: 1.0, top_k: 0, top_p: 1.0, ..Default::default() };
625        let got = sample(&mut row.clone(), &p, 0.9999);
626        assert_eq!(got, reference_sample(&row, &p, 0.9999));
627        assert!(got >= 3990, "equal probabilities walk the id order: {got}");
628    }
629
630    #[test]
631    fn logprobs_top_k_is_the_sorted_head() {
632        let row = vec![1.0f32, 5.0, 3.0, 5.0, 0.5];
633        let (_, top) = logprobs(&row, 0, 3);
634        assert_eq!(top.iter().map(|t| t.0).collect::<Vec<_>>(), vec![1, 3, 2]);
635        assert!(top[0].1 > top[2].1);
636    }
637
638    #[test]
639    fn logprobs_are_normalized() {
640        let (lp, top) = logprobs(&[0.0, 0.0], 0, 2);
641        assert!((lp - (0.5f32).ln()).abs() < 1e-6);
642        assert_eq!(top.len(), 2);
643    }
644}