Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Documentation

on:
push:
branches: ["main"]
paths:
- "docs/**"
- ".github/workflows/docs.yml"
pull_request:
paths:
- "docs/**"
- ".github/workflows/docs.yml"
workflow_dispatch:

concurrency:
group: documentation-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build documentation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: docs/requirements.txt
- name: Install documentation dependencies
run: python -m pip install -r docs/requirements.txt
- name: Build documentation
run: mkdocs build --strict --config-file docs/mkdocs.yml

deploy:
name: Deploy documentation
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: docs/requirements.txt
- name: Install documentation dependencies
run: python -m pip install -r docs/requirements.txt
- name: Deploy to GitHub Pages
run: mkdocs gh-deploy --force --config-file docs/mkdocs.yml
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ vector quantization and approximate nearest-neighbor search. It provides:
[multi-bit](https://arxiv.org/abs/2409.09913) RaBitQ quantizers;
- IVF, HNSW, and [SymphonyQG](https://dl.acm.org/doi/abs/10.1145/3709730)
indexes powered by RaBitQ;
- Euclidean distance, inner product, and cosine similarity estimation; and
- Euclidean distance and inner-product search (cosine search is
available by normalizing vectors before using inner product); and
- optimized AVX2 and AVX-512 kernels with runtime CPU dispatch.

RaBitQ is developed by the
Expand All @@ -29,11 +30,14 @@ University, Singapore. A GPU implementation is also available in
- a C++17 compiler
- CMake 3.15 or newer
- OpenMP
- an x86-64 CPU with AVX2 and FMA support
- an x86-64 CPU supported by the selected kernels: most paths accept either
AVX2 with FMA or AVX-512F/BW/DQ with FMA

AVX2 and FMA are the baseline SIMD requirements. On CPUs that also support
AVX-512F, AVX-512BW, and AVX-512DQ, RaBitQ selects its AVX-512 kernels at
runtime. AVX-512 VPOPCNTDQ provides additional acceleration when available.
Most SIMD entry points select AVX-512 kernels when AVX-512F, AVX-512BW, and
AVX-512DQ are detected; otherwise they use AVX2 when AVX2 and FMA are
available. AVX-512 VPOPCNTDQ enables additional popcount kernels. The HNSW
AVX-512 core path also checks for AVX2 and FMA, and otherwise uses its AVX2
path when available. AVX-512 translation units are compiled with FMA enabled.

On Ubuntu or Debian, install the system build tools and then install RaBitQ
from the repository:
Expand Down Expand Up @@ -88,11 +92,14 @@ persistence.

- CMake 3.10 or newer
- a C++17 compiler with OpenMP support
- an x86-64 CPU with AVX2 and FMA support
- an x86-64 CPU supported by the selected kernels: most paths accept either
AVX2 with FMA or AVX-512F/BW/DQ with FMA

AVX2 and FMA are the baseline SIMD requirements. On CPUs that also support
AVX-512F, AVX-512BW, and AVX-512DQ, RaBitQ selects its AVX-512 kernels at
runtime. AVX-512 VPOPCNTDQ provides additional acceleration when available.
Most SIMD entry points select AVX-512 kernels when AVX-512F, AVX-512BW, and
AVX-512DQ are detected; otherwise they use AVX2 when AVX2 and FMA are
available. AVX-512 VPOPCNTDQ enables additional popcount kernels. The HNSW
AVX-512 core path also checks for AVX2 and FMA, and otherwise uses its AVX2
path when available. AVX-512 translation units are compiled with FMA enabled.

Clone and build the library and example programs:

Expand Down Expand Up @@ -134,15 +141,18 @@ the build, formatting, pre-commit, and static-analysis workflows.

## Why RaBitQ?

- **High accuracy with tiny codes.** RaBitQ provides state-of-the-art
similarity estimation across different bit widths and remains effective at
one bit per dimension.
- **Fast distance estimation.** Its kernels use bitwise operations and
[FastScan](https://arxiv.org/abs/1704.07355) for efficient search.
- **High accuracy with tiny codes.** RaBitQ provides state-of-the-art similarity
estimation across different bit widths and remains effective with a
one-bit code per padded dimension plus per-vector factors.
- **Fast distance estimation.** IVF and SymphonyQG use
[FastScan](https://arxiv.org/abs/1704.07355) for batched estimates, while
HNSW uses single-code AVX2 or AVX-512 kernels.
- **Theoretical error bounds.** RaBitQ provides an asymptotically optimal error
bound that can support reliable ordering and reranking.
- **Multiple index trade-offs.** IVF and HNSW prioritize memory efficiency,
while SymphonyQG uses additional memory to optimize query performance.
- **Multiple index trade-offs.** IVF stores quantized codes without the raw
dataset. HNSW adds graph links but also searches from quantized codes.
SymphonyQG retains raw vectors and stores per-neighborhood quantization data
to improve its access pattern.

In typical workloads, 4-bit, 5-bit, and 7-bit quantization can achieve roughly
90%, 95%, and 99% recall, respectively, without reranking. Actual results
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/compact_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The code sequentially stores the binary value for each of the 64 dimensions.

![2-bit Storage](assets/img/compact/2bit.png){ width=300px align=center }

The code is stored in a byte array of length 16. Each row in the figure represents a byte. The 0-th byte stores the 2-bit codes of the 0-th, 16-th, 32-th and 48-th dimensions. The 1-th byte stores the 2-bit codes of the 1-th, 17-th, 33-th and 49-th dimensions, so on and so forth. This storage allows efficient unpacking with SIMD, i.e., shifting and masking with `SSE`.
The code is stored in a byte array of length 16. Each row in the figure represents a byte. The 0-th byte stores the 2-bit codes of the 0-th, 16-th, 32-th and 48-th dimensions. The 1-th byte stores the 2-bit codes of the 1-th, 17-th, 33-th and 49-th dimensions, and so on. This layout supports efficient shifting and masking in the library's AVX2 and AVX-512 unpacking kernels.

## 3-bit = 2-bit + 1-bit

Expand All @@ -48,7 +48,7 @@ The code is stored in a byte array of length 16. Each row in the figure represen
![4-bit Storage](assets/img/compact/4bit.png){ width=600px align=center }


The code is stored in a byte array of length 32. The 0-th byte stores the 4-bit codes of the 0-th and 16-th dimensions. The 1-th byte stores the 4-bit codes of the 1-th and 17-th dimensions, so on and so forth. This storage allows efficient unpacking with SIMD, i.e., shifting and masking with `SSE`.
The code is stored in a byte array of length 32. The 0-th byte stores the 4-bit codes of the 0-th and 16-th dimensions. The 1-th byte stores the 4-bit codes of the 1-th and 17-th dimensions, and so on. This layout supports efficient shifting and masking in the library's AVX2 and AVX-512 unpacking kernels.

## 5-bit = 4-bit + 1-bit

Expand All @@ -62,7 +62,8 @@ The code is stored in a byte array of length 48.
- The second 16 bytes store the 6-bit codes of the 16-th to the 31-th dimensions and the upper 2-bit codes of the 48-th to 63-th dimensions.
- The third 16 bytes store the lower 4-bit codes of the 32-th to 47-th dimensions and the lower 4-bit codes of the 48-th to 63-th dimensions.

This storage allows efficient unpacking with SIMD, i.e., shifting and masking with `SSE`.
This layout supports efficient shifting and masking in the library's AVX2 and
AVX-512 unpacking kernels.

## 7-bit = 6-bit + 1-bit

Expand All @@ -74,4 +75,3 @@ The code of 8-bit is aligned with byte arrays and needs no specialized design.




184 changes: 130 additions & 54 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,130 @@
# Welcome to The RaBitQ Library

The RaBitQ Library provides efficient and lightweight implementations of the RaBitQ quantization algorithm ([1-bit version](https://arxiv.org/abs/2405.12497) and [multi-bit version](https://arxiv.org/abs/2409.09913)) and its applications in high-dimensional vector search. The core algorithm RaBitQ is based on the research from [VectorDB group](https://vectordb-ntu.github.io/) at Nanyang Technological University, Singapore.

The library provides the following key features:

* **RaBitQ** - a vector quantization algorithm as a drop-in replacement of binary and scalar quantization, offering an optimal theoretical error bound
* **RaBitQ for Vector Search** - a reference implementation of RaBitQ's combination with popular vector search indexes

The RaBitQ Library supports estimating similarity metrics including Euclidean distance, inner product and cosine similarity.

## RaBitQ

RaBitQ is a vector quantization algorithm as a drop-in replacement of binary and scalar quantization. The key advantages of RaBitQ include

- **High Accuracy with Tiny Space** - RaBitQ achieves the state-of-the-art accuracy under diverse bit-width for the estimation of similarity metrics. It produces promising accuracy with even **1-bit per dimension**.
- **Fast Distance Estimation** - RaBitQ supports to estimate the similarity metrics with high efficiency based on bitwise operations or [FastScan](https://arxiv.org/abs/1704.07355).
- **Theoretical Error Bound** - RaBitQ provides an asymptotically optimal error bound for the estimation of distances and inner product. The error bound can be used for reliable ordering and reranking.

In this library, we provide simple interfaces to support advanced features of RaBitQ. The details are presented in [RaBitQ](rabitq/rabitq.md).

## RaBitQ for Vector Search
In the library, RaBitQ is combined with [IVF](index/ivf.md), [HNSW](index/hnsw.md) and [QG](index/qg.md) to deliever different trade-offs among time, space and accuracy.

Using RaBitQ with IVF and HNSW targets a balance between memory consumption and query performance. Only the quantization codes produced by RaBitQ are stored and the raw data vectors are not accessed during querying. Thus, these methods consume less memory than the raw dataset.
Using **4-bit, 5-bit and 7-bit** quantization usually suffices to produce **90%, 95% and 99% recall** respectively without reranking.

Using RaBitQ with QG targets the best query performance by using more memory. It creates multiple quantization codes for every vector to optimize the data access pattern. Thus, QG usually consumes 2x memory of the raw dataset.


## RaBitQ in Industry

The RaBitQ algorithm has been implemented in many real-world systems in industry including

- [Milvus](https://github.com/milvus-io/milvus) - IVF + RaBitQ (C++)
- [Faiss](https://github.com/facebookresearch/faiss) - IVF + RaBitQ (C++)
- [VSAG](https://github.com/antgroup/vsag) - HGraph + RaBitQ (C++)
- [VectorChord](https://github.com/tensorchord/VectorChord) - IVF + RaBitQ (Rust)
- [Volcengine OpenSearch](https://www.volcengine.com/docs/6465/1553583) - DiskANN + RaBitQ
- [CockroachDB](https://github.com/cockroachdb/cockroach) - CSPANN + RaBitQ (Golang)
- [ElasticSearch](https://github.com/elastic/elasticsearch) - HNSW + RaBitQ (Java - the algorithm is adopted with some minor modifications and renamed as "BBQ")
- [Lucene](https://github.com/apache/lucene) - HNSW + RaBitQ (Java - the algorithm is adopted with some minor modifications and renamed as "BBQ")

## Acknowledgement

We acknowledge Alexandr Guzhva, Li Liu, Chao Gao, Silu Huang, Jiabao Jin, Xiaoyao Zhong and Jinjing Zhou for valuable feedbacks.


## Reference
Please provide a reference of our paper if it helps in your systems or research projects.

<pre style="white-space: pre-wrap; word-break: break-word; font-family: monospace; background: #f5f5f5; padding: 1em; border-radius: 5px; font-size: 0.85em;">
Jianyang Gao, Yutong Gou, Yuexuan Xu, Yongyi Yang, Cheng Long, Raymond Chi-Wing Wong, "Practical and Asymptotically Optimal Quantization of High-Dimensional Vectors in Euclidean Space for Approximate Nearest Neighbor Search", SIGMOD 2025, available at https://arxiv.org/abs/2409.09913
</pre>
# Compact vector search with RaBitQ

<div class="hero" markdown>

**RaBitQ Library** is a C++17 library with Python bindings for compact,
accurate vector quantization and approximate nearest-neighbor search.

Build with the low-level quantizer or use complete IVF, HNSW, and SymphonyQG
indexes backed by optimized AVX2 and AVX-512 kernels.

[Get started](quick_start.md){ .md-button .md-button--primary }
[View on GitHub](https://github.com/VectorDB-NTU/RaBitQ-Library){ .md-button }
[Install from PyPI](https://pypi.org/project/rabitqlib/){ .md-button }

</div>

<div class="feature-grid">
<div class="feature-card">
<h2>Compact by design</h2>
<p>Use RaBitQ as an alternative to binary or scalar quantization, with
useful estimates from a one-bit code per padded dimension plus a small
set of per-vector factors.</p>
</div>
<div class="feature-card">
<h2>Fast on modern CPUs</h2>
<p>Runtime dispatch selects optimized AVX2 or AVX-512 kernels. IVF and
SymphonyQG use FastScan for batched distance estimation.</p>
</div>
<div class="feature-card">
<h2>Ready for vector search</h2>
<p>Choose IVF, HNSW, or SymphonyQG to balance memory, indexing cost,
latency, and recall for your workload.</p>
</div>
</div>

## Start with Python

Install the latest release from PyPI:

```bash
python -m pip install rabitqlib
```

Build an IVF index and search a batch of queries:

```python
import numpy as np
from rabitqlib import IvfIndex

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

cluster_ids = (np.arange(len(data)) % 5).astype(np.uint32)
centroids = np.stack(
[data[cluster_ids == cluster].mean(axis=0) for cluster in range(5)]
).astype(np.float32)

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

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

[Continue to the complete quick start](quick_start.md){ .md-button .md-button--primary }

## Choose an index

| Index | Best fit | Typical relative memory | Main search control |
| --- | --- | --- | --- |
| [IVF + RaBitQ](index/ivf.md) | Large datasets and predictable memory use | Lowest | Number of probed clusters |
| [HNSW + RaBitQ](index/hnsw.md) | General-purpose graph search | Moderate | Search candidate list size |
| [SymphonyQG](index/qg.md) | Latency-focused graph search | Highest | Search window size |

IVF and HNSW store quantized vectors instead of accessing raw vectors during
search. SymphonyQG uses additional memory and multiple codes per vector to
optimize its access pattern.

These are typical relative profiles, not fixed guarantees. Actual memory,
latency, and recall depend on vector dimension, quantization width, graph
degree, and search parameters.

## Why RaBitQ?

- **High accuracy with tiny codes.** RaBitQ provides strong similarity
estimates across different bit widths and remains effective with a one-bit
code per padded dimension plus per-vector factors.
- **Fast distance estimation.** IVF and SymphonyQG use
[FastScan](https://arxiv.org/abs/1704.07355) for batched estimates; HNSW
uses single-code AVX kernels.
- **Theoretical error bounds.** An asymptotically optimal error bound supports
reliable ordering and reranking.
- **Multiple integration points.** Use the quantizer directly or select a
complete vector-search index.

The library supports Euclidean distance and inner product. Cosine similarity
can be implemented by normalizing vectors and using inner product.
It implements the [1-bit RaBitQ](https://arxiv.org/abs/2405.12497) and
[multi-bit RaBitQ](https://arxiv.org/abs/2409.09913) research from the
[VectorDB Group](https://vectordb-ntu.github.io/) at Nanyang Technological
University.

## Used across the vector-search ecosystem

RaBitQ has been adopted by projects including
[Milvus](https://github.com/milvus-io/milvus),
[Faiss](https://github.com/facebookresearch/faiss),
[VSAG](https://github.com/antgroup/vsag),
[VectorChord](https://github.com/tensorchord/VectorChord),
[CockroachDB](https://github.com/cockroachdb/cockroach),
[Elasticsearch](https://github.com/elastic/elasticsearch),
[Lucene](https://github.com/apache/lucene),
[turbopuffer](https://turbopuffer.com/blog/ann-v3), and
[Zvec](https://github.com/alibaba/zvec).

## Citation

If RaBitQ helps your research or system, please cite:

> Jianyang Gao, Yutong Gou, Yuexuan Xu, Yongyi Yang, Cheng Long, and Raymond
> Chi-Wing Wong. “Practical and Asymptotically Optimal Quantization of
> High-Dimensional Vectors in Euclidean Space for Approximate Nearest Neighbor
> Search.” SIGMOD 2025. [arXiv:2409.09913](https://arxiv.org/abs/2409.09913).
17 changes: 12 additions & 5 deletions docs/docs/index/hnsw.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# HNSW + RaBitQ
[HNSW](https://arxiv.org/abs/1603.09320) is a popular graph-based index. HNSW + RaBitQ consumes the more memory than IVF + RaBitQ because it needs to store the edges of every vertex in a graph (e.g., 32 edges = 1,024 bits). In terms of the time-accuracy trade-off, HNSW + RaBitQ and IVF + RaBitQ perform differently across datasets—sometimes the former works better, and sometimes the latter does.
[HNSW](https://arxiv.org/abs/1603.09320) is a popular graph-based index. Under
comparable quantization settings, HNSW + RaBitQ typically uses more memory
than IVF + RaBitQ because it stores graph structures in addition to quantized
vectors. The actual comparison depends on vector dimension, bit width, graph
degree, and index parameters.
This document describes how the library integrates HNSW with RaBitQ to support efficient vector search.

## Index Construction

We build the HNSW graph by incrementally inserting new elements following the standard HNSW routine. Currently, we support building the index using raw data vectors and storing the corresponding quantization codes.
The graph is built by incrementally inserting raw data vectors. Raw vectors
are used to construct and prune graph links, but the completed index stores
the links, centroids, cluster IDs, labels, RaBitQ codes, and factors rather
than a copy of the raw dataset. Total bit widths from 1 through 9 are
supported.

Users can invoke:

Expand All @@ -21,9 +29,9 @@ HierarchicalNSW::construct(size_t cluster_num,
- **data**: Pointer to the raw data vectors.
- **data_num**: The number of data vectors.
- **centroids**: Centroids computed by K-means clustering on the raw data vectors (we recommend `cluster_num = 16`).
- **cluster_ids**: Array of length `data_num` where each entry indicates the centroid ID (0–15) for the corresponding data vector.
- **cluster_ids**: Array of length `data_num`; every entry must be in the range `[0, cluster_num)`.
- **num_threads**: Number of threads to use (default: 0, which auto-selects).
- **faster**: If `true`, enales fast quantizer.
- **faster**: If `true`, enables the faster quantizer.


During construction, we first rotate the centroids and then insert each element one by one. For each element:
Expand Down Expand Up @@ -90,4 +98,3 @@ Repeat until `candidate_set` is empty:
- Insert the neighbor into `candidate_set` with its (possibly refined) estimated distance.

The search terminates when `candidate_set` is empty.

Loading
Loading