[Experiment do not review - for collab with Magdalen] Decoupled yield_k knob for Design B attribute-diversity search#1262
Draft
narendatha wants to merge 11 commits into
Draft
Conversation
Remove the experimental_diversity_search feature gate (graduate the diverse-search primitives to always-on) and add a post-processing-only attribute-bucket diversity path: a plain greedy search over the top-L pool followed by AttributeBucketDiversity, which keeps at most diverse_results_k results per distinct attribute value. Exposed as SearchMode::DiverseAttribute and wired into the disk benchmark via new attributes/diverse_attribute_id/diverse_results_k inputs.
Add DiverseAdaptiveSearch, a greedy graph search that samples attribute-bucket concentration during traversal and grows the search list L when few distinct buckets are seen (yield = sum min(count_b,k)/visited). The enlarged pool feeds the existing AttributeBucketDiversity post-processor. Wired through SearchMode::DiverseAttribute's optional adaptive_l and the disk benchmark. Reuses inline-filter compute_adaptive_l. Includes unit tests for the bucket-yield sizing.
AttributeBucketDiversity::post_process previously recomputed exact distances for every candidate in the L-pool, fetching each full-precision vector and re-evaluating the distance. This duplicated work already done during traversal and made Design A slower than the queue method at higher L. Mirror RerankAndFilter: consult scratch.distance_cache first and only fetch/recompute for cache misses, then sort and run the greedy per-bucket selection. Recall and IO counts are unchanged; redundant vector fetches and distance recomputation are eliminated. Add diverse-search-results.md documenting the Enron/Caselaw 3-way comparison (standard, queue, Design A, Design B) and this optimization.
Couple the diversity yield to num_buckets, k, and sample size via the denominator min(sample_visited, num_buckets*k), so yield spans [0, 1] in both few-bucket and diffuse regimes. Add AttributeValueProvider::num_buckets (default None disables growth) and report it from FileAttributeProvider. Scale L linearly from the yield deficit (1 - yield) rather than reusing the inline-filter specificity curve.
Rerun Design B across all four datasets after the yield-normalization fix. Recall improves on the concentrated, uncorrelated datasets (Enron, YFCC) and is unchanged on the diffuse dataset (Caselaw doc_id). On the correlated Caselaw jurisdiction attribute Design B now sits just below the queue at every L. Add reusable chart_data.json + plot_qps_vs_recall.py and regenerate the four PNGs.
…lable When AttributeValueProvider::num_buckets returns None, diverse_yield now normalizes by sample_visited alone instead of skipping adaptive growth. This restores the pre-normalization heuristic for that path: L still grows on concentrated samples (never worse than fixed-L Design A) rather than collapsing to Design A.
Decouples the adaptive-L yield sampler's per-bucket cap (yield_k) from the output cap (diverse_results_k). Setting yield_k > diverse_results_k requires seeing several points per bucket before yield saturates, keeping L growing on distance-correlated attributes. yield_k defaults to the effective diverse_results_k, so existing configs are unchanged.
yield_k knob for Design B attribute-diversity searchyield_k knob for Design B attribute-diversity search
Contributor
|
Thanks for posting these! I think the results make a lot of sense. I agree that experimenting with auto-deriving I feel like this is trending in the direction of some sort of auto-detection of correlation during search, but that might be a pretty big can of worms haha |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Motivation
Design B grows the search list
Lmid-search from the observed bucketconcentration of the attribute sample. The yield estimator caps each bucket's
contribution at
kslots:matched = Σ_b min(count_b, k)
achievable = min(sample_visited, num_buckets · k)
yield = matched / achievable (∈ [0, 1])
Today
kis the same value asdiverse_results_k(the per-bucket output cap).On a distance-correlated attribute — where the near neighbors of a query all
share a few attribute values — the near buckets get "counted as satisfied" after
a single far-away hit. Yield saturates early, adaptive-L stops growing, and the
search terminates before the close attribute-satisfying points are found.
Jurisdiction (60 buckets) regressed slightly below the plain-queue baseline for
exactly this reason.
What this changes
Decouple the sampler's per-bucket cap (
yield_k) from the output cap(
diverse_results_k):yield_kpoints per bucket before that bucket is considered"satisfied" changes when yield saturates, and hence how aggressively
Lgrows.yield_kdefaults to the effectivediverse_results_k, so existing configsare unchanged (
yield_k = 1reproduces the current Design B behaviorbit-for-bit).
Plumbing
diskann/src/graph/search/diverse_adaptive_search.rsDiverseAdaptiveSearch::newtakesyield_k; yield helpers (diverse_matched_slots,diverse_yield) parameterized on itdiskann-disk/src/search/search_mode.rsSearchMode::DiverseAttribute { …, yield_k }variant +diverse_attribute()constructor paramdiskann-disk/src/search/provider/disk_provider.rs*yield_kto the adaptive search;AttributeBucketDiversityprocessor still uses*diverse_results_kdiskann-benchmark/src/inputs/graph_index.rsyield_kfield on theadaptive_lconfig block (serde defaultNone)diskann-benchmark/src/disk_index/search.rsyield_k, defaulting to the effectivediverse_results_kResults — full
yield_ksweepAll configs:
sample_count=100,scale_factor=4.0,diverse_results_k=1,recall@10,
search_list=[20,40,80,100].yield_k=1== current Design B.Jurisdiction (Caselaw, ~60 buckets, distance-correlated) — helps strongly
YFCC camera (~67 buckets) — helps slightly
Caselaw doc_id (diffuse) — exact no-op (identical for all
yield_k)Enron (~12.8k buckets, concentrated) — hurts
Why the sign flips — regime analysis
The behavior is fully explained by which term wins in
achievable = min(sample_visited, num_buckets · yield_k):num_buckets ≫ sample_count(Enron): denominator is pinned atsample_visited. Raisingyield_konly loosens the per-bucket caps, whichraises
matched→ raises yield → less over-fetch → fewer IOs and lowerrecall.
yield_k = 1is optimal.num_buckets ≲ sample_count(jurisdiction, YFCC): denominator isnum_buckets · yield_k. Raisingyield_klowers yield → more over-fetch→ higher recall at the cost of more IOs.
min(count_b, yield_k)is insensitive toyield_k— an exact no-op.Takeaway: there is no safe universal default. The right
yield_kdepends onthe bucket-count-vs-sample regime and the attribute/distance correlation, which
a client can't easily observe. This is the core argument for keeping it out of the
main Design B PR.
Open questions
num_buckets ≤ sample_count, which is cheaply detectable at runtime. Couldyield_kbe inferred from observed bucket saturation rather than configured?scale_factor. Both control over-fetch aggressiveness;would a smarter
scale_factorheuristic subsume the useful cases?Planned experiments
yield_k ∈ {1,2,3,4}sweep on jurisdiction (correlated) — helpsyield_kfromnum_bucketsvssample_countscale_factor-only heuristicTesting
cargo build --release -p diskann-benchmark --features disk-index✅cargo test -p diskann diverse_adaptive_search— 10 tests pass ✅