/* vindex_bench.c — standalone proof harness for the engram HNSW ANN index. * * Measures brute-force cosine top-k (the correctness ORACLE) vs vindex_search * (HNSW) on: (a) the REAL paged store harvested read-only, and (b) synthetic * clustered data at several sizes to trace the scaling curve. Reports build time, * per-query latency (brute vs HNSW), and recall@k (HNSW top-k vs brute top-k). * * Read-only: never opens a socket, never writes the store. Safe on an nsbx clone. * * Build: cc -O2 -std=c11 vindex_bench.c engram_vindex.c -lm -o vindex_bench * Usage: vindex_bench store [nqueries] [k] [ef_csv] * vindex_bench synth [dim] [clusters] [nqueries] [k] [ef_csv] */ #include "engram_vindex.h" #include #include #include #include #include #include /* ── deterministic PRNG (splitmix64) so runs are reproducible ─────────────── */ static uint64_t g_seed = 0xD1B54A32D192ED03ULL; static uint64_t sm(void){ uint64_t z = (g_seed += 0x9E3779B97F4A7C15ULL); z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; return z ^ (z >> 31); } static double urand(void){ return (double)((sm() >> 11) + 1) * (1.0/9007199254740993.0); } static double grand(void){ /* Box-Muller */ double u1 = urand(), u2 = urand(); return sqrt(-2.0*log(u1)) * cos(2.0*M_PI*u2); } static double now_s(void){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec*1e-9; } /* L2-normalise a row in place. */ static void l2norm(float* v, int dim){ double ss = 0; for (int i=0;i 0){ float inv = (float)(1.0/sqrt(ss)); for (int i=0;i= out_d[k-1]) continue; int p = k-1; while (p>0 && out_d[p-1] > d){ out_d[p]=out_d[p-1]; out_ids[p]=out_ids[p-1]; p--; } out_d[p]=d; out_ids[p]=i; } } /* recall@k: |brute_topk ∩ hnsw_topk| / k. Both are id arrays of length k. */ static double recall_at_k(const int* gt, const uint64_t* ann, int nann, int k){ int hit = 0; for (int i=0;i %.3f s (%.1f k nodes/s)\n", bM?bM:VINDEX_DEFAULT_M, bEFC?bEFC:VINDEX_DEFAULT_EF_CONSTRUCTION, bt, n/1000.0/bt); /* choose query vectors: perturb random dataset rows (near-but-not-identical). */ int* qidx = malloc((size_t)nq*sizeof(int)); float* qv = malloc((size_t)nq*dim*sizeof(float)); for (int i=0;i [nq] [k] [ef_csv] | synth [dim] [clusters] [nq] [k] [ef_csv] | sweep [nq] [k] [ef_csv]\n", argv[0]); return 2; } int defef[8]; int ndef; if (strcmp(argv[1],"sweep")==0){ if (argc < 4){ fprintf(stderr,"sweep needs \n"); return 2; } int dim = atoi(argv[2]); int Ns[16]; int nN = parse_csv(argv[3], Ns, 16); int nq = (argc>4)?atoi(argv[4]):200; int k = (argc>5)?atoi(argv[5]):10; ndef = (argc>6)?parse_csv(argv[6],defef,8):parse_csv("64,128,200",defef,8); for (int s=0;s \n"); return 2; } const char* path = argv[2]; int dim = atoi(argv[3]); int nq = (argc>4)?atoi(argv[4]):500; int k = (argc>5)?atoi(argv[5]):10; ndef = (argc>6)?parse_csv(argv[6],defef,8):parse_csv("32,64,128,200,400",defef,8); printf("Harvesting emb vectors from %s (dim=%d) ...\n", path, dim); float* data=NULL; int n=0; double t0=now_s(); int h = vindex_harvest_from_store(path, dim, &data, NULL, &n); double harvest_s = now_s()-t0; if (h < 0 || n == 0){ fprintf(stderr,"harvest failed (h=%d n=%d) — wrong dim or path?\n", h, n); return 1; } printf("Harvested %d live embedded nodes in %.2f s\n", n, harvest_s); for (int i=0;i n) nq = n; run_bench("REAL STORE", data, n, dim, nq, k, defef, ndef, 0.0); free(data); return 0; } if (strcmp(argv[1],"synth")==0){ if (argc < 3){ fprintf(stderr,"synth needs \n"); return 2; } int N = atoi(argv[2]); int dim = (argc>3)?atoi(argv[3]):768; int clusters = (argc>4)?atoi(argv[4]):200; int nq = (argc>5)?atoi(argv[5]):500; int k = (argc>6)?atoi(argv[6]):10; ndef = (argc>7)?parse_csv(argv[7],defef,8):parse_csv("64,128,200",defef,8); printf("Generating %d synthetic clustered vectors (dim=%d clusters=%d) ...\n", N, dim, clusters); float* data = malloc((size_t)N*dim*sizeof(float)); if (!data){ fprintf(stderr,"OOM allocating %zu bytes\n", (size_t)N*dim*sizeof(float)); return 1; } gen_synth(data, N, dim, clusters, 0.35); char lbl[64]; snprintf(lbl,sizeof lbl,"SYNTH"); run_bench(lbl, data, N, dim, nq, k, defef, ndef, 0.0); free(data); return 0; } fprintf(stderr,"unknown mode '%s'\n", argv[1]); return 2; }