Skip to content

RaBitQ Library 0.8.2

This release shortens the Python workflow to three lines for every index — construct, add, search — and makes adding vectors to IVF-RaBitQ much faster when the index assigns their clusters. Two default changes alter the results of existing code; see Changes to results.

index = IvfIndex(dim, num_clusters, metric="ip")  # or HnswIndex(dim) / SymqgIndex(dim)
index.add(vectors)  # the first add trains the index
ids, distances = index.search(queries, k=10)

Python changes

  • The first add on an untrained IvfIndex or HnswIndex trains it on the added vectors, as train(vectors) does, so train is needed only to train on a sample. If that add fails, the index stays untrained and empty. add with cluster_ids still requires a trained index. IVF-RaBitQ needs at least num_clusters rows; HNSW-RaBitQ trains one centroid per row when given fewer rows than num_clusters. An empty first add raises RuntimeError. An IvfIndex created in the 0.7 max_elements form is still filled only by build, and untrained HNSW-RaBitQ files saved by 0.7 need train before add; a 0.7-form HnswIndex trains on its first add.
  • nbits defaults to 7 for IvfIndex and HnswIndex; the 0.7 HNSW-RaBitQ form keeps 8 and SymphonyQG keeps 32.
  • IvfIndex.search no longer requires nprobe: omitted, None or 0 probes num_clusters / 16 clusters, rounded and at least one.
  • k may exceed the number of points. IVF-RaBitQ, HNSW-RaBitQ and SymphonyQG fill the missing slots with ID 2**32 - 1 and distance inf instead of raising ValueError; a trained index without points returns only such slots. Test np.isfinite(distances) to find real results.
  • train, add and search on every index accept num_threads=None for their defaults: all CPUs for train and add, one thread for search. When the first add trains, None uses index.cp.num_threads for training.
  • SymqgIndex.add returns the point IDs, HnswIndex reports M and ef_construction, and rabitqlib.__version__ reports the package version.

Changes to results

  • HnswIndex.search and SymqgIndex.search without ef use max(k, 64) instead of max(k, 10): recall rises and queries take longer. Pass ef= to keep the old window.
  • IvfIndex and HnswIndex training (index.cp) and the C++ default_training_parameters run 10 k-means iterations instead of 25, as Faiss IVF does. The same seed therefore no longer reproduces a 0.8.1 index; set index.cp.niter = 25 to restore the old training. The k-means classes keep 25.

Performance

IVF-RaBitQ add without cluster_ids assigns vectors to Flat-routed centroids (the auto choice below 5,000 clusters) with the blocked exact assignment of k-means instead of one centroid scan per vector. On GIST1M with 4,096 clusters, nbits=5 and 48 threads, a separate A/B run (not part of the benchmark tables) measured adding all vectors at 24–26 s in 0.8.1 and 6.4–6.7 s now, with identical index files. Assignments can differ only at floating-point ties, where the new result matches a float64 reference. See the refreshed benchmarks, whose IVF-RaBitQ results now use this default train and add path.

C++ changes

  • IVF::search and QuantizedGraph searches accept k above the point count and fill missing slots with kPidMax and +inf instead of throwing.
  • QuantizedGraph::search_with_scratch throws std::invalid_argument when the result pool's capacity is not k.
  • default_training_parameters runs 10 iterations, as above.

Upgrading

From 0.8.1, index file formats, the stable core API v1 and the Python signatures are compatible: every new default fills a parameter that was required or adds a value that previously raised. Review searches that omit ef and indexes trained with default settings, which change as described above. Rebuild C++ consumers with matching headers and libraries; no cross-version C++ binary ABI is promised.

When upgrading from 0.7, follow the 0.8.0 release notes and the migration guide.