@delali/narsil 0.2.4-next.45 → 0.2.4-next.47
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/dist/{chunk-OBXS7OZM.mjs → chunk-4COAUBLJ.mjs} +1 -1
- package/dist/{chunk-WDL632CN.mjs → chunk-DFZDV5FV.mjs} +244 -239
- package/dist/{chunk-XNCJFU2E.mjs → chunk-EV45L35L.mjs} +225 -81
- package/dist/{chunk-WASNWIEK.mjs → chunk-UZTT2BKQ.mjs} +1 -1
- package/dist/{direct-executor-IMXNRS7O.mjs → direct-executor-EOTFGXTO.mjs} +3 -3
- package/dist/distribution.mjs +4 -4
- package/dist/index.browser.mjs +467 -318
- package/dist/index.mjs +4 -4
- package/dist/persistence/durability/checkpoint-worker.mjs +4 -4
- package/dist/vector/hnsw-build-worker.mjs +1 -1
- package/dist/vector/search-worker.mjs +4 -3
- package/dist/workers/entry.mjs +1 -1
- package/package.json +1 -1
package/dist/index.browser.mjs
CHANGED
|
@@ -2406,12 +2406,12 @@ function removeNeighbor(adj, ord, layer, neighborOrd) {
|
|
|
2406
2406
|
return;
|
|
2407
2407
|
}
|
|
2408
2408
|
}
|
|
2409
|
-
function replaceNeighbors(adj, ord, layer, neighbors) {
|
|
2409
|
+
function replaceNeighbors(adj, ord, layer, neighbors, count) {
|
|
2410
2410
|
const base = layerBase(adj, ord, layer);
|
|
2411
2411
|
if (base === ABSENT) return;
|
|
2412
2412
|
const array = layerArray(adj, layer);
|
|
2413
2413
|
const stride = layer === 0 ? adj.level0Stride : adj.upperStride;
|
|
2414
|
-
const limit = Math.min(
|
|
2414
|
+
const limit = Math.min(count, stride - 1);
|
|
2415
2415
|
for (let i = 0; i < limit; i++) {
|
|
2416
2416
|
array[base + i + 1] = neighbors[i];
|
|
2417
2417
|
}
|
|
@@ -12031,90 +12031,6 @@ function createVectorStore() {
|
|
|
12031
12031
|
};
|
|
12032
12032
|
}
|
|
12033
12033
|
|
|
12034
|
-
// src/core/heap.ts
|
|
12035
|
-
function createMinHeap(compare) {
|
|
12036
|
-
return createHeap(compare);
|
|
12037
|
-
}
|
|
12038
|
-
function createMaxHeap(compare) {
|
|
12039
|
-
return createHeap((a, b) => compare(b, a));
|
|
12040
|
-
}
|
|
12041
|
-
function createBoundedMaxHeap(compare, capacity) {
|
|
12042
|
-
const heap = createMaxHeap(compare);
|
|
12043
|
-
const innerPush = heap.push;
|
|
12044
|
-
return {
|
|
12045
|
-
push(item) {
|
|
12046
|
-
if (heap.size < capacity) {
|
|
12047
|
-
innerPush(item);
|
|
12048
|
-
return;
|
|
12049
|
-
}
|
|
12050
|
-
const top = heap.peek();
|
|
12051
|
-
if (top !== void 0 && compare(item, top) < 0) {
|
|
12052
|
-
heap.pop();
|
|
12053
|
-
innerPush(item);
|
|
12054
|
-
}
|
|
12055
|
-
},
|
|
12056
|
-
pop: heap.pop,
|
|
12057
|
-
peek: heap.peek,
|
|
12058
|
-
get size() {
|
|
12059
|
-
return heap.size;
|
|
12060
|
-
},
|
|
12061
|
-
toSortedArray: heap.toSortedArray
|
|
12062
|
-
};
|
|
12063
|
-
}
|
|
12064
|
-
function createHeap(compare) {
|
|
12065
|
-
const items = [];
|
|
12066
|
-
function siftUp(idx) {
|
|
12067
|
-
while (idx > 0) {
|
|
12068
|
-
const parent = idx - 1 >> 1;
|
|
12069
|
-
if (compare(items[idx], items[parent]) >= 0) break;
|
|
12070
|
-
const tmp = items[idx];
|
|
12071
|
-
items[idx] = items[parent];
|
|
12072
|
-
items[parent] = tmp;
|
|
12073
|
-
idx = parent;
|
|
12074
|
-
}
|
|
12075
|
-
}
|
|
12076
|
-
function siftDown2(idx) {
|
|
12077
|
-
const len = items.length;
|
|
12078
|
-
while (true) {
|
|
12079
|
-
let smallest = idx;
|
|
12080
|
-
const left = 2 * idx + 1;
|
|
12081
|
-
const right = 2 * idx + 2;
|
|
12082
|
-
if (left < len && compare(items[left], items[smallest]) < 0) smallest = left;
|
|
12083
|
-
if (right < len && compare(items[right], items[smallest]) < 0) smallest = right;
|
|
12084
|
-
if (smallest === idx) break;
|
|
12085
|
-
const tmp = items[idx];
|
|
12086
|
-
items[idx] = items[smallest];
|
|
12087
|
-
items[smallest] = tmp;
|
|
12088
|
-
idx = smallest;
|
|
12089
|
-
}
|
|
12090
|
-
}
|
|
12091
|
-
return {
|
|
12092
|
-
push(item) {
|
|
12093
|
-
items.push(item);
|
|
12094
|
-
siftUp(items.length - 1);
|
|
12095
|
-
},
|
|
12096
|
-
pop() {
|
|
12097
|
-
if (items.length === 0) return void 0;
|
|
12098
|
-
const top = items[0];
|
|
12099
|
-
const last = items.pop();
|
|
12100
|
-
if (items.length > 0) {
|
|
12101
|
-
items[0] = last;
|
|
12102
|
-
siftDown2(0);
|
|
12103
|
-
}
|
|
12104
|
-
return top;
|
|
12105
|
-
},
|
|
12106
|
-
peek() {
|
|
12107
|
-
return items[0];
|
|
12108
|
-
},
|
|
12109
|
-
get size() {
|
|
12110
|
-
return items.length;
|
|
12111
|
-
},
|
|
12112
|
-
toSortedArray() {
|
|
12113
|
-
return [...items].sort(compare);
|
|
12114
|
-
}
|
|
12115
|
-
};
|
|
12116
|
-
}
|
|
12117
|
-
|
|
12118
12034
|
// src/vector/hnsw/shared.ts
|
|
12119
12035
|
var COMPACTION_TOMBSTONE_RATIO = 0.1;
|
|
12120
12036
|
var COMPACTION_ABSOLUTE_THRESHOLD = 1e3;
|
|
@@ -12165,8 +12081,6 @@ function toScore(distance, metric) {
|
|
|
12165
12081
|
return 1 / (1 + distance);
|
|
12166
12082
|
}
|
|
12167
12083
|
}
|
|
12168
|
-
var distanceAsc = (a, b) => a.distance - b.distance;
|
|
12169
|
-
var distanceDesc = (a, b) => b.distance - a.distance;
|
|
12170
12084
|
function randomLevel(mL) {
|
|
12171
12085
|
let u = Math.random();
|
|
12172
12086
|
if (u === 0) u = Number.MIN_VALUE;
|
|
@@ -12193,37 +12107,168 @@ function addConnection(connections, ord) {
|
|
|
12193
12107
|
connections.push(ord);
|
|
12194
12108
|
}
|
|
12195
12109
|
|
|
12110
|
+
// src/vector/hnsw/workspace.ts
|
|
12111
|
+
var INITIAL_LIST_CAPACITY = 64;
|
|
12112
|
+
function createList(capacity) {
|
|
12113
|
+
return { ords: new Int32Array(capacity), distances: new Float64Array(capacity), size: 0 };
|
|
12114
|
+
}
|
|
12115
|
+
function createHeap(greatestFirst) {
|
|
12116
|
+
return { ...createList(INITIAL_LIST_CAPACITY), greatestFirst, topOrd: -1, topDistance: 0 };
|
|
12117
|
+
}
|
|
12118
|
+
function createHNSWWorkspace() {
|
|
12119
|
+
return {
|
|
12120
|
+
frontier: createHeap(false),
|
|
12121
|
+
found: createHeap(true),
|
|
12122
|
+
entryPoints: new Int32Array(INITIAL_LIST_CAPACITY),
|
|
12123
|
+
entryPointCount: 0,
|
|
12124
|
+
traversal: createList(INITIAL_LIST_CAPACITY),
|
|
12125
|
+
working: createList(INITIAL_LIST_CAPACITY),
|
|
12126
|
+
insertSelection: createList(INITIAL_LIST_CAPACITY),
|
|
12127
|
+
pruneCandidates: createList(INITIAL_LIST_CAPACITY),
|
|
12128
|
+
pruneSelection: createList(INITIAL_LIST_CAPACITY),
|
|
12129
|
+
repairCandidates: createList(INITIAL_LIST_CAPACITY),
|
|
12130
|
+
repairSelection: createList(INITIAL_LIST_CAPACITY)
|
|
12131
|
+
};
|
|
12132
|
+
}
|
|
12133
|
+
function ensureListCapacity(list, needed) {
|
|
12134
|
+
if (needed <= list.ords.length) return;
|
|
12135
|
+
let capacity = list.ords.length === 0 ? INITIAL_LIST_CAPACITY : list.ords.length;
|
|
12136
|
+
while (capacity < needed) capacity *= 2;
|
|
12137
|
+
const ords = new Int32Array(capacity);
|
|
12138
|
+
ords.set(list.ords);
|
|
12139
|
+
list.ords = ords;
|
|
12140
|
+
const distances = new Float64Array(capacity);
|
|
12141
|
+
distances.set(list.distances);
|
|
12142
|
+
list.distances = distances;
|
|
12143
|
+
}
|
|
12144
|
+
function appendToList(list, ord, distance) {
|
|
12145
|
+
ensureListCapacity(list, list.size + 1);
|
|
12146
|
+
list.ords[list.size] = ord;
|
|
12147
|
+
list.distances[list.size] = distance;
|
|
12148
|
+
list.size += 1;
|
|
12149
|
+
}
|
|
12150
|
+
function copyList(source, target) {
|
|
12151
|
+
ensureListCapacity(target, source.size);
|
|
12152
|
+
target.ords.set(source.ords.subarray(0, source.size));
|
|
12153
|
+
target.distances.set(source.distances.subarray(0, source.size));
|
|
12154
|
+
target.size = source.size;
|
|
12155
|
+
}
|
|
12156
|
+
function sortListByDistance(list) {
|
|
12157
|
+
const { ords, distances } = list;
|
|
12158
|
+
for (let i = 1; i < list.size; i++) {
|
|
12159
|
+
const ord = ords[i];
|
|
12160
|
+
const distance = distances[i];
|
|
12161
|
+
let j = i - 1;
|
|
12162
|
+
while (j >= 0 && distances[j] > distance) {
|
|
12163
|
+
distances[j + 1] = distances[j];
|
|
12164
|
+
ords[j + 1] = ords[j];
|
|
12165
|
+
j -= 1;
|
|
12166
|
+
}
|
|
12167
|
+
distances[j + 1] = distance;
|
|
12168
|
+
ords[j + 1] = ord;
|
|
12169
|
+
}
|
|
12170
|
+
}
|
|
12171
|
+
function heapPrecedes(heap, a, b) {
|
|
12172
|
+
return heap.greatestFirst ? heap.distances[a] > heap.distances[b] : heap.distances[a] < heap.distances[b];
|
|
12173
|
+
}
|
|
12174
|
+
function swapHeapEntries(heap, a, b) {
|
|
12175
|
+
const ord = heap.ords[a];
|
|
12176
|
+
heap.ords[a] = heap.ords[b];
|
|
12177
|
+
heap.ords[b] = ord;
|
|
12178
|
+
const distance = heap.distances[a];
|
|
12179
|
+
heap.distances[a] = heap.distances[b];
|
|
12180
|
+
heap.distances[b] = distance;
|
|
12181
|
+
}
|
|
12182
|
+
function pushHeap(heap, ord, distance) {
|
|
12183
|
+
appendToList(heap, ord, distance);
|
|
12184
|
+
let index = heap.size - 1;
|
|
12185
|
+
while (index > 0) {
|
|
12186
|
+
const parent = index - 1 >> 1;
|
|
12187
|
+
if (!heapPrecedes(heap, index, parent)) break;
|
|
12188
|
+
swapHeapEntries(heap, index, parent);
|
|
12189
|
+
index = parent;
|
|
12190
|
+
}
|
|
12191
|
+
}
|
|
12192
|
+
function popHeap(heap) {
|
|
12193
|
+
if (heap.size === 0) return false;
|
|
12194
|
+
heap.topOrd = heap.ords[0];
|
|
12195
|
+
heap.topDistance = heap.distances[0];
|
|
12196
|
+
heap.size -= 1;
|
|
12197
|
+
if (heap.size > 0) {
|
|
12198
|
+
heap.ords[0] = heap.ords[heap.size];
|
|
12199
|
+
heap.distances[0] = heap.distances[heap.size];
|
|
12200
|
+
let index = 0;
|
|
12201
|
+
for (; ; ) {
|
|
12202
|
+
const left = 2 * index + 1;
|
|
12203
|
+
const right = left + 1;
|
|
12204
|
+
let first = index;
|
|
12205
|
+
if (left < heap.size && heapPrecedes(heap, left, first)) first = left;
|
|
12206
|
+
if (right < heap.size && heapPrecedes(heap, right, first)) first = right;
|
|
12207
|
+
if (first === index) break;
|
|
12208
|
+
swapHeapEntries(heap, index, first);
|
|
12209
|
+
index = first;
|
|
12210
|
+
}
|
|
12211
|
+
}
|
|
12212
|
+
return true;
|
|
12213
|
+
}
|
|
12214
|
+
function resetHeap(heap) {
|
|
12215
|
+
heap.size = 0;
|
|
12216
|
+
}
|
|
12217
|
+
function drainHeapNearestFirst(heap, list) {
|
|
12218
|
+
const count = heap.size;
|
|
12219
|
+
ensureListCapacity(list, count);
|
|
12220
|
+
list.size = count;
|
|
12221
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
12222
|
+
if (!popHeap(heap)) break;
|
|
12223
|
+
list.ords[i] = heap.topOrd;
|
|
12224
|
+
list.distances[i] = heap.topDistance;
|
|
12225
|
+
}
|
|
12226
|
+
}
|
|
12227
|
+
function setSingleEntryPoint(workspace, ord) {
|
|
12228
|
+
workspace.entryPoints[0] = ord;
|
|
12229
|
+
workspace.entryPointCount = 1;
|
|
12230
|
+
}
|
|
12231
|
+
function setEntryPointsFromList(workspace, list) {
|
|
12232
|
+
if (list.size > workspace.entryPoints.length) {
|
|
12233
|
+
let capacity = workspace.entryPoints.length;
|
|
12234
|
+
while (capacity < list.size) capacity *= 2;
|
|
12235
|
+
workspace.entryPoints = new Int32Array(capacity);
|
|
12236
|
+
}
|
|
12237
|
+
workspace.entryPoints.set(list.ords.subarray(0, list.size));
|
|
12238
|
+
workspace.entryPointCount = list.size;
|
|
12239
|
+
}
|
|
12240
|
+
|
|
12196
12241
|
// src/vector/hnsw/graph-ops.ts
|
|
12197
|
-
function searchLayer(state, qVec, qMag,
|
|
12242
|
+
function searchLayer(state, qVec, qMag, ef, layer, metric, skipTombstones, distFn, results) {
|
|
12198
12243
|
const getDistance = distFn ?? ((ord) => queryDistanceByOrd(state, qVec, qMag, ord, metric));
|
|
12244
|
+
const workspace = state.workspace;
|
|
12245
|
+
const frontier = workspace.frontier;
|
|
12246
|
+
const found = workspace.found;
|
|
12199
12247
|
const adjacency = state.adjacency;
|
|
12200
12248
|
const neighbors = layerArray(adjacency, layer);
|
|
12201
12249
|
const visited = state.visited;
|
|
12202
12250
|
const stamp = nextVisitStamp(state);
|
|
12203
|
-
|
|
12204
|
-
|
|
12251
|
+
resetHeap(frontier);
|
|
12252
|
+
resetHeap(found);
|
|
12205
12253
|
let furthestDist = Number.POSITIVE_INFINITY;
|
|
12206
|
-
for (
|
|
12254
|
+
for (let i = 0; i < workspace.entryPointCount; i++) {
|
|
12255
|
+
const epOrd = workspace.entryPoints[i];
|
|
12207
12256
|
if (visited[epOrd] === stamp) continue;
|
|
12208
12257
|
visited[epOrd] = stamp;
|
|
12209
12258
|
if (!nodeExists(state, epOrd)) continue;
|
|
12210
12259
|
if (skipTombstones && state.tombstones[epOrd] === 1) continue;
|
|
12211
12260
|
const dist = getDistance(epOrd);
|
|
12212
12261
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
const nearest = candidates.pop();
|
|
12224
|
-
if (!nearest) break;
|
|
12225
|
-
if (nearest.distance > furthestDist) break;
|
|
12226
|
-
const base = layerBase(adjacency, nearest.ord, layer);
|
|
12262
|
+
pushHeap(frontier, epOrd, dist);
|
|
12263
|
+
pushHeap(found, epOrd, dist);
|
|
12264
|
+
if (found.size > ef) {
|
|
12265
|
+
popHeap(found);
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
if (found.size > 0) furthestDist = found.distances[0];
|
|
12269
|
+
while (popHeap(frontier)) {
|
|
12270
|
+
if (frontier.topDistance > furthestDist) break;
|
|
12271
|
+
const base = layerBase(adjacency, frontier.topOrd, layer);
|
|
12227
12272
|
if (base === -1) continue;
|
|
12228
12273
|
const count = neighbors[base];
|
|
12229
12274
|
for (let i = 1; i <= count; i++) {
|
|
@@ -12234,55 +12279,41 @@ function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones,
|
|
|
12234
12279
|
if (!nodeExists(state, neighborOrd)) continue;
|
|
12235
12280
|
const dist = getDistance(neighborOrd);
|
|
12236
12281
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
12237
|
-
if (dist < furthestDist ||
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
results.pop();
|
|
12282
|
+
if (dist < furthestDist || found.size < ef) {
|
|
12283
|
+
pushHeap(frontier, neighborOrd, dist);
|
|
12284
|
+
pushHeap(found, neighborOrd, dist);
|
|
12285
|
+
if (found.size > ef) {
|
|
12286
|
+
popHeap(found);
|
|
12243
12287
|
}
|
|
12244
|
-
|
|
12245
|
-
if (newTop) furthestDist = newTop.distance;
|
|
12288
|
+
furthestDist = found.distances[0];
|
|
12246
12289
|
}
|
|
12247
12290
|
}
|
|
12248
12291
|
}
|
|
12249
|
-
|
|
12250
|
-
}
|
|
12251
|
-
function nearestFromHeap(heap) {
|
|
12252
|
-
let nearest;
|
|
12253
|
-
while (heap.size > 0) {
|
|
12254
|
-
nearest = heap.pop();
|
|
12255
|
-
}
|
|
12256
|
-
return nearest;
|
|
12292
|
+
drainHeapNearestFirst(found, results);
|
|
12257
12293
|
}
|
|
12258
|
-
function selectNeighborsHeuristic(state,
|
|
12259
|
-
const working =
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12294
|
+
function selectNeighborsHeuristic(state, candidates, maxConnections, metric, selected) {
|
|
12295
|
+
const working = state.workspace.working;
|
|
12296
|
+
copyList(candidates, working);
|
|
12297
|
+
sortListByDistance(working);
|
|
12298
|
+
ensureListCapacity(selected, Math.min(maxConnections, working.size));
|
|
12299
|
+
selected.size = 0;
|
|
12300
|
+
for (let i = 0; i < working.size; i++) {
|
|
12301
|
+
if (selected.size >= maxConnections) break;
|
|
12302
|
+
const candidateOrd = working.ords[i];
|
|
12303
|
+
const candidateDistance = working.distances[i];
|
|
12265
12304
|
let accepted = true;
|
|
12266
|
-
for (
|
|
12267
|
-
const distBetween = nodeDistanceByOrd(state,
|
|
12268
|
-
if (
|
|
12305
|
+
for (let s = 0; s < selected.size; s++) {
|
|
12306
|
+
const distBetween = nodeDistanceByOrd(state, candidateOrd, selected.ords[s], metric);
|
|
12307
|
+
if (candidateDistance >= distBetween) {
|
|
12269
12308
|
accepted = false;
|
|
12270
12309
|
break;
|
|
12271
12310
|
}
|
|
12272
12311
|
}
|
|
12273
|
-
if (accepted)
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
}
|
|
12278
|
-
}
|
|
12279
|
-
{
|
|
12280
|
-
for (const disc of discarded) {
|
|
12281
|
-
if (selected.length >= maxConnections) break;
|
|
12282
|
-
selected.push(disc);
|
|
12283
|
-
}
|
|
12312
|
+
if (!accepted) continue;
|
|
12313
|
+
selected.ords[selected.size] = candidateOrd;
|
|
12314
|
+
selected.distances[selected.size] = candidateDistance;
|
|
12315
|
+
selected.size += 1;
|
|
12284
12316
|
}
|
|
12285
|
-
return selected;
|
|
12286
12317
|
}
|
|
12287
12318
|
function pruneConnections(state, ord, layer, metric) {
|
|
12288
12319
|
const adjacency = state.adjacency;
|
|
@@ -12292,17 +12323,20 @@ function pruneConnections(state, ord, layer, metric) {
|
|
|
12292
12323
|
const neighbors = layerArray(adjacency, layer);
|
|
12293
12324
|
const count = neighbors[base];
|
|
12294
12325
|
if (count <= mc) return;
|
|
12295
|
-
const
|
|
12326
|
+
const candidates = state.workspace.pruneCandidates;
|
|
12327
|
+
candidates.size = 0;
|
|
12328
|
+
ensureListCapacity(candidates, count);
|
|
12296
12329
|
for (let i = 1; i <= count; i++) {
|
|
12297
12330
|
const connOrd = neighbors[base + i];
|
|
12298
12331
|
const dist = nodeDistanceByOrd(state, ord, connOrd, metric);
|
|
12299
12332
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
12300
|
-
|
|
12333
|
+
candidates.ords[candidates.size] = connOrd;
|
|
12334
|
+
candidates.distances[candidates.size] = dist;
|
|
12335
|
+
candidates.size += 1;
|
|
12301
12336
|
}
|
|
12302
|
-
const
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
replaceNeighbors(adjacency, ord, layer, next);
|
|
12337
|
+
const kept = state.workspace.pruneSelection;
|
|
12338
|
+
selectNeighborsHeuristic(state, candidates, mc, metric, kept);
|
|
12339
|
+
replaceNeighbors(adjacency, ord, layer, kept.ords, kept.size);
|
|
12306
12340
|
}
|
|
12307
12341
|
|
|
12308
12342
|
// src/vector/hnsw/mutation.ts
|
|
@@ -12312,30 +12346,23 @@ function clearTombstone(state, ord) {
|
|
|
12312
12346
|
state.tombstoneCount--;
|
|
12313
12347
|
}
|
|
12314
12348
|
}
|
|
12315
|
-
function
|
|
12349
|
+
function highestNode(state, includeTombstoned) {
|
|
12316
12350
|
let bestOrd = -1;
|
|
12317
12351
|
let bestLayer = -1;
|
|
12318
12352
|
for (let ord = 0; ord < state.adjacency.slots; ord++) {
|
|
12319
12353
|
const level = nodeMaxLayer(state, ord);
|
|
12320
12354
|
if (level === -1) continue;
|
|
12321
|
-
if (state.tombstones[ord] === 1) continue;
|
|
12355
|
+
if (!includeTombstoned && state.tombstones[ord] === 1) continue;
|
|
12322
12356
|
if (level > bestLayer) {
|
|
12323
12357
|
bestLayer = level;
|
|
12324
12358
|
bestOrd = ord;
|
|
12325
12359
|
}
|
|
12326
12360
|
}
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
bestLayer = level;
|
|
12333
|
-
bestOrd = ord;
|
|
12334
|
-
}
|
|
12335
|
-
}
|
|
12336
|
-
}
|
|
12337
|
-
state.entryPointOrd = bestOrd;
|
|
12338
|
-
state.topLayer = bestOrd === -1 ? -1 : bestLayer;
|
|
12361
|
+
return bestOrd;
|
|
12362
|
+
}
|
|
12363
|
+
function setEntryPoint(state, ord) {
|
|
12364
|
+
state.entryPointOrd = ord;
|
|
12365
|
+
state.topLayer = ord === -1 ? -1 : nodeMaxLayer(state, ord);
|
|
12339
12366
|
}
|
|
12340
12367
|
function insertNode(state, docId) {
|
|
12341
12368
|
const ord = state.store.getOrdinal(docId);
|
|
@@ -12363,39 +12390,30 @@ function insertNode(state, docId) {
|
|
|
12363
12390
|
return;
|
|
12364
12391
|
}
|
|
12365
12392
|
const metric = state.buildMetric;
|
|
12393
|
+
const workspace = state.workspace;
|
|
12394
|
+
const candidates = workspace.traversal;
|
|
12395
|
+
const selected = workspace.insertSelection;
|
|
12366
12396
|
const insertDistFn = (candOrd) => nodeDistanceByOrd(state, ord, candOrd, metric);
|
|
12367
|
-
|
|
12397
|
+
setSingleEntryPoint(workspace, state.entryPointOrd);
|
|
12368
12398
|
for (let layer = state.topLayer; layer > l; layer--) {
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
currentEPs = [nearest.ord];
|
|
12399
|
+
searchLayer(state, entry.vector, entry.magnitude, 1, layer, metric, false, insertDistFn, candidates);
|
|
12400
|
+
if (candidates.size > 0) {
|
|
12401
|
+
setSingleEntryPoint(workspace, candidates.ords[0]);
|
|
12373
12402
|
}
|
|
12374
12403
|
}
|
|
12375
12404
|
for (let layer = Math.min(l, state.topLayer); layer >= 0; layer--) {
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
state
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
false,
|
|
12385
|
-
insertDistFn
|
|
12386
|
-
);
|
|
12387
|
-
const candidates = heap.toSortedArray().reverse();
|
|
12388
|
-
const mc = maxConns(state, layer);
|
|
12389
|
-
const neighbors = selectNeighborsHeuristic(state, ord, candidates, mc, layer, metric);
|
|
12390
|
-
for (const neighbor of neighbors) {
|
|
12391
|
-
addNeighbor(state.adjacency, ord, layer, neighbor.ord);
|
|
12392
|
-
if (layer <= nodeMaxLayer(state, neighbor.ord)) {
|
|
12393
|
-
addNeighbor(state.adjacency, neighbor.ord, layer, ord);
|
|
12394
|
-
pruneConnections(state, neighbor.ord, layer, metric);
|
|
12405
|
+
searchLayer(state, entry.vector, entry.magnitude, state.efCons, layer, metric, false, insertDistFn, candidates);
|
|
12406
|
+
selectNeighborsHeuristic(state, candidates, maxConns(state, layer), metric, selected);
|
|
12407
|
+
for (let i = 0; i < selected.size; i++) {
|
|
12408
|
+
const neighborOrd = selected.ords[i];
|
|
12409
|
+
addNeighbor(state.adjacency, ord, layer, neighborOrd);
|
|
12410
|
+
if (layer <= nodeMaxLayer(state, neighborOrd)) {
|
|
12411
|
+
addNeighbor(state.adjacency, neighborOrd, layer, ord);
|
|
12412
|
+
pruneConnections(state, neighborOrd, layer, metric);
|
|
12395
12413
|
}
|
|
12396
12414
|
}
|
|
12397
|
-
if (candidates.
|
|
12398
|
-
|
|
12415
|
+
if (candidates.size > 0) {
|
|
12416
|
+
setEntryPointsFromList(workspace, candidates);
|
|
12399
12417
|
}
|
|
12400
12418
|
}
|
|
12401
12419
|
if (l > state.topLayer) {
|
|
@@ -12425,17 +12443,18 @@ function removeNodeEager(state, ord, excludeOrds) {
|
|
|
12425
12443
|
candidateOrds.add(otherOrd);
|
|
12426
12444
|
}
|
|
12427
12445
|
}
|
|
12428
|
-
const candidates =
|
|
12446
|
+
const candidates = state.workspace.repairCandidates;
|
|
12447
|
+
candidates.size = 0;
|
|
12429
12448
|
for (const candOrd of candidateOrds) {
|
|
12430
12449
|
const dist = nodeDistanceByOrd(state, neighborOrd, candOrd, metric);
|
|
12431
12450
|
if (dist === Number.POSITIVE_INFINITY) continue;
|
|
12432
|
-
candidates
|
|
12451
|
+
appendToList(candidates, candOrd, dist);
|
|
12433
12452
|
}
|
|
12434
|
-
const selected =
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12453
|
+
const selected = state.workspace.repairSelection;
|
|
12454
|
+
selectNeighborsHeuristic(state, candidates, mc, metric, selected);
|
|
12455
|
+
replaceNeighbors(state.adjacency, neighborOrd, layer, selected.ords, selected.size);
|
|
12456
|
+
for (let i = 0; i < selected.size; i++) {
|
|
12457
|
+
const newConnOrd = selected.ords[i];
|
|
12439
12458
|
if (layer <= nodeMaxLayer(state, newConnOrd)) {
|
|
12440
12459
|
addNeighbor(state.adjacency, newConnOrd, layer, neighborOrd);
|
|
12441
12460
|
pruneConnections(state, newConnOrd, layer, metric);
|
|
@@ -12448,11 +12467,11 @@ function removeNodeEager(state, ord, excludeOrds) {
|
|
|
12448
12467
|
clearTombstone(state, ord);
|
|
12449
12468
|
if (state.entryPointOrd === ord) {
|
|
12450
12469
|
if (state.nodeCount === 0) {
|
|
12451
|
-
state
|
|
12452
|
-
state.topLayer = -1;
|
|
12470
|
+
setEntryPoint(state, -1);
|
|
12453
12471
|
return;
|
|
12454
12472
|
}
|
|
12455
|
-
|
|
12473
|
+
const live = highestNode(state, false);
|
|
12474
|
+
setEntryPoint(state, live === -1 ? highestNode(state, true) : live);
|
|
12456
12475
|
}
|
|
12457
12476
|
}
|
|
12458
12477
|
function markTombstone(state, docId) {
|
|
@@ -12463,24 +12482,7 @@ function markTombstone(state, docId) {
|
|
|
12463
12482
|
state.tombstoneCount++;
|
|
12464
12483
|
}
|
|
12465
12484
|
if (state.entryPointOrd === ord) {
|
|
12466
|
-
|
|
12467
|
-
let bestLayer = -1;
|
|
12468
|
-
for (let o = 0; o < state.adjacency.slots; o++) {
|
|
12469
|
-
const level = nodeMaxLayer(state, o);
|
|
12470
|
-
if (level === -1) continue;
|
|
12471
|
-
if (state.tombstones[o] === 1) continue;
|
|
12472
|
-
if (level > bestLayer) {
|
|
12473
|
-
bestLayer = level;
|
|
12474
|
-
bestOrd = o;
|
|
12475
|
-
}
|
|
12476
|
-
}
|
|
12477
|
-
if (bestOrd !== -1) {
|
|
12478
|
-
state.entryPointOrd = bestOrd;
|
|
12479
|
-
state.topLayer = bestLayer;
|
|
12480
|
-
} else {
|
|
12481
|
-
state.entryPointOrd = -1;
|
|
12482
|
-
state.topLayer = -1;
|
|
12483
|
-
}
|
|
12485
|
+
setEntryPoint(state, highestNode(state, false));
|
|
12484
12486
|
}
|
|
12485
12487
|
}
|
|
12486
12488
|
function compactTombstones(state) {
|
|
@@ -12593,7 +12595,7 @@ function deserializeGraph(state, data) {
|
|
|
12593
12595
|
for (const node of nodes) {
|
|
12594
12596
|
createNode(state.adjacency, node.ord, node.maxLayer);
|
|
12595
12597
|
for (const [layer, neighbors] of node.layers) {
|
|
12596
|
-
replaceNeighbors(state.adjacency, node.ord, layer, neighbors);
|
|
12598
|
+
replaceNeighbors(state.adjacency, node.ord, layer, neighbors, neighbors.length);
|
|
12597
12599
|
}
|
|
12598
12600
|
state.nodeCount++;
|
|
12599
12601
|
}
|
|
@@ -12660,20 +12662,20 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
|
|
|
12660
12662
|
const metric = searchMetric;
|
|
12661
12663
|
distFn = (ord) => store.distanceFromArena(arenaQuery, ord, metric);
|
|
12662
12664
|
}
|
|
12663
|
-
|
|
12665
|
+
const workspace = state.workspace;
|
|
12666
|
+
const candidates = workspace.traversal;
|
|
12667
|
+
setSingleEntryPoint(workspace, state.entryPointOrd);
|
|
12664
12668
|
for (let layer = state.topLayer; layer >= 1; layer--) {
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
currentEPs = [nearest.ord];
|
|
12669
|
+
searchLayer(state, query, qMag, 1, layer, searchMetric, true, distFn, candidates);
|
|
12670
|
+
if (candidates.size > 0) {
|
|
12671
|
+
setSingleEntryPoint(workspace, candidates.ords[0]);
|
|
12669
12672
|
}
|
|
12670
12673
|
}
|
|
12671
|
-
|
|
12672
|
-
const candidateArray = candidateHeap.toSortedArray().reverse();
|
|
12674
|
+
searchLayer(state, query, qMag, ef, 0, searchMetric, true, distFn, candidates);
|
|
12673
12675
|
if (useQuantized) {
|
|
12674
12676
|
return rerankWithFullPrecision(
|
|
12675
12677
|
state,
|
|
12676
|
-
|
|
12678
|
+
candidates,
|
|
12677
12679
|
query,
|
|
12678
12680
|
qMag,
|
|
12679
12681
|
arenaQuery,
|
|
@@ -12685,33 +12687,35 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
|
|
|
12685
12687
|
);
|
|
12686
12688
|
}
|
|
12687
12689
|
const hits = [];
|
|
12688
|
-
for (
|
|
12689
|
-
|
|
12690
|
-
|
|
12690
|
+
for (let i = 0; i < candidates.size; i++) {
|
|
12691
|
+
const ord = candidates.ords[i];
|
|
12692
|
+
if (filter && !ordinalFilterHas(filter, ord)) continue;
|
|
12693
|
+
const score = toScore(candidates.distances[i], searchMetric);
|
|
12691
12694
|
if (score < minSimilarity) continue;
|
|
12692
|
-
if (!hasDocument(
|
|
12693
|
-
hits.push({ ord
|
|
12695
|
+
if (!hasDocument(ord)) continue;
|
|
12696
|
+
hits.push({ ord, score });
|
|
12694
12697
|
}
|
|
12695
12698
|
return hits;
|
|
12696
12699
|
}
|
|
12697
12700
|
function rerankWithFullPrecision(state, candidates, query, qMag, arenaQuery, k, metric, minSimilarity, filter, hasDocument) {
|
|
12698
12701
|
const reranked = [];
|
|
12699
12702
|
const rerankLimit = Math.max(k * SQ8_OVERSELECTION_FACTOR, 10);
|
|
12700
|
-
for (
|
|
12701
|
-
|
|
12703
|
+
for (let i = 0; i < candidates.size; i++) {
|
|
12704
|
+
const ord = candidates.ords[i];
|
|
12705
|
+
if (filter && !ordinalFilterHas(filter, ord)) continue;
|
|
12702
12706
|
let fullDistance;
|
|
12703
12707
|
if (arenaQuery) {
|
|
12704
|
-
fullDistance = state.store.distanceFromArena(arenaQuery,
|
|
12708
|
+
fullDistance = state.store.distanceFromArena(arenaQuery, ord, metric);
|
|
12705
12709
|
if (fullDistance === Number.POSITIVE_INFINITY) continue;
|
|
12706
12710
|
} else {
|
|
12707
|
-
const entry = entryForOrd(state,
|
|
12711
|
+
const entry = entryForOrd(state, ord);
|
|
12708
12712
|
if (!entry) continue;
|
|
12709
12713
|
fullDistance = toDistance(query, entry.vector, qMag, entry.magnitude, metric);
|
|
12710
12714
|
}
|
|
12711
12715
|
const score = toScore(fullDistance, metric);
|
|
12712
12716
|
if (score < minSimilarity) continue;
|
|
12713
|
-
if (!hasDocument(
|
|
12714
|
-
reranked.push({ ord
|
|
12717
|
+
if (!hasDocument(ord)) continue;
|
|
12718
|
+
reranked.push({ ord, score });
|
|
12715
12719
|
if (reranked.length >= rerankLimit) break;
|
|
12716
12720
|
}
|
|
12717
12721
|
return reranked;
|
|
@@ -12759,7 +12763,8 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
12759
12763
|
visited: new Uint32Array(0),
|
|
12760
12764
|
visitStamp: 0,
|
|
12761
12765
|
entryPointOrd: -1,
|
|
12762
|
-
topLayer: -1
|
|
12766
|
+
topLayer: -1,
|
|
12767
|
+
workspace: createHNSWWorkspace()
|
|
12763
12768
|
};
|
|
12764
12769
|
function clear() {
|
|
12765
12770
|
resetAdjacency(state.adjacency);
|
|
@@ -13046,9 +13051,54 @@ var ESTIMATED_MS_PER_TOMBSTONE = 0.05;
|
|
|
13046
13051
|
var ESTIMATED_MS_PER_VECTOR_REBUILD = 0.15;
|
|
13047
13052
|
var WORKER_BUILD_SIZE_THRESHOLD = 5e3;
|
|
13048
13053
|
var WORKER_COPY_MIN_VECTORS = 1024;
|
|
13054
|
+
var BUILD_CHUNK_SIZE = 100;
|
|
13055
|
+
var REBUILD_REMOVED_RATIO = 0.2;
|
|
13049
13056
|
function liveSize(state) {
|
|
13050
13057
|
return state.store.size - state.tombstones.size;
|
|
13051
13058
|
}
|
|
13059
|
+
function adoptGraph(state, graph) {
|
|
13060
|
+
if (graph === null && state.hnsw !== null) {
|
|
13061
|
+
state.hnsw.clear();
|
|
13062
|
+
}
|
|
13063
|
+
state.hnsw = graph;
|
|
13064
|
+
state.compactedNodeCount = 0;
|
|
13065
|
+
if (graph === null) return;
|
|
13066
|
+
for (const docId of state.tombstones) {
|
|
13067
|
+
if (graph.has(docId)) {
|
|
13068
|
+
graph.markTombstone(docId);
|
|
13069
|
+
}
|
|
13070
|
+
}
|
|
13071
|
+
}
|
|
13072
|
+
async function insertIntoGraph(state, graph, docIds, admit, inserted) {
|
|
13073
|
+
let count = 0;
|
|
13074
|
+
for (const docId of docIds) {
|
|
13075
|
+
if (state.disposed) return false;
|
|
13076
|
+
if (!admit(docId)) continue;
|
|
13077
|
+
graph.insertNode(docId);
|
|
13078
|
+
state.buffer.delete(docId);
|
|
13079
|
+
inserted?.(docId);
|
|
13080
|
+
count += 1;
|
|
13081
|
+
if (count % BUILD_CHUNK_SIZE === 0) {
|
|
13082
|
+
await yieldToEventLoop();
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
return !state.disposed;
|
|
13086
|
+
}
|
|
13087
|
+
async function buildGraphFromStore(state) {
|
|
13088
|
+
const graph = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
|
|
13089
|
+
const completed = await insertIntoGraph(state, graph, allLiveDocIds(state), () => true);
|
|
13090
|
+
if (completed) {
|
|
13091
|
+
adoptGraph(state, graph);
|
|
13092
|
+
}
|
|
13093
|
+
}
|
|
13094
|
+
function graphNeedsRebuild(state) {
|
|
13095
|
+
const graph = state.hnsw;
|
|
13096
|
+
if (graph === null) return false;
|
|
13097
|
+
const removed = graph.tombstoneCount + state.compactedNodeCount;
|
|
13098
|
+
const held = graph.size + removed;
|
|
13099
|
+
if (held === 0) return false;
|
|
13100
|
+
return removed / held > REBUILD_REMOVED_RATIO;
|
|
13101
|
+
}
|
|
13052
13102
|
function yieldToEventLoop() {
|
|
13053
13103
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
13054
13104
|
}
|
|
@@ -13692,7 +13742,7 @@ function captureCloneSnapshot(state) {
|
|
|
13692
13742
|
};
|
|
13693
13743
|
}
|
|
13694
13744
|
function scheduleWorkerCopyLoad(state) {
|
|
13695
|
-
if (state.disposed || state.workerCopyLoading) return;
|
|
13745
|
+
if (state.disposed || state.workerCopyLoading || state.building) return;
|
|
13696
13746
|
if (state.workerCopyHandle !== null) return;
|
|
13697
13747
|
if (!state.hnsw || state.buffer.size > 0) return;
|
|
13698
13748
|
if (liveSize(state) < WORKER_COPY_MIN_VECTORS) return;
|
|
@@ -13773,7 +13823,7 @@ async function searchViaWorkerCopies(state, query, k, metric, minSimilarity, efS
|
|
|
13773
13823
|
}
|
|
13774
13824
|
|
|
13775
13825
|
// src/vector/vector-index/build.ts
|
|
13776
|
-
async function tryWorkerBuild(state, liveDocIds
|
|
13826
|
+
async function tryWorkerBuild(state, liveDocIds) {
|
|
13777
13827
|
const vectorData = new Float32Array(liveDocIds.length * state.dimension);
|
|
13778
13828
|
const validDocIds = [];
|
|
13779
13829
|
let offset = 0;
|
|
@@ -13782,6 +13832,7 @@ async function tryWorkerBuild(state, liveDocIds, bufferSnapshot) {
|
|
|
13782
13832
|
if (!entry || state.tombstones.has(docId)) continue;
|
|
13783
13833
|
vectorData.set(entry.vector, offset);
|
|
13784
13834
|
validDocIds.push(docId);
|
|
13835
|
+
state.buffer.delete(docId);
|
|
13785
13836
|
offset += state.dimension;
|
|
13786
13837
|
}
|
|
13787
13838
|
if (validDocIds.length === 0) return false;
|
|
@@ -13793,64 +13844,77 @@ async function tryWorkerBuild(state, liveDocIds, bufferSnapshot) {
|
|
|
13793
13844
|
};
|
|
13794
13845
|
const timeoutMs = Math.max(1e4, liveDocIds.length * 2);
|
|
13795
13846
|
const outcome = await dispatchWorkerBuild(validDocIds, packedData, state.dimension, resolvedConfig, timeoutMs, true);
|
|
13796
|
-
if (!outcome.ok) return false;
|
|
13847
|
+
if (!outcome.ok || state.disposed) return false;
|
|
13797
13848
|
const newHnsw = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
|
|
13798
13849
|
newHnsw.deserialize(outcome.graph);
|
|
13799
|
-
state
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13850
|
+
adoptGraph(state, newHnsw);
|
|
13851
|
+
return true;
|
|
13852
|
+
}
|
|
13853
|
+
async function promoteToGraph(state) {
|
|
13854
|
+
const liveDocIds = Array.from(allLiveDocIds(state));
|
|
13855
|
+
if (liveDocIds.length === 0) return;
|
|
13856
|
+
if (state.sq8) {
|
|
13857
|
+
calibrateAndQuantizeAll(state);
|
|
13804
13858
|
}
|
|
13805
|
-
|
|
13806
|
-
state
|
|
13859
|
+
if (liveDocIds.length > WORKER_BUILD_SIZE_THRESHOLD) {
|
|
13860
|
+
const built = await tryWorkerBuild(state, liveDocIds);
|
|
13861
|
+
if (built) return;
|
|
13862
|
+
}
|
|
13863
|
+
await buildGraphFromStore(state);
|
|
13864
|
+
}
|
|
13865
|
+
function quantizeIncoming(state, docId) {
|
|
13866
|
+
const sq8 = state.sq8;
|
|
13867
|
+
if (sq8 === null) return false;
|
|
13868
|
+
if (!sq8.isCalibrated()) {
|
|
13869
|
+
calibrateAndQuantizeAll(state);
|
|
13870
|
+
return false;
|
|
13871
|
+
}
|
|
13872
|
+
const entry = state.store.get(docId);
|
|
13873
|
+
if (entry === void 0) return false;
|
|
13874
|
+
const outsideBounds = sq8.needsRecalibration(entry.vector);
|
|
13875
|
+
sq8.quantize(docId, entry.vector);
|
|
13876
|
+
return outsideBounds;
|
|
13877
|
+
}
|
|
13878
|
+
async function growGraph(state, graph, bufferSnapshot) {
|
|
13879
|
+
let outsideCalibration = false;
|
|
13880
|
+
const admit = (docId) => {
|
|
13881
|
+
if (state.hnsw !== graph) return false;
|
|
13882
|
+
if (state.tombstones.has(docId) || !state.store.has(docId)) {
|
|
13883
|
+
state.buffer.delete(docId);
|
|
13884
|
+
return false;
|
|
13885
|
+
}
|
|
13886
|
+
return true;
|
|
13887
|
+
};
|
|
13888
|
+
const inserted = (docId) => {
|
|
13889
|
+
if (quantizeIncoming(state, docId)) {
|
|
13890
|
+
outsideCalibration = true;
|
|
13891
|
+
}
|
|
13892
|
+
};
|
|
13893
|
+
await insertIntoGraph(state, graph, bufferSnapshot, admit, inserted);
|
|
13894
|
+
if (outsideCalibration) {
|
|
13895
|
+
recalibrateFromStore(state);
|
|
13807
13896
|
}
|
|
13808
|
-
return true;
|
|
13809
13897
|
}
|
|
13810
13898
|
function triggerBuild(state) {
|
|
13811
13899
|
if (state.building) return;
|
|
13812
13900
|
invalidateWorkerCopies(state);
|
|
13813
13901
|
state.building = true;
|
|
13814
|
-
const liveDocIds = [];
|
|
13815
|
-
for (const [docId] of state.store.entries()) {
|
|
13816
|
-
if (state.tombstones.has(docId)) continue;
|
|
13817
|
-
liveDocIds.push(docId);
|
|
13818
|
-
}
|
|
13819
13902
|
const bufferSnapshot = new Set(state.buffer);
|
|
13903
|
+
const existingGraph = state.hnsw;
|
|
13820
13904
|
const buildPromise = (async () => {
|
|
13821
13905
|
try {
|
|
13822
|
-
if (liveDocIds.length === 0 || state.disposed) return;
|
|
13823
|
-
if (state.sq8) {
|
|
13824
|
-
calibrateAndQuantizeAll(state);
|
|
13825
|
-
}
|
|
13826
|
-
if (liveDocIds.length > WORKER_BUILD_SIZE_THRESHOLD) {
|
|
13827
|
-
const workerResult = await tryWorkerBuild(state, liveDocIds, bufferSnapshot);
|
|
13828
|
-
if (workerResult) return;
|
|
13829
|
-
}
|
|
13830
|
-
const newHnsw = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
|
|
13831
|
-
const CHUNK_SIZE2 = 100;
|
|
13832
|
-
for (let i = 0; i < liveDocIds.length; i++) {
|
|
13833
|
-
const docId = liveDocIds[i];
|
|
13834
|
-
if (!state.store.has(docId) || state.tombstones.has(docId)) continue;
|
|
13835
|
-
newHnsw.insertNode(docId);
|
|
13836
|
-
if ((i + 1) % CHUNK_SIZE2 === 0) {
|
|
13837
|
-
if (state.disposed) return;
|
|
13838
|
-
await yieldToEventLoop();
|
|
13839
|
-
}
|
|
13840
|
-
}
|
|
13841
13906
|
if (state.disposed) return;
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
newHnsw.markTombstone(docId);
|
|
13846
|
-
}
|
|
13847
|
-
}
|
|
13848
|
-
for (const docId of bufferSnapshot) {
|
|
13849
|
-
state.buffer.delete(docId);
|
|
13907
|
+
if (existingGraph !== null) {
|
|
13908
|
+
await growGraph(state, existingGraph, bufferSnapshot);
|
|
13909
|
+
return;
|
|
13850
13910
|
}
|
|
13911
|
+
await promoteToGraph(state);
|
|
13851
13912
|
} finally {
|
|
13852
13913
|
state.building = false;
|
|
13853
13914
|
state.pendingBuild = null;
|
|
13915
|
+
if (state.buffer.size > 0) {
|
|
13916
|
+
scheduleBuild(state);
|
|
13917
|
+
}
|
|
13854
13918
|
}
|
|
13855
13919
|
})();
|
|
13856
13920
|
state.pendingBuild = buildPromise;
|
|
@@ -13870,6 +13934,7 @@ function scheduleBuild(state) {
|
|
|
13870
13934
|
function compact(state) {
|
|
13871
13935
|
if (state.tombstones.size === 0) return;
|
|
13872
13936
|
if (state.hnsw) {
|
|
13937
|
+
state.compactedNodeCount += state.hnsw.tombstoneCount;
|
|
13873
13938
|
state.hnsw.compactTombstones();
|
|
13874
13939
|
}
|
|
13875
13940
|
for (const docId of state.tombstones) {
|
|
@@ -13884,43 +13949,48 @@ function compact(state) {
|
|
|
13884
13949
|
recalibrateFromStore(state);
|
|
13885
13950
|
}
|
|
13886
13951
|
}
|
|
13887
|
-
async function
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13952
|
+
async function insertMissing(state, graph) {
|
|
13953
|
+
const missingOrReplaced = (docId) => !graph.has(docId) || state.buffer.has(docId);
|
|
13954
|
+
await insertIntoGraph(state, graph, allLiveDocIds(state), missingOrReplaced);
|
|
13955
|
+
}
|
|
13956
|
+
async function foldIntoGraph(state) {
|
|
13957
|
+
const rebuildNeeded = graphNeedsRebuild(state);
|
|
13958
|
+
const compactRecalibrates = state.tombstones.size > 0 && state.sq8?.isCalibrated() === true;
|
|
13891
13959
|
compact(state);
|
|
13892
|
-
|
|
13893
|
-
|
|
13894
|
-
if (state.hnsw) {
|
|
13895
|
-
state.hnsw.clear();
|
|
13896
|
-
state.hnsw = null;
|
|
13897
|
-
}
|
|
13960
|
+
if (liveSize(state) === 0) {
|
|
13961
|
+
adoptGraph(state, null);
|
|
13898
13962
|
state.buffer.clear();
|
|
13899
13963
|
if (state.sq8) {
|
|
13900
13964
|
state.sq8.clear();
|
|
13901
13965
|
}
|
|
13902
13966
|
return;
|
|
13903
13967
|
}
|
|
13904
|
-
|
|
13905
|
-
|
|
13968
|
+
const graph = state.hnsw;
|
|
13969
|
+
if (graph === null || rebuildNeeded) {
|
|
13970
|
+
await buildGraphFromStore(state);
|
|
13971
|
+
} else {
|
|
13972
|
+
await insertMissing(state, graph);
|
|
13906
13973
|
}
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
for (const [docId] of state.store.entries()) {
|
|
13910
|
-
if (state.tombstones.has(docId)) continue;
|
|
13911
|
-
liveDocIds.push(docId);
|
|
13974
|
+
if (state.sq8 && state.store.size > 0 && !compactRecalibrates) {
|
|
13975
|
+
recalibrateFromStore(state);
|
|
13912
13976
|
}
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
await yieldToEventLoop();
|
|
13918
|
-
}
|
|
13977
|
+
}
|
|
13978
|
+
async function optimize(state) {
|
|
13979
|
+
while (state.pendingBuild) {
|
|
13980
|
+
await state.pendingBuild;
|
|
13919
13981
|
}
|
|
13920
|
-
state.
|
|
13921
|
-
state.
|
|
13922
|
-
|
|
13923
|
-
|
|
13982
|
+
if (state.disposed) return;
|
|
13983
|
+
state.building = true;
|
|
13984
|
+
const work = foldIntoGraph(state);
|
|
13985
|
+
state.pendingBuild = work;
|
|
13986
|
+
try {
|
|
13987
|
+
await work;
|
|
13988
|
+
} finally {
|
|
13989
|
+
state.building = false;
|
|
13990
|
+
state.pendingBuild = null;
|
|
13991
|
+
if (state.buffer.size > 0) {
|
|
13992
|
+
scheduleBuild(state);
|
|
13993
|
+
}
|
|
13924
13994
|
}
|
|
13925
13995
|
}
|
|
13926
13996
|
function maintenanceStatus(state) {
|
|
@@ -13928,12 +13998,13 @@ function maintenanceStatus(state) {
|
|
|
13928
13998
|
const tombstoneRatio = storeSize > 0 ? state.tombstones.size / storeSize : 0;
|
|
13929
13999
|
const graphCount = state.hnsw ? 1 : 0;
|
|
13930
14000
|
const estimatedCompactMs = Math.round(state.tombstones.size * ESTIMATED_MS_PER_TOMBSTONE * state.dimensionScale);
|
|
13931
|
-
const
|
|
14001
|
+
const optimizeVectorCount = state.hnsw === null || graphNeedsRebuild(state) ? storeSize : state.buffer.size;
|
|
14002
|
+
const estimatedOptimizeMs = Math.round(optimizeVectorCount * ESTIMATED_MS_PER_VECTOR_REBUILD * state.dimensionScale);
|
|
13932
14003
|
return {
|
|
13933
14004
|
tombstoneRatio,
|
|
13934
14005
|
graphCount,
|
|
13935
14006
|
bufferSize: state.buffer.size,
|
|
13936
|
-
building: state.building,
|
|
14007
|
+
building: state.building || state.buildScheduled,
|
|
13937
14008
|
estimatedCompactMs,
|
|
13938
14009
|
estimatedOptimizeMs
|
|
13939
14010
|
};
|
|
@@ -14024,10 +14095,7 @@ function deserialize(state, payload) {
|
|
|
14024
14095
|
state.store.clear();
|
|
14025
14096
|
state.tombstones.clear();
|
|
14026
14097
|
state.buffer.clear();
|
|
14027
|
-
|
|
14028
|
-
state.hnsw.clear();
|
|
14029
|
-
state.hnsw = null;
|
|
14030
|
-
}
|
|
14098
|
+
adoptGraph(state, null);
|
|
14031
14099
|
if (state.sq8) {
|
|
14032
14100
|
state.sq8.clear();
|
|
14033
14101
|
}
|
|
@@ -14052,7 +14120,7 @@ function deserialize(state, payload) {
|
|
|
14052
14120
|
state.sq8 ?? void 0
|
|
14053
14121
|
);
|
|
14054
14122
|
restoredHnsw.deserialize(graphData);
|
|
14055
|
-
state
|
|
14123
|
+
adoptGraph(state, restoredHnsw);
|
|
14056
14124
|
for (let i = 1; i < payload.graphs.length; i++) {
|
|
14057
14125
|
const additionalGraph = payload.graphs[i];
|
|
14058
14126
|
for (const [nodeDocId] of additionalGraph.nodes) {
|
|
@@ -14068,8 +14136,7 @@ function deserialize(state, payload) {
|
|
|
14068
14136
|
}
|
|
14069
14137
|
}
|
|
14070
14138
|
if (state.buffer.size > restoredHnsw.size) {
|
|
14071
|
-
state
|
|
14072
|
-
state.hnsw = null;
|
|
14139
|
+
adoptGraph(state, null);
|
|
14073
14140
|
state.buffer.clear();
|
|
14074
14141
|
for (const [docId] of state.store.entries()) {
|
|
14075
14142
|
if (state.tombstones.has(docId)) continue;
|
|
@@ -14084,6 +14151,87 @@ function deserialize(state, payload) {
|
|
|
14084
14151
|
}
|
|
14085
14152
|
}
|
|
14086
14153
|
|
|
14154
|
+
// src/core/heap.ts
|
|
14155
|
+
function createMaxHeap(compare) {
|
|
14156
|
+
return createHeap2((a, b) => compare(b, a));
|
|
14157
|
+
}
|
|
14158
|
+
function createBoundedMaxHeap(compare, capacity) {
|
|
14159
|
+
const heap = createMaxHeap(compare);
|
|
14160
|
+
const innerPush = heap.push;
|
|
14161
|
+
return {
|
|
14162
|
+
push(item) {
|
|
14163
|
+
if (heap.size < capacity) {
|
|
14164
|
+
innerPush(item);
|
|
14165
|
+
return;
|
|
14166
|
+
}
|
|
14167
|
+
const top = heap.peek();
|
|
14168
|
+
if (top !== void 0 && compare(item, top) < 0) {
|
|
14169
|
+
heap.pop();
|
|
14170
|
+
innerPush(item);
|
|
14171
|
+
}
|
|
14172
|
+
},
|
|
14173
|
+
pop: heap.pop,
|
|
14174
|
+
peek: heap.peek,
|
|
14175
|
+
get size() {
|
|
14176
|
+
return heap.size;
|
|
14177
|
+
},
|
|
14178
|
+
toSortedArray: heap.toSortedArray
|
|
14179
|
+
};
|
|
14180
|
+
}
|
|
14181
|
+
function createHeap2(compare) {
|
|
14182
|
+
const items = [];
|
|
14183
|
+
function siftUp(idx) {
|
|
14184
|
+
while (idx > 0) {
|
|
14185
|
+
const parent = idx - 1 >> 1;
|
|
14186
|
+
if (compare(items[idx], items[parent]) >= 0) break;
|
|
14187
|
+
const tmp = items[idx];
|
|
14188
|
+
items[idx] = items[parent];
|
|
14189
|
+
items[parent] = tmp;
|
|
14190
|
+
idx = parent;
|
|
14191
|
+
}
|
|
14192
|
+
}
|
|
14193
|
+
function siftDown2(idx) {
|
|
14194
|
+
const len = items.length;
|
|
14195
|
+
while (true) {
|
|
14196
|
+
let smallest = idx;
|
|
14197
|
+
const left = 2 * idx + 1;
|
|
14198
|
+
const right = 2 * idx + 2;
|
|
14199
|
+
if (left < len && compare(items[left], items[smallest]) < 0) smallest = left;
|
|
14200
|
+
if (right < len && compare(items[right], items[smallest]) < 0) smallest = right;
|
|
14201
|
+
if (smallest === idx) break;
|
|
14202
|
+
const tmp = items[idx];
|
|
14203
|
+
items[idx] = items[smallest];
|
|
14204
|
+
items[smallest] = tmp;
|
|
14205
|
+
idx = smallest;
|
|
14206
|
+
}
|
|
14207
|
+
}
|
|
14208
|
+
return {
|
|
14209
|
+
push(item) {
|
|
14210
|
+
items.push(item);
|
|
14211
|
+
siftUp(items.length - 1);
|
|
14212
|
+
},
|
|
14213
|
+
pop() {
|
|
14214
|
+
if (items.length === 0) return void 0;
|
|
14215
|
+
const top = items[0];
|
|
14216
|
+
const last = items.pop();
|
|
14217
|
+
if (items.length > 0) {
|
|
14218
|
+
items[0] = last;
|
|
14219
|
+
siftDown2(0);
|
|
14220
|
+
}
|
|
14221
|
+
return top;
|
|
14222
|
+
},
|
|
14223
|
+
peek() {
|
|
14224
|
+
return items[0];
|
|
14225
|
+
},
|
|
14226
|
+
get size() {
|
|
14227
|
+
return items.length;
|
|
14228
|
+
},
|
|
14229
|
+
toSortedArray() {
|
|
14230
|
+
return [...items].sort(compare);
|
|
14231
|
+
}
|
|
14232
|
+
};
|
|
14233
|
+
}
|
|
14234
|
+
|
|
14087
14235
|
// src/vector/vector-index/search.ts
|
|
14088
14236
|
function* bufferCandidates(state, filter) {
|
|
14089
14237
|
for (const docId of state.buffer) {
|
|
@@ -14234,6 +14382,7 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
14234
14382
|
buffer: /* @__PURE__ */ new Set(),
|
|
14235
14383
|
sq8: quantizationMode === "sq8" ? createScalarQuantizer(dimension, store) : null,
|
|
14236
14384
|
hnsw: null,
|
|
14385
|
+
compactedNodeCount: 0,
|
|
14237
14386
|
building: false,
|
|
14238
14387
|
buildScheduled: false,
|
|
14239
14388
|
pendingBuild: null,
|