UNPKG

@claude-flow/embeddings

Version:

V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking

137 lines • 5.49 kB
/** * Embedding quantization — ADR-121 Phase 3. * * Two opt-in quantization paths: * * 1. **int8** — per-vector linear quantization. 4× memory * reduction (float32 → int8). Works with streaming inserts * because each vector encodes independently. Used by * `SearchableEmbeddingCache` when `quantize: 'int8'` is set. * * 2. **RaBitQ** — 1-bit-per-dimension quantization via * `@ruvector/rabitq-wasm`. 32× memory reduction. Build-once * batch API (not streaming) — exposed here as the * `RabitqSnapshot` class for callers that want to compress * a fixed corpus and query it. Recall recovered via an * exact-L2² rerank pool (default 20× the requested k). * * Each path is opt-in + has a measurable acceptance gate: * int8 → ≥0.98 cosine-similarity recall after dequantize * RaBitQ → ≥0.95 recall after rerank, 32× memory reduction * * For int8 the recall acceptance is measured per-vector * (cosine(original, dequant(quant(original)))). For RaBitQ * recall is measured against the top-k from a brute-force baseline. */ import type { CacheSearchHit } from './searchable-embedding-cache.js'; /** * Encoded int8 vector: the int8 buffer + the scale factor needed to * dequantize. Scale is per-vector (each vector's max-abs sets its own * scale) — that's the standard approach for embedding quantization * and preserves recall best for unit-normalized vectors. */ export interface Int8EncodedVector { /** Int8 buffer, same length as the source float32 vector. */ readonly bytes: Int8Array; /** Scale factor: float32 ≈ int8 * scale. */ readonly scale: number; } /** * Encode a float32 vector as int8. Returns the byte buffer + a per-vector * scale that maps int8 back to ~float32 on decode. * * Algorithm: find max-abs value, scale so max-abs maps to 127, round * to nearest int8. This is min-max scaling clamped to [-127, 127] and * is the cheapest path that preserves direction (which is what cosine * similarity cares about for unit-normalized embeddings). */ export declare function int8Encode(vec: Float32Array): Int8EncodedVector; /** * Decode an int8-encoded vector back to float32. Round-trips with * recall ≥0.98 on unit-normalized vectors (the ADR-121 Phase 3 gate). */ export declare function int8Decode(encoded: Int8EncodedVector): Float32Array; /** * Measure round-trip recall for int8 encoding on a corpus. * Recall here = mean cosine similarity between `vec` and * `int8Decode(int8Encode(vec))` across all corpus vectors. * * Used by the CI benchmark to assert the ADR-121 Phase 3 acceptance * gate (≥0.98 recall for int8). */ export declare function measureInt8RoundTripRecall(corpus: Float32Array[]): { meanRecall: number; minRecall: number; maxRecall: number; count: number; }; export interface RabitqSnapshotOptions { /** Embedding dimension. Required. */ readonly dimension: number; /** Random rotation seed (default 0n) — same seed = bit-identical codes. */ readonly seed?: bigint; /** Multiplier on `k` for the exact-L2² rerank pool (default 20). */ readonly rerankFactor?: number; } /** * Snapshot result — what callers get back from `search`. `id` is the * caller-supplied key (string), preserved through the build/search * round-trip via the snapshot's internal id-map. */ export interface RabitqSnapshotHit { readonly id: string; /** Approximate L2² distance after RaBitQ rerank. */ readonly distance: number; /** Original vector (preserved by the snapshot for downstream use). */ readonly vector: Float32Array; } /** * Batch RaBitQ snapshot — compress a corpus of vectors once, then * query against the 1-bit index with exact-L2² rerank for recall. * * Lifecycle: * const snap = await RabitqSnapshot.build(entries, { dimension: 384 }); * const hits = await snap.search(queryVec, 10); * snap.dispose(); * * Memory profile: * raw = N × dim × 4 bytes (float32) * rabitq = N × dim / 8 bytes (1 bit per dim) → 32× smaller * (Plus a per-snapshot id-map for string → numeric position; small.) * * If `@ruvector/rabitq-wasm` isn't installed, `build` throws a * clear named error so callers know which optional peer to install. */ export declare class RabitqSnapshot { private readonly index; private readonly idMap; private readonly vectors; private disposed; private constructor(); static build(entries: Array<{ id: string; vector: Float32Array; }>, options: RabitqSnapshotOptions): Promise<RabitqSnapshot>; /** Query the snapshot for the `k` nearest neighbors of `query`. */ search(query: Float32Array, k: number): Promise<RabitqSnapshotHit[]>; /** Number of vectors in the snapshot. */ get size(): number; /** * Release the WASM index. Idempotent. Memory held by the JS-side * id-map and vectors is GC'd normally. */ dispose(): void; } /** * Measure the *theoretical* memory reduction for a corpus when * encoded with RaBitQ vs raw float32. Returns the byte counts so * callers can publish the metric to the tracker. Doesn't actually * build the index (cheap to compute, deterministic). */ export declare function rabitqMemoryReduction(corpusSize: number, dimension: number): { rawBytes: number; rabitqBytes: number; reductionFactor: number; }; export type { CacheSearchHit }; //# sourceMappingURL=quantization.d.ts.map