Files
neuron/docs/architecture/design/perf/engram-m8-profile.md
T

8.4 KiB
Raw Blame History

Engram Tiered Storage — M8 Performance Profile (milestone-0 sample)

Milestone: M8 (ANN wired into engram_activate seed selection). Trunk = worktree /tmp/engram-tiered-wt, branch engram-tiered-storage, HEAD 1507614. Date: 2026-08-12. Author: first full-binary build + profile of the tiered trunk.

This is sample zero of an accumulating per-milestone profile series (see the BUILD-LEDGER "keep performance telemetry CONTINUOUSLY" decision). Compare future milestones (M9, M10, …) against these numbers.

Methodology (read this before trusting a number)

  • On a COPY, never live. All runtime measurements used a read-only copy of the live store booted on a non-live port (:8798) with a throwaway $HOME. The live engram service (:8742, ~/.neuron/engram) was never touched.
  • Two store substrates were used:
    1. Live-egm copy (neuron.egm 458 MB + neuron.wal 44 MB, copied read-only) — this substrate crashes both the M8 binary and the live binary on boot (see Integration Findings). Unusable for runtime measurement.
    2. Clean import — a dir seeded with only snapshot.json (65 MB, stable 06:10), which the binary imported into a fresh 59.9 MB neuron.egm. All healthy runtime numbers below are from this substrate (real graph content, healthy store).
  • Build machine: Apple Silicon (arm64), macOS. Native cc -O2 compile; fold done in a memory-capped (--memory=3g --memory-swap=3g, no swap) linux/amd64 container running elc-linux-amd64.
  • Hardware/thermals uncontrolled; single run per metric unless noted. Treat as order-of-magnitude, not benchmark-grade, except the module benchmark (Test suite).

Build

Metric Value
Fold input engram/src/server.el (44,273 B El, no imports)
Fold output engram.c (30,855 B, 645 lines C), ELC_EXIT=0, 0 fold warnings
Fold time (pure elc) sub-second (server.el is small, importless)
Fold container wall ~38 s (dominated by one-time apt-get install libcurl4 in the throwaway container; the elc invocation itself is <1 s)
Compile cc -O2 -DHAVE_CURL engram.c el_runtime.c engram_store.c engram_vindex.c -lssl -lcrypto -lcurl -lpthread -lm
Compile time 1.83 s wall
Compile warnings 3, all -Wparentheses-equality in the folded engram.c (El if-expr codegen emits if ((x == 0))); cosmetic. el_runtime.c / engram_store.c / engram_vindex.c compiled 0 warnings — notably none around the M8 deferred free(e_eff) or the vindex integration.
Binary 482,008 B (471 KB) Mach-O arm64 executable
ANN linkage verified nm: vindex_search, vindex_build_from_store, eg_vindex_sync, _eg_vindex, store_scan_nodes, engram_store_boot, engram_activate all present

Boot & footprint (clean-import substrate)

Metric Value
Boot from healthy neuron.egm ~2 s to listening
Boot from snapshot.json (one-time import + fresh egm) ~9 s
node_count 13,036 (matches ledger import-dedup: 13,038 snapshot 2 dup-id entries)
edge_count / layer_count 43,402 / 5
embedded_count 4,190
Fresh egm size 59.9 MB (vs the live egm's bloated 458 MB — see Findings)
RSS after boot 126.2 MB (whole 13k-node/43k-edge/4,190-emb graph resident + fresh egm)

Activation latency — q="bullshit" (clean-import substrate)

50 sequential GET /api/activate?q=bullshit&limit=10&depth=3:

Metric Value
p50 34.35 ms
p95 35.26 ms
min / max 33.50 ms / 9,886 ms
Sample n=50
  • The max = 9.9 s is the first call only — a cold query-embedding fetch (eg_embed_fetch → Ollama nomic-embed-text, cold model load). All subsequent calls hit the single-slot query-embedding cache (_eg_qcache) → 34 ms steady state.
  • This 34 ms is the lexical/spread path, NOT the ANN seed path. The store's 4,190 embeddings were generated by the live neuron's native embedding model; the harness's nomic-embed-text query vectors are a different vector space, so no candidate cleared ENGRAM_EMBED_SEED_MIN=0.60 (act-stats: dup_seeds:0, ctx_cos:-2.000 sentinel) → results empty, ANN discovery produced no admitted seeds. Environmental (embedding provenance), not an M8 defect. The ANN wiring still executed (embed fetch succeeded, embed_breaker_open:0; eg_vindex_sync + seed block + free(e_eff) all ran) without crashing on the real store.

KEY M8 METRIC — ANN vs O(n) seed selection (authoritative)

Because the HTTP path can't exercise ANN seeding without embedding-space parity, the authoritative ANN-vs-exact-scan numbers come from the module benchmark (engram/test/run_vindex_tests.sh, PASS 1, optimised), which measures the exact vindex_search code the M8 wiring calls, at full size:

N (768-dim vectors) Brute-force (O(n)) ANN (HNSW) Speedup
5,000 3.475 ms/query 0.353 ms/query 9.8×
20,000 13.809 ms/query 0.698 ms/query 19.8×
  • recall@10 = 0.9365 at ef_search=128 (gate ≥0.90 — PASS). Lower ef trades recall for latency: ef=64→0.844, ef=32→0.750, ef=10→0.625.
  • Determinism: two independent seeded builds give byte-identical query results.
  • vindex_build_from_store over a real engram_store: inserts exactly the embedded nodes, top-1 resolves to the correct node id at ~0 distance.
  • HNSW build cost (single-threaded, note for boot/index-build budgeting): 5,000 vectors ≈ 1535 s, 20,000 vectors ≈ 75 s. In-process the index is built lazily on first activation (eg_vindex_sync) and grown incrementally; the M8 seed block only fires once vindex_size ≥ ENGRAM_EMBED_SEED_K. At the real store's 4,190 embedded nodes this is a one-time few-second first-activation cost — worth watching as the embedded set grows (a future milestone may want to build the index at boot or persist it via vindex_save/vindex_load).

Seed-set parity note (why there is no runtime A/B toggle)

M8 has no ANN on/off env flag by design (ENGRAM_EMBED_SEED_K is a compile-time constant). The exact O(n) cosine scan is preserved verbatim and "tops up" any seed slot the ANN leaves unfilled; every ANN candidate is admitted through the identical cosine/dedup/threshold gate the exact scan uses. So ANN changes only which nodes are discovered and how fast, never the final seed set — parity is structural, not A/B-tested via flag. Recovered-behaviour-when-index-absent is the pre-M8 exact scan.

Integration Findings (first full build of the tiered trunk)

  1. CRITICAL / pre-existing (NOT M8): btree_insert stack-buffer-overflow on opening the live 458 MB neuron.egm. SIGABRT (__stack_chk_fail) via btree_insert ← btree_put ← edge_place/apply_edge_put ← engram_open ← engram_store_boot. Reproduces on the M8 binary and the deployed live binary — the live engram :8742 was crash-looping (18 crash reports 16:18→17:44 on this date; service refusing connections). A clean import into a fresh 59.9 MB egm does not crash (43k edges load fine), so the trigger is the specific pathological live store: 458 MB (8× the healthy 59.9 MB) from a day of churn/tombstones with no M5 compaction + a 44 MB un-checkpointed WAL replayed on open. The bug is in engram_store.c (the edge B-tree / WAL-redo path), upstream of everything M8 touched (M8 lives in el_runtime.c::engram_activate). Fix required before any re-cutover; btree_insert must bound-check regardless of on-disk content.
  2. M8 deferred free(e_eff) (the flagged memory-management concern): compiled warning-free, and the wired path executed end-to-end over HTTP on the real store (with a real query embedding) with no crash / no new crash report — no double-free or use-after-free observed. Freed on all early-return paths and exactly once post-seed-selection.
  3. Write durability + retrieval-fix dedup: create → checkpoint → clean SIGTERM → restart → node found by id and by search (node_count 13,036→13,037 preserved).

Caveats

  • All on a copy; healthy-substrate numbers are from a re-imported store, not the live paged store (which is currently un-bootable — Finding 1).
  • Single-run metrics; no thermal control.
  • Embedding-space mismatch prevented a real semantic q=bullshit activation in this harness; the ANN speedup number is the module benchmark, which is the correct gauge.