lance-format / lance-format/lance
bug: the current IVF build path does not enforce the precomputed-input contracts
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 7.1k
- Forks
- 852
- Avg merge
- 3d 18h
- Merged PRs (30d)
- 272
Description
Description
IvfBuildParams documents two contracts on its precomputed inputs (rust/lance-index/src/vector/ivf/builder.rs):
/// Precomputed partitions file (row_id -> partition_id)
/// mutually exclusive with `precomputed_shuffle_buffers`
pub precomputed_partitions_file: Option<String>,
/// Precomputed shuffle buffers (row_id -> partition_id, pq_code)
/// mutually exclusive with `precomputed_partitions_file`
/// requires `centroids` to be set
pub precomputed_shuffle_buffers: Option<(Path, Vec<String>)>,
sanity_check_ivf_params enforces them, but the only path that reaches it is build_ivf_model_and_pq, used by the legacy IVF_PQ and IVF_HNSW_PQ writers. IvfIndexBuilder, the V3 path every new index takes, never checks them.
With buffers set and no centroids, build_ivf_model trains centroids from a fresh sample. shuffle_dataset then reads the buffer dataset as the shuffle input, and since those batches already carry the quantizer's code column, shuffle_data returns them untouched, keeping the __ivf_part_id the buffers were written with. The index ends up storing the newly trained centroids over rows partitioned against different ones, so find_partitions picks probes by proximity to centroids that say nothing about where the rows went: the probed partitions hold rows that are not near the query, and the near rows sit in partitions that are never probed. Recall collapses, and nothing reports it at build or query time. For L2 and cosine PQ the stored codes are residuals against the buffers' centroid while the scorer adds back the trained one, so the distances are wrong too.
With both precomputed inputs set, shuffle_data loads the partition file and calls try_with_column(PART_ID_FIELD) on batches that already have that column. SchemaExt::try_with_column rejects the duplicate name, and the call site .expects inside a tokio::spawn, so the build panics in a worker task.
precomputed_partitions_file without centroids breaks the same way the buffers do: the file's assignment belongs to whichever centroids produced it.
The legacy check has a third rule this report does not cover. It also requires a PQ codebook when the buffers are set, and V3 has no equivalent: Quantization and QuantizerBuildParams expose no way to ask whether a model was supplied, so that check cannot be written generically where the builder sits today.
Expected behavior
Reject all three combinations with an invalid_input error before the build starts, as the legacy writer does.
Lance version
13.0.0-beta.4 (main)
Language binding
Rust
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in rust/lance-index/src/vector/ivf/builder.rs, reading sanity_check_ivf_params alongside the IvfIndexBuilder path and the legacy build_ivf_model_and_pq path. Trace how precomputed partitions and shuffle buffers reach the build, then verify the V3 path rejects the three invalid combinations with an invalid_input error before building.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- machine-learning, search
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100