114 lines
3.8 KiB
C
114 lines
3.8 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file fm_golden_vectors.h
|
|
* @brief Reader for the PNGOLDEN1 CPU-reference vectors the Metal kernel
|
|
* tests check against. Header-only so a test file just includes it.
|
|
*
|
|
* PROVENANCE: the .bin files in metal/tests/golden are byte copies of the
|
|
* qwen35moe golden set generated by Pantheon's gen_golden_qwen35.c from a
|
|
* fixed xorshift32 seed (0x9E3779B9), regenerated there with
|
|
* `make USE_BLAS=1 golden`. They are checked in here so foundry's tests
|
|
* need no sibling checkout. Point FM_GOLDEN_DIR at a freshly regenerated
|
|
* Pantheon set to check for drift.
|
|
*
|
|
* FILE FORMAT (self-describing):
|
|
* line 1, ASCII: PNGOLDEN1 <name> <dtype> <count> seed=0x9E3779B9\n
|
|
* remainder: <count> little-endian <dtype> values
|
|
*
|
|
* @status Reads the f32 files, which is every vector these tests use.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#ifndef FOUNDRY_METAL_TEST_GOLDEN_VECTORS_H
|
|
#define FOUNDRY_METAL_TEST_GOLDEN_VECTORS_H
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Directory the vectors are read from. Set once at startup. */
|
|
static char fm_golden_dir[1024] = "metal/tests/golden";
|
|
|
|
/**
|
|
* Points the reader at the golden directory: $FM_GOLDEN_DIR if set,
|
|
* otherwise the `golden` directory beside the test binary, which makes the
|
|
* tests runnable from any working directory.
|
|
*
|
|
* @param argv0 argv[0] of the test binary
|
|
*/
|
|
static void fm_golden_locate(const char *argv0)
|
|
{
|
|
const char *env = getenv("FM_GOLDEN_DIR");
|
|
if (env && env[0]) {
|
|
snprintf(fm_golden_dir, sizeof(fm_golden_dir), "%s", env);
|
|
return;
|
|
}
|
|
const char *slash = argv0 ? strrchr(argv0, '/') : NULL;
|
|
if (slash)
|
|
snprintf(fm_golden_dir, sizeof(fm_golden_dir), "%.*s/golden",
|
|
(int)(slash - argv0), argv0);
|
|
}
|
|
|
|
/**
|
|
* Reads a PNGOLDEN1 f32 vector.
|
|
*
|
|
* Never returns a short or silently defaulted buffer: a missing file, a bad
|
|
* magic, a non-f32 dtype, a count that disagrees with `expect_count`, or a
|
|
* truncated payload all print the reason and return NULL, so a test can
|
|
* only ever run against the real oracle.
|
|
*
|
|
* @param name File name inside the golden directory
|
|
* @param expect_count Element count the caller requires
|
|
* @return malloc'd array of expect_count floats (caller frees),
|
|
* or NULL
|
|
*/
|
|
static float *fm_golden_load_f32(const char *name, size_t expect_count)
|
|
{
|
|
char path[1152];
|
|
char header[256];
|
|
char magic[16] = {0}, vec_name[64] = {0}, dtype[8] = {0};
|
|
unsigned long count = 0;
|
|
float *values = NULL;
|
|
FILE *f = NULL;
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", fm_golden_dir, name);
|
|
f = fopen(path, "rb");
|
|
if (!f) {
|
|
fprintf(stderr, " golden: cannot open %s\n", path);
|
|
return NULL;
|
|
}
|
|
if (!fgets(header, sizeof(header), f)) {
|
|
fprintf(stderr, " golden: %s has no header line\n", path);
|
|
goto cleanup;
|
|
}
|
|
if (sscanf(header, "%15s %63s %7s %lu", magic, vec_name, dtype, &count) != 4 ||
|
|
strcmp(magic, "PNGOLDEN1") != 0 || strcmp(dtype, "f32") != 0) {
|
|
fprintf(stderr, " golden: %s has a bad header: %s", path, header);
|
|
goto cleanup;
|
|
}
|
|
if (count != expect_count) {
|
|
fprintf(stderr, " golden: %s holds %lu values, expected %zu\n",
|
|
path, count, expect_count);
|
|
goto cleanup;
|
|
}
|
|
|
|
values = (float *)malloc(expect_count * sizeof(float));
|
|
if (!values) {
|
|
fprintf(stderr, " golden: out of memory for %s\n", path);
|
|
goto cleanup;
|
|
}
|
|
if (fread(values, sizeof(float), expect_count, f) != expect_count) {
|
|
fprintf(stderr, " golden: %s payload is truncated\n", path);
|
|
free(values);
|
|
values = NULL;
|
|
}
|
|
|
|
cleanup:
|
|
fclose(f);
|
|
return values;
|
|
}
|
|
|
|
#endif /* FOUNDRY_METAL_TEST_GOLDEN_VECTORS_H */
|