From ff0c05fb4e2439c8de576fa4e93699b63ae28187 Mon Sep 17 00:00:00 2001 From: Ishwar Bhati Date: Thu, 21 May 2026 13:47:38 -0700 Subject: [PATCH] LeanVec primary-only: add StorageKind variants Add two new StorageKind enum values for primary-only LeanVec: LeanVecLVQ4PrimaryOnly and LeanVecLVQ8PrimaryOnly. These select the new LeanDataset, void, ...> partial specialization which omits the secondary (reranking) tier for ~50% memory savings at the cost of reranking accuracy. Vamana-only (IVF support out of scope). - api_defs.h: extend StorageKind enum - svs_runtime_utils.h: extend is_leanvec_storage(), add LeanDatasetPrimaryOnlyType alias, StorageType specializations, new dispatch cases in SVS_DISPATCH_STORAGE_KIND - vamana_index_impl.h, dynamic_vamana_index_leanvec_impl.h: new switch cases in dispatch_leanvec_storage_kind - runtime_test.cpp: write/read round-trip tests for both new kinds --- bindings/cpp/include/svs/runtime/api_defs.h | 4 ++ .../src/dynamic_vamana_index_leanvec_impl.h | 12 +++++ bindings/cpp/src/svs_runtime_utils.h | 23 ++++++++- bindings/cpp/src/vamana_index_impl.h | 12 +++++ bindings/cpp/tests/runtime_test.cpp | 50 +++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) diff --git a/bindings/cpp/include/svs/runtime/api_defs.h b/bindings/cpp/include/svs/runtime/api_defs.h index f77df9d7..ebe878e3 100644 --- a/bindings/cpp/include/svs/runtime/api_defs.h +++ b/bindings/cpp/include/svs/runtime/api_defs.h @@ -106,6 +106,10 @@ enum class StorageKind { LeanVec4x4, LeanVec4x8, LeanVec8x8, + // Primary-only LeanVec variants: only the lossy primary tier is stored, no + // secondary (reranking) tier. Trades reranking accuracy for ~50% less memory. + LeanVecLVQ4PrimaryOnly, + LeanVecLVQ8PrimaryOnly, }; enum class ErrorCode { diff --git a/bindings/cpp/src/dynamic_vamana_index_leanvec_impl.h b/bindings/cpp/src/dynamic_vamana_index_leanvec_impl.h index 4d59281d..39ecb16d 100644 --- a/bindings/cpp/src/dynamic_vamana_index_leanvec_impl.h +++ b/bindings/cpp/src/dynamic_vamana_index_leanvec_impl.h @@ -95,6 +95,18 @@ struct DynamicVamanaIndexLeanVecImpl : public DynamicVamanaIndexImpl { storage::StorageType{}, std::forward(args)... ); + case StorageKind::LeanVecLVQ4PrimaryOnly: + return f( + storage:: + StorageType{}, + std::forward(args)... + ); + case StorageKind::LeanVecLVQ8PrimaryOnly: + return f( + storage:: + StorageType{}, + std::forward(args)... + ); default: throw StatusException{ ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required"}; diff --git a/bindings/cpp/src/svs_runtime_utils.h b/bindings/cpp/src/svs_runtime_utils.h index b081c91b..6577618d 100644 --- a/bindings/cpp/src/svs_runtime_utils.h +++ b/bindings/cpp/src/svs_runtime_utils.h @@ -114,7 +114,8 @@ inline constexpr bool is_lvq_storage(StorageKind kind) { inline constexpr bool is_leanvec_storage(StorageKind kind) { return kind == StorageKind::LeanVec4x4 || kind == StorageKind::LeanVec4x8 || - kind == StorageKind::LeanVec8x8; + kind == StorageKind::LeanVec8x8 || kind == StorageKind::LeanVecLVQ4PrimaryOnly || + kind == StorageKind::LeanVecLVQ8PrimaryOnly; } inline bool is_supported_storage_kind(StorageKind kind) { @@ -344,6 +345,24 @@ template struct StorageType { using type = LeanDatasetType<8, 8, allocator_type>; }; +// Primary-only LeanVec: secondary tier type is `void`. +template < + size_t I1, + typename Alloc, + size_t LeanVecDims = svs::Dynamic, + size_t Extent = svs::Dynamic> +using LeanDatasetPrimaryOnlyType = + svs::leanvec::LeanDataset, void, LeanVecDims, Extent, Alloc>; + +template struct StorageType { + using allocator_type = rebind_extracted_allocator_t; + using type = LeanDatasetPrimaryOnlyType<4, allocator_type>; +}; +template struct StorageType { + using allocator_type = rebind_extracted_allocator_t; + using type = LeanDatasetPrimaryOnlyType<8, allocator_type>; +}; + template struct StorageFactory { using StorageType = LeanVecStorageType; @@ -394,6 +413,8 @@ auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) { SVS_DISPATCH_STORAGE_KIND(LeanVec4x4); SVS_DISPATCH_STORAGE_KIND(LeanVec4x8); SVS_DISPATCH_STORAGE_KIND(LeanVec8x8); + SVS_DISPATCH_STORAGE_KIND(LeanVecLVQ4PrimaryOnly); + SVS_DISPATCH_STORAGE_KIND(LeanVecLVQ8PrimaryOnly); default: throw StatusException( ErrorCode::INVALID_ARGUMENT, "Unknown or unsupported SVS storage kind" diff --git a/bindings/cpp/src/vamana_index_impl.h b/bindings/cpp/src/vamana_index_impl.h index 550257d4..94fb93f0 100644 --- a/bindings/cpp/src/vamana_index_impl.h +++ b/bindings/cpp/src/vamana_index_impl.h @@ -555,6 +555,18 @@ struct VamanaIndexLeanVecImpl : public VamanaIndexImpl { storage::StorageType{}, std::forward(args)... ); + case StorageKind::LeanVecLVQ4PrimaryOnly: + return f( + storage:: + StorageType{}, + std::forward(args)... + ); + case StorageKind::LeanVecLVQ8PrimaryOnly: + return f( + storage:: + StorageType{}, + std::forward(args)... + ); default: throw StatusException{ ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required"}; diff --git a/bindings/cpp/tests/runtime_test.cpp b/bindings/cpp/tests/runtime_test.cpp index abd14296..4e8397b4 100644 --- a/bindings/cpp/tests/runtime_test.cpp +++ b/bindings/cpp/tests/runtime_test.cpp @@ -333,6 +333,56 @@ CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVec4x4", "[runtime]") { ); } +CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVecLVQ4PrimaryOnly", "[runtime]") { + const auto& test_data = get_test_data(); + auto build_func = [](svs::runtime::v0::DynamicVamanaIndex** index) { + svs::runtime::v0::VamanaIndex::BuildParams build_params{64}; + svs::runtime::v0::VamanaIndex::DynamicIndexParams dynamic_index_params{19}; + return svs::runtime::v0::DynamicVamanaIndexLeanVec::build( + index, + test_d, + svs::runtime::v0::MetricType::L2, + svs::runtime::v0::StorageKind::LeanVecLVQ4PrimaryOnly, + 32, + build_params, + {}, + dynamic_index_params + ); + }; + write_and_read_index( + build_func, + test_data, + test_n, + test_d, + svs::runtime::v0::StorageKind::LeanVecLVQ4PrimaryOnly + ); +} + +CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVecLVQ8PrimaryOnly", "[runtime]") { + const auto& test_data = get_test_data(); + auto build_func = [](svs::runtime::v0::DynamicVamanaIndex** index) { + svs::runtime::v0::VamanaIndex::BuildParams build_params{64}; + svs::runtime::v0::VamanaIndex::DynamicIndexParams dynamic_index_params{19}; + return svs::runtime::v0::DynamicVamanaIndexLeanVec::build( + index, + test_d, + svs::runtime::v0::MetricType::L2, + svs::runtime::v0::StorageKind::LeanVecLVQ8PrimaryOnly, + 32, + build_params, + {}, + dynamic_index_params + ); + }; + write_and_read_index( + build_func, + test_data, + test_n, + test_d, + svs::runtime::v0::StorageKind::LeanVecLVQ8PrimaryOnly + ); +} + CATCH_TEST_CASE("LeanVecWithTrainingData", "[runtime]") { const auto& test_data = get_test_data(); // Build LeanVec index with explicit training