Repository navigation
feat: three-line index workflow, faster IVF add, and v0.8.2 - #209
Merged
Merged
Conversation
add without cluster_ids routed each vector with the flat initializer's single-query centroid scan, so every vector streamed the whole centroid table from memory. With train() leaving assignment to add, this dominated the default build path (GIST1M, 4096 clusters, 48 threads: add 24-26 s versus 2.5 s with explicit cluster ids). Flat routing now rotates rows in 128 MiB blocks and assigns them with k-means' blocked exact assignment, which reuses each centroid tile across many rows. On the same workload add takes 6.4 s with byte-identical index files; on other data, assignments differ only at float-rounding ties, where the new choice matches a float64 reference. Flat RaBitQ and HNSW routing are unchanged.
Make the three indexes start with the same three lines: construct, add, search. - IvfIndex and HnswIndex train on the first add when untrained, as train(data) does, staging the work on a fresh index so a failed call leaves the index untrained and empty. HNSW trains one centroid per row when given fewer rows than num_clusters. add with cluster_ids still requires training. - nbits defaults to 7 for IvfIndex and HnswIndex. - IVF nprobe may be omitted: num_clusters / 16, rounded, at least 1. - Graph search ef defaults to max(k, 64) instead of max(k, 10). - Index training defaults to 10 k-means iterations instead of 25, for IvfIndex and HnswIndex (C++ default_training_parameters). - k may exceed the number of points: IVF, HNSW and SymphonyQG pad missing results with ID 2**32 - 1 and distance inf instead of raising, in C++ and Python. A trained index without points returns only padding. - num_threads accepts None everywhere (add: all CPUs, search: 1 thread). - SymqgIndex.add returns the point IDs; HnswIndex exposes M and ef_construction; rabitqlib.__version__ reports the package version. The ef and niter defaults change results of code that relies on them. Docs describe the new workflow and recommend np.isfinite(distances) to find missing results.
- QuantizedGraph::search_with_scratch rejects a result pool whose capacity is not k. Padding had removed the check that previously caught it, so a larger pool overflowed the result buffers. - Flat routing in IVF add caches the centroid norms on first use, cleared whenever the initializer changes, and passes them to exact_assign. Single -row adds no longer recompute every norm (960 dims, 4096 clusters, one thread: 3.31 ms -> 0.47 ms per add; 0.45 ms before blocked routing). - IVF and SymphonyQG size their internal result and search pools by the point count, not by k, so a large k only costs the output arrays. - An empty first add on an untrained IvfIndex raises RuntimeError, as for HnswIndex, instead of a row-count ValueError. - __version__ comes from the CMake project version compiled into the extension, so installs without package metadata still import. - Docs: fix the migration guide (k padding, ef changing results, state errors, untrained 0.7 HNSW files), restore nprobe in the README example, correct thread-default and is_trained wording, document the 0.7-form IVF exception, and note that flat add routing and query routing may resolve near-ties differently. - Tests cover the scratch pool check, batching and reload independence of flat routing, first-add settings (raw storage, initializers, cp, drawn seed, preallocated HNSW capacity) and the empty first add.
Rerun every RaBitQ-Library method on GIST1M and DBpedia with a wheel built from the 0.8.2 release candidate (1d49ecd; GCC 14.3.0, Release, native optimization OFF). Faiss and SuperKMeans results are retained. IVF-RaBitQ now builds the way the library does by default: IvfIndex.train (10 iterations, QGKMeans above 2,000 clusters) followed by add, which assigns the vectors, instead of RaBitQKMeans with exact final assignment and explicit cluster IDs. The benchmark script, protocol text, provenance, key numbers and homepage figure are updated. HNSW-RaBitQ and SymphonyQG protocols are unchanged; k-means results are bit-identical to 0.8.0.
Summarize what the library offers before the news: the reference implementation from the RaBitQ authors, fast indexing and search with a link to the benchmarks, and the choice between ready-made indexes and the core components.
Bump the version to 0.8.2, update install pins and version text, and add the 0.8.2 release notes with their navigation entry and a README news entry. The notes call out the two default changes that alter results: graph search ef and index training iterations.
- Homepage benchmark figure: label the results RaBitQ-Library 0.8.2. - 0.8.2 notes: a 0.7-form IvfIndex is filled only by build, while a 0.7-form HnswIndex trains on its first add; an empty first add raises RuntimeError; state that the 24-26 s -> 6.4-6.7 s add timing is a separate A/B run with nbits=5, not part of the benchmark tables. - HNSW removal example uses the IDs returned by add, so the page runs in order. - Migration guide lists the 0.8.2 C++ behavior changes. - Benchmark pages: SymphonyQG uses nbits, and the wheel command is described as the equivalent v0.8.2 build. - usage.md: deprecated search_batch accepts None; only build needs an integer. - num_clusters docs and docstrings mention the first add and HNSW's row cap; the 0.7 IvfIndex deprecation message says the first add trains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Python users now get an index running in three lines, with the same shape for every index:
This PR also makes IVF-RaBitQ
addmuch faster when it assigns clusters itself, refreshes the benchmark results, and prepares the v0.8.2 release. Release notes:docs/docs/releases/0.8.2.md.Changes
IVF add routing (fix)
autobelow 5,000 clusters),addwithoutcluster_idsscanned every centroid separately for each vector.exact_assign, with centroid norms cached per centroid set.adddrops from 24–26 s to 6.4–6.7 s, with byte-identical index files.Python API (additive)
addon an untrainedIvfIndexorHnswIndextrains it. The training runs on a fresh object that is swapped in only on success, so a failed call leaves the index untrained and empty.nbitsdefaults to 7.nprobeis optional; it defaults tonum_clusters / 16.kmay exceed the point count; missing slots are padded instead of raising, in both C++ and Python.num_threads=Noneis accepted bytrain,addandsearch.SymqgIndex.addreturns IDs.HnswIndex.MandHnswIndex.ef_constructionproperties, andrabitqlib.__version__.Changes to results (in the release notes)
efnow defaults tomax(k, 64)instead ofmax(k, 10).cp, and C++default_training_parameters) runs 10 iterations instead of 25, for both IVF and HNSW.Review fixes
An independent code review and docs review of the first two commits found the following; all are fixed with regression tests:
QuantizedGraph::search_with_scratchoverflowed memory when given a result pool larger thank. It now checks that the pool capacity equalsk.k.addhad regressed 4–7×; fixed by the cached centroid norms.addraised the wrong error type.__version__failed without package metadata; it is now compiled into the extension from the CMake version.Benchmarks
All library methods were rerun on GIST1M and DBpedia with a wheel built from 1d49ecd (GCC 14.3.0, Release, native optimization OFF). IVF-RaBitQ now uses the library's default
train+addpath.Docs and release
pyproject.tomlandCMakeLists.txt.Checks
Run on Linux x86-64, Xeon Gold 6418H, GCC 14.3.0 / clang 23.
ctest(native optimization OFF)pytest tests/pythoncheck-format.sh,check-python.shcheck-tidy.sh(full)check-isa-symbols.shmkdocs build --strict.github/scriptsunit testsDoc examples run: README, quick start, and the HNSW and SymphonyQG minimal workflows.
Downstream compatibility (before merging the version bump)
tests/python/test_txtai_compat.py, which mirrors txtai's adapter call for call, passes. The real txtai adapter has not been run; txtai is not installed in the test environment. txtai's HNSW searches omitef, so they pick up the newefdefault, which changes their results and latency.