Files
hot-step-cpp-ROCm/engine/tools/mdx23c-test.cpp
T
2026-08-16 18:24:52 +07:00

140 lines
4.5 KiB
C++

// mdx23c-test.cpp: validation for mdx23c-ggml.h.
//
// Without a goldens file: loads the model, runs one forward at the trained
// frame count and checks the output shape, finiteness and range. That alone
// catches the shape/layout mistakes that are easy to make in a conv U-Net.
//
// With a goldens .bin (scripts/dump_mdx23c_goldens.py): also compares against
// the PyTorch reference elementwise.
//
// Usage:
// mdx23c-test <model.gguf> [goldens.bin]
//
// Part of HOT-Step CPP. MIT license.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "mdx23c-ggml.h"
namespace {
// Flat golden layout: magic "MDXG", int32 T, dim_f, cin, n_inst,
// then f32 input [T*dim_f*cin], f32 output [n_inst*cin*dim_f*T].
struct Goldens {
int32_t T = 0, dim_f = 0, cin = 0, n_inst = 0;
std::vector<float> input, out;
};
bool load_goldens(const char * path, Goldens & g) {
FILE * f = fopen(path, "rb");
if (!f) { fprintf(stderr, "cannot open %s\n", path); return false; }
char magic[4];
int32_t hdr[4];
if (fread(magic, 1, 4, f) != 4 || memcmp(magic, "MDXG", 4) != 0 ||
fread(hdr, sizeof(int32_t), 4, f) != 4) {
fprintf(stderr, "%s: bad header\n", path); fclose(f); return false;
}
g.T = hdr[0]; g.dim_f = hdr[1]; g.cin = hdr[2]; g.n_inst = hdr[3];
g.input.resize((size_t) g.T * g.dim_f * g.cin);
g.out.resize((size_t) g.n_inst * g.cin * g.dim_f * g.T);
bool ok = fread(g.input.data(), sizeof(float), g.input.size(), f) == g.input.size()
&& fread(g.out.data(), sizeof(float), g.out.size(), f) == g.out.size();
fclose(f);
if (!ok) fprintf(stderr, "%s: truncated\n", path);
return ok;
}
} // namespace
int main(int argc, char ** argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <model.gguf> [goldens.bin]\n", argv[0]);
return 2;
}
Mdx23c m;
if (!mdx_load(&m, argv[1])) return 1;
const Mdx23cConfig & c = m.cfg;
const int cin = c.n_audio_channels * 2;
int T = c.chunk_size / c.hop_length + 1;
Goldens g;
const bool have_goldens = (argc >= 3) && load_goldens(argv[2], g);
if (argc >= 3 && !have_goldens) return 1;
if (have_goldens) {
if (g.dim_f != c.dim_f || g.cin != cin || g.n_inst != c.n_instruments) {
fprintf(stderr, "goldens/model mismatch\n");
return 1;
}
T = g.T;
}
printf("T=%d dim_f=%d cin=%d instruments=%d\n", T, c.dim_f, cin, c.n_instruments);
std::vector<float> in((size_t) T * c.dim_f * cin);
if (have_goldens) {
memcpy(in.data(), g.input.data(), in.size() * sizeof(float));
} else {
unsigned s = 1234;
for (size_t i = 0; i < in.size(); i++) {
s = s * 1664525u + 1013904223u;
in[i] = ((float) (s >> 8) / 8388608.0f - 1.0f) * 0.05f;
}
}
std::vector<float> out((size_t) c.n_instruments * cin * c.dim_f * T);
mdx_forward(&m, in.data(), T, out.data());
double pk = 0.0;
bool finite = true;
for (float v : out) {
if (!std::isfinite(v)) finite = false;
double a = std::fabs((double) v);
if (a > pk) pk = a;
}
printf("output %zu values, peak %.6f, %s\n", out.size(), pk,
finite ? "all finite" : "NON-FINITE");
if (!finite) return 1;
int rc = 0;
if (have_goldens) {
double max_abs = 0.0, sum = 0.0, ref_pk = 0.0;
for (size_t i = 0; i < out.size(); i++) {
double d = std::fabs((double) out[i] - (double) g.out[i]);
if (d > max_abs) max_abs = d;
sum += d;
double a = std::fabs((double) g.out[i]);
if (a > ref_pk) ref_pk = a;
}
const double mean = sum / (double) out.size();
const double rel = ref_pk > 0.0 ? max_abs / ref_pk : max_abs;
printf("vs PyTorch: max %.3e mean %.3e ref_pk %.3e rel %.2e %s\n",
max_abs, mean, ref_pk, rel, rel <= 2e-2 ? "ok" : "FAIL");
rc = rel <= 2e-2 ? 0 : 1;
} else {
printf("(no goldens supplied — shape/finiteness check only)\n");
}
// Reload guard: backend_init hands out a refcounted shared singleton, so a
// wrong teardown only shows up on the SECOND load in a process.
{
Mdx23c m2;
if (!mdx_load(&m2, argv[1])) {
fprintf(stderr, "reload FAILED — backend teardown is wrong\n");
mdx_free(&m);
return 1;
}
printf("reload ok\n");
mdx_free(&m2);
}
mdx_free(&m);
printf("%s\n", rc == 0 ? "PASS" : "FAIL");
return rc;
}