Repository navigation
[diskann-inmem] Add support for Product Quantization - #1469
Conversation
0a45cfa to
f9ddb82
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1469 +/- ##
==========================================
+ Coverage 91.03% 92.11% +1.08%
==========================================
Files 586 590 +4
Lines 119926 121332 +1406
==========================================
+ Hits 109176 111767 +2591
+ Misses 10750 9565 -1185
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Benchmarking JSON{
"search_directories": [
"redacted"
],
"output_directory": null,
"jobs": [
{
"type": "inmem2",
"content": {
"build": {
"alpha": 1.2,
"l_build": 50,
"max_degree": 42,
"num_threads": 8,
"pruned_degree": 32
},
"data": {
"data": "wikipedia/wikipedia_base_100K.bin",
"data_type": "float32",
"distance": "inner_product"
},
"search": {
"groundtruth": "wikipedia/wikipedia-100k.gt.bin",
"num_threads": [
8
],
"queries": "wikipedia/wikipedia_query.bin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
]
},
"quantization": {
"product": {
"chunks": 192,
"centers": 256,
"seed": 1
}
}
}
},
{
"type": "inmem2",
"content": {
"build": {
"alpha": 1.2,
"l_build": 50,
"max_degree": 42,
"num_threads": 8,
"pruned_degree": 32
},
"data": {
"data": "msturing/msturing-base-100k.fbin",
"data_type": "float32",
"distance": "squared_l2"
},
"search": {
"groundtruth": "msturing/msturing-100k.gt.10k.bin",
"num_threads": [
8
],
"queries": "msturing/queries-10k.fbin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
]
},
"quantization": {
"product": {
"chunks": 25,
"centers": 256,
"seed": 1
}
}
}
},
{
"type": "inmem2",
"content": {
"build": {
"alpha": 1.2,
"l_build": 50,
"max_degree": 42,
"num_threads": 8,
"pruned_degree": 32
},
"data": {
"data": "openai-v3/base-100k.bin",
"data_type": "float32",
"distance": "cosine"
},
"search": {
"groundtruth": "openai-v3/gt-100k.bin",
"num_threads": [
8
],
"queries": "openai-v3/query.bin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
]
},
"quantization": {
"product": {
"chunks": 384,
"centers": 256,
"seed": 1
}
}
}
},
{
"type": "graph-index-build-pq",
"content": {
"index_operation": {
"search_phase": {
"groundtruth": "wikipedia/wikipedia-100k.gt.bin",
"num_threads": [
8
],
"queries": "wikipedia/wikipedia_query.bin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
],
"search-type": "topk"
},
"source": {
"alpha": 1.2000000476837158,
"backedge_ratio": 1.0,
"data": "wikipedia/wikipedia_base_100K.bin",
"data_type": "float32",
"distance": "inner_product",
"index-source": "Build",
"insert_retry": null,
"l_build": 50,
"max_degree": 32,
"multi_insert": null,
"num_threads": 8,
"save_path": null,
"start_point_strategy": "medoid"
}
},
"max_fp_vecs_per_prune": 0,
"num_pq_chunks": 196,
"seed": 13076402859301299683,
"use_fp_for_search": false
}
},
{
"type": "graph-index-build-pq",
"content": {
"index_operation": {
"search_phase": {
"groundtruth": "msturing/msturing-100k.gt.10k.bin",
"num_threads": [
8
],
"queries": "msturing/queries-10k.fbin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
],
"search-type": "topk"
},
"source": {
"alpha": 1.2000000476837158,
"backedge_ratio": 1.0,
"data": "msturing/msturing-base-100k.fbin",
"data_type": "float32",
"distance": "squared_l2",
"index-source": "Build",
"insert_retry": null,
"l_build": 50,
"max_degree": 32,
"multi_insert": null,
"num_threads": 8,
"save_path": null,
"start_point_strategy": "medoid"
}
},
"max_fp_vecs_per_prune": 0,
"num_pq_chunks": 25,
"seed": 13076402859301299683,
"use_fp_for_search": false
}
},
{
"type": "graph-index-build-pq",
"content": {
"index_operation": {
"search_phase": {
"groundtruth": "openai-v3/gt-100k.bin",
"num_threads": [
8
],
"queries": "openai-v3/query.bin",
"reps": 3,
"runs": [
{
"recall_k": 10,
"search_l": [
10,
20,
30,
40,
50,
60,
70,
80,
90,
100
],
"search_n": 10
}
],
"search-type": "topk"
},
"source": {
"alpha": 1.2000000476837158,
"backedge_ratio": 1.0,
"data": "openai-v3/base-100k.bin",
"data_type": "float32",
"distance": "cosine",
"index-source": "Build",
"insert_retry": null,
"l_build": 50,
"max_degree": 32,
"multi_insert": null,
"num_threads": 8,
"save_path": null,
"start_point_strategy": "medoid"
}
},
"max_fp_vecs_per_prune": 0,
"num_pq_chunks": 384,
"seed": 13076402859301299683,
"use_fp_for_search": false
}
}
]
}
|
There was a problem hiding this comment.
🟡 Changes recommended
A duplicated match arm can fail warnings-as-errors Clippy checks, alongside several documentation and diagnostic issues.
5 open findings
What changed in this PR
Adds Product Quantization support to diskann-inmem, including shared reranking infrastructure, integration coverage, and benchmarking.
Changes:
- Implements PQ search, insertion, pruning, metrics, reranking, and distance-table pooling.
- Refactors spherical quantization to share internal metric and reranking logic.
- Adds integration baselines and benchmark support.
| File | Description |
|---|---|
diskann-utils/src/object_pool.rs |
Adds empty capacity-bounded pool construction. |
diskann-inmem/src/repr/test/mod.rs |
Shares quantized representation test helpers. |
diskann-inmem/src/repr/spherical.rs |
Adopts shared quantization internals. |
diskann-inmem/src/repr/product.rs |
Implements the PQ representation. |
diskann-inmem/src/repr/mod.rs |
Exports PQ APIs. |
diskann-inmem/src/repr/internal/quantization/rerank.rs |
Adds shared reranking logic. |
diskann-inmem/src/repr/internal/quantization/mod.rs |
Adds internal metric conversions. |
diskann-inmem/src/repr/internal/mod.rs |
Registers quantization internals. |
diskann-inmem/integration/main.rs |
Registers PQ regression tests. |
diskann-inmem/integration/jsons/graph/product/l2.json |
Configures L2 PQ tests. |
diskann-inmem/integration/jsons/graph/product/l2-baseline.json |
Records L2 baselines. |
diskann-inmem/integration/jsons/graph/product/ip.json |
Configures inner-product PQ tests. |
diskann-inmem/integration/jsons/graph/product/ip-baseline.json |
Records inner-product baselines. |
diskann-inmem/integration/jsons/graph/product/cosine.json |
Configures cosine PQ tests. |
diskann-inmem/integration/jsons/graph/product/cosine-baseline.json |
Records cosine baselines. |
diskann-inmem/integration/index/runner.rs |
Builds PQ integration indexes. |
diskann-inmem/integration/index/object.rs |
Enables PQ index dispatch. |
diskann-benchmark/src/index/inmem2.rs |
Adds PQ benchmark training and execution. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
514ba3a to
4711c41
Compare
4711c41 to
e153d29
Compare
e153d29 to
f110aec
Compare
Jack Moffitt (metajack)
left a comment
There was a problem hiding this comment.
Here are comments for the first three commits. Will finish on Monday.
1cac598 to
88b694e
Compare
88b694e to
aa5f142
Compare
aa5f142 to
0aa580f
Compare
This adds support for scalar quantization (bits 1 through 8) to `diskann-inmem`, follow the pattern mostly set up in #1469. This does require the `Quantizer` trait to differentiate between distances for insert and distances for general queries due to the need to rescale queries for wiki-cohere embedding. I've found several areas in `diskann-quantization` that need help: 1. Compression and distance methods should not panic on invalid vector lengths - they should return error types instead (that would remove some of the semi-redundant length checking in this PR). 2. `diskann-quantization` needs better support for asymmetrical compression. It's really a shame that we need to handle the somewhat sketch query rescaling for inner-products manually.



Add support for PQ for
diskann-inmem. This follows the work in #1458 and #1432.For reviewers, this PR is broken into 6 commits:
Commit 1
Adds a
with_capacityconstructor toObjectPoolto create an initially empty pool. This is used for distance table buffering in the PQ repr.Commit 2
Factors out
Rerankertorepr/internal/quantization/rerank.rs. This will be shared between spherical and PQ. In addition, an internalMetricenum is introduced for the three currently supported metrics:SquaredL2,InnerProduct, andCosine.I kept
spherical::Rerankas a public version of the internalRerankenum to try to decouple the public configuration API from how things are done on the back end. Yes, this is a little bit of code duplication, but keeps the API looking likeThe amount of duplication is small, keeps the interface consistent, and lets us experiment on the back end a little.
Additionally, the routines for
test_expand_beamandtest_pruneare moved out of thesphericaltest module and intorepr/test/mod.rs. These will be reused by the PQ representation.Commit 3
Adds a generic
Quantizedrepresentation. The extension point is a [Quantizer] trait which contains methods for quantizing data and creating distance computers. Distance computation uses BYOTE to allow implementations to create multiple specialized computers as they see fit. These specialized computers are inlined directly into theExpandBeamandPrunetrait objects used for search and prune respectively.The goal behind this approach is to make it easy to implement scalar, minmax, and binary quantizers (since we just need to implement the
Quantizertrait) and to make development of new quantizers easy. And hopefully BYOTE means that the performance cost of this is minimal.One final advantage is that the
Quantizedrepresentation is a single concrete type, minimizing monomorphization bloat of this approach.Commit 4
This actually adds the PQ
Quantizer.Query distances are done using the usual trick of pre-populating a lookup table with distances between query chunk fragments and the PQ pivots (see #1458 for the implementation). Since these tables are fairly large (~200KB for a 192 PQ chunks), an
ObjectPoolis used to reuse allocations. The configuration for this pool inConfigis such that an unboundedObjectPoolis not allowed.Quant-quant distances use the
PaddedTableintroduced in #1458.Tests are similar to spherical, but significantly easier since PQ can losslessly represent the test dataset.
Commit 5
Adds integration test support for PQ and baselines covering L2, inner-product, and cosine.
Commit 6
Adds support in
diskann-benchmarkfor the new PQ backend.Benchmarks
The infrastructure in #1458 pays off. This is considerably faster than the PQ index in
diskann-providers. A benchmarking input JSON is included in the comment - highlights are reported here.Note that the performance increase is somewhat exaggerated due to the small size of the dataset (less memory bandwidth because more fits in cache), which highlights perf differences that a bandwidth-constrained environment would hide.