Skip to content

Quick Start

RaBitQ Library provides Python bindings for complete vector-search indexes and a C++17 API for both indexes and low-level quantization.

Requirements

Platform CPU baseline Python wheel targets
Linux x86-64 AVX2 and FMA CPython 3.11–3.14
Linux ARM64 (AArch64) NEON CPython 3.11–3.14
Windows x86-64 AVX2 and FMA CPython 3.11–3.14
macOS 14+ ARM64 (Apple Silicon) NEON CPython 3.11–3.14

Source builds require a C++17 compiler, OpenMP, and CMake 3.20 or newer. Windows uses MSVC (Visual Studio 2026 with the Desktop development with C++ workload and CMake 4.2+ for its generator). Apple Silicon uses AppleClang and an external OpenMP runtime such as Homebrew libomp.

Linux ARM64 source builds and repaired wheels run on native AArch64 CI. Linux ARM64 wheels carry manylinux_2_27_aarch64 and manylinux_2_28_aarch64 tags.

CPU dispatch details On x86-64, optional AVX-512 kernels require AVX2, FMA, and AVX-512F/BW/DQ; MSVC builds also require AVX-512VL/CD. Popcount-specific kernels additionally require AVX-512 VPOPCNTDQ. Detection checks CPU features and OS support for the required register state. Missing AVX-512 features select the AVX2 backend. On ARM64, the build excludes x86 kernels and uses NEON and portable scalar implementations. Standard FastScan requires AVX2/FMA, a supported AVX-512 backend, or NEON; scalar fallbacks do not remove that requirement.

Python

Install

python -m pip install "rabitqlib>=0.6.0"

Configurable IVF routing and the concurrency contract below require 0.6.0; see the upgrade notes. For unreleased changes, install from a checkout.

Wheels target the platforms above and require no compiler or CMake. Linux ARM64 and macOS ARM64 wheels bundle OpenMP, so wheel users do not need a separate OpenMP installation. Release wheels disable native CPU tuning and select supported kernels at runtime.

Build and search an IVF index

The following complete example uses deterministic synthetic data and does not require a dataset download:

import numpy as np
from rabitqlib import FinalAssignmentMode, IvfIndex, RaBitQKMeans

rng = np.random.default_rng(42)
data = rng.standard_normal((500, 64)).astype(np.float32)
queries = rng.standard_normal((5, 64)).astype(np.float32)

clustering = RaBitQKMeans(
    64, 5, num_threads=2, final_assignment=FinalAssignmentMode.Exact
)
clustering.train(data)

index = IvfIndex(
    dim=64,
    max_elements=len(data),
    num_clusters=5,
    nbits=4,
    metric="l2",
)
index.build(data, clustering.centroids, clustering.assignments)

ids, distances = index.search(queries, k=10, nprobe=5)
print(ids.shape, distances.shape)  # (5, 10) (5, 10)
print(ids[0])

For raw-vector reranking, use nbits=32 in the constructor above. The index copies the original float32 vectors instead of storing extra-bit codes, while retaining one-bit codes for filtering. Build, search, and save/load use the same APIs; the original data can be released after construction.

IVF selects FastScan precision automatically: HACC for 4–9-bit codes, standard FastScan for 1–3-bit codes and raw vectors. To override that choice:

ids, distances = index.search(queries, k=10, nprobe=5, high_accuracy=True)
ids, distances = index.search(queries, k=10, nprobe=5, high_accuracy=False)
# Omit high_accuracy, or pass None, to use automatic selection.

Add and remove IVF vectors

Starting with 0.5.1, a built or loaded IVF index can accept new vectors and exclude existing vectors from search without the original dataset:

new_vectors = rng.standard_normal((50, 64)).astype(np.float32)
new_ids = index.add(new_vectors)  # automatic cluster routing; ids 500..549
removed = index.remove(new_ids[:10])  # returns 10; storage is retained

Batch additions because each add() copies the index storage. Removed IDs are not reused, and both additions and removals survive save/load. See the IVF update guide for costs and limits.

The metric argument accepts "l2" and "ip" (also spelled "innerproduct"). To search by cosine similarity, normalize database and query vectors first and use metric="ip".

Add and remove HNSW vectors

With 0.5.2 or newer, reuse data, queries, and clustering from the IVF example above to build and update an HNSW index:

from rabitqlib import HnswIndex

hnsw = HnswIndex(
    dim=64, max_elements=len(data), M=16, ef_construction=100, nbits=4,
)
hnsw.build(data, clustering.centroids, clustering.assignments)

# HNSW needs explicit capacity growth before adding beyond max_elements.
new_vectors = rng.standard_normal((50, 64)).astype(np.float32)
hnsw.resize(hnsw.max_elements + len(new_vectors))
new_ids = hnsw.add(new_vectors)  # automatic nearest-centroid routing
removed = hnsw.remove(new_ids[:10])  # returns 10
ids, distances = hnsw.search(queries, k=10, ef=100)

Removed points remain in the graph and count toward capacity, but never appear in results. Additions and removals survive save/load; files containing HNSW removals require 0.5.2 or newer. Do not update an index while it is being searched. See the HNSW update guide for recall and capacity considerations.

See the Python examples for IVF, HNSW, and SymphonyQG. For clustering, choose RaBitQKMeans for flat assignment, recommended for small cluster counts, or QGKMeans for graph assignment.

Threading and file paths

Starting with 0.6.0, the library releases the Python GIL during native index search, construction, updates, and file I/O. The following contract applies to IvfIndex, HnswIndex, and SymqgIndex:

Operation on the same index Concurrent access
search, search_batch, save, and property reads May run together
build, add, remove, and resize, where available Require exclusive access

A conflicting call immediately raises RuntimeError("Index is busy: conflicting operation in progress"). It does not wait or queue an update. Access protection includes Python input conversion and is released when the operation returns or raises an exception. Different index objects can operate independently. Concurrent saves must use different output paths, including any sidecar files.

Search parameters such as ef, nprobe, precision, and num_threads belong to each call. When using a Python thread pool, consider num_threads=1 to avoid starting multiple native worker pools. Keep input arrays and any shared backing storage unchanged for the entire call; do not resize or modify them from another thread. Returned result arrays own their storage.

These access checks belong to the Python wrappers. C++ callers must synchronize index updates themselves and use separate search output buffers.

num_threads=0 selects the detected available logical CPU count. Positive values set an upper limit, capped at that count; small workloads may use fewer workers. Python index methods default to one thread; clustering defaults to 0. On Linux, CPU detection accounts for affinity and OpenMP binding. Apply binding before starting the program.

Index save/load paths are UTF-8 strings on Windows and native path bytes on POSIX in C++; Python paths are Unicode strings on all platforms.

Index file compatibility

The compatibility suite loads frozen files written by v0.5.2, compares search IDs and distances, and verifies save/load migration and rejection of truncated or unsupported versions. See the fixture provenance and coverage. Compatibility is format-specific; preserving old readers' ability to open new files is not guaranteed. Consult each index's format documentation before downgrading the library.

Build the Python bindings from source

Source builds require Python 3.11 or newer, a C++17 compiler, CMake 3.20 or newer, and OpenMP. To install the current development version on Ubuntu or Debian:

sudo apt-get update
sudo apt-get install -y build-essential cmake libomp-dev
git clone https://github.com/VectorDB-NTU/RaBitQ-Library.git
cd RaBitQ-Library
python -m pip install .

On Apple Silicon, use a native ARM64 Python environment. From the repository root:

brew install cmake ninja libomp
python -m pip install . -Ccmake.define.OpenMP_ROOT="$(brew --prefix libomp)"

On Windows, install the build tools listed above, then run python -m pip install . from the repository root. For a portable source build on any supported platform, also pass -Ccmake.define.RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF.

C++

On Linux, clone the repository and build the library and examples:

git clone https://github.com/VectorDB-NTU/RaBitQ-Library.git
cd RaBitQ-Library

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel

CMake enables native CPU tuning by default where supported by the compiler. For portable binaries within a supported OS and architecture, configure with -DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF; runtime kernel selection remains active. See the platform-specific build commands for Linux ARM64, Windows, and Apple Silicon.

C++ examples

Example executables are written to bin/. Their source demonstrates complete indexing and querying workflows:

The low-level quantization example is provided as source and is not currently a CMake target. For the GIST benchmark workflow, see example.sh.

Use in another C++ project

The C++ API and ABI are still evolving. For reproducible builds, pin a release or commit and include RaBitQ-Library as a Git submodule:

git submodule add https://github.com/VectorDB-NTU/RaBitQ-Library.git third_party/rabitqlib
git submodule update --init --recursive
git -C third_party/rabitqlib checkout <release-or-commit>
git add third_party/rabitqlib

Add the library and link its namespaced target in your CMakeLists.txt:

set(RABITQ_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/rabitqlib)
target_link_libraries(my_program PRIVATE rabitqlib::rabitqlib)

Update the pinned revision deliberately when adopting upstream changes:

git -C third_party/rabitqlib fetch
git -C third_party/rabitqlib checkout <release-or-commit>
git add third_party/rabitqlib

Install the C++ library

Installation is useful for package managers, container images, and shared server environments. Disable native optimization for use on other CPUs:

cmake -S . -B build \
  -DRABITQ_BUILD_SAMPLES=OFF \
  -DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$HOME/.local"
cmake --build build --parallel
cmake --install build

Consume the installed package with:

find_package(rabitqlib CONFIG REQUIRED)
target_link_libraries(my_program PRIVATE rabitqlib::rabitqlib)

For a non-system prefix, point CMake to the installation:

cmake -S . -B build -DCMAKE_PREFIX_PATH="$HOME/.local"
cmake --build build --parallel

Both submodule and installed-package integration require OpenMP on the consuming system. The downstream consumer test provides a complete installed-package example.

Run the C++ tests on Linux

cmake -S . -B build -DRABITQ_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF
cmake --build build --parallel
ctest --test-dir build --output-on-failure

GoogleTest is downloaded during test configuration.

Next steps