From ad31648c42bb4f4105c26bcad45fb0479cd4e41e Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Wed, 7 Jan 2026 17:35:37 +0100 Subject: [PATCH 01/18] First draft of C API design --- bindings/SVS_C_API_Design.md | 173 +++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 bindings/SVS_C_API_Design.md diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md new file mode 100644 index 00000000..7fc18860 --- /dev/null +++ b/bindings/SVS_C_API_Design.md @@ -0,0 +1,173 @@ +# SVS C API Design proposal + +## Intro + +This document contains SVS C API design proposal. + +## Aspects and requirements + +* Simplicity - minimal set of operations to create an index and use it +* Flexibility - let user to modify as many parameters and options as possible + * Build parameters + * Block sizes + * Search parameters + * Allocation + * Thread pooling +* Error handling + +## Concept + +## Key abstractions + +* Index + * Initialized with non-empty dataset + * TopK Search + * Range search + * Iterative search + * Filtered search +* MutableIndex : Index + * Initialized with a dataset and labels list + * Add vectors with labels + * Remove vectors by labels + * Check if a label exists + * Compute distance for label + * Get vector by label +* Factory + * Created with minimal parameters: index algorithm, mutable/immutable, dimensions, distance metric + * Set storage configuration - default: Simple+FP32 + * Set algorithm configuration + * Set custom threadpool - default: SVS native + * Set custom allocator - default: SVS internal + * Mutable only: set allocation blocking - default: SVS internal +* Index data storage cofiguration + * Created with Kind: Simple, SQ, LVQ, LeanVec + * Configuration "Simple": + * Type: FP32, FP16, BF16 + * Configuration "SQ": + * Signed/Unsigned + * Configuration "LVQ": + * Primary size: 4, 8 + * Residual size: 0, 4, 8 + * Configuration LeanVec: + * Leanvec dimensions: + * Primary storage configuration: FP32, FP16, BF16, LVQ4, LVQ8 + * Secondary storage configuration: FP32, FP16, BF16, LVQ4, LVQ8 +* Threadpool configuration + * Threadpool kind: native, OMP, custom + * SVS native: size + * OMP: nothing (??size??) + * Custom: `struct IThreadPool { size_t (*size)(void); void (*parallel_for)(void (*f)(size_t), size_t); };` +* Allocator configuration + * Kind: simple, hugepage, custom + * Custom allocator: `struct IAlloc { void* (*alloc)(size_t); void (*dealloc)(void*, size_t); };` + +## Error handling + +There is 2 possible ways to handle errors in C API + +1. All API calls return status/error code when results are passed via "output argument" - "Intel oneDNN style" +2. All API calls return results when error handling is managed via "optional" pointer to status/error code variable - "OpenCL style" + +Proposed API utilizes the second approach. + +### Error details/message challenge + +Possible ways to provide errors messages: + +* Static map from error code to constant strings + * Simple API: `const char* svs_error_message(svs_status_t);` + * Requires fine-grained error codes + * Cost of code-to-message table maintainance +* Dynamically generated strings + * Complicated API to manage dynamically allocated strings + * Simplified error codes + * Easy to add/update error messages + * Allow to handle/explain object states (arguments, etc.) in messages + +## API Sample + +```c +// typedefs +int label_t; + +// Error handling +typedef enum { + svs_success; + svs_invalid_argument; + svs_out_of_memory; + ... +} svs_status_t; + +// Opaque types +svs_algorithm_t; +svs_storage_t; +svs_threadpool_t; +svs_allocator_t; + +// Enums +enum svs_metric_t; +enum svs_type_t; + +// Algorithm +typedef struct {...} svs_flat_params_t; +svs_algorithm_t svs_algo_create_flat(svs_flat_params_t* /*=NULL*/, svs_status_t* err_ret /*=NULL*/); + +typedef struct {...} svs_vamana_params_t; +svs_algorithm_t svs_algo_create_vamana(svs_vamana_params_t* params /*=NULL*/, svs_status_t* err_ret /*=NULL*/); + +svs_algorithm_t svs_algo_create_ivf(..., svs_status_t* err_ret /*=NULL*/); + +// Storage +svs_storage_t svs_storage_create_simple(svs_type_t, svs_status_t* err_ret /*=NULL*/); +svs_storage_t svs_storage_create_sq(svs_type_t, svs_status_t* err_ret /*=NULL*/); +svs_storage_t svs_storage_create_lvq(svs_type_t primary, svs_type_t residual, svs_status_t* err_ret /*=NULL*/); +svs_storage_t svs_storage_create_leanvec(size_t leanvec_dims, svs_type_t primary, svs_type_t secondary, svs_status_t* err_ret /*=NULL*/); + + +// Threadpool +svs_threadpool_t svs_threadpool_create_native(size, svs_status_t* err_ret /*=NULL*/); +svs_threadpool_t svs_threadpool_create_omp(svs_status_t* err_ret /*=NULL*/); + +typedef struct { + size_t (*size)(void); + void (*parallel_for)(void (*f)(size_t), size_t); +} svs_threadpool_i; +svs_threadpool_t svs_threadpool_create_custom(svs_threadpool_i*, svs_status_t* err_ret /*=NULL*/); + +// Allocator +svs_allocator_t svs_allocator_create_simple(svs_status_t* err_ret /*=NULL*/); +svs_allocator_t svs_allocator_create_hugepage(svs_status_t* err_ret /*=NULL*/); + +typedef struct svs_allocator_i_tag { + void* (*alloc)(size_t); + void (*dealloc)(void*, size_t); +} svs_allocator_i; +svs_allocator_t svs_allocator_create_custom(svs_allocator_i*, svs_status_t* err_ret /*=NULL*/); + + +// Factory +svs_factory_t svs_factory_create(svs_algorithm_t, size_t dims, svs_metric_t, svs_status_t* err_ret /*=NULL*/); +void svs_factory_set_storage(svs_factory_t, svs_storage_t, svs_status_t* err_ret /*=NULL*/); +void svs_factory_set_threadpool(svs_factory_t, svs_threadpool_t, svs_status_t* err_ret /*=NULL*/); +void svs_factory_set_allocator(svs_factory_t, svs_allocator_t, svs_status_t* err_ret /*=NULL*/); + +// Index +svs_index_t svs_index_create(svs_factory_t, float* data, size_t size, svs_status_t* err_ret /*=NULL*/); +svs_index_t svs_index_create_dynamic(svs_factory_t, float* data, svs_label_t* labels, size_t size, size_t blocksize /*=0*/, svs_status_t* err_ret /*=NULL*/); + +// Results +typedef struct { + size_t queries_num; + size_t* results_nums; + svs_label_t* labels; + float* distances; +} svs_results_t; + +svs_results_t (*svs_results_allocator_i)(size_t queries_num, size_t* results_nums); + +// Index queries +size_t svs_index_search_topk(svs_index_t, float* queries, size_t* queries_num, int k, svs_results_allocator_i results, svs_status_t* err_ret /*=NULL*/); +size_t svs_index_search_range(svs_index_t, float* queries, size_t* queries_num, float range, svs_results_allocator_i results, svs_status_t* err_ret /*=NULL*/); + + +``` From e1968312591be09567e08c5ee2049a61f9ffe07c Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 12 Jan 2026 14:46:01 +0100 Subject: [PATCH 02/18] First C API implementation draft with sample --- bindings/SVS_C_API_Design.md | 2 + bindings/c/CMakeLists.txt | 119 ++++++++++ bindings/c/include/svs/c_api/svs_c.h | 118 ++++++++++ bindings/c/samples/CMakeLists.txt | 23 ++ bindings/c/samples/simple.c | 113 ++++++++++ bindings/c/src/svs_c.cpp | 323 +++++++++++++++++++++++++++ 6 files changed, 698 insertions(+) create mode 100644 bindings/c/CMakeLists.txt create mode 100644 bindings/c/include/svs/c_api/svs_c.h create mode 100644 bindings/c/samples/CMakeLists.txt create mode 100644 bindings/c/samples/simple.c create mode 100644 bindings/c/src/svs_c.cpp diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md index 7fc18860..549d45d7 100644 --- a/bindings/SVS_C_API_Design.md +++ b/bindings/SVS_C_API_Design.md @@ -129,6 +129,7 @@ svs_threadpool_t svs_threadpool_create_native(size, svs_status_t* err_ret /*=NUL svs_threadpool_t svs_threadpool_create_omp(svs_status_t* err_ret /*=NULL*/); typedef struct { + void* ctx; size_t (*size)(void); void (*parallel_for)(void (*f)(size_t), size_t); } svs_threadpool_i; @@ -139,6 +140,7 @@ svs_allocator_t svs_allocator_create_simple(svs_status_t* err_ret /*=NULL*/); svs_allocator_t svs_allocator_create_hugepage(svs_status_t* err_ret /*=NULL*/); typedef struct svs_allocator_i_tag { + void* ctx; void* (*alloc)(size_t); void (*dealloc)(void*, size_t); } svs_allocator_i; diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt new file mode 100644 index 00000000..8da04b8e --- /dev/null +++ b/bindings/c/CMakeLists.txt @@ -0,0 +1,119 @@ +# Copyright 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.21) +project(svs_c_api VERSION 0.1.0 LANGUAGES CXX C) +set(TARGET_NAME svs_c_api) + +set(SVS_C_API_HEADERS + include/svs/c_api/svs_c.h +) + +set(SVS_C_API_SOURCES + src/svs_c.cpp +) + +add_library(${TARGET_NAME} SHARED + ${SVS_C_API_HEADERS} + ${SVS_C_API_SOURCES} +) + +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_C_API_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} ) + +target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs +) +link_mkl_static(${TARGET_NAME}) +# target_compile_definitions(${TARGET_NAME} PRIVATE +# PUBLIC "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\"" +# PUBLIC "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\"" +# ) + +# 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}" +# ) + +# Build tests if requested +# if(SVS_BUILD_C_API_TESTS) +# add_subdirectory(tests) +# endif() + +add_subdirectory(samples) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h new file mode 100644 index 00000000..fbcae0e7 --- /dev/null +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -0,0 +1,118 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif +#include + +enum svs_error_code { + SVS_OK = 0, + SVS_ERROR_GENERIC = 1, + SVS_ERROR_INVALID_ARGUMENT = 2, + SVS_ERROR_OUT_OF_MEMORY = 3, + SVS_ERROR_INDEX_BUILD_FAILED = 4, + SVS_ERROR_NOT_IMPLEMENTED = 5 +}; + +enum svs_distance_metric { + SVS_DISTANCE_METRIC_EUCLIDEAN = 0, + SVS_DISTANCE_METRIC_COSINE = 1, + SVS_DISTANCE_METRIC_DOT_PRODUCT = 2 +}; + +enum svs_algorithm_type { + SVS_ALGORITHM_TYPE_VAMANA = 0, + SVS_ALGORITHM_TYPE_FLAT = 1, + SVS_ALGORITHM_TYPE_IVF = 2, +}; + +enum svs_data_type { + SVS_DATA_TYPE_FLOAT32 = 0, + SVS_DATA_TYPE_FLOAT16 = 1, + SVS_DATA_TYPE_INT8 = 2, + SVS_DATA_TYPE_UINT8 = 1, + SVS_DATA_TYPE_INT4 = 4, + SVS_DATA_TYPE_UINT4 = 5 +}; + +enum svs_storage_kind { SVS_STORAGE_KIND_SIMPLE = 0, SVS_STORAGE_KIND_LEANVEC = 1 }; + +struct svs_search_results { + size_t num_queries; + size_t* results_per_query; + size_t* indices; + float* distances; +}; + +typedef struct svs_index* svs_index_t; +typedef struct svs_index_builder* svs_index_builder_t; +typedef struct svs_algorithm* svs_algorithm_t; +typedef struct svs_storage* svs_storage_t; +typedef struct svs_search_results* svs_search_results_t; + +typedef enum svs_error_code svs_error_code_t; +typedef enum svs_distance_metric svs_distance_metric_t; +typedef enum svs_algorithm_type svs_algorithm_type_t; +typedef enum svs_data_type svs_data_type_t; + +svs_algorithm_t svs_algorithm_create_vamana( + size_t graph_degree, + size_t build_window_size, + size_t search_window_size, + svs_error_code_t* out_code +); +void svs_algorithm_free(svs_algorithm_t algorithm); + +svs_storage_t +svs_storage_create_simple(svs_data_type_t data_type, svs_error_code_t* out_code); +svs_storage_t svs_storage_create_leanvec( + size_t lenavec_dims, + svs_data_type_t primary, + svs_data_type_t secondary, + svs_error_code_t* out_code +); +void svs_storage_free(svs_storage_t storage); + +svs_index_builder_t svs_index_builder_create( + svs_distance_metric_t metric, + size_t dimension, + svs_algorithm_t algorithm, + svs_error_code_t* out_code +); +void svs_index_builder_free(svs_index_builder_t builder); + +void svs_index_builder_set_storage( + svs_index_builder_t builder, svs_storage_t storage, svs_error_code_t* out_code +); + +svs_index_t svs_index_build( + svs_index_builder_t builder, + const float* data, + size_t num_vectors, + svs_error_code_t* out_code +); +void svs_index_free(svs_index_t index); + +svs_search_results_t +svs_index_search(svs_index_t index, const float* queries, size_t num_queries, size_t k); +void svs_search_results_free(svs_search_results_t results); + +#ifdef __cplusplus +} +#endif diff --git a/bindings/c/samples/CMakeLists.txt b/bindings/c/samples/CMakeLists.txt new file mode 100644 index 00000000..fb0520d4 --- /dev/null +++ b/bindings/c/samples/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(SAMPLE_SIMPLE_TARGET c_api_simple) + +add_executable(${SAMPLE_SIMPLE_TARGET} simple.c) + +target_link_libraries(${SAMPLE_SIMPLE_TARGET} PRIVATE svs_c_api) + +target_include_directories(${SAMPLE_SIMPLE_TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) \ No newline at end of file diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c new file mode 100644 index 00000000..5fa75319 --- /dev/null +++ b/bindings/c/samples/simple.c @@ -0,0 +1,113 @@ +#include "svs/c_api/svs_c.h" +#include +#include +#include + +#define NUM_VECTORS 10000 +#define NUM_QUERIES 5 +#define DIMENSION 128 +#define K 10 + +void generate_random_data(float* data, size_t count, size_t dim) { + for (size_t i = 0; i < count * dim; i++) { + data[i] = (float)rand() / RAND_MAX; + } +} + +int main() { + int ret = 0; + srand(time(NULL)); + svs_error_code_t error = SVS_OK; + + float* data = NULL; + float* queries = NULL; + svs_algorithm_t algorithm = NULL; + svs_storage_t storage = NULL; + svs_index_builder_t builder = NULL; + svs_index_t index = NULL; + svs_search_results_t results = NULL; + + // Allocate random data + data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float)); + queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float)); + + if (!data || !queries) { + fprintf(stderr, "Failed to allocate memory\n"); + ret = 1; + goto cleanup; + } + + generate_random_data(data, NUM_VECTORS, DIMENSION); + generate_random_data(queries, NUM_QUERIES, DIMENSION); + + // Create Vamana algorithm + algorithm = svs_algorithm_create_vamana(64, 128, 100, &error); + if (error != SVS_OK) { + fprintf(stderr, "Failed to create algorithm: %d\n", error); + ret = 1; + goto cleanup; + } + + // Create storage + storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, &error); + if (error != SVS_OK) { + fprintf(stderr, "Failed to create storage: %d\n", error); + ret = 1; + goto cleanup; + } + + // Create index builder + builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, &error); + if (error != SVS_OK) { + fprintf(stderr, "Failed to create index builder: %d\n", error); + ret = 1; + goto cleanup; + } + + svs_index_builder_set_storage(builder, storage, &error); + if (error != SVS_OK) { + fprintf(stderr, "Failed to set storage: %d\n", error); + ret = 1; + goto cleanup; + } + + // Build index + printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION); + index = svs_index_build(builder, data, NUM_VECTORS, &error); + if (error != SVS_OK) { + fprintf(stderr, "Failed to build index: %d\n", error); + ret = 1; + goto cleanup; + } + printf("Index built successfully!\n"); + + // Search + printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K); + results = svs_index_search(index, queries, NUM_QUERIES, K); + + // Print results + size_t offset = 0; + for (size_t q = 0; q < results->num_queries; q++) { + printf("Query %zu results:\n", q); + for (size_t i = 0; i < results->results_per_query[q]; i++) { + printf(" [%zu] id=%zu, distance=%.4f\n", + i, results->indices[offset + i], results->distances[offset + i]); + } + offset += results->results_per_query[q]; + } + + printf("Done!\n"); + +cleanup: + // Cleanup + svs_search_results_free(results); + svs_index_free(index); + svs_index_builder_free(builder); + svs_storage_free(storage); + svs_algorithm_free(algorithm); + free(data); + free(queries); + + return ret; +} \ No newline at end of file diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp new file mode 100644 index 00000000..3b653a1d --- /dev/null +++ b/bindings/c/src/svs_c.cpp @@ -0,0 +1,323 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "svs/c_api/svs_c.h" + +#include +#include +#include +#include +#include + +struct SVS_Algorithm { + svs_algorithm_type type; + SVS_Algorithm(svs_algorithm_type type) + : type(type) {} + virtual ~SVS_Algorithm() = default; +}; + +struct SVS_AlgorithmVamana : public SVS_Algorithm { + size_t graph_degree; + size_t build_window_size; + size_t search_window_size; + + SVS_AlgorithmVamana( + size_t graph_degree, size_t build_window_size, size_t search_window_size + ) + : SVS_Algorithm{SVS_ALGORITHM_TYPE_VAMANA} + , graph_degree(graph_degree) + , build_window_size(build_window_size) + , search_window_size(search_window_size) {} + + svs::index::vamana::VamanaBuildParameters get_build_parameters() const { + svs::index::vamana::VamanaBuildParameters params; + params.graph_max_degree = graph_degree; + params.window_size = build_window_size; + return params; + } +}; + +struct SVS_Storage { + svs_storage_kind kind; + SVS_Storage(svs_storage_kind kind) + : kind(kind) {} + virtual ~SVS_Storage() = default; +}; + +struct SVS_StorageSimple : public SVS_Storage { + svs_data_type_t data_type; + + SVS_StorageSimple(svs_data_type_t data_type) + : SVS_Storage{SVS_STORAGE_KIND_SIMPLE} + , data_type(data_type) {} +}; + +struct SVS_StorageLeanVec : public SVS_Storage { + size_t lenavec_dims; + svs_data_type_t primary_type; + svs_data_type_t secondary_type; + + SVS_StorageLeanVec( + size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary + ) + : SVS_Storage{SVS_STORAGE_KIND_LEANVEC} + , lenavec_dims(lenavec_dims) + , primary_type(primary) + , secondary_type(secondary) {} +}; + +struct SVS_IndexBuilder { + svs_distance_metric_t distance_metric; + size_t dimension; + std::shared_ptr algorithm; + std::shared_ptr storage; + + SVS_IndexBuilder( + svs_distance_metric_t distance_metric, + size_t dimension, + std::shared_ptr algorithm + ) + : distance_metric(distance_metric) + , dimension(dimension) + , algorithm(std::move(algorithm)) + , storage(std::make_shared(SVS_DATA_TYPE_FLOAT32)) {} + + ~SVS_IndexBuilder() {} + + void set_storage(std::shared_ptr storage) { + this->storage = std::move(storage); + } +}; + +struct SVS_Index { + svs_algorithm_type algorithm; + SVS_Index(svs_algorithm_type algorithm) + : algorithm(algorithm) {} + virtual ~SVS_Index() = default; +}; + +struct SVS_IndexVamana : public SVS_Index { + svs::Vamana index; + SVS_IndexVamana(svs::Vamana&& index) + : SVS_Index{SVS_ALGORITHM_TYPE_VAMANA} + , index(std::move(index)) {} + ~SVS_IndexVamana() {} +}; + +// C API function implementations +struct svs_index { + std::shared_ptr impl; +}; + +struct svs_index_builder { + std::shared_ptr impl; +}; + +struct svs_algorithm { + std::shared_ptr impl; +}; + +struct svs_storage { + std::shared_ptr impl; +}; + +extern "C" svs_algorithm_t svs_algorithm_create_vamana( + size_t graph_degree, + size_t build_window_size, + size_t search_window_size, + svs_error_code_t* out_code +) { + auto algorithm = std::make_shared( + graph_degree, build_window_size, search_window_size + ); + if (out_code) { + *out_code = SVS_OK; + } + auto result = new svs_algorithm; + result->impl = algorithm; + return result; +} + +extern "C" void svs_algorithm_free(svs_algorithm_t algorithm) { delete algorithm; } + +extern "C" svs_storage_t +svs_storage_create_simple(svs_data_type_t data_type, svs_error_code_t* out_code) { + auto storage = std::make_shared(data_type); + if (out_code) { + *out_code = SVS_OK; + } + auto result = new svs_storage; + result->impl = storage; + return result; +} + +extern "C" svs_storage_t svs_storage_create_leanvec( + size_t lenavec_dims, + svs_data_type_t primary, + svs_data_type_t secondary, + svs_error_code_t* out_code +) { + auto storage = std::make_shared(lenavec_dims, primary, secondary); + if (out_code) { + *out_code = SVS_OK; + } + auto result = new svs_storage; + result->impl = storage; + return result; +} + +extern "C" void svs_storage_free(svs_storage_t storage) { delete storage; } + +extern "C" svs_index_builder_t svs_index_builder_create( + svs_distance_metric_t metric, + size_t dimension, + svs_algorithm_t algorithm, + svs_error_code_t* out_code +) { + auto builder = std::make_shared(metric, dimension, algorithm->impl); + if (out_code) { + *out_code = SVS_OK; + } + auto result = new svs_index_builder; + result->impl = builder; + return result; +} + +extern "C" void svs_index_builder_free(svs_index_builder_t builder) { delete builder; } + +extern "C" void svs_index_builder_set_storage( + svs_index_builder_t builder, svs_storage_t storage, svs_error_code_t* out_code +) { + builder->impl->set_storage(storage->impl); + if (out_code) { + *out_code = SVS_OK; + } + return; +} + +extern "C" svs_index_t svs_index_build( + svs_index_builder_t builder, + const float* data, + size_t num_vectors, + svs_error_code_t* out_code +) { + if (builder == nullptr || num_vectors == 0 || data == nullptr) { + if (out_code) { + *out_code = SVS_ERROR_INVALID_ARGUMENT; + } + return nullptr; + } + if (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA) { + if (out_code) { + *out_code = SVS_ERROR_NOT_IMPLEMENTED; + } + return nullptr; + } + if (builder->impl->storage->kind != SVS_STORAGE_KIND_SIMPLE) { + if (out_code) { + *out_code = SVS_ERROR_NOT_IMPLEMENTED; + } + return nullptr; + } + + svs::DistanceType distance_type; + switch (builder->impl->distance_metric) { + case SVS_DISTANCE_METRIC_EUCLIDEAN: + distance_type = svs::DistanceType::L2; + break; + case SVS_DISTANCE_METRIC_DOT_PRODUCT: + distance_type = svs::DistanceType::MIP; + break; + case SVS_DISTANCE_METRIC_COSINE: + distance_type = svs::DistanceType::Cosine; + break; + default: + if (out_code) { + *out_code = SVS_ERROR_INVALID_ARGUMENT; + } + return nullptr; + } + + auto src_data = + svs::data::ConstSimpleDataView(data, num_vectors, builder->impl->dimension); + + auto simple_data = svs::data::SimpleData(num_vectors, builder->impl->dimension); + + svs::data::copy(src_data, simple_data); + + auto vamana_algorithm = + std::static_pointer_cast(builder->impl->algorithm); + auto index = std::make_shared(svs::Vamana::build( + vamana_algorithm->get_build_parameters(), std::move(simple_data), distance_type + )); + if (out_code) { + *out_code = SVS_OK; + } + auto result = new svs_index; + result->impl = index; + return result; +} + +extern "C" void svs_index_free(svs_index_t index) { delete index; } + +extern "C" svs_search_results_t +svs_index_search(svs_index_t index, const float* queries, size_t num_queries, size_t k) { + if (index == nullptr || queries == nullptr || num_queries == 0 || k == 0) { + return nullptr; + } + if (index->impl->algorithm != SVS_ALGORITHM_TYPE_VAMANA) { + return nullptr; + } + auto& vamana_index = static_cast(*index->impl).index; + + auto queries_view = svs::data::ConstSimpleDataView( + queries, num_queries, vamana_index.dimensions() + ); + + auto& vamana_idx = static_cast(*index->impl).index; + auto vamana_results = svs::QueryResult(num_queries, k); + + vamana_idx.search( + vamana_results.view(), queries_view, vamana_idx.get_search_parameters() + ); + + svs_search_results_t results = new svs_search_results{0, nullptr, nullptr, nullptr}; + + results->num_queries = num_queries; + results->results_per_query = new size_t[num_queries]; + results->indices = new size_t[num_queries * k]; + results->distances = new float[num_queries * k]; + + for (size_t i = 0; i < num_queries; ++i) { + results->results_per_query[i] = k; + for (size_t j = 0; j < k; ++j) { + results->indices[i * k + j] = vamana_results.index(i, j); + results->distances[i * k + j] = vamana_results.distance(i, j); + } + } + + return results; +} + +extern "C" void svs_search_results_free(svs_search_results_t results) { + if (results == nullptr) { + return; + } + delete[] results->results_per_query; + delete[] results->indices; + delete[] results->distances; + delete results; +} From 6ca5115007a2a4104eaf721cc4ec054a654c6819 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 13 Jan 2026 14:51:07 +0100 Subject: [PATCH 03/18] Some code refactoring --- bindings/c/CMakeLists.txt | 4 + bindings/c/src/algorithm.hpp | 84 ++++++++++++ bindings/c/src/index.hpp | 65 ++++++++++ bindings/c/src/index_builder.hpp | 89 +++++++++++++ bindings/c/src/storage.hpp | 51 ++++++++ bindings/c/src/svs_c.cpp | 214 +++++++------------------------ 6 files changed, 339 insertions(+), 168 deletions(-) create mode 100644 bindings/c/src/algorithm.hpp create mode 100644 bindings/c/src/index.hpp create mode 100644 bindings/c/src/index_builder.hpp create mode 100644 bindings/c/src/storage.hpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 8da04b8e..1dda7033 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -21,6 +21,10 @@ set(SVS_C_API_HEADERS ) set(SVS_C_API_SOURCES + src/algorithm.hpp + src/storage.hpp + src/index.hpp + src/index_builder.hpp src/svs_c.cpp ) diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp new file mode 100644 index 00000000..01ce71fe --- /dev/null +++ b/bindings/c/src/algorithm.hpp @@ -0,0 +1,84 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +// #include +// #include +// #include +#include +#include +#include + +namespace svs::c_runtime { + +struct Algorithm { + struct SearchParams { + svs_algorithm_type type; + SearchParams(svs_algorithm_type type) + : type(type) {} + virtual ~SearchParams() = default; + }; + + svs_algorithm_type type; + Algorithm(svs_algorithm_type type) + : type(type) {} + virtual ~Algorithm() = default; + + virtual std::shared_ptr get_default_search_params() const = 0; +}; + +struct AlgorithmVamana : public Algorithm { + struct SearchParamsVamana : public SearchParams { + size_t search_window_size; + SearchParamsVamana(size_t search_window_size) + : SearchParams{SVS_ALGORITHM_TYPE_VAMANA} + , search_window_size(search_window_size) {} + + svs::index::vamana::VamanaSearchParameters get_search_parameters() const { + svs::index::vamana::VamanaSearchParameters params; + params.buffer_config_ = + svs::index::vamana::SearchBufferConfig{search_window_size}; + return params; + } + }; + + size_t graph_degree; + size_t build_window_size; + SearchParamsVamana default_search_params; + + AlgorithmVamana( + size_t graph_degree, size_t build_window_size, size_t search_window_size + ) + : Algorithm{SVS_ALGORITHM_TYPE_VAMANA} + , graph_degree(graph_degree) + , build_window_size(build_window_size) + , default_search_params(search_window_size) {} + + svs::index::vamana::VamanaBuildParameters get_build_parameters() const { + svs::index::vamana::VamanaBuildParameters params; + params.graph_max_degree = graph_degree; + params.window_size = build_window_size; + return params; + } + + std::shared_ptr get_default_search_params() const override { + return std::make_shared(default_search_params); + } +}; + +} // namespace svs::c_runtime \ No newline at end of file diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp new file mode 100644 index 00000000..2de8a24d --- /dev/null +++ b/bindings/c/src/index.hpp @@ -0,0 +1,65 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "algorithm.hpp" + +// #include +// #include +// #include +// #include +#include + +namespace svs::c_runtime { +struct Index { + svs_algorithm_type algorithm; + Index(svs_algorithm_type algorithm) + : algorithm(algorithm) {} + virtual ~Index() = default; + virtual svs::QueryResult search( + svs::data::ConstSimpleDataView queries, + size_t num_neighbors, + const std::shared_ptr& search_params + ) = 0; +}; + +struct IndexVamana : public Index { + svs::Vamana index; + IndexVamana(svs::Vamana&& index) + : Index{SVS_ALGORITHM_TYPE_VAMANA} + , index(std::move(index)) {} + ~IndexVamana() {} + virtual svs::QueryResult search( + svs::data::ConstSimpleDataView queries, + size_t num_neighbors, + const std::shared_ptr& search_params + ) { + auto vamana_search_params = + std::static_pointer_cast(search_params); + auto results = svs::QueryResult(queries.size(), num_neighbors); + + auto params = index.get_search_parameters(); + if (vamana_search_params) { + params = vamana_search_params->get_search_parameters(); + } + + index.search(results.view(), queries, params); + return std::move(results); + } +}; +} // namespace svs::c_runtime diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp new file mode 100644 index 00000000..e4e01506 --- /dev/null +++ b/bindings/c/src/index_builder.hpp @@ -0,0 +1,89 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "algorithm.hpp" +#include "index.hpp" +#include "storage.hpp" + +#include +#include +#include +#include +#include + +namespace svs::c_runtime { + +struct IndexBuilder { + svs_distance_metric_t distance_metric; + size_t dimension; + std::shared_ptr algorithm; + std::shared_ptr storage; + IndexBuilder( + svs_distance_metric_t distance_metric, + size_t dimension, + std::shared_ptr algorithm + ) + : distance_metric(distance_metric) + , dimension(dimension) + , algorithm(std::move(algorithm)) + , storage(std::make_shared(SVS_DATA_TYPE_FLOAT32)) {} + + ~IndexBuilder() {} + + void set_storage(std::shared_ptr storage) { + this->storage = std::move(storage); + } + + svs::DistanceType get_distance_type() const { + switch (distance_metric) { + case SVS_DISTANCE_METRIC_EUCLIDEAN: + return svs::DistanceType::L2; + case SVS_DISTANCE_METRIC_DOT_PRODUCT: + return svs::DistanceType::MIP; + case SVS_DISTANCE_METRIC_COSINE: + return svs::DistanceType::Cosine; + default: + return svs::DistanceType::L2; // Default fallback + } + } + + std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA && + storage->kind == SVS_STORAGE_KIND_SIMPLE) { + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + svs::index::vamana::VamanaBuildParameters build_params = + vamana_algorithm->get_build_parameters(); + + auto storage = svs::data::SimpleData(data.size(), data.dimensions()); + + svs::data::copy(data, storage); + + auto index = std::make_shared(svs::Vamana::build( + vamana_algorithm->get_build_parameters(), + std::move(storage), + get_distance_type() + )); + + return index; + } + return nullptr; + } +}; +} // namespace svs::c_runtime diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp new file mode 100644 index 00000000..46b687de --- /dev/null +++ b/bindings/c/src/storage.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include + +namespace svs::c_runtime { + +struct Storage { + svs_storage_kind kind; + Storage(svs_storage_kind kind) + : kind(kind) {} + virtual ~Storage() = default; +}; + +struct StorageSimple : public Storage { + svs_data_type_t data_type; + + StorageSimple(svs_data_type_t data_type) + : Storage{SVS_STORAGE_KIND_SIMPLE} + , data_type(data_type) {} +}; + +struct StorageLeanVec : public Storage { + size_t lenavec_dims; + svs_data_type_t primary_type; + svs_data_type_t secondary_type; + + StorageLeanVec(size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary) + : Storage{SVS_STORAGE_KIND_LEANVEC} + , lenavec_dims(lenavec_dims) + , primary_type(primary) + , secondary_type(secondary) {} +}; + +} // namespace svs::c_runtime diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 3b653a1d..6ab48c22 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -16,122 +16,37 @@ #include "svs/c_api/svs_c.h" -#include -#include +#include "algorithm.hpp" +#include "index.hpp" +#include "index_builder.hpp" +#include "storage.hpp" + +#include #include -#include #include -struct SVS_Algorithm { - svs_algorithm_type type; - SVS_Algorithm(svs_algorithm_type type) - : type(type) {} - virtual ~SVS_Algorithm() = default; -}; - -struct SVS_AlgorithmVamana : public SVS_Algorithm { - size_t graph_degree; - size_t build_window_size; - size_t search_window_size; - - SVS_AlgorithmVamana( - size_t graph_degree, size_t build_window_size, size_t search_window_size - ) - : SVS_Algorithm{SVS_ALGORITHM_TYPE_VAMANA} - , graph_degree(graph_degree) - , build_window_size(build_window_size) - , search_window_size(search_window_size) {} - - svs::index::vamana::VamanaBuildParameters get_build_parameters() const { - svs::index::vamana::VamanaBuildParameters params; - params.graph_max_degree = graph_degree; - params.window_size = build_window_size; - return params; - } -}; - -struct SVS_Storage { - svs_storage_kind kind; - SVS_Storage(svs_storage_kind kind) - : kind(kind) {} - virtual ~SVS_Storage() = default; -}; - -struct SVS_StorageSimple : public SVS_Storage { - svs_data_type_t data_type; - - SVS_StorageSimple(svs_data_type_t data_type) - : SVS_Storage{SVS_STORAGE_KIND_SIMPLE} - , data_type(data_type) {} -}; - -struct SVS_StorageLeanVec : public SVS_Storage { - size_t lenavec_dims; - svs_data_type_t primary_type; - svs_data_type_t secondary_type; - - SVS_StorageLeanVec( - size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary - ) - : SVS_Storage{SVS_STORAGE_KIND_LEANVEC} - , lenavec_dims(lenavec_dims) - , primary_type(primary) - , secondary_type(secondary) {} -}; - -struct SVS_IndexBuilder { - svs_distance_metric_t distance_metric; - size_t dimension; - std::shared_ptr algorithm; - std::shared_ptr storage; - - SVS_IndexBuilder( - svs_distance_metric_t distance_metric, - size_t dimension, - std::shared_ptr algorithm - ) - : distance_metric(distance_metric) - , dimension(dimension) - , algorithm(std::move(algorithm)) - , storage(std::make_shared(SVS_DATA_TYPE_FLOAT32)) {} +// C API implementation +#define SET_ERR_CODE(code_ptr, code_value) \ + do { \ + if (code_ptr) { \ + *(code_ptr) = (code_value); \ + } \ + } while (0) - ~SVS_IndexBuilder() {} - - void set_storage(std::shared_ptr storage) { - this->storage = std::move(storage); - } -}; - -struct SVS_Index { - svs_algorithm_type algorithm; - SVS_Index(svs_algorithm_type algorithm) - : algorithm(algorithm) {} - virtual ~SVS_Index() = default; -}; - -struct SVS_IndexVamana : public SVS_Index { - svs::Vamana index; - SVS_IndexVamana(svs::Vamana&& index) - : SVS_Index{SVS_ALGORITHM_TYPE_VAMANA} - , index(std::move(index)) {} - ~SVS_IndexVamana() {} -}; - -// C API function implementations struct svs_index { - std::shared_ptr impl; + std::shared_ptr impl; }; struct svs_index_builder { - std::shared_ptr impl; + std::shared_ptr impl; }; struct svs_algorithm { - std::shared_ptr impl; + std::shared_ptr impl; }; struct svs_storage { - std::shared_ptr impl; + std::shared_ptr impl; }; extern "C" svs_algorithm_t svs_algorithm_create_vamana( @@ -140,12 +55,11 @@ extern "C" svs_algorithm_t svs_algorithm_create_vamana( size_t search_window_size, svs_error_code_t* out_code ) { - auto algorithm = std::make_shared( + using namespace svs::c_runtime; + auto algorithm = std::make_shared( graph_degree, build_window_size, search_window_size ); - if (out_code) { - *out_code = SVS_OK; - } + SET_ERR_CODE(out_code, SVS_OK); auto result = new svs_algorithm; result->impl = algorithm; return result; @@ -155,10 +69,9 @@ extern "C" void svs_algorithm_free(svs_algorithm_t algorithm) { delete algorithm extern "C" svs_storage_t svs_storage_create_simple(svs_data_type_t data_type, svs_error_code_t* out_code) { - auto storage = std::make_shared(data_type); - if (out_code) { - *out_code = SVS_OK; - } + using namespace svs::c_runtime; + auto storage = std::make_shared(data_type); + SET_ERR_CODE(out_code, SVS_OK); auto result = new svs_storage; result->impl = storage; return result; @@ -170,10 +83,9 @@ extern "C" svs_storage_t svs_storage_create_leanvec( svs_data_type_t secondary, svs_error_code_t* out_code ) { - auto storage = std::make_shared(lenavec_dims, primary, secondary); - if (out_code) { - *out_code = SVS_OK; - } + using namespace svs::c_runtime; + auto storage = std::make_shared(lenavec_dims, primary, secondary); + SET_ERR_CODE(out_code, SVS_OK); auto result = new svs_storage; result->impl = storage; return result; @@ -187,10 +99,9 @@ extern "C" svs_index_builder_t svs_index_builder_create( svs_algorithm_t algorithm, svs_error_code_t* out_code ) { - auto builder = std::make_shared(metric, dimension, algorithm->impl); - if (out_code) { - *out_code = SVS_OK; - } + using namespace svs::c_runtime; + auto builder = std::make_shared(metric, dimension, algorithm->impl); + SET_ERR_CODE(out_code, SVS_OK); auto result = new svs_index_builder; result->impl = builder; return result; @@ -202,9 +113,7 @@ extern "C" void svs_index_builder_set_storage( svs_index_builder_t builder, svs_storage_t storage, svs_error_code_t* out_code ) { builder->impl->set_storage(storage->impl); - if (out_code) { - *out_code = SVS_OK; - } + SET_ERR_CODE(out_code, SVS_OK); return; } @@ -215,57 +124,28 @@ extern "C" svs_index_t svs_index_build( svs_error_code_t* out_code ) { if (builder == nullptr || num_vectors == 0 || data == nullptr) { - if (out_code) { - *out_code = SVS_ERROR_INVALID_ARGUMENT; - } + SET_ERR_CODE(out_code, SVS_ERROR_INVALID_ARGUMENT); return nullptr; } if (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA) { - if (out_code) { - *out_code = SVS_ERROR_NOT_IMPLEMENTED; - } + SET_ERR_CODE(out_code, SVS_ERROR_NOT_IMPLEMENTED); return nullptr; } if (builder->impl->storage->kind != SVS_STORAGE_KIND_SIMPLE) { - if (out_code) { - *out_code = SVS_ERROR_NOT_IMPLEMENTED; - } + SET_ERR_CODE(out_code, SVS_ERROR_NOT_IMPLEMENTED); return nullptr; } - svs::DistanceType distance_type; - switch (builder->impl->distance_metric) { - case SVS_DISTANCE_METRIC_EUCLIDEAN: - distance_type = svs::DistanceType::L2; - break; - case SVS_DISTANCE_METRIC_DOT_PRODUCT: - distance_type = svs::DistanceType::MIP; - break; - case SVS_DISTANCE_METRIC_COSINE: - distance_type = svs::DistanceType::Cosine; - break; - default: - if (out_code) { - *out_code = SVS_ERROR_INVALID_ARGUMENT; - } - return nullptr; - } - auto src_data = svs::data::ConstSimpleDataView(data, num_vectors, builder->impl->dimension); - auto simple_data = svs::data::SimpleData(num_vectors, builder->impl->dimension); - - svs::data::copy(src_data, simple_data); - - auto vamana_algorithm = - std::static_pointer_cast(builder->impl->algorithm); - auto index = std::make_shared(svs::Vamana::build( - vamana_algorithm->get_build_parameters(), std::move(simple_data), distance_type - )); - if (out_code) { - *out_code = SVS_OK; + auto index = builder->impl->build(src_data); + if (index == nullptr) { + SET_ERR_CODE(out_code, SVS_ERROR_INDEX_BUILD_FAILED); + return nullptr; } + + SET_ERR_CODE(out_code, SVS_OK); auto result = new svs_index; result->impl = index; return result; @@ -281,18 +161,16 @@ svs_index_search(svs_index_t index, const float* queries, size_t num_queries, si if (index->impl->algorithm != SVS_ALGORITHM_TYPE_VAMANA) { return nullptr; } - auto& vamana_index = static_cast(*index->impl).index; + + using namespace svs::c_runtime; + + auto& vamana_index = static_cast(*index->impl).index; auto queries_view = svs::data::ConstSimpleDataView( queries, num_queries, vamana_index.dimensions() ); - auto& vamana_idx = static_cast(*index->impl).index; - auto vamana_results = svs::QueryResult(num_queries, k); - - vamana_idx.search( - vamana_results.view(), queries_view, vamana_idx.get_search_parameters() - ); + auto search_results = index->impl->search(queries_view, k, nullptr); svs_search_results_t results = new svs_search_results{0, nullptr, nullptr, nullptr}; @@ -304,8 +182,8 @@ svs_index_search(svs_index_t index, const float* queries, size_t num_queries, si for (size_t i = 0; i < num_queries; ++i) { results->results_per_query[i] = k; for (size_t j = 0; j < k; ++j) { - results->indices[i * k + j] = vamana_results.index(i, j); - results->distances[i * k + j] = vamana_results.distance(i, j); + results->indices[i * k + j] = search_results.index(i, j); + results->distances[i * k + j] = search_results.distance(i, j); } } From 19201d18ea017aa6198ff1021e6b10ee6bff9630 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 19 Jan 2026 15:55:15 +0100 Subject: [PATCH 04/18] Improve error handling and add LeanVec support --- bindings/c/include/svs/c_api/svs_c.h | 45 +++-- bindings/c/samples/simple.c | 69 +++++--- bindings/c/src/index_builder.hpp | 86 +++++++--- bindings/c/src/storage.hpp | 118 ++++++++++++- bindings/c/src/svs_c.cpp | 240 ++++++++++++++++++--------- bindings/c/src/types_support.hpp | 59 +++++++ 6 files changed, 473 insertions(+), 144 deletions(-) create mode 100644 bindings/c/src/types_support.hpp diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index fbcae0e7..e293ec7c 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -19,6 +19,7 @@ #ifdef __cplusplus extern "C" { #endif +#include #include enum svs_error_code { @@ -43,12 +44,13 @@ enum svs_algorithm_type { }; enum svs_data_type { - SVS_DATA_TYPE_FLOAT32 = 0, - SVS_DATA_TYPE_FLOAT16 = 1, - SVS_DATA_TYPE_INT8 = 2, - SVS_DATA_TYPE_UINT8 = 1, - SVS_DATA_TYPE_INT4 = 4, - SVS_DATA_TYPE_UINT4 = 5 + SVS_DATA_TYPE_VOID = 0, + SVS_DATA_TYPE_FLOAT32 = 32, + SVS_DATA_TYPE_FLOAT16 = 16, + SVS_DATA_TYPE_INT8 = 9, + SVS_DATA_TYPE_UINT8 = 8, + SVS_DATA_TYPE_INT4 = 5, + SVS_DATA_TYPE_UINT4 = 4 }; enum svs_storage_kind { SVS_STORAGE_KIND_SIMPLE = 0, SVS_STORAGE_KIND_LEANVEC = 1 }; @@ -60,6 +62,7 @@ struct svs_search_results { float* distances; }; +typedef struct svs_error_desc* svs_error_t; typedef struct svs_index* svs_index_t; typedef struct svs_index_builder* svs_index_builder_t; typedef struct svs_algorithm* svs_algorithm_t; @@ -71,21 +74,26 @@ typedef enum svs_distance_metric svs_distance_metric_t; typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; +svs_error_t svs_error_init(); +bool svs_error_ok(svs_error_t err); +svs_error_code_t svs_error_get_code(svs_error_t err); +const char* svs_error_get_message(svs_error_t err); +void svs_error_free(svs_error_t err); + svs_algorithm_t svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, - svs_error_code_t* out_code + svs_error_t out_err /*=NULL*/ ); void svs_algorithm_free(svs_algorithm_t algorithm); -svs_storage_t -svs_storage_create_simple(svs_data_type_t data_type, svs_error_code_t* out_code); +svs_storage_t svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err); svs_storage_t svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, - svs_error_code_t* out_code + svs_error_t out_err /*=NULL*/ ); void svs_storage_free(svs_storage_t storage); @@ -93,24 +101,29 @@ svs_index_builder_t svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, svs_algorithm_t algorithm, - svs_error_code_t* out_code + svs_error_t out_err /*=NULL*/ ); void svs_index_builder_free(svs_index_builder_t builder); -void svs_index_builder_set_storage( - svs_index_builder_t builder, svs_storage_t storage, svs_error_code_t* out_code +bool svs_index_builder_set_storage( + svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err /*=NULL*/ ); svs_index_t svs_index_build( svs_index_builder_t builder, const float* data, size_t num_vectors, - svs_error_code_t* out_code + svs_error_t out_err /*=NULL*/ ); void svs_index_free(svs_index_t index); -svs_search_results_t -svs_index_search(svs_index_t index, const float* queries, size_t num_queries, size_t k); +svs_search_results_t svs_index_search( + svs_index_t index, + const float* queries, + size_t num_queries, + size_t k, + svs_error_t out_err /*=NULL*/ +); void svs_search_results_free(svs_search_results_t results); #ifdef __cplusplus diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index 5fa75319..b3875fb8 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -17,7 +17,7 @@ void generate_random_data(float* data, size_t count, size_t dim) { int main() { int ret = 0; srand(time(NULL)); - svs_error_code_t error = SVS_OK; + svs_error_t error = svs_error_init(); float* data = NULL; float* queries = NULL; @@ -30,7 +30,7 @@ int main() { // Allocate random data data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float)); queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float)); - + if (!data || !queries) { fprintf(stderr, "Failed to allocate memory\n"); ret = 1; @@ -41,42 +41,60 @@ int main() { generate_random_data(queries, NUM_QUERIES, DIMENSION); // Create Vamana algorithm - algorithm = svs_algorithm_create_vamana(64, 128, 100, &error); - if (error != SVS_OK) { - fprintf(stderr, "Failed to create algorithm: %d\n", error); + algorithm = svs_algorithm_create_vamana(64, 128, 100, error); + if (!algorithm) { + fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error)); ret = 1; goto cleanup; } // Create storage - storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, &error); - if (error != SVS_OK) { - fprintf(stderr, "Failed to create storage: %d\n", error); + // Simple storage + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, &error); + + // LeanVec storage + size_t leanvec_dims = DIMENSION / 2; + // OK: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT4, error); + + // OK: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT8, error); + + // ERROR: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, + // SVS_DATA_TYPE_UINT4, error); + + storage = svs_storage_create_leanvec( + leanvec_dims, SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error + ); + if (!storage) { + fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error)); ret = 1; goto cleanup; } // Create index builder builder = svs_index_builder_create( - SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, &error); - if (error != SVS_OK) { - fprintf(stderr, "Failed to create index builder: %d\n", error); + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error + ); + if (!builder) { + fprintf( + stderr, "Failed to create index builder: %s\n", svs_error_get_message(error) + ); ret = 1; goto cleanup; } - svs_index_builder_set_storage(builder, storage, &error); - if (error != SVS_OK) { - fprintf(stderr, "Failed to set storage: %d\n", error); + if (!svs_index_builder_set_storage(builder, storage, error)) { + fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error)); ret = 1; goto cleanup; } // Build index printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION); - index = svs_index_build(builder, data, NUM_VECTORS, &error); - if (error != SVS_OK) { - fprintf(stderr, "Failed to build index: %d\n", error); + index = svs_index_build(builder, data, NUM_VECTORS, error); + if (!index) { + fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error)); ret = 1; goto cleanup; } @@ -84,15 +102,25 @@ int main() { // Search printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K); - results = svs_index_search(index, queries, NUM_QUERIES, K); + results = svs_index_search(index, queries, NUM_QUERIES, K, error); + if (!results) { + fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Search completed successfully!\n"); // Print results size_t offset = 0; for (size_t q = 0; q < results->num_queries; q++) { printf("Query %zu results:\n", q); for (size_t i = 0; i < results->results_per_query[q]; i++) { - printf(" [%zu] id=%zu, distance=%.4f\n", - i, results->indices[offset + i], results->distances[offset + i]); + printf( + " [%zu] id=%zu, distance=%.4f\n", + i, + results->indices[offset + i], + results->distances[offset + i] + ); } offset += results->results_per_query[q]; } @@ -108,6 +136,7 @@ int main() { svs_algorithm_free(algorithm); free(data); free(queries); + svs_error_free(error); return ret; } \ No newline at end of file diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index e4e01506..472ed980 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -20,6 +20,7 @@ #include "algorithm.hpp" #include "index.hpp" #include "storage.hpp" +#include "types_support.hpp" #include #include @@ -29,6 +30,64 @@ namespace svs::c_runtime { +template +svs::Vamana build_vamana_index_uncompressed( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + SimpleDataBuilder builder, + svs::DistanceType distance_type +) { + auto data = builder.build(std::move(src_data)); + return svs::Vamana::build(build_params, std::move(data), distance_type); +} + +template +svs::Vamana build_vamana_index_leanvec( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + LeanVecDataBuilder builder, + svs::DistanceType distance_type +) { + auto data = builder.build(std::move(src_data)); + return svs::Vamana::build(build_params, std::move(data), distance_type); +} + +template +void register_build_vamana_index_methods(Dispatcher& dispatcher) { + dispatcher + .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_uncompressed); + + dispatcher + .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<4, 4>); + dispatcher + .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<4, 8>); + dispatcher + .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<8, 8>); +} +using BuildIndexDispatcher = svs::lib::Dispatcher< + svs::Vamana, + const svs::index::vamana::VamanaBuildParameters&, + svs::data::ConstSimpleDataView, + const Storage*, + svs::DistanceType>; + +BuildIndexDispatcher build_vamana_index_dispatcher() { + auto dispatcher = BuildIndexDispatcher{}; + register_build_vamana_index_methods(dispatcher); + return dispatcher; +} + +svs::Vamana build_vamana_index( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + const Storage* storage, + svs::DistanceType distance_type +) { + return build_vamana_index_dispatcher().invoke( + build_params, std::move(src_data), storage, distance_type + ); +} + struct IndexBuilder { svs_distance_metric_t distance_metric; size_t dimension; @@ -50,35 +109,20 @@ struct IndexBuilder { this->storage = std::move(storage); } - svs::DistanceType get_distance_type() const { - switch (distance_metric) { - case SVS_DISTANCE_METRIC_EUCLIDEAN: - return svs::DistanceType::L2; - case SVS_DISTANCE_METRIC_DOT_PRODUCT: - return svs::DistanceType::MIP; - case SVS_DISTANCE_METRIC_COSINE: - return svs::DistanceType::Cosine; - default: - return svs::DistanceType::L2; // Default fallback - } - } - std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA && - storage->kind == SVS_STORAGE_KIND_SIMPLE) { + (storage->kind == SVS_STORAGE_KIND_SIMPLE || + storage->kind == SVS_STORAGE_KIND_LEANVEC)) { auto vamana_algorithm = std::static_pointer_cast(algorithm); svs::index::vamana::VamanaBuildParameters build_params = vamana_algorithm->get_build_parameters(); - auto storage = svs::data::SimpleData(data.size(), data.dimensions()); - - svs::data::copy(data, storage); - - auto index = std::make_shared(svs::Vamana::build( + auto index = std::make_shared(build_vamana_index( vamana_algorithm->get_build_parameters(), - std::move(storage), - get_distance_type() + data, + storage.get(), + to_distance_type(distance_metric) )); return index; diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 46b687de..662320ff 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -17,9 +17,17 @@ #include "svs/c_api/svs_c.h" +#include "types_support.hpp" + #include +#include +#include +#include +#include +#include -namespace svs::c_runtime { +namespace svs { +namespace c_runtime { struct Storage { svs_storage_kind kind; @@ -29,23 +37,117 @@ struct Storage { }; struct StorageSimple : public Storage { - svs_data_type_t data_type; + svs::DataType data_type; StorageSimple(svs_data_type_t data_type) : Storage{SVS_STORAGE_KIND_SIMPLE} - , data_type(data_type) {} + , data_type(to_data_type(data_type)) {} }; struct StorageLeanVec : public Storage { size_t lenavec_dims; - svs_data_type_t primary_type; - svs_data_type_t secondary_type; + size_t primary_bits; + size_t secondary_bits; StorageLeanVec(size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary) : Storage{SVS_STORAGE_KIND_LEANVEC} , lenavec_dims(lenavec_dims) - , primary_type(primary) - , secondary_type(secondary) {} + , primary_bits(to_bits_number(primary)) + , secondary_bits(to_bits_number(secondary)) {} + + static size_t to_bits_number(svs_data_type_t data_type) { + switch (data_type) { + case SVS_DATA_TYPE_INT4: + case SVS_DATA_TYPE_UINT4: + return 4; + case SVS_DATA_TYPE_INT8: + case SVS_DATA_TYPE_UINT8: + return 8; + default: + return 0; + } + } +}; + +} // namespace c_runtime + +template class SimpleDataBuilder { + public: + SimpleDataBuilder() {} + + using SimpleDataType = + svs::data::SimpleData>>; + + SimpleDataType build(svs::data::ConstSimpleDataView view) { + auto data = SimpleDataType(view.size(), view.dimensions()); + svs::data::copy(view, data); + return data; + } +}; + +template +struct lib::DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = SimpleDataBuilder; + + static int64_t match(From from) { + if constexpr (svs::is_arithmetic_v) { + if (from->kind == SVS_STORAGE_KIND_SIMPLE) { + auto simple = static_cast(from); + if (simple->data_type == svs::datatype_v) { + return svs::lib::perfect_match; + } + } + } + return svs::lib::invalid_match; + } + + static To convert(From from) { return To{}; } +}; + +template class LeanVecDataBuilder { + size_t leanvec_dims_; + + public: + LeanVecDataBuilder(size_t leanvec_dims) + : leanvec_dims_(leanvec_dims) {} + svs::threads::ThreadPoolHandle default_threadpool() { + return svs::threads::ThreadPoolHandle(svs::threads::DefaultThreadPool(4)); + } + using LeanDatasetType = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ, + svs::leanvec::UsingLVQ, + svs::Dynamic, + svs::Dynamic, + svs::data::Blocked>>; + + template LeanDatasetType build(svs::data::ConstSimpleDataView view) { + auto pool = default_threadpool(); + return LeanDatasetType::reduce( + view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_} + ); + } +}; + +template +struct lib::DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = LeanVecDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_LEANVEC) { + auto leanvec = static_cast(from); + if (leanvec->primary_bits == I1 && leanvec->secondary_bits == I2) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From from) { + auto leanvec = static_cast(from); + return LeanVecDataBuilder(leanvec->lenavec_dims); + } }; -} // namespace svs::c_runtime +} // namespace svs diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 6ab48c22..0f7fb2e1 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -26,13 +26,37 @@ #include // C API implementation -#define SET_ERR_CODE(code_ptr, code_value) \ - do { \ - if (code_ptr) { \ - *(code_ptr) = (code_value); \ - } \ +struct svs_error_desc { + svs_error_code_t code; + std::string message; +}; + +#define SET_ERROR(err, c, msg) \ + do { \ + if (err) { \ + err->code = (c); \ + err->message = (msg); \ + } \ } while (0) +template +inline auto +runtime_error_wrapper(Callable&& func, Result err_res, svs_error_t err) noexcept { + try { + SET_ERROR(err, SVS_OK, "Success"); + return func(); + } catch (const std::invalid_argument& ex) { + SET_ERROR(err, SVS_ERROR_INVALID_ARGUMENT, ex.what()); + return err_res; + } catch (const std::exception& ex) { + SET_ERROR(err, SVS_ERROR_GENERIC, ex.what()); + return err_res; + } catch (...) { + SET_ERROR(err, SVS_ERROR_GENERIC, "An unknown error has occurred."); + return err_res; + } +} + struct svs_index { std::shared_ptr impl; }; @@ -49,46 +73,70 @@ struct svs_storage { std::shared_ptr impl; }; +extern "C" svs_error_t svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } +extern "C" bool svs_error_ok(svs_error_t err) { return err->code == SVS_OK; } +extern "C" svs_error_code_t svs_error_get_code(svs_error_t err) { return err->code; } +extern "C" const char* svs_error_get_message(svs_error_t err) { + return err->message.c_str(); +} +extern "C" void svs_error_free(svs_error_t err) { delete err; } + extern "C" svs_algorithm_t svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, - svs_error_code_t* out_code + svs_error_t out_err ) { - using namespace svs::c_runtime; - auto algorithm = std::make_shared( - graph_degree, build_window_size, search_window_size + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto algorithm = std::make_shared( + graph_degree, build_window_size, search_window_size + ); + auto result = new svs_algorithm; + result->impl = algorithm; + return result; + }, + nullptr, + out_err ); - SET_ERR_CODE(out_code, SVS_OK); - auto result = new svs_algorithm; - result->impl = algorithm; - return result; } extern "C" void svs_algorithm_free(svs_algorithm_t algorithm) { delete algorithm; } extern "C" svs_storage_t -svs_storage_create_simple(svs_data_type_t data_type, svs_error_code_t* out_code) { - using namespace svs::c_runtime; - auto storage = std::make_shared(data_type); - SET_ERR_CODE(out_code, SVS_OK); - auto result = new svs_storage; - result->impl = storage; - return result; +svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err) { + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto storage = std::make_shared(data_type); + auto result = new svs_storage; + result->impl = storage; + return result; + }, + nullptr, + out_err + ); } extern "C" svs_storage_t svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, - svs_error_code_t* out_code + svs_error_t out_err ) { - using namespace svs::c_runtime; - auto storage = std::make_shared(lenavec_dims, primary, secondary); - SET_ERR_CODE(out_code, SVS_OK); - auto result = new svs_storage; - result->impl = storage; - return result; + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto storage = + std::make_shared(lenavec_dims, primary, secondary); + auto result = new svs_storage; + result->impl = storage; + return result; + }, + nullptr, + out_err + ); } extern "C" void svs_storage_free(svs_storage_t storage) { delete storage; } @@ -97,97 +145,131 @@ extern "C" svs_index_builder_t svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, svs_algorithm_t algorithm, - svs_error_code_t* out_code + svs_error_t out_err ) { - using namespace svs::c_runtime; - auto builder = std::make_shared(metric, dimension, algorithm->impl); - SET_ERR_CODE(out_code, SVS_OK); - auto result = new svs_index_builder; - result->impl = builder; - return result; + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto builder = + std::make_shared(metric, dimension, algorithm->impl); + auto result = new svs_index_builder; + result->impl = builder; + return result; + }, + nullptr, + out_err + ); } extern "C" void svs_index_builder_free(svs_index_builder_t builder) { delete builder; } -extern "C" void svs_index_builder_set_storage( - svs_index_builder_t builder, svs_storage_t storage, svs_error_code_t* out_code +extern "C" bool svs_index_builder_set_storage( + svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err ) { - builder->impl->set_storage(storage->impl); - SET_ERR_CODE(out_code, SVS_OK); - return; + if (builder == nullptr || storage == nullptr) { + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); + return false; + } + return runtime_error_wrapper( + [&]() { + builder->impl->set_storage(storage->impl); + return true; + }, + false, + out_err + ); } extern "C" svs_index_t svs_index_build( - svs_index_builder_t builder, - const float* data, - size_t num_vectors, - svs_error_code_t* out_code + svs_index_builder_t builder, const float* data, size_t num_vectors, svs_error_t out_err ) { if (builder == nullptr || num_vectors == 0 || data == nullptr) { - SET_ERR_CODE(out_code, SVS_ERROR_INVALID_ARGUMENT); + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); return nullptr; } if (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA) { - SET_ERR_CODE(out_code, SVS_ERROR_NOT_IMPLEMENTED); + SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); return nullptr; } - if (builder->impl->storage->kind != SVS_STORAGE_KIND_SIMPLE) { - SET_ERR_CODE(out_code, SVS_ERROR_NOT_IMPLEMENTED); + if (builder->impl->storage->kind != SVS_STORAGE_KIND_SIMPLE && + builder->impl->storage->kind != SVS_STORAGE_KIND_LEANVEC) { + SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); return nullptr; } - auto src_data = - svs::data::ConstSimpleDataView(data, num_vectors, builder->impl->dimension); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto src_data = svs::data::ConstSimpleDataView( + data, num_vectors, builder->impl->dimension + ); - auto index = builder->impl->build(src_data); - if (index == nullptr) { - SET_ERR_CODE(out_code, SVS_ERROR_INDEX_BUILD_FAILED); - return nullptr; - } + auto index = builder->impl->build(src_data); + if (index == nullptr) { + SET_ERROR(out_err, SVS_ERROR_INDEX_BUILD_FAILED, "Index build failed"); + return svs_index_t{nullptr}; + } - SET_ERR_CODE(out_code, SVS_OK); - auto result = new svs_index; - result->impl = index; - return result; + auto result = new svs_index; + result->impl = index; + return result; + }, + nullptr, + out_err + ); } extern "C" void svs_index_free(svs_index_t index) { delete index; } -extern "C" svs_search_results_t -svs_index_search(svs_index_t index, const float* queries, size_t num_queries, size_t k) { +extern "C" svs_search_results_t svs_index_search( + svs_index_t index, + const float* queries, + size_t num_queries, + size_t k, + svs_error_t out_err +) { if (index == nullptr || queries == nullptr || num_queries == 0 || k == 0) { + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); return nullptr; } if (index->impl->algorithm != SVS_ALGORITHM_TYPE_VAMANA) { + SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); return nullptr; } - using namespace svs::c_runtime; + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; - auto& vamana_index = static_cast(*index->impl).index; + auto& vamana_index = static_cast(*index->impl).index; - auto queries_view = svs::data::ConstSimpleDataView( - queries, num_queries, vamana_index.dimensions() - ); + auto queries_view = svs::data::ConstSimpleDataView( + queries, num_queries, vamana_index.dimensions() + ); - auto search_results = index->impl->search(queries_view, k, nullptr); + auto search_results = index->impl->search(queries_view, k, nullptr); - svs_search_results_t results = new svs_search_results{0, nullptr, nullptr, nullptr}; + svs_search_results_t results = + new svs_search_results{0, nullptr, nullptr, nullptr}; - results->num_queries = num_queries; - results->results_per_query = new size_t[num_queries]; - results->indices = new size_t[num_queries * k]; - results->distances = new float[num_queries * k]; + results->num_queries = num_queries; + results->results_per_query = new size_t[num_queries]; + results->indices = new size_t[num_queries * k]; + results->distances = new float[num_queries * k]; - for (size_t i = 0; i < num_queries; ++i) { - results->results_per_query[i] = k; - for (size_t j = 0; j < k; ++j) { - results->indices[i * k + j] = search_results.index(i, j); - results->distances[i * k + j] = search_results.distance(i, j); - } - } + for (size_t i = 0; i < num_queries; ++i) { + results->results_per_query[i] = k; + for (size_t j = 0; j < k; ++j) { + results->indices[i * k + j] = search_results.index(i, j); + results->distances[i * k + j] = search_results.distance(i, j); + } + } - return results; + return results; + }, + nullptr, + out_err + ); } extern "C" void svs_search_results_free(svs_search_results_t results) { diff --git a/bindings/c/src/types_support.hpp b/bindings/c/src/types_support.hpp new file mode 100644 index 00000000..5d8ca6b8 --- /dev/null +++ b/bindings/c/src/types_support.hpp @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include +#include + +namespace svs { +namespace c_runtime { + +svs::DistanceType to_distance_type(svs_distance_metric_t distance_metric) { + switch (distance_metric) { + case SVS_DISTANCE_METRIC_EUCLIDEAN: + return svs::DistanceType::L2; + case SVS_DISTANCE_METRIC_DOT_PRODUCT: + return svs::DistanceType::MIP; + case SVS_DISTANCE_METRIC_COSINE: + return svs::DistanceType::Cosine; + default: + return svs::DistanceType::L2; // Default fallback + } +} + +svs::DataType to_data_type(svs_data_type_t data_type) { + switch (data_type) { + case SVS_DATA_TYPE_FLOAT32: + return svs::DataType::float32; + case SVS_DATA_TYPE_FLOAT16: + return svs::DataType::float16; + case SVS_DATA_TYPE_INT8: + return svs::DataType::int8; + case SVS_DATA_TYPE_UINT8: + return svs::DataType::uint8; + case SVS_DATA_TYPE_INT4: + return svs::DataType::int8; // No direct mapping, using int8 as placeholder + case SVS_DATA_TYPE_UINT4: + return svs::DataType::uint8; // No direct mapping, using uint8 as placeholder + default: + return svs::DataType::undef; + } +} + +} // namespace c_runtime +} // namespace svs From 5941e16b588a1c44e51b4d35cba74eed7ff0c83f Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 11:41:40 +0100 Subject: [PATCH 05/18] Add basic thread pool configuration support --- bindings/c/include/svs/c_api/svs_c.h | 16 ++++++++ bindings/c/src/index_builder.hpp | 39 +++++++++++++----- bindings/c/src/storage.hpp | 10 +++-- bindings/c/src/svs_c.cpp | 60 ++++++++++++++++----------- bindings/c/src/thread_pool.hpp | 61 ++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 37 deletions(-) create mode 100644 bindings/c/src/thread_pool.hpp diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index e293ec7c..b8d2b81d 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -55,6 +55,13 @@ enum svs_data_type { enum svs_storage_kind { SVS_STORAGE_KIND_SIMPLE = 0, SVS_STORAGE_KIND_LEANVEC = 1 }; +enum svs_thread_pool_kind { + SVS_THREAD_POOL_KIND_NATIVE = 0, + SVS_THREAD_POOL_KIND_OMP = 1, + SVS_THREAD_POOL_KIND_SINGLE_THREAD = 2, + SVS_THREAD_POOL_KIND_MANUAL = 3 +}; + struct svs_search_results { size_t num_queries; size_t* results_per_query; @@ -68,11 +75,13 @@ typedef struct svs_index_builder* svs_index_builder_t; typedef struct svs_algorithm* svs_algorithm_t; typedef struct svs_storage* svs_storage_t; typedef struct svs_search_results* svs_search_results_t; +typedef struct svs_thread_pool* svs_thread_pool_t; typedef enum svs_error_code svs_error_code_t; typedef enum svs_distance_metric svs_distance_metric_t; typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; +typedef enum svs_thread_pool_kind svs_thread_pool_kind_t; svs_error_t svs_error_init(); bool svs_error_ok(svs_error_t err); @@ -109,6 +118,13 @@ bool svs_index_builder_set_storage( svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err /*=NULL*/ ); +bool svs_index_builder_set_thread_pool( + svs_index_builder_t builder, + svs_thread_pool_kind_t kind, + size_t num_threads, + svs_error_t out_err /*=NULL*/ +); + svs_index_t svs_index_build( svs_index_builder_t builder, const float* data, diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index 472ed980..80ac7f0c 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -20,6 +20,7 @@ #include "algorithm.hpp" #include "index.hpp" #include "storage.hpp" +#include "thread_pool.hpp" #include "types_support.hpp" #include @@ -35,10 +36,13 @@ svs::Vamana build_vamana_index_uncompressed( const svs::index::vamana::VamanaBuildParameters& build_params, svs::data::ConstSimpleDataView src_data, SimpleDataBuilder builder, - svs::DistanceType distance_type + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool ) { - auto data = builder.build(std::move(src_data)); - return svs::Vamana::build(build_params, std::move(data), distance_type); + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance_type, std::move(pool) + ); } template @@ -46,10 +50,13 @@ svs::Vamana build_vamana_index_leanvec( const svs::index::vamana::VamanaBuildParameters& build_params, svs::data::ConstSimpleDataView src_data, LeanVecDataBuilder builder, - svs::DistanceType distance_type + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool ) { - auto data = builder.build(std::move(src_data)); - return svs::Vamana::build(build_params, std::move(data), distance_type); + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance_type, std::move(pool) + ); } template @@ -69,7 +76,8 @@ using BuildIndexDispatcher = svs::lib::Dispatcher< const svs::index::vamana::VamanaBuildParameters&, svs::data::ConstSimpleDataView, const Storage*, - svs::DistanceType>; + svs::DistanceType, + svs::threads::ThreadPoolHandle>; BuildIndexDispatcher build_vamana_index_dispatcher() { auto dispatcher = BuildIndexDispatcher{}; @@ -81,10 +89,11 @@ svs::Vamana build_vamana_index( const svs::index::vamana::VamanaBuildParameters& build_params, svs::data::ConstSimpleDataView src_data, const Storage* storage, - svs::DistanceType distance_type + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool ) { return build_vamana_index_dispatcher().invoke( - build_params, std::move(src_data), storage, distance_type + build_params, std::move(src_data), storage, distance_type, std::move(pool) ); } @@ -93,6 +102,8 @@ struct IndexBuilder { size_t dimension; std::shared_ptr algorithm; std::shared_ptr storage; + ThreadPoolBuilder pool_builder; + IndexBuilder( svs_distance_metric_t distance_metric, size_t dimension, @@ -101,7 +112,8 @@ struct IndexBuilder { : distance_metric(distance_metric) , dimension(dimension) , algorithm(std::move(algorithm)) - , storage(std::make_shared(SVS_DATA_TYPE_FLOAT32)) {} + , storage(std::make_shared(SVS_DATA_TYPE_FLOAT32)) + , pool_builder{} {} ~IndexBuilder() {} @@ -109,6 +121,10 @@ struct IndexBuilder { this->storage = std::move(storage); } + void set_thread_pool(ThreadPoolBuilder thread_pool_builder) { + std::swap(this->pool_builder, thread_pool_builder); + } + std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA && (storage->kind == SVS_STORAGE_KIND_SIMPLE || @@ -122,7 +138,8 @@ struct IndexBuilder { vamana_algorithm->get_build_parameters(), data, storage.get(), - to_distance_type(distance_metric) + to_distance_type(distance_metric), + pool_builder.build() )); return index; diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 662320ff..c81ee056 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -78,7 +78,10 @@ template class SimpleDataBuilder { using SimpleDataType = svs::data::SimpleData>>; - SimpleDataType build(svs::data::ConstSimpleDataView view) { + SimpleDataType build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& SVS_UNUSED(pool) + ) { auto data = SimpleDataType(view.size(), view.dimensions()); svs::data::copy(view, data); return data; @@ -121,8 +124,9 @@ template class LeanVecDataBuilder { svs::Dynamic, svs::data::Blocked>>; - template LeanDatasetType build(svs::data::ConstSimpleDataView view) { - auto pool = default_threadpool(); + template + LeanDatasetType + build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { return LeanDatasetType::reduce( view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_} ); diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 0f7fb2e1..7f1df71e 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -20,6 +20,8 @@ #include "index.hpp" #include "index_builder.hpp" #include "storage.hpp" +#include "thread_pool.hpp" +#include "types_support.hpp" #include #include @@ -31,17 +33,17 @@ struct svs_error_desc { std::string message; }; -#define SET_ERROR(err, c, msg) \ - do { \ - if (err) { \ - err->code = (c); \ - err->message = (msg); \ - } \ +#define SET_ERROR(err, c, msg) \ + do { \ + if (err) { \ + (err)->code = (c); \ + (err)->message = (msg); \ + } \ } while (0) -template -inline auto -runtime_error_wrapper(Callable&& func, Result err_res, svs_error_t err) noexcept { +template > +inline Result +runtime_error_wrapper(Callable&& func, svs_error_t err, Result err_res = {}) noexcept { try { SET_ERROR(err, SVS_OK, "Success"); return func(); @@ -87,7 +89,7 @@ extern "C" svs_algorithm_t svs_algorithm_create_vamana( size_t search_window_size, svs_error_t out_err ) { - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; auto algorithm = std::make_shared( @@ -97,7 +99,6 @@ extern "C" svs_algorithm_t svs_algorithm_create_vamana( result->impl = algorithm; return result; }, - nullptr, out_err ); } @@ -106,7 +107,7 @@ extern "C" void svs_algorithm_free(svs_algorithm_t algorithm) { delete algorithm extern "C" svs_storage_t svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err) { - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; auto storage = std::make_shared(data_type); @@ -114,7 +115,6 @@ svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err) { result->impl = storage; return result; }, - nullptr, out_err ); } @@ -125,7 +125,7 @@ extern "C" svs_storage_t svs_storage_create_leanvec( svs_data_type_t secondary, svs_error_t out_err ) { - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; auto storage = @@ -134,7 +134,6 @@ extern "C" svs_storage_t svs_storage_create_leanvec( result->impl = storage; return result; }, - nullptr, out_err ); } @@ -147,7 +146,7 @@ extern "C" svs_index_builder_t svs_index_builder_create( svs_algorithm_t algorithm, svs_error_t out_err ) { - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; auto builder = @@ -156,7 +155,6 @@ extern "C" svs_index_builder_t svs_index_builder_create( result->impl = builder; return result; }, - nullptr, out_err ); } @@ -170,12 +168,30 @@ extern "C" bool svs_index_builder_set_storage( SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); return false; } - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { builder->impl->set_storage(storage->impl); return true; }, - false, + out_err + ); +} + +extern "C" bool svs_index_builder_set_thread_pool( + svs_index_builder_t builder, + svs_thread_pool_kind_t kind, + size_t num_threads, + svs_error_t out_err +) { + if (builder == nullptr) { + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); + return false; + } + return runtime_error_wrapper( + [&]() { + builder->impl->set_thread_pool({kind, num_threads}); + return true; + }, out_err ); } @@ -197,7 +213,7 @@ extern "C" svs_index_t svs_index_build( return nullptr; } - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; auto src_data = svs::data::ConstSimpleDataView( @@ -214,7 +230,6 @@ extern "C" svs_index_t svs_index_build( result->impl = index; return result; }, - nullptr, out_err ); } @@ -237,7 +252,7 @@ extern "C" svs_search_results_t svs_index_search( return nullptr; } - return runtime_error_wrapper( + return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; @@ -267,7 +282,6 @@ extern "C" svs_search_results_t svs_index_search( return results; }, - nullptr, out_err ); } diff --git a/bindings/c/src/thread_pool.hpp b/bindings/c/src/thread_pool.hpp new file mode 100644 index 00000000..2d651ae2 --- /dev/null +++ b/bindings/c/src/thread_pool.hpp @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "types_support.hpp" + +#include + +#include +#include + +namespace svs::c_runtime { + +struct ThreadPoolBuilder { + svs_thread_pool_kind kind; + size_t num_threads; + ThreadPoolBuilder(svs_thread_pool_kind kind, size_t num_threads) + : kind(kind) + , num_threads(num_threads) {} + + ThreadPoolBuilder() + : ThreadPoolBuilder(SVS_THREAD_POOL_KIND_NATIVE, default_threads_num()) {} + + static size_t default_threads_num() { + return std::max(size_t{1}, size_t{std::thread::hardware_concurrency()}); + } + + svs::threads::ThreadPoolHandle build() const { + using namespace svs::threads; + switch (kind) { + case SVS_THREAD_POOL_KIND_NATIVE: + return ThreadPoolHandle(NativeThreadPool(num_threads)); + case SVS_THREAD_POOL_KIND_OMP: + return ThreadPoolHandle(OMPThreadPool(num_threads)); + case SVS_THREAD_POOL_KIND_SINGLE_THREAD: + return ThreadPoolHandle(SequentialThreadPool()); + case SVS_THREAD_POOL_KIND_MANUAL: + throw std::invalid_argument( + "SVS_THREAD_POOL_KIND_MANUAL cannot be built automatically." + ); + default: + throw std::invalid_argument("Unknown svs_thread_pool_kind value."); + } + } +}; +} // namespace svs::c_runtime From b1e85f4a7b026edaaa1289e1973b72b206cdbe01 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 13:32:04 +0100 Subject: [PATCH 06/18] Set default visibility hidden --- bindings/c/CMakeLists.txt | 87 +++++++++++---------- bindings/c/c_apiConfig.cmake.in | 19 +++++ bindings/c/include/svs/c_api/svs_c.h | 39 ++++----- bindings/c/include/svs/c_api/svs_c_config.h | 37 +++++++++ 4 files changed, 122 insertions(+), 60 deletions(-) create mode 100644 bindings/c/c_apiConfig.cmake.in create mode 100644 bindings/c/include/svs/c_api/svs_c_config.h diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 1dda7033..993af01d 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -17,6 +17,7 @@ project(svs_c_api VERSION 0.1.0 LANGUAGES CXX C) set(TARGET_NAME svs_c_api) set(SVS_C_API_HEADERS + include/svs/c_api/svs_c_config.h include/svs/c_api/svs_c.h ) @@ -25,6 +26,8 @@ set(SVS_C_API_SOURCES src/storage.hpp src/index.hpp src/index_builder.hpp + src/thread_pool.hpp + src/types_support.hpp src/svs_c.cpp ) @@ -42,7 +45,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC OpenMP::OpenMP_CXX) target_compile_options(${TARGET_NAME} PRIVATE -DSVS_ENABLE_OMP=1 -# -fvisibility=hidden + -fvisibility=hidden ) if(UNIX AND NOT APPLE) @@ -66,54 +69,54 @@ link_mkl_static(${TARGET_NAME}) # PUBLIC "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\"" # ) -# installing -# include(GNUInstallDirs) +# 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") +set(SVS_C_API_EXPORT_NAME ${TARGET_NAME}) +set(VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}ConfigVersion.cmake") +set(SVS_C_API_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/svs_c_api) +set(SVS_C_API_COMPONENT_NAME "C_API") -# 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(TARGETS ${TARGET_NAME} + EXPORT ${SVS_C_API_EXPORT_NAME} + COMPONENT ${SVS_C_API_COMPONENT_NAME} + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include/svs/c_api + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) -# install(DIRECTORY include/svs/runtime -# COMPONENT ${SVS_RUNTIME_COMPONENT_NAME} -# DESTINATION include/svs -# FILES_MATCHING PATTERN "*.h" -# ) +install(DIRECTORY include/svs/c_api + COMPONENT ${SVS_C_API_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} -# ) +install(EXPORT ${SVS_C_API_EXPORT_NAME} + COMPONENT ${SVS_C_API_COMPONENT_NAME} + NAMESPACE svs:: + DESTINATION ${SVS_C_API_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}" -# ) +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/c_apiConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}Config.cmake" + INSTALL_DESTINATION "${SVS_C_API_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 -# ) +# 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}" -# ) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}Config.cmake" + "${VERSION_CONFIG}" + COMPONENT ${SVS_C_API_COMPONENT_NAME} + DESTINATION "${SVS_C_API_CONFIG_INSTALL_DIR}" +) # Build tests if requested # if(SVS_BUILD_C_API_TESTS) diff --git a/bindings/c/c_apiConfig.cmake.in b/bindings/c/c_apiConfig.cmake.in new file mode 100644 index 00000000..d6ee49d4 --- /dev/null +++ b/bindings/c/c_apiConfig.cmake.in @@ -0,0 +1,19 @@ +# Copyright 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@PACKAGE_INIT@ + +set(TARGET_NAME svs_c_api) +include("${CMAKE_CURRENT_LIST_DIR}/${TARGET_NAME}.cmake") +check_required_components(${TARGET_NAME}) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index b8d2b81d..7e767ae8 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -16,6 +16,8 @@ #pragma once +#include "svs_c_config.h" + #ifdef __cplusplus extern "C" { #endif @@ -83,64 +85,65 @@ typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; typedef enum svs_thread_pool_kind svs_thread_pool_kind_t; -svs_error_t svs_error_init(); -bool svs_error_ok(svs_error_t err); -svs_error_code_t svs_error_get_code(svs_error_t err); -const char* svs_error_get_message(svs_error_t err); -void svs_error_free(svs_error_t err); +SVS_API svs_error_t svs_error_init(); +SVS_API bool svs_error_ok(svs_error_t err); +SVS_API svs_error_code_t svs_error_get_code(svs_error_t err); +SVS_API const char* svs_error_get_message(svs_error_t err); +SVS_API void svs_error_free(svs_error_t err); -svs_algorithm_t svs_algorithm_create_vamana( +SVS_API svs_algorithm_t svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, svs_error_t out_err /*=NULL*/ ); -void svs_algorithm_free(svs_algorithm_t algorithm); +SVS_API void svs_algorithm_free(svs_algorithm_t algorithm); -svs_storage_t svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err); -svs_storage_t svs_storage_create_leanvec( +SVS_API svs_storage_t +svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err); +SVS_API svs_storage_t svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, svs_error_t out_err /*=NULL*/ ); -void svs_storage_free(svs_storage_t storage); +SVS_API void svs_storage_free(svs_storage_t storage); -svs_index_builder_t svs_index_builder_create( +SVS_API svs_index_builder_t svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, svs_algorithm_t algorithm, svs_error_t out_err /*=NULL*/ ); -void svs_index_builder_free(svs_index_builder_t builder); +SVS_API void svs_index_builder_free(svs_index_builder_t builder); -bool svs_index_builder_set_storage( +SVS_API bool svs_index_builder_set_storage( svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err /*=NULL*/ ); -bool svs_index_builder_set_thread_pool( +SVS_API bool svs_index_builder_set_thread_pool( svs_index_builder_t builder, svs_thread_pool_kind_t kind, size_t num_threads, svs_error_t out_err /*=NULL*/ ); -svs_index_t svs_index_build( +SVS_API svs_index_t svs_index_build( svs_index_builder_t builder, const float* data, size_t num_vectors, svs_error_t out_err /*=NULL*/ ); -void svs_index_free(svs_index_t index); +SVS_API void svs_index_free(svs_index_t index); -svs_search_results_t svs_index_search( +SVS_API svs_search_results_t svs_index_search( svs_index_t index, const float* queries, size_t num_queries, size_t k, svs_error_t out_err /*=NULL*/ ); -void svs_search_results_free(svs_search_results_t results); +SVS_API void svs_search_results_free(svs_search_results_t results); #ifdef __cplusplus } diff --git a/bindings/c/include/svs/c_api/svs_c_config.h b/bindings/c/include/svs/c_api/svs_c_config.h new file mode 100644 index 00000000..00398585 --- /dev/null +++ b/bindings/c/include/svs/c_api/svs_c_config.h @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// All symbols shall be internal unless marked as SVS_API +#if defined _WIN32 || defined __CYGWIN__ +#define SVS_HELPER_DLL_IMPORT __declspec(dllimport) +#define SVS_HELPER_DLL_EXPORT __declspec(dllexport) +#else +#if __GNUC__ >= 4 +#define SVS_HELPER_DLL_IMPORT __attribute__((visibility("default"))) +#define SVS_HELPER_DLL_EXPORT __attribute__((visibility("default"))) +#else +#define SVS_HELPER_DLL_IMPORT +#define SVS_HELPER_DLL_EXPORT +#endif +#endif + +#ifdef svs_c_api_EXPORTS +#define SVS_API SVS_HELPER_DLL_EXPORT +#else +#define SVS_API SVS_HELPER_DLL_IMPORT +#endif From 47e8096e3cf3d1ef2da3b46ab3c0b910d3772a3a Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 14:22:30 +0100 Subject: [PATCH 07/18] Add FP16 simple storage --- bindings/c/samples/simple.c | 20 +++++++++++++------- bindings/c/src/index_builder.hpp | 3 +++ bindings/c/src/storage.hpp | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index b3875fb8..eb968202 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -50,22 +50,28 @@ int main() { // Create storage // Simple storage - // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, &error); + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error); + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error); // LeanVec storage size_t leanvec_dims = DIMENSION / 2; - // OK: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, // SVS_DATA_TYPE_UINT4, error); - // OK: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, // SVS_DATA_TYPE_UINT8, error); - // ERROR: storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, - // SVS_DATA_TYPE_UINT4, error); - + // OK: storage = svs_storage_create_leanvec( - leanvec_dims, SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error + leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error ); + + // ERROR: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, + // SVS_DATA_TYPE_UINT4, error); + if (!storage) { fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error)); ret = 1; diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index 80ac7f0c..f440d7ac 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace svs::c_runtime { @@ -63,6 +64,8 @@ template void register_build_vamana_index_methods(Dispatcher& dispatcher) { dispatcher .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_uncompressed); + dispatcher + .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_uncompressed); dispatcher .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<4, 4>); diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index c81ee056..32dfaa8f 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace svs { @@ -78,8 +79,9 @@ template class SimpleDataBuilder { using SimpleDataType = svs::data::SimpleData>>; + template SimpleDataType build( - svs::data::ConstSimpleDataView view, + svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& SVS_UNUSED(pool) ) { auto data = SimpleDataType(view.size(), view.dimensions()); From 3ce98f991ce1540e143942047dea52964eda0c7c Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 15:12:07 +0100 Subject: [PATCH 08/18] Add search parameters support --- bindings/c/include/svs/c_api/svs_c.h | 78 ++++++++++++++------------ bindings/c/samples/simple.c | 20 +++++-- bindings/c/src/algorithm.hpp | 12 ++-- bindings/c/src/index.hpp | 2 +- bindings/c/src/svs_c.cpp | 83 ++++++++++++++++++---------- 5 files changed, 120 insertions(+), 75 deletions(-) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 7e767ae8..6d34fd37 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -71,77 +71,87 @@ struct svs_search_results { float* distances; }; -typedef struct svs_error_desc* svs_error_t; -typedef struct svs_index* svs_index_t; -typedef struct svs_index_builder* svs_index_builder_t; -typedef struct svs_algorithm* svs_algorithm_t; -typedef struct svs_storage* svs_storage_t; -typedef struct svs_search_results* svs_search_results_t; -typedef struct svs_thread_pool* svs_thread_pool_t; - +// Handle typedefs; "_h" suffix indicates a handle to an opaque struct +typedef struct svs_error_desc* svs_error_h; +typedef struct svs_index* svs_index_h; +typedef struct svs_index_builder* svs_index_builder_h; +typedef struct svs_algorithm* svs_algorithm_h; +typedef struct svs_storage* svs_storage_h; +typedef struct svs_search_params* svs_search_params_h; + +// Fully defined types; "_t" suffix indicates a fully defined struct typedef enum svs_error_code svs_error_code_t; typedef enum svs_distance_metric svs_distance_metric_t; typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; typedef enum svs_thread_pool_kind svs_thread_pool_kind_t; -SVS_API svs_error_t svs_error_init(); -SVS_API bool svs_error_ok(svs_error_t err); -SVS_API svs_error_code_t svs_error_get_code(svs_error_t err); -SVS_API const char* svs_error_get_message(svs_error_t err); -SVS_API void svs_error_free(svs_error_t err); +typedef struct svs_search_results* svs_search_results_t; + +SVS_API svs_error_h svs_error_init(); +SVS_API bool svs_error_ok(svs_error_h err); +SVS_API svs_error_code_t svs_error_get_code(svs_error_h err); +SVS_API const char* svs_error_get_message(svs_error_h err); +SVS_API void svs_error_free(svs_error_h err); -SVS_API svs_algorithm_t svs_algorithm_create_vamana( +SVS_API svs_algorithm_h svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, - svs_error_t out_err /*=NULL*/ + svs_error_h out_err /*=NULL*/ +); +SVS_API void svs_algorithm_free(svs_algorithm_h algorithm); + +SVS_API svs_search_params_h svs_search_params_create_vamana( + size_t search_window_size, + svs_error_h out_err /*=NULL*/ ); -SVS_API void svs_algorithm_free(svs_algorithm_t algorithm); +SVS_API void svs_search_params_free(svs_search_params_h params); -SVS_API svs_storage_t -svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err); -SVS_API svs_storage_t svs_storage_create_leanvec( +SVS_API svs_storage_h +svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err); +SVS_API svs_storage_h svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, - svs_error_t out_err /*=NULL*/ + svs_error_h out_err /*=NULL*/ ); -SVS_API void svs_storage_free(svs_storage_t storage); +SVS_API void svs_storage_free(svs_storage_h storage); -SVS_API svs_index_builder_t svs_index_builder_create( +SVS_API svs_index_builder_h svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, - svs_algorithm_t algorithm, - svs_error_t out_err /*=NULL*/ + svs_algorithm_h algorithm, + svs_error_h out_err /*=NULL*/ ); -SVS_API void svs_index_builder_free(svs_index_builder_t builder); +SVS_API void svs_index_builder_free(svs_index_builder_h builder); SVS_API bool svs_index_builder_set_storage( - svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err /*=NULL*/ + svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err /*=NULL*/ ); SVS_API bool svs_index_builder_set_thread_pool( - svs_index_builder_t builder, + svs_index_builder_h builder, svs_thread_pool_kind_t kind, size_t num_threads, - svs_error_t out_err /*=NULL*/ + svs_error_h out_err /*=NULL*/ ); -SVS_API svs_index_t svs_index_build( - svs_index_builder_t builder, +SVS_API svs_index_h svs_index_build( + svs_index_builder_h builder, const float* data, size_t num_vectors, - svs_error_t out_err /*=NULL*/ + svs_error_h out_err /*=NULL*/ ); -SVS_API void svs_index_free(svs_index_t index); +SVS_API void svs_index_free(svs_index_h index); SVS_API svs_search_results_t svs_index_search( - svs_index_t index, + svs_index_h index, const float* queries, size_t num_queries, size_t k, - svs_error_t out_err /*=NULL*/ + svs_search_params_h search_params /*=NULL*/, + svs_error_h out_err /*=NULL*/ ); SVS_API void svs_search_results_free(svs_search_results_t results); diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index eb968202..cadc6333 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -17,14 +17,14 @@ void generate_random_data(float* data, size_t count, size_t dim) { int main() { int ret = 0; srand(time(NULL)); - svs_error_t error = svs_error_init(); + svs_error_h error = svs_error_init(); float* data = NULL; float* queries = NULL; - svs_algorithm_t algorithm = NULL; - svs_storage_t storage = NULL; - svs_index_builder_t builder = NULL; - svs_index_t index = NULL; + svs_algorithm_h algorithm = NULL; + svs_storage_h storage = NULL; + svs_index_builder_h builder = NULL; + svs_index_h index = NULL; svs_search_results_t results = NULL; // Allocate random data @@ -106,9 +106,17 @@ int main() { } printf("Index built successfully!\n"); + // Search params + svs_search_params_h search_params = svs_search_params_create_vamana(100, error); + if (!search_params) { + fprintf(stderr, "Failed to create search params: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + // Search printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K); - results = svs_index_search(index, queries, NUM_QUERIES, K, error); + results = svs_index_search(index, queries, NUM_QUERIES, K, search_params, error); if (!results) { fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error)); ret = 1; diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp index 01ce71fe..65099ecf 100644 --- a/bindings/c/src/algorithm.hpp +++ b/bindings/c/src/algorithm.hpp @@ -43,10 +43,10 @@ struct Algorithm { }; struct AlgorithmVamana : public Algorithm { - struct SearchParamsVamana : public SearchParams { + struct SearchParams : public Algorithm::SearchParams { size_t search_window_size; - SearchParamsVamana(size_t search_window_size) - : SearchParams{SVS_ALGORITHM_TYPE_VAMANA} + SearchParams(size_t search_window_size) + : Algorithm::SearchParams{SVS_ALGORITHM_TYPE_VAMANA} , search_window_size(search_window_size) {} svs::index::vamana::VamanaSearchParameters get_search_parameters() const { @@ -59,7 +59,7 @@ struct AlgorithmVamana : public Algorithm { size_t graph_degree; size_t build_window_size; - SearchParamsVamana default_search_params; + SearchParams default_search_params; AlgorithmVamana( size_t graph_degree, size_t build_window_size, size_t search_window_size @@ -76,8 +76,8 @@ struct AlgorithmVamana : public Algorithm { return params; } - std::shared_ptr get_default_search_params() const override { - return std::make_shared(default_search_params); + std::shared_ptr get_default_search_params() const override { + return std::make_shared(default_search_params); } }; diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index 2de8a24d..fb1d95ec 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -50,7 +50,7 @@ struct IndexVamana : public Index { const std::shared_ptr& search_params ) { auto vamana_search_params = - std::static_pointer_cast(search_params); + std::static_pointer_cast(search_params); auto results = svs::QueryResult(queries.size(), num_neighbors); auto params = index.get_search_parameters(); diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 7f1df71e..49df3c1c 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -43,7 +43,7 @@ struct svs_error_desc { template > inline Result -runtime_error_wrapper(Callable&& func, svs_error_t err, Result err_res = {}) noexcept { +runtime_error_wrapper(Callable&& func, svs_error_h err, Result err_res = {}) noexcept { try { SET_ERROR(err, SVS_OK, "Success"); return func(); @@ -71,23 +71,27 @@ struct svs_algorithm { std::shared_ptr impl; }; +struct svs_search_params { + std::shared_ptr impl; +}; + struct svs_storage { std::shared_ptr impl; }; -extern "C" svs_error_t svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } -extern "C" bool svs_error_ok(svs_error_t err) { return err->code == SVS_OK; } -extern "C" svs_error_code_t svs_error_get_code(svs_error_t err) { return err->code; } -extern "C" const char* svs_error_get_message(svs_error_t err) { +extern "C" svs_error_h svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } +extern "C" bool svs_error_ok(svs_error_h err) { return err->code == SVS_OK; } +extern "C" svs_error_code_t svs_error_get_code(svs_error_h err) { return err->code; } +extern "C" const char* svs_error_get_message(svs_error_h err) { return err->message.c_str(); } -extern "C" void svs_error_free(svs_error_t err) { delete err; } +extern "C" void svs_error_free(svs_error_h err) { delete err; } -extern "C" svs_algorithm_t svs_algorithm_create_vamana( +extern "C" svs_algorithm_h svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, - svs_error_t out_err + svs_error_h out_err ) { return runtime_error_wrapper( [&]() { @@ -103,10 +107,32 @@ extern "C" svs_algorithm_t svs_algorithm_create_vamana( ); } -extern "C" void svs_algorithm_free(svs_algorithm_t algorithm) { delete algorithm; } +extern "C" void svs_algorithm_free(svs_algorithm_h algorithm) { delete algorithm; } + +extern "C" svs_search_params_h svs_search_params_create_vamana( + size_t search_window_size, + svs_error_h out_err +) { + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto params = std::make_shared( + search_window_size + ); + auto result = new svs_search_params; + result->impl = params; + return result; + }, + out_err + ); +} + +extern "C" void svs_search_params_free(svs_search_params_h params) { + delete params; +} -extern "C" svs_storage_t -svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err) { +extern "C" svs_storage_h +svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err) { return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; @@ -119,11 +145,11 @@ svs_storage_create_simple(svs_data_type_t data_type, svs_error_t out_err) { ); } -extern "C" svs_storage_t svs_storage_create_leanvec( +extern "C" svs_storage_h svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, - svs_error_t out_err + svs_error_h out_err ) { return runtime_error_wrapper( [&]() { @@ -138,13 +164,13 @@ extern "C" svs_storage_t svs_storage_create_leanvec( ); } -extern "C" void svs_storage_free(svs_storage_t storage) { delete storage; } +extern "C" void svs_storage_free(svs_storage_h storage) { delete storage; } -extern "C" svs_index_builder_t svs_index_builder_create( +extern "C" svs_index_builder_h svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, - svs_algorithm_t algorithm, - svs_error_t out_err + svs_algorithm_h algorithm, + svs_error_h out_err ) { return runtime_error_wrapper( [&]() { @@ -159,10 +185,10 @@ extern "C" svs_index_builder_t svs_index_builder_create( ); } -extern "C" void svs_index_builder_free(svs_index_builder_t builder) { delete builder; } +extern "C" void svs_index_builder_free(svs_index_builder_h builder) { delete builder; } extern "C" bool svs_index_builder_set_storage( - svs_index_builder_t builder, svs_storage_t storage, svs_error_t out_err + svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err ) { if (builder == nullptr || storage == nullptr) { SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); @@ -178,10 +204,10 @@ extern "C" bool svs_index_builder_set_storage( } extern "C" bool svs_index_builder_set_thread_pool( - svs_index_builder_t builder, + svs_index_builder_h builder, svs_thread_pool_kind_t kind, size_t num_threads, - svs_error_t out_err + svs_error_h out_err ) { if (builder == nullptr) { SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); @@ -196,8 +222,8 @@ extern "C" bool svs_index_builder_set_thread_pool( ); } -extern "C" svs_index_t svs_index_build( - svs_index_builder_t builder, const float* data, size_t num_vectors, svs_error_t out_err +extern "C" svs_index_h svs_index_build( + svs_index_builder_h builder, const float* data, size_t num_vectors, svs_error_h out_err ) { if (builder == nullptr || num_vectors == 0 || data == nullptr) { SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); @@ -223,7 +249,7 @@ extern "C" svs_index_t svs_index_build( auto index = builder->impl->build(src_data); if (index == nullptr) { SET_ERROR(out_err, SVS_ERROR_INDEX_BUILD_FAILED, "Index build failed"); - return svs_index_t{nullptr}; + return svs_index_h{nullptr}; } auto result = new svs_index; @@ -234,14 +260,15 @@ extern "C" svs_index_t svs_index_build( ); } -extern "C" void svs_index_free(svs_index_t index) { delete index; } +extern "C" void svs_index_free(svs_index_h index) { delete index; } extern "C" svs_search_results_t svs_index_search( - svs_index_t index, + svs_index_h index, const float* queries, size_t num_queries, size_t k, - svs_error_t out_err + svs_search_params_h search_params, + svs_error_h out_err ) { if (index == nullptr || queries == nullptr || num_queries == 0 || k == 0) { SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); @@ -262,7 +289,7 @@ extern "C" svs_search_results_t svs_index_search( queries, num_queries, vamana_index.dimensions() ); - auto search_results = index->impl->search(queries_view, k, nullptr); + auto search_results = index->impl->search(queries_view, k, search_params->impl); svs_search_results_t results = new svs_search_results{0, nullptr, nullptr, nullptr}; From 0a014dec15dc46bb9d2e8ffcb3f3c244b191f59f Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 16:17:01 +0100 Subject: [PATCH 09/18] Add LVQ and SQ storage kinds support --- bindings/c/include/svs/c_api/svs_c.h | 16 +++- bindings/c/samples/simple.c | 18 +++- bindings/c/src/index_builder.hpp | 57 +++++++++--- bindings/c/src/storage.hpp | 129 +++++++++++++++++++++++++-- bindings/c/src/svs_c.cpp | 49 ++++++---- 5 files changed, 231 insertions(+), 38 deletions(-) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 6d34fd37..309077e2 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -55,7 +55,12 @@ enum svs_data_type { SVS_DATA_TYPE_UINT4 = 4 }; -enum svs_storage_kind { SVS_STORAGE_KIND_SIMPLE = 0, SVS_STORAGE_KIND_LEANVEC = 1 }; +enum svs_storage_kind { + SVS_STORAGE_KIND_SIMPLE = 0, + SVS_STORAGE_KIND_LEANVEC = 1, + SVS_STORAGE_KIND_LVQ = 2, + SVS_STORAGE_KIND_SQ = 3 +}; enum svs_thread_pool_kind { SVS_THREAD_POOL_KIND_NATIVE = 0, @@ -103,8 +108,7 @@ SVS_API svs_algorithm_h svs_algorithm_create_vamana( SVS_API void svs_algorithm_free(svs_algorithm_h algorithm); SVS_API svs_search_params_h svs_search_params_create_vamana( - size_t search_window_size, - svs_error_h out_err /*=NULL*/ + size_t search_window_size, svs_error_h out_err /*=NULL*/ ); SVS_API void svs_search_params_free(svs_search_params_h params); @@ -116,6 +120,12 @@ SVS_API svs_storage_h svs_storage_create_leanvec( svs_data_type_t secondary, svs_error_h out_err /*=NULL*/ ); +SVS_API svs_storage_h svs_storage_create_lvq( + svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err /*=NULL*/ +); +SVS_API svs_storage_h svs_storage_create_sq( + svs_data_type_t data_type, svs_error_h out_err /*=NULL*/ +); SVS_API void svs_storage_free(svs_storage_h storage); SVS_API svs_index_builder_h svs_index_builder_create( diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index cadc6333..a8f1c99c 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -72,6 +72,20 @@ int main() { // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, // SVS_DATA_TYPE_UINT4, error); + // LVQ Storage + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error); + + // Scalar Quantized Storage + // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error); + + // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); + if (!storage) { fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error)); ret = 1; @@ -109,7 +123,9 @@ int main() { // Search params svs_search_params_h search_params = svs_search_params_create_vamana(100, error); if (!search_params) { - fprintf(stderr, "Failed to create search params: %s\n", svs_error_get_message(error)); + fprintf( + stderr, "Failed to create search params: %s\n", svs_error_get_message(error) + ); ret = 1; goto cleanup; } diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index f440d7ac..5c9b2955 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -60,19 +60,50 @@ svs::Vamana build_vamana_index_leanvec( ); } +template +svs::Vamana build_vamana_index_lvq( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + LVQDataBuilder builder, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance_type, std::move(pool) + ); +} + +template +svs::Vamana build_vamana_index_sq( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + SQDataBuilder builder, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance_type, std::move(pool) + ); +} + template void register_build_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher - .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_uncompressed); - dispatcher - .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_uncompressed); - - dispatcher - .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<4, 4>); - dispatcher - .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<4, 8>); - dispatcher - .register_target(svs::lib::dispatcher_build_docs, &build_vamana_index_leanvec<8, 8>); + dispatcher.register_target(&build_vamana_index_uncompressed); + dispatcher.register_target(&build_vamana_index_uncompressed); + + dispatcher.register_target(&build_vamana_index_leanvec<4, 4>); + dispatcher.register_target(&build_vamana_index_leanvec<4, 8>); + dispatcher.register_target(&build_vamana_index_leanvec<8, 8>); + + dispatcher.register_target(&build_vamana_index_lvq<4, 0>); + dispatcher.register_target(&build_vamana_index_lvq<8, 0>); + dispatcher.register_target(&build_vamana_index_lvq<4, 4>); + dispatcher.register_target(&build_vamana_index_lvq<4, 8>); + + dispatcher.register_target(&build_vamana_index_sq); + dispatcher.register_target(&build_vamana_index_sq); } using BuildIndexDispatcher = svs::lib::Dispatcher< svs::Vamana, @@ -129,9 +160,7 @@ struct IndexBuilder { } std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { - if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA && - (storage->kind == SVS_STORAGE_KIND_SIMPLE || - storage->kind == SVS_STORAGE_KIND_LEANVEC)) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { auto vamana_algorithm = std::static_pointer_cast(algorithm); svs::index::vamana::VamanaBuildParameters build_params = diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 32dfaa8f..29efc25f 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -21,11 +21,16 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include +#include namespace svs { namespace c_runtime { @@ -40,9 +45,15 @@ struct Storage { struct StorageSimple : public Storage { svs::DataType data_type; - StorageSimple(svs_data_type_t data_type) + StorageSimple(svs_data_type_t dt) : Storage{SVS_STORAGE_KIND_SIMPLE} - , data_type(to_data_type(data_type)) {} + , data_type(to_data_type(dt)) { + if (dt != SVS_DATA_TYPE_FLOAT32 && dt != SVS_DATA_TYPE_FLOAT16) { + throw std::invalid_argument( + "Simple storage only supports float32 and float16 data types" + ); + } + } }; struct StorageLeanVec : public Storage { @@ -64,8 +75,48 @@ struct StorageLeanVec : public Storage { case SVS_DATA_TYPE_INT8: case SVS_DATA_TYPE_UINT8: return 8; + case SVS_DATA_TYPE_VOID: + return 0; default: + throw std::invalid_argument("Unsupported data type for LeanVec storage"); + } + } +}; + +struct StorageLVQ : public Storage { + size_t primary_bits; + size_t residual_bits; + + StorageLVQ(svs_data_type_t primary, svs_data_type_t residual) + : Storage{SVS_STORAGE_KIND_LVQ} + , primary_bits(to_bits_number(primary)) + , residual_bits(to_bits_number(residual)) {} + + static size_t to_bits_number(svs_data_type_t data_type) { + switch (data_type) { + case SVS_DATA_TYPE_INT4: + case SVS_DATA_TYPE_UINT4: + return 4; + case SVS_DATA_TYPE_INT8: + case SVS_DATA_TYPE_UINT8: + return 8; + case SVS_DATA_TYPE_VOID: return 0; + default: + throw std::invalid_argument("Unsupported data type for LVQ storage"); + } + } +}; + +struct StorageSQ : public Storage { + svs::DataType data_type; + + StorageSQ(svs_data_type_t dt) + : Storage{SVS_STORAGE_KIND_SQ} + , data_type(to_data_type(dt)) { + if (dt != SVS_DATA_TYPE_UINT8 && dt != SVS_DATA_TYPE_INT8) { + throw std::invalid_argument("Scalar quantization only supports 8-bit data types" + ); } } }; @@ -116,9 +167,7 @@ template class LeanVecDataBuilder { public: LeanVecDataBuilder(size_t leanvec_dims) : leanvec_dims_(leanvec_dims) {} - svs::threads::ThreadPoolHandle default_threadpool() { - return svs::threads::ThreadPoolHandle(svs::threads::DefaultThreadPool(4)); - } + using LeanDatasetType = svs::leanvec::LeanDataset< svs::leanvec::UsingLVQ, svs::leanvec::UsingLVQ, @@ -156,4 +205,74 @@ struct lib::DispatchConverter class LVQDataBuilder { + public: + LVQDataBuilder() {} + + using LVQDatasetType = svs::quantization::lvq::LVQDataset< + PrimaryBits, + ResidualBits, + svs::Dynamic, + svs::quantization::lvq::Sequential, + svs::data::Blocked>>; + + template + LVQDatasetType + build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { + return LVQDatasetType::compress(view, pool, 0); + } +}; + +template +struct lib::DispatchConverter< + const c_runtime::Storage*, + LVQDataBuilder> { + using From = const svs::c_runtime::Storage*; + using To = LVQDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_LVQ) { + auto lvq = static_cast(from); + if (lvq->primary_bits == PrimaryBits && lvq->residual_bits == ResidualBits) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From from) { return To{}; } +}; + +template class SQDataBuilder { + public: + SQDataBuilder() {} + + using SQDatasetType = svs::quantization::scalar:: + SQDataset>>; + + template + SQDatasetType + build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { + return SQDatasetType::compress(view, pool); + } +}; + +template +struct lib::DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = SQDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_SQ) { + auto sq = static_cast(from); + if (sq->data_type == svs::datatype_v) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From from) { return To{}; } +}; + } // namespace svs diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 49df3c1c..e454fa2c 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -109,16 +109,13 @@ extern "C" svs_algorithm_h svs_algorithm_create_vamana( extern "C" void svs_algorithm_free(svs_algorithm_h algorithm) { delete algorithm; } -extern "C" svs_search_params_h svs_search_params_create_vamana( - size_t search_window_size, - svs_error_h out_err -) { +extern "C" svs_search_params_h +svs_search_params_create_vamana(size_t search_window_size, svs_error_h out_err) { return runtime_error_wrapper( [&]() { using namespace svs::c_runtime; - auto params = std::make_shared( - search_window_size - ); + auto params = + std::make_shared(search_window_size); auto result = new svs_search_params; result->impl = params; return result; @@ -127,9 +124,7 @@ extern "C" svs_search_params_h svs_search_params_create_vamana( ); } -extern "C" void svs_search_params_free(svs_search_params_h params) { - delete params; -} +extern "C" void svs_search_params_free(svs_search_params_h params) { delete params; } extern "C" svs_storage_h svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err) { @@ -164,6 +159,35 @@ extern "C" svs_storage_h svs_storage_create_leanvec( ); } +extern "C" svs_storage_h svs_storage_create_lvq( + svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err +) { + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto storage = std::make_shared(primary, residual); + auto result = new svs_storage; + result->impl = storage; + return result; + }, + out_err + ); +} + +extern "C" svs_storage_h +svs_storage_create_sq(svs_data_type_t data_type, svs_error_h out_err) { + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto storage = std::make_shared(data_type); + auto result = new svs_storage; + result->impl = storage; + return result; + }, + out_err + ); +} + extern "C" void svs_storage_free(svs_storage_h storage) { delete storage; } extern "C" svs_index_builder_h svs_index_builder_create( @@ -233,11 +257,6 @@ extern "C" svs_index_h svs_index_build( SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); return nullptr; } - if (builder->impl->storage->kind != SVS_STORAGE_KIND_SIMPLE && - builder->impl->storage->kind != SVS_STORAGE_KIND_LEANVEC) { - SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); - return nullptr; - } return runtime_error_wrapper( [&]() { From c5009bb03948e60146aa2cb7697f1fc38f2a23db Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 20 Jan 2026 16:32:06 +0100 Subject: [PATCH 10/18] Cleanup code in index build dispatching --- bindings/c/src/index_builder.hpp | 74 +++++++------------------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index 5c9b2955..10a98403 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -32,53 +32,11 @@ namespace svs::c_runtime { -template -svs::Vamana build_vamana_index_uncompressed( - const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - SimpleDataBuilder builder, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = builder.build(std::move(src_data), pool); - return svs::Vamana::build( - build_params, std::move(data), distance_type, std::move(pool) - ); -} - -template -svs::Vamana build_vamana_index_leanvec( - const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - LeanVecDataBuilder builder, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = builder.build(std::move(src_data), pool); - return svs::Vamana::build( - build_params, std::move(data), distance_type, std::move(pool) - ); -} - -template -svs::Vamana build_vamana_index_lvq( - const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - LVQDataBuilder builder, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = builder.build(std::move(src_data), pool); - return svs::Vamana::build( - build_params, std::move(data), distance_type, std::move(pool) - ); -} - -template -svs::Vamana build_vamana_index_sq( +template +svs::Vamana build_vamana_index( const svs::index::vamana::VamanaBuildParameters& build_params, svs::data::ConstSimpleDataView src_data, - SQDataBuilder builder, + DataBuilder builder, svs::DistanceType distance_type, svs::threads::ThreadPoolHandle pool ) { @@ -90,20 +48,20 @@ svs::Vamana build_vamana_index_sq( template void register_build_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher.register_target(&build_vamana_index_uncompressed); - dispatcher.register_target(&build_vamana_index_uncompressed); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index_leanvec<4, 4>); - dispatcher.register_target(&build_vamana_index_leanvec<4, 8>); - dispatcher.register_target(&build_vamana_index_leanvec<8, 8>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index_lvq<4, 0>); - dispatcher.register_target(&build_vamana_index_lvq<8, 0>); - dispatcher.register_target(&build_vamana_index_lvq<4, 4>); - dispatcher.register_target(&build_vamana_index_lvq<4, 8>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index_sq); - dispatcher.register_target(&build_vamana_index_sq); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); } using BuildIndexDispatcher = svs::lib::Dispatcher< svs::Vamana, @@ -119,7 +77,7 @@ BuildIndexDispatcher build_vamana_index_dispatcher() { return dispatcher; } -svs::Vamana build_vamana_index( +svs::Vamana dispatch_vamana_index_build( const svs::index::vamana::VamanaBuildParameters& build_params, svs::data::ConstSimpleDataView src_data, const Storage* storage, @@ -166,7 +124,7 @@ struct IndexBuilder { svs::index::vamana::VamanaBuildParameters build_params = vamana_algorithm->get_build_parameters(); - auto index = std::make_shared(build_vamana_index( + auto index = std::make_shared(dispatch_vamana_index_build( vamana_algorithm->get_build_parameters(), data, storage.get(), From a1547d818e2712e0067db63a1011378d2e01053a Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Wed, 21 Jan 2026 13:38:05 +0100 Subject: [PATCH 11/18] Add custom thread pool support --- bindings/c/CMakeLists.txt | 2 +- bindings/c/include/svs/c_api/svs_c.h | 162 ++++++++++++++++++++++++--- bindings/c/samples/simple.c | 25 +++++ bindings/c/src/index_builder.hpp | 6 +- bindings/c/src/svs_c.cpp | 26 ++++- bindings/c/src/thread_pool.hpp | 61 ---------- bindings/c/src/threadpool.hpp | 111 ++++++++++++++++++ 7 files changed, 308 insertions(+), 85 deletions(-) delete mode 100644 bindings/c/src/thread_pool.hpp create mode 100644 bindings/c/src/threadpool.hpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 993af01d..e03d9f44 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -26,7 +26,7 @@ set(SVS_C_API_SOURCES src/storage.hpp src/index.hpp src/index_builder.hpp - src/thread_pool.hpp + src/threadpool.hpp src/types_support.hpp src/svs_c.cpp ) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 309077e2..b5f6c43f 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -49,10 +49,10 @@ enum svs_data_type { SVS_DATA_TYPE_VOID = 0, SVS_DATA_TYPE_FLOAT32 = 32, SVS_DATA_TYPE_FLOAT16 = 16, - SVS_DATA_TYPE_INT8 = 9, - SVS_DATA_TYPE_UINT8 = 8, - SVS_DATA_TYPE_INT4 = 5, - SVS_DATA_TYPE_UINT4 = 4 + SVS_DATA_TYPE_INT8 = 8, + SVS_DATA_TYPE_UINT8 = SVS_DATA_TYPE_INT8 - 1, + SVS_DATA_TYPE_INT4 = 4, + SVS_DATA_TYPE_UINT4 = SVS_DATA_TYPE_INT4 - 1 }; enum svs_storage_kind { @@ -62,18 +62,36 @@ enum svs_storage_kind { SVS_STORAGE_KIND_SQ = 3 }; -enum svs_thread_pool_kind { - SVS_THREAD_POOL_KIND_NATIVE = 0, - SVS_THREAD_POOL_KIND_OMP = 1, - SVS_THREAD_POOL_KIND_SINGLE_THREAD = 2, - SVS_THREAD_POOL_KIND_MANUAL = 3 +enum svs_threadpool_kind { + SVS_THREADPOOL_KIND_NATIVE = 0, + SVS_THREADPOOL_KIND_OMP = 1, + SVS_THREADPOOL_KIND_SINGLE_THREAD = 2, + SVS_THREADPOOL_KIND_CUSTOM = 3 }; +// clang-format off +struct svs_threadpool_interface_ops { + size_t (*size)(void* self); + void (*parallel_for)( + void* self, + void (*func)(void* svs_param, size_t n), + void* svs_param, + size_t n + ); +}; +// clang-format on + +struct svs_threadpool_interface { + struct svs_threadpool_interface_ops ops; + void* self; +}; + +/// @brief Structure to hold search results struct svs_search_results { - size_t num_queries; - size_t* results_per_query; - size_t* indices; - float* distances; + size_t num_queries; /// Number of query vectors + size_t* results_per_query; /// Number of results per query + size_t* indices; /// Indices of the nearest neighbors + float* distances; /// Distances to the nearest neighbors }; // Handle typedefs; "_h" suffix indicates a handle to an opaque struct @@ -89,72 +107,181 @@ typedef enum svs_error_code svs_error_code_t; typedef enum svs_distance_metric svs_distance_metric_t; typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; -typedef enum svs_thread_pool_kind svs_thread_pool_kind_t; +typedef enum svs_threadpool_kind svs_threadpool_kind_t; +typedef struct svs_threadpool_interface* svs_threadpool_interface_t; typedef struct svs_search_results* svs_search_results_t; +/// @brief Create an error handle +/// @return A handle to the created error object SVS_API svs_error_h svs_error_init(); + +/// @brief Check if the error handle indicates success +/// @param err The error handle to check +/// @return true if no error occurred, false otherwise SVS_API bool svs_error_ok(svs_error_h err); + +/// @brief Get the error code from the error handle +/// @param err The error handle +/// @return The error code SVS_API svs_error_code_t svs_error_get_code(svs_error_h err); + +/// @brief Get the error message from the error handle +/// @param err The error handle +/// @return A string describing the error SVS_API const char* svs_error_get_message(svs_error_h err); + +/// @brief Free the error handle +/// @param err The error handle to free SVS_API void svs_error_free(svs_error_h err); +/// @brief Create a Vamana algorithm configuration +/// @param graph_degree The graph degree parameter +/// @param build_window_size The build window size parameter +/// @param search_window_size Default search window size parameter +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created Vamana algorithm SVS_API svs_algorithm_h svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the algorithm configuration handle +/// @param algorithm The algorithm handle to free SVS_API void svs_algorithm_free(svs_algorithm_h algorithm); +/// @brief Create Vamana search parameters +/// @param search_window_size The search window size parameter +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created Vamana search parameters SVS_API svs_search_params_h svs_search_params_create_vamana( size_t search_window_size, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the search parameters handle +/// @param params The search parameters handle to free SVS_API void svs_search_params_free(svs_search_params_h params); +/// @brief Create a simple storage configuration +/// @param data_type The data type of the vectors +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created simple storage SVS_API svs_storage_h svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err); + +/// @brief Create a LeanVec storage configuration +/// @param lenavec_dims The number of LeanVec dimensions +/// @param primary The data type of the primary quantization +/// @param secondary The data type of the secondary quantization +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created LeanVec storage SVS_API svs_storage_h svs_storage_create_leanvec( size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary, svs_error_h out_err /*=NULL*/ ); + +/// @brief Create an LVQ storage configuration +/// @param primary The data type of the primary quantization +/// @param residual The data type of the residual quantization +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created LVQ storage SVS_API svs_storage_h svs_storage_create_lvq( svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err /*=NULL*/ ); + +/// @brief Create a Scalar Quantization storage configuration +/// @param data_type The data type of the quantized vectors +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created Scalar Quantization storage SVS_API svs_storage_h svs_storage_create_sq( svs_data_type_t data_type, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the storage handle +/// @param storage The storage handle to free SVS_API void svs_storage_free(svs_storage_h storage); +/// @brief Create an index builder configuration +/// @param metric The distance metric to use +/// @param dimension The dimensionality of the vectors +/// @param algorithm The algorithm configuration to use +/// @param out_err An optional error handle to capture errors +/// @return A handle to the created index builder +/// @remarks Default storage configuration is equivalent to +/// svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32) SVS_API svs_index_builder_h svs_index_builder_create( svs_distance_metric_t metric, size_t dimension, svs_algorithm_h algorithm, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the index builder handle +/// @param builder The index builder handle to free SVS_API void svs_index_builder_free(svs_index_builder_h builder); +/// @brief Set the storage configuration for the index builder +/// @param builder The index builder handle +/// @param storage The storage configuration handle +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure SVS_API bool svs_index_builder_set_storage( svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err /*=NULL*/ ); -SVS_API bool svs_index_builder_set_thread_pool( +/// @brief Set the thread pool configuration for the index builder +/// @param builder The index builder handle +/// @param kind The kind of thread pool to use +/// @param num_threads The number of threads to use (if applicable) +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_builder_set_threadpool( svs_index_builder_h builder, - svs_thread_pool_kind_t kind, + svs_threadpool_kind_t kind, size_t num_threads, svs_error_h out_err /*=NULL*/ ); +/// @brief Set the custom thread pool for the index builder +/// @param builder The index builder handle +/// @param pool The custom thread pool interface +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_builder_set_threadpool_custom( + svs_index_builder_h builder, + svs_threadpool_interface_t pool, + svs_error_h out_err /*=NULL*/ +); + +/// @brief Build an index from the provided data +/// @param builder The index builder handle +/// @param data Pointer to the vector data (float array) +/// @param num_vectors The number of vectors in the data +/// @param out_err An optional error handle to capture errors +/// @return A handle to the built index SVS_API svs_index_h svs_index_build( svs_index_builder_h builder, const float* data, size_t num_vectors, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the index handle +/// @param index The index handle to free SVS_API void svs_index_free(svs_index_h index); +/// @brief Search the index with the provided queries +/// @param index The index handle +/// @param queries Pointer to the query data (float array) +/// @param num_queries The number of query vectors +/// @param k The number of nearest neighbors to retrieve per query +/// @param search_params The search parameters handle (can be NULL for defaults) +/// @param out_err An optional error handle to capture errors +/// @return A pointer to the search results structure SVS_API svs_search_results_t svs_index_search( svs_index_h index, const float* queries, @@ -163,6 +290,9 @@ SVS_API svs_search_results_t svs_index_search( svs_search_params_h search_params /*=NULL*/, svs_error_h out_err /*=NULL*/ ); + +/// @brief Free the search results structure +/// @param results The search results structure to release SVS_API void svs_search_results_free(svs_search_results_t results); #ifdef __cplusplus diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index a8f1c99c..46b09be4 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -14,6 +14,24 @@ void generate_random_data(float* data, size_t count, size_t dim) { } } +size_t sequential_tp_size(void* self) { return 1; } + +void sequential_tp_parallel_for( + void* self, void (*func)(void*, size_t), void* svs_param, size_t n +) { + for (size_t i = 0; i < n; ++i) { + func(svs_param, i); + } +} + +static struct svs_threadpool_interface sequential_threadpool = { + { + &sequential_tp_size, + &sequential_tp_parallel_for, + }, + NULL, +}; + int main() { int ret = 0; srand(time(NULL)); @@ -110,6 +128,13 @@ int main() { goto cleanup; } + // Set custom sequential threadpool + if (!svs_index_builder_set_threadpool_custom(builder, &sequential_threadpool, error)) { + fprintf(stderr, "Failed to set threadpool: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + // Build index printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION); index = svs_index_build(builder, data, NUM_VECTORS, error); diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index 10a98403..06f7e62b 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -20,7 +20,7 @@ #include "algorithm.hpp" #include "index.hpp" #include "storage.hpp" -#include "thread_pool.hpp" +#include "threadpool.hpp" #include "types_support.hpp" #include @@ -113,8 +113,8 @@ struct IndexBuilder { this->storage = std::move(storage); } - void set_thread_pool(ThreadPoolBuilder thread_pool_builder) { - std::swap(this->pool_builder, thread_pool_builder); + void set_threadpool_builder(ThreadPoolBuilder threadpool_builder) { + std::swap(this->pool_builder, threadpool_builder); } std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index e454fa2c..7f482ac5 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -20,7 +20,7 @@ #include "index.hpp" #include "index_builder.hpp" #include "storage.hpp" -#include "thread_pool.hpp" +#include "threadpool.hpp" #include "types_support.hpp" #include @@ -227,9 +227,9 @@ extern "C" bool svs_index_builder_set_storage( ); } -extern "C" bool svs_index_builder_set_thread_pool( +extern "C" bool svs_index_builder_set_threadpool( svs_index_builder_h builder, - svs_thread_pool_kind_t kind, + svs_threadpool_kind_t kind, size_t num_threads, svs_error_h out_err ) { @@ -239,7 +239,25 @@ extern "C" bool svs_index_builder_set_thread_pool( } return runtime_error_wrapper( [&]() { - builder->impl->set_thread_pool({kind, num_threads}); + builder->impl->set_threadpool_builder({kind, num_threads}); + return true; + }, + out_err + ); +} + +extern "C" bool svs_index_builder_set_threadpool_custom( + svs_index_builder_h builder, + svs_threadpool_interface_t pool, + svs_error_h out_err /*=NULL*/ +) { + if (builder == nullptr) { + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); + return false; + } + return runtime_error_wrapper( + [&]() { + builder->impl->set_threadpool_builder({pool}); return true; }, out_err diff --git a/bindings/c/src/thread_pool.hpp b/bindings/c/src/thread_pool.hpp deleted file mode 100644 index 2d651ae2..00000000 --- a/bindings/c/src/thread_pool.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2026 Intel Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include "svs/c_api/svs_c.h" - -#include "types_support.hpp" - -#include - -#include -#include - -namespace svs::c_runtime { - -struct ThreadPoolBuilder { - svs_thread_pool_kind kind; - size_t num_threads; - ThreadPoolBuilder(svs_thread_pool_kind kind, size_t num_threads) - : kind(kind) - , num_threads(num_threads) {} - - ThreadPoolBuilder() - : ThreadPoolBuilder(SVS_THREAD_POOL_KIND_NATIVE, default_threads_num()) {} - - static size_t default_threads_num() { - return std::max(size_t{1}, size_t{std::thread::hardware_concurrency()}); - } - - svs::threads::ThreadPoolHandle build() const { - using namespace svs::threads; - switch (kind) { - case SVS_THREAD_POOL_KIND_NATIVE: - return ThreadPoolHandle(NativeThreadPool(num_threads)); - case SVS_THREAD_POOL_KIND_OMP: - return ThreadPoolHandle(OMPThreadPool(num_threads)); - case SVS_THREAD_POOL_KIND_SINGLE_THREAD: - return ThreadPoolHandle(SequentialThreadPool()); - case SVS_THREAD_POOL_KIND_MANUAL: - throw std::invalid_argument( - "SVS_THREAD_POOL_KIND_MANUAL cannot be built automatically." - ); - default: - throw std::invalid_argument("Unknown svs_thread_pool_kind value."); - } - } -}; -} // namespace svs::c_runtime diff --git a/bindings/c/src/threadpool.hpp b/bindings/c/src/threadpool.hpp new file mode 100644 index 00000000..110dac5b --- /dev/null +++ b/bindings/c/src/threadpool.hpp @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "types_support.hpp" + +#include + +#include +#include + +namespace svs::c_runtime { + +class ThreadPoolBuilder { + struct CustomThreadPool { + static svs_threadpool_interface_t validate(svs_threadpool_interface_t impl) { + if (impl == nullptr) { + throw std::invalid_argument("Custom threadpool pointer cannot be null."); + } + if (impl->ops.size == nullptr || impl->ops.parallel_for == nullptr) { + throw std::invalid_argument( + "Custom threadpool interface has null function pointers." + ); + } + return impl; + } + + CustomThreadPool(svs_threadpool_interface_t impl) + : impl{validate(impl)} {} + + size_t size() const { + assert(impl != nullptr); + return impl->ops.size(impl->self); + } + + void parallel_for(std::function f, size_t n) const { + assert(impl != nullptr); + impl->ops.parallel_for( + impl->self, + [](void* svs_param, size_t i) { + auto& func = *static_cast*>(svs_param); + func(i); + }, + &f, + n + ); + } + + svs_threadpool_interface_t impl; + }; + + svs_threadpool_kind kind; + size_t num_threads; + svs_threadpool_interface_t user_threadpool; + + public: + ThreadPoolBuilder() + : ThreadPoolBuilder(SVS_THREADPOOL_KIND_NATIVE, default_threads_num()) {} + + ThreadPoolBuilder(svs_threadpool_kind kind, size_t num_threads) + : kind(kind) + , num_threads(num_threads) { + if (kind == SVS_THREADPOOL_KIND_CUSTOM) { + throw std::invalid_argument( + "SVS_THREADPOOL_KIND_CUSTOM cannot be built automatically." + ); + } + } + + ThreadPoolBuilder(svs_threadpool_interface_t pool) + : kind(SVS_THREADPOOL_KIND_CUSTOM) + , num_threads(0) + , user_threadpool(CustomThreadPool::validate(pool)) {} + + static size_t default_threads_num() { + return std::max(size_t{1}, size_t{std::thread::hardware_concurrency()}); + } + + svs::threads::ThreadPoolHandle build() const { + using namespace svs::threads; + switch (kind) { + case SVS_THREADPOOL_KIND_NATIVE: + return ThreadPoolHandle(NativeThreadPool(num_threads)); + case SVS_THREADPOOL_KIND_OMP: + return ThreadPoolHandle(OMPThreadPool(num_threads)); + case SVS_THREADPOOL_KIND_SINGLE_THREAD: + return ThreadPoolHandle(SequentialThreadPool()); + case SVS_THREADPOOL_KIND_CUSTOM: + assert(user_threadpool != nullptr); + return ThreadPoolHandle(CustomThreadPool{this->user_threadpool}); + default: + throw std::invalid_argument("Unknown svs_threadpool_kind value."); + } + } +}; +} // namespace svs::c_runtime From 9c4cd43ca92278a1577bc2c040e409b1df332454 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Wed, 21 Jan 2026 16:58:00 +0100 Subject: [PATCH 12/18] Add some vamana parameters getters/setters --- bindings/c/include/svs/c_api/svs_c.h | 74 +++++++++ bindings/c/src/algorithm.hpp | 26 ++-- bindings/c/src/index_builder.hpp | 5 +- bindings/c/src/svs_c.cpp | 214 +++++++++++++++++++++++++++ 4 files changed, 305 insertions(+), 14 deletions(-) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index b5f6c43f..91c378f7 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -152,6 +152,80 @@ SVS_API svs_algorithm_h svs_algorithm_create_vamana( /// @param algorithm The algorithm handle to free SVS_API void svs_algorithm_free(svs_algorithm_h algorithm); +/// @brief Get the alpha parameter from a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param out_alpha Pointer to store the retrieved alpha parameter +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_get_alpha( + svs_algorithm_h algorithm, float* out_alpha, svs_error_h out_err /*=NULL*/ +); + +/// @brief Set the alpha parameter in a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param alpha The alpha parameter to set +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_set_alpha( + svs_algorithm_h algorithm, float alpha, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get the graph degree parameter from a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param out_graph_degree Pointer to store the retrieved graph degree parameter +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_get_graph_degree( + svs_algorithm_h algorithm, size_t* out_graph_degree, svs_error_h out_err /*=NULL*/ +); + +/// @brief Set the graph degree parameter in a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param graph_degree The graph degree parameter to set +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_set_graph_degree( + svs_algorithm_h algorithm, size_t graph_degree, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get the build window size parameter from a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param out_build_window_size Pointer to store the retrieved build window size parameter +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_get_build_window_size( + svs_algorithm_h algorithm, size_t* out_build_window_size, svs_error_h out_err /*=NULL*/ +); + +/// @brief Set the build window size parameter in a Vamana algorithm configuration +/// @param algorithm The algorithm handle +/// @param build_window_size The build window size parameter to set +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_set_build_window_size( + svs_algorithm_h algorithm, size_t build_window_size, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get whether to use full search history in the Vamana algorithm +/// @param algorithm The algorithm handle +/// @param out_use_full_search_history Pointer to store whether full search history is used +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_get_use_search_history( + svs_algorithm_h algorithm, + bool* out_use_full_search_history, + svs_error_h out_err /*=NULL*/ +); + +/// @brief Set whether to use full search history in the Vamana algorithm +/// @param algorithm The algorithm handle +/// @param use_full_search_history Whether to use full search history +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_algorithm_vamana_set_use_search_history( + svs_algorithm_h algorithm, bool use_full_search_history, svs_error_h out_err /*=NULL*/ +); + /// @brief Create Vamana search parameters /// @param search_window_size The search window size parameter /// @param out_err An optional error handle to capture errors diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp index 65099ecf..83694b24 100644 --- a/bindings/c/src/algorithm.hpp +++ b/bindings/c/src/algorithm.hpp @@ -40,6 +40,7 @@ struct Algorithm { virtual ~Algorithm() = default; virtual std::shared_ptr get_default_search_params() const = 0; + virtual void set_default_search_params(const std::shared_ptr& params) = 0; }; struct AlgorithmVamana : public Algorithm { @@ -57,28 +58,33 @@ struct AlgorithmVamana : public Algorithm { } }; - size_t graph_degree; - size_t build_window_size; + svs::index::vamana::VamanaBuildParameters build_params; SearchParams default_search_params; AlgorithmVamana( size_t graph_degree, size_t build_window_size, size_t search_window_size ) : Algorithm{SVS_ALGORITHM_TYPE_VAMANA} - , graph_degree(graph_degree) - , build_window_size(build_window_size) - , default_search_params(search_window_size) {} + , build_params() + , default_search_params(search_window_size) { + build_params.graph_max_degree = graph_degree; + build_params.window_size = build_window_size; + } + + svs::index::vamana::VamanaBuildParameters& build_parameters() { return build_params; } - svs::index::vamana::VamanaBuildParameters get_build_parameters() const { - svs::index::vamana::VamanaBuildParameters params; - params.graph_max_degree = graph_degree; - params.window_size = build_window_size; - return params; + const svs::index::vamana::VamanaBuildParameters& build_parameters() const { + return build_params; } std::shared_ptr get_default_search_params() const override { return std::make_shared(default_search_params); } + + void set_default_search_params(const std::shared_ptr& params + ) override { + default_search_params = *std::static_pointer_cast(params); + } }; } // namespace svs::c_runtime \ No newline at end of file diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index 06f7e62b..a615a246 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -121,11 +121,8 @@ struct IndexBuilder { if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { auto vamana_algorithm = std::static_pointer_cast(algorithm); - svs::index::vamana::VamanaBuildParameters build_params = - vamana_algorithm->get_build_parameters(); - auto index = std::make_shared(dispatch_vamana_index_build( - vamana_algorithm->get_build_parameters(), + vamana_algorithm->build_parameters(), data, storage.get(), to_distance_type(distance_metric), diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 7f482ac5..239937f7 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -41,6 +41,38 @@ struct svs_error_desc { } \ } while (0) +#define EXPECT_NOT_NULL(arg, out_err) \ + do { \ + if ((arg) == nullptr) { \ + SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, #arg " should not be NULL"); \ + return false; \ + } \ + } while (0) + +#define EXPECT_EQUAL(actual, expected, out_err, msg) \ + do { \ + if ((actual) != (expected)) { \ + SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, msg); \ + return false; \ + } \ + } while (0) + +#define EXPECT_NEQUAL(actual, unexpected, out_err, msg) \ + do { \ + if ((actual) == (unexpected)) { \ + SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, msg); \ + return false; \ + } \ + } while (0) + +#define NOT_IMPLEMENTED_IF(cond, out_err, msg) \ + do { \ + if (cond) { \ + SET_ERROR((out_err), SVS_ERROR_NOT_IMPLEMENTED, msg); \ + return false; \ + } \ + } while (0) + template > inline Result runtime_error_wrapper(Callable&& func, svs_error_h err, Result err_res = {}) noexcept { @@ -109,6 +141,188 @@ extern "C" svs_algorithm_h svs_algorithm_create_vamana( extern "C" void svs_algorithm_free(svs_algorithm_h algorithm) { delete algorithm; } +extern "C" bool svs_algorithm_vamana_get_alpha( + svs_algorithm_h algorithm, float* out_alpha, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_NOT_NULL(out_alpha, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + *out_alpha = vamana_algorithm->build_parameters().alpha; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_set_alpha( + svs_algorithm_h algorithm, float alpha, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + vamana_algorithm->build_parameters().alpha = alpha; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_get_graph_degree( + svs_algorithm_h algorithm, size_t* out_graph_degree, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_NOT_NULL(out_graph_degree, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + *out_graph_degree = vamana_algorithm->build_parameters().graph_max_degree; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_set_graph_degree( + svs_algorithm_h algorithm, size_t graph_degree, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + vamana_algorithm->build_parameters().graph_max_degree = graph_degree; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_get_build_window_size( + svs_algorithm_h algorithm, size_t* out_build_window_size, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_NOT_NULL(out_build_window_size, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + *out_build_window_size = vamana_algorithm->build_parameters().window_size; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_set_build_window_size( + svs_algorithm_h algorithm, size_t build_window_size, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + vamana_algorithm->build_parameters().window_size = build_window_size; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_get_use_search_history( + svs_algorithm_h algorithm, bool* out_use_full_search_history, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_NOT_NULL(out_use_full_search_history, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + *out_use_full_search_history = + vamana_algorithm->build_parameters().use_full_search_history; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_set_use_search_history( + svs_algorithm_h algorithm, bool use_full_search_history, svs_error_h out_err +) { + EXPECT_NOT_NULL(algorithm, out_err); + EXPECT_EQUAL( + algorithm->impl->type, + SVS_ALGORITHM_TYPE_VAMANA, + out_err, + "Algorithm type does not support this operation" + ); + return runtime_error_wrapper( + [&]() { + using namespace svs::c_runtime; + auto vamana_algorithm = + std::static_pointer_cast(algorithm->impl); + vamana_algorithm->build_parameters().use_full_search_history = + use_full_search_history; + return true; + }, + out_err + ); +} + extern "C" svs_search_params_h svs_search_params_create_vamana(size_t search_window_size, svs_error_h out_err) { return runtime_error_wrapper( From 4e6181af7e910d0c0605c19522f710075cbdb4b5 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 22 Jan 2026 12:33:12 +0100 Subject: [PATCH 13/18] Extend/improve error handling --- bindings/c/CMakeLists.txt | 5 +- bindings/c/include/svs/c_api/svs_c.h | 9 +- bindings/c/src/error.cpp | 28 +++ bindings/c/src/error.hpp | 124 +++++++++++ bindings/c/src/svs_c.cpp | 322 +++++++++++---------------- 5 files changed, 295 insertions(+), 193 deletions(-) create mode 100644 bindings/c/src/error.cpp create mode 100644 bindings/c/src/error.hpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index e03d9f44..c4866398 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -23,11 +23,14 @@ set(SVS_C_API_HEADERS set(SVS_C_API_SOURCES src/algorithm.hpp - src/storage.hpp + src/error.hpp src/index.hpp src/index_builder.hpp + src/storage.hpp src/threadpool.hpp src/types_support.hpp + + src/error.cpp src/svs_c.cpp ) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 91c378f7..928305fb 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -30,7 +30,10 @@ enum svs_error_code { SVS_ERROR_INVALID_ARGUMENT = 2, SVS_ERROR_OUT_OF_MEMORY = 3, SVS_ERROR_INDEX_BUILD_FAILED = 4, - SVS_ERROR_NOT_IMPLEMENTED = 5 + SVS_ERROR_NOT_IMPLEMENTED = 5, + SVS_ERROR_UNSUPPORTED_HW = 6, + SVS_ERROR_RUNTIME = 7, + SVS_ERROR_UNKNOWN = 1000 }; enum svs_distance_metric { @@ -246,13 +249,13 @@ SVS_API svs_storage_h svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err); /// @brief Create a LeanVec storage configuration -/// @param lenavec_dims The number of LeanVec dimensions +/// @param leanvec_dims The number of LeanVec dimensions /// @param primary The data type of the primary quantization /// @param secondary The data type of the secondary quantization /// @param out_err An optional error handle to capture errors /// @return A handle to the created LeanVec storage SVS_API svs_storage_h svs_storage_create_leanvec( - size_t lenavec_dims, + size_t leanvec_dims, svs_data_type_t primary, svs_data_type_t secondary, svs_error_h out_err /*=NULL*/ diff --git a/bindings/c/src/error.cpp b/bindings/c/src/error.cpp new file mode 100644 index 00000000..df2e9e8f --- /dev/null +++ b/bindings/c/src/error.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "svs/c_api/svs_c.h" + +#include "error.hpp" + +#include + +extern "C" svs_error_h svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } +extern "C" bool svs_error_ok(svs_error_h err) { return err->code == SVS_OK; } +extern "C" svs_error_code_t svs_error_get_code(svs_error_h err) { return err->code; } +extern "C" const char* svs_error_get_message(svs_error_h err) { + return err->message.c_str(); +} +extern "C" void svs_error_free(svs_error_h err) { delete err; } diff --git a/bindings/c/src/error.hpp b/bindings/c/src/error.hpp new file mode 100644 index 00000000..48f40f80 --- /dev/null +++ b/bindings/c/src/error.hpp @@ -0,0 +1,124 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include +#include + +#include + +// C API error structure +struct svs_error_desc { + svs_error_code_t code; + std::string message; +}; + +#define SET_ERROR(err, c, msg) \ + do { \ + if (err) { \ + (err)->code = (c); \ + (err)->message = (msg); \ + } \ + } while (0) + +#define NOT_IMPLEMENTED_IF(cond, msg) \ + do { \ + if (cond) { \ + throw svs::c_runtime::not_implemented(msg); \ + } \ + } while (0) + +#define INVALID_ARGUMENT_IF(cond, msg) \ + do { \ + if (cond) { \ + throw std::invalid_argument(msg); \ + } \ + } while (0) + +#define EXPECT_ARG_IN_RANGE(arg, min_val, max_val) \ + INVALID_ARGUMENT_IF( \ + (arg) < (min_val) || (arg) > (max_val), \ + #arg " should be in range [" #min_val ", " #max_val "]" \ + ) + +#define EXPECT_ARG_GT_THAN(arg, threshold) \ + INVALID_ARGUMENT_IF((arg) <= (threshold), #arg " should be greater than " #threshold) + +#define EXPECT_ARG_GE_THAN(arg, threshold) \ + INVALID_ARGUMENT_IF( \ + (arg) < (threshold), #arg " should be greater than or equal to " #threshold \ + ) + +#define EXPECT_ARG_NOT_NULL(arg) \ + INVALID_ARGUMENT_IF((arg) == nullptr, #arg " should not be NULL") + +#define EXPECT_ARG_IS_NULL(arg) \ + INVALID_ARGUMENT_IF((arg) != nullptr, #arg " should be NULL") + +#define EXPECT_ARG_EQ_TO(actual, expected) \ + INVALID_ARGUMENT_IF( \ + (actual) != (expected), "Expected " #actual " to be equal to " #expected \ + ) + +#define EXPECT_ARG_NE_TO(actual, expected) \ + INVALID_ARGUMENT_IF( \ + (actual) == (expected), "Expected " #actual " to be not equal to " #expected \ + ) + +namespace svs::c_runtime { + +class not_implemented : public std::logic_error { + public: + using std::logic_error::logic_error; +}; + +class unsupported_hw : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +// A helper to wrap C++ exceptions and convert them to C error codes/messages. +template > +Result wrap_exceptions(Callable&& func, svs_error_h err, Result err_res = {}) noexcept { + try { + SET_ERROR(err, SVS_OK, "Success"); + return func(); + } catch (const std::invalid_argument& ex) { + SET_ERROR(err, SVS_ERROR_INVALID_ARGUMENT, ex.what()); + return err_res; + } catch (const svs::c_runtime::not_implemented& ex) { + SET_ERROR(err, SVS_ERROR_NOT_IMPLEMENTED, ex.what()); + return err_res; + } catch (const svs::c_runtime::unsupported_hw& ex) { + SET_ERROR(err, SVS_ERROR_UNSUPPORTED_HW, ex.what()); + return err_res; + } catch (const svs::lib::ANNException& ex) { + SET_ERROR(err, SVS_ERROR_GENERIC, ex.what()); + return err_res; + } catch (const std::runtime_error& ex) { + SET_ERROR(err, SVS_ERROR_RUNTIME, ex.what()); + return err_res; + } catch (const std::exception& ex) { + SET_ERROR(err, SVS_ERROR_UNKNOWN, ex.what()); + return err_res; + } catch (...) { + SET_ERROR(err, SVS_ERROR_UNKNOWN, "An unknown error has occurred."); + return err_res; + } +} +} // namespace svs::c_runtime diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 239937f7..93c4b2cb 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -17,6 +17,7 @@ #include "svs/c_api/svs_c.h" #include "algorithm.hpp" +#include "error.hpp" #include "index.hpp" #include "index_builder.hpp" #include "storage.hpp" @@ -28,69 +29,6 @@ #include // C API implementation -struct svs_error_desc { - svs_error_code_t code; - std::string message; -}; - -#define SET_ERROR(err, c, msg) \ - do { \ - if (err) { \ - (err)->code = (c); \ - (err)->message = (msg); \ - } \ - } while (0) - -#define EXPECT_NOT_NULL(arg, out_err) \ - do { \ - if ((arg) == nullptr) { \ - SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, #arg " should not be NULL"); \ - return false; \ - } \ - } while (0) - -#define EXPECT_EQUAL(actual, expected, out_err, msg) \ - do { \ - if ((actual) != (expected)) { \ - SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, msg); \ - return false; \ - } \ - } while (0) - -#define EXPECT_NEQUAL(actual, unexpected, out_err, msg) \ - do { \ - if ((actual) == (unexpected)) { \ - SET_ERROR((out_err), SVS_ERROR_INVALID_ARGUMENT, msg); \ - return false; \ - } \ - } while (0) - -#define NOT_IMPLEMENTED_IF(cond, out_err, msg) \ - do { \ - if (cond) { \ - SET_ERROR((out_err), SVS_ERROR_NOT_IMPLEMENTED, msg); \ - return false; \ - } \ - } while (0) - -template > -inline Result -runtime_error_wrapper(Callable&& func, svs_error_h err, Result err_res = {}) noexcept { - try { - SET_ERROR(err, SVS_OK, "Success"); - return func(); - } catch (const std::invalid_argument& ex) { - SET_ERROR(err, SVS_ERROR_INVALID_ARGUMENT, ex.what()); - return err_res; - } catch (const std::exception& ex) { - SET_ERROR(err, SVS_ERROR_GENERIC, ex.what()); - return err_res; - } catch (...) { - SET_ERROR(err, SVS_ERROR_GENERIC, "An unknown error has occurred."); - return err_res; - } -} - struct svs_index { std::shared_ptr impl; }; @@ -111,23 +49,18 @@ struct svs_storage { std::shared_ptr impl; }; -extern "C" svs_error_h svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } -extern "C" bool svs_error_ok(svs_error_h err) { return err->code == SVS_OK; } -extern "C" svs_error_code_t svs_error_get_code(svs_error_h err) { return err->code; } -extern "C" const char* svs_error_get_message(svs_error_h err) { - return err->message.c_str(); -} -extern "C" void svs_error_free(svs_error_h err) { delete err; } - extern "C" svs_algorithm_h svs_algorithm_create_vamana( size_t graph_degree, size_t build_window_size, size_t search_window_size, svs_error_h out_err ) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_ARG_GT_THAN(graph_degree, 0); + EXPECT_ARG_GT_THAN(build_window_size, 0); + EXPECT_ARG_GT_THAN(search_window_size, 0); auto algorithm = std::make_shared( graph_degree, build_window_size, search_window_size ); @@ -141,20 +74,21 @@ extern "C" svs_algorithm_h svs_algorithm_create_vamana( extern "C" void svs_algorithm_free(svs_algorithm_h algorithm) { delete algorithm; } +#define EXPECT_VAMANA(algorithm) \ + EXPECT_ARG_NOT_NULL(algorithm); \ + INVALID_ARGUMENT_IF( \ + (algorithm->impl->type != SVS_ALGORITHM_TYPE_VAMANA), \ + "Algorithm type does not support this operation" \ + ) + extern "C" bool svs_algorithm_vamana_get_alpha( svs_algorithm_h algorithm, float* out_alpha, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_NOT_NULL(out_alpha, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_NOT_NULL(out_alpha); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); *out_alpha = vamana_algorithm->build_parameters().alpha; @@ -167,16 +101,11 @@ extern "C" bool svs_algorithm_vamana_get_alpha( extern "C" bool svs_algorithm_vamana_set_alpha( svs_algorithm_h algorithm, float alpha, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_GT_THAN(alpha, 0.0f); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); vamana_algorithm->build_parameters().alpha = alpha; @@ -189,17 +118,11 @@ extern "C" bool svs_algorithm_vamana_set_alpha( extern "C" bool svs_algorithm_vamana_get_graph_degree( svs_algorithm_h algorithm, size_t* out_graph_degree, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_NOT_NULL(out_graph_degree, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_NOT_NULL(out_graph_degree); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); *out_graph_degree = vamana_algorithm->build_parameters().graph_max_degree; @@ -212,16 +135,11 @@ extern "C" bool svs_algorithm_vamana_get_graph_degree( extern "C" bool svs_algorithm_vamana_set_graph_degree( svs_algorithm_h algorithm, size_t graph_degree, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_GT_THAN(graph_degree, 0); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); vamana_algorithm->build_parameters().graph_max_degree = graph_degree; @@ -234,17 +152,11 @@ extern "C" bool svs_algorithm_vamana_set_graph_degree( extern "C" bool svs_algorithm_vamana_get_build_window_size( svs_algorithm_h algorithm, size_t* out_build_window_size, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_NOT_NULL(out_build_window_size, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_NOT_NULL(out_build_window_size); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); *out_build_window_size = vamana_algorithm->build_parameters().window_size; @@ -257,16 +169,11 @@ extern "C" bool svs_algorithm_vamana_get_build_window_size( extern "C" bool svs_algorithm_vamana_set_build_window_size( svs_algorithm_h algorithm, size_t build_window_size, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_GT_THAN(build_window_size, 0); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); vamana_algorithm->build_parameters().window_size = build_window_size; @@ -279,17 +186,11 @@ extern "C" bool svs_algorithm_vamana_set_build_window_size( extern "C" bool svs_algorithm_vamana_get_use_search_history( svs_algorithm_h algorithm, bool* out_use_full_search_history, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_NOT_NULL(out_use_full_search_history, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); + EXPECT_ARG_NOT_NULL(out_use_full_search_history); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); *out_use_full_search_history = @@ -303,16 +204,10 @@ extern "C" bool svs_algorithm_vamana_get_use_search_history( extern "C" bool svs_algorithm_vamana_set_use_search_history( svs_algorithm_h algorithm, bool use_full_search_history, svs_error_h out_err ) { - EXPECT_NOT_NULL(algorithm, out_err); - EXPECT_EQUAL( - algorithm->impl->type, - SVS_ALGORITHM_TYPE_VAMANA, - out_err, - "Algorithm type does not support this operation" - ); - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_VAMANA(algorithm); auto vamana_algorithm = std::static_pointer_cast(algorithm->impl); vamana_algorithm->build_parameters().use_full_search_history = @@ -325,9 +220,10 @@ extern "C" bool svs_algorithm_vamana_set_use_search_history( extern "C" svs_search_params_h svs_search_params_create_vamana(size_t search_window_size, svs_error_h out_err) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_ARG_GT_THAN(search_window_size, 0); auto params = std::make_shared(search_window_size); auto result = new svs_search_params; @@ -342,9 +238,13 @@ extern "C" void svs_search_params_free(svs_search_params_h params) { delete para extern "C" svs_storage_h svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + INVALID_ARGUMENT_IF( + data_type != SVS_DATA_TYPE_FLOAT32 && data_type != SVS_DATA_TYPE_FLOAT16, + "Simple storage only supports float32 and float16 data types" + ); auto storage = std::make_shared(data_type); auto result = new svs_storage; result->impl = storage; @@ -355,16 +255,33 @@ svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err) { } extern "C" svs_storage_h svs_storage_create_leanvec( - size_t lenavec_dims, + size_t leanvec_dims, svs_data_type_t primary, svs_data_type_t secondary, svs_error_h out_err ) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_ARG_GT_THAN(leanvec_dims, 0); + NOT_IMPLEMENTED_IF( + (primary == SVS_DATA_TYPE_FLOAT32 || primary == SVS_DATA_TYPE_FLOAT16 || + secondary == SVS_DATA_TYPE_FLOAT32 || secondary == SVS_DATA_TYPE_FLOAT16), + "Unsupported simple data types for LeanVec primary and secondary" + ); + INVALID_ARGUMENT_IF( + (primary != SVS_DATA_TYPE_INT4 && primary != SVS_DATA_TYPE_UINT4 && + primary != SVS_DATA_TYPE_INT8 && primary != SVS_DATA_TYPE_UINT8), + "Unsupported data type for LeanVec primary storage" + ); + INVALID_ARGUMENT_IF( + (secondary != SVS_DATA_TYPE_INT4 && secondary != SVS_DATA_TYPE_UINT4 && + secondary != SVS_DATA_TYPE_INT8 && secondary != SVS_DATA_TYPE_UINT8), + "Unsupported data type for LeanVec secondary storage" + ); + auto storage = - std::make_shared(lenavec_dims, primary, secondary); + std::make_shared(leanvec_dims, primary, secondary); auto result = new svs_storage; result->impl = storage; return result; @@ -376,9 +293,20 @@ extern "C" svs_storage_h svs_storage_create_leanvec( extern "C" svs_storage_h svs_storage_create_lvq( svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err ) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + INVALID_ARGUMENT_IF( + (primary != SVS_DATA_TYPE_INT4 && primary != SVS_DATA_TYPE_UINT4 && + primary != SVS_DATA_TYPE_INT8 && primary != SVS_DATA_TYPE_UINT8), + "Unsupported data type for LeanVec primary storage" + ); + INVALID_ARGUMENT_IF( + (residual != SVS_DATA_TYPE_INT4 && residual != SVS_DATA_TYPE_UINT4 && + residual != SVS_DATA_TYPE_INT8 && residual != SVS_DATA_TYPE_UINT8 && + residual != SVS_DATA_TYPE_VOID), + "Unsupported data type for LeanVec secondary storage" + ); auto storage = std::make_shared(primary, residual); auto result = new svs_storage; result->impl = storage; @@ -390,9 +318,13 @@ extern "C" svs_storage_h svs_storage_create_lvq( extern "C" svs_storage_h svs_storage_create_sq(svs_data_type_t data_type, svs_error_h out_err) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + INVALID_ARGUMENT_IF( + data_type != SVS_DATA_TYPE_UINT8 && data_type != SVS_DATA_TYPE_INT8, + "Scalar quantization only supports 8-bit data types" + ); auto storage = std::make_shared(data_type); auto result = new svs_storage; result->impl = storage; @@ -410,9 +342,17 @@ extern "C" svs_index_builder_h svs_index_builder_create( svs_algorithm_h algorithm, svs_error_h out_err ) { - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + INVALID_ARGUMENT_IF( + metric != SVS_DISTANCE_METRIC_EUCLIDEAN && + metric != SVS_DISTANCE_METRIC_COSINE && + metric != SVS_DISTANCE_METRIC_DOT_PRODUCT, + "Unsupported distance metric" + ); + EXPECT_ARG_GT_THAN(dimension, 0); + EXPECT_ARG_NOT_NULL(algorithm); auto builder = std::make_shared(metric, dimension, algorithm->impl); auto result = new svs_index_builder; @@ -428,12 +368,11 @@ extern "C" void svs_index_builder_free(svs_index_builder_h builder) { delete bui extern "C" bool svs_index_builder_set_storage( svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err ) { - if (builder == nullptr || storage == nullptr) { - SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); - return false; - } - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_NOT_NULL(storage); builder->impl->set_storage(storage->impl); return true; }, @@ -451,8 +390,11 @@ extern "C" bool svs_index_builder_set_threadpool( SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); return false; } - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_GT_THAN(num_threads, 0); builder->impl->set_threadpool_builder({kind, num_threads}); return true; }, @@ -465,12 +407,11 @@ extern "C" bool svs_index_builder_set_threadpool_custom( svs_threadpool_interface_t pool, svs_error_h out_err /*=NULL*/ ) { - if (builder == nullptr) { - SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); - return false; - } - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_NOT_NULL(pool); builder->impl->set_threadpool_builder({pool}); return true; }, @@ -481,18 +422,16 @@ extern "C" bool svs_index_builder_set_threadpool_custom( extern "C" svs_index_h svs_index_build( svs_index_builder_h builder, const float* data, size_t num_vectors, svs_error_h out_err ) { - if (builder == nullptr || num_vectors == 0 || data == nullptr) { - SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); - return nullptr; - } - if (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA) { - SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); - return nullptr; - } - - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_GT_THAN(num_vectors, 0); + EXPECT_ARG_NOT_NULL(data); + NOT_IMPLEMENTED_IF( + (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA), + "Only Vamana algorithm is currently supported for index building" + ); auto src_data = svs::data::ConstSimpleDataView( data, num_vectors, builder->impl->dimension ); @@ -530,17 +469,22 @@ extern "C" svs_search_results_t svs_index_search( return nullptr; } - return runtime_error_wrapper( + using namespace svs::c_runtime; + return wrap_exceptions( [&]() { - using namespace svs::c_runtime; - + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(queries); + EXPECT_ARG_GT_THAN(num_queries, 0); + EXPECT_ARG_GT_THAN(k, 0); auto& vamana_index = static_cast(*index->impl).index; auto queries_view = svs::data::ConstSimpleDataView( queries, num_queries, vamana_index.dimensions() ); - auto search_results = index->impl->search(queries_view, k, search_params->impl); + auto search_results = index->impl->search( + queries_view, k, search_params == nullptr ? nullptr : search_params->impl + ); svs_search_results_t results = new svs_search_results{0, nullptr, nullptr, nullptr}; From cf45cd0ce049f9924ec34a746ec8c4c7787113bf Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 22 Jan 2026 14:50:06 +0100 Subject: [PATCH 14/18] Update C API design document --- bindings/SVS_C_API_Design.md | 819 +++++++++++++++++++++------ bindings/c/include/svs/c_api/svs_c.h | 6 +- bindings/c/samples/simple.c | 2 +- bindings/c/src/error.cpp | 2 +- bindings/c/src/svs_c.cpp | 2 +- bindings/c/src/threadpool.hpp | 10 +- 6 files changed, 662 insertions(+), 179 deletions(-) diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md index 549d45d7..af74ef8d 100644 --- a/bindings/SVS_C_API_Design.md +++ b/bindings/SVS_C_API_Design.md @@ -1,175 +1,658 @@ -# SVS C API Design proposal - -## Intro - -This document contains SVS C API design proposal. - -## Aspects and requirements - -* Simplicity - minimal set of operations to create an index and use it -* Flexibility - let user to modify as many parameters and options as possible - * Build parameters - * Block sizes - * Search parameters - * Allocation - * Thread pooling -* Error handling - -## Concept - -## Key abstractions - -* Index - * Initialized with non-empty dataset - * TopK Search - * Range search - * Iterative search - * Filtered search -* MutableIndex : Index - * Initialized with a dataset and labels list - * Add vectors with labels - * Remove vectors by labels - * Check if a label exists - * Compute distance for label - * Get vector by label -* Factory - * Created with minimal parameters: index algorithm, mutable/immutable, dimensions, distance metric - * Set storage configuration - default: Simple+FP32 - * Set algorithm configuration - * Set custom threadpool - default: SVS native - * Set custom allocator - default: SVS internal - * Mutable only: set allocation blocking - default: SVS internal -* Index data storage cofiguration - * Created with Kind: Simple, SQ, LVQ, LeanVec - * Configuration "Simple": - * Type: FP32, FP16, BF16 - * Configuration "SQ": - * Signed/Unsigned - * Configuration "LVQ": - * Primary size: 4, 8 - * Residual size: 0, 4, 8 - * Configuration LeanVec: - * Leanvec dimensions: - * Primary storage configuration: FP32, FP16, BF16, LVQ4, LVQ8 - * Secondary storage configuration: FP32, FP16, BF16, LVQ4, LVQ8 -* Threadpool configuration - * Threadpool kind: native, OMP, custom - * SVS native: size - * OMP: nothing (??size??) - * Custom: `struct IThreadPool { size_t (*size)(void); void (*parallel_for)(void (*f)(size_t), size_t); };` -* Allocator configuration - * Kind: simple, hugepage, custom - * Custom allocator: `struct IAlloc { void* (*alloc)(size_t); void (*dealloc)(void*, size_t); };` - -## Error handling - -There is 2 possible ways to handle errors in C API - -1. All API calls return status/error code when results are passed via "output argument" - "Intel oneDNN style" -2. All API calls return results when error handling is managed via "optional" pointer to status/error code variable - "OpenCL style" - -Proposed API utilizes the second approach. - -### Error details/message challenge - -Possible ways to provide errors messages: - -* Static map from error code to constant strings - * Simple API: `const char* svs_error_message(svs_status_t);` - * Requires fine-grained error codes - * Cost of code-to-message table maintainance -* Dynamically generated strings - * Complicated API to manage dynamically allocated strings - * Simplified error codes - * Easy to add/update error messages - * Allow to handle/explain object states (arguments, etc.) in messages - -## API Sample - -```c -// typedefs -int label_t; - -// Error handling -typedef enum { - svs_success; - svs_invalid_argument; - svs_out_of_memory; - ... -} svs_status_t; - -// Opaque types -svs_algorithm_t; -svs_storage_t; -svs_threadpool_t; -svs_allocator_t; - -// Enums -enum svs_metric_t; -enum svs_type_t; - -// Algorithm -typedef struct {...} svs_flat_params_t; -svs_algorithm_t svs_algo_create_flat(svs_flat_params_t* /*=NULL*/, svs_status_t* err_ret /*=NULL*/); - -typedef struct {...} svs_vamana_params_t; -svs_algorithm_t svs_algo_create_vamana(svs_vamana_params_t* params /*=NULL*/, svs_status_t* err_ret /*=NULL*/); - -svs_algorithm_t svs_algo_create_ivf(..., svs_status_t* err_ret /*=NULL*/); - -// Storage -svs_storage_t svs_storage_create_simple(svs_type_t, svs_status_t* err_ret /*=NULL*/); -svs_storage_t svs_storage_create_sq(svs_type_t, svs_status_t* err_ret /*=NULL*/); -svs_storage_t svs_storage_create_lvq(svs_type_t primary, svs_type_t residual, svs_status_t* err_ret /*=NULL*/); -svs_storage_t svs_storage_create_leanvec(size_t leanvec_dims, svs_type_t primary, svs_type_t secondary, svs_status_t* err_ret /*=NULL*/); - - -// Threadpool -svs_threadpool_t svs_threadpool_create_native(size, svs_status_t* err_ret /*=NULL*/); -svs_threadpool_t svs_threadpool_create_omp(svs_status_t* err_ret /*=NULL*/); - -typedef struct { - void* ctx; - size_t (*size)(void); - void (*parallel_for)(void (*f)(size_t), size_t); -} svs_threadpool_i; -svs_threadpool_t svs_threadpool_create_custom(svs_threadpool_i*, svs_status_t* err_ret /*=NULL*/); - -// Allocator -svs_allocator_t svs_allocator_create_simple(svs_status_t* err_ret /*=NULL*/); -svs_allocator_t svs_allocator_create_hugepage(svs_status_t* err_ret /*=NULL*/); - -typedef struct svs_allocator_i_tag { - void* ctx; - void* (*alloc)(size_t); - void (*dealloc)(void*, size_t); -} svs_allocator_i; -svs_allocator_t svs_allocator_create_custom(svs_allocator_i*, svs_status_t* err_ret /*=NULL*/); - - -// Factory -svs_factory_t svs_factory_create(svs_algorithm_t, size_t dims, svs_metric_t, svs_status_t* err_ret /*=NULL*/); -void svs_factory_set_storage(svs_factory_t, svs_storage_t, svs_status_t* err_ret /*=NULL*/); -void svs_factory_set_threadpool(svs_factory_t, svs_threadpool_t, svs_status_t* err_ret /*=NULL*/); -void svs_factory_set_allocator(svs_factory_t, svs_allocator_t, svs_status_t* err_ret /*=NULL*/); +# SVS C API Design Proposal -// Index -svs_index_t svs_index_create(svs_factory_t, float* data, size_t size, svs_status_t* err_ret /*=NULL*/); -svs_index_t svs_index_create_dynamic(svs_factory_t, float* data, svs_label_t* labels, size_t size, size_t blocksize /*=0*/, svs_status_t* err_ret /*=NULL*/); +## Overview -// Results -typedef struct { - size_t queries_num; - size_t* results_nums; - svs_label_t* labels; - float* distances; -} svs_results_t; +This document describes the design proposal for the Scalable Vector Search (SVS) C API. The API provides a C interface to SVS's vector similarity search capabilities, enabling integration with C applications and other languages that support C FFI (Foreign Function Interface). -svs_results_t (*svs_results_allocator_i)(size_t queries_num, size_t* results_nums); +### Design Goals -// Index queries -size_t svs_index_search_topk(svs_index_t, float* queries, size_t* queries_num, int k, svs_results_allocator_i results, svs_status_t* err_ret /*=NULL*/); -size_t svs_index_search_range(svs_index_t, float* queries, size_t* queries_num, float range, svs_results_allocator_i results, svs_status_t* err_ret /*=NULL*/); +The SVS C API is designed with the following principles: +1. **Simplicity** - Provide a minimal, intuitive set of operations to create and use vector search indices +2. **Flexibility** - Allow fine-grained control over: + - Index building parameters (graph degree, window sizes, etc.) + - Memory allocation strategies (simple, hugepage, custom) + - Thread pool configuration (native, OpenMP, custom) + - Vector storage formats (simple, compressed, quantized) + - Search parameters and filters + - Logging system +3. **Safety** - Comprehensive error handling with detailed error messages +4. **Portability** - Standard C interface that works across platforms and languages + +## Architecture Overview + +The API is built around a builder pattern with the following core abstractions: + +``` +┌─────────────────┐ +│ Index Builder │ Configure index parameters +│ - Algorithm │ +│ - Storage │ +│ - Threadpool │ +└────────┬────────┘ + │ build() + ↓ +┌─────────────────┐ +│ Index │ Perform searches +│ - search() │ with optional search params +└─────────────────┘ + │ + ├─ Search Params (optional) + └─ Search Results +``` + +## Core Components + +### 1. Index + +The main search structure providing vector similarity search operations. + +**Current Capabilities:** +- **TopK Search** - Find the k nearest neighbors for query vectors +- Configurable search parameters (window size, etc.) +- Multiple distance metrics (Euclidean, Cosine, Inner Product) + +**Requirements:** +- Built from a non-empty dataset using Index Builder +- Immutable after creation + +**Future Extensions:** +- Range search (all neighbors within distance threshold) +- Filtered search (predicate-based filtering) +- Dynamic updates (add/remove vectors) + +### 2. Index Builder + +Configures and creates index instances using the builder pattern. + +**Required Parameters:** +- Algorithm configuration handle +- Vector dimensions +- Distance metric (Euclidean, Cosine, Inner Product) + +**Optional Configuration:** +- Storage format (default: Simple FP32) +- Thread pool kind and size (default: native with hardware concurrency) +- Custom thread pool interface (for advanced use cases) + +### 3. Algorithm Configuration + +Defines the search algorithm and its parameters. + +**Current Support:** +- **Vamana** - Graph-based approximate nearest neighbor search + - Graph degree (connectivity) + - Build window size (construction search budget) + - Default search window size + - Alpha parameter (pruning threshold) + - Search history mode + +**Future Support:** +- **Flat** - Exhaustive brute-force search +- **IVF** - Inverted file with clustering + +### 4. Storage Configuration + +Defines how vectors are stored in memory, supporting various compression schemes. + +| Storage Type | Configuration Options | Description | +|--------------|----------------------|-------------| +| **Simple** | FP32, FP16, INT8, UINT8, INT4, UINT4 | Uncompressed storage | +| **SQ** | INT8, UINT8 | Scalar quantization | +| **LVQ** | Primary: INT4/UINT4/INT8/UINT8
Residual: VOID/INT4/UINT4/INT8/UINT8 | Locally-adaptive vector quantization | +| **LeanVec** | Dimensions
Primary: data type
Secondary: data type | LeanVec dimensionality reduced storage | + +**Example:** +```c +// Simple FP32 storage (default) +svs_storage_h storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, err); + +// LVQ with 8-bit primary and 4-bit residual +svs_storage_h storage = svs_storage_create_lvq( + SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT4, err +); + +// LeanVec with 128 dimensions +svs_storage_h storage = svs_storage_create_leanvec( + 128, SVS_DATA_TYPE_FLOAT16, SVS_DATA_TYPE_INT8, err +); + +// Scalar quantization +svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, err); +``` + +### 5. Thread Pool Configuration + +Controls parallelization strategy for index operations. + +| Type | Configuration | Use Case | +|------|---------------|----------| +| **Native** | Thread count | Default SVS thread pool (recommended) | +| **OpenMP** | Uses OMP_NUM_THREADS | Integration with OpenMP applications | +| **Single Thread** | No parallelization | Debugging or minimal overhead | +| **Custom** | User-defined interface | Custom scheduling/work-stealing | + +**Custom Interface:** +```c +struct svs_threadpool_interface_ops { + size_t (*size)(void* self); + void (*parallel_for)( + void* self, + void (*func)(void* svs_param, size_t i), + void* svs_param, // SVS state + size_t n // Number of tasks + ); +}; + +struct svs_threadpool_interface { + struct svs_threadpool_interface_ops ops; + void* self; // User-defined state +}; +``` + +### 6. Search Parameters + +Configures runtime search behavior (algorithm-specific). + +**Vamana Search Parameters:** +- **Search window size** - Controls search accuracy vs. speed tradeoff + - Larger values: more accurate but slower + - Smaller values: faster but less accurate + - Typically 50-200 for good recall + +**Usage:** +```c +// Use custom search parameters +svs_search_params_h params = svs_search_params_create_vamana(100, err); +svs_search_results_t results = svs_index_search( + index, queries, num_queries, k, params, err +); +svs_search_params_free(params); + +// Or use defaults from algorithm configuration +svs_search_results_t results = svs_index_search( + index, queries, num_queries, k, NULL, err +); +``` + +## Error Handling Strategy + +The API uses a dual approach for error reporting: return codes and optional detailed error information. + +### Return Values + +- Functions returning handles return `NULL` on failure +- Functions returning booleans return `false` on failure +- All functions accept an optional `svs_error_h` parameter for detailed diagnostics + +### Detailed Error Information + +For comprehensive error diagnostics, create an error handle and pass it to API calls: + +```c +// Create error handle +svs_error_h err = svs_error_create(); + +// Use in API calls (last parameter, can be NULL) +svs_algorithm_h algo = svs_algorithm_create_vamana( + 64, // graph_degree + 128, // build_window_size + 128, // search_window_size + err // optional error handle (can be NULL) +); + +if (algo == NULL) { + // Check error status + if (!svs_error_ok(err)) { + // Query error details + svs_error_code_t code = svs_error_get_code(err); + const char* msg = svs_error_get_message(err); + fprintf(stderr, "Error [%d]: %s\n", code, msg); + } +} + +// Error handle can be reused across multiple calls +svs_storage_h storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, err); + +// Free error handle when done +svs_error_free(err); +``` + +### Error Codes + +```c +enum svs_error_code { + SVS_OK = 0, // Success + SVS_ERROR_GENERIC = 1, // Generic/unspecified error + SVS_ERROR_INVALID_ARGUMENT = 2, // Invalid function parameter + SVS_ERROR_OUT_OF_MEMORY = 3, // Memory allocation failed + SVS_ERROR_INDEX_BUILD_FAILED = 4, // Index construction failed + SVS_ERROR_NOT_IMPLEMENTED = 5, // Feature not yet available + SVS_ERROR_UNSUPPORTED_HW = 6, // Hardware doesn't support required features + SVS_ERROR_RUNTIME = 7, // Runtime error during operation + SVS_ERROR_UNKNOWN = 1000 // Unknown/unexpected error +}; +``` + +### Best Practices + +1. **Always check return values** - Test for `NULL` or `false` before using results +2. **Use error handles during development** - Provides detailed diagnostics and error messages +3. **Reuse error handles** - Single handle can be reused across multiple API calls +4. **Free all resources** - Always call appropriate `_free()` functions to prevent leaks +5. **Pass NULL for optional parameters** - Error handle and search params can be `NULL` if not needed +6. **Check `svs_error_ok()`** - Use this helper to check if operation succeeded + +## Naming Conventions + +Consistent naming improves API discoverability and reduces cognitive load. + +### Prefixes + +- `svs_` - All functions and types +- `SVS_` - Macros and constants + +### Type Suffixes + +| Suffix | Meaning | Example | +|--------|---------|----------| +| `_t` | Value type (enum, struct) | `svs_metric_t`, `svs_error_code_t` | +| `_h` | Handle (opaque pointer) | `svs_index_h`, `svs_algorithm_h` | +| `_i` | Interface structure | `svs_allocator_i`, `svs_threadpool_i` | + +### Function Naming Pattern + +``` +svs_[_]_ +``` + +**Examples:** + +| Function | Breakdown | Description | +|----------|-----------|-------------| +| `svs_index_search()` | `svs` + `index` + `search` | Generic index search | +| `svs_algo_vamana_set_alpha()` | `svs` + `algo` + `vamana` + `set_alpha` | Set Vamana-specific parameter | +| `svs_storage_create_lvq()` | `svs` + `storage` + `create` + `lvq` | Create LVQ storage configuration | +| `svs_factory_set_threadpool()` | `svs` + `factory` + `set` + `threadpool` | Configure builder thread pool | + +### Examples by Category + +```c +// Handles (opaque pointers) +typedef struct svs_index* svs_index_h; +typedef struct svs_algorithm* svs_algorithm_h; +typedef struct svs_storage* svs_storage_h; + +// Value types +typedef enum svs_metric svs_metric_t; +typedef enum svs_error_code svs_error_code_t; + +// Interface structures +typedef struct svs_allocator_interface svs_allocator_i; +typedef struct svs_threadpool_interface svs_threadpool_i; +``` + +## API Reference + +### Type Definitions + +```c +// Opaque handles (suffix: _h) +typedef struct svs_error_desc* svs_error_h; +typedef struct svs_index* svs_index_h; +typedef struct svs_index_builder* svs_index_builder_h; +typedef struct svs_algorithm* svs_algorithm_h; +typedef struct svs_storage* svs_storage_h; +typedef struct svs_search_params* svs_search_params_h; + +// Fully defined types (suffix: _t) +typedef enum svs_error_code svs_error_code_t; +typedef enum svs_distance_metric svs_distance_metric_t; +typedef enum svs_algorithm_type svs_algorithm_type_t; +typedef enum svs_data_type svs_data_type_t; +typedef enum svs_storage_kind svs_storage_kind_t; +typedef enum svs_threadpool_kind svs_threadpool_kind_t; + +// Interface pointers +typedef struct svs_threadpool_interface* svs_threadpool_i; +typedef struct svs_search_results* svs_search_results_t; +``` + +### Error Handling API + +```c +// Create and manage error handles +svs_error_h svs_error_create(void); +void svs_error_free(svs_error_h err); + +// Query error information +bool svs_error_ok(svs_error_h err); +svs_error_code_t svs_error_get_code(svs_error_h err); +const char* svs_error_get_message(svs_error_h err); +``` + +### Algorithm API + +Create and configure search algorithms. + +```c +// Vamana graph-based approximate nearest neighbor search +svs_algorithm_h svs_algorithm_create_vamana( + size_t graph_degree, // Graph connectivity (e.g., 64) + size_t build_window_size, // Construction search window (e.g., 128) + size_t search_window_size, // Default query search window (e.g., 128) + svs_error_h out_err // optional, can be NULL +); + +// Cleanup +void svs_algorithm_free(svs_algorithm_h algorithm); + +// Get/Set Vamana parameters +bool svs_algorithm_vamana_get_alpha( + svs_algorithm_h algorithm, + float* out_alpha, + svs_error_h out_err +); + +bool svs_algorithm_vamana_set_alpha( + svs_algorithm_h algorithm, + float alpha, // Pruning parameter (typically 1.0 - 1.4) + svs_error_h out_err +); + +bool svs_algorithm_vamana_get_graph_degree( + svs_algorithm_h algorithm, + size_t* out_graph_degree, + svs_error_h out_err +); + +bool svs_algorithm_vamana_set_graph_degree( + svs_algorithm_h algorithm, + size_t graph_degree, + svs_error_h out_err +); + +bool svs_algorithm_vamana_get_build_window_size( + svs_algorithm_h algorithm, + size_t* out_build_window_size, + svs_error_h out_err +); + +bool svs_algorithm_vamana_set_build_window_size( + svs_algorithm_h algorithm, + size_t build_window_size, + svs_error_h out_err +); + +bool svs_algorithm_vamana_get_use_search_history( + svs_algorithm_h algorithm, + bool* out_use_full_search_history, + svs_error_h out_err +); + +bool svs_algorithm_vamana_set_use_search_history( + svs_algorithm_h algorithm, + bool use_full_search_history, + svs_error_h out_err +); +``` + +### Storage API + +Configure vector storage format and compression. + +```c +// Simple uncompressed storage +svs_storage_h svs_storage_create_simple( + svs_data_type_t data_type, // SVS_DATA_TYPE_FLOAT32, FLOAT16, INT8, etc. + svs_error_h out_err // optional, can be NULL +); + +// Scalar quantization +svs_storage_h svs_storage_create_sq( + svs_data_type_t data_type, // SVS_DATA_TYPE_INT8, SVS_DATA_TYPE_UINT8 + svs_error_h out_err +); + +// Locally-adaptive Vector Quantization (LVQ) +svs_storage_h svs_storage_create_lvq( + svs_data_type_t primary, // Primary quantization type + svs_data_type_t residual, // Residual type (or SVS_DATA_TYPE_VOID) + svs_error_h out_err +); + +// LeanVec two-level hierarchical storage +svs_storage_h svs_storage_create_leanvec( + size_t leanvec_dims, // Primary dimensions (usually much smaller) + svs_data_type_t primary, // Primary storage type + svs_data_type_t secondary, // Secondary/residual storage type + svs_error_h out_err +); + +// Cleanup +void svs_storage_free(svs_storage_h storage); +``` + + +### Search Parameters API + +Configure runtime search behavior. + +```c +// Create Vamana search parameters +svs_search_params_h svs_search_params_create_vamana( + size_t search_window_size, // Search window size (e.g., 100) + svs_error_h out_err // optional, can be NULL +); + +// Cleanup +void svs_search_params_free(svs_search_params_h params); +``` + +### Index Builder API + +Configure and build index instances. + +```c +// Create index builder with required parameters +svs_index_builder_h svs_index_builder_create( + svs_distance_metric_t metric, // Distance metric + size_t dimension, // Vector dimensionality + svs_algorithm_h algorithm, // Algorithm configuration + svs_error_h out_err // optional, can be NULL +); + +// Configure storage (optional, default: Simple FP32) +bool svs_index_builder_set_storage( + svs_index_builder_h builder, + svs_storage_h storage, // Storage configuration + svs_error_h out_err +); + +// Configure thread pool (optional, default: native) +bool svs_index_builder_set_threadpool( + svs_index_builder_h builder, + svs_threadpool_kind_t kind, // Thread pool type + size_t num_threads, // Number of threads (for native) + svs_error_h out_err +); + +// Configure custom thread pool (advanced) +bool svs_index_builder_set_threadpool_custom( + svs_index_builder_h builder, + svs_threadpool_interface_t pool, // Custom thread pool interface + svs_error_h out_err +); + +// Cleanup +void svs_index_builder_free(svs_index_builder_h builder); +``` + + +### Index API + +Build and query vector search indices. + +```c +// Build index from vector data +svs_index_h svs_index_build( + svs_index_builder_h builder, + const float* data, // Vector data [num_vectors × dimensions] + size_t num_vectors, + svs_error_h out_err // optional, can be NULL +); + +// Cleanup +void svs_index_free(svs_index_h index); +``` + +### Search Results + +```c +// Search results structure +struct svs_search_results { + size_t num_queries; // Number of query vectors + size_t* results_per_query; // Number of results per query + size_t* indices; // Indices of the nearest neighbors + float* distances; // Distances to the nearest neighbors +}; + +typedef struct svs_search_results* svs_search_results_t; + +// Access pattern: +// For query i, neighbor j (where k is the number of neighbors): +// index = results->indices[i * k + j] +// distance = results->distances[i * k + j] +``` + +### Search Operations + +```c +// Top-K nearest neighbor search +svs_search_results_t svs_index_search( + svs_index_h index, + const float* queries, // Query vectors [num_queries × dimensions] + size_t num_queries, + size_t k, // Number of neighbors to return + svs_search_params_h search_params, // optional, can be NULL for defaults + svs_error_h out_err // optional, can be NULL +); + +// Cleanup search results +void svs_search_results_free(svs_search_results_t results); +``` + +## Complete Usage Example + +```c +#include "svs/c_api/svs_c.h" +#include +#include + +int main() { + // 1. Create error handle for diagnostics + svs_error_h err = svs_error_create(); + + // 2. Create Vamana algorithm configuration + svs_algorithm_h algo = svs_algorithm_create_vamana( + 64, // graph_degree + 128, // build_window_size + 128, // default search_window_size + err + ); + if (!algo || !svs_error_ok(err)) { + fprintf(stderr, "Algorithm creation failed: %s\n", + svs_error_get_message(err)); + svs_error_free(err); + return 1; + } + + // 3. Create index builder + size_t dimensions = 128; + svs_index_builder_h builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, + dimensions, + algo, + err + ); + + // 4. Optional: Configure storage (default is FP32) + svs_storage_h storage = svs_storage_create_simple( + SVS_DATA_TYPE_FLOAT32, err + ); + svs_index_builder_set_storage(builder, storage, err); + + // 5. Optional: Configure thread pool + svs_index_builder_set_threadpool( + builder, + SVS_THREADPOOL_KIND_NATIVE, + 8, // num_threads + err + ); + + // 6. Prepare data + size_t num_vectors = 10000; + float* data = (float*)malloc(num_vectors * dimensions * sizeof(float)); + // ... fill data with vectors ... + + // 7. Build index + svs_index_h index = svs_index_build(builder, data, num_vectors, err); + if (!index || !svs_error_ok(err)) { + fprintf(stderr, "Index build failed: %s\n", + svs_error_get_message(err)); + goto cleanup; + } + + // 8. Prepare queries + size_t num_queries = 10; + float* queries = (float*)malloc(num_queries * dimensions * sizeof(float)); + // ... fill queries ... + + // 9. Perform search with default parameters + size_t k = 5; + svs_search_results_t results = svs_index_search( + index, queries, num_queries, k, NULL, err + ); + + // Or with custom search parameters: + // svs_search_params_h params = svs_search_params_create_vamana(100, err); + // svs_search_results_t results = svs_index_search( + // index, queries, num_queries, k, params, err + // ); + // svs_search_params_free(params); + + // 10. Process results + if (results && svs_error_ok(err)) { + for (size_t i = 0; i < results->num_queries; i++) { + printf("Query %zu:\n", i); + for (size_t j = 0; j < k; j++) { + size_t idx = i * k + j; + printf(" Index: %zu, Distance: %f\n", + results->indices[idx], results->distances[idx]); + } + } + svs_search_results_free(results); + } + + // 11. Cleanup +cleanup: + if (index) svs_index_free(index); + if (builder) svs_index_builder_free(builder); + if (storage) svs_storage_free(storage); + if (algo) svs_algorithm_free(algo); + svs_error_free(err); + + free(data); + free(queries); + + return 0; +} +``` + +## Next Steps + +- See [ERROR_HANDLING.md](c/ERROR_HANDLING.md) for comprehensive error handling guide +- See [examples/c/](../examples/c/) for additional usage examples +- See [bindings/c/samples/](c/samples/) for complete sample applications ``` diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 928305fb..c436c641 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -112,12 +112,12 @@ typedef enum svs_algorithm_type svs_algorithm_type_t; typedef enum svs_data_type svs_data_type_t; typedef enum svs_threadpool_kind svs_threadpool_kind_t; -typedef struct svs_threadpool_interface* svs_threadpool_interface_t; +typedef struct svs_threadpool_interface* svs_threadpool_i; typedef struct svs_search_results* svs_search_results_t; /// @brief Create an error handle /// @return A handle to the created error object -SVS_API svs_error_h svs_error_init(); +SVS_API svs_error_h svs_error_create(); /// @brief Check if the error handle indicates success /// @param err The error handle to check @@ -330,7 +330,7 @@ SVS_API bool svs_index_builder_set_threadpool( /// @return true on success, false on failure SVS_API bool svs_index_builder_set_threadpool_custom( svs_index_builder_h builder, - svs_threadpool_interface_t pool, + svs_threadpool_i pool, svs_error_h out_err /*=NULL*/ ); diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index 46b09be4..4b245cdc 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -35,7 +35,7 @@ static struct svs_threadpool_interface sequential_threadpool = { int main() { int ret = 0; srand(time(NULL)); - svs_error_h error = svs_error_init(); + svs_error_h error = svs_error_create(); float* data = NULL; float* queries = NULL; diff --git a/bindings/c/src/error.cpp b/bindings/c/src/error.cpp index df2e9e8f..f3d845f7 100644 --- a/bindings/c/src/error.cpp +++ b/bindings/c/src/error.cpp @@ -19,7 +19,7 @@ #include -extern "C" svs_error_h svs_error_init() { return new svs_error_desc{SVS_OK, "Success"}; } +extern "C" svs_error_h svs_error_create() { return new svs_error_desc{SVS_OK, "Success"}; } extern "C" bool svs_error_ok(svs_error_h err) { return err->code == SVS_OK; } extern "C" svs_error_code_t svs_error_get_code(svs_error_h err) { return err->code; } extern "C" const char* svs_error_get_message(svs_error_h err) { diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 93c4b2cb..9248ac32 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -404,7 +404,7 @@ extern "C" bool svs_index_builder_set_threadpool( extern "C" bool svs_index_builder_set_threadpool_custom( svs_index_builder_h builder, - svs_threadpool_interface_t pool, + svs_threadpool_i pool, svs_error_h out_err /*=NULL*/ ) { using namespace svs::c_runtime; diff --git a/bindings/c/src/threadpool.hpp b/bindings/c/src/threadpool.hpp index 110dac5b..d2bef66b 100644 --- a/bindings/c/src/threadpool.hpp +++ b/bindings/c/src/threadpool.hpp @@ -28,7 +28,7 @@ namespace svs::c_runtime { class ThreadPoolBuilder { struct CustomThreadPool { - static svs_threadpool_interface_t validate(svs_threadpool_interface_t impl) { + static svs_threadpool_i validate(svs_threadpool_i impl) { if (impl == nullptr) { throw std::invalid_argument("Custom threadpool pointer cannot be null."); } @@ -40,7 +40,7 @@ class ThreadPoolBuilder { return impl; } - CustomThreadPool(svs_threadpool_interface_t impl) + CustomThreadPool(svs_threadpool_i impl) : impl{validate(impl)} {} size_t size() const { @@ -61,12 +61,12 @@ class ThreadPoolBuilder { ); } - svs_threadpool_interface_t impl; + svs_threadpool_i impl; }; svs_threadpool_kind kind; size_t num_threads; - svs_threadpool_interface_t user_threadpool; + svs_threadpool_i user_threadpool; public: ThreadPoolBuilder() @@ -82,7 +82,7 @@ class ThreadPoolBuilder { } } - ThreadPoolBuilder(svs_threadpool_interface_t pool) + ThreadPoolBuilder(svs_threadpool_i pool) : kind(SVS_THREADPOOL_KIND_CUSTOM) , num_threads(0) , user_threadpool(CustomThreadPool::validate(pool)) {} From e045debcd3e8a9ed597f0069cb3a9c2d47434acd Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Wed, 25 Feb 2026 10:28:12 +0100 Subject: [PATCH 15/18] Add index save/load API (#251) Add `svs_index_load()` and `svs_index_save()` API implementation for static Vamana index --- bindings/SVS_C_API_Design.md | 1 - bindings/c/CMakeLists.txt | 4 +- bindings/c/include/svs/c_api/svs_c.h | 22 ++- bindings/c/samples/CMakeLists.txt | 15 +- bindings/c/samples/save_load.c | 261 +++++++++++++++++++++++++++ bindings/c/src/index.hpp | 15 +- bindings/c/src/index_builder.hpp | 77 ++++++++ bindings/c/src/storage.hpp | 19 ++ bindings/c/src/svs_c.cpp | 52 +++++- 9 files changed, 439 insertions(+), 27 deletions(-) create mode 100644 bindings/c/samples/save_load.c diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md index af74ef8d..9d58a6db 100644 --- a/bindings/SVS_C_API_Design.md +++ b/bindings/SVS_C_API_Design.md @@ -226,7 +226,6 @@ enum svs_error_code { SVS_ERROR_GENERIC = 1, // Generic/unspecified error SVS_ERROR_INVALID_ARGUMENT = 2, // Invalid function parameter SVS_ERROR_OUT_OF_MEMORY = 3, // Memory allocation failed - SVS_ERROR_INDEX_BUILD_FAILED = 4, // Index construction failed SVS_ERROR_NOT_IMPLEMENTED = 5, // Feature not yet available SVS_ERROR_UNSUPPORTED_HW = 6, // Hardware doesn't support required features SVS_ERROR_RUNTIME = 7, // Runtime error during operation diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index c4866398..74f036ce 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -66,7 +66,9 @@ set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVER target_link_libraries(${TARGET_NAME} PRIVATE svs::svs ) -link_mkl_static(${TARGET_NAME}) +if (SVS_EXPERIMENTAL_LINK_STATIC_MKL) + link_mkl_static(${TARGET_NAME}) +endif() # target_compile_definitions(${TARGET_NAME} PRIVATE # PUBLIC "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\"" # PUBLIC "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\"" diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index c436c641..3c622f07 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -29,7 +29,6 @@ enum svs_error_code { SVS_ERROR_GENERIC = 1, SVS_ERROR_INVALID_ARGUMENT = 2, SVS_ERROR_OUT_OF_MEMORY = 3, - SVS_ERROR_INDEX_BUILD_FAILED = 4, SVS_ERROR_NOT_IMPLEMENTED = 5, SVS_ERROR_UNSUPPORTED_HW = 6, SVS_ERROR_RUNTIME = 7, @@ -329,9 +328,7 @@ SVS_API bool svs_index_builder_set_threadpool( /// @param out_err An optional error handle to capture errors /// @return true on success, false on failure SVS_API bool svs_index_builder_set_threadpool_custom( - svs_index_builder_h builder, - svs_threadpool_i pool, - svs_error_h out_err /*=NULL*/ + svs_index_builder_h builder, svs_threadpool_i pool, svs_error_h out_err /*=NULL*/ ); /// @brief Build an index from the provided data @@ -347,6 +344,15 @@ SVS_API svs_index_h svs_index_build( svs_error_h out_err /*=NULL*/ ); +/// @brief Load an index from disk +/// @param builder The index builder handle (used for configuration) +/// @param directory The directory path to load the index from +/// @param out_err An optional error handle to capture errors +/// @return A handle to the loaded index +SVS_API svs_index_h svs_index_load( + svs_index_builder_h builder, const char* directory, svs_error_h out_err /*=NULL*/ +); + /// @brief Free the index handle /// @param index The index handle to free SVS_API void svs_index_free(svs_index_h index); @@ -372,6 +378,14 @@ SVS_API svs_search_results_t svs_index_search( /// @param results The search results structure to release SVS_API void svs_search_results_free(svs_search_results_t results); +/// @brief Save the index to disk +/// @param index The index handle +/// @param directory The directory path to save the index to +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool +svs_index_save(svs_index_h index, const char* directory, svs_error_h out_err /*=NULL*/); + #ifdef __cplusplus } #endif diff --git a/bindings/c/samples/CMakeLists.txt b/bindings/c/samples/CMakeLists.txt index fb0520d4..22e8b8e0 100644 --- a/bindings/c/samples/CMakeLists.txt +++ b/bindings/c/samples/CMakeLists.txt @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(SAMPLE_SIMPLE_TARGET c_api_simple) +foreach(SAMPLE_NAME simple save_load) + set(SAMPLE_TARGET c_api_${SAMPLE_NAME}) + add_executable(${SAMPLE_TARGET} ${SAMPLE_NAME}.c) -add_executable(${SAMPLE_SIMPLE_TARGET} simple.c) + target_link_libraries(${SAMPLE_TARGET} PRIVATE svs_c_api) -target_link_libraries(${SAMPLE_SIMPLE_TARGET} PRIVATE svs_c_api) - -target_include_directories(${SAMPLE_SIMPLE_TARGET} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../include -) \ No newline at end of file + target_include_directories(${SAMPLE_TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ) +endforeach(SAMPLE_NAME) diff --git a/bindings/c/samples/save_load.c b/bindings/c/samples/save_load.c new file mode 100644 index 00000000..f33fd271 --- /dev/null +++ b/bindings/c/samples/save_load.c @@ -0,0 +1,261 @@ +// required for nftw +#define _XOPEN_SOURCE 500 +// required for mkdtemp +#define _GNU_SOURCE + +#include "svs/c_api/svs_c.h" +#include +#include +#include +#include +#include +#include + +#define NUM_VECTORS 10000 +#define NUM_QUERIES 1 +#define DIMENSION 128 +#define K 10 + +void generate_random_data(float* data, size_t count, size_t dim) { + for (size_t i = 0; i < count * dim; i++) { + data[i] = (float)rand() / RAND_MAX; + } +} + +int nftw_callback( + const char* fpath, const struct stat* sb, int typeflag, struct FTW* ftwbuf +) { + if (typeflag == FTW_DP) { + // directory, remove it + return rmdir(fpath); + } else { + // file, remove it + return unlink(fpath); + } +} + +int remove_directory_recursive(const char* path) { + // remove the directory and its contents using function nftw() + return nftw(path, nftw_callback, 64, FTW_DEPTH | FTW_PHYS); +} + +int main() { + int ret = 0; + srand(time(NULL)); + svs_error_h error = svs_error_create(); + + float* data = NULL; + float* queries = NULL; + svs_algorithm_h algorithm = NULL; + svs_storage_h storage = NULL; + svs_index_builder_h builder = NULL; + svs_index_h index = NULL; + svs_search_results_t results = NULL; + char tmp_dir_template[] = "svs_index_XXXXXX"; + char* tmp_dir = NULL; + svs_search_results_t loaded_results = NULL; + + // Allocate random data + data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float)); + queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float)); + + if (!data || !queries) { + fprintf(stderr, "Failed to allocate memory\n"); + ret = 1; + goto cleanup; + } + + generate_random_data(data, NUM_VECTORS, DIMENSION); + generate_random_data(queries, NUM_QUERIES, DIMENSION); + + // Create Vamana algorithm + algorithm = svs_algorithm_create_vamana(64, 128, 100, error); + if (!algorithm) { + fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Create storage + // Simple storage + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error); + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error); + + // LeanVec storage + size_t leanvec_dims = DIMENSION / 2; + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT4, error); + + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT8, error); + + // OK: + storage = svs_storage_create_leanvec( + leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error + ); + + // ERROR: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, + // SVS_DATA_TYPE_UINT4, error); + + // LVQ Storage + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error); + + // Scalar Quantized Storage + // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error); + + // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); + + if (!storage) { + fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Create index builder + builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error + ); + if (!builder) { + fprintf( + stderr, "Failed to create index builder: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + + if (!svs_index_builder_set_storage(builder, storage, error)) { + fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Build index + printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION); + index = svs_index_build(builder, data, NUM_VECTORS, error); + if (!index) { + fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Index built successfully!\n"); + + // Search + printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K); + results = + svs_index_search(index, queries, NUM_QUERIES, K, NULL /* search_params */, error); + if (!results) { + fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Search completed successfully!\n"); + + // Create temporary directory for saving the index + tmp_dir = mkdtemp(tmp_dir_template); + if (!tmp_dir) { + fprintf(stderr, "Failed to create temporary directory\n"); + ret = 1; + goto cleanup; + } + + printf("Saving index to directory: %s\n", tmp_dir); + // Save the index to disk + if (!svs_index_save(index, tmp_dir, error)) { + fprintf(stderr, "Failed to save index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Index saved successfully!\n"); + + svs_index_free(index); + index = NULL; + // Load the index from disk + printf("Loading index from directory: %s\n", tmp_dir); + index = svs_index_load(builder, tmp_dir, error); + if (!index) { + fprintf(stderr, "Failed to load index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Index loaded successfully!\n"); + + // Search the loaded index + printf( + "Searching loaded index for %d queries for top-%d neighbors...\n", NUM_QUERIES, K + ); + loaded_results = + svs_index_search(index, queries, NUM_QUERIES, K, NULL /* search_params */, error); + if (!loaded_results) { + fprintf( + stderr, "Failed to search loaded index: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + printf("Search on loaded index completed successfully!\n"); + + // Compare results + if (results->num_queries != loaded_results->num_queries) { + fprintf( + stderr, "Mismatch in number of queries between original and loaded results\n" + ); + ret = 1; + goto cleanup; + } + + size_t offset = 0; + for (size_t q = 0; q < results->num_queries; q++) { + if (results->results_per_query[q] != loaded_results->results_per_query[q]) { + fprintf(stderr, "Mismatch in number of results for query %zu\n", q); + ret = 1; + goto cleanup; + } + printf("Query %zu results:\n", q); + for (size_t i = 0; i < results->results_per_query[q]; i++) { + if (results->indices[offset + i] != loaded_results->indices[offset + i]) { + fprintf( + stderr, "Mismatch in neighbor indices for query %zu, result %zu\n", q, i + ); + ret = 1; + goto cleanup; + } + printf( + " [%zu] id=%zu, distance=%.4f, diff=%.4f\n", + i, + results->indices[offset + i], + results->distances[offset + i], + results->distances[offset + i] - loaded_results->distances[offset + i] + ); + } + offset += results->results_per_query[q]; + } + + printf("Done!\n"); + +cleanup: + // Cleanup + if (tmp_dir) { + // remove the temporary directory and its contents + remove_directory_recursive(tmp_dir); + } + svs_search_results_free(results); + svs_search_results_free(loaded_results); + svs_index_free(index); + svs_index_builder_free(builder); + svs_storage_free(storage); + svs_algorithm_free(algorithm); + free(data); + free(queries); + svs_error_free(error); + + return ret; +} \ No newline at end of file diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index fb1d95ec..94540772 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -19,12 +19,14 @@ #include "algorithm.hpp" -// #include -// #include -// #include -// #include +#include +#include +#include #include +#include +#include + namespace svs::c_runtime { struct Index { svs_algorithm_type algorithm; @@ -36,6 +38,7 @@ struct Index { size_t num_neighbors, const std::shared_ptr& search_params ) = 0; + virtual void save(const std::filesystem::path& directory) = 0; }; struct IndexVamana : public Index { @@ -61,5 +64,9 @@ struct IndexVamana : public Index { index.search(results.view(), queries, params); return std::move(results); } + + void save(const std::filesystem::path& directory) override { + index.save(directory / "config", directory / "graph", directory / "data"); + } }; } // namespace svs::c_runtime diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index a615a246..bbc27bb2 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -30,6 +30,9 @@ #include #include +#include +#include + namespace svs::c_runtime { template @@ -46,6 +49,23 @@ svs::Vamana build_vamana_index( ); } +template +svs::Vamana load_vamana_index( + const std::filesystem::path& directory, + DataLoader loader, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + auto data = loader.load(directory / "data"); + return svs::Vamana::assemble( + directory / "config", + svs::GraphLoader{directory / "graph"}, + std::move(data), + distance_type, + std::move(pool) + ); +} + template void register_build_vamana_index_methods(Dispatcher& dispatcher) { dispatcher.register_target(&build_vamana_index>); @@ -63,6 +83,25 @@ void register_build_vamana_index_methods(Dispatcher& dispatcher) { dispatcher.register_target(&build_vamana_index>); dispatcher.register_target(&build_vamana_index>); } + +template +void register_load_vamana_index_methods(Dispatcher& dispatcher) { + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); +} + using BuildIndexDispatcher = svs::lib::Dispatcher< svs::Vamana, const svs::index::vamana::VamanaBuildParameters&, @@ -89,6 +128,30 @@ svs::Vamana dispatch_vamana_index_build( ); } +using LoadIndexDispatcher = svs::lib::Dispatcher< + svs::Vamana, + const std::filesystem::path&, + const Storage*, + svs::DistanceType, + svs::threads::ThreadPoolHandle>; + +LoadIndexDispatcher load_vamana_index_dispatcher() { + auto dispatcher = LoadIndexDispatcher{}; + register_load_vamana_index_methods(dispatcher); + return dispatcher; +} + +svs::Vamana dispatch_vamana_index_load( + const std::filesystem::path& directory, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + return load_vamana_index_dispatcher().invoke( + directory, storage, distance_type, std::move(pool) + ); +} + struct IndexBuilder { svs_distance_metric_t distance_metric; size_t dimension; @@ -133,5 +196,19 @@ struct IndexBuilder { } return nullptr; } + + std::shared_ptr load(const std::filesystem::path& directory) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { + auto index = std::make_shared(dispatch_vamana_index_load( + directory, + storage.get(), + to_distance_type(distance_metric), + pool_builder.build() + )); + + return index; + } + return nullptr; + } }; } // namespace svs::c_runtime diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 29efc25f..32da312f 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -32,6 +32,9 @@ #include #include +#include +#include + namespace svs { namespace c_runtime { @@ -139,6 +142,10 @@ template class SimpleDataBuilder { svs::data::copy(view, data); return data; } + + SimpleDataType load(const std::filesystem::path& path) { + return svs::lib::load_from_disk(path); + } }; template @@ -182,6 +189,10 @@ template class LeanVecDataBuilder { view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_} ); } + + LeanDatasetType load(const std::filesystem::path& path) { + return svs::lib::load_from_disk(path); + } }; template @@ -221,6 +232,10 @@ template class LVQDataBuilder { build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { return LVQDatasetType::compress(view, pool, 0); } + + LVQDatasetType load(const std::filesystem::path& path) { + return svs::lib::load_from_disk(path); + } }; template @@ -255,6 +270,10 @@ template class SQDataBuilder { build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { return SQDatasetType::compress(view, pool); } + + SQDatasetType load(const std::filesystem::path& path) { + return svs::lib::load_from_disk(path); + } }; template diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 9248ac32..6e4578d2 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -24,6 +24,9 @@ #include "threadpool.hpp" #include "types_support.hpp" +#include +#include + #include #include #include @@ -438,7 +441,7 @@ extern "C" svs_index_h svs_index_build( auto index = builder->impl->build(src_data); if (index == nullptr) { - SET_ERROR(out_err, SVS_ERROR_INDEX_BUILD_FAILED, "Index build failed"); + SET_ERROR(out_err, SVS_ERROR_RUNTIME, "Index build failed"); return svs_index_h{nullptr}; } @@ -450,6 +453,30 @@ extern "C" svs_index_h svs_index_build( ); } +extern "C" svs_index_h +svs_index_load(svs_index_builder_h builder, const char* directory, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_NOT_NULL(directory); + NOT_IMPLEMENTED_IF( + (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA), + "Only Vamana algorithm is currently supported for index loading" + ); + auto index = builder->impl->load(std::filesystem::path{directory}); + if (index == nullptr) { + SET_ERROR(out_err, SVS_ERROR_RUNTIME, "Index load failed"); + return svs_index_h{nullptr}; + } + auto result = new svs_index; + result->impl = index; + return result; + }, + out_err + ); +} + extern "C" void svs_index_free(svs_index_h index) { delete index; } extern "C" svs_search_results_t svs_index_search( @@ -460,15 +487,6 @@ extern "C" svs_search_results_t svs_index_search( svs_search_params_h search_params, svs_error_h out_err ) { - if (index == nullptr || queries == nullptr || num_queries == 0 || k == 0) { - SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); - return nullptr; - } - if (index->impl->algorithm != SVS_ALGORITHM_TYPE_VAMANA) { - SET_ERROR(out_err, SVS_ERROR_NOT_IMPLEMENTED, "Not implemented"); - return nullptr; - } - using namespace svs::c_runtime; return wrap_exceptions( [&]() { @@ -517,3 +535,17 @@ extern "C" void svs_search_results_free(svs_search_results_t results) { delete[] results->distances; delete results; } + +extern "C" bool +svs_index_save(svs_index_h index, const char* directory, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(directory); + index->impl->save(std::filesystem::path{directory}); + return true; + }, + out_err + ); +} From 62cca5b287b8bef51b9fa2c1020e7a8863edae11 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 9 Mar 2026 16:51:52 +0100 Subject: [PATCH 16/18] Add dynamic Vamana index support (#252) Done: - [x] Create dynamic index with specified block size (default block size should be supported) - [x] Initialized with a dataset and labels list - [x] Add labeled vectors to a dynamic index - [x] Remove vectors by labels - [x] Check if a label exists - [x] Compute distance for label - [x] Get vector by label - [x] Consolidate/compact dynamic index - [x] Implement Save/Load --- bindings/c/include/svs/c_api/svs_c.h | 115 ++++++ bindings/c/samples/CMakeLists.txt | 5 +- bindings/c/samples/dynamic.c | 384 +++++++++++++++++++ bindings/c/src/dispatcher_dynamic_vamana.hpp | 199 ++++++++++ bindings/c/src/dispatcher_vamana.hpp | 139 +++++++ bindings/c/src/index.hpp | 108 +++++- bindings/c/src/index_builder.hpp | 171 +++------ bindings/c/src/storage.hpp | 115 +++--- bindings/c/src/svs_c.cpp | 249 +++++++++++- 9 files changed, 1309 insertions(+), 176 deletions(-) create mode 100644 bindings/c/samples/dynamic.c create mode 100644 bindings/c/src/dispatcher_dynamic_vamana.hpp create mode 100644 bindings/c/src/dispatcher_vamana.hpp diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 3c622f07..65cf7fc4 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -344,6 +344,25 @@ SVS_API svs_index_h svs_index_build( svs_error_h out_err /*=NULL*/ ); +/// @brief Build a dynamic index from the provided data and IDs +/// @param builder The index builder handle +/// @param data Pointer to the vector data (float array) +/// @param ids Pointer to the vector IDs (size_t array). Can be NULL if IDs should be +/// auto-generated from 0 to num_vectors-1. +/// @param num_vectors The number of vectors in the data +/// @param blocksize_bytes The block size in bytes for dynamic index building (0 for +/// default) +/// @param out_err An optional error handle to capture errors +/// @return A handle to the built dynamic index +SVS_API svs_index_h svs_index_build_dynamic( + svs_index_builder_h builder, + const float* data, + const size_t* ids /*=NULL*/, + size_t num_vectors, + size_t blocksize_bytes /*=0*/, + svs_error_h out_err /*=NULL*/ +); + /// @brief Load an index from disk /// @param builder The index builder handle (used for configuration) /// @param directory The directory path to load the index from @@ -353,6 +372,19 @@ SVS_API svs_index_h svs_index_load( svs_index_builder_h builder, const char* directory, svs_error_h out_err /*=NULL*/ ); +/// @brief Load a dynamic index from disk +/// @param builder The index builder handle (used for configuration) +/// @param directory The directory path to load the index from +/// @param blocksize_bytes The block size in bytes for dynamic index loading (0 for default) +/// @param out_err An optional error handle to capture errors +/// @return A handle to the loaded dynamic index +SVS_API svs_index_h svs_index_load_dynamic( + svs_index_builder_h builder, + const char* directory, + size_t blocksize_bytes /*=0*/, + svs_error_h out_err /*=NULL*/ +); + /// @brief Free the index handle /// @param index The index handle to free SVS_API void svs_index_free(svs_index_h index); @@ -386,6 +418,89 @@ SVS_API void svs_search_results_free(svs_search_results_t results); SVS_API bool svs_index_save(svs_index_h index, const char* directory, svs_error_h out_err /*=NULL*/); +/// @brief Add points to a dynamic index +/// @param index The dynamic index handle +/// @param new_points Pointer to the new vector data (float array) +/// @param ids Pointer to the new vector IDs (size_t array) +/// @param num_vectors The number of new vectors to add +/// @param out_err An optional error handle to capture errors +/// @return number of points successfully added, or (size_t)-1 on failure +SVS_API size_t svs_index_dynamic_add_points( + svs_index_h index, + const float* new_points, + const size_t* ids, + size_t num_vectors, + svs_error_h out_err /*=NULL*/ +); + +/// @brief Delete points from a dynamic index +/// @param index The dynamic index handle +/// @param ids Pointer to the vector IDs to delete (size_t array) +/// @param num_ids The number of vector IDs to delete +/// @param out_err An optional error handle to capture errors +/// @return number of points successfully deleted, or (size_t)-1 on failure +SVS_API size_t svs_index_dynamic_delete_points( + svs_index_h index, const size_t* ids, size_t num_ids, svs_error_h out_err /*=NULL*/ +); +/// @brief Check if a dynamic index has a specific ID +/// @param index The dynamic index handle +/// @param id The vector ID to check for +/// @param out_has_id Pointer to store whether the ID exists in the index +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_dynamic_has_id( + svs_index_h index, size_t id, bool* out_has_id, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get the distance from a specific ID to a query vector in an index +/// @param index The index handle +/// @param id The vector ID to get the distance for +/// @param query Pointer to the query vector data (float array) +/// @param out_distance Pointer to store the retrieved distance +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_get_distance( + svs_index_h index, + size_t id, + const float* query, + float* out_distance, + svs_error_h out_err /*=NULL*/ +); + +/// @brief Reconstruct the vectors for specific IDs in an index +/// @param index The index handle +/// @param ids Pointer to the vector IDs to reconstruct (size_t array) +/// @param num_ids The number of vector IDs to reconstruct +/// @param out_data Pointer to store the reconstructed vector data (float array with size +/// num_ids * data_dim) +/// @param data_dim The dimensionality of the vectors +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_reconstruct( + svs_index_h index, + const size_t* ids, + size_t num_ids, + float* out_data, + size_t data_dim, + svs_error_h out_err /*=NULL*/ +); + +/// @brief Consolidate a dynamic index to optimize storage and search performance +/// @param index The dynamic index handle +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool +svs_index_dynamic_consolidate(svs_index_h index, svs_error_h out_err /*=NULL*/); + +/// @brief Compact a dynamic index to remove deleted entries and optimize storage +/// @param index The dynamic index handle +/// @param batchsize The batch size for compaction (0 for default) +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_dynamic_compact( + svs_index_h index, size_t batchsize /*=0*/, svs_error_h out_err /*=NULL*/ +); + #ifdef __cplusplus } #endif diff --git a/bindings/c/samples/CMakeLists.txt b/bindings/c/samples/CMakeLists.txt index 22e8b8e0..f758504e 100644 --- a/bindings/c/samples/CMakeLists.txt +++ b/bindings/c/samples/CMakeLists.txt @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -foreach(SAMPLE_NAME simple save_load) +foreach(SAMPLE_NAME simple save_load dynamic) set(SAMPLE_TARGET c_api_${SAMPLE_NAME}) + list(APPEND SAMPLE_TARGETS ${SAMPLE_TARGET}) add_executable(${SAMPLE_TARGET} ${SAMPLE_NAME}.c) target_link_libraries(${SAMPLE_TARGET} PRIVATE svs_c_api) @@ -22,3 +23,5 @@ foreach(SAMPLE_NAME simple save_load) ${CMAKE_CURRENT_SOURCE_DIR}/../include ) endforeach(SAMPLE_NAME) + +add_custom_target(c_api_samples ALL DEPENDS ${SAMPLE_TARGETS}) diff --git a/bindings/c/samples/dynamic.c b/bindings/c/samples/dynamic.c new file mode 100644 index 00000000..794cfbfc --- /dev/null +++ b/bindings/c/samples/dynamic.c @@ -0,0 +1,384 @@ +#include "svs/c_api/svs_c.h" +#include +#include +#include + +#define INITIAL_VECTORS 10000 +#define TAILING_VECTORS 1000 +#define NUM_VECTORS (INITIAL_VECTORS + TAILING_VECTORS) +#define DELETE_VECTORS_BEGIN 5000 +#define DELETE_VECTORS_END 8000 +#define NUM_QUERIES 5 +#define DIMENSION 128 +#define K 10 + +void generate_random_data(float* data, size_t count, size_t dim) { + for (size_t i = 0; i < count * dim; i++) { + data[i] = (float)rand() / RAND_MAX; + } +} + +size_t sequential_tp_size(void* self) { return 1; } + +void sequential_tp_parallel_for( + void* self, void (*func)(void*, size_t), void* svs_param, size_t n +) { + for (size_t i = 0; i < n; ++i) { + func(svs_param, i); + } +} + +static struct svs_threadpool_interface sequential_threadpool = { + { + &sequential_tp_size, + &sequential_tp_parallel_for, + }, + NULL, +}; + +int main() { + int ret = 0; + srand(time(NULL)); + svs_error_h error = svs_error_create(); + + float* data = NULL; + size_t* ids = NULL; + float* queries = NULL; + svs_algorithm_h algorithm = NULL; + svs_storage_h storage = NULL; + svs_index_builder_h builder = NULL; + svs_index_h index = NULL; + svs_search_params_h search_params = NULL; + svs_search_results_t results = NULL; + + // Allocate random data + data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float)); + ids = (size_t*)malloc(NUM_VECTORS * sizeof(size_t)); + queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float)); + + if (!data || !ids || !queries) { + fprintf(stderr, "Failed to allocate memory\n"); + ret = 1; + goto cleanup; + } + + generate_random_data(data, NUM_VECTORS, DIMENSION); + for (size_t i = 0; i < NUM_VECTORS; i++) { + ids[i] = i; + } + generate_random_data(queries, NUM_QUERIES, DIMENSION); + + // Create Vamana algorithm + algorithm = svs_algorithm_create_vamana(64, 128, 100, error); + if (!algorithm) { + fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Create storage + // Simple storage + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error); + // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error); + + // LeanVec storage + size_t leanvec_dims = DIMENSION / 2; + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT4, error); + + // OK: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4, + // SVS_DATA_TYPE_UINT8, error); + + // OK: + storage = svs_storage_create_leanvec( + leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error + ); + + // ERROR: + // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8, + // SVS_DATA_TYPE_UINT4, error); + + // LVQ Storage + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error); + + // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error); + + // Scalar Quantized Storage + // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error); + + // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); + + if (!storage) { + fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Create index builder + builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error + ); + if (!builder) { + fprintf( + stderr, "Failed to create index builder: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + + if (!svs_index_builder_set_storage(builder, storage, error)) { + fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Set custom sequential threadpool + if (!svs_index_builder_set_threadpool_custom(builder, &sequential_threadpool, error)) { + fprintf(stderr, "Failed to set threadpool: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + + // Build index + printf( + "Building dynamic index with %d vectors of dimension %d...\n", + INITIAL_VECTORS, + DIMENSION + ); + index = svs_index_build_dynamic(builder, data, ids, INITIAL_VECTORS, 0, error); + if (!index) { + fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Index built successfully!\n"); + + // Add more points to the index + printf("Adding %d more vectors to the index...\n", TAILING_VECTORS); + size_t num_added = svs_index_dynamic_add_points( + index, + data + INITIAL_VECTORS * DIMENSION, + ids + INITIAL_VECTORS, + TAILING_VECTORS, + error + ); + if (num_added == (size_t)-1) { + fprintf( + stderr, "Failed to add points to index: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + printf("Points added successfully!\n"); + + // Search params + search_params = svs_search_params_create_vamana(100, error); + if (!search_params) { + fprintf( + stderr, "Failed to create search params: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + + // Search + printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K); + results = svs_index_search(index, queries, NUM_QUERIES, K, search_params, error); + if (!results) { + fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error)); + ret = 1; + goto cleanup; + } + printf("Search completed successfully!\n"); + + // Print results + size_t offset = 0; + for (size_t q = 0; q < results->num_queries; q++) { + printf("Query %zu results:\n", q); + for (size_t i = 0; i < results->results_per_query[q]; i++) { + printf( + " [%zu] id=%zu, distance=%.4f\n", + i, + results->indices[offset + i], + results->distances[offset + i] + ); + } + offset += results->results_per_query[q]; + } + svs_search_results_free(results); + results = NULL; + + // Delete some points + printf( + "Deleting vectors %d-%d from the index...\n", + DELETE_VECTORS_BEGIN, + DELETE_VECTORS_END - 1 + ); + size_t num_deleted = svs_index_dynamic_delete_points( + index, ids + DELETE_VECTORS_BEGIN, DELETE_VECTORS_END - DELETE_VECTORS_BEGIN, error + ); + if (num_deleted == (size_t)-1) { + fprintf( + stderr, "Failed to delete points from index: %s\n", svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + printf("Deleted %zu points successfully!\n", num_deleted); + + // Search again after deletion + printf("Searching again after deletion...\n"); + results = svs_index_search(index, queries, NUM_QUERIES, K, search_params, error); + if (!results) { + fprintf( + stderr, + "Failed to search index after deletion: %s\n", + svs_error_get_message(error) + ); + ret = 1; + goto cleanup; + } + printf("Search after deletion completed successfully!\n"); + + // Validate that deleted points are not returned in search results + printf("Validating results after deletion...\n"); + offset = 0; + for (size_t q = 0; q < results->num_queries; q++) { + for (size_t i = 0; i < results->results_per_query[q]; i++) { + size_t id = results->indices[offset + i]; + if (id >= DELETE_VECTORS_BEGIN && id < DELETE_VECTORS_END) { + fprintf(stderr, "Error: Deleted id %zu returned in search results!\n", id); + ret = 1; + } + } + offset += results->results_per_query[q]; + } + + // Check if specific IDs exist in the index + printf("Checking if specific IDs exist in the index...\n"); + size_t check_ids[] = { + DELETE_VECTORS_BEGIN - 1, + DELETE_VECTORS_BEGIN, + DELETE_VECTORS_BEGIN + 1, + DELETE_VECTORS_END - 1, + DELETE_VECTORS_END, + DELETE_VECTORS_END + 1}; + for (size_t i = 0; i < sizeof(check_ids) / sizeof(check_ids[0]); i++) { + size_t id = check_ids[i]; + bool has_id = false; + if (!svs_index_dynamic_has_id(index, id, &has_id, error)) { + fprintf( + stderr, + "Failed to check if index has id %zu: %s\n", + id, + svs_error_get_message(error) + ); + ret = 1; + continue; + } + if (id >= DELETE_VECTORS_BEGIN && id < DELETE_VECTORS_END) { + if (has_id) { + fprintf(stderr, "Error: Deleted id %zu still exists in the index!\n", id); + ret = 1; + } + } else { + if (!has_id) { + fprintf(stderr, "Error: Existing id %zu not found in the index!\n", id); + ret = 1; + } + } + } + if (ret == 0) { + printf("ID existence validation passed!\n"); + } + + // Get distance to a specific ID + printf("Getting distance to a specific ID...\n"); + size_t test_id = DELETE_VECTORS_BEGIN - 1; + float distance = 0.0f; + if (!svs_index_get_distance(index, test_id, queries, &distance, error)) { + fprintf( + stderr, + "Failed to get distance to id %zu: %s\n", + test_id, + svs_error_get_message(error) + ); + ret = 1; + } else { + printf("Distance from id %zu to query[0]: %.4f\n", test_id, distance); + } + + // Reconstruct a specific ID + printf("Reconstructing a specific ID...\n"); + float* origin_vector = data + test_id * DIMENSION; + float* reconstructed = (float*)malloc(DIMENSION * sizeof(float)); + if (!reconstructed) { + fprintf(stderr, "Failed to allocate memory for reconstruction\n"); + ret = 1; + goto cleanup; + } + if (!svs_index_reconstruct(index, &test_id, 1, reconstructed, DIMENSION, error)) { + fprintf( + stderr, + "Failed to reconstruct id %zu: %s\n", + test_id, + svs_error_get_message(error) + ); + ret = 1; + } else { + printf( + "Original vector for id %zu: [%.4f, %.4f, ...]\n", + test_id, + origin_vector[0], + origin_vector[1] + ); + printf( + "Reconstructed vector for id %zu: [%.4f, %.4f, ...]\n", + test_id, + reconstructed[0], + reconstructed[1] + ); + } + free(reconstructed); + + // Consolidate the dynamic index + printf("Consolidating the dynamic index...\n"); + if (!svs_index_dynamic_consolidate(index, error)) { + fprintf(stderr, "Failed to consolidate index: %s\n", svs_error_get_message(error)); + ret = 1; + } else { + printf("Index consolidated successfully!\n"); + } + + // Compact the dynamic index + printf("Compacting the dynamic index...\n"); + if (!svs_index_dynamic_compact(index, 0, error)) { + fprintf(stderr, "Failed to compact index: %s\n", svs_error_get_message(error)); + ret = 1; + } else { + printf("Index compacted successfully!\n"); + } + + printf("Done!\n"); + +cleanup: + // Cleanup + svs_search_results_free(results); + svs_search_params_free(search_params); + svs_index_free(index); + svs_index_builder_free(builder); + svs_storage_free(storage); + svs_algorithm_free(algorithm); + free(data); + free(ids); + free(queries); + svs_error_free(error); + + return ret; +} \ No newline at end of file diff --git a/bindings/c/src/dispatcher_dynamic_vamana.hpp b/bindings/c/src/dispatcher_dynamic_vamana.hpp new file mode 100644 index 00000000..950b9f45 --- /dev/null +++ b/bindings/c/src/dispatcher_dynamic_vamana.hpp @@ -0,0 +1,199 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "algorithm.hpp" +#include "index.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace svs::c_runtime { + +using DynamicVamanaSource = std::variant< + std::pair, std::span>, + std::filesystem::path>; + +template +svs::DynamicVamana build_dynamic_vamana_index( + const svs::index::vamana::VamanaBuildParameters& build_params, + std::pair, std::span> src_data, + DataBuilder builder, + Distance D, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + svs::data::BlockingParameters block_params; + if (blocksize_bytes != 0) { + block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); + } + using allocator_type = typename DataBuilder::allocator_type; + auto allocator = allocator_type{block_params}; + auto data = builder.build(std::move(src_data.first), pool, allocator); + return svs::DynamicVamana::build( + build_params, + std::move(data), + std::move(src_data.second), + std::move(D), + std::move(pool) + ); +} + +template +svs::DynamicVamana load_dynamic_vamana_index( + const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), + const std::filesystem::path& directory, + DataLoader loader, + Distance D, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + svs::data::BlockingParameters block_params; + if (blocksize_bytes != 0) { + block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); + } + using allocator_type = typename DataLoader::allocator_type; + auto allocator = allocator_type{block_params}; + auto data = loader.load(directory / "data", allocator); + return svs::DynamicVamana::assemble( + directory / "config", + svs::GraphLoader{directory / "graph"}, + std::move(data), + std::move(D), + std::move(pool) + ); +} + +template void for_simple_specializations(F&& f) { +#define X(T, A, D) f.template operator(), D>(); +#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) +#define XXX(T) XX(T, svs::data::Blocked>) + XXX(float) + XXX(svs::Float16) +#undef XXX +#undef XX +#undef X +} + +template void for_leanvec_specializations(F&& f) { + using byte_alloc = svs::data::Blocked>; + +#define X(P, S, D) f.template operator(), D>(); +#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) + // Pattern: + // PrimaryBits, SecondaryBits, Distance + XX(4, 4) + XX(4, 8) + XX(8, 8) +#undef XX +#undef X +} + +template void for_lvq_specializations(F&& f) { + using byte_alloc = svs::data::Blocked>; +#define X(P, S, D) f.template operator(), D>(); +#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) + // Pattern: + // PrimaryBits, SecondaryBits, Distance + XX(4, 0) + XX(8, 0) + XX(4, 4) + XX(4, 8) +#undef XX +#undef X +} + +template void for_sq_specializations(F&& f) { +#define X(T, A, D) f.template operator(), D>(); +#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) +#define XXX(T) XX(T, svs::data::Blocked>) + XXX(uint8_t) + XXX(int8_t) +#undef XXX +#undef XX +#undef X +} + +template +void register_dynamic_vamana_index_specializations(Dispatcher& dispatcher) { + auto build_closure = [&dispatcher]() { + dispatcher.register_target(&build_dynamic_vamana_index); + }; + auto load_closure = [&dispatcher]() { + dispatcher.register_target(&load_dynamic_vamana_index); + }; + + for_simple_specializations(build_closure); + for_simple_specializations(load_closure); + for_leanvec_specializations(build_closure); + for_leanvec_specializations(load_closure); + for_lvq_specializations(build_closure); + for_lvq_specializations(load_closure); + for_sq_specializations(build_closure); + for_sq_specializations(load_closure); +} + +using BuildDynamicIndexDispatcher = svs::lib::Dispatcher< + svs::DynamicVamana, + const svs::index::vamana::VamanaBuildParameters&, + DynamicVamanaSource, + const Storage*, + svs::DistanceType, + svs::threads::ThreadPoolHandle, + size_t>; + +const BuildDynamicIndexDispatcher& build_dynamic_vamana_index_dispatcher() { + static BuildDynamicIndexDispatcher dispatcher = [] { + BuildDynamicIndexDispatcher d{}; + register_dynamic_vamana_index_specializations(d); + return d; + }(); + return dispatcher; +} + +svs::DynamicVamana dispatch_dynamic_vamana_index_build( + const svs::index::vamana::VamanaBuildParameters& build_params, + DynamicVamanaSource src_data, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + return build_dynamic_vamana_index_dispatcher().invoke( + build_params, + std::move(src_data), + storage, + distance_type, + std::move(pool), + blocksize_bytes + ); +} +} // namespace svs::c_runtime diff --git a/bindings/c/src/dispatcher_vamana.hpp b/bindings/c/src/dispatcher_vamana.hpp new file mode 100644 index 00000000..bb30b76d --- /dev/null +++ b/bindings/c/src/dispatcher_vamana.hpp @@ -0,0 +1,139 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "algorithm.hpp" +#include "index.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace svs::c_runtime { + +using VamanaSource = + std::variant, std::filesystem::path>; + +template +svs::Vamana build_vamana_index( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + DataBuilder builder, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance_type, std::move(pool) + ); +} + +template +svs::Vamana load_vamana_index( + const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), + const std::filesystem::path& directory, + DataLoader loader, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + auto data = loader.load(directory / "data"); + return svs::Vamana::assemble( + directory / "config", + svs::GraphLoader{directory / "graph"}, + std::move(data), + distance_type, + std::move(pool) + ); +} + +template +void register_build_vamana_index_methods(Dispatcher& dispatcher) { + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); + + dispatcher.register_target(&build_vamana_index>); + dispatcher.register_target(&build_vamana_index>); +} + +template +void register_load_vamana_index_methods(Dispatcher& dispatcher) { + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); + + dispatcher.register_target(&load_vamana_index>); + dispatcher.register_target(&load_vamana_index>); +} + +using BuildIndexDispatcher = svs::lib::Dispatcher< + svs::Vamana, + const svs::index::vamana::VamanaBuildParameters&, + VamanaSource, + const Storage*, + svs::DistanceType, + svs::threads::ThreadPoolHandle>; + +const BuildIndexDispatcher& build_vamana_index_dispatcher() { + static BuildIndexDispatcher dispatcher = [] { + BuildIndexDispatcher d{}; + register_build_vamana_index_methods(d); + register_load_vamana_index_methods(d); + return d; + }(); + return dispatcher; +} + +svs::Vamana dispatch_vamana_index_build( + const svs::index::vamana::VamanaBuildParameters& build_params, + VamanaSource src_data, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + return build_vamana_index_dispatcher().invoke( + build_params, std::move(src_data), storage, distance_type, std::move(pool) + ); +} +} // namespace svs::c_runtime diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index 94540772..0ca4338c 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -22,10 +22,12 @@ #include #include #include +#include #include #include #include +#include namespace svs::c_runtime { struct Index { @@ -39,6 +41,24 @@ struct Index { const std::shared_ptr& search_params ) = 0; virtual void save(const std::filesystem::path& directory) = 0; + virtual size_t dimensions() const = 0; + virtual float get_distance(size_t id, std::span query) const = 0; + virtual void + reconstruct_at(svs::data::SimpleDataView dst, std::span ids) = 0; +}; + +struct DynamicIndex : public Index { + DynamicIndex(svs_algorithm_type algorithm) + : Index(algorithm) {} + ~DynamicIndex() = default; + + virtual size_t add_points( + svs::data::ConstSimpleDataView new_points, std::span ids + ) = 0; + virtual size_t delete_points(std::span ids) = 0; + virtual bool has_id(size_t id) const = 0; + virtual void consolidate() = 0; + virtual void compact(size_t batchsize) = 0; }; struct IndexVamana : public Index { @@ -46,12 +66,12 @@ struct IndexVamana : public Index { IndexVamana(svs::Vamana&& index) : Index{SVS_ALGORITHM_TYPE_VAMANA} , index(std::move(index)) {} - ~IndexVamana() {} - virtual svs::QueryResult search( + ~IndexVamana() = default; + svs::QueryResult search( svs::data::ConstSimpleDataView queries, size_t num_neighbors, const std::shared_ptr& search_params - ) { + ) override { auto vamana_search_params = std::static_pointer_cast(search_params); auto results = svs::QueryResult(queries.size(), num_neighbors); @@ -68,5 +88,87 @@ struct IndexVamana : public Index { void save(const std::filesystem::path& directory) override { index.save(directory / "config", directory / "graph", directory / "data"); } + + size_t dimensions() const override { return index.dimensions(); } + + float get_distance(size_t id, std::span query) const override { + return index.get_distance(id, query); + } + + void reconstruct_at(svs::data::SimpleDataView dst, std::span ids) + override { + index.reconstruct_at(dst, ids); + } +}; + +struct DynamicIndexVamana : public DynamicIndex { + svs::DynamicVamana index; + DynamicIndexVamana(svs::DynamicVamana&& index) + : DynamicIndex(SVS_ALGORITHM_TYPE_VAMANA) + , index(std::move(index)) {} + ~DynamicIndexVamana() = default; + + svs::QueryResult search( + svs::data::ConstSimpleDataView queries, + size_t num_neighbors, + const std::shared_ptr& search_params + ) override { + auto vamana_search_params = + std::static_pointer_cast(search_params); + auto results = svs::QueryResult(queries.size(), num_neighbors); + + auto params = index.get_search_parameters(); + if (vamana_search_params) { + params = vamana_search_params->get_search_parameters(); + } + + index.search(results.view(), queries, params); + return std::move(results); + } + + void save(const std::filesystem::path& directory) override { + index.save(directory / "config", directory / "graph", directory / "data"); + } + + size_t dimensions() const override { return index.dimensions(); } + + size_t add_points( + svs::data::ConstSimpleDataView new_points, std::span ids + ) override { + auto old_size = index.size(); + index.add_points(new_points, ids); + // TODO: This is a bit of a hack - we should ideally return the number of points + // actually added, but for now we can just return index size change. + return index.size() - old_size; + } + + size_t delete_points(std::span ids) override { + auto old_size = index.size(); + index.delete_points(ids); + // TODO: This is a bit of a hack - we should ideally return the number of points + // actually deleted, but for now we can just return index size change. + return old_size - index.size(); + } + + bool has_id(size_t id) const override { return index.has_id(id); } + + float get_distance(size_t id, std::span query) const override { + return index.get_distance(id, query); + } + + void reconstruct_at(svs::data::SimpleDataView dst, std::span ids) + override { + index.reconstruct_at(dst, ids); + } + + void consolidate() override { index.consolidate(); } + + void compact(size_t batchsize) override { + if (batchsize == 0) { + index.compact(); // Use default batch size + } else { + index.compact(batchsize); + } + } }; } // namespace svs::c_runtime diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index bbc27bb2..e17c6372 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -18,6 +18,8 @@ #include "svs/c_api/svs_c.h" #include "algorithm.hpp" +#include "dispatcher_dynamic_vamana.hpp" +#include "dispatcher_vamana.hpp" #include "index.hpp" #include "storage.hpp" #include "threadpool.hpp" @@ -35,123 +37,6 @@ namespace svs::c_runtime { -template -svs::Vamana build_vamana_index( - const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - DataBuilder builder, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = builder.build(std::move(src_data), pool); - return svs::Vamana::build( - build_params, std::move(data), distance_type, std::move(pool) - ); -} - -template -svs::Vamana load_vamana_index( - const std::filesystem::path& directory, - DataLoader loader, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = loader.load(directory / "data"); - return svs::Vamana::assemble( - directory / "config", - svs::GraphLoader{directory / "graph"}, - std::move(data), - distance_type, - std::move(pool) - ); -} - -template -void register_build_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); -} - -template -void register_load_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); -} - -using BuildIndexDispatcher = svs::lib::Dispatcher< - svs::Vamana, - const svs::index::vamana::VamanaBuildParameters&, - svs::data::ConstSimpleDataView, - const Storage*, - svs::DistanceType, - svs::threads::ThreadPoolHandle>; - -BuildIndexDispatcher build_vamana_index_dispatcher() { - auto dispatcher = BuildIndexDispatcher{}; - register_build_vamana_index_methods(dispatcher); - return dispatcher; -} - -svs::Vamana dispatch_vamana_index_build( - const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - const Storage* storage, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - return build_vamana_index_dispatcher().invoke( - build_params, std::move(src_data), storage, distance_type, std::move(pool) - ); -} - -using LoadIndexDispatcher = svs::lib::Dispatcher< - svs::Vamana, - const std::filesystem::path&, - const Storage*, - svs::DistanceType, - svs::threads::ThreadPoolHandle>; - -LoadIndexDispatcher load_vamana_index_dispatcher() { - auto dispatcher = LoadIndexDispatcher{}; - register_load_vamana_index_methods(dispatcher); - return dispatcher; -} - -svs::Vamana dispatch_vamana_index_load( - const std::filesystem::path& directory, - const Storage* storage, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - return load_vamana_index_dispatcher().invoke( - directory, storage, distance_type, std::move(pool) - ); -} - struct IndexBuilder { svs_distance_metric_t distance_metric; size_t dimension; @@ -186,7 +71,7 @@ struct IndexBuilder { auto index = std::make_shared(dispatch_vamana_index_build( vamana_algorithm->build_parameters(), - data, + VamanaSource{data}, storage.get(), to_distance_type(distance_metric), pool_builder.build() @@ -199,8 +84,11 @@ struct IndexBuilder { std::shared_ptr load(const std::filesystem::path& directory) { if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { - auto index = std::make_shared(dispatch_vamana_index_load( - directory, + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + auto index = std::make_shared(dispatch_vamana_index_build( + vamana_algorithm->build_parameters(), + VamanaSource{directory}, storage.get(), to_distance_type(distance_metric), pool_builder.build() @@ -210,5 +98,48 @@ struct IndexBuilder { } return nullptr; } + + std::shared_ptr build_dynamic( + const svs::data::ConstSimpleDataView& data, + std::span ids, + size_t blocksize_bytes + ) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + auto index = + std::make_shared(dispatch_dynamic_vamana_index_build( + vamana_algorithm->build_parameters(), + DynamicVamanaSource{std::make_pair(data, ids)}, + storage.get(), + to_distance_type(distance_metric), + pool_builder.build(), + blocksize_bytes + )); + + return index; + } + return nullptr; + } + + std::shared_ptr + load_dynamic(const std::filesystem::path& directory, size_t blocksize_bytes) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + auto index = + std::make_shared(dispatch_dynamic_vamana_index_build( + vamana_algorithm->build_parameters(), + DynamicVamanaSource{directory}, + storage.get(), + to_distance_type(distance_metric), + pool_builder.build(), + blocksize_bytes + )); + + return index; + } + return nullptr; + } }; } // namespace svs::c_runtime diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 32da312f..5395c44c 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -126,32 +126,35 @@ struct StorageSQ : public Storage { } // namespace c_runtime -template class SimpleDataBuilder { +template > +class SimpleDataBuilder { public: SimpleDataBuilder() {} - using SimpleDataType = - svs::data::SimpleData>>; + using data_type = svs::data::SimpleData; + using allocator_type = Allocator; template - SimpleDataType build( + data_type build( svs::data::ConstSimpleDataView view, - svs::threads::ThreadPoolHandle& SVS_UNUSED(pool) + svs::threads::ThreadPoolHandle& SVS_UNUSED(pool), + const allocator_type& allocator = {} ) { - auto data = SimpleDataType(view.size(), view.dimensions()); + auto data = data_type(view.size(), view.dimensions(), allocator); svs::data::copy(view, data); return data; } - SimpleDataType load(const std::filesystem::path& path) { - return svs::lib::load_from_disk(path); + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); } }; -template -struct lib::DispatchConverter> { +template +struct lib::DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = SimpleDataBuilder; + using To = SimpleDataBuilder; static int64_t match(From from) { if constexpr (svs::is_arithmetic_v) { @@ -168,37 +171,44 @@ struct lib::DispatchConverter> { static To convert(From from) { return To{}; } }; -template class LeanVecDataBuilder { +template > +class LeanVecDataBuilder { size_t leanvec_dims_; public: LeanVecDataBuilder(size_t leanvec_dims) : leanvec_dims_(leanvec_dims) {} - using LeanDatasetType = svs::leanvec::LeanDataset< + using data_type = svs::leanvec::LeanDataset< svs::leanvec::UsingLVQ, svs::leanvec::UsingLVQ, svs::Dynamic, svs::Dynamic, - svs::data::Blocked>>; + Allocator>; + using allocator_type = Allocator; template - LeanDatasetType - build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { - return LeanDatasetType::reduce( - view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_} + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::reduce( + view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator ); } - LeanDatasetType load(const std::filesystem::path& path) { - return svs::lib::load_from_disk(path); + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); } }; -template -struct lib::DispatchConverter> { +template +struct lib:: + DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = LeanVecDataBuilder; + using To = LeanVecDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_LEANVEC) { @@ -212,38 +222,47 @@ struct lib::DispatchConverter(from); - return LeanVecDataBuilder(leanvec->lenavec_dims); + return To{leanvec->lenavec_dims}; } }; -template class LVQDataBuilder { +template < + size_t PrimaryBits, + size_t ResidualBits, + typename Allocator = svs::lib::Allocator> +class LVQDataBuilder { public: LVQDataBuilder() {} - using LVQDatasetType = svs::quantization::lvq::LVQDataset< + using data_type = svs::quantization::lvq::LVQDataset< PrimaryBits, ResidualBits, svs::Dynamic, svs::quantization::lvq::Sequential, - svs::data::Blocked>>; + Allocator>; + using allocator_type = Allocator; template - LVQDatasetType - build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { - return LVQDatasetType::compress(view, pool, 0); + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::compress(view, pool, 0, allocator); } - LVQDatasetType load(const std::filesystem::path& path) { - return svs::lib::load_from_disk(path); + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); } }; -template +template struct lib::DispatchConverter< const c_runtime::Storage*, - LVQDataBuilder> { + LVQDataBuilder> { using From = const svs::c_runtime::Storage*; - using To = LVQDataBuilder; + using To = LVQDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_LVQ) { @@ -258,28 +277,32 @@ struct lib::DispatchConverter< static To convert(From from) { return To{}; } }; -template class SQDataBuilder { +template > class SQDataBuilder { public: SQDataBuilder() {} - using SQDatasetType = svs::quantization::scalar:: - SQDataset>>; + using data_type = svs::quantization::scalar::SQDataset; + using allocator_type = Allocator; template - SQDatasetType - build(svs::data::ConstSimpleDataView view, svs::threads::ThreadPoolHandle& pool) { - return SQDatasetType::compress(view, pool); + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::compress(view, pool, allocator); } - SQDatasetType load(const std::filesystem::path& path) { - return svs::lib::load_from_disk(path); + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); } }; -template -struct lib::DispatchConverter> { +template +struct lib::DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = SQDataBuilder; + using To = SQDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_SQ) { diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 6e4578d2..0c6290c1 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -26,6 +26,9 @@ #include #include +#include +#include +#include #include #include @@ -406,9 +409,7 @@ extern "C" bool svs_index_builder_set_threadpool( } extern "C" bool svs_index_builder_set_threadpool_custom( - svs_index_builder_h builder, - svs_threadpool_i pool, - svs_error_h out_err /*=NULL*/ + svs_index_builder_h builder, svs_threadpool_i pool, svs_error_h out_err /*=NULL*/ ) { using namespace svs::c_runtime; return wrap_exceptions( @@ -453,6 +454,83 @@ extern "C" svs_index_h svs_index_build( ); } +extern "C" svs_index_h svs_index_build_dynamic( + svs_index_builder_h builder, + const float* data, + const size_t* ids, + size_t num_vectors, + size_t blocksize_bytes, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_GT_THAN(num_vectors, 0); + EXPECT_ARG_NOT_NULL(data); + NOT_IMPLEMENTED_IF( + (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA), + "Only Vamana algorithm is currently supported for dynamic index building" + ); + auto src_data = svs::data::ConstSimpleDataView( + data, num_vectors, builder->impl->dimension + ); + + std::vector generated_ids; + if (ids == nullptr) { + // If IDs are not provided, generate them as a sequence from 0 to + // num_vectors-1 + generated_ids.resize(num_vectors); + std::iota(generated_ids.begin(), generated_ids.end(), 0); + ids = generated_ids.data(); + } + + auto index = builder->impl->build_dynamic( + src_data, std::span(ids, num_vectors), blocksize_bytes + ); + if (index == nullptr) { + SET_ERROR(out_err, SVS_ERROR_RUNTIME, "Dynamic index build failed"); + return svs_index_h{nullptr}; + } + + auto result = new svs_index; + result->impl = index; + return result; + }, + out_err + ); +} + +extern "C" svs_index_h svs_index_load_dynamic( + svs_index_builder_h builder, + const char* directory, + size_t blocksize_bytes, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(builder); + EXPECT_ARG_NOT_NULL(directory); + NOT_IMPLEMENTED_IF( + (builder->impl->algorithm->type != SVS_ALGORITHM_TYPE_VAMANA), + "Only Vamana algorithm is currently supported for dynamic index loading" + ); + auto index = builder->impl->load_dynamic( + std::filesystem::path{directory}, blocksize_bytes + ); + if (index == nullptr) { + SET_ERROR(out_err, SVS_ERROR_RUNTIME, "Dynamic index load failed"); + return svs_index_h{nullptr}; + } + auto result = new svs_index; + result->impl = index; + return result; + }, + out_err + ); +} + extern "C" svs_index_h svs_index_load(svs_index_builder_h builder, const char* directory, svs_error_h out_err) { using namespace svs::c_runtime; @@ -494,13 +572,14 @@ extern "C" svs_search_results_t svs_index_search( EXPECT_ARG_NOT_NULL(queries); EXPECT_ARG_GT_THAN(num_queries, 0); EXPECT_ARG_GT_THAN(k, 0); - auto& vamana_index = static_cast(*index->impl).index; + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); auto queries_view = svs::data::ConstSimpleDataView( - queries, num_queries, vamana_index.dimensions() + queries, num_queries, index_ptr->dimensions() ); - auto search_results = index->impl->search( + auto search_results = index_ptr->search( queries_view, k, search_params == nullptr ? nullptr : search_params->impl ); @@ -549,3 +628,161 @@ svs_index_save(svs_index_h index, const char* directory, svs_error_h out_err) { out_err ); } + +extern "C" size_t svs_index_dynamic_add_points( + svs_index_h index, + const float* new_points, + const size_t* ids, + size_t num_vectors, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(new_points); + EXPECT_ARG_NOT_NULL(ids); + EXPECT_ARG_GT_THAN(num_vectors, 0); + auto dynamic_index_ptr = std::dynamic_pointer_cast(index->impl); + INVALID_ARGUMENT_IF( + dynamic_index_ptr == nullptr, "Index does not support dynamic updates" + ); + auto src_data = svs::data::ConstSimpleDataView( + new_points, num_vectors, dynamic_index_ptr->dimensions() + ); + return dynamic_index_ptr->add_points(src_data, std::span(ids, num_vectors)); + }, + out_err, + static_cast(-1) + ); +} + +extern "C" size_t svs_index_dynamic_delete_points( + svs_index_h index, const size_t* ids, size_t num_ids, svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(ids); + EXPECT_ARG_GT_THAN(num_ids, 0); + auto dynamic_index_ptr = std::dynamic_pointer_cast(index->impl); + INVALID_ARGUMENT_IF( + dynamic_index_ptr == nullptr, "Index does not support dynamic updates" + ); + return dynamic_index_ptr->delete_points(std::span(ids, num_ids)); + }, + out_err, + static_cast(-1) + ); +} + +extern "C" bool svs_index_dynamic_has_id( + svs_index_h index, size_t id, bool* out_has_id, svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(out_has_id); + auto dynamic_index_ptr = std::dynamic_pointer_cast(index->impl); + INVALID_ARGUMENT_IF( + dynamic_index_ptr == nullptr, "Index does not support dynamic updates" + ); + *out_has_id = dynamic_index_ptr->has_id(id); + return true; + }, + out_err, + false + ); +} + +extern "C" bool svs_index_get_distance( + svs_index_h index, + size_t id, + const float* query, + float* out_distance, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(query); + EXPECT_ARG_NOT_NULL(out_distance); + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + *out_distance = index_ptr->get_distance(id, std::span{query, index_ptr->dimensions()}); + return true; + }, + out_err, + false + ); +} + +extern "C" bool svs_index_reconstruct( + svs_index_h index, + const size_t* ids, + size_t num_ids, + float* out_data, + size_t data_dim, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(ids); + EXPECT_ARG_NOT_NULL(out_data); + EXPECT_ARG_GT_THAN(num_ids, 0); + auto index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + INVALID_ARGUMENT_IF( + data_dim != index_ptr->dimensions(), + "Output data dimensionality does not match index dimensionality" + ); + index_ptr->reconstruct_at( + svs::data::SimpleDataView(out_data, num_ids, data_dim), + std::span(ids, num_ids) + ); + return true; + }, + out_err, + false + ); +} + +extern "C" bool svs_index_dynamic_consolidate(svs_index_h index, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + auto dynamic_index_ptr = std::dynamic_pointer_cast(index->impl); + INVALID_ARGUMENT_IF( + dynamic_index_ptr == nullptr, "Index does not support dynamic updates" + ); + dynamic_index_ptr->consolidate(); + return true; + }, + out_err, + false + ); +} + +extern "C" bool +svs_index_dynamic_compact(svs_index_h index, size_t batchsize, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + auto dynamic_index_ptr = std::dynamic_pointer_cast(index->impl); + INVALID_ARGUMENT_IF( + dynamic_index_ptr == nullptr, "Index does not support dynamic updates" + ); + dynamic_index_ptr->compact(batchsize); + return true; + }, + out_err, + false + ); +} From c2f03b9611e49644a02ab56a9c616ecdc123f641 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 10 Mar 2026 11:28:48 +0100 Subject: [PATCH 17/18] [C API] Refactor C API implementation to make it portable to the public repo --- bindings/c/CMakeLists.txt | 76 +++++- bindings/c/src/allocator.hpp | 29 +++ bindings/c/src/data_builder.hpp | 21 ++ bindings/c/src/data_builder/leanvec.hpp | 124 +++++++++ bindings/c/src/data_builder/lvq.hpp | 120 +++++++++ bindings/c/src/data_builder/simple.hpp | 95 +++++++ bindings/c/src/data_builder/sq.hpp | 91 +++++++ bindings/c/src/dispatcher_dynamic_vamana.cpp | 168 +++++++++++++ bindings/c/src/dispatcher_dynamic_vamana.hpp | 173 +------------ bindings/c/src/dispatcher_vamana.cpp | 132 ++++++++++ bindings/c/src/dispatcher_vamana.hpp | 112 +-------- bindings/c/src/index.hpp | 4 +- bindings/c/src/index_builder.hpp | 13 +- bindings/c/src/storage.hpp | 251 ++++--------------- bindings/c/src/types_support.hpp | 4 +- 15 files changed, 933 insertions(+), 480 deletions(-) create mode 100644 bindings/c/src/allocator.hpp create mode 100644 bindings/c/src/data_builder.hpp create mode 100644 bindings/c/src/data_builder/leanvec.hpp create mode 100644 bindings/c/src/data_builder/lvq.hpp create mode 100644 bindings/c/src/data_builder/simple.hpp create mode 100644 bindings/c/src/data_builder/sq.hpp create mode 100644 bindings/c/src/dispatcher_dynamic_vamana.cpp create mode 100644 bindings/c/src/dispatcher_vamana.cpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 74f036ce..c9991168 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -32,6 +32,8 @@ set(SVS_C_API_SOURCES src/error.cpp src/svs_c.cpp + src/dispatcher_vamana.cpp + src/dispatcher_dynamic_vamana.cpp ) add_library(${TARGET_NAME} SHARED @@ -41,6 +43,7 @@ add_library(${TARGET_NAME} SHARED target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src ) find_package(OpenMP REQUIRED) @@ -69,10 +72,75 @@ target_link_libraries(${TARGET_NAME} PRIVATE if (SVS_EXPERIMENTAL_LINK_STATIC_MKL) link_mkl_static(${TARGET_NAME}) endif() -# target_compile_definitions(${TARGET_NAME} PRIVATE -# PUBLIC "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\"" -# PUBLIC "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\"" -# ) + +if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC) + message(STATUS "Enabling LVQ/LeanVec support in C API") + target_compile_definitions(${TARGET_NAME} PRIVATE SVS_RUNTIME_ENABLE_LVQ_LEANVEC) + if(SVS_LVQ_HEADER) + target_compile_definitions(${TARGET_NAME} PRIVATE + SVS_LVQ_HEADER="${SVS_LVQ_HEADER}" + ) + # "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\"" + endif() + if(SVS_LEANVEC_HEADER) + target_compile_definitions(${TARGET_NAME} PRIVATE + SVS_LEANVEC_HEADER="${SVS_LEANVEC_HEADER}" + ) + # "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\"" + endif() + + if (RUNTIME_BINDINGS_PRIVATE_SOURCE_BUILD) + message(STATUS "Building directly from private sources with IVF support") + target_link_libraries(${TARGET_NAME} PRIVATE + svs::svs + svs_compile_options + ) + link_mkl_static(${TARGET_NAME}) + 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 bindings/cpp directory as CMake source root." + ) + 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/nightly/svs-shared-library-lto-nightly-2026-02-05-1017.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/nightly/svs-shared-library-nightly-2026-02-05-1017.tar.gz" + CACHE STRING "URL to download SVS shared library") + endif() + include(FetchContent) + FetchContent_Declare( + svs + URL ${SVS_URL} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + 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() + message(STATUS "LVQ/LeanVec support is disabled in C API") + # 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) diff --git a/bindings/c/src/allocator.hpp b/bindings/c/src/allocator.hpp new file mode 100644 index 00000000..b6859945 --- /dev/null +++ b/bindings/c/src/allocator.hpp @@ -0,0 +1,29 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace svs { +namespace c_runtime { + +template > +using MaybeBlockedAlloc = + std::conditional_t, Allocator>; + +} // namespace c_runtime +} // namespace svs diff --git a/bindings/c/src/data_builder.hpp b/bindings/c/src/data_builder.hpp new file mode 100644 index 00000000..f4dfafd9 --- /dev/null +++ b/bindings/c/src/data_builder.hpp @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "data_builder/leanvec.hpp" +#include "data_builder/lvq.hpp" +#include "data_builder/simple.hpp" +#include "data_builder/sq.hpp" diff --git a/bindings/c/src/data_builder/leanvec.hpp b/bindings/c/src/data_builder/leanvec.hpp new file mode 100644 index 00000000..87588c7e --- /dev/null +++ b/bindings/c/src/data_builder/leanvec.hpp @@ -0,0 +1,124 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + +#include "svs/c_api/svs_c.h" + +#include "storage.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef SVS_LEANVEC_HEADER +#include SVS_LEANVEC_HEADER +#else // SVS_LEANVEC_HEADER not defined +#ifdef SVS_RUNTIME_ENABLE_IVF +#include +#endif +#include +#endif // SVS_LEANVEC_HEADER + +#include +#include + +namespace svs { + +template > +class LeanVecDataBuilder { + size_t leanvec_dims_; + + public: + LeanVecDataBuilder(size_t leanvec_dims) + : leanvec_dims_(leanvec_dims) {} + + using data_type = svs::leanvec::LeanDataset< + svs::leanvec::UsingLVQ, + svs::leanvec::UsingLVQ, + svs::Dynamic, + svs::Dynamic, + Allocator>; + using allocator_type = Allocator; + + template + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::reduce( + view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator + ); + } + + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); + } +}; + +template +struct lib:: + DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = LeanVecDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_LEANVEC) { + auto leanvec = static_cast(from); + if (leanvec->primary_bits == I1 && leanvec->secondary_bits == I2) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From from) { + auto leanvec = static_cast(from); + return To{leanvec->lenavec_dims}; + } +}; + +template void for_leanvec_specializations(F&& f) { + using byte_alloc = svs::c_runtime::MaybeBlockedAlloc; + +#define X(P, S, D) f.template operator(), D>(); +#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) + // Pattern: + // PrimaryBits, SecondaryBits, Distance + XX(4, 4) + XX(4, 8) + XX(8, 8) +#undef XX +#undef X +} + +} // namespace svs + +#else // SVS_RUNTIME_ENABLE_LVQ_LEANVEC not enabled +namespace svs { +// Define empty stubs for LeanVec-related functions when LVQ/LeanVec support is disabled +template void for_leanvec_specializations(F&&) {} +} // namespace svs + +#endif // SVS_RUNTIME_ENABLE_LVQ_LEANVEC diff --git a/bindings/c/src/data_builder/lvq.hpp b/bindings/c/src/data_builder/lvq.hpp new file mode 100644 index 00000000..7a9e8ca5 --- /dev/null +++ b/bindings/c/src/data_builder/lvq.hpp @@ -0,0 +1,120 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + +#include "svs/c_api/svs_c.h" + +#include "storage.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef SVS_LVQ_HEADER +#include SVS_LVQ_HEADER +#else // SVS_LVQ_HEADER not defined +#ifdef SVS_RUNTIME_ENABLE_IVF +#include +#endif +#include +#endif // SVS_LVQ_HEADER + +#include +#include + +namespace svs { + +template < + size_t PrimaryBits, + size_t ResidualBits, + typename Allocator = svs::lib::Allocator> +class LVQDataBuilder { + public: + LVQDataBuilder() {} + + using data_type = svs::quantization::lvq::LVQDataset< + PrimaryBits, + ResidualBits, + svs::Dynamic, + svs::quantization::lvq::Sequential, + Allocator>; + using allocator_type = Allocator; + + template + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::compress(view, pool, 0, allocator); + } + + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); + } +}; + +template +struct lib::DispatchConverter< + const c_runtime::Storage*, + LVQDataBuilder> { + using From = const svs::c_runtime::Storage*; + using To = LVQDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_LVQ) { + auto lvq = static_cast(from); + if (lvq->primary_bits == PrimaryBits && lvq->residual_bits == ResidualBits) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From SVS_UNUSED(from)) { return To{}; } +}; + +template void for_lvq_specializations(F&& f) { + using byte_alloc = svs::c_runtime::MaybeBlockedAlloc; +#define X(P, S, D) f.template operator(), D>(); +#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) + // Pattern: + // PrimaryBits, SecondaryBits, Distance + XX(4, 0) + XX(8, 0) + XX(4, 4) + XX(4, 8) +#undef XX +#undef X +} + +} // namespace svs + +#else // SVS_RUNTIME_ENABLE_LVQ_LEANVEC not enabled +namespace svs { +// Define empty stubs for LVQ-related functions when LVQ/LeanVec support is disabled +template void for_lvq_specializations(F&&) {} +} // namespace svs + +#endif // SVS_RUNTIME_ENABLE_LVQ_LEANVEC diff --git a/bindings/c/src/data_builder/simple.hpp b/bindings/c/src/data_builder/simple.hpp new file mode 100644 index 00000000..36482909 --- /dev/null +++ b/bindings/c/src/data_builder/simple.hpp @@ -0,0 +1,95 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "allocator.hpp" +#include "storage.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace svs { + +template > +class SimpleDataBuilder { + public: + SimpleDataBuilder() {} + + using data_type = svs::data::SimpleData; + using allocator_type = Allocator; + + template + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& SVS_UNUSED(pool), + const allocator_type& allocator = {} + ) { + auto data = data_type(view.size(), view.dimensions(), allocator); + svs::data::copy(view, data); + return data; + } + + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); + } +}; + +template +struct lib::DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = SimpleDataBuilder; + + static int64_t match(From from) { + if constexpr (svs::is_arithmetic_v) { + if (from->kind == SVS_STORAGE_KIND_SIMPLE) { + auto simple = static_cast(from); + if (simple->data_type == svs::datatype_v) { + return svs::lib::perfect_match; + } + } + } + return svs::lib::invalid_match; + } + + static To convert(From SVS_UNUSED(from)) { return To{}; } +}; + +template void for_simple_specializations(F&& f) { + using float_alloc = svs::c_runtime::MaybeBlockedAlloc; + using float16_alloc = svs::c_runtime::MaybeBlockedAlloc; +#define X(T, A, D) f.template operator(), D>(); +#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) + XX(float, float_alloc) + XX(svs::Float16, float16_alloc) +#undef XX +#undef X +} + +} // namespace svs diff --git a/bindings/c/src/data_builder/sq.hpp b/bindings/c/src/data_builder/sq.hpp new file mode 100644 index 00000000..a062ff7b --- /dev/null +++ b/bindings/c/src/data_builder/sq.hpp @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "svs/c_api/svs_c.h" + +#include "allocator.hpp" +#include "storage.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace svs { + +template > class SQDataBuilder { + public: + SQDataBuilder() {} + + using data_type = svs::quantization::scalar::SQDataset; + using allocator_type = Allocator; + + template + data_type build( + svs::data::ConstSimpleDataView view, + svs::threads::ThreadPoolHandle& pool, + const allocator_type& allocator = {} + ) { + return data_type::compress(view, pool, allocator); + } + + data_type + load(const std::filesystem::path& path, const allocator_type& allocator = {}) { + return svs::lib::load_from_disk(path, allocator); + } +}; + +template +struct lib::DispatchConverter> { + using From = const svs::c_runtime::Storage*; + using To = SQDataBuilder; + + static int64_t match(From from) { + if (from->kind == SVS_STORAGE_KIND_SQ) { + auto sq = static_cast(from); + if (sq->data_type == svs::datatype_v) { + return svs::lib::perfect_match; + } + } + return svs::lib::invalid_match; + } + + static To convert(From SVS_UNUSED(from)) { return To{}; } +}; + +template void for_sq_specializations(F&& f) { + using int8_alloc = svs::c_runtime::MaybeBlockedAlloc; + using uint8_alloc = svs::c_runtime::MaybeBlockedAlloc; +#define X(T, A, D) f.template operator(), D>(); +#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) + XX(uint8_t, uint8_alloc) + XX(int8_t, int8_alloc) +#undef XX +#undef X +} + +} // namespace svs diff --git a/bindings/c/src/dispatcher_dynamic_vamana.cpp b/bindings/c/src/dispatcher_dynamic_vamana.cpp new file mode 100644 index 00000000..3d5669fb --- /dev/null +++ b/bindings/c/src/dispatcher_dynamic_vamana.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "dispatcher_dynamic_vamana.hpp" + +#include "algorithm.hpp" +#include "allocator.hpp" +#include "data_builder.hpp" +#include "index.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace svs::c_runtime { + +template +svs::DynamicVamana build_dynamic_vamana_index( + const svs::index::vamana::VamanaBuildParameters& build_params, + std::pair, std::span> src_data, + DataBuilder builder, + Distance D, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + svs::data::BlockingParameters block_params; + if (blocksize_bytes != 0) { + block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); + } + using allocator_type = typename DataBuilder::allocator_type; + auto allocator = allocator_type{block_params}; + auto data = builder.build(std::move(src_data.first), pool, allocator); + return svs::DynamicVamana::build( + build_params, + std::move(data), + std::move(src_data.second), + std::move(D), + std::move(pool) + ); +} + +template +svs::DynamicVamana load_dynamic_vamana_index( + const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), + const std::filesystem::path& directory, + DataLoader loader, + Distance D, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + svs::data::BlockingParameters block_params; + if (blocksize_bytes != 0) { + block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); + } + using allocator_type = typename DataLoader::allocator_type; + auto allocator = allocator_type{block_params}; + auto data = loader.load(directory / "data", allocator); + return svs::DynamicVamana::assemble( + directory / "config", + svs::GraphLoader{directory / "graph"}, + std::move(data), + std::move(D), + std::move(pool) + ); +} + +template +void register_dynamic_vamana_index_specializations(Dispatcher& dispatcher) { + auto build_closure = [&dispatcher]() { + dispatcher.register_target(&build_dynamic_vamana_index); + }; + auto load_closure = [&dispatcher]() { + dispatcher.register_target(&load_dynamic_vamana_index); + }; + + for_simple_specializations(build_closure); + for_simple_specializations(load_closure); + for_leanvec_specializations(build_closure); + for_leanvec_specializations(load_closure); + for_lvq_specializations(build_closure); + for_lvq_specializations(load_closure); + for_sq_specializations(build_closure); + for_sq_specializations(load_closure); +} + +using DynamicVamanaSource = std::variant< + std::pair, std::span>, + std::filesystem::path>; + +using BuildDynamicIndexDispatcher = svs::lib::Dispatcher< + svs::DynamicVamana, + const svs::index::vamana::VamanaBuildParameters&, + DynamicVamanaSource, + const Storage*, + svs::DistanceType, + svs::threads::ThreadPoolHandle, + size_t>; + +const BuildDynamicIndexDispatcher& build_dynamic_vamana_index_dispatcher() { + static BuildDynamicIndexDispatcher dispatcher = [] { + BuildDynamicIndexDispatcher d{}; + register_dynamic_vamana_index_specializations(d); + return d; + }(); + return dispatcher; +} + +svs::DynamicVamana dispatch_dynamic_vamana_index_build( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView data, + std::span ids, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + return build_dynamic_vamana_index_dispatcher().invoke( + build_params, + DynamicVamanaSource{std::make_pair(data, ids)}, + storage, + distance_type, + std::move(pool), + blocksize_bytes + ); +} + +svs::DynamicVamana dispatch_dynamic_vamana_index_load( + const svs::index::vamana::VamanaBuildParameters& build_params, + const std::filesystem::path& directory, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool, + size_t blocksize_bytes +) { + return build_dynamic_vamana_index_dispatcher().invoke( + build_params, + DynamicVamanaSource{directory}, + storage, + distance_type, + std::move(pool), + blocksize_bytes + ); +} +} // namespace svs::c_runtime diff --git a/bindings/c/src/dispatcher_dynamic_vamana.hpp b/bindings/c/src/dispatcher_dynamic_vamana.hpp index 950b9f45..57fee780 100644 --- a/bindings/c/src/dispatcher_dynamic_vamana.hpp +++ b/bindings/c/src/dispatcher_dynamic_vamana.hpp @@ -15,185 +15,38 @@ */ #pragma once -#include "svs/c_api/svs_c.h" - -#include "algorithm.hpp" -#include "index.hpp" #include "storage.hpp" -#include "threadpool.hpp" -#include "types_support.hpp" -#include +#include #include -#include #include -#include +#include #include #include -#include #include #include #include namespace svs::c_runtime { -using DynamicVamanaSource = std::variant< - std::pair, std::span>, - std::filesystem::path>; - -template -svs::DynamicVamana build_dynamic_vamana_index( +svs::DynamicVamana dispatch_dynamic_vamana_index_build( const svs::index::vamana::VamanaBuildParameters& build_params, - std::pair, std::span> src_data, - DataBuilder builder, - Distance D, - svs::threads::ThreadPoolHandle pool, - size_t blocksize_bytes -) { - svs::data::BlockingParameters block_params; - if (blocksize_bytes != 0) { - block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); - } - using allocator_type = typename DataBuilder::allocator_type; - auto allocator = allocator_type{block_params}; - auto data = builder.build(std::move(src_data.first), pool, allocator); - return svs::DynamicVamana::build( - build_params, - std::move(data), - std::move(src_data.second), - std::move(D), - std::move(pool) - ); -} - -template -svs::DynamicVamana load_dynamic_vamana_index( - const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), - const std::filesystem::path& directory, - DataLoader loader, - Distance D, + svs::data::ConstSimpleDataView data, + std::span ids, + const Storage* storage, + svs::DistanceType distance_type, svs::threads::ThreadPoolHandle pool, size_t blocksize_bytes -) { - svs::data::BlockingParameters block_params; - if (blocksize_bytes != 0) { - block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes); - } - using allocator_type = typename DataLoader::allocator_type; - auto allocator = allocator_type{block_params}; - auto data = loader.load(directory / "data", allocator); - return svs::DynamicVamana::assemble( - directory / "config", - svs::GraphLoader{directory / "graph"}, - std::move(data), - std::move(D), - std::move(pool) - ); -} - -template void for_simple_specializations(F&& f) { -#define X(T, A, D) f.template operator(), D>(); -#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) -#define XXX(T) XX(T, svs::data::Blocked>) - XXX(float) - XXX(svs::Float16) -#undef XXX -#undef XX -#undef X -} - -template void for_leanvec_specializations(F&& f) { - using byte_alloc = svs::data::Blocked>; +); -#define X(P, S, D) f.template operator(), D>(); -#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) - // Pattern: - // PrimaryBits, SecondaryBits, Distance - XX(4, 4) - XX(4, 8) - XX(8, 8) -#undef XX -#undef X -} - -template void for_lvq_specializations(F&& f) { - using byte_alloc = svs::data::Blocked>; -#define X(P, S, D) f.template operator(), D>(); -#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity) - // Pattern: - // PrimaryBits, SecondaryBits, Distance - XX(4, 0) - XX(8, 0) - XX(4, 4) - XX(4, 8) -#undef XX -#undef X -} - -template void for_sq_specializations(F&& f) { -#define X(T, A, D) f.template operator(), D>(); -#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity) -#define XXX(T) XX(T, svs::data::Blocked>) - XXX(uint8_t) - XXX(int8_t) -#undef XXX -#undef XX -#undef X -} - -template -void register_dynamic_vamana_index_specializations(Dispatcher& dispatcher) { - auto build_closure = [&dispatcher]() { - dispatcher.register_target(&build_dynamic_vamana_index); - }; - auto load_closure = [&dispatcher]() { - dispatcher.register_target(&load_dynamic_vamana_index); - }; - - for_simple_specializations(build_closure); - for_simple_specializations(load_closure); - for_leanvec_specializations(build_closure); - for_leanvec_specializations(load_closure); - for_lvq_specializations(build_closure); - for_lvq_specializations(load_closure); - for_sq_specializations(build_closure); - for_sq_specializations(load_closure); -} - -using BuildDynamicIndexDispatcher = svs::lib::Dispatcher< - svs::DynamicVamana, - const svs::index::vamana::VamanaBuildParameters&, - DynamicVamanaSource, - const Storage*, - svs::DistanceType, - svs::threads::ThreadPoolHandle, - size_t>; - -const BuildDynamicIndexDispatcher& build_dynamic_vamana_index_dispatcher() { - static BuildDynamicIndexDispatcher dispatcher = [] { - BuildDynamicIndexDispatcher d{}; - register_dynamic_vamana_index_specializations(d); - return d; - }(); - return dispatcher; -} - -svs::DynamicVamana dispatch_dynamic_vamana_index_build( +svs::DynamicVamana dispatch_dynamic_vamana_index_load( const svs::index::vamana::VamanaBuildParameters& build_params, - DynamicVamanaSource src_data, + const std::filesystem::path& directory, const Storage* storage, svs::DistanceType distance_type, svs::threads::ThreadPoolHandle pool, size_t blocksize_bytes -) { - return build_dynamic_vamana_index_dispatcher().invoke( - build_params, - std::move(src_data), - storage, - distance_type, - std::move(pool), - blocksize_bytes - ); -} -} // namespace svs::c_runtime +); + +} // namespace svs::c_runtime \ No newline at end of file diff --git a/bindings/c/src/dispatcher_vamana.cpp b/bindings/c/src/dispatcher_vamana.cpp new file mode 100644 index 00000000..1c9b873b --- /dev/null +++ b/bindings/c/src/dispatcher_vamana.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2026 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "dispatcher_vamana.hpp" + +#include "algorithm.hpp" +#include "data_builder.hpp" +#include "index.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace svs::c_runtime { + +template +svs::Vamana build_vamana_index( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView src_data, + DataBuilder builder, + Distance distance, + svs::threads::ThreadPoolHandle pool +) { + auto data = builder.build(std::move(src_data), pool); + return svs::Vamana::build( + build_params, std::move(data), distance, std::move(pool) + ); +} + +template +svs::Vamana load_vamana_index( + const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), + const std::filesystem::path& directory, + DataLoader loader, + Distance distance, + svs::threads::ThreadPoolHandle pool +) { + auto data = loader.load(directory / "data"); + return svs::Vamana::assemble( + directory / "config", + svs::GraphLoader{directory / "graph"}, + std::move(data), + distance, + std::move(pool) + ); +} + +template +void register_vamana_index_specializations(Dispatcher& dispatcher) { + auto build_closure = [&dispatcher]() { + dispatcher.register_target(&build_vamana_index); + }; + auto load_closure = [&dispatcher]() { + dispatcher.register_target(&load_vamana_index); + }; + + for_simple_specializations(build_closure); + for_simple_specializations(load_closure); + for_leanvec_specializations(build_closure); + for_leanvec_specializations(load_closure); + for_lvq_specializations(build_closure); + for_lvq_specializations(load_closure); + for_sq_specializations(build_closure); + for_sq_specializations(load_closure); +} + +using VamanaSource = + std::variant, std::filesystem::path>; + +using BuildIndexDispatcher = svs::lib::Dispatcher< + svs::Vamana, + const svs::index::vamana::VamanaBuildParameters&, + VamanaSource, + const Storage*, + svs::DistanceType, + svs::threads::ThreadPoolHandle>; + +const BuildIndexDispatcher& build_vamana_index_dispatcher() { + static BuildIndexDispatcher dispatcher = [] { + BuildIndexDispatcher d{}; + register_vamana_index_specializations(d); + return d; + }(); + return dispatcher; +} + +svs::Vamana dispatch_vamana_index_build( + const svs::index::vamana::VamanaBuildParameters& build_params, + svs::data::ConstSimpleDataView data, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + return build_vamana_index_dispatcher().invoke( + build_params, VamanaSource{std::move(data)}, storage, distance_type, std::move(pool) + ); +} + +svs::Vamana dispatch_vamana_index_load( + const svs::index::vamana::VamanaBuildParameters& build_params, + const std::filesystem::path& directory, + const Storage* storage, + svs::DistanceType distance_type, + svs::threads::ThreadPoolHandle pool +) { + return build_vamana_index_dispatcher().invoke( + build_params, VamanaSource{directory}, storage, distance_type, std::move(pool) + ); +} +} // namespace svs::c_runtime diff --git a/bindings/c/src/dispatcher_vamana.hpp b/bindings/c/src/dispatcher_vamana.hpp index bb30b76d..90174dfe 100644 --- a/bindings/c/src/dispatcher_vamana.hpp +++ b/bindings/c/src/dispatcher_vamana.hpp @@ -15,125 +15,33 @@ */ #pragma once -#include "svs/c_api/svs_c.h" - -#include "algorithm.hpp" -#include "index.hpp" #include "storage.hpp" #include "threadpool.hpp" -#include "types_support.hpp" #include +#include #include -#include #include -#include +#include #include #include -#include -#include namespace svs::c_runtime { - -using VamanaSource = - std::variant, std::filesystem::path>; - -template -svs::Vamana build_vamana_index( +svs::Vamana dispatch_vamana_index_build( const svs::index::vamana::VamanaBuildParameters& build_params, - svs::data::ConstSimpleDataView src_data, - DataBuilder builder, - svs::DistanceType distance_type, - svs::threads::ThreadPoolHandle pool -) { - auto data = builder.build(std::move(src_data), pool); - return svs::Vamana::build( - build_params, std::move(data), distance_type, std::move(pool) - ); -} - -template -svs::Vamana load_vamana_index( - const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params), - const std::filesystem::path& directory, - DataLoader loader, + svs::data::ConstSimpleDataView data, + const Storage* storage, svs::DistanceType distance_type, svs::threads::ThreadPoolHandle pool -) { - auto data = loader.load(directory / "data"); - return svs::Vamana::assemble( - directory / "config", - svs::GraphLoader{directory / "graph"}, - std::move(data), - distance_type, - std::move(pool) - ); -} - -template -void register_build_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); +); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); - - dispatcher.register_target(&build_vamana_index>); - dispatcher.register_target(&build_vamana_index>); -} - -template -void register_load_vamana_index_methods(Dispatcher& dispatcher) { - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); - - dispatcher.register_target(&load_vamana_index>); - dispatcher.register_target(&load_vamana_index>); -} - -using BuildIndexDispatcher = svs::lib::Dispatcher< - svs::Vamana, - const svs::index::vamana::VamanaBuildParameters&, - VamanaSource, - const Storage*, - svs::DistanceType, - svs::threads::ThreadPoolHandle>; - -const BuildIndexDispatcher& build_vamana_index_dispatcher() { - static BuildIndexDispatcher dispatcher = [] { - BuildIndexDispatcher d{}; - register_build_vamana_index_methods(d); - register_load_vamana_index_methods(d); - return d; - }(); - return dispatcher; -} - -svs::Vamana dispatch_vamana_index_build( +svs::Vamana dispatch_vamana_index_load( const svs::index::vamana::VamanaBuildParameters& build_params, - VamanaSource src_data, + const std::filesystem::path& directory, const Storage* storage, svs::DistanceType distance_type, svs::threads::ThreadPoolHandle pool -) { - return build_vamana_index_dispatcher().invoke( - build_params, std::move(src_data), storage, distance_type, std::move(pool) - ); -} +); + } // namespace svs::c_runtime diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index 0ca4338c..42547c1d 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -82,7 +82,7 @@ struct IndexVamana : public Index { } index.search(results.view(), queries, params); - return std::move(results); + return results; } void save(const std::filesystem::path& directory) override { @@ -123,7 +123,7 @@ struct DynamicIndexVamana : public DynamicIndex { } index.search(results.view(), queries, params); - return std::move(results); + return results; } void save(const std::filesystem::path& directory) override { diff --git a/bindings/c/src/index_builder.hpp b/bindings/c/src/index_builder.hpp index e17c6372..288d9efa 100644 --- a/bindings/c/src/index_builder.hpp +++ b/bindings/c/src/index_builder.hpp @@ -71,7 +71,7 @@ struct IndexBuilder { auto index = std::make_shared(dispatch_vamana_index_build( vamana_algorithm->build_parameters(), - VamanaSource{data}, + data, storage.get(), to_distance_type(distance_metric), pool_builder.build() @@ -86,9 +86,9 @@ struct IndexBuilder { if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { auto vamana_algorithm = std::static_pointer_cast(algorithm); - auto index = std::make_shared(dispatch_vamana_index_build( + auto index = std::make_shared(dispatch_vamana_index_load( vamana_algorithm->build_parameters(), - VamanaSource{directory}, + directory, storage.get(), to_distance_type(distance_metric), pool_builder.build() @@ -110,7 +110,8 @@ struct IndexBuilder { auto index = std::make_shared(dispatch_dynamic_vamana_index_build( vamana_algorithm->build_parameters(), - DynamicVamanaSource{std::make_pair(data, ids)}, + data, + ids, storage.get(), to_distance_type(distance_metric), pool_builder.build(), @@ -128,9 +129,9 @@ struct IndexBuilder { auto vamana_algorithm = std::static_pointer_cast(algorithm); auto index = - std::make_shared(dispatch_dynamic_vamana_index_build( + std::make_shared(dispatch_dynamic_vamana_index_load( vamana_algorithm->build_parameters(), - DynamicVamanaSource{directory}, + directory, storage.get(), to_distance_type(distance_metric), pool_builder.build(), diff --git a/bindings/c/src/storage.hpp b/bindings/c/src/storage.hpp index 5395c44c..b35927ca 100644 --- a/bindings/c/src/storage.hpp +++ b/bindings/c/src/storage.hpp @@ -17,20 +17,16 @@ #include "svs/c_api/svs_c.h" +#include "allocator.hpp" +#include "error.hpp" #include "types_support.hpp" -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include + +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC +#include +#endif #include #include @@ -68,7 +64,19 @@ struct StorageLeanVec : public Storage { : Storage{SVS_STORAGE_KIND_LEANVEC} , lenavec_dims(lenavec_dims) , primary_bits(to_bits_number(primary)) - , secondary_bits(to_bits_number(secondary)) {} + , secondary_bits(to_bits_number(secondary)) { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + if (!enabled()) { + throw svs::c_runtime::unsupported_hw( + "LeanVec storage is not supported on this hardware" + ); + } +#else + throw svs::c_runtime::not_implemented( + "LeanVec storage is not implemented in this build" + ); +#endif + } static size_t to_bits_number(svs_data_type_t data_type) { switch (data_type) { @@ -84,6 +92,14 @@ struct StorageLeanVec : public Storage { throw std::invalid_argument("Unsupported data type for LeanVec storage"); } } + + static bool enabled() { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + return svs::detail::intel_enabled(); +#else + return false; +#endif + } }; struct StorageLVQ : public Storage { @@ -93,7 +109,18 @@ struct StorageLVQ : public Storage { StorageLVQ(svs_data_type_t primary, svs_data_type_t residual) : Storage{SVS_STORAGE_KIND_LVQ} , primary_bits(to_bits_number(primary)) - , residual_bits(to_bits_number(residual)) {} + , residual_bits(to_bits_number(residual)) { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + if (!enabled()) { + throw svs::c_runtime::unsupported_hw( + "LVQ storage is not supported on this hardware" + ); + } +#else + throw svs::c_runtime::not_implemented("LVQ storage is not implemented in this build" + ); +#endif + } static size_t to_bits_number(svs_data_type_t data_type) { switch (data_type) { @@ -109,6 +136,14 @@ struct StorageLVQ : public Storage { throw std::invalid_argument("Unsupported data type for LVQ storage"); } } + + static bool enabled() { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + return svs::detail::intel_enabled(); +#else + return false; +#endif + } }; struct StorageSQ : public Storage { @@ -125,196 +160,4 @@ struct StorageSQ : public Storage { }; } // namespace c_runtime - -template > -class SimpleDataBuilder { - public: - SimpleDataBuilder() {} - - using data_type = svs::data::SimpleData; - using allocator_type = Allocator; - - template - data_type build( - svs::data::ConstSimpleDataView view, - svs::threads::ThreadPoolHandle& SVS_UNUSED(pool), - const allocator_type& allocator = {} - ) { - auto data = data_type(view.size(), view.dimensions(), allocator); - svs::data::copy(view, data); - return data; - } - - data_type - load(const std::filesystem::path& path, const allocator_type& allocator = {}) { - return svs::lib::load_from_disk(path, allocator); - } -}; - -template -struct lib::DispatchConverter> { - using From = const svs::c_runtime::Storage*; - using To = SimpleDataBuilder; - - static int64_t match(From from) { - if constexpr (svs::is_arithmetic_v) { - if (from->kind == SVS_STORAGE_KIND_SIMPLE) { - auto simple = static_cast(from); - if (simple->data_type == svs::datatype_v) { - return svs::lib::perfect_match; - } - } - } - return svs::lib::invalid_match; - } - - static To convert(From from) { return To{}; } -}; - -template > -class LeanVecDataBuilder { - size_t leanvec_dims_; - - public: - LeanVecDataBuilder(size_t leanvec_dims) - : leanvec_dims_(leanvec_dims) {} - - using data_type = svs::leanvec::LeanDataset< - svs::leanvec::UsingLVQ, - svs::leanvec::UsingLVQ, - svs::Dynamic, - svs::Dynamic, - Allocator>; - using allocator_type = Allocator; - - template - data_type build( - svs::data::ConstSimpleDataView view, - svs::threads::ThreadPoolHandle& pool, - const allocator_type& allocator = {} - ) { - return data_type::reduce( - view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator - ); - } - - data_type - load(const std::filesystem::path& path, const allocator_type& allocator = {}) { - return svs::lib::load_from_disk(path, allocator); - } -}; - -template -struct lib:: - DispatchConverter> { - using From = const svs::c_runtime::Storage*; - using To = LeanVecDataBuilder; - - static int64_t match(From from) { - if (from->kind == SVS_STORAGE_KIND_LEANVEC) { - auto leanvec = static_cast(from); - if (leanvec->primary_bits == I1 && leanvec->secondary_bits == I2) { - return svs::lib::perfect_match; - } - } - return svs::lib::invalid_match; - } - - static To convert(From from) { - auto leanvec = static_cast(from); - return To{leanvec->lenavec_dims}; - } -}; - -template < - size_t PrimaryBits, - size_t ResidualBits, - typename Allocator = svs::lib::Allocator> -class LVQDataBuilder { - public: - LVQDataBuilder() {} - - using data_type = svs::quantization::lvq::LVQDataset< - PrimaryBits, - ResidualBits, - svs::Dynamic, - svs::quantization::lvq::Sequential, - Allocator>; - using allocator_type = Allocator; - - template - data_type build( - svs::data::ConstSimpleDataView view, - svs::threads::ThreadPoolHandle& pool, - const allocator_type& allocator = {} - ) { - return data_type::compress(view, pool, 0, allocator); - } - - data_type - load(const std::filesystem::path& path, const allocator_type& allocator = {}) { - return svs::lib::load_from_disk(path, allocator); - } -}; - -template -struct lib::DispatchConverter< - const c_runtime::Storage*, - LVQDataBuilder> { - using From = const svs::c_runtime::Storage*; - using To = LVQDataBuilder; - - static int64_t match(From from) { - if (from->kind == SVS_STORAGE_KIND_LVQ) { - auto lvq = static_cast(from); - if (lvq->primary_bits == PrimaryBits && lvq->residual_bits == ResidualBits) { - return svs::lib::perfect_match; - } - } - return svs::lib::invalid_match; - } - - static To convert(From from) { return To{}; } -}; - -template > class SQDataBuilder { - public: - SQDataBuilder() {} - - using data_type = svs::quantization::scalar::SQDataset; - using allocator_type = Allocator; - - template - data_type build( - svs::data::ConstSimpleDataView view, - svs::threads::ThreadPoolHandle& pool, - const allocator_type& allocator = {} - ) { - return data_type::compress(view, pool, allocator); - } - - data_type - load(const std::filesystem::path& path, const allocator_type& allocator = {}) { - return svs::lib::load_from_disk(path, allocator); - } -}; - -template -struct lib::DispatchConverter> { - using From = const svs::c_runtime::Storage*; - using To = SQDataBuilder; - - static int64_t match(From from) { - if (from->kind == SVS_STORAGE_KIND_SQ) { - auto sq = static_cast(from); - if (sq->data_type == svs::datatype_v) { - return svs::lib::perfect_match; - } - } - return svs::lib::invalid_match; - } - - static To convert(From from) { return To{}; } -}; - } // namespace svs diff --git a/bindings/c/src/types_support.hpp b/bindings/c/src/types_support.hpp index 5d8ca6b8..b3f9b38b 100644 --- a/bindings/c/src/types_support.hpp +++ b/bindings/c/src/types_support.hpp @@ -23,7 +23,7 @@ namespace svs { namespace c_runtime { -svs::DistanceType to_distance_type(svs_distance_metric_t distance_metric) { +inline svs::DistanceType to_distance_type(svs_distance_metric_t distance_metric) { switch (distance_metric) { case SVS_DISTANCE_METRIC_EUCLIDEAN: return svs::DistanceType::L2; @@ -36,7 +36,7 @@ svs::DistanceType to_distance_type(svs_distance_metric_t distance_metric) { } } -svs::DataType to_data_type(svs_data_type_t data_type) { +inline svs::DataType to_data_type(svs_data_type_t data_type) { switch (data_type) { case SVS_DATA_TYPE_FLOAT32: return svs::DataType::float32; From 04bbff0ae24abbb2d4cdeb5a61379d8addeb00a9 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Tue, 10 Mar 2026 12:14:56 +0100 Subject: [PATCH 18/18] Apply formatting via pre-commit hook --- bindings/SVS_C_API_Design.md | 30 ++++++++++---------- bindings/c/samples/dynamic.c | 2 +- bindings/c/samples/save_load.c | 2 +- bindings/c/samples/simple.c | 2 +- bindings/c/src/algorithm.hpp | 2 +- bindings/c/src/dispatcher_dynamic_vamana.hpp | 2 +- bindings/c/src/svs_c.cpp | 3 +- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md index 9d58a6db..a7598049 100644 --- a/bindings/SVS_C_API_Design.md +++ b/bindings/SVS_C_API_Design.md @@ -551,7 +551,7 @@ void svs_search_results_free(svs_search_results_t results); int main() { // 1. Create error handle for diagnostics svs_error_h err = svs_error_create(); - + // 2. Create Vamana algorithm configuration svs_algorithm_h algo = svs_algorithm_create_vamana( 64, // graph_degree @@ -560,12 +560,12 @@ int main() { err ); if (!algo || !svs_error_ok(err)) { - fprintf(stderr, "Algorithm creation failed: %s\n", + fprintf(stderr, "Algorithm creation failed: %s\n", svs_error_get_message(err)); svs_error_free(err); return 1; } - + // 3. Create index builder size_t dimensions = 128; svs_index_builder_h builder = svs_index_builder_create( @@ -574,13 +574,13 @@ int main() { algo, err ); - + // 4. Optional: Configure storage (default is FP32) svs_storage_h storage = svs_storage_create_simple( SVS_DATA_TYPE_FLOAT32, err ); svs_index_builder_set_storage(builder, storage, err); - + // 5. Optional: Configure thread pool svs_index_builder_set_threadpool( builder, @@ -588,38 +588,38 @@ int main() { 8, // num_threads err ); - + // 6. Prepare data size_t num_vectors = 10000; float* data = (float*)malloc(num_vectors * dimensions * sizeof(float)); // ... fill data with vectors ... - + // 7. Build index svs_index_h index = svs_index_build(builder, data, num_vectors, err); if (!index || !svs_error_ok(err)) { - fprintf(stderr, "Index build failed: %s\n", + fprintf(stderr, "Index build failed: %s\n", svs_error_get_message(err)); goto cleanup; } - + // 8. Prepare queries size_t num_queries = 10; float* queries = (float*)malloc(num_queries * dimensions * sizeof(float)); // ... fill queries ... - + // 9. Perform search with default parameters size_t k = 5; svs_search_results_t results = svs_index_search( index, queries, num_queries, k, NULL, err ); - + // Or with custom search parameters: // svs_search_params_h params = svs_search_params_create_vamana(100, err); // svs_search_results_t results = svs_index_search( // index, queries, num_queries, k, params, err // ); // svs_search_params_free(params); - + // 10. Process results if (results && svs_error_ok(err)) { for (size_t i = 0; i < results->num_queries; i++) { @@ -632,7 +632,7 @@ int main() { } svs_search_results_free(results); } - + // 11. Cleanup cleanup: if (index) svs_index_free(index); @@ -640,10 +640,10 @@ cleanup: if (storage) svs_storage_free(storage); if (algo) svs_algorithm_free(algo); svs_error_free(err); - + free(data); free(queries); - + return 0; } ``` diff --git a/bindings/c/samples/dynamic.c b/bindings/c/samples/dynamic.c index 794cfbfc..c205257f 100644 --- a/bindings/c/samples/dynamic.c +++ b/bindings/c/samples/dynamic.c @@ -381,4 +381,4 @@ int main() { svs_error_free(error); return ret; -} \ No newline at end of file +} diff --git a/bindings/c/samples/save_load.c b/bindings/c/samples/save_load.c index f33fd271..c6ee30d1 100644 --- a/bindings/c/samples/save_load.c +++ b/bindings/c/samples/save_load.c @@ -258,4 +258,4 @@ int main() { svs_error_free(error); return ret; -} \ No newline at end of file +} diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c index 4b245cdc..acc23a1d 100644 --- a/bindings/c/samples/simple.c +++ b/bindings/c/samples/simple.c @@ -194,4 +194,4 @@ int main() { svs_error_free(error); return ret; -} \ No newline at end of file +} diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp index 83694b24..127a7902 100644 --- a/bindings/c/src/algorithm.hpp +++ b/bindings/c/src/algorithm.hpp @@ -87,4 +87,4 @@ struct AlgorithmVamana : public Algorithm { } }; -} // namespace svs::c_runtime \ No newline at end of file +} // namespace svs::c_runtime diff --git a/bindings/c/src/dispatcher_dynamic_vamana.hpp b/bindings/c/src/dispatcher_dynamic_vamana.hpp index 57fee780..41ac71da 100644 --- a/bindings/c/src/dispatcher_dynamic_vamana.hpp +++ b/bindings/c/src/dispatcher_dynamic_vamana.hpp @@ -49,4 +49,4 @@ svs::DynamicVamana dispatch_dynamic_vamana_index_load( size_t blocksize_bytes ); -} // namespace svs::c_runtime \ No newline at end of file +} // namespace svs::c_runtime diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 0c6290c1..4f48a802 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -712,7 +712,8 @@ extern "C" bool svs_index_get_distance( EXPECT_ARG_NOT_NULL(out_distance); auto& index_ptr = index->impl; INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); - *out_distance = index_ptr->get_distance(id, std::span{query, index_ptr->dimensions()}); + *out_distance = + index_ptr->get_distance(id, std::span{query, index_ptr->dimensions()}); return true; }, out_err,