Repository navigation
Implement filter search for disk path using AdpativeL #1173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Yaohong Deng (dyhyfu)
merged 17 commits into
main
from
u/yaohongdeng/impl_adaptiveL_disk
Jul 1, 2026
Merged
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
e176e43
Implement filter search for disk path using AdpativeL
1e82dd7
Use SearchPlan to avoid invalid parameter combinations
2d239f5
Add SearchPlan file
fc0b540
resolve comments
9ebc0f6
Merge branch 'main' into u/yaohongdeng/impl_adaptiveL_disk
0bdbe63
Adapt InlineFilterSearch to new API
be79ce5
rename SearchPlan to SearchMode
f86d7bc
update SearchMode in benchmark
d1dcec3
fix format issue
e1c60fa
Merge branch 'main' into u/yaohongdeng/impl_adaptiveL_disk
bf11a17
Eliminate inner dyn-Fn dispatch in InlineFilter label provider
631bdcb
Add PostporcessStrategy for more clear code logic for None means Acce…
a1a804e
merge from main
8c06b03
fix format issue
aeda982
Add a json deserializable struct in benchmark module for SearchMode
1f77321
remove unused import
2853447
Add tolerance for old is_flat_search input in benchmark
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| { | ||
| "search_directories": [ | ||
| "test_data/disk_index_search" | ||
| ], | ||
| "jobs": [ | ||
| { | ||
| "type": "disk-index", | ||
| "content": { | ||
| "source": { | ||
| "disk-index-source": "Build", | ||
| "data_type": "float32", | ||
| "data": "disk_index_siftsmall_learn_256pts_data.fbin", | ||
| "distance": "squared_l2", | ||
| "dim": 128, | ||
| "max_degree": 32, | ||
| "l_build": 50, | ||
| "num_threads": 1, | ||
| "build_ram_limit_gb": 2.0, | ||
| "num_pq_chunks": 128, | ||
| "quantization_type": "FP", | ||
| "save_path": "siftsmall_index_filter_graph" | ||
| }, | ||
| "search_phase": { | ||
| "queries": "disk_index_sample_query_10pts.fbin", | ||
| "groundtruth": "disk_index_10pts_idx_uint32_truth_search_filter_res.bin", | ||
| "search_list": [10, 20, 40], | ||
| "beam_width": 4, | ||
| "recall_at": 10, | ||
| "num_threads": 1, | ||
| "is_flat_search": false, | ||
| "distance": "squared_l2", | ||
| "vector_filters_file": "disk_index_10pts_idx_uint32_range_res_r_100000.bin" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "type": "disk-index", | ||
| "content": { | ||
| "source": { | ||
| "disk-index-source": "Build", | ||
| "data_type": "float32", | ||
| "data": "disk_index_siftsmall_learn_256pts_data.fbin", | ||
| "distance": "squared_l2", | ||
| "dim": 128, | ||
| "max_degree": 32, | ||
| "l_build": 50, | ||
| "num_threads": 1, | ||
| "build_ram_limit_gb": 2.0, | ||
| "num_pq_chunks": 128, | ||
| "quantization_type": "FP", | ||
| "save_path": "siftsmall_index_filter_flat" | ||
| }, | ||
| "search_phase": { | ||
| "queries": "disk_index_sample_query_10pts.fbin", | ||
| "groundtruth": "disk_index_10pts_idx_uint32_truth_search_filter_res.bin", | ||
| "search_list": [10, 20, 40], | ||
| "beam_width": 4, | ||
| "recall_at": 10, | ||
| "num_threads": 1, | ||
| "is_flat_search": true, | ||
| "distance": "squared_l2", | ||
| "vector_filters_file": "disk_index_10pts_idx_uint32_range_res_r_100000.bin" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| /* | ||
| * Copyright (c) Microsoft Corporation. | ||
| * Licensed under the MIT license. | ||
| */ | ||
|
|
||
| //! Top-level disk search plan. | ||
| //! | ||
| //! Encodes the algorithm + filter combination for `DiskIndexSearcher::search` | ||
| //! as a sum type so that invalid combinations (flat scan with adaptive L, | ||
| //! inline filter without a predicate) are unrepresentable at the API boundary. | ||
|
|
||
| use diskann::graph::search::AdaptiveL; | ||
|
|
||
| /// Owned closure used to filter vector IDs during disk search. | ||
| /// | ||
| /// The `&u32` argument is the disk-side internal/external ID (they coincide | ||
| /// on the disk path by construction). | ||
| pub type SearchPredicate<'a> = Box<dyn Fn(&u32) -> bool + Send + Sync + 'a>; | ||
|
|
||
| /// Top-level disk search plan. | ||
| /// | ||
| /// Three variants encode the algorithm + filter combination: | ||
| /// | ||
| /// * `FlatScan` — brute-force linear scan, with or without an inline filter. | ||
| /// * `Graph` — plain greedy beam search; the optional filter is applied as a | ||
| /// hard post-filter during reranking (no traversal-time effect). | ||
| /// * `InlineFilter` — label-filtered graph search; the predicate is consulted | ||
| /// at visit time (not just during rerank). `adaptive_l = Some(_)` grows the | ||
| /// beam mid-search if the observed match specificity is low. | ||
|
|
||
| pub enum SearchPlan<'a> { | ||
| FlatScan { filter: Option<SearchPredicate<'a>> }, | ||
|
|
||
| Graph { filter: Option<SearchPredicate<'a>> }, | ||
|
|
||
| InlineFilter { | ||
| predicate: SearchPredicate<'a>, | ||
| adaptive_l: Option<AdaptiveL>, | ||
| }, | ||
| } | ||
|
|
||
| impl<'a> SearchPlan<'a> { | ||
| /// Flat scan over all vectors. Recall baseline. | ||
| pub fn flat() -> Self { | ||
| Self::FlatScan { filter: None } | ||
| } | ||
|
|
||
| /// Flat scan restricted to vectors that satisfy `predicate`. | ||
| pub fn flat_filtered<F>(predicate: F) -> Self | ||
| where | ||
| F: Fn(&u32) -> bool + Send + Sync + 'a, | ||
| { | ||
| Self::FlatScan { | ||
| filter: Some(Box::new(predicate)), | ||
| } | ||
| } | ||
|
|
||
| /// Plain greedy graph search; no filter. | ||
| pub fn graph() -> Self { | ||
| Self::Graph { filter: None } | ||
| } | ||
|
|
||
| /// Plain greedy graph search with a hard post-filter applied during | ||
| /// reranking. Traversal is unaffected. | ||
| pub fn graph_filtered<F>(predicate: F) -> Self | ||
| where | ||
| F: Fn(&u32) -> bool + Send + Sync + 'a, | ||
| { | ||
| Self::Graph { | ||
| filter: Some(Box::new(predicate)), | ||
| } | ||
| } | ||
|
|
||
| /// Inline label-filtered graph search. `adaptive_l = Some(_)` enables | ||
| /// mid-search beam widening; `None` runs inline tracking only (no | ||
| /// resizing). | ||
| pub fn inline_filter<F>(predicate: F, adaptive_l: Option<AdaptiveL>) -> Self | ||
| where | ||
| F: Fn(&u32) -> bool + Send + Sync + 'a, | ||
| { | ||
| Self::InlineFilter { | ||
| predicate: Box::new(predicate), | ||
| adaptive_l, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn flat_no_filter_constructor() { | ||
| let plan = SearchPlan::flat(); | ||
| assert!(matches!(plan, SearchPlan::FlatScan { filter: None })); | ||
| } | ||
|
|
||
| #[test] | ||
| fn flat_filtered_constructor() { | ||
| let plan = SearchPlan::flat_filtered(|id| *id == 5); | ||
| match &plan { | ||
| SearchPlan::FlatScan { filter: Some(p) } => { | ||
| assert!(p(&5)); | ||
| assert!(!p(&4)); | ||
| } | ||
| _ => panic!("expected FlatScan with filter"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn graph_no_filter_constructor() { | ||
| let plan = SearchPlan::graph(); | ||
| assert!(matches!(plan, SearchPlan::Graph { filter: None })); | ||
| } | ||
|
|
||
| #[test] | ||
| fn graph_filtered_constructor() { | ||
| let plan = SearchPlan::graph_filtered(|id| *id == 7); | ||
| match &plan { | ||
| SearchPlan::Graph { filter: Some(p) } => { | ||
| assert!(p(&7)); | ||
| assert!(!p(&6)); | ||
| } | ||
| _ => panic!("expected Graph with filter"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn inline_filter_constructor_without_adaptive_l() { | ||
| let plan = SearchPlan::inline_filter(|id| *id == 3, None); | ||
| match &plan { | ||
| SearchPlan::InlineFilter { | ||
| predicate, | ||
| adaptive_l: None, | ||
| } => { | ||
| assert!(predicate(&3)); | ||
| assert!(!predicate(&2)); | ||
| } | ||
| _ => panic!("expected InlineFilter with adaptive_l = None"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn inline_filter_constructor_with_adaptive_l() { | ||
| let adaptive = AdaptiveL::new(5, 16.0).expect("valid AdaptiveL"); | ||
| let plan = SearchPlan::inline_filter(|id| *id == 11, Some(adaptive)); | ||
| match &plan { | ||
| SearchPlan::InlineFilter { | ||
| adaptive_l: Some(_), | ||
| .. | ||
| } => {} | ||
| _ => panic!("expected InlineFilter with adaptive_l = Some"), | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.