Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/build-cpp-runtime-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,4 @@ jobs:
-w /workspace \
-e SUFFIX=${{ matrix.suffix }} \
svs-manylinux228:latest \
/bin/bash .github/scripts/test-cpp-runtime-bindings.sh
/bin/bash .github/scripts/test-faiss.sh
6 changes: 3 additions & 3 deletions .github/workflows/build-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
strategy:
matrix:
build_type: [RelWithDebugInfo]
cxx: [clang++-15]
cxx: [clang++-20]
include:
- cxx: clang++-15
package: llvm@15
- cxx: clang++-20
package: llvm@20
cc_name: clang
cxx_name: clang++
needs_prefix: true
Expand Down
1 change: 1 addition & 0 deletions bindings/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ set(SVS_C_API_SOURCES
src/filtered_search.hpp
src/index.hpp
src/index_builder.hpp
src/leanvec_training_data.hpp
src/storage.hpp
src/threadpool.hpp
src/types_support.hpp
Expand Down
40 changes: 40 additions & 0 deletions bindings/c/include/svs/c_api/svs_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ 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;
typedef struct svs_leanvec_training_data* svs_leanvec_training_data_h;

// Fully defined types; "_t" suffix indicates a fully defined struct
typedef enum svs_error_code svs_error_code_t;
Expand Down Expand Up @@ -305,6 +306,31 @@ SVS_API svs_storage_h svs_storage_create_sq(
/// @param storage The storage handle to free
SVS_API void svs_storage_free(svs_storage_h storage);

/// @brief Train LeanVec dimensionality-reduction matrices from a data sample
/// @param dim The dimensionality of the data (and training queries)
/// @param num_vectors The number of data vectors in x
/// @param x Pointer to the data vectors [num_vectors x dim] (float array)
/// @param num_queries The number of training queries in x_q (0 for in-distribution)
/// @param x_q Pointer to the training queries [num_queries x dim], or NULL. When
/// provided, matrices are trained out-of-distribution (OOD) using these queries;
/// when num_queries is 0 or x_q is NULL, in-distribution (PCA) matrices are computed.
/// @param leanvec_dims The reduced number of LeanVec dimensions
/// @param out_err An optional error handle to capture errors
/// @return A handle to the trained LeanVec matrices
SVS_API svs_leanvec_training_data_h svs_leanvec_training_data_build(
size_t dim,
size_t num_vectors,
const float* x,
size_t num_queries,
const float* x_q /*=NULL*/,
size_t leanvec_dims,
svs_error_h out_err /*=NULL*/
);

/// @brief Free the LeanVec training data handle
/// @param training_data The training data handle to free
SVS_API void svs_leanvec_training_data_free(svs_leanvec_training_data_h training_data);

/// @brief Create an index builder configuration
/// @param metric The distance metric to use
/// @param dimension The dimensionality of the vectors
Expand Down Expand Up @@ -333,6 +359,20 @@ SVS_API bool svs_index_builder_set_storage(
svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err /*=NULL*/
);

/// @brief Attach trained LeanVec matrices to the index builder
/// @param builder The index builder handle
/// @param training_data The trained LeanVec matrices to use when reducing the data.
/// Only applies when the builder's storage is configured for LeanVec; the reduced
/// dataset is built using these matrices instead of computing PCA matrices at build
/// time. Pass NULL to clear a previously attached training data.
/// @param out_err An optional error handle to capture errors
/// @return true on success, false on failure
SVS_API bool svs_index_builder_set_leanvec_training_data(
svs_index_builder_h builder,
svs_leanvec_training_data_h training_data,
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
Expand Down
18 changes: 15 additions & 3 deletions bindings/c/src/data_builder/leanvec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,25 @@
#endif // SVS_LEANVEC_HEADER

#include <filesystem>
#include <optional>
#include <stdexcept>
#include <utility>

namespace svs {

template <size_t I1, size_t I2, typename Allocator = svs::lib::Allocator<std::byte>>
class LeanVecDataBuilder {
size_t leanvec_dims_;
// Pre-trained (e.g. out-of-distribution) matrices; empty for PCA reduction.
std::optional<svs::leanvec::LeanVecMatrices<svs::Dynamic>> matrices_;

public:
LeanVecDataBuilder(size_t leanvec_dims)
: leanvec_dims_(leanvec_dims) {}
LeanVecDataBuilder(
size_t leanvec_dims,
std::optional<svs::leanvec::LeanVecMatrices<svs::Dynamic>> matrices = std::nullopt
)
: leanvec_dims_(leanvec_dims)
, matrices_(std::move(matrices)) {}

using data_type = svs::leanvec::LeanDataset<
svs::leanvec::UsingLVQ<I1>,
Expand All @@ -67,7 +75,7 @@ class LeanVecDataBuilder {
const allocator_type& allocator = {}
) {
return data_type::reduce(
view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator
view, matrices_, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator
);
}

Expand Down Expand Up @@ -95,6 +103,10 @@ struct lib::

static To convert(From from) {
auto leanvec = static_cast<const c_runtime::StorageLeanVec*>(from);
if (leanvec->training_data) {
return To{
leanvec->training_data->leanvec_dims(), leanvec->training_data->matrices()};
}
return To{leanvec->lenavec_dims};
}
};
Expand Down
94 changes: 94 additions & 0 deletions bindings/c/src/leanvec_training_data.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 <svs/core/data/simple.h>
#include <svs/core/medioid.h>
#include <svs/lib/static.h>
#include <svs/lib/threads/threadpool.h>

#ifdef SVS_LEANVEC_HEADER
#include SVS_LEANVEC_HEADER
#else
#include <svs/leanvec/leanvec.h>
#endif

#include <cstddef>

namespace svs::c_runtime {

// Holds LeanVec dimensionality-reduction matrices trained from a data sample.
// Mirrors the runtime bindings' LeanVecTrainingData: matrices are computed once
// and later handed to LeanVecDataBuilder to reduce the dataset. When training
// queries are supplied the matrices are learned out-of-distribution (OOD),
// otherwise in-distribution (PCA) matrices are used for both data and queries.
class LeanVecTrainingData {
public:
using matrices_type = svs::leanvec::LeanVecMatrices<svs::Dynamic>;

LeanVecTrainingData(
svs::data::ConstSimpleDataView<float> data,
svs::data::ConstSimpleDataView<float> queries,
size_t leanvec_dims,
svs::threads::ThreadPoolHandle& pool
)
: leanvec_dims_{leanvec_dims}
, matrices_{
queries.size() == 0 ? compute_pca(data, leanvec_dims, pool)
: compute_ood(data, queries, leanvec_dims, pool)} {}

size_t leanvec_dims() const { return leanvec_dims_; }
const matrices_type& matrices() const { return matrices_; }

private:
size_t leanvec_dims_;
matrices_type matrices_;

static matrices_type compute_pca(
svs::data::ConstSimpleDataView<float> data,
size_t leanvec_dims,
svs::threads::ThreadPoolHandle& pool
) {
auto means = svs::utils::compute_medioid(data, pool);
auto matrix = svs::leanvec::compute_leanvec_matrix<svs::Dynamic, svs::Dynamic>(
data, means, pool, svs::lib::MaybeStatic{leanvec_dims}
);
// A copy is used for the query matrix: in PCA mode data and query
// transforms are identical, and passing the same object twice trips
// use-after-move warnings and DenseArray double-free issues.
auto query_matrix = matrix;
return matrices_type{std::move(matrix), std::move(query_matrix)};
}

static matrices_type compute_ood(
svs::data::ConstSimpleDataView<float> data,
svs::data::ConstSimpleDataView<float> queries,
size_t leanvec_dims,
svs::threads::ThreadPoolHandle& pool
) {
return svs::leanvec::compute_leanvec_matrices_ood<svs::Dynamic>(
data, queries, pool, svs::lib::MaybeStatic{leanvec_dims}
);
}
};

} // namespace svs::c_runtime

#endif // SVS_RUNTIME_ENABLE_LVQ_LEANVEC
8 changes: 8 additions & 0 deletions bindings/c/src/storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
#include <svs/lib/type_traits.h>

#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
#include "leanvec_training_data.hpp"

#include <svs/cpuid.h>
#endif

#include <filesystem>
#include <memory>
#include <stdexcept>

namespace svs {
Expand Down Expand Up @@ -59,6 +62,11 @@ struct StorageLeanVec : public Storage {
size_t lenavec_dims;
size_t primary_bits;
size_t secondary_bits;
#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
// Pre-trained reduction matrices; when set, they are used instead of PCA
// matrices computed at build time (enables out-of-distribution LeanVec).
std::shared_ptr<const LeanVecTrainingData> training_data;
#endif

StorageLeanVec(size_t lenavec_dims, svs_data_type_t primary, svs_data_type_t secondary)
: Storage{SVS_STORAGE_KIND_LEANVEC}
Expand Down
98 changes: 98 additions & 0 deletions bindings/c/src/svs_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "threadpool.hpp"
#include "types_support.hpp"

#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
#include "leanvec_training_data.hpp"
#endif

#include <filesystem>
#include <memory>
#include <numeric>
Expand Down Expand Up @@ -55,6 +59,12 @@ struct svs_storage {
std::shared_ptr<svs::c_runtime::Storage> impl;
};

struct svs_leanvec_training_data {
#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
std::shared_ptr<const svs::c_runtime::LeanVecTrainingData> impl;
#endif
};

extern "C" svs_algorithm_h svs_algorithm_create_vamana(
size_t graph_degree,
size_t build_window_size,
Expand Down Expand Up @@ -342,6 +352,63 @@ svs_storage_create_sq(svs_data_type_t data_type, svs_error_h out_err) {

extern "C" void svs_storage_free(svs_storage_h storage) { delete storage; }

extern "C" svs_leanvec_training_data_h svs_leanvec_training_data_build(
size_t dim,
size_t num_vectors,
const float* x,
size_t num_queries,
const float* x_q,
size_t leanvec_dims,
svs_error_h out_err
) {
using namespace svs::c_runtime;
return wrap_exceptions(
[&]() -> svs_leanvec_training_data_h {
#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
EXPECT_ARG_GT_THAN(dim, 0);
EXPECT_ARG_GT_THAN(num_vectors, 0);
EXPECT_ARG_NOT_NULL(x);
EXPECT_ARG_GT_THAN(leanvec_dims, 0);
EXPECT_ARG_GE_THAN(dim, leanvec_dims);
INVALID_ARGUMENT_IF(
(num_queries > 0 && x_q == nullptr),
"x_q should not be NULL when num_queries is greater than 0"
);

auto data = svs::data::ConstSimpleDataView<float>(x, num_vectors, dim);
// A zero-sized view selects the in-distribution (PCA) path.
auto queries = svs::data::ConstSimpleDataView<float>(
x_q, (x_q == nullptr) ? 0 : num_queries, dim
);

auto pool = ThreadPoolBuilder{}.build();
auto training_data = std::make_shared<const LeanVecTrainingData>(
data, queries, leanvec_dims, pool
);

auto result = new svs_leanvec_training_data;
result->impl = std::move(training_data);
return result;
#else
(void)dim;
(void)num_vectors;
(void)x;
(void)num_queries;
(void)x_q;
(void)leanvec_dims;
throw svs::c_runtime::not_implemented(
"LeanVec training data is not implemented in this build"
);
#endif
},
out_err
);
}

extern "C" void svs_leanvec_training_data_free(svs_leanvec_training_data_h training_data) {
delete training_data;
}

extern "C" svs_index_builder_h svs_index_builder_create(
svs_distance_metric_t metric,
size_t dimension,
Expand Down Expand Up @@ -386,6 +453,37 @@ extern "C" bool svs_index_builder_set_storage(
);
}

extern "C" bool svs_index_builder_set_leanvec_training_data(
svs_index_builder_h builder,
svs_leanvec_training_data_h training_data,
svs_error_h out_err
) {
using namespace svs::c_runtime;
return wrap_exceptions(
[&]() -> bool {
#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
EXPECT_ARG_NOT_NULL(builder);
auto storage =
std::dynamic_pointer_cast<StorageLeanVec>(builder->impl->storage);
INVALID_ARGUMENT_IF(
(storage == nullptr),
"LeanVec training data can only be set on LeanVec storage"
);
storage->training_data =
(training_data == nullptr) ? nullptr : training_data->impl;
return true;
#else
(void)builder;
(void)training_data;
throw svs::c_runtime::not_implemented(
"LeanVec training data is not implemented in this build"
);
#endif
},
out_err
);
}

extern "C" bool svs_index_builder_set_threadpool(
svs_index_builder_h builder,
svs_threadpool_kind_t kind,
Expand Down
Loading
Loading