Home/Benchmarks
Not a synthetic corpus. These numbers come from the actual release binary run against real codebases and a hand-curated NL→code gold set, by the harness in bench/run_bench.py.
Four terms carry most of this page. None of them is complicated, but a number like “MRR 0.67” means nothing without a sense of what good looks like — so here is the whole vocabulary, once, with a reference point.
recall@5 and recall@10 are the same idea with a wider net — “was it anywhere in the top 5 / top 10”.Every bar below is computed straight from bench/results.json by bench/make_charts.py — no hand-drawn numbers. The headline: swapping in the local semantic embedder roughly doubles MRR and recall@1 on paraphrased queries.
recall@k and MRR · higher is better · 18-query NL→code gold set
rank of the gold file, 1 = best · grey = lexical, blue = semantic · ✕ = not in top-10
chunks / second on real repos · hash embedder
p50 / p99 · in-process · lower is better
on a 1-line edit
This is not a mock-up. We recorded the actual session — building the binary, then running the whole suite end to end (A retrieval quality · B indexing · C engine latency · D filesystem) — and replay it below with its real timing (long idle pauses are compressed). Here is the exact machine it ran on.
Recorded with bench/record_cast.py (every line timestamped); replayed by a tiny vanilla-JS player — no asciinema, no external libraries. The same run wrote bench/results.json that powers the charts above.
The exact file the run just produced — shown verbatim, then charted straight from it.
recall@k and MRR · higher is better · 18-query NL→code gold set
SynaFS ships a built-in syna bench, but its corpus is synthetic — recall is 1.0 by construction, which proves the pipeline runs but says nothing about quality. The harness here is different: it indexes real source trees and grades retrieval against a gold set whose answers are fixed by inspection, swapping only the embedder so the comparison is clean.
cargo build --release --features coderank.node_modules, target, build output) from real repositories into a clean tree.recall@k = share of queries with a gold hit in the top k; MRR = mean of 1/rank.syna bench; measure indexing throughput by wall-clock over the real trees.| Natural-language query | Gold file |
|---|---|
| “compress response header fields for an http2 stream” | syna-grpc/src/hpack.rs |
| “get notified of every write across a whole mounted filesystem” | syna-engine/src/fanotify.rs |
| “demand and check the caller's certificate during the secure handshake” | syna-web/src/tls.rs |
| “run a neural code representation model locally without python” | syna-embed/src/coderank.rs |
Reference machine: x86-64 Linux, CPU-only inference, the pure-Rust engine (brute-force vector search over an in-memory snapshot). Your numbers will vary by corpus, embedder, and hardware. Raw output lives in bench/results.json.
bench/gold.json is 18 natural-language queries over the SynaFS source. Each is a paraphrase that deliberately avoids the codebase's own identifiers; the gold answer is the file that primarily implements that concept (e.g. "compress response header fields for an http2 stream" → syna-grpc/src/hpack.rs). Relevance is graded at file level. We index the same tree twice and run the identical hybrid pipeline, changing only the embedder.
| Embedder | recall@1 | recall@3 | recall@5 | recall@10 | MRR |
|---|---|---|---|---|---|
| Lexical (hash baseline) | 0.111 | 0.278 | 0.389 | 0.500 | 0.216 |
| Semantic (CodeRankEmbed) | 0.333 | 0.556 | 0.556 | 0.611 | 0.434 |
Semantic embeddings roughly double MRR (0.210 → 0.434) and recall@1 (0.111 → 0.333). The biggest per-query wins are exactly where lexical overlap is weakest: "get notified of every write across a whole mounted filesystem" → fanotify.rs climbs from rank 10 → 1, and "demand and check the caller's certificate during the secure handshake" → tls.rs from 6 → 1. Several hard queries (hpack.rs, wal.rs, ws.rs) are missed by both in the top-10 — the set is small and untuned, not rigged toward a win.
End-to-end syna index (tree-walk → tree-sitter chunk → embed → persist) over real source trees, code files only. The offline hash embedder isolates engine/chunking throughput; semantic indexing is bound by CPU model inference and is far slower (it is the quality path, not the throughput path).
| Corpus | Files | Chunks | files/s | MB/s | chunks/s | Index |
|---|---|---|---|---|---|---|
| SynaFS (self) | 60 | 964 | 466 | 5.7 | 7,493 | 7 MB |
| rogers | 467 | 4,304 | 324 | 4.4 | 2,988 | 34 MB |
| nidavellir | 559 | 21,604 | 839 | 64.2 | 32,438 | 198 MB |
Index size is the current split snapshot (manifest.json + docs.bin + units.ndjson): full vectors still dominate disk. PQ is shipped as an opt-in scale path (SYNA_ANN=pq); making it automatic at large N remains future work.
From syna bench at 1,000 files / 5,000 chunks. These are in-process (no per-call snapshot reload), so they reflect engine latency, not CLI start-up. Corpus is synthetic; the latency is real.
| Metric | p50 | p99 |
|---|---|---|
| Search latency | 0.21 ms | 0.25 ms |
| 1-line reindex latency | 19.5 ms | 21.3 ms |
SynaFS is a real FUSE filesystem, so we measured ordinary file ops through the mount against the raw backing disk, plus the cost of a semantic search done by listing a magic directory. The honest picture: metadata is essentially free, every latency stays under ~100 µs, and a semantic query runs in a fraction of a millisecond — but raw byte streaming carries real overhead, because the current pure-Rust FUSE copies bytes through userspace (kernel FUSE_PASSTHROUGH would close that gap).
sequential read & write · mount as a share of raw
stat / readdir / open+read · p50 · lower is better
semantic query through the magic path
| Operation | raw | mount |
|---|---|---|
| Sequential read | 32.2 GB/s | 6.4 GB/s |
| Sequential write (index-on-write) | 2.5 GB/s | 0.7 GB/s |
| stat / getattr | 3.8 µs | 4.1 µs |
| readdir | 2.5 µs | 19.0 µs |
| open + read (small file) | 5.6 µs | 23.0 µs |
| Semantic query · ls magic path | — | 0.37 ms |
stat is identical through the mount because attributes are cached; throughput is lower because reads/writes round-trip through userspace and writes also enqueue the reindex. Magic-path semantic query is 0.37 ms p50 / 71 ms p99 in the checked-in run, so the median is the fast path, not the full latency envelope. Reproduce: python3 bench/fs_bench.py.Exact brute-force vector search is O(N) per query, so SynaFS includes a pure-Rust HNSW approximate-nearest-neighbour index behind the same VectorIndex trait and selectable with SYNA_ANN=hnsw. The current checked-in public artifact is a 10k-vector smoke run: 2.6× faster p50 search with recall 1.000. Full 100k headlines remain pending until regenerated and committed.
HNSW vs exact brute-force · by corpus size
recall@10 vs exact brute-force · 0–1
p50 search · brute-force vs HNSW
| Vectors | search p50 · brute → HNSW | recall@10 | speedup | HNSW build |
|---|---|---|---|---|
| 10,000 | 0.79 → 0.30 ms | 1.000 | 2.6× | 2.7 s |
syna ann-bench --sizes 10000 --dim 768 --queries 200.M0 kept every vector as full f32 — ~3 KB each, so a few million chunks no longer fit in RAM. SynaFS now ships a pure-Rust PQ-compressed index (SYNA_ANN=pq): each vector becomes a 96-byte code, the raw f32s move to an on-disk tier, and search re-scores the top candidates exactly from disk — so RAM drops 32× while recall@10 stays at brute-force. Reproduce with syna pq-bench.
full f32 vs PQ codes · lower is better
full f32 → PQ code
recall@10 at 100k · PQ + exact rerank
plain PQ (O(N)) vs IVF-PQ · fewer codes is better
| Vectors | RAM (raw → PQ) | compression | recall@10 | search p50 | IVF scan |
|---|---|---|---|---|---|
| 1,000 | 3.07 → 0.10 MB | 32× | 1.000 | 0.24 ms | 12.9% |
| 10,000 | 30.7 → 0.96 MB | 32× | 1.000 | 0.68 ms | 8.0% |
| 100,000 | 307 → 9.6 MB | 32× | 1.000 | 6.45 ms | 8.2% |
syna pq-bench --sizes 1000,10000,100000.Every vector backend — the brute-force scan, HNSW's distance, PQ's exact rerank — bottlenecks on the same dot product over f32 embeddings. We replaced the scalar loop with an AVX2+FMA kernel (runtime-detected via std::arch, scalar fallback), keeping it pure-Rust and dependency-free. Isolated, the kernel is 5.5–8.7× faster; end-to-end search shows ~2.8× because the dot is one stage of many.
AVX2+FMA vs scalar dot · by vector dim
ns/op · scalar vs SIMD
black_box to defeat dead-code elimination; 768-d is the CodeRankEmbed dimension. Reproduce: syna simd-bench.A one-line edit used to rebuild the whole index and re-serialize every vector — O(N) per edit, growing as a file accrues versions. Now an edit folds only its new chunks into the live index and appends them to docs.ndjson; only the small vector-free manifest is rewritten. Per-edit cost is O(changed) and independent of edit history.
per-edit reindex ms over 300 edits
reindex p50 · first vs last quartile
syna incr-bench.The 18-query gold set above is hand-written; this is its held-out complement. Following the CodeSearchNet method, a harness auto-harvests 186 docstring→function pairs from the real source and strips the doc comments from the indexed code, so the query text is never in the index (no leakage). Semantic embeddings win decisively on this larger, bias-free set.
recall@k & MRR · semantic vs lexical · higher is better
python3 bench/csn_bench.py.private-generated multi-repo sweep · not a public leaderboard
With the real CodeRankEmbed embedder, every cold syna query reloads the 137M-parameter model (~140 ms) before a ~36 ms search. A per-repo daemon holds the engine and its model resident; query/edit/commit/index route to it over gRPC-Web, paying the load once per session instead of once per call.
full syna query wall · CodeRankEmbed · lower is better
cumulative wall over 20 queries · lower is better
python3 bench/daemon_bench.py.Test files repeat the implementation's vocabulary almost verbatim, and RRF scores cluster within a few percent, so a well-named test regularly edged out the very code it exercises. Two priors fix this. Stage 2.5 applies a mild demotion to test-suite paths (tests/, test_*.py, *.test.js, *_test.go, conftest.py, …) — about 7 ranks at the top, enough to flip a near-tie toward the implementation, and uniform so genuinely test-seeking queries (whose competitors are also tests) keep their order. Stage 3.5 caps any one file at two chunks in the top-N, with displaced hits backfilling the tail; the failure that motivated it was a single well-named test file flooding an entire top-3 while the answer sat one rank below.
recall@1 and MRR · coderank index · higher is better
python3 experiments/harness/retrieval_gate.py --corpus <dir>. SYNA_DEMOTE_TESTS=0 and SYNA_DIVERSITY=0 opt out of each stage independently.Embedding dominates indexing wall time — on a real corpus it is the overwhelming majority of it, which makes the forward pass the only thing worth accelerating. The embedder now runs on Apple Metal behind an opt-in build feature (--features coderank-metal, SYNA_DEVICE=metal), and the win is close to the arithmetic limit of moving that pass onto the GPU. Peak memory drops too, because model tensors live in GPU-side allocations instead of the malloc heap.
355 files / 1,873 chunks · same worker settings · lower is better
peak RSS during the same index · lower is better
docs/embedding-decision.md §3.2 rather than as a JSON artifact. CUDA remains unverified: the coderank-cuda feature exists and is wired the same way, but there is no NVIDIA device in CI or on the dev boxes, so nobody has run it. Both GPU features are #[cfg]-gated and off by default; the tested default is still CPU, and a GPU request without the matching feature falls back to CPU with a warning.Every benchmark above this point uses the synthetic hash embedder, which is near-free by design — that is what isolates the index, store and search machinery. Profiling the real CodeRankEmbed model tells a much blunter story about production, and it is the single most useful measurement in this document.
| Indexing | hash | coderank |
|---|---|---|
| 37 chunks | ~0.00 s | 2.6 s |
| 346 chunks | ~0.001 s | 31 s |
| process RSS | 6 MB | ~1.1 GB |
file workers × candle's internal threads · 346 chunks · lower is better
naive pad-to-longest batching vs per-text · 346 chunks · lower is better
batch × padded_seq² budget, so short chunks share a pass while long ones stay in small groups. That version ships and is worth ~1.48× on short-chunk batches, with a padding key-mask making every row's output bit-identical to encoding it alone.These were all on the list and are all recorded as declined, so the decision is explicit rather than forgotten. Each sits below the noise floor of a real embedder — building them would add risk and complexity for a gain no deployment could observe.
SYNA_ANN=pq. It trades exact recall for 32× memory, which only pays off past the millions-of-vectors regime; auto-gating that is a policy call best made with data from a real large index rather than presumed. The backend is built, benched, and one flag away.docs/performance.md on a 32-core host with CodeRankEmbed on CPU; they are committed as a document rather than as a JSON artifact, so unlike the charted sections above there is no machine-readable run to replay. The thread sweep and the batching comparison are the same 346-chunk corpus and the same measurement pass. Note the doc predates the shipped length-sorted batching and still describes only the reverted naive version — the code in crates/syna-embed/src/coderank.rs is the current truth.Two clusters of work, both aimed at the parts of a session that are pure overhead. Cold start is everything paid before the first useful answer — loading a 137M-parameter model, rebuilding an index structure, re-reading a tree to check for edits. Payload is what each reply carries: the same answer can arrive as pretty-printed JSON full of fields no tool reads, or as compact text.
| What changed | Before | After |
|---|---|---|
| Cold start | ||
| Warm daemon instead of reloading the model per call | 263 ms | 39 ms |
HNSW graph cached on disk (.syna/vindex.bin) instead of rebuilt on open | 250 ms | 50 ms |
| Model weights loaded lazily — graph-only sessions never pay for them | 5–6 s | 0.64 s |
| Drift cache remembers its verdict instead of re-reading the tree every query | ~140 ms/query | stat-only |
| Query embedding on Metal instead of CPU | 37 ms | 10.5 ms |
| Payload | ||
search replies as compact text, not pretty-printed JSON | — | −59% tok |
symbol_lookup drops the 64-hex id no tool ever consumes | — | −68% tok |
| A batch of questions returns snippets, not N full source blocks | — | −42% tok |
| Top hit's source inlined, so the answer needs no follow-up read | 14 ops | 7 ops |
bench/daemon_results.json, and the charted version is section J.SYNA_ANN=pq; reproduce with syna pq-bench). And the scan is no longer O(N): an IVF coarse quantizer (≈√N cells, probe a few) touches only ~8% of the codes at 100k — recall@10 stays ~0.99 and that fraction keeps shrinking as the corpus grows. §E adds pure-Rust HNSW for 2.6× faster exact search on top.bench/csn_bench.py). And it's no longer only our own source: the same harness now runs across 7 external third-party repos (TS/JS + Python, private-generated held-out pairs, bench/csn_multi.py). Out-of-domain retrieval is harder; the current arcflo aggregate is recall@10 0.343 and MRR 0.162, without a public semantic-vs-lexical split, so it is not yet a publishable external win claim. Honest remainder: absolute external numbers trail the Rust target SynaFS was tuned on, and a public CoIR leaderboard is still future work.# build with the local semantic embedder, then run the harness cargo build --release --features coderank python3 bench/run_bench.py # → bench/results.json python3 bench/run_bench.py --skip-coderank # lexical only, no model download
Benchmark write-up ships with the source repo as docs/benchmarks.md.