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
16 changes: 14 additions & 2 deletions .github/scripts/build-cpp-runtime-bindings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@

set -e # Exit on error

# Source environment setup (for compiler and MKL)
# Source environment setup (for compiler)
source /etc/bashrc || true

# Source MKL environment if IVF is enabled
if [ "${ENABLE_IVF:-OFF}" = "ON" ]; then
if [ -f /opt/intel/oneapi/setvars.sh ]; then
source /opt/intel/oneapi/setvars.sh --include-intel-llvm 2>/dev/null || true
echo "MKL sourced: MKLROOT=${MKLROOT}"
else
echo "ERROR: IVF enabled but MKL setvars.sh not found"
exit 1
fi
fi

# Create build+install directories for cpp runtime bindings
rm -rf /workspace/bindings/cpp/build_cpp_bindings /workspace/install_cpp_bindings
mkdir -p /workspace/bindings/cpp/build_cpp_bindings /workspace/install_cpp_bindings
Expand All @@ -31,6 +42,7 @@ CMAKE_ARGS=(
"-DCMAKE_INSTALL_PREFIX=/workspace/install_cpp_bindings"
"-DCMAKE_INSTALL_LIBDIR=lib"
"-DSVS_RUNTIME_ENABLE_LVQ_LEANVEC=${ENABLE_LVQ_LEANVEC:-ON}"
"-DSVS_RUNTIME_ENABLE_IVF=${ENABLE_IVF:-OFF}"
)

if [ -n "$SVS_URL" ]; then
Expand All @@ -45,7 +57,7 @@ cmake --install .
# Build conda package for cpp runtime bindings
source /opt/conda/etc/profile.d/conda.sh
cd /workspace
ENABLE_LVQ_LEANVEC=${ENABLE_LVQ_LEANVEC:-ON} SVS_URL="${SVS_URL}" SUFFIX="${SUFFIX}" conda build bindings/cpp/conda-recipe --output-folder /workspace/conda-bld
ENABLE_LVQ_LEANVEC=${ENABLE_LVQ_LEANVEC:-ON} ENABLE_IVF=${ENABLE_IVF:-OFF} SVS_URL="${SVS_URL}" SUFFIX="${SUFFIX}" conda build bindings/cpp/conda-recipe --output-folder /workspace/conda-bld

# Create tarball with symlink for compatibility
cd /workspace/install_cpp_bindings && \
Expand Down
9 changes: 8 additions & 1 deletion .github/scripts/test-cpp-runtime-bindings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ conda install -y mkl=2022.2.1 mkl-devel=2022.2.1
conda install -y /runtime_conda/libsvs-runtime-*.conda

# Validate python and C++ tests against FAISS CI
git clone https://github.com/facebookresearch/faiss.git
ENABLE_IVF="${ENABLE_IVF:-OFF}"
if [ "${ENABLE_IVF}" = "ON" ]; then
echo "IVF enabled: cloning forked FAISS with IVF support"
git clone --branch ib/svs_ivf https://github.com/ibhati/faiss.git
else
echo "IVF disabled: cloning upstream FAISS"
git clone https://github.com/facebookresearch/faiss.git
fi
cd faiss

echo "==============================================="
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/build-cpp-runtime-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ jobs:
include:
- name: "with static library"
enable_lvq_leanvec: "ON"
enable_ivf: "OFF"
suffix: ""
- name: "public only"
enable_lvq_leanvec: "OFF"
enable_ivf: "OFF"
suffix: "-public-only"
- name: "IVF public only"
enable_lvq_leanvec: "OFF"
enable_ivf: "ON"
suffix: "-ivf"
fail-fast: false

steps:
Expand All @@ -56,6 +62,7 @@ jobs:
-v ${{ github.workspace }}:/workspace \
-w /workspace \
-e ENABLE_LVQ_LEANVEC=${{ matrix.enable_lvq_leanvec }} \
-e ENABLE_IVF=${{ matrix.enable_ivf }} \
-e SUFFIX=${{ matrix.suffix }} \
svs-manylinux228:latest \
/bin/bash .github/scripts/build-cpp-runtime-bindings.sh
Expand All @@ -80,8 +87,11 @@ jobs:
docker run --rm \
-v ${{ github.workspace }}:/workspace \
-w /workspace \
-e ENABLE_IVF=${{ matrix.enable_ivf }} \
svs-manylinux228:latest \
/bin/bash -c "source /etc/bashrc || true && ctest --test-dir bindings/cpp/build_cpp_bindings/tests --output-on-failure --no-tests=error --verbose"
/bin/bash -c "source /etc/bashrc || true && \
if [ \"\${ENABLE_IVF}\" = 'ON' ] && [ -f /opt/intel/oneapi/setvars.sh ]; then source /opt/intel/oneapi/setvars.sh 2>/dev/null || true; fi && \
ctest --test-dir bindings/cpp/build_cpp_bindings/tests --output-on-failure --no-tests=error --verbose"

# Run full test script using the built artifacts
test:
Expand All @@ -93,8 +103,13 @@ jobs:
include:
- name: "with static library"
suffix: ""
enable_ivf: "OFF"
- name: "public only"
suffix: "-public-only"
enable_ivf: "OFF"
- name: "IVF public only"
suffix: "-ivf"
enable_ivf: "ON"

steps:
- uses: actions/checkout@v6
Expand All @@ -121,5 +136,6 @@ jobs:
-v ${{ github.workspace }}/runtime_conda:/runtime_conda \
-w /workspace \
-e SUFFIX=${{ matrix.suffix }} \
-e ENABLE_IVF=${{ matrix.enable_ivf }} \
svs-manylinux228:latest \
/bin/bash .github/scripts/test-cpp-runtime-bindings.sh
42 changes: 30 additions & 12 deletions bindings/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ cmake_minimum_required(VERSION 3.21)
project(svs_runtime VERSION 0.2.0 LANGUAGES CXX)
set(TARGET_NAME svs_runtime)

# IVF requires MKL, so it's optional
option(SVS_RUNTIME_ENABLE_IVF "Enable compilation of SVS runtime with IVF support (requires MKL)" OFF)

set(SVS_RUNTIME_HEADERS
include/svs/runtime/version.h
include/svs/runtime/api_defs.h
Expand All @@ -38,6 +41,23 @@ set(SVS_RUNTIME_SOURCES
src/flat_index.cpp
)

# Add IVF files if enabled
if (SVS_RUNTIME_ENABLE_IVF)
message(STATUS "SVS runtime will be built with IVF support (requires MKL)")
list(APPEND SVS_RUNTIME_HEADERS
include/svs/runtime/ivf_index.h
include/svs/runtime/dynamic_ivf_index.h
)
list(APPEND SVS_RUNTIME_SOURCES
src/ivf_index_impl.h
src/dynamic_ivf_index_impl.h
src/ivf_index.cpp
src/dynamic_ivf_index.cpp
)
else()
message(STATUS "SVS runtime will be built without IVF support")
endif()

option(SVS_RUNTIME_ENABLE_LVQ_LEANVEC "Enable compilation of SVS runtime with LVQ and LeanVec support" ON)
if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
message(STATUS "SVS runtime will be built with LVQ support")
Expand Down Expand Up @@ -75,14 +95,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )

if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
if(DEFINED SVS_LVQ_HEADER AND DEFINED SVS_LEANVEC_HEADER)
# expected that pre-defined headers are implementation headers
message(STATUS "Using pre-defined LVQ header: ${SVS_LVQ_HEADER}")
message(STATUS "Using pre-defined LeanVec header: ${SVS_LEANVEC_HEADER}")
else()
set(SVS_LVQ_HEADER "svs/extensions/vamana/lvq.h")
set(SVS_LEANVEC_HEADER "svs/extensions/vamana/leanvec.h")
endif()

if(RUNTIME_BINDINGS_PRIVATE_SOURCE_BUILD)
message(STATUS "Building directly from private sources")
Expand Down Expand Up @@ -123,13 +135,14 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
svs::svs_static_library
)
endif()
target_compile_definitions(${TARGET_NAME} PRIVATE
PUBLIC "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\""
PUBLIC "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\""
)
target_compile_definitions(${TARGET_NAME} PUBLIC SVS_RUNTIME_HAVE_LVQ_LEANVEC)
else()
# Include the SVS library directly if needed.
if (NOT TARGET svs::svs)
# Pass IVF flag to parent build if IVF is enabled
if (SVS_RUNTIME_ENABLE_IVF)
set(SVS_EXPERIMENTAL_ENABLE_IVF ON CACHE BOOL "" FORCE)
endif()
add_subdirectory("../.." "${CMAKE_CURRENT_BINARY_DIR}/svs")
endif()
target_link_libraries(${TARGET_NAME} PRIVATE
Expand All @@ -139,6 +152,11 @@ else()
)
endif()

# IVF requires Intel(R) MKL support
if (SVS_RUNTIME_ENABLE_IVF)
target_compile_definitions(${TARGET_NAME} PRIVATE SVS_HAVE_MKL=1)
endif()

# installing
include(GNUInstallDirs)

Expand Down
12 changes: 12 additions & 0 deletions bindings/cpp/conda-recipe/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@ else
echo "WARNING: gcc-toolset-11 not found, proceeding without sourcing it"
fi

# Source MKL environment if IVF is enabled (IVF requires MKL)
if [ "${ENABLE_IVF:-OFF}" = "ON" ]; then
if [ -f /opt/intel/oneapi/setvars.sh ]; then
source /opt/intel/oneapi/setvars.sh --include-intel-llvm 2>/dev/null || true
echo "MKL sourced for IVF build: MKLROOT=${MKLROOT}"
else
echo "ERROR: IVF enabled but MKL setvars.sh not found"
exit 1
fi
fi

# build runtime tests flag?
CMAKE_ARGS=(
"-DCMAKE_INSTALL_PREFIX=${PREFIX}"
"-DSVS_BUILD_RUNTIME_TESTS=OFF"
"-DSVS_RUNTIME_ENABLE_LVQ_LEANVEC=${ENABLE_LVQ_LEANVEC:-ON}"
"-DSVS_RUNTIME_ENABLE_IVF=${ENABLE_IVF:-OFF}"
)

# Add SVS_URL if specified (for fetching static library)
Expand Down
5 changes: 5 additions & 0 deletions bindings/cpp/conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ build:
string: {{ GIT_DESCRIBE_HASH }}_{{ buildnumber }}{{ variant_suffix }}
skip: true # [not linux]
include_recipe: False
script_env:
- ENABLE_IVF
- ENABLE_LVQ_LEANVEC
- SUFFIX
- SVS_URL

requirements:
build:
Expand Down
166 changes: 166 additions & 0 deletions bindings/cpp/include/svs/runtime/dynamic_ivf_index.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright 2026 Intel Corporation
*
* 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.
*/

#pragma once
#include <svs/runtime/api_defs.h>
#include <svs/runtime/ivf_index.h>

#include <cstddef>
#include <istream>
#include <ostream>

namespace svs {
namespace runtime {
namespace v0 {

/// @brief Abstract interface for dynamic IVF indices (supports add/delete).
struct SVS_RUNTIME_API DynamicIVFIndex : public IVFIndex {
/// @brief Utility function to check storage kind support.
static Status check_storage_kind(StorageKind storage_kind) noexcept;

/// @brief Build a dynamic IVF index.
///
/// @param index Output pointer to the created index.
/// @param dim Dimensionality of vectors.
/// @param metric Distance metric to use.
/// @param storage_kind Storage type for the dataset.
/// @param n Number of initial vectors (can be 0 for empty index).
/// @param data Pointer to initial vector data (can be nullptr if n=0).
/// @param labels Pointer to labels for initial vectors (can be nullptr if n=0).
/// @param params Build parameters for clustering.
/// @param default_search_params Default search parameters.
/// @param num_threads Number of threads for operations.
/// @param intra_query_threads Number of threads for intra-query parallelism.
/// @return Status indicating success or error.
static Status build(
DynamicIVFIndex** index,
size_t dim,
MetricType metric,
StorageKind storage_kind,
size_t n,
const float* data,
const size_t* labels,
const IVFIndex::BuildParams& params = {},
const IVFIndex::SearchParams& default_search_params = {},
size_t num_threads = 0,
size_t intra_query_threads = 1
) noexcept;

/// @brief Destroy a dynamic IVF index.
static Status destroy(DynamicIVFIndex* index) noexcept;

/// @brief Add vectors to the index.
///
/// @param n Number of vectors to add.
/// @param labels Pointer to labels for the new vectors.
/// @param x Pointer to vector data (row-major, n x dimensions).
/// @param reuse_empty Whether to reuse empty slots from deleted vectors.
/// @return Status indicating success or error.
virtual Status
add(size_t n, const size_t* labels, const float* x, bool reuse_empty = false
) noexcept = 0;

/// @brief Remove vectors from the index by ID.
///
/// @param n Number of vectors to remove.
/// @param labels Pointer to labels of vectors to remove.
/// @return Status indicating success or error.
virtual Status remove(size_t n, const size_t* labels) noexcept = 0;

/// @brief Remove vectors matching a selector.
///
/// @param num_removed Output: number of vectors actually removed.
/// @param selector Filter to determine which vectors to remove.
/// @return Status indicating success or error.
virtual Status
remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0;

/// @brief Check if an ID exists in the index.
///
/// @param exists Output: true if the ID exists.
/// @param id The ID to check.
/// @return Status indicating success or error.
virtual Status has_id(bool* exists, size_t id) const noexcept = 0;

/// @brief Consolidate the index (clean up deleted entries).
virtual Status consolidate() noexcept = 0;

/// @brief Compact the index (reclaim memory from deleted entries).
///
/// @param batchsize Number of entries to process per batch.
/// @return Status indicating success or error.
virtual Status compact(size_t batchsize = 1'000'000) noexcept = 0;

/// @brief Save the index to a stream.
virtual Status save(std::ostream& out) const noexcept = 0;

/// @brief Load a dynamic IVF index from a stream.
///
/// @param index Output pointer to the loaded index.
/// @param in Input stream containing the serialized index.
/// @param metric Distance metric to use.
/// @param storage_kind Storage type for the dataset.
/// @param num_threads Number of threads for operations.
/// @param intra_query_threads Number of threads for intra-query parallelism.
/// @return Status indicating success or error.
static Status load(
DynamicIVFIndex** index,
std::istream& in,
MetricType metric,
StorageKind storage_kind,
size_t num_threads = 0,
size_t intra_query_threads = 1
) noexcept;
};

/// @brief Specialization for building LeanVec-based dynamic IVF indices.
struct SVS_RUNTIME_API DynamicIVFIndexLeanVec : public DynamicIVFIndex {
/// @brief Build a LeanVec dynamic IVF index with specified leanvec dimensions.
static Status build(
DynamicIVFIndex** index,
size_t dim,
MetricType metric,
StorageKind storage_kind,
size_t n,
const float* data,
const size_t* labels,
size_t leanvec_dims,
const IVFIndex::BuildParams& params = {},
const IVFIndex::SearchParams& default_search_params = {},
size_t num_threads = 0,
size_t intra_query_threads = 1
) noexcept;

/// @brief Build a LeanVec dynamic IVF index with provided training data.
static Status build(
DynamicIVFIndex** index,
size_t dim,
MetricType metric,
StorageKind storage_kind,
size_t n,
const float* data,
const size_t* labels,
const LeanVecTrainingData* training_data,
const IVFIndex::BuildParams& params = {},
const IVFIndex::SearchParams& default_search_params = {},
size_t num_threads = 0,
size_t intra_query_threads = 1
) noexcept;
};

} // namespace v0
} // namespace runtime
} // namespace svs
Loading
Loading