Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0bc303139 | |||
| 4965600d65 |
@@ -0,0 +1,97 @@
|
||||
# Perf Profile — M9 Geometry Priming (ENGRAM_GEOMETRY_PRIMING)
|
||||
|
||||
**Date:** 2026-08-12
|
||||
**Branch:** `engram-tiered-storage`
|
||||
**Change:** `ENGRAM_GEOMETRY_PRIMING` (default OFF) in `el_runtime.c` `engram_activate` + `engram_geometry.c`
|
||||
**Method:** A/B over 15 representative queries against a **copy** of the recovered store
|
||||
(`~/.neuron/engram/.neuron.egm.disabled`, ~4190 embedded nodes, 768-d nomic-embed-text),
|
||||
throwaway HOME, ports 48799/48800. **Live `:8742` never touched.** `engram.c` (folded from
|
||||
`server.el`) reused byte-identical across M8 and M9, so the only variable is `el_runtime.c`.
|
||||
|
||||
Three configs: **A** = M9 flag OFF · **B** = M9 flag ON (`=1`) · **C** = pre-M9 M8 baseline binary.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
| Artifact | Result |
|
||||
|---|---|
|
||||
| M9 `-O2` link (`… engram_geometry.c … -lssl -lcrypto -lcurl -lpthread -lm`) | rc=0, 499,720 B arm64 |
|
||||
| ASan/UBSan link (`-fsanitize=address,undefined -O1`) | rc=0, 1,945,616 B |
|
||||
| Warnings from `el_runtime.c` / `engram_geometry.c` | **0** (3 pre-existing `-Wparentheses-equality` in generated `engram.c` only) |
|
||||
| `nm`: `engram_geo_mean_build`, `engram_geometry_descriptor` | present (T); `eg_geometry_priming_on` inlined (static-local `.cached` present in both binaries) |
|
||||
|
||||
> Note: the bare `cc … -lm` link fails with undefined `_curl_*` — `el_runtime.c` uses libcurl for
|
||||
> the ollama embedder. The canonical link must include `-lssl -lcrypto -lcurl` (per `link.sh`).
|
||||
|
||||
---
|
||||
|
||||
## Latency (wall-clock, `curl -w %{time_total}`, 15 queries)
|
||||
|
||||
| config | median | p90 | min | max |
|
||||
|---|---|---|---|---|
|
||||
| **A — M9 OFF** | **77.8 ms** | 80.5 ms | 71.1 | 84.2 |
|
||||
| C — M8 baseline | 76.0 ms | 81.2 ms | 71.4 | 91.4 |
|
||||
| **B — M9 ON** | **249.6 ms** | **1039.2 ms** | 169.2 | **1256.3** |
|
||||
|
||||
- **OFF adds zero cost:** 77.8 ms vs M8 76.0 ms — within noise. The flag is free when unset.
|
||||
- **ON regresses hard:** **3.21x median** (+171.8 ms), **~13x p90** (80 → 1039 ms), max **1.26 s**.
|
||||
- The warm-cache path (global mean already built) is ~0.5 s; the cold path pays the full
|
||||
`engram_geo_mean_build` scan (O(N·dim) over ~4190 × 768). The persistent per-query cost is the
|
||||
**descriptor** itself — covariance eigensolve over up to `max_members` (400) × 768-d plus one
|
||||
`store_get_node` **paged read per member** — run on *every* activation while the flag is ON.
|
||||
|
||||
---
|
||||
|
||||
## Retrieval quality (the win it was supposed to buy)
|
||||
|
||||
**Coherence** — mean pairwise cosine in centered space, top-20 by activation strength
|
||||
(node embeddings re-derived via nomic-embed-text; centered against the mean of the gathered
|
||||
result set — the *true* store-wide mean is not exposed by the API, flagged as an approximation):
|
||||
|
||||
| | OFF | ON | Δ |
|
||||
|---|---|---|---|
|
||||
| mean over 15 queries | 0.1067 | 0.1114 | **+0.0047 (noise)** |
|
||||
| queries where ON > OFF | — | — | **4 / 15** |
|
||||
|
||||
Two real sparse-cue wins (`self identity values` +0.118, `hebbian learning edges` +0.064), but the
|
||||
**polysemous cues — the disambiguation target — are mostly flat or down.**
|
||||
|
||||
**Disambiguation** — no clean "scope to one sense" pattern on polysemous cues. Additions/drops are
|
||||
small (±2..8 of 300-item sets) and not sense-coherent (e.g. `memory` gains some on-domain nodes but
|
||||
also infra items; `core` similar).
|
||||
|
||||
**Count shift:** ON adds sub-threshold neighbors to sparse cues (+3..+4) and trims a few from dense
|
||||
polysemous cues (−1..−3) — consistent with priming warming sparse neighborhoods and damping
|
||||
off-domain seeds on dense ones, but the net does not move measured coherence.
|
||||
|
||||
---
|
||||
|
||||
## Correctness / safety (all pass)
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| Byte-identical: **A (OFF) == C (M8)** result id sequence + order, all 15 queries (incl. 301/294/263-item sets) | **PASS** (only wall-clock ACT-R fields differ; `activation_strength` max \|Δ\| = 2e-5) |
|
||||
| WM `promoted` ≤ 24 under ON | holds (exactly 24 on dense cues) |
|
||||
| Queries with results under OFF → empty under ON | 0 |
|
||||
| Crash / hang under ON | none (max hops = 1) |
|
||||
| ASan + UBSan under ON (cold build + warm descriptor paths) | **CLEAN** — no report |
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
- **Deploy default-OFF binary: GO.** Byte-identical to M8, zero cost off, clean build, sanitizer clean.
|
||||
- **Enable flag: NO-GO (for now).** 3.21x median / ~13x p90 latency for no reliable quality gain
|
||||
(coherence +0.0047 mean = noise; no clean disambiguation). Correctness/safety are fine — it simply
|
||||
does not earn its cost. **This is a cost/benefit NO-GO, not a defect.**
|
||||
|
||||
### Prerequisites before re-evaluating the flag
|
||||
1. **Amortize the descriptor cost.** The per-query geo-mean build + eigensolve + paged reads
|
||||
dominate. Cache the neighborhood descriptor (it is the M10 cell-assembly cache's job) and/or
|
||||
compute geometry periodically/off-hot-path rather than on every `engram_activate`.
|
||||
2. **Center against the true store-wide mean** (the `GeoMeanCache` already computes it) rather than
|
||||
a per-query gathered-set approximation, and re-measure coherence — the current signal may be
|
||||
understated by the approximation.
|
||||
3. **Re-tune** `ENGRAM_GEO_SEED_LO` / `PRIME_SCALE` / `PRIME_MAX` and re-measure only after (1),
|
||||
so tuning is not chasing latency noise.
|
||||
@@ -0,0 +1,130 @@
|
||||
# Runbook — M9 Geometry Priming: Cutover & Reversal
|
||||
|
||||
**Date:** 2026-08-12
|
||||
**Component:** engram activation (`lang/runtime/el_runtime.c` → `engram_activate`)
|
||||
**Branch:** `engram-tiered-storage`
|
||||
**Flag:** `ENGRAM_GEOMETRY_PRIMING` (env, **default OFF = current M8 behavior, byte-identical**)
|
||||
**Blast radius if wrong:** the core recall path of Will's live memory. Treat with according care.
|
||||
|
||||
---
|
||||
|
||||
## 1. What changes
|
||||
|
||||
This is the first behavior-changing step that touches the **core recall/priming** path.
|
||||
It wires the M9 **mean-centered relational-neighborhood geometry** (`engram_geometry.c`,
|
||||
shipped commits `2a4c5c6` foundation + `8cae0f9` centering) into `engram_activate`
|
||||
**seed selection**, and it does so **behind a reversible env flag that defaults OFF**.
|
||||
|
||||
- **Flag OFF (default):** `engram_activate` runs the exact M8 code path. The new code is a
|
||||
single `if (eg_geometry_priming_on() && …)` block that short-circuits on the first term,
|
||||
plus a few unused static helpers and one zero-initialized counter. **No behavioral change.**
|
||||
- **Flag ON (`ENGRAM_GEOMETRY_PRIMING=1`):** after M8 produces its ANN seed set, the
|
||||
**centered** geometry of that neighborhood is computed and used to, **composing with**
|
||||
(never replacing) M8's ANN candidate generation:
|
||||
1. **Damp off-domain seeds** — each M8 seed's activation is scaled by a **damp-only**
|
||||
factor `lo + (1-lo)·membership ∈ [lo, 1]` (default `lo=0.5`). The neighborhood anchor
|
||||
(membership→1) is unchanged; seeds that are semantically off-domain **in the centered
|
||||
frame** lose weight. This is the disambiguation win. It can only *sharpen*, never amplify.
|
||||
2. **Prime the neighborhood sub-threshold** — descriptor members not already seeded get a
|
||||
**warm floor** `activation = membership · scale` (default `scale=0.08`, strictly below the
|
||||
WM promotion gate `0.15`), capped at `ENGRAM_GEO_PRIME_MAX` (default 32), ISE nodes skipped.
|
||||
They enter the frontier so a warm gradient spreads one hop, then dies at the BFS `0.02`
|
||||
cutoff. **Safe because the BFS keeps the max** (`el_runtime.c` `if (!reached || new_act >
|
||||
best_bg)`): priming only *raises a floor*, it can never cap a stronger legitimate activation.
|
||||
|
||||
### Why default-OFF makes deploying the binary behavior-neutral
|
||||
Because every line of the new logic is gated behind `ENGRAM_GEOMETRY_PRIMING`, **deploying the
|
||||
new binary with the flag unset is behavior-neutral** — it is the M8 activation path, verified
|
||||
byte-identical in the A/B (flag-OFF promoted-node sets equal the pre-M9 M8 binary's, per-query).
|
||||
Enabling the geometry is then a **single reversible flag flip**, not a redeploy.
|
||||
|
||||
---
|
||||
|
||||
## 2. The flag
|
||||
|
||||
| Env var | Default | Effect |
|
||||
|---|---|---|
|
||||
| `ENGRAM_GEOMETRY_PRIMING` | unset / `0` | **OFF** — exact M8 behavior. |
|
||||
| `ENGRAM_GEOMETRY_PRIMING=1` | — | **ON** — centered-geometry seed damping + sub-threshold priming. |
|
||||
| `ENGRAM_GEO_SEED_LO` | `0.5` | Seed damp floor (factor ∈ [LO,1]). `1.0` disables damping. |
|
||||
| `ENGRAM_GEO_PRIME_SCALE` | `0.08` | Warm-floor scale; clamped `(0, WM_gate=0.15)`. |
|
||||
| `ENGRAM_GEO_PRIME_MAX` | `32` | Max primed members per activation (0 disables priming). |
|
||||
|
||||
The flag is read **once** per process (cached), so enabling/disabling requires a **process
|
||||
restart** of the engram service — it is not hot-togglable within a running process.
|
||||
|
||||
---
|
||||
|
||||
## 3. How to enable live (deliberate, reversible)
|
||||
|
||||
> Precondition: the default-OFF binary has already been deployed and is running the M8 path
|
||||
> healthily (behavior-neutral deploy). Do this only with Will present, per the standing rails.
|
||||
|
||||
1. **Snapshot first** (always, before any activation-behavior change):
|
||||
`~/.neuron/backups/pre-geometry-priming-<ts>/` ← copy `neuron.egm`, `neuron.wal`,
|
||||
the current `engram` binary, and `ai.neuron.engram.plist`.
|
||||
2. Add `ENGRAM_GEOMETRY_PRIMING=1` to the engram service environment
|
||||
(`ai.neuron.engram.plist` `EnvironmentVariables`).
|
||||
3. `launchctl bootout gui/$(id -u)/ai.neuron.engram` → `launchctl bootstrap …` (restart so the
|
||||
flag is re-read).
|
||||
4. **Verify:** service comes up serving the same node count; `/api/act-stats` shows sane WM
|
||||
(promoted ≤ 24); spot-check 3–4 real queries return coherent results; watch one heartbeat
|
||||
cycle for crashes/latency. The `geo_primed` counter (if surfaced) should be > 0.
|
||||
|
||||
---
|
||||
|
||||
## 4. Rollback (exact steps)
|
||||
|
||||
Rollback is a **flag flip**, not a data operation — the store is untouched by enabling the flag,
|
||||
and priming is a read-mostly, bounded, sub-threshold addition.
|
||||
|
||||
**Fast path (preferred) — disable the flag:**
|
||||
1. Remove `ENGRAM_GEOMETRY_PRIMING` (or set `=0`) from `ai.neuron.engram.plist`.
|
||||
2. `launchctl bootout … && launchctl bootstrap …`.
|
||||
3. Verify: service healthy, activation is the M8 path again. **Done** — no data change to undo.
|
||||
|
||||
**Full path (only if the binary itself is suspect) — redeploy prior binary:**
|
||||
1. `launchctl bootout gui/$(id -u)/ai.neuron.engram`.
|
||||
2. Restore the prior `engram` binary from `~/.neuron/backups/pre-geometry-priming-<ts>/`.
|
||||
3. Restore `ai.neuron.engram.plist` from the same backup (flag absent).
|
||||
4. `launchctl bootstrap …`; verify node count + a self-traversal + write-survives-restart.
|
||||
5. If (and only if) the store was somehow mutated: restore `neuron.egm` + `neuron.wal` from the
|
||||
backup. **Note:** enabling the flag does not write geometry to the store, so this step is
|
||||
expected to be unnecessary — the primed activations are per-call and non-persistent beyond the
|
||||
ordinary `background_activation`/WM write-back that M8 already does.
|
||||
|
||||
**Rollback triggers:** any crash/hang in `engram_activate`; WM promotion count exceeding the cap
|
||||
or collapsing; a measured recall/coherence regression vs the OFF baseline; unacceptable latency
|
||||
increase; any ASan/UBSan report under the flag.
|
||||
|
||||
---
|
||||
|
||||
## 5. Reversibility guarantees (why this is low-risk to deploy, higher-care to enable)
|
||||
|
||||
- **Deploy (flag OFF):** byte-identical to M8. Verified in A/B. Zero-risk redeploy.
|
||||
- **Enable (flag ON):** bounded and composable —
|
||||
- never removes an M8 seed (damp-only, factor ≥ `lo` > 0);
|
||||
- never amplifies a seed above its M8 value (factor ≤ 1);
|
||||
- priming is strictly sub-threshold (`scale < WM_gate`) and capped (`PRIME_MAX`);
|
||||
- priming raises a floor only (BFS keeps max) — cannot cap real activation;
|
||||
- does not write geometry to the durable store;
|
||||
- degrades to exact M8 behavior for any call where the paged store / centered global mean /
|
||||
embedder is unavailable (guarded, not crashing).
|
||||
- **Disable:** one env removal + restart; no data to reconcile.
|
||||
|
||||
---
|
||||
|
||||
## 6. Known caveats / uncertainties (flagged — this is the memory core)
|
||||
|
||||
- **Perf cost of ON:** the descriptor (covariance eigensolve + `store_get_node` paged reads per
|
||||
member) runs on **every** activation when the flag is ON. See
|
||||
`docs/architecture/design/perf/engram-geometry-priming-profile.md` for the measured OFF-vs-ON
|
||||
latency. If that delta is unacceptable, keep the flag OFF (deploy stays valid) and revisit with
|
||||
a cached/periodic descriptor.
|
||||
- **Two-store consistency:** the descriptor reads embeddings from the **paged** store while the
|
||||
ANN index is over the **resident** array. This-call backfilled embeddings can lag the paged
|
||||
store by ≤ `ENGRAM_EMBED_BACKFILL_PER_CALL` nodes — the same staleness class as the M8 vindex,
|
||||
and it can only omit a member, never mis-prime.
|
||||
- **Damp tuning:** `lo=0.5` can at most halve an off-domain seed. If a coherence regression is
|
||||
observed, raise `ENGRAM_GEO_SEED_LO` toward `1.0` (→ priming-only, no damping) before disabling
|
||||
entirely.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Reversal / Decisions — §5 Geometry Operators EL Cutover
|
||||
|
||||
**Date:** 2026-08-13
|
||||
**Branch:** `engram-tiered-storage` (worktree `/tmp/engram-tiered-wt`)
|
||||
**Parent commit:** `5336cfe` (M9 §5 geometry operators as C functions + EL builtins, staged)
|
||||
**Scope:** make the six engram geometry operators callable from a compiled `.el`
|
||||
program, and demonstrate it on real store data. Staged, reversible. NOT pushed,
|
||||
NOT tagged. Live `:8742` daemon and `~/.neuron/engram` never touched.
|
||||
|
||||
---
|
||||
|
||||
## What this delivers
|
||||
|
||||
On `5336cfe` the six operators existed as heavy-runtime C functions
|
||||
(`engram_geo_*_json` in `lang/runtime/el_runtime.c:12287-12385`, declared in
|
||||
`el_runtime.h:627-632`) but the EL call surface was deferred. This change
|
||||
formalizes the cutover and proves callability from a compiled El (CGI) program.
|
||||
|
||||
### Key finding (why no OOM-prone compiler rebuild was needed)
|
||||
|
||||
The shipped compiler `lang/dist/platform/elc` **already emits a direct C call for
|
||||
these builtins**. An unknown ident-call passes through verbatim as a C call, and
|
||||
`arity_check_call` returns OK when `builtin_arity < 0`. So a compiled `.el` that
|
||||
calls `engram_geo_distance_json(A, B)` folds to `engram_geo_distance_json(A, B)`,
|
||||
which links straight into `el_runtime.c`. No self-host fold of `elc-cli.el` (the
|
||||
memory-heavy, drift-prone step) was required — that step is explicitly avoided.
|
||||
|
||||
---
|
||||
|
||||
## Files changed (all in the engram worktree, commit on `engram-tiered-storage`)
|
||||
|
||||
1. **`lang/el-compiler/src/codegen.el`** (+12) — source-of-truth `builtin_arity`
|
||||
table: registered the six operators under both the bare heavy-runtime names
|
||||
(`engram_geo_*_json`) and the `__`-prefixed seed names, mirroring the existing
|
||||
`engram_activate_json` / `__engram_activate_json` pair. Effect: a future
|
||||
legitimately-rebuilt elc validates arg counts. No effect on the shipped binary.
|
||||
|
||||
2. **`lang/elc.c`** (+36) — the folded-C mirror of the same table, kept in sync
|
||||
with `codegen.el`. (`lang/elc.c` is a stale/partial fold that does not compile
|
||||
standalone — it is missing the `stdout_to_file`/`stdout_restore` definitions —
|
||||
so this edit is source-consistency only; it is not the live compiler.)
|
||||
|
||||
3. **`lang/runtime/engram.el`** (+31) — six module wrappers
|
||||
`engram_geo_*_json(...) -> String { return __engram_geo_*_json(...) }`,
|
||||
mirroring the existing `engram_activate_json` wrapper. Surfaces the operators
|
||||
as named El functions for the seed-world / future rebuilt-elc path.
|
||||
|
||||
4. **`lang/runtime/engram_geometry.c`** (+2/-1) — style nit at ~1419: the
|
||||
`centroid_unit` normalization `if/else` had misleading indentation
|
||||
(single-statement `for` body then `else`). Braced the `if` arm. Behavior
|
||||
identical; not a numerical change.
|
||||
|
||||
---
|
||||
|
||||
## Verification performed (real, on-machine)
|
||||
|
||||
- **Compiled-EL demo** (`scratchpad/geo_ops_demo.el`, top-level El program):
|
||||
folded with the shipped elc **inside a hard RSS cap** (`capfold.sh` monitor,
|
||||
peak RSS ~4MB), cc-linked against `el_runtime.c + engram_store.c +
|
||||
engram_geometry.c + engram_vindex.c`, run against a **COPY** of the store
|
||||
(`demostore/neuron.egm` from `real_copy.egm`, 13,036 nodes, throwaway `HOME`,
|
||||
no server, not `:8742`). Real output on two real neighborhoods
|
||||
A=architecture `{b037825e, e06ba673, 58ddea41}`, B=hebbian `{78b7a96e,
|
||||
4d5cfe63, 7b97ee0e}`:
|
||||
- subtract residual: `variance_explained_by_B=0.447564, residual_scale=0.304879,
|
||||
removed_dims=3, residual_n_axes=8, centroid_diff_mag=0.125119`
|
||||
- subtract setdiff: `n_only=43, removed=72, centroid_diff_mag=0.125119`
|
||||
- distance: `centroid_distance=0.125119, centroid_cosine=0.778572,
|
||||
wasserstein2=0.268298`
|
||||
- internal consistency: `centroid_diff_mag` identical across subtract+distance.
|
||||
- **C unit suite** `test_geo_ops.c`: 20/20 checks pass, ASan+UBSan clean, after
|
||||
the `engram_geometry.c` edit. No regression.
|
||||
|
||||
---
|
||||
|
||||
## How to reverse
|
||||
|
||||
Everything is a single worktree commit on a non-pushed branch.
|
||||
|
||||
- **Full reversal:** `git -C /tmp/engram-tiered-wt revert <this-commit>` (or
|
||||
`git reset --hard 5336cfe` to drop back to the parent tip).
|
||||
- **Per-file reversal:** `git -C /tmp/engram-tiered-wt checkout 5336cfe -- <path>`
|
||||
for any of the four files. Each edit is additive/local:
|
||||
- The arity entries (`codegen.el`, `elc.c`) are inert unless elc is rebuilt.
|
||||
- The `engram.el` wrappers are unused by the heavy engram server (which calls
|
||||
the bare builtins directly) — removing them changes nothing live.
|
||||
- The `engram_geometry.c` brace change is behavior-neutral.
|
||||
- **No runtime/deploy reversal needed:** nothing was deployed. `:8742`, the
|
||||
launch agent, and `~/.neuron/engram` were never modified. No tag, no push.
|
||||
|
||||
---
|
||||
|
||||
## Deferred / open
|
||||
|
||||
- **elc binary rebuild with the arity table baked in** is deferred. The canonical
|
||||
rebuild path (`elc elc-cli.el > elc-new.c`; AGENTS.md) is the self-host fold —
|
||||
the memory-heavy, compiler-revision-drift step. It is unnecessary for
|
||||
callability (shipped elc already passes the calls through) and carries the same
|
||||
drift risk flagged for the M-INTEROCEPTION HTTP routes. Do it only as part of a
|
||||
deliberate, capped compiler-cutover.
|
||||
- **HTTP routes** for the operators (server.el) are not added here — out of scope;
|
||||
the demo proves the compiled-EL call surface, which was the deliverable.
|
||||
Reference in New Issue
Block a user