@delali/narsil 0.2.2-next.29 → 0.2.3-next.32
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/README.md +12 -9
- package/dist/adapters/filesystem.d.ts +1 -1
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/adapters/memory.d.ts +1 -1
- package/dist/{adapters-CVqI5NBt.d.ts → adapters-79OIa4xm.d.ts} +1 -1
- package/dist/brute-force-DbHYCTc3.d.ts +8 -0
- package/dist/chunk-BFIH7OV5.mjs +185 -0
- package/dist/chunk-C6VVLMRX.mjs +63 -0
- package/dist/{chunk-UNJZ7ZDB.mjs → chunk-EMI63EXP.mjs} +33 -2
- package/dist/chunk-MFFAJ2NR.mjs +135 -0
- package/dist/chunk-OBXS7OZM.mjs +376 -0
- package/dist/{chunk-ASI6AOW2.mjs → chunk-RO4EFWE6.mjs} +7833 -3524
- package/dist/{chunk-WLR5BFL3.mjs → chunk-TEAJO7CU.mjs} +3067 -2087
- package/dist/{chunk-S7RJJMHI.mjs → chunk-WDL632CN.mjs} +699 -222
- package/dist/{chunk-BKAI2WXI.mjs → chunk-WTONLDU3.mjs} +5 -0
- package/dist/client-DQHB_swQ.d.ts +793 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.mjs +843 -0
- package/dist/{config-DXypXAfN.d.ts → config-ChZ7lfqc.d.ts} +3 -3
- package/dist/direct-executor-YARCDMJF.mjs +8 -0
- package/dist/distribution/coordinator/etcd.d.ts +3 -3
- package/dist/distribution/coordinator/etcd.mjs +471 -2
- package/dist/distribution/coordinator/in-memory.d.ts +3 -3
- package/dist/distribution/coordinator/in-memory.mjs +286 -1
- package/dist/distribution/transport/grpc.d.ts +45 -0
- package/dist/distribution/transport/grpc.mjs +586 -0
- package/dist/distribution/transport/in-memory.d.ts +118 -0
- package/dist/distribution/transport/in-memory.mjs +214 -0
- package/dist/distribution/transport/tcp.d.ts +27 -0
- package/dist/distribution/transport/tcp.mjs +595 -0
- package/dist/distribution.d.ts +241 -192
- package/dist/distribution.mjs +6620 -4795
- package/dist/embeddings/openai.d.ts +1 -1
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-CO9Wnfie.d.ts → engine-DScGlT2Z.d.ts} +30 -3
- package/dist/index.browser.mjs +10035 -3914
- package/dist/index.d.ts +8 -8
- package/dist/index.mjs +8 -5
- package/dist/invalidation/broadcast-channel.d.ts +1 -1
- package/dist/invalidation/filesystem.d.ts +1 -1
- package/dist/invalidation/filesystem.mjs +3 -1
- package/dist/invalidation/noop.d.ts +1 -1
- package/dist/options-BORJg5XB.d.ts +234 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +4 -2
- package/dist/persistence/durability/checkpoint-worker.mjs +8 -5
- package/dist/react.d.ts +340 -0
- package/dist/react.mjs +687 -0
- package/dist/{schema-CE6S_aAr.d.ts → schema-emTQfEiX.d.ts} +21 -7
- package/dist/{search-DLBtiPdQ.d.ts → search-CQ-9bKxo.d.ts} +226 -14
- package/dist/server.d.ts +42 -356
- package/dist/server.mjs +493 -106
- package/dist/tasks-CnMLpfk-.d.ts +274 -0
- package/dist/types-BQDlIvfS.d.ts +34 -0
- package/dist/{types-BV3nAKTL.d.ts → types-Bfhhnh3G.d.ts} +23 -1
- package/dist/types-D5OwhtsQ.d.ts +97 -0
- package/dist/vector/hnsw-build-worker.d.ts +2 -7
- package/dist/vector/hnsw-build-worker.mjs +2 -1
- package/dist/vector/search-worker.d.ts +373 -0
- package/dist/vector/search-worker.mjs +279 -0
- package/dist/workers/entry.mjs +43 -25
- package/package.json +42 -2
- package/dist/chunk-FKK76OR7.mjs +0 -456
- package/dist/chunk-RQLXC2YS.mjs +0 -269
- package/dist/direct-executor-3W4QRA4E.mjs +0 -5
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { compareCodePoints } from './chunk-BFIH7OV5.mjs';
|
|
2
|
+
|
|
1
3
|
// src/vector/simd-wasm-binary.ts
|
|
2
4
|
var SIMD_DISTANCE_WASM_BASE64 = "AGFzbQEAAAABFQNgA39/fwF9YAJ/fwF9YAN/f38BfwMHBgABAAACAgUDAQABB2sHBm1lbW9yeQIAC2RvdF9wcm9kdWN0AAAJbWFnbml0dWRlAAEac3F1YXJlZF9ldWNsaWRlYW5fZGlzdGFuY2UAAhJldWNsaWRlYW5fZGlzdGFuY2UAAwZkb3RfdTgABAlzcWRpc3RfdTgABQqvBwauAQQBfwF7AX0BfyACQXxxIQb9DAAAAAAAAAAAAAAAAAAAAAAhBEEAIQMCQANAIAMgBk8NASAEIAAgA0ECdGr9AAQAIAEgA0ECdGr9AAQA/eYB/eQBIQQgA0EEaiEDDAALCyAE/R8AIAT9HwGSIAT9HwIgBP0fA5KSIQUCQANAIAMgAk8NASAFIAAgA0ECdGoqAgAgASADQQJ0aioCAJSSIQUgA0EBaiEDDAALCyAFC6kBBAF/AnsBfQF/IAFBfHEhBv0MAAAAAAAAAAAAAAAAAAAAACEDQQAhAgJAA0AgAiAGTw0BIAAgAkECdGr9AAQAIQQgAyAEIAT95gH95AEhAyACQQRqIQIMAAsLIAP9HwAgA/0fAZIgA/0fAiAD/R8DkpIhBQJAA0AgAiABTw0BIAUgACACQQJ0aioCACAAIAJBAnRqKgIAlJIhBSACQQFqIQIMAAsLIAWRC8ABBQF/AnsBfQF/AX0gAkF8cSEH/QwAAAAAAAAAAAAAAAAAAAAAIQRBACEDAkADQCADIAdPDQEgACADQQJ0av0ABAAgASADQQJ0av0ABAD95QEhBSAEIAUgBf3mAf3kASEEIANBBGohAwwACwsgBP0fACAE/R8BkiAE/R8CIAT9HwOSkiEGAkADQCADIAJPDQEgACADQQJ0aioCACABIANBAnRqKgIAkyEIIAYgCCAIlJIhBiADQQFqIQMMAAsLIAYLCwAgACABIAIQApELtAEDAX8DewJ/IAJBcHEhCEEA/REhBEEAIQMCQANAIAMgCE8NASAAIANq/QAEACEFIAEgA2r9AAQAIQYgBCAF/YkBIAb9iQH9ugH9rgEhBCAEIAX9igEgBv2KAf26Af2uASEEIANBEGohAwwACwsgBP0bACAE/RsBaiAE/RsCIAT9GwNqaiEHAkADQCADIAJPDQEgByAAIANqLQAAIAEgA2otAABsaiEHIANBAWohAwwACwsgBwvNAQMBfwV7A38gAkFwcSELQQD9ESEEQQAhAwJAA0AgAyALTw0BIAAgA2r9AAQAIQUgASADav0ABAAhBiAF/YkBIAb9iQH9kQEhByAF/YoBIAb9igH9kQEhCCAEIAcgB/26Af2uASEEIAQgCCAI/boB/a4BIQQgA0EQaiEDDAALCyAE/RsAIAT9GwFqIAT9GwIgBP0bA2pqIQkCQANAIAMgAk8NASAAIANqLQAAIAEgA2otAABrIQogCSAKIApsaiEJIANBAWohAwwACwsgCQs=";
|
|
5
|
+
var SIMD_DISTANCE_SHARED_MEMORY_WASM_BASE64 = "AGFzbQEAAAABFQNgA39/fwF9YAJ/fwF9YAN/f38BfwISAQNlbnYGbWVtb3J5AgMBgIAEAwcGAAEAAAICB2IGC2RvdF9wcm9kdWN0AAAJbWFnbml0dWRlAAEac3F1YXJlZF9ldWNsaWRlYW5fZGlzdGFuY2UAAhJldWNsaWRlYW5fZGlzdGFuY2UAAwZkb3RfdTgABAlzcWRpc3RfdTgABQqvBwauAQQBfwF7AX0BfyACQXxxIQb9DAAAAAAAAAAAAAAAAAAAAAAhBEEAIQMCQANAIAMgBk8NASAEIAAgA0ECdGr9AAQAIAEgA0ECdGr9AAQA/eYB/eQBIQQgA0EEaiEDDAALCyAE/R8AIAT9HwGSIAT9HwIgBP0fA5KSIQUCQANAIAMgAk8NASAFIAAgA0ECdGoqAgAgASADQQJ0aioCAJSSIQUgA0EBaiEDDAALCyAFC6kBBAF/AnsBfQF/IAFBfHEhBv0MAAAAAAAAAAAAAAAAAAAAACEDQQAhAgJAA0AgAiAGTw0BIAAgAkECdGr9AAQAIQQgAyAEIAT95gH95AEhAyACQQRqIQIMAAsLIAP9HwAgA/0fAZIgA/0fAiAD/R8DkpIhBQJAA0AgAiABTw0BIAUgACACQQJ0aioCACAAIAJBAnRqKgIAlJIhBSACQQFqIQIMAAsLIAWRC8ABBQF/AnsBfQF/AX0gAkF8cSEH/QwAAAAAAAAAAAAAAAAAAAAAIQRBACEDAkADQCADIAdPDQEgACADQQJ0av0ABAAgASADQQJ0av0ABAD95QEhBSAEIAUgBf3mAf3kASEEIANBBGohAwwACwsgBP0fACAE/R8BkiAE/R8CIAT9HwOSkiEGAkADQCADIAJPDQEgACADQQJ0aioCACABIANBAnRqKgIAkyEIIAYgCCAIlJIhBiADQQFqIQMMAAsLIAYLCwAgACABIAIQApELtAEDAX8DewJ/IAJBcHEhCEEA/REhBEEAIQMCQANAIAMgCE8NASAAIANq/QAEACEFIAEgA2r9AAQAIQYgBCAF/YkBIAb9iQH9ugH9rgEhBCAEIAX9igEgBv2KAf26Af2uASEEIANBEGohAwwACwsgBP0bACAE/RsBaiAE/RsCIAT9GwNqaiEHAkADQCADIAJPDQEgByAAIANqLQAAIAEgA2otAABsaiEHIANBAWohAwwACwsgBwvNAQMBfwV7A38gAkFwcSELQQD9ESEEQQAhAwJAA0AgAyALTw0BIAAgA2r9AAQAIQUgASADav0ABAAhBiAF/YkBIAb9iQH9kQEhByAF/YoBIAb9igH9kQEhCCAEIAcgB/26Af2uASEEIAQgCCAI/boB/a4BIQQgA0EQaiEDDAALCyAE/RsAIAT9GwFqIAT9GwIgBP0bA2pqIQkCQANAIAMgAk8NASAAIANqLQAAIAEgA2otAABrIQogCSAKIApsaiEJIANBAWohAwwACwsgCQs=";
|
|
3
6
|
|
|
4
7
|
// src/vector/simd.ts
|
|
5
8
|
var wasmExports = null;
|
|
@@ -97,7 +100,7 @@ function createArenaSimd() {
|
|
|
97
100
|
if (module === null) return null;
|
|
98
101
|
const instance = new WebAssembly.Instance(module);
|
|
99
102
|
const exports$1 = instance.exports;
|
|
100
|
-
if (typeof exports$1.dot_u8 !== "function" || typeof exports$1.sqdist_u8 !== "function" || !(exports$1.memory instanceof WebAssembly.Memory)) {
|
|
103
|
+
if (typeof exports$1.dot_u8 !== "function" || typeof exports$1.sqdist_u8 !== "function" || typeof exports$1.dot_product !== "function" || typeof exports$1.magnitude !== "function" || typeof exports$1.squared_euclidean_distance !== "function" || !(exports$1.memory instanceof WebAssembly.Memory)) {
|
|
101
104
|
return null;
|
|
102
105
|
}
|
|
103
106
|
return exports$1;
|
|
@@ -105,6 +108,88 @@ function createArenaSimd() {
|
|
|
105
108
|
return null;
|
|
106
109
|
}
|
|
107
110
|
}
|
|
111
|
+
var sharedMemoryModule;
|
|
112
|
+
function getSharedMemoryModule() {
|
|
113
|
+
if (sharedMemoryModule !== void 0) return sharedMemoryModule;
|
|
114
|
+
try {
|
|
115
|
+
const bytes = decodeBase64(SIMD_DISTANCE_SHARED_MEMORY_WASM_BASE64);
|
|
116
|
+
const wasmBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
117
|
+
if (typeof WebAssembly === "undefined" || typeof WebAssembly.validate !== "function" || !WebAssembly.validate(wasmBuffer)) {
|
|
118
|
+
sharedMemoryModule = null;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
sharedMemoryModule = new WebAssembly.Module(wasmBuffer);
|
|
122
|
+
} catch {
|
|
123
|
+
sharedMemoryModule = null;
|
|
124
|
+
}
|
|
125
|
+
return sharedMemoryModule;
|
|
126
|
+
}
|
|
127
|
+
function createSharedArenaSimd(memory) {
|
|
128
|
+
try {
|
|
129
|
+
const module = getSharedMemoryModule();
|
|
130
|
+
if (module === null) return null;
|
|
131
|
+
const instance = new WebAssembly.Instance(module, { env: { memory } });
|
|
132
|
+
const exports$1 = instance.exports;
|
|
133
|
+
if (typeof exports$1.dot_u8 !== "function" || typeof exports$1.sqdist_u8 !== "function" || typeof exports$1.dot_product !== "function" || typeof exports$1.magnitude !== "function" || typeof exports$1.squared_euclidean_distance !== "function") {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
memory,
|
|
138
|
+
dot_product: exports$1.dot_product,
|
|
139
|
+
magnitude: exports$1.magnitude,
|
|
140
|
+
squared_euclidean_distance: exports$1.squared_euclidean_distance,
|
|
141
|
+
dot_u8: exports$1.dot_u8,
|
|
142
|
+
sqdist_u8: exports$1.sqdist_u8
|
|
143
|
+
};
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function arenaFloat32Distance(simd, byteA, byteB, dimension, metric, magnitudeA, magnitudeB) {
|
|
149
|
+
if (metric === "euclidean") {
|
|
150
|
+
return Math.sqrt(simd.squared_euclidean_distance(byteA, byteB, dimension));
|
|
151
|
+
}
|
|
152
|
+
const dot = simd.dot_product(byteA, byteB, dimension);
|
|
153
|
+
if (metric === "dotProduct") return -dot;
|
|
154
|
+
if (magnitudeA === 0 || magnitudeB === 0) return 1;
|
|
155
|
+
return 1 - dot / (magnitudeA * magnitudeB);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/vector/ordinal-filter.ts
|
|
159
|
+
function createOrdinalFilter(slots) {
|
|
160
|
+
return { bits: new Uint8Array(slots + 7 >>> 3), count: 0 };
|
|
161
|
+
}
|
|
162
|
+
function addToOrdinalFilter(filter, ordinal) {
|
|
163
|
+
const index = ordinal >>> 3;
|
|
164
|
+
if (index >= filter.bits.length) return;
|
|
165
|
+
const bit = 1 << (ordinal & 7);
|
|
166
|
+
if ((filter.bits[index] & bit) !== 0) return;
|
|
167
|
+
filter.bits[index] |= bit;
|
|
168
|
+
filter.count += 1;
|
|
169
|
+
}
|
|
170
|
+
function ordinalFilterHas(filter, ordinal) {
|
|
171
|
+
const index = ordinal >>> 3;
|
|
172
|
+
if (index >= filter.bits.length) return false;
|
|
173
|
+
return (filter.bits[index] & 1 << (ordinal & 7)) !== 0;
|
|
174
|
+
}
|
|
175
|
+
function removeFromOrdinalFilter(filter, ordinal) {
|
|
176
|
+
const index = ordinal >>> 3;
|
|
177
|
+
if (index >= filter.bits.length) return;
|
|
178
|
+
const bit = 1 << (ordinal & 7);
|
|
179
|
+
if ((filter.bits[index] & bit) === 0) return;
|
|
180
|
+
filter.bits[index] &= ~bit;
|
|
181
|
+
filter.count -= 1;
|
|
182
|
+
}
|
|
183
|
+
function* ordinalFilterValues(filter) {
|
|
184
|
+
const bits = filter.bits;
|
|
185
|
+
for (let index = 0; index < bits.length; index++) {
|
|
186
|
+
const byte = bits[index];
|
|
187
|
+
if (byte === 0) continue;
|
|
188
|
+
for (let offset = 0; offset < 8; offset++) {
|
|
189
|
+
if ((byte & 1 << offset) !== 0) yield index << 3 | offset;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
108
193
|
|
|
109
194
|
// src/vector/similarity.ts
|
|
110
195
|
function magnitude(v) {
|
|
@@ -140,22 +225,30 @@ function euclideanDistance(a, b) {
|
|
|
140
225
|
return Math.sqrt(sum);
|
|
141
226
|
}
|
|
142
227
|
|
|
143
|
-
// src/vector/vector-store.ts
|
|
228
|
+
// src/vector/vector-store/index.ts
|
|
144
229
|
var INITIAL_CAPACITY = 16;
|
|
145
230
|
var PAGE_BYTES = 65536;
|
|
231
|
+
var UNKNOWN_PARTITION = -1;
|
|
232
|
+
var NO_PARTITION = -2;
|
|
146
233
|
function createVectorStore() {
|
|
147
234
|
const docToOrd = /* @__PURE__ */ new Map();
|
|
148
235
|
const recycledOrds = /* @__PURE__ */ new Map();
|
|
149
236
|
const ordToDoc = [];
|
|
237
|
+
let partitionOf = new Int32Array(0);
|
|
238
|
+
let unknownPartitions = 0;
|
|
150
239
|
let simd = createArenaSimd();
|
|
151
240
|
let dimension = 0;
|
|
152
241
|
let capacity = 0;
|
|
153
242
|
let arena = new Float32Array(0);
|
|
154
243
|
let mags = new Float64Array(0);
|
|
155
244
|
let liveCount = 0;
|
|
245
|
+
let scratchByteLength = 0;
|
|
246
|
+
let scratchFloatLength = 0;
|
|
156
247
|
function initStorage(dim) {
|
|
157
248
|
dimension = dim;
|
|
158
249
|
if (simd) {
|
|
250
|
+
scratchByteLength = Math.max(16, Math.ceil(dim * 4 / 16) * 16);
|
|
251
|
+
scratchFloatLength = scratchByteLength / 4;
|
|
159
252
|
arena = new Float32Array(simd.memory.buffer);
|
|
160
253
|
}
|
|
161
254
|
}
|
|
@@ -167,16 +260,18 @@ function createVectorStore() {
|
|
|
167
260
|
nextMags.set(mags);
|
|
168
261
|
mags = nextMags;
|
|
169
262
|
if (simd) {
|
|
170
|
-
const requiredBytes = newCap * dimension * 4;
|
|
263
|
+
const requiredBytes = scratchByteLength + newCap * dimension * 4;
|
|
171
264
|
const have = simd.memory.buffer.byteLength;
|
|
172
265
|
if (requiredBytes > have) {
|
|
173
266
|
try {
|
|
174
267
|
simd.memory.grow(Math.ceil((requiredBytes - have) / PAGE_BYTES));
|
|
175
268
|
} catch {
|
|
176
269
|
const migrated = new Float32Array(newCap * dimension);
|
|
177
|
-
migrated.set(arena.subarray(
|
|
270
|
+
migrated.set(arena.subarray(scratchFloatLength, scratchFloatLength + capacity * dimension));
|
|
178
271
|
arena = migrated;
|
|
179
272
|
simd = null;
|
|
273
|
+
scratchByteLength = 0;
|
|
274
|
+
scratchFloatLength = 0;
|
|
180
275
|
capacity = newCap;
|
|
181
276
|
return;
|
|
182
277
|
}
|
|
@@ -190,22 +285,48 @@ function createVectorStore() {
|
|
|
190
285
|
capacity = newCap;
|
|
191
286
|
}
|
|
192
287
|
function writeVector(ord, vector) {
|
|
193
|
-
arena.set(vector, ord * dimension);
|
|
288
|
+
arena.set(vector, scratchFloatLength + ord * dimension);
|
|
194
289
|
mags[ord] = magnitude(vector);
|
|
195
290
|
}
|
|
196
291
|
function entryAt(ord) {
|
|
197
|
-
const base = ord * dimension;
|
|
292
|
+
const base = scratchFloatLength + ord * dimension;
|
|
198
293
|
return { vector: arena.subarray(base, base + dimension), magnitude: mags[ord] };
|
|
199
294
|
}
|
|
295
|
+
function ensurePartitionSlots(ord) {
|
|
296
|
+
if (ord < partitionOf.length) return;
|
|
297
|
+
let length = partitionOf.length === 0 ? INITIAL_CAPACITY : partitionOf.length;
|
|
298
|
+
while (length <= ord) length *= 2;
|
|
299
|
+
const grown = new Int32Array(length).fill(UNKNOWN_PARTITION);
|
|
300
|
+
grown.set(partitionOf);
|
|
301
|
+
partitionOf = grown;
|
|
302
|
+
}
|
|
303
|
+
function recordPartition(ord, partitionId, wasLive) {
|
|
304
|
+
ensurePartitionSlots(ord);
|
|
305
|
+
const previous = partitionOf[ord];
|
|
306
|
+
const given = partitionId !== void 0 && partitionId >= 0 ? partitionId : void 0;
|
|
307
|
+
const next = given ?? (wasLive ? previous : UNKNOWN_PARTITION);
|
|
308
|
+
partitionOf[ord] = next;
|
|
309
|
+
if (!wasLive && next === UNKNOWN_PARTITION) {
|
|
310
|
+
unknownPartitions += 1;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (wasLive && previous === UNKNOWN_PARTITION && next !== UNKNOWN_PARTITION) {
|
|
314
|
+
unknownPartitions -= 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
200
317
|
return {
|
|
201
318
|
get size() {
|
|
202
319
|
return liveCount;
|
|
203
320
|
},
|
|
204
|
-
|
|
321
|
+
get slots() {
|
|
322
|
+
return ordToDoc.length;
|
|
323
|
+
},
|
|
324
|
+
insert(docId, vector, partitionId) {
|
|
205
325
|
if (dimension === 0) initStorage(vector.length);
|
|
206
326
|
const existing = docToOrd.get(docId);
|
|
207
327
|
if (existing !== void 0) {
|
|
208
328
|
writeVector(existing, vector);
|
|
329
|
+
recordPartition(existing, partitionId, true);
|
|
209
330
|
return;
|
|
210
331
|
}
|
|
211
332
|
const recycled = recycledOrds.get(docId);
|
|
@@ -215,6 +336,7 @@ function createVectorStore() {
|
|
|
215
336
|
ordToDoc[recycled] = docId;
|
|
216
337
|
ensureCapacity2(recycled + 1);
|
|
217
338
|
writeVector(recycled, vector);
|
|
339
|
+
recordPartition(recycled, partitionId, false);
|
|
218
340
|
liveCount++;
|
|
219
341
|
return;
|
|
220
342
|
}
|
|
@@ -223,14 +345,59 @@ function createVectorStore() {
|
|
|
223
345
|
ordToDoc.push(docId);
|
|
224
346
|
ensureCapacity2(ord + 1);
|
|
225
347
|
writeVector(ord, vector);
|
|
348
|
+
recordPartition(ord, partitionId, false);
|
|
226
349
|
liveCount++;
|
|
227
350
|
},
|
|
351
|
+
setPartition(docId, partitionId) {
|
|
352
|
+
const ord = docToOrd.get(docId);
|
|
353
|
+
if (ord === void 0) return;
|
|
354
|
+
recordPartition(ord, partitionId, true);
|
|
355
|
+
},
|
|
356
|
+
forgetPartition(docId) {
|
|
357
|
+
const ord = docToOrd.get(docId);
|
|
358
|
+
if (ord === void 0 || ord >= partitionOf.length) return;
|
|
359
|
+
if (partitionOf[ord] === UNKNOWN_PARTITION) {
|
|
360
|
+
unknownPartitions -= 1;
|
|
361
|
+
}
|
|
362
|
+
partitionOf[ord] = NO_PARTITION;
|
|
363
|
+
},
|
|
364
|
+
partitionOfOrdinal(ordinal) {
|
|
365
|
+
if (ordinal < 0 || ordinal >= partitionOf.length) return void 0;
|
|
366
|
+
const partitionId = partitionOf[ordinal];
|
|
367
|
+
return partitionId < 0 ? void 0 : partitionId;
|
|
368
|
+
},
|
|
369
|
+
get partitionsKnown() {
|
|
370
|
+
return unknownPartitions === 0;
|
|
371
|
+
},
|
|
372
|
+
partitionFilter(partitionIds) {
|
|
373
|
+
const filter = createOrdinalFilter(ordToDoc.length);
|
|
374
|
+
let highest = -1;
|
|
375
|
+
for (const partitionId of partitionIds) {
|
|
376
|
+
if (partitionId > highest) highest = partitionId;
|
|
377
|
+
}
|
|
378
|
+
if (highest < 0) return filter;
|
|
379
|
+
const wanted = new Uint8Array(highest + 1);
|
|
380
|
+
for (const partitionId of partitionIds) {
|
|
381
|
+
if (partitionId >= 0) wanted[partitionId] = 1;
|
|
382
|
+
}
|
|
383
|
+
const upperBound = Math.min(ordToDoc.length, partitionOf.length);
|
|
384
|
+
for (let ord = 0; ord < upperBound; ord++) {
|
|
385
|
+
if (ordToDoc[ord] === void 0) continue;
|
|
386
|
+
const partitionId = partitionOf[ord];
|
|
387
|
+
if (partitionId < 0 || partitionId > highest || wanted[partitionId] === 0) continue;
|
|
388
|
+
addToOrdinalFilter(filter, ord);
|
|
389
|
+
}
|
|
390
|
+
return filter;
|
|
391
|
+
},
|
|
228
392
|
remove(docId) {
|
|
229
393
|
const ord = docToOrd.get(docId);
|
|
230
394
|
if (ord === void 0) return;
|
|
231
395
|
docToOrd.delete(docId);
|
|
232
396
|
ordToDoc[ord] = void 0;
|
|
233
397
|
recycledOrds.set(docId, ord);
|
|
398
|
+
if (ord < partitionOf.length && partitionOf[ord] === UNKNOWN_PARTITION) {
|
|
399
|
+
unknownPartitions -= 1;
|
|
400
|
+
}
|
|
234
401
|
liveCount--;
|
|
235
402
|
},
|
|
236
403
|
get(docId) {
|
|
@@ -251,6 +418,8 @@ function createVectorStore() {
|
|
|
251
418
|
docToOrd.clear();
|
|
252
419
|
recycledOrds.clear();
|
|
253
420
|
ordToDoc.length = 0;
|
|
421
|
+
partitionOf = new Int32Array(0);
|
|
422
|
+
unknownPartitions = 0;
|
|
254
423
|
mags = new Float64Array(0);
|
|
255
424
|
if (!simd) {
|
|
256
425
|
arena = new Float32Array(0);
|
|
@@ -258,6 +427,8 @@ function createVectorStore() {
|
|
|
258
427
|
dimension = 0;
|
|
259
428
|
capacity = 0;
|
|
260
429
|
liveCount = 0;
|
|
430
|
+
scratchByteLength = 0;
|
|
431
|
+
scratchFloatLength = 0;
|
|
261
432
|
},
|
|
262
433
|
getOrdinal(docId) {
|
|
263
434
|
return docToOrd.get(docId);
|
|
@@ -272,20 +443,12 @@ function createVectorStore() {
|
|
|
272
443
|
distanceByOrdinal(ordA, ordB, metric) {
|
|
273
444
|
if (ordToDoc[ordA] === void 0 || ordToDoc[ordB] === void 0) return Number.POSITIVE_INFINITY;
|
|
274
445
|
if (simd) {
|
|
275
|
-
const byteA = ordA * dimension * 4;
|
|
276
|
-
const byteB = ordB * dimension * 4;
|
|
277
|
-
|
|
278
|
-
return Math.sqrt(simd.squared_euclidean_distance(byteA, byteB, dimension));
|
|
279
|
-
}
|
|
280
|
-
const dot = simd.dot_product(byteA, byteB, dimension);
|
|
281
|
-
if (metric === "dotProduct") return -dot;
|
|
282
|
-
const magA = mags[ordA];
|
|
283
|
-
const magB = mags[ordB];
|
|
284
|
-
if (magA === 0 || magB === 0) return 1;
|
|
285
|
-
return 1 - dot / (magA * magB);
|
|
446
|
+
const byteA = scratchByteLength + ordA * dimension * 4;
|
|
447
|
+
const byteB = scratchByteLength + ordB * dimension * 4;
|
|
448
|
+
return arenaFloat32Distance(simd, byteA, byteB, dimension, metric, mags[ordA], mags[ordB]);
|
|
286
449
|
}
|
|
287
|
-
const baseA = ordA * dimension;
|
|
288
|
-
const baseB = ordB * dimension;
|
|
450
|
+
const baseA = scratchFloatLength + ordA * dimension;
|
|
451
|
+
const baseB = scratchFloatLength + ordB * dimension;
|
|
289
452
|
const a = arena.subarray(baseA, baseA + dimension);
|
|
290
453
|
const b = arena.subarray(baseB, baseB + dimension);
|
|
291
454
|
switch (metric) {
|
|
@@ -297,6 +460,68 @@ function createVectorStore() {
|
|
|
297
460
|
return euclideanDistance(a, b);
|
|
298
461
|
}
|
|
299
462
|
},
|
|
463
|
+
prepareQueryArena(query) {
|
|
464
|
+
if (!simd || dimension === 0 || query.length !== dimension) return null;
|
|
465
|
+
arena.set(query, 0);
|
|
466
|
+
return { magnitude: simd.magnitude(0, dimension) };
|
|
467
|
+
},
|
|
468
|
+
distanceFromArena(prepared, ordinal, metric) {
|
|
469
|
+
if (!simd || ordinal < 0 || ordinal >= ordToDoc.length || ordToDoc[ordinal] === void 0) {
|
|
470
|
+
return Number.POSITIVE_INFINITY;
|
|
471
|
+
}
|
|
472
|
+
const byteOffset = scratchByteLength + ordinal * dimension * 4;
|
|
473
|
+
return arenaFloat32Distance(simd, 0, byteOffset, dimension, metric, prepared.magnitude, mags[ordinal]);
|
|
474
|
+
},
|
|
475
|
+
exportSnapshot() {
|
|
476
|
+
const slots = ordToDoc.length;
|
|
477
|
+
const vectors = new Float32Array(slots * dimension);
|
|
478
|
+
if (slots > 0 && dimension > 0) {
|
|
479
|
+
vectors.set(arena.subarray(scratchFloatLength, scratchFloatLength + slots * dimension));
|
|
480
|
+
}
|
|
481
|
+
const magnitudes = new Float64Array(slots);
|
|
482
|
+
magnitudes.set(mags.subarray(0, Math.min(slots, mags.length)));
|
|
483
|
+
const docIds = new Array(slots);
|
|
484
|
+
for (let ord = 0; ord < slots; ord++) {
|
|
485
|
+
docIds[ord] = ordToDoc[ord] ?? null;
|
|
486
|
+
}
|
|
487
|
+
return { dimension, slots, vectors, magnitudes, docIds };
|
|
488
|
+
},
|
|
489
|
+
copySnapshotInto(vectors, magnitudes) {
|
|
490
|
+
const slots = ordToDoc.length;
|
|
491
|
+
if (slots > 0 && dimension > 0) {
|
|
492
|
+
vectors.set(arena.subarray(scratchFloatLength, scratchFloatLength + slots * dimension));
|
|
493
|
+
}
|
|
494
|
+
magnitudes.set(mags.subarray(0, Math.min(slots, mags.length)));
|
|
495
|
+
},
|
|
496
|
+
restoreSnapshot(snapshot) {
|
|
497
|
+
docToOrd.clear();
|
|
498
|
+
recycledOrds.clear();
|
|
499
|
+
ordToDoc.length = 0;
|
|
500
|
+
partitionOf = new Int32Array(0);
|
|
501
|
+
unknownPartitions = 0;
|
|
502
|
+
liveCount = 0;
|
|
503
|
+
capacity = 0;
|
|
504
|
+
dimension = 0;
|
|
505
|
+
scratchByteLength = 0;
|
|
506
|
+
scratchFloatLength = 0;
|
|
507
|
+
if (!simd) {
|
|
508
|
+
arena = new Float32Array(0);
|
|
509
|
+
}
|
|
510
|
+
if (snapshot.dimension === 0 || snapshot.slots === 0) return;
|
|
511
|
+
initStorage(snapshot.dimension);
|
|
512
|
+
ensureCapacity2(snapshot.slots);
|
|
513
|
+
arena.set(snapshot.vectors.subarray(0, snapshot.slots * snapshot.dimension), scratchFloatLength);
|
|
514
|
+
mags.set(snapshot.magnitudes.subarray(0, Math.min(snapshot.slots, mags.length)));
|
|
515
|
+
ensurePartitionSlots(snapshot.slots);
|
|
516
|
+
for (let ord = 0; ord < snapshot.slots; ord++) {
|
|
517
|
+
const docId = snapshot.docIds[ord];
|
|
518
|
+
ordToDoc.push(docId ?? void 0);
|
|
519
|
+
if (docId === null || docId === void 0) continue;
|
|
520
|
+
docToOrd.set(docId, ord);
|
|
521
|
+
unknownPartitions += 1;
|
|
522
|
+
liveCount++;
|
|
523
|
+
}
|
|
524
|
+
},
|
|
300
525
|
estimateMemory(dimension2) {
|
|
301
526
|
const count = liveCount;
|
|
302
527
|
if (count === 0) return 0;
|
|
@@ -395,12 +620,201 @@ function createHeap(compare) {
|
|
|
395
620
|
};
|
|
396
621
|
}
|
|
397
622
|
|
|
398
|
-
// src/vector/hnsw/
|
|
623
|
+
// src/vector/hnsw/adjacency.ts
|
|
399
624
|
var MAX_LAYER_CAP = 32;
|
|
625
|
+
var MAX_M = 512;
|
|
626
|
+
var ABSENT = -1;
|
|
627
|
+
var INITIAL_UPPER_SLOTS = 64;
|
|
628
|
+
function createAdjacency(m, mMax0) {
|
|
629
|
+
return {
|
|
630
|
+
m,
|
|
631
|
+
mMax0,
|
|
632
|
+
level0Stride: mMax0 + 2,
|
|
633
|
+
upperStride: m + 2,
|
|
634
|
+
capacity: 0,
|
|
635
|
+
slots: 0,
|
|
636
|
+
nodeLevels: new Int8Array(0),
|
|
637
|
+
level0: new Int32Array(0),
|
|
638
|
+
upperBase: new Int32Array(0),
|
|
639
|
+
upper: new Int32Array(0),
|
|
640
|
+
upperUsed: 0,
|
|
641
|
+
freeBlocks: Array.from({ length: MAX_LAYER_CAP + 1 }, () => [])
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function ensureAdjacencyCapacity(adj, needed) {
|
|
645
|
+
if (needed <= adj.capacity) return;
|
|
646
|
+
let newCapacity = adj.capacity === 0 ? 16 : adj.capacity;
|
|
647
|
+
while (newCapacity < needed) newCapacity *= 2;
|
|
648
|
+
const nextLevels = new Int8Array(newCapacity).fill(ABSENT);
|
|
649
|
+
nextLevels.set(adj.nodeLevels);
|
|
650
|
+
adj.nodeLevels = nextLevels;
|
|
651
|
+
const nextLevel0 = new Int32Array(newCapacity * adj.level0Stride);
|
|
652
|
+
nextLevel0.set(adj.level0);
|
|
653
|
+
adj.level0 = nextLevel0;
|
|
654
|
+
const nextUpperBase = new Int32Array(newCapacity).fill(ABSENT);
|
|
655
|
+
nextUpperBase.set(adj.upperBase);
|
|
656
|
+
adj.upperBase = nextUpperBase;
|
|
657
|
+
adj.capacity = newCapacity;
|
|
658
|
+
}
|
|
659
|
+
function allocateUpperBlock(adj, levels) {
|
|
660
|
+
const reusable = adj.freeBlocks[levels].pop();
|
|
661
|
+
const size = levels * adj.upperStride;
|
|
662
|
+
if (reusable !== void 0) {
|
|
663
|
+
adj.upper.fill(0, reusable, reusable + size);
|
|
664
|
+
return reusable;
|
|
665
|
+
}
|
|
666
|
+
if (adj.upperUsed + size > adj.upper.length) {
|
|
667
|
+
const required = adj.upperUsed + size;
|
|
668
|
+
let nextLength = adj.upper.length === 0 ? INITIAL_UPPER_SLOTS * adj.upperStride : adj.upper.length;
|
|
669
|
+
while (nextLength < required) nextLength *= 2;
|
|
670
|
+
const nextUpper = new Int32Array(nextLength);
|
|
671
|
+
nextUpper.set(adj.upper);
|
|
672
|
+
adj.upper = nextUpper;
|
|
673
|
+
}
|
|
674
|
+
const offset = adj.upperUsed;
|
|
675
|
+
adj.upperUsed += size;
|
|
676
|
+
adj.upper.fill(0, offset, offset + size);
|
|
677
|
+
return offset;
|
|
678
|
+
}
|
|
679
|
+
function createNode(adj, ord, maxLayer) {
|
|
680
|
+
ensureAdjacencyCapacity(adj, ord + 1);
|
|
681
|
+
if (ord >= adj.slots) adj.slots = ord + 1;
|
|
682
|
+
const existingLevel = adj.nodeLevels[ord];
|
|
683
|
+
const existingBase = adj.upperBase[ord];
|
|
684
|
+
if (existingLevel >= 1 && existingBase !== ABSENT) {
|
|
685
|
+
adj.freeBlocks[existingLevel].push(existingBase);
|
|
686
|
+
}
|
|
687
|
+
adj.nodeLevels[ord] = maxLayer;
|
|
688
|
+
adj.level0[ord * adj.level0Stride] = 0;
|
|
689
|
+
adj.upperBase[ord] = maxLayer >= 1 ? allocateUpperBlock(adj, maxLayer) : ABSENT;
|
|
690
|
+
}
|
|
691
|
+
function deleteNode(adj, ord) {
|
|
692
|
+
if (ord < 0 || ord >= adj.capacity) return;
|
|
693
|
+
const level = adj.nodeLevels[ord];
|
|
694
|
+
const base = adj.upperBase[ord];
|
|
695
|
+
if (level >= 1 && base !== ABSENT) {
|
|
696
|
+
adj.freeBlocks[level].push(base);
|
|
697
|
+
}
|
|
698
|
+
adj.nodeLevels[ord] = ABSENT;
|
|
699
|
+
adj.upperBase[ord] = ABSENT;
|
|
700
|
+
adj.level0[ord * adj.level0Stride] = 0;
|
|
701
|
+
}
|
|
702
|
+
function resetAdjacency(adj) {
|
|
703
|
+
adj.nodeLevels = new Int8Array(0);
|
|
704
|
+
adj.level0 = new Int32Array(0);
|
|
705
|
+
adj.upperBase = new Int32Array(0);
|
|
706
|
+
adj.upper = new Int32Array(0);
|
|
707
|
+
adj.upperUsed = 0;
|
|
708
|
+
adj.capacity = 0;
|
|
709
|
+
adj.slots = 0;
|
|
710
|
+
for (const list of adj.freeBlocks) list.length = 0;
|
|
711
|
+
}
|
|
712
|
+
function nodeLevel(adj, ord) {
|
|
713
|
+
if (ord < 0 || ord >= adj.capacity) return ABSENT;
|
|
714
|
+
return adj.nodeLevels[ord];
|
|
715
|
+
}
|
|
716
|
+
function hasNode(adj, ord) {
|
|
717
|
+
return nodeLevel(adj, ord) !== ABSENT;
|
|
718
|
+
}
|
|
719
|
+
function layerArray(adj, layer) {
|
|
720
|
+
return layer === 0 ? adj.level0 : adj.upper;
|
|
721
|
+
}
|
|
722
|
+
function layerBase(adj, ord, layer) {
|
|
723
|
+
const level = nodeLevel(adj, ord);
|
|
724
|
+
if (level === ABSENT || layer > level) return ABSENT;
|
|
725
|
+
if (layer === 0) return ord * adj.level0Stride;
|
|
726
|
+
const base = adj.upperBase[ord];
|
|
727
|
+
if (base === ABSENT) return ABSENT;
|
|
728
|
+
return base + (layer - 1) * adj.upperStride;
|
|
729
|
+
}
|
|
730
|
+
function neighborCount(adj, ord, layer) {
|
|
731
|
+
const base = layerBase(adj, ord, layer);
|
|
732
|
+
if (base === ABSENT) return 0;
|
|
733
|
+
return layerArray(adj, layer)[base];
|
|
734
|
+
}
|
|
735
|
+
function collectNeighbors(adj, ord, layer) {
|
|
736
|
+
const base = layerBase(adj, ord, layer);
|
|
737
|
+
if (base === ABSENT) return [];
|
|
738
|
+
const array = layerArray(adj, layer);
|
|
739
|
+
const count = array[base];
|
|
740
|
+
const collected = new Array(count);
|
|
741
|
+
for (let i = 0; i < count; i++) {
|
|
742
|
+
collected[i] = array[base + i + 1];
|
|
743
|
+
}
|
|
744
|
+
return collected;
|
|
745
|
+
}
|
|
746
|
+
function addNeighbor(adj, ord, layer, neighborOrd) {
|
|
747
|
+
const base = layerBase(adj, ord, layer);
|
|
748
|
+
if (base === ABSENT) return;
|
|
749
|
+
const array = layerArray(adj, layer);
|
|
750
|
+
const count = array[base];
|
|
751
|
+
for (let i = 1; i <= count; i++) {
|
|
752
|
+
if (array[base + i] === neighborOrd) return;
|
|
753
|
+
}
|
|
754
|
+
const stride = layer === 0 ? adj.level0Stride : adj.upperStride;
|
|
755
|
+
if (count + 1 > stride - 1) return;
|
|
756
|
+
array[base + count + 1] = neighborOrd;
|
|
757
|
+
array[base] = count + 1;
|
|
758
|
+
}
|
|
759
|
+
function removeNeighbor(adj, ord, layer, neighborOrd) {
|
|
760
|
+
const base = layerBase(adj, ord, layer);
|
|
761
|
+
if (base === ABSENT) return;
|
|
762
|
+
const array = layerArray(adj, layer);
|
|
763
|
+
const count = array[base];
|
|
764
|
+
for (let i = 1; i <= count; i++) {
|
|
765
|
+
if (array[base + i] !== neighborOrd) continue;
|
|
766
|
+
for (let j = i; j < count; j++) {
|
|
767
|
+
array[base + j] = array[base + j + 1];
|
|
768
|
+
}
|
|
769
|
+
array[base] = count - 1;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function replaceNeighbors(adj, ord, layer, neighbors) {
|
|
774
|
+
const base = layerBase(adj, ord, layer);
|
|
775
|
+
if (base === ABSENT) return;
|
|
776
|
+
const array = layerArray(adj, layer);
|
|
777
|
+
const stride = layer === 0 ? adj.level0Stride : adj.upperStride;
|
|
778
|
+
const limit = Math.min(neighbors.length, stride - 1);
|
|
779
|
+
for (let i = 0; i < limit; i++) {
|
|
780
|
+
array[base + i + 1] = neighbors[i];
|
|
781
|
+
}
|
|
782
|
+
array[base] = limit;
|
|
783
|
+
}
|
|
784
|
+
function exportAdjacency(adj) {
|
|
785
|
+
return {
|
|
786
|
+
m: adj.m,
|
|
787
|
+
mMax0: adj.mMax0,
|
|
788
|
+
capacity: adj.capacity,
|
|
789
|
+
slots: adj.slots,
|
|
790
|
+
upperUsed: adj.upperUsed,
|
|
791
|
+
nodeLevels: adj.nodeLevels.slice(),
|
|
792
|
+
level0: adj.level0.slice(),
|
|
793
|
+
upperBase: adj.upperBase.slice(),
|
|
794
|
+
upper: adj.upper.slice(0, adj.upperUsed)
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function importAdjacency(snapshot) {
|
|
798
|
+
const adj = createAdjacency(snapshot.m, snapshot.mMax0);
|
|
799
|
+
adj.capacity = snapshot.capacity;
|
|
800
|
+
adj.slots = snapshot.slots;
|
|
801
|
+
adj.upperUsed = snapshot.upperUsed;
|
|
802
|
+
adj.nodeLevels = snapshot.nodeLevels;
|
|
803
|
+
adj.level0 = snapshot.level0;
|
|
804
|
+
adj.upperBase = snapshot.upperBase;
|
|
805
|
+
adj.upper = snapshot.upper;
|
|
806
|
+
return adj;
|
|
807
|
+
}
|
|
808
|
+
function estimateAdjacencyBytes(adj) {
|
|
809
|
+
return adj.nodeLevels.byteLength + adj.level0.byteLength + adj.upperBase.byteLength + adj.upper.byteLength;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/vector/hnsw/shared.ts
|
|
400
813
|
var COMPACTION_TOMBSTONE_RATIO = 0.1;
|
|
401
814
|
var COMPACTION_ABSOLUTE_THRESHOLD = 1e3;
|
|
402
815
|
var SQ8_OVERSELECTION_FACTOR = 2;
|
|
403
816
|
function ensureCapacity(state, needed) {
|
|
817
|
+
ensureAdjacencyCapacity(state.adjacency, needed);
|
|
404
818
|
if (needed <= state.capacity) return;
|
|
405
819
|
let newCap = state.capacity === 0 ? 16 : state.capacity;
|
|
406
820
|
while (newCap < needed) newCap *= 2;
|
|
@@ -419,6 +833,12 @@ function nextVisitStamp(state) {
|
|
|
419
833
|
}
|
|
420
834
|
return state.visitStamp;
|
|
421
835
|
}
|
|
836
|
+
function nodeExists(state, ord) {
|
|
837
|
+
return hasNode(state.adjacency, ord);
|
|
838
|
+
}
|
|
839
|
+
function nodeMaxLayer(state, ord) {
|
|
840
|
+
return nodeLevel(state.adjacency, ord);
|
|
841
|
+
}
|
|
422
842
|
function toDistance(a, b, magA, magB, metric) {
|
|
423
843
|
switch (metric) {
|
|
424
844
|
case "cosine":
|
|
@@ -470,6 +890,8 @@ function addConnection(connections, ord) {
|
|
|
470
890
|
// src/vector/hnsw/graph-ops.ts
|
|
471
891
|
function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones, distFn) {
|
|
472
892
|
const getDistance = distFn ?? ((ord) => queryDistanceByOrd(state, qVec, qMag, ord, metric));
|
|
893
|
+
const adjacency = state.adjacency;
|
|
894
|
+
const neighbors = layerArray(adjacency, layer);
|
|
473
895
|
const visited = state.visited;
|
|
474
896
|
const stamp = nextVisitStamp(state);
|
|
475
897
|
const candidates = createMinHeap(distanceAsc);
|
|
@@ -478,8 +900,7 @@ function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones,
|
|
|
478
900
|
for (const epOrd of eps) {
|
|
479
901
|
if (visited[epOrd] === stamp) continue;
|
|
480
902
|
visited[epOrd] = stamp;
|
|
481
|
-
|
|
482
|
-
if (!node) continue;
|
|
903
|
+
if (!nodeExists(state, epOrd)) continue;
|
|
483
904
|
if (skipTombstones && state.tombstones[epOrd] === 1) continue;
|
|
484
905
|
const dist = getDistance(epOrd);
|
|
485
906
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
@@ -496,14 +917,15 @@ function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones,
|
|
|
496
917
|
const nearest = candidates.pop();
|
|
497
918
|
if (!nearest) break;
|
|
498
919
|
if (nearest.distance > furthestDist) break;
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
501
|
-
|
|
920
|
+
const base = layerBase(adjacency, nearest.ord, layer);
|
|
921
|
+
if (base === -1) continue;
|
|
922
|
+
const count = neighbors[base];
|
|
923
|
+
for (let i = 1; i <= count; i++) {
|
|
924
|
+
const neighborOrd = neighbors[base + i];
|
|
502
925
|
if (visited[neighborOrd] === stamp) continue;
|
|
503
926
|
visited[neighborOrd] = stamp;
|
|
504
927
|
if (skipTombstones && state.tombstones[neighborOrd] === 1) continue;
|
|
505
|
-
|
|
506
|
-
if (!neighborNode) continue;
|
|
928
|
+
if (!nodeExists(state, neighborOrd)) continue;
|
|
507
929
|
const dist = getDistance(neighborOrd);
|
|
508
930
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
509
931
|
if (dist < furthestDist || results.size < ef) {
|
|
@@ -557,12 +979,16 @@ function selectNeighborsHeuristic(state, targetOrd, candidates, maxConnections,
|
|
|
557
979
|
return selected;
|
|
558
980
|
}
|
|
559
981
|
function pruneConnections(state, ord, layer, metric) {
|
|
560
|
-
const
|
|
561
|
-
|
|
982
|
+
const adjacency = state.adjacency;
|
|
983
|
+
const base = layerBase(adjacency, ord, layer);
|
|
984
|
+
if (base === -1) return;
|
|
562
985
|
const mc = maxConns(state, layer);
|
|
563
|
-
|
|
986
|
+
const neighbors = layerArray(adjacency, layer);
|
|
987
|
+
const count = neighbors[base];
|
|
988
|
+
if (count <= mc) return;
|
|
564
989
|
const conns = [];
|
|
565
|
-
for (
|
|
990
|
+
for (let i = 1; i <= count; i++) {
|
|
991
|
+
const connOrd = neighbors[base + i];
|
|
566
992
|
const dist = nodeDistanceByOrd(state, ord, connOrd, metric);
|
|
567
993
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
568
994
|
conns.push({ ord: connOrd, distance: dist });
|
|
@@ -570,14 +996,131 @@ function pruneConnections(state, ord, layer, metric) {
|
|
|
570
996
|
const pruned = selectNeighborsHeuristic(state, ord, conns, mc, layer, metric);
|
|
571
997
|
const next = [];
|
|
572
998
|
for (const p of pruned) addConnection(next, p.ord);
|
|
573
|
-
|
|
999
|
+
replaceNeighbors(adjacency, ord, layer, next);
|
|
574
1000
|
}
|
|
575
1001
|
|
|
576
|
-
// src/vector/hnsw/
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (
|
|
1002
|
+
// src/vector/hnsw/search.ts
|
|
1003
|
+
var ABSENT_DOCUMENT_RANK = 4294967295;
|
|
1004
|
+
function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSearch, hasDocument) {
|
|
1005
|
+
if (query.length !== state.dimension) {
|
|
1006
|
+
throw new Error(`Query dimension mismatch: expected ${state.dimension}, got ${query.length}`);
|
|
1007
|
+
}
|
|
1008
|
+
const liveSize = state.nodeCount - state.tombstoneCount;
|
|
1009
|
+
if (state.entryPointOrd === -1 || liveSize === 0) {
|
|
1010
|
+
return [];
|
|
1011
|
+
}
|
|
1012
|
+
const useQuantized = state.quantizer?.isCalibrated() === true && state.quantizer.size > 0;
|
|
1013
|
+
const defaultEf = 50;
|
|
1014
|
+
let ef = Math.max(efSearch ?? defaultEf, k);
|
|
1015
|
+
if (filter && filter.count < liveSize) {
|
|
1016
|
+
const selectivity = filter.count / liveSize;
|
|
1017
|
+
ef = Math.max(ef, Math.ceil(k / Math.max(selectivity, 0.01)));
|
|
1018
|
+
ef = Math.min(ef, liveSize);
|
|
1019
|
+
}
|
|
1020
|
+
const store = state.store;
|
|
1021
|
+
const arenaQuery = store.prepareQueryArena(query);
|
|
1022
|
+
const qMag = arenaQuery ? arenaQuery.magnitude : magnitude(query);
|
|
1023
|
+
let quantizedDistFn;
|
|
1024
|
+
if (useQuantized && state.quantizer) {
|
|
1025
|
+
const q = state.quantizer;
|
|
1026
|
+
const metric = searchMetric;
|
|
1027
|
+
const quantizedArenaQuery = q.prepareQueryArena(query);
|
|
1028
|
+
if (quantizedArenaQuery) {
|
|
1029
|
+
quantizedDistFn = (ord) => q.distanceFromArena(quantizedArenaQuery, ord, metric);
|
|
1030
|
+
} else {
|
|
1031
|
+
const prepared = q.prepareQuery(query);
|
|
1032
|
+
if (prepared) {
|
|
1033
|
+
quantizedDistFn = (ord) => q.distanceFromPreparedByOrdinal(prepared, ord, metric);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
let distFn = quantizedDistFn;
|
|
1038
|
+
if (!distFn && arenaQuery) {
|
|
1039
|
+
const metric = searchMetric;
|
|
1040
|
+
distFn = (ord) => store.distanceFromArena(arenaQuery, ord, metric);
|
|
1041
|
+
}
|
|
1042
|
+
let currentEPs = [state.entryPointOrd];
|
|
1043
|
+
for (let layer = state.topLayer; layer >= 1; layer--) {
|
|
1044
|
+
const heap = searchLayer(state, query, qMag, currentEPs, 1, layer, searchMetric, true, distFn);
|
|
1045
|
+
const nearest = nearestFromHeap(heap);
|
|
1046
|
+
if (nearest) {
|
|
1047
|
+
currentEPs = [nearest.ord];
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const candidateHeap = searchLayer(state, query, qMag, currentEPs, ef, 0, searchMetric, true, distFn);
|
|
1051
|
+
const candidateArray = candidateHeap.toSortedArray().reverse();
|
|
1052
|
+
if (useQuantized) {
|
|
1053
|
+
return rerankWithFullPrecision(
|
|
1054
|
+
state,
|
|
1055
|
+
candidateArray,
|
|
1056
|
+
query,
|
|
1057
|
+
qMag,
|
|
1058
|
+
arenaQuery,
|
|
1059
|
+
k,
|
|
1060
|
+
searchMetric,
|
|
1061
|
+
minSimilarity,
|
|
1062
|
+
filter,
|
|
1063
|
+
hasDocument
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
const hits = [];
|
|
1067
|
+
for (const cand of candidateArray) {
|
|
1068
|
+
if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
|
|
1069
|
+
const score = toScore(cand.distance, searchMetric);
|
|
1070
|
+
if (score < minSimilarity) continue;
|
|
1071
|
+
if (!hasDocument(cand.ord)) continue;
|
|
1072
|
+
hits.push({ ord: cand.ord, score });
|
|
1073
|
+
}
|
|
1074
|
+
return hits;
|
|
1075
|
+
}
|
|
1076
|
+
function rerankWithFullPrecision(state, candidates, query, qMag, arenaQuery, k, metric, minSimilarity, filter, hasDocument) {
|
|
1077
|
+
const reranked = [];
|
|
1078
|
+
const rerankLimit = Math.max(k * SQ8_OVERSELECTION_FACTOR, 10);
|
|
1079
|
+
for (const cand of candidates) {
|
|
1080
|
+
if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
|
|
1081
|
+
let fullDistance;
|
|
1082
|
+
if (arenaQuery) {
|
|
1083
|
+
fullDistance = state.store.distanceFromArena(arenaQuery, cand.ord, metric);
|
|
1084
|
+
if (fullDistance === Number.POSITIVE_INFINITY) continue;
|
|
1085
|
+
} else {
|
|
1086
|
+
const entry = entryForOrd(state, cand.ord);
|
|
1087
|
+
if (!entry) continue;
|
|
1088
|
+
fullDistance = toDistance(query, entry.vector, qMag, entry.magnitude, metric);
|
|
1089
|
+
}
|
|
1090
|
+
const score = toScore(fullDistance, metric);
|
|
1091
|
+
if (score < minSimilarity) continue;
|
|
1092
|
+
if (!hasDocument(cand.ord)) continue;
|
|
1093
|
+
reranked.push({ ord: cand.ord, score });
|
|
1094
|
+
if (reranked.length >= rerankLimit) break;
|
|
1095
|
+
}
|
|
1096
|
+
return reranked;
|
|
580
1097
|
}
|
|
1098
|
+
function search(state, query, k, searchMetric, minSimilarity, filter, efSearch) {
|
|
1099
|
+
const hasDocument = (ord) => state.store.docIdForOrdinal(ord) !== void 0;
|
|
1100
|
+
const hits = collectHits(state, query, k, searchMetric, minSimilarity, filter, efSearch, hasDocument);
|
|
1101
|
+
const results = [];
|
|
1102
|
+
for (const hit of hits) {
|
|
1103
|
+
const docId = state.store.docIdForOrdinal(hit.ord);
|
|
1104
|
+
if (docId === void 0) continue;
|
|
1105
|
+
results.push({
|
|
1106
|
+
docId,
|
|
1107
|
+
score: hit.score,
|
|
1108
|
+
termFrequencies: {},
|
|
1109
|
+
fieldLengths: {},
|
|
1110
|
+
idf: {}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
results.sort((a, b) => b.score - a.score || compareCodePoints(a.docId, b.docId));
|
|
1114
|
+
return results.slice(0, k);
|
|
1115
|
+
}
|
|
1116
|
+
function searchOrdinals(state, query, k, searchMetric, minSimilarity, rankByOrdinal, filter, efSearch) {
|
|
1117
|
+
const hasDocument = (ord) => ord < rankByOrdinal.length && rankByOrdinal[ord] !== ABSENT_DOCUMENT_RANK;
|
|
1118
|
+
const hits = collectHits(state, query, k, searchMetric, minSimilarity, filter, efSearch, hasDocument);
|
|
1119
|
+
hits.sort((a, b) => b.score - a.score || rankByOrdinal[a.ord] - rankByOrdinal[b.ord]);
|
|
1120
|
+
return hits.slice(0, k);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/vector/hnsw/mutation.ts
|
|
581
1124
|
function clearTombstone(state, ord) {
|
|
582
1125
|
if (state.tombstones[ord] === 1) {
|
|
583
1126
|
state.tombstones[ord] = 0;
|
|
@@ -587,21 +1130,21 @@ function clearTombstone(state, ord) {
|
|
|
587
1130
|
function reassignEntryPoint(state) {
|
|
588
1131
|
let bestOrd = -1;
|
|
589
1132
|
let bestLayer = -1;
|
|
590
|
-
for (let ord = 0; ord < state.
|
|
591
|
-
const
|
|
592
|
-
if (
|
|
1133
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1134
|
+
const level = nodeMaxLayer(state, ord);
|
|
1135
|
+
if (level === -1) continue;
|
|
593
1136
|
if (state.tombstones[ord] === 1) continue;
|
|
594
|
-
if (
|
|
595
|
-
bestLayer =
|
|
1137
|
+
if (level > bestLayer) {
|
|
1138
|
+
bestLayer = level;
|
|
596
1139
|
bestOrd = ord;
|
|
597
1140
|
}
|
|
598
1141
|
}
|
|
599
1142
|
if (bestOrd === -1) {
|
|
600
|
-
for (let ord = 0; ord < state.
|
|
601
|
-
const
|
|
602
|
-
if (
|
|
603
|
-
if (
|
|
604
|
-
bestLayer =
|
|
1143
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1144
|
+
const level = nodeMaxLayer(state, ord);
|
|
1145
|
+
if (level === -1) continue;
|
|
1146
|
+
if (level > bestLayer) {
|
|
1147
|
+
bestLayer = level;
|
|
605
1148
|
bestOrd = ord;
|
|
606
1149
|
}
|
|
607
1150
|
}
|
|
@@ -622,16 +1165,12 @@ function insertNode(state, docId) {
|
|
|
622
1165
|
throw new Error(`Vector dimension mismatch: expected ${state.dimension}, got ${entry.vector.length}`);
|
|
623
1166
|
}
|
|
624
1167
|
ensureCapacity(state, ord + 1);
|
|
625
|
-
if (state
|
|
1168
|
+
if (nodeExists(state, ord)) {
|
|
626
1169
|
removeNodeEager(state, ord);
|
|
627
1170
|
}
|
|
628
1171
|
clearTombstone(state, ord);
|
|
629
1172
|
const l = randomLevel(state.mL);
|
|
630
|
-
|
|
631
|
-
maxLayer: l,
|
|
632
|
-
connections: Array.from({ length: l + 1 }, () => [])
|
|
633
|
-
};
|
|
634
|
-
state.nodesByOrd[ord] = node;
|
|
1173
|
+
createNode(state.adjacency, ord, l);
|
|
635
1174
|
state.nodeCount++;
|
|
636
1175
|
if (state.entryPointOrd === -1) {
|
|
637
1176
|
state.entryPointOrd = ord;
|
|
@@ -664,10 +1203,9 @@ function insertNode(state, docId) {
|
|
|
664
1203
|
const mc = maxConns(state, layer);
|
|
665
1204
|
const neighbors = selectNeighborsHeuristic(state, ord, candidates, mc, layer, metric);
|
|
666
1205
|
for (const neighbor of neighbors) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
addConnection(neighborNode.connections[layer], ord);
|
|
1206
|
+
addNeighbor(state.adjacency, ord, layer, neighbor.ord);
|
|
1207
|
+
if (layer <= nodeMaxLayer(state, neighbor.ord)) {
|
|
1208
|
+
addNeighbor(state.adjacency, neighbor.ord, layer, ord);
|
|
671
1209
|
pruneConnections(state, neighbor.ord, layer, metric);
|
|
672
1210
|
}
|
|
673
1211
|
}
|
|
@@ -681,24 +1219,21 @@ function insertNode(state, docId) {
|
|
|
681
1219
|
}
|
|
682
1220
|
}
|
|
683
1221
|
function removeNodeEager(state, ord, excludeOrds) {
|
|
684
|
-
const
|
|
685
|
-
if (
|
|
1222
|
+
const maxLayer = nodeMaxLayer(state, ord);
|
|
1223
|
+
if (maxLayer === -1) return;
|
|
686
1224
|
const metric = state.buildMetric;
|
|
687
|
-
for (let layer = 0; layer <=
|
|
688
|
-
|
|
689
|
-
const formerNeighbors = [...node.connections[layer]];
|
|
1225
|
+
for (let layer = 0; layer <= maxLayer; layer++) {
|
|
1226
|
+
const formerNeighbors = collectNeighbors(state.adjacency, ord, layer);
|
|
690
1227
|
for (const neighborOrd of formerNeighbors) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
removeFromArray(neighborNode.connections[layer], ord);
|
|
1228
|
+
if (layer <= nodeMaxLayer(state, neighborOrd)) {
|
|
1229
|
+
removeNeighbor(state.adjacency, neighborOrd, layer, ord);
|
|
694
1230
|
}
|
|
695
1231
|
}
|
|
696
1232
|
for (const neighborOrd of formerNeighbors) {
|
|
697
|
-
|
|
698
|
-
if (!neighborNode || layer >= neighborNode.connections.length) continue;
|
|
1233
|
+
if (layer > nodeMaxLayer(state, neighborOrd)) continue;
|
|
699
1234
|
const mc = maxConns(state, layer);
|
|
700
|
-
if (
|
|
701
|
-
const candidateOrds = new Set(
|
|
1235
|
+
if (neighborCount(state.adjacency, neighborOrd, layer) >= mc) continue;
|
|
1236
|
+
const candidateOrds = new Set(collectNeighbors(state.adjacency, neighborOrd, layer));
|
|
702
1237
|
for (const otherOrd of formerNeighbors) {
|
|
703
1238
|
if (otherOrd !== neighborOrd && otherOrd !== ord) {
|
|
704
1239
|
if (excludeOrds?.has(otherOrd)) continue;
|
|
@@ -714,17 +1249,16 @@ function removeNodeEager(state, ord, excludeOrds) {
|
|
|
714
1249
|
const selected = selectNeighborsHeuristic(state, neighborOrd, candidates, mc, layer, metric);
|
|
715
1250
|
const newConns = [];
|
|
716
1251
|
for (const s of selected) addConnection(newConns, s.ord);
|
|
717
|
-
|
|
1252
|
+
replaceNeighbors(state.adjacency, neighborOrd, layer, newConns);
|
|
718
1253
|
for (const newConnOrd of newConns) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
addConnection(newConnNode.connections[layer], neighborOrd);
|
|
1254
|
+
if (layer <= nodeMaxLayer(state, newConnOrd)) {
|
|
1255
|
+
addNeighbor(state.adjacency, newConnOrd, layer, neighborOrd);
|
|
722
1256
|
pruneConnections(state, newConnOrd, layer, metric);
|
|
723
1257
|
}
|
|
724
1258
|
}
|
|
725
1259
|
}
|
|
726
1260
|
}
|
|
727
|
-
state.
|
|
1261
|
+
deleteNode(state.adjacency, ord);
|
|
728
1262
|
state.nodeCount--;
|
|
729
1263
|
clearTombstone(state, ord);
|
|
730
1264
|
if (state.entryPointOrd === ord) {
|
|
@@ -738,7 +1272,7 @@ function removeNodeEager(state, ord, excludeOrds) {
|
|
|
738
1272
|
}
|
|
739
1273
|
function markTombstone(state, docId) {
|
|
740
1274
|
const ord = state.store.getOrdinal(docId);
|
|
741
|
-
if (ord === void 0 || state
|
|
1275
|
+
if (ord === void 0 || !nodeExists(state, ord)) return;
|
|
742
1276
|
if (state.tombstones[ord] === 0) {
|
|
743
1277
|
state.tombstones[ord] = 1;
|
|
744
1278
|
state.tombstoneCount++;
|
|
@@ -746,12 +1280,12 @@ function markTombstone(state, docId) {
|
|
|
746
1280
|
if (state.entryPointOrd === ord) {
|
|
747
1281
|
let bestOrd = -1;
|
|
748
1282
|
let bestLayer = -1;
|
|
749
|
-
for (let o = 0; o < state.
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
1283
|
+
for (let o = 0; o < state.adjacency.slots; o++) {
|
|
1284
|
+
const level = nodeMaxLayer(state, o);
|
|
1285
|
+
if (level === -1) continue;
|
|
752
1286
|
if (state.tombstones[o] === 1) continue;
|
|
753
|
-
if (
|
|
754
|
-
bestLayer =
|
|
1287
|
+
if (level > bestLayer) {
|
|
1288
|
+
bestLayer = level;
|
|
755
1289
|
bestOrd = o;
|
|
756
1290
|
}
|
|
757
1291
|
}
|
|
@@ -767,8 +1301,8 @@ function markTombstone(state, docId) {
|
|
|
767
1301
|
function compactTombstones(state) {
|
|
768
1302
|
if (state.tombstoneCount === 0) return;
|
|
769
1303
|
const tombstonedOrds = [];
|
|
770
|
-
for (let ord = 0; ord < state.
|
|
771
|
-
if (state.tombstones[ord] === 1 && state
|
|
1304
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1305
|
+
if (state.tombstones[ord] === 1 && nodeExists(state, ord)) {
|
|
772
1306
|
tombstonedOrds.push(ord);
|
|
773
1307
|
}
|
|
774
1308
|
}
|
|
@@ -780,12 +1314,12 @@ function compactTombstones(state) {
|
|
|
780
1314
|
function rebuild(state) {
|
|
781
1315
|
if (state.tombstoneCount === 0 && state.nodeCount === 0) return;
|
|
782
1316
|
const liveOrds = [];
|
|
783
|
-
for (let ord = 0; ord < state.
|
|
784
|
-
if (state
|
|
1317
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1318
|
+
if (nodeExists(state, ord) && state.tombstones[ord] !== 1) {
|
|
785
1319
|
liveOrds.push(ord);
|
|
786
1320
|
}
|
|
787
1321
|
}
|
|
788
|
-
state.
|
|
1322
|
+
resetAdjacency(state.adjacency);
|
|
789
1323
|
state.tombstones.fill(0);
|
|
790
1324
|
state.tombstoneCount = 0;
|
|
791
1325
|
state.nodeCount = 0;
|
|
@@ -802,16 +1336,16 @@ function rebuild(state) {
|
|
|
802
1336
|
// src/vector/hnsw/persistence.ts
|
|
803
1337
|
function serializeGraph(state) {
|
|
804
1338
|
const nodeArray = [];
|
|
805
|
-
for (let ord = 0; ord < state.
|
|
806
|
-
const
|
|
807
|
-
if (
|
|
1339
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1340
|
+
const maxLayer = nodeMaxLayer(state, ord);
|
|
1341
|
+
if (maxLayer === -1) continue;
|
|
808
1342
|
if (state.tombstones[ord] === 1) continue;
|
|
809
1343
|
const docId = state.store.docIdForOrdinal(ord);
|
|
810
1344
|
if (docId === void 0) continue;
|
|
811
1345
|
const layerConns = [];
|
|
812
|
-
for (let layer = 0; layer
|
|
1346
|
+
for (let layer = 0; layer <= maxLayer; layer++) {
|
|
813
1347
|
const liveNeighbors = [];
|
|
814
|
-
for (const neighborOrd of
|
|
1348
|
+
for (const neighborOrd of collectNeighbors(state.adjacency, ord, layer)) {
|
|
815
1349
|
if (state.tombstones[neighborOrd] === 1) continue;
|
|
816
1350
|
const neighborDoc = state.store.docIdForOrdinal(neighborOrd);
|
|
817
1351
|
if (neighborDoc === void 0) continue;
|
|
@@ -821,7 +1355,7 @@ function serializeGraph(state) {
|
|
|
821
1355
|
layerConns.push([layer, liveNeighbors]);
|
|
822
1356
|
}
|
|
823
1357
|
}
|
|
824
|
-
nodeArray.push([docId,
|
|
1358
|
+
nodeArray.push([docId, maxLayer, layerConns]);
|
|
825
1359
|
}
|
|
826
1360
|
const entryPoint = state.entryPointOrd === -1 ? null : state.store.docIdForOrdinal(state.entryPointOrd) ?? null;
|
|
827
1361
|
return {
|
|
@@ -833,54 +1367,66 @@ function serializeGraph(state) {
|
|
|
833
1367
|
nodes: nodeArray
|
|
834
1368
|
};
|
|
835
1369
|
}
|
|
836
|
-
function
|
|
837
|
-
|
|
838
|
-
state.tombstones.fill(0);
|
|
839
|
-
state.tombstoneCount = 0;
|
|
840
|
-
state.nodeCount = 0;
|
|
841
|
-
state.entryPointOrd = -1;
|
|
842
|
-
state.topLayer = -1;
|
|
1370
|
+
function resolveNodes(state, data) {
|
|
1371
|
+
const nodes = [];
|
|
843
1372
|
let maxOrd = -1;
|
|
844
1373
|
for (const [docId, maxLayer, layerConns] of data.nodes) {
|
|
845
1374
|
const ord = state.store.getOrdinal(docId);
|
|
846
1375
|
if (ord === void 0) continue;
|
|
847
1376
|
if (state.store.entryForOrdinal(ord) === void 0) continue;
|
|
848
|
-
const
|
|
1377
|
+
const clampedMaxLayer = Math.min(Math.max(maxLayer, 0), MAX_LAYER_CAP);
|
|
1378
|
+
const layers = [];
|
|
849
1379
|
for (const [layer, neighbors] of layerConns) {
|
|
850
|
-
if (layer <
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1380
|
+
if (layer < 0 || layer > clampedMaxLayer) continue;
|
|
1381
|
+
const limit = maxConns(state, layer);
|
|
1382
|
+
const resolved = [];
|
|
1383
|
+
for (const neighborDoc of neighbors) {
|
|
1384
|
+
if (resolved.length >= limit) break;
|
|
1385
|
+
const neighborOrd = state.store.getOrdinal(neighborDoc);
|
|
1386
|
+
if (neighborOrd === void 0) continue;
|
|
1387
|
+
addConnection(resolved, neighborOrd);
|
|
1388
|
+
if (neighborOrd > maxOrd) maxOrd = neighborOrd;
|
|
859
1389
|
}
|
|
1390
|
+
layers.push([layer, resolved]);
|
|
860
1391
|
}
|
|
861
|
-
const node = { maxLayer, connections };
|
|
862
|
-
state.nodesByOrd[ord] = node;
|
|
863
|
-
state.nodeCount++;
|
|
864
1392
|
if (ord > maxOrd) maxOrd = ord;
|
|
1393
|
+
nodes.push({ ord, maxLayer: clampedMaxLayer, layers });
|
|
865
1394
|
}
|
|
1395
|
+
return { nodes, maxOrd };
|
|
1396
|
+
}
|
|
1397
|
+
function deserializeGraph(state, data) {
|
|
1398
|
+
resetAdjacency(state.adjacency);
|
|
1399
|
+
state.tombstones.fill(0);
|
|
1400
|
+
state.tombstoneCount = 0;
|
|
1401
|
+
state.nodeCount = 0;
|
|
1402
|
+
state.entryPointOrd = -1;
|
|
1403
|
+
state.topLayer = -1;
|
|
1404
|
+
const { nodes, maxOrd } = resolveNodes(state, data);
|
|
866
1405
|
if (maxOrd >= 0) {
|
|
867
1406
|
ensureCapacity(state, maxOrd + 1);
|
|
868
1407
|
}
|
|
1408
|
+
for (const node of nodes) {
|
|
1409
|
+
createNode(state.adjacency, node.ord, node.maxLayer);
|
|
1410
|
+
for (const [layer, neighbors] of node.layers) {
|
|
1411
|
+
replaceNeighbors(state.adjacency, node.ord, layer, neighbors);
|
|
1412
|
+
}
|
|
1413
|
+
state.nodeCount++;
|
|
1414
|
+
}
|
|
869
1415
|
if (data.entryPoint != null && data.entryPoint !== "") {
|
|
870
1416
|
const epOrd = state.store.getOrdinal(data.entryPoint);
|
|
871
|
-
if (epOrd !== void 0 && state
|
|
1417
|
+
if (epOrd !== void 0 && nodeExists(state, epOrd)) {
|
|
872
1418
|
state.entryPointOrd = epOrd;
|
|
873
|
-
state.topLayer = data.maxLayer;
|
|
1419
|
+
state.topLayer = Math.min(Math.max(data.maxLayer, 0), MAX_LAYER_CAP);
|
|
874
1420
|
}
|
|
875
1421
|
}
|
|
876
1422
|
if (state.entryPointOrd === -1 && state.nodeCount > 0) {
|
|
877
1423
|
let bestOrd = -1;
|
|
878
1424
|
let bestLayer = -1;
|
|
879
|
-
for (let ord = 0; ord < state.
|
|
880
|
-
const
|
|
881
|
-
if (
|
|
882
|
-
if (
|
|
883
|
-
bestLayer =
|
|
1425
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1426
|
+
const level = nodeMaxLayer(state, ord);
|
|
1427
|
+
if (level === -1) continue;
|
|
1428
|
+
if (level > bestLayer) {
|
|
1429
|
+
bestLayer = level;
|
|
884
1430
|
bestOrd = ord;
|
|
885
1431
|
}
|
|
886
1432
|
}
|
|
@@ -889,102 +1435,6 @@ function deserializeGraph(state, data) {
|
|
|
889
1435
|
}
|
|
890
1436
|
}
|
|
891
1437
|
|
|
892
|
-
// src/vector/hnsw/search.ts
|
|
893
|
-
function search(state, query, k, searchMetric, minSimilarity, filterDocIds, efSearch) {
|
|
894
|
-
if (query.length !== state.dimension) {
|
|
895
|
-
throw new Error(`Query dimension mismatch: expected ${state.dimension}, got ${query.length}`);
|
|
896
|
-
}
|
|
897
|
-
const liveSize = state.nodeCount - state.tombstoneCount;
|
|
898
|
-
if (state.entryPointOrd === -1 || liveSize === 0) {
|
|
899
|
-
return [];
|
|
900
|
-
}
|
|
901
|
-
const useQuantized = state.quantizer?.isCalibrated() === true && state.quantizer.size > 0;
|
|
902
|
-
const defaultEf = 50;
|
|
903
|
-
let ef = Math.max(efSearch ?? defaultEf, k);
|
|
904
|
-
let filterOrds;
|
|
905
|
-
if (filterDocIds) {
|
|
906
|
-
filterOrds = /* @__PURE__ */ new Set();
|
|
907
|
-
for (const docId of filterDocIds) {
|
|
908
|
-
const o = state.store.getOrdinal(docId);
|
|
909
|
-
if (o !== void 0) filterOrds.add(o);
|
|
910
|
-
}
|
|
911
|
-
if (filterOrds.size < liveSize) {
|
|
912
|
-
const selectivity = filterOrds.size / liveSize;
|
|
913
|
-
ef = Math.max(ef, Math.ceil(k / Math.max(selectivity, 0.01)));
|
|
914
|
-
ef = Math.min(ef, liveSize);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
const qMag = magnitude(query);
|
|
918
|
-
let quantizedDistFn;
|
|
919
|
-
if (useQuantized && state.quantizer) {
|
|
920
|
-
const q = state.quantizer;
|
|
921
|
-
const metric = searchMetric;
|
|
922
|
-
const arenaQuery = q.prepareQueryArena(query);
|
|
923
|
-
if (arenaQuery) {
|
|
924
|
-
quantizedDistFn = (ord) => q.distanceFromArena(arenaQuery, ord, metric);
|
|
925
|
-
} else {
|
|
926
|
-
const prepared = q.prepareQuery(query);
|
|
927
|
-
if (prepared) {
|
|
928
|
-
quantizedDistFn = (ord) => q.distanceFromPreparedByOrdinal(prepared, ord, metric);
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
let currentEPs = [state.entryPointOrd];
|
|
933
|
-
for (let layer = state.topLayer; layer >= 1; layer--) {
|
|
934
|
-
const heap = searchLayer(state, query, qMag, currentEPs, 1, layer, searchMetric, true, quantizedDistFn);
|
|
935
|
-
const nearest = nearestFromHeap(heap);
|
|
936
|
-
if (nearest) {
|
|
937
|
-
currentEPs = [nearest.ord];
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
const candidateHeap = searchLayer(state, query, qMag, currentEPs, ef, 0, searchMetric, true, quantizedDistFn);
|
|
941
|
-
const candidateArray = candidateHeap.toSortedArray().reverse();
|
|
942
|
-
if (useQuantized) {
|
|
943
|
-
return rerankWithFullPrecision(state, candidateArray, query, qMag, k, searchMetric, minSimilarity, filterOrds);
|
|
944
|
-
}
|
|
945
|
-
const results = [];
|
|
946
|
-
for (const cand of candidateArray) {
|
|
947
|
-
if (filterOrds && !filterOrds.has(cand.ord)) continue;
|
|
948
|
-
const score = toScore(cand.distance, searchMetric);
|
|
949
|
-
if (score < minSimilarity) continue;
|
|
950
|
-
const docId = state.store.docIdForOrdinal(cand.ord);
|
|
951
|
-
if (docId === void 0) continue;
|
|
952
|
-
results.push({
|
|
953
|
-
docId,
|
|
954
|
-
score,
|
|
955
|
-
termFrequencies: {},
|
|
956
|
-
fieldLengths: {},
|
|
957
|
-
idf: {}
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
results.sort((a, b) => b.score - a.score || a.docId.localeCompare(b.docId));
|
|
961
|
-
return results.slice(0, k);
|
|
962
|
-
}
|
|
963
|
-
function rerankWithFullPrecision(state, candidates, query, qMag, k, metric, minSimilarity, filterOrds) {
|
|
964
|
-
const reranked = [];
|
|
965
|
-
const rerankLimit = Math.max(k * SQ8_OVERSELECTION_FACTOR, 10);
|
|
966
|
-
for (const cand of candidates) {
|
|
967
|
-
if (filterOrds && !filterOrds.has(cand.ord)) continue;
|
|
968
|
-
const entry = entryForOrd(state, cand.ord);
|
|
969
|
-
if (!entry) continue;
|
|
970
|
-
const fullDistance = toDistance(query, entry.vector, qMag, entry.magnitude, metric);
|
|
971
|
-
const score = toScore(fullDistance, metric);
|
|
972
|
-
if (score < minSimilarity) continue;
|
|
973
|
-
const docId = state.store.docIdForOrdinal(cand.ord);
|
|
974
|
-
if (docId === void 0) continue;
|
|
975
|
-
reranked.push({
|
|
976
|
-
docId,
|
|
977
|
-
score,
|
|
978
|
-
termFrequencies: {},
|
|
979
|
-
fieldLengths: {},
|
|
980
|
-
idf: {}
|
|
981
|
-
});
|
|
982
|
-
if (reranked.length >= rerankLimit) break;
|
|
983
|
-
}
|
|
984
|
-
reranked.sort((a, b) => b.score - a.score || a.docId.localeCompare(b.docId));
|
|
985
|
-
return reranked.slice(0, k);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
1438
|
// src/vector/hnsw/index.ts
|
|
989
1439
|
function createHNSWIndex(dimension, store, config, quantizer) {
|
|
990
1440
|
const M = config?.m ?? 16;
|
|
@@ -1001,7 +1451,7 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
1001
1451
|
efCons,
|
|
1002
1452
|
buildMetric,
|
|
1003
1453
|
mL,
|
|
1004
|
-
|
|
1454
|
+
adjacency: createAdjacency(M, Mmax0),
|
|
1005
1455
|
tombstones: new Uint8Array(0),
|
|
1006
1456
|
tombstoneCount: 0,
|
|
1007
1457
|
nodeCount: 0,
|
|
@@ -1012,7 +1462,7 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
1012
1462
|
topLayer: -1
|
|
1013
1463
|
};
|
|
1014
1464
|
function clear() {
|
|
1015
|
-
state.
|
|
1465
|
+
resetAdjacency(state.adjacency);
|
|
1016
1466
|
state.tombstones = new Uint8Array(0);
|
|
1017
1467
|
state.tombstoneCount = 0;
|
|
1018
1468
|
state.nodeCount = 0;
|
|
@@ -1023,8 +1473,8 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
1023
1473
|
state.topLayer = -1;
|
|
1024
1474
|
}
|
|
1025
1475
|
function* entriesIterator() {
|
|
1026
|
-
for (let ord = 0; ord < state.
|
|
1027
|
-
if (state.
|
|
1476
|
+
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
1477
|
+
if (!hasNode(state.adjacency, ord)) continue;
|
|
1028
1478
|
if (state.tombstones[ord] === 1) continue;
|
|
1029
1479
|
const docId = state.store.docIdForOrdinal(ord);
|
|
1030
1480
|
if (docId === void 0) continue;
|
|
@@ -1062,25 +1512,52 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
1062
1512
|
get metric() {
|
|
1063
1513
|
return state.buildMetric;
|
|
1064
1514
|
},
|
|
1515
|
+
get adjacencyBytes() {
|
|
1516
|
+
return estimateAdjacencyBytes(state.adjacency);
|
|
1517
|
+
},
|
|
1065
1518
|
insertNode: (docId) => insertNode(state, docId),
|
|
1066
1519
|
markTombstone: (docId) => markTombstone(state, docId),
|
|
1067
1520
|
has: (docId) => {
|
|
1068
1521
|
const ord = state.store.getOrdinal(docId);
|
|
1069
|
-
return ord !== void 0 && state.
|
|
1522
|
+
return ord !== void 0 && hasNode(state.adjacency, ord) && state.tombstones[ord] !== 1;
|
|
1070
1523
|
},
|
|
1071
1524
|
isTombstoned: (docId) => {
|
|
1072
1525
|
const ord = state.store.getOrdinal(docId);
|
|
1073
1526
|
return ord !== void 0 && state.tombstones[ord] === 1;
|
|
1074
1527
|
},
|
|
1075
|
-
search: (query, k, searchMetric, minSimilarity,
|
|
1528
|
+
search: (query, k, searchMetric, minSimilarity, filter, efSearch) => search(state, query, k, searchMetric, minSimilarity, filter, efSearch),
|
|
1076
1529
|
clear,
|
|
1077
1530
|
entries: entriesIterator,
|
|
1078
1531
|
compactionNeeded,
|
|
1079
1532
|
compactTombstones: () => compactTombstones(state),
|
|
1080
1533
|
rebuild: () => rebuild(state),
|
|
1081
1534
|
serialize: () => serializeGraph(state),
|
|
1082
|
-
deserialize: (data) => deserializeGraph(state, data)
|
|
1535
|
+
deserialize: (data) => deserializeGraph(state, data),
|
|
1536
|
+
exportSnapshot: () => ({
|
|
1537
|
+
dimension: state.dimension,
|
|
1538
|
+
m: state.M,
|
|
1539
|
+
efConstruction: state.efCons,
|
|
1540
|
+
metric: state.buildMetric,
|
|
1541
|
+
adjacency: exportAdjacency(state.adjacency),
|
|
1542
|
+
tombstones: state.tombstones.slice(),
|
|
1543
|
+
tombstoneCount: state.tombstoneCount,
|
|
1544
|
+
nodeCount: state.nodeCount,
|
|
1545
|
+
capacity: state.capacity,
|
|
1546
|
+
entryPointOrd: state.entryPointOrd,
|
|
1547
|
+
topLayer: state.topLayer
|
|
1548
|
+
}),
|
|
1549
|
+
restoreSnapshot: (snapshot) => {
|
|
1550
|
+
state.adjacency = importAdjacency(snapshot.adjacency);
|
|
1551
|
+
state.tombstones = snapshot.tombstones;
|
|
1552
|
+
state.tombstoneCount = snapshot.tombstoneCount;
|
|
1553
|
+
state.nodeCount = snapshot.nodeCount;
|
|
1554
|
+
state.capacity = Math.max(snapshot.capacity, snapshot.adjacency.capacity);
|
|
1555
|
+
state.visited = new Uint32Array(state.capacity);
|
|
1556
|
+
state.visitStamp = 0;
|
|
1557
|
+
state.entryPointOrd = snapshot.entryPointOrd;
|
|
1558
|
+
state.topLayer = snapshot.topLayer;
|
|
1559
|
+
}
|
|
1083
1560
|
};
|
|
1084
1561
|
}
|
|
1085
1562
|
|
|
1086
|
-
export { cosineSimilarityWithMagnitudes, createArenaSimd, createBoundedMaxHeap, createHNSWIndex, createVectorStore, dotProduct, euclideanDistance, isSimdAvailable, magnitude };
|
|
1563
|
+
export { ABSENT_DOCUMENT_RANK, MAX_M, addToOrdinalFilter, arenaFloat32Distance, cosineSimilarityWithMagnitudes, createArenaSimd, createBoundedMaxHeap, createHNSWIndex, createOrdinalFilter, createSharedArenaSimd, createVectorStore, dotProduct, euclideanDistance, importAdjacency, isSimdAvailable, magnitude, ordinalFilterHas, ordinalFilterValues, removeFromOrdinalFilter, searchOrdinals, toScore };
|