Skip to content

Commit 8fb4d42

Browse files
partychenCopilot
andauthored
Migrate disk PQ flat scan to flat API (#1341)
- [x] Does this PR have a descriptive title that could go in our release notes? **Yes.** - [ ] Does this PR add any new dependencies? **No.** - [ ] Does this PR modify any existing APIs? **No.** The query-aware flat-search API was introduced separately in #1359; this PR adopts it in `diskann-disk`. - [x] Is the change to the API backwards compatible? **Yes.** Existing disk search modes and result semantics are preserved. - [x] Should this result in any changes to our documentation, either updating existing docs or adding new ones? **Yes.** The affected implementation rustdoc is updated. #### Reference Issues/PRs Built on the query-aware flat-search API merged in #1359. #### What does this implement/fix? Briefly explain your changes. - Replaces the disk-specific manual PQ flat-scan pipeline with the shared flat k-NN API. - Implements `DistancesUnordered` on `DiskAccessor` to expose complete, batched PQ-distance scanning. - Initializes query-dependent PQ state after every pooled scratch checkout. - Preserves scan-time filtering before approximate top-k selection and full-precision reranking afterward. - Preserves the existing pooled scratch and indexed-vector result behavior across graph and flat search modes. The disk backend constructs a query-aware `DiskAccessor` and passes it directly to `flat::knn_search`. The generic flat layer now owns top-k selection, comparison accounting, error escalation, and post-processing. `DiskAccessor` continues to own disk-specific PQ preprocessing, batching, filtering, data access, and distance computation. #### Any other comments? This PR has been rebased onto `main` after #1359 merged. Its diff is limited to the two `diskann-disk` implementation files. #### Architecture simplification Before this change, disk flat search manually coordinated filtering, batching, PQ-distance collection, top-k selection, comparison accounting, and post-processing inside `DiskANNIndex::flat_search`. Graph and flat search already used the same `DiskAccessor` and scratch pool, but the flat algorithm duplicated orchestration now provided by the shared flat API. ```mermaid flowchart TB subgraph Before["Before: disk-specific flat orchestration"] direction LR F1["FlatScan"] --> M["DiskANNIndex::flat_search"] M --> FI["filter IDs"] FI --> B["manual batch loop"] B --> PQ1["DiskAccessor::pq_distances"] PQ1 --> K1["local NeighborPriorityQueue"] K1 --> PP1["disk post-processor"] end subgraph After["After: shared flat orchestration"] direction LR F2["FlatScan"] --> K2["flat::knn_search"] K2 --> DU["DiskAccessor<br/>DistancesUnordered"] DU --> PQ2["filtered, batched PQ scan"] K2 --> TK["shared top-k · stats · errors"] TK --> PP2["RerankAndFilter"] end G["Graph search"] --> SA["DiskAccessor<br/>SearchAccessor"] DU --> S["pooled DiskSearchScratch<br/>per-query PQ preparation"] SA --> S ``` `DiskAccessor` now exposes the disk scan through `DistancesUnordered`, allowing `flat::knn_search` to drive the common k-NN workflow while graph traversal continues to use the existing `SearchAccessor` implementation. Both paths preserve their distinct filtering stages and share the same pooled query-state lifecycle. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 149ce76 commit 8fb4d42

3 files changed

Lines changed: 307 additions & 148 deletions

File tree

diskann-disk/src/search/pq/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ pub use pq_scratch::PQScratch;
1111
pub(crate) use crate::storage::quant::pq::PQData;
1212

1313
mod quantizer_preprocess;
14-
pub use quantizer_preprocess::quantizer_preprocess;

diskann-disk/src/search/pq/quantizer_preprocess.rs

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,51 @@
66
use diskann::ANNResult;
77
use diskann_vector::distance::Metric;
88

9-
use diskann_providers::model::compute_pq_distance;
109
use diskann_providers::utils::BridgeErr;
1110

1211
use super::{PQData, PQScratch};
1312

14-
/// Preprocesses the query vector for PQ distance calculations.
15-
/// This function rotates the query vector and prepares the PQ table distances
16-
/// for efficient computation during search operations.
17-
pub fn quantizer_preprocess(
18-
pq_scratch: &mut PQScratch,
19-
pq_data: &PQData,
20-
metric: Metric,
21-
id_to_calculate_pq_distance: &[u32],
22-
) -> ANNResult<()> {
23-
let table = pq_data.pq_table();
24-
let expected_len = table.ncenters() * table.nchunks();
25-
let dst = diskann_utils::views::MutMatrixView::try_from(
26-
&mut (*pq_scratch.aligned_pqtable_dist_scratch)[..expected_len],
27-
table.nchunks(),
28-
table.ncenters(),
29-
)
30-
.bridge_err()?;
31-
32-
match metric {
33-
// Prior to the introduction of the `quantizer_preprocess` method, the
34-
// disk index was hard-coded to use L2 distance for comparisons.
35-
//
36-
// We're keeping that behavior here - treating `Cosine` and `CosineNormalized`
37-
// as L2 until a more thorough evaluation can be made.
38-
Metric::L2 | Metric::Cosine | Metric::CosineNormalized => {
39-
table.process_into::<diskann_quantization::distances::SquaredL2>(
40-
&pq_scratch.query_scratch,
41-
dst,
42-
);
43-
}
44-
Metric::InnerProduct => {
45-
table.process_into::<diskann_quantization::distances::InnerProduct>(
46-
&pq_scratch.query_scratch,
47-
dst,
48-
);
49-
}
13+
impl PQScratch {
14+
pub(crate) fn prepare_query(
15+
&mut self,
16+
pq_data: &PQData,
17+
metric: Metric,
18+
query: &[f32],
19+
) -> ANNResult<()> {
20+
self.set(query)?;
21+
self.preprocess_query(pq_data, metric)
5022
}
5123

52-
// Compute the pq distance between query vector to all the vertex in the pq
53-
// calculation id scratch.
54-
compute_pq_distance(
55-
id_to_calculate_pq_distance,
56-
pq_data.get_num_chunks(),
57-
&pq_scratch.aligned_pqtable_dist_scratch,
58-
pq_data.pq_compressed_data().as_slice(),
59-
&mut pq_scratch.aligned_pq_coord_scratch,
60-
&mut pq_scratch.aligned_dist_scratch,
61-
)?;
24+
fn preprocess_query(&mut self, pq_data: &PQData, metric: Metric) -> ANNResult<()> {
25+
let table = pq_data.pq_table();
26+
let expected_len = table.ncenters() * table.nchunks();
27+
let dst = diskann_utils::views::MutMatrixView::try_from(
28+
&mut self.aligned_pqtable_dist_scratch[..expected_len],
29+
table.nchunks(),
30+
table.ncenters(),
31+
)
32+
.bridge_err()?;
6233

63-
Ok(())
34+
match metric {
35+
// Prior to moving query preprocessing onto `PQScratch`, the disk index
36+
// was hard-coded to use L2 distance for comparisons.
37+
//
38+
// We're keeping that behavior here - treating `Cosine` and `CosineNormalized`
39+
// as L2 until a more thorough evaluation can be made.
40+
Metric::L2 | Metric::Cosine | Metric::CosineNormalized => {
41+
table.process_into::<diskann_quantization::distances::SquaredL2>(
42+
&self.query_scratch,
43+
dst,
44+
);
45+
}
46+
Metric::InnerProduct => {
47+
table.process_into::<diskann_quantization::distances::InnerProduct>(
48+
&self.query_scratch,
49+
dst,
50+
);
51+
}
52+
}
53+
54+
Ok(())
55+
}
6456
}

0 commit comments

Comments
 (0)