Skip to content

Convert BMW implementations into publicly-available PruningScorers#2995

Open
barbarj wants to merge 3 commits into
quickwit-oss:mainfrom
paradedb:rj.convert-bmw-to-scorers
Open

Convert BMW implementations into publicly-available PruningScorers#2995
barbarj wants to merge 3 commits into
quickwit-oss:mainfrom
paradedb:rj.convert-bmw-to-scorers

Conversation

@barbarj

@barbarj barbarj commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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:

  • Introduces 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. I did this with as minimal changes as possible,. only changing what was necessary for external iteration. (mostly transferring loop state into a struct)
  • Have BooleanWeight::pruning_scorer drive the BMW choice.
  • Changes the visibility of the BMW scorers and their inputs to be
    available to consumers of the library.

The internal code-paths to drive BMW are unchanged at the call to for_each_pruning and above.

Local runs of the relevant in-repo benchmarks show ~no performance differences.

barbarj and others added 3 commits July 10, 2026 10:12
…#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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread src/query/boost_query.rs
Comment on lines +81 to +82
self.weight
.pruning_scorer(reader, boost * self.boost, init_threshold)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +51 to +53
TermOrEmptyOrAllScorer::TermScorer(term_scorer) => Ok(Box::new(
BlockWandSingleScorer::new(*term_scorer, init_threshold),
)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@barbarj
barbarj marked this pull request as draft July 10, 2026 17:09
@barbarj

barbarj commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Converted to draft after realizing there's some tweaks to the BMW implementations that aren't in our fork. I'll get them into my code and then re-open this.

Nevermind, I just misread something.

@barbarj
barbarj marked this pull request as ready for review July 10, 2026 17:16
@stuhood
stuhood requested review from PSeitz and fulmicoton July 10, 2026 17:43
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.

Implementing Block WAND optimization for more queries

1 participant