143 lines
7.6 KiB
C
143 lines
7.6 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_topk_reference.h
|
|
* @brief The CPU reference the on-card top-k and sampler are gated against: an iterative
|
|
* argmax over an untilized bf16 logit row, and Pantheon's sparse-survivor sampler.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* WHY PCC IS THE WRONG GATE FOR THIS OP, ENTIRELY
|
|
* ---------------------------------------------------------------------------
|
|
* Every other op in this backend is accepted at PCC ≥ 0.99 against a CPU fp32 reference.
|
|
* **That gate cannot see this op's failure mode.** Top-k returns INDICES; a result whose
|
|
* k values are all correct and whose k-th index is a tie resolved the other way correlates
|
|
* at 1.0 with the truth and is a wrong answer — the model emits a different token. So the
|
|
* acceptance here is EXACT INDEX AGREEMENT, pair for pair, and the sampled token id
|
|
* compared as an integer.
|
|
*
|
|
* The same reasoning one level down: bf16 has EIGHT mantissa bits and MiniCPM5-1B's
|
|
* vocabulary is 130,560, so a logit row has on average two entries per representable value
|
|
* before the distribution is even considered. **Ties are the main event.** The inputs the
|
|
* suite builds therefore plant exact ties and adjacent-representable near-ties on purpose,
|
|
* at and around the selection boundary, because that is where a reference and an
|
|
* implementation disagree silently.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* INDEPENDENCE — WHAT THIS FILE DELIBERATELY DOES NOT SHARE WITH THE KERNEL
|
|
* ---------------------------------------------------------------------------
|
|
* The selection here is the plain O(N·V) iterative argmax on **fp32 values**, with the
|
|
* comparator written as a float comparison — NOT the kernel's order-preserving integer key
|
|
* (`ftk_key_from_bf16`) and NOT its accept-interval pruning. A reference that shared the
|
|
* kernel's representation could not disagree with it, and a check that cannot disagree is
|
|
* not a check (dev notes §3, "any metric computed on a round trip through your own code is
|
|
* measuring your code's self-consistency").
|
|
*
|
|
* The ONE thing that is shared by construction is `ft_tt_topk_reference_exp()`, because an
|
|
* exact sampled-token gate needs both sides to compute the same exponential and the device
|
|
* has no FPU and no libm. It is a transliteration, and `test_topk_ops.c` PARSES the
|
|
* coefficients out of `kernels/resident_topk_protocol.h` and compares them against these —
|
|
* a retyped constant is exactly the drift dev notes §4 catalogues. `expf` is kept beside it
|
|
* so the approximation itself has a measured error rather than an assumed one.
|
|
*
|
|
* @status Off-hardware only; no device access anywhere in this file. Drives both the
|
|
* off-hardware gate and the on-card acceptance.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#ifndef FT_TT_TOPK_REFERENCE_H
|
|
#define FT_TT_TOPK_REFERENCE_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include "../ft_tt_op_sample.h"
|
|
|
|
/**
|
|
* Exact top-`select_count` of an untilized bf16 logit row.
|
|
*
|
|
* Iterative argmax, `select_count` rounds, each taking the global maximum of what is left
|
|
* with the comparator `(v > best) || (v == best && index < best_index)` — value-for-value
|
|
* and tie-for-tie what Pantheon's `fc_topk_logits` reproduces from the host's serial scan.
|
|
* Output is DESCENDING by value, ties by ascending index, which is the order the card
|
|
* writes and the order `fc_topk_logits` returns.
|
|
*
|
|
* @param row `vocab` bf16 patterns, untilized, in vocabulary index order
|
|
* @param vocab its length
|
|
* @param select_count pairs to produce; <= FT_TT_OP_SAMPLE_MAX_SELECT and <= vocab
|
|
* @param out_indices receives `select_count` vocabulary indices
|
|
* @param out_values receives `select_count` fp32 values (exact widenings of the bf16)
|
|
* @return 0 on success, -1 on a bad argument
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: yes
|
|
*/
|
|
int ft_tt_topk_reference_select(const uint16_t *row, uint32_t vocab, uint32_t select_count,
|
|
uint32_t *out_indices, float *out_values);
|
|
|
|
/** The order-preserving integer key, for the exhaustive 65,536-pattern order sweep. This
|
|
* IS the kernel's map, transliterated, and the sweep is what proves it agrees with float
|
|
* order everywhere except the documented ±0 case. */
|
|
uint32_t ft_tt_topk_reference_key(uint16_t bf16_bits);
|
|
|
|
/** The kernel's software exp, transliterated. Only x <= 0 is defined. */
|
|
float ft_tt_topk_reference_exp(float x);
|
|
|
|
/** xorshift32, and the [0,1) draw built on it. Both must match the kernel exactly or the
|
|
* sampled-token gate is statistical rather than exact. */
|
|
uint32_t ft_tt_topk_reference_rng_next(uint32_t *state);
|
|
float ft_tt_topk_reference_rng_unit(uint32_t *state);
|
|
|
|
/**
|
|
* The sampler, transliterated from Pantheon `src/core/engine.c` `sparse_sample()` and
|
|
* `sparse_argmax()` — the path the K80 ALREADY takes whenever `fc_topk_logits` reduced the
|
|
* row, so this is the deployed semantics and not a new approximation.
|
|
*
|
|
* THE ORDER IS THE CONTRACT AND IT IS ASSERTED SEPARATELY:
|
|
* temperature <= 0 -> argmax on the RAW values, NO repetition penalty, lowest id wins
|
|
* temperature > 0 -> penalty -> temperature -> softmax -> top_k -> top_p ->
|
|
* renormalize -> cumulative draw
|
|
* Applying the penalty before the greedy argmax is a real bug that shipped once and lost an
|
|
* answer on a near tie; the two paths are separate functions here for the same reason they
|
|
* are separate in the kernel.
|
|
*
|
|
* @param indices survivors, DESCENDING by value as the card writes them
|
|
* @param values their fp32 values
|
|
* @param count how many
|
|
* @param params the knobs; `rng_seed` is ignored, `rng_state` carries the stream
|
|
* @param recent the repetition window, OLDEST FIRST; may be NULL
|
|
* @param recent_count its length
|
|
* @param rng_state in/out; advanced once per stochastic draw, untouched when greedy
|
|
* @param use_libm_exp non-zero uses `expf` instead of the transliterated polynomial — the
|
|
* arm that measures the approximation rather than assuming it
|
|
* @return the sampled vocabulary index
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: yes
|
|
*/
|
|
uint32_t ft_tt_topk_reference_sample(const uint32_t *indices, const float *values,
|
|
uint32_t count,
|
|
const ft_tt_op_sample_params *params,
|
|
const uint32_t *recent, uint32_t recent_count,
|
|
uint32_t *rng_state, int use_libm_exp);
|
|
|
|
/**
|
|
* Tilize a bf16 logit row into the face-ordered [32 x vocab] tile strip the card reads,
|
|
* writing `row` with the logits and every other row with `filler`.
|
|
*
|
|
* The filler is not decoration. The LM head runs at Mt = 1, so 32 token rows exist and one
|
|
* is being selected over; filling the other 31 with values that would WIN if the kernel
|
|
* read the wrong row is what turns a row-indexing bug into a red test instead of a
|
|
* plausible answer.
|
|
*
|
|
* @param row_values `vocab` bf16 patterns
|
|
* @param vocab must be a multiple of 32
|
|
* @param row 0..31, the token row
|
|
* @param filler bf16 pattern written to the other 31 rows
|
|
* @param out receives `vocab / 32 * 2048` bytes
|
|
* @param out_bytes its size
|
|
* @return 0 on success, -1 on a bad argument
|
|
*/
|
|
int ft_tt_topk_reference_tilize_row(const uint16_t *row_values, uint32_t vocab, uint32_t row,
|
|
uint16_t filler, void *out, size_t out_bytes);
|
|
|
|
#endif /* FT_TT_TOPK_REFERENCE_H */
|