Skip to content

Tweak behavior of adaptive L - #1354

Open
Magdalen Dobson Manohar (magdalendobson) wants to merge 6 commits into
mainfrom
users/magdalen/adaptive_l_improvement
Open

Tweak behavior of adaptive L#1354
Magdalen Dobson Manohar (magdalendobson) wants to merge 6 commits into
mainfrom
users/magdalen/adaptive_l_improvement

Conversation

@magdalendobson

Copy link
Copy Markdown
Contributor

Adaptive L search had a bit of strange behavior at the margins with the interaction of sample_count and resizing. If no matching samples were found when a resize was attempted, it would default to the maximum multiplier, meaning that if sample_count is relatively low, and selectivity is low but not nearly low enough to prompt resizing to the maximum multiplier, you could see an unnecessary, very large increase in L_search. This PR fixes that issue by resizing to 1/sample_count if no matching elements were found, and then allowing more than one opportunity for the queue to resize, allowing a resize attempt each time the number of comparisons doubled. This allows for more graceful behavior, and makes it harder to enact a pathological mismatch between sampling and resizing.

Along the way, after Mark noticed some issues with nondeterminism in log10, it adds some tolerance to the unit tests that depend on that function.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refines DiskANN’s inline filtered search behavior when using adaptive-L, aiming to avoid pathological over-expansion of L_search when sampling finds zero matches and to allow more gradual resizing as sampling progresses. It also updates the inline graph test baselines and expectations to reflect the adjusted adaptive-L behavior, and makes the adaptive-L unit tests more tolerant to minor floating-point variability in log10-driven calculations.

Changes:

  • Update adaptive-L to treat “0 matches” as an estimated specificity of 1 / visited, avoiding immediate jumps to the max multiplier.
  • Allow repeated adaptive-L resize opportunities at doubling sample thresholds (N, 2N, 4N, …) instead of only once.
  • Refresh inline search test expectations/baselines and add tolerance in adaptive-L unit tests to reduce flakiness from floating-point nondeterminism.

Reviewed changes

Copilot reviewed 2 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
diskann/src/graph/search/inline_filter_search.rs Adjusts adaptive-L sampling/resizing logic; updates adaptive-L unit tests for tolerance.
diskann/src/graph/test/cases/inline.rs Updates test expectations and test naming/docs to match the new adaptive-L behavior.
diskann/test/generated/graph/test/cases/inline/inline_search_three_level_no_adaptive_l_with_l2_finds_no_matches.json Updates generated baseline for the renamed/adjusted three-level non-adaptive-L test (L=2).
diskann/test/generated/graph/test/cases/inline/inline_search_three_level_adaptive_l_with_l2_finds_matches.json Updates generated baseline for the renamed/adjusted three-level adaptive-L test (L=2).
diskann/test/generated/graph/test/cases/inline/inline_adaptive_l_max.json Updates generated baseline to reflect new incremental growth behavior (fewer results returned).
diskann/test/generated/graph/test/cases/inline/inline_adaptive_l_logarithmic.json Updates generated baseline values consistent with the revised adaptive-L behavior.
diskann/test/generated/graph/test/cases/inline/inline_adaptive_l_linear.json Updates generated baseline values and expected IDs consistent with the revised adaptive-L behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +267 to 269
if new_l > scratch.best.capacity() {
scratch.resize(new_l);
}
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.56%. Comparing base (860cf47) to head (f268dcb).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1354   +/-   ##
=======================================
  Coverage   91.55%   91.56%           
=======================================
  Files         521      521           
  Lines      100347   100390   +43     
=======================================
+ Hits        91877    91922   +45     
+ Misses       8470     8468    -2     
Flag Coverage Δ
miri 91.56% <100.00%> (+<0.01%) ⬆️
unittests 91.24% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
diskann/src/graph/search/inline_filter_search.rs 99.51% <100.00%> (+0.04%) ⬆️
diskann/src/graph/test/cases/inline.rs 99.75% <100.00%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Magdalen - I have some concerns about the increased complexity and loss of test accuracy. Please see the inline comments.

scratch.hops += scratch.beam_nodes.len() as u32;

// Adaptive L: after enough samples, estimate specificity and scale L.
// Adaptive L: estimate specificity at N samples, then at 2N, 4N, and so on.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silly nit: May want to add one more: 2N, 4N, 8N since this plausibly looks like the sequence could be 2N, 4N, 6N.

adaptive_l.scale_factor,
);
if new_l > l_search {
if new_l > scratch.best.capacity() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll agree with the bot - we probably want scratch.best.search_l() here.

/// This will boost `l` to 10. The additional point 44 requires this larger `l` to hit.
/// We do not expect `43` to be hit.
/// This will boost `l` to 10. Re-evaluating at each 2x sample threshold allows the
/// search to reach the additional points 44 and 43.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including "43" in the test results defeats the purpose of the test. Since all matching points are now returned, we are no longer testing that the search window is upper-bounded.

While this can be fixed by changing the point to "42", I have another concern. The behavior of the algorithm becomes much harder to predict once multiple sample points are introduced. Before when there was a single sample point and change to L, it was easier to understand and test. Now that there are multiple rounds, this gets harder. For this test case, the behavior looks something like:

  • Sample at 10 visited with 2 matches. Specificity = 0.2 so we double the original L-search (5) to 10.
  • Sample at 20 visited with 2 matches. Specificity = 0.1, so we double the original L-search (5) to 10.
  • Sample at 40 visited with 2 matches. Specificity = 0.05, so we change the original L-search (5) to 12 and thus pick up the extra point that used to be on the boundary.

I also suspect that changing L-search later into a search yields diminishing returns since nodes that have already fallen off the back of the queue cannot be recovered. This can kind of be seen in graph/test/cases/inline/inline_adaptive_l_max which (1) is no longer testing the maximum search case, so is now misnamed and (2) goes from finding 3 of the 4 matching points to just 1 (albeit, with less exploration).

Can we find an alternate way to avoid this complexity? For example, resample only when matched == 0 and then stop resampling once at least one matched point is found? Alternatively, if this is the best approach, can the tests be adjusted so corner cases and bounds are still exercised and checked?

/// No matching items are found durihng the sample window. Adaptive will boost the
/// window size to the max.
/// No matching items are found during the initial sample window. With 2x sample
/// thresholds, adaptive L grows incrementally and reaches the nearest match.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment: this case is now either misnamed or is not set up incorrectly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants