metrale_sampling/
feed_argmax.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Host reference for one row of `argmax_bf16_batch_feed`
4//! (`kernels/gb10/common/argmax_feed.cu`), computed from the same BF16 bytes.
5//!
6//! Owner: metrale-sampling.
7//! Invariants: none beyond the types. NaN input is outside the contract.
8//!
9//! The kernel runs two reductions over a row and combines them:
10//!
11//! * `p`: the plain argmax, the same scan and tree reduction as
12//!   `argmax_bf16_batch`. Each of the `BLOCK` threads keeps the first strict
13//!   maximum of its strided slice above the `-1e30` floor. At each merge of
14//!   threads `t` and `t + s`, `t` keeps its value unless `t + s` holds a
15//!   strictly larger one. Ties therefore follow the tree order, not the
16//!   lowest index. [`plain_kernel_argmax`] simulates this, floor included.
17//! * `q`: the row with the two masked ids set to `-inf` (as
18//!   `PostCloseThinkMask` does) and the last index holding the maximum, the
19//!   tie rule of the temperature-0 host sampler ([`masked_last_wins`]).
20//! * The answer is `q` when `p` is a masked id, else `p` ([`feed_argmax`]).
21//!   The synchronous decode step does the same: it takes the device argmax
22//!   unless a `think_ended` row's argmax landed on a masked id, and then
23//!   samples on the host.
24//!
25//! `feed_argmax_tests.rs` checks `q` against the host sampler, `p` against
26//! `argmax_bf16` for rows with a unique maximum, and pins the tie cases.
27
28/// 2026-09-25: "No id" in a row mask.
29pub const NO_ID: u32 = u32::MAX;
30/// 2026-09-25: The floor the plain kernel starts its running maximum at.
31pub const PLAIN_FLOOR: f32 = -1e30;
32/// 2026-09-25: Threads per row block of the kernel; the tie order depends on it.
33pub const BLOCK: usize = 1024;
34
35/// 2026-09-25: BF16 bits to f32, exact (what `__bfloat162float` computes).
36#[inline]
37pub fn bf16_to_f32(bits: u16) -> f32 {
38    f32::from_bits((bits as u32) << 16)
39}
40
41/// 2026-09-25: `p`: the plain kernel argmax of `row`, tie order and floor included.
42pub fn plain_kernel_argmax(row: &[f32]) -> u32 {
43    let mut val = [PLAIN_FLOOR; BLOCK];
44    let mut idx = [0u32; BLOCK];
45    for (i, &v) in row.iter().enumerate() {
46        let t = i % BLOCK;
47        if v > val[t] {
48            val[t] = v;
49            idx[t] = i as u32;
50        }
51    }
52    let mut s = BLOCK / 2;
53    while s > 0 {
54        for t in 0..s {
55            if val[t + s] > val[t] {
56                val[t] = val[t + s];
57                idx[t] = idx[t + s];
58            }
59        }
60        s /= 2;
61    }
62    idx[0]
63}
64
65/// 2026-09-25: The row with the masked ids at `-inf`; out-of-range ids are ignored.
66pub fn masked_row(row: &[f32], mask: [u32; 2]) -> Vec<f32> {
67    let mut out = row.to_vec();
68    for m in mask {
69        if let Some(v) = out.get_mut(m as usize) {
70            *v = f32::NEG_INFINITY;
71        }
72    }
73    out
74}
75
76/// 2026-09-25: `q`: the last index holding the maximum of [`masked_row`], with
77/// the maximum starting at `-inf`; an empty row gives 0.
78pub fn masked_last_wins(row: &[f32], mask: [u32; 2]) -> u32 {
79    let mut best = f32::NEG_INFINITY;
80    let mut idx = 0u32;
81    for (i, &v) in masked_row(row, mask).iter().enumerate() {
82        if v > best || (v == best && i as u32 > idx) {
83            best = v;
84            idx = i as u32;
85        }
86    }
87    idx
88}
89
90/// 2026-09-25: One row of `argmax_bf16_batch_feed`, from the BF16 bits.
91pub fn feed_argmax(row_bf16: &[u16], mask: [u32; 2]) -> u32 {
92    let row: Vec<f32> = row_bf16.iter().map(|&b| bf16_to_f32(b)).collect();
93    feed_argmax_f32(&row, mask)
94}
95
96/// 2026-09-25: [`feed_argmax`] over an f32 row.
97pub fn feed_argmax_f32(row: &[f32], mask: [u32; 2]) -> u32 {
98    let p = plain_kernel_argmax(row);
99    if p == mask[0] || p == mask[1] {
100        masked_last_wins(row, mask)
101    } else {
102        p
103    }
104}