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 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 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}