23f7624596
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Blocked by required conditions
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Blocked by required conditions
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Blocked by required conditions
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Blocked by required conditions
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Blocked by required conditions
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Waiting to run
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Waiting to run
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Deploy & Release (push) Waiting to run
CI/CD Pipeline / CI Status (push) Waiting to run
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Waiting to run
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Waiting to run
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
🔍 Verification Pipeline / ⚡ Performance Verification (push) Waiting to run
🔍 Verification Pipeline / 📊 Verification Report (push) Waiting to run
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
634 lines
27 KiB
JavaScript
Executable File
634 lines
27 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* benchmark-intelligence.mjs — Real, reusable benchmark harness for the
|
|
* RuVector / AgentDB intelligence stack.
|
|
*
|
|
* Measures, on the machine it runs on, against the BUILT exports under
|
|
* v3/@claude-flow/cli/dist/src/...
|
|
* (never against source, never hardcoded):
|
|
*
|
|
* 1. HNSW search vs in-process brute-force cosine baseline
|
|
* at N = 1000, 5000, 20000, 50000:
|
|
* - per-query ms (HNSW + brute force)
|
|
* - speedup ratio (brute / hnsw)
|
|
* - recall@10 (HNSW results vs exact top-10)
|
|
* 2. Int8 quantization: measured compression ratio + reconstruction cosine.
|
|
* 3. RaBitQ: memory compression ratio (+ retrieval speed if a populated
|
|
* index is feasible, else null with a reason).
|
|
* 4. SONA WASM adapt latency (ms/call, warmed).
|
|
* 5. MoE gate: confirm the gate LEARNS (probability/Q shift after rewards).
|
|
* 6. Embedding backend actually in use (onnx vs mock) — honest.
|
|
*
|
|
* DESIGN NOTES
|
|
* ------------
|
|
* - All vectors are generated with a seeded deterministic RNG so the run is
|
|
* reproducible and safe to re-run. We deliberately do NOT route HNSW/quant
|
|
* benchmarks through the embedding backend: that backend can be mock on a
|
|
* given machine, which would make the structural benchmarks non-deterministic
|
|
* and conflate two separate measurements. The embedding backend is instead
|
|
* reported HONESTLY as its own item (#6).
|
|
* - Every number printed/emitted comes from a measurement in THIS process.
|
|
* Unmeasurable items are emitted as `null` with a `reason` string.
|
|
* - Exit code is 0 on success (a benchmark being unmeasurable is not a failure;
|
|
* only an unexpected crash is). The script prints a markdown table to stdout
|
|
* and writes a machine-readable JSON object after a `===BENCH_JSON===` marker.
|
|
*
|
|
* USAGE
|
|
* node scripts/benchmark-intelligence.mjs # default sizes
|
|
* node scripts/benchmark-intelligence.mjs --sizes 1000,5000
|
|
* node scripts/benchmark-intelligence.mjs --queries 50 --dims 384
|
|
* node scripts/benchmark-intelligence.mjs --json-only
|
|
*
|
|
* Created for the ruflo intelligence stack. Co-Authored-By: RuFlo <ruv@ruv.net>
|
|
*/
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
import path from 'node:path';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
const DIST = path.join(REPO_ROOT, 'v3', '@claude-flow', 'cli', 'dist', 'src');
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CLI args
|
|
// ----------------------------------------------------------------------------
|
|
function parseArgs(argv) {
|
|
const args = { sizes: [1000, 5000, 20000, 50000], queries: 30, dims: 384, jsonOnly: false, only: null };
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === '--sizes') args.sizes = argv[++i].split(',').map((s) => parseInt(s.trim(), 10)).filter(Boolean);
|
|
else if (a === '--queries') args.queries = parseInt(argv[++i], 10);
|
|
else if (a === '--dims') args.dims = parseInt(argv[++i], 10);
|
|
else if (a === '--json-only') args.jsonOnly = true;
|
|
// --only=hnsw,sona,moe runs ONLY those sub-benches. Saves multi-minute wall
|
|
// when a Darwin-core agent only needs one dimension's measurement.
|
|
// Accept both `--only hnsw,sona` and `--only=hnsw,sona` forms.
|
|
else if (a === '--only') args.only = new Set(argv[++i].split(',').map(s => s.trim()).filter(Boolean));
|
|
else if (a.startsWith('--only=')) args.only = new Set(a.slice(7).split(',').map(s => s.trim()).filter(Boolean));
|
|
else if (a === '--help' || a === '-h') {
|
|
console.log('Usage: node scripts/benchmark-intelligence.mjs [--sizes N,N] [--queries K] [--dims D] [--json-only] [--only=hnsw,sona,moe]');
|
|
process.exit(0);
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
const ARGS = parseArgs(process.argv);
|
|
|
|
const log = (...m) => { if (!ARGS.jsonOnly) console.log(...m); };
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Deterministic RNG (mulberry32) + unit-vector generator
|
|
// ----------------------------------------------------------------------------
|
|
function mulberry32(seed) {
|
|
let a = seed >>> 0;
|
|
return function () {
|
|
a |= 0; a = (a + 0x6d2b79f5) | 0;
|
|
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
};
|
|
}
|
|
|
|
/** Generate a clustered set of unit-length Float32 vectors (deterministic). */
|
|
function makeDataset(n, dims, seed) {
|
|
const rng = mulberry32(seed);
|
|
// A handful of cluster centroids so nearest-neighbour structure is non-trivial.
|
|
const numClusters = Math.max(8, Math.round(Math.sqrt(n) / 4));
|
|
const centroids = [];
|
|
for (let c = 0; c < numClusters; c++) {
|
|
const v = new Float32Array(dims);
|
|
for (let d = 0; d < dims; d++) v[d] = rng() * 2 - 1;
|
|
normalize(v);
|
|
centroids.push(v);
|
|
}
|
|
const vectors = new Array(n);
|
|
for (let i = 0; i < n; i++) {
|
|
const base = centroids[i % numClusters];
|
|
const v = new Float32Array(dims);
|
|
for (let d = 0; d < dims; d++) v[d] = base[d] + (rng() * 2 - 1) * 0.35; // jitter around centroid
|
|
normalize(v);
|
|
vectors[i] = v;
|
|
}
|
|
return vectors;
|
|
}
|
|
|
|
function normalize(v) {
|
|
let s = 0;
|
|
for (let i = 0; i < v.length; i++) s += v[i] * v[i];
|
|
const inv = s > 0 ? 1 / Math.sqrt(s) : 0;
|
|
for (let i = 0; i < v.length; i++) v[i] *= inv;
|
|
return v;
|
|
}
|
|
|
|
function cosine(a, b) {
|
|
let dot = 0, na = 0, nb = 0;
|
|
for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; }
|
|
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
return denom > 0 ? dot / denom : 0;
|
|
}
|
|
|
|
/** Exact top-k by cosine (brute force) — returns array of ids. */
|
|
function bruteTopK(vectors, query, k) {
|
|
const scored = new Array(vectors.length);
|
|
for (let i = 0; i < vectors.length; i++) scored[i] = [i, cosine(query, vectors[i])];
|
|
scored.sort((x, y) => y[1] - x[1]);
|
|
return scored.slice(0, k).map((s) => s[0]);
|
|
}
|
|
|
|
const now = () => performance.now();
|
|
const round = (x, d = 4) => (x == null || Number.isNaN(x) ? null : Number(x.toFixed(d)));
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 1. HNSW vs brute-force
|
|
// ----------------------------------------------------------------------------
|
|
async function benchHnsw() {
|
|
const out = { unit: 'ms/query', backend: null, byN: {}, note: '' };
|
|
let createVectorDB, getStatus, loadRuVector;
|
|
try {
|
|
({ createVectorDB, getStatus, loadRuVector } = await import(path.join(DIST, 'ruvector', 'vector-db.js')));
|
|
await loadRuVector();
|
|
out.backend = getStatus();
|
|
} catch (e) {
|
|
out.error = `failed to load ruvector vector-db: ${e.message}`;
|
|
return out;
|
|
}
|
|
|
|
// ruvector createVectorDB enforces 384 dims on this build; honour that.
|
|
const dims = ARGS.dims;
|
|
const K = 10;
|
|
|
|
for (const N of ARGS.sizes) {
|
|
const entry = { n: N };
|
|
let dataset, queries;
|
|
try {
|
|
dataset = makeDataset(N, dims, 1234 + N);
|
|
// Queries: reuse a deterministic subset perturbed slightly so they are
|
|
// near (but not identical to) indexed points.
|
|
const qRng = mulberry32(99 + N);
|
|
queries = [];
|
|
for (let q = 0; q < ARGS.queries; q++) {
|
|
const src = dataset[Math.floor(qRng() * N)];
|
|
const v = new Float32Array(dims);
|
|
for (let d = 0; d < dims; d++) v[d] = src[d] + (qRng() * 2 - 1) * 0.05;
|
|
queries.push(normalize(v));
|
|
}
|
|
} catch (e) {
|
|
entry.error = `dataset build failed: ${e.message}`;
|
|
out.byN[N] = entry;
|
|
continue;
|
|
}
|
|
|
|
// --- Build HNSW index ---
|
|
let db, buildMs;
|
|
try {
|
|
const t0 = now();
|
|
db = await createVectorDB(dims);
|
|
for (let i = 0; i < N; i++) await db.insert(dataset[i], String(i));
|
|
buildMs = now() - t0;
|
|
} catch (e) {
|
|
entry.error = `hnsw build failed: ${e.message}`;
|
|
out.byN[N] = entry;
|
|
continue;
|
|
}
|
|
entry.buildMs = round(buildMs, 2);
|
|
entry.indexSize = await db.size();
|
|
|
|
// --- HNSW query timing + recall ---
|
|
// Warm the index first: the native NAPI search path has per-call marshalling
|
|
// + JIT + page-fault overhead that dominates the FIRST touch of each query.
|
|
// The brute-force baseline below runs in an already-hot JS loop, so timing
|
|
// HNSW cold would compare a cold path against a warm one. We discard one full
|
|
// pass so both sides are measured at steady state — this measures search
|
|
// performance, not first-call overhead.
|
|
for (const q of queries) await db.search(q, K);
|
|
|
|
let hnswTotal = 0, recallHits = 0, recallTotal = 0;
|
|
const exactByQuery = queries.map((q) => bruteTopK(dataset, q, K)); // ground truth
|
|
for (let qi = 0; qi < queries.length; qi++) {
|
|
const q = queries[qi];
|
|
const t0 = now();
|
|
const res = await db.search(q, K);
|
|
hnswTotal += now() - t0;
|
|
const ids = (res || []).map((r) => parseInt(r.id, 10));
|
|
const truth = new Set(exactByQuery[qi]);
|
|
for (const id of ids) if (truth.has(id)) recallHits++;
|
|
recallTotal += K;
|
|
}
|
|
const hnswPerQuery = hnswTotal / queries.length;
|
|
|
|
// --- Brute-force baseline timing (independent of ground-truth precompute) ---
|
|
let bruteTotal = 0;
|
|
for (const q of queries) {
|
|
const t0 = now();
|
|
bruteTopK(dataset, q, K);
|
|
bruteTotal += now() - t0;
|
|
}
|
|
const brutePerQuery = bruteTotal / queries.length;
|
|
|
|
entry.hnswMsPerQuery = round(hnswPerQuery, 5);
|
|
entry.bruteMsPerQuery = round(brutePerQuery, 5);
|
|
entry.speedup = round(hnswPerQuery > 0 ? brutePerQuery / hnswPerQuery : null, 2);
|
|
entry.recallAt10 = round(recallTotal > 0 ? recallHits / recallTotal : null, 4);
|
|
out.byN[N] = entry;
|
|
|
|
if (db.clear) await db.clear();
|
|
log(` HNSW N=${N}: build=${entry.buildMs}ms hnsw=${entry.hnswMsPerQuery}ms brute=${entry.bruteMsPerQuery}ms speedup=${entry.speedup}x recall@10=${entry.recallAt10}`);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 2. Int8 quantization
|
|
// ----------------------------------------------------------------------------
|
|
async function benchInt8() {
|
|
const out = {};
|
|
let encodeEmbedding, decodeEmbedding, encodedByteSize;
|
|
try {
|
|
({ encodeEmbedding, decodeEmbedding, encodedByteSize } = await import(path.join(DIST, 'memory', 'embedding-quantization.js')));
|
|
} catch (e) {
|
|
return { error: `failed to load embedding-quantization: ${e.message}` };
|
|
}
|
|
const dims = ARGS.dims;
|
|
const SAMPLES = 200;
|
|
const rng = mulberry32(4242);
|
|
let cosSum = 0;
|
|
let rawBytes = 0, encBase64Bytes = 0, quantRawBytes = 0;
|
|
for (let s = 0; s < SAMPLES; s++) {
|
|
const v = new Float32Array(dims);
|
|
for (let d = 0; d < dims; d++) v[d] = rng() * 2 - 1;
|
|
normalize(v);
|
|
const encoded = encodeEmbedding(v); // "inline:<base64>"
|
|
const decoded = decodeEmbedding(encoded); // Float32Array | null
|
|
if (!decoded) continue;
|
|
cosSum += cosine(v, decoded);
|
|
rawBytes += dims * 4; // float32 source bytes
|
|
// measured base64 transport payload (the blob, sans "inline:" prefix)
|
|
const b64 = encoded.startsWith('inline:') ? encoded.slice(7) : encoded;
|
|
encBase64Bytes += b64.length; // chars == bytes for base64 ASCII
|
|
// measured raw quantized byte count: decode the base64 to get its true
|
|
// pre-encoding size (header + 1 byte/dim). We do NOT assume the format.
|
|
quantRawBytes += Buffer.from(b64, 'base64').length;
|
|
}
|
|
out.dims = dims;
|
|
out.samples = SAMPLES;
|
|
out.reconstructionCosine = round(cosSum / SAMPLES, 6);
|
|
out.rawBytesPerVec = round(rawBytes / SAMPLES, 1); // 1536 for 384-d f32
|
|
out.quantizedRawBytesPerVec = round(quantRawBytes / SAMPLES, 1); // ~400 (header+int8)
|
|
out.base64BytesPerVec = round(encBase64Bytes / SAMPLES, 1); // ~536 transport
|
|
out.encodedByteSizeReported = encodedByteSize ? encodedByteSize(dims) : null;
|
|
// Honest int8 compression = float32 source bytes / int8 quantized bytes.
|
|
out.compressionRatioInt8 = round(rawBytes / quantRawBytes, 3);
|
|
// Transport (base64) ratio — what actually lands in the embedding_ref column.
|
|
out.compressionRatioBase64 = round(rawBytes / encBase64Bytes, 3);
|
|
log(` Int8: reconstructionCosine=${out.reconstructionCosine} compression(int8)=${out.compressionRatioInt8}x compression(base64)=${out.compressionRatioBase64}x`);
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 3. RaBitQ
|
|
// ----------------------------------------------------------------------------
|
|
async function benchRabitq() {
|
|
const out = {};
|
|
let buildRabitqIndex, getRabitqStatus, searchRabitq;
|
|
try {
|
|
({ buildRabitqIndex, getRabitqStatus, searchRabitq } = await import(path.join(DIST, 'memory', 'rabitq-index.js')));
|
|
} catch (e) {
|
|
return { error: `failed to load rabitq-index: ${e.message}` };
|
|
}
|
|
// Memory compression ratio is a structural property: 1-bit packing of a
|
|
// float32 vector = 32x. The WASM module reports the measured ratio at build
|
|
// time. buildRabitqIndex pulls from a SQLite memory DB — if no populated DB
|
|
// exists we cannot measure retrieval speed; report that honestly.
|
|
const status = getRabitqStatus();
|
|
out.available = !!status.available;
|
|
out.statusBefore = status;
|
|
|
|
let build = null;
|
|
try {
|
|
build = await buildRabitqIndex({ dimensions: ARGS.dims });
|
|
} catch (e) {
|
|
out.buildError = e.message;
|
|
}
|
|
out.build = build;
|
|
|
|
if (build && build.success && build.vectorCount > 0) {
|
|
out.compressionRatio = round(build.compressionRatio, 3);
|
|
out.buildTimeMs = round(build.buildTimeMs, 3);
|
|
// Retrieval timing on the populated index.
|
|
try {
|
|
const rng = mulberry32(7);
|
|
const q = Array.from({ length: ARGS.dims }, () => rng() * 2 - 1);
|
|
const reps = 20;
|
|
const t0 = now();
|
|
for (let i = 0; i < reps; i++) await searchRabitq(q, { k: 10 });
|
|
out.searchMsPerQuery = round((now() - t0) / reps, 5);
|
|
} catch (e) {
|
|
out.searchMsPerQuery = null;
|
|
out.searchNote = `retrieval not measured: ${e.message}`;
|
|
}
|
|
} else {
|
|
// No populated SQLite vector store on this machine — compression ratio is
|
|
// reported from status/build if the WASM module surfaced it, else from the
|
|
// documented 1-bit packing invariant is NOT assumed; we mark it null.
|
|
out.compressionRatio = build && typeof build.compressionRatio === 'number' && build.compressionRatio > 0
|
|
? round(build.compressionRatio, 3)
|
|
: (typeof status.compressionRatio === 'number' && status.compressionRatio > 0 ? round(status.compressionRatio, 3) : null);
|
|
out.searchMsPerQuery = null;
|
|
out.searchNote = 'not measured: no populated RaBitQ/SQLite index available on this machine (build returned vectorCount=0)';
|
|
}
|
|
log(` RaBitQ: available=${out.available} compressionRatio=${out.compressionRatio} searchMsPerQuery=${out.searchMsPerQuery ?? 'null'}`);
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 4. SONA WASM adapt latency (warmed)
|
|
// ----------------------------------------------------------------------------
|
|
async function benchSona() {
|
|
const out = {};
|
|
let isRuvllmWasmAvailable, initRuvllmWasm, createSonaInstant;
|
|
try {
|
|
({ isRuvllmWasmAvailable, initRuvllmWasm, createSonaInstant } = await import(path.join(DIST, 'ruvector', 'ruvllm-wasm.js')));
|
|
} catch (e) {
|
|
return { error: `failed to load ruvllm-wasm: ${e.message}` };
|
|
}
|
|
const available = await isRuvllmWasmAvailable();
|
|
out.wasmAvailable = available;
|
|
if (!available) {
|
|
out.adaptMsPerCall = null;
|
|
out.note = 'not measured: @ruvector/ruvllm-wasm not available on this machine';
|
|
log(' SONA: WASM not available');
|
|
return out;
|
|
}
|
|
await initRuvllmWasm();
|
|
const sona = await createSonaInstant({ hiddenDim: 64 });
|
|
// Warm-up (JIT + WASM page faults).
|
|
for (let i = 0; i < 1000; i++) sona.adapt(0.7 + (i % 3) * 0.1);
|
|
// Measured loop.
|
|
const ITER = 20000;
|
|
const rng = mulberry32(11);
|
|
const t0 = now();
|
|
for (let i = 0; i < ITER; i++) sona.adapt(rng());
|
|
const totalMs = now() - t0;
|
|
out.iterations = ITER;
|
|
out.totalMs = round(totalMs, 3);
|
|
out.adaptMsPerCall = round(totalMs / ITER, 6);
|
|
out.targetMet_0_05ms = out.adaptMsPerCall != null ? out.adaptMsPerCall < 0.05 : null;
|
|
if (sona.reset) sona.reset();
|
|
log(` SONA: ${out.adaptMsPerCall} ms/adapt-call (warmed, ${ITER} iters)`);
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 5. MoE gate learns (Q-value / probability shift after rewards)
|
|
// ----------------------------------------------------------------------------
|
|
async function benchMoeGate() {
|
|
const out = {};
|
|
let createQLearningRouter;
|
|
try {
|
|
({ createQLearningRouter } = await import(path.join(DIST, 'ruvector', 'q-learning-router.js')));
|
|
} catch (e) {
|
|
return { error: `failed to load q-learning-router: ${e.message}` };
|
|
}
|
|
let router;
|
|
try {
|
|
router = createQLearningRouter({ saveInterval: 1e9 }); // never auto-persist during bench
|
|
} catch (e) {
|
|
return { error: `failed to construct router: ${e.message}` };
|
|
}
|
|
|
|
const task = 'optimize the database query performance bottleneck';
|
|
// Discover available actions from an initial (greedy) decision.
|
|
const before = router.route(task, false);
|
|
const actions = (before.alternatives && before.alternatives.length)
|
|
? before.alternatives.map((a) => a.route)
|
|
: [before.route];
|
|
if (actions.length < 2) {
|
|
out.note = 'gate exposed fewer than 2 actions; cannot demonstrate competitive shift';
|
|
}
|
|
// Pick a "good" action and reward it repeatedly; pick a "bad" action and
|
|
// penalise it. We then verify the gate's Q-value / probability for the good
|
|
// action rose relative to before.
|
|
const good = before.route; // initial greedy pick (argmax of qValues)
|
|
const bad = actions.find((a) => a !== good) || good;
|
|
|
|
// The greedy decision's chosen route is the argmax, so its Q is the max of
|
|
// the qValues vector. We read the Q of whatever route is greedily chosen
|
|
// before and after training. After rewarding `good`, a learning gate should
|
|
// (a) keep choosing `good` and (b) have raised its Q for that context.
|
|
const maxQ = (decision) => (decision.qValues && decision.qValues.length ? Math.max(...decision.qValues) : null);
|
|
// Q assigned specifically to the `good` route: if `good` is the greedy pick
|
|
// it equals maxQ; otherwise it's its score in alternatives.
|
|
const qForRoute = (decision, route) => {
|
|
if (decision.route === route) return maxQ(decision);
|
|
const alt = (decision.alternatives || []).find((a) => a.route === route);
|
|
return alt ? alt.score : null;
|
|
};
|
|
|
|
const beforeGoodQ = qForRoute(before, good);
|
|
const beforeConf = before.confidence;
|
|
|
|
// Train: reward `good`, penalise `bad`.
|
|
const REWARDS = 200;
|
|
for (let i = 0; i < REWARDS; i++) {
|
|
router.update(task, good, 1.0, task);
|
|
if (bad !== good) router.update(task, bad, -1.0, task);
|
|
}
|
|
|
|
const after = router.route(task, false); // greedy after training
|
|
const afterGoodQ = qForRoute(after, good);
|
|
const afterConf = after.confidence;
|
|
out.afterGreedyRoute = after.route;
|
|
out.goodStillChosen = after.route === good;
|
|
|
|
out.actionsObserved = actions.length;
|
|
out.goodAction = good;
|
|
out.badAction = bad;
|
|
out.rewardsApplied = REWARDS;
|
|
out.beforeGoodQ = round(beforeGoodQ, 5);
|
|
out.afterGoodQ = round(afterGoodQ, 5);
|
|
out.qShift = (beforeGoodQ != null && afterGoodQ != null) ? round(afterGoodQ - beforeGoodQ, 5) : null;
|
|
out.beforeConfidence = round(beforeConf, 5);
|
|
out.afterConfidence = round(afterConf, 5);
|
|
out.confidenceShift = (beforeConf != null && afterConf != null) ? round(afterConf - beforeConf, 5) : null;
|
|
// The gate "learns" if reward changed its internal valuation in the rewarded
|
|
// direction (Q rose) OR its confidence in the greedy pick rose.
|
|
out.gateLearned = (out.qShift != null && out.qShift > 0) || (out.confidenceShift != null && out.confidenceShift > 0);
|
|
const stats = router.getStats ? router.getStats() : null;
|
|
out.routerStats = stats;
|
|
log(` MoE gate: qShift=${out.qShift} confShift=${out.confidenceShift} learned=${out.gateLearned}`);
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 6. Embedding backend honesty
|
|
// ----------------------------------------------------------------------------
|
|
async function benchEmbeddingBackend() {
|
|
const out = {};
|
|
let generateEmbedding;
|
|
try {
|
|
({ generateEmbedding } = await import(path.join(DIST, 'memory', 'memory-initializer.js')));
|
|
} catch (e) {
|
|
return { error: `failed to load memory-initializer: ${e.message}` };
|
|
}
|
|
try {
|
|
const r = await generateEmbedding('benchmark probe: authentication and database optimization patterns');
|
|
out.backend = r.backend; // 'onnx' | 'mock' — the authoritative signal
|
|
out.model = r.model;
|
|
out.dimensions = r.dimensions;
|
|
out.honest = `backend=${r.backend} (model string '${r.model}' is reported regardless of backend; backend field is authoritative)`;
|
|
} catch (e) {
|
|
out.backend = null;
|
|
out.note = `not measured: ${e.message}`;
|
|
}
|
|
log(` Embedding backend: ${out.backend} (model=${out.model}, dims=${out.dimensions})`);
|
|
return out;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Markdown report
|
|
// ----------------------------------------------------------------------------
|
|
function printMarkdown(results) {
|
|
const lines = [];
|
|
lines.push('');
|
|
lines.push('## Intelligence Benchmark — Measured Results');
|
|
lines.push('');
|
|
lines.push(`- Host: ${process.platform}/${process.arch}, Node ${process.version}`);
|
|
lines.push(`- dist: ${path.relative(REPO_ROOT, DIST)}`);
|
|
lines.push(`- dims=${ARGS.dims}, queries/size=${ARGS.queries}`);
|
|
lines.push('');
|
|
|
|
// HNSW table
|
|
lines.push('### 1. HNSW vs brute-force cosine');
|
|
const h = results.hnsw;
|
|
if (h && h.byN && !h.error) {
|
|
lines.push(`backend: \`${JSON.stringify(h.backend)}\``);
|
|
lines.push('');
|
|
lines.push('| N | build ms | HNSW ms/q | brute ms/q | speedup | recall@10 |');
|
|
lines.push('|--:|--:|--:|--:|--:|--:|');
|
|
for (const N of ARGS.sizes) {
|
|
const e = h.byN[N];
|
|
if (!e) continue;
|
|
if (e.error) { lines.push(`| ${N} | error: ${e.error} | | | | |`); continue; }
|
|
lines.push(`| ${N} | ${e.buildMs} | ${e.hnswMsPerQuery} | ${e.bruteMsPerQuery} | ${e.speedup}x | ${e.recallAt10} |`);
|
|
}
|
|
} else {
|
|
lines.push(`error: ${h?.error ?? 'no data'}`);
|
|
}
|
|
lines.push('');
|
|
|
|
// Int8
|
|
lines.push('### 2. Int8 quantization');
|
|
const q = results.int8;
|
|
if (q && !q.error) {
|
|
lines.push('| metric | value |');
|
|
lines.push('|--|--:|');
|
|
lines.push(`| reconstruction cosine | ${q.reconstructionCosine} |`);
|
|
lines.push(`| compression (int8 quantized) | ${q.compressionRatioInt8}x |`);
|
|
lines.push(`| compression (base64 transport) | ${q.compressionRatioBase64}x |`);
|
|
lines.push(`| f32 source bytes/vec | ${q.rawBytesPerVec} |`);
|
|
lines.push(`| int8 quantized bytes/vec | ${q.quantizedRawBytesPerVec} |`);
|
|
lines.push(`| base64 transport bytes/vec | ${q.base64BytesPerVec} |`);
|
|
} else { lines.push(`error: ${q?.error ?? 'no data'}`); }
|
|
lines.push('');
|
|
|
|
// RaBitQ
|
|
lines.push('### 3. RaBitQ');
|
|
const rb = results.rabitq;
|
|
if (rb && !rb.error) {
|
|
lines.push('| metric | value |');
|
|
lines.push('|--|--:|');
|
|
lines.push(`| available | ${rb.available} |`);
|
|
lines.push(`| compression ratio | ${rb.compressionRatio ?? 'null'} |`);
|
|
lines.push(`| search ms/query | ${rb.searchMsPerQuery ?? 'null'} |`);
|
|
if (rb.searchNote) lines.push(`| note | ${rb.searchNote} |`);
|
|
} else { lines.push(`error: ${rb?.error ?? 'no data'}`); }
|
|
lines.push('');
|
|
|
|
// SONA
|
|
lines.push('### 4. SONA WASM adapt latency (warmed)');
|
|
const s = results.sona;
|
|
if (s && !s.error) {
|
|
lines.push('| metric | value |');
|
|
lines.push('|--|--:|');
|
|
lines.push(`| wasm available | ${s.wasmAvailable} |`);
|
|
lines.push(`| adapt ms/call | ${s.adaptMsPerCall ?? 'null'} |`);
|
|
if (s.iterations) lines.push(`| iterations | ${s.iterations} |`);
|
|
if (s.targetMet_0_05ms != null) lines.push(`| < 0.05ms target met | ${s.targetMet_0_05ms} |`);
|
|
if (s.note) lines.push(`| note | ${s.note} |`);
|
|
} else { lines.push(`error: ${s?.error ?? 'no data'}`); }
|
|
lines.push('');
|
|
|
|
// MoE
|
|
lines.push('### 5. MoE gate learning');
|
|
const m = results.moeGate;
|
|
if (m && !m.error) {
|
|
lines.push('| metric | value |');
|
|
lines.push('|--|--:|');
|
|
lines.push(`| actions observed | ${m.actionsObserved} |`);
|
|
lines.push(`| rewards applied | ${m.rewardsApplied} |`);
|
|
lines.push(`| good-action Q before → after | ${m.beforeGoodQ} → ${m.afterGoodQ} (Δ ${m.qShift}) |`);
|
|
lines.push(`| confidence before → after | ${m.beforeConfidence} → ${m.afterConfidence} (Δ ${m.confidenceShift}) |`);
|
|
lines.push(`| **gate learned** | **${m.gateLearned}** |`);
|
|
} else { lines.push(`error: ${m?.error ?? 'no data'}`); }
|
|
lines.push('');
|
|
|
|
// Embedding backend
|
|
lines.push('### 6. Embedding backend (honest)');
|
|
const eb = results.embeddingBackend;
|
|
if (eb && !eb.error) {
|
|
lines.push('| metric | value |');
|
|
lines.push('|--|--|');
|
|
lines.push(`| **backend in use** | **${eb.backend}** |`);
|
|
lines.push(`| model string | ${eb.model} |`);
|
|
lines.push(`| dimensions | ${eb.dimensions} |`);
|
|
if (eb.note) lines.push(`| note | ${eb.note} |`);
|
|
} else { lines.push(`error: ${eb?.error ?? 'no data'}`); }
|
|
lines.push('');
|
|
|
|
console.log(lines.join('\n'));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Main
|
|
// ----------------------------------------------------------------------------
|
|
async function main() {
|
|
log('Intelligence benchmark — measuring against built dist exports...');
|
|
log(`dist: ${DIST}`);
|
|
|
|
const results = {
|
|
meta: {
|
|
timestamp: new Date().toISOString(),
|
|
platform: `${process.platform}/${process.arch}`,
|
|
node: process.version,
|
|
dist: DIST,
|
|
dims: ARGS.dims,
|
|
queriesPerSize: ARGS.queries,
|
|
sizes: ARGS.sizes,
|
|
},
|
|
};
|
|
|
|
// Each benchmark is isolated: a failure in one does not abort the rest.
|
|
// --only filter skips dims not requested; saves wall-time for Darwin
|
|
// per-dimension agents (e.g. `--only=hnsw` runs only the HNSW bench).
|
|
const want = (name) => !ARGS.only || ARGS.only.has(name);
|
|
|
|
if (want('hnsw')) { log('\n[1/6] HNSW vs brute-force...'); try { results.hnsw = await benchHnsw(); } catch (e) { results.hnsw = { error: e.stack || e.message }; } }
|
|
if (want('int8')) { log('\n[2/6] Int8 quantization...'); try { results.int8 = await benchInt8(); } catch (e) { results.int8 = { error: e.stack || e.message }; } }
|
|
if (want('rabitq')) { log('\n[3/6] RaBitQ...'); try { results.rabitq = await benchRabitq(); } catch (e) { results.rabitq = { error: e.stack || e.message }; } }
|
|
if (want('sona')) { log('\n[4/6] SONA WASM adapt...'); try { results.sona = await benchSona(); } catch (e) { results.sona = { error: e.stack || e.message }; } }
|
|
if (want('moe') || want('moeGate')) { log('\n[5/6] MoE gate learning...'); try { results.moeGate = await benchMoeGate(); } catch (e) { results.moeGate = { error: e.stack || e.message }; } }
|
|
if (want('embedding') || want('embeddingBackend')) { log('\n[6/6] Embedding backend...'); try { results.embeddingBackend = await benchEmbeddingBackend(); } catch (e) { results.embeddingBackend = { error: e.stack || e.message }; } }
|
|
|
|
if (!ARGS.jsonOnly) printMarkdown(results);
|
|
|
|
// Machine-readable block (always emitted, after a stable marker).
|
|
console.log('\n===BENCH_JSON===');
|
|
console.log(JSON.stringify(results));
|
|
|
|
return results;
|
|
}
|
|
|
|
main()
|
|
.then(() => process.exit(0))
|
|
.catch((e) => { console.error('FATAL:', e.stack || e.message); process.exit(1); });
|