Skip to content

feat: make initial IVF filter expansion configurable - #68

Merged
JingsongLi merged 1 commit into
apache:mainfrom
shyjsarah:codex/auto-filter-expansion-factor
Aug 4, 2026
Merged

feat: make initial IVF filter expansion configurable#68
JingsongLi merged 1 commit into
apache:mainfrom
shyjsarah:codex/auto-filter-expansion-factor

Conversation

@shyjsarah

@shyjsarah shyjsarah commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Purpose

Filtered automatic IVF search currently expands the initial nprobe by inverse selectivity without a configurable bound. For selective filters, that can make the first search round much wider than desired even though the progressive search path can retry with a larger width when needed.

Changes

  • Add an optional max_initial_filter_expansion_factor for automatic IVF search in Rust and Java.
  • Keep the option unset by default, preserving the current unlimited initial expansion behavior.
  • Interpret factor 1 as no filter-driven initial expansion; for example, with base nprobe = 16, factor 4 caps the initial width at 64.
  • Apply the cap only to the initial width. Progressive retries may expand beyond it when filtered results do not fill top_k.
  • Reject zero factors, explicit IVF widths combined with the option, and use with DiskANN.
  • Preserve the existing C FFI ABI; C-created search parameters retain the current behavior.
  • Document the Rust and Java APIs, including the recall trade-off.

The capped automatic calculation is:

base = unfiltered automatic nprobe
scaled = ceil(base * vector_count / min(matching_count, vector_count))
cap = min(nlist, base * factor)
initial = min(scaled, cap)

Recall trade-off

The factor is an optional workload-specific tuning knob, not a recommended default. Lower factors reduce initial search work but may reduce Recall@K compared with uncapped automatic search. Progressive expansion occurs only when fewer than top_k valid results are returned; if the capped round already fills top_k, search stops even though probing more lists could improve recall.

The primary Rust and Java examples therefore continue to use automatic(10). Factor 4 appears only in a clearly marked optional tuning example and should be selected using representative latency and Recall@K measurements.

Compatibility

  • No default behavior change.
  • No storage-format change.
  • No C ABI change.
  • Explicit nprobe behavior is unchanged.

Tests

  • cargo test --workspace
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo fmt --all -- --check
  • mvn -f java/pom.xml test -q
  • Java/JNI native validation, panic-boundary, and handle-safety tests
  • python3 tools/check_license_headers.py
  • git diff --check

@JingsongLi

Copy link
Copy Markdown
Contributor

Please rebase main.

@jerry-024

Copy link
Copy Markdown
Contributor

Overall, this approach is aligned with common industry practice for filtered IVF search: start with a bounded probe budget and expand progressively when the filtered result set is too short. Keeping the option unset by default also preserves existing behavior.

I think two documentation changes are still needed before merge:

  1. Please state the recall trade-off explicitly. Progressive expansion is triggered only when fewer than top_k valid results are returned. If a capped initial nprobe already fills top_k, the search stops even though probing more lists could improve Recall@K. Suggested wording:

    Lower factors reduce initial search work but may reduce recall compared with uncapped automatic search. Progressive expansion occurs only when fewer than top_k valid results are returned.

  2. The primary Rust and Java examples currently use factor 4, which may look like a generally recommended value. Please keep the main examples as automatic(10) and move the capped form to an optional tuning example. If factor 4 remains in the primary examples, please support that recommendation with a small benchmark comparing uncapped and capped search latency and Recall@K on representative filtered workloads.

@shyjsarah
shyjsarah force-pushed the codex/auto-filter-expansion-factor branch from 700575d to 3d3ca6b Compare August 4, 2026 02:18
@shyjsarah

Copy link
Copy Markdown
Contributor Author

@JingsongLi @jerry-024 Addressed the review feedback in 3d3ca6b:

  • Rebased onto the latest main (2a8d88a) and integrated the IVF-PQ batch table reuse/budget changes from ivfpq: Reuse query distance tables across IVF lists #67.
  • Added the explicit Recall@K trade-off to the shared docs and Rust/Java API documentation. Progressive expansion is now documented as occurring only when fewer than top_k valid results are returned.
  • Restored the primary Rust and Java examples to plain automatic(10).
  • Moved factor 4 to a clearly marked optional workload-specific tuning example, so the PR does not recommend it as a default or require a benchmark-backed default claim.

Revalidated with workspace tests, Clippy, Maven tests, Java/JNI native validation, panic-boundary and handle-safety tests, formatting, license headers, and diff checks.

@jerry-024 jerry-024 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.

+1

@JingsongLi
JingsongLi merged commit ba41a5f into apache:main Aug 4, 2026
9 checks passed
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.

3 participants