engram: fix lazy-embed index gap (#20) and make activate's cosine scan lazy; extract vindex harvest primitive with a bench/oracle harness
El SDK CI - dev / build-and-test (pull_request) Successful in 6m42s

Adds an O(1) "seen" bitmap so lazily-embedded older nodes get picked up
incrementally instead of only on a full rebuild (embed-gap #20).

Replaces engram_activate's O(N*D) cosine prescan with a lazy-memoized
cosine cache (eg_cosq_at), proven bit-identical to the old path.

Extracts a clean vindex_harvest_from_store primitive (read-only vector
harvest, careful malloc/ownership/error-path handling) reused by both
index-build and the new vindex_bench.c — a read-only proof harness
comparing brute-force vs HNSW recall/latency on both the real store and
synthetic data.

.nsbx-env intentionally excluded — local sandbox config (ports, paths,
dev-only placeholder key), not checked in.
This commit is contained in:
bigmerge
2026-08-15 14:26:16 -05:00
parent 6621a4dbc5
commit 08cbcef5d9
5 changed files with 426 additions and 43 deletions
+91 -29
View File
@@ -9352,6 +9352,21 @@ static double engram_goal_bias(const EngramNode* n, const char* query) {
static VIndex* _eg_vindex = NULL;
static int32_t _eg_vindex_dim = 0;
static int64_t _eg_vindex_built_nc = 0; /* g->node_count at last (re)build */
static uint8_t* _eg_vindex_seen = NULL;/* per-ordinal: 1 iff inserted into _eg_vindex */
static int64_t _eg_vindex_seen_cap = 0;
/* Grow the per-ordinal "indexed" bitmap to hold at least `need` entries, zeroing
* the new tail. Returns 0 on success, -1 on OOM (caller keeps the old map). */
static int eg_vindex_seen_ensure(int64_t need) {
if (need <= _eg_vindex_seen_cap) return 0;
int64_t nc = _eg_vindex_seen_cap ? _eg_vindex_seen_cap : 1024;
while (nc < need) nc *= 2;
uint8_t* ns = (uint8_t*)realloc(_eg_vindex_seen, (size_t)nc);
if (!ns) return -1;
memset(ns + _eg_vindex_seen_cap, 0, (size_t)(nc - _eg_vindex_seen_cap));
_eg_vindex_seen = ns; _eg_vindex_seen_cap = nc;
return 0;
}
static VIndex* eg_vindex_sync(EngramStore* g, int32_t dim) {
if (!g || dim <= 0) return _eg_vindex;
@@ -9360,22 +9375,32 @@ static VIndex* eg_vindex_sync(EngramStore* g, int32_t dim) {
if (_eg_vindex && (_eg_vindex_dim != dim || g->node_count < _eg_vindex_built_nc)) {
vindex_free(_eg_vindex);
_eg_vindex = NULL; _eg_vindex_dim = 0; _eg_vindex_built_nc = 0;
free(_eg_vindex_seen); _eg_vindex_seen = NULL; _eg_vindex_seen_cap = 0;
}
if (!_eg_vindex) {
VIndex* idx = vindex_create((int)dim, 0, 0);
if (!idx) return NULL;
if (eg_vindex_seen_ensure(g->node_count)) { vindex_free(idx); return NULL; }
for (int64_t i = 0; i < g->node_count; i++) {
EngramNode* n = &g->nodes[i];
if (n->emb && n->emb_dim == dim)
(void)vindex_insert(idx, (uint64_t)i, n->emb);
if (n->emb && n->emb_dim == dim && vindex_insert(idx, (uint64_t)i, n->emb) == 0)
_eg_vindex_seen[i] = 1;
}
_eg_vindex = idx; _eg_vindex_dim = dim; _eg_vindex_built_nc = g->node_count;
} else if (g->node_count > _eg_vindex_built_nc) {
/* Incremental: index newly-appended nodes that already carry an emb. */
for (int64_t i = _eg_vindex_built_nc; i < g->node_count; i++) {
} else if (eg_vindex_seen_ensure(g->node_count) == 0) {
/* Incremental (embed-gap #20 fix): index EVERY node that now carries an emb
* but is not yet in the index whether newly APPENDED or lazily EMBEDDED on
* an OLDER ordinal by the backfill loop. The previous tail-only scan left a
* lazily-embedded older node invisible until the next full rebuild; that node
* was silently absent from route_nearest / autoconnect (which have NO exact
* top-up, unlike engram_activate) and lowered activation recall. This O(node_count)
* presence check carries no D factor, so it is negligible beside the cosq scan on
* the same path. On seen-map OOM we fall through unchanged (index simply not grown). */
for (int64_t i = 0; i < g->node_count; i++) {
if (_eg_vindex_seen[i]) continue;
EngramNode* n = &g->nodes[i];
if (n->emb && n->emb_dim == dim)
(void)vindex_insert(_eg_vindex, (uint64_t)i, n->emb);
if (n->emb && n->emb_dim == dim && vindex_insert(_eg_vindex, (uint64_t)i, n->emb) == 0)
_eg_vindex_seen[i] = 1;
}
_eg_vindex_built_nc = g->node_count;
}
@@ -9438,6 +9463,25 @@ static int eg_geo_prime_max(void){ /* cap primed members added to the front
if (v < 0) v = 0; if (v > 256) v = 256; return v;
}
/* Lazy per-node cosine vs the effective query (M8.1 activate-latency fix,
* 2026-08-14). Computes cos(node_i, query) ON DEMAND and memoizes it, replacing
* the full O(N·D) prescan that dominated live activate latency (~330ms + CPU peg
* + restart thrash). Returns exactly the value the prescan produced for any node
* it is asked about; nodes never asked about are never computed and the prescan
* version never READ them either (Pass-3 skips !reached[i]; the exact top-up only
* runs when the ANN underfills), so engram_activate stays BIT-IDENTICAL while
* touching only ANN candidates + propagation-reached nodes. Degrades to the -2.0
* "no/!=dim embedding" sentinel exactly as the prescan did. Callers guard with
* `if (cosq)`, so cosq/cos_done are non-NULL here. */
static inline double eg_cosq_at(EngramStore* g, double* cosq, unsigned char* cos_done,
const float* qv, int32_t dim, int64_t i) {
if (cos_done[i]) return cosq[i];
EngramNode* n = &g->nodes[i];
cosq[i] = (n->emb && n->emb_dim == dim && qv) ? eg_cosine(n->emb, qv, dim) : -2.0;
cos_done[i] = 1;
return cosq[i];
}
el_val_t engram_activate(el_val_t query, el_val_t depth) {
EngramStore* g = engram_get();
const char* q = EL_CSTR(query);
@@ -9537,15 +9581,26 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
* twice, coherently HippoRAG). cosq stays NULL when the embedder is
* unavailable; every consumer degrades to pure lexical behavior. */
double* cosq = NULL;
unsigned char* cos_done = NULL; /* M8.1: which cosq[i] have been computed (lazy) */
float* cosq_qv = NULL; /* M8.1: OWNED copy of the effective query vector,
* kept alive past the e_eff free (9889) for the
* lazy cosq reads in Pass-2/Pass-3 propagation. */
if (q_emb) {
const float* qv = e_eff ? e_eff : q_emb;
cosq = calloc((size_t)g->node_count, sizeof(double));
if (cosq) {
for (int64_t i = 0; i < g->node_count; i++) {
EngramNode* n = &g->nodes[i];
cosq[i] = (n->emb && n->emb_dim == q_dim)
? eg_cosine(n->emb, qv, q_dim) : -2.0;
}
cosq = calloc((size_t)g->node_count, sizeof(double));
cos_done = calloc((size_t)g->node_count, 1);
if (cosq && cos_done && qv) {
cosq_qv = malloc((size_t)q_dim * sizeof(float));
if (cosq_qv) memcpy(cosq_qv, qv, (size_t)q_dim * sizeof(float));
}
/* M8.1 activate-latency fix: NO full O(N·D) prescan here. cosq is filled
* lazily via eg_cosq_at() only for ANN candidates + propagation-reached
* nodes bit-identical to the prescan (see eg_cosq_at). If any alloc
* failed, degrade to embedder-down behaviour: every `if (cosq)` consumer
* skips and activation falls back to pure lexical, exactly as before. */
if (!cosq || !cos_done || !cosq_qv) {
free(cosq); free(cos_done); free(cosq_qv);
cosq = NULL; cos_done = NULL; cosq_qv = NULL;
}
}
/* NOTE (M8): e_eff is NOT freed here anymore — the effective query vector
@@ -9558,7 +9613,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
int64_t* best_hops = calloc((size_t)g->node_count, sizeof(int64_t));
int* reached = calloc((size_t)g->node_count, sizeof(int));
if (!best_bg || !best_hops || !reached) {
free(best_bg); free(best_hops); free(reached); free(cosq); free(e_eff); return out;
free(best_bg); free(best_hops); free(reached); free(cosq); free(cos_done); free(cosq_qv); free(e_eff); return out;
}
/* ── LAYER 1: broad fan-out (background activation) ─────────────────
@@ -9569,7 +9624,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
SeedEntry* seeds = malloc((size_t)g->node_count * sizeof(SeedEntry));
int64_t seed_count = 0;
if (!seeds) {
free(best_bg); free(best_hops); free(reached); free(cosq); free(e_eff); return out;
free(best_bg); free(best_hops); free(reached); free(cosq); free(cos_done); free(cosq_qv); free(e_eff); return out;
}
/* Tokenize once: a node seeds if it matches ANY query token, and its seed
* activation is scaled by token coverage (fraction of distinct query
@@ -9662,7 +9717,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
if (bi < 0 || bi >= g->node_count) continue; /* stale id guard */
if (reached[bi]) continue; /* lexically seeded */
if (seed_dup && seed_dup[bi]) continue;
double bc = cosq[bi]; /* EXACT cosine — parity gate */
double bc = eg_cosq_at(g, cosq, cos_done, cosq_qv, q_dim, bi); /* EXACT cosine — parity gate (lazy) */
if (!(bc > ENGRAM_EMBED_SEED_MIN)) continue;
EngramNode* n = &g->nodes[bi];
uint64_t key = eg_content_key(n);
@@ -9704,7 +9759,8 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
for (int64_t i = 0; i < g->node_count; i++) {
if (reached[i]) continue;
if (seed_dup && seed_dup[i]) continue;
if (cosq[i] > bc) { bc = cosq[i]; bi = i; }
double ci = eg_cosq_at(g, cosq, cos_done, cosq_qv, q_dim, i);
if (ci > bc) { bc = ci; bi = i; }
}
if (bi < 0) break;
EngramNode* n = &g->nodes[bi];
@@ -9880,7 +9936,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
typedef struct { int64_t idx; int64_t hops; double act; } Frontier;
Frontier* fr = malloc((size_t)(g->node_count * (max_depth + 1)) * sizeof(Frontier) + 16 * sizeof(Frontier));
if (!fr) {
free(best_bg); free(best_hops); free(reached); free(seeds); free(cosq); return out;
free(best_bg); free(best_hops); free(reached); free(seeds); free(cosq); free(cos_done); free(cosq_qv); return out;
}
int64_t fhead = 0, ftail = 0;
int64_t fcap = (int64_t)((size_t)(g->node_count * (max_depth + 1)) + 16);
@@ -9982,9 +10038,12 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
* information, no penalty); cosq == NULL (embedder down) means
* no gating at all same graceful degradation as seeding. */
double qgate = 1.0;
if (cosq && cosq[oi] > -1.5) {
double c = cosq[oi] > 0.0 ? cosq[oi] : 0.0;
qgate = ENGRAM_QGATE_FLOOR + (1.0 - ENGRAM_QGATE_FLOOR) * c;
if (cosq) {
double coi = eg_cosq_at(g, cosq, cos_done, cosq_qv, q_dim, oi);
if (coi > -1.5) {
double c = coi > 0.0 ? coi : 0.0;
qgate = ENGRAM_QGATE_FLOOR + (1.0 - ENGRAM_QGATE_FLOOR) * c;
}
}
/* ── ACT-R fan effect (2026-08-11 self-review) ──
* Symmetric degree normalization over the (source, target) pair.
@@ -10036,7 +10095,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
double* inhibition = calloc((size_t)g->node_count, sizeof(double));
if (!inhibition) {
free(best_bg); free(best_hops); free(reached); free(seeds); free(fr);
free(cosq); return out;
free(cosq); free(cos_done); free(cosq_qv); return out;
}
for (int64_t ei = 0; ei < g->edge_count; ei++) {
EngramEdge* e = &g->edges[ei];
@@ -10061,7 +10120,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
double* wm_weights = calloc((size_t)g->node_count, sizeof(double));
if (!wm_weights) {
free(best_bg); free(best_hops); free(reached); free(seeds);
free(fr); free(inhibition); free(cosq); return out;
free(fr); free(inhibition); free(cosq); free(cos_done); free(cosq_qv); return out;
}
/* Per-call breakthrough budget (2026-08-02) — see ENGRAM_BREAKTHROUGH_BUDGET. */
int64_t bt_budget = ENGRAM_BREAKTHROUGH_BUDGET;
@@ -10135,9 +10194,12 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
* dragged in), not the semantic one (what the query actually
* means); relevance to the current query is not stale merely
* because the node was in WM a moment ago. */
if (cosq && cosq[i] > ENGRAM_EMBED_S0) {
raw_wm += ENGRAM_EMBED_WM_WEIGHT
* (cosq[i] - ENGRAM_EMBED_S0) / (1.0 - ENGRAM_EMBED_S0);
if (cosq) {
double ci = eg_cosq_at(g, cosq, cos_done, cosq_qv, q_dim, i);
if (ci > ENGRAM_EMBED_S0) {
raw_wm += ENGRAM_EMBED_WM_WEIGHT
* (ci - ENGRAM_EMBED_S0) / (1.0 - ENGRAM_EMBED_S0);
}
}
/* Threshold gate: must exceed per-type threshold to enter working
* memory. Type threshold replaces the old flat 0.2 filter. */
@@ -10817,7 +10879,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
int64_t rcount = 0;
if (!results) {
free(best_bg); free(best_hops); free(reached); free(seeds);
free(fr); free(inhibition); free(wm_weights); free(cosq);
free(fr); free(inhibition); free(wm_weights); free(cosq); free(cos_done); free(cosq_qv);
free(was_wm); return out;
}
for (int64_t i = 0; i < g->node_count; i++) {
@@ -10885,7 +10947,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
}
free(best_bg); free(best_hops); free(reached);
free(seeds); free(fr); free(inhibition); free(wm_weights); free(results);
free(cosq);
free(cosq); free(cos_done); free(cosq_qv);
return out;
}