Disable TopK/filtered pruning, preserve legacy threshold behavior#682
Disable TopK/filtered pruning, preserve legacy threshold behavior#682tlwillke wants to merge 1 commit into
Conversation
MarkWolters
left a comment
There was a problem hiding this comment.
Code looks good, as do the tests. There is a minor conflict in GraphSearcher that needs to be resolved before this can be merged.
|
This branch is a hotfix for 4.0.0-rc.8. We need to cherry pick the fix for the latest main. |
ashkrisk
left a comment
There was a problem hiding this comment.
I ran a test with cohere-1M, everything looks as expected. There's a big recall improvement with hierarchy off.
| topK | overquery | usePruning | addHierarchy | recall_main | recall_pr | recall_change |
|---|---|---|---|---|---|---|
| 10 | 1.0 | True | True | 0.57925 | 0.59661 | 0.01736 |
| 10 | 2.0 | True | True | 0.75996 | 0.77704 | 0.01708 |
| 10 | 1.0 | False | True | 0.59821 | 0.59661 | -0.00160 |
| 10 | 2.0 | False | True | 0.77833 | 0.77704 | -0.00129 |
| 10 | 1.0 | True | False | 0.45247 | 0.58810 | 0.13563 |
| 10 | 2.0 | True | False | 0.69523 | 0.77637 | 0.08114 |
| 10 | 1.0 | False | False | 0.57372 | 0.58810 | 0.01438 |
| 10 | 2.0 | False | False | 0.77301 | 0.77637 | 0.00336 |
| * Threshold searches, where {@code threshold > 0}, continue to use their | ||
| * legacy threshold early-termination behavior. |
There was a problem hiding this comment.
A clarification might be helpful here since this tripped me up a bit.
| * Threshold searches, where {@code threshold > 0}, continue to use their | |
| * legacy threshold early-termination behavior. | |
| * Threshold searches, where {@code threshold > 0}, continue to use their | |
| * legacy threshold early-termination behavior which disregards this setting. |
|
|
||
| assertEquals(pruningOff.getVisitedCount(), pruningOn.getVisitedCount()); | ||
| assertEquals(pruningOff.getNodes().length, pruningOn.getNodes().length); | ||
| assertArrayEquals(sortedNodes(pruningOff), sortedNodes(pruningOn)); |
There was a problem hiding this comment.
nit: should we be sorting the nodes here? If we're expecting the exact same results that would include the order of the results as well.
| assertTrue(factory.getScoreTracker(false, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); | ||
| assertTrue(factory.getScoreTracker(true, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); |
There was a problem hiding this comment.
nit: A comment here would improve readability
// `usePruning` parameter is deprecated and should have no effect.| assertTrue(factory.getScoreTracker(false, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); | |
| assertTrue(factory.getScoreTracker(true, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); | |
| // `usePruning` parameter is deprecated and should have no effect. | |
| assertTrue(factory.getScoreTracker(false, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); | |
| assertTrue(factory.getScoreTracker(true, RERANK_K, 0.0f) instanceof ScoreTracker.NoOpTracker); |
This is a hotfix for JV 4.0.0-rc.8. Resolves #678.
Summary
This hotfix disables the unsafe
threshold == 0graph-search pruning path while preserving legacy threshold-search behavior for compatibility.GraphSearcher.usePruning(boolean)is now deprecated and retained only for source and binary compatibility. Calls to this method no longer enable TopK or filtered-search pruning.Threshold searches, where
threshold > 0, continue to use the existing legacy threshold early-termination path. This is intentional because threshold search may already be used by customers, and changing that behavior in this hotfix could be a compatibility break.In short:
Why This Change Is Needed
The existing TopK/filtered pruning implementation did not demonstrate reliable value and carried recall risk.
The original pruning design had several problems:
Pruning was used to skip graph expansion.
The search could pop a candidate and skip expanding its neighbors, then continue searching. In graph search, expansion is how better candidates are discovered, so this changes the traversal and can reduce recall.
TopK pruning was redundant with standard graph-search termination.
Plain TopK already has the standard frontier-vs-result-queue termination condition. Adding another relaxed-monotonicity signal can only terminate earlier than the normal search and therefore risks recall loss.
Filtered TopK did not show useful benefit.
This implementation already applies
acceptOrdsduring traversal. In low-cardinality filtering tests, additional pruning saved almost no visits while sometimes reducing recall.Threshold pruning is imperfect but may be in use.
Diagnostic threshold testing showed that the legacy threshold path can save substantial work for very selective thresholds, but it can lose recall for less-selective thresholds. Because this behavior existed previously, this hotfix preserves it rather than changing threshold semantics.
Evidence From Filtering Tests
Filtered TopK was the primary case where pruning was expected to help. The observed benefit was negligible.
This indicates that the existing filtered-search implementation already gets the useful behavior from applying
acceptOrdsduring traversal, and the additional relaxed pruning tracker does not provide meaningful value.Evidence From Threshold Diagnostics
Threshold pruning remains preserved for compatibility, but diagnostic testing showed why it should not be expanded or treated as a validated general pruning mechanism.
Implementation Notes
GraphSearcher.usePruning(boolean)The API is deprecated and retained for compatibility.
Compatibility Notes
usePruning(boolean)method still exists.usePruning(true)no longer enable TopK or filtered pruning.Tests Added / Updated
TestPruningCompatibilityAdds explicit coverage for the new compatibility contract.
testScoreTrackerFactoryPolicyVerifies tracker selection:
This confirms that
RelaxedMonotonicityTrackeris disabled forthreshold == 0, while legacy threshold behavior is preserved.testUsePruningIgnoredForTopKAndFilteredTopKVerifies that
usePruning(true)has no effect for:The test compares pruning-on and pruning-off searches over the same graph, queries, and filters, and asserts:
testUsePruningIgnoredForThresholdSearchVerifies that
usePruning(true)andusePruning(false)produce identical threshold-search behavior.This confirms that the public pruning API is ignored, while the legacy threshold path remains unchanged.
TestLowCardinalityFilteringKeeps coverage for filtered TopK search behavior and filter correctness.
The test evaluates filtered search across multiple selectivities:
For each selectivity, the test compares pruning-off and pruning-on behavior over the same graph, queries, and filter set. It validates that returned nodes satisfy
acceptOrdsand records the visit/recall effect of pruning.This test documents why filtered TopK pruning is being disabled: the measured visit reduction was negligible while recall could decrease slightly.
Test2DThresholdContinues to validate legacy threshold-search behavior.
This test is retained because threshold searches are intentionally preserved for compatibility. It checks that threshold queries continue to return sufficient matching nodes and meet the existing visited/recall expectations.
Release Note Text
Future Work
Any future pruning implementation should be reintroduced only after a separate redesign and validation effort. A replacement should: