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
2 changes: 2 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
BinPackArguments: false
BinPackParameters: false
# Preserve the existing initializer-list closing-brace layout on LLVM 23.
BreakBeforeCloseBracketBracedList: false
BreakConstructorInitializers: BeforeComma
ColumnLimit: 92
CompactNamespaces: false
Expand Down
5 changes: 5 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ Checks: >
readability-inconsistent-declaration-parameter-name,
readability-misleading-indentation,
readability-redundant-control-flow,
-performance-enum-size,
-portability-avoid-pragma-once,
-portability-no-assembler,
-portability-simd-intrinsics,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling

# LLVM 23's new wildcard matches must not change enum ABI, replace the project's
# pragma-once convention, or reject the CPU detector's required inline assembly.
# Keep the established checks blocking. Naming starts as warnings while existing
# internal/parameter names are migrated; do not bulk-fix public interfaces.
WarningsAsErrors: "*,-readability-identifier-naming"
Expand Down
20 changes: 20 additions & 0 deletions .github/actions/setup-clang/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Set up Clang 23
description: Install the pinned compiler, analysis, formatting and OpenMP tools on Ubuntu.
runs:
using: composite
steps:
- name: Install LLVM 23 tools
shell: bash
run: |
set -euo pipefail
# shellcheck source=/dev/null
source /etc/os-release
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key \
| sudo tee /usr/share/keyrings/apt.llvm.org.asc >/dev/null
echo "deb [signed-by=/usr/share/keyrings/apt.llvm.org.asc] https://apt.llvm.org/$VERSION_CODENAME/ llvm-toolchain-$VERSION_CODENAME-23 main" \
| sudo tee /etc/apt/sources.list.d/llvm-23.list >/dev/null
sudo apt-get update -qq
sudo apt-get install -y clang-23 clang-format-23 clang-tidy-23 libomp-23-dev cmake ninja-build
clang++-23 --version
clang-format-23 --version
clang-tidy-23 --version
59 changes: 51 additions & 8 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,19 @@ jobs:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp_format == 'true') }}
name: C++ formatting
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Install clang-format
run: |
sudo apt-get update -qq
sudo apt-get install -y clang-format-15
- uses: ./.github/actions/setup-clang
- name: Check formatting
run: ./scripts/check-format.sh

clang-tidy-shard:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp_tidy == 'true') }}
name: C++ static analysis shard ${{ matrix.shard }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
Expand All @@ -41,15 +38,15 @@ jobs:
env:
TIDY_SHARD_INDEX: ${{ matrix.shard }}
TIDY_SHARD_COUNT: "2"
CXX: clang++-23
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-clang
- uses: actions/setup-python@v7
with:
python-version: "3.11"
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y clang-tidy-15 libomp-15-dev cmake ninja-build
python -m pip install "numpy>=1.23" "pybind11>=2.12"
- name: Locate pybind11
run: echo "pybind11_DIR=$(python -m pybind11 --cmakedir)" >> "$GITHUB_ENV"
Expand All @@ -59,6 +56,7 @@ jobs:
-DRABITQ_BUILD_SAMPLES=OFF
-DRABITQ_BUILD_TESTS=OFF
-DRABITQ_BUILD_PYTHON_BINDINGS=ON
-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF
-DCMAKE_BUILD_TYPE=Release
-Dpybind11_DIR="$pybind11_DIR"
- name: Run clang-tidy
Expand Down Expand Up @@ -134,6 +132,51 @@ jobs:
cmake --build build-source-consumer
ctest --test-dir build-source-consumer --output-on-failure

clang:
name: Clang 23 (OpenMP ${{ matrix.openmp }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
openmp: ["ON", "OFF"]
env:
CC: clang-23
CXX: clang++-23
OMP_NUM_THREADS: "2"
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-clang
- name: Configure portable build
run: >-
cmake -S . -B build-clang -G Ninja
-DCMAKE_BUILD_TYPE=Release
-DRABITQ_BUILD_TESTS=ON
-DRABITQ_BUILD_SAMPLES=OFF
-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF
-DRABITQ_USE_OPENMP=${{ matrix.openmp }}
-DRABITQ_BUILD_INDEX=${{ matrix.openmp }}
- name: Build and test
run: |
cmake --build build-clang --parallel 2
ctest --test-dir build-clang --output-on-failure --no-tests=error
- name: Test installed index consumer
if: matrix.openmp == 'ON'
run: |
cmake --install build-clang --prefix "$RUNNER_TEMP/rabitq-clang-install"
cmake -S tests/consumer -B build-clang-consumer -G Ninja \
-DCMAKE_PREFIX_PATH="$RUNNER_TEMP/rabitq-clang-install"
cmake --build build-clang-consumer --parallel 2
ctest --test-dir build-clang-consumer --output-on-failure --no-tests=error
- name: Test core and FastScan integrations
run: >-
cmake -DRABITQ_SOURCE_DIR="$PWD"
-DWORK_DIR="$RUNNER_TEMP/rabitq-clang-core"
-DRABITQ_USE_OPENMP=${{ matrix.openmp }}
-P tests/core_consumer/run.cmake

windows:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
Expand Down
76 changes: 46 additions & 30 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,33 @@ Run commands from the repository root in your activated
the [source-build prerequisites](CONTRIBUTING.md#setup-for-code-changes).
Refresh APT package lists with `sudo apt-get update` before installing tools.

## Compiler and tool versions

Library builds require C++17; the LLVM 23 development tools below do not impose a
Clang 23 minimum on downstream users. CMake respects the caller's compiler choice.
OpenMP is required for ANN indexes and Python bindings, and optional for core/FastScan.

Linux CI explicitly builds and tests with Clang 23, including installed consumers
and OpenMP ON/OFF numerical builds, alongside the existing GCC jobs. Native ARM64,
AppleClang and MSVC checks remain separate. Older GCC/Clang minimum versions have
not yet been established by a dedicated compatibility matrix.

Formatting and static analysis use LLVM 23 as the shared local/CI baseline. Keep
this major version fixed rather than following unversioned distribution defaults.
The LLVM APT release branch supplies updated builds within that major version;
CI prints the exact tool versions, rather than promising a fixed patch build.
Upgrading them requires reviewing format differences and diagnostics together with
the scripts and CI configuration. The Ubuntu CI setup lives in
[setup-clang](.github/actions/setup-clang/action.yml).

## C++ formatting

RaBitQ uses the repository's `.clang-format` configuration and clang-format
15. Install that version on Ubuntu or Debian with:
23. After enabling the [LLVM 23 package repository](https://apt.llvm.org/)
for your distribution, install that version on Ubuntu or Debian with:

```bash
sudo apt-get install clang-format-15
sudo apt-get install clang-format-23
```

Format all project-maintained C and C++ files:
Expand All @@ -28,14 +48,15 @@ Verify formatting without changing files:
./scripts/check-format.sh
```

The scripts intentionally exclude vendored code in `include/rabitqlib/third/`.
The scripts intentionally exclude vendored code in `include/rabitqlib/third/`
and the frozen compatibility fixture `tests/fixtures/core_api_v1/consumer.cpp`.
To use a nonstandard executable name, set `CLANG_FORMAT`; it must still identify
itself as clang-format 15. On macOS, use Bash 4+ on PATH for the shell wrappers.
Apple SDKs newer than clang-tidy 15 may require a matching newer analyzer; set
itself as clang-format 23. On macOS, use Bash 4+ on PATH for the shell wrappers.
Apple SDKs newer than clang-tidy 23 may require a matching newer analyzer; set
`RUN_CLANG_TIDY` to its runner and report that version separately from the Linux
clang-tidy 15 baseline.
clang-tidy 23 baseline.

clangd embeds its own formatter, so use clangd 15 in editors such as VS Code
clangd embeds its own formatter, so use clangd 23 in editors such as VS Code
if format-on-save must exactly match CI. If another clangd version is required,
disable format-on-save and run the repository scripts before submitting.

Expand All @@ -56,7 +77,7 @@ performance checks. Install the pinned analyzer and the dependencies needed to
configure the core library and Python bindings:

```bash
sudo apt-get install clang-tidy-15 libomp-15-dev cmake ninja-build
sudo apt-get install clang-23 clang-tidy-23 libomp-23-dev cmake ninja-build
python -m pip install "numpy>=1.23" "pybind11>=2.12"
```

Expand All @@ -66,18 +87,20 @@ and run the check with the same compiler used by CMake:
```bash
export pybind11_DIR="$(python -m pybind11 --cmakedir)"
cmake -S . -B build-tidy -G Ninja \
-DCMAKE_CXX_COMPILER=c++ \
-DCMAKE_CXX_COMPILER=clang++-23 \
-DPython_EXECUTABLE="$(python -c 'import sys; print(sys.executable)')" \
-DRABITQ_BUILD_SAMPLES=OFF \
-DRABITQ_BUILD_TESTS=OFF \
-DRABITQ_BUILD_PYTHON_BINDINGS=ON \
-DCMAKE_BUILD_TYPE=Release \
-Dpybind11_DIR="$pybind11_DIR"
CXX=c++ ./scripts/check-tidy.sh build-tidy
CXX=clang++-23 ./scripts/check-tidy.sh build-tidy
```

Use a fresh build directory when changing compilers. `CMAKE_CXX_COMPILER`
selects the compiler explicitly; `CXX=c++` on the check command must match it.
selects the compiler explicitly; `CXX` on the check command must match it.
Other supported compilers can still build the library; this command selects the
development analysis baseline only.

The wrapper supplies clang-tidy with that compiler's standard-library include
paths and reports diagnostics only for first-party files. Vendored Eigen and
Expand All @@ -97,33 +120,26 @@ was focused or complete.

The optional include dependency check uses clang-tidy's
`misc-include-cleaner` to report missing and unused includes. Run it locally when
reviewing include changes. On Ubuntu, install unversioned distribution packages:
reviewing include changes. Use LLVM 23 to match the script's default analyzer
and the shared development baseline. After enabling
[LLVM's APT repository](https://apt.llvm.org/) for your distribution, install
the matching compiler, analysis and OpenMP packages:

```bash
sudo apt-get update
sudo apt-get install clang-tidy clang libomp-dev cmake ninja-build
```

Clang-tidy 17 or newer is required. On older distributions such as Ubuntu 22.04,
use [LLVM's APT repository](https://apt.llvm.org/) to install a newer release.
For example, install LLVM 22 and its matching analysis and OpenMP packages:

```bash
wget https://apt.llvm.org/llvm.sh
sudo bash llvm.sh 22
sudo apt-get install clang-tidy-22 libomp-22-dev cmake ninja-build
export CLANG_TIDY=clang-tidy-22
export CXX=clang++-22
sudo apt-get install clang-23 clang-tidy-23 libomp-23-dev cmake ninja-build
export CLANG_TIDY=clang-tidy-23
export CXX=clang++-23
```

Configure and run the same check locally (use a fresh build directory when
changing compilers). Clang needs its matching OpenMP development package;
for the LLVM 22 example above, that is `libomp-22-dev`. An older installed
`libomp-dev` alone may not be found by Clang 22:
for LLVM 23, that is `libomp-23-dev`. An older installed
`libomp-dev` alone may not be found by Clang 23:

```bash
cmake -S . -B build-includes -G Ninja \
-DCMAKE_CXX_COMPILER="${CXX:-clang++}" \
-DCMAKE_CXX_COMPILER="${CXX:-clang++-23}" \
-DPython_EXECUTABLE="$(python -c 'import sys; print(sys.executable)')" \
-Dpybind11_DIR="$(python -m pybind11 --cmakedir)" \
-DRABITQ_BUILD_SAMPLES=ON \
Expand Down Expand Up @@ -175,7 +191,7 @@ run this example from the repository root using the
existing project Python environment:

```bash
CXX=c++ python - <<'PY'
CXX=clang++-23 python - <<'PY'
import json
from pathlib import Path
import subprocess
Expand All @@ -201,7 +217,7 @@ translation units that include it directly or transitively, covering affected
template instantiations and ISA variants. Configure any required test/sample
targets first if they are absent from the database. Do not analyze a header as
a standalone translation unit. If the consumer set is unclear, run the full
check. Replace `CXX=c++` in the example if you configured with another compiler.
check. Replace `CXX=clang++-23` in the example if you configured with another compiler.
The wrapper's success message covers only the selected database entries in this
mode; it does not mean the full pre-merge check passed.

Expand Down
4 changes: 2 additions & 2 deletions benchmarks/hotpaths/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ void run(int argc, char** argv) {
const double delta = static_cast<double>(data[row * dim + d]) - centroid[d];
reference += delta * delta;
}
if (!(std::abs(distances[row] - reference) <= 2e-6 * static_cast<double>(dim)
)) {
if (!(std::abs(distances[row] - reference) <=
2e-6 * static_cast<double>(dim))) {
throw std::runtime_error("L2 differs from float64 reference");
}
}
Expand Down
8 changes: 5 additions & 3 deletions include/rabitqlib/clustering/detail/kmeans.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ class LloydKMeans : public Parameters {
);
bool in_range = true;
bool finite = true;
#pragma omp parallel for num_threads(threads) reduction(&& : finite, in_range) schedule(static)
#pragma omp parallel for num_threads(threads) reduction(&& : finite, in_range) \
schedule(static)
for (std::ptrdiff_t loop_index = 0; loop_index < static_cast<std::ptrdiff_t>(n * d);
++loop_index) {
const size_t i = static_cast<size_t>(loop_index);
Expand Down Expand Up @@ -330,8 +331,9 @@ class LloydKMeans : public Parameters {
);
const auto [nsplit, shift] =
consolidate_centroids(x, n, sums, cluster_sizes, previous_centroids);
iteration_stats.push_back(KMeansIterationStats{
iteration + 1, obj, shift, nsplit});
iteration_stats.push_back(
KMeansIterationStats{iteration + 1, obj, shift, nsplit}
);

if (verbose) {
std::cout << Assignment::kName << " iteration " << iteration + 1 << "/"
Expand Down
8 changes: 5 additions & 3 deletions include/rabitqlib/clustering/qgkmeans.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ class QGAssigner {

scratch_.reserve(num_threads_);
for (uint32_t thread = 0; thread < num_threads_; ++thread) {
scratch_.emplace_back(std::make_unique<symqg::detail::SearchScratch>(
graph_->padded_dim(), M_, ef_search_, 1
));
scratch_.emplace_back(
std::make_unique<symqg::detail::SearchScratch>(
graph_->padded_dim(), M_, ef_search_, 1
)
);
}
}

Expand Down
8 changes: 4 additions & 4 deletions include/rabitqlib/core/detail/rabitq_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,8 +672,8 @@ inline void ex_bits_code_with_factor_with_scratch(

// Cauchy-Schwarz makes this nonnegative; collinear vectors can round below zero.
const T normalized_error =
(((l2_sqr * l2norm_sqr<T>(xu_cb.data(), dim)) / (ip_resi_xucb * ip_resi_xucb)) - 1
) /
(((l2_sqr * l2norm_sqr<T>(xu_cb.data(), dim)) / (ip_resi_xucb * ip_resi_xucb)) -
1) /
(dim - 1);
T tmp_error = l2_norm * kConstEpsilon * std::sqrt(std::max(normalized_error, T{0}));

Expand Down Expand Up @@ -1003,8 +1003,8 @@ inline void code_factors(
// Cauchy-Schwarz guarantees this value is nonnegative. Clamp small negative
// round-off errors for collinear residual/code vectors before taking the square root.
const T normalized_error =
(((l2_sqr * l2norm_sqr<T>(xu_cb.data(), dim)) / (ip_resi_xucb * ip_resi_xucb)) - 1
) /
(((l2_sqr * l2norm_sqr<T>(xu_cb.data(), dim)) / (ip_resi_xucb * ip_resi_xucb)) -
1) /
(dim - 1);
T tmp_error = l2_norm * kConstEpsilon * std::sqrt(std::max(normalized_error, T{0}));

Expand Down
7 changes: 4 additions & 3 deletions include/rabitqlib/core/rotator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Rotator {

public:
explicit Rotator() = default;
explicit Rotator(size_t dim, size_t padded_dim) : dim_(dim), padded_dim_(padded_dim){};
explicit Rotator(size_t dim, size_t padded_dim) : dim_(dim), padded_dim_(padded_dim) {};
virtual ~Rotator() = default;
virtual void rotate(const T* src, T* dst) const = 0;
virtual void load(std::ifstream&) = 0;
Expand Down Expand Up @@ -93,8 +93,9 @@ class MatrixRotator : public Rotator<T> {
: MatrixRotator(dim, padded_dim, AllocateOnly{}) {
RowMajorMatrix<T> rand = random_gaussian_matrix<T>(padded_dim, padded_dim);
Eigen::HouseholderQR<RowMajorMatrix<T>> qr(rand);
RowMajorMatrix<T> q_inv = qr.householderQ().transpose(
); // The inverse of an orthogonal matrix is its transpose.
RowMajorMatrix<T> q_inv =
qr.householderQ()
.transpose(); // The inverse of an orthogonal matrix is its transpose.

// the random matrix only need the first dim rows, since we just pad zeros for
// the vector to be rotated to padded dimension
Expand Down
Loading
Loading