Skip to content
Open
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
30 changes: 28 additions & 2 deletions .github/workflows/unit-tests.yaml
Comment thread
r-devulap marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,38 @@ jobs:
- name: Set up GCC
run: |
sudo apt install -y gcc
- name: Install Meson and Ninja
- name: Install Meson, Ninja, and GTest
run: |
sudo apt update && sudo apt install -y meson ninja-build
sudo apt update && sudo apt install -y meson ninja-build pkg-config libgtest-dev
- uses: actions/checkout@v4
- name: Initialize Git Submodules
run: git submodule update --init

- name: Build test_simd_kernels (native C++)
working-directory: jvector-native/src/main/native
run: |
meson setup build --wipe
ninja -C build test_simd_kernels

- name: Run test_simd_kernels — no ISA cap (auto-detect)
if: matrix.max_isa == 'avx512f'
working-directory: jvector-native/src/main/native
run: ./build/test_simd_kernels

- name: Run test_simd_kernels — capped at avx2
if: matrix.max_isa == 'avx2'
working-directory: jvector-native/src/main/native
env:
JVECTOR_MAX_ISA: avx2
run: ./build/test_simd_kernels

- name: Run test_simd_kernels — capped at sse42
if: matrix.max_isa == 'sse42'
working-directory: jvector-native/src/main/native
env:
JVECTOR_MAX_ISA: sse42
run: ./build/test_simd_kernels

- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion jvector-native/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
<argument>${native.buildtype}</argument>
</arguments>
<skip>false</skip>
<workingDirectory>${project.basedir}/src/main/native/</workingDirectory>
<workingDirectory>${project.basedir}/src/main/native/src/</workingDirectory>
</configuration>
</execution>
</executions>
Expand Down
110 changes: 110 additions & 0 deletions jvector-native/src/main/native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,116 @@ The output is `target/meson-build/libjvector.so.<version>` (relative to the proj

---

## How to run tests

> **Standalone development tool.** The test suite is a native C++ executable
> that links directly against `libjvector.so`. It exists solely to help develop
> and debug the native module — it is **not** part of the JVector Java build and
> is never invoked by Maven or the Java test harness.

The test suite uses [Google Test](https://github.com/google/googletest) and is
built automatically when `gtest_main` is available via pkg-config.

### Install Google Test

**Ubuntu / Debian:**
```bash
sudo apt install libgtest-dev cmake
cd /usr/src/gtest && sudo cmake . && sudo make && sudo cp lib/*.a /usr/local/lib
```

Or via vcpkg / Conan if you prefer a package manager.

### Build and run

```bash
# 1. Configure (from the native source directory)
cd jvector-native/src/main/native
meson setup ../../../target/meson-build --wipe --buildtype=debugoptimized

# 2. Compile (the test binary is only emitted when gtest is found)
meson compile -C ../../../target/meson-build

# 3. Run via meson's test runner (pretty output, parallel execution)
meson test -C ../../../target/meson-build --suite simd_kernels -v

# — or run the binary directly —
../../../target/meson-build/test_simd_kernels
```

#### What the tests cover

| Source file | What it tests |
|---|---|
| `tests/test_similarity.cpp` | `cosine_f32`, `dot_product_f32`, `euclidean_f32` across all ISA tiers |
| `tests/test_elementwise.cpp` | Element-wise vector operations |
| `tests/test_cpu_features.cpp` | CPUID feature detection |
| `tests/test_helpers.cpp` | Shared test utilities (not a test suite on its own) |

#### Forcing a specific ISA in tests

Set `JVECTOR_MAX_ISA` to cap dispatch before running:

```bash
JVECTOR_MAX_ISA=avx2 ../../../target/meson-build/test_simd_kernels
JVECTOR_MAX_ISA=sse42 ../../../target/meson-build/test_simd_kernels
```

---

## How to run benchmarks

> **Standalone development tool.** The benchmark binary links directly against
> `libjvector.so` and is intended for native-layer micro-benchmarking during
> development. It is **not** wired into the JVector Java build or any Maven
> profile.

The benchmark suite uses [Google Benchmark](https://github.com/google/benchmark)
and is built automatically when the `benchmark` pkg-config package is found.

### Install Google Benchmark

**Ubuntu / Debian:**
```bash
sudo apt install libbenchmark-dev
```

### Build and run

```bash
# 1. Configure (benchmarks are built at -O3 regardless of buildtype)
cd jvector-native/src/main/native
meson setup ../../../target/meson-build --wipe --buildtype=release

# 2. Compile
meson compile -C ../../../target/meson-build

# 3. Run all benchmarks
../../../target/meson-build/bench_simd_kernels

# Run a specific kernel
../../../target/meson-build/bench_simd_kernels --benchmark_filter=cosine_f32

# Human-readable output with extra statistics
../../../target/meson-build/bench_simd_kernels --benchmark_format=console --benchmark_repetitions=3
```

#### What the benchmarks cover

| Source file | Kernels benchmarked | Dimensions |
|---|---|---|
| `benchmarks/bench_similarity_f32.cpp` | `cosine_f32`, `dot_product_f32`, `euclidean_f32` | 128, 256, 512, 1024, 1536, 3072 |

The binary dispatches to the **best ISA available on the host** at startup. Use
`JVECTOR_MAX_ISA` to benchmark a specific tier:

```bash
JVECTOR_MAX_ISA=avx2 ../../../target/meson-build/bench_simd_kernels
JVECTOR_MAX_ISA=sse42 ../../../target/meson-build/bench_simd_kernels
```

---

## How it is integrated into JVector

```
Expand Down
118 changes: 118 additions & 0 deletions jvector-native/src/main/native/benchmarks/bench_similarity_f32.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright DataStax, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Google Benchmark micro-benchmarks for the fp32 vector similarity kernels:
// cosine_f32, dot_product_f32, euclidean_f32
//
// Parameterised over the realistic embedding dimensions used in production:
// 128, 256, 512, 1024, 1536, 3072
//
// Build (requires google-benchmark installed or available via pkg-config):
// meson setup build && ninja -C build bench_simd_kernels
Comment thread
MarkWolters marked this conversation as resolved.
//
// Run:
// ./build/bench_simd_kernels [--benchmark_filter=<pattern>]

#include <benchmark/benchmark.h>
#include <cmath>
#include <vector>

#include "jvector_simd.h"

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

// Deterministic, non-zero float vector: avoids degenerate cosine=NaN cases.
static std::vector<float> make_vec(size_t n, float seed)
{
std::vector<float> v(n);
for (size_t i = 0; i < n; ++i) {
v[i] = seed * (1.0f + static_cast<float>(i % 7) * 0.13f);
if (i % 3 == 0) v[i] = -v[i];
v[i] += 0.5f;
}
return v;
}

// Benchmark sizes matching production embedding dimensions.
static const std::vector<int64_t> kBenchSizes = {128, 256, 512, 1024, 1536, 3072};

// ---------------------------------------------------------------------------
// dot_product_f32
// ---------------------------------------------------------------------------

static void BM_dot_product_f32(benchmark::State& state)
{
const size_t n = static_cast<size_t>(state.range(0));
auto a = make_vec(n, 0.7f);
auto b = make_vec(n, 1.3f);

for (auto _ : state) {
float result = dot_product_f32(a.data(), 0, b.data(), 0, n);
benchmark::DoNotOptimize(result);
}

state.SetItemsProcessed(state.iterations() * static_cast<int64_t>(n));
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(n) * 2 * sizeof(float));
}
BENCHMARK(BM_dot_product_f32)->ArgsProduct({kBenchSizes});

// ---------------------------------------------------------------------------
// euclidean_f32
// ---------------------------------------------------------------------------

static void BM_euclidean_f32(benchmark::State& state)
{
const size_t n = static_cast<size_t>(state.range(0));
auto a = make_vec(n, 0.7f);
auto b = make_vec(n, 1.3f);

for (auto _ : state) {
float result = euclidean_f32(a.data(), 0, b.data(), 0, n);
benchmark::DoNotOptimize(result);
}

state.SetItemsProcessed(state.iterations() * static_cast<int64_t>(n));
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(n) * 2 * sizeof(float));
}
BENCHMARK(BM_euclidean_f32)->ArgsProduct({kBenchSizes});

// ---------------------------------------------------------------------------
// cosine_f32
// ---------------------------------------------------------------------------

static void BM_cosine_f32(benchmark::State& state)
{
const size_t n = static_cast<size_t>(state.range(0));
auto a = make_vec(n, 0.7f);
auto b = make_vec(n, 1.3f);

for (auto _ : state) {
float result = cosine_f32(a.data(), 0, b.data(), 0, n);
benchmark::DoNotOptimize(result);
}

state.SetItemsProcessed(state.iterations() * static_cast<int64_t>(n));
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(n) * 2 * sizeof(float));
}
BENCHMARK(BM_cosine_f32)->ArgsProduct({kBenchSizes});

// ---------------------------------------------------------------------------
// Entry point — benchmark::Initialize parses --benchmark_* flags.
// ---------------------------------------------------------------------------

BENCHMARK_MAIN();
Loading
Loading