diff --git a/src/VecSim/algorithms/svs/svs_utils.h b/src/VecSim/algorithms/svs/svs_utils.h index 8dfa23d53..bf7211c67 100644 --- a/src/VecSim/algorithms/svs/svs_utils.h +++ b/src/VecSim/algorithms/svs/svs_utils.h @@ -66,11 +66,6 @@ inline double toVecSimDistance(float v) { return 1.0 - static_cast(v); } -template <> -inline double toVecSimDistance(float v) { - return 1.0 - static_cast(v); -} - // VecSim allocator wrapper for SVS containers template using SVSAllocator = VecsimSTLAllocator; diff --git a/src/VecSim/index_factories/components/preprocessors_factory.h b/src/VecSim/index_factories/components/preprocessors_factory.h index c9e73036b..a097991f8 100644 --- a/src/VecSim/index_factories/components/preprocessors_factory.h +++ b/src/VecSim/index_factories/components/preprocessors_factory.h @@ -44,7 +44,7 @@ CreatePreprocessorsContainerParams(VecSimMetric metric, size_t dim, bool is_norm size_t processed_bytes_count = dim * sizeof(DataType); VecSimMetric pp_metric = metric; - if (metric == VecSimMetric_Cosine) { + if (VecSimMetric_IsCosineFamily(metric)) { // if metric is cosine and DataType is integral, the processed_bytes_count includes the // norm appended to the vector. if (std::is_integral::value) { @@ -78,7 +78,7 @@ PreprocessorsContainerAbstract * CreatePreprocessorsContainer(std::shared_ptr allocator, PreprocessorsContainerParams params) { - if (params.metric == VecSimMetric_Cosine) { + if (VecSimMetric_IsCosineFamily(params.metric)) { auto multiPPContainer = new (allocator) MultiPreprocessorsContainer( allocator, params.query_alignment, params.storage_alignment); auto cosine_preprocessor = new (allocator) @@ -118,13 +118,13 @@ template size_t EstimatePreprocessorsContainerMemory(VecSimMetric metric, bool is_normalized = false) { size_t allocations_overhead = VecSimAllocator::getAllocationOverheadSize(); VecSimMetric pp_metric; - if (is_normalized && metric == VecSimMetric_Cosine) { + if (is_normalized && VecSimMetric_IsCosineFamily(metric)) { pp_metric = VecSimMetric_IP; } else { pp_metric = metric; } - if (pp_metric == VecSimMetric_Cosine) { + if (VecSimMetric_IsCosineFamily(pp_metric)) { constexpr size_t n_preprocessors = 1; // One entry in preprocessors array size_t est = diff --git a/src/VecSim/index_factories/svs_factory.cpp b/src/VecSim/index_factories/svs_factory.cpp index 33adf6fb1..da7d284bd 100644 --- a/src/VecSim/index_factories/svs_factory.cpp +++ b/src/VecSim/index_factories/svs_factory.cpp @@ -48,7 +48,7 @@ VecSimIndex *NewIndexImpl(const VecSimParams *params, bool is_normalized) { abstractInitParams.allocator, svsParams.metric, svsParams.dim, is_normalized, 0); IndexComponents, float> components = { nullptr, preprocessors}; // calculator is not in use in svs. - bool forcePreprocessing = !is_normalized && svsParams.metric == VecSimMetric_Cosine; + bool forcePreprocessing = !is_normalized && VecSimMetric_IsCosineFamily(svsParams.metric); if (svsParams.multi) { return new (abstractInitParams.allocator) SVSIndex( @@ -113,6 +113,7 @@ VecSimIndex *NewIndexImpl(const VecSimParams *params, bool is_normalized) { return NewIndexImpl(params, is_normalized); case VecSimMetric_IP: case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return NewIndexImpl(params, is_normalized); default: // If we got here something is wrong. diff --git a/src/VecSim/spaces/computer/preprocessors.h b/src/VecSim/spaces/computer/preprocessors.h index 988ba9dab..b6f0c177a 100644 --- a/src/VecSim/spaces/computer/preprocessors.h +++ b/src/VecSim/spaces/computer/preprocessors.h @@ -242,8 +242,9 @@ class QuantPreprocessor : public PreprocessorInterface { using sq8 = vecsim_types::sq8; static_assert(Metric == VecSimMetric_L2 || Metric == VecSimMetric_IP || - Metric == VecSimMetric_Cosine, - "QuantPreprocessor only supports L2, IP and Cosine metrics"); + Metric == VecSimMetric_Cosine || + Metric == VecSimMetric_CosineSimilarity, + "QuantPreprocessor only supports L2, IP and cosine-based metrics"); // Helper function to perform quantization. This function is used by the storage preprocessing // methods. diff --git a/src/VecSim/spaces/spaces.cpp b/src/VecSim/spaces/spaces.cpp index 7df458cd2..95ed0598a 100644 --- a/src/VecSim/spaces/spaces.cpp +++ b/src/VecSim/spaces/spaces.cpp @@ -26,6 +26,7 @@ dist_func_t GetDistFunc(VecSimMetric metri unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: case VecSimMetric_IP: return IP_BF16_GetDistFunc(dim, alignment); case VecSimMetric_L2: @@ -39,6 +40,7 @@ dist_func_t GetDistFunc(VecSimMetric metric unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: case VecSimMetric_IP: return IP_FP16_GetDistFunc(dim, alignment); case VecSimMetric_L2: @@ -52,6 +54,7 @@ dist_func_t GetDistFunc(VecSimMetric metric, size_t dim, unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: case VecSimMetric_IP: return IP_FP32_GetDistFunc(dim, alignment); case VecSimMetric_L2: @@ -65,6 +68,7 @@ dist_func_t GetDistFunc(VecSimMetric metric, size_t dim, unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: case VecSimMetric_IP: return IP_FP64_GetDistFunc(dim, alignment); case VecSimMetric_L2: @@ -78,6 +82,7 @@ dist_func_t GetDistFunc(VecSimMetric metric, size_t dim, unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return Cosine_INT8_GetDistFunc(dim, alignment); case VecSimMetric_IP: return IP_INT8_GetDistFunc(dim, alignment); @@ -92,6 +97,7 @@ dist_func_t GetDistFunc(VecSimMetric metric, size_t dim, unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return Cosine_UINT8_GetDistFunc(dim, alignment); case VecSimMetric_IP: return IP_UINT8_GetDistFunc(dim, alignment); @@ -106,6 +112,7 @@ dist_func_t GetDistFunc(VecSimMetric metric, si unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return Cosine_SQ8_SQ8_GetDistFunc(dim, alignment); case VecSimMetric_IP: return IP_SQ8_SQ8_GetDistFunc(dim, alignment); @@ -120,6 +127,7 @@ dist_func_t GetDistFunc(VecSimMetric met unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return Cosine_SQ8_FP32_GetDistFunc(dim, alignment); case VecSimMetric_IP: return IP_SQ8_FP32_GetDistFunc(dim, alignment); @@ -135,6 +143,7 @@ GetDistFunc(VecSimMetric metric unsigned char *alignment) { switch (metric) { case VecSimMetric_Cosine: + case VecSimMetric_CosineSimilarity: return Cosine_SQ8_FP16_GetDistFunc(dim, alignment); case VecSimMetric_IP: return IP_SQ8_FP16_GetDistFunc(dim, alignment); diff --git a/src/VecSim/utils/vec_utils.cpp b/src/VecSim/utils/vec_utils.cpp index 3459d3d4f..4e104f881 100644 --- a/src/VecSim/utils/vec_utils.cpp +++ b/src/VecSim/utils/vec_utils.cpp @@ -223,6 +223,8 @@ const char *VecSimMetric_ToString(VecSimMetric vecsimMetric) { switch (vecsimMetric) { case VecSimMetric_Cosine: return "COSINE"; + case VecSimMetric_CosineSimilarity: + return "COSINE_SIMILARITY"; case VecSimMetric_IP: return "IP"; case VecSimMetric_L2: @@ -295,7 +297,8 @@ size_t VecSimType_sizeof(VecSimType type) { size_t VecSimParams_GetStoredDataSize(VecSimType type, size_t dim, VecSimMetric metric) { size_t storedDataSize = VecSimType_sizeof(type) * dim; - if (metric == VecSimMetric_Cosine && (type == VecSimType_INT8 || type == VecSimType_UINT8)) { + if (VecSimMetric_IsCosineFamily(metric) && + (type == VecSimType_INT8 || type == VecSimType_UINT8)) { storedDataSize += sizeof(float); // For the norm } return storedDataSize; diff --git a/src/VecSim/vec_sim.cpp b/src/VecSim/vec_sim.cpp index 4fed53ab8..499d0dff7 100644 --- a/src/VecSim/vec_sim.cpp +++ b/src/VecSim/vec_sim.cpp @@ -259,7 +259,7 @@ extern "C" size_t VecSimParams_GetQueryBlobSize(VecSimType type, size_t dim, Vec type == VecSimType_BFLOAT16 || type == VecSimType_FLOAT16 || type == VecSimType_INT8 || type == VecSimType_UINT8); size_t blobSize = VecSimType_sizeof(type) * dim; - if (metric == VecSimMetric_Cosine && (type == VecSimType_INT8 || type == VecSimType_UINT8)) { + if (VecSimMetric_IsCosineFamily(metric) && (type == VecSimType_INT8 || type == VecSimType_UINT8)) { blobSize += sizeof(float); // For the norm } return blobSize; diff --git a/src/VecSim/vec_sim_common.h b/src/VecSim/vec_sim_common.h index fe10a5a0c..2ef5aabc1 100644 --- a/src/VecSim/vec_sim_common.h +++ b/src/VecSim/vec_sim_common.h @@ -84,7 +84,18 @@ typedef enum { } VecSimBool; // Distance metric -typedef enum { VecSimMetric_L2, VecSimMetric_IP, VecSimMetric_Cosine } VecSimMetric; +typedef enum { + VecSimMetric_L2, + VecSimMetric_IP, + VecSimMetric_Cosine, + VecSimMetric_CosineSimilarity +} VecSimMetric; + +// Returns true for any metric whose internal execution path uses cosine distance, +// regardless of how scores are presented at the API boundary. +static inline bool VecSimMetric_IsCosineFamily(VecSimMetric metric) { + return metric == VecSimMetric_Cosine || metric == VecSimMetric_CosineSimilarity; +} typedef size_t labelType; typedef unsigned int idType; diff --git a/src/python_bindings/bindings.cpp b/src/python_bindings/bindings.cpp index b68c14653..6056a3e62 100644 --- a/src/python_bindings/bindings.cpp +++ b/src/python_bindings/bindings.cpp @@ -679,6 +679,7 @@ PYBIND11_MODULE(VecSim, m) { .value("VecSimMetric_L2", VecSimMetric_L2) .value("VecSimMetric_IP", VecSimMetric_IP) .value("VecSimMetric_Cosine", VecSimMetric_Cosine) + .value("VecSimMetric_CosineSimilarity", VecSimMetric_CosineSimilarity) .export_values(); py::enum_(m, "VecSimOptionMode") diff --git a/tests/unit/test_components.cpp b/tests/unit/test_components.cpp index eb465fa92..f24f4a28c 100644 --- a/tests/unit/test_components.cpp +++ b/tests/unit/test_components.cpp @@ -1313,11 +1313,15 @@ TEST_P(QuantPreprocessorMetricTest, QuantizationBlobSizeAndMetadata) { case VecSimMetric_Cosine: runQuantizationTest(); break; + case VecSimMetric_CosineSimilarity: + runQuantizationTest(); + break; } } INSTANTIATE_TEST_SUITE_P(QuantPreprocessorTests, QuantPreprocessorMetricTest, - testing::Values(VecSimMetric_L2, VecSimMetric_IP, VecSimMetric_Cosine), + testing::Values(VecSimMetric_L2, VecSimMetric_IP, VecSimMetric_Cosine, + VecSimMetric_CosineSimilarity), [](const testing::TestParamInfo &info) { return VecSimMetric_ToString(info.param); }); @@ -1477,11 +1481,15 @@ TEST_P(QuantPreprocessorFP16MetricTest, QuantizationBlobSizeAndMetadata) { case VecSimMetric_Cosine: runQuantizationTest(); break; + case VecSimMetric_CosineSimilarity: + runQuantizationTest(); + break; } } INSTANTIATE_TEST_SUITE_P(QuantPreprocessorFP16Tests, QuantPreprocessorFP16MetricTest, - testing::Values(VecSimMetric_L2, VecSimMetric_IP, VecSimMetric_Cosine), + testing::Values(VecSimMetric_L2, VecSimMetric_IP, VecSimMetric_Cosine, + VecSimMetric_CosineSimilarity), [](const testing::TestParamInfo &info) { return VecSimMetric_ToString(info.param); }); diff --git a/tests/unit/test_spaces.cpp b/tests/unit/test_spaces.cpp index 8c4681ae7..c76bf16d7 100644 --- a/tests/unit/test_spaces.cpp +++ b/tests/unit/test_spaces.cpp @@ -492,49 +492,49 @@ TEST_F(SpacesTest, SQ8_FP16_l2sqr_odd_dim_unaligned_metadata_test) { TEST_F(SpacesTest, GetDistFuncInvalidMetricFP32) { EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricFP64) { EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricBF16) { - EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, + EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricFP16) { EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricINT8) { EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricUINT8) { EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricSQ8) { // SQ8 to SQ8 (symmetric) EXPECT_THROW( - (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), 10, nullptr)), + (spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricSQ8ToFloat) { // SQ8 to float (asymmetric) - EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), + EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } TEST_F(SpacesTest, GetDistFuncInvalidMetricSQ8ToFP16) { // SQ8 storage with FP16 query (asymmetric) - EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_Cosine + 1), + EXPECT_THROW((spaces::GetDistFunc((VecSimMetric)(VecSimMetric_CosineSimilarity + 1), 10, nullptr)), std::invalid_argument); } @@ -546,9 +546,11 @@ TEST_F(SpacesTest, GetDistFuncSQ8Symmetric) { auto l2_func = spaces::GetDistFunc(VecSimMetric_L2, dim, nullptr); auto ip_func = spaces::GetDistFunc(VecSimMetric_IP, dim, nullptr); auto cosine_func = spaces::GetDistFunc(VecSimMetric_Cosine, dim, nullptr); + auto cosine_similarity_func = spaces::GetDistFunc(VecSimMetric_CosineSimilarity, dim, nullptr); ASSERT_EQ(l2_func, L2_SQ8_SQ8_GetDistFunc(dim, nullptr)); ASSERT_EQ(ip_func, IP_SQ8_SQ8_GetDistFunc(dim, nullptr)); ASSERT_EQ(cosine_func, Cosine_SQ8_SQ8_GetDistFunc(dim, nullptr)); + ASSERT_EQ(cosine_similarity_func, Cosine_SQ8_SQ8_GetDistFunc(dim, nullptr)); } TEST_F(SpacesTest, GetDistFuncSQ8Asymmetric) { @@ -557,9 +559,11 @@ TEST_F(SpacesTest, GetDistFuncSQ8Asymmetric) { auto l2_func = spaces::GetDistFunc(VecSimMetric_L2, dim, nullptr); auto ip_func = spaces::GetDistFunc(VecSimMetric_IP, dim, nullptr); auto cosine_func = spaces::GetDistFunc(VecSimMetric_Cosine, dim, nullptr); + auto cosine_similarity_func = spaces::GetDistFunc(VecSimMetric_CosineSimilarity, dim, nullptr); ASSERT_EQ(l2_func, L2_SQ8_FP32_GetDistFunc(dim, nullptr)); ASSERT_EQ(ip_func, IP_SQ8_FP32_GetDistFunc(dim, nullptr)); ASSERT_EQ(cosine_func, Cosine_SQ8_FP32_GetDistFunc(dim, nullptr)); + ASSERT_EQ(cosine_similarity_func, Cosine_SQ8_FP32_GetDistFunc(dim, nullptr)); } TEST_F(SpacesTest, GetDistFuncSQ8FP16Asymmetric) { @@ -569,9 +573,12 @@ TEST_F(SpacesTest, GetDistFuncSQ8FP16Asymmetric) { auto l2_func = spaces::GetDistFunc(VecSimMetric_L2, dim, nullptr); auto ip_func = spaces::GetDistFunc(VecSimMetric_IP, dim, nullptr); auto cosine_func = spaces::GetDistFunc(VecSimMetric_Cosine, dim, nullptr); + auto cosine_similarity_func = + spaces::GetDistFunc(VecSimMetric_CosineSimilarity, dim, nullptr); ASSERT_EQ(l2_func, L2_SQ8_FP16_GetDistFunc(dim, nullptr)); ASSERT_EQ(ip_func, IP_SQ8_FP16_GetDistFunc(dim, nullptr)); ASSERT_EQ(cosine_func, Cosine_SQ8_FP16_GetDistFunc(dim, nullptr)); + ASSERT_EQ(cosine_similarity_func, Cosine_SQ8_FP16_GetDistFunc(dim, nullptr)); // dim < 16 takes the scalar early-return in every SQ8_FP16 dispatcher (no SIMD tier). size_t small_dim = 8;