From b2d6285a0cd9697fed2c7123a96023c9d9b712f2 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 30 Oct 2025 16:59:55 +0100 Subject: [PATCH 01/46] Initial implementation of SVS Runtime package --- bindings/cpp/CMakeLists.txt | 168 ++++++ bindings/cpp/include/IndexSVSFlatImpl.h | 52 ++ bindings/cpp/include/IndexSVSImplDefs.h | 74 +++ bindings/cpp/include/IndexSVSVamanaImpl.h | 109 ++++ bindings/cpp/include/IndexSVSVamanaLVQImpl.h | 40 ++ .../cpp/include/IndexSVSVamanaLeanVecImpl.h | 72 +++ bindings/cpp/runtimeConfig.cmake.in | 18 + bindings/cpp/src/IndexSVSFlatImpl.cpp | 152 ++++++ bindings/cpp/src/IndexSVSImplUtils.h | 34 ++ bindings/cpp/src/IndexSVSVamanaImpl.cpp | 507 ++++++++++++++++++ bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp | 208 +++++++ .../cpp/src/IndexSVSVamanaLeanVecImpl.cpp | 280 ++++++++++ 12 files changed, 1714 insertions(+) create mode 100644 bindings/cpp/CMakeLists.txt create mode 100644 bindings/cpp/include/IndexSVSFlatImpl.h create mode 100644 bindings/cpp/include/IndexSVSImplDefs.h create mode 100644 bindings/cpp/include/IndexSVSVamanaImpl.h create mode 100644 bindings/cpp/include/IndexSVSVamanaLVQImpl.h create mode 100644 bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h create mode 100644 bindings/cpp/runtimeConfig.cmake.in create mode 100644 bindings/cpp/src/IndexSVSFlatImpl.cpp create mode 100644 bindings/cpp/src/IndexSVSImplUtils.h create mode 100644 bindings/cpp/src/IndexSVSVamanaImpl.cpp create mode 100644 bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp create mode 100644 bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt new file mode 100644 index 00000000..813d9729 --- /dev/null +++ b/bindings/cpp/CMakeLists.txt @@ -0,0 +1,168 @@ +# Copyright (C) 2023 Intel Corporation +# +# This software and the related documents are Intel copyrighted materials, +# and your use of them is governed by the express license under which they +# were provided to you ("License"). Unless the License provides otherwise, +# you may not use, modify, copy, publish, distribute, disclose or transmit +# this software or the related documents without Intel's prior written +# permission. +# +# This software and the related documents are provided as is, with no +# express or implied warranties, other than those that are expressly stated +# in the License. + +cmake_minimum_required(VERSION 3.21) +project(svs_runtime VERSION 0.0.10 LANGUAGES CXX) +set(TARGET_NAME svs_runtime) + +set(SVS_RUNTIME_HEADERS + include/IndexSVSImplDefs.h + include/IndexSVSFlatImpl.h + include/IndexSVSVamanaImpl.h +) + +set(SVS_RUNTIME_SOURCES + src/IndexSVSImplUtils.h + src/IndexSVSFlatImpl.cpp + src/IndexSVSVamanaImpl.cpp +) + +option(SVS_RUNTIME_ENABLE_LVQ "Enable compilation of SVS runtime with LVQ support" ON) +if (SVS_RUNTIME_ENABLE_LVQ) + message(STATUS "SVS runtime will be built with LVQ support") + list(APPEND SVS_RUNTIME_HEADERS + include/IndexSVSVamanaLVQImpl.h + ) + list(APPEND SVS_RUNTIME_SOURCES + src/IndexSVSVamanaLVQImpl.cpp + ) +else() + message(STATUS "SVS runtime will be built without LVQ support") +endif() + +option(SVS_RUNTIME_ENABLE_LEANVEC "Enable compilation of SVS runtime with LeanVec support" ON) +if (SVS_RUNTIME_ENABLE_LEANVEC) + message(STATUS "SVS runtime will be built with LeanVec support") + list(APPEND SVS_RUNTIME_HEADERS + include/IndexSVSVamanaLeanVecImpl.h + ) + list(APPEND SVS_RUNTIME_SOURCES + src/IndexSVSVamanaLeanVecImpl.cpp + ) +else() + message(STATUS "SVS runtime will be built without LeanVec support") +endif() + +find_package(OpenMP REQUIRED) + +add_library(${TARGET_NAME} SHARED + ${SVS_RUNTIME_HEADERS} + ${SVS_RUNTIME_SOURCES} +) + +target_link_libraries(${TARGET_NAME} PUBLIC + OpenMP::OpenMP_CXX +) + +target_include_directories(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_options(${TARGET_NAME} PRIVATE + -DSVS_ENABLE_OMP=1 + -fvisibility=hidden +) + +target_compile_features(${TARGET_NAME} INTERFACE cxx_std_20) +set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_RUNTIME_HEADERS}") +set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 20) +set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON) +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 OR SVS_RUNTIME_ENABLE_LEANVEC)) + if(TARGET svs::svs_static_library) + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + svs::svs_static_library + svs_compile_options + svs_x86_options_base + ) + else() + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251017-faiss.tar.gz" + CACHE STRING "URL to download SVS shared library") + include(FetchContent) + FetchContent_Declare( + svs + URL ${SVS_URL} + ) + FetchContent_MakeAvailable(svs) + list(APPEND CMAKE_PREFIX_PATH "${svs_SOURCE_DIR}") + find_package(svs REQUIRED) + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + svs::svs_compile_options + svs::svs_static_library + ) + endif() +else() + # Include the SVS library directly if needed. + if (NOT TARGET svs::svs) + add_subdirectory("../.." "${CMAKE_CURRENT_BINARY_DIR}/svs") + endif() + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + svs_compile_options + svs_x86_options_base + ) +endif() + +# installing +include(GNUInstallDirs) + +set(SVS_RUNTIME_EXPORT_NAME ${TARGET_NAME}) +set(VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}ConfigVersion.cmake") +set(SVS_RUNTIME_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/svs_runtime) +set(SVS_RUNTIME_COMPONENT_NAME "Runtime") + +install(TARGETS ${TARGET_NAME} + EXPORT ${SVS_RUNTIME_EXPORT_NAME} + COMPONENT ${SVS_RUNTIME_COMPONENT_NAME} + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include/svs/runtime + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(DIRECTORY include/svs/runtime + COMPONENT ${SVS_RUNTIME_COMPONENT_NAME} + DESTINATION include/svs + FILES_MATCHING PATTERN "*.h" +) + +install(EXPORT ${SVS_RUNTIME_EXPORT_NAME} + COMPONENT ${SVS_RUNTIME_COMPONENT_NAME} + NAMESPACE svs:: + DESTINATION ${SVS_RUNTIME_CONFIG_INSTALL_DIR} +) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/runtimeConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}Config.cmake" + INSTALL_DESTINATION "${SVS_RUNTIME_CONFIG_INSTALL_DIR}" +) + +# Don't make compatibility guarantees until we reach a compatibility milestone. +write_basic_package_version_file( + ${VERSION_CONFIG} + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}Config.cmake" + "${VERSION_CONFIG}" + COMPONENT ${SVS_RUNTIME_COMPONENT_NAME} + DESTINATION "${SVS_RUNTIME_CONFIG_INSTALL_DIR}" +) + diff --git a/bindings/cpp/include/IndexSVSFlatImpl.h b/bindings/cpp/include/IndexSVSFlatImpl.h new file mode 100644 index 00000000..2539de33 --- /dev/null +++ b/bindings/cpp/include/IndexSVSFlatImpl.h @@ -0,0 +1,52 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSImplDefs.h" +#include +#include +#include + +namespace svs { +class Flat; + +namespace runtime { +class SVS_RUNTIME_API IndexSVSFlatImpl { + public: + static IndexSVSFlatImpl* build(size_t dim, MetricType metric) noexcept; + static void destroy(IndexSVSFlatImpl* impl) noexcept; + + Status add(size_t n, const float* x) noexcept; + void reset() noexcept; + + Status search( + size_t n, const float* x, size_t k, float* distances, size_t* labels + ) const noexcept; + + Status serialize(std::ostream& out) const noexcept; + + Status deserialize(std::istream& in) noexcept; + + private: + IndexSVSFlatImpl(size_t dim, MetricType metric); + ~IndexSVSFlatImpl(); + Status init_impl(size_t n, const float* x) noexcept; + + MetricType metric_type_; + size_t dim_; + std::unique_ptr impl{nullptr}; +}; +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h new file mode 100644 index 00000000..7967ff44 --- /dev/null +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -0,0 +1,74 @@ +/* + * Copyright 2025 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 +#include + +#ifdef svs_runtime_EXPORTS +#define SVS_RUNTIME_API __attribute__((visibility("default"))) +#define SVS_RUNTIME_API_INTERFACE // reserved for future use +#else +#define SVS_RUNTIME_API +#define SVS_RUNTIME_API_INTERFACE // reserved for future use +#endif + +namespace svs { +namespace runtime { +enum class MetricType { L2, INNER_PRODUCT }; + +enum class ErrorCode { + SUCCESS = 0, + UNKNOWN_ERROR = 1, + INVALID_ARGUMENT = 2, + NOT_IMPLEMENTED = 3, + NOT_INITIALIZED = 4 +}; + +struct Status { + ErrorCode code = ErrorCode::SUCCESS; + const char* message = nullptr; + constexpr bool ok() const { return code == ErrorCode::SUCCESS; } +}; + +constexpr Status Status_Ok{ErrorCode::SUCCESS, nullptr}; + +struct SVS_RUNTIME_API_INTERFACE IDFilter { + virtual bool is_member(size_t id) const = 0; + virtual ~IDFilter() = default; + + // Helper method to allow using IDFilter instances as callable objects + bool operator()(size_t id) const { return this->is_member(id); } +}; + +struct SearchResultsStorage { + std::span labels; // faiss::idx_t is int64_t + std::span distances; +}; + +struct SVS_RUNTIME_API_INTERFACE ResultsAllocator { + virtual SearchResultsStorage allocate(std::span result_counts) const = 0; + virtual ~ResultsAllocator() = default; + + // Helper method to allow using ResultsAllocator instances as callable objects + SearchResultsStorage operator()(std::span result_counts) const { + return this->allocate(result_counts); + } +}; + +} // namespace runtime +} // namespace svs \ No newline at end of file diff --git a/bindings/cpp/include/IndexSVSVamanaImpl.h b/bindings/cpp/include/IndexSVSVamanaImpl.h new file mode 100644 index 00000000..649906dc --- /dev/null +++ b/bindings/cpp/include/IndexSVSVamanaImpl.h @@ -0,0 +1,109 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" + +#include +#include +#include +#include +#include +#include + +namespace svs { +class DynamicVamana; + +namespace runtime { + +struct SVS_RUNTIME_API IndexSVSVamanaImpl { + struct SearchParams { + size_t search_window_size = 0; + size_t search_buffer_capacity = 0; + }; + + enum StorageKind { FP32, FP16, SQI8 } storage_kind = StorageKind::FP32; + + struct BuildParams { + StorageKind storage_kind; + size_t graph_max_degree; + size_t prune_to; + float alpha = 1.2; + size_t construction_window_size = 40; + size_t max_candidate_pool_size = 200; + bool use_full_search_history = true; + }; + + static IndexSVSVamanaImpl* + build(size_t dim, MetricType metric, const BuildParams& params) noexcept; + static void destroy(IndexSVSVamanaImpl* impl) noexcept; + + Status add(size_t n, const float* x) noexcept; + + Status search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept; + + Status range_search( + size_t n, + const float* x, + float radius, + const ResultsAllocator& results, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept; + + size_t remove_ids(const IDFilter& selector) noexcept; + + virtual void reset() noexcept; + + /* Serialization and deserialization helpers */ + Status serialize_impl(std::ostream& out) const noexcept; + virtual Status deserialize_impl(std::istream& in) noexcept; + + MetricType metric_type_; + size_t dim_; + SearchParams default_search_params{10, 10}; + BuildParams build_params{}; + + protected: + IndexSVSVamanaImpl(); + + IndexSVSVamanaImpl( + size_t d, + size_t degree, + MetricType metric = MetricType::L2, + StorageKind storage = StorageKind::FP32 + ); + + virtual ~IndexSVSVamanaImpl(); + + /* Initializes the implementation, using the provided data */ + virtual Status init_impl(size_t n, const float* x) noexcept; + + /* The actual SVS implementation */ + std::unique_ptr impl{nullptr}; + size_t ntotal_soft_deleted{0}; +}; + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h new file mode 100644 index 00000000..f72822a0 --- /dev/null +++ b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h @@ -0,0 +1,40 @@ +/* + * Copyright 2025 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 "IndexSVSVamanaImpl.h" + +namespace svs::runtime { + +struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { + enum LVQLevel { LVQ4x0, LVQ4x4, LVQ4x8 }; + static IndexSVSVamanaLVQImpl* + build(size_t dim, MetricType metric, const BuildParams& params, LVQLevel lvq) noexcept; + + Status deserialize_impl(std::istream& in) noexcept override; + + protected: + IndexSVSVamanaLVQImpl(); + IndexSVSVamanaLVQImpl(size_t d, size_t degree, MetricType metric, LVQLevel lvq_level); + + ~IndexSVSVamanaLVQImpl() override; + + Status init_impl(size_t n, const float* x) noexcept override; + + LVQLevel lvq_level; +}; + +} // namespace svs::runtime diff --git a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h new file mode 100644 index 00000000..eac2a760 --- /dev/null +++ b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h @@ -0,0 +1,72 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" +#include "IndexSVSVamanaImpl.h" + +#include +#include + +namespace svs { +namespace leanvec { +template struct LeanVecMatrices; +} // namespace leanvec + +namespace runtime { + +struct SVS_RUNTIME_API IndexSVSVamanaLeanVecImpl : IndexSVSVamanaImpl { + enum LeanVecLevel { LeanVec4x4, LeanVec4x8, LeanVec8x8 }; + + static IndexSVSVamanaLeanVecImpl* build( + size_t dim, + MetricType metric, + const BuildParams& params, + size_t leanvec_dims, + LeanVecLevel leanvec_level + ) noexcept; + + void reset() noexcept override; + + Status train(size_t n, const float* x) noexcept; + + Status deserialize_impl(std::istream& in) noexcept override; + + bool is_trained() const noexcept { return trained; } + + protected: + IndexSVSVamanaLeanVecImpl(); + + IndexSVSVamanaLeanVecImpl( + size_t d, + size_t degree, + MetricType metric = MetricType::L2, + size_t leanvec_dims = 0, + LeanVecLevel leanvec_level = LeanVecLevel::LeanVec4x4 + ); + + ~IndexSVSVamanaLeanVecImpl() override; + + Status init_impl(size_t n, const float* x) noexcept override; + + size_t leanvec_d; + LeanVecLevel leanvec_level; + std::unique_ptr> leanvec_matrix; + bool trained = false; +}; + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/runtimeConfig.cmake.in b/bindings/cpp/runtimeConfig.cmake.in new file mode 100644 index 00000000..4c4570ba --- /dev/null +++ b/bindings/cpp/runtimeConfig.cmake.in @@ -0,0 +1,18 @@ +# Copyright 2023 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. + +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/svs_runtime.cmake") +check_required_components(svs_runtime) diff --git a/bindings/cpp/src/IndexSVSFlatImpl.cpp b/bindings/cpp/src/IndexSVSFlatImpl.cpp new file mode 100644 index 00000000..393f73a6 --- /dev/null +++ b/bindings/cpp/src/IndexSVSFlatImpl.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSFlatImpl.h" +#include "IndexSVSImplUtils.h" + +#include +#include +#include + +#include + +namespace svs { + +namespace runtime { + +IndexSVSFlatImpl* IndexSVSFlatImpl::build(size_t dim, MetricType metric) noexcept { + return new IndexSVSFlatImpl(dim, metric); +} + +void IndexSVSFlatImpl::destroy(IndexSVSFlatImpl* impl) noexcept { delete impl; } + +Status IndexSVSFlatImpl::init_impl(size_t n, const float* x) noexcept { + auto data = svs::data::SimpleData(n, dim_); + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + svs::threads::parallel_for( + threadpool, svs::threads::StaticPartition(n), [&](auto is, auto SVS_UNUSED(tid)) { + for (auto i : is) { + data.set_datum(i, std::span(x + i * dim_, dim_)); + } + } + ); + + switch (metric_type_) { + case MetricType::INNER_PRODUCT: + impl.reset(new svs::Flat( + svs::Flat::assemble( + std::move(data), svs::DistanceIP(), std::move(threadpool) + ) + )); + break; + case MetricType::L2: + impl.reset(new svs::Flat( + svs::Flat::assemble( + std::move(data), svs::DistanceL2(), std::move(threadpool) + ) + )); + break; + default: + impl = nullptr; + return {ErrorCode::INVALID_ARGUMENT, "not supported SVS distance"}; + } + return Status_Ok; +} + +Status IndexSVSFlatImpl::add(size_t n, const float* x) noexcept { + if (!impl) { + return init_impl(n, x); + } + + return { + ErrorCode::NOT_IMPLEMENTED, + "IndexSVSFlat does not support adding points after initialization" + }; +} + +void IndexSVSFlatImpl::reset() noexcept { impl.reset(); } + +Status IndexSVSFlatImpl::search( + size_t n, const float* x, size_t k, float* distances, size_t* labels +) const noexcept { + if (!impl) { + return {ErrorCode::UNKNOWN_ERROR, "SVS index not initialized"}; + } + if (k == 0) { + return {ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; + } + + auto queries = svs::data::ConstSimpleDataView(x, n, dim_); + + auto results = svs::QueryResult{queries.size(), static_cast(k)}; + impl->search(results.view(), queries, {}); + + svs::threads::parallel_for( + impl->get_threadpool_handle(), + svs::threads::StaticPartition(n), + [&](auto is, auto SVS_UNUSED(tid)) { + for (auto i : is) { + for (size_t j = 0; j < k; ++j) { + labels[j + i * k] = results.index(i, j); + distances[j + i * k] = results.distance(i, j); + } + } + } + ); + return Status_Ok; +} + +Status IndexSVSFlatImpl::serialize(std::ostream& out) const noexcept { + if (!impl) { + return {ErrorCode::UNKNOWN_ERROR, "Cannot serialize: SVS index not initialized."}; + } + + impl->save(out); + return Status_Ok; +} + +Status IndexSVSFlatImpl::deserialize(std::istream& in) noexcept { + if (impl) { + return { + ErrorCode::UNKNOWN_ERROR, "Cannot deserialize: SVS index already initialized." + }; + } + + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + using storage_type = typename svs::VectorDataLoader::return_type; + + svs::DistanceDispatcher dispatcher(to_svs_distance(metric_type_)); + dispatcher([&](auto&& distance) { + impl.reset(new svs::Flat( + svs::Flat::assemble( + in, std::move(distance), std::move(threadpool) + ) + )); + }); + + return Status_Ok; +} + +IndexSVSFlatImpl::IndexSVSFlatImpl(size_t dim, MetricType metric) + : metric_type_(metric) + , dim_(dim) {} +IndexSVSFlatImpl::~IndexSVSFlatImpl() = default; + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/IndexSVSImplUtils.h b/bindings/cpp/src/IndexSVSImplUtils.h new file mode 100644 index 00000000..8487519e --- /dev/null +++ b/bindings/cpp/src/IndexSVSImplUtils.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" +#include + +namespace svs { +namespace runtime { +inline svs::DistanceType to_svs_distance(MetricType metric) { + switch (metric) { + case MetricType::L2: + return svs::DistanceType::L2; + case MetricType::INNER_PRODUCT: + return svs::DistanceType::MIP; + } + throw ANNEXCEPTION("unreachable reached"); // Make GCC happy +} +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/IndexSVSVamanaImpl.cpp b/bindings/cpp/src/IndexSVSVamanaImpl.cpp new file mode 100644 index 00000000..ecf6f85c --- /dev/null +++ b/bindings/cpp/src/IndexSVSVamanaImpl.cpp @@ -0,0 +1,507 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSVamanaImpl.h" +#include "IndexSVSImplUtils.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace svs { +namespace runtime { +namespace { + +std::variant +get_storage_variant(IndexSVSVamanaImpl::StorageKind kind) { + switch (kind) { + case IndexSVSVamanaImpl::StorageKind::FP32: + return float{}; + case IndexSVSVamanaImpl::StorageKind::FP16: + return svs::Float16{}; + case IndexSVSVamanaImpl::StorageKind::SQI8: + return std::int8_t{}; + default: + throw ANNEXCEPTION("not supported SVS storage kind"); + } +} + +svs::index::vamana::VamanaBuildParameters +get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { + return svs::index::vamana::VamanaBuildParameters{ + params.alpha, + params.graph_max_degree, + params.construction_window_size, + params.max_candidate_pool_size, + params.prune_to, + params.use_full_search_history + }; +} + +template < + typename T, + typename Alloc = svs::data::Blocked>, + svs::data::ImmutableMemoryDataset Dataset, + svs::threads::ThreadPool Pool> + requires std::is_floating_point_v || std::is_same_v +svs::data::SimpleData +make_storage(const Dataset& data, Pool& pool) { + svs::data::SimpleData result( + data.size(), data.dimensions(), Alloc{} + ); + svs::threads::parallel_for( + pool, + svs::threads::StaticPartition(result.size()), + [&](auto is, auto SVS_UNUSED(tid)) { + for (auto i : is) { + result.set_datum(i, data.get_datum(i)); + } + } + ); + return result; +} + +template < + typename T, + typename Alloc = svs::data::Blocked>, + svs::data::ImmutableMemoryDataset Dataset, + svs::threads::ThreadPool Pool> + requires std::is_integral_v +svs::quantization::scalar::SQDataset +make_storage(const Dataset& data, Pool& pool) { + return svs::quantization::scalar::SQDataset::compress( + data, pool, Alloc{} + ); +} + +template +svs::DynamicVamana* +init_impl_t(IndexSVSVamanaImpl* index, MetricType metric, size_t n, const float* x) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + auto data = make_storage( + svs::data::ConstSimpleDataView(x, n, index->dim_), threadpool + ); + + std::vector labels(data.size()); + std::iota(labels.begin(), labels.end(), 0); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::move(data), + std::move(labels), + std::forward(distance), + std::move(threadpool) + ) + ); + }); +} + +template < + typename T, + typename Alloc = svs::data::Blocked>, + typename Enabler = void> +struct storage_type; + +template +struct storage_type< + T, + Alloc, + std::enable_if_t || std::is_same_v>> { + using type = svs::data::SimpleData; +}; + +template +struct storage_type>> { + using type = svs::quantization::scalar::SQDataset; +}; + +template >> +using storage_type_t = typename storage_type::type; + +template +svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::assemble>( + stream, std::forward(distance), std::move(threadpool) + ) + ); + }); +} + +svs::index::vamana::VamanaSearchParameters make_search_parameters( + const std::unique_ptr& impl, + const IndexSVSVamanaImpl::SearchParams& default_params, + const IndexSVSVamanaImpl::SearchParams* params +) { + if (!impl) { + throw ANNEXCEPTION("Index not initialized"); + } + + auto search_window_size = default_params.search_window_size; + auto search_buffer_capacity = default_params.search_buffer_capacity; + + if (params != nullptr) { + if (params->search_window_size > 0) + search_window_size = params->search_window_size; + if (params->search_buffer_capacity > 0) + search_buffer_capacity = params->search_buffer_capacity; + } + + return impl->get_search_parameters().buffer_config( + {search_window_size, search_buffer_capacity} + ); +} +} // namespace + +IndexSVSVamanaImpl* IndexSVSVamanaImpl::build( + size_t dim, MetricType metric, const BuildParams& params +) noexcept { + try { + auto index = new IndexSVSVamanaImpl( + dim, params.graph_max_degree, metric, params.storage_kind + ); + index->build_params = params; + return index; + } catch (...) { return nullptr; } +} + +void IndexSVSVamanaImpl::destroy(IndexSVSVamanaImpl* impl) noexcept { delete impl; } + +IndexSVSVamanaImpl::IndexSVSVamanaImpl() = default; + +IndexSVSVamanaImpl::IndexSVSVamanaImpl( + size_t d, size_t degree, MetricType metric, StorageKind storage +) + : metric_type_(metric) + , dim_(d) + , build_params{ + storage, + degree, + degree < 4 ? degree : degree - 4, + metric == MetricType::L2 ? 1.2f : 0.95f, + 40, + 200, + true + } {} + +IndexSVSVamanaImpl::~IndexSVSVamanaImpl() = default; + +Status IndexSVSVamanaImpl::add(size_t n, const float* x) noexcept { + if (!impl) { + return init_impl(n, x); + } + + // construct sequential labels + std::vector labels(n); + + std::iota(labels.begin(), labels.end(), impl->size()); + + auto data = svs::data::ConstSimpleDataView(x, n, dim_); + impl->add_points(data, labels); + return Status_Ok; +} + +void IndexSVSVamanaImpl::reset() noexcept { + if (impl) { + impl.reset(); + } + ntotal_soft_deleted = 0; +} + +Status IndexSVSVamanaImpl::search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + const SearchParams* params, + IDFilter* filter +) const noexcept { + if (!impl) { + for (size_t i = 0; i < n; ++i) { + distances[i] = std::numeric_limits::infinity(); + labels[i] = -1; + } + return Status{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + if (k == 0) { + return Status{ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; + } + + auto sp = make_search_parameters(impl, default_search_params, params); + + // Simple search + if (filter == nullptr) { + auto queries = svs::data::ConstSimpleDataView(x, n, dim_); + + // TODO: faiss use int64_t as label whereas SVS uses size_t? + auto results = svs::QueryResultView{ + svs::MatrixView{ + svs::make_dims(n, k), static_cast(static_cast(labels)) + }, + svs::MatrixView{svs::make_dims(n, k), distances} + }; + impl->search(results, queries, sp); + return Status_Ok; + } + + // Selective search with IDSelector + auto old_sp = impl->get_search_parameters(); + impl->set_search_parameters(sp); + + auto search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { + for (auto i : range) { + // For every query + auto query = std::span(x + i * dim_, dim_); + auto curr_distances = std::span(distances + i * k, k); + auto curr_labels = std::span(labels + i * k, k); + + auto iterator = impl->batch_iterator(query); + size_t found = 0; + do { + iterator.next(k); + for (auto& neighbor : iterator.results()) { + if (filter->is_member(neighbor.id())) { + curr_distances[found] = neighbor.distance(); + curr_labels[found] = neighbor.id(); + found++; + if (found == k) { + break; + } + } + } + } while (found < k && !iterator.done()); + // Pad with -1s + for (; found < k; ++found) { + curr_distances[found] = -1; + curr_labels[found] = -1; + } + } + }; + + auto threadpool = + svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + + svs::threads::parallel_for( + threadpool, svs::threads::StaticPartition{n}, search_closure + ); + + impl->set_search_parameters(old_sp); + + return Status_Ok; +} + +Status IndexSVSVamanaImpl::range_search( + size_t n, + const float* x, + float radius, + const ResultsAllocator& results, + const SearchParams* params, + IDFilter* filter +) const noexcept { + if (!impl) { + return Status{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + if (radius <= 0) { + return Status{ErrorCode::INVALID_ARGUMENT, "radius must be greater than 0"}; + } + + auto sp = make_search_parameters(impl, default_search_params, params); + auto old_sp = impl->get_search_parameters(); + impl->set_search_parameters(sp); + + // Using ResultHandler makes no sense due to it's complexity, overhead and + // missed features; e.g. add_result() does not indicate whether result added + // or not - we have to manually manage threshold comparison and id + // selection. + + // Prepare output buffers + std::vector>> all_results(n); + // Reserve space for allocation to avoid multiple reallocations + // Use search_buffer_capacity as a heuristic + const auto result_capacity = sp.buffer_config_.get_total_capacity(); + for (auto& res : all_results) { + res.reserve(result_capacity); + } + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric_type_)); + + std::function compare = distance_dispatcher([](auto&& dist) { + return std::function{svs::distance::comparator(dist)}; + }); + + std::function select = [](size_t) { return true; }; + if (filter != nullptr) { + select = [&](size_t id) { return filter->is_member(id); }; + } + + // Set iterator batch size to search window size + auto batch_size = sp.buffer_config_.get_search_window_size(); + + auto range_search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { + for (auto i : range) { + // For every query + auto query = std::span(x + i * dim_, dim_); + + auto iterator = impl->batch_iterator(query); + bool in_range = true; + + do { + iterator.next(batch_size); + for (auto& neighbor : iterator.results()) { + // SVS comparator functor returns true if the first distance + // is 'closer' than the second one + in_range = compare(neighbor.distance(), radius); + if (in_range) { + // Selective search with IDSelector + if (select(neighbor.id())) { + all_results[i].push_back(neighbor); + } + } else { + // Since iterator.results() are ordered by distance, we + // can stop processing + break; + } + } + } while (in_range && !iterator.done()); + } + }; + + auto threadpool = + svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + + svs::threads::parallel_for( + threadpool, svs::threads::StaticPartition{n}, range_search_closure + ); + + // Allocate output + std::vector result_counts(n); + std::transform( + all_results.begin(), all_results.end(), result_counts.begin(), [](const auto& res) { + return res.size(); + } + ); + auto results_storage = results(result_counts); + + // Fill in results + for (size_t q = 0, ofs = 0; q < n; ++q) { + for (const auto& [id, distance] : all_results[q]) { + results_storage.labels[ofs] = id; + results_storage.distances[ofs] = distance; + ofs++; + } + } + + impl->set_search_parameters(old_sp); + return Status_Ok; +} + +size_t IndexSVSVamanaImpl::remove_ids(const IDFilter& selector) noexcept { + if (!impl) { + return 0; + } + + auto ids = impl->all_ids(); + std::vector ids_to_delete; + std::copy_if(ids.begin(), ids.end(), std::back_inserter(ids_to_delete), [&](size_t id) { + return selector(id); + }); + + // SVS deletion is a soft deletion, meaning the corresponding vectors are + // marked as deleted but still present in both the dataset and the graph, + // and will be navigated through during search. + // Actual cleanup happens once a large enough number of soft deleted vectors + // are collected. + impl->delete_points(ids_to_delete); + // ntotal -= ids.size(); + ntotal_soft_deleted += ids_to_delete.size(); + + auto ntotal = impl->size(); + const float cleanup_threshold = .5f; + if (ntotal == 0 || (float)ntotal_soft_deleted / ntotal > cleanup_threshold) { + impl->consolidate(); + impl->compact(); + ntotal_soft_deleted = 0; + } + return ids_to_delete.size(); +} + +Status IndexSVSVamanaImpl::init_impl(size_t n, const float* x) noexcept { + if (impl) { + return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; + } + + impl.reset( + std::visit( + [&](auto element) { + using ElementType = std::decay_t; + return init_impl_t(this, metric_type_, n, x); + }, + get_storage_variant(storage_kind) + ) + ); + return Status_Ok; +} + +Status IndexSVSVamanaImpl::serialize_impl(std::ostream& out) const noexcept { + if (!impl) { + return Status{ + ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized." + }; + } + + impl->save(out); + return Status_Ok; +} + +Status IndexSVSVamanaImpl::deserialize_impl(std::istream& in) noexcept { + if (impl) { + return Status{ + ErrorCode::INVALID_ARGUMENT, + "Cannot deserialize: SVS index already initialized." + }; + } + + impl.reset( + std::visit( + [&](auto element) { + using ElementType = std::decay_t; + return deserialize_impl_t(in, metric_type_); + }, + get_storage_variant(storage_kind) + ) + ); + return Status_Ok; +} + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp new file mode 100644 index 00000000..fea9b571 --- /dev/null +++ b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp @@ -0,0 +1,208 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSVamanaLVQImpl.h" +#include "IndexSVSImplUtils.h" + +#include + +#include +#include +#include +#include +//#include +#include + +namespace svs::runtime { + +namespace { +using blocked_alloc_type = svs::data::Blocked>; +using blocked_alloc_type_sq = svs::data::Blocked>; + +using strategy_type_4 = svs::quantization::lvq::Turbo<16, 8>; + +using storage_type_4x0 = svs::quantization::lvq:: + LVQDataset<4, 0, svs::Dynamic, strategy_type_4, blocked_alloc_type>; +using storage_type_4x4 = svs::quantization::lvq:: + LVQDataset<4, 4, svs::Dynamic, strategy_type_4, blocked_alloc_type>; +using storage_type_4x8 = svs::quantization::lvq:: + LVQDataset<4, 8, svs::Dynamic, strategy_type_4, blocked_alloc_type>; +using storage_type_sq = + svs::quantization::scalar::SQDataset; + +svs::index::vamana::VamanaBuildParameters +get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { + return svs::index::vamana::VamanaBuildParameters{ + params.alpha, + params.graph_max_degree, + params.construction_window_size, + params.max_candidate_pool_size, + params.prune_to, + params.use_full_search_history + }; +} + +template +svs::DynamicVamana* init_impl_t( + IndexSVSVamanaImpl* index, + StorageType&& storage, + MetricType metric, + ThreadPoolProto&& threadpool +) { + std::vector labels(storage.size()); + std::iota(labels.begin(), labels.end(), 0); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::forward(storage), + std::move(labels), + std::forward(distance), + std::forward(threadpool) + ) + ); + }); +} + +template +svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::assemble( + stream, std::forward(distance), std::move(threadpool) + ) + ); + }); +} + +} // namespace + +IndexSVSVamanaLVQImpl* IndexSVSVamanaLVQImpl::build( + size_t dim, MetricType metric, const BuildParams& params, LVQLevel lvq +) noexcept { + try { + auto index = new IndexSVSVamanaLVQImpl(dim, params.graph_max_degree, metric, lvq); + index->build_params = params; + return index; + } catch (...) { return nullptr; } +} + +IndexSVSVamanaLVQImpl::IndexSVSVamanaLVQImpl() = default; + +IndexSVSVamanaLVQImpl::IndexSVSVamanaLVQImpl( + size_t d, size_t degree, MetricType metric, LVQLevel lvq_level +) + : IndexSVSVamanaImpl(d, degree, metric) + , lvq_level{lvq_level} {} + +IndexSVSVamanaLVQImpl::~IndexSVSVamanaLVQImpl() = default; + +Status IndexSVSVamanaLVQImpl::init_impl(size_t n, const float* x) noexcept { + if (impl) { + return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; + } + + // TODO: support ConstSimpleDataView in SVS shared/static lib + const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); + + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + std::variant< + std::monostate, + storage_type_4x0, + storage_type_4x4, + storage_type_4x8, + storage_type_sq> + compressed_data; + + if (svs::detail::intel_enabled()) { + switch (lvq_level) { + case LVQLevel::LVQ4x0: + compressed_data = + storage_type_4x0::compress(data, threadpool, 0, blocked_alloc_type{}); + break; + case LVQLevel::LVQ4x4: + compressed_data = + storage_type_4x4::compress(data, threadpool, 0, blocked_alloc_type{}); + break; + case LVQLevel::LVQ4x8: + compressed_data = + storage_type_4x8::compress(data, threadpool, 0, blocked_alloc_type{}); + break; + default: + return Status{ErrorCode::NOT_IMPLEMENTED, "not supported SVS LVQ level"}; + } + } else { + compressed_data = + storage_type_sq::compress(data, threadpool, blocked_alloc_type_sq{}); + } + + return std::visit( + [&](auto&& storage) { + if constexpr (std::is_same_v, std::monostate>) { + return Status{ + ErrorCode::NOT_INITIALIZED, "SVS LVQ data is not initialized." + }; + } else { + impl.reset(init_impl_t( + this, + std::forward(storage), + metric_type_, + std::move(threadpool) + )); + return Status_Ok; + } + }, + compressed_data + ); +} + +Status IndexSVSVamanaLVQImpl::deserialize_impl(std::istream& in) noexcept { + if (impl) { + return Status{ + ErrorCode::INVALID_ARGUMENT, + "Cannot deserialize: SVS index already initialized." + }; + } + + if (svs::detail::intel_enabled()) { + switch (lvq_level) { + case LVQLevel::LVQ4x0: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + case LVQLevel::LVQ4x4: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + case LVQLevel::LVQ4x8: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + default: + return Status(ErrorCode::NOT_IMPLEMENTED, "not supported SVS LVQ level"); + } + } else { + impl.reset(deserialize_impl_t(in, metric_type_)); + } + return Status_Ok; +} + +} // namespace svs::runtime diff --git a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp new file mode 100644 index 00000000..5d459614 --- /dev/null +++ b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp @@ -0,0 +1,280 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSVamanaLeanVecImpl.h" +#include "IndexSVSImplUtils.h" + +#include + +#include +#include +#include +#include +//#include +#include +#include + +namespace svs::runtime { + +namespace { +using blocked_alloc_type = svs::data::Blocked>; +using blocked_alloc_type_sq = svs::data::Blocked>; + +using storage_type_4x4 = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ<4>, + svs::leanvec::UsingLVQ<4>, + svs::Dynamic, + svs::Dynamic, + blocked_alloc_type>; +using storage_type_4x8 = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ<4>, + svs::leanvec::UsingLVQ<8>, + svs::Dynamic, + svs::Dynamic, + blocked_alloc_type>; +using storage_type_8x8 = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ<8>, + svs::leanvec::UsingLVQ<8>, + svs::Dynamic, + svs::Dynamic, + blocked_alloc_type>; +using storage_type_sq = + svs::quantization::scalar::SQDataset; + +svs::index::vamana::VamanaBuildParameters +get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { + return svs::index::vamana::VamanaBuildParameters{ + params.alpha, + params.graph_max_degree, + params.construction_window_size, + params.max_candidate_pool_size, + params.prune_to, + params.use_full_search_history + }; +} + +template +svs::DynamicVamana* init_impl_t( + IndexSVSVamanaImpl* index, + StorageType&& storage, + MetricType metric, + ThreadPoolProto&& threadpool +) { + std::vector labels(storage.size()); + std::iota(labels.begin(), labels.end(), 0); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::forward(storage), + std::move(labels), + std::forward(distance), + std::forward(threadpool) + ) + ); + }); +} + +template +svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::assemble( + stream, std::forward(distance), std::move(threadpool) + ) + ); + }); +} + +} // namespace + +IndexSVSVamanaLeanVecImpl* IndexSVSVamanaLeanVecImpl::build( + size_t dim, + MetricType metric, + const BuildParams& params, + size_t leanvec_dims, + LeanVecLevel leanvec_level +) noexcept { + try { + auto index = new IndexSVSVamanaLeanVecImpl( + dim, params.graph_max_degree, metric, leanvec_dims, leanvec_level + ); + index->build_params = params; + return index; + } catch (...) { return nullptr; } +} + +IndexSVSVamanaLeanVecImpl::IndexSVSVamanaLeanVecImpl() = default; + +IndexSVSVamanaLeanVecImpl::IndexSVSVamanaLeanVecImpl( + size_t d, + size_t degree, + MetricType metric, + size_t leanvec_dims, + LeanVecLevel leanvec_level +) + : IndexSVSVamanaImpl(d, degree, metric) + , leanvec_d{leanvec_dims == 0 ? d / 2 : leanvec_dims} + , leanvec_level{leanvec_level} {} + +IndexSVSVamanaLeanVecImpl::~IndexSVSVamanaLeanVecImpl() = default; + +void IndexSVSVamanaLeanVecImpl::reset() noexcept { + IndexSVSVamanaImpl::reset(); + leanvec_matrix.reset(); +} + +Status IndexSVSVamanaLeanVecImpl::train(size_t n, const float* x) noexcept { + const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + auto means = svs::utils::compute_medioid(data, threadpool); + auto matrix = svs::leanvec::compute_leanvec_matrix( + data, means, threadpool, svs::lib::MaybeStatic{leanvec_d} + ); + leanvec_matrix = + std::make_unique>(matrix, matrix); + trained = true; + return Status_Ok; +} + +Status IndexSVSVamanaLeanVecImpl::init_impl(size_t n, const float* x) noexcept { + if (impl) { + return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; + } + + if (!is_trained()) { + return { + ErrorCode::NOT_INITIALIZED, + "Cannot initialize SVS LeanVec index without training first." + }; + } + + // TODO: support ConstSimpleDataView in SVS shared/static lib + const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); + std::vector labels(n); + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + std::variant< + std::monostate, + storage_type_4x4, + storage_type_4x8, + storage_type_8x8, + storage_type_sq> + compressed_data; + + if (svs::detail::intel_enabled()) { + switch (leanvec_level) { + case LeanVecLevel::LeanVec4x4: + compressed_data = storage_type_4x4::reduce( + data, + *leanvec_matrix, + threadpool, + 0, + svs::lib::MaybeStatic(leanvec_d), + blocked_alloc_type{} + ); + break; + case LeanVecLevel::LeanVec4x8: + compressed_data = storage_type_4x8::reduce( + data, + *leanvec_matrix, + threadpool, + 0, + svs::lib::MaybeStatic(leanvec_d), + blocked_alloc_type{} + ); + break; + case LeanVecLevel::LeanVec8x8: + compressed_data = storage_type_8x8::reduce( + data, + *leanvec_matrix, + threadpool, + 0, + svs::lib::MaybeStatic(leanvec_d), + blocked_alloc_type{} + ); + break; + default: + return Status{ + ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level" + }; + } + } else { + compressed_data = + storage_type_sq::compress(data, threadpool, blocked_alloc_type_sq{}); + } + + return std::visit( + [&](auto&& storage) { + if constexpr (std::is_same_v, std::monostate>) { + return Status{ + ErrorCode::NOT_INITIALIZED, "SVS LeanVec data is not initialized." + }; + } else { + impl.reset(init_impl_t( + this, + std::forward(storage), + metric_type_, + std::move(threadpool) + )); + return Status_Ok; + } + }, + compressed_data + ); +} + +Status IndexSVSVamanaLeanVecImpl::deserialize_impl(std::istream& in) noexcept { + if (impl) { + return Status{ + ErrorCode::INVALID_ARGUMENT, + "Cannot deserialize: SVS index already initialized." + }; + } + + if (svs::detail::intel_enabled()) { + switch (leanvec_level) { + case LeanVecLevel::LeanVec4x4: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + case LeanVecLevel::LeanVec4x8: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + case LeanVecLevel::LeanVec8x8: + impl.reset(deserialize_impl_t(in, metric_type_)); + break; + default: + return Status( + ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level" + ); + } + } else { + impl.reset(deserialize_impl_t(in, metric_type_)); + } + + trained = true; + return Status_Ok; +} + +} // namespace svs::runtime From 71a3f835ae4fe842ef18cec3c487a1561463e168 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Thu, 30 Oct 2025 23:57:08 -0700 Subject: [PATCH 02/46] Initial support for private source --- bindings/cpp/CMakeLists.txt | 8 +++++++- bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp | 5 ++++- bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp | 5 ++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 813d9729..08f2b3c0 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -81,7 +81,13 @@ 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 OR SVS_RUNTIME_ENABLE_LEANVEC)) - if(TARGET svs::svs_static_library) + if(SVS_BUILD_RUNTIME_BINDINGS) + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + ) + # Using this to decide whether to include lvq_impl.h but can transition to SVS_LVQ_HEADER eventually + target_compile_definitions(${TARGET_NAME} PUBLIC SVS_BUILD_RUNTIME_BINDINGS) + elseif(TARGET svs::svs_static_library) target_link_libraries(${TARGET_NAME} PRIVATE svs::svs svs::svs_static_library diff --git a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp index fea9b571..4cd2b259 100644 --- a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp @@ -23,9 +23,12 @@ #include #include #include -//#include #include +#ifdef SVS_BUILD_RUNTIME_BINDINGS +#include +#endif + namespace svs::runtime { namespace { diff --git a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp index 5d459614..988a77dd 100644 --- a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp @@ -23,10 +23,13 @@ #include #include #include -//#include #include #include +#ifdef SVS_BUILD_RUNTIME_BINDINGS +#include +#endif + namespace svs::runtime { namespace { From 3f95bab37f424d1a733d6143bb5a9a6a35a87c6b Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 00:26:12 -0700 Subject: [PATCH 03/46] Point SVS_URL to LTO-enabled tarball --- bindings/cpp/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 08f2b3c0..9e0f563a 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -95,7 +95,8 @@ if ((SVS_RUNTIME_ENABLE_LVQ OR SVS_RUNTIME_ENABLE_LEANVEC)) svs_x86_options_base ) else() - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251017-faiss.tar.gz" + # Links to LTO-enabled static library, requires GCC/G++ 11.2 + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251030-737-bindings.tar.gz" CACHE STRING "URL to download SVS shared library") include(FetchContent) FetchContent_Declare( From 111448e5a18e0bd67b01a67455684c4ecba5aa09 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 31 Oct 2025 15:08:50 +0100 Subject: [PATCH 04/46] Improve svs_runtime CMakeLists.txt to use pre-defined LVQ heades and allow non-LTO linking --- bindings/cpp/CMakeLists.txt | 95 +++++++++++-------- bindings/cpp/runtimeConfig.cmake.in | 2 +- bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp | 5 +- .../cpp/src/IndexSVSVamanaLeanVecImpl.cpp | 5 +- 4 files changed, 57 insertions(+), 50 deletions(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 9e0f563a..b8f6d9b1 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -1,15 +1,16 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright 2025 Intel Corporation # -# This software and the related documents are Intel copyrighted materials, -# and your use of them is governed by the express license under which they -# were provided to you ("License"). Unless the License provides otherwise, -# you may not use, modify, copy, publish, distribute, disclose or transmit -# this software or the related documents without Intel's prior written -# permission. +# 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 # -# This software and the related documents are provided as is, with no -# express or implied warranties, other than those that are expressly stated -# in the License. +# 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. cmake_minimum_required(VERSION 3.21) project(svs_runtime VERSION 0.0.10 LANGUAGES CXX) @@ -27,52 +28,43 @@ set(SVS_RUNTIME_SOURCES src/IndexSVSVamanaImpl.cpp ) -option(SVS_RUNTIME_ENABLE_LVQ "Enable compilation of SVS runtime with LVQ support" ON) -if (SVS_RUNTIME_ENABLE_LVQ) +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") list(APPEND SVS_RUNTIME_HEADERS include/IndexSVSVamanaLVQImpl.h - ) - list(APPEND SVS_RUNTIME_SOURCES - src/IndexSVSVamanaLVQImpl.cpp - ) -else() - message(STATUS "SVS runtime will be built without LVQ support") -endif() - -option(SVS_RUNTIME_ENABLE_LEANVEC "Enable compilation of SVS runtime with LeanVec support" ON) -if (SVS_RUNTIME_ENABLE_LEANVEC) - message(STATUS "SVS runtime will be built with LeanVec support") - list(APPEND SVS_RUNTIME_HEADERS include/IndexSVSVamanaLeanVecImpl.h ) list(APPEND SVS_RUNTIME_SOURCES + src/IndexSVSVamanaLVQImpl.cpp src/IndexSVSVamanaLeanVecImpl.cpp ) else() - message(STATUS "SVS runtime will be built without LeanVec support") + message(STATUS "SVS runtime will be built without LVQ or LeanVec support") endif() -find_package(OpenMP REQUIRED) - add_library(${TARGET_NAME} SHARED ${SVS_RUNTIME_HEADERS} ${SVS_RUNTIME_SOURCES} ) -target_link_libraries(${TARGET_NAME} PUBLIC - OpenMP::OpenMP_CXX -) - target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) +find_package(OpenMP REQUIRED) +target_link_libraries(${TARGET_NAME} PUBLIC OpenMP::OpenMP_CXX) + target_compile_options(${TARGET_NAME} PRIVATE -DSVS_ENABLE_OMP=1 -fvisibility=hidden ) +if(UNIX AND NOT APPLE) + # Don't export 3rd-party symbols from the lib + target_link_options(${TARGET_NAME} PRIVATE "SHELL:-Wl,--exclude-libs,ALL") +endif() + target_compile_features(${TARGET_NAME} INTERFACE cxx_std_20) set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_RUNTIME_HEADERS}") set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 20) @@ -80,24 +72,41 @@ set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON) 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 OR SVS_RUNTIME_ENABLE_LEANVEC)) - if(SVS_BUILD_RUNTIME_BINDINGS) - target_link_libraries(${TARGET_NAME} PRIVATE - svs::svs - ) - # Using this to decide whether to include lvq_impl.h but can transition to SVS_LVQ_HEADER eventually - target_compile_definitions(${TARGET_NAME} PUBLIC SVS_BUILD_RUNTIME_BINDINGS) - elseif(TARGET svs::svs_static_library) +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 ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) + if(TARGET svs::svs_static_library) + # Links to SVS static library built as part of the main SVS build target_link_libraries(${TARGET_NAME} PRIVATE svs::svs svs::svs_static_library svs_compile_options svs_x86_options_base ) + elseif(TARGET svs::svs) + message(FATAL_ERROR + "Pre-built LVQ/LeanVec SVS library cannot be used in SVS main build. " + "Please build SVS Runtime using bindins/cpp directory as CMake source root." + ) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251030-737-bindings.tar.gz" - CACHE STRING "URL to download SVS shared library") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "11.2") + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251030-737-bindings.tar.gz" + CACHE STRING "URL to download SVS shared library") + else() + message(WARNING + "Pre-built LVQ/LeanVec SVS library requires GCC/G++ v.11.2 to apply LTO optimizations." + "Current compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" + ) + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251017-faiss.tar.gz") + endif() include(FetchContent) FetchContent_Declare( svs @@ -112,6 +121,10 @@ if ((SVS_RUNTIME_ENABLE_LVQ OR SVS_RUNTIME_ENABLE_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}\"" + ) else() # Include the SVS library directly if needed. if (NOT TARGET svs::svs) diff --git a/bindings/cpp/runtimeConfig.cmake.in b/bindings/cpp/runtimeConfig.cmake.in index 4c4570ba..5c869b85 100644 --- a/bindings/cpp/runtimeConfig.cmake.in +++ b/bindings/cpp/runtimeConfig.cmake.in @@ -1,4 +1,4 @@ -# Copyright 2023 Intel Corporation +# Copyright 2025 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp index 4cd2b259..394c1180 100644 --- a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp @@ -20,14 +20,11 @@ #include #include -#include #include #include #include -#ifdef SVS_BUILD_RUNTIME_BINDINGS -#include -#endif +#include SVS_LVQ_HEADER namespace svs::runtime { diff --git a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp index 988a77dd..94cb5ce5 100644 --- a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp @@ -21,14 +21,11 @@ #include #include -#include #include #include #include -#ifdef SVS_BUILD_RUNTIME_BINDINGS -#include -#endif +#include SVS_LEANVEC_HEADER namespace svs::runtime { From 0ca8bbf10752c83f260a5958adeae7dea6e96700 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 31 Oct 2025 15:51:05 +0100 Subject: [PATCH 05/46] Fixup C++ runtime CMakeLists for internal build --- bindings/cpp/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index b8f6d9b1..2d21c9f0 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -82,11 +82,11 @@ else() endif() if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) - if(TARGET svs::svs_static_library) + if(TARGET svs_static_library) # Links to SVS static library built as part of the main SVS build target_link_libraries(${TARGET_NAME} PRIVATE - svs::svs - svs::svs_static_library + svs_devel + svs_static_library svs_compile_options svs_x86_options_base ) From 923e00256e9c55ad3b16bf17070c6b36da2e560b Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 31 Oct 2025 17:35:33 +0100 Subject: [PATCH 06/46] Apply formatting --- bindings/cpp/include/IndexSVSFlatImpl.h | 5 +- bindings/cpp/src/IndexSVSFlatImpl.cpp | 34 ++++----- bindings/cpp/src/IndexSVSVamanaImpl.cpp | 73 ++++++++----------- bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp | 33 ++++----- .../cpp/src/IndexSVSVamanaLeanVecImpl.cpp | 39 ++++------ tools/clang-format.sh | 2 +- 6 files changed, 76 insertions(+), 110 deletions(-) diff --git a/bindings/cpp/include/IndexSVSFlatImpl.h b/bindings/cpp/include/IndexSVSFlatImpl.h index 2539de33..2856e9e0 100644 --- a/bindings/cpp/include/IndexSVSFlatImpl.h +++ b/bindings/cpp/include/IndexSVSFlatImpl.h @@ -31,9 +31,8 @@ class SVS_RUNTIME_API IndexSVSFlatImpl { Status add(size_t n, const float* x) noexcept; void reset() noexcept; - Status search( - size_t n, const float* x, size_t k, float* distances, size_t* labels - ) const noexcept; + Status search(size_t n, const float* x, size_t k, float* distances, size_t* labels) + const noexcept; Status serialize(std::ostream& out) const noexcept; diff --git a/bindings/cpp/src/IndexSVSFlatImpl.cpp b/bindings/cpp/src/IndexSVSFlatImpl.cpp index 393f73a6..a099917c 100644 --- a/bindings/cpp/src/IndexSVSFlatImpl.cpp +++ b/bindings/cpp/src/IndexSVSFlatImpl.cpp @@ -39,7 +39,9 @@ Status IndexSVSFlatImpl::init_impl(size_t n, const float* x) noexcept { svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); svs::threads::parallel_for( - threadpool, svs::threads::StaticPartition(n), [&](auto is, auto SVS_UNUSED(tid)) { + threadpool, + svs::threads::StaticPartition(n), + [&](auto is, auto SVS_UNUSED(tid)) { for (auto i : is) { data.set_datum(i, std::span(x + i * dim_, dim_)); } @@ -48,18 +50,14 @@ Status IndexSVSFlatImpl::init_impl(size_t n, const float* x) noexcept { switch (metric_type_) { case MetricType::INNER_PRODUCT: - impl.reset(new svs::Flat( - svs::Flat::assemble( - std::move(data), svs::DistanceIP(), std::move(threadpool) - ) - )); + impl.reset(new svs::Flat(svs::Flat::assemble( + std::move(data), svs::DistanceIP(), std::move(threadpool) + ))); break; case MetricType::L2: - impl.reset(new svs::Flat( - svs::Flat::assemble( - std::move(data), svs::DistanceL2(), std::move(threadpool) - ) - )); + impl.reset(new svs::Flat(svs::Flat::assemble( + std::move(data), svs::DistanceL2(), std::move(threadpool) + ))); break; default: impl = nullptr; @@ -75,8 +73,7 @@ Status IndexSVSFlatImpl::add(size_t n, const float* x) noexcept { return { ErrorCode::NOT_IMPLEMENTED, - "IndexSVSFlat does not support adding points after initialization" - }; + "IndexSVSFlat does not support adding points after initialization"}; } void IndexSVSFlatImpl::reset() noexcept { impl.reset(); } @@ -123,8 +120,7 @@ Status IndexSVSFlatImpl::serialize(std::ostream& out) const noexcept { Status IndexSVSFlatImpl::deserialize(std::istream& in) noexcept { if (impl) { return { - ErrorCode::UNKNOWN_ERROR, "Cannot deserialize: SVS index already initialized." - }; + ErrorCode::UNKNOWN_ERROR, "Cannot deserialize: SVS index already initialized."}; } auto threadpool = @@ -133,11 +129,9 @@ Status IndexSVSFlatImpl::deserialize(std::istream& in) noexcept { svs::DistanceDispatcher dispatcher(to_svs_distance(metric_type_)); dispatcher([&](auto&& distance) { - impl.reset(new svs::Flat( - svs::Flat::assemble( - in, std::move(distance), std::move(threadpool) - ) - )); + impl.reset(new svs::Flat(svs::Flat::assemble( + in, std::move(distance), std::move(threadpool) + ))); }); return Status_Ok; diff --git a/bindings/cpp/src/IndexSVSVamanaImpl.cpp b/bindings/cpp/src/IndexSVSVamanaImpl.cpp index ecf6f85c..55df68d0 100644 --- a/bindings/cpp/src/IndexSVSVamanaImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaImpl.cpp @@ -52,8 +52,7 @@ get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { params.construction_window_size, params.max_candidate_pool_size, params.prune_to, - params.use_full_search_history - }; + params.use_full_search_history}; } template < @@ -107,15 +106,13 @@ init_impl_t(IndexSVSVamanaImpl* index, MetricType metric, size_t n, const float* svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::move(data), - std::move(labels), - std::forward(distance), - std::move(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::move(data), + std::move(labels), + std::forward(distance), + std::move(threadpool) + )); }); } @@ -209,8 +206,7 @@ IndexSVSVamanaImpl::IndexSVSVamanaImpl( metric == MetricType::L2 ? 1.2f : 0.95f, 40, 200, - true - } {} + true} {} IndexSVSVamanaImpl::~IndexSVSVamanaImpl() = default; @@ -266,10 +262,8 @@ Status IndexSVSVamanaImpl::search( // TODO: faiss use int64_t as label whereas SVS uses size_t? auto results = svs::QueryResultView{ svs::MatrixView{ - svs::make_dims(n, k), static_cast(static_cast(labels)) - }, - svs::MatrixView{svs::make_dims(n, k), distances} - }; + svs::make_dims(n, k), static_cast(static_cast(labels))}, + svs::MatrixView{svs::make_dims(n, k), distances}}; impl->search(results, queries, sp); return Status_Ok; } @@ -406,9 +400,10 @@ Status IndexSVSVamanaImpl::range_search( // Allocate output std::vector result_counts(n); std::transform( - all_results.begin(), all_results.end(), result_counts.begin(), [](const auto& res) { - return res.size(); - } + all_results.begin(), + all_results.end(), + result_counts.begin(), + [](const auto& res) { return res.size(); } ); auto results_storage = results(result_counts); @@ -460,23 +455,20 @@ Status IndexSVSVamanaImpl::init_impl(size_t n, const float* x) noexcept { return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; } - impl.reset( - std::visit( - [&](auto element) { - using ElementType = std::decay_t; - return init_impl_t(this, metric_type_, n, x); - }, - get_storage_variant(storage_kind) - ) - ); + impl.reset(std::visit( + [&](auto element) { + using ElementType = std::decay_t; + return init_impl_t(this, metric_type_, n, x); + }, + get_storage_variant(storage_kind) + )); return Status_Ok; } Status IndexSVSVamanaImpl::serialize_impl(std::ostream& out) const noexcept { if (!impl) { return Status{ - ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized." - }; + ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized."}; } impl->save(out); @@ -487,19 +479,16 @@ Status IndexSVSVamanaImpl::deserialize_impl(std::istream& in) noexcept { if (impl) { return Status{ ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized." - }; + "Cannot deserialize: SVS index already initialized."}; } - impl.reset( - std::visit( - [&](auto element) { - using ElementType = std::decay_t; - return deserialize_impl_t(in, metric_type_); - }, - get_storage_variant(storage_kind) - ) - ); + impl.reset(std::visit( + [&](auto element) { + using ElementType = std::decay_t; + return deserialize_impl_t(in, metric_type_); + }, + get_storage_variant(storage_kind) + )); return Status_Ok; } diff --git a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp index 394c1180..85b115ee 100644 --- a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp @@ -51,8 +51,7 @@ get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { params.construction_window_size, params.max_candidate_pool_size, params.prune_to, - params.use_full_search_history - }; + params.use_full_search_history}; } template @@ -67,15 +66,13 @@ svs::DynamicVamana* init_impl_t( svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::forward(storage), - std::move(labels), - std::forward(distance), - std::forward(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::forward(storage), + std::move(labels), + std::forward(distance), + std::forward(threadpool) + )); }); } @@ -86,11 +83,9 @@ svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::assemble( - stream, std::forward(distance), std::move(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::assemble( + stream, std::forward(distance), std::move(threadpool) + )); }); } @@ -161,8 +156,7 @@ Status IndexSVSVamanaLVQImpl::init_impl(size_t n, const float* x) noexcept { [&](auto&& storage) { if constexpr (std::is_same_v, std::monostate>) { return Status{ - ErrorCode::NOT_INITIALIZED, "SVS LVQ data is not initialized." - }; + ErrorCode::NOT_INITIALIZED, "SVS LVQ data is not initialized."}; } else { impl.reset(init_impl_t( this, @@ -181,8 +175,7 @@ Status IndexSVSVamanaLVQImpl::deserialize_impl(std::istream& in) noexcept { if (impl) { return Status{ ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized." - }; + "Cannot deserialize: SVS index already initialized."}; } if (svs::detail::intel_enabled()) { diff --git a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp index 94cb5ce5..5148cca3 100644 --- a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp +++ b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp @@ -62,8 +62,7 @@ get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { params.construction_window_size, params.max_candidate_pool_size, params.prune_to, - params.use_full_search_history - }; + params.use_full_search_history}; } template @@ -78,15 +77,13 @@ svs::DynamicVamana* init_impl_t( svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::forward(storage), - std::move(labels), - std::forward(distance), - std::forward(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::build( + get_build_parameters(index->build_params), + std::forward(storage), + std::move(labels), + std::forward(distance), + std::forward(threadpool) + )); }); } @@ -97,11 +94,9 @@ svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::assemble( - stream, std::forward(distance), std::move(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::assemble( + stream, std::forward(distance), std::move(threadpool) + )); }); } @@ -165,8 +160,7 @@ Status IndexSVSVamanaLeanVecImpl::init_impl(size_t n, const float* x) noexcept { if (!is_trained()) { return { ErrorCode::NOT_INITIALIZED, - "Cannot initialize SVS LeanVec index without training first." - }; + "Cannot initialize SVS LeanVec index without training first."}; } // TODO: support ConstSimpleDataView in SVS shared/static lib @@ -217,8 +211,7 @@ Status IndexSVSVamanaLeanVecImpl::init_impl(size_t n, const float* x) noexcept { break; default: return Status{ - ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level" - }; + ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level"}; } } else { compressed_data = @@ -229,8 +222,7 @@ Status IndexSVSVamanaLeanVecImpl::init_impl(size_t n, const float* x) noexcept { [&](auto&& storage) { if constexpr (std::is_same_v, std::monostate>) { return Status{ - ErrorCode::NOT_INITIALIZED, "SVS LeanVec data is not initialized." - }; + ErrorCode::NOT_INITIALIZED, "SVS LeanVec data is not initialized."}; } else { impl.reset(init_impl_t( this, @@ -249,8 +241,7 @@ Status IndexSVSVamanaLeanVecImpl::deserialize_impl(std::istream& in) noexcept { if (impl) { return Status{ ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized." - }; + "Cannot deserialize: SVS index already initialized."}; } if (svs::detail::intel_enabled()) { diff --git a/tools/clang-format.sh b/tools/clang-format.sh index e92dd4a7..7646ae5a 100755 --- a/tools/clang-format.sh +++ b/tools/clang-format.sh @@ -16,7 +16,7 @@ # Allow users to supply a custom path to `clang-format` CLANGFORMAT="${1:-clang-format}" -DIRECTORIES=( "bindings/python/src" "bindings/python/include" "include" "benchmark" "tests" "utils" "examples/cpp" ) +DIRECTORIES=( "bindings/python/src" "bindings/python/include" "bindings/cpp" "include" "benchmark" "tests" "utils" "examples/cpp" ) for i in "${DIRECTORIES[@]}" do From 0c490d11028938f6483fa116514d866660f4c7cc Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 11:45:34 -0700 Subject: [PATCH 07/46] Re-add private source build condition --- bindings/cpp/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 2d21c9f0..489b0b57 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -82,7 +82,12 @@ else() endif() if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) - if(TARGET svs_static_library) + if(RUNTIME_BINDINGS_PRIVATE_SOURCE_BUILD) + message(STATUS "Building directly from private sources") + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + ) + elseif(TARGET svs_static_library) # Links to SVS static library built as part of the main SVS build target_link_libraries(${TARGET_NAME} PRIVATE svs_devel From 0067a3af49563688001e05a1b1cefeccb5af5d23 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 12:52:50 -0700 Subject: [PATCH 08/46] Add pipeline to build cpp bindings --- .../workflows/build-cpp-runtime-bindings.yml | 65 +++++++++++++++++++ bindings/cpp/CMakeLists.txt | 2 +- docker/x86_64/build-svs-shared-static.sh | 36 ++++++++++ docker/x86_64/manylinux228/Dockerfile | 43 ++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-cpp-runtime-bindings.yml create mode 100644 docker/x86_64/build-svs-shared-static.sh create mode 100644 docker/x86_64/manylinux228/Dockerfile diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml new file mode 100644 index 00000000..6e5b5199 --- /dev/null +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -0,0 +1,65 @@ +# Copyright 2025 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. + +name: Build C++ Runtime Bindings +run-name: ${{ github.event.inputs.run_name || github.event.pull_request.title }} + +on: + workflow_dispatch: + inputs: + run_name: + description: "Custom workflow name" + required: false + submodule_url: + description: "Submodule url (e.g. https://github.com/intel/ScalableVectorSearch.git)" + required: false + submodule_sha: + description: "Submodule sha/branch" + required: false + pull_request: + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + build-cpp-runtime-bindings: + runs-on: [self-hosted, Linux, ubuntu-22.04] + strategy: + matrix: + platform: + - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} + + - name: Build Docker image + run: | + docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . + + - name: Build libraries in Docker container + run: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + -e PLATFORM_NAME=${{ matrix.platform.suffix }} \ + -e CC=${{ matrix.platform.cc }} \ + -e CXX=${{ matrix.platform.cxx }} \ + svs-${{ matrix.platform.name }}:latest \ + /bin/bash -c "chmod +x docker/x86_64/build-svs-shared-static.sh && ./docker/x86_64//build-svs-shared-static.sh" + + - name: Upload cpp runtime bindings artifacts + uses: actions/upload-artifact@v4 + with: + name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} + path: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz + retention-days: 7 # Reduce retention due to size \ No newline at end of file diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 489b0b57..fc1d0045 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -103,7 +103,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "11.2") - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251030-737-bindings.tar.gz" + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251031-740.tar.gz" CACHE STRING "URL to download SVS shared library") else() message(WARNING diff --git a/docker/x86_64/build-svs-shared-static.sh b/docker/x86_64/build-svs-shared-static.sh new file mode 100644 index 00000000..ff2d8af1 --- /dev/null +++ b/docker/x86_64/build-svs-shared-static.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Copyright 2024 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. + +set -e # Exit on error + +# Source environment setup (for compiler and MKL) +source /etc/bashrc || true + +# Set CMake arguments based on environment variables + +# 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 + +# Build and install runtime bindings library +cd /workspace/bindings/cpp/build_cpp_bindings +CC=gcc CXX=g++ cmake .. -DCMAKE_INSTALL_PREFIX=/workspace/install_cpp_bindings +cmake --build . -j +cmake --install . + +# Create tarball with symlink for compatibility +cd /workspace/install_cpp_bindings && \ +ln -s lib lib64 && \ +tar -czvf /workspace/svs-cpp-runtime-bindings${PLATFORM_NAME}.tar.gz . diff --git a/docker/x86_64/manylinux228/Dockerfile b/docker/x86_64/manylinux228/Dockerfile new file mode 100644 index 00000000..aab1d7b4 --- /dev/null +++ b/docker/x86_64/manylinux228/Dockerfile @@ -0,0 +1,43 @@ +# Copyright 2025 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. + +# Use --build-arg REGISTRY="quay.io/" to revert to default +ARG REGISTRY=cache-registry.caas.intel.com/cache-quay/ +FROM ${REGISTRY}pypa/manylinux_2_28_x86_64 + +ENV http_proxy=http://proxy-dmz.intel.com:911 +ENV https_proxy=http://proxy-dmz.intel.com:912 +ENV no_proxy=localhost,127.0.0.1 + +# Install CMake3 +RUN yum install -y cmake3 \ + && (if [ -x /usr/local/bin/cmake ]; then rm /usr/local/bin/cmake; fi) \ + && ln -sf /usr/bin/cmake3 /usr/local/bin/cmake + +# Install gcc-11 +RUN yum install -y gcc-toolset-11 valgrind wget + +# Install Intel(R) MKL +COPY ./ScalableVectorSearch/docker/x86_64/manylinux2014/oneAPI.repo /etc/yum.repos.d/oneAPI.repo +RUN yum install -y intel-oneapi-mkl-core-devel-2024.1 + +# Configure environment setup properly without recursion +RUN echo '# Configure gcc-11' > /etc/profile.d/01-gcc.sh && \ + echo 'source scl_source enable gcc-toolset-11' >> /etc/profile.d/01-gcc.sh && \ + chmod +x /etc/profile.d/01-gcc.sh + +# Copy sde script to run in test script later +# Need the 32-bit compatibility libraries to make sde work +RUN yum install -y glibc.i686 +COPY ./.github/scripts/run_sde.sh /tmp/run_sde.sh From 54fe55002ad3336a03e2730a7ace42cf95cc5bce Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 13:00:19 -0700 Subject: [PATCH 09/46] rename bash script --- .github/workflows/build-cpp-runtime-bindings.yml | 2 +- ...build-svs-shared-static.sh => build-cpp-runtime-bindings.sh} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docker/x86_64/{build-svs-shared-static.sh => build-cpp-runtime-bindings.sh} (100%) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index 6e5b5199..ce4f45e7 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -55,7 +55,7 @@ jobs: -e CC=${{ matrix.platform.cc }} \ -e CXX=${{ matrix.platform.cxx }} \ svs-${{ matrix.platform.name }}:latest \ - /bin/bash -c "chmod +x docker/x86_64/build-svs-shared-static.sh && ./docker/x86_64//build-svs-shared-static.sh" + /bin/bash -c "chmod +x docker/x86_64/build-cpp-runtime-bindings.sh && ./docker/x86_64/build-cpp-runtime-bindings.sh" - name: Upload cpp runtime bindings artifacts uses: actions/upload-artifact@v4 diff --git a/docker/x86_64/build-svs-shared-static.sh b/docker/x86_64/build-cpp-runtime-bindings.sh similarity index 100% rename from docker/x86_64/build-svs-shared-static.sh rename to docker/x86_64/build-cpp-runtime-bindings.sh From 4d74c8b3280c3d362f86947a5bd300dbc1ee7472 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 13:07:08 -0700 Subject: [PATCH 10/46] add steps to fix yaml error --- .github/workflows/build-cpp-runtime-bindings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index ce4f45e7..d063501a 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -42,6 +42,7 @@ jobs: platform: - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} + steps: - name: Build Docker image run: | docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . From e42d806c926c6713f3323d27463c42ea6283308f Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 31 Oct 2025 16:33:15 -0700 Subject: [PATCH 11/46] Change runs-on to ubuntu-22.04 --- .github/workflows/build-cpp-runtime-bindings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index d063501a..9ae8a2b9 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -36,7 +36,7 @@ concurrency: jobs: build-cpp-runtime-bindings: - runs-on: [self-hosted, Linux, ubuntu-22.04] + runs-on: ubuntu-22.04 strategy: matrix: platform: From 8e3b15f4bc28e92e1701af00e6a2742c6b908069 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Mon, 3 Nov 2025 07:01:07 -0800 Subject: [PATCH 12/46] Add missing repo checkout --- .github/workflows/build-cpp-runtime-bindings.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index 9ae8a2b9..3264fa89 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -43,6 +43,8 @@ jobs: - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} steps: + - uses: actions/checkout@v5 + - name: Build Docker image run: | docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . From 0a9c3d6bc96c0bad3a15642f0a4438c2472599f0 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Mon, 3 Nov 2025 07:14:15 -0800 Subject: [PATCH 13/46] clean up copyright/docker files --- docker/x86_64/build-cpp-runtime-bindings.sh | 2 +- docker/x86_64/manylinux228/Dockerfile | 19 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/docker/x86_64/build-cpp-runtime-bindings.sh b/docker/x86_64/build-cpp-runtime-bindings.sh index ff2d8af1..40d399b4 100644 --- a/docker/x86_64/build-cpp-runtime-bindings.sh +++ b/docker/x86_64/build-cpp-runtime-bindings.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2024 Intel Corporation +# Copyright 2025 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/docker/x86_64/manylinux228/Dockerfile b/docker/x86_64/manylinux228/Dockerfile index aab1d7b4..1351ce0c 100644 --- a/docker/x86_64/manylinux228/Dockerfile +++ b/docker/x86_64/manylinux228/Dockerfile @@ -12,13 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Use --build-arg REGISTRY="quay.io/" to revert to default -ARG REGISTRY=cache-registry.caas.intel.com/cache-quay/ -FROM ${REGISTRY}pypa/manylinux_2_28_x86_64 - -ENV http_proxy=http://proxy-dmz.intel.com:911 -ENV https_proxy=http://proxy-dmz.intel.com:912 -ENV no_proxy=localhost,127.0.0.1 +FROM quay.io/pypa/manylinux_2_28_x86_64:latest # Install CMake3 RUN yum install -y cmake3 \ @@ -26,18 +20,9 @@ RUN yum install -y cmake3 \ && ln -sf /usr/bin/cmake3 /usr/local/bin/cmake # Install gcc-11 -RUN yum install -y gcc-toolset-11 valgrind wget - -# Install Intel(R) MKL -COPY ./ScalableVectorSearch/docker/x86_64/manylinux2014/oneAPI.repo /etc/yum.repos.d/oneAPI.repo -RUN yum install -y intel-oneapi-mkl-core-devel-2024.1 +RUN yum install -y gcc-toolset-11 wget # Configure environment setup properly without recursion RUN echo '# Configure gcc-11' > /etc/profile.d/01-gcc.sh && \ echo 'source scl_source enable gcc-toolset-11' >> /etc/profile.d/01-gcc.sh && \ chmod +x /etc/profile.d/01-gcc.sh - -# Copy sde script to run in test script later -# Need the 32-bit compatibility libraries to make sde work -RUN yum install -y glibc.i686 -COPY ./.github/scripts/run_sde.sh /tmp/run_sde.sh From 32d3d9a840d05944d8c02337225347fe66e7a1f0 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Mon, 3 Nov 2025 07:42:40 -0800 Subject: [PATCH 14/46] set CMAKE_INSTALL_LIBDIR --- docker/x86_64/build-cpp-runtime-bindings.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/x86_64/build-cpp-runtime-bindings.sh b/docker/x86_64/build-cpp-runtime-bindings.sh index 40d399b4..7a0dbda9 100644 --- a/docker/x86_64/build-cpp-runtime-bindings.sh +++ b/docker/x86_64/build-cpp-runtime-bindings.sh @@ -26,7 +26,7 @@ mkdir -p /workspace/bindings/cpp/build_cpp_bindings /workspace/install_cpp_bindi # Build and install runtime bindings library cd /workspace/bindings/cpp/build_cpp_bindings -CC=gcc CXX=g++ cmake .. -DCMAKE_INSTALL_PREFIX=/workspace/install_cpp_bindings +CC=gcc CXX=g++ cmake .. -DCMAKE_INSTALL_PREFIX=/workspace/install_cpp_bindings -DCMAKE_INSTALL_LIBDIR=lib cmake --build . -j cmake --install . From f8791dce5297a7bdf54e51a4085aeda69d49e2a3 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Mon, 3 Nov 2025 08:08:16 -0800 Subject: [PATCH 15/46] Add flexibility to gcc version check 11.2.* --- bindings/cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index fc1d0045..abb7c0cb 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -102,7 +102,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) ) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "11.2") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3") set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251031-740.tar.gz" CACHE STRING "URL to download SVS shared library") else() From 473a74b56000a4b5ca3601c63ed15e80a3997f36 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Mon, 3 Nov 2025 17:59:15 -0800 Subject: [PATCH 16/46] Add resuable function for linking statically to MKL --- .github/workflows/build-cpp-runtime-bindings.yml | 2 +- bindings/cpp/CMakeLists.txt | 1 + cmake/mkl.cmake | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index 3264fa89..3516f390 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -65,4 +65,4 @@ jobs: with: name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} path: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz - retention-days: 7 # Reduce retention due to size \ No newline at end of file + retention-days: 7 # Reduce retention due to size diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index abb7c0cb..dcd9a384 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -87,6 +87,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) target_link_libraries(${TARGET_NAME} PRIVATE svs::svs ) + link_mkl_static(${TARGET_NAME}) elseif(TARGET svs_static_library) # Links to SVS static library built as part of the main SVS build target_link_libraries(${TARGET_NAME} PRIVATE diff --git a/cmake/mkl.cmake b/cmake/mkl.cmake index 088cfbea..a1fcc826 100644 --- a/cmake/mkl.cmake +++ b/cmake/mkl.cmake @@ -95,4 +95,18 @@ elseif(NOT SVS_EXPERIMENTAL_LINK_STATIC_MKL) ${SVS_LIB} INTERFACE $ ) target_link_libraries(${SVS_LIB} INTERFACE $) +else() # if static link and not custom mkl + message(STATUS "Statically linking ${target} to Intel(R) MKL") + function(link_mkl_static target) + target_link_libraries(${target} PRIVATE + -Wl,--start-group + ${MKL_ROOT}/lib/intel64/libmkl_intel_lp64.a + ${MKL_ROOT}/lib/intel64/libmkl_sequential.a + ${MKL_ROOT}/lib/intel64/libmkl_core.a + -Wl,--end-group -lpthread -lm -ldl + ) + if(UNIX AND NOT APPLE) + target_link_options(${target} PRIVATE "SHELL:-Wl,--exclude-libs,ALL") + endif() + endfunction() endif() From 8966732be2fdc603efb8d360fa769aca6e4cdbb2 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 4 Nov 2025 17:14:15 +0100 Subject: [PATCH 17/46] Improved runtime API draft in new header --- bindings/cpp/include/vamana_index.h | 121 ++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 bindings/cpp/include/vamana_index.h diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h new file mode 100644 index 00000000..07642d16 --- /dev/null +++ b/bindings/cpp/include/vamana_index.h @@ -0,0 +1,121 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" + +#include +#include +#include +#include +#include +#include + +namespace svs { +namespace runtime { + +// Abstract interface for Vamana-based indexes. +struct SVS_RUNTIME_API VamanaIndex { + enum StorageKind { + FP32, FP16, SQI8, + LVQ4x0, LVQ4x4, LVQ4x8, + LeanVec4x4, LeanVec4x8, LeanVec8x8, + }; + + struct BuildParams { + size_t dim; + size_t graph_max_degree; + size_t prune_to = 0; + float alpha = 0; + size_t construction_window_size = 40; + size_t max_candidate_pool_size = 200; + bool use_full_search_history = true; + }; + + struct SearchParams { + size_t search_window_size = 0; + size_t search_buffer_capacity = 0; + }; + + virtual ~VamanaIndex() = default; + + virtual size_t size() const noexcept = 0; + virtual size_t dimensions() const noexcept = 0; + virtual MetricType metric_type() const noexcept = 0; + virtual StorageKind get_storage_kind() const noexcept = 0; + + virtual Status add(size_t n, const size_t* labels, const float* x) noexcept = 0; + virtual size_t remove_selected(const IDFilter& selector) noexcept = 0; + + virtual Status search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept = 0; + + virtual Status range_search( + size_t n, + const float* x, + float radius, + const ResultsAllocator& results, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept = 0; + + virtual void reset() noexcept = 0; + virtual Status serialize(std::ostream& out) const noexcept = 0; + + // Static constructors and destructors + static VamanaIndex* build( + MetricType metric, + StorageKind storage_kind, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10,10} + ) noexcept; + + static void destroy(VamanaIndex* impl) noexcept; + static VamanaIndex* deserialize(std::istream& in, MetricType metric, VamanaIndex::StorageKind storage_kind) noexcept; +}; + +struct SVS_RUNTIME_API VamanaIndexLeanVecFactory { + virtual ~VamanaIndexLeanVecFactory() = default; + + static VamanaIndexLeanVecFactory* train( + size_t d, + size_t n, + const float* x, + size_t leanvec_dims + ) noexcept; + + static void destroy(VamanaIndexLeanVecFactory* info) noexcept; + + virtual Status serialize(std::ostream& out) const noexcept; + static VamanaIndexLeanVecFactory* deserialize(std::istream& in) noexcept; + + virtual VamanaIndex* buildIndex( + size_t dim, + MetricType metric, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {} + ) noexcept; +}; + +} // namespace runtime +} // namespace svs From 3bbc18c3cfdbd1ae98b4b5a59ba7b1fa1ad8d56c Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 4 Nov 2025 18:08:58 +0100 Subject: [PATCH 18/46] Update API header with comments --- bindings/cpp/include/vamana_index.h | 36 ++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index 07642d16..34170996 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -35,6 +35,11 @@ struct SVS_RUNTIME_API VamanaIndex { LeanVec4x4, LeanVec4x8, LeanVec8x8, }; + // TODO: + // 1. Should StorageKind, metric, be a part of BuildParams? + // 2. Does it make sense to have "Common" BuildParams{dim, metric} struct for other index algos (Flat, IVF)? + // Or dim, metric, storage kind should be passed separately to the build() method? + // What about storage kind in Flat, IVF? struct BuildParams { size_t dim; size_t graph_max_degree; @@ -50,15 +55,16 @@ struct SVS_RUNTIME_API VamanaIndex { size_t search_buffer_capacity = 0; }; - virtual ~VamanaIndex() = default; - + // Unused for now: virtual size_t size() const noexcept = 0; virtual size_t dimensions() const noexcept = 0; virtual MetricType metric_type() const noexcept = 0; virtual StorageKind get_storage_kind() const noexcept = 0; virtual Status add(size_t n, const size_t* labels, const float* x) noexcept = 0; - virtual size_t remove_selected(const IDFilter& selector) noexcept = 0; + virtual Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0; + // Further method for deletion can be added later: + // virtual Status remove(size_t n, const size_t* labels) noexcept = 0; virtual Status search( size_t n, @@ -79,37 +85,41 @@ struct SVS_RUNTIME_API VamanaIndex { IDFilter* filter = nullptr ) const noexcept = 0; - virtual void reset() noexcept = 0; + virtual Status reset() noexcept = 0; + // TODO: Does it make sense to rename it to "save()"? virtual Status serialize(std::ostream& out) const noexcept = 0; // Static constructors and destructors - static VamanaIndex* build( + static Status build( + VamanaIndex** index, MetricType metric, StorageKind storage_kind, const VamanaIndex::BuildParams& params, const VamanaIndex::SearchParams& default_search_params = {10,10} ) noexcept; - static void destroy(VamanaIndex* impl) noexcept; - static VamanaIndex* deserialize(std::istream& in, MetricType metric, VamanaIndex::StorageKind storage_kind) noexcept; + static Status destroy(VamanaIndex* index) noexcept; + // TODO: Does it make sense to rename it to "load()"? + // TODO: is it possible to get metric and storage kind from the stream instead of passing them explicitly? + static Status deserialize(VamanaIndex** index, std::istream& in, MetricType metric, VamanaIndex::StorageKind storage_kind) noexcept; }; struct SVS_RUNTIME_API VamanaIndexLeanVecFactory { - virtual ~VamanaIndexLeanVecFactory() = default; - - static VamanaIndexLeanVecFactory* train( + static Status train( + VamanaIndexLeanVecFactory** factory, size_t d, size_t n, const float* x, size_t leanvec_dims ) noexcept; - static void destroy(VamanaIndexLeanVecFactory* info) noexcept; + static Status destroy(VamanaIndexLeanVecFactory* factory) noexcept; virtual Status serialize(std::ostream& out) const noexcept; - static VamanaIndexLeanVecFactory* deserialize(std::istream& in) noexcept; + static Status deserialize(VamanaIndexLeanVecFactory** factory, std::istream& in) noexcept; - virtual VamanaIndex* buildIndex( + virtual Status buildIndex( + VamanaIndex** index, size_t dim, MetricType metric, const VamanaIndex::BuildParams& params, From 0540d00ac98b65d3c300a5675013cc3f2f7bdfd7 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Tue, 4 Nov 2025 11:11:50 -0800 Subject: [PATCH 19/46] Initial faiss python test validation --- .../workflows/build-cpp-runtime-bindings.yml | 37 +++++++++++++++++++ docker/x86_64/manylinux228/Dockerfile | 6 +++ docker/x86_64/test-cpp-runtime-bindings.sh | 33 +++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 docker/x86_64/test-cpp-runtime-bindings.sh diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index 3516f390..ce8da6ee 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -66,3 +66,40 @@ jobs: name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} path: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz retention-days: 7 # Reduce retention due to size + + test: + needs: build-cpp-runtime-bindings + runs-on: ubuntu-22.04 + strategy: + matrix: + platform: + - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} + + steps: + - uses: actions/checkout@v5 + + - name: Build Docker image + run: | + docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . + + # Need to download for a new job + - name: Download shared libraries + uses: actions/download-artifact@v4 + with: + name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz + path: runtime_lib + + - name: List available artifacts + run: | + ls -la runtime_lib/ + + - name: Test in Docker container + run: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -v ${{ github.workspace }}/runtime_lib:/runtime_lib \ + -w /workspace \ + -e CC=${{ matrix.platform.cc }} \ + -e CXX=${{ matrix.platform.cxx }} \ + svs-${{ matrix.platform.name }}:latest \ + /bin/bash -c "chmod +x docker/x86_64/test-cpp-runtime-bindings.sh && ./docker/x86_64/test-cpp-runtime-bindings.sh" diff --git a/docker/x86_64/manylinux228/Dockerfile b/docker/x86_64/manylinux228/Dockerfile index 1351ce0c..491cbf03 100644 --- a/docker/x86_64/manylinux228/Dockerfile +++ b/docker/x86_64/manylinux228/Dockerfile @@ -26,3 +26,9 @@ RUN yum install -y gcc-toolset-11 wget RUN echo '# Configure gcc-11' > /etc/profile.d/01-gcc.sh && \ echo 'source scl_source enable gcc-toolset-11' >> /etc/profile.d/01-gcc.sh && \ chmod +x /etc/profile.d/01-gcc.sh + +# Download and install Miniforge +RUN wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O /tmp/miniforge.sh && \ + bash /tmp/miniforge.sh -b -p /opt/conda && \ + rm /tmp/miniforge.sh +ENV PATH="/opt/conda/bin:$PATH" diff --git a/docker/x86_64/test-cpp-runtime-bindings.sh b/docker/x86_64/test-cpp-runtime-bindings.sh new file mode 100644 index 00000000..b9e7cb4e --- /dev/null +++ b/docker/x86_64/test-cpp-runtime-bindings.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +# Source environment setup (for compiler and MKL) +source /etc/bashrc || true + +# chmod +x docker/x86_64/list-dependencies.sh +# ./docker/x86_64/list-dependencies.sh + +# FAISS validation scope for now +# TODO: point to root repo eventually +git clone -b rfsaliev/svs-faiss-bindings https://github.com/ahuber21/faiss.git +cd faiss +sed -i "s|set(SVS_URL .*|set(SVS_URL \"file:///runtime_lib/svs-cpp-runtime-bindings${PLATFORM_NAME}.tar.gz\" CACHE STRING \"SVS URL\")|" faiss/CMakeLists.txt + +echo "================================================" +echo " Runnning validation of library against FAISS CI" +echo "------------------------------------------------" +echo " FAISS Build: " +mkdir build && cd build +# TODO: create conda env +cmake -DBUILD_TESTING=ON -DFAISS_ENABLE_SVS=ON -DFAISS_ENABLE_GPU=OFF .. +make -j swigfaiss +echo "------------------------------------------------" +echo " FAISS python bindings: " +cd faiss/python/ +python setup.py build +echo "------------------------------------------------" +echo " FAISS python tests: " +cd ../../../tests/ +PYTHONPATH=../build/faiss/python/build/lib/ OMP_NUM_THREADS=8 python -m unittest test_svs.py + +# TODO: C++ tests From d5d69560ba2c7d0135a41b13d26814c3514115b3 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Tue, 4 Nov 2025 11:29:37 -0800 Subject: [PATCH 20/46] Fix upload path --- docker/x86_64/test-cpp-runtime-bindings.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docker/x86_64/test-cpp-runtime-bindings.sh b/docker/x86_64/test-cpp-runtime-bindings.sh index b9e7cb4e..b2325bef 100644 --- a/docker/x86_64/test-cpp-runtime-bindings.sh +++ b/docker/x86_64/test-cpp-runtime-bindings.sh @@ -1,4 +1,18 @@ #!/bin/bash +# Copyright 2025 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. + set -e # Source environment setup (for compiler and MKL) From 883266fbaeaa9af48d64171851ba84f973e6f40d Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Tue, 4 Nov 2025 12:49:37 -0800 Subject: [PATCH 21/46] Actually fix upload path --- .github/workflows/build-cpp-runtime-bindings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index ce8da6ee..6152f6c3 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -86,7 +86,7 @@ jobs: - name: Download shared libraries uses: actions/download-artifact@v4 with: - name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz + name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} path: runtime_lib - name: List available artifacts From dc6aa107b72cb16c49a349a2d3aa65a1792734d4 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Tue, 4 Nov 2025 13:24:37 -0800 Subject: [PATCH 22/46] Add conda env creation --- docker/x86_64/test-cpp-runtime-bindings.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker/x86_64/test-cpp-runtime-bindings.sh b/docker/x86_64/test-cpp-runtime-bindings.sh index b2325bef..fa890bd2 100644 --- a/docker/x86_64/test-cpp-runtime-bindings.sh +++ b/docker/x86_64/test-cpp-runtime-bindings.sh @@ -22,6 +22,15 @@ source /etc/bashrc || true # ./docker/x86_64/list-dependencies.sh # FAISS validation scope for now +# Create conda env matching https://github.com/facebookresearch/faiss/blob/main/.github/actions/build_cmake/action.yml +conda create -y -n svsenv python=3.11 +source /opt/conda/etc/profile.d/conda.sh +conda activate svsenv +conda config --set solver libmamba +conda install -y -c conda-forge cmake=3.30.4 make=4.2 swig=4.0 "numpy>=2.0,<3.0" scipy=1.16 pytest=7.4 gflags=2.2 +conda install -y -c conda-forge gxx_linux-64=14.2 sysroot_linux-64=2.17 +conda install -y mkl=2022.2.1 mkl-devel=2022.2.1 + # TODO: point to root repo eventually git clone -b rfsaliev/svs-faiss-bindings https://github.com/ahuber21/faiss.git cd faiss From ec1cce58186131af7815f5043a8c56506b1d5d68 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Wed, 5 Nov 2025 16:35:02 +0100 Subject: [PATCH 23/46] First implementation of the improved runtime Vamana API --- bindings/cpp/CMakeLists.txt | 7 + bindings/cpp/include/IndexSVSImplDefs.h | 9 +- bindings/cpp/include/dynamic_vamana_index.h | 79 +++ bindings/cpp/include/training.h | 42 ++ bindings/cpp/include/vamana_index.h | 77 +-- bindings/cpp/src/dynamic_vamana_index.cpp | 203 +++++++ bindings/cpp/src/dynamic_vamana_index_impl.h | 590 +++++++++++++++++++ bindings/cpp/src/svs_runtime_utils.h | 280 +++++++++ bindings/cpp/src/training.cpp | 52 ++ bindings/cpp/src/training_impl.h | 100 ++++ include/svs/extensions/vamana/scalar.h | 2 +- 11 files changed, 1368 insertions(+), 73 deletions(-) create mode 100644 bindings/cpp/include/dynamic_vamana_index.h create mode 100644 bindings/cpp/include/training.h create mode 100644 bindings/cpp/src/dynamic_vamana_index.cpp create mode 100644 bindings/cpp/src/dynamic_vamana_index_impl.h create mode 100644 bindings/cpp/src/svs_runtime_utils.h create mode 100644 bindings/cpp/src/training.cpp create mode 100644 bindings/cpp/src/training_impl.h diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index dcd9a384..08e8b7f6 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -20,12 +20,19 @@ set(SVS_RUNTIME_HEADERS include/IndexSVSImplDefs.h include/IndexSVSFlatImpl.h include/IndexSVSVamanaImpl.h + include/training.h + include/vamana_index.h + include/dynamic_vamana_index.h ) set(SVS_RUNTIME_SOURCES src/IndexSVSImplUtils.h + src/svs_runtime_utils.h + src/dynamic_vamana_index_impl.h src/IndexSVSFlatImpl.cpp src/IndexSVSVamanaImpl.cpp + src/training.cpp + src/dynamic_vamana_index.cpp ) option(SVS_RUNTIME_ENABLE_LVQ_LEANVEC "Enable compilation of SVS runtime with LVQ and LeanVec support" ON) diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index 7967ff44..da827bb7 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -31,12 +31,19 @@ namespace svs { namespace runtime { enum class MetricType { L2, INNER_PRODUCT }; +enum class StorageKind { + FP32, FP16, SQI8, + LVQ4x0, LVQ4x4, LVQ4x8, + LeanVec4x4, LeanVec4x8, LeanVec8x8, +}; + enum class ErrorCode { SUCCESS = 0, UNKNOWN_ERROR = 1, INVALID_ARGUMENT = 2, NOT_IMPLEMENTED = 3, - NOT_INITIALIZED = 4 + NOT_INITIALIZED = 4, + RUNTIME_ERROR = 5 }; struct Status { diff --git a/bindings/cpp/include/dynamic_vamana_index.h b/bindings/cpp/include/dynamic_vamana_index.h new file mode 100644 index 00000000..584c5bdb --- /dev/null +++ b/bindings/cpp/include/dynamic_vamana_index.h @@ -0,0 +1,79 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" +#include "vamana_index.h" +#include "training.h" + +#include +#include +#include + +namespace svs { +namespace runtime { + +// Abstract interface for Dynamic Vamana-based indexes. +struct SVS_RUNTIME_API DynamicVamanaIndex : public VamanaIndex { + + virtual Status add(size_t n, const size_t* labels, const float* x) noexcept = 0; + virtual Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0; + virtual Status remove(size_t n, const size_t* labels) noexcept = 0; + + virtual Status reset() noexcept = 0; + + // Static constructors and destructors + static Status build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10, 10, 0, 0} + ) noexcept; + + static Status destroy(DynamicVamanaIndex* index) noexcept; + + virtual Status save(std::ostream& out) const noexcept = 0; + static Status load(DynamicVamanaIndex** index, std::istream& in, MetricType metric, StorageKind storage_kind) noexcept; +}; + +struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex { + // Specialization to build LeanVec-based Vamana index with specified leanvec dims + static Status build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + size_t leanvec_dims, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10, 10, 0, 0} + ) noexcept; + + // Specialization to build LeanVec-based Vamana index with provided training data + static Status build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + const LeanVecTrainingData* training_data, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10, 10, 0, 0} + ) noexcept; +}; + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/training.h new file mode 100644 index 00000000..99084e37 --- /dev/null +++ b/bindings/cpp/include/training.h @@ -0,0 +1,42 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" + +#include +#include +#include + +namespace svs { +namespace runtime { +struct SVS_RUNTIME_API LeanVecTrainingData { + virtual ~LeanVecTrainingData() = 0; + Status build( + LeanVecTrainingData** training_data, + size_t dim, + size_t n, + const float* x, + size_t leanvec_dims + ) noexcept; + + static Status destroy(LeanVecTrainingData* training_data) noexcept; + + virtual Status save(std::ostream& out) const noexcept; + static Status load(LeanVecTrainingData** training_data, std::istream& in) noexcept; +}; +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index 34170996..ad36aef1 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -18,30 +18,16 @@ #include "IndexSVSImplDefs.h" #include -#include -#include -#include -#include -#include namespace svs { namespace runtime { // Abstract interface for Vamana-based indexes. +// NOTE VamanaIndex is not implemented directly, only DynamicVamanaIndex is implemented. struct SVS_RUNTIME_API VamanaIndex { - enum StorageKind { - FP32, FP16, SQI8, - LVQ4x0, LVQ4x4, LVQ4x8, - LeanVec4x4, LeanVec4x8, LeanVec8x8, - }; + virtual ~VamanaIndex() = 0; - // TODO: - // 1. Should StorageKind, metric, be a part of BuildParams? - // 2. Does it make sense to have "Common" BuildParams{dim, metric} struct for other index algos (Flat, IVF)? - // Or dim, metric, storage kind should be passed separately to the build() method? - // What about storage kind in Flat, IVF? struct BuildParams { - size_t dim; size_t graph_max_degree; size_t prune_to = 0; float alpha = 0; @@ -51,21 +37,12 @@ struct SVS_RUNTIME_API VamanaIndex { }; struct SearchParams { - size_t search_window_size = 0; - size_t search_buffer_capacity = 0; + size_t search_window_size = 10; + size_t search_buffer_capacity = 10; + size_t prefetch_lookahead = 0; + size_t prefetch_step = 0; }; - // Unused for now: - virtual size_t size() const noexcept = 0; - virtual size_t dimensions() const noexcept = 0; - virtual MetricType metric_type() const noexcept = 0; - virtual StorageKind get_storage_kind() const noexcept = 0; - - virtual Status add(size_t n, const size_t* labels, const float* x) noexcept = 0; - virtual Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0; - // Further method for deletion can be added later: - // virtual Status remove(size_t n, const size_t* labels) noexcept = 0; - virtual Status search( size_t n, const float* x, @@ -84,48 +61,6 @@ struct SVS_RUNTIME_API VamanaIndex { const SearchParams* params = nullptr, IDFilter* filter = nullptr ) const noexcept = 0; - - virtual Status reset() noexcept = 0; - // TODO: Does it make sense to rename it to "save()"? - virtual Status serialize(std::ostream& out) const noexcept = 0; - - // Static constructors and destructors - static Status build( - VamanaIndex** index, - MetricType metric, - StorageKind storage_kind, - const VamanaIndex::BuildParams& params, - const VamanaIndex::SearchParams& default_search_params = {10,10} - ) noexcept; - - static Status destroy(VamanaIndex* index) noexcept; - // TODO: Does it make sense to rename it to "load()"? - // TODO: is it possible to get metric and storage kind from the stream instead of passing them explicitly? - static Status deserialize(VamanaIndex** index, std::istream& in, MetricType metric, VamanaIndex::StorageKind storage_kind) noexcept; -}; - -struct SVS_RUNTIME_API VamanaIndexLeanVecFactory { - static Status train( - VamanaIndexLeanVecFactory** factory, - size_t d, - size_t n, - const float* x, - size_t leanvec_dims - ) noexcept; - - static Status destroy(VamanaIndexLeanVecFactory* factory) noexcept; - - virtual Status serialize(std::ostream& out) const noexcept; - static Status deserialize(VamanaIndexLeanVecFactory** factory, std::istream& in) noexcept; - - virtual Status buildIndex( - VamanaIndex** index, - size_t dim, - MetricType metric, - const VamanaIndex::BuildParams& params, - const VamanaIndex::SearchParams& default_search_params = {} - ) noexcept; }; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp new file mode 100644 index 00000000..92edb4c4 --- /dev/null +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2025 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. + */ + +#include "dynamic_vamana_index.h" +#include "svs_runtime_utils.h" +#include "dynamic_vamana_index_impl.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include SVS_LVQ_HEADER +#include SVS_LEANVEC_HEADER + +namespace svs { +namespace runtime { + +namespace { +template +struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { + std::unique_ptr impl_; + + DynamicVamanaIndexManagerBase(std::unique_ptr impl) + : impl_{std::move(impl)} { + assert(impl_ != nullptr); + } + + DynamicVamanaIndexManagerBase(const DynamicVamanaIndexManagerBase&) = delete; + DynamicVamanaIndexManagerBase& operator=(const DynamicVamanaIndexManagerBase&) = delete; + DynamicVamanaIndexManagerBase(DynamicVamanaIndexManagerBase&&) = default; + DynamicVamanaIndexManagerBase& operator=(DynamicVamanaIndexManagerBase&&) = default; + ~DynamicVamanaIndexManagerBase() override = default; + + Status add(size_t n, const size_t* labels, const float* x) noexcept override { + SVS_RUNTIME_TRY_BEGIN + svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; + std::span lbls(labels, n); + impl_->add(data, lbls); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept override { + SVS_RUNTIME_TRY_BEGIN + *num_removed = impl_->remove_selected(selector); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status remove(size_t n, const size_t* labels) noexcept override { + SVS_RUNTIME_TRY_BEGIN + std::span lbls(labels, n); + impl_->remove(lbls); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept override { + SVS_RUNTIME_TRY_BEGIN + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and here + impl_->search(n, x, k, distances, labels, params, filter); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status range_search( + size_t n, + const float* x, + float radius, + const ResultsAllocator& results, + const SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const noexcept override { + SVS_RUNTIME_TRY_BEGIN + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and here + impl_->range_search(n, x, radius, results, params, filter); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status reset() noexcept override { + SVS_RUNTIME_TRY_BEGIN + impl_->reset(); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status save(std::ostream& out) const noexcept override { + SVS_RUNTIME_TRY_BEGIN + impl_->save(out); + return Status_Ok; + SVS_RUNTIME_TRY_END + } +}; + +using DynamicVamanaIndexManager = DynamicVamanaIndexManagerBase; +using DynamicVamanaIndexLeanVecImplTrainedManager = DynamicVamanaIndexManagerBase; +using DynamicVamanaIndexLeanVecImplDimsManager = DynamicVamanaIndexManagerBase; + +} // namespace + +Status DynamicVamanaIndex::build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + const DynamicVamanaIndex::BuildParams& params, + const DynamicVamanaIndex::SearchParams& default_search_params +) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + auto impl = std::make_unique(dim, metric, storage_kind, params, default_search_params); + *index = new DynamicVamanaIndexManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +Status DynamicVamanaIndex::destroy(DynamicVamanaIndex* index) noexcept { + SVS_RUNTIME_TRY_BEGIN + delete index; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +Status DynamicVamanaIndex::load(DynamicVamanaIndex** index, std::istream& in, MetricType metric, StorageKind storage_kind) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + std::unique_ptr impl{DynamicVamanaIndexImpl::load(in, metric, storage_kind)}; + *index = new DynamicVamanaIndexManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +// Specialization to build LeanVec-based Vamana index with specified leanvec dims +Status DynamicVamanaIndexLeanVec::build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + size_t leanvec_dims, + const DynamicVamanaIndex::BuildParams& params, + const DynamicVamanaIndex::SearchParams& default_search_params +) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + auto impl = std::make_unique(dim, metric, storage_kind, leanvec_dims, params, default_search_params); + *index = new DynamicVamanaIndexLeanVecImplDimsManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +// Specialization to build LeanVec-based Vamana index with provided training data +Status DynamicVamanaIndexLeanVec::build( + DynamicVamanaIndex** index, + size_t dim, + MetricType metric, + StorageKind storage_kind, + const LeanVecTrainingData* training_data, + const DynamicVamanaIndex::BuildParams& params, + const DynamicVamanaIndex::SearchParams& default_search_params +) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + auto training_data_impl = static_cast(training_data)->impl_; + auto impl = std::make_unique(dim, metric, storage_kind, training_data_impl, params, default_search_params); + *index = new DynamicVamanaIndexLeanVecImplTrainedManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/dynamic_vamana_index_impl.h b/bindings/cpp/src/dynamic_vamana_index_impl.h new file mode 100644 index 00000000..3f1cd063 --- /dev/null +++ b/bindings/cpp/src/dynamic_vamana_index_impl.h @@ -0,0 +1,590 @@ +/* + * Copyright 2025 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_utils.h" +#include "training_impl.h" +#include "dynamic_vamana_index.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include SVS_LVQ_HEADER +#include SVS_LEANVEC_HEADER + +namespace svs { +namespace runtime { + +// Vamana index implementation +class DynamicVamanaIndexImpl { + public: + DynamicVamanaIndexImpl( + size_t dim, + MetricType metric, + StorageKind storage_kind, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params + ) : dim_{dim}, + metric_type_{metric}, + storage_kind_{storage_kind}, + build_params_{params}, + default_search_params_{default_search_params} { + if (build_params_.prune_to == 0) { + build_params_.prune_to = + build_params_.graph_max_degree < 4 ? build_params_.graph_max_degree + : build_params_.graph_max_degree - 4; + } + if (build_params_.alpha == 0) { + build_params_.alpha = metric == MetricType::L2 ? 1.2f : 0.95f; + } + } + + size_t size() const { return impl_ ? impl_->size() : 0; } + + size_t dimensions() const { return dim_; } + + MetricType metric_type() const { return metric_type_; } + + StorageKind get_storage_kind() const { return storage_kind_; } + + + void add(data::ConstSimpleDataView data, std::span labels) { + if (!impl_) { + return init_impl(data, labels); + } + + impl_->add_points(data, labels); + } + + void search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + const VamanaIndex::SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const { + if (!impl_) { + for (size_t i = 0; i < n; ++i) { + distances[i] = std::numeric_limits::infinity(); + labels[i] = -1; + } + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + if (k == 0) { + throw StatusException{ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; + } + + auto sp = make_search_parameters(params); + + // Simple search + if (filter == nullptr) { + auto queries = svs::data::ConstSimpleDataView(x, n, dim_); + + // TODO: faiss use int64_t as label whereas SVS uses size_t? + auto results = svs::QueryResultView{ + svs::MatrixView{ + svs::make_dims(n, k), static_cast(static_cast(labels))}, + svs::MatrixView{svs::make_dims(n, k), distances}}; + impl_->search(results, queries, sp); + } + + // Selective search with IDSelector + auto old_sp = impl_->get_search_parameters(); + impl_->set_search_parameters(sp); + + auto search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { + for (auto i : range) { + // For every query + auto query = std::span(x + i * dim_, dim_); + auto curr_distances = std::span(distances + i * k, k); + auto curr_labels = std::span(labels + i * k, k); + + auto iterator = impl_->batch_iterator(query); + size_t found = 0; + do { + iterator.next(k); + for (auto& neighbor : iterator.results()) { + if (filter->is_member(neighbor.id())) { + curr_distances[found] = neighbor.distance(); + curr_labels[found] = neighbor.id(); + found++; + if (found == k) { + break; + } + } + } + } while (found < k && !iterator.done()); + // Pad with -1s + for (; found < k; ++found) { + curr_distances[found] = -1; + curr_labels[found] = -1; + } + } + }; + + auto threadpool = + svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + + svs::threads::parallel_for( + threadpool, svs::threads::StaticPartition{n}, search_closure + ); + + impl_->set_search_parameters(old_sp); + } + + void range_search( + size_t n, + const float* x, + float radius, + const ResultsAllocator& results, + const VamanaIndex::SearchParams* params = nullptr, + IDFilter* filter = nullptr + ) const { + if (!impl_) { + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + if (radius <= 0) { + throw StatusException{ErrorCode::INVALID_ARGUMENT, "radius must be greater than 0"}; + } + + auto sp = make_search_parameters(params); + auto old_sp = impl_->get_search_parameters(); + impl_->set_search_parameters(sp); + + // Using ResultHandler makes no sense due to it's complexity, overhead and + // missed features; e.g. add_result() does not indicate whether result added + // or not - we have to manually manage threshold comparison and id + // selection. + + // Prepare output buffers + std::vector>> all_results(n); + // Reserve space for allocation to avoid multiple reallocations + // Use search_buffer_capacity as a heuristic + const auto result_capacity = sp.buffer_config_.get_total_capacity(); + for (auto& res : all_results) { + res.reserve(result_capacity); + } + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric_type_)); + + std::function compare = distance_dispatcher([](auto&& dist) { + return std::function{svs::distance::comparator(dist)}; + }); + + std::function select = [](size_t) { return true; }; + if (filter != nullptr) { + select = [&](size_t id) { return filter->is_member(id); }; + } + + // Set iterator batch size to search window size + auto batch_size = sp.buffer_config_.get_search_window_size(); + + auto range_search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { + for (auto i : range) { + // For every query + auto query = std::span(x + i * dim_, dim_); + + auto iterator = impl_->batch_iterator(query); + bool in_range = true; + + do { + iterator.next(batch_size); + for (auto& neighbor : iterator.results()) { + // SVS comparator functor returns true if the first distance + // is 'closer' than the second one + in_range = compare(neighbor.distance(), radius); + if (in_range) { + // Selective search with IDSelector + if (select(neighbor.id())) { + all_results[i].push_back(neighbor); + } + } else { + // Since iterator.results() are ordered by distance, we + // can stop processing + break; + } + } + } while (in_range && !iterator.done()); + } + }; + + auto threadpool = + svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + + svs::threads::parallel_for( + threadpool, svs::threads::StaticPartition{n}, range_search_closure + ); + + // Allocate output + std::vector result_counts(n); + std::transform( + all_results.begin(), + all_results.end(), + result_counts.begin(), + [](const auto& res) { return res.size(); } + ); + auto results_storage = results(result_counts); + + // Fill in results + for (size_t q = 0, ofs = 0; q < n; ++q) { + for (const auto& [id, distance] : all_results[q]) { + results_storage.labels[ofs] = id; + results_storage.distances[ofs] = distance; + ofs++; + } + } + + impl_->set_search_parameters(old_sp); + } + + size_t remove(std::span labels) { + if (!impl_) { + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + // SVS deletion is a soft deletion, meaning the corresponding vectors are + // marked as deleted but still present in both the dataset and the graph, + // and will be navigated through during search. + // Actual cleanup happens once a large enough number of soft deleted vectors + // are collected. + impl_->delete_points(labels); + ntotal_soft_deleted += labels.size(); + + auto ntotal = impl_->size(); + const float cleanup_threshold = .5f; + if (ntotal == 0 || (float)ntotal_soft_deleted / ntotal > cleanup_threshold) { + impl_->consolidate(); + impl_->compact(); + ntotal_soft_deleted = 0; + } + return labels.size(); + } + + size_t remove_selected(const IDFilter& selector) { + if (!impl_) { + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + auto ids = impl_->all_ids(); + std::vector ids_to_delete; + std::copy_if(ids.begin(), ids.end(), std::back_inserter(ids_to_delete), [&](size_t id) { + return selector(id); + }); + + return remove(ids_to_delete); + } + + void reset() { + impl_.reset(); + ntotal_soft_deleted = 0; + } + + void save(std::ostream& out) const { + if (!impl_) { + throw StatusException{ + ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized."}; + } + + impl_->save(out); + } + +protected: + // Utility functions + svs::index::vamana::VamanaBuildParameters vamana_build_parameters() const { + return svs::index::vamana::VamanaBuildParameters{ + build_params_.alpha, + build_params_.graph_max_degree, + build_params_.construction_window_size, + build_params_.max_candidate_pool_size, + build_params_.prune_to, + build_params_.use_full_search_history}; + } + + svs::index::vamana::VamanaSearchParameters make_search_parameters( + const VamanaIndex::SearchParams* params + ) const { + if (!impl_) { + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + auto sp = impl_->get_search_parameters(); + + auto search_window_size = default_search_params_.search_window_size; + auto search_buffer_capacity = default_search_params_.search_buffer_capacity; + if (default_search_params_.prefetch_lookahead > 0) { + sp = sp.prefetch_lookahead(default_search_params_.prefetch_lookahead); + } + if (default_search_params_.prefetch_step > 0) { + sp = sp.prefetch_step(default_search_params_.prefetch_step); + } + + if (params != nullptr) { + if (params->search_window_size > 0) + search_window_size = params->search_window_size; + if (params->search_buffer_capacity > 0) + search_buffer_capacity = params->search_buffer_capacity; + if (params->prefetch_lookahead > 0) { + sp = sp.prefetch_lookahead(params->prefetch_lookahead); + } + if (params->prefetch_step > 0) { + sp = sp.prefetch_step(params->prefetch_step); + } + } + + return impl_->get_search_parameters().buffer_config( + {search_window_size, search_buffer_capacity} + ); + } + + template + svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + auto storage = make_storage(std::forward(tag), data, threadpool); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana(svs::DynamicVamana::build( + this->vamana_build_parameters(), + std::move(storage), + std::move(labels), + std::forward(distance), + std::move(threadpool) + )); + }); + } + + virtual void init_impl(data::ConstSimpleDataView data, std::span labels) { + impl_.reset(storage::dispatch_storage_kind( + get_storage_kind(), + [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + using Tag = std::decay_t; + return init_impl_t(std::forward(tag), metric, data, labels); + }, + metric_type_, + data, + labels + )); + } + + DynamicVamanaIndexImpl(std::unique_ptr&& impl, + MetricType metric, + StorageKind storage_kind) + : impl_{std::move(impl)} { + dim_ = impl_->dimensions(); + const auto& buffer_config = impl_->get_search_parameters().buffer_config_; + default_search_params_ = {buffer_config.get_search_window_size(), buffer_config.get_total_capacity()}; + metric_type_ = metric; + storage_kind_ = storage_kind; + build_params_ = { + impl_->get_graph_max_degree(), + impl_->get_prune_to(), + impl_->get_alpha(), + impl_->get_construction_window_size(), + impl_->get_max_candidates(), + impl_->get_full_search_history() + }; + } + + template + static svs::DynamicVamana* deserialize_impl_t(StorageTag&& SVS_UNUSED(tag), std::istream& stream, MetricType metric) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana( + svs::DynamicVamana::assemble>( + stream, std::forward(distance), std::move(threadpool) + ) + ); + }); + } + +public: + static DynamicVamanaIndexImpl* load(std::istream& stream, MetricType metric, StorageKind storage_kind) { + return storage::dispatch_storage_kind( + storage_kind, + [&](auto&& tag, std::istream& stream, MetricType metric) { + using Tag = std::decay_t; + std::unique_ptr impl{deserialize_impl_t(std::forward(tag), stream, metric)}; + + return new DynamicVamanaIndexImpl( + std::move(impl), + metric, + storage_kind + ); + }, + stream, + metric + ); + } + + // Data members +protected: + size_t dim_; + MetricType metric_type_; + StorageKind storage_kind_; + VamanaIndex::BuildParams build_params_; + VamanaIndex::SearchParams default_search_params_; + std::unique_ptr impl_; + size_t ntotal_soft_deleted{0}; +}; + +struct DynamicVamanaIndexLeanVecImplTrained : public DynamicVamanaIndexImpl { + using DynamicVamanaIndexImpl::DynamicVamanaIndexImpl; + DynamicVamanaIndexLeanVecImplTrained( + size_t dim, + MetricType metric, + StorageKind storage_kind, + const LeanVecTrainingDataImpl& training_data, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10,10} + ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params}, + training_data_{training_data} {} + + template + svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels, std::optional matrices) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + auto storage = make_storage(std::forward(tag), data, threadpool, 0, std::move(matrices)); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana(svs::DynamicVamana::build( + this->vamana_build_parameters(), + std::move(storage), + std::move(labels), + std::forward(distance), + std::move(threadpool) + )); + }); + } + + template + static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { + using SK = StorageKind; + using namespace svs::runtime::storage; + switch (kind) { + case SK::LeanVec4x4: + return f(LeanVec4x4Tag{}, std::forward(args)...); + case SK::LeanVec4x8: + return f(LeanVec4x8Tag{}, std::forward(args)...); + case SK::LeanVec8x8: + return f(LeanVec8x8Tag{}, std::forward(args)...); + default: + throw ANNEXCEPTION("not supported SVS leanvec storage kind"); + } + } + + void init_impl(data::ConstSimpleDataView data, std::span labels) override { + impl_.reset(DynamicVamanaIndexLeanVecImplTrained::dispatch_storage_kind( + this->storage_kind_, + [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + using Tag = std::decay_t; + return DynamicVamanaIndexLeanVecImplTrained::init_impl_t(std::forward(tag), metric, data, labels, training_data_.get_leanvec_matrices()); + }, + metric_type_, + data, + labels + )); + } + + LeanVecTrainingDataImpl training_data_; +}; + +struct DynamicVamanaIndexLeanVecImplDims : public DynamicVamanaIndexImpl { + using DynamicVamanaIndexImpl::DynamicVamanaIndexImpl; + DynamicVamanaIndexLeanVecImplDims( + size_t dim, + MetricType metric, + StorageKind storage_kind, + size_t leanvec_dims, + const VamanaIndex::BuildParams& params, + const VamanaIndex::SearchParams& default_search_params = {10,10} + ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params}, + leanvec_dims_{leanvec_dims} {} + + template + svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels, size_t leanvec_dims) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + auto storage = make_storage(std::forward(tag), data, threadpool, leanvec_dims); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + return new svs::DynamicVamana(svs::DynamicVamana::build( + this->vamana_build_parameters(), + std::move(storage), + std::move(labels), + std::forward(distance), + std::move(threadpool) + )); + }); + } + + template + static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { + using SK = StorageKind; + using namespace svs::runtime::storage; + switch (kind) { + case SK::LeanVec4x4: + return f(LeanVec4x4Tag{}, std::forward(args)...); + case SK::LeanVec4x8: + return f(LeanVec4x8Tag{}, std::forward(args)...); + case SK::LeanVec8x8: + return f(LeanVec8x8Tag{}, std::forward(args)...); + default: + throw ANNEXCEPTION("not supported SVS leanvec storage kind"); + } + } + + void init_impl(data::ConstSimpleDataView data, std::span labels) override { + impl_.reset(DynamicVamanaIndexLeanVecImplDims::dispatch_storage_kind( + this->storage_kind_, + [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + using Tag = std::decay_t; + return DynamicVamanaIndexLeanVecImplDims::init_impl_t(std::forward(tag), metric, data, labels, leanvec_dims_); + }, + metric_type_, + data, + labels + )); + } + + size_t leanvec_dims_; +}; + +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h new file mode 100644 index 00000000..cec62b13 --- /dev/null +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -0,0 +1,280 @@ +/* + * Copyright 2025 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 + +// TODO emplace content of IndexSVSImplUtils.h here +#include "IndexSVSImplUtils.h" + +// TODO remove unused includes +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SVS_LVQ_HEADER +#define SVS_LVQ_HEADER "svs/quantization/lvq/lvq.h" +#endif + +#ifndef SVS_LEANVEC_HEADER +#define SVS_LEANVEC_HEADER "svs/leanvec/leanvec.h" +#endif + +#include SVS_LVQ_HEADER +#include SVS_LEANVEC_HEADER + +namespace svs::runtime { + +class StatusException : public svs::lib::ANNException { + public: + StatusException(const svs::runtime::ErrorCode& code, const std::string& message) + : svs::lib::ANNException(message), errcode_{code} {} + + svs::runtime::ErrorCode code() const { return errcode_; } + + private: + svs::runtime::ErrorCode errcode_; +}; + +#define SVS_RUNTIME_TRY_BEGIN try { + +#define SVS_RUNTIME_TRY_END \ + } \ + catch (const svs::runtime::StatusException& ex) { \ + return svs::runtime::Status(ex.code(), ex.what()); \ + } \ + catch (const std::invalid_argument& ex) { \ + return svs::runtime::Status(svs::runtime::ErrorCode::INVALID_ARGUMENT, ex.what()); \ + } \ + catch (const std::runtime_error& ex) { \ + return svs::runtime::Status(svs::runtime::ErrorCode::RUNTIME_ERROR, ex.what()); \ + } \ + catch (const std::exception& ex) { \ + return svs::runtime::Status(svs::runtime::ErrorCode::UNKNOWN_ERROR, ex.what()); \ + } \ + catch (...) { \ + return svs::runtime::Status( \ + svs::runtime::ErrorCode::UNKNOWN_ERROR, "An unknown error has occurred." \ + ); \ + } + +using LeanVecMatricesType = svs::leanvec::LeanVecMatrices; + +namespace storage { + +template inline constexpr bool is_simple_dataset = false; +template < + typename Elem, + size_t Extent, + typename Allocator> +inline constexpr bool +is_simple_dataset> = true; + +template +concept IsSimpleDataset = is_simple_dataset; + +inline constexpr bool is_lvq_storage(StorageKind kind) { + return kind == StorageKind::LVQ4x0 || + kind == StorageKind::LVQ4x4 || + kind == StorageKind::LVQ4x8; +} + +template +concept IsLVQStorageKind = is_lvq_storage(K); + +inline constexpr bool is_leanvec_storage(StorageKind kind) { + return kind == StorageKind::LeanVec4x4 || + kind == StorageKind::LeanVec4x8 || + kind == StorageKind::LeanVec8x8; +} + +template +concept IsLeanVecStorageKind = is_leanvec_storage(K); + + +// Storage kind processing +// Most kinds map to std::byte storage, but some have specific element types. +// Storage kind tag types for function argument deduction +template struct StorageKindTag { + static constexpr StorageKind value = K; +}; + +using FP32Tag = StorageKindTag; +using FP16Tag = StorageKindTag; +using SQI8Tag = StorageKindTag; +using LVQ4x0Tag = StorageKindTag; +using LVQ4x4Tag = StorageKindTag; +using LVQ4x8Tag = StorageKindTag; +using LeanVec4x4Tag = StorageKindTag; +using LeanVec4x8Tag = StorageKindTag; +using LeanVec8x8Tag = StorageKindTag; + +// Storage types +template +using SimpleDatasetType = svs::data::SimpleData< + T, + svs::Dynamic, + svs::data::Blocked>>; + +template +using SQDatasetType = svs::quantization::scalar::SQDataset< + T, + svs::Dynamic, + svs::data::Blocked>>; + +template +using LVQDatasetType = svs::quantization::lvq::LVQDataset< + Primary, + Residual, + svs::Dynamic, + svs::quantization::lvq::Turbo<16, 8>, + svs::data::Blocked>>; + +template +using LeanDatasetType = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ, + svs::leanvec::UsingLVQ, + svs::Dynamic, + svs::Dynamic, + svs::data::Blocked>>; + +template struct StorageType; +template using StorageType_t = typename StorageType::type; + +template <> +struct StorageType { + using type = SimpleDatasetType; +}; + +template <> +struct StorageType { + using type = SimpleDatasetType; +}; + +template <> +struct StorageType { + using type = SQDatasetType; +}; + + +template <> +struct StorageType { + using type = LVQDatasetType<4,0>; +}; + +template <> +struct StorageType { + using type = LVQDatasetType<4,4>; +}; + +template <> +struct StorageType { + using type = LVQDatasetType<4,8>; +}; + +template <> +struct StorageType { + using type = LeanDatasetType<4,4>; +}; + +template <> +struct StorageType { + using type = LeanDatasetType<4,8>; +}; + +template <> +struct StorageType { + using type = LeanDatasetType<8,8>; +}; + +template +StorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { + StorageType result(data.size(), data.dimensions()); + svs::threads::parallel_for( + pool, + svs::threads::StaticPartition(result.size()), + [&](auto is, auto SVS_UNUSED(tid)) { + for (auto i : is) { + result.set_datum(i, data.get_datum(i)); + } + } + ); + return result; +} + +template +SQStorageType +make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { + return SQStorageType::compress(data, pool); +} + +template +LVQStorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { + return LVQStorageType::compress(data, pool, 0); +} + +template +LeanVecStorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool, size_t leanvec_d = 0, std::optional matrices = std::nullopt) { + if (leanvec_d == 0) { + leanvec_d = (data.dimensions() + 1) / 2; + } + return LeanVecStorageType::reduce(data, std::move(matrices), pool, 0, svs::lib::MaybeStatic{leanvec_d}); +} + +template +auto make_storage(StorageTag&& SVS_UNUSED(tag), Args&&... args) { + return make_storage>(std::forward(args)...); +} + +template +static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { + using SK = StorageKind; + switch (kind) { + case SK::FP32: + return f(FP32Tag{}, std::forward(args)...); + case SK::FP16: + return f(FP16Tag{}, std::forward(args)...); + case SK::SQI8: + return f(SQI8Tag{}, std::forward(args)...); + case SK::LVQ4x0: + return f(LVQ4x0Tag{}, std::forward(args)...); + case SK::LVQ4x4: + return f(LVQ4x4Tag{}, std::forward(args)...); + case SK::LVQ4x8: + return f(LVQ4x8Tag{}, std::forward(args)...); + case SK::LeanVec4x4: + return f(LeanVec4x4Tag{}, std::forward(args)...); + case SK::LeanVec4x8: + return f(LeanVec4x8Tag{}, std::forward(args)...); + case SK::LeanVec8x8: + return f(LeanVec8x8Tag{}, std::forward(args)...); + default: + throw ANNEXCEPTION("not supported SVS storage kind"); + } +} + +} // namespace storage +} // namespace svs::runtime diff --git a/bindings/cpp/src/training.cpp b/bindings/cpp/src/training.cpp new file mode 100644 index 00000000..d04a133b --- /dev/null +++ b/bindings/cpp/src/training.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2025 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. + */ + +#include "svs_runtime_utils.h" +#include "training.h" +#include "training_impl.h" + +namespace svs { +namespace runtime { + +Status LeanVecTrainingData::build( + LeanVecTrainingData** training_data, + size_t dim, + size_t n, + const float* x, + size_t leanvec_dims + ) noexcept { + SVS_RUNTIME_TRY_BEGIN + const auto data = svs::data::ConstSimpleDataView(x, n, dim); + *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl{data, leanvec_dims}}; + return Status_Ok; + SVS_RUNTIME_TRY_END + } + +Status LeanVecTrainingData::destroy(LeanVecTrainingData* training_data) noexcept { + SVS_RUNTIME_TRY_BEGIN + delete training_data; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +Status LeanVecTrainingData::load(LeanVecTrainingData** training_data, std::istream& in) noexcept { + SVS_RUNTIME_TRY_BEGIN + *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl::load(in)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h new file mode 100644 index 00000000..4eed2247 --- /dev/null +++ b/bindings/cpp/src/training_impl.h @@ -0,0 +1,100 @@ +/* + * Copyright 2025 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_utils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include SVS_LVQ_HEADER +#include SVS_LEANVEC_HEADER + +namespace svs { +namespace runtime { + +struct LeanVecTrainingDataImpl { + LeanVecTrainingDataImpl(LeanVecMatricesType&& matrices) + : leanvec_matrices{std::move(matrices)} + {} + + LeanVecTrainingDataImpl(const svs::data::ConstSimpleDataView& data, size_t leanvec_dims) + : LeanVecTrainingDataImpl{compute_leanvec_matrices(data, leanvec_dims)} + {} + + const LeanVecMatricesType& get_leanvec_matrices() const { + return leanvec_matrices; + } + + void save(std::ostream& out) const { + lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_save"}; + svs::lib::save_to_disk(leanvec_matrices, tempdir); + lib::DirectoryArchiver::pack(tempdir, out); + } + + static LeanVecTrainingDataImpl load(std::istream& in) { + lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_load"};\ + lib::DirectoryArchiver::unpack(in, tempdir); + return LeanVecTrainingDataImpl{svs::lib::load_from_disk(tempdir)}; + } + +private: + LeanVecMatricesType leanvec_matrices; + + static LeanVecMatricesType compute_leanvec_matrices( + const svs::data::ConstSimpleDataView& data, + size_t leanvec_dims + ) { + auto threadpool = + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + + auto means = svs::utils::compute_medioid(data, threadpool); + auto matrix = svs::leanvec::compute_leanvec_matrix( + data, means, threadpool, svs::lib::MaybeStatic{leanvec_dims} + ); + return LeanVecMatricesType{matrix, matrix}; + } +}; + +struct LeanVecTrainingDataManager : public svs::runtime::LeanVecTrainingData { + LeanVecTrainingDataImpl impl_; + + LeanVecTrainingDataManager(LeanVecTrainingDataImpl impl) + : impl_{std::move(impl)} + {} + + Status save(std::ostream& out) const noexcept override { + SVS_RUNTIME_TRY_BEGIN + impl_.save(out); + return Status_Ok; + SVS_RUNTIME_TRY_END + } +}; + +} // namespace runtime +} // namespace svs diff --git a/include/svs/extensions/vamana/scalar.h b/include/svs/extensions/vamana/scalar.h index 94948ee4..4842f2e6 100644 --- a/include/svs/extensions/vamana/scalar.h +++ b/include/svs/extensions/vamana/scalar.h @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#pragma once #include "svs/index/vamana/extensions.h" #include "svs/quantization/scalar/scalar.h" From 696519b10d3cb295101d2a3d5f7b06b6911f7013 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Wed, 5 Nov 2025 12:59:06 -0800 Subject: [PATCH 24/46] Add faiss c++ tests --- docker/x86_64/test-cpp-runtime-bindings.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/x86_64/test-cpp-runtime-bindings.sh b/docker/x86_64/test-cpp-runtime-bindings.sh index fa890bd2..4aac89de 100644 --- a/docker/x86_64/test-cpp-runtime-bindings.sh +++ b/docker/x86_64/test-cpp-runtime-bindings.sh @@ -43,7 +43,10 @@ echo " FAISS Build: " mkdir build && cd build # TODO: create conda env cmake -DBUILD_TESTING=ON -DFAISS_ENABLE_SVS=ON -DFAISS_ENABLE_GPU=OFF .. -make -j swigfaiss +make -j swigfaiss faiss_test +echo "------------------------------------------------" +echo " FAISS C++ tests: " +./tests/faiss_test --gtest_filter=SVS.* echo "------------------------------------------------" echo " FAISS python bindings: " cd faiss/python/ From 5b2707e0b3bba1c788af807a37da1ef7b6864661 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 6 Nov 2025 10:30:35 +0100 Subject: [PATCH 25/46] Apply formatting --- bindings/cpp/include/IndexSVSImplDefs.h | 14 +- bindings/cpp/include/dynamic_vamana_index.h | 13 +- bindings/cpp/src/dynamic_vamana_index.cpp | 104 ++++++----- bindings/cpp/src/dynamic_vamana_index_impl.h | 181 ++++++++++++------- bindings/cpp/src/svs_runtime_utils.h | 102 +++++------ bindings/cpp/src/training.cpp | 34 ++-- bindings/cpp/src/training_impl.h | 36 ++-- 7 files changed, 278 insertions(+), 206 deletions(-) diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index da827bb7..289ee8f8 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -31,10 +31,16 @@ namespace svs { namespace runtime { enum class MetricType { L2, INNER_PRODUCT }; -enum class StorageKind { - FP32, FP16, SQI8, - LVQ4x0, LVQ4x4, LVQ4x8, - LeanVec4x4, LeanVec4x8, LeanVec8x8, +enum class StorageKind { + FP32, + FP16, + SQI8, + LVQ4x0, + LVQ4x4, + LVQ4x8, + LeanVec4x4, + LeanVec4x8, + LeanVec8x8, }; enum class ErrorCode { diff --git a/bindings/cpp/include/dynamic_vamana_index.h b/bindings/cpp/include/dynamic_vamana_index.h index 584c5bdb..50c4ceb8 100644 --- a/bindings/cpp/include/dynamic_vamana_index.h +++ b/bindings/cpp/include/dynamic_vamana_index.h @@ -16,8 +16,8 @@ #pragma once #include "IndexSVSImplDefs.h" -#include "vamana_index.h" #include "training.h" +#include "vamana_index.h" #include #include @@ -28,9 +28,9 @@ namespace runtime { // Abstract interface for Dynamic Vamana-based indexes. struct SVS_RUNTIME_API DynamicVamanaIndex : public VamanaIndex { - virtual Status add(size_t n, const size_t* labels, const float* x) noexcept = 0; - virtual Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0; + virtual Status + remove_selected(size_t* num_removed, const IDFilter& selector) noexcept = 0; virtual Status remove(size_t n, const size_t* labels) noexcept = 0; virtual Status reset() noexcept = 0; @@ -48,7 +48,12 @@ struct SVS_RUNTIME_API DynamicVamanaIndex : public VamanaIndex { static Status destroy(DynamicVamanaIndex* index) noexcept; virtual Status save(std::ostream& out) const noexcept = 0; - static Status load(DynamicVamanaIndex** index, std::istream& in, MetricType metric, StorageKind storage_kind) noexcept; + static Status load( + DynamicVamanaIndex** index, + std::istream& in, + MetricType metric, + StorageKind storage_kind + ) noexcept; }; struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex { diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index 92edb4c4..507c11e5 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -15,8 +15,8 @@ */ #include "dynamic_vamana_index.h" -#include "svs_runtime_utils.h" #include "dynamic_vamana_index_impl.h" +#include "svs_runtime_utils.h" #include #include @@ -26,10 +26,10 @@ #include #include #include -#include -#include #include #include +#include +#include #include #include SVS_LVQ_HEADER @@ -45,8 +45,8 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { DynamicVamanaIndexManagerBase(std::unique_ptr impl) : impl_{std::move(impl)} { - assert(impl_ != nullptr); - } + assert(impl_ != nullptr); + } DynamicVamanaIndexManagerBase(const DynamicVamanaIndexManagerBase&) = delete; DynamicVamanaIndexManagerBase& operator=(const DynamicVamanaIndexManagerBase&) = delete; @@ -56,25 +56,26 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { Status add(size_t n, const size_t* labels, const float* x) noexcept override { SVS_RUNTIME_TRY_BEGIN - svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; - std::span lbls(labels, n); - impl_->add(data, lbls); - return Status_Ok; + svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; + std::span lbls(labels, n); + impl_->add(data, lbls); + return Status_Ok; SVS_RUNTIME_TRY_END } - Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept override { + Status + remove_selected(size_t* num_removed, const IDFilter& selector) noexcept override { SVS_RUNTIME_TRY_BEGIN - *num_removed = impl_->remove_selected(selector); - return Status_Ok; + *num_removed = impl_->remove_selected(selector); + return Status_Ok; SVS_RUNTIME_TRY_END } Status remove(size_t n, const size_t* labels) noexcept override { SVS_RUNTIME_TRY_BEGIN - std::span lbls(labels, n); - impl_->remove(lbls); - return Status_Ok; + std::span lbls(labels, n); + impl_->remove(lbls); + return Status_Ok; SVS_RUNTIME_TRY_END } @@ -88,9 +89,10 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { IDFilter* filter = nullptr ) const noexcept override { SVS_RUNTIME_TRY_BEGIN - // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and here - impl_->search(n, x, k, distances, labels, params, filter); - return Status_Ok; + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and + // here + impl_->search(n, x, k, distances, labels, params, filter); + return Status_Ok; SVS_RUNTIME_TRY_END } @@ -103,30 +105,33 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { IDFilter* filter = nullptr ) const noexcept override { SVS_RUNTIME_TRY_BEGIN - // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and here - impl_->range_search(n, x, radius, results, params, filter); - return Status_Ok; + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and + // here + impl_->range_search(n, x, radius, results, params, filter); + return Status_Ok; SVS_RUNTIME_TRY_END } Status reset() noexcept override { SVS_RUNTIME_TRY_BEGIN - impl_->reset(); - return Status_Ok; + impl_->reset(); + return Status_Ok; SVS_RUNTIME_TRY_END } Status save(std::ostream& out) const noexcept override { SVS_RUNTIME_TRY_BEGIN - impl_->save(out); - return Status_Ok; + impl_->save(out); + return Status_Ok; SVS_RUNTIME_TRY_END } }; using DynamicVamanaIndexManager = DynamicVamanaIndexManagerBase; -using DynamicVamanaIndexLeanVecImplTrainedManager = DynamicVamanaIndexManagerBase; -using DynamicVamanaIndexLeanVecImplDimsManager = DynamicVamanaIndexManagerBase; +using DynamicVamanaIndexLeanVecImplTrainedManager = + DynamicVamanaIndexManagerBase; +using DynamicVamanaIndexLeanVecImplDimsManager = + DynamicVamanaIndexManagerBase; } // namespace @@ -140,25 +145,33 @@ Status DynamicVamanaIndex::build( ) noexcept { *index = nullptr; SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique(dim, metric, storage_kind, params, default_search_params); - *index = new DynamicVamanaIndexManager{std::move(impl)}; - return Status_Ok; + auto impl = std::make_unique( + dim, metric, storage_kind, params, default_search_params + ); + *index = new DynamicVamanaIndexManager{std::move(impl)}; + return Status_Ok; SVS_RUNTIME_TRY_END } Status DynamicVamanaIndex::destroy(DynamicVamanaIndex* index) noexcept { SVS_RUNTIME_TRY_BEGIN - delete index; - return Status_Ok; + delete index; + return Status_Ok; SVS_RUNTIME_TRY_END } -Status DynamicVamanaIndex::load(DynamicVamanaIndex** index, std::istream& in, MetricType metric, StorageKind storage_kind) noexcept { +Status DynamicVamanaIndex::load( + DynamicVamanaIndex** index, + std::istream& in, + MetricType metric, + StorageKind storage_kind +) noexcept { *index = nullptr; SVS_RUNTIME_TRY_BEGIN - std::unique_ptr impl{DynamicVamanaIndexImpl::load(in, metric, storage_kind)}; - *index = new DynamicVamanaIndexManager{std::move(impl)}; - return Status_Ok; + std::unique_ptr impl{ + DynamicVamanaIndexImpl::load(in, metric, storage_kind)}; + *index = new DynamicVamanaIndexManager{std::move(impl)}; + return Status_Ok; SVS_RUNTIME_TRY_END } @@ -174,9 +187,11 @@ Status DynamicVamanaIndexLeanVec::build( ) noexcept { *index = nullptr; SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique(dim, metric, storage_kind, leanvec_dims, params, default_search_params); - *index = new DynamicVamanaIndexLeanVecImplDimsManager{std::move(impl)}; - return Status_Ok; + auto impl = std::make_unique( + dim, metric, storage_kind, leanvec_dims, params, default_search_params + ); + *index = new DynamicVamanaIndexLeanVecImplDimsManager{std::move(impl)}; + return Status_Ok; SVS_RUNTIME_TRY_END } @@ -192,10 +207,13 @@ Status DynamicVamanaIndexLeanVec::build( ) noexcept { *index = nullptr; SVS_RUNTIME_TRY_BEGIN - auto training_data_impl = static_cast(training_data)->impl_; - auto impl = std::make_unique(dim, metric, storage_kind, training_data_impl, params, default_search_params); - *index = new DynamicVamanaIndexLeanVecImplTrainedManager{std::move(impl)}; - return Status_Ok; + auto training_data_impl = + static_cast(training_data)->impl_; + auto impl = std::make_unique( + dim, metric, storage_kind, training_data_impl, params, default_search_params + ); + *index = new DynamicVamanaIndexLeanVecImplTrainedManager{std::move(impl)}; + return Status_Ok; SVS_RUNTIME_TRY_END } diff --git a/bindings/cpp/src/dynamic_vamana_index_impl.h b/bindings/cpp/src/dynamic_vamana_index_impl.h index 3f1cd063..92789dbc 100644 --- a/bindings/cpp/src/dynamic_vamana_index_impl.h +++ b/bindings/cpp/src/dynamic_vamana_index_impl.h @@ -16,9 +16,9 @@ #pragma once +#include "dynamic_vamana_index.h" #include "svs_runtime_utils.h" #include "training_impl.h" -#include "dynamic_vamana_index.h" #include #include @@ -28,10 +28,10 @@ #include #include #include -#include -#include #include #include +#include +#include #include #include SVS_LVQ_HEADER @@ -49,15 +49,16 @@ class DynamicVamanaIndexImpl { StorageKind storage_kind, const VamanaIndex::BuildParams& params, const VamanaIndex::SearchParams& default_search_params - ) : dim_{dim}, - metric_type_{metric}, - storage_kind_{storage_kind}, - build_params_{params}, - default_search_params_{default_search_params} { + ) + : dim_{dim} + , metric_type_{metric} + , storage_kind_{storage_kind} + , build_params_{params} + , default_search_params_{default_search_params} { if (build_params_.prune_to == 0) { - build_params_.prune_to = - build_params_.graph_max_degree < 4 ? build_params_.graph_max_degree - : build_params_.graph_max_degree - 4; + build_params_.prune_to = build_params_.graph_max_degree < 4 + ? build_params_.graph_max_degree + : build_params_.graph_max_degree - 4; } if (build_params_.alpha == 0) { build_params_.alpha = metric == MetricType::L2 ? 1.2f : 0.95f; @@ -72,7 +73,6 @@ class DynamicVamanaIndexImpl { StorageKind get_storage_kind() const { return storage_kind_; } - void add(data::ConstSimpleDataView data, std::span labels) { if (!impl_) { return init_impl(data, labels); @@ -172,7 +172,8 @@ class DynamicVamanaIndexImpl { throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; } if (radius <= 0) { - throw StatusException{ErrorCode::INVALID_ARGUMENT, "radius must be greater than 0"}; + throw StatusException{ + ErrorCode::INVALID_ARGUMENT, "radius must be greater than 0"}; } auto sp = make_search_parameters(params); @@ -295,9 +296,12 @@ class DynamicVamanaIndexImpl { auto ids = impl_->all_ids(); std::vector ids_to_delete; - std::copy_if(ids.begin(), ids.end(), std::back_inserter(ids_to_delete), [&](size_t id) { - return selector(id); - }); + std::copy_if( + ids.begin(), + ids.end(), + std::back_inserter(ids_to_delete), + [&](size_t id) { return selector(id); } + ); return remove(ids_to_delete); } @@ -316,7 +320,7 @@ class DynamicVamanaIndexImpl { impl_->save(out); } -protected: + protected: // Utility functions svs::index::vamana::VamanaBuildParameters vamana_build_parameters() const { return svs::index::vamana::VamanaBuildParameters{ @@ -328,9 +332,8 @@ class DynamicVamanaIndexImpl { build_params_.use_full_search_history}; } - svs::index::vamana::VamanaSearchParameters make_search_parameters( - const VamanaIndex::SearchParams* params - ) const { + svs::index::vamana::VamanaSearchParameters + make_search_parameters(const VamanaIndex::SearchParams* params) const { if (!impl_) { throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; } @@ -365,9 +368,15 @@ class DynamicVamanaIndexImpl { } template - svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels) { + svs::DynamicVamana* init_impl_t( + Tag&& tag, + MetricType metric, + const svs::data::ConstSimpleDataView& data, + std::span labels + ) { auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() + )); auto storage = make_storage(std::forward(tag), data, threadpool); @@ -383,10 +392,16 @@ class DynamicVamanaIndexImpl { }); } - virtual void init_impl(data::ConstSimpleDataView data, std::span labels) { + virtual void + init_impl(data::ConstSimpleDataView data, std::span labels) { impl_.reset(storage::dispatch_storage_kind( get_storage_kind(), - [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + [this]( + auto&& tag, + MetricType metric, + data::ConstSimpleDataView data, + std::span labels + ) { using Tag = std::decay_t; return init_impl_t(std::forward(tag), metric, data, labels); }, @@ -396,13 +411,16 @@ class DynamicVamanaIndexImpl { )); } - DynamicVamanaIndexImpl(std::unique_ptr&& impl, + DynamicVamanaIndexImpl( + std::unique_ptr&& impl, MetricType metric, - StorageKind storage_kind) - : impl_{std::move(impl)} { + StorageKind storage_kind + ) + : impl_{std::move(impl)} { dim_ = impl_->dimensions(); const auto& buffer_config = impl_->get_search_parameters().buffer_config_; - default_search_params_ = {buffer_config.get_search_window_size(), buffer_config.get_total_capacity()}; + default_search_params_ = { + buffer_config.get_search_window_size(), buffer_config.get_total_capacity()}; metric_type_ = metric; storage_kind_ = storage_kind; build_params_ = { @@ -411,38 +429,38 @@ class DynamicVamanaIndexImpl { impl_->get_alpha(), impl_->get_construction_window_size(), impl_->get_max_candidates(), - impl_->get_full_search_history() - }; + impl_->get_full_search_history()}; } template - static svs::DynamicVamana* deserialize_impl_t(StorageTag&& SVS_UNUSED(tag), std::istream& stream, MetricType metric) { + static svs::DynamicVamana* deserialize_impl_t( + StorageTag&& SVS_UNUSED(tag), std::istream& stream, MetricType metric + ) { auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() + )); svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::assemble>( - stream, std::forward(distance), std::move(threadpool) - ) - ); + return new svs::DynamicVamana(svs::DynamicVamana::assemble< + float, + storage::StorageType_t>( + stream, std::forward(distance), std::move(threadpool) + )); }); } -public: - static DynamicVamanaIndexImpl* load(std::istream& stream, MetricType metric, StorageKind storage_kind) { + public: + static DynamicVamanaIndexImpl* + load(std::istream& stream, MetricType metric, StorageKind storage_kind) { return storage::dispatch_storage_kind( storage_kind, [&](auto&& tag, std::istream& stream, MetricType metric) { using Tag = std::decay_t; - std::unique_ptr impl{deserialize_impl_t(std::forward(tag), stream, metric)}; + std::unique_ptr impl{ + deserialize_impl_t(std::forward(tag), stream, metric)}; - return new DynamicVamanaIndexImpl( - std::move(impl), - metric, - storage_kind - ); + return new DynamicVamanaIndexImpl(std::move(impl), metric, storage_kind); }, stream, metric @@ -450,7 +468,7 @@ class DynamicVamanaIndexImpl { } // Data members -protected: + protected: size_t dim_; MetricType metric_type_; StorageKind storage_kind_; @@ -468,16 +486,25 @@ struct DynamicVamanaIndexLeanVecImplTrained : public DynamicVamanaIndexImpl { StorageKind storage_kind, const LeanVecTrainingDataImpl& training_data, const VamanaIndex::BuildParams& params, - const VamanaIndex::SearchParams& default_search_params = {10,10} - ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params}, - training_data_{training_data} {} + const VamanaIndex::SearchParams& default_search_params = {10, 10} + ) + : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params} + , training_data_{training_data} {} template - svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels, std::optional matrices) { + svs::DynamicVamana* init_impl_t( + Tag&& tag, + MetricType metric, + const svs::data::ConstSimpleDataView& data, + std::span labels, + std::optional matrices + ) { auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() + )); - auto storage = make_storage(std::forward(tag), data, threadpool, 0, std::move(matrices)); + auto storage = + make_storage(std::forward(tag), data, threadpool, 0, std::move(matrices)); svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { @@ -507,12 +534,24 @@ struct DynamicVamanaIndexLeanVecImplTrained : public DynamicVamanaIndexImpl { } } - void init_impl(data::ConstSimpleDataView data, std::span labels) override { + void init_impl(data::ConstSimpleDataView data, std::span labels) + override { impl_.reset(DynamicVamanaIndexLeanVecImplTrained::dispatch_storage_kind( this->storage_kind_, - [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + [this]( + auto&& tag, + MetricType metric, + data::ConstSimpleDataView data, + std::span labels + ) { using Tag = std::decay_t; - return DynamicVamanaIndexLeanVecImplTrained::init_impl_t(std::forward(tag), metric, data, labels, training_data_.get_leanvec_matrices()); + return DynamicVamanaIndexLeanVecImplTrained::init_impl_t( + std::forward(tag), + metric, + data, + labels, + training_data_.get_leanvec_matrices() + ); }, metric_type_, data, @@ -531,14 +570,22 @@ struct DynamicVamanaIndexLeanVecImplDims : public DynamicVamanaIndexImpl { StorageKind storage_kind, size_t leanvec_dims, const VamanaIndex::BuildParams& params, - const VamanaIndex::SearchParams& default_search_params = {10,10} - ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params}, - leanvec_dims_{leanvec_dims} {} + const VamanaIndex::SearchParams& default_search_params = {10, 10} + ) + : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params} + , leanvec_dims_{leanvec_dims} {} template - svs::DynamicVamana* init_impl_t(Tag&& tag, MetricType metric, const svs::data::ConstSimpleDataView& data, std::span labels, size_t leanvec_dims) { + svs::DynamicVamana* init_impl_t( + Tag&& tag, + MetricType metric, + const svs::data::ConstSimpleDataView& data, + std::span labels, + size_t leanvec_dims + ) { auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() + )); auto storage = make_storage(std::forward(tag), data, threadpool, leanvec_dims); @@ -570,12 +617,20 @@ struct DynamicVamanaIndexLeanVecImplDims : public DynamicVamanaIndexImpl { } } - void init_impl(data::ConstSimpleDataView data, std::span labels) override { + void init_impl(data::ConstSimpleDataView data, std::span labels) + override { impl_.reset(DynamicVamanaIndexLeanVecImplDims::dispatch_storage_kind( this->storage_kind_, - [this](auto&& tag, MetricType metric, data::ConstSimpleDataView data, std::span labels) { + [this]( + auto&& tag, + MetricType metric, + data::ConstSimpleDataView data, + std::span labels + ) { using Tag = std::decay_t; - return DynamicVamanaIndexLeanVecImplDims::init_impl_t(std::forward(tag), metric, data, labels, leanvec_dims_); + return DynamicVamanaIndexLeanVecImplDims::init_impl_t( + std::forward(tag), metric, data, labels, leanvec_dims_ + ); }, metric_type_, data, diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index cec62b13..b61892e1 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -28,11 +28,11 @@ #include #include #include +#include +#include #include #include #include -#include -#include #include #ifndef SVS_LVQ_HEADER @@ -51,7 +51,8 @@ namespace svs::runtime { class StatusException : public svs::lib::ANNException { public: StatusException(const svs::runtime::ErrorCode& code, const std::string& message) - : svs::lib::ANNException(message), errcode_{code} {} + : svs::lib::ANNException(message) + , errcode_{code} {} svs::runtime::ErrorCode code() const { return errcode_; } @@ -60,10 +61,9 @@ class StatusException : public svs::lib::ANNException { }; #define SVS_RUNTIME_TRY_BEGIN try { - #define SVS_RUNTIME_TRY_END \ } \ - catch (const svs::runtime::StatusException& ex) { \ + catch (const svs::runtime::StatusException& ex) { \ return svs::runtime::Status(ex.code(), ex.what()); \ } \ catch (const std::invalid_argument& ex) { \ @@ -86,19 +86,15 @@ using LeanVecMatricesType = svs::leanvec::LeanVecMatrices; namespace storage { template inline constexpr bool is_simple_dataset = false; -template < - typename Elem, - size_t Extent, - typename Allocator> -inline constexpr bool -is_simple_dataset> = true; +template +inline constexpr bool is_simple_dataset> = + true; template concept IsSimpleDataset = is_simple_dataset; inline constexpr bool is_lvq_storage(StorageKind kind) { - return kind == StorageKind::LVQ4x0 || - kind == StorageKind::LVQ4x4 || + return kind == StorageKind::LVQ4x0 || kind == StorageKind::LVQ4x4 || kind == StorageKind::LVQ4x8; } @@ -106,15 +102,13 @@ template concept IsLVQStorageKind = is_lvq_storage(K); inline constexpr bool is_leanvec_storage(StorageKind kind) { - return kind == StorageKind::LeanVec4x4 || - kind == StorageKind::LeanVec4x8 || + return kind == StorageKind::LeanVec4x4 || kind == StorageKind::LeanVec4x8 || kind == StorageKind::LeanVec8x8; } template concept IsLeanVecStorageKind = is_leanvec_storage(K); - // Storage kind processing // Most kinds map to std::byte storage, but some have specific element types. // Storage kind tag types for function argument deduction @@ -134,16 +128,12 @@ using LeanVec8x8Tag = StorageKindTag; // Storage types template -using SimpleDatasetType = svs::data::SimpleData< - T, - svs::Dynamic, - svs::data::Blocked>>; +using SimpleDatasetType = + svs::data::SimpleData>>; template -using SQDatasetType = svs::quantization::scalar::SQDataset< - T, - svs::Dynamic, - svs::data::Blocked>>; +using SQDatasetType = svs::quantization::scalar:: + SQDataset>>; template using LVQDatasetType = svs::quantization::lvq::LVQDataset< @@ -164,53 +154,43 @@ using LeanDatasetType = svs::leanvec::LeanDataset< template struct StorageType; template using StorageType_t = typename StorageType::type; -template <> -struct StorageType { +template <> struct StorageType { using type = SimpleDatasetType; }; -template <> -struct StorageType { +template <> struct StorageType { using type = SimpleDatasetType; }; -template <> -struct StorageType { +template <> struct StorageType { using type = SQDatasetType; }; - -template <> -struct StorageType { - using type = LVQDatasetType<4,0>; +template <> struct StorageType { + using type = LVQDatasetType<4, 0>; }; -template <> -struct StorageType { - using type = LVQDatasetType<4,4>; +template <> struct StorageType { + using type = LVQDatasetType<4, 4>; }; -template <> -struct StorageType { - using type = LVQDatasetType<4,8>; +template <> struct StorageType { + using type = LVQDatasetType<4, 8>; }; -template <> -struct StorageType { - using type = LeanDatasetType<4,4>; +template <> struct StorageType { + using type = LeanDatasetType<4, 4>; }; -template <> -struct StorageType { - using type = LeanDatasetType<4,8>; +template <> struct StorageType { + using type = LeanDatasetType<4, 8>; }; -template <> -struct StorageType { - using type = LeanDatasetType<8,8>; +template <> struct StorageType { + using type = LeanDatasetType<8, 8>; }; -template +template StorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { StorageType result(data.size(), data.dimensions()); svs::threads::parallel_for( @@ -225,23 +205,31 @@ StorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool return result; } -template -SQStorageType -make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { +template +SQStorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { return SQStorageType::compress(data, pool); } -template +template < + svs::quantization::lvq::IsLVQDataset LVQStorageType, + svs::threads::ThreadPool Pool> LVQStorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { return LVQStorageType::compress(data, pool, 0); } -template -LeanVecStorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool, size_t leanvec_d = 0, std::optional matrices = std::nullopt) { +template +LeanVecStorageType make_storage( + const svs::data::ConstSimpleDataView& data, + Pool& pool, + size_t leanvec_d = 0, + std::optional matrices = std::nullopt +) { if (leanvec_d == 0) { leanvec_d = (data.dimensions() + 1) / 2; } - return LeanVecStorageType::reduce(data, std::move(matrices), pool, 0, svs::lib::MaybeStatic{leanvec_d}); + return LeanVecStorageType::reduce( + data, std::move(matrices), pool, 0, svs::lib::MaybeStatic{leanvec_d} + ); } template diff --git a/bindings/cpp/src/training.cpp b/bindings/cpp/src/training.cpp index d04a133b..a6680b2d 100644 --- a/bindings/cpp/src/training.cpp +++ b/bindings/cpp/src/training.cpp @@ -14,38 +14,40 @@ * limitations under the License. */ -#include "svs_runtime_utils.h" #include "training.h" +#include "svs_runtime_utils.h" #include "training_impl.h" namespace svs { namespace runtime { Status LeanVecTrainingData::build( - LeanVecTrainingData** training_data, - size_t dim, - size_t n, - const float* x, - size_t leanvec_dims - ) noexcept { + LeanVecTrainingData** training_data, + size_t dim, + size_t n, + const float* x, + size_t leanvec_dims +) noexcept { SVS_RUNTIME_TRY_BEGIN - const auto data = svs::data::ConstSimpleDataView(x, n, dim); - *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl{data, leanvec_dims}}; - return Status_Ok; + const auto data = svs::data::ConstSimpleDataView(x, n, dim); + *training_data = + new LeanVecTrainingDataManager{LeanVecTrainingDataImpl{data, leanvec_dims}}; + return Status_Ok; SVS_RUNTIME_TRY_END - } +} Status LeanVecTrainingData::destroy(LeanVecTrainingData* training_data) noexcept { SVS_RUNTIME_TRY_BEGIN - delete training_data; - return Status_Ok; + delete training_data; + return Status_Ok; SVS_RUNTIME_TRY_END } -Status LeanVecTrainingData::load(LeanVecTrainingData** training_data, std::istream& in) noexcept { +Status +LeanVecTrainingData::load(LeanVecTrainingData** training_data, std::istream& in) noexcept { SVS_RUNTIME_TRY_BEGIN - *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl::load(in)}; - return Status_Ok; + *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl::load(in)}; + return Status_Ok; SVS_RUNTIME_TRY_END } } // namespace runtime diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h index 4eed2247..805b4007 100644 --- a/bindings/cpp/src/training_impl.h +++ b/bindings/cpp/src/training_impl.h @@ -14,7 +14,7 @@ * limitations under the License. */ - #pragma once +#pragma once #include "svs_runtime_utils.h" @@ -26,10 +26,10 @@ #include #include #include -#include -#include #include #include +#include +#include #include #include SVS_LVQ_HEADER @@ -40,16 +40,14 @@ namespace runtime { struct LeanVecTrainingDataImpl { LeanVecTrainingDataImpl(LeanVecMatricesType&& matrices) - : leanvec_matrices{std::move(matrices)} - {} + : leanvec_matrices{std::move(matrices)} {} - LeanVecTrainingDataImpl(const svs::data::ConstSimpleDataView& data, size_t leanvec_dims) - : LeanVecTrainingDataImpl{compute_leanvec_matrices(data, leanvec_dims)} - {} + LeanVecTrainingDataImpl( + const svs::data::ConstSimpleDataView& data, size_t leanvec_dims + ) + : LeanVecTrainingDataImpl{compute_leanvec_matrices(data, leanvec_dims)} {} - const LeanVecMatricesType& get_leanvec_matrices() const { - return leanvec_matrices; - } + const LeanVecMatricesType& get_leanvec_matrices() const { return leanvec_matrices; } void save(std::ostream& out) const { lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_save"}; @@ -58,20 +56,21 @@ struct LeanVecTrainingDataImpl { } static LeanVecTrainingDataImpl load(std::istream& in) { - lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_load"};\ + lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_load"}; lib::DirectoryArchiver::unpack(in, tempdir); - return LeanVecTrainingDataImpl{svs::lib::load_from_disk(tempdir)}; + return LeanVecTrainingDataImpl{ + svs::lib::load_from_disk(tempdir)}; } -private: + private: LeanVecMatricesType leanvec_matrices; static LeanVecMatricesType compute_leanvec_matrices( - const svs::data::ConstSimpleDataView& data, - size_t leanvec_dims + const svs::data::ConstSimpleDataView& data, size_t leanvec_dims ) { auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); + svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() + )); auto means = svs::utils::compute_medioid(data, threadpool); auto matrix = svs::leanvec::compute_leanvec_matrix( @@ -85,8 +84,7 @@ struct LeanVecTrainingDataManager : public svs::runtime::LeanVecTrainingData { LeanVecTrainingDataImpl impl_; LeanVecTrainingDataManager(LeanVecTrainingDataImpl impl) - : impl_{std::move(impl)} - {} + : impl_{std::move(impl)} {} Status save(std::ostream& out) const noexcept override { SVS_RUNTIME_TRY_BEGIN From fdcffe2ccff47e7d5fe3cbeb5c0004ea116cf501 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 6 Nov 2025 15:12:34 +0100 Subject: [PATCH 26/46] Fix `LeanVecTrainingData` API --- bindings/cpp/include/training.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/training.h index 99084e37..990d46e4 100644 --- a/bindings/cpp/include/training.h +++ b/bindings/cpp/include/training.h @@ -25,7 +25,7 @@ namespace svs { namespace runtime { struct SVS_RUNTIME_API LeanVecTrainingData { virtual ~LeanVecTrainingData() = 0; - Status build( + static Status build( LeanVecTrainingData** training_data, size_t dim, size_t n, From ae0f97df549b23d1bd5847effaa693e0cac2ab79 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 6 Nov 2025 15:17:28 +0100 Subject: [PATCH 27/46] Refactored VamanaIndex implementation code --- bindings/cpp/include/vamana_index.h | 2 +- bindings/cpp/src/dynamic_vamana_index.cpp | 14 +- bindings/cpp/src/dynamic_vamana_index_impl.h | 237 +++++++------------ bindings/cpp/src/svs_runtime_utils.h | 139 +++++------ bindings/cpp/src/training_impl.h | 39 ++- 5 files changed, 190 insertions(+), 241 deletions(-) diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index ad36aef1..06128522 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -22,7 +22,7 @@ namespace svs { namespace runtime { -// Abstract interface for Vamana-based indexes. +// Abstract interface for Vamana-based indices. // NOTE VamanaIndex is not implemented directly, only DynamicVamanaIndex is implemented. struct SVS_RUNTIME_API VamanaIndex { virtual ~VamanaIndex() = 0; diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index 507c11e5..3d7ae894 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -128,10 +128,8 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { }; using DynamicVamanaIndexManager = DynamicVamanaIndexManagerBase; -using DynamicVamanaIndexLeanVecImplTrainedManager = - DynamicVamanaIndexManagerBase; -using DynamicVamanaIndexLeanVecImplDimsManager = - DynamicVamanaIndexManagerBase; +using DynamicVamanaIndexLeanVecImplManager = + DynamicVamanaIndexManagerBase; } // namespace @@ -187,10 +185,10 @@ Status DynamicVamanaIndexLeanVec::build( ) noexcept { *index = nullptr; SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique( + auto impl = std::make_unique( dim, metric, storage_kind, leanvec_dims, params, default_search_params ); - *index = new DynamicVamanaIndexLeanVecImplDimsManager{std::move(impl)}; + *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; return Status_Ok; SVS_RUNTIME_TRY_END } @@ -209,10 +207,10 @@ Status DynamicVamanaIndexLeanVec::build( SVS_RUNTIME_TRY_BEGIN auto training_data_impl = static_cast(training_data)->impl_; - auto impl = std::make_unique( + auto impl = std::make_unique( dim, metric, storage_kind, training_data_impl, params, default_search_params ); - *index = new DynamicVamanaIndexLeanVecImplTrainedManager{std::move(impl)}; + *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; return Status_Ok; SVS_RUNTIME_TRY_END } diff --git a/bindings/cpp/src/dynamic_vamana_index_impl.h b/bindings/cpp/src/dynamic_vamana_index_impl.h index 92789dbc..cae36b92 100644 --- a/bindings/cpp/src/dynamic_vamana_index_impl.h +++ b/bindings/cpp/src/dynamic_vamana_index_impl.h @@ -150,8 +150,7 @@ class DynamicVamanaIndexImpl { } }; - auto threadpool = - svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + auto threadpool = default_threadpool(); svs::threads::parallel_for( threadpool, svs::threads::StaticPartition{n}, search_closure @@ -237,8 +236,7 @@ class DynamicVamanaIndexImpl { } }; - auto threadpool = - svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); + auto threadpool = default_threadpool(); svs::threads::parallel_for( threadpool, svs::threads::StaticPartition{n}, range_search_closure @@ -367,23 +365,28 @@ class DynamicVamanaIndexImpl { ); } - template - svs::DynamicVamana* init_impl_t( + template + static svs::DynamicVamana* build_impl( Tag&& tag, MetricType metric, + const index::vamana::VamanaBuildParameters& parameters, const svs::data::ConstSimpleDataView& data, - std::span labels + std::span labels, + StorageArgs&&... storage_args ) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() - )); + auto threadpool = default_threadpool(); - auto storage = make_storage(std::forward(tag), data, threadpool); + auto storage = make_storage( + std::forward(tag), + data, + threadpool, + std::forward(storage_args)... + ); svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { return new svs::DynamicVamana(svs::DynamicVamana::build( - this->vamana_build_parameters(), + parameters, std::move(storage), std::move(labels), std::forward(distance), @@ -398,19 +401,24 @@ class DynamicVamanaIndexImpl { get_storage_kind(), [this]( auto&& tag, - MetricType metric, data::ConstSimpleDataView data, std::span labels ) { using Tag = std::decay_t; - return init_impl_t(std::forward(tag), metric, data, labels); + return build_impl( + std::forward(tag), + this->metric_type_, + this->vamana_build_parameters(), + data, + labels + ); }, - metric_type_, data, labels )); } + // Constructor used during loading DynamicVamanaIndexImpl( std::unique_ptr&& impl, MetricType metric, @@ -432,21 +440,20 @@ class DynamicVamanaIndexImpl { impl_->get_full_search_history()}; } - template - static svs::DynamicVamana* deserialize_impl_t( - StorageTag&& SVS_UNUSED(tag), std::istream& stream, MetricType metric - ) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() - )); + template + static svs::DynamicVamana* + load_impl_t(Tag&& SVS_UNUSED(tag), std::istream& stream, MetricType metric) { + auto threadpool = default_threadpool(); svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::assemble< - float, - storage::StorageType_t>( - stream, std::forward(distance), std::move(threadpool) - )); + return new svs::DynamicVamana( + svs::DynamicVamana::assemble>( + stream, + std::forward(distance), + std::move(threadpool) + ) + ); }); } @@ -458,7 +465,7 @@ class DynamicVamanaIndexImpl { [&](auto&& tag, std::istream& stream, MetricType metric) { using Tag = std::decay_t; std::unique_ptr impl{ - deserialize_impl_t(std::forward(tag), stream, metric)}; + load_impl_t(std::forward(tag), stream, metric)}; return new DynamicVamanaIndexImpl(std::move(impl), metric, storage_kind); }, @@ -478,9 +485,19 @@ class DynamicVamanaIndexImpl { size_t ntotal_soft_deleted{0}; }; -struct DynamicVamanaIndexLeanVecImplTrained : public DynamicVamanaIndexImpl { - using DynamicVamanaIndexImpl::DynamicVamanaIndexImpl; - DynamicVamanaIndexLeanVecImplTrained( +struct DynamicVamanaIndexLeanVecImpl : public DynamicVamanaIndexImpl { + DynamicVamanaIndexLeanVecImpl( + std::unique_ptr&& impl, + MetricType metric, + StorageKind storage_kind + ) + : DynamicVamanaIndexImpl{std::move(impl), metric, storage_kind} + , leanvec_dims_{0} + , leanvec_matrices_{std::nullopt} { + check_storage_kind(storage_kind); + } + + DynamicVamanaIndexLeanVecImpl( size_t dim, MetricType metric, StorageKind storage_kind, @@ -489,82 +506,12 @@ struct DynamicVamanaIndexLeanVecImplTrained : public DynamicVamanaIndexImpl { const VamanaIndex::SearchParams& default_search_params = {10, 10} ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params} - , training_data_{training_data} {} - - template - svs::DynamicVamana* init_impl_t( - Tag&& tag, - MetricType metric, - const svs::data::ConstSimpleDataView& data, - std::span labels, - std::optional matrices - ) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() - )); - - auto storage = - make_storage(std::forward(tag), data, threadpool, 0, std::move(matrices)); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::build( - this->vamana_build_parameters(), - std::move(storage), - std::move(labels), - std::forward(distance), - std::move(threadpool) - )); - }); + , leanvec_dims_{training_data.get_leanvec_dims()} + , leanvec_matrices_{training_data.get_leanvec_matrices()} { + check_storage_kind(storage_kind); } - template - static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { - using SK = StorageKind; - using namespace svs::runtime::storage; - switch (kind) { - case SK::LeanVec4x4: - return f(LeanVec4x4Tag{}, std::forward(args)...); - case SK::LeanVec4x8: - return f(LeanVec4x8Tag{}, std::forward(args)...); - case SK::LeanVec8x8: - return f(LeanVec8x8Tag{}, std::forward(args)...); - default: - throw ANNEXCEPTION("not supported SVS leanvec storage kind"); - } - } - - void init_impl(data::ConstSimpleDataView data, std::span labels) - override { - impl_.reset(DynamicVamanaIndexLeanVecImplTrained::dispatch_storage_kind( - this->storage_kind_, - [this]( - auto&& tag, - MetricType metric, - data::ConstSimpleDataView data, - std::span labels - ) { - using Tag = std::decay_t; - return DynamicVamanaIndexLeanVecImplTrained::init_impl_t( - std::forward(tag), - metric, - data, - labels, - training_data_.get_leanvec_matrices() - ); - }, - metric_type_, - data, - labels - )); - } - - LeanVecTrainingDataImpl training_data_; -}; - -struct DynamicVamanaIndexLeanVecImplDims : public DynamicVamanaIndexImpl { - using DynamicVamanaIndexImpl::DynamicVamanaIndexImpl; - DynamicVamanaIndexLeanVecImplDims( + DynamicVamanaIndexLeanVecImpl( size_t dim, MetricType metric, StorageKind storage_kind, @@ -573,72 +520,70 @@ struct DynamicVamanaIndexLeanVecImplDims : public DynamicVamanaIndexImpl { const VamanaIndex::SearchParams& default_search_params = {10, 10} ) : DynamicVamanaIndexImpl{dim, metric, storage_kind, params, default_search_params} - , leanvec_dims_{leanvec_dims} {} - - template - svs::DynamicVamana* init_impl_t( - Tag&& tag, - MetricType metric, - const svs::data::ConstSimpleDataView& data, - std::span labels, - size_t leanvec_dims - ) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() - )); - - auto storage = make_storage(std::forward(tag), data, threadpool, leanvec_dims); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::build( - this->vamana_build_parameters(), - std::move(storage), - std::move(labels), - std::forward(distance), - std::move(threadpool) - )); - }); + , leanvec_dims_{leanvec_dims} + , leanvec_matrices_{std::nullopt} { + check_storage_kind(storage_kind); } template - static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { - using SK = StorageKind; - using namespace svs::runtime::storage; + static auto dispatch_leanvec_storage_kind(StorageKind kind, F&& f, Args&&... args) { switch (kind) { - case SK::LeanVec4x4: - return f(LeanVec4x4Tag{}, std::forward(args)...); - case SK::LeanVec4x8: - return f(LeanVec4x8Tag{}, std::forward(args)...); - case SK::LeanVec8x8: - return f(LeanVec8x8Tag{}, std::forward(args)...); + case StorageKind::LeanVec4x4: + return f(storage::LeanVec4x4Tag{}, std::forward(args)...); + case StorageKind::LeanVec4x8: + return f(storage::LeanVec4x8Tag{}, std::forward(args)...); + case StorageKind::LeanVec8x8: + return f(storage::LeanVec8x8Tag{}, std::forward(args)...); default: - throw ANNEXCEPTION("not supported SVS leanvec storage kind"); + throw StatusException{ + ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required"}; } } void init_impl(data::ConstSimpleDataView data, std::span labels) override { - impl_.reset(DynamicVamanaIndexLeanVecImplDims::dispatch_storage_kind( + assert(storage::is_leanvec_storage(this->storage_kind_)); + impl_.reset(dispatch_leanvec_storage_kind( this->storage_kind_, [this]( auto&& tag, - MetricType metric, data::ConstSimpleDataView data, std::span labels ) { using Tag = std::decay_t; - return DynamicVamanaIndexLeanVecImplDims::init_impl_t( - std::forward(tag), metric, data, labels, leanvec_dims_ + return DynamicVamanaIndexImpl::build_impl( + std::forward(tag), + this->metric_type_, + this->vamana_build_parameters(), + data, + labels, + this->leanvec_dims_, + this->leanvec_matrices_ ); }, - metric_type_, data, labels )); } + protected: size_t leanvec_dims_; + std::optional leanvec_matrices_; + + StorageKind check_storage_kind(StorageKind kind) { + if (!storage::is_leanvec_storage(kind)) { + throw StatusException( + ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required" + ); + } + if (!svs::detail::lvq_leanvec_enabled()) { + throw StatusException( + ErrorCode::NOT_IMPLEMENTED, + "LeanVec storage kind requested but not supported by CPU" + ); + } + return kind; + } }; } // namespace runtime diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index b61892e1..ef8e4ace 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include #include @@ -85,6 +87,7 @@ using LeanVecMatricesType = svs::leanvec::LeanVecMatrices; namespace storage { +// Simplified trait checking template inline constexpr bool is_simple_dataset = false; template inline constexpr bool is_simple_dataset> = @@ -93,22 +96,17 @@ inline constexpr bool is_simple_dataset concept IsSimpleDataset = is_simple_dataset; +// Consolidated storage kind checks using constexpr functions inline constexpr bool is_lvq_storage(StorageKind kind) { return kind == StorageKind::LVQ4x0 || kind == StorageKind::LVQ4x4 || kind == StorageKind::LVQ4x8; } -template -concept IsLVQStorageKind = is_lvq_storage(K); - inline constexpr bool is_leanvec_storage(StorageKind kind) { return kind == StorageKind::LeanVec4x4 || kind == StorageKind::LeanVec4x8 || kind == StorageKind::LeanVec8x8; } -template -concept IsLeanVecStorageKind = is_leanvec_storage(K); - // Storage kind processing // Most kinds map to std::byte storage, but some have specific element types. // Storage kind tag types for function argument deduction @@ -116,15 +114,26 @@ template struct StorageKindTag { static constexpr StorageKind value = K; }; -using FP32Tag = StorageKindTag; -using FP16Tag = StorageKindTag; -using SQI8Tag = StorageKindTag; -using LVQ4x0Tag = StorageKindTag; -using LVQ4x4Tag = StorageKindTag; -using LVQ4x8Tag = StorageKindTag; -using LeanVec4x4Tag = StorageKindTag; -using LeanVec4x8Tag = StorageKindTag; -using LeanVec8x8Tag = StorageKindTag; +#define SVS_DEFINE_STORAGE_KIND_TAG(Kind) \ + using Kind##Tag = StorageKindTag + +SVS_DEFINE_STORAGE_KIND_TAG(FP32); +SVS_DEFINE_STORAGE_KIND_TAG(FP16); +SVS_DEFINE_STORAGE_KIND_TAG(SQI8); +SVS_DEFINE_STORAGE_KIND_TAG(LVQ4x0); +SVS_DEFINE_STORAGE_KIND_TAG(LVQ4x4); +SVS_DEFINE_STORAGE_KIND_TAG(LVQ4x8); +SVS_DEFINE_STORAGE_KIND_TAG(LeanVec4x4); +SVS_DEFINE_STORAGE_KIND_TAG(LeanVec4x8); +SVS_DEFINE_STORAGE_KIND_TAG(LeanVec8x8); + +#undef SVS_DEFINE_STORAGE_KIND_TAG + +template inline constexpr bool is_storage_tag = false; +template inline constexpr bool is_storage_tag> = true; + +template +concept StorageTag = is_storage_tag; // Storage types template @@ -151,45 +160,28 @@ using LeanDatasetType = svs::leanvec::LeanDataset< svs::Dynamic, svs::data::Blocked>>; -template struct StorageType; -template using StorageType_t = typename StorageType::type; - -template <> struct StorageType { - using type = SimpleDatasetType; -}; - -template <> struct StorageType { - using type = SimpleDatasetType; -}; - -template <> struct StorageType { - using type = SQDatasetType; -}; - -template <> struct StorageType { - using type = LVQDatasetType<4, 0>; -}; - -template <> struct StorageType { - using type = LVQDatasetType<4, 4>; -}; - -template <> struct StorageType { - using type = LVQDatasetType<4, 8>; -}; +// Storage type mapping - use macro to reduce repetition +template struct StorageType; +template using StorageType_t = typename StorageType::type; -template <> struct StorageType { - using type = LeanDatasetType<4, 4>; -}; +#define DEFINE_STORAGE_TYPE(Kind, ...) \ + template <> struct StorageType { \ + using type = __VA_ARGS__; \ + } -template <> struct StorageType { - using type = LeanDatasetType<4, 8>; -}; +DEFINE_STORAGE_TYPE(FP32, SimpleDatasetType); +DEFINE_STORAGE_TYPE(FP16, SimpleDatasetType); +DEFINE_STORAGE_TYPE(SQI8, SQDatasetType); +DEFINE_STORAGE_TYPE(LVQ4x0, LVQDatasetType<4, 0>); +DEFINE_STORAGE_TYPE(LVQ4x4, LVQDatasetType<4, 4>); +DEFINE_STORAGE_TYPE(LVQ4x8, LVQDatasetType<4, 8>); +DEFINE_STORAGE_TYPE(LeanVec4x4, LeanDatasetType<4, 4>); +DEFINE_STORAGE_TYPE(LeanVec4x8, LeanDatasetType<4, 8>); +DEFINE_STORAGE_TYPE(LeanVec8x8, LeanDatasetType<8, 8>); -template <> struct StorageType { - using type = LeanDatasetType<8, 8>; -}; +#undef DEFINE_STORAGE_TYPE +// Storage factory functions template StorageType make_storage(const svs::data::ConstSimpleDataView& data, Pool& pool) { StorageType result(data.size(), data.dimensions()); @@ -232,37 +224,52 @@ LeanVecStorageType make_storage( ); } -template -auto make_storage(StorageTag&& SVS_UNUSED(tag), Args&&... args) { - return make_storage>(std::forward(args)...); +template +auto make_storage(Tag&& SVS_UNUSED(tag), Args&&... args) { + return make_storage>(std::forward(args)...); +} + +inline StorageKind to_supported_storage_kind(StorageKind kind) { + if (svs::detail::lvq_leanvec_enabled()) { + return kind; + } else if (is_lvq_storage(kind) || is_leanvec_storage(kind)) { + return StorageKind::SQI8; + } + throw StatusException( + svs::runtime::ErrorCode::NOT_IMPLEMENTED, + "SVS runtime does not support the requested storage kind." + ); } template -static auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { - using SK = StorageKind; - switch (kind) { - case SK::FP32: +auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { + switch (to_supported_storage_kind(kind)) { + case StorageKind::FP32: return f(FP32Tag{}, std::forward(args)...); - case SK::FP16: + case StorageKind::FP16: return f(FP16Tag{}, std::forward(args)...); - case SK::SQI8: + case StorageKind::SQI8: return f(SQI8Tag{}, std::forward(args)...); - case SK::LVQ4x0: + case StorageKind::LVQ4x0: return f(LVQ4x0Tag{}, std::forward(args)...); - case SK::LVQ4x4: + case StorageKind::LVQ4x4: return f(LVQ4x4Tag{}, std::forward(args)...); - case SK::LVQ4x8: + case StorageKind::LVQ4x8: return f(LVQ4x8Tag{}, std::forward(args)...); - case SK::LeanVec4x4: + case StorageKind::LeanVec4x4: return f(LeanVec4x4Tag{}, std::forward(args)...); - case SK::LeanVec4x8: + case StorageKind::LeanVec4x8: return f(LeanVec4x8Tag{}, std::forward(args)...); - case SK::LeanVec8x8: + case StorageKind::LeanVec8x8: return f(LeanVec8x8Tag{}, std::forward(args)...); default: throw ANNEXCEPTION("not supported SVS storage kind"); } } - } // namespace storage + +inline svs::threads::ThreadPoolHandle default_threadpool() { + return svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads()) + ); +} } // namespace svs::runtime diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h index 805b4007..fdc54d49 100644 --- a/bindings/cpp/src/training_impl.h +++ b/bindings/cpp/src/training_impl.h @@ -18,19 +18,13 @@ #include "svs_runtime_utils.h" -#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include SVS_LVQ_HEADER #include SVS_LEANVEC_HEADER @@ -40,18 +34,21 @@ namespace runtime { struct LeanVecTrainingDataImpl { LeanVecTrainingDataImpl(LeanVecMatricesType&& matrices) - : leanvec_matrices{std::move(matrices)} {} + : leanvec_dims_{matrices.view_data_matrix().dimensions()} + , leanvec_matrices_{std::move(matrices)} {} LeanVecTrainingDataImpl( const svs::data::ConstSimpleDataView& data, size_t leanvec_dims ) - : LeanVecTrainingDataImpl{compute_leanvec_matrices(data, leanvec_dims)} {} + : leanvec_dims_{leanvec_dims} + , leanvec_matrices_{compute_leanvec_matrices(data, leanvec_dims)} {} - const LeanVecMatricesType& get_leanvec_matrices() const { return leanvec_matrices; } + size_t get_leanvec_dims() const { return leanvec_dims_; } + const LeanVecMatricesType& get_leanvec_matrices() const { return leanvec_matrices_; } void save(std::ostream& out) const { lib::UniqueTempDirectory tempdir{"svs_leanvec_matrix_save"}; - svs::lib::save_to_disk(leanvec_matrices, tempdir); + svs::lib::save_to_disk(leanvec_matrices_, tempdir); lib::DirectoryArchiver::pack(tempdir, out); } @@ -63,26 +60,26 @@ struct LeanVecTrainingDataImpl { } private: - LeanVecMatricesType leanvec_matrices; + size_t leanvec_dims_; + LeanVecMatricesType leanvec_matrices_; static LeanVecMatricesType compute_leanvec_matrices( const svs::data::ConstSimpleDataView& data, size_t leanvec_dims ) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads() - )); + auto threadpool = default_threadpool(); auto means = svs::utils::compute_medioid(data, threadpool); auto matrix = svs::leanvec::compute_leanvec_matrix( data, means, threadpool, svs::lib::MaybeStatic{leanvec_dims} ); + // Intentionally using the same matrix for both elements of LeanVecMatricesType. + // This may be required by downstream code expecting two matrices, even if they are + // identical. return LeanVecMatricesType{matrix, matrix}; } }; struct LeanVecTrainingDataManager : public svs::runtime::LeanVecTrainingData { - LeanVecTrainingDataImpl impl_; - LeanVecTrainingDataManager(LeanVecTrainingDataImpl impl) : impl_{std::move(impl)} {} @@ -92,6 +89,8 @@ struct LeanVecTrainingDataManager : public svs::runtime::LeanVecTrainingData { return Status_Ok; SVS_RUNTIME_TRY_END } + + LeanVecTrainingDataImpl impl_; }; } // namespace runtime From 5ab0758d4da6cf50c990ca34ee0366c26cc2600e Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 6 Nov 2025 16:12:44 +0100 Subject: [PATCH 28/46] Fix non-filtered case in `DynamicVamanaIndex::search()` --- bindings/cpp/src/dynamic_vamana_index_impl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/cpp/src/dynamic_vamana_index_impl.h b/bindings/cpp/src/dynamic_vamana_index_impl.h index cae36b92..fcca47b5 100644 --- a/bindings/cpp/src/dynamic_vamana_index_impl.h +++ b/bindings/cpp/src/dynamic_vamana_index_impl.h @@ -114,6 +114,7 @@ class DynamicVamanaIndexImpl { svs::make_dims(n, k), static_cast(static_cast(labels))}, svs::MatrixView{svs::make_dims(n, k), distances}}; impl_->search(results, queries, sp); + return; } // Selective search with IDSelector From ad9bd20bce54b327cbc860f94c02dcd2783e30d4 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 6 Nov 2025 17:58:12 +0100 Subject: [PATCH 29/46] Add new `FlatIndex` API and fix abstract interface descructors --- bindings/cpp/CMakeLists.txt | 3 + bindings/cpp/include/flat_index.h | 45 +++++++ bindings/cpp/include/vamana_index.h | 2 +- bindings/cpp/src/dynamic_vamana_index.cpp | 4 + bindings/cpp/src/flat_index.cpp | 108 ++++++++++++++++ bindings/cpp/src/flat_index_impl.h | 149 ++++++++++++++++++++++ 6 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 bindings/cpp/include/flat_index.h create mode 100644 bindings/cpp/src/flat_index.cpp create mode 100644 bindings/cpp/src/flat_index_impl.h diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 08e8b7f6..a0ae1345 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -23,16 +23,19 @@ set(SVS_RUNTIME_HEADERS include/training.h include/vamana_index.h include/dynamic_vamana_index.h + include/flat_index.h ) set(SVS_RUNTIME_SOURCES src/IndexSVSImplUtils.h src/svs_runtime_utils.h src/dynamic_vamana_index_impl.h + src/flat_index_impl.h src/IndexSVSFlatImpl.cpp src/IndexSVSVamanaImpl.cpp src/training.cpp src/dynamic_vamana_index.cpp + src/flat_index.cpp ) option(SVS_RUNTIME_ENABLE_LVQ_LEANVEC "Enable compilation of SVS runtime with LVQ and LeanVec support" ON) diff --git a/bindings/cpp/include/flat_index.h b/bindings/cpp/include/flat_index.h new file mode 100644 index 00000000..50dee83b --- /dev/null +++ b/bindings/cpp/include/flat_index.h @@ -0,0 +1,45 @@ +/* + * Copyright 2025 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 "IndexSVSImplDefs.h" + +#include +#include +#include + +namespace svs { +namespace runtime { + +// Abstract interface for Flat indices. +struct SVS_RUNTIME_API FlatIndex { + // Static constructors and destructors + static Status build(FlatIndex** index, size_t dim, MetricType metric) noexcept; + static Status destroy(FlatIndex* index) noexcept; + virtual ~FlatIndex(); + + virtual Status search( + size_t n, const float* x, size_t k, float* distances, size_t* labels + ) const noexcept = 0; + + virtual Status add(size_t n, const float* x) noexcept = 0; + virtual Status reset() noexcept = 0; + + virtual Status save(std::ostream& out) const noexcept = 0; + static Status load(FlatIndex** index, std::istream& in, MetricType metric) noexcept; +}; +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index 06128522..f93c3bd7 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -25,7 +25,7 @@ namespace runtime { // Abstract interface for Vamana-based indices. // NOTE VamanaIndex is not implemented directly, only DynamicVamanaIndex is implemented. struct SVS_RUNTIME_API VamanaIndex { - virtual ~VamanaIndex() = 0; + virtual ~VamanaIndex(); struct BuildParams { size_t graph_max_degree; diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index 3d7ae894..b79a66d7 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -133,6 +133,10 @@ using DynamicVamanaIndexLeanVecImplManager = } // namespace +// VamanaIndex interface implementation +VamanaIndex::~VamanaIndex() = default; + +// DynamicVamanaIndex interface implementation Status DynamicVamanaIndex::build( DynamicVamanaIndex** index, size_t dim, diff --git a/bindings/cpp/src/flat_index.cpp b/bindings/cpp/src/flat_index.cpp new file mode 100644 index 00000000..641edbe7 --- /dev/null +++ b/bindings/cpp/src/flat_index.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2025 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. + */ + +#include "flat_index.h" +#include "flat_index_impl.h" +#include "svs_runtime_utils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace svs { +namespace runtime { + +namespace { +struct FlatIndexManager : public FlatIndex { + std::unique_ptr impl_; + + FlatIndexManager(std::unique_ptr impl) + : impl_{std::move(impl)} { + assert(impl_ != nullptr); + } + + ~FlatIndexManager() override = default; + + Status add(size_t n, const float* x) noexcept override { + SVS_RUNTIME_TRY_BEGIN + svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; + impl_->add(data); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status search(size_t n, const float* x, size_t k, float* distances, size_t* labels) + const noexcept override { + SVS_RUNTIME_TRY_BEGIN + // TODO wrap arguments into proper data structures in FlatIndexImpl and + // here + impl_->search(n, x, k, distances, labels); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status reset() noexcept override { + SVS_RUNTIME_TRY_BEGIN + impl_->reset(); + return Status_Ok; + SVS_RUNTIME_TRY_END + } + + Status save(std::ostream& out) const noexcept override { + SVS_RUNTIME_TRY_BEGIN + impl_->save(out); + return Status_Ok; + SVS_RUNTIME_TRY_END + } +}; +} // namespace + +// FlatIndex interface implementation +FlatIndex::~FlatIndex() = default; + +Status FlatIndex::build(FlatIndex** index, size_t dim, MetricType metric) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + auto impl = std::make_unique(dim, metric); + *index = new FlatIndexManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +Status FlatIndex::destroy(FlatIndex* index) noexcept { + SVS_RUNTIME_TRY_BEGIN + delete index; + return Status_Ok; + SVS_RUNTIME_TRY_END +} + +Status FlatIndex::load(FlatIndex** index, std::istream& in, MetricType metric) noexcept { + *index = nullptr; + SVS_RUNTIME_TRY_BEGIN + std::unique_ptr impl{FlatIndexImpl::load(in, metric)}; + *index = new FlatIndexManager{std::move(impl)}; + return Status_Ok; + SVS_RUNTIME_TRY_END +} +} // namespace runtime +} // namespace svs diff --git a/bindings/cpp/src/flat_index_impl.h b/bindings/cpp/src/flat_index_impl.h new file mode 100644 index 00000000..b6071218 --- /dev/null +++ b/bindings/cpp/src/flat_index_impl.h @@ -0,0 +1,149 @@ +/* + * Copyright 2025 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 "flat_index.h" +#include "svs_runtime_utils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace svs { +namespace runtime { + +// Vamana index implementation +class FlatIndexImpl { + public: + FlatIndexImpl(size_t dim, MetricType metric) + : dim_{dim} + , metric_type_{metric} {} + + size_t size() const { return impl_ ? impl_->size() : 0; } + + size_t dimensions() const { return dim_; } + + MetricType metric_type() const { return metric_type_; } + + void add(data::ConstSimpleDataView data) { + if (!impl_) { + return init_impl(data); + } + + throw StatusException{ + ErrorCode::NOT_IMPLEMENTED, + "Flat index does not support adding points after initialization"}; + } + + void search( + size_t n, + const float* x, + size_t k, + float* distances, + size_t* labels, + IDFilter* filter = nullptr + ) const { + if (!impl_) { + for (size_t i = 0; i < n; ++i) { + distances[i] = std::numeric_limits::infinity(); + labels[i] = -1; + } + throw StatusException{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; + } + + if (k == 0) { + throw StatusException{ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; + } + + // Simple search + if (filter == nullptr) { + auto queries = svs::data::ConstSimpleDataView(x, n, dim_); + + // TODO: faiss use int64_t as label whereas SVS uses size_t? + auto results = svs::QueryResultView{ + svs::MatrixView{ + svs::make_dims(n, k), static_cast(static_cast(labels))}, + svs::MatrixView{svs::make_dims(n, k), distances}}; + impl_->search(results, queries, {}); + } else { + throw StatusException{ + ErrorCode::NOT_IMPLEMENTED, "Filtered search not implemented yet"}; + } + } + + void reset() { impl_.reset(); } + + void save(std::ostream& out) const { + if (!impl_) { + throw StatusException{ + ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized."}; + } + + impl_->save(out); + } + + static FlatIndexImpl* load(std::istream& in, MetricType metric) { + auto threadpool = default_threadpool(); + using storage_type = svs::runtime::storage::StorageType_t; + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); + return distance_dispatcher([&](auto&& distance) { + auto impl = new svs::Flat{svs::Flat::assemble( + in, std::forward(distance), std::move(threadpool) + )}; + + return new FlatIndexImpl(std::unique_ptr{impl}, metric); + }); + } + + protected: + // Constructor used during loading + FlatIndexImpl(std::unique_ptr&& impl, MetricType metric) + : dim_{impl->dimensions()} + , metric_type_{metric} + , impl_{std::move(impl)} {} + + void init_impl(data::ConstSimpleDataView data) { + auto threadpool = default_threadpool(); + + auto storage = svs::runtime::storage::make_storage( + svs::runtime::storage::FP32Tag{}, data, threadpool + ); + + svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric_type_)); + impl_.reset(distance_dispatcher([&](auto&& distance) { + return new svs::Flat(svs::Flat::assemble( + std::move(storage), + std::forward(distance), + std::move(threadpool) + )); + })); + } + + // Data members + size_t dim_; + MetricType metric_type_; + std::unique_ptr impl_; +}; +} // namespace runtime +} // namespace svs From 1a1e2df86282263124e682b05dfcb2171b5985e7 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Thu, 6 Nov 2025 09:30:27 -0800 Subject: [PATCH 30/46] Update static library tarball --- bindings/cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index a0ae1345..3fe0cb85 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -114,7 +114,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3") - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251031-740.tar.gz" + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251106-762.tar.gz" CACHE STRING "URL to download SVS shared library") else() message(WARNING From a551e9ba8bc232abf4ac5e11e01a85b9b403e6e2 Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Thu, 6 Nov 2025 21:33:23 -0800 Subject: [PATCH 31/46] feature: add version namespace --- bindings/cpp/include/IndexSVSFlatImpl.h | 9 ++ bindings/cpp/include/IndexSVSImplDefs.h | 14 +++ bindings/cpp/include/IndexSVSVamanaImpl.h | 7 ++ bindings/cpp/include/IndexSVSVamanaLVQImpl.h | 10 +- .../cpp/include/IndexSVSVamanaLeanVecImpl.h | 7 ++ bindings/cpp/include/dynamic_vamana_index.h | 8 ++ bindings/cpp/include/flat_index.h | 8 ++ bindings/cpp/include/training.h | 9 ++ bindings/cpp/include/vamana_index.h | 8 ++ bindings/cpp/include/version.h | 110 ++++++++++++++++++ 10 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 bindings/cpp/include/version.h diff --git a/bindings/cpp/include/IndexSVSFlatImpl.h b/bindings/cpp/include/IndexSVSFlatImpl.h index 2856e9e0..a784585c 100644 --- a/bindings/cpp/include/IndexSVSFlatImpl.h +++ b/bindings/cpp/include/IndexSVSFlatImpl.h @@ -15,6 +15,7 @@ */ #include "IndexSVSImplDefs.h" +#include "version.h" #include #include #include @@ -23,6 +24,8 @@ namespace svs { class Flat; namespace runtime { +namespace v0 { + class SVS_RUNTIME_API IndexSVSFlatImpl { public: static IndexSVSFlatImpl* build(size_t dim, MetricType metric) noexcept; @@ -47,5 +50,11 @@ class SVS_RUNTIME_API IndexSVSFlatImpl { size_t dim_; std::unique_ptr impl{nullptr}; }; + +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::IndexSVSFlatImpl; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index 289ee8f8..b9ac391b 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -29,6 +29,8 @@ namespace svs { namespace runtime { +namespace v0 { + enum class MetricType { L2, INNER_PRODUCT }; enum class StorageKind { @@ -83,5 +85,17 @@ struct SVS_RUNTIME_API_INTERFACE ResultsAllocator { } }; +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::MetricType; +using v0::StorageKind; +using v0::ErrorCode; +using v0::Status; +using v0::Status_Ok; +using v0::IDFilter; +using v0::SearchResultsStorage; +using v0::ResultsAllocator; + } // namespace runtime } // namespace svs \ No newline at end of file diff --git a/bindings/cpp/include/IndexSVSVamanaImpl.h b/bindings/cpp/include/IndexSVSVamanaImpl.h index 649906dc..890634df 100644 --- a/bindings/cpp/include/IndexSVSVamanaImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaImpl.h @@ -16,6 +16,7 @@ #pragma once #include "IndexSVSImplDefs.h" +#include "version.h" #include #include @@ -28,6 +29,7 @@ namespace svs { class DynamicVamana; namespace runtime { +namespace v0 { struct SVS_RUNTIME_API IndexSVSVamanaImpl { struct SearchParams { @@ -105,5 +107,10 @@ struct SVS_RUNTIME_API IndexSVSVamanaImpl { size_t ntotal_soft_deleted{0}; }; +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::IndexSVSVamanaImpl; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h index f72822a0..7df5577e 100644 --- a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h @@ -16,8 +16,9 @@ #pragma once #include "IndexSVSVamanaImpl.h" +#include "version.h" -namespace svs::runtime { +namespace svs::runtime::v0 { struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { enum LVQLevel { LVQ4x0, LVQ4x4, LVQ4x8 }; @@ -37,4 +38,9 @@ struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { LVQLevel lvq_level; }; -} // namespace svs::runtime +} // namespace svs::runtime::v0 + +// Bring current version APIs to parent namespace +namespace svs::runtime { + using v0::IndexSVSVamanaLVQImpl; +} diff --git a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h index eac2a760..c845c570 100644 --- a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h @@ -17,6 +17,7 @@ #pragma once #include "IndexSVSImplDefs.h" #include "IndexSVSVamanaImpl.h" +#include "version.h" #include #include @@ -27,6 +28,7 @@ template struct LeanVecMatrices; } // namespace leanvec namespace runtime { +namespace v0 { struct SVS_RUNTIME_API IndexSVSVamanaLeanVecImpl : IndexSVSVamanaImpl { enum LeanVecLevel { LeanVec4x4, LeanVec4x8, LeanVec8x8 }; @@ -68,5 +70,10 @@ struct SVS_RUNTIME_API IndexSVSVamanaLeanVecImpl : IndexSVSVamanaImpl { bool trained = false; }; +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::IndexSVSVamanaLeanVecImpl; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/dynamic_vamana_index.h b/bindings/cpp/include/dynamic_vamana_index.h index 50c4ceb8..b3b0a333 100644 --- a/bindings/cpp/include/dynamic_vamana_index.h +++ b/bindings/cpp/include/dynamic_vamana_index.h @@ -18,6 +18,7 @@ #include "IndexSVSImplDefs.h" #include "training.h" #include "vamana_index.h" +#include "version.h" #include #include @@ -25,6 +26,7 @@ namespace svs { namespace runtime { +namespace v0 { // Abstract interface for Dynamic Vamana-based indexes. struct SVS_RUNTIME_API DynamicVamanaIndex : public VamanaIndex { @@ -80,5 +82,11 @@ struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex { ) noexcept; }; +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::DynamicVamanaIndex; +using v0::DynamicVamanaIndexLeanVec; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/flat_index.h b/bindings/cpp/include/flat_index.h index 50dee83b..11a2ee0e 100644 --- a/bindings/cpp/include/flat_index.h +++ b/bindings/cpp/include/flat_index.h @@ -16,6 +16,7 @@ #pragma once #include "IndexSVSImplDefs.h" +#include "version.h" #include #include @@ -23,6 +24,7 @@ namespace svs { namespace runtime { +namespace v0 { // Abstract interface for Flat indices. struct SVS_RUNTIME_API FlatIndex { @@ -41,5 +43,11 @@ struct SVS_RUNTIME_API FlatIndex { virtual Status save(std::ostream& out) const noexcept = 0; static Status load(FlatIndex** index, std::istream& in, MetricType metric) noexcept; }; + +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::FlatIndex; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/training.h index 990d46e4..7e87be0a 100644 --- a/bindings/cpp/include/training.h +++ b/bindings/cpp/include/training.h @@ -16,6 +16,7 @@ #pragma once #include "IndexSVSImplDefs.h" +#include "version.h" #include #include @@ -23,6 +24,8 @@ namespace svs { namespace runtime { +namespace v0 { + struct SVS_RUNTIME_API LeanVecTrainingData { virtual ~LeanVecTrainingData() = 0; static Status build( @@ -38,5 +41,11 @@ struct SVS_RUNTIME_API LeanVecTrainingData { virtual Status save(std::ostream& out) const noexcept; static Status load(LeanVecTrainingData** training_data, std::istream& in) noexcept; }; + +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::LeanVecTrainingData; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index f93c3bd7..e03a6032 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -16,11 +16,13 @@ #pragma once #include "IndexSVSImplDefs.h" +#include "version.h" #include namespace svs { namespace runtime { +namespace v0 { // Abstract interface for Vamana-based indices. // NOTE VamanaIndex is not implemented directly, only DynamicVamanaIndex is implemented. @@ -62,5 +64,11 @@ struct SVS_RUNTIME_API VamanaIndex { IDFilter* filter = nullptr ) const noexcept = 0; }; + +} // namespace v0 + +// Bring current version APIs to parent namespace +using v0::VamanaIndex; + } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/version.h b/bindings/cpp/include/version.h new file mode 100644 index 00000000..e41755a6 --- /dev/null +++ b/bindings/cpp/include/version.h @@ -0,0 +1,110 @@ +/* + * Copyright 2025 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 + +/// +/// @brief Version information and API versioning for SVS Runtime +/// +/// This header defines the SVS Runtime API versioning scheme similar to oneDAL: +/// 1. Versioned namespaces (e.g., v0, v1) for API stability +/// 2. Using declarations to bring current version to parent namespace +/// 3. Clean integration points for external libraries +/// +/// Usage: +/// - Users can access APIs via svs::runtime::FlatIndex (maps to current version) +/// - External integrators can use namespace aliases (e.g., namespace svs_api = svs::runtime::v0) +/// - Specific versions can be accessed via svs::runtime::v0::FlatIndex +/// + +///// Version Numbers + +#ifndef SVS_RUNTIME_VERSION_MAJOR +/// Major version number - incremented for breaking API changes +/// When this changes, a new version namespace (e.g., v0 -> v1) is created +#define SVS_RUNTIME_VERSION_MAJOR 0 +#endif + +#ifndef SVS_RUNTIME_VERSION_MINOR +/// Minor version number - incremented for backward-compatible feature additions +#define SVS_RUNTIME_VERSION_MINOR 1 +#endif + +#ifndef SVS_RUNTIME_VERSION_PATCH +/// Patch version number - incremented for backward-compatible bug fixes +#define SVS_RUNTIME_VERSION_PATCH 0 +#endif + +#ifndef SVS_RUNTIME_VERSION_STRING +/// Complete version string +#define SVS_RUNTIME_VERSION_STRING "0.1.0" +#endif + +///// API Version Namespace Declaration + +namespace svs { +namespace runtime { + /// Current API version namespace (v0) + /// All public runtime APIs live here and are accessible as svs::runtime::FunctionName + /// via using declarations + namespace v0 { + // Public runtime APIs will be defined in their respective headers + // and brought up via using declarations + } + + // Using declarations to bring current version APIs to parent namespace + // These will be added as we define versioned APIs in their respective headers +} // namespace runtime +} // namespace svs + +///// Integration Support + +/// Helper macro to create namespace aliases for external integrators +/// Example: SVS_RUNTIME_CREATE_API_ALIAS(svs_runtime_api, v0) +/// creates: namespace svs_runtime_api = svs::runtime::v0; +#define SVS_RUNTIME_CREATE_API_ALIAS(alias_name, version_ns) \ + namespace alias_name = svs::runtime::version_ns + +/// +/// @brief Version information structure for runtime queries +/// +namespace svs::runtime::v0 { + +struct VersionInfo { + static constexpr int major = SVS_RUNTIME_VERSION_MAJOR; + static constexpr int minor = SVS_RUNTIME_VERSION_MINOR; + static constexpr int patch = SVS_RUNTIME_VERSION_PATCH; + static constexpr const char* version_string = SVS_RUNTIME_VERSION_STRING; + static constexpr const char* api_namespace = "v0"; + + /// Get the complete version as a string + static const char* get_version() { return version_string; } + + /// Get the API namespace identifier + static const char* get_api_namespace() { return api_namespace; } + + /// Check if this version is compatible with a requested major version + static bool is_compatible_with_major(int requested_major) { + return major == requested_major; + } +}; + +} // namespace svs::runtime::v0 + +// Bring current version APIs to parent namespace +namespace svs::runtime { + using v0::VersionInfo; +} // namespace svs::runtime From bcd7cb368c6af90a4814033e63b26b808cfa0cb0 Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Thu, 6 Nov 2025 21:55:20 -0800 Subject: [PATCH 32/46] fix: add version.h to cmake --- bindings/cpp/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 3fe0cb85..2efd56ed 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -17,6 +17,7 @@ project(svs_runtime VERSION 0.0.10 LANGUAGES CXX) set(TARGET_NAME svs_runtime) set(SVS_RUNTIME_HEADERS + include/version.h include/IndexSVSImplDefs.h include/IndexSVSFlatImpl.h include/IndexSVSVamanaImpl.h From 1d100af392a31218eb3eefc2968cd746c5cf81e3 Mon Sep 17 00:00:00 2001 From: Andreas Huber <9201869+ahuber21@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:14:08 +0100 Subject: [PATCH 33/46] Replace macros with runtime_error_wrapper() for error handling (#214) I think this produces more readable code. And since `runtime_error_wrapper()` is an internal function, there shouldn't be any disadvantages for the shared lib. --- bindings/cpp/src/dynamic_vamana_index.cpp | 118 +++++++++------------- bindings/cpp/src/svs_runtime_utils.h | 38 +++---- bindings/cpp/src/training.cpp | 23 ++--- bindings/cpp/src/training_impl.h | 5 +- 4 files changed, 79 insertions(+), 105 deletions(-) diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index b79a66d7..bf53ef61 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -55,28 +55,25 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { ~DynamicVamanaIndexManagerBase() override = default; Status add(size_t n, const size_t* labels, const float* x) noexcept override { - SVS_RUNTIME_TRY_BEGIN - svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; - std::span lbls(labels, n); - impl_->add(data, lbls); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; + std::span lbls(labels, n); + impl_->add(data, lbls); + }); } Status remove_selected(size_t* num_removed, const IDFilter& selector) noexcept override { - SVS_RUNTIME_TRY_BEGIN - *num_removed = impl_->remove_selected(selector); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + *num_removed = impl_->remove_selected(selector); + }); } Status remove(size_t n, const size_t* labels) noexcept override { - SVS_RUNTIME_TRY_BEGIN - std::span lbls(labels, n); - impl_->remove(lbls); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + std::span lbls(labels, n); + impl_->remove(lbls); + }); } Status search( @@ -88,12 +85,11 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { const SearchParams* params = nullptr, IDFilter* filter = nullptr ) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and - // here - impl_->search(n, x, k, distances, labels, params, filter); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and + // here + impl_->search(n, x, k, distances, labels, params, filter); + }); } Status range_search( @@ -104,26 +100,19 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex { const SearchParams* params = nullptr, IDFilter* filter = nullptr ) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and - // here - impl_->range_search(n, x, radius, results, params, filter); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + // TODO wrap arguments into proper data structures in DynamicVamanaIndexImpl and + // here + impl_->range_search(n, x, radius, results, params, filter); + }); } Status reset() noexcept override { - SVS_RUNTIME_TRY_BEGIN - impl_->reset(); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { impl_->reset(); }); } Status save(std::ostream& out) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - impl_->save(out); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { impl_->save(out); }); } }; @@ -146,20 +135,16 @@ Status DynamicVamanaIndex::build( const DynamicVamanaIndex::SearchParams& default_search_params ) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique( - dim, metric, storage_kind, params, default_search_params - ); - *index = new DynamicVamanaIndexManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + auto impl = std::make_unique( + dim, metric, storage_kind, params, default_search_params + ); + *index = new DynamicVamanaIndexManager{std::move(impl)}; + }); } Status DynamicVamanaIndex::destroy(DynamicVamanaIndex* index) noexcept { - SVS_RUNTIME_TRY_BEGIN - delete index; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { delete index; }); } Status DynamicVamanaIndex::load( @@ -169,12 +154,11 @@ Status DynamicVamanaIndex::load( StorageKind storage_kind ) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - std::unique_ptr impl{ - DynamicVamanaIndexImpl::load(in, metric, storage_kind)}; - *index = new DynamicVamanaIndexManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + std::unique_ptr impl{ + DynamicVamanaIndexImpl::load(in, metric, storage_kind)}; + *index = new DynamicVamanaIndexManager{std::move(impl)}; + }); } // Specialization to build LeanVec-based Vamana index with specified leanvec dims @@ -188,13 +172,12 @@ Status DynamicVamanaIndexLeanVec::build( const DynamicVamanaIndex::SearchParams& default_search_params ) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique( - dim, metric, storage_kind, leanvec_dims, params, default_search_params - ); - *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + auto impl = std::make_unique( + dim, metric, storage_kind, leanvec_dims, params, default_search_params + ); + *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; + }); } // Specialization to build LeanVec-based Vamana index with provided training data @@ -208,15 +191,14 @@ Status DynamicVamanaIndexLeanVec::build( const DynamicVamanaIndex::SearchParams& default_search_params ) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - auto training_data_impl = - static_cast(training_data)->impl_; - auto impl = std::make_unique( - dim, metric, storage_kind, training_data_impl, params, default_search_params - ); - *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + auto training_data_impl = + static_cast(training_data)->impl_; + auto impl = std::make_unique( + dim, metric, storage_kind, training_data_impl, params, default_search_params + ); + *index = new DynamicVamanaIndexLeanVecImplManager{std::move(impl)}; + }); } } // namespace runtime diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index ef8e4ace..24ba188d 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -21,7 +21,10 @@ // TODO remove unused includes #include +#include +#include #include +#include #include #include @@ -62,26 +65,23 @@ class StatusException : public svs::lib::ANNException { svs::runtime::ErrorCode errcode_; }; -#define SVS_RUNTIME_TRY_BEGIN try { -#define SVS_RUNTIME_TRY_END \ - } \ - catch (const svs::runtime::StatusException& ex) { \ - return svs::runtime::Status(ex.code(), ex.what()); \ - } \ - catch (const std::invalid_argument& ex) { \ - return svs::runtime::Status(svs::runtime::ErrorCode::INVALID_ARGUMENT, ex.what()); \ - } \ - catch (const std::runtime_error& ex) { \ - return svs::runtime::Status(svs::runtime::ErrorCode::RUNTIME_ERROR, ex.what()); \ - } \ - catch (const std::exception& ex) { \ - return svs::runtime::Status(svs::runtime::ErrorCode::UNKNOWN_ERROR, ex.what()); \ - } \ - catch (...) { \ - return svs::runtime::Status( \ - svs::runtime::ErrorCode::UNKNOWN_ERROR, "An unknown error has occurred." \ - ); \ +template +inline auto runtime_error_wrapper(Callable&& func) noexcept -> Status { + try { + func(); + return Status_Ok; + } catch (const svs::runtime::StatusException& ex) { + return Status(ex.code(), ex.what()); + } catch (const std::invalid_argument& ex) { + return Status(ErrorCode::INVALID_ARGUMENT, ex.what()); + } catch (const std::runtime_error& ex) { + return Status(ErrorCode::RUNTIME_ERROR, ex.what()); + } catch (const std::exception& ex) { + return Status(ErrorCode::UNKNOWN_ERROR, ex.what()); + } catch (...) { + return Status(ErrorCode::UNKNOWN_ERROR, "An unknown error has occurred."); } +} using LeanVecMatricesType = svs::leanvec::LeanVecMatrices; diff --git a/bindings/cpp/src/training.cpp b/bindings/cpp/src/training.cpp index a6680b2d..1e49f5a8 100644 --- a/bindings/cpp/src/training.cpp +++ b/bindings/cpp/src/training.cpp @@ -28,27 +28,22 @@ Status LeanVecTrainingData::build( const float* x, size_t leanvec_dims ) noexcept { - SVS_RUNTIME_TRY_BEGIN - const auto data = svs::data::ConstSimpleDataView(x, n, dim); - *training_data = - new LeanVecTrainingDataManager{LeanVecTrainingDataImpl{data, leanvec_dims}}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + const auto data = svs::data::ConstSimpleDataView(x, n, dim); + *training_data = + new LeanVecTrainingDataManager{LeanVecTrainingDataImpl{data, leanvec_dims}}; + }); } Status LeanVecTrainingData::destroy(LeanVecTrainingData* training_data) noexcept { - SVS_RUNTIME_TRY_BEGIN - delete training_data; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { delete training_data; }); } Status LeanVecTrainingData::load(LeanVecTrainingData** training_data, std::istream& in) noexcept { - SVS_RUNTIME_TRY_BEGIN - *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl::load(in)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + *training_data = new LeanVecTrainingDataManager{LeanVecTrainingDataImpl::load(in)}; + }); } } // namespace runtime } // namespace svs diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h index fdc54d49..f8ed77a4 100644 --- a/bindings/cpp/src/training_impl.h +++ b/bindings/cpp/src/training_impl.h @@ -84,10 +84,7 @@ struct LeanVecTrainingDataManager : public svs::runtime::LeanVecTrainingData { : impl_{std::move(impl)} {} Status save(std::ostream& out) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - impl_.save(out); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { impl_.save(out); }); } LeanVecTrainingDataImpl impl_; From c2181e279f22899b8720f20099d7b0781bc7689e Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 12:32:51 +0100 Subject: [PATCH 34/46] Clang-format --- bindings/cpp/include/IndexSVSImplDefs.h | 10 +++--- bindings/cpp/include/IndexSVSVamanaLVQImpl.h | 2 +- bindings/cpp/include/version.h | 33 ++++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index b9ac391b..50f01298 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -88,14 +88,14 @@ struct SVS_RUNTIME_API_INTERFACE ResultsAllocator { } // namespace v0 // Bring current version APIs to parent namespace -using v0::MetricType; -using v0::StorageKind; using v0::ErrorCode; -using v0::Status; -using v0::Status_Ok; using v0::IDFilter; -using v0::SearchResultsStorage; +using v0::MetricType; using v0::ResultsAllocator; +using v0::SearchResultsStorage; +using v0::Status; +using v0::Status_Ok; +using v0::StorageKind; } // namespace runtime } // namespace svs \ No newline at end of file diff --git a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h index 7df5577e..0ede86af 100644 --- a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h @@ -42,5 +42,5 @@ struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { // Bring current version APIs to parent namespace namespace svs::runtime { - using v0::IndexSVSVamanaLVQImpl; +using v0::IndexSVSVamanaLVQImpl; } diff --git a/bindings/cpp/include/version.h b/bindings/cpp/include/version.h index e41755a6..cc405e70 100644 --- a/bindings/cpp/include/version.h +++ b/bindings/cpp/include/version.h @@ -26,7 +26,8 @@ /// /// Usage: /// - Users can access APIs via svs::runtime::FlatIndex (maps to current version) -/// - External integrators can use namespace aliases (e.g., namespace svs_api = svs::runtime::v0) +/// - External integrators can use namespace aliases (e.g., namespace svs_api = +/// svs::runtime::v0) /// - Specific versions can be accessed via svs::runtime::v0::FlatIndex /// @@ -57,16 +58,16 @@ namespace svs { namespace runtime { - /// Current API version namespace (v0) - /// All public runtime APIs live here and are accessible as svs::runtime::FunctionName - /// via using declarations - namespace v0 { - // Public runtime APIs will be defined in their respective headers - // and brought up via using declarations - } - - // Using declarations to bring current version APIs to parent namespace - // These will be added as we define versioned APIs in their respective headers +/// Current API version namespace (v0) +/// All public runtime APIs live here and are accessible as svs::runtime::FunctionName +/// via using declarations +namespace v0 { +// Public runtime APIs will be defined in their respective headers +// and brought up via using declarations +} + +// Using declarations to bring current version APIs to parent namespace +// These will be added as we define versioned APIs in their respective headers } // namespace runtime } // namespace svs @@ -85,17 +86,17 @@ namespace svs::runtime::v0 { struct VersionInfo { static constexpr int major = SVS_RUNTIME_VERSION_MAJOR; - static constexpr int minor = SVS_RUNTIME_VERSION_MINOR; + static constexpr int minor = SVS_RUNTIME_VERSION_MINOR; static constexpr int patch = SVS_RUNTIME_VERSION_PATCH; static constexpr const char* version_string = SVS_RUNTIME_VERSION_STRING; static constexpr const char* api_namespace = "v0"; - + /// Get the complete version as a string static const char* get_version() { return version_string; } - + /// Get the API namespace identifier static const char* get_api_namespace() { return api_namespace; } - + /// Check if this version is compatible with a requested major version static bool is_compatible_with_major(int requested_major) { return major == requested_major; @@ -106,5 +107,5 @@ struct VersionInfo { // Bring current version APIs to parent namespace namespace svs::runtime { - using v0::VersionInfo; +using v0::VersionInfo; } // namespace svs::runtime From 73c18abb6449f5905ff99e0690c66652e44f7835 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 12:33:40 +0100 Subject: [PATCH 35/46] Fix Flat index wrapper and LeanVec Training --- bindings/cpp/CMakeLists.txt | 1 + bindings/cpp/include/training.h | 4 +- bindings/cpp/src/dynamic_vamana_index.cpp | 3 -- bindings/cpp/src/flat_index.cpp | 66 +++++++++++------------ bindings/cpp/src/training.cpp | 2 + bindings/cpp/src/vamana_index.cpp | 26 +++++++++ 6 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 bindings/cpp/src/vamana_index.cpp diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 2efd56ed..489256b6 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -35,6 +35,7 @@ set(SVS_RUNTIME_SOURCES src/IndexSVSFlatImpl.cpp src/IndexSVSVamanaImpl.cpp src/training.cpp + src/vamana_index.cpp src/dynamic_vamana_index.cpp src/flat_index.cpp ) diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/training.h index 7e87be0a..4250297a 100644 --- a/bindings/cpp/include/training.h +++ b/bindings/cpp/include/training.h @@ -27,7 +27,7 @@ namespace runtime { namespace v0 { struct SVS_RUNTIME_API LeanVecTrainingData { - virtual ~LeanVecTrainingData() = 0; + virtual ~LeanVecTrainingData(); static Status build( LeanVecTrainingData** training_data, size_t dim, @@ -38,7 +38,7 @@ struct SVS_RUNTIME_API LeanVecTrainingData { static Status destroy(LeanVecTrainingData* training_data) noexcept; - virtual Status save(std::ostream& out) const noexcept; + virtual Status save(std::ostream& out) const noexcept = 0; static Status load(LeanVecTrainingData** training_data, std::istream& in) noexcept; }; diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index bf53ef61..d541b84c 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -122,9 +122,6 @@ using DynamicVamanaIndexLeanVecImplManager = } // namespace -// VamanaIndex interface implementation -VamanaIndex::~VamanaIndex() = default; - // DynamicVamanaIndex interface implementation Status DynamicVamanaIndex::build( DynamicVamanaIndex** index, diff --git a/bindings/cpp/src/flat_index.cpp b/bindings/cpp/src/flat_index.cpp index 641edbe7..2facf877 100644 --- a/bindings/cpp/src/flat_index.cpp +++ b/bindings/cpp/src/flat_index.cpp @@ -44,35 +44,35 @@ struct FlatIndexManager : public FlatIndex { ~FlatIndexManager() override = default; Status add(size_t n, const float* x) noexcept override { - SVS_RUNTIME_TRY_BEGIN - svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; - impl_->add(data); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + svs::data::ConstSimpleDataView data{x, n, impl_->dimensions()}; + impl_->add(data); + return Status_Ok; + }); } Status search(size_t n, const float* x, size_t k, float* distances, size_t* labels) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - // TODO wrap arguments into proper data structures in FlatIndexImpl and - // here - impl_->search(n, x, k, distances, labels); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + // TODO wrap arguments into proper data structures in FlatIndexImpl and + // here + impl_->search(n, x, k, distances, labels); + return Status_Ok; + }); } Status reset() noexcept override { - SVS_RUNTIME_TRY_BEGIN - impl_->reset(); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + impl_->reset(); + return Status_Ok; + }); } Status save(std::ostream& out) const noexcept override { - SVS_RUNTIME_TRY_BEGIN - impl_->save(out); - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + impl_->save(out); + return Status_Ok; + }); } }; } // namespace @@ -82,27 +82,27 @@ FlatIndex::~FlatIndex() = default; Status FlatIndex::build(FlatIndex** index, size_t dim, MetricType metric) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - auto impl = std::make_unique(dim, metric); - *index = new FlatIndexManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + auto impl = std::make_unique(dim, metric); + *index = new FlatIndexManager{std::move(impl)}; + return Status_Ok; + }); } Status FlatIndex::destroy(FlatIndex* index) noexcept { - SVS_RUNTIME_TRY_BEGIN - delete index; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + delete index; + return Status_Ok; + }); } Status FlatIndex::load(FlatIndex** index, std::istream& in, MetricType metric) noexcept { *index = nullptr; - SVS_RUNTIME_TRY_BEGIN - std::unique_ptr impl{FlatIndexImpl::load(in, metric)}; - *index = new FlatIndexManager{std::move(impl)}; - return Status_Ok; - SVS_RUNTIME_TRY_END + return runtime_error_wrapper([&] { + std::unique_ptr impl{FlatIndexImpl::load(in, metric)}; + *index = new FlatIndexManager{std::move(impl)}; + return Status_Ok; + }); } } // namespace runtime } // namespace svs diff --git a/bindings/cpp/src/training.cpp b/bindings/cpp/src/training.cpp index 1e49f5a8..88e30946 100644 --- a/bindings/cpp/src/training.cpp +++ b/bindings/cpp/src/training.cpp @@ -21,6 +21,8 @@ namespace svs { namespace runtime { +LeanVecTrainingData::~LeanVecTrainingData() = default; + Status LeanVecTrainingData::build( LeanVecTrainingData** training_data, size_t dim, diff --git a/bindings/cpp/src/vamana_index.cpp b/bindings/cpp/src/vamana_index.cpp new file mode 100644 index 00000000..048cc832 --- /dev/null +++ b/bindings/cpp/src/vamana_index.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2025 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. + */ + +#include "vamana_index.h" + +namespace svs { +namespace runtime { + +// VamanaIndex interface implementation +VamanaIndex::~VamanaIndex() = default; + +} // namespace runtime +} // namespace svs \ No newline at end of file From c7e039f66d9f1442c627265d68469397bde12211 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 12:49:18 +0100 Subject: [PATCH 36/46] Inline versioning namespace --- bindings/cpp/include/IndexSVSFlatImpl.h | 5 ---- bindings/cpp/include/IndexSVSImplDefs.h | 13 ++-------- bindings/cpp/include/IndexSVSVamanaImpl.h | 5 ---- bindings/cpp/include/IndexSVSVamanaLVQImpl.h | 6 ----- .../cpp/include/IndexSVSVamanaLeanVecImpl.h | 5 ---- bindings/cpp/include/dynamic_vamana_index.h | 6 ----- bindings/cpp/include/flat_index.h | 5 ---- bindings/cpp/include/training.h | 5 ---- bindings/cpp/include/vamana_index.h | 5 ---- bindings/cpp/include/version.h | 26 ++++++++++--------- 10 files changed, 16 insertions(+), 65 deletions(-) diff --git a/bindings/cpp/include/IndexSVSFlatImpl.h b/bindings/cpp/include/IndexSVSFlatImpl.h index a784585c..0f8fbeeb 100644 --- a/bindings/cpp/include/IndexSVSFlatImpl.h +++ b/bindings/cpp/include/IndexSVSFlatImpl.h @@ -15,7 +15,6 @@ */ #include "IndexSVSImplDefs.h" -#include "version.h" #include #include #include @@ -52,9 +51,5 @@ class SVS_RUNTIME_API IndexSVSFlatImpl { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::IndexSVSFlatImpl; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index 50f01298..1ee30a8f 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -16,6 +16,8 @@ #pragma once +#include "version.h" + #include #include @@ -86,16 +88,5 @@ struct SVS_RUNTIME_API_INTERFACE ResultsAllocator { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::ErrorCode; -using v0::IDFilter; -using v0::MetricType; -using v0::ResultsAllocator; -using v0::SearchResultsStorage; -using v0::Status; -using v0::Status_Ok; -using v0::StorageKind; - } // namespace runtime } // namespace svs \ No newline at end of file diff --git a/bindings/cpp/include/IndexSVSVamanaImpl.h b/bindings/cpp/include/IndexSVSVamanaImpl.h index 890634df..27d06308 100644 --- a/bindings/cpp/include/IndexSVSVamanaImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaImpl.h @@ -16,7 +16,6 @@ #pragma once #include "IndexSVSImplDefs.h" -#include "version.h" #include #include @@ -108,9 +107,5 @@ struct SVS_RUNTIME_API IndexSVSVamanaImpl { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::IndexSVSVamanaImpl; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h index 0ede86af..d45874b4 100644 --- a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h @@ -16,7 +16,6 @@ #pragma once #include "IndexSVSVamanaImpl.h" -#include "version.h" namespace svs::runtime::v0 { @@ -39,8 +38,3 @@ struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { }; } // namespace svs::runtime::v0 - -// Bring current version APIs to parent namespace -namespace svs::runtime { -using v0::IndexSVSVamanaLVQImpl; -} diff --git a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h index c845c570..935c68e9 100644 --- a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h +++ b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h @@ -17,7 +17,6 @@ #pragma once #include "IndexSVSImplDefs.h" #include "IndexSVSVamanaImpl.h" -#include "version.h" #include #include @@ -71,9 +70,5 @@ struct SVS_RUNTIME_API IndexSVSVamanaLeanVecImpl : IndexSVSVamanaImpl { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::IndexSVSVamanaLeanVecImpl; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/dynamic_vamana_index.h b/bindings/cpp/include/dynamic_vamana_index.h index b3b0a333..8ec742d2 100644 --- a/bindings/cpp/include/dynamic_vamana_index.h +++ b/bindings/cpp/include/dynamic_vamana_index.h @@ -18,7 +18,6 @@ #include "IndexSVSImplDefs.h" #include "training.h" #include "vamana_index.h" -#include "version.h" #include #include @@ -83,10 +82,5 @@ struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::DynamicVamanaIndex; -using v0::DynamicVamanaIndexLeanVec; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/flat_index.h b/bindings/cpp/include/flat_index.h index 11a2ee0e..68f6c7bc 100644 --- a/bindings/cpp/include/flat_index.h +++ b/bindings/cpp/include/flat_index.h @@ -16,7 +16,6 @@ #pragma once #include "IndexSVSImplDefs.h" -#include "version.h" #include #include @@ -45,9 +44,5 @@ struct SVS_RUNTIME_API FlatIndex { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::FlatIndex; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/training.h index 4250297a..b108abfc 100644 --- a/bindings/cpp/include/training.h +++ b/bindings/cpp/include/training.h @@ -16,7 +16,6 @@ #pragma once #include "IndexSVSImplDefs.h" -#include "version.h" #include #include @@ -43,9 +42,5 @@ struct SVS_RUNTIME_API LeanVecTrainingData { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::LeanVecTrainingData; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/vamana_index.h index e03a6032..9bc82c31 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/vamana_index.h @@ -16,7 +16,6 @@ #pragma once #include "IndexSVSImplDefs.h" -#include "version.h" #include @@ -66,9 +65,5 @@ struct SVS_RUNTIME_API VamanaIndex { }; } // namespace v0 - -// Bring current version APIs to parent namespace -using v0::VamanaIndex; - } // namespace runtime } // namespace svs diff --git a/bindings/cpp/include/version.h b/bindings/cpp/include/version.h index cc405e70..b46558e5 100644 --- a/bindings/cpp/include/version.h +++ b/bindings/cpp/include/version.h @@ -54,22 +54,29 @@ #define SVS_RUNTIME_VERSION_STRING "0.1.0" #endif -///// API Version Namespace Declaration +#ifndef SVS_RUNTIME_API_VERSION +/// Default to current major version if not specified by client +#define SVS_RUNTIME_API_VERSION SVS_RUNTIME_VERSION_MAJOR +#endif +#if (SVS_RUNTIME_API_VERSION == 0) +/// Use v0 API +/// Current API version namespace +#define SVS_RUNTIME_CURRENT_API_NAMESPACE v0 namespace svs { namespace runtime { /// Current API version namespace (v0) /// All public runtime APIs live here and are accessible as svs::runtime::FunctionName -/// via using declarations -namespace v0 { +/// due to inline namespace +inline namespace v0 { // Public runtime APIs will be defined in their respective headers -// and brought up via using declarations +// IMPORTANT: include this header before other runtime headers to ensure proper versioning } - -// Using declarations to bring current version APIs to parent namespace -// These will be added as we define versioned APIs in their respective headers } // namespace runtime } // namespace svs +#else +#error "Unsupported SVS Runtime major version" +#endif ///// Integration Support @@ -104,8 +111,3 @@ struct VersionInfo { }; } // namespace svs::runtime::v0 - -// Bring current version APIs to parent namespace -namespace svs::runtime { -using v0::VersionInfo; -} // namespace svs::runtime From 597c4b50c7e17f4ceed5433d602d84dd1f1b7fd6 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 13:01:08 +0100 Subject: [PATCH 37/46] Remove outdated SVS Runtime code --- bindings/cpp/CMakeLists.txt | 13 - bindings/cpp/include/IndexSVSFlatImpl.h | 55 -- bindings/cpp/include/IndexSVSVamanaImpl.h | 111 ---- bindings/cpp/include/IndexSVSVamanaLVQImpl.h | 40 -- .../cpp/include/IndexSVSVamanaLeanVecImpl.h | 74 --- bindings/cpp/src/IndexSVSFlatImpl.cpp | 146 ------ bindings/cpp/src/IndexSVSImplUtils.h | 34 -- bindings/cpp/src/IndexSVSVamanaImpl.cpp | 496 ------------------ bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp | 201 ------- .../cpp/src/IndexSVSVamanaLeanVecImpl.cpp | 271 ---------- bindings/cpp/src/svs_runtime_utils.h | 13 +- 11 files changed, 11 insertions(+), 1443 deletions(-) delete mode 100644 bindings/cpp/include/IndexSVSFlatImpl.h delete mode 100644 bindings/cpp/include/IndexSVSVamanaImpl.h delete mode 100644 bindings/cpp/include/IndexSVSVamanaLVQImpl.h delete mode 100644 bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h delete mode 100644 bindings/cpp/src/IndexSVSFlatImpl.cpp delete mode 100644 bindings/cpp/src/IndexSVSImplUtils.h delete mode 100644 bindings/cpp/src/IndexSVSVamanaImpl.cpp delete mode 100644 bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp delete mode 100644 bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 489256b6..6226dda6 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -19,8 +19,6 @@ set(TARGET_NAME svs_runtime) set(SVS_RUNTIME_HEADERS include/version.h include/IndexSVSImplDefs.h - include/IndexSVSFlatImpl.h - include/IndexSVSVamanaImpl.h include/training.h include/vamana_index.h include/dynamic_vamana_index.h @@ -28,12 +26,9 @@ set(SVS_RUNTIME_HEADERS ) set(SVS_RUNTIME_SOURCES - src/IndexSVSImplUtils.h src/svs_runtime_utils.h src/dynamic_vamana_index_impl.h src/flat_index_impl.h - src/IndexSVSFlatImpl.cpp - src/IndexSVSVamanaImpl.cpp src/training.cpp src/vamana_index.cpp src/dynamic_vamana_index.cpp @@ -43,14 +38,6 @@ set(SVS_RUNTIME_SOURCES 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") - list(APPEND SVS_RUNTIME_HEADERS - include/IndexSVSVamanaLVQImpl.h - include/IndexSVSVamanaLeanVecImpl.h - ) - list(APPEND SVS_RUNTIME_SOURCES - src/IndexSVSVamanaLVQImpl.cpp - src/IndexSVSVamanaLeanVecImpl.cpp - ) else() message(STATUS "SVS runtime will be built without LVQ or LeanVec support") endif() diff --git a/bindings/cpp/include/IndexSVSFlatImpl.h b/bindings/cpp/include/IndexSVSFlatImpl.h deleted file mode 100644 index 0f8fbeeb..00000000 --- a/bindings/cpp/include/IndexSVSFlatImpl.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include "IndexSVSImplDefs.h" -#include -#include -#include - -namespace svs { -class Flat; - -namespace runtime { -namespace v0 { - -class SVS_RUNTIME_API IndexSVSFlatImpl { - public: - static IndexSVSFlatImpl* build(size_t dim, MetricType metric) noexcept; - static void destroy(IndexSVSFlatImpl* impl) noexcept; - - Status add(size_t n, const float* x) noexcept; - void reset() noexcept; - - Status search(size_t n, const float* x, size_t k, float* distances, size_t* labels) - const noexcept; - - Status serialize(std::ostream& out) const noexcept; - - Status deserialize(std::istream& in) noexcept; - - private: - IndexSVSFlatImpl(size_t dim, MetricType metric); - ~IndexSVSFlatImpl(); - Status init_impl(size_t n, const float* x) noexcept; - - MetricType metric_type_; - size_t dim_; - std::unique_ptr impl{nullptr}; -}; - -} // namespace v0 -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/include/IndexSVSVamanaImpl.h b/bindings/cpp/include/IndexSVSVamanaImpl.h deleted file mode 100644 index 27d06308..00000000 --- a/bindings/cpp/include/IndexSVSVamanaImpl.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2025 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 "IndexSVSImplDefs.h" - -#include -#include -#include -#include -#include -#include - -namespace svs { -class DynamicVamana; - -namespace runtime { -namespace v0 { - -struct SVS_RUNTIME_API IndexSVSVamanaImpl { - struct SearchParams { - size_t search_window_size = 0; - size_t search_buffer_capacity = 0; - }; - - enum StorageKind { FP32, FP16, SQI8 } storage_kind = StorageKind::FP32; - - struct BuildParams { - StorageKind storage_kind; - size_t graph_max_degree; - size_t prune_to; - float alpha = 1.2; - size_t construction_window_size = 40; - size_t max_candidate_pool_size = 200; - bool use_full_search_history = true; - }; - - static IndexSVSVamanaImpl* - build(size_t dim, MetricType metric, const BuildParams& params) noexcept; - static void destroy(IndexSVSVamanaImpl* impl) noexcept; - - Status add(size_t n, const float* x) noexcept; - - Status search( - size_t n, - const float* x, - size_t k, - float* distances, - size_t* labels, - const SearchParams* params = nullptr, - IDFilter* filter = nullptr - ) const noexcept; - - Status range_search( - size_t n, - const float* x, - float radius, - const ResultsAllocator& results, - const SearchParams* params = nullptr, - IDFilter* filter = nullptr - ) const noexcept; - - size_t remove_ids(const IDFilter& selector) noexcept; - - virtual void reset() noexcept; - - /* Serialization and deserialization helpers */ - Status serialize_impl(std::ostream& out) const noexcept; - virtual Status deserialize_impl(std::istream& in) noexcept; - - MetricType metric_type_; - size_t dim_; - SearchParams default_search_params{10, 10}; - BuildParams build_params{}; - - protected: - IndexSVSVamanaImpl(); - - IndexSVSVamanaImpl( - size_t d, - size_t degree, - MetricType metric = MetricType::L2, - StorageKind storage = StorageKind::FP32 - ); - - virtual ~IndexSVSVamanaImpl(); - - /* Initializes the implementation, using the provided data */ - virtual Status init_impl(size_t n, const float* x) noexcept; - - /* The actual SVS implementation */ - std::unique_ptr impl{nullptr}; - size_t ntotal_soft_deleted{0}; -}; - -} // namespace v0 -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h b/bindings/cpp/include/IndexSVSVamanaLVQImpl.h deleted file mode 100644 index d45874b4..00000000 --- a/bindings/cpp/include/IndexSVSVamanaLVQImpl.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2025 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 "IndexSVSVamanaImpl.h" - -namespace svs::runtime::v0 { - -struct SVS_RUNTIME_API IndexSVSVamanaLVQImpl : IndexSVSVamanaImpl { - enum LVQLevel { LVQ4x0, LVQ4x4, LVQ4x8 }; - static IndexSVSVamanaLVQImpl* - build(size_t dim, MetricType metric, const BuildParams& params, LVQLevel lvq) noexcept; - - Status deserialize_impl(std::istream& in) noexcept override; - - protected: - IndexSVSVamanaLVQImpl(); - IndexSVSVamanaLVQImpl(size_t d, size_t degree, MetricType metric, LVQLevel lvq_level); - - ~IndexSVSVamanaLVQImpl() override; - - Status init_impl(size_t n, const float* x) noexcept override; - - LVQLevel lvq_level; -}; - -} // namespace svs::runtime::v0 diff --git a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h b/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h deleted file mode 100644 index 935c68e9..00000000 --- a/bindings/cpp/include/IndexSVSVamanaLeanVecImpl.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2025 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 "IndexSVSImplDefs.h" -#include "IndexSVSVamanaImpl.h" - -#include -#include - -namespace svs { -namespace leanvec { -template struct LeanVecMatrices; -} // namespace leanvec - -namespace runtime { -namespace v0 { - -struct SVS_RUNTIME_API IndexSVSVamanaLeanVecImpl : IndexSVSVamanaImpl { - enum LeanVecLevel { LeanVec4x4, LeanVec4x8, LeanVec8x8 }; - - static IndexSVSVamanaLeanVecImpl* build( - size_t dim, - MetricType metric, - const BuildParams& params, - size_t leanvec_dims, - LeanVecLevel leanvec_level - ) noexcept; - - void reset() noexcept override; - - Status train(size_t n, const float* x) noexcept; - - Status deserialize_impl(std::istream& in) noexcept override; - - bool is_trained() const noexcept { return trained; } - - protected: - IndexSVSVamanaLeanVecImpl(); - - IndexSVSVamanaLeanVecImpl( - size_t d, - size_t degree, - MetricType metric = MetricType::L2, - size_t leanvec_dims = 0, - LeanVecLevel leanvec_level = LeanVecLevel::LeanVec4x4 - ); - - ~IndexSVSVamanaLeanVecImpl() override; - - Status init_impl(size_t n, const float* x) noexcept override; - - size_t leanvec_d; - LeanVecLevel leanvec_level; - std::unique_ptr> leanvec_matrix; - bool trained = false; -}; - -} // namespace v0 -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/src/IndexSVSFlatImpl.cpp b/bindings/cpp/src/IndexSVSFlatImpl.cpp deleted file mode 100644 index a099917c..00000000 --- a/bindings/cpp/src/IndexSVSFlatImpl.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include "IndexSVSFlatImpl.h" -#include "IndexSVSImplUtils.h" - -#include -#include -#include - -#include - -namespace svs { - -namespace runtime { - -IndexSVSFlatImpl* IndexSVSFlatImpl::build(size_t dim, MetricType metric) noexcept { - return new IndexSVSFlatImpl(dim, metric); -} - -void IndexSVSFlatImpl::destroy(IndexSVSFlatImpl* impl) noexcept { delete impl; } - -Status IndexSVSFlatImpl::init_impl(size_t n, const float* x) noexcept { - auto data = svs::data::SimpleData(n, dim_); - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - svs::threads::parallel_for( - threadpool, - svs::threads::StaticPartition(n), - [&](auto is, auto SVS_UNUSED(tid)) { - for (auto i : is) { - data.set_datum(i, std::span(x + i * dim_, dim_)); - } - } - ); - - switch (metric_type_) { - case MetricType::INNER_PRODUCT: - impl.reset(new svs::Flat(svs::Flat::assemble( - std::move(data), svs::DistanceIP(), std::move(threadpool) - ))); - break; - case MetricType::L2: - impl.reset(new svs::Flat(svs::Flat::assemble( - std::move(data), svs::DistanceL2(), std::move(threadpool) - ))); - break; - default: - impl = nullptr; - return {ErrorCode::INVALID_ARGUMENT, "not supported SVS distance"}; - } - return Status_Ok; -} - -Status IndexSVSFlatImpl::add(size_t n, const float* x) noexcept { - if (!impl) { - return init_impl(n, x); - } - - return { - ErrorCode::NOT_IMPLEMENTED, - "IndexSVSFlat does not support adding points after initialization"}; -} - -void IndexSVSFlatImpl::reset() noexcept { impl.reset(); } - -Status IndexSVSFlatImpl::search( - size_t n, const float* x, size_t k, float* distances, size_t* labels -) const noexcept { - if (!impl) { - return {ErrorCode::UNKNOWN_ERROR, "SVS index not initialized"}; - } - if (k == 0) { - return {ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; - } - - auto queries = svs::data::ConstSimpleDataView(x, n, dim_); - - auto results = svs::QueryResult{queries.size(), static_cast(k)}; - impl->search(results.view(), queries, {}); - - svs::threads::parallel_for( - impl->get_threadpool_handle(), - svs::threads::StaticPartition(n), - [&](auto is, auto SVS_UNUSED(tid)) { - for (auto i : is) { - for (size_t j = 0; j < k; ++j) { - labels[j + i * k] = results.index(i, j); - distances[j + i * k] = results.distance(i, j); - } - } - } - ); - return Status_Ok; -} - -Status IndexSVSFlatImpl::serialize(std::ostream& out) const noexcept { - if (!impl) { - return {ErrorCode::UNKNOWN_ERROR, "Cannot serialize: SVS index not initialized."}; - } - - impl->save(out); - return Status_Ok; -} - -Status IndexSVSFlatImpl::deserialize(std::istream& in) noexcept { - if (impl) { - return { - ErrorCode::UNKNOWN_ERROR, "Cannot deserialize: SVS index already initialized."}; - } - - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - using storage_type = typename svs::VectorDataLoader::return_type; - - svs::DistanceDispatcher dispatcher(to_svs_distance(metric_type_)); - dispatcher([&](auto&& distance) { - impl.reset(new svs::Flat(svs::Flat::assemble( - in, std::move(distance), std::move(threadpool) - ))); - }); - - return Status_Ok; -} - -IndexSVSFlatImpl::IndexSVSFlatImpl(size_t dim, MetricType metric) - : metric_type_(metric) - , dim_(dim) {} -IndexSVSFlatImpl::~IndexSVSFlatImpl() = default; - -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/src/IndexSVSImplUtils.h b/bindings/cpp/src/IndexSVSImplUtils.h deleted file mode 100644 index 8487519e..00000000 --- a/bindings/cpp/src/IndexSVSImplUtils.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2025 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 "IndexSVSImplDefs.h" -#include - -namespace svs { -namespace runtime { -inline svs::DistanceType to_svs_distance(MetricType metric) { - switch (metric) { - case MetricType::L2: - return svs::DistanceType::L2; - case MetricType::INNER_PRODUCT: - return svs::DistanceType::MIP; - } - throw ANNEXCEPTION("unreachable reached"); // Make GCC happy -} -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/src/IndexSVSVamanaImpl.cpp b/bindings/cpp/src/IndexSVSVamanaImpl.cpp deleted file mode 100644 index 55df68d0..00000000 --- a/bindings/cpp/src/IndexSVSVamanaImpl.cpp +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include "IndexSVSVamanaImpl.h" -#include "IndexSVSImplUtils.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace svs { -namespace runtime { -namespace { - -std::variant -get_storage_variant(IndexSVSVamanaImpl::StorageKind kind) { - switch (kind) { - case IndexSVSVamanaImpl::StorageKind::FP32: - return float{}; - case IndexSVSVamanaImpl::StorageKind::FP16: - return svs::Float16{}; - case IndexSVSVamanaImpl::StorageKind::SQI8: - return std::int8_t{}; - default: - throw ANNEXCEPTION("not supported SVS storage kind"); - } -} - -svs::index::vamana::VamanaBuildParameters -get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { - return svs::index::vamana::VamanaBuildParameters{ - params.alpha, - params.graph_max_degree, - params.construction_window_size, - params.max_candidate_pool_size, - params.prune_to, - params.use_full_search_history}; -} - -template < - typename T, - typename Alloc = svs::data::Blocked>, - svs::data::ImmutableMemoryDataset Dataset, - svs::threads::ThreadPool Pool> - requires std::is_floating_point_v || std::is_same_v -svs::data::SimpleData -make_storage(const Dataset& data, Pool& pool) { - svs::data::SimpleData result( - data.size(), data.dimensions(), Alloc{} - ); - svs::threads::parallel_for( - pool, - svs::threads::StaticPartition(result.size()), - [&](auto is, auto SVS_UNUSED(tid)) { - for (auto i : is) { - result.set_datum(i, data.get_datum(i)); - } - } - ); - return result; -} - -template < - typename T, - typename Alloc = svs::data::Blocked>, - svs::data::ImmutableMemoryDataset Dataset, - svs::threads::ThreadPool Pool> - requires std::is_integral_v -svs::quantization::scalar::SQDataset -make_storage(const Dataset& data, Pool& pool) { - return svs::quantization::scalar::SQDataset::compress( - data, pool, Alloc{} - ); -} - -template -svs::DynamicVamana* -init_impl_t(IndexSVSVamanaImpl* index, MetricType metric, size_t n, const float* x) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - auto data = make_storage( - svs::data::ConstSimpleDataView(x, n, index->dim_), threadpool - ); - - std::vector labels(data.size()); - std::iota(labels.begin(), labels.end(), 0); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::move(data), - std::move(labels), - std::forward(distance), - std::move(threadpool) - )); - }); -} - -template < - typename T, - typename Alloc = svs::data::Blocked>, - typename Enabler = void> -struct storage_type; - -template -struct storage_type< - T, - Alloc, - std::enable_if_t || std::is_same_v>> { - using type = svs::data::SimpleData; -}; - -template -struct storage_type>> { - using type = svs::quantization::scalar::SQDataset; -}; - -template >> -using storage_type_t = typename storage_type::type; - -template -svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana( - svs::DynamicVamana::assemble>( - stream, std::forward(distance), std::move(threadpool) - ) - ); - }); -} - -svs::index::vamana::VamanaSearchParameters make_search_parameters( - const std::unique_ptr& impl, - const IndexSVSVamanaImpl::SearchParams& default_params, - const IndexSVSVamanaImpl::SearchParams* params -) { - if (!impl) { - throw ANNEXCEPTION("Index not initialized"); - } - - auto search_window_size = default_params.search_window_size; - auto search_buffer_capacity = default_params.search_buffer_capacity; - - if (params != nullptr) { - if (params->search_window_size > 0) - search_window_size = params->search_window_size; - if (params->search_buffer_capacity > 0) - search_buffer_capacity = params->search_buffer_capacity; - } - - return impl->get_search_parameters().buffer_config( - {search_window_size, search_buffer_capacity} - ); -} -} // namespace - -IndexSVSVamanaImpl* IndexSVSVamanaImpl::build( - size_t dim, MetricType metric, const BuildParams& params -) noexcept { - try { - auto index = new IndexSVSVamanaImpl( - dim, params.graph_max_degree, metric, params.storage_kind - ); - index->build_params = params; - return index; - } catch (...) { return nullptr; } -} - -void IndexSVSVamanaImpl::destroy(IndexSVSVamanaImpl* impl) noexcept { delete impl; } - -IndexSVSVamanaImpl::IndexSVSVamanaImpl() = default; - -IndexSVSVamanaImpl::IndexSVSVamanaImpl( - size_t d, size_t degree, MetricType metric, StorageKind storage -) - : metric_type_(metric) - , dim_(d) - , build_params{ - storage, - degree, - degree < 4 ? degree : degree - 4, - metric == MetricType::L2 ? 1.2f : 0.95f, - 40, - 200, - true} {} - -IndexSVSVamanaImpl::~IndexSVSVamanaImpl() = default; - -Status IndexSVSVamanaImpl::add(size_t n, const float* x) noexcept { - if (!impl) { - return init_impl(n, x); - } - - // construct sequential labels - std::vector labels(n); - - std::iota(labels.begin(), labels.end(), impl->size()); - - auto data = svs::data::ConstSimpleDataView(x, n, dim_); - impl->add_points(data, labels); - return Status_Ok; -} - -void IndexSVSVamanaImpl::reset() noexcept { - if (impl) { - impl.reset(); - } - ntotal_soft_deleted = 0; -} - -Status IndexSVSVamanaImpl::search( - size_t n, - const float* x, - size_t k, - float* distances, - size_t* labels, - const SearchParams* params, - IDFilter* filter -) const noexcept { - if (!impl) { - for (size_t i = 0; i < n; ++i) { - distances[i] = std::numeric_limits::infinity(); - labels[i] = -1; - } - return Status{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; - } - - if (k == 0) { - return Status{ErrorCode::INVALID_ARGUMENT, "k must be greater than 0"}; - } - - auto sp = make_search_parameters(impl, default_search_params, params); - - // Simple search - if (filter == nullptr) { - auto queries = svs::data::ConstSimpleDataView(x, n, dim_); - - // TODO: faiss use int64_t as label whereas SVS uses size_t? - auto results = svs::QueryResultView{ - svs::MatrixView{ - svs::make_dims(n, k), static_cast(static_cast(labels))}, - svs::MatrixView{svs::make_dims(n, k), distances}}; - impl->search(results, queries, sp); - return Status_Ok; - } - - // Selective search with IDSelector - auto old_sp = impl->get_search_parameters(); - impl->set_search_parameters(sp); - - auto search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { - for (auto i : range) { - // For every query - auto query = std::span(x + i * dim_, dim_); - auto curr_distances = std::span(distances + i * k, k); - auto curr_labels = std::span(labels + i * k, k); - - auto iterator = impl->batch_iterator(query); - size_t found = 0; - do { - iterator.next(k); - for (auto& neighbor : iterator.results()) { - if (filter->is_member(neighbor.id())) { - curr_distances[found] = neighbor.distance(); - curr_labels[found] = neighbor.id(); - found++; - if (found == k) { - break; - } - } - } - } while (found < k && !iterator.done()); - // Pad with -1s - for (; found < k; ++found) { - curr_distances[found] = -1; - curr_labels[found] = -1; - } - } - }; - - auto threadpool = - svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); - - svs::threads::parallel_for( - threadpool, svs::threads::StaticPartition{n}, search_closure - ); - - impl->set_search_parameters(old_sp); - - return Status_Ok; -} - -Status IndexSVSVamanaImpl::range_search( - size_t n, - const float* x, - float radius, - const ResultsAllocator& results, - const SearchParams* params, - IDFilter* filter -) const noexcept { - if (!impl) { - return Status{ErrorCode::NOT_INITIALIZED, "Index not initialized"}; - } - if (radius <= 0) { - return Status{ErrorCode::INVALID_ARGUMENT, "radius must be greater than 0"}; - } - - auto sp = make_search_parameters(impl, default_search_params, params); - auto old_sp = impl->get_search_parameters(); - impl->set_search_parameters(sp); - - // Using ResultHandler makes no sense due to it's complexity, overhead and - // missed features; e.g. add_result() does not indicate whether result added - // or not - we have to manually manage threshold comparison and id - // selection. - - // Prepare output buffers - std::vector>> all_results(n); - // Reserve space for allocation to avoid multiple reallocations - // Use search_buffer_capacity as a heuristic - const auto result_capacity = sp.buffer_config_.get_total_capacity(); - for (auto& res : all_results) { - res.reserve(result_capacity); - } - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric_type_)); - - std::function compare = distance_dispatcher([](auto&& dist) { - return std::function{svs::distance::comparator(dist)}; - }); - - std::function select = [](size_t) { return true; }; - if (filter != nullptr) { - select = [&](size_t id) { return filter->is_member(id); }; - } - - // Set iterator batch size to search window size - auto batch_size = sp.buffer_config_.get_search_window_size(); - - auto range_search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) { - for (auto i : range) { - // For every query - auto query = std::span(x + i * dim_, dim_); - - auto iterator = impl->batch_iterator(query); - bool in_range = true; - - do { - iterator.next(batch_size); - for (auto& neighbor : iterator.results()) { - // SVS comparator functor returns true if the first distance - // is 'closer' than the second one - in_range = compare(neighbor.distance(), radius); - if (in_range) { - // Selective search with IDSelector - if (select(neighbor.id())) { - all_results[i].push_back(neighbor); - } - } else { - // Since iterator.results() are ordered by distance, we - // can stop processing - break; - } - } - } while (in_range && !iterator.done()); - } - }; - - auto threadpool = - svs::threads::OMPThreadPool(std::min(n, size_t(omp_get_max_threads()))); - - svs::threads::parallel_for( - threadpool, svs::threads::StaticPartition{n}, range_search_closure - ); - - // Allocate output - std::vector result_counts(n); - std::transform( - all_results.begin(), - all_results.end(), - result_counts.begin(), - [](const auto& res) { return res.size(); } - ); - auto results_storage = results(result_counts); - - // Fill in results - for (size_t q = 0, ofs = 0; q < n; ++q) { - for (const auto& [id, distance] : all_results[q]) { - results_storage.labels[ofs] = id; - results_storage.distances[ofs] = distance; - ofs++; - } - } - - impl->set_search_parameters(old_sp); - return Status_Ok; -} - -size_t IndexSVSVamanaImpl::remove_ids(const IDFilter& selector) noexcept { - if (!impl) { - return 0; - } - - auto ids = impl->all_ids(); - std::vector ids_to_delete; - std::copy_if(ids.begin(), ids.end(), std::back_inserter(ids_to_delete), [&](size_t id) { - return selector(id); - }); - - // SVS deletion is a soft deletion, meaning the corresponding vectors are - // marked as deleted but still present in both the dataset and the graph, - // and will be navigated through during search. - // Actual cleanup happens once a large enough number of soft deleted vectors - // are collected. - impl->delete_points(ids_to_delete); - // ntotal -= ids.size(); - ntotal_soft_deleted += ids_to_delete.size(); - - auto ntotal = impl->size(); - const float cleanup_threshold = .5f; - if (ntotal == 0 || (float)ntotal_soft_deleted / ntotal > cleanup_threshold) { - impl->consolidate(); - impl->compact(); - ntotal_soft_deleted = 0; - } - return ids_to_delete.size(); -} - -Status IndexSVSVamanaImpl::init_impl(size_t n, const float* x) noexcept { - if (impl) { - return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; - } - - impl.reset(std::visit( - [&](auto element) { - using ElementType = std::decay_t; - return init_impl_t(this, metric_type_, n, x); - }, - get_storage_variant(storage_kind) - )); - return Status_Ok; -} - -Status IndexSVSVamanaImpl::serialize_impl(std::ostream& out) const noexcept { - if (!impl) { - return Status{ - ErrorCode::NOT_INITIALIZED, "Cannot serialize: SVS index not initialized."}; - } - - impl->save(out); - return Status_Ok; -} - -Status IndexSVSVamanaImpl::deserialize_impl(std::istream& in) noexcept { - if (impl) { - return Status{ - ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized."}; - } - - impl.reset(std::visit( - [&](auto element) { - using ElementType = std::decay_t; - return deserialize_impl_t(in, metric_type_); - }, - get_storage_variant(storage_kind) - )); - return Status_Ok; -} - -} // namespace runtime -} // namespace svs diff --git a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp deleted file mode 100644 index 85b115ee..00000000 --- a/bindings/cpp/src/IndexSVSVamanaLVQImpl.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include "IndexSVSVamanaLVQImpl.h" -#include "IndexSVSImplUtils.h" - -#include - -#include -#include -#include -#include - -#include SVS_LVQ_HEADER - -namespace svs::runtime { - -namespace { -using blocked_alloc_type = svs::data::Blocked>; -using blocked_alloc_type_sq = svs::data::Blocked>; - -using strategy_type_4 = svs::quantization::lvq::Turbo<16, 8>; - -using storage_type_4x0 = svs::quantization::lvq:: - LVQDataset<4, 0, svs::Dynamic, strategy_type_4, blocked_alloc_type>; -using storage_type_4x4 = svs::quantization::lvq:: - LVQDataset<4, 4, svs::Dynamic, strategy_type_4, blocked_alloc_type>; -using storage_type_4x8 = svs::quantization::lvq:: - LVQDataset<4, 8, svs::Dynamic, strategy_type_4, blocked_alloc_type>; -using storage_type_sq = - svs::quantization::scalar::SQDataset; - -svs::index::vamana::VamanaBuildParameters -get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { - return svs::index::vamana::VamanaBuildParameters{ - params.alpha, - params.graph_max_degree, - params.construction_window_size, - params.max_candidate_pool_size, - params.prune_to, - params.use_full_search_history}; -} - -template -svs::DynamicVamana* init_impl_t( - IndexSVSVamanaImpl* index, - StorageType&& storage, - MetricType metric, - ThreadPoolProto&& threadpool -) { - std::vector labels(storage.size()); - std::iota(labels.begin(), labels.end(), 0); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::forward(storage), - std::move(labels), - std::forward(distance), - std::forward(threadpool) - )); - }); -} - -template -svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::assemble( - stream, std::forward(distance), std::move(threadpool) - )); - }); -} - -} // namespace - -IndexSVSVamanaLVQImpl* IndexSVSVamanaLVQImpl::build( - size_t dim, MetricType metric, const BuildParams& params, LVQLevel lvq -) noexcept { - try { - auto index = new IndexSVSVamanaLVQImpl(dim, params.graph_max_degree, metric, lvq); - index->build_params = params; - return index; - } catch (...) { return nullptr; } -} - -IndexSVSVamanaLVQImpl::IndexSVSVamanaLVQImpl() = default; - -IndexSVSVamanaLVQImpl::IndexSVSVamanaLVQImpl( - size_t d, size_t degree, MetricType metric, LVQLevel lvq_level -) - : IndexSVSVamanaImpl(d, degree, metric) - , lvq_level{lvq_level} {} - -IndexSVSVamanaLVQImpl::~IndexSVSVamanaLVQImpl() = default; - -Status IndexSVSVamanaLVQImpl::init_impl(size_t n, const float* x) noexcept { - if (impl) { - return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; - } - - // TODO: support ConstSimpleDataView in SVS shared/static lib - const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); - - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - std::variant< - std::monostate, - storage_type_4x0, - storage_type_4x4, - storage_type_4x8, - storage_type_sq> - compressed_data; - - if (svs::detail::intel_enabled()) { - switch (lvq_level) { - case LVQLevel::LVQ4x0: - compressed_data = - storage_type_4x0::compress(data, threadpool, 0, blocked_alloc_type{}); - break; - case LVQLevel::LVQ4x4: - compressed_data = - storage_type_4x4::compress(data, threadpool, 0, blocked_alloc_type{}); - break; - case LVQLevel::LVQ4x8: - compressed_data = - storage_type_4x8::compress(data, threadpool, 0, blocked_alloc_type{}); - break; - default: - return Status{ErrorCode::NOT_IMPLEMENTED, "not supported SVS LVQ level"}; - } - } else { - compressed_data = - storage_type_sq::compress(data, threadpool, blocked_alloc_type_sq{}); - } - - return std::visit( - [&](auto&& storage) { - if constexpr (std::is_same_v, std::monostate>) { - return Status{ - ErrorCode::NOT_INITIALIZED, "SVS LVQ data is not initialized."}; - } else { - impl.reset(init_impl_t( - this, - std::forward(storage), - metric_type_, - std::move(threadpool) - )); - return Status_Ok; - } - }, - compressed_data - ); -} - -Status IndexSVSVamanaLVQImpl::deserialize_impl(std::istream& in) noexcept { - if (impl) { - return Status{ - ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized."}; - } - - if (svs::detail::intel_enabled()) { - switch (lvq_level) { - case LVQLevel::LVQ4x0: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - case LVQLevel::LVQ4x4: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - case LVQLevel::LVQ4x8: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - default: - return Status(ErrorCode::NOT_IMPLEMENTED, "not supported SVS LVQ level"); - } - } else { - impl.reset(deserialize_impl_t(in, metric_type_)); - } - return Status_Ok; -} - -} // namespace svs::runtime diff --git a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp b/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp deleted file mode 100644 index 5148cca3..00000000 --- a/bindings/cpp/src/IndexSVSVamanaLeanVecImpl.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include "IndexSVSVamanaLeanVecImpl.h" -#include "IndexSVSImplUtils.h" - -#include - -#include -#include -#include -#include -#include - -#include SVS_LEANVEC_HEADER - -namespace svs::runtime { - -namespace { -using blocked_alloc_type = svs::data::Blocked>; -using blocked_alloc_type_sq = svs::data::Blocked>; - -using storage_type_4x4 = svs::leanvec::LeanDataset< - svs::leanvec::UsingLVQ<4>, - svs::leanvec::UsingLVQ<4>, - svs::Dynamic, - svs::Dynamic, - blocked_alloc_type>; -using storage_type_4x8 = svs::leanvec::LeanDataset< - svs::leanvec::UsingLVQ<4>, - svs::leanvec::UsingLVQ<8>, - svs::Dynamic, - svs::Dynamic, - blocked_alloc_type>; -using storage_type_8x8 = svs::leanvec::LeanDataset< - svs::leanvec::UsingLVQ<8>, - svs::leanvec::UsingLVQ<8>, - svs::Dynamic, - svs::Dynamic, - blocked_alloc_type>; -using storage_type_sq = - svs::quantization::scalar::SQDataset; - -svs::index::vamana::VamanaBuildParameters -get_build_parameters(const IndexSVSVamanaImpl::BuildParams& params) { - return svs::index::vamana::VamanaBuildParameters{ - params.alpha, - params.graph_max_degree, - params.construction_window_size, - params.max_candidate_pool_size, - params.prune_to, - params.use_full_search_history}; -} - -template -svs::DynamicVamana* init_impl_t( - IndexSVSVamanaImpl* index, - StorageType&& storage, - MetricType metric, - ThreadPoolProto&& threadpool -) { - std::vector labels(storage.size()); - std::iota(labels.begin(), labels.end(), 0); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::build( - get_build_parameters(index->build_params), - std::forward(storage), - std::move(labels), - std::forward(distance), - std::forward(threadpool) - )); - }); -} - -template -svs::DynamicVamana* deserialize_impl_t(std::istream& stream, MetricType metric) { - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric)); - return distance_dispatcher([&](auto&& distance) { - return new svs::DynamicVamana(svs::DynamicVamana::assemble( - stream, std::forward(distance), std::move(threadpool) - )); - }); -} - -} // namespace - -IndexSVSVamanaLeanVecImpl* IndexSVSVamanaLeanVecImpl::build( - size_t dim, - MetricType metric, - const BuildParams& params, - size_t leanvec_dims, - LeanVecLevel leanvec_level -) noexcept { - try { - auto index = new IndexSVSVamanaLeanVecImpl( - dim, params.graph_max_degree, metric, leanvec_dims, leanvec_level - ); - index->build_params = params; - return index; - } catch (...) { return nullptr; } -} - -IndexSVSVamanaLeanVecImpl::IndexSVSVamanaLeanVecImpl() = default; - -IndexSVSVamanaLeanVecImpl::IndexSVSVamanaLeanVecImpl( - size_t d, - size_t degree, - MetricType metric, - size_t leanvec_dims, - LeanVecLevel leanvec_level -) - : IndexSVSVamanaImpl(d, degree, metric) - , leanvec_d{leanvec_dims == 0 ? d / 2 : leanvec_dims} - , leanvec_level{leanvec_level} {} - -IndexSVSVamanaLeanVecImpl::~IndexSVSVamanaLeanVecImpl() = default; - -void IndexSVSVamanaLeanVecImpl::reset() noexcept { - IndexSVSVamanaImpl::reset(); - leanvec_matrix.reset(); -} - -Status IndexSVSVamanaLeanVecImpl::train(size_t n, const float* x) noexcept { - const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - auto means = svs::utils::compute_medioid(data, threadpool); - auto matrix = svs::leanvec::compute_leanvec_matrix( - data, means, threadpool, svs::lib::MaybeStatic{leanvec_d} - ); - leanvec_matrix = - std::make_unique>(matrix, matrix); - trained = true; - return Status_Ok; -} - -Status IndexSVSVamanaLeanVecImpl::init_impl(size_t n, const float* x) noexcept { - if (impl) { - return Status{ErrorCode::UNKNOWN_ERROR, "Index already initialized"}; - } - - if (!is_trained()) { - return { - ErrorCode::NOT_INITIALIZED, - "Cannot initialize SVS LeanVec index without training first."}; - } - - // TODO: support ConstSimpleDataView in SVS shared/static lib - const auto data = svs::data::SimpleDataView(const_cast(x), n, dim_); - std::vector labels(n); - auto threadpool = - svs::threads::ThreadPoolHandle(svs::threads::OMPThreadPool(omp_get_max_threads())); - - std::variant< - std::monostate, - storage_type_4x4, - storage_type_4x8, - storage_type_8x8, - storage_type_sq> - compressed_data; - - if (svs::detail::intel_enabled()) { - switch (leanvec_level) { - case LeanVecLevel::LeanVec4x4: - compressed_data = storage_type_4x4::reduce( - data, - *leanvec_matrix, - threadpool, - 0, - svs::lib::MaybeStatic(leanvec_d), - blocked_alloc_type{} - ); - break; - case LeanVecLevel::LeanVec4x8: - compressed_data = storage_type_4x8::reduce( - data, - *leanvec_matrix, - threadpool, - 0, - svs::lib::MaybeStatic(leanvec_d), - blocked_alloc_type{} - ); - break; - case LeanVecLevel::LeanVec8x8: - compressed_data = storage_type_8x8::reduce( - data, - *leanvec_matrix, - threadpool, - 0, - svs::lib::MaybeStatic(leanvec_d), - blocked_alloc_type{} - ); - break; - default: - return Status{ - ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level"}; - } - } else { - compressed_data = - storage_type_sq::compress(data, threadpool, blocked_alloc_type_sq{}); - } - - return std::visit( - [&](auto&& storage) { - if constexpr (std::is_same_v, std::monostate>) { - return Status{ - ErrorCode::NOT_INITIALIZED, "SVS LeanVec data is not initialized."}; - } else { - impl.reset(init_impl_t( - this, - std::forward(storage), - metric_type_, - std::move(threadpool) - )); - return Status_Ok; - } - }, - compressed_data - ); -} - -Status IndexSVSVamanaLeanVecImpl::deserialize_impl(std::istream& in) noexcept { - if (impl) { - return Status{ - ErrorCode::INVALID_ARGUMENT, - "Cannot deserialize: SVS index already initialized."}; - } - - if (svs::detail::intel_enabled()) { - switch (leanvec_level) { - case LeanVecLevel::LeanVec4x4: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - case LeanVecLevel::LeanVec4x8: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - case LeanVecLevel::LeanVec8x8: - impl.reset(deserialize_impl_t(in, metric_type_)); - break; - default: - return Status( - ErrorCode::NOT_IMPLEMENTED, "not supported SVS LeanVec level" - ); - } - } else { - impl.reset(deserialize_impl_t(in, metric_type_)); - } - - trained = true; - return Status_Ok; -} - -} // namespace svs::runtime diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index 24ba188d..b30cf3d2 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -16,8 +16,7 @@ #pragma once -// TODO emplace content of IndexSVSImplUtils.h here -#include "IndexSVSImplUtils.h" +#include "IndexSVSImplDefs.h" // TODO remove unused includes #include @@ -53,6 +52,16 @@ namespace svs::runtime { +inline svs::DistanceType to_svs_distance(MetricType metric) { + switch (metric) { + case MetricType::L2: + return svs::DistanceType::L2; + case MetricType::INNER_PRODUCT: + return svs::DistanceType::MIP; + } + throw ANNEXCEPTION("unreachable reached"); // Make GCC happy +} + class StatusException : public svs::lib::ANNException { public: StatusException(const svs::runtime::ErrorCode& code, const std::string& message) From b2c61bae4092238c236ec49b9e6607393ab557ab Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 14:08:45 +0100 Subject: [PATCH 38/46] Improve Status structure to avoid potential memory leaks --- bindings/cpp/CMakeLists.txt | 1 + bindings/cpp/include/IndexSVSImplDefs.h | 23 +++++++++++-- bindings/cpp/src/IndexSVSImplDefs.cpp | 45 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 bindings/cpp/src/IndexSVSImplDefs.cpp diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 6226dda6..7bfd233e 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -29,6 +29,7 @@ set(SVS_RUNTIME_SOURCES src/svs_runtime_utils.h src/dynamic_vamana_index_impl.h src/flat_index_impl.h + src/IndexSVSImplDefs.cpp src/training.cpp src/vamana_index.cpp src/dynamic_vamana_index.cpp diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/IndexSVSImplDefs.h index 1ee30a8f..e3a31a99 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/IndexSVSImplDefs.h @@ -57,12 +57,31 @@ enum class ErrorCode { }; struct Status { + constexpr Status(ErrorCode c = ErrorCode::SUCCESS, const char* msg = nullptr) + : code(c) + , message_storage_(nullptr) { + if (msg != nullptr) { + store_message(msg); + } + } + + constexpr ~Status() noexcept { + if (message_storage_ != nullptr) { + destroy_message(); + } + } + ErrorCode code = ErrorCode::SUCCESS; - const char* message = nullptr; + const char* message() const { return message_storage_ ? message_storage_ : ""; }; constexpr bool ok() const { return code == ErrorCode::SUCCESS; } + + private: + void store_message(const char* msg) noexcept; + void destroy_message() noexcept; + char* message_storage_ = nullptr; }; -constexpr Status Status_Ok{ErrorCode::SUCCESS, nullptr}; +constexpr Status Status_Ok{}; struct SVS_RUNTIME_API_INTERFACE IDFilter { virtual bool is_member(size_t id) const = 0; diff --git a/bindings/cpp/src/IndexSVSImplDefs.cpp b/bindings/cpp/src/IndexSVSImplDefs.cpp new file mode 100644 index 00000000..1c979db9 --- /dev/null +++ b/bindings/cpp/src/IndexSVSImplDefs.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2025 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. + */ + +#include "IndexSVSImplDefs.h" +#include +#include + +namespace svs { +namespace runtime { +void Status::store_message(const char* msg) noexcept { + assert(msg != nullptr); + try { + auto len = std::strlen(msg); + message_storage_ = new char[len + 1]; + std::strcpy(message_storage_, msg); + } catch (...) { + // In case of any error, leave message_storage_ as nullptr + if (message_storage_) { + delete[] message_storage_; + message_storage_ = nullptr; + } + return; + } +} + +void Status::destroy_message() noexcept { + assert(message_storage_ != nullptr); + delete[] message_storage_; + message_storage_ = nullptr; +} +} // namespace runtime +} // namespace svs \ No newline at end of file From fc11a3a94755963763aff2f6fbcedbc54447359a Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 15:37:01 +0100 Subject: [PATCH 39/46] Move public headers to svs/runtime subdir --- bindings/cpp/CMakeLists.txt | 14 +++++++------- .../{IndexSVSImplDefs.h => svs/runtime/api_defs.h} | 2 +- .../{ => svs/runtime}/dynamic_vamana_index.h | 6 +++--- .../cpp/include/{ => svs/runtime}/flat_index.h | 2 +- bindings/cpp/include/{ => svs/runtime}/training.h | 2 +- .../cpp/include/{ => svs/runtime}/vamana_index.h | 2 +- bindings/cpp/include/{ => svs/runtime}/version.h | 0 .../cpp/src/{IndexSVSImplDefs.cpp => api_defs.cpp} | 3 ++- bindings/cpp/src/dynamic_vamana_index.cpp | 3 ++- bindings/cpp/src/dynamic_vamana_index_impl.h | 3 ++- bindings/cpp/src/flat_index.cpp | 3 ++- bindings/cpp/src/flat_index_impl.h | 3 ++- bindings/cpp/src/svs_runtime_utils.h | 2 +- bindings/cpp/src/training.cpp | 3 ++- bindings/cpp/src/training_impl.h | 2 ++ bindings/cpp/src/vamana_index.cpp | 2 +- 16 files changed, 30 insertions(+), 22 deletions(-) rename bindings/cpp/include/{IndexSVSImplDefs.h => svs/runtime/api_defs.h} (98%) rename bindings/cpp/include/{ => svs/runtime}/dynamic_vamana_index.h (96%) rename bindings/cpp/include/{ => svs/runtime}/flat_index.h (97%) rename bindings/cpp/include/{ => svs/runtime}/training.h (97%) rename bindings/cpp/include/{ => svs/runtime}/vamana_index.h (98%) rename bindings/cpp/include/{ => svs/runtime}/version.h (100%) rename bindings/cpp/src/{IndexSVSImplDefs.cpp => api_defs.cpp} (97%) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 7bfd233e..5989961d 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -17,19 +17,19 @@ project(svs_runtime VERSION 0.0.10 LANGUAGES CXX) set(TARGET_NAME svs_runtime) set(SVS_RUNTIME_HEADERS - include/version.h - include/IndexSVSImplDefs.h - include/training.h - include/vamana_index.h - include/dynamic_vamana_index.h - include/flat_index.h + include/svs/runtime/version.h + include/svs/runtime/api_defs.h + include/svs/runtime/training.h + include/svs/runtime/vamana_index.h + include/svs/runtime/dynamic_vamana_index.h + include/svs/runtime/flat_index.h ) set(SVS_RUNTIME_SOURCES src/svs_runtime_utils.h src/dynamic_vamana_index_impl.h src/flat_index_impl.h - src/IndexSVSImplDefs.cpp + src/api_defs.cpp src/training.cpp src/vamana_index.cpp src/dynamic_vamana_index.cpp diff --git a/bindings/cpp/include/IndexSVSImplDefs.h b/bindings/cpp/include/svs/runtime/api_defs.h similarity index 98% rename from bindings/cpp/include/IndexSVSImplDefs.h rename to bindings/cpp/include/svs/runtime/api_defs.h index e3a31a99..f22228bf 100644 --- a/bindings/cpp/include/IndexSVSImplDefs.h +++ b/bindings/cpp/include/svs/runtime/api_defs.h @@ -16,7 +16,7 @@ #pragma once -#include "version.h" +#include #include #include diff --git a/bindings/cpp/include/dynamic_vamana_index.h b/bindings/cpp/include/svs/runtime/dynamic_vamana_index.h similarity index 96% rename from bindings/cpp/include/dynamic_vamana_index.h rename to bindings/cpp/include/svs/runtime/dynamic_vamana_index.h index 8ec742d2..d978347d 100644 --- a/bindings/cpp/include/dynamic_vamana_index.h +++ b/bindings/cpp/include/svs/runtime/dynamic_vamana_index.h @@ -15,9 +15,9 @@ */ #pragma once -#include "IndexSVSImplDefs.h" -#include "training.h" -#include "vamana_index.h" +#include +#include +#include #include #include diff --git a/bindings/cpp/include/flat_index.h b/bindings/cpp/include/svs/runtime/flat_index.h similarity index 97% rename from bindings/cpp/include/flat_index.h rename to bindings/cpp/include/svs/runtime/flat_index.h index 68f6c7bc..7d98462c 100644 --- a/bindings/cpp/include/flat_index.h +++ b/bindings/cpp/include/svs/runtime/flat_index.h @@ -15,7 +15,7 @@ */ #pragma once -#include "IndexSVSImplDefs.h" +#include #include #include diff --git a/bindings/cpp/include/training.h b/bindings/cpp/include/svs/runtime/training.h similarity index 97% rename from bindings/cpp/include/training.h rename to bindings/cpp/include/svs/runtime/training.h index b108abfc..08b2e36d 100644 --- a/bindings/cpp/include/training.h +++ b/bindings/cpp/include/svs/runtime/training.h @@ -15,7 +15,7 @@ */ #pragma once -#include "IndexSVSImplDefs.h" +#include #include #include diff --git a/bindings/cpp/include/vamana_index.h b/bindings/cpp/include/svs/runtime/vamana_index.h similarity index 98% rename from bindings/cpp/include/vamana_index.h rename to bindings/cpp/include/svs/runtime/vamana_index.h index 9bc82c31..8c20a042 100644 --- a/bindings/cpp/include/vamana_index.h +++ b/bindings/cpp/include/svs/runtime/vamana_index.h @@ -15,7 +15,7 @@ */ #pragma once -#include "IndexSVSImplDefs.h" +#include #include diff --git a/bindings/cpp/include/version.h b/bindings/cpp/include/svs/runtime/version.h similarity index 100% rename from bindings/cpp/include/version.h rename to bindings/cpp/include/svs/runtime/version.h diff --git a/bindings/cpp/src/IndexSVSImplDefs.cpp b/bindings/cpp/src/api_defs.cpp similarity index 97% rename from bindings/cpp/src/IndexSVSImplDefs.cpp rename to bindings/cpp/src/api_defs.cpp index 1c979db9..110bca85 100644 --- a/bindings/cpp/src/IndexSVSImplDefs.cpp +++ b/bindings/cpp/src/api_defs.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "IndexSVSImplDefs.h" +#include + #include #include diff --git a/bindings/cpp/src/dynamic_vamana_index.cpp b/bindings/cpp/src/dynamic_vamana_index.cpp index d541b84c..b02e03b0 100644 --- a/bindings/cpp/src/dynamic_vamana_index.cpp +++ b/bindings/cpp/src/dynamic_vamana_index.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "dynamic_vamana_index.h" +#include + #include "dynamic_vamana_index_impl.h" #include "svs_runtime_utils.h" diff --git a/bindings/cpp/src/dynamic_vamana_index_impl.h b/bindings/cpp/src/dynamic_vamana_index_impl.h index fcca47b5..01de5c22 100644 --- a/bindings/cpp/src/dynamic_vamana_index_impl.h +++ b/bindings/cpp/src/dynamic_vamana_index_impl.h @@ -16,10 +16,11 @@ #pragma once -#include "dynamic_vamana_index.h" #include "svs_runtime_utils.h" #include "training_impl.h" +#include + #include #include #include diff --git a/bindings/cpp/src/flat_index.cpp b/bindings/cpp/src/flat_index.cpp index 2facf877..111f324c 100644 --- a/bindings/cpp/src/flat_index.cpp +++ b/bindings/cpp/src/flat_index.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "flat_index.h" +#include + #include "flat_index_impl.h" #include "svs_runtime_utils.h" diff --git a/bindings/cpp/src/flat_index_impl.h b/bindings/cpp/src/flat_index_impl.h index b6071218..021a022a 100644 --- a/bindings/cpp/src/flat_index_impl.h +++ b/bindings/cpp/src/flat_index_impl.h @@ -16,9 +16,10 @@ #pragma once -#include "flat_index.h" #include "svs_runtime_utils.h" +#include + #include #include #include diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index b30cf3d2..7e67e452 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -16,7 +16,7 @@ #pragma once -#include "IndexSVSImplDefs.h" +#include // TODO remove unused includes #include diff --git a/bindings/cpp/src/training.cpp b/bindings/cpp/src/training.cpp index 88e30946..aa974338 100644 --- a/bindings/cpp/src/training.cpp +++ b/bindings/cpp/src/training.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "training.h" +#include + #include "svs_runtime_utils.h" #include "training_impl.h" diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h index f8ed77a4..733ae78e 100644 --- a/bindings/cpp/src/training_impl.h +++ b/bindings/cpp/src/training_impl.h @@ -18,6 +18,8 @@ #include "svs_runtime_utils.h" +#include + #include #include diff --git a/bindings/cpp/src/vamana_index.cpp b/bindings/cpp/src/vamana_index.cpp index 048cc832..8b31b260 100644 --- a/bindings/cpp/src/vamana_index.cpp +++ b/bindings/cpp/src/vamana_index.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "vamana_index.h" +#include namespace svs { namespace runtime { From 3e25ae3e036375b036e7bfe3367075ea9a3abeab Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 16:43:36 +0100 Subject: [PATCH 40/46] Fix Status linkage visibility --- bindings/cpp/include/svs/runtime/api_defs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/include/svs/runtime/api_defs.h b/bindings/cpp/include/svs/runtime/api_defs.h index f22228bf..466615c3 100644 --- a/bindings/cpp/include/svs/runtime/api_defs.h +++ b/bindings/cpp/include/svs/runtime/api_defs.h @@ -56,7 +56,7 @@ enum class ErrorCode { RUNTIME_ERROR = 5 }; -struct Status { +struct SVS_RUNTIME_API Status { constexpr Status(ErrorCode c = ErrorCode::SUCCESS, const char* msg = nullptr) : code(c) , message_storage_(nullptr) { From 33bbe21463ed423a0b3ecaf551549bae08fceb4c Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 17:08:07 +0100 Subject: [PATCH 41/46] Fix non-LVQ failures on non-Intel platforms --- bindings/cpp/src/svs_runtime_utils.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index 7e67e452..7929dd89 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -244,10 +244,7 @@ inline StorageKind to_supported_storage_kind(StorageKind kind) { } else if (is_lvq_storage(kind) || is_leanvec_storage(kind)) { return StorageKind::SQI8; } - throw StatusException( - svs::runtime::ErrorCode::NOT_IMPLEMENTED, - "SVS runtime does not support the requested storage kind." - ); + return kind; } template From dfc84a88b817052953e57e8f2b3d935cbf0420a5 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 7 Nov 2025 17:28:54 +0100 Subject: [PATCH 42/46] Fix " not found" compilation error --- cmake/mkl.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/mkl.cmake b/cmake/mkl.cmake index a1fcc826..46194421 100644 --- a/cmake/mkl.cmake +++ b/cmake/mkl.cmake @@ -108,5 +108,8 @@ else() # if static link and not custom mkl if(UNIX AND NOT APPLE) target_link_options(${target} PRIVATE "SHELL:-Wl,--exclude-libs,ALL") endif() + target_include_directories( + ${target} PRIVATE $ + ) endfunction() endif() From a65a6417e42aeb7262ef9d1ba06d98b8e68cc1d8 Mon Sep 17 00:00:00 2001 From: ethanglaser <42726565+ethanglaser@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:34:07 -0800 Subject: [PATCH 43/46] Update static library/tarball to latest --- bindings/cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 5989961d..9cf10943 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -104,7 +104,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3") - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251106-762.tar.gz" + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251107-770.tar.gz" CACHE STRING "URL to download SVS shared library") else() message(WARNING From 1f20a5b68594c5155268cba5cf6761d8f97f53d2 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 7 Nov 2025 13:34:23 -0800 Subject: [PATCH 44/46] Avoid passing exact same LeanVecMatricesType args --- bindings/cpp/src/training_impl.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/cpp/src/training_impl.h b/bindings/cpp/src/training_impl.h index 733ae78e..ddfdc370 100644 --- a/bindings/cpp/src/training_impl.h +++ b/bindings/cpp/src/training_impl.h @@ -74,10 +74,10 @@ struct LeanVecTrainingDataImpl { auto matrix = svs::leanvec::compute_leanvec_matrix( data, means, threadpool, svs::lib::MaybeStatic{leanvec_dims} ); - // Intentionally using the same matrix for both elements of LeanVecMatricesType. - // This may be required by downstream code expecting two matrices, even if they are - // identical. - return LeanVecMatricesType{matrix, matrix}; + // Create a copy of the matrix for the query matrix to avoid double free. + // LeanVecMatrices expects two separate matrix objects. + auto matrix_copy = matrix; + return LeanVecMatricesType{std::move(matrix), std::move(matrix_copy)}; } }; From df8630f282ff7a1388a808197e3e8cc388712f72 Mon Sep 17 00:00:00 2001 From: ethanglaser <42726565+ethanglaser@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:28:21 -0800 Subject: [PATCH 45/46] Update static library tarball link --- bindings/cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 9cf10943..197405b9 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -104,7 +104,7 @@ if ((SVS_RUNTIME_ENABLE_LVQ_LEANVEC)) else() # Links to LTO-enabled static library, requires GCC/G++ 11.2 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3") - set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251107-770.tar.gz" + set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v1.0.0-dev/svs-shared-library-1.0.0-NIGHTLY-20251107-773.tar.gz" CACHE STRING "URL to download SVS shared library") else() message(WARNING From bb0d1b694a93b2a77d36eae1d6bcc03b83b90b18 Mon Sep 17 00:00:00 2001 From: ethanglaser Date: Fri, 7 Nov 2025 16:41:46 -0800 Subject: [PATCH 46/46] Clean up docker and pipeline --- .../workflows/build-cpp-runtime-bindings.yml | 27 +++++-------------- .gitignore | 3 +++ docker/x86_64/build-cpp-runtime-bindings.sh | 4 +-- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml index 6152f6c3..4e9b4712 100644 --- a/.github/workflows/build-cpp-runtime-bindings.yml +++ b/.github/workflows/build-cpp-runtime-bindings.yml @@ -37,56 +37,45 @@ concurrency: jobs: build-cpp-runtime-bindings: runs-on: ubuntu-22.04 - strategy: - matrix: - platform: - - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} steps: - uses: actions/checkout@v5 - name: Build Docker image run: | - docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . + docker build -t svs-manylinux228:latest -f docker/x86_64/manylinux228/Dockerfile . - name: Build libraries in Docker container run: | docker run --rm \ -v ${{ github.workspace }}:/workspace \ -w /workspace \ - -e PLATFORM_NAME=${{ matrix.platform.suffix }} \ - -e CC=${{ matrix.platform.cc }} \ - -e CXX=${{ matrix.platform.cxx }} \ - svs-${{ matrix.platform.name }}:latest \ + svs-manylinux228:latest \ /bin/bash -c "chmod +x docker/x86_64/build-cpp-runtime-bindings.sh && ./docker/x86_64/build-cpp-runtime-bindings.sh" - name: Upload cpp runtime bindings artifacts uses: actions/upload-artifact@v4 with: - name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} - path: svs-cpp-runtime-bindings${{ matrix.platform.suffix }}.tar.gz + name: svs-cpp-runtime-bindings + path: svs-cpp-runtime-bindings.tar.gz retention-days: 7 # Reduce retention due to size test: needs: build-cpp-runtime-bindings runs-on: ubuntu-22.04 - strategy: - matrix: - platform: - - {name: 'manylinux228', dockerfile: 'docker/x86_64/manylinux228/Dockerfile', cc: 'gcc', cxx: 'g++', suffix: ''} steps: - uses: actions/checkout@v5 - name: Build Docker image run: | - docker build -t svs-${{ matrix.platform.name }}:latest -f ${{ matrix.platform.dockerfile }} . + docker build -t svs-manylinux228:latest -f docker/x86_64/manylinux228/Dockerfile . # Need to download for a new job - name: Download shared libraries uses: actions/download-artifact@v4 with: - name: svs-cpp-runtime-bindings${{ matrix.platform.suffix }} + name: svs-cpp-runtime-bindings path: runtime_lib - name: List available artifacts @@ -99,7 +88,5 @@ jobs: -v ${{ github.workspace }}:/workspace \ -v ${{ github.workspace }}/runtime_lib:/runtime_lib \ -w /workspace \ - -e CC=${{ matrix.platform.cc }} \ - -e CXX=${{ matrix.platform.cxx }} \ - svs-${{ matrix.platform.name }}:latest \ + svs-manylinux228:latest \ /bin/bash -c "chmod +x docker/x86_64/test-cpp-runtime-bindings.sh && ./docker/x86_64/test-cpp-runtime-bindings.sh" diff --git a/.gitignore b/.gitignore index c7456765..e0c76cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,8 @@ __pycache__/ /bindings/python/_skbuild/ /bindings/python/dist/ +# CPP bindings build files +/bindings/cpp/build/ + # Example generated files. example_data_*/ diff --git a/docker/x86_64/build-cpp-runtime-bindings.sh b/docker/x86_64/build-cpp-runtime-bindings.sh index 7a0dbda9..529ceffe 100644 --- a/docker/x86_64/build-cpp-runtime-bindings.sh +++ b/docker/x86_64/build-cpp-runtime-bindings.sh @@ -18,8 +18,6 @@ set -e # Exit on error # Source environment setup (for compiler and MKL) source /etc/bashrc || true -# Set CMake arguments based on environment variables - # 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 @@ -33,4 +31,4 @@ cmake --install . # Create tarball with symlink for compatibility cd /workspace/install_cpp_bindings && \ ln -s lib lib64 && \ -tar -czvf /workspace/svs-cpp-runtime-bindings${PLATFORM_NAME}.tar.gz . +tar -czvf /workspace/svs-cpp-runtime-bindings.tar.gz .