diff --git a/bindings/SVS_C_API_Design.md b/bindings/SVS_C_API_Design.md new file mode 100644 index 00000000..a7598049 --- /dev/null +++ b/bindings/SVS_C_API_Design.md @@ -0,0 +1,657 @@ +# SVS C API Design Proposal + +## Overview + +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). + +### Design Goals + +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_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/CMakeLists.txt b/bindings/c/CMakeLists.txt new file mode 100644 index 00000000..c9991168 --- /dev/null +++ b/bindings/c/CMakeLists.txt @@ -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. + +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_config.h + include/svs/c_api/svs_c.h +) + +set(SVS_C_API_SOURCES + src/algorithm.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 + src/dispatcher_vamana.cpp + src/dispatcher_dynamic_vamana.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 + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +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 +) +if (SVS_EXPERIMENTAL_LINK_STATIC_MKL) + link_mkl_static(${TARGET_NAME}) +endif() + +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) + +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_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/c_api + COMPONENT ${SVS_C_API_COMPONENT_NAME} + DESTINATION include/svs + FILES_MATCHING PATTERN "*.h" +) + +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}/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 +) + +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) +# add_subdirectory(tests) +# endif() + +add_subdirectory(samples) 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 new file mode 100644 index 00000000..65cf7fc4 --- /dev/null +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -0,0 +1,506 @@ +/* + * 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_config.h" + +#ifdef __cplusplus +extern "C" { +#endif +#include +#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_NOT_IMPLEMENTED = 5, + SVS_ERROR_UNSUPPORTED_HW = 6, + SVS_ERROR_RUNTIME = 7, + SVS_ERROR_UNKNOWN = 1000 +}; + +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_VOID = 0, + SVS_DATA_TYPE_FLOAT32 = 32, + SVS_DATA_TYPE_FLOAT16 = 16, + 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 { + SVS_STORAGE_KIND_SIMPLE = 0, + SVS_STORAGE_KIND_LEANVEC = 1, + SVS_STORAGE_KIND_LVQ = 2, + SVS_STORAGE_KIND_SQ = 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; /// 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 +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_threadpool_kind svs_threadpool_kind_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_create(); + +/// @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 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 +/// @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 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 leanvec_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*/ +); + +/// @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_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_i 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 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 +/// @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 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); + +/// @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, + size_t num_queries, + size_t k, + 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); + +/// @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*/); + +/// @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/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 diff --git a/bindings/c/samples/CMakeLists.txt b/bindings/c/samples/CMakeLists.txt new file mode 100644 index 00000000..f758504e --- /dev/null +++ b/bindings/c/samples/CMakeLists.txt @@ -0,0 +1,27 @@ +# 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. + +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) + + target_include_directories(${SAMPLE_TARGET} PRIVATE + ${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..c205257f --- /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; +} diff --git a/bindings/c/samples/save_load.c b/bindings/c/samples/save_load.c new file mode 100644 index 00000000..c6ee30d1 --- /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; +} diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c new file mode 100644 index 00000000..acc23a1d --- /dev/null +++ b/bindings/c/samples/simple.c @@ -0,0 +1,197 @@ +#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; + } +} + +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; + 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; + + // 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; + } + + // 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); + 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 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, 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]; + } + + 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); + svs_error_free(error); + + return ret; +} diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp new file mode 100644 index 00000000..127a7902 --- /dev/null +++ b/bindings/c/src/algorithm.hpp @@ -0,0 +1,90 @@ +/* + * 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; + virtual void set_default_search_params(const std::shared_ptr& params) = 0; +}; + +struct AlgorithmVamana : public Algorithm { + struct SearchParams : public Algorithm::SearchParams { + size_t search_window_size; + 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 { + svs::index::vamana::VamanaSearchParameters params; + params.buffer_config_ = + svs::index::vamana::SearchBufferConfig{search_window_size}; + return params; + } + }; + + 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} + , 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; } + + 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 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 new file mode 100644 index 00000000..41ac71da --- /dev/null +++ b/bindings/c/src/dispatcher_dynamic_vamana.hpp @@ -0,0 +1,52 @@ +/* + * 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 "storage.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace svs::c_runtime { + +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 +); + +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 +); + +} // namespace svs::c_runtime 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 new file mode 100644 index 00000000..90174dfe --- /dev/null +++ b/bindings/c/src/dispatcher_vamana.hpp @@ -0,0 +1,47 @@ +/* + * 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 "storage.hpp" +#include "threadpool.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace svs::c_runtime { +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 +); + +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 +); + +} // namespace svs::c_runtime diff --git a/bindings/c/src/error.cpp b/bindings/c/src/error.cpp new file mode 100644 index 00000000..f3d845f7 --- /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_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) { + 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/index.hpp b/bindings/c/src/index.hpp new file mode 100644 index 00000000..42547c1d --- /dev/null +++ b/bindings/c/src/index.hpp @@ -0,0 +1,174 @@ +/* + * 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 + +#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; + 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 { + svs::Vamana index; + IndexVamana(svs::Vamana&& index) + : Index{SVS_ALGORITHM_TYPE_VAMANA} + , index(std::move(index)) {} + ~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); + + auto params = index.get_search_parameters(); + if (vamana_search_params) { + params = vamana_search_params->get_search_parameters(); + } + + index.search(results.view(), queries, params); + return 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(); } + + 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 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 new file mode 100644 index 00000000..288d9efa --- /dev/null +++ b/bindings/c/src/index_builder.hpp @@ -0,0 +1,146 @@ +/* + * 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 "dispatcher_dynamic_vamana.hpp" +#include "dispatcher_vamana.hpp" +#include "index.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#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; + ThreadPoolBuilder pool_builder; + + 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)) + , pool_builder{} {} + + ~IndexBuilder() {} + + void set_storage(std::shared_ptr storage) { + this->storage = std::move(storage); + } + + void set_threadpool_builder(ThreadPoolBuilder threadpool_builder) { + std::swap(this->pool_builder, threadpool_builder); + } + + std::shared_ptr build(const svs::data::ConstSimpleDataView& data) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + auto index = std::make_shared(dispatch_vamana_index_build( + vamana_algorithm->build_parameters(), + data, + storage.get(), + to_distance_type(distance_metric), + pool_builder.build() + )); + + return index; + } + return nullptr; + } + + std::shared_ptr load(const std::filesystem::path& directory) { + if (algorithm->type == SVS_ALGORITHM_TYPE_VAMANA) { + auto vamana_algorithm = std::static_pointer_cast(algorithm); + + auto index = std::make_shared(dispatch_vamana_index_load( + vamana_algorithm->build_parameters(), + directory, + storage.get(), + to_distance_type(distance_metric), + pool_builder.build() + )); + + return index; + } + 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(), + 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_load( + vamana_algorithm->build_parameters(), + 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 new file mode 100644 index 00000000..b35927ca --- /dev/null +++ b/bindings/c/src/storage.hpp @@ -0,0 +1,163 @@ +/* + * 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 "error.hpp" +#include "types_support.hpp" + +#include +#include + +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC +#include +#endif + +#include +#include + +namespace svs { +namespace c_runtime { + +struct Storage { + svs_storage_kind kind; + Storage(svs_storage_kind kind) + : kind(kind) {} + virtual ~Storage() = default; +}; + +struct StorageSimple : public Storage { + svs::DataType data_type; + + StorageSimple(svs_data_type_t dt) + : Storage{SVS_STORAGE_KIND_SIMPLE} + , 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 { + size_t lenavec_dims; + 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_bits(to_bits_number(primary)) + , 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) { + 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 LeanVec storage"); + } + } + + static bool enabled() { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + return svs::detail::intel_enabled(); +#else + return false; +#endif + } +}; + +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)) { +#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) { + 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"); + } + } + + static bool enabled() { +#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC + return svs::detail::intel_enabled(); +#else + return false; +#endif + } +}; + +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" + ); + } + } +}; + +} // namespace c_runtime +} // namespace svs diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp new file mode 100644 index 00000000..4f48a802 --- /dev/null +++ b/bindings/c/src/svs_c.cpp @@ -0,0 +1,789 @@ +/* + * 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 "algorithm.hpp" +#include "error.hpp" +#include "index.hpp" +#include "index_builder.hpp" +#include "storage.hpp" +#include "threadpool.hpp" +#include "types_support.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +// C API implementation +struct svs_index { + std::shared_ptr impl; +}; + +struct svs_index_builder { + std::shared_ptr impl; +}; + +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_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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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 + ); + auto result = new svs_algorithm; + result->impl = algorithm; + return result; + }, + out_err + ); +} + +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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + return true; + }, + out_err + ); +} + +extern "C" bool svs_algorithm_vamana_set_alpha( + svs_algorithm_h algorithm, float alpha, svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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 = + 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 +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_VAMANA(algorithm); + 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) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_GT_THAN(search_window_size, 0); + 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_h +svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + return result; + }, + out_err + ); +} + +extern "C" svs_storage_h svs_storage_create_leanvec( + size_t leanvec_dims, + svs_data_type_t primary, + svs_data_type_t secondary, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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(leanvec_dims, primary, secondary); + auto result = new svs_storage; + result->impl = storage; + return result; + }, + out_err + ); +} + +extern "C" svs_storage_h svs_storage_create_lvq( + svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + return result; + }, + out_err + ); +} + +extern "C" svs_storage_h +svs_storage_create_sq(svs_data_type_t data_type, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + 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( + svs_distance_metric_t metric, + size_t dimension, + svs_algorithm_h algorithm, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + 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; + result->impl = builder; + return result; + }, + out_err + ); +} + +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_h builder, svs_storage_h storage, svs_error_h out_err +) { + 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; + }, + out_err + ); +} + +extern "C" bool svs_index_builder_set_threadpool( + svs_index_builder_h builder, + svs_threadpool_kind_t kind, + size_t num_threads, + svs_error_h out_err +) { + if (builder == nullptr) { + SET_ERROR(out_err, SVS_ERROR_INVALID_ARGUMENT, "Invalid argument"); + return false; + } + 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; + }, + out_err + ); +} + +extern "C" bool svs_index_builder_set_threadpool_custom( + svs_index_builder_h builder, svs_threadpool_i pool, svs_error_h out_err /*=NULL*/ +) { + 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; + }, + 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 +) { + 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 index building" + ); + auto src_data = svs::data::ConstSimpleDataView( + data, num_vectors, builder->impl->dimension + ); + + auto index = builder->impl->build(src_data); + if (index == nullptr) { + SET_ERROR(out_err, SVS_ERROR_RUNTIME, "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_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; + 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( + svs_index_h index, + const float* queries, + size_t num_queries, + size_t k, + svs_search_params_h search_params, + svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(queries); + EXPECT_ARG_GT_THAN(num_queries, 0); + EXPECT_ARG_GT_THAN(k, 0); + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + + auto queries_view = svs::data::ConstSimpleDataView( + queries, num_queries, index_ptr->dimensions() + ); + + auto search_results = index_ptr->search( + queries_view, k, search_params == nullptr ? nullptr : search_params->impl + ); + + 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] = search_results.index(i, j); + results->distances[i * k + j] = search_results.distance(i, j); + } + } + + return results; + }, + out_err + ); +} + +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; +} + +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 + ); +} + +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 + ); +} diff --git a/bindings/c/src/threadpool.hpp b/bindings/c/src/threadpool.hpp new file mode 100644 index 00000000..d2bef66b --- /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_i validate(svs_threadpool_i 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_i 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_i impl; + }; + + svs_threadpool_kind kind; + size_t num_threads; + svs_threadpool_i 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_i 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 diff --git a/bindings/c/src/types_support.hpp b/bindings/c/src/types_support.hpp new file mode 100644 index 00000000..b3f9b38b --- /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 { + +inline 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 + } +} + +inline 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