sliftutils 1.4.82 → 1.4.84
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/index.d.ts
CHANGED
|
@@ -2372,6 +2372,9 @@ declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" {
|
|
|
2372
2372
|
config: IvfConfig;
|
|
2373
2373
|
count: number;
|
|
2374
2374
|
flat: TransactionSetStore<StoredEmbedding>;
|
|
2375
|
+
byRef: {
|
|
2376
|
+
[ref: string]: Uint8Array;
|
|
2377
|
+
};
|
|
2375
2378
|
steps: {
|
|
2376
2379
|
[step: string]: boolean;
|
|
2377
2380
|
};
|
|
@@ -2393,8 +2396,9 @@ declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" {
|
|
|
2393
2396
|
probeBudget: number;
|
|
2394
2397
|
resultCount: number;
|
|
2395
2398
|
}): SearchHit[] | undefined;
|
|
2399
|
+
export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
|
|
2396
2400
|
export declare function insertEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): undefined;
|
|
2397
|
-
export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>,
|
|
2401
|
+
export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): void;
|
|
2398
2402
|
|
|
2399
2403
|
}
|
|
2400
2404
|
|
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@ async function buildInputs(count: number): Promise<EmbeddingInput[]> {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function freshDatabase(config: IvfConfig): InMemoryDatabase<IvfEmbeddingRoot> {
|
|
36
|
-
const root: IvfEmbeddingRoot = { config, count: 0, flat: {}, steps: {}, centroids: {}, cells: {} };
|
|
36
|
+
const root: IvfEmbeddingRoot = { config, count: 0, flat: {}, byRef: {}, steps: {}, centroids: {}, cells: {} };
|
|
37
37
|
return new InMemoryDatabase<IvfEmbeddingRoot>(root);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -10,6 +10,9 @@ export type IvfEmbeddingRoot = {
|
|
|
10
10
|
config: IvfConfig;
|
|
11
11
|
count: number;
|
|
12
12
|
flat: TransactionSetStore<StoredEmbedding>;
|
|
13
|
+
byRef: {
|
|
14
|
+
[ref: string]: Uint8Array;
|
|
15
|
+
};
|
|
13
16
|
steps: {
|
|
14
17
|
[step: string]: boolean;
|
|
15
18
|
};
|
|
@@ -31,5 +34,6 @@ export declare function searchEmbeddings(database: Database<IvfEmbeddingRoot>, q
|
|
|
31
34
|
probeBudget: number;
|
|
32
35
|
resultCount: number;
|
|
33
36
|
}): SearchHit[] | undefined;
|
|
37
|
+
export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
|
|
34
38
|
export declare function insertEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): undefined;
|
|
35
|
-
export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>,
|
|
39
|
+
export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import cborx from "cbor-x";
|
|
1
2
|
import { Database, namespaceDatabase } from "./Database";
|
|
2
3
|
import { TransactionSetStore, transactionRead, transactionMutate, transactionDelete, replayTransactionStore } from "./transactionSet";
|
|
3
4
|
import { StoredEmbedding, EmbeddingFormat, getCloseness, embeddingToFloat32, releaseFloat32, encodeEmbedding, hashEmbedding } from "../embeddingFormats";
|
|
@@ -17,6 +18,10 @@ export type IvfEmbeddingRoot = {
|
|
|
17
18
|
config: IvfConfig;
|
|
18
19
|
count: number;
|
|
19
20
|
flat: TransactionSetStore<StoredEmbedding>;
|
|
21
|
+
// Direct ref -> embedding index. Each entry is one CBOR blob at its own key, so a lookup or delete is a
|
|
22
|
+
// single key-value op (no transaction-set replay) and deleteData can drop it (it's a primitive leaf). Refs
|
|
23
|
+
// and their embeddings never move, so a rebuild leaves this untouched.
|
|
24
|
+
byRef: { [ref: string]: Uint8Array };
|
|
20
25
|
steps: { [step: string]: boolean };
|
|
21
26
|
centroids: TransactionSetStore<StoredEmbedding>;
|
|
22
27
|
cells: { [cellId: string]: TransactionSetStore<StoredEmbedding> };
|
|
@@ -26,6 +31,14 @@ export type EmbeddingInput = { ref: string; embedding: StoredEmbedding };
|
|
|
26
31
|
export type SearchHit = { ref: string; closeness: number };
|
|
27
32
|
type CellEntry = { ref: string; embedding: StoredEmbedding };
|
|
28
33
|
|
|
34
|
+
const embeddingCbor = new cborx.Encoder({ structuredClone: true });
|
|
35
|
+
function encodeRefValue(embedding: StoredEmbedding): Uint8Array {
|
|
36
|
+
return embeddingCbor.encode(embedding) as Uint8Array;
|
|
37
|
+
}
|
|
38
|
+
function decodeRefValue(value: Uint8Array): StoredEmbedding {
|
|
39
|
+
return embeddingCbor.decode(value) as StoredEmbedding;
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
// Below this many embeddings we skip the IVF entirely (flat store). 1024 = a clean power of two.
|
|
30
43
|
const FLAT_LIMIT = 1024;
|
|
31
44
|
// Force a from-scratch rebuild once at each of these (multiples of 4 above FLAT_LIMIT, up to ~16k), gated by a step flag so each runs at most once ever.
|
|
@@ -239,6 +252,24 @@ export function searchEmbeddings(
|
|
|
239
252
|
return hits.slice(0, options.resultCount);
|
|
240
253
|
}
|
|
241
254
|
|
|
255
|
+
// Direct ref -> stored embedding lookup through the byRef index: one key-value read per ref, no cell scan. A
|
|
256
|
+
// search returns refs; this fetches the data behind them. Skips refs that aren't present; undefined if not synced.
|
|
257
|
+
export function lookupEmbeddings(
|
|
258
|
+
database: Database<IvfEmbeddingRoot>,
|
|
259
|
+
refs: string[],
|
|
260
|
+
): Map<string, StoredEmbedding> | undefined {
|
|
261
|
+
const values = database.readData(root => refs.map(ref => root.byRef[ref]));
|
|
262
|
+
if (!values) return undefined;
|
|
263
|
+
const result = new Map<string, StoredEmbedding>();
|
|
264
|
+
for (let index = 0; index < refs.length; index++) {
|
|
265
|
+
const value = values[index];
|
|
266
|
+
if (value) {
|
|
267
|
+
result.set(refs[index], decodeRefValue(value));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
242
273
|
export function insertEmbeddings(
|
|
243
274
|
database: Database<IvfEmbeddingRoot>,
|
|
244
275
|
items: EmbeddingInput[],
|
|
@@ -255,6 +286,9 @@ export function insertEmbeddings(
|
|
|
255
286
|
if (!steps[STEP_IVF]) {
|
|
256
287
|
const flatWrites = items.map(item => ({ key: item.ref, value: item.embedding }));
|
|
257
288
|
transactionMutate(flatStore(database), flatWrites);
|
|
289
|
+
for (const item of items) {
|
|
290
|
+
database.writeData(root => root.byRef[item.ref], encodeRefValue(item.embedding));
|
|
291
|
+
}
|
|
258
292
|
database.writeData(root => root.count, newCount);
|
|
259
293
|
if (newCount > FLAT_LIMIT) {
|
|
260
294
|
rebuildStructure(database);
|
|
@@ -279,6 +313,9 @@ export function insertEmbeddings(
|
|
|
279
313
|
const memberWrites = group.map(item => ({ key: item.ref, value: item.embedding }));
|
|
280
314
|
transactionMutate(cellStore(database, cellId), memberWrites);
|
|
281
315
|
}
|
|
316
|
+
for (const item of items) {
|
|
317
|
+
database.writeData(root => root.byRef[item.ref], encodeRefValue(item.embedding));
|
|
318
|
+
}
|
|
282
319
|
database.writeData(root => root.count, newCount);
|
|
283
320
|
|
|
284
321
|
|
|
@@ -305,18 +342,21 @@ export function insertEmbeddings(
|
|
|
305
342
|
|
|
306
343
|
export function removeEmbeddings(
|
|
307
344
|
database: Database<IvfEmbeddingRoot>,
|
|
308
|
-
|
|
345
|
+
refs: string[],
|
|
309
346
|
) {
|
|
310
|
-
if (!
|
|
347
|
+
if (!refs.length) return;
|
|
311
348
|
const steps = database.readData(root => root.steps);
|
|
312
349
|
const count = database.readData(root => root.count);
|
|
313
350
|
if (!steps) return;
|
|
314
351
|
if (count === undefined) return;
|
|
315
352
|
|
|
316
353
|
if (!steps[STEP_IVF]) {
|
|
317
|
-
const flatDeletes =
|
|
354
|
+
const flatDeletes = refs.map(ref => ({ key: ref, value: undefined }));
|
|
318
355
|
transactionMutate(flatStore(database), flatDeletes);
|
|
319
|
-
|
|
356
|
+
for (const ref of refs) {
|
|
357
|
+
database.deleteData(root => root.byRef[ref]);
|
|
358
|
+
}
|
|
359
|
+
database.writeData(root => root.count, Math.max(0, count - refs.length));
|
|
320
360
|
return;
|
|
321
361
|
}
|
|
322
362
|
|
|
@@ -324,11 +364,20 @@ export function removeEmbeddings(
|
|
|
324
364
|
if (!centroids) return;
|
|
325
365
|
if (!centroids.size) return;
|
|
326
366
|
|
|
367
|
+
// Look up each ref's embedding through byRef, then rank its cells the way an insert would, so we delete from
|
|
368
|
+
// the cell it actually landed in (plus a few neighbours, in case a rebuild nudged it across a boundary).
|
|
369
|
+
const refValues = database.readData(root => refs.map(ref => root.byRef[ref]));
|
|
370
|
+
if (!refValues) return;
|
|
327
371
|
const candidatesByItem: { ref: string; cellIds: string[] }[] = [];
|
|
328
372
|
const candidateSet = new Set<string>();
|
|
329
|
-
for (
|
|
330
|
-
const
|
|
331
|
-
|
|
373
|
+
for (let index = 0; index < refs.length; index++) {
|
|
374
|
+
const value = refValues[index];
|
|
375
|
+
if (!value) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const embedding = decodeRefValue(value);
|
|
379
|
+
const cellIds = rankCellsByCloseness(embedding, centroids).slice(0, 1 + DELETE_FALLBACK_CELLS);
|
|
380
|
+
candidatesByItem.push({ ref: refs[index], cellIds });
|
|
332
381
|
for (const cellId of cellIds) {
|
|
333
382
|
candidateSet.add(cellId);
|
|
334
383
|
}
|
|
@@ -343,30 +392,33 @@ export function removeEmbeddings(
|
|
|
343
392
|
}
|
|
344
393
|
|
|
345
394
|
const deletesByCell = new Map<string, string[]>();
|
|
395
|
+
const deletedRefs: string[] = [];
|
|
346
396
|
for (const candidate of candidatesByItem) {
|
|
347
397
|
for (const cellId of candidate.cellIds) {
|
|
348
398
|
const members = membersByCell.get(cellId);
|
|
349
399
|
if (!members || !members.has(candidate.ref)) {
|
|
350
400
|
continue;
|
|
351
401
|
}
|
|
352
|
-
let
|
|
353
|
-
if (!
|
|
354
|
-
|
|
355
|
-
deletesByCell.set(cellId,
|
|
402
|
+
let cellRefs = deletesByCell.get(cellId);
|
|
403
|
+
if (!cellRefs) {
|
|
404
|
+
cellRefs = [];
|
|
405
|
+
deletesByCell.set(cellId, cellRefs);
|
|
356
406
|
}
|
|
357
|
-
|
|
407
|
+
cellRefs.push(candidate.ref);
|
|
408
|
+
deletedRefs.push(candidate.ref);
|
|
358
409
|
break;
|
|
359
410
|
}
|
|
360
411
|
}
|
|
361
412
|
|
|
362
|
-
let deletedCount = 0;
|
|
363
413
|
for (const cellId of deletesByCell.keys()) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
const memberDeletes = refs.map(ref => ({ key: ref, value: undefined }));
|
|
414
|
+
const cellRefs = deletesByCell.get(cellId)!;
|
|
415
|
+
const memberDeletes = cellRefs.map(ref => ({ key: ref, value: undefined }));
|
|
367
416
|
transactionMutate(cellStore(database, cellId), memberDeletes);
|
|
368
417
|
}
|
|
369
|
-
|
|
370
|
-
database.
|
|
418
|
+
for (const ref of deletedRefs) {
|
|
419
|
+
database.deleteData(root => root.byRef[ref]);
|
|
420
|
+
}
|
|
421
|
+
if (deletedRefs.length) {
|
|
422
|
+
database.writeData(root => root.count, Math.max(0, count - deletedRefs.length));
|
|
371
423
|
}
|
|
372
424
|
}
|