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
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:
@@ -538,19 +538,20 @@ static int strset_add(StrSet* s, const char* key){ /* 1 added, 0 dup, -1 err *
|
||||
}
|
||||
static void strset_free(StrSet* s){ for(size_t i=0;i<s->cap;i++) free(s->k[i]); free(s->k); }
|
||||
|
||||
int vindex_build_from_store(VIndex* ix, const char* store_path,
|
||||
char*** ids_out, int* n_out){
|
||||
if (!ix || !store_path) return -1;
|
||||
int vindex_harvest_from_store(const char* store_path, int dim,
|
||||
float** vecs_out, char*** ids_out, int* n_out){
|
||||
if (!store_path || dim <= 0 || !vecs_out) return -1;
|
||||
int fd = open(store_path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0){ close(fd); return -1; }
|
||||
uint64_t npages = (uint64_t)st.st_size / VS_PAGE_SIZE;
|
||||
|
||||
char** ids = NULL; size_t ids_n = 0, ids_cap = 0;
|
||||
float* vecs = NULL; size_t vn = 0, vcap = 0; /* row-major float[vn*dim] */
|
||||
char** ids = NULL; size_t ids_n = 0, ids_cap = 0;
|
||||
StrSet seen = {0,0,0};
|
||||
int inserted = 0;
|
||||
uint8_t page[VS_PAGE_SIZE];
|
||||
int failed = 0;
|
||||
|
||||
for (uint64_t pg = 2; pg < npages; pg++){ /* pages 0,1 = superblocks */
|
||||
if (vs_pread(fd, pg, page)) continue;
|
||||
@@ -563,28 +564,67 @@ int vindex_build_from_store(VIndex* ix, const char* store_path,
|
||||
if ((size_t)off + VS_REC_HDR > VS_PAGE_SIZE) continue;
|
||||
uint8_t* body=NULL; size_t blen=0;
|
||||
if (vs_read_body(fd, page, off, len, &body, &blen)) continue;
|
||||
char* id=NULL; float* emb=NULL; int dim=0;
|
||||
vs_parse_node(body, blen, &id, &emb, &dim);
|
||||
char* id=NULL; float* emb=NULL; int edim=0;
|
||||
vs_parse_node(body, blen, &id, &emb, &edim);
|
||||
free(body);
|
||||
if (!id || !emb || dim != ix->dim){ free(id); free(emb); continue; }
|
||||
if (!id || !emb || edim != dim){ free(id); free(emb); continue; }
|
||||
int add = strset_add(&seen, id);
|
||||
if (add <= 0){ free(id); free(emb); continue; } /* dup or err */
|
||||
if (vindex_insert(ix, (uint64_t)inserted, emb) != 0){ free(id); free(emb); break; }
|
||||
if (vn == vcap){
|
||||
size_t nc = vcap ? vcap*2 : 1024;
|
||||
float* nv = (float*)realloc(vecs, nc*(size_t)dim*sizeof(float));
|
||||
if (!nv){ free(id); free(emb); failed = 1; goto out; }
|
||||
vecs = nv; vcap = nc;
|
||||
}
|
||||
memcpy(vecs + vn*(size_t)dim, emb, (size_t)dim*sizeof(float));
|
||||
free(emb);
|
||||
if (ids_n == ids_cap){
|
||||
size_t nc = ids_cap ? ids_cap*2 : 256;
|
||||
size_t nc = ids_cap ? ids_cap*2 : 1024;
|
||||
char** ni = (char**)realloc(ids, nc*sizeof(char*));
|
||||
if (!ni){ free(id); break; }
|
||||
if (!ni){ free(id); failed = 1; goto out; }
|
||||
ids = ni; ids_cap = nc;
|
||||
}
|
||||
ids[ids_n++] = id; /* transfers ownership */
|
||||
inserted++;
|
||||
vn++;
|
||||
}
|
||||
}
|
||||
out:
|
||||
close(fd);
|
||||
strset_free(&seen);
|
||||
if (ids_out){ *ids_out = ids; if (n_out) *n_out = (int)ids_n; }
|
||||
else { for (size_t i=0;i<ids_n;i++) free(ids[i]); free(ids); if (n_out) *n_out=(int)ids_n; }
|
||||
if (failed){
|
||||
free(vecs);
|
||||
for (size_t i=0;i<ids_n;i++) free(ids[i]);
|
||||
free(ids);
|
||||
return -1;
|
||||
}
|
||||
*vecs_out = vecs;
|
||||
if (n_out) *n_out = (int)vn;
|
||||
if (ids_out){ *ids_out = ids; }
|
||||
else { for (size_t i=0;i<ids_n;i++) free(ids[i]); free(ids); }
|
||||
return (int)vn;
|
||||
}
|
||||
|
||||
int vindex_build_from_store(VIndex* ix, const char* store_path,
|
||||
char*** ids_out, int* n_out){
|
||||
if (!ix || !store_path) return -1;
|
||||
float* vecs = NULL; char** ids = NULL; int n = 0;
|
||||
int h = vindex_harvest_from_store(store_path, ix->dim, &vecs, &ids, &n);
|
||||
if (h < 0) return -1;
|
||||
int inserted = 0;
|
||||
for (int i = 0; i < n; i++){
|
||||
if (vindex_insert(ix, (uint64_t)inserted, vecs + (size_t)i*ix->dim) != 0) break;
|
||||
inserted++;
|
||||
}
|
||||
free(vecs);
|
||||
if (ids_out){
|
||||
*ids_out = ids; if (n_out) *n_out = inserted;
|
||||
/* free any ids beyond what we inserted (insert failure tail) */
|
||||
for (int i = inserted; i < n; i++) free(ids[i]);
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) free(ids[i]);
|
||||
free(ids);
|
||||
if (n_out) *n_out = inserted;
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user