@hviana/sema 0.1.4 → 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/train_base.js +13 -10
- package/dist/src/alu/src/index.js +1 -1
- package/dist/src/config.d.ts +16 -19
- package/dist/src/config.js +3 -8
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -6
- 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-hnsw → rabitq-ivf}/src/index.d.ts +2 -5
- 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-hnsw → rabitq-ivf}/src/prng.d.ts +1 -1
- package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/prng.js +1 -1
- package/dist/src/store-sqlite.d.ts +26 -1
- package/dist/src/store-sqlite.js +190 -8
- package/dist/src/store.d.ts +2 -9
- package/dist/src/store.js +6 -23
- package/example/train_base.ts +13 -10
- package/package.json +2 -2
- 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 -200
- package/dist/src/rabitq-hnsw/src/database.js +0 -388
- package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
- package/dist/src/rabitq-hnsw/src/heap.js +0 -89
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
- package/dist/src/rabitq-hnsw/src/hnsw.js +0 -474
- package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
- package/dist/src/rabitq-hnsw/src/store.js +0 -825
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -948
- 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/dist/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.d.ts +0 -0
- /package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.js +0 -0
- /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
export { VectorDatabase } from "./database.js";
|
|
2
2
|
export type { DatabaseOptions, ExternalId, QueryResult, StorageStats, } from "./database.js";
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
3
|
+
export { IvfIndex } from "./ivf.js";
|
|
4
|
+
export type { IvfConfig, IvfHit } from "./ivf.js";
|
|
5
5
|
export { RaBitQuantizer } from "./rabitq.js";
|
|
6
6
|
export type { QueryContext, RaBitQOptions } from "./rabitq.js";
|
|
7
|
-
export { Store } from "./store.js";
|
|
8
|
-
export type { GlobalState, NodeRec, StoreConfig } from "./store.js";
|
|
9
|
-
export { Heap } from "./heap.js";
|
|
10
7
|
export { Prng } from "./prng.js";
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export { VectorDatabase } from "./database.js";
|
|
2
|
-
export {
|
|
2
|
+
export { IvfIndex } from "./ivf.js";
|
|
3
3
|
export { RaBitQuantizer } from "./rabitq.js";
|
|
4
|
-
export { Store } from "./store.js";
|
|
5
|
-
export { Heap } from "./heap.js";
|
|
6
4
|
export { Prng } from "./prng.js";
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
export interface IvfConfig {
|
|
3
|
+
dim: number;
|
|
4
|
+
efSearch: number;
|
|
5
|
+
queryBits: number;
|
|
6
|
+
rotationRounds: number;
|
|
7
|
+
seed: number;
|
|
8
|
+
centroid: Float64Array | null;
|
|
9
|
+
codeWords: number;
|
|
10
|
+
paddedDim: number;
|
|
11
|
+
}
|
|
12
|
+
export interface IvfHit {
|
|
13
|
+
/** external (user) id */
|
|
14
|
+
id: number;
|
|
15
|
+
/** estimated cosine distance (1 - cosine) */
|
|
16
|
+
distance: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class IvfIndex {
|
|
19
|
+
readonly db: DatabaseSync;
|
|
20
|
+
private readonly quantizer;
|
|
21
|
+
private readonly codeBytes;
|
|
22
|
+
private readonly chunkBytes;
|
|
23
|
+
private _efSearch;
|
|
24
|
+
/** Pivot codes, K × codeBytes, contiguous — the insert/query routing scan
|
|
25
|
+
* is one linear pass over this array. */
|
|
26
|
+
private centCodes;
|
|
27
|
+
/** Aligned 32-bit word view of {@link centCodes} — the routing scan's
|
|
28
|
+
* operand (4 bytes per XOR+popcount instead of byte re-composition). */
|
|
29
|
+
private centWords;
|
|
30
|
+
/** Per-cluster entry count (live + dead) — the split trigger. */
|
|
31
|
+
private centEntries;
|
|
32
|
+
/** Per-cluster chunk count (tail seq = count − 1). */
|
|
33
|
+
private centChunks;
|
|
34
|
+
private K;
|
|
35
|
+
private live;
|
|
36
|
+
private totalSlots;
|
|
37
|
+
private nextId;
|
|
38
|
+
/** Storage row reads (chunk blob fetches) — the cache-independent
|
|
39
|
+
* scalability witness, same discipline as the graph index it replaces. */
|
|
40
|
+
reads: number;
|
|
41
|
+
lastQueryDistComps: number;
|
|
42
|
+
lastQueryStorageReads: number;
|
|
43
|
+
private readonly chunkCache;
|
|
44
|
+
private readonly dirtyChunks;
|
|
45
|
+
private readonly dirtyCents;
|
|
46
|
+
private metaDirty;
|
|
47
|
+
private readonly cacheEnabled;
|
|
48
|
+
private readonly chunkCacheMax;
|
|
49
|
+
private sSelChunk;
|
|
50
|
+
private sUpsChunk;
|
|
51
|
+
private sDelChunk;
|
|
52
|
+
private sSelVmap;
|
|
53
|
+
private sInsVmap;
|
|
54
|
+
private sUpdVmap;
|
|
55
|
+
private sDelVmap;
|
|
56
|
+
private sUpsCent;
|
|
57
|
+
private sMeta;
|
|
58
|
+
constructor(dbPath: string, options: {
|
|
59
|
+
dim?: number;
|
|
60
|
+
efSearch?: number;
|
|
61
|
+
queryBits?: number;
|
|
62
|
+
rotationRounds?: number;
|
|
63
|
+
seed?: number;
|
|
64
|
+
centroid?: ArrayLike<number>;
|
|
65
|
+
cacheSizeMb?: number;
|
|
66
|
+
});
|
|
67
|
+
readonly dim: number;
|
|
68
|
+
private prepareAll;
|
|
69
|
+
/** Load the pivot table into RAM — K rows, ~(codeBytes + 8) bytes each. */
|
|
70
|
+
private loadCents;
|
|
71
|
+
private growCents;
|
|
72
|
+
get size(): number;
|
|
73
|
+
get physicalSize(): number;
|
|
74
|
+
get clusterCount(): number;
|
|
75
|
+
get bytesPerVector(): number;
|
|
76
|
+
get efSearch(): number;
|
|
77
|
+
set efSearch(v: number);
|
|
78
|
+
resetReads(): void;
|
|
79
|
+
/** Fetch a chunk, serving dirty/cached copies first. `retain` controls
|
|
80
|
+
* whether a clean fetch enters the cache — read-only consumers pass the
|
|
81
|
+
* cacheEnabled flag so the `cacheSizeMb: 0` mode stays honestly uncached;
|
|
82
|
+
* writers always retain (the copy is about to become dirty). */
|
|
83
|
+
private chunkAt;
|
|
84
|
+
private cacheChunk;
|
|
85
|
+
private markDirty;
|
|
86
|
+
/** Write every dirty chunk row; drop the clean cache only when retention
|
|
87
|
+
* is off (no budget) — under a budget the warm set survives the flush.
|
|
88
|
+
* Runs inside the caller's transaction. */
|
|
89
|
+
private flushChunks;
|
|
90
|
+
private flushCents;
|
|
91
|
+
/** Persist all buffered state. MUST be called before the enclosing
|
|
92
|
+
* transaction commits (upsertMany/deleteMany do; single-op paths too). */
|
|
93
|
+
private flushAll;
|
|
94
|
+
private static readonly SUPER_G;
|
|
95
|
+
private static readonly SUPER_TOP;
|
|
96
|
+
/** Majority super-pivot codes, one per group of SUPER_G clusters. */
|
|
97
|
+
private superWords;
|
|
98
|
+
/** Group ids whose super-pivot is stale (member pivot changed). */
|
|
99
|
+
private readonly superStale;
|
|
100
|
+
/** Scratch word view of the code being routed (avoids per-call copies). */
|
|
101
|
+
private routeScratch;
|
|
102
|
+
private markSuperStale;
|
|
103
|
+
/** Recompute stale super-pivots (majority bit over member pivots). */
|
|
104
|
+
private freshenSupers;
|
|
105
|
+
/** Word-popcount Hamming between the routing scratch and a words-row. */
|
|
106
|
+
private hamAt;
|
|
107
|
+
/** Nearest cluster to a code — exact over member pivots of the SUPER_TOP
|
|
108
|
+
* nearest super-groups (or over everything while K is small). Pure RAM,
|
|
109
|
+
* no allocation, no storage reads. */
|
|
110
|
+
private nearestCid;
|
|
111
|
+
/** The nprobe nearest clusters to a QUERY (accurate estimator on pivots),
|
|
112
|
+
* as cids ordered nearest-first. */
|
|
113
|
+
private probeOrder;
|
|
114
|
+
private probeOrderByCode;
|
|
115
|
+
/** Whether an external id is present (live). */
|
|
116
|
+
has(ext: number): boolean;
|
|
117
|
+
/** The stored code for an ext (a copy), or null. */
|
|
118
|
+
codeOf(ext: number): Uint8Array | null;
|
|
119
|
+
/** Insert a code under `ext`. Caller owns the transaction; buffered rows
|
|
120
|
+
* are flushed by {@link commitFlush}. Throws if ext is already live. */
|
|
121
|
+
insert(ext: number, code: Uint8Array): void;
|
|
122
|
+
/** Insert-or-update with ONE vmap probe. The point probe is a real cost
|
|
123
|
+
* at bulk-load rates (statement dispatch + a B-tree descent per row —
|
|
124
|
+
* profiled as the single largest insert-path term), so the presence check
|
|
125
|
+
* and the update lookup share it. */
|
|
126
|
+
upsert(ext: number, code: Uint8Array): void;
|
|
127
|
+
/** The insert core — presence already established by the caller. */
|
|
128
|
+
private insertNew;
|
|
129
|
+
private newCluster;
|
|
130
|
+
private appendToCluster;
|
|
131
|
+
/** Tombstone a live ext. Returns false when absent. */
|
|
132
|
+
remove(ext: number): boolean;
|
|
133
|
+
/** Update the code bound to a live ext. A byte-identical code is a no-op
|
|
134
|
+
* (content-addressed callers re-upsert unchanged vectors wholesale after a
|
|
135
|
+
* restart; each no-op otherwise costs a tombstone + reinsert). */
|
|
136
|
+
update(ext: number, code: Uint8Array): void;
|
|
137
|
+
private updateAt;
|
|
138
|
+
/** Flush buffered chunk/cent/meta rows. Call before COMMIT. */
|
|
139
|
+
commitFlush(): void;
|
|
140
|
+
/** Split a full cluster in two, deterministically. Dead slots are dropped
|
|
141
|
+
* (splits double as incremental compaction). Both halves get fresh
|
|
142
|
+
* majority-bit pivots, so routing sharpens as the corpus grows. */
|
|
143
|
+
private split;
|
|
144
|
+
/** Bit-majority sample size per split side. A pivot is a routing aid, not
|
|
145
|
+
* a stored value: the majority bit of a 512-member deterministic sample
|
|
146
|
+
* agrees with the full-population majority except on near-tied bits,
|
|
147
|
+
* where either choice routes equally well — and counting every member of
|
|
148
|
+
* a 4096-entry cluster was 25% of bulk-load CPU (profiled). */
|
|
149
|
+
private static readonly MAJORITY_SAMPLE;
|
|
150
|
+
/** Majority-bit code of the side's members (the binary centroid), or null
|
|
151
|
+
* when the side is empty. Members are sampled on a deterministic stride
|
|
152
|
+
* when the side exceeds {@link MAJORITY_SAMPLE}. */
|
|
153
|
+
private majorityCode;
|
|
154
|
+
/** Rewrite a cluster's chunks from a member list (one side of a split or a
|
|
155
|
+
* compaction survivor set), updating vmap rows. `dropFrom` deletes any
|
|
156
|
+
* leftover chunk rows beyond the new count. */
|
|
157
|
+
private rebuildCluster;
|
|
158
|
+
/** Multi-row vmap statements, prepared per row count (powers of a fixed
|
|
159
|
+
* batch width) — REPLACE semantics on the ext primary key. */
|
|
160
|
+
private readonly vmapBatchStmts;
|
|
161
|
+
private static readonly VMAP_BATCH;
|
|
162
|
+
private vmapBatchStmt;
|
|
163
|
+
/** Write flat (ext, id, cid, seq, slot) tuples in wide batches. */
|
|
164
|
+
private flushVmapRows;
|
|
165
|
+
/** ef → clusters probed. ef is the familiar "candidate breadth" knob; a
|
|
166
|
+
* probe scans one whole cluster, so nprobe = ef/4 keeps the default
|
|
167
|
+
* (ef 64 → 16 probes) both accurate on trained stores and bounded. */
|
|
168
|
+
private nprobeOf;
|
|
169
|
+
/** k-NN with a full-precision query (accurate RaBitQ estimator). */
|
|
170
|
+
searchKnn(vec: ArrayLike<number>, k: number, ef?: number): IvfHit[];
|
|
171
|
+
/** k-NN with an already-quantized code (sign-bit Hamming distance). */
|
|
172
|
+
searchKnnByCode(code: Uint8Array, k: number, ef?: number): IvfHit[];
|
|
173
|
+
private scanClusters;
|
|
174
|
+
/** Stream live entries whose internal id is > `after`, in id order.
|
|
175
|
+
* Internal ids are monotone at insert and PRESERVED by update/compact, so
|
|
176
|
+
* the largest id a caller has seen is a durable incremental watermark. */
|
|
177
|
+
keysSince(after: number, batch?: number): IterableIterator<{
|
|
178
|
+
ext: number;
|
|
179
|
+
internal: number;
|
|
180
|
+
}>;
|
|
181
|
+
/** Stream every live external id. */
|
|
182
|
+
keys(): IterableIterator<number>;
|
|
183
|
+
/** Drop tombstoned slots by rewriting each cluster that carries any, then
|
|
184
|
+
* VACUUM. Internal ids and cluster assignment are preserved — routing
|
|
185
|
+
* quality is untouched, only dead space is reclaimed. */
|
|
186
|
+
compact(): void;
|
|
187
|
+
/** Heat SQLite's page cache with sequential scans of the chunk and vmap
|
|
188
|
+
* tables — a cold session otherwise warms it through random point reads.
|
|
189
|
+
* Purely a latency optimisation; returns rows touched. */
|
|
190
|
+
warmCache(): number;
|
|
191
|
+
begin(): void;
|
|
192
|
+
commit(): void;
|
|
193
|
+
rollback(): void;
|
|
194
|
+
/** Encode a raw vector to its 1-bit code bytes. */
|
|
195
|
+
encodeToBytes(vec: ArrayLike<number>): Uint8Array;
|
|
196
|
+
codeToBytes(code: ArrayLike<number>): Uint8Array;
|
|
197
|
+
bytesToCode(bytes: Uint8Array): Uint32Array;
|
|
198
|
+
get codeWords(): number;
|
|
199
|
+
close(): void;
|
|
200
|
+
}
|