-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathembedding-benchmark.ts
More file actions
165 lines (134 loc) · 4.72 KB
/
Copy pathembedding-benchmark.ts
File metadata and controls
165 lines (134 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env node
/**
* Embedding benchmark runner — measures search recall across all models.
*
* Each model runs in a forked subprocess so that a crash (OOM, WASM segfault
* in the ONNX runtime) only kills the child — the parent survives and collects
* partial results from whichever models succeeded.
*
* Usage: node scripts/embedding-benchmark.js > result.json
*/
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
import Database from 'better-sqlite3';
import { resolveBenchmarkSource, srcImport } from './lib/bench-config.js';
import { forkWorker } from './lib/fork-engine.js';
const MODEL_WORKER_KEY = '__BENCH_MODEL__';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, '..');
// ── Worker process: benchmark a single model, write JSON to stdout ───────
if (process.env[MODEL_WORKER_KEY]) {
const modelKey = process.env[MODEL_WORKER_KEY];
const { srcDir, cleanup } = await resolveBenchmarkSource();
const dbPath = path.join(root, '.codegraph', 'graph.db');
const { buildEmbeddings, MODELS, searchData, disposeModel } = await import(
srcImport(srcDir, 'domain/search/index.js')
);
const TEST_PATTERN = /\.(test|spec)\.|__test__|__tests__|\.stories\./;
function splitIdentifier(name) {
return name
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
.replace(/[_-]+/g, ' ')
.trim();
}
function loadSymbols() {
const db = new Database(dbPath, { readonly: true });
let rows = db
.prepare(
`SELECT name, kind, file FROM nodes WHERE kind IN ('function', 'method', 'class') ORDER BY file, line`,
)
.all();
db.close();
rows = rows.filter((r) => !TEST_PATTERN.test(r.file));
const seen = new Set();
const symbols = [];
for (const row of rows) {
if (seen.has(row.name)) continue;
seen.add(row.name);
const query = splitIdentifier(row.name);
if (query.length < 4) continue;
symbols.push({ name: row.name, kind: row.kind, file: row.file, query });
}
return symbols;
}
// Redirect console.log to stderr so only JSON goes to stdout
const origLog = console.log;
console.log = (...args) => console.error(...args);
const symbols = loadSymbols();
console.error(` [${modelKey}] Loaded ${symbols.length} symbols`);
const embedStart = performance.now();
await buildEmbeddings(root, modelKey, dbPath, { strategy: 'structured' });
const embedTimeMs = Math.round(performance.now() - embedStart);
let hits1 = 0;
let hits3 = 0;
let hits5 = 0;
let hits10 = 0;
const searchStart = performance.now();
for (const { name, query } of symbols) {
const data = await searchData(query, dbPath, { minScore: 0.01, limit: 10 });
if (!data) continue;
const names = data.results.map((r) => r.name);
const rank = names.indexOf(name) + 1;
if (rank === 1) hits1++;
if (rank >= 1 && rank <= 3) hits3++;
if (rank >= 1 && rank <= 5) hits5++;
if (rank >= 1 && rank <= 10) hits10++;
}
const searchTimeMs = Math.round(performance.now() - searchStart);
try { await disposeModel(); } catch { /* best-effort */ }
const total = symbols.length;
const modelResult = {
dim: MODELS[modelKey].dim,
contextWindow: MODELS[modelKey].contextWindow,
hits1,
hits3,
hits5,
hits10,
misses: total - hits10,
total,
embedTimeMs,
searchTimeMs,
};
console.log = origLog;
console.log(JSON.stringify({ symbols: symbols.length, result: modelResult }));
cleanup();
process.exit(0);
}
// ── Parent process: fork one child per model, assemble final output ──────
const { version, srcDir, cleanup } = await resolveBenchmarkSource();
const dbPath = path.join(root, '.codegraph', 'graph.db');
const { MODELS } = await import(srcImport(srcDir, 'domain/search/index.js'));
const TIMEOUT_MS = 600_000;
const hasHfToken = !!process.env.HF_TOKEN;
const modelKeys = Object.keys(MODELS);
const results = {};
let symbolCount = 0;
const scriptPath = fileURLToPath(import.meta.url);
for (const key of modelKeys) {
if (key === 'jina-code' && !hasHfToken) {
console.error(`Skipping ${key} (HF_TOKEN not set)`);
continue;
}
const data = await forkWorker(scriptPath, MODEL_WORKER_KEY, key, process.argv.slice(2), TIMEOUT_MS);
if (data) {
results[key] = data.result;
if (data.symbols) symbolCount = data.symbols;
const r = data.result;
console.error(
` Hit@1=${r.hits1}/${r.total} Hit@3=${r.hits3}/${r.total} Hit@5=${r.hits5}/${r.total} misses=${r.misses}`,
);
} else {
console.error(` ${key}: FAILED (worker crashed or timed out)`);
}
}
const output = {
version,
date: new Date().toISOString().slice(0, 10),
strategy: 'structured',
symbols: symbolCount,
models: results,
};
console.log(JSON.stringify(output, null, 2));
cleanup();