127 lines
4.7 KiB
C
127 lines
4.7 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/* test_prefill.c — validate + benchmark the batched multi-token prefill
|
|
* against the sequential single-token forward.
|
|
*
|
|
* Usage: test_prefill <model.gguf> [P_validate] [P_bench]
|
|
*
|
|
* Feeds identical fixed input embeddings through:
|
|
* (a) P sequential pn_llama_forward() calls (embd_override per token)
|
|
* (b) one pn_llama_prefill() batched call
|
|
* and compares the resulting last-token logits.
|
|
*/
|
|
|
|
#include "../include/pantheon.h"
|
|
#include "../src/core/engine.h"
|
|
#include "../src/models/llama.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 float* make_embeds(int P, int n_embd, unsigned seed) {
|
|
srand(seed);
|
|
float *e = malloc((size_t)P * n_embd * sizeof(float));
|
|
for (int i = 0; i < P * n_embd; i++)
|
|
e[i] = ((float)rand() / (float)RAND_MAX - 0.5f) * 0.2f;
|
|
return e;
|
|
}
|
|
|
|
/* Sequential reference: returns owned logits for the last token. */
|
|
static float* seq_last_logits(pn_model *model, pn_state *state,
|
|
const float *emb, int P, int n_embd, int n_vocab) {
|
|
pn_state_reset(state);
|
|
tm_tensor *logits = NULL;
|
|
for (int p = 0; p < P; p++) {
|
|
if (logits) tm_free(logits);
|
|
state->embd_override = emb + (size_t)p * n_embd;
|
|
logits = model->forward(model, 0, state);
|
|
state->embd_override = NULL;
|
|
if (!logits) return NULL;
|
|
}
|
|
float *out = malloc((size_t)n_vocab * sizeof(float));
|
|
memcpy(out, logits->data, (size_t)n_vocab * sizeof(float));
|
|
tm_free(logits);
|
|
return out;
|
|
}
|
|
|
|
static float* batched_last_logits(pn_model *model, pn_state *state,
|
|
const float *emb, int P, int n_embd, int n_vocab) {
|
|
pn_state_reset(state);
|
|
const float **rows = calloc((size_t)P, sizeof(float *));
|
|
uint32_t *toks = calloc((size_t)P, sizeof(uint32_t));
|
|
for (int p = 0; p < P; p++) rows[p] = emb + (size_t)p * n_embd;
|
|
tm_tensor *logits = pn_llama_prefill(model, toks, rows, P, state);
|
|
free(rows); free(toks);
|
|
if (!logits) return NULL;
|
|
float *out = malloc((size_t)n_vocab * sizeof(float));
|
|
memcpy(out, logits->data, (size_t)n_vocab * sizeof(float));
|
|
tm_free(logits);
|
|
return out;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 2) { fprintf(stderr, "usage: %s <model.gguf> [Pval] [Pbench]\n", argv[0]); return 2; }
|
|
int Pval = argc > 2 ? atoi(argv[2]) : 64;
|
|
int Pbench = argc > 3 ? atoi(argv[3]) : 256;
|
|
|
|
pn_model *model = pn_load(argv[1]);
|
|
if (!model) { fprintf(stderr, "load failed\n"); return 1; }
|
|
pn_state *state = pn_state_new(model);
|
|
if (!state) { fprintf(stderr, "state failed\n"); return 1; }
|
|
|
|
int n_embd = model->config.n_embd;
|
|
int n_vocab = model->config.n_vocab;
|
|
printf("model: n_embd=%d n_vocab=%d n_layer=%d n_head=%d arch=%d\n",
|
|
n_embd, n_vocab, model->config.n_layer, model->config.n_head, model->config.arch);
|
|
|
|
/* ---- Validation ---- */
|
|
float *emb = make_embeds(Pval, n_embd, 1234);
|
|
float *seqL = seq_last_logits(model, state, emb, Pval, n_embd, n_vocab);
|
|
if (!seqL) { fprintf(stderr, "sequential failed\n"); return 1; }
|
|
float *batL = batched_last_logits(model, state, emb, Pval, n_embd, n_vocab);
|
|
if (!batL) { fprintf(stderr, "BATCHED PREFILL UNSUPPORTED (returned NULL)\n"); return 3; }
|
|
|
|
double max_abs = 0, dot = 0, na = 0, nb = 0;
|
|
for (int v = 0; v < n_vocab; v++) {
|
|
double d = fabs((double)seqL[v] - (double)batL[v]);
|
|
if (d > max_abs) max_abs = d;
|
|
dot += (double)seqL[v] * batL[v];
|
|
na += (double)seqL[v] * seqL[v];
|
|
nb += (double)batL[v] * batL[v];
|
|
}
|
|
double cosine = dot / (sqrt(na) * sqrt(nb) + 1e-30);
|
|
printf("VALIDATE P=%d: max_abs_diff=%.3e cosine=%.8f\n", Pval, max_abs, cosine);
|
|
int ok = (max_abs < 1e-3) || (cosine > 0.9999);
|
|
printf(" -> %s\n", ok ? "PASS" : "FAIL");
|
|
free(emb); free(seqL); free(batL);
|
|
|
|
/* ---- Benchmark ---- */
|
|
float *embB = make_embeds(Pbench, n_embd, 99);
|
|
|
|
double t0 = now_ms();
|
|
float *s = seq_last_logits(model, state, embB, Pbench, n_embd, n_vocab);
|
|
double seq_ms = now_ms() - t0;
|
|
free(s);
|
|
|
|
t0 = now_ms();
|
|
float *b = batched_last_logits(model, state, embB, Pbench, n_embd, n_vocab);
|
|
double bat_ms = now_ms() - t0;
|
|
free(b);
|
|
|
|
printf("BENCH P=%d: sequential=%.1f ms (%.2f ms/tok) batched=%.1f ms (%.2f ms/tok) speedup=%.2fx\n",
|
|
Pbench, seq_ms, seq_ms / Pbench, bat_ms, bat_ms / Pbench, seq_ms / bat_ms);
|
|
|
|
free(embB);
|
|
pn_state_free(state);
|
|
pn_model_free(model);
|
|
return ok ? 0 : 1;
|
|
}
|