Skip to content

feat(index): inject globally-trained SQ bounds through the build params#7901

Closed
xuanyu-z wants to merge 4 commits into
lance-format:mainfrom
xuanyu-z:feat-sq-bounds-injection
Closed

feat(index): inject globally-trained SQ bounds through the build params#7901
xuanyu-z wants to merge 4 commits into
lance-format:mainfrom
xuanyu-z:feat-sq-bounds-injection

Conversation

@xuanyu-z

@xuanyu-z xuanyu-z commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem

A distributed (fragment-sharded) build of an SQ-quantized index (IVF_SQ, IVF_HNSW_SQ) lets each shard's ScalarQuantizer::build self-train its own min/max bounds from its own sample. SQ codes are only meaningful relative to the bounds they were trained with, so per-shard codes end up on different scales. When the shards' segments are merged, the merger keeps only the first shard's ScalarQuantizationMetadata and dequantizes every shard's codes against it — silently producing wrong quantized values and wrong search results, with no error.

shard A bounds [0,10]: value 5 → code 128
shard B bounds [0,50]: value 5 → code 26     ── merge, dequant against A's [0,10] ──▶  26 → 1.0   ✗ (was 5)
shard C bounds [0,20]: value 5 → code 64                                                64 → 2.5   ✗ (was 5)

Fix

Mirror PQBuildParams::with_codebook: give SQ the same "inject a globally-trained quantizer" hook so every shard quantizes on an identical scale.

  • Add optional bounds: Option<Range<f64>> to SQBuildParams, plus SQBuildParams::with_bounds(num_bits, bounds).
  • When set, ScalarQuantizer::build constructs the quantizer directly from the injected bounds (via the pre-existing ScalarQuantizer::with_bounds) and skips the per-shard training scan. The field rides the existing quantizer_params → load_or_build_quantizer → Q::build channel — no builder plumbing changes.
  • Harden the segment merger: both SQ paths (IVF_SQ, IVF_HNSW_SQ) previously validated only dim. They now reject shards whose SQ metadata differs — num_bits and bounds (ensure_sq_metadata_compatible) — turning silent corruption into a hard error.

Why exact-equality (not the tolerance used for PQ codebooks / centroids)

ensure_fixed_size_list_compatible compares PQ codebooks and IVF centroids with a float tolerance, because those can drift within training. SQ num_bits/bounds are different: they're injected verbatim into every shard, so any difference is not benign drift — it means a shard used a different scale and its codes are incomparable. Exact inequality is exactly the check we want; a tolerance would let a divergent scale slip through.

Compatibility

  • Behavior & on-disk format unchanged: bounds defaults to None → existing self-training behavior is byte-for-byte the same; bounds were already persisted in ScalarQuantizationMetadata, so no format/proto change.
  • Public Rust API: this adds a field to the public SQBuildParams, mirroring exactly how codebook: Option<ArrayRef> was added to PQBuildParams (also a plain public struct). Code constructing SQBuildParams via Default/..Default::default() is unaffected; exhaustive struct literals (SQBuildParams { num_bits, sample_rate }) need the new field — the same source-level break the PQBuildParams::codebook addition carried. Flagging so it can be labeled/documented per the repo's convention if needed.

Out of scope (caller's responsibility)

This adds the mechanism. Computing the global bounds — a coordinator-side pass over all data to find the true global min/max before sharding — is the caller's job, exactly as computing a global PQ codebook is today. Injecting bounds that don't cover all data would clip out-of-range values (scale_to_u8 already saturates rather than panicking). Part of #6309 (Distributed Indexes Search).

Tests

  • test_merge_sq_segments_with_injected_bounds (IVF_SQ, IVF_HNSW_SQ): a sharded-then-merged index carries exactly the injected bounds; for the deterministic IVF_SQ case it returns byte-identical Top-K to a single-shot baseline built with the same bounds.
  • test_merge_sq_segments_rejects_mismatched_bounds: merging shards with different bounds errors.
  • test_build_with_injected_bounds / test_build_without_bounds_self_trains: the build short-circuit and the None default.
  • test_build_rejects_non_finite_injected_bounds (NaN, ±inf; start == end stays valid) and test_build_with_injected_bounds_rejects_unsupported_type (Int32): input validation on the fast path.
  • For IVF_HNSW_SQ the merge test aggregates top-k overlap across all queries and asserts the K/3 recall floor (the file's single-vs-split SQ idiom; HNSW build is non-deterministic, so a per-query threshold flakes).

Follow-ups (out of scope here)

  • Language bindings: Python/Java expose segment build + merge but not bounds; JNI sets bounds: None. So distributed SQ builds are Rust-only for now — a binding user's independently-trained segments will be rejected by the new merger guard (a hard error, not silent corruption). Exposing sq_bounds through both bindings is a follow-up.
  • Shard-side sampling I/O: load_or_build_quantizer still samples vectors before build sees the injected bounds; only the final min/max pass is skipped. A pre-trained quantizer could take dim/type from the schema and skip sampling entirely — a perf follow-up for distributed builds.
  • Global-bounds computation (the coordinator-side pass) and num_bits validation at the build boundary (SQ only encodes u8 today; SQ4 is a TODO).

Distributed (fragment-sharded) builds of SQ-quantized indexes (IVF_SQ,
IVF_HNSW_SQ) currently let each shard's `ScalarQuantizer::build`
self-train its own min/max bounds from its own sample, so per-shard
codes end up on different scales and the segment merge silently
produces incorrect quantized values (all merged codes are dequantized
against the first shard's bounds).

Mirror `PQBuildParams::with_codebook`: add an optional
`bounds: Option<Range<f64>>` to `SQBuildParams` plus a
`SQBuildParams::with_bounds(num_bits, bounds)` constructor. When set,
`ScalarQuantizer::build` constructs the quantizer directly from the
injected bounds and skips the per-shard `update_bounds` training scan,
so every shard quantizes on an identical scale. The field rides the
existing `quantizer_params -> load_or_build_quantizer -> Q::build`
channel; no builder plumbing changes.

Also harden the segment merger: both SQ paths (IVF_SQ and IVF_HNSW_SQ)
previously captured only the first shard's `ScalarQuantizationMetadata`
and validated only `dim`. They now return an error ("SQ bounds mismatch
across shards") when later shards carry different bounds, turning
silent corruption into a hard failure.

Backward compatible: `bounds` defaults to `None`, which keeps the
existing self-training behavior, and the on-disk format is unchanged
(bounds were already persisted in `ScalarQuantizationMetadata`; the
proto still carries only `num_bits`).

Relates to lance-format#6309.
@github-actions github-actions Bot added A-index Vector index, linalg, tokenizer A-java Java bindings + JNI enhancement New feature or request labels Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Scalar quantization now accepts injected bounds, uses them during quantizer construction, and validates matching SQ metadata when merging IVF-SQ and IVF-HNSW-SQ shards. Tests cover shared-bound merges and mismatched-bound failures.

Changes

Scalar quantization bounds

Layer / File(s) Summary
Injected SQ bounds
rust/lance-index/src/vector/sq/builder.rs, rust/lance-index/src/vector/sq.rs, java/lance-jni/src/utils.rs, rust/lance/src/index/vector*
SQBuildParams accepts optional fixed bounds, quantizer construction uses them when provided, and existing callers initialize bounds explicitly.
Shard bounds validation
rust/lance-index/src/vector/distributed/index_merger.rs
IVF-SQ and IVF-HNSW-SQ merges compare scalar quantization metadata across shards and reject mismatched num_bits or bounds.
Distributed merge coverage
rust/lance/src/index/vector/ivf/v2.rs
Tests use shared bounds for mergeable shards and cover metadata preservation, query results, and mismatched-bound failures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SQBuildParams
  participant ScalarQuantizer
  participant Shards
  participant IndexMerger
  SQBuildParams->>ScalarQuantizer: supply optional bounds
  ScalarQuantizer->>Shards: build SQ shard indexes
  Shards->>IndexMerger: submit shard metadata
  IndexMerger->>IndexMerger: compare SQ metadata
  IndexMerger-->>Shards: merge indexes or return mismatch error
Loading

Suggested reviewers: xuanwo, westonpace, jackye1995

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: injecting global SQ bounds through build params.
Description check ✅ Passed The description is detailed and directly matches the changeset, including bounds injection, validation, and merge hardening.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-index/src/vector/distributed/index_merger.rs`:
- Around line 186-194: Update ensure_sq_bounds_match to validate
candidate.num_bits against reference.num_bits in addition to bounds. Return an
index error on either mismatch that includes both shards’ bounds and bit-width
values, with variable names and relevant types, while preserving the successful
Ok path when metadata matches.

In `@rust/lance-index/src/vector/sq.rs`:
- Around line 148-153: Validate cloned bounds in the params.bounds branch before
calling Self::with_bounds: require both endpoints to be finite and bounds.start
strictly less than bounds.end. For invalid values, return Error::invalid_input
with both bound values included in the message, and add rejection tests covering
empty, reversed, infinite, and NaN bounds.

In `@rust/lance-index/src/vector/sq/builder.rs`:
- Around line 14-19: Document the public SQBuildParams bounds API, including
SQBuildParams::with_bounds, with a compiling usage example that matches the
current method signature. Link the documentation to the relevant quantizer build
method and associated structs, and keep the example synchronized with the actual
API.

In `@rust/lance/src/index/vector/details.rs`:
- Around line 1314-1318: Update the SQBuildParams fixture in
test_vector_index_details_roundtrip to use explicit non-default bounds via
Some(...), then extend the restored SQ-stage assertions to verify the exact
bounds range is preserved through protobuf/details round-trip.

In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 4115-4120: Update the regression assertion around the SQ bounds
mismatch error to verify that the error is specifically the Error::index
variant, while retaining the existing “SQ bounds mismatch across shards” message
check. Ensure both the variant and message content must match before the test
succeeds.
- Around line 4063-4065: Update the HNSW graph construction assertion in the
surrounding test to validate recall against ground_truth, not merely that
ids_split contains K results. Compute or use the existing recall comparison and
require recall to be at least 0.5, while preserving the current result-count
handling where applicable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 1fe2ffb1-3e99-48c1-9197-711679e003c9

📥 Commits

Reviewing files that changed from the base of the PR and between ecfded9 and fa9d942.

📒 Files selected for processing (7)
  • java/lance-jni/src/utils.rs
  • rust/lance-index/src/vector/distributed/index_merger.rs
  • rust/lance-index/src/vector/sq.rs
  • rust/lance-index/src/vector/sq/builder.rs
  • rust/lance/src/index/vector.rs
  • rust/lance/src/index/vector/details.rs
  • rust/lance/src/index/vector/ivf/v2.rs

Comment thread rust/lance-index/src/vector/distributed/index_merger.rs Outdated
Comment thread rust/lance-index/src/vector/sq.rs
Comment thread rust/lance-index/src/vector/sq/builder.rs Outdated
Comment thread rust/lance/src/index/vector/details.rs
Comment thread rust/lance/src/index/vector/ivf/v2.rs
Comment thread rust/lance/src/index/vector/ivf/v2.rs
…e, harden tests

- Reject non-finite (NaN/±inf) injected bounds and non-float element types on
  the injected-bounds fast path (previously deferred to quantize time);
  `start == end` stays valid (a globally constant column). Tests added.
- Merge guard now compares `num_bits` as well as `bounds`
  (`ensure_sq_metadata_compatible`), so shards on different scales can't merge
  even with matching bounds (matters for future SQ4); error names both values.
- IVF_HNSW_SQ merge test asserts a K/3 top-k overlap floor vs the single-index
  baseline (the file's existing non-flaky idiom) instead of only length.
- Test cleanup: reuse the existing `build_segments_for_fragment_groups` helper
  in the merge test; extract an `f32_fsl` fixture for the SQ build tests; use
  8 bits (not the unsupported 4) in the params test.

@coderabbitai coderabbitai Bot 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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/index/vector/ivf/v2.rs (1)

4001-4010: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Cover num_bits in the merge tests.

The new merge contract validates both bounds and num_bits, but this test only asserts bounds and only creates a bounds mismatch. Assert sq_meta.num_bits == 8 and add a differing-bit-width case, or directly unit-test the compatibility helper.

As per coding guidelines, “Every bugfix and feature must have corresponding tests.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 4001 - 4010, The merge
test around ds_single and ds_split only validates SQ bounds; extend coverage to
the full compatibility contract by asserting sq_meta.num_bits equals 8 and
adding a merge case with differing bit widths, or add a focused unit test for
the compatibility helper that verifies rejection. Keep the existing
bounds-mismatch coverage intact.

Source: Coding guidelines

♻️ Duplicate comments (3)
rust/lance-index/src/vector/sq.rs (1)

163-168: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject reversed injected bounds.

The fast path accepts ranges such as 2.0..1.0 and stores them unchanged. Equal endpoints are intentionally supported, but valid bounds must satisfy start <= end; otherwise invalid metadata can reach quantization and shard merging.

Proposed fix
-            if !bounds.start.is_finite() || !bounds.end.is_finite() {
+            if !bounds.start.is_finite()
+                || !bounds.end.is_finite()
+                || bounds.start > bounds.end
+            {
                 return Err(Error::invalid_input(format!(
-                    "SQ builder: injected bounds must be finite, got {}..{}",
+                    "SQ builder: invalid injected bounds: start={}, end={}; expected finite start <= end",
                     bounds.start, bounds.end
                 )));
             }

As per coding guidelines, “Validate inputs at API boundaries and reject invalid values with descriptive errors; never silently clamp or adjust them.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-index/src/vector/sq.rs` around lines 163 - 168, Update the
injected-bounds validation in the SQ builder to reject finite ranges where
bounds.start is greater than bounds.end, returning a descriptive invalid-input
error that includes both values. Preserve acceptance of equal endpoints and the
existing non-finite validation behavior.

Source: Coding guidelines

rust/lance/src/index/vector/ivf/v2.rs (2)

4060-4072: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Require at least 0.5 HNSW-SQ overlap.

With K = 10, overlap >= K / 3 accepts only 3/10 = 0.3 overlap, below the required 0.5 recall floor.

Proposed fix
-                overlap >= K / 3,
+                overlap as f32 / K as f32 >= 0.5,

As per coding guidelines, “Include ... vector-index recall assertions of at least 0.5.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 4060 - 4072, Update the
overlap assertion in the single-vs-split HNSW-SQ comparison to enforce at least
0.5 recall, replacing the K / 3 threshold with a threshold that guarantees half
of K while preserving the existing overlap calculation and diagnostic message.

Source: Coding guidelines


4122-4127: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the error variant as well.

This regression test checks only the message substring. Capture the error and assert the concrete index-error variant in addition to the "SQ metadata mismatch across shards" message.

As per coding guidelines, “Assert on both the error variant and the message content in tests; do not check only is_err().”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 4122 - 4127, Update the
regression test around the existing error assertion to inspect the captured
error’s concrete index-error variant in addition to checking that its message
contains “SQ metadata mismatch across shards.” Preserve the current failure
message while replacing the message-only validation with assertions covering
both the variant and message content.

Source: Coding guidelines

🟡 Other comments (1)
rust/lance-index/src/vector/sq.rs-421-451 (1)

421-451: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the concrete error contract in these tests.

The rejection tests only call is_err(), so they do not verify the InvalidInput variant or message content. Capture each error and assert both the concrete variant and descriptive message, including for unsupported element types.

As per coding guidelines, “Assert on both the error variant and the message content in tests; do not check only is_err().”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-index/src/vector/sq.rs` around lines 421 - 451, The tests
test_build_rejects_non_finite_injected_bounds and
test_build_with_injected_bounds_rejects_unsupported_type should validate the
concrete InvalidInput error contract instead of only calling is_err(). Capture
each ScalarQuantizer::build error, assert it matches the InvalidInput variant,
and verify the message contains the descriptive text for non-finite bounds and
unsupported element types.

Source: Coding guidelines

🧹 Nitpick comments (1)
rust/lance/src/index/vector/ivf/v2.rs (1)

3922-3937: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use an enum for the SQ index kind.

make_sq_index_params uses arbitrary strings as a variant discriminator. Pass IndexType::IvfSq / IndexType::IvfHnswSq instead and match exhaustively; update the rstest cases and comparisons accordingly.

As per coding guidelines, “Use enums instead of magic numbers for format versions, variant types, and discriminators, and rely on exhaustive match.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 3922 - 3937, Update
make_sq_index_params to accept IndexType instead of &str, matching exhaustively
on IndexType::IvfSq and IndexType::IvfHnswSq and removing the unexpected-string
panic branch. Update all rstest cases, callers, and related comparisons to pass
and compare the enum variants directly.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 4001-4010: The merge test around ds_single and ds_split only
validates SQ bounds; extend coverage to the full compatibility contract by
asserting sq_meta.num_bits equals 8 and adding a merge case with differing bit
widths, or add a focused unit test for the compatibility helper that verifies
rejection. Keep the existing bounds-mismatch coverage intact.

---

Other comments:
In `@rust/lance-index/src/vector/sq.rs`:
- Around line 421-451: The tests test_build_rejects_non_finite_injected_bounds
and test_build_with_injected_bounds_rejects_unsupported_type should validate the
concrete InvalidInput error contract instead of only calling is_err(). Capture
each ScalarQuantizer::build error, assert it matches the InvalidInput variant,
and verify the message contains the descriptive text for non-finite bounds and
unsupported element types.

---

Duplicate comments:
In `@rust/lance-index/src/vector/sq.rs`:
- Around line 163-168: Update the injected-bounds validation in the SQ builder
to reject finite ranges where bounds.start is greater than bounds.end, returning
a descriptive invalid-input error that includes both values. Preserve acceptance
of equal endpoints and the existing non-finite validation behavior.

In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 4060-4072: Update the overlap assertion in the single-vs-split
HNSW-SQ comparison to enforce at least 0.5 recall, replacing the K / 3 threshold
with a threshold that guarantees half of K while preserving the existing overlap
calculation and diagnostic message.
- Around line 4122-4127: Update the regression test around the existing error
assertion to inspect the captured error’s concrete index-error variant in
addition to checking that its message contains “SQ metadata mismatch across
shards.” Preserve the current failure message while replacing the message-only
validation with assertions covering both the variant and message content.

---

Nitpick comments:
In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 3922-3937: Update make_sq_index_params to accept IndexType instead
of &str, matching exhaustively on IndexType::IvfSq and IndexType::IvfHnswSq and
removing the unexpected-string panic branch. Update all rstest cases, callers,
and related comparisons to pass and compare the enum variants directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 846465e2-7724-4c2d-a9e6-15ac92c97c4e

📥 Commits

Reviewing files that changed from the base of the PR and between fa9d942 and dca1acd.

📒 Files selected for processing (3)
  • rust/lance-index/src/vector/distributed/index_merger.rs
  • rust/lance-index/src/vector/sq.rs
  • rust/lance/src/index/vector/ivf/v2.rs

…act tests, robust HNSW recall

- Document that injected bounds are in the quantizer's POST-TRANSFORM space:
  cosine indexes normalize first, so cosine bounds cover the normalized
  components (-1..1), not the raw values -- wrong-space bounds clip codes and
  silently collapse the index. (Mechanism is space-agnostic; this is a caller
  contract.) Add a compiling doc example on `with_bounds`.
- Reject reversed injected bounds (start > end) too, alongside NaN/inf;
  start == end stays valid.
- Error-path tests now assert the exact variant (InvalidInput / Index) plus
  message, not only is_err()/substring.
- IVF_HNSW_SQ merge test: aggregate top-k overlap across all queries and check
  the K/3 recall floor (the file's existing single-vs-split SQ idiom); the
  build is non-deterministic, so a per-query threshold flaked -- verified
  stable across repeated runs.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
rust/lance-index/src/vector/sq/builder.rs-19-24 (1)

19-24: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the endpoint validity contract.

The build path rejects non-finite or reversed bounds and permits equal endpoints. State those valid-value rules here so callers can validate inputs before build time.

As per coding guidelines, “Add doc comments to public API elements that convey semantic meaning, valid values, and effects.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-index/src/vector/sq/builder.rs` around lines 19 - 24, Update the
public `bounds` field documentation to state that bounds must have finite
endpoints, may use equal lower and upper endpoints, and must not be reversed.
Keep the existing post-transform-space and clipping behavior documentation
intact.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-index/src/vector/sq/builder.rs`:
- Around line 14-24: The public SQBuildParams change adding bounds breaks
existing exhaustive struct literals. Preserve source compatibility for prior
SQBuildParams literals, or coordinate this as an intentional breaking API
change; do not rely on with_bounds, and follow the project’s
deprecation-and-replacement convention if an API transition is required.

---

Other comments:
In `@rust/lance-index/src/vector/sq/builder.rs`:
- Around line 19-24: Update the public `bounds` field documentation to state
that bounds must have finite endpoints, may use equal lower and upper endpoints,
and must not be reversed. Keep the existing post-transform-space and clipping
behavior documentation intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 9d538b88-b40d-415c-801e-7869d6cf8e5d

📥 Commits

Reviewing files that changed from the base of the PR and between dca1acd and 3b40a21.

📒 Files selected for processing (3)
  • rust/lance-index/src/vector/sq.rs
  • rust/lance-index/src/vector/sq/builder.rs
  • rust/lance/src/index/vector/ivf/v2.rs

Comment on lines +14 to +24
/// User-provided, globally-trained quantization bounds. When set, per-build
/// training is skipped and these exact bounds are used, so codes are
/// comparable across independently built shards (mirrors
/// `PQBuildParams::codebook`).
///
/// Must be the global min/max in the quantizer's **post-transform** space:
/// cosine indexes L2-normalize first, so cosine bounds cover the normalized
/// components (`-1.0..=1.0`), not the raw values; L2/dot use the raw
/// min/max. Bounds that don't cover that space clip every code to 0 and
/// silently collapse the index.
pub bounds: Option<std::ops::Range<f64>>,

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve compatibility for existing SQBuildParams literals.

Adding bounds makes downstream exhaustive literals fail to compile; with_bounds does not preserve callers that only set the prior public fields. Keep this source-compatible or release it as a coordinated breaking API change.

As per coding guidelines, “Do not break public API signatures; deprecate old APIs with #[deprecated] or @deprecated and add a replacement.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-index/src/vector/sq/builder.rs` around lines 14 - 24, The public
SQBuildParams change adding bounds breaks existing exhaustive struct literals.
Preserve source compatibility for prior SQBuildParams literals, or coordinate
this as an intentional breaking API change; do not rely on with_bounds, and
follow the project’s deprecation-and-replacement convention if an API transition
is required.

Source: Coding guidelines

…s merge guard

- Aggregate IVF_HNSW_SQ recall floor raised K/3 -> 0.5 (the repo minimum). The
  aggregate measures ~0.71-0.79 across runs with tight variance, so 0.5 is a
  stable, non-flaky floor (the earlier K/3 was over-anchored on a single noisy
  per-query sample).
- Add a focused unit test for `ensure_sq_metadata_compatible` covering the
  num_bits mismatch path (identical bounds, different num_bits -> rejected),
  which the integration test alone didn't exercise; also assert num_bits == 8
  in the successful merge test.

@coderabbitai coderabbitai Bot 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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/index/vector/ivf/v2.rs (1)

4128-4136: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Match error by reference. matches!(error, lance_core::Error::Index { .. }) consumes error, so the later to_string() call on the same binding won’t compile. Use matches!(&error, ...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 4128 - 4136, Update the
first assertion’s matches! call to match error by reference using &error,
preserving ownership so the subsequent error.to_string() assertion remains
compilable.
🟡 Other comments (1)
rust/lance-index/src/vector/distributed/index_merger.rs-1679-1692 (1)

1679-1692: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the error variant and diagnostic content.

The is_err() checks can pass for any error. Capture each failure and assert Error::Index plus the "SQ metadata mismatch across shards" message, including the relevant num_bits/bounds context.

As per coding guidelines, “Assert on both the error variant and the message content in tests; do not check only is_err().”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-index/src/vector/distributed/index_merger.rs` around lines 1679 -
1692, Update ensure_sq_metadata_compatible_checks_bounds_and_num_bits to capture
each incompatible-metadata result and assert it is the Error::Index variant with
the “SQ metadata mismatch across shards” diagnostic, including the relevant
num_bits and bounds values for each failure case. Replace the generic is_err()
assertions while preserving the existing compatible-metadata check.

Source: Coding guidelines

🧹 Nitpick comments (1)
rust/lance/src/index/vector/ivf/v2.rs (1)

3922-3939: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use IndexType for the SQ test cases instead of strings. Pass IndexType::IvfSq / IndexType::IvfHnswSq into make_sq_index_params, and switch the later index_kind == "IVF_SQ" check to an enum match so the variants stay typed consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/ivf/v2.rs` around lines 3922 - 3939, Update
make_sq_index_params to accept IndexType instead of &str and match on
IndexType::IvfSq and IndexType::IvfHnswSq. Change all SQ test call sites to pass
those enum variants, and replace the later index_kind == "IVF_SQ" comparison
with an enum-based match while preserving the existing behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 4128-4136: Update the first assertion’s matches! call to match
error by reference using &error, preserving ownership so the subsequent
error.to_string() assertion remains compilable.

---

Other comments:
In `@rust/lance-index/src/vector/distributed/index_merger.rs`:
- Around line 1679-1692: Update
ensure_sq_metadata_compatible_checks_bounds_and_num_bits to capture each
incompatible-metadata result and assert it is the Error::Index variant with the
“SQ metadata mismatch across shards” diagnostic, including the relevant num_bits
and bounds values for each failure case. Replace the generic is_err() assertions
while preserving the existing compatible-metadata check.

---

Nitpick comments:
In `@rust/lance/src/index/vector/ivf/v2.rs`:
- Around line 3922-3939: Update make_sq_index_params to accept IndexType instead
of &str and match on IndexType::IvfSq and IndexType::IvfHnswSq. Change all SQ
test call sites to pass those enum variants, and replace the later index_kind ==
"IVF_SQ" comparison with an enum-based match while preserving the existing
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: c1f5644c-eb1d-4515-9791-7ca72ab431dd

📥 Commits

Reviewing files that changed from the base of the PR and between 3b40a21 and 06581a0.

📒 Files selected for processing (2)
  • rust/lance-index/src/vector/distributed/index_merger.rs
  • rust/lance/src/index/vector/ivf/v2.rs

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.19277% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-index/src/vector/sq.rs 97.72% 0 Missing and 2 partials ⚠️
rust/lance/src/index/vector/ivf/v2.rs 97.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@xuanyu-z

Copy link
Copy Markdown
Contributor Author

Not sure if the left over coderabbit comment need to be addressed, need a maintainer here

@xuanyu-z xuanyu-z closed this Jul 22, 2026
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 A-java Java bindings + JNI enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant