@hviana/sema 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -5
- package/CITATION.cff +49 -0
- package/HOW_IT_WORKS.md +11 -12
- package/README.md +7 -5
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +19 -21
- package/dist/example/train_base.js +1687 -1952
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +24 -27
- package/dist/src/alu/src/alu.d.ts +169 -194
- package/dist/src/alu/src/alu.js +374 -414
- package/dist/src/alu/src/expr.d.ts +36 -43
- package/dist/src/alu/src/expr.js +252 -278
- package/dist/src/alu/src/index.d.ts +9 -98
- package/dist/src/alu/src/index.js +10 -64
- package/dist/src/alu/src/kernel-arith.d.ts +1 -5
- package/dist/src/alu/src/kernel-arith.js +195 -285
- package/dist/src/alu/src/kernel-bits.d.ts +1 -5
- package/dist/src/alu/src/kernel-bits.js +81 -115
- package/dist/src/alu/src/kernel-logic.js +33 -63
- package/dist/src/alu/src/kernel-nd.js +155 -195
- package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
- package/dist/src/alu/src/kernel-numeric.js +259 -331
- package/dist/src/alu/src/operation.d.ts +112 -132
- package/dist/src/alu/src/operation.js +149 -163
- package/dist/src/alu/src/parser.d.ts +168 -175
- package/dist/src/alu/src/parser.js +382 -421
- package/dist/src/alu/src/resonance.d.ts +16 -25
- package/dist/src/alu/src/resonance.js +49 -56
- package/dist/src/alu/src/text.d.ts +3 -7
- package/dist/src/alu/src/text.js +31 -37
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +150 -160
- package/dist/src/alu/test/alu.test.js +543 -656
- package/dist/src/bytes.d.ts +1 -5
- package/dist/src/bytes.js +33 -40
- package/dist/src/config.d.ts +99 -102
- package/dist/src/config.js +78 -83
- package/dist/src/derive/src/deduction.d.ts +60 -62
- package/dist/src/derive/src/deduction.js +103 -109
- package/dist/src/derive/src/index.d.ts +1 -7
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +57 -61
- package/dist/src/derive/src/rewrite.d.ts +15 -18
- package/dist/src/derive/src/rewrite.js +59 -67
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +175 -179
- package/dist/src/derive/test/derive.test.js +96 -100
- package/dist/src/extension.d.ts +13 -16
- package/dist/src/geometry.d.ts +17 -42
- package/dist/src/geometry.js +235 -266
- package/dist/src/index.d.ts +2 -17
- package/dist/src/index.js +2 -12
- package/dist/src/ingest-cache.d.ts +25 -30
- package/dist/src/ingest-cache.js +108 -127
- package/dist/src/mind/articulation.d.ts +1 -5
- package/dist/src/mind/articulation.js +77 -112
- package/dist/src/mind/attention.d.ts +40 -108
- package/dist/src/mind/attention.js +749 -871
- package/dist/src/mind/canonical.d.ts +4 -19
- package/dist/src/mind/canonical.js +27 -31
- package/dist/src/mind/graph-search.d.ts +201 -225
- package/dist/src/mind/graph-search.js +742 -821
- package/dist/src/mind/index.d.ts +2 -10
- package/dist/src/mind/junction.d.ts +31 -58
- package/dist/src/mind/junction.js +172 -237
- package/dist/src/mind/learning.d.ts +16 -52
- package/dist/src/mind/learning.js +143 -165
- package/dist/src/mind/match.d.ts +20 -69
- package/dist/src/mind/match.js +259 -318
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +9 -13
- package/dist/src/mind/mechanisms/cast.js +363 -456
- package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
- package/dist/src/mind/mechanisms/confluence.js +152 -183
- package/dist/src/mind/mechanisms/cover.d.ts +2 -8
- package/dist/src/mind/mechanisms/cover.js +148 -210
- package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
- package/dist/src/mind/mechanisms/extraction.js +234 -288
- package/dist/src/mind/mechanisms/recall.d.ts +6 -10
- package/dist/src/mind/mechanisms/recall.js +126 -185
- package/dist/src/mind/mind.d.ts +129 -154
- package/dist/src/mind/mind.js +258 -288
- package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
- package/dist/src/mind/pipeline-mechanism.js +161 -172
- package/dist/src/mind/pipeline.d.ts +4 -14
- package/dist/src/mind/pipeline.js +125 -189
- package/dist/src/mind/primitives.d.ts +8 -32
- package/dist/src/mind/primitives.js +99 -117
- package/dist/src/mind/rationale.d.ts +86 -98
- package/dist/src/mind/rationale.js +113 -121
- package/dist/src/mind/reasoning.d.ts +2 -13
- package/dist/src/mind/reasoning.js +129 -166
- package/dist/src/mind/recognition.d.ts +1 -4
- package/dist/src/mind/recognition.js +183 -208
- package/dist/src/mind/resonance.d.ts +5 -22
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +6 -25
- package/dist/src/mind/trace.js +50 -58
- package/dist/src/mind/traverse.d.ts +16 -58
- package/dist/src/mind/traverse.js +304 -357
- package/dist/src/mind/types.d.ts +120 -127
- package/dist/src/mind/types.js +60 -69
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +9 -13
- package/dist/src/sema.js +26 -40
- package/dist/src/store-sqlite.d.ts +169 -166
- package/dist/src/store-sqlite.js +762 -662
- package/dist/src/store.d.ts +586 -630
- package/dist/src/store.js +1423 -1581
- package/dist/src/vec.d.ts +5 -9
- package/dist/src/vec.js +61 -73
- package/example/train_base.ts +13 -10
- package/package.json +1 -1
- package/src/alu/README.md +1 -1
- package/src/alu/src/index.ts +1 -1
- package/src/config.ts +19 -27
- package/src/index.ts +6 -11
- package/src/rabitq-ivf/README.md +56 -0
- package/src/rabitq-ivf/src/database.ts +276 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
- package/src/rabitq-ivf/src/ivf.ts +1330 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
- package/src/store-sqlite.ts +196 -9
- package/src/store.ts +8 -32
- package/test/08-storage.test.mjs +3 -3
- package/test/14-scaling.test.mjs +2 -2
- package/test/35-ivf.test.mjs +263 -0
- package/test/36-bloom.test.mjs +123 -0
- package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
- package/dist/src/rabitq-hnsw/src/database.js +0 -405
- package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
- package/dist/src/rabitq-hnsw/src/heap.js +0 -94
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
- package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
- package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
- package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
- package/dist/src/rabitq-hnsw/src/prng.js +0 -38
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
- package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
- package/dist/src/rabitq-hnsw/src/store.js +0 -916
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
- package/src/rabitq-hnsw/README.md +0 -303
- package/src/rabitq-hnsw/src/database.ts +0 -492
- package/src/rabitq-hnsw/src/heap.ts +0 -90
- package/src/rabitq-hnsw/src/hnsw.ts +0 -514
- package/src/rabitq-hnsw/src/store.ts +0 -994
- package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
- /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
package/dist/src/vec.d.ts
CHANGED
|
@@ -14,21 +14,17 @@ export declare function normalize(v: Vec): Vec;
|
|
|
14
14
|
export declare function cosine(a: Vec, b: Vec): number;
|
|
15
15
|
/** Set vector epsilon thresholds. Called once by Mind at construction. */
|
|
16
16
|
export declare function setVecConfig(cfg: {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
normalizeEpsilon?: number;
|
|
18
|
+
cosineEpsilon?: number;
|
|
19
19
|
}): void;
|
|
20
20
|
export interface Permutation {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
fwd: Uint32Array;
|
|
22
|
+
inv: Uint32Array;
|
|
23
23
|
}
|
|
24
24
|
/** The keyring: one independent permutation per seat.
|
|
25
25
|
* Independent keys do not commute, so an address in a tree is the
|
|
26
26
|
* path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
|
|
27
|
-
export declare function makeKeyring(
|
|
28
|
-
D: number,
|
|
29
|
-
seats: number,
|
|
30
|
-
rand: () => number,
|
|
31
|
-
): Permutation[];
|
|
27
|
+
export declare function makeKeyring(D: number, seats: number, rand: () => number): Permutation[];
|
|
32
28
|
/** Apply permutation: out[i] = v[table[i]]. */
|
|
33
29
|
export declare function permute(v: Vec, table: Uint32Array): Vec;
|
|
34
30
|
/** Permute into existing buffer — zero allocation. */
|
package/dist/src/vec.js
CHANGED
|
@@ -4,49 +4,44 @@
|
|
|
4
4
|
// No weights. No gradients.
|
|
5
5
|
/** Deterministic PRNG (32-bit mixer). */
|
|
6
6
|
export function rng(seed) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
let s = seed >>> 0;
|
|
8
|
+
return () => {
|
|
9
|
+
s = (s + 0x6d2b79f5) >>> 0;
|
|
10
|
+
let t = s;
|
|
11
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
12
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
13
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
15
|
}
|
|
16
16
|
/** Standard normal sample (Box–Muller). */
|
|
17
17
|
function gaussian(rand) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
18
|
+
let u = 0, v = 0;
|
|
19
|
+
while (u === 0)
|
|
20
|
+
u = rand();
|
|
21
|
+
while (v === 0)
|
|
22
|
+
v = rand();
|
|
23
|
+
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
26
24
|
}
|
|
27
25
|
export const zeros = (D) => new Float32Array(D);
|
|
28
26
|
export const copy = (v) => new Float32Array(v);
|
|
29
27
|
/** Random point on the unit sphere. */
|
|
30
28
|
export function randomUnit(D, rand) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return normalize(v);
|
|
29
|
+
const v = zeros(D);
|
|
30
|
+
for (let i = 0; i < D; i++)
|
|
31
|
+
v[i] = gaussian(rand);
|
|
32
|
+
return normalize(v);
|
|
36
33
|
}
|
|
37
34
|
/** target += src * scale (in place). */
|
|
38
35
|
export function addInto(target, src, scale = 1) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return target;
|
|
36
|
+
for (let i = 0; i < target.length; i++)
|
|
37
|
+
target[i] += src[i] * scale;
|
|
38
|
+
return target;
|
|
43
39
|
}
|
|
44
40
|
export function dot(a, b) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return s;
|
|
41
|
+
let s = 0;
|
|
42
|
+
for (let i = 0; i < a.length; i++)
|
|
43
|
+
s += a[i] * b[i];
|
|
44
|
+
return s;
|
|
50
45
|
}
|
|
51
46
|
const norm = (v) => Math.sqrt(dot(v, v));
|
|
52
47
|
// Epsilon thresholds (settable once via setVecConfig).
|
|
@@ -54,68 +49,61 @@ let _normalizeEpsilon = 1e-12;
|
|
|
54
49
|
let _cosineEpsilon = 1e-12;
|
|
55
50
|
/** In-place normalization. */
|
|
56
51
|
export function normalize(v) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
const n = norm(v);
|
|
53
|
+
if (n > _normalizeEpsilon) {
|
|
54
|
+
for (let i = 0; i < v.length; i++)
|
|
55
|
+
v[i] /= n;
|
|
61
56
|
}
|
|
62
|
-
|
|
63
|
-
return v;
|
|
57
|
+
return v;
|
|
64
58
|
}
|
|
65
59
|
/** Resonance: 1 = same, 0 = unrelated. */
|
|
66
60
|
export function cosine(a, b) {
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
const na = norm(a), nb = norm(b);
|
|
62
|
+
return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
|
|
69
63
|
}
|
|
70
64
|
/** Set vector epsilon thresholds. Called once by Mind at construction. */
|
|
71
65
|
export function setVecConfig(cfg) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
66
|
+
if (cfg.normalizeEpsilon !== undefined) {
|
|
67
|
+
_normalizeEpsilon = cfg.normalizeEpsilon;
|
|
68
|
+
}
|
|
69
|
+
if (cfg.cosineEpsilon !== undefined)
|
|
70
|
+
_cosineEpsilon = cfg.cosineEpsilon;
|
|
78
71
|
}
|
|
79
72
|
/** One fixed random permutation (Fisher–Yates). */
|
|
80
73
|
function makePermutation(D, rand) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
return { fwd, inv };
|
|
74
|
+
const fwd = new Uint32Array(D);
|
|
75
|
+
for (let i = 0; i < D; i++)
|
|
76
|
+
fwd[i] = i;
|
|
77
|
+
for (let i = D - 1; i > 0; i--) {
|
|
78
|
+
const j = Math.floor(rand() * (i + 1));
|
|
79
|
+
const t = fwd[i];
|
|
80
|
+
fwd[i] = fwd[j];
|
|
81
|
+
fwd[j] = t;
|
|
82
|
+
}
|
|
83
|
+
const inv = new Uint32Array(D);
|
|
84
|
+
for (let i = 0; i < D; i++)
|
|
85
|
+
inv[fwd[i]] = i;
|
|
86
|
+
return { fwd, inv };
|
|
96
87
|
}
|
|
97
88
|
/** The keyring: one independent permutation per seat.
|
|
98
89
|
* Independent keys do not commute, so an address in a tree is the
|
|
99
90
|
* path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
|
|
100
91
|
export function makeKeyring(D, seats, rand) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return ring;
|
|
92
|
+
const ring = [];
|
|
93
|
+
for (let s = 0; s < seats; s++)
|
|
94
|
+
ring.push(makePermutation(D, rand));
|
|
95
|
+
return ring;
|
|
106
96
|
}
|
|
107
97
|
/** Apply permutation: out[i] = v[table[i]]. */
|
|
108
98
|
export function permute(v, table) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return out;
|
|
99
|
+
const out = new Float32Array(v.length);
|
|
100
|
+
for (let i = 0; i < v.length; i++)
|
|
101
|
+
out[i] = v[table[i]];
|
|
102
|
+
return out;
|
|
114
103
|
}
|
|
115
104
|
/** Permute into existing buffer — zero allocation. */
|
|
116
105
|
export function permuteInto(out, v, table) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return out;
|
|
106
|
+
for (let i = 0; i < v.length; i++)
|
|
107
|
+
out[i] = v[table[i]];
|
|
108
|
+
return out;
|
|
121
109
|
}
|
package/example/train_base.ts
CHANGED
|
@@ -1610,20 +1610,23 @@ async function saveProgress(store: Store, p: SavedProgress): Promise<void> {
|
|
|
1610
1610
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1611
1611
|
|
|
1612
1612
|
async function main(): Promise<void> {
|
|
1613
|
-
// The vector indices'
|
|
1614
|
-
//
|
|
1615
|
-
//
|
|
1616
|
-
//
|
|
1617
|
-
//
|
|
1618
|
-
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
1622
|
-
|
|
1613
|
+
// The vector indices' memory knob (MiB) — each index's SQLite page cache.
|
|
1614
|
+
// The IVF index routes inserts through a RAM-resident pivot table and
|
|
1615
|
+
// appends to chunk blobs, so this cache mostly serves query-time cluster
|
|
1616
|
+
// scans; 256 MiB comfortably covers the probed working set of a trained
|
|
1617
|
+
// store. Override with VECTOR_CACHE_MB (64 is the library default).
|
|
1618
|
+
const VECTOR_CACHE_MB = Math.max(0, Number(env("VECTOR_CACHE_MB", "256")));
|
|
1619
|
+
// Page cache for the MAIN DAG database (node/kid/edge/contain tables).
|
|
1620
|
+
// Training issues millions of content-addressed point probes per session
|
|
1621
|
+
// against a GB-scale file; the library default (64 MiB) is sized for a
|
|
1622
|
+
// small machine — a training box affords more. Override with
|
|
1623
|
+
// SQLITE_CACHE_MB.
|
|
1624
|
+
const SQLITE_CACHE_MB = Math.max(0, Number(env("SQLITE_CACHE_MB", "256")));
|
|
1623
1625
|
const store = new SQliteStore({
|
|
1624
1626
|
path: DB_PATH,
|
|
1625
1627
|
D,
|
|
1626
1628
|
vectorCacheMb: VECTOR_CACHE_MB,
|
|
1629
|
+
sqliteCacheMb: SQLITE_CACHE_MB,
|
|
1627
1630
|
});
|
|
1628
1631
|
|
|
1629
1632
|
// The store IS the model: memories, progress, and metadata all persist in
|
package/package.json
CHANGED
package/src/alu/README.md
CHANGED
|
@@ -15,7 +15,7 @@ search as authoritative axioms (at `STEP` cost, like a learned edge).
|
|
|
15
15
|
|
|
16
16
|
It has no dependency on the rest of the codebase except the pure byte helpers in
|
|
17
17
|
`../bytes.ts`, and is intended to be reused as a self-contained sublibrary in
|
|
18
|
-
the spirit of `derive/` and `rabitq-
|
|
18
|
+
the spirit of `derive/` and `rabitq-ivf/`.
|
|
19
19
|
|
|
20
20
|
## The thesis: one tiny kernel, everything else is a rewrite
|
|
21
21
|
|
package/src/alu/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// alu — the ALU sub-library: a tiny irreducible kernel from which arithmetic,
|
|
2
2
|
// logic, and numerical computation are all DERIVED.
|
|
3
3
|
//
|
|
4
|
-
// Self-contained in the spirit of derive/ and rabitq-
|
|
4
|
+
// Self-contained in the spirit of derive/ and rabitq-ivf/: it imports only the
|
|
5
5
|
// pure byte helpers (../../bytes.js) and nothing else from SEMA. The host
|
|
6
6
|
// reaches meaning (operator synonymy, the symbolic inverse) through the injected
|
|
7
7
|
// AluResonance, pre-resolved into a synchronous snapshot before the search runs.
|
package/src/config.ts
CHANGED
|
@@ -39,18 +39,9 @@ export interface AlphabetConfig {
|
|
|
39
39
|
|
|
40
40
|
export interface StoreConfig {
|
|
41
41
|
minHaloMass: number;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* index inserts that exist so a partial query can resonate a sub-region
|
|
46
|
-
* and climb, never as dedup targets. Deposit roots and halo-bearing
|
|
47
|
-
* targets always build with the full `efConstruction`. A smaller budget
|
|
48
|
-
* here is the one deliberate speed-for-quality trade in ingestion (an
|
|
49
|
-
* interior's layer-0 wiring is built from a narrower candidate beam);
|
|
50
|
-
* the recall suite (partial recall, multi-topic attention, counterfactual
|
|
51
|
-
* anchoring) is the gate for its value. Set equal to `efConstruction`
|
|
52
|
-
* to disable the trade. */
|
|
53
|
-
efConstructionInterior: number;
|
|
42
|
+
/** Query breadth of the IVF vector indices: clusters probed per query =
|
|
43
|
+
* ceil(efSearch / 4). Inserts have no quality knob — the partitioned
|
|
44
|
+
* index routes and appends, so ingestion cost is flat by construction. */
|
|
54
45
|
efSearch: number;
|
|
55
46
|
/** Compact the in-memory vector indices after this many vectors are written.
|
|
56
47
|
* Compaction rebuilds an index from its live codes to reclaim the slots left
|
|
@@ -58,7 +49,7 @@ export interface StoreConfig {
|
|
|
58
49
|
* (not on a flush count that goes quiet during repeat-heavy training) keeps
|
|
59
50
|
* the index dense and query cost bounded. */
|
|
60
51
|
compactEveryNWrites: number;
|
|
61
|
-
/** Over-fetch factor for
|
|
52
|
+
/** Over-fetch factor for vector-index queries (ANN recall cushion). */
|
|
62
53
|
overfetch: number;
|
|
63
54
|
/** Combined buffered-write ceiling before a flush of both vector indices
|
|
64
55
|
* (content + halo). Higher ⇒ fewer, larger flushes into the in-memory
|
|
@@ -91,14 +82,20 @@ export interface StoreConfig {
|
|
|
91
82
|
* round-trips through the quantizer. An eviction or a reopen reads the
|
|
92
83
|
* 2-bit row — the fidelity every cross-session consumer already gets. */
|
|
93
84
|
haloCacheBytes: number;
|
|
94
|
-
/** Size, in MiB, of each `rabitq-
|
|
95
|
-
* (forwarded as its `cacheSizeMb`
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* with the budget at 0. Exposed so a scaling test can set it to 0 and measure
|
|
85
|
+
/** Size, in MiB, of each `rabitq-ivf` `VectorDatabase`'s memory budget
|
|
86
|
+
* (forwarded as its `cacheSizeMb` — its SQLite page cache). A PURE latency
|
|
87
|
+
* optimisation — the index reads chunk blobs from SQLite on demand, so its
|
|
88
|
+
* correctness and its per-operation storage-read count are identical with
|
|
89
|
+
* the budget at 0. Exposed so a scaling test can set it to 0 and measure
|
|
100
90
|
* the honest, cache-independent cost. */
|
|
101
91
|
vectorCacheMb: number;
|
|
92
|
+
/** Size, in MiB, of the MAIN DAG database's SQLite page cache. The node /
|
|
93
|
+
* kid / edge / contain tables serve millions of point probes per training
|
|
94
|
+
* session (content-addressed findLeaf/findBranch, parent probes, contain
|
|
95
|
+
* appends); SQLite's default cache (~2 MiB) thrashes once the DB outgrows
|
|
96
|
+
* it, so every probe pays a file read. A PURE latency knob — correctness
|
|
97
|
+
* and result identical at any value. */
|
|
98
|
+
sqliteCacheMb: number;
|
|
102
99
|
/** Max entries in the skipped-interior LRU set. Interiors that
|
|
103
100
|
* {@link Store.indexSubtree} has already visited (indexed or skipped) are
|
|
104
101
|
* remembered here so subsequent calls prune their subtrees. Session-local
|
|
@@ -151,9 +148,6 @@ export const DEFAULT_CONFIG: MindConfig = {
|
|
|
151
148
|
},
|
|
152
149
|
store: {
|
|
153
150
|
minHaloMass: 1,
|
|
154
|
-
m: 8,
|
|
155
|
-
efConstruction: 64,
|
|
156
|
-
efConstructionInterior: 16,
|
|
157
151
|
efSearch: 64,
|
|
158
152
|
compactEveryNWrites: 50_000,
|
|
159
153
|
overfetch: 4,
|
|
@@ -165,6 +159,7 @@ export const DEFAULT_CONFIG: MindConfig = {
|
|
|
165
159
|
pendingGistBytes: 16_000_000,
|
|
166
160
|
haloCacheBytes: 16_000_000,
|
|
167
161
|
vectorCacheMb: 64,
|
|
162
|
+
sqliteCacheMb: 64,
|
|
168
163
|
coveredIdsMax: 100_000,
|
|
169
164
|
chainCacheBytes: 16_000_000,
|
|
170
165
|
},
|
|
@@ -194,11 +189,6 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
|
|
|
194
189
|
},
|
|
195
190
|
store: {
|
|
196
191
|
minHaloMass: opts.store?.minHaloMass ?? DEFAULT_CONFIG.store.minHaloMass,
|
|
197
|
-
m: opts.store?.m ?? DEFAULT_CONFIG.store.m,
|
|
198
|
-
efConstruction: opts.store?.efConstruction ??
|
|
199
|
-
DEFAULT_CONFIG.store.efConstruction,
|
|
200
|
-
efConstructionInterior: opts.store?.efConstructionInterior ??
|
|
201
|
-
DEFAULT_CONFIG.store.efConstructionInterior,
|
|
202
192
|
efSearch: opts.store?.efSearch ?? DEFAULT_CONFIG.store.efSearch,
|
|
203
193
|
compactEveryNWrites: opts.store?.compactEveryNWrites ??
|
|
204
194
|
DEFAULT_CONFIG.store.compactEveryNWrites,
|
|
@@ -218,6 +208,8 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
|
|
|
218
208
|
DEFAULT_CONFIG.store.haloCacheBytes,
|
|
219
209
|
vectorCacheMb: opts.store?.vectorCacheMb ??
|
|
220
210
|
DEFAULT_CONFIG.store.vectorCacheMb,
|
|
211
|
+
sqliteCacheMb: opts.store?.sqliteCacheMb ??
|
|
212
|
+
DEFAULT_CONFIG.store.sqliteCacheMb,
|
|
221
213
|
coveredIdsMax: opts.store?.coveredIdsMax ??
|
|
222
214
|
DEFAULT_CONFIG.store.coveredIdsMax,
|
|
223
215
|
chainCacheBytes: opts.store?.chainCacheBytes ??
|
package/src/index.ts
CHANGED
|
@@ -13,26 +13,21 @@ export * from "./store-sqlite.js";
|
|
|
13
13
|
export * from "./config.js";
|
|
14
14
|
export * from "./extension.js";
|
|
15
15
|
export * from "./ingest-cache.js";
|
|
16
|
-
// rabitq-
|
|
17
|
-
// `StoreConfig` are internal names that collide with sema's own top-level
|
|
18
|
-
// `store.js` / `config.js` exports, and sema code that needs the rabitq ones
|
|
19
|
-
// imports them from the subpath directly. Re-export the public vector-DB
|
|
20
|
-
// surface under the sema root, omitting the three colliding names.
|
|
16
|
+
// rabitq-ivf: the partitioned (IVF) vector index over 1-bit RaBitQ codes.
|
|
21
17
|
export {
|
|
22
|
-
|
|
23
|
-
HnswIndex,
|
|
18
|
+
IvfIndex,
|
|
24
19
|
Prng,
|
|
25
20
|
RaBitQuantizer,
|
|
26
21
|
VectorDatabase,
|
|
27
|
-
} from "./rabitq-
|
|
22
|
+
} from "./rabitq-ivf/src/index.js";
|
|
28
23
|
export type {
|
|
29
24
|
DatabaseOptions,
|
|
30
25
|
ExternalId,
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
IvfConfig,
|
|
27
|
+
IvfHit,
|
|
33
28
|
QueryContext,
|
|
34
29
|
QueryResult,
|
|
35
30
|
RaBitQOptions,
|
|
36
31
|
StorageStats,
|
|
37
|
-
} from "./rabitq-
|
|
32
|
+
} from "./rabitq-ivf/src/index.js";
|
|
38
33
|
export * from "./derive/src/index.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# rabitq-ivf
|
|
2
|
+
|
|
3
|
+
A small TypeScript library for **approximate nearest-neighbour search** using an
|
|
4
|
+
**adaptive partitioned (IVF) index** over **1-bit RaBitQ codes**, with
|
|
5
|
+
**cosine** distance. The entire index lives in **SQLite** — the durable copy of
|
|
6
|
+
every code is on disk, resident memory is bounded (the pivot table plus a capped
|
|
7
|
+
page cache, never the whole collection), and the index survives process
|
|
8
|
+
restarts.
|
|
9
|
+
|
|
10
|
+
The vectors are stored **only** as 1-bit RaBitQ codes — the original vectors are
|
|
11
|
+
never kept — so the index is both fast _and_ tiny on disk:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
a 1024-d vector:
|
|
15
|
+
Float32 : 1024 × 4 bytes = 4096 bytes
|
|
16
|
+
RaBitQ 1-bit (code) : 1024 × 1 bit = 128 bytes (32× smaller)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The code _is_ the entire per-vector representation. `get` returns the bare code,
|
|
20
|
+
and `insert`, `update`, and `query` all accept either a raw vector or a code —
|
|
21
|
+
detected by length.
|
|
22
|
+
|
|
23
|
+
## The index
|
|
24
|
+
|
|
25
|
+
- **Clusters, not a graph.** The collection is partitioned into clusters, each
|
|
26
|
+
with a binary pivot code and its member codes packed in fixed-size chunk
|
|
27
|
+
blobs.
|
|
28
|
+
- **Insert = route + append.** Find the nearest pivot (one linear Hamming scan
|
|
29
|
+
of the RAM-resident pivot table) and append to that cluster's tail chunk. No
|
|
30
|
+
beam search, no neighbour rewiring — per-insert cost is essentially flat in
|
|
31
|
+
collection size.
|
|
32
|
+
- **Query = probe + scan.** Rank all pivots with the accurate RaBitQ estimator,
|
|
33
|
+
scan the `ceil(efSearch/4)` nearest clusters with the same estimator, keep the
|
|
34
|
+
top k. Per-query storage reads are bounded by nprobe × chunks-per-cluster —
|
|
35
|
+
constant once the collection has split.
|
|
36
|
+
- **Adaptive, deterministic splits.** A cluster reaching 4096 entries is
|
|
37
|
+
median-split on the margin between two farthest-point seeds (two exact halves,
|
|
38
|
+
cascade-proof), and both halves get fresh majority-bit pivots. There is no
|
|
39
|
+
RNG: the index is a pure function of the insertion sequence.
|
|
40
|
+
- **Same durability discipline as the rest of Sema**: WAL, batched caller-owned
|
|
41
|
+
transactions (`upsertMany`), 1 KiB pages, 64 MiB WAL autocheckpoint.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { VectorDatabase } from "./src/index.js";
|
|
47
|
+
|
|
48
|
+
const db = new VectorDatabase({ dbPath: "vectors.db", dim: 1024 });
|
|
49
|
+
db.upsertMany([{ id: 1, vector: v1 }, { id: 2, vector: v2 }]);
|
|
50
|
+
const hits = db.query(q, 10); // [{ id, distance }]
|
|
51
|
+
db.close();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`lastQueryStorageReads` reports the cache-independent storage-read count of the
|
|
55
|
+
most recent query — the honest scalability witness the test suite asserts on
|
|
56
|
+
(`test/35-ivf.test.mjs` at the repo root).
|