Skip to content

Commit 8decacf

Browse files
committed
Replace PQ with IVF-PQ
1 parent 33206e5 commit 8decacf

10 files changed

Lines changed: 537 additions & 132 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ await writeVectors({
6666
normalize: true, // L2-normalize on write; lets search skip sqrt for cosine
6767
binary: true, // also write 1-bit-per-dim sign column for binary+rerank search
6868
clusters: 128, // k-means clusters for phase-1 pruning (implies binary: true)
69-
pq: true, // optional product-quantized codes for approximate scoring before rerank
69+
pq: true, // optional IVF-PQ index for approximate scoring before rerank
7070
vectors: myEmbedder(), // any sync or async iterable of { id, vector }
7171
})
7272
```
@@ -181,7 +181,7 @@ Core columns: `id` (STRING), `vector` (`FIXED_LEN_BYTE_ARRAY(4 × dim)`, raw flo
181181

182182
A `cachedAsyncBuffer` deduplicates footer / offset-index byte ranges across all the parallel `parquetRead` calls.
183183

184-
**PQ + rerank path** (`algorithm: 'pq'`, or `auto` when a file has PQ but no binary column): scan compact `vector_pq` codes over the selected cluster ranges, approximate-score candidates with lookup tables built from the query and stored PQ codebooks, then fetch full float32 vectors only for the candidate pool and exact-rerank as above. When `clusters > 0`, PQ uses the same contiguous cluster row ranges as the binary path.
184+
**IVF-PQ + rerank path** (`algorithm: 'pq'`, or `auto` when a file has PQ but no binary column): rank stored float IVF centroids against the query, scan compact residual `vector_pq` codes over the selected IVF row groups, approximate-score candidates with lookup tables built from the query, IVF centroid, and residual PQ codebooks, then fetch full float32 vectors only for the candidate pool and exact-rerank as above. IVF-PQ uses its own row ordering and should not be combined with binary `clusters`.
185185

186186
For pre-normalized vectors with `metric: 'cosine'`, the search normalizes the query once and scores via dot product to skip the per-candidate sqrt loop.
187187

@@ -208,9 +208,13 @@ Key-value metadata:
208208
| `hypvector.clusters` | number of k-means clusters (0 if not clustered) |
209209
| `hypvector.centroids` | base64-encoded centroid binary codes (`clusters × dim/8` bytes); present when `clusters > 0` |
210210
| `hypvector.clusterCounts` | base64-encoded `Uint32Array` of per-cluster row counts; present when `clusters > 0` |
211+
| `hypvector.pq.mode` | `ivf`; present when `pq: true` |
211212
| `hypvector.pq.segments` | number of PQ sub-vectors / bytes per code; present when `pq: true` |
212213
| `hypvector.pq.centroids` | centroids per PQ sub-vector; present when `pq: true` |
213-
| `hypvector.pq.codebooks` | base64-encoded `Float32Array` codebooks (`pq.centroids × dim` floats); present when `pq: true` |
214+
| `hypvector.pq.codebooks` | base64-encoded residual `Float32Array` codebooks (`pq.centroids × dim` floats); present when `pq: true` |
215+
| `hypvector.ivf.clusters` | number of non-empty IVF lists; present when `pq: true` |
216+
| `hypvector.ivf.centroids` | base64-encoded float IVF centroids (`ivf.clusters × dim` float32 values); present when `pq: true` |
217+
| `hypvector.ivf.counts` | base64-encoded `Uint32Array` of per-IVF-list row counts; present when `pq: true` |
214218

215219
### CLI
216220

bin/inspect.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export async function inspect({ path }) {
2626
if (meta.hasPq) {
2727
console.log(`PQ segments: ${meta.pqSegments}`)
2828
console.log(`PQ centroids: ${meta.pqCentroids}`)
29+
console.log(`IVF clusters: ${meta.ivfClusters}`)
2930
}
3031
console.log(`Row groups: ${metadata.row_groups.length.toLocaleString()}`)
3132
console.log(`Raw float32 size: ${rawSize.toLocaleString()} bytes`)

scripts/ablation.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* A) base vector + id only (search must use exact full scan)
88
* B) +binary adds vector_bin column (binary phase 1 + per-cand phase 2 reads)
99
* C) +cluster B plus k-means clustering + centroids/counts KV
10-
* D) +PQ C plus vector_pq column + PQ codebooks
10+
* D) IVF-PQ vector_pq column + IVF centroids + residual PQ codebooks
1111
*
1212
* Page size is held at 32 KB for B-D so we isolate the feature contribution
1313
* from the page-size knob.
@@ -41,7 +41,7 @@ const variants = [
4141
{ name: 'A_base', label: 'A) base (vec only)', opts: { binary: false } },
4242
{ name: 'B_binary', label: 'B) +binary', opts: { binary: true } },
4343
{ name: 'C_cluster', label: 'C) +cluster', opts: { binary: true, clusters: 128 } },
44-
{ name: 'D_pq', label: 'D) +cluster+PQ', opts: { binary: true, clusters: 128, pq: true }, search: { algorithm: 'pq' } },
44+
{ name: 'D_ivfpq', label: 'D) IVF-PQ', opts: { pq: true, ivfClusters: 128 }, search: { algorithm: 'pq' } },
4545
]
4646

4747
for (const v of variants) {

src/constants.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@ export const defaultClusterIterations = 6
3333
// file has cluster metadata. Lower = faster but lower recall.
3434
export const defaultClusterProbeFraction = 0.25
3535

36-
// Default product quantization settings. The initial PQ path stores one
37-
// code byte per segment, with values in [0, defaultPqCentroids).
36+
// Default residual product quantization settings. The IVF-PQ path stores
37+
// one code byte per segment, with values in [0, defaultPqCentroids).
3838
export const defaultPqSegments = 32
39-
export const defaultPqCentroids = 16
39+
export const defaultPqCentroids = 64
4040
export const defaultPqIterations = 8
4141
export const defaultPqSampleSize = 4096
42+
43+
// Default IVF coarse quantizer settings for the IVF-PQ path.
44+
export const defaultIvfClusters = 128
45+
export const defaultIvfIterations = 6
46+
export const defaultIvfSampleSize = 4096

0 commit comments

Comments
 (0)