Convert BMW implementations into publicly-available PruningScorers#2995
Convert BMW implementations into publicly-available PruningScorers#2995barbarj wants to merge 3 commits into
Conversation
…#162) As a step towards [this](paradedb/paradedb#5300), do the following: - Introduce a `PruningScorer` trait that extends a `Scorer` to support setting a threshold. - Adds a `pruning_scorer` method to the `Weight` trait, with a default implementation returning a `BasicPruningScorer`. - Moves the logic for all 3 (union, single, and intersection) block-max-wand variants into `PruningScorer` implementations so that they can be driven externally. - Has `BooleanWeight::pruning_scorer` drive the BMW choice. - Changes the visibility of the BMW scorers and they're inputs to be available to consumers of the library. In repo benchmarks show this change to be a strict improvement for BMW intersection and at most low-single-digit-percentage-points (<4%) worse for some BMW query shapes when ran on both my Mac and a dedicated Linux box. --------- Signed-off-by: RJ Barman <rjhallsted@gmail.com> Co-authored-by: Stu Hood <stuhood@paradedb.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de89b397f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| self.weight | ||
| .pruning_scorer(reader, boost * self.boost, init_threshold) |
There was a problem hiding this comment.
Preserve negative boosts when pruning boosted queries
When BoostQuery::new is used programmatically with a negative boost, this now delegates to the child pruning scorer with negative term weights. The block-max scorers compare block_max_score() <= threshold to skip blocks, but with a negative BM25 weight the stored block max is no longer an upper bound on the boosted scores, so once the top-k threshold rises above that negative value the scorer can skip blocks containing less-negative (therefore better) documents. The previous BoostWeight path used the blanket scorer filtering over actual boosted scores, so please either reject negative boosts here or fall back to non-BMW pruning for them.
Useful? React with 👍 / 👎.
| TermOrEmptyOrAllScorer::TermScorer(term_scorer) => Ok(Box::new( | ||
| BlockWandSingleScorer::new(*term_scorer, init_threshold), | ||
| )), |
There was a problem hiding this comment.
Avoid BMW for Basic term pruning scorers
For callers of the new Weight::pruning_scorer API, a scoring TermQuery can still be built with IndexRecordOption::Basic (this is a documented public constructor option), but this path always wraps the resulting TermScorer in BlockWandSingleScorer. Basic postings do not store valid block-wand maxima for full blocks (SkipReader fills the block-wand tf/fieldnorm with zero), so after the threshold becomes positive the scorer can treat later full blocks as unable to compete and skip matching documents with real scores above the threshold. Please gate this on freq_reading_option() == ReadFreq or fall back to BasicPruningScorer for Basic postings.
Useful? React with 👍 / 👎.
|
Nevermind, I just misread something. |
This PR closes #2390 by allowing external callers to choose if they want pruning or regular behavior from a scorer while keeping the applicability of the BMW optimization inside of the
Weight. We (ParadeDB) recently used this change to apply BMW to certain joins where we use the scorer directly. (See here)This works by:
PruningScorertrait that extends aScorerto supportsetting a threshold.
pruning_scorermethod to theWeighttrait, with a defaultimplementation returning a
BasicPruningScorer.block-max-wand variants into
PruningScorerimplementations so thatthey can be driven externally. I did this with as minimal changes as possible,. only changing what was necessary for external iteration. (mostly transferring loop state into a struct)
BooleanWeight::pruning_scorerdrive the BMW choice.available to consumers of the library.
The internal code-paths to drive BMW are unchanged at the call to
for_each_pruningand above.Local runs of the relevant in-repo benchmarks show ~no performance differences.