Skip to content

perf(index): vectorized merge kernels and frequency-bound pruning for FTS conjunctions#7625

Draft
BubbleCal wants to merge 1 commit into
yang/lan2-88-fts-bulk-conjunctionfrom
yang/lan2-88-fts-simd-merge-kernels
Draft

perf(index): vectorized merge kernels and frequency-bound pruning for FTS conjunctions#7625
BubbleCal wants to merge 1 commit into
yang/lan2-88-fts-bulk-conjunctionfrom
yang/lan2-88-fts-simd-merge-kernels

Conversation

@BubbleCal

@BubbleCal BubbleCal commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Stacked on #7624.

What

Two refinements of the bulk conjunction merge introduced in #7624, both result-identical:

  • Clause-count-specialized merge kernels (2 and 3 clauses, generic fallback) keep cursors and bounds in registers, and the catch-up scans use an AVX2 find_next_geq (branchless 8-wide compare + movemask, the analogue of Lucene's VectorUtil.findNextGEQ) instead of the mispredict-heavy scalar walk. Runtime-dispatched with a scalar fallback; #[target_feature(enable = "avx2")] covers the whole kernel so the vector scan inlines.
  • Per-clause frequency-bucketed score-bound LUT: kernels skip a lead doc before any follower advance when even its doc-length-free score bound (plus the other clauses' block maxes) cannot beat the threshold — the score-first ordering Lucene's conjunction scorer uses, with no doc-length load. The bound is monotone (doc length only shrinks a BM25 weight; the clamp bucket holds the clause sup), so every skipped doc would also fail the exact per-candidate prune: the emitted results are unchanged.

Measured vs #7624 (its base)

Per-branch-tip wheels, 1000 queries × 8 concurrent. AND on the 200M-doc
v3-256 index (warm, prewarmed); phrase on the 50M-doc v3-256 positions
index (steady-state passes 3-4 of 4, no prewarm):

query #7624 this PR
AND 3w k10 @200M 0.058s / 136 qps 0.046s / 172 qps (+27% qps)
AND 3w k100 @200M 0.116s / 69 qps 0.092s / 86 qps (+25% qps)
phrase 3w k10 @50m 0.235s / 34 qps 0.226s / 35 qps (+4%)
phrase 2w k10 @50m 0.080s / 100 qps 0.078s / 102 qps (~flat)

Profile: the scalar catch-up was 41.5% of the AND profile at ~6ns/element (branch mispredicts on irregular doc gaps); the AVX2 kernels cut it to 32.7% and falling.

Verification

  • Bulk-vs-classic A/B: score_diff=0 (200 query×k pairs, AND @200M + phrase @50m).
  • Parity tests extended to 2/3/4-clause cases so the specialized and generic kernels are all exercised against the classic loop.
  • cargo test -p lance-index, clippy -D warnings, fmt --check clean.

🤖 Generated with Claude Code

@github-actions github-actions Bot added A-index Vector index, linalg, tokenizer performance labels Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

ACTION NEEDED
Lance follows the Conventional Commits specification for release automation.

The PR title and description are used as the merge commit message. Please update your PR title and description to match the specification.

For details on the error please inspect the "PR Title Check" action.

…r bulk fts conjunctions

Three changes to the bulk AND/phrase candidate pipeline, all
score-identical to the classic loop:

- Clause-count-specialized merge kernels (2 and 3 clauses) keep cursors
  and bounds in registers, with an AVX2 find_next_geq catch-up scan (the
  analogue of Lucene's VectorUtil.findNextGEQ) replacing the mispredict-
  heavy scalar walk. Generic kernel covers other widths.
- Two-pass batched scoring: kernels only record (doc, offsets) matches;
  doc lengths are then gathered back-to-back so their cache misses
  overlap, and the prune/score/insert pass runs in doc order with the
  classic semantics.
- A per-clause frequency-bucketed score-bound LUT lets kernels skip lead
  docs before any follower advance when even the doc-length-free bound
  cannot beat the threshold (score-first ordering, Lucene-style); the
  bound is monotone so the skipped set is a subset of the exact prune.

Warm mmlb AND @200M, 8 concurrent: k10 0.060->0.049s (161qps), k100
0.118->0.098s (81qps); phrase\@50m 3w k10 0.210->0.204s, 2w 0.042->0.040s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BubbleCal BubbleCal force-pushed the yang/lan2-88-fts-simd-merge-kernels branch from 2f9de34 to 3c60d9a Compare July 4, 2026 18:17
@BubbleCal BubbleCal force-pushed the yang/lan2-88-fts-bulk-conjunction branch from fc1af4b to 22e3522 Compare July 4, 2026 18:17
@BubbleCal BubbleCal changed the title perf(index): SIMD merge kernels and frequency-bound pruning for FTS conjunctions perf(index): vectorized merge kernels and frequency-bound pruning for FTS conjunctions Jul 4, 2026
@BubbleCal BubbleCal marked this pull request as draft July 5, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-index Vector index, linalg, tokenizer performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant