Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bindings/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ set(SVS_C_API_SOURCES
src/dispatcher_vamana.cpp
src/dispatcher_dynamic_vamana.cpp
src/leanvec_training_data.cpp
src/data_builder.cpp
)

add_library(${TARGET_NAME} SHARED
Expand Down Expand Up @@ -140,7 +141,7 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
else()
# Links to LTO-enabled static library, requires GCC/G++ 11.2
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3")
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-07-21-127.tar.gz"
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-09-09-1508.tar.gz"
CACHE STRING "URL to download SVS shared library")
else()
# The fallback is correct but slower, so nothing downstream fails and CI
Expand Down
96 changes: 96 additions & 0 deletions bindings/c/include/svs/c/svs_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,102 @@ 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 Estimate the memory usage of an index based on the builder configuration and
/// number of vectors
/// @param builder The index builder handle
/// @param num_vectors The number of vectors to be indexed
/// @param out_breakdown Pointer to a structure to hold the memory breakdown
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
SVS_API bool svs_index_builder_estimate_memory(
svs_index_builder_h builder,
size_t num_vectors,
svs_memory_breakdown_t* out_breakdown,
svs_error_h out_err /*=NULL*/
);

/// @brief Returns default block size in bytes for dynamic index building based on the
/// builder configuration
/// @param builder The index builder handle
/// @param out_blocksize_bytes Pointer to a variable to receive the default block size in
/// bytes
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
SVS_API bool svs_index_builder_get_default_blocksize_bytes(
svs_index_builder_h builder, size_t* out_blocksize_bytes, svs_error_h out_err /*=NULL*/
);

/// @brief Estimate the memory usage of a dynamic index based on the builder configuration,
/// number of vectors, and block size
/// @param builder The index builder handle
/// @param num_vectors The number of vectors to be indexed
/// @param blocksize_bytes The block size in bytes for dynamic index building (0 for
/// default)
/// @param out_breakdown Pointer to a structure to hold the memory breakdown
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
SVS_API bool svs_index_builder_estimate_memory_dynamic(
svs_index_builder_h builder,
size_t num_vectors,
size_t blocksize_bytes,
svs_memory_breakdown_t* out_breakdown,
svs_error_h out_err /*=NULL*/
);

/// @brief Estimate the memory usage of a search operation based on the builder
/// configuration, search parameters, number of queries, and nearest neighbors to retrieve
/// @param builder The index builder handle
/// @param num_queries The number of queries to be performed
/// @param num_neighbors The number of nearest neighbors to retrieve per query
/// @param search_params The search parameters handle; if NULL, the builder's default search
/// parameters are used
/// @param id_filter An optional ID filter interface; if NULL, no filtering is applied
/// @param out_size Pointer to a variable to receive the estimated memory size
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
/// @remarks If @p id_filter is provided with `filter_rate > 0.0` then the function will
/// account for the filter hit rate during the search, elsewhere it assumes all candidates
/// pass the filter. The estimated memory size is for the search operation itself and does
/// not include the memory used by the index, the query data and the results structure.
SVS_API bool svs_index_builder_estimate_search_memory(
svs_index_builder_h builder,
size_t num_queries,
size_t num_neighbors,
svs_search_params_h search_params,
svs_id_filter_i id_filter /*=NULL*/,
size_t* out_size,
svs_error_h out_err /*=NULL*/
);

/// @brief Estimate the memory usage of a dynamic search operation based on the builder
/// configuration, search parameters, number of queries, nearest neighbors to retrieve, and
/// block size
/// @param builder The index builder handle
/// @param num_queries The number of queries to be performed
/// @param num_neighbors The number of nearest neighbors to retrieve per query
/// @param search_params The search parameters handle; if NULL, the builder's default search
/// parameters are used
/// @param id_filter An optional ID filter interface; if NULL, no filtering is applied
/// @param blocksize_bytes The block size in bytes for dynamic search (0 for default) -
/// reserved for future use
/// @param out_size Pointer to a variable to receive the estimated memory size
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
/// @remarks If @p id_filter is provided with `filter_rate > 0.0` then the function will
/// account for the filter hit rate during the search, elsewhere it assumes all candidates
/// pass the filter. The estimated memory size is for the search operation itself and does
/// not include the memory used by the index, the query data and the results structure.
SVS_API bool svs_index_builder_estimate_search_memory_dynamic(
svs_index_builder_h builder,
size_t num_queries,
size_t num_neighbors,
svs_search_params_h search_params,
svs_id_filter_i id_filter /*=NULL*/,
size_t blocksize_bytes,
size_t* out_size,
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)
Expand Down
5 changes: 5 additions & 0 deletions bindings/c/src/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ struct AlgorithmVamana : public Algorithm {
svs::index::vamana::SearchBufferConfig{search_window_size};
return params;
}

void apply_to(svs::index::vamana::VamanaSearchParameters& params) const {
params.buffer_config_ =
svs::index::vamana::SearchBufferConfig{search_window_size};
}
};

svs::index::vamana::VamanaBuildParameters build_params;
Expand Down
133 changes: 133 additions & 0 deletions bindings/c/src/data_builder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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 "data_builder.hpp"

#include "storage.hpp"

#include <svs/core/data/simple.h>
#include <svs/lib/dispatcher.h>
#include <svs/lib/misc.h>

#include <variant>

namespace svs::c_runtime {
// namespace {
template <typename DataBuilder>
size_t
estimate_size(DataBuilder builder, size_t num_vectors, size_t dimension, svs::lib::Empty) {
using allocator_type = typename DataBuilder::allocator_type;
static_assert(
!svs::data::is_blocked_v<allocator_type>,
"estimate_size requires a non-blocked allocator type."
);
return builder.estimate_size(num_vectors, dimension, allocator_type{});
}

template <typename DataBuilder>
size_t estimate_blocked_size(
DataBuilder builder, size_t num_vectors, size_t dimension, size_t blocksize_bytes
) {
using allocator_type = typename DataBuilder::allocator_type;
static_assert(
svs::data::is_blocked_v<allocator_type>,
"estimate_blocked_size requires a blocked allocator type."
);
svs::data::BlockingParameters block_params;
if (blocksize_bytes != 0) {
block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes);
}
auto allocator = allocator_type{block_params};
return builder.estimate_size(num_vectors, dimension, allocator);
}

template <typename Dispatcher>
void register_data_size_specializations(Dispatcher& dispatcher) {
auto size_closure = [&dispatcher]<typename DataBuilder, typename = void>() {
dispatcher.register_target(&estimate_size<DataBuilder>);
};

for_simple_specializations<false>(size_closure);
for_leanvec_specializations<false>(size_closure);
for_lvq_specializations<false>(size_closure);
for_sq_specializations<false>(size_closure);

auto blocked_size_closure = [&dispatcher]<typename DataBuilder, typename = void>() {
dispatcher.register_target(&estimate_blocked_size<DataBuilder>);
};

for_simple_specializations<true>(blocked_size_closure);
for_leanvec_specializations<true>(blocked_size_closure);
for_lvq_specializations<true>(blocked_size_closure);
for_sq_specializations<true>(blocked_size_closure);
}

using BlocksizeArg = std::variant<svs::lib::Empty, size_t>;

using EstimateSizeDispatcher =
svs::lib::Dispatcher<size_t, const Storage*, size_t, size_t, BlocksizeArg>;

const EstimateSizeDispatcher& build_data_size_dispatcher() {
static EstimateSizeDispatcher dispatcher = [] {
EstimateSizeDispatcher d{};
register_data_size_specializations(d);
return d;
}();
return dispatcher;
}

size_t dispatch_data_size_estimation(
const Storage* storage,
size_t num_vectors,
size_t dimension,
BlocksizeArg blocksize_bytes
) {
return build_data_size_dispatcher().invoke(
storage, num_vectors, dimension, blocksize_bytes
);
}
//} // namespace

size_t estimate_data_size(const Storage* storage, size_t num_vectors, size_t dimension) {
if (storage == nullptr) {
throw std::invalid_argument("Storage pointer cannot be null.");
}
if (num_vectors == 0) {
throw std::invalid_argument("Number of vectors must be greater than zero.");
}
if (dimension == 0) {
throw std::invalid_argument("Dimension must be greater than zero.");
}
return dispatch_data_size_estimation(
storage, num_vectors, dimension, svs::lib::Empty{}
);
}

size_t estimate_data_size_blocked(
const Storage* storage, size_t num_vectors, size_t dimension, size_t blocksize_bytes
) {
if (storage == nullptr) {
throw std::invalid_argument("Storage pointer cannot be null.");
}
if (num_vectors == 0) {
throw std::invalid_argument("Number of vectors must be greater than zero.");
}
if (dimension == 0) {
throw std::invalid_argument("Dimension must be greater than zero.");
}
return dispatch_data_size_estimation(storage, num_vectors, dimension, blocksize_bytes);
}
} // namespace svs::c_runtime
8 changes: 8 additions & 0 deletions bindings/c/src/data_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@
#include "data_builder/lvq.hpp"
#include "data_builder/simple.hpp"
#include "data_builder/sq.hpp"
#include "storage.hpp"

namespace svs::c_runtime {
size_t estimate_data_size(const Storage* storage, size_t num_vectors, size_t dimension);
size_t estimate_data_size_blocked(
const Storage* storage, size_t num_vectors, size_t dimension, size_t blocksize_bytes
);
} // namespace svs::c_runtime
40 changes: 40 additions & 0 deletions bindings/c/src/data_builder/leanvec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "svs/c/svs_c.h"

#include "data_builder/lvq.hpp"
#include "leanvec_training_data.hpp"
#include "storage.hpp"
#include "types_support.hpp"
Expand Down Expand Up @@ -85,6 +86,45 @@ class LeanVecDataBuilder {
load(const std::filesystem::path& path, const allocator_type& allocator = {}) {
return svs::lib::load_from_disk<data_type>(path, allocator);
}

size_t estimate_size(
size_t num_vectors, size_t dimension, const allocator_type& allocator = {}
) const {
// Current version of LeanVecDataBuilder supports LVQ-only datasets, so we can
// directly reuse LVQDataBuilder::estimate_size()
//
// LeanDataset uses primary-only LVQ (ResidualBits == 0), so we can use
// LVQDataBuilder<I1, 0> and LVQDataBuilder<I2, 0> to estimate sizes for primary and
// secondary datasets.

// Estimate primary size
using primary_data_builder = LVQDataBuilder<I1, 0, allocator_type>;
const auto primary_size =
primary_data_builder{}.estimate_size(num_vectors, leanvec_dims_, allocator);

// Estimate secondary size
using secondary_data_builder = LVQDataBuilder<I2, 0, allocator_type>;
const auto secondary_size =
secondary_data_builder{}.estimate_size(num_vectors, dimension, allocator);

// Note: the following sizes are not included in the current estimate as they are
// not included in memory breakdown calculations in the current implementation. They
// can be added if needed.

// LeanVec matrices are 2 SimpleData matrices of float, each of size (dimension x
// leanvec_dims)
const size_t matrices_size = 0; // 2 * dimension * leanvec_dims_ * sizeof(float);

// LeanVec means is the vector of double of size (dimension)
const size_t means_size = 0; // dimension * sizeof(double);

// is_pca_ flag is a boolean, so it takes 1 byte
const size_t is_pca_size = 0; // sizeof(bool);

const auto total_size =
primary_size + secondary_size + matrices_size + means_size + is_pca_size;
return total_size;
}
};

template <size_t I1, size_t I2, typename Alloc>
Expand Down
Loading
Loading