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
17 changes: 17 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,20 @@ jobs:

- name: Run pytest
run: pytest --tb=short -q

python-windows:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.python_build == 'true') }}
name: Python Windows x86-64 (VS 2026)
runs-on: windows-2025-vs2026
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.11"
cache: pip
- name: Build and install package
run: python -m pip install ".[test]" -v
- name: Test Python bindings
run: python -m pytest tests/python --tb=short -q
47 changes: 42 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,44 @@ jobs:
path: wheelhouse/*.whl
if-no-files-found: error

windows-wheels:
name: Windows x86-64 wheels
needs: [prepare, changes]
if: >-
!cancelled() && needs.prepare.result == 'success' &&
needs.prepare.outputs.build == 'true' &&
(needs.changes.result != 'success' || needs.changes.outputs.wheels == 'true')
runs-on: windows-2025-vs2026
timeout-minutes: 90
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.sha }}
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Build and test PR wheel
if: github.event_name == 'pull_request'
uses: pypa/cibuildwheel@v4.2.0
env:
CIBW_BUILD: cp311-win_amd64
with:
output-dir: wheelhouse
- name: Build and test release wheels
if: github.event_name != 'pull_request'
uses: pypa/cibuildwheel@v4.2.0
with:
output-dir: wheelhouse
- name: Store wheel artifacts
uses: actions/upload-artifact@v7
with:
name: windows-x86_64-wheels
path: wheelhouse/*.whl
if-no-files-found: error

release-tag:
name: Create version tag
needs: [prepare, linux-wheels]
needs: [prepare, linux-wheels, windows-wheels]
if: >-
(github.event_name == 'push' || github.event_name == 'workflow_run') &&
needs.prepare.outputs.publish == 'true'
Expand Down Expand Up @@ -126,7 +161,7 @@ jobs:
if: >-
(github.event_name == 'push' || github.event_name == 'workflow_run') &&
needs.prepare.outputs.publish == 'true'
needs: [prepare, linux-wheels, release-tag]
needs: [prepare, linux-wheels, windows-wheels, release-tag]
runs-on: ubuntu-latest
environment:
name: pypi
Expand All @@ -138,8 +173,9 @@ jobs:
- name: Download wheel artifacts
uses: actions/download-artifact@v8
with:
name: linux-x86_64-wheels
pattern: '*-wheels'
path: wheelhouse
merge-multiple: true

- name: Publish wheels
uses: pypa/gh-action-pypi-publish@release/v1
Expand All @@ -152,7 +188,7 @@ jobs:
if: >-
(github.event_name == 'push' || github.event_name == 'workflow_run') &&
needs.prepare.outputs.publish == 'true'
needs: [prepare, linux-wheels, pypi-publish]
needs: [prepare, linux-wheels, windows-wheels, pypi-publish]
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -165,8 +201,9 @@ jobs:
- name: Download wheel artifacts
uses: actions/download-artifact@v8
with:
name: linux-x86_64-wheels
pattern: '*-wheels'
path: wheelhouse
merge-multiple: true

- name: Create GitHub release
env:
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ jobs:
- name: Test
run: ctest --test-dir build --output-on-failure

windows:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
name: Windows x86-64 (VS 2026)
runs-on: windows-2025-vs2026
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- name: Configure
run: >-
cmake -S . -B build-windows -G "Visual Studio 18 2026" -A x64
-DRABITQ_BUILD_TESTS=ON
-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF
- name: Build
run: cmake --build build-windows --config Release --parallel 2
- name: Test
run: ctest --test-dir build-windows -C Release --output-on-failure

sanitizers:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ Recommended:

### Persistence and Python boundaries

Filesystem path strings are UTF-8 on Windows and native path bytes on POSIX. Use
`io_impl::filesystem_path` from `utils/path.hpp` before opening files or querying
filesystem metadata, including IVF's HNSW sidecar. It converts UTF-8 to native Unicode
on Windows and preserves existing path bytes on Linux/macOS.

Never silently reinterpret an old index file. Format changes require a magic/version discriminator,
validated sizes before allocation, checked reads, and a compatibility fixture or explicit rejection
path. Review each affected index format; preserve SymphonyQG's versioned quantized format and legacy
Expand Down
61 changes: 43 additions & 18 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ option(RABITQ_BUILD_SAMPLES "Build C++ sample programs" ON)
option(RABITQ_BUILD_TESTS "Build C++ tests" OFF)
option(RABITQ_BUILD_PYTHON_BINDINGS "Build Python bindings" OFF)
option(RABITQ_ENABLE_SANITIZERS "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
# Enable native tuning for local builds; release wheels explicitly set this OFF.
option(
RABITQ_ENABLE_NATIVE_OPTIMIZATION
"Optimize generic code for the build machine"
Expand All @@ -29,6 +30,14 @@ target_compile_options(rabitq_compile_options INTERFACE
$<$<AND:$<BOOL:${RABITQ_ENABLE_NATIVE_OPTIMIZATION}>,$<COMPILE_LANG_AND_ID:CXX,Clang,GNU>>:-march=native>
)
target_link_libraries(rabitq_compile_options INTERFACE OpenMP::OpenMP_CXX)
if(MSVC)
# MSVC spells the GNU restrict extension differently. Public headers use
# __restrict__, so consumers need the same definition as the library.
target_compile_definitions(rabitq_compile_options INTERFACE __restrict__=__restrict)
# Eigen's inline code must have one ISA-independent definition across the
# generic, AVX2, and AVX-512 translation units on MSVC.
target_compile_definitions(rabitq_compile_options INTERFACE EIGEN_DONT_VECTORIZE)
endif()
if(RABITQ_ENABLE_SANITIZERS)
target_compile_options(rabitq_compile_options INTERFACE
$<$<COMPILE_LANG_AND_ID:CXX,Clang,GNU>:-fsanitize=address,undefined;-fno-omit-frame-pointer>
Expand Down Expand Up @@ -116,24 +125,40 @@ set_target_properties(rabitq_core PROPERTIES
)
rabitq_enable_warnings(rabitq_core)

set_source_files_properties(${RABITQ_AVX2_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_AVX512_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mfma"
)
set_source_files_properties(${RABITQ_AVX512_POPCNT_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx512vpopcntdq;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX2_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX512_CORE_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX512_POPCNT_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx512vpopcntdq;-mfma"
)
if(MSVC)
enable_language(ASM_MASM)
target_sources(rabitq_core PRIVATE src/simd/fht_avx2_masm.asm)
set_source_files_properties(
${RABITQ_AVX2_SOURCES} ${RABITQ_HNSW_AVX2_SOURCES}
PROPERTIES COMPILE_OPTIONS "/arch:AVX2"
)
# MSVC enables the complete F/CD/VL/BW/DQ group. Keep the optional
# backend's runtime guard in cpu_features.cpp aligned with this flag.
set_source_files_properties(
${RABITQ_AVX512_SOURCES} ${RABITQ_AVX512_POPCNT_SOURCES}
${RABITQ_HNSW_AVX512_CORE_SOURCES} ${RABITQ_HNSW_AVX512_POPCNT_SOURCES}
PROPERTIES COMPILE_OPTIONS "/arch:AVX512"
)
else()
set_source_files_properties(${RABITQ_AVX2_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_AVX512_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mfma"
)
set_source_files_properties(${RABITQ_AVX512_POPCNT_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx512vpopcntdq;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX2_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX512_CORE_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx2;-mfma"
)
set_source_files_properties(${RABITQ_HNSW_AVX512_POPCNT_SOURCES} PROPERTIES
COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512dq;-mavx512vpopcntdq;-mfma"
)
endif()

add_library(rabitq_headers INTERFACE)
add_library(rabitqlib::rabitqlib ALIAS rabitq_headers)
Expand Down
7 changes: 7 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ must happen in generic code first.
- Preserve stricter predicates: population-count kernels need AVX512_VPOPCNTDQ, and HNSW's
AVX-512 core variant also needs AVX2 for its warmup implementation. Do not infer support
from a backend name or from `__AVX*__` macros in a public header.
All AVX-512 backends require AVX2, which GCC/Clang also enable with `-mavx512f`.
MSVC's `/arch:AVX512` additionally requires AVX512VL and AVX512CD; keep these checks
synchronized with the compiler flags. This optional target follows the compiler-group
approach documented in [NumPy's MSVC compatibility rules](https://numpy.org/doc/1.26/reference/simd/build-options.html#on-x86-microsoft-visual-c-c).
Missing group features select the AVX2 fallback; they do not raise the library's minimum
CPU requirements. Native tuning defaults to on for local builds and must be explicitly
disabled for distributed builds; release wheels already set it to off.
- Put calculations and scratch-storage helpers outside the dispatcher. Shared implementation
headers use internal linkage so independently compiled backends retain their own bodies.
Include the FFHT implementation inside the private kernel namespace for the same reason.
Expand Down
56 changes: 25 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,30 @@

## News

- **September 2026 — IVF raw-vector reranking:** Set `nbits=32` to store raw
float32 vectors for reranking. IVF now selects HACC automatically for 4–9-bit
codes, using standard FastScan for 1–3 bits and raw vectors; manual overrides
remain available. See the [IVF documentation](docs/docs/index/ivf.md).
- **September 2026 — Windows x86-64 support:** C++ and Python source builds now
support MSVC, runtime AVX2/AVX-512 dispatch, and Unicode index paths. Windows
wheels are configured for the next release. See the [Windows build instructions](tests/README.md#prerequisites).

- **September 2026 — Quantized SymphonyQG:** SymphonyQG now supports optional
4-bit and 8-bit RaBitQ vector storage. Select QG-quant with
`quantization_bits=4` or `quantization_bits=8`; vanilla raw-vector QG remains
the default. See the [SymphonyQG documentation](docs/docs/index/qg.md) for
details.
- **September 2026 — IVF raw-vector reranking:** Use `nbits=32` for float32
reranking. Quantized IVF automatically selects HACC for 4–9-bit codes.
See the [IVF documentation](docs/docs/index/ivf.md).

- **September 2026 — Quantized SymphonyQG:** Set `quantization_bits=4` or `8`
for compact vector storage; raw vectors remain the default.
See the [SymphonyQG documentation](docs/docs/index/qg.md).

## Install

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

Prebuilt wheels support Linux x86-64 and CPython 3.11–3.14. AVX2 + FMA is the
portable CPU baseline; supported AVX-512 kernels are selected at runtime.
Wheels: CPython 3.11–3.14 on Linux x86-64; Windows x86-64 is planned for the
next release. Requires AVX2 and FMA, with optional AVX-512 acceleration.

## Python quick start

The following complete example builds a small IVF index and searches it. It
uses deterministic synthetic data, so no dataset download is required.
Build and search a small IVF index using synthetic data:

```python
import numpy as np
Expand Down Expand Up @@ -92,11 +92,16 @@ Python bindings are also available for `HnswIndex` and `SymqgIndex`. See the
[Python examples](sample/python/) for index construction, querying, and index
persistence.

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.

<details>
<summary>Build the Python bindings from source</summary>

Source builds require a C++17 compiler, CMake 3.20 or newer, and OpenMP. On
Ubuntu or Debian:
Windows, install Visual Studio 2026 with the Desktop development with C++
workload, then run `python -m pip install .` from the repository root.
On Ubuntu or Debian:

```bash
sudo apt-get update
Expand Down Expand Up @@ -218,19 +223,7 @@ and HNSW implementations, with links to the source code.

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

<details>
<summary>CPU dispatch details</summary>

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. AVX-512 translation units are
compiled with FMA enabled.

</details>
- an x86-64 CPU with AVX2 and FMA

Clone and build the library and example programs:

Expand All @@ -242,10 +235,11 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
```

Release builds enable native CPU tuning by default. To build a binary that can
be moved between AVX2- and AVX-512-capable machines, configure with
`-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF`; the ISA-specific kernels will still
be selected at runtime.
For MSVC, follow the [Windows build instructions](tests/README.md#prerequisites).
Local GCC/Clang builds enable `-march=native` by default; set
`-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF` for portable binaries, as release
wheels do. See [CPU dispatch details](DEVELOPMENT.md#dispatch-conventions-and-coverage)
for backend requirements and fallbacks.

### Use RaBitQ-Library in another C++ project

Expand Down
14 changes: 9 additions & 5 deletions docs/docs/quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ a C++17 API for both indexes and low-level quantization.

- An x86-64 CPU supported by the selected kernels: most paths accept either
AVX2 with FMA or AVX-512F/BW/DQ with FMA
- Linux x86-64 and CPython 3.11–3.14 for prebuilt Python wheels
- For source builds: a C++17 compiler with OpenMP support and CMake 3.20 or newer
- Linux x86-64 and CPython 3.11–3.14 for currently published Python wheels;
Windows x86-64 wheels are configured for the next release
- For source builds: a C++17 compiler with OpenMP support and CMake 3.20 or newer;
Windows builds use Visual Studio 2026 with the C++ workload

<details>
<summary>CPU dispatch details</summary>
Expand All @@ -29,9 +31,11 @@ path when available. AVX-512 translation units are compiled with FMA enabled.
python -m pip install rabitqlib
```

Prebuilt wheels support Linux x86-64 and CPython 3.11–3.14 and do not require
a compiler or CMake. AVX2 + FMA is the portable CPU baseline; supported
AVX-512 kernels are selected at runtime.
Published Linux x86-64 wheels for CPython 3.11–3.14 do not require a compiler
or CMake. Windows x86-64 wheels are configured for the next release; until then,
installing on Windows builds from source with Visual Studio 2026 and CMake.
AVX2 + FMA is the CPU baseline; supported AVX-512 kernels are selected at
runtime.

### Build and search an IVF index

Expand Down
3 changes: 2 additions & 1 deletion include/rabitqlib/defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
#include <stdexcept>

#include "rabitqlib/third/Eigen/Dense"
#include "rabitqlib/utils/bitops.hpp" // IWYU pragma: keep; used by BIT_ID

#define BIT_ID(x) (__builtin_popcount((x)-1))
#define BIT_ID(x) (rabitqlib::bitops::popcount32((x)-1))
#define LOWBIT(x) ((x) & (-(x)))

namespace rabitqlib {
Expand Down
4 changes: 3 additions & 1 deletion include/rabitqlib/fastscan/fastscan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ void accumulate(
template <typename T>
inline void pack_lut(size_t dim, const T* __restrict__ query, T* __restrict__ lut) {
size_t num_codebook = dim >> 2;
// Preserve +0 when the first selected coordinate is -0.
volatile T zero = 0;
for (size_t i = 0; i < num_codebook; ++i) {
lut[0] = 0;
lut[0] = zero;
for (size_t j = 1; j < 16; ++j) {
lut[j] = lut[j - LOWBIT(j)] + query[kPos[j]];
}
Expand Down
Loading
Loading