agentic-qe 3.6.9 → 3.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/.validation/schemas/skill-eval.schema.json +11 -1
- package/.claude/skills/pr-review/SKILL.md +2 -2
- package/.claude/skills/qcsd-production-swarm/SKILL.md +2781 -0
- package/.claude/skills/qcsd-production-swarm/evals/qcsd-production-swarm.yaml +246 -0
- package/.claude/skills/qcsd-production-swarm/schemas/output.json +505 -0
- package/.claude/skills/qcsd-production-swarm/scripts/validate-config.json +25 -0
- package/.claude/skills/skills-manifest.json +5 -5
- package/package.json +1 -1
- package/scripts/benchmark-hnsw-loading.ts +480 -0
- package/scripts/benchmark-kg-assisted.ts +725 -0
- package/scripts/collect-production-telemetry.sh +291 -0
- package/scripts/detect-skill-conflicts.ts +347 -0
- package/scripts/eval-driven-workflow.ts +704 -0
- package/scripts/run-skill-eval.ts +210 -10
- package/scripts/score-skill-quality.ts +511 -0
- package/v3/CHANGELOG.md +44 -0
- package/v3/assets/skills/pr-review/SKILL.md +2 -2
- package/v3/dist/cli/bundle.js +1526 -700
- package/v3/dist/cli/commands/code.d.ts.map +1 -1
- package/v3/dist/cli/commands/code.js +9 -85
- package/v3/dist/cli/commands/code.js.map +1 -1
- package/v3/dist/cli/commands/coverage.d.ts.map +1 -1
- package/v3/dist/cli/commands/coverage.js +3 -28
- package/v3/dist/cli/commands/coverage.js.map +1 -1
- package/v3/dist/cli/commands/hooks.d.ts.map +1 -1
- package/v3/dist/cli/commands/hooks.js +143 -2
- package/v3/dist/cli/commands/hooks.js.map +1 -1
- package/v3/dist/cli/commands/security.d.ts.map +1 -1
- package/v3/dist/cli/commands/security.js +3 -29
- package/v3/dist/cli/commands/security.js.map +1 -1
- package/v3/dist/cli/commands/test.d.ts.map +1 -1
- package/v3/dist/cli/commands/test.js +11 -58
- package/v3/dist/cli/commands/test.js.map +1 -1
- package/v3/dist/cli/utils/file-discovery.d.ts +27 -0
- package/v3/dist/cli/utils/file-discovery.d.ts.map +1 -0
- package/v3/dist/cli/utils/file-discovery.js +105 -0
- package/v3/dist/cli/utils/file-discovery.js.map +1 -0
- package/v3/dist/coordination/task-executor.d.ts.map +1 -1
- package/v3/dist/coordination/task-executor.js +304 -44
- package/v3/dist/coordination/task-executor.js.map +1 -1
- package/v3/dist/domains/code-intelligence/coordinator.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/coordinator.js +8 -1
- package/v3/dist/domains/code-intelligence/coordinator.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.js +10 -0
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.d.ts +7 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.js +10 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js +34 -10
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js.map +1 -1
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.d.ts +9 -0
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.d.ts.map +1 -1
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.js +38 -3
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js +58 -6
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/mocha-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/mocha-generator.js +79 -7
- package/v3/dist/domains/test-generation/generators/mocha-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts +4 -0
- package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/pytest-generator.js +77 -10
- package/v3/dist/domains/test-generation/generators/pytest-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts +21 -0
- package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/interfaces.d.ts +21 -0
- package/v3/dist/domains/test-generation/interfaces.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/services/test-generator.d.ts +22 -0
- package/v3/dist/domains/test-generation/services/test-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/services/test-generator.js +163 -3
- package/v3/dist/domains/test-generation/services/test-generator.js.map +1 -1
- package/v3/dist/init/init-wizard-hooks.d.ts +8 -1
- package/v3/dist/init/init-wizard-hooks.d.ts.map +1 -1
- package/v3/dist/init/init-wizard-hooks.js +47 -39
- package/v3/dist/init/init-wizard-hooks.js.map +1 -1
- package/v3/dist/init/phases/07-hooks.d.ts +11 -1
- package/v3/dist/init/phases/07-hooks.d.ts.map +1 -1
- package/v3/dist/init/phases/07-hooks.js +46 -50
- package/v3/dist/init/phases/07-hooks.js.map +1 -1
- package/v3/dist/init/settings-merge.d.ts +35 -0
- package/v3/dist/init/settings-merge.d.ts.map +1 -0
- package/v3/dist/init/settings-merge.js +140 -0
- package/v3/dist/init/settings-merge.js.map +1 -0
- package/v3/dist/integrations/agentic-flow/model-router/router.js +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/router.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.js +18 -3
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.d.ts +3 -3
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.js +18 -0
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.js.map +1 -1
- package/v3/dist/kernel/unified-memory-hnsw.d.ts +29 -0
- package/v3/dist/kernel/unified-memory-hnsw.d.ts.map +1 -1
- package/v3/dist/kernel/unified-memory-hnsw.js +136 -0
- package/v3/dist/kernel/unified-memory-hnsw.js.map +1 -1
- package/v3/dist/kernel/unified-memory.d.ts +2 -2
- package/v3/dist/kernel/unified-memory.d.ts.map +1 -1
- package/v3/dist/kernel/unified-memory.js +7 -9
- package/v3/dist/kernel/unified-memory.js.map +1 -1
- package/v3/dist/learning/qe-hooks.d.ts.map +1 -1
- package/v3/dist/learning/qe-hooks.js +34 -3
- package/v3/dist/learning/qe-hooks.js.map +1 -1
- package/v3/dist/mcp/bundle.js +1403 -425
- package/v3/dist/mcp/handlers/domain-handler-configs.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/domain-handler-configs.js +40 -31
- package/v3/dist/mcp/handlers/domain-handler-configs.js.map +1 -1
- package/v3/dist/mcp/handlers/task-handlers.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/task-handlers.js +68 -5
- package/v3/dist/mcp/handlers/task-handlers.js.map +1 -1
- package/v3/dist/mcp/protocol-server.d.ts.map +1 -1
- package/v3/dist/mcp/protocol-server.js +16 -2
- package/v3/dist/mcp/protocol-server.js.map +1 -1
- package/v3/package.json +1 -1
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HNSW Loading Benchmark
|
|
3
|
+
*
|
|
4
|
+
* Compares vector index build performance across implementations:
|
|
5
|
+
* 1. Pure JS InMemoryHNSWIndex (current, efConstruction=200)
|
|
6
|
+
* 2. Pure JS InMemoryHNSWIndex with reduced efConstruction=50
|
|
7
|
+
* 3. @ruvector/gnn differentiableSearch (Rust/NAPI)
|
|
8
|
+
* 4. Optimized JS with Float32Array deserialization
|
|
9
|
+
*
|
|
10
|
+
* Same scenario for all: 5073 vectors, 768 dimensions, cosine similarity.
|
|
11
|
+
* Measures: build time, search time, memory usage, recall quality.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { performance } from 'perf_hooks';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Scenario Configuration (identical for all implementations)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
const NUM_VECTORS = 5073;
|
|
20
|
+
const DIMENSIONS = 768;
|
|
21
|
+
const NUM_SEARCHES = 100;
|
|
22
|
+
const SEARCH_K = 5;
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Generate deterministic test data (same seed for all)
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
function seededRandom(seed: number): () => number {
|
|
29
|
+
let s = seed;
|
|
30
|
+
return () => {
|
|
31
|
+
s = (s * 1664525 + 1013904223) & 0xFFFFFFFF;
|
|
32
|
+
return (s >>> 0) / 0xFFFFFFFF;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function generateVectors(count: number, dim: number): { ids: string[]; embeddings: number[][] } {
|
|
37
|
+
const rand = seededRandom(42);
|
|
38
|
+
const ids: string[] = [];
|
|
39
|
+
const embeddings: number[][] = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < count; i++) {
|
|
42
|
+
ids.push(`code-intelligence:kg:node:${i}`);
|
|
43
|
+
const vec: number[] = new Array(dim);
|
|
44
|
+
let norm = 0;
|
|
45
|
+
for (let d = 0; d < dim; d++) {
|
|
46
|
+
vec[d] = rand() * 2 - 1;
|
|
47
|
+
norm += vec[d] * vec[d];
|
|
48
|
+
}
|
|
49
|
+
// Normalize for cosine similarity
|
|
50
|
+
norm = Math.sqrt(norm);
|
|
51
|
+
for (let d = 0; d < dim; d++) vec[d] /= norm;
|
|
52
|
+
embeddings.push(vec);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { ids, embeddings };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function generateQueries(count: number, dim: number): number[][] {
|
|
59
|
+
const rand = seededRandom(12345);
|
|
60
|
+
const queries: number[][] = [];
|
|
61
|
+
for (let i = 0; i < count; i++) {
|
|
62
|
+
const vec: number[] = new Array(dim);
|
|
63
|
+
let norm = 0;
|
|
64
|
+
for (let d = 0; d < dim; d++) {
|
|
65
|
+
vec[d] = rand() * 2 - 1;
|
|
66
|
+
norm += vec[d] * vec[d];
|
|
67
|
+
}
|
|
68
|
+
norm = Math.sqrt(norm);
|
|
69
|
+
for (let d = 0; d < dim; d++) vec[d] /= norm;
|
|
70
|
+
queries.push(vec);
|
|
71
|
+
}
|
|
72
|
+
return queries;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Simulate SQLite buffer format (Float32LE)
|
|
76
|
+
function vectorToBuffer(vec: number[]): Buffer {
|
|
77
|
+
const buf = Buffer.alloc(vec.length * 4);
|
|
78
|
+
for (let i = 0; i < vec.length; i++) buf.writeFloatLE(vec[i], i * 4);
|
|
79
|
+
return buf;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Current slow deserialization
|
|
83
|
+
function bufferToFloatArraySlow(buffer: Buffer, dimensions: number): number[] {
|
|
84
|
+
const arr: number[] = [];
|
|
85
|
+
for (let i = 0; i < dimensions; i++) arr.push(buffer.readFloatLE(i * 4));
|
|
86
|
+
return arr;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Optimized deserialization via Float32Array view
|
|
90
|
+
function bufferToFloatArrayFast(buffer: Buffer, dimensions: number): number[] {
|
|
91
|
+
const f32 = new Float32Array(buffer.buffer, buffer.byteOffset, dimensions);
|
|
92
|
+
return Array.from(f32);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Brute-force ground truth for recall measurement
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
function cosineSim(a: number[], b: number[]): number {
|
|
100
|
+
let dot = 0, na = 0, nb = 0;
|
|
101
|
+
for (let i = 0; i < a.length; i++) {
|
|
102
|
+
dot += a[i] * b[i];
|
|
103
|
+
na += a[i] * a[i];
|
|
104
|
+
nb += b[i] * b[i];
|
|
105
|
+
}
|
|
106
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
107
|
+
return denom === 0 ? 0 : dot / denom;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function bruteForceTopK(query: number[], embeddings: number[][], ids: string[], k: number): string[] {
|
|
111
|
+
const scored = ids.map((id, i) => ({ id, score: cosineSim(query, embeddings[i]) }));
|
|
112
|
+
scored.sort((a, b) => b.score - a.score);
|
|
113
|
+
return scored.slice(0, k).map(s => s.id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function recallAtK(predicted: string[], actual: string[]): number {
|
|
117
|
+
const actualSet = new Set(actual);
|
|
118
|
+
let hits = 0;
|
|
119
|
+
for (const p of predicted) {
|
|
120
|
+
if (actualSet.has(p)) hits++;
|
|
121
|
+
}
|
|
122
|
+
return hits / actual.length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Implementation 1: Pure JS HNSW (current)
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
async function benchmarkPureJS(
|
|
130
|
+
label: string,
|
|
131
|
+
efConstruction: number,
|
|
132
|
+
buffers: Buffer[],
|
|
133
|
+
ids: string[],
|
|
134
|
+
embeddings: number[][],
|
|
135
|
+
queries: number[][],
|
|
136
|
+
groundTruth: string[][]
|
|
137
|
+
) {
|
|
138
|
+
// Import from compiled output
|
|
139
|
+
const { InMemoryHNSWIndex } = await import('../v3/dist/kernel/unified-memory-hnsw.js');
|
|
140
|
+
|
|
141
|
+
const index = new InMemoryHNSWIndex();
|
|
142
|
+
if (efConstruction !== 200) {
|
|
143
|
+
(index as any).efConstruction = efConstruction;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const memBefore = process.memoryUsage().heapUsed;
|
|
147
|
+
|
|
148
|
+
// Measure deserialization + build
|
|
149
|
+
const buildStart = performance.now();
|
|
150
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
151
|
+
const embedding = bufferToFloatArraySlow(buffers[i], DIMENSIONS);
|
|
152
|
+
index.add(ids[i], embedding);
|
|
153
|
+
}
|
|
154
|
+
const buildMs = performance.now() - buildStart;
|
|
155
|
+
|
|
156
|
+
const memAfter = process.memoryUsage().heapUsed;
|
|
157
|
+
|
|
158
|
+
// Measure search
|
|
159
|
+
const searchStart = performance.now();
|
|
160
|
+
const searchResults: string[][] = [];
|
|
161
|
+
for (const q of queries) {
|
|
162
|
+
const results = index.search(q, SEARCH_K);
|
|
163
|
+
searchResults.push(results.map(r => r.id));
|
|
164
|
+
}
|
|
165
|
+
const searchMs = performance.now() - searchStart;
|
|
166
|
+
|
|
167
|
+
// Compute recall
|
|
168
|
+
let totalRecall = 0;
|
|
169
|
+
for (let i = 0; i < queries.length; i++) {
|
|
170
|
+
totalRecall += recallAtK(searchResults[i], groundTruth[i]);
|
|
171
|
+
}
|
|
172
|
+
const avgRecall = totalRecall / queries.length;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
label,
|
|
176
|
+
buildMs: Math.round(buildMs),
|
|
177
|
+
searchMs: Math.round(searchMs),
|
|
178
|
+
avgSearchMs: (searchMs / queries.length).toFixed(2),
|
|
179
|
+
memoryMB: Math.round((memAfter - memBefore) / 1024 / 1024),
|
|
180
|
+
recall: (avgRecall * 100).toFixed(1),
|
|
181
|
+
indexSize: index.size(),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Implementation 2: Pure JS with Fast Deserialization
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
async function benchmarkPureJSFastDeser(
|
|
190
|
+
buffers: Buffer[],
|
|
191
|
+
ids: string[],
|
|
192
|
+
embeddings: number[][],
|
|
193
|
+
queries: number[][],
|
|
194
|
+
groundTruth: string[][]
|
|
195
|
+
) {
|
|
196
|
+
const { InMemoryHNSWIndex } = await import('../v3/dist/kernel/unified-memory-hnsw.js');
|
|
197
|
+
const index = new InMemoryHNSWIndex();
|
|
198
|
+
|
|
199
|
+
const memBefore = process.memoryUsage().heapUsed;
|
|
200
|
+
|
|
201
|
+
const buildStart = performance.now();
|
|
202
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
203
|
+
const embedding = bufferToFloatArrayFast(buffers[i], DIMENSIONS);
|
|
204
|
+
index.add(ids[i], embedding);
|
|
205
|
+
}
|
|
206
|
+
const buildMs = performance.now() - buildStart;
|
|
207
|
+
|
|
208
|
+
const memAfter = process.memoryUsage().heapUsed;
|
|
209
|
+
|
|
210
|
+
const searchStart = performance.now();
|
|
211
|
+
const searchResults: string[][] = [];
|
|
212
|
+
for (const q of queries) {
|
|
213
|
+
const results = index.search(q, SEARCH_K);
|
|
214
|
+
searchResults.push(results.map(r => r.id));
|
|
215
|
+
}
|
|
216
|
+
const searchMs = performance.now() - searchStart;
|
|
217
|
+
|
|
218
|
+
let totalRecall = 0;
|
|
219
|
+
for (let i = 0; i < queries.length; i++) {
|
|
220
|
+
totalRecall += recallAtK(searchResults[i], groundTruth[i]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
label: 'Pure JS (efC=200) + Float32Array deser',
|
|
225
|
+
buildMs: Math.round(buildMs),
|
|
226
|
+
searchMs: Math.round(searchMs),
|
|
227
|
+
avgSearchMs: (searchMs / queries.length).toFixed(2),
|
|
228
|
+
memoryMB: Math.round((memAfter - memBefore) / 1024 / 1024),
|
|
229
|
+
recall: ((totalRecall / queries.length) * 100).toFixed(1),
|
|
230
|
+
indexSize: index.size(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Implementation 3: @ruvector/gnn (Rust/NAPI)
|
|
236
|
+
// ============================================================================
|
|
237
|
+
|
|
238
|
+
async function benchmarkRuvector(
|
|
239
|
+
buffers: Buffer[],
|
|
240
|
+
ids: string[],
|
|
241
|
+
embeddings: number[][],
|
|
242
|
+
queries: number[][],
|
|
243
|
+
groundTruth: string[][]
|
|
244
|
+
) {
|
|
245
|
+
const { differentiableSearch, init } = await import('@ruvector/gnn');
|
|
246
|
+
init();
|
|
247
|
+
|
|
248
|
+
const memBefore = process.memoryUsage().heapUsed;
|
|
249
|
+
|
|
250
|
+
// ruvector doesn't have a persistent index - it does brute-force differentiable search
|
|
251
|
+
// Build = converting buffers to Float32Arrays (the "index" is just the array of embeddings)
|
|
252
|
+
const buildStart = performance.now();
|
|
253
|
+
const indexedVectors: Float32Array[] = [];
|
|
254
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
255
|
+
const f32 = new Float32Array(buffers[i].buffer, buffers[i].byteOffset, DIMENSIONS);
|
|
256
|
+
indexedVectors.push(new Float32Array(f32)); // copy since buffer may be reused
|
|
257
|
+
}
|
|
258
|
+
const buildMs = performance.now() - buildStart;
|
|
259
|
+
|
|
260
|
+
const memAfter = process.memoryUsage().heapUsed;
|
|
261
|
+
|
|
262
|
+
// Search
|
|
263
|
+
const searchStart = performance.now();
|
|
264
|
+
const searchResults: string[][] = [];
|
|
265
|
+
for (const q of queries) {
|
|
266
|
+
const queryF32 = new Float32Array(q);
|
|
267
|
+
const result = differentiableSearch(
|
|
268
|
+
queryF32 as unknown as number[],
|
|
269
|
+
indexedVectors as unknown as number[][],
|
|
270
|
+
SEARCH_K,
|
|
271
|
+
1.0
|
|
272
|
+
);
|
|
273
|
+
searchResults.push(result.indices.map(idx => ids[idx]));
|
|
274
|
+
}
|
|
275
|
+
const searchMs = performance.now() - searchStart;
|
|
276
|
+
|
|
277
|
+
let totalRecall = 0;
|
|
278
|
+
for (let i = 0; i < queries.length; i++) {
|
|
279
|
+
totalRecall += recallAtK(searchResults[i], groundTruth[i]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
label: '@ruvector/gnn differentiableSearch (Rust)',
|
|
284
|
+
buildMs: Math.round(buildMs),
|
|
285
|
+
searchMs: Math.round(searchMs),
|
|
286
|
+
avgSearchMs: (searchMs / queries.length).toFixed(2),
|
|
287
|
+
memoryMB: Math.round((memAfter - memBefore) / 1024 / 1024),
|
|
288
|
+
recall: ((totalRecall / queries.length) * 100).toFixed(1),
|
|
289
|
+
indexSize: indexedVectors.length,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// Implementation 4: Optimized JS HNSW (reduced efConstruction + fast deser)
|
|
295
|
+
// ============================================================================
|
|
296
|
+
|
|
297
|
+
async function benchmarkOptimizedJS(
|
|
298
|
+
buffers: Buffer[],
|
|
299
|
+
ids: string[],
|
|
300
|
+
embeddings: number[][],
|
|
301
|
+
queries: number[][],
|
|
302
|
+
groundTruth: string[][]
|
|
303
|
+
) {
|
|
304
|
+
const { InMemoryHNSWIndex } = await import('../v3/dist/kernel/unified-memory-hnsw.js');
|
|
305
|
+
const index = new InMemoryHNSWIndex();
|
|
306
|
+
(index as any).efConstruction = 50;
|
|
307
|
+
|
|
308
|
+
const memBefore = process.memoryUsage().heapUsed;
|
|
309
|
+
|
|
310
|
+
const buildStart = performance.now();
|
|
311
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
312
|
+
const embedding = bufferToFloatArrayFast(buffers[i], DIMENSIONS);
|
|
313
|
+
index.add(ids[i], embedding);
|
|
314
|
+
}
|
|
315
|
+
const buildMs = performance.now() - buildStart;
|
|
316
|
+
|
|
317
|
+
const memAfter = process.memoryUsage().heapUsed;
|
|
318
|
+
|
|
319
|
+
const searchStart = performance.now();
|
|
320
|
+
const searchResults: string[][] = [];
|
|
321
|
+
for (const q of queries) {
|
|
322
|
+
const results = index.search(q, SEARCH_K);
|
|
323
|
+
searchResults.push(results.map(r => r.id));
|
|
324
|
+
}
|
|
325
|
+
const searchMs = performance.now() - searchStart;
|
|
326
|
+
|
|
327
|
+
let totalRecall = 0;
|
|
328
|
+
for (let i = 0; i < queries.length; i++) {
|
|
329
|
+
totalRecall += recallAtK(searchResults[i], groundTruth[i]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
label: 'Optimized JS (efC=50 + Float32Array deser)',
|
|
334
|
+
buildMs: Math.round(buildMs),
|
|
335
|
+
searchMs: Math.round(searchMs),
|
|
336
|
+
avgSearchMs: (searchMs / queries.length).toFixed(2),
|
|
337
|
+
memoryMB: Math.round((memAfter - memBefore) / 1024 / 1024),
|
|
338
|
+
recall: ((totalRecall / queries.length) * 100).toFixed(1),
|
|
339
|
+
indexSize: index.size(),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Deserialization-only micro benchmark
|
|
345
|
+
// ============================================================================
|
|
346
|
+
|
|
347
|
+
function benchmarkDeserialization(buffers: Buffer[]) {
|
|
348
|
+
// Slow path
|
|
349
|
+
const slowStart = performance.now();
|
|
350
|
+
for (const buf of buffers) {
|
|
351
|
+
bufferToFloatArraySlow(buf, DIMENSIONS);
|
|
352
|
+
}
|
|
353
|
+
const slowMs = performance.now() - slowStart;
|
|
354
|
+
|
|
355
|
+
// Fast path
|
|
356
|
+
const fastStart = performance.now();
|
|
357
|
+
for (const buf of buffers) {
|
|
358
|
+
bufferToFloatArrayFast(buf, DIMENSIONS);
|
|
359
|
+
}
|
|
360
|
+
const fastMs = performance.now() - fastStart;
|
|
361
|
+
|
|
362
|
+
return { slowMs: Math.round(slowMs), fastMs: Math.round(fastMs) };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Main
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
async function main() {
|
|
370
|
+
console.log('='.repeat(70));
|
|
371
|
+
console.log('HNSW Loading Benchmark');
|
|
372
|
+
console.log(`Scenario: ${NUM_VECTORS} vectors × ${DIMENSIONS} dims, ${NUM_SEARCHES} searches, k=${SEARCH_K}`);
|
|
373
|
+
console.log('='.repeat(70));
|
|
374
|
+
|
|
375
|
+
// Generate data
|
|
376
|
+
console.log('\nGenerating test data...');
|
|
377
|
+
const { ids, embeddings } = generateVectors(NUM_VECTORS, DIMENSIONS);
|
|
378
|
+
const queries = generateQueries(NUM_SEARCHES, DIMENSIONS);
|
|
379
|
+
|
|
380
|
+
// Convert to SQLite buffer format (simulates reading from DB)
|
|
381
|
+
const buffers = embeddings.map(vectorToBuffer);
|
|
382
|
+
|
|
383
|
+
// Compute brute-force ground truth
|
|
384
|
+
console.log('Computing ground truth (brute force)...');
|
|
385
|
+
const groundTruth = queries.map(q => bruteForceTopK(q, embeddings, ids, SEARCH_K));
|
|
386
|
+
|
|
387
|
+
// Deserialization micro-benchmark
|
|
388
|
+
console.log('\n--- Deserialization Benchmark ---');
|
|
389
|
+
const deser = benchmarkDeserialization(buffers);
|
|
390
|
+
console.log(` readFloatLE loop: ${deser.slowMs}ms`);
|
|
391
|
+
console.log(` Float32Array view: ${deser.fastMs}ms`);
|
|
392
|
+
console.log(` Speedup: ${(deser.slowMs / deser.fastMs).toFixed(1)}x`);
|
|
393
|
+
|
|
394
|
+
// Results table
|
|
395
|
+
const results: any[] = [];
|
|
396
|
+
|
|
397
|
+
// 1. Current implementation (Pure JS, efC=200, slow deser)
|
|
398
|
+
console.log('\n[1/5] Pure JS HNSW (efC=200, readFloatLE) — current baseline...');
|
|
399
|
+
results.push(await benchmarkPureJS(
|
|
400
|
+
'Pure JS (efC=200) + readFloatLE [CURRENT]',
|
|
401
|
+
200, buffers, ids, embeddings, queries, groundTruth
|
|
402
|
+
));
|
|
403
|
+
console.log(` Build: ${results[results.length-1].buildMs}ms`);
|
|
404
|
+
|
|
405
|
+
// Force GC between tests
|
|
406
|
+
if (global.gc) global.gc();
|
|
407
|
+
|
|
408
|
+
// 2. Pure JS with fast deserialization
|
|
409
|
+
console.log('\n[2/5] Pure JS HNSW (efC=200, Float32Array)...');
|
|
410
|
+
results.push(await benchmarkPureJSFastDeser(buffers, ids, embeddings, queries, groundTruth));
|
|
411
|
+
console.log(` Build: ${results[results.length-1].buildMs}ms`);
|
|
412
|
+
|
|
413
|
+
if (global.gc) global.gc();
|
|
414
|
+
|
|
415
|
+
// 3. Optimized JS (efC=50 + fast deser)
|
|
416
|
+
console.log('\n[3/5] Optimized JS HNSW (efC=50, Float32Array)...');
|
|
417
|
+
results.push(await benchmarkOptimizedJS(buffers, ids, embeddings, queries, groundTruth));
|
|
418
|
+
console.log(` Build: ${results[results.length-1].buildMs}ms`);
|
|
419
|
+
|
|
420
|
+
if (global.gc) global.gc();
|
|
421
|
+
|
|
422
|
+
// 4. Pure JS with efC=50 but slow deser
|
|
423
|
+
console.log('\n[4/5] Pure JS HNSW (efC=50, readFloatLE)...');
|
|
424
|
+
results.push(await benchmarkPureJS(
|
|
425
|
+
'Pure JS (efC=50) + readFloatLE',
|
|
426
|
+
50, buffers, ids, embeddings, queries, groundTruth
|
|
427
|
+
));
|
|
428
|
+
console.log(` Build: ${results[results.length-1].buildMs}ms`);
|
|
429
|
+
|
|
430
|
+
if (global.gc) global.gc();
|
|
431
|
+
|
|
432
|
+
// 5. @ruvector/gnn
|
|
433
|
+
console.log('\n[5/5] @ruvector/gnn differentiableSearch (Rust/NAPI)...');
|
|
434
|
+
try {
|
|
435
|
+
results.push(await benchmarkRuvector(buffers, ids, embeddings, queries, groundTruth));
|
|
436
|
+
console.log(` Build: ${results[results.length-1].buildMs}ms, Search: ${results[results.length-1].searchMs}ms`);
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.log(` SKIPPED: ${(e as Error).message}`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Print results table
|
|
442
|
+
console.log('\n' + '='.repeat(70));
|
|
443
|
+
console.log('RESULTS');
|
|
444
|
+
console.log('='.repeat(70));
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log(
|
|
447
|
+
'Implementation'.padEnd(48) +
|
|
448
|
+
'Build(ms)'.padStart(10) +
|
|
449
|
+
'Search(ms)'.padStart(11) +
|
|
450
|
+
'Avg/q(ms)'.padStart(10) +
|
|
451
|
+
'Mem(MB)'.padStart(8) +
|
|
452
|
+
'Recall%'.padStart(8)
|
|
453
|
+
);
|
|
454
|
+
console.log('-'.repeat(95));
|
|
455
|
+
|
|
456
|
+
for (const r of results) {
|
|
457
|
+
console.log(
|
|
458
|
+
r.label.padEnd(48) +
|
|
459
|
+
String(r.buildMs).padStart(10) +
|
|
460
|
+
String(r.searchMs).padStart(11) +
|
|
461
|
+
String(r.avgSearchMs).padStart(10) +
|
|
462
|
+
String(r.memoryMB).padStart(8) +
|
|
463
|
+
String(r.recall + '%').padStart(8)
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
console.log('-'.repeat(95));
|
|
468
|
+
|
|
469
|
+
// Speedup summary
|
|
470
|
+
if (results.length >= 2) {
|
|
471
|
+
const baseline = results[0].buildMs;
|
|
472
|
+
console.log('\nSpeedup vs current baseline:');
|
|
473
|
+
for (let i = 1; i < results.length; i++) {
|
|
474
|
+
const speedup = (baseline / results[i].buildMs).toFixed(1);
|
|
475
|
+
console.log(` ${results[i].label}: ${speedup}x faster build`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
main().catch(console.error);
|