Pantheon/tests/test_qmv_threads.c

102 lines
3.2 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/* Bit-identical + throughput check for the parallelized pn_qmv.
*
* Compares single-threaded vs pool-parallel pn_qmv on real Q4_K tensors from
* the LightOnOCR LM, then times both. Bounded: a few seconds total. */
#include "src/formats/gguf.h"
#include "src/core/qweight.h"
#include "threadpool/threadpool.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
static double now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}
static void fill_vec(float *v, int n, unsigned seed) {
for (int i = 0; i < n; i++) {
seed = seed * 1664525u + 1013904223u;
v[i] = ((float)(seed >> 8) / (float)(1u << 24)) * 2.0f - 1.0f;
}
}
static int test_tensor(pn_gguf_file *g, const char *name, fw_pool *pool, int iters) {
pn_qweight w;
if (pn_qweight_from_gguf(&w, g, name) != 0) {
printf(" [skip] %s not found\n", name);
return 0;
}
float *vec = malloc((size_t)w.cols * sizeof(float));
float *o_ref = malloc((size_t)w.rows * sizeof(float));
float *o_mt = malloc((size_t)w.rows * sizeof(float));
fill_vec(vec, w.cols, 12345u);
/* Single-threaded reference (no pool registered) */
pn_qmv_set_pool(NULL);
pn_qmv(&w, vec, o_ref);
/* Pool-parallel */
pn_qmv_set_pool(pool);
pn_qmv(&w, vec, o_mt);
double max_abs = 0.0;
int nbad = 0;
for (int r = 0; r < w.rows; r++) {
double d = fabs((double)o_ref[r] - (double)o_mt[r]);
if (d > max_abs) max_abs = d;
if (o_ref[r] != o_mt[r]) nbad++;
}
/* Bench single-threaded */
pn_qmv_set_pool(NULL);
double t0 = now_ms();
for (int i = 0; i < iters; i++) pn_qmv(&w, vec, o_ref);
double st = (now_ms() - t0) / iters;
/* Bench parallel */
pn_qmv_set_pool(pool);
t0 = now_ms();
for (int i = 0; i < iters; i++) pn_qmv(&w, vec, o_mt);
double mt = (now_ms() - t0) / iters;
printf(" %-22s rows=%6d cols=%5d maxdiff=%.3g bitdiff=%d 1T=%.4f ms MT=%.4f ms %.2fx\n",
name, w.rows, w.cols, max_abs, nbad, st, mt, st / mt);
free(vec); free(o_ref); free(o_mt);
return nbad;
}
int main(int argc, char **argv) {
const char *path = argc > 1 ? argv[1]
: "models/lightonocr-lm-Q4_K_M.gguf";
pn_gguf_file *g = pn_gguf_open(path);
if (!g) { fprintf(stderr, "open failed: %s\n", path); return 1; }
fw_pool *pool = fw_pool_new(0);
printf("pn_qmv thread validation (pool=%p)\n", (void *)pool);
int total_bad = 0;
total_bad += test_tensor(g, "token_embd.weight", pool, 30);
total_bad += test_tensor(g, "output.weight", pool, 50);
total_bad += test_tensor(g, "blk.0.ffn_down.weight", pool, 200);
total_bad += test_tensor(g, "blk.0.ffn_gate.weight", pool, 200);
total_bad += test_tensor(g, "blk.0.attn_q.weight", pool, 200);
total_bad += test_tensor(g, "blk.0.attn_v.weight", pool, 200);
printf("\n%s (total bit-differing rows = %d)\n",
total_bad == 0 ? "BIT-IDENTICAL: PASS" : "MISMATCH: FAIL", total_bad);
fw_pool_free(pool);
pn_gguf_close(g);
return total_bad == 0 ? 0 : 1;
}