Foundry/tt/convert/tests/test_bfp.c

428 lines
17 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file test_bfp.c
* @brief Off-device test: ft_bfp.c is bit-exact against the committed block-float
* reference vectors, for BFP8 and BFP4, exp_a and exp_b, pack and unpack.
*
* NO DEVICE IS TOUCHED. The comparison is against
* reference/ft_bfp_reference_vectors.h, written by
* reference/generate_bfp_reference.c only after its independent
* definition-level reference agreed with ft_bfp.c on every byte and word.
*
* ---------------------------------------------------------------------------
* THE VECTORS ARE THE TEST, SO THE VECTORS ARE THEMSELVES CHECKED
* ---------------------------------------------------------------------------
* "Bit-exact on random floats" is a weak claim: random floats never land on a
* round-to-nearest-even tie, never carry a denormal, and rarely span more than
* a couple of binades inside one 16-element block. So this file first asserts
* what the input vectors actually CONTAIN — ties of both parities, exact
* zeros, signed zeros, denormals, infinities, NaN, mantissa saturation, and
* exponent differences past the 31-bit shift boundary — and only then asserts
* that the packed bytes match. A coverage check that fails means the vectors
* stopped testing something, even if every byte still matches.
*
* @status Green on macOS/arm64 under
* `cc -std=c99 -Wall -Wextra -Werror -pedantic -Wshadow -Wconversion`.
* Mutation-proven: see docs/tt_bringup_log.md §12.
* @issues None
* @todo None
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "ft_bfp.h"
#include "ft_tt_tilize.h"
#include "ft_bfp_reference_vectors.h"
/* ------------------------------------------------------------------------- */
/* Tiny harness */
/* ------------------------------------------------------------------------- */
static int checks_run;
static int checks_failed;
static void check(int condition, const char *what)
{
checks_run++;
if (condition) {
printf(" ok %s\n", what);
} else {
checks_failed++;
printf(" FAIL %s\n", what);
}
}
static void check_u64(uint64_t got, uint64_t want, const char *what)
{
checks_run++;
if (got == want) {
printf(" ok %s = %" PRIu64 "\n", what, got);
} else {
checks_failed++;
printf(" FAIL %s = %" PRIu64 ", expected %" PRIu64 "\n", what, got, want);
}
}
static void section(const char *name)
{
printf("\n== %s ==\n", name);
}
/* ------------------------------------------------------------------------- */
/* Vector coverage — what is actually in the inputs */
/* ------------------------------------------------------------------------- */
#define TOTAL_VALUES (FT_BFP_REF_TILE_COUNT * FT_BFP_REF_TILE_VALUES)
typedef struct {
uint32_t zeros;
uint32_t negative_zeros;
uint32_t denormals;
uint32_t infinities;
uint32_t nans;
uint32_t max_mantissa; /**< fraction 0x7fffff — the saturation feeder */
uint32_t blocks;
uint32_t blocks_shift_over_31; /**< blocks with an exponent spread > 31 */
uint32_t blocks_uniform_exp;
} vector_coverage;
static void survey_vectors(vector_coverage *cov)
{
memset(cov, 0, sizeof(*cov));
for (uint32_t i = 0; i < TOTAL_VALUES; i++) {
uint32_t bits = ft_bfp_ref_input_bits[i];
uint32_t exp = (bits >> 23) & 0xffu;
uint32_t frac = bits & 0x7fffffu;
if (exp == 0 && frac == 0) {
cov->zeros++;
if (bits >> 31)
cov->negative_zeros++;
} else if (exp == 0) {
cov->denormals++;
} else if (exp == 0xffu && frac == 0) {
cov->infinities++;
} else if (exp == 0xffu) {
cov->nans++;
}
if (frac == 0x7fffffu)
cov->max_mantissa++;
}
/* A block is 16 contiguous values of a face row; in the row-major tile
* that is a contiguous run of 16, so blocks tile the input linearly. */
for (uint32_t b = 0; b < TOTAL_VALUES / FT_BFP_BLOCK_VALUES; b++) {
const uint32_t *block = &ft_bfp_ref_input_bits[b * FT_BFP_BLOCK_VALUES];
uint32_t min_exp = 0xffffffffu;
uint32_t max_exp = 0;
cov->blocks++;
for (uint32_t j = 0; j < FT_BFP_BLOCK_VALUES; j++) {
uint32_t exp = (block[j] >> 23) & 0xffu;
if (exp == 0)
continue; /* zero and denormal do not participate */
if (exp < min_exp)
min_exp = exp;
if (exp > max_exp)
max_exp = exp;
}
if (min_exp == 0xffffffffu)
continue;
if (max_exp - min_exp > 31)
cov->blocks_shift_over_31++;
if (max_exp == min_exp)
cov->blocks_uniform_exp++;
}
}
static void test_vector_coverage(void)
{
vector_coverage cov;
section("reference vectors cover the interesting paths");
survey_vectors(&cov);
check(cov.zeros > 0, "vectors contain exact zeros");
check(cov.negative_zeros > 0, "vectors contain negative zeros");
check(cov.denormals > 0, "vectors contain denormals");
check(cov.infinities > 0, "vectors contain infinities");
check(cov.nans > 0, "vectors contain NaN");
check(cov.max_mantissa > 0, "vectors contain full mantissas (saturation feeder)");
check(cov.blocks_shift_over_31 > 0, "vectors contain blocks whose exponent spread exceeds 31");
check(cov.blocks_uniform_exp > 0, "vectors contain blocks with a single exponent");
printf(" zeros=%u (-0: %u) denormals=%u inf=%u nan=%u full-mantissa=%u\n", cov.zeros, cov.negative_zeros,
cov.denormals, cov.infinities, cov.nans, cov.max_mantissa);
printf(" blocks=%u spread>31=%u uniform-exp=%u\n", cov.blocks, cov.blocks_shift_over_31,
cov.blocks_uniform_exp);
}
/* ------------------------------------------------------------------------- */
/* Ties — proven reached, not assumed */
/* ------------------------------------------------------------------------- */
static void test_tie_coverage(void)
{
static float values[TOTAL_VALUES];
static unsigned char packed[TOTAL_VALUES * 2];
uint64_t seen = 0, up = 0;
section("round-to-nearest-even ties are actually exercised");
for (uint32_t i = 0; i < TOTAL_VALUES; i++)
memcpy(&values[i], &ft_bfp_ref_input_bits[i], sizeof(float));
ft_bfp_tie_counters_reset();
check(ft_bfp_pack_tiles(FT_BFP_BFP8_B, values, FT_BFP_REF_TILE_COUNT, packed) == FT_BFP_OK, "BFP8 pack succeeded");
ft_bfp_tie_counters(&seen, &up);
check(seen > 0, "BFP8 pack hit the tie path");
check(up > 0, "BFP8 ties rounded UP at least once (odd guard bit)");
check(seen > up, "BFP8 ties rounded DOWN at least once (even guard bit)");
printf(" BFP8 ties: %" PRIu64 " (%" PRIu64 " up, %" PRIu64 " down)\n", seen, up, seen - up);
ft_bfp_tie_counters_reset();
check(ft_bfp_pack_tiles(FT_BFP_BFP4_B, values, FT_BFP_REF_TILE_COUNT, packed) == FT_BFP_OK, "BFP4 pack succeeded");
ft_bfp_tie_counters(&seen, &up);
check(seen > 0, "BFP4 pack hit the tie path");
check(up > 0, "BFP4 ties rounded UP at least once");
check(seen > up, "BFP4 ties rounded DOWN at least once");
printf(" BFP4 ties: %" PRIu64 " (%" PRIu64 " up, %" PRIu64 " down)\n", seen, up, seen - up);
ft_bfp_tie_counters_reset();
}
/* ------------------------------------------------------------------------- */
/* Bit-exactness against the reference vectors */
/* ------------------------------------------------------------------------- */
static void compare_packed(ft_bfp_format format, const char *name, const uint32_t *expected, size_t expected_words)
{
static float values[TOTAL_VALUES];
static uint32_t packed[TOTAL_VALUES]; /* >= 272 words per tile, comfortably */
size_t words = ft_bfp_tile_bytes(format) / 4u * FT_BFP_REF_TILE_COUNT;
size_t mismatches = 0;
size_t first_mismatch = 0;
for (uint32_t i = 0; i < TOTAL_VALUES; i++)
memcpy(&values[i], &ft_bfp_ref_input_bits[i], sizeof(float));
memset(packed, 0xa5, sizeof(packed));
if (ft_bfp_pack_tiles(format, values, FT_BFP_REF_TILE_COUNT, packed) != FT_BFP_OK) {
check(0, name);
return;
}
if (words != expected_words) {
checks_run++;
checks_failed++;
printf(" FAIL %s: produced %zu words, reference has %zu\n", name, words, expected_words);
return;
}
for (size_t i = 0; i < words; i++) {
if (packed[i] != expected[i]) {
if (mismatches == 0)
first_mismatch = i;
mismatches++;
}
}
checks_run++;
if (mismatches == 0) {
printf(" ok %s: %zu/%zu words identical to the reference\n", name, words, words);
} else {
checks_failed++;
printf(" FAIL %s: %zu/%zu words differ; first at word %zu (got 0x%08x, reference 0x%08x)\n", name, mismatches,
words, first_mismatch, packed[first_mismatch], expected[first_mismatch]);
}
}
/** Pack the reference tiles one call per tile; 1 if the words match the reference. */
static int per_tile_packing_matches_reference(void)
{
static float values[TOTAL_VALUES];
static uint32_t packed[TOTAL_VALUES];
size_t tile_words = ft_bfp_tile_bytes(FT_BFP_BFP8_B) / 4u;
for (uint32_t i = 0; i < TOTAL_VALUES; i++)
memcpy(&values[i], &ft_bfp_ref_input_bits[i], sizeof(float));
for (uint32_t t = 0; t < FT_BFP_REF_TILE_COUNT; t++)
if (ft_bfp_pack_tiles(FT_BFP_BFP8_B, values + t * FT_BFP_REF_TILE_VALUES, 1, packed + t * tile_words) != FT_BFP_OK)
return 0;
return memcmp(packed, ft_bfp_ref_bfp8b, sizeof(ft_bfp_ref_bfp8b)) == 0;
}
static void test_pack_bit_exact(void)
{
section("pack is bit-exact against the reference vectors");
check_u64(ft_bfp_tile_bytes(FT_BFP_BFP8_B), 1088, "BFP8 tile bytes");
check_u64(ft_bfp_tile_bytes(FT_BFP_BFP4_B), 576, "BFP4 tile bytes");
check_u64(ft_bfp_tile_bytes(FT_BFP_BFP8_A), 1088, "BFP8 exp_a tile bytes");
check_u64(ft_bfp_tile_bytes(FT_BFP_BFP4_A), 576, "BFP4 exp_a tile bytes");
compare_packed(FT_BFP_BFP8_B, "BFP8_b", ft_bfp_ref_bfp8b,
sizeof(ft_bfp_ref_bfp8b) / sizeof(ft_bfp_ref_bfp8b[0]));
compare_packed(FT_BFP_BFP4_B, "BFP4_b", ft_bfp_ref_bfp4b,
sizeof(ft_bfp_ref_bfp4b) / sizeof(ft_bfp_ref_bfp4b[0]));
compare_packed(FT_BFP_BFP8_A, "BFP8 exp_a", ft_bfp_ref_bfp8_expa,
sizeof(ft_bfp_ref_bfp8_expa) / sizeof(ft_bfp_ref_bfp8_expa[0]));
compare_packed(FT_BFP_BFP4_A, "BFP4 exp_a", ft_bfp_ref_bfp4_expa,
sizeof(ft_bfp_ref_bfp4_expa) / sizeof(ft_bfp_ref_bfp4_expa[0]));
/* compare_packed packs all four tiles in one call; pin that packing them one
* call per tile gives the same words. */
check(per_tile_packing_matches_reference(), "per-tile and single-call packing agree (BFP8_b)");
}
static void test_unpack_bit_exact(void)
{
size_t index = 0;
size_t mismatches = 0;
section("unpack is bit-exact against the reference vectors");
for (uint32_t e = 0; e < FT_BFP_REF_UNPACK_EXP_B_COUNT; e++) {
uint32_t shared = ft_bfp_ref_unpack_shared_exp_b[e];
for (uint32_t d = 0; d < 256; d++) {
if (ft_bfp_convert_datum(FT_BFP_BFP8_B, (uint8_t)d, shared) != ft_bfp_ref_unpack_bfp8b[index])
mismatches++;
index++;
}
}
check_u64(mismatches, 0, "BFP8_b unpack mismatches over 256 data x every shared exponent");
index = 0;
mismatches = 0;
for (uint32_t e = 0; e < FT_BFP_REF_UNPACK_EXP_B_COUNT; e++) {
uint32_t shared = ft_bfp_ref_unpack_shared_exp_b[e];
for (uint32_t d = 0; d < 16; d++) {
if (ft_bfp_convert_datum(FT_BFP_BFP4_B, (uint8_t)d, shared) != ft_bfp_ref_unpack_bfp4b[index])
mismatches++;
index++;
}
}
check_u64(mismatches, 0, "BFP4_b unpack mismatches");
index = 0;
mismatches = 0;
for (uint32_t e = 0; e < FT_BFP_REF_UNPACK_EXP_A_COUNT; e++) {
uint32_t shared = ft_bfp_ref_unpack_shared_exp_a[e];
for (uint32_t d = 0; d < 256; d++) {
if (ft_bfp_convert_datum(FT_BFP_BFP8_A, (uint8_t)d, shared) != ft_bfp_ref_unpack_bfp8_expa[index])
mismatches++;
index++;
}
}
check_u64(mismatches, 0, "BFP8 exp_a unpack mismatches");
index = 0;
mismatches = 0;
for (uint32_t e = 0; e < FT_BFP_REF_UNPACK_EXP_A_COUNT; e++) {
uint32_t shared = ft_bfp_ref_unpack_shared_exp_a[e];
for (uint32_t d = 0; d < 16; d++) {
if (ft_bfp_convert_datum(FT_BFP_BFP4_A, (uint8_t)d, shared) != ft_bfp_ref_unpack_bfp4_expa[index])
mismatches++;
index++;
}
}
check_u64(mismatches, 0, "BFP4 exp_a unpack mismatches");
}
/* ------------------------------------------------------------------------- */
/* Behaviour the header promises */
/* ------------------------------------------------------------------------- */
static float float_from_bits(uint32_t bits)
{
float f;
memcpy(&f, &bits, sizeof(f));
return f;
}
static uint32_t bits_from_float(float f)
{
uint32_t bits;
memcpy(&bits, &f, sizeof(bits));
return bits;
}
static void test_documented_behaviour(void)
{
static float tile[FT_TT_TILE_VALUES];
static unsigned char packed[1088];
static float restored[FT_TT_TILE_VALUES];
section("documented edge behaviour");
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x00000000u, 127), 0, "+0 packs to datum 0");
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x80000000u, 127), 0, "-0 packs to datum 0 (sign dropped)");
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x00000001u, 127), 0, "smallest denormal packs to datum 0");
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x007fffffu, 127), 0, "largest denormal packs to datum 0");
/* 1.99999988 under its own exponent: mantissa 0xffffff >> 17 = 0x7f, and
* the residue rounds up, which must saturate rather than carry. */
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x3f7fffffu, 126), 0x7f, "rounding overflow saturates at 0x7f");
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP4_B, 0x3f7fffffu, 126), 0x7, "BFP4 rounding overflow saturates");
/* A value 2^40 below the block's shared exponent shifts away entirely. */
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0xbf800000u, 127 + 40), 0,
"value far below the shared exponent packs to +0, not -0");
/* Exactly representable: 1.0 with shared exponent 127 is 0x40, and it must
* come back as exactly 1.0. */
check_u64(ft_bfp_convert_float_bits(FT_BFP_BFP8_B, 0x3f800000u, 127), 0x40, "1.0 packs to 0x40");
check_u64(ft_bfp_convert_datum(FT_BFP_BFP8_B, 0x40, 127), 0x3f800000u, "0x40 unpacks to exactly 1.0");
/* Whole-tile round trip on values that BFP8 represents exactly: a power of
* two per block plus multiples of 2^-7 of it. */
for (uint32_t row = 0; row < FT_TT_TILE_HEIGHT; row++) {
for (uint32_t col = 0; col < FT_TT_TILE_WIDTH; col++)
tile[row * FT_TT_TILE_WIDTH + col] = float_from_bits(0x3f800000u) * (float)(64 + (col % 64)) / 64.0f;
}
check(ft_bfp_pack_tiles(FT_BFP_BFP8_B, tile, 1, packed) == FT_BFP_OK, "pack of an exactly-representable tile");
check(ft_bfp_unpack_tiles(FT_BFP_BFP8_B, packed, 1, restored) == FT_BFP_OK, "unpack of that tile");
{
size_t differing = 0;
for (uint32_t i = 0; i < FT_TT_TILE_VALUES; i++) {
if (bits_from_float(restored[i]) != bits_from_float(tile[i]))
differing++;
}
check_u64(differing, 0, "exactly-representable tile survives pack+unpack unchanged");
}
section("argument validation");
check(ft_bfp_pack_tiles((ft_bfp_format)99, tile, 1, packed) == FT_BFP_EINVAL, "unknown format is rejected");
check(ft_bfp_pack_tiles(FT_BFP_BFP8_B, NULL, 1, packed) == FT_BFP_EINVAL, "NULL input is rejected");
check(ft_bfp_pack_tiles(FT_BFP_BFP8_B, tile, 1, NULL) == FT_BFP_EINVAL, "NULL output is rejected");
check(ft_bfp_pack_tiles(FT_BFP_BFP8_B, NULL, 0, NULL) == FT_BFP_OK, "zero tiles is a no-op, not an error");
check_u64(ft_bfp_tile_bytes((ft_bfp_format)99), 0, "unknown format has no tile size");
}
int main(void)
{
printf("test_bfp — ft_bfp.c against the block-float reference vectors\n");
test_vector_coverage();
test_tie_coverage();
test_pack_bit_exact();
test_unpack_bit_exact();
test_documented_behaviour();
printf("\n%d checks, %d failed\n", checks_run, checks_failed);
return checks_failed == 0 ? 0 : 1;
}