From 84befb1fdc59b3d1a9231ab5ce09fe11e9af22aa Mon Sep 17 00:00:00 2001 From: Zhang Xiangze Date: Wed, 3 Jun 2026 07:04:01 +0000 Subject: [PATCH 1/3] Micro benchmarks for sq8 tiered hnsw index Including TopK/Range/Memory tests Also add a script to convert fp32/fp16 index files to sq8 --- src/VecSim/algorithms/hnsw/hnsw.h | 6 + .../algorithms/hnsw/hnsw_serializer.cpp | 4 +- src/VecSim/algorithms/hnsw/hnsw_serializer.h | 5 +- .../hnsw/hnsw_serializer_declarations.h | 5 +- .../algorithms/hnsw/hnsw_serializer_impl.h | 23 ++- src/VecSim/algorithms/hnsw/hnsw_tiered.h | 11 +- src/VecSim/index_factories/hnsw_factory.cpp | 101 +++++++++++-- src/VecSim/index_factories/tiered_factory.h | 5 +- tests/benchmark/bm_common.h | 49 ++++++ tests/benchmark/bm_definitions.h | 7 +- .../bm_basics_initialize_fp16.h | 16 ++ .../bm_basics_initialize_fp32.h | 16 ++ tests/benchmark/bm_vecsim_basics.h | 33 +++++ tests/benchmark/bm_vecsim_index.h | 42 ++++++ .../benchmark/data/scripts/convert_to_sq8.py | 140 ++++++++++++++++++ .../run_files/bm_basics_multi_fp16.cpp | 7 +- .../run_files/bm_basics_multi_fp32.cpp | 7 +- .../run_files/bm_basics_single_fp16.cpp | 6 +- .../run_files/bm_basics_single_fp32.cpp | 6 +- tests/unit/test_hnsw_sq8.cpp | 22 --- 20 files changed, 457 insertions(+), 54 deletions(-) create mode 100644 tests/benchmark/data/scripts/convert_to_sq8.py diff --git a/src/VecSim/algorithms/hnsw/hnsw.h b/src/VecSim/algorithms/hnsw/hnsw.h index cd37bc1f0..016f31469 100644 --- a/src/VecSim/algorithms/hnsw/hnsw.h +++ b/src/VecSim/algorithms/hnsw/hnsw.h @@ -170,6 +170,12 @@ class HNSWIndex : public VecSimIndexAbstract, #include "VecSim/algorithms/hnsw/hnsw_base_tests_friends.h" #include "hnsw_serializer_declarations.h" + +public: + // Serialization-only fields for V5 format (SQ8 support) + VecSimQuantType quantType = VecSimQuant_NONE; + std::vector serializedMeanVector; // Mean vector for SQ8 indices (empty if not SQ8) + #endif protected: diff --git a/src/VecSim/algorithms/hnsw/hnsw_serializer.cpp b/src/VecSim/algorithms/hnsw/hnsw_serializer.cpp index 552b3138a..dbef75d55 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_serializer.cpp +++ b/src/VecSim/algorithms/hnsw/hnsw_serializer.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -30,8 +31,7 @@ HNSWSerializer::EncodingVersion HNSWSerializer::ReadVersion(std::ifstream &input } void HNSWSerializer::saveIndex(const std::string &location) { - validateSave(); - EncodingVersion version = EncodingVersion::V4; + EncodingVersion version = getWriteVersion(); std::ofstream output(location, std::ios::binary); writeBinaryPOD(output, version); saveIndexIMP(output); diff --git a/src/VecSim/algorithms/hnsw/hnsw_serializer.h b/src/VecSim/algorithms/hnsw/hnsw_serializer.h index 57e901d3f..0325ff55e 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_serializer.h +++ b/src/VecSim/algorithms/hnsw/hnsw_serializer.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -22,6 +23,7 @@ class HNSWSerializer : public Serializer { DEPRECATED = 2, // Last deprecated version V3, V4, + V5, // SQ8 quantization fields (quantType + mean vector) INVALID }; @@ -37,6 +39,7 @@ class HNSWSerializer : public Serializer { EncodingVersion m_version; private: - virtual void validateSave() const = 0; + virtual EncodingVersion getWriteVersion() const { return EncodingVersion::V4; } + void saveIndexFields(std::ofstream &output) const = 0; }; diff --git a/src/VecSim/algorithms/hnsw/hnsw_serializer_declarations.h b/src/VecSim/algorithms/hnsw/hnsw_serializer_declarations.h index 44e072f41..184ffc3f9 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_serializer_declarations.h +++ b/src/VecSim/algorithms/hnsw/hnsw_serializer_declarations.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -26,8 +27,8 @@ virtual void saveIndexIMP(std::ofstream &output) override; void restoreGraph(std::ifstream &input, HNSWSerializer::EncodingVersion version); private: -// Functions for index saving. -void validateSave() const override; +HNSWSerializer::EncodingVersion getWriteVersion() const override; + void saveIndexFields(std::ofstream &output) const override; void saveGraph(std::ofstream &output) const; diff --git a/src/VecSim/algorithms/hnsw/hnsw_serializer_impl.h b/src/VecSim/algorithms/hnsw/hnsw_serializer_impl.h index 8204e1086..bf05102d3 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_serializer_impl.h +++ b/src/VecSim/algorithms/hnsw/hnsw_serializer_impl.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -40,13 +41,9 @@ HNSWIndex::HNSWIndex(std::ifstream &input, const HNSWParams } template -void HNSWIndex::validateSave() const { - // V4 does not store quantization settings, and its loader always creates unquantized - // components. Reject the save rather than write a file the loader would misread. - if (this->isQuantized) { - throw std::runtime_error( - "Cannot save index: serialization of quantized indexes is not supported"); - } +HNSWSerializer::EncodingVersion HNSWIndex::getWriteVersion() const { + return this->isQuantized ? HNSWSerializer::EncodingVersion::V5 + : HNSWSerializer::EncodingVersion::V4; } template @@ -265,6 +262,18 @@ void HNSWIndex::saveIndexFields(std::ofstream &output) const writeBinaryPOD(output, this->maxElements); // This will be used to restore the index initial // capacity + // V5 fields: SQ8 quantization support (between factory params and index build params) + if (this->quantType != VecSimQuant_NONE) { + writeBinaryPOD(output, this->quantType); + // Write mean vector (dim floats). Empty vector means no mean (zero-mean SQ8). + bool hasMean = !this->serializedMeanVector.empty(); + writeBinaryPOD(output, hasMean); + if (hasMean) { + output.write(reinterpret_cast(this->serializedMeanVector.data()), + this->dim * sizeof(float)); + } + } + // Save index build parameters writeBinaryPOD(output, this->M); writeBinaryPOD(output, this->M0); diff --git a/src/VecSim/algorithms/hnsw/hnsw_tiered.h b/src/VecSim/algorithms/hnsw/hnsw_tiered.h index 49ea13184..97591b3eb 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_tiered.h +++ b/src/VecSim/algorithms/hnsw/hnsw_tiered.h @@ -799,9 +799,14 @@ TieredHNSWIndex::TieredHNSWIndex(HNSWIndex(hnsw_index, bf_index, tiered_index_params, allocator), labelToInsertJobs(this->allocator), idToRepairJobs(this->allocator), idToSwapJob(this->allocator), invalidJobs(this->allocator), currInvalidJobId(0), - readySwapJobs(0), - isQuantized(tiered_index_params.primaryIndexParams->algoParams.hnswParams.quantType != - VecSimQuant_NONE) { + readySwapJobs(0), isQuantized(false) { + // For benchmark, we create tiered hnsw index from existing hnsw index + // primaryIndexParams is nullptr and accumulation phase is skipped for sq8 + if (!tiered_index_params.primaryIndexParams) { + return; + } + this->isQuantized = + tiered_index_params.primaryIndexParams->algoParams.hnswParams.quantType != VecSimQuant_NONE; const auto &hnsw_params = tiered_index_params.primaryIndexParams->algoParams.hnswParams; const size_t normalization_set_size = tiered_index_params.specificParams.tieredHnswParams.QuantNormalizationSetSize; diff --git a/src/VecSim/index_factories/hnsw_factory.cpp b/src/VecSim/index_factories/hnsw_factory.cpp index 08891dcc6..14a906014 100644 --- a/src/VecSim/index_factories/hnsw_factory.cpp +++ b/src/VecSim/index_factories/hnsw_factory.cpp @@ -71,8 +71,16 @@ VecSimIndex *NewIndex_SQ8(const HNSWParams *hnswParams, AbstractIndexInitParams IndexComponents components = CreateSQ8IndexComponents( abstractInitParams.allocator, abstractInitParams.dim, mean_ptr); - return NewIndex_ChooseMultiOrSingle(hnswParams, abstractInitParams, - components); + auto index = + NewIndex_ChooseMultiOrSingle(hnswParams, abstractInitParams, components); +#ifdef BUILD_TESTS + // Store quantization metadata for re-serialization. + index->quantType = hnswParams->quantType; + if (mean_ptr != nullptr) { + index->serializedMeanVector.assign(mean_ptr, mean_ptr + abstractInitParams.dim); + } +#endif + return index; } VecSimIndex *NewIndex(const VecSimParams *params, bool is_normalized) { @@ -270,7 +278,8 @@ template inline VecSimIndex *NewIndex_ChooseMultiOrSingle(std::ifstream &input, const HNSWParams *params, const AbstractIndexInitParams &abstractInitParams, IndexComponents &components, - HNSWSerializer::EncodingVersion version) { + HNSWSerializer::EncodingVersion version, + const float *mean_ptr = nullptr) { HNSWIndex *index = nullptr; // check if single and call the ctor that loads index information from file. if (params->multi) @@ -282,17 +291,41 @@ inline VecSimIndex *NewIndex_ChooseMultiOrSingle(std::ifstream &input, const HNS index->restoreGraph(input, version); +#ifdef BUILD_TESTS + // Store quantization metadata for re-serialization. + index->quantType = params->quantType; + if (mean_ptr != nullptr) { + index->serializedMeanVector.assign(mean_ptr, mean_ptr + abstractInitParams.dim); + } +#endif + return index; } -// Initialize @params from file for V3 -static void InitializeParams(std::ifstream &source_params, HNSWParams ¶ms) { +// Initialize @params from file for V3+. For V5+ also reads quantType and mean vector. +static void InitializeParams(std::ifstream &source_params, HNSWParams ¶ms, + HNSWSerializer::EncodingVersion version, + std::vector &meanVector) { Serializer::readBinaryPOD(source_params, params.dim); Serializer::readBinaryPOD(source_params, params.type); Serializer::readBinaryPOD(source_params, params.metric); Serializer::readBinaryPOD(source_params, params.blockSize); Serializer::readBinaryPOD(source_params, params.multi); Serializer::readBinaryPOD(source_params, params.initialCapacity); + + // V5: read quantization fields + if (version >= HNSWSerializer::EncodingVersion::V5) { + Serializer::readBinaryPOD(source_params, params.quantType); + if (params.quantType == VecSimQuant_SQ8) { + bool hasMean = false; + Serializer::readBinaryPOD(source_params, hasMean); + if (hasMean) { + meanVector.resize(params.dim); + source_params.read(reinterpret_cast(meanVector.data()), + params.dim * sizeof(float)); + } + } + } } VecSimIndex *NewIndex(const std::string &location, bool is_normalized) { @@ -317,14 +350,60 @@ VecSimIndex *NewIndex(const std::string &location, bool is_normalized) { bad_name); } - HNSWParams params; - InitializeParams(input, params); - - VecSimParams vecsimParams = {.algo = VecSimAlgo_HNSWLIB, - .algoParams = {.hnswParams = HNSWParams{params}}}; + HNSWParams params = {}; + std::vector meanVector; + InitializeParams(input, params, version, meanVector); AbstractIndexInitParams abstractInitParams = - VecSimFactory::NewAbstractInitParams(¶ms, vecsimParams.logCtx, is_normalized); + VecSimFactory::NewAbstractInitParams(¶ms, nullptr, is_normalized); + + // SQ8 quantized index path + if (params.quantType == VecSimQuant_SQ8) { + const float *mean_ptr = meanVector.empty() ? nullptr : meanVector.data(); + VecSimMetric metric = params.metric; + if (is_normalized && metric == VecSimMetric_Cosine) { + metric = VecSimMetric_IP; + } + + // Override blob sizes for SQ8 storage layout. + size_t dim = params.dim; + if (metric == VecSimMetric_L2) { + abstractInitParams.storedDataSize = + GetSQ8StoredDataSize(dim, mean_ptr != nullptr); + } else { + abstractInitParams.storedDataSize = + GetSQ8StoredDataSize(dim, mean_ptr != nullptr); + } + + if (params.type == VecSimType_FLOAT32) { + abstractInitParams.inputBlobSize = dim * sizeof(float); + if (metric == VecSimMetric_L2) { + auto components = CreateSQ8IndexComponents( + abstractInitParams.allocator, dim, mean_ptr); + return NewIndex_ChooseMultiOrSingle(input, ¶ms, abstractInitParams, + components, version, mean_ptr); + } else { + auto components = CreateSQ8IndexComponents( + abstractInitParams.allocator, dim, mean_ptr); + return NewIndex_ChooseMultiOrSingle(input, ¶ms, abstractInitParams, + components, version, mean_ptr); + } + } else if (params.type == VecSimType_FLOAT16) { + abstractInitParams.inputBlobSize = dim * sizeof(float16); + if (metric == VecSimMetric_L2) { + auto components = CreateSQ8IndexComponents( + abstractInitParams.allocator, dim, mean_ptr); + return NewIndex_ChooseMultiOrSingle( + input, ¶ms, abstractInitParams, components, version, mean_ptr); + } else { + auto components = CreateSQ8IndexComponents( + abstractInitParams.allocator, dim, mean_ptr); + return NewIndex_ChooseMultiOrSingle( + input, ¶ms, abstractInitParams, components, version, mean_ptr); + } + } + } + if (params.type == VecSimType_FLOAT32) { IndexComponents indexComponents = CreateIndexComponents( abstractInitParams.allocator, params.metric, abstractInitParams.dim, is_normalized); diff --git a/src/VecSim/index_factories/tiered_factory.h b/src/VecSim/index_factories/tiered_factory.h index fbb55d3b3..6f0d1f2b6 100644 --- a/src/VecSim/index_factories/tiered_factory.h +++ b/src/VecSim/index_factories/tiered_factory.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -42,7 +43,9 @@ VecSimIndex *NewIndex(const TieredIndexParams *params, HNSWIndexgetInputBlobSize() == abstractInitParams.storedDataSize); - assert(hnsw_index->getStoredDataSize() == abstractInitParams.storedDataSize); + const HNSWParams *hnsw_params = ¶ms->primaryIndexParams->algoParams.hnswParams; + assert(hnsw_params->quantType != VecSimQuant_NONE || + hnsw_index->getStoredDataSize() == abstractInitParams.storedDataSize); auto frontendIndex = static_cast *>( BruteForceFactory::NewIndex(&bf_params, abstractInitParams, false)); diff --git a/tests/benchmark/bm_common.h b/tests/benchmark/bm_common.h index 0642e6e0a..6ef4f9fc9 100644 --- a/tests/benchmark/bm_common.h +++ b/tests/benchmark/bm_common.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -35,6 +36,7 @@ class BM_VecSimCommon : public BM_VecSimIndex { // with respect to the results returned by the flat index. static void TopK_HNSW(benchmark::State &st, unsigned short index_offset = 0); static void TopK_Tiered(benchmark::State &st, unsigned short index_offset = 0); + static void TopK_Tiered_SQ8(benchmark::State &st); // Does nothing but returning the index memory. static void Memory(benchmark::State &st, IndexTypeIndex index_type); @@ -149,6 +151,53 @@ void BM_VecSimCommon::TopK_Tiered(benchmark::State &st, unsigned s st.counters["num_threads"] = (double)BM_VecSimGeneral::mock_thread_pool->thread_pool_size; } +template +void BM_VecSimCommon::TopK_Tiered_SQ8(benchmark::State &st) { + size_t ef = st.range(0); + size_t k = st.range(1); + std::atomic_int correct = 0; + std::atomic_int iter = 0; + auto tiered_index = + dynamic_cast *>(GET_INDEX(INDEX_TIERED_HNSW_SQ8)); + size_t total_iters = 50; + VecSimQueryReply *all_results[total_iters]; + + auto parallel_knn_search = [](AsyncJob *job) { + auto *search_job = reinterpret_cast(job); + HNSWRuntimeParams hnswRuntimeParams = {.efRuntime = search_job->ef}; + auto query_params = BM_VecSimGeneral::CreateQueryParams(hnswRuntimeParams); + size_t cur_iter = search_job->iter; + auto hnsw_results = VecSimIndex_TopKQuery(GET_INDEX(INDEX_TIERED_HNSW_SQ8), + QUERIES[cur_iter % N_QUERIES].data(), + search_job->k, &query_params, BY_SCORE); + search_job->all_results[cur_iter] = hnsw_results; + delete job; + }; + + for (auto _ : st) { + auto search_job = new (tiered_index->getAllocator()) + tieredIndexMock::SearchJobMock(tiered_index->getAllocator(), parallel_knn_search, + tiered_index, k, ef, iter++, all_results); + tiered_index->submitSingleJob(search_job); + if (iter == total_iters) { + BM_VecSimGeneral::mock_thread_pool->thread_pool_wait(); + } + } + + // Measure recall + for (iter = 0; iter < total_iters; iter++) { + auto bf_results = VecSimIndex_TopKQuery( + GET_INDEX(INDEX_BF), QUERIES[iter % N_QUERIES].data(), k, nullptr, BY_SCORE); + BM_VecSimGeneral::MeasureRecall(all_results[iter], bf_results, correct); + + VecSimQueryReply_Free(bf_results); + VecSimQueryReply_Free(all_results[iter]); + } + + st.counters["Recall"] = (float)correct / (float)(k * iter); + st.counters["num_threads"] = (double)BM_VecSimGeneral::mock_thread_pool->thread_pool_size; +} + #define REGISTER_TopK_BF(BM_CLASS, BM_FUNC) \ BENCHMARK_REGISTER_F(BM_CLASS, BM_FUNC) \ ->Arg(10) \ diff --git a/tests/benchmark/bm_definitions.h b/tests/benchmark/bm_definitions.h index d4701ee41..990c8d8fb 100644 --- a/tests/benchmark/bm_definitions.h +++ b/tests/benchmark/bm_definitions.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -33,6 +34,8 @@ enum IndexTypeIndex { INDEX_SVS, INDEX_TIERED_SVS, INDEX_SVS_QUANTIZED, + INDEX_HNSW_SQ8, + INDEX_TIERED_HNSW_SQ8, NUMBER_OF_INDEX_TYPES // Keep last }; @@ -48,7 +51,9 @@ enum IndexTypeFlags { INDEX_MASK_TIERED_HNSW = 1 << 4, INDEX_MASK_SVS = 1 << 5, INDEX_MASK_TIERED_SVS = 1 << 6, - INDEX_MASK_SVS_COMPRESSED = 1 << 7 + INDEX_MASK_SVS_COMPRESSED = 1 << 7, + INDEX_MASK_HNSW_SQ8 = 1 << 8, + INDEX_MASK_TIERED_HNSW_SQ8 = 1 << 9 }; // Smart pointer wrapper for VecSimIndex with configurable ownership diff --git a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h index bbbfd79b4..3d06a9e6d 100644 --- a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h +++ b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -81,3 +82,18 @@ BENCHMARK_REGISTER_F(BM_VecSimBasics, BM_DELETE_LABEL_ASYNC) ->Arg(100) ->Arg(BM_VecSimGeneral::block_size) ->ArgName("SwapJobsThreshold"); + +// Memory SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimCommon, BM_FUNC_NAME(Memory, Tiered_SQ8), fp16_index_t) +(benchmark::State &st) { Memory(st, INDEX_TIERED_HNSW_SQ8); } +BENCHMARK_REGISTER_F(BM_VecSimCommon, BM_FUNC_NAME(Memory, Tiered_SQ8))->Iterations(1); + +// TopK SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8), fp16_index_t) +(benchmark::State &st) { TopK_Tiered_SQ8(st); } +REGISTER_TopK_Tiered(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8)); + +// Range SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t) +(benchmark::State &st) { Range_Tiered_SQ8(st); } +REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t); \ No newline at end of file diff --git a/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h b/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h index 3e994f33d..12e2d8608 100644 --- a/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h +++ b/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -88,3 +89,18 @@ BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_RELABEL_LABEL, fp32_index_t) REGISTER_RelabelLabel(BM_RELABEL_LABEL, INDEX_BF); REGISTER_RelabelLabel(BM_RELABEL_LABEL, INDEX_HNSW); REGISTER_RelabelLabel(BM_RELABEL_LABEL, INDEX_TIERED_HNSW); + +// Memory SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimCommon, BM_FUNC_NAME(Memory, Tiered_SQ8), fp32_index_t) +(benchmark::State &st) { Memory(st, INDEX_TIERED_HNSW_SQ8); } +BENCHMARK_REGISTER_F(BM_VecSimCommon, BM_FUNC_NAME(Memory, Tiered_SQ8))->Iterations(1); + +// TopK SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8), fp32_index_t) +(benchmark::State &st) { TopK_Tiered_SQ8(st); } +REGISTER_TopK_Tiered(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8)); + +// Range SQ8 Tiered +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC_NAME(Range, Tiered_SQ8), fp32_index_t) +(benchmark::State &st) { Range_Tiered_SQ8(st); } +REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp32_index_t); diff --git a/tests/benchmark/bm_vecsim_basics.h b/tests/benchmark/bm_vecsim_basics.h index da5a6de42..2b67a34cb 100644 --- a/tests/benchmark/bm_vecsim_basics.h +++ b/tests/benchmark/bm_vecsim_basics.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -48,6 +49,7 @@ class BM_VecSimBasics : public BM_VecSimCommon { static void Range_BF(benchmark::State &st); static void Range_HNSW(benchmark::State &st); + static void Range_Tiered_SQ8(benchmark::State &st); // Reproduces allocation/deallocation oscillation issue at block size boundaries. // Sets up index at blockSize+1 capacity, then repeatedly deletes and re-adds the same vector, @@ -354,6 +356,37 @@ void BM_VecSimBasics::Range_HNSW(benchmark::State &st) { st.counters["Recall"] = (float)total_res / total_res_bf; } +template +void BM_VecSimBasics::Range_Tiered_SQ8(benchmark::State &st) { + double radius = (1.0 / 100.0) * (double)st.range(0); + double epsilon = (1.0 / 1000.0) * (double)st.range(1); + size_t iter = 0; + size_t total_res = 0; + size_t total_res_bf = 0; + HNSWRuntimeParams hnswRuntimeParams = {.epsilon = epsilon}; + auto query_params = BM_VecSimGeneral::CreateQueryParams(hnswRuntimeParams); + + for (auto _ : st) { + auto hnsw_results = + VecSimIndex_RangeQuery(GET_INDEX(INDEX_TIERED_HNSW_SQ8), + QUERIES[iter % N_QUERIES].data(), radius, &query_params, BY_ID); + st.PauseTiming(); + total_res += VecSimQueryReply_Len(hnsw_results); + + // Measure recall: + auto bf_results = VecSimIndex_RangeQuery( + GET_INDEX(INDEX_BF), QUERIES[iter % N_QUERIES].data(), radius, nullptr, BY_ID); + total_res_bf += VecSimQueryReply_Len(bf_results); + + VecSimQueryReply_Free(bf_results); + VecSimQueryReply_Free(hnsw_results); + iter++; + st.ResumeTiming(); + } + st.counters["Avg. results number"] = (double)total_res / iter; + st.counters["Recall"] = (float)total_res / total_res_bf; +} + template void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { auto index = GET_INDEX(st.range(0)); diff --git a/tests/benchmark/bm_vecsim_index.h b/tests/benchmark/bm_vecsim_index.h index 73e74b1a3..18a375c6f 100644 --- a/tests/benchmark/bm_vecsim_index.h +++ b/tests/benchmark/bm_vecsim_index.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-Present, Redis Ltd. * All rights reserved. + * SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -164,6 +165,47 @@ void BM_VecSimIndex::Initialize() { } } + if (enabled_index_types & IndexTypeFlags::INDEX_MASK_HNSW_SQ8) { + // Initialize and load SQ8-quantized HNSW index. + std::string hnsw_sq8_index_file = hnsw_index_file; + hnsw_sq8_index_file.replace(hnsw_sq8_index_file.find(".hnsw_v3"), + std::string(".hnsw_v3").length(), "-sq8.hnsw_v5"); + indices[INDEX_HNSW_SQ8] = + IndexPtr(HNSWFactory::NewIndex(AttachRootPath(hnsw_sq8_index_file))); + + auto *hnsw_sq8_index = CastToHNSW(indices[INDEX_HNSW_SQ8]); + size_t ef_r = 10; + hnsw_sq8_index->setEf(ef_r); + + // Create tiered index from the loaded SQ8 HNSW index. + if (enabled_index_types & IndexTypeFlags::INDEX_MASK_TIERED_HNSW_SQ8) { + // Reuse existing mock_thread_pool if already created, otherwise create one. + if (!BM_VecSimGeneral::mock_thread_pool) { + BM_VecSimGeneral::mock_thread_pool = new tieredIndexMock(); + BM_VecSimGeneral::mock_thread_pool->init_threads(); + } + auto &mock_thread_pool = *BM_VecSimGeneral::mock_thread_pool; + VecSimParams params = { + .algo = VecSimAlgo_HNSWLIB, + .algoParams = {.hnswParams = HNSWParams{.quantType = hnsw_sq8_index->quantType}}}; + TieredIndexParams tiered_params = { + .jobQueue = &mock_thread_pool.jobQ, + .jobQueueCtx = mock_thread_pool.ctx, + .submitCb = tieredIndexMock::submit_callback, + .flatBufferLimit = block_size, + .primaryIndexParams = ¶ms, + .specificParams = { + TieredHNSWParams{.swapJobThreshold = 0, .QuantNormalizationSetSize = 0}}}; + + auto *sq8_tiered_index = TieredFactory::TieredHNSWFactory::NewIndex( + &tiered_params, hnsw_sq8_index); + + indices[INDEX_TIERED_HNSW_SQ8] = IndexPtr(sq8_tiered_index); + // Release HNSW_SQ8 ownership since tiered will free it + indices[INDEX_HNSW_SQ8].release_ownership(); + } + } + // Load the test query vectors from file. Index file path is relative to repository root dir. loadTestVectors(AttachRootPath(test_queries_file), type); VecSim_SetLogCallbackFunction(nullptr); diff --git a/tests/benchmark/data/scripts/convert_to_sq8.py b/tests/benchmark/data/scripts/convert_to_sq8.py new file mode 100644 index 000000000..80d256e63 --- /dev/null +++ b/tests/benchmark/data/scripts/convert_to_sq8.py @@ -0,0 +1,140 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: BSD-3-Clause +# +# This file is a template file for converting an existing HNSW index file +# (*.hnsw_v3) to SQ8 quantized HNSW index (*-sq8.hnsw_v5). +# Usage: poetry run python tests/benchmark/data/scripts/convert_to_sq8.py + +import numpy as np +from VecSim import * +import os + +# Paths +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..')) +DATA_DIR = os.path.join(REPO_ROOT, 'tests', 'benchmark', 'data') + +# Index parameters (must match the source index) + +''' +# For dbpedia-cosine-dim768-M64-efc512.hnsw_v3 +INPUT_INDEX = os.path.join(DATA_DIR, 'dbpedia-cosine-dim768-M64-efc512.hnsw_v3') +#INPUT_INDEX = os.path.join(DATA_DIR, 'dbpedia-cosine-dim768-M64-efc512-fp16.hnsw_v3') +DIM = 768 +M = 64 +EF_CONSTRUCTION = 512 +METRIC = VecSimMetric_Cosine +MULTI = False +TYPE = VecSimType_FLOAT16 +#TYPE = VecSimType_FLOAT32 +''' + +# For fashion_images_multi_value-cosine-dim512-M64-efc512.hnsw_v3 +#INPUT_INDEX = os.path.join(DATA_DIR, 'fashion_images_multi_value-cosine-dim512-M64-efc512.hnsw_v3') +INPUT_INDEX = os.path.join(DATA_DIR, 'fashion_images_multi_value-cosine-dim512-M64-efc512-fp16.hnsw_v3') +DIM = 512 +M = 64 +EF_CONSTRUCTION = 512 +METRIC = VecSimMetric_Cosine +MULTI = True +TYPE = VecSimType_FLOAT16 +#TYPE = VecSimType_FLOAT32 +N_LABELS = 44441 # the number of unique labels + +OUTPUT_INDEX = INPUT_INDEX.replace('.hnsw_v3', '-sq8.hnsw_v5') + +def convert(): + print(f'Loading source index: {INPUT_INDEX}') + source = HNSWIndex(INPUT_INDEX) + n_vectors = source.index_size() + print(f' Loaded {n_vectors} vectors, dim={DIM}, multi={MULTI}') + + # Extract all vectors from the source index + print('Extracting vectors...') + + if MULTI: + # For multi-value indices, each label can have multiple vectors. + # Discover labels by iterating and collecting non-empty results. + n_labels = N_LABELS + + # Collect all vectors grouped by label + label_vectors = {} # label -> list of vectors + total_extracted = 0 + for label in range(n_labels): + vecs = source.get_vector(label) + if vecs is not None and len(vecs) > 0: + label_vectors[label] = vecs # shape (num_vecs_for_label, dim) + total_extracted += len(vecs) + if label % 100000 == 0: + print(f' Extracted label {label}/{n_labels} ({total_extracted} vectors so far)') + print(f' Extracted {n_labels} labels, {total_extracted} vectors total') + + # Gather all vectors into a single array for computing the mean + all_vectors = np.vstack(list(label_vectors.values())) + else: + # Single-value index: one vector per label, labels are 0..n_vectors-1 + all_vectors = np.zeros((n_vectors, DIM), dtype=np.float32) + for label in range(n_vectors): + vecs = source.get_vector(label) + all_vectors[label] = vecs[0] # get_vector returns array of shape (1, dim) + if label % 100000 == 0: + print(f' Extracted {label}/{n_vectors}') + print(f' Extracted {n_vectors}/{n_vectors}') + + del source + + # Compute mean vector for SQ8 quantization + print('Computing mean vector...') + mean = all_vectors.mean(axis=0).astype(np.float32) + + # Create SQ8 HNSW index + print('Creating SQ8 HNSW index...') + params = HNSWParams() + params.dim = DIM + params.metric = METRIC + params.multi = MULTI + params.type = TYPE + params.M = M + params.efConstruction = EF_CONSTRUCTION + params.quantType = VecSimQuant_SQ8 + params.quantParams = mean + + sq8_index = HNSWIndex(params) + + # Add vectors + print('Indexing vectors...') + if MULTI: + added = 0 + for label, vecs in label_vectors.items(): + for vec in vecs: + sq8_index.add_vector(vec.astype(np.float16), label) + #sq8_index.add_vector(vec, label) + added += 1 + if label % 100000 == 0: + print(f' label {label}/{n_labels} ({added} vectors added)') + print(f' Done: {added} vectors added across {len(label_vectors)} labels') + else: + for label in range(n_vectors): + sq8_index.add_vector(all_vectors[label].astype(np.float16), label) + #sq8_index.add_vector(all_vectors[label], label) + if label % 100000 == 0: + print(f' {label}/{n_vectors}') + print(f' {n_vectors}/{n_vectors}') + + # Save + print(f'Saving SQ8 index to: {OUTPUT_INDEX}') + sq8_index.save_index(OUTPUT_INDEX) + + # Verify + print('Verifying saved index...') + loaded = HNSWIndex(OUTPUT_INDEX) + loaded.check_integrity() + expected_vectors = n_vectors + assert loaded.index_size() == expected_vectors, \ + f'Expected {expected_vectors} vectors, got {loaded.index_size()}' + file_size = os.path.getsize(OUTPUT_INDEX) + print(f' File size: {file_size / (1024**3):.2f} GB') + print(f' Vectors: {loaded.index_size()}') + print('Done!') + +if __name__ == '__main__': + convert() diff --git a/tests/benchmark/run_files/bm_basics_multi_fp16.cpp b/tests/benchmark/run_files/bm_basics_multi_fp16.cpp index f7c407895..8ad11ba32 100644 --- a/tests/benchmark/run_files/bm_basics_multi_fp16.cpp +++ b/tests/benchmark/run_files/bm_basics_multi_fp16.cpp @@ -1,3 +1,6 @@ +/* + * SPDX-FileCopyrightText: Modifications Copyright 2026 Arm Limited and/or its affiliates + */ #include "benchmark/bm_vecsim_basics.h" #include "VecSim/algorithms/brute_force/brute_force_multi.h" #include "VecSim/algorithms/hnsw/hnsw_multi.h" @@ -10,7 +13,9 @@ bool BM_VecSimGeneral::is_multi = true; uint32_t BM_VecSimGeneral::enabled_index_types = IndexTypeFlags::INDEX_MASK_BF | IndexTypeFlags::INDEX_MASK_HNSW | - IndexTypeFlags::INDEX_MASK_TIERED_HNSW; + IndexTypeFlags::INDEX_MASK_TIERED_HNSW | + IndexTypeFlags::INDEX_MASK_HNSW_SQ8 | + IndexTypeFlags::INDEX_MASK_TIERED_HNSW_SQ8; size_t BM_VecSimGeneral::n_queries = 10000; size_t BM_VecSimGeneral::n_vectors = 1111025; diff --git a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp index d12aa9d61..c45d72caa 100644 --- a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp @@ -1,3 +1,6 @@ +/* + * SPDX-FileCopyrightText: Modifications Copyright 2026 Arm Limited and/or its affiliates + */ #include "benchmark/bm_vecsim_basics.h" #include "VecSim/algorithms/brute_force/brute_force_multi.h" #include "VecSim/algorithms/hnsw/hnsw_multi.h" @@ -9,7 +12,9 @@ bool BM_VecSimGeneral::is_multi = true; uint32_t BM_VecSimGeneral::enabled_index_types = IndexTypeFlags::INDEX_MASK_BF | IndexTypeFlags::INDEX_MASK_HNSW | - IndexTypeFlags::INDEX_MASK_TIERED_HNSW; + IndexTypeFlags::INDEX_MASK_TIERED_HNSW | + IndexTypeFlags::INDEX_MASK_HNSW_SQ8 | + IndexTypeFlags::INDEX_MASK_TIERED_HNSW_SQ8; size_t BM_VecSimGeneral::n_queries = 10000; size_t BM_VecSimGeneral::n_vectors = 1111025; diff --git a/tests/benchmark/run_files/bm_basics_single_fp16.cpp b/tests/benchmark/run_files/bm_basics_single_fp16.cpp index 564160a07..19950fffd 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp16.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp16.cpp @@ -1,3 +1,6 @@ +/* + * SPDX-FileCopyrightText: Modifications Copyright 2026 Arm Limited and/or its affiliates + */ #include "benchmark/bm_vecsim_basics.h" #include "VecSim/algorithms/brute_force/brute_force_single.h" #include "VecSim/algorithms/hnsw/hnsw_single.h" @@ -8,7 +11,8 @@ ***************************************/ bool BM_VecSimGeneral::is_multi = false; -uint32_t BM_VecSimGeneral::enabled_index_types = DEFAULT_BM_INDEXES_MASK; +uint32_t BM_VecSimGeneral::enabled_index_types = + DEFAULT_BM_INDEXES_MASK | INDEX_MASK_HNSW_SQ8 | INDEX_MASK_TIERED_HNSW_SQ8; size_t BM_VecSimGeneral::n_queries = 10000; size_t BM_VecSimGeneral::n_vectors = 1000000; diff --git a/tests/benchmark/run_files/bm_basics_single_fp32.cpp b/tests/benchmark/run_files/bm_basics_single_fp32.cpp index 37915235f..d6587bc78 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp32.cpp @@ -1,3 +1,6 @@ +/* + * SPDX-FileCopyrightText: Modifications Copyright 2026 Arm Limited and/or its affiliates + */ #include "benchmark/bm_vecsim_basics.h" #include "VecSim/algorithms/brute_force/brute_force_single.h" #include "VecSim/algorithms/hnsw/hnsw_single.h" @@ -7,7 +10,8 @@ ***************************************/ bool BM_VecSimGeneral::is_multi = false; -uint32_t BM_VecSimGeneral::enabled_index_types = DEFAULT_BM_INDEXES_MASK; +uint32_t BM_VecSimGeneral::enabled_index_types = + DEFAULT_BM_INDEXES_MASK | INDEX_MASK_HNSW_SQ8 | INDEX_MASK_TIERED_HNSW_SQ8; size_t BM_VecSimGeneral::n_queries = 10000; size_t BM_VecSimGeneral::n_vectors = 1000000; diff --git a/tests/unit/test_hnsw_sq8.cpp b/tests/unit/test_hnsw_sq8.cpp index b0f64521d..795dae0fe 100644 --- a/tests/unit/test_hnsw_sq8.cpp +++ b/tests/unit/test_hnsw_sq8.cpp @@ -638,28 +638,6 @@ TEST(HNSWSQ8ParamsTest, MeanCenteredFP16L2BoundedValues) { } } -// V4 cannot encode the quantization settings needed to reload an SQ8 index. -TYPED_TEST(HNSWSQ8Test, RejectsSerialization) { - HNSWParams params = {.dim = 4, .initialCapacity = 1}; - this->SetUp(params); - ASSERT_EQ(this->GenerateAndAddVector(0, 0.25f, 0.25f), 1); - - const auto file_name = std::string(getenv("ROOT")) + "/tests/unit/sq8_should_not_be_written"; - const std::string existing_contents = "existing snapshot"; - { - std::ofstream output(file_name, std::ios::binary); - output.write(existing_contents.data(), existing_contents.size()); - } - - EXPECT_THROW(this->CastToHNSW()->saveIndex(file_name), std::runtime_error); - - std::ifstream input(file_name, std::ios::binary); - const std::string saved_contents{std::istreambuf_iterator(input), - std::istreambuf_iterator()}; - EXPECT_EQ(saved_contents, existing_contents); - std::remove(file_name.c_str()); -} - // Exercise graph construction's stored-to-stored IP kernel with non-degenerate quantization ranges. TYPED_TEST(HNSWSQ8Test, GraphConstructionIP) { constexpr size_t n = 100; From 08a69b219995003d1a01f6412d1668f5f9d99658 Mon Sep 17 00:00:00 2001 From: Zhang Xiangze Date: Thu, 24 Sep 2026 09:02:05 +0000 Subject: [PATCH 2/3] Fix benchmarks --- src/VecSim/algorithms/hnsw/hnsw_tiered.h | 11 ++-- src/VecSim/index_factories/hnsw_factory.cpp | 24 +++++++-- src/VecSim/index_factories/hnsw_factory.h | 2 + src/VecSim/index_factories/tiered_factory.cpp | 9 +++- src/VecSim/index_factories/tiered_factory.h | 10 ++-- tests/benchmark/bm_common.h | 6 +-- .../bm_basics_initialize_fp16.h | 2 +- tests/benchmark/bm_vecsim_general.h | 2 + tests/benchmark/bm_vecsim_index.h | 11 ++-- tests/unit/test_hnsw_sq8.cpp | 51 +++++++++++++++++++ 10 files changed, 100 insertions(+), 28 deletions(-) diff --git a/src/VecSim/algorithms/hnsw/hnsw_tiered.h b/src/VecSim/algorithms/hnsw/hnsw_tiered.h index 97591b3eb..49ea13184 100644 --- a/src/VecSim/algorithms/hnsw/hnsw_tiered.h +++ b/src/VecSim/algorithms/hnsw/hnsw_tiered.h @@ -799,14 +799,9 @@ TieredHNSWIndex::TieredHNSWIndex(HNSWIndex(hnsw_index, bf_index, tiered_index_params, allocator), labelToInsertJobs(this->allocator), idToRepairJobs(this->allocator), idToSwapJob(this->allocator), invalidJobs(this->allocator), currInvalidJobId(0), - readySwapJobs(0), isQuantized(false) { - // For benchmark, we create tiered hnsw index from existing hnsw index - // primaryIndexParams is nullptr and accumulation phase is skipped for sq8 - if (!tiered_index_params.primaryIndexParams) { - return; - } - this->isQuantized = - tiered_index_params.primaryIndexParams->algoParams.hnswParams.quantType != VecSimQuant_NONE; + readySwapJobs(0), + isQuantized(tiered_index_params.primaryIndexParams->algoParams.hnswParams.quantType != + VecSimQuant_NONE) { const auto &hnsw_params = tiered_index_params.primaryIndexParams->algoParams.hnswParams; const size_t normalization_set_size = tiered_index_params.specificParams.tieredHnswParams.QuantNormalizationSetSize; diff --git a/src/VecSim/index_factories/hnsw_factory.cpp b/src/VecSim/index_factories/hnsw_factory.cpp index 14a906014..45de187cd 100644 --- a/src/VecSim/index_factories/hnsw_factory.cpp +++ b/src/VecSim/index_factories/hnsw_factory.cpp @@ -43,6 +43,14 @@ template : sq8::storage_bytes_count(dim); } +size_t GetSQ8StoredDataSize(VecSimMetric metric, size_t dim, bool with_mean) { + if (metric == VecSimMetric_L2) { + return GetSQ8StoredDataSize(dim, with_mean); + } + assert(metric == VecSimMetric_IP || metric == VecSimMetric_Cosine); + return GetSQ8StoredDataSize(dim, with_mean); +} + // Cosine over pre-normalized vectors is computed as inner product. [[nodiscard]] constexpr VecSimMetric ResolveSQ8Metric(VecSimMetric metric, bool is_normalized) { return (is_normalized && metric == VecSimMetric_Cosine) ? VecSimMetric_IP : metric; @@ -357,16 +365,22 @@ VecSimIndex *NewIndex(const std::string &location, bool is_normalized) { AbstractIndexInitParams abstractInitParams = VecSimFactory::NewAbstractInitParams(¶ms, nullptr, is_normalized); - // SQ8 quantized index path - if (params.quantType == VecSimQuant_SQ8) { + if (params.quantType != VecSimQuant_NONE) { + // Reject unknown quantizers instead of silently loading an unquantized index. + if (params.quantType != VecSimQuant_SQ8) { + return NULL; + } + const float *mean_ptr = meanVector.empty() ? nullptr : meanVector.data(); - VecSimMetric metric = params.metric; - if (is_normalized && metric == VecSimMetric_Cosine) { - metric = VecSimMetric_IP; + const VecSimMetric metric = ResolveSQ8Metric(params.metric, is_normalized); + + if (!SQ8ParamsSupported(params.type, metric)) { + return NULL; } // Override blob sizes for SQ8 storage layout. size_t dim = params.dim; + abstractInitParams.isQuantized = true; if (metric == VecSimMetric_L2) { abstractInitParams.storedDataSize = GetSQ8StoredDataSize(dim, mean_ptr != nullptr); diff --git a/src/VecSim/index_factories/hnsw_factory.h b/src/VecSim/index_factories/hnsw_factory.h index 28f95008f..5a5e03603 100644 --- a/src/VecSim/index_factories/hnsw_factory.h +++ b/src/VecSim/index_factories/hnsw_factory.h @@ -24,6 +24,8 @@ namespace HNSWFactory { */ VecSimIndex *NewIndex(const VecSimParams *params, bool is_normalized = false); VecSimIndex *NewIndex(const HNSWParams *params, bool is_normalized = false); + +size_t GetSQ8StoredDataSize(VecSimMetric metric, size_t dim, bool with_mean); size_t EstimateInitialSize(const HNSWParams *params, bool is_normalized = false); size_t EstimateInitialSize(const HNSWParams *params, bool is_normalized, bool with_mean); size_t EstimateElementSize(const HNSWParams *params); diff --git a/src/VecSim/index_factories/tiered_factory.cpp b/src/VecSim/index_factories/tiered_factory.cpp index c638a8495..ed46c017c 100644 --- a/src/VecSim/index_factories/tiered_factory.cpp +++ b/src/VecSim/index_factories/tiered_factory.cpp @@ -85,8 +85,13 @@ inline VecSimIndex *NewIndex(const TieredIndexParams *params) { AbstractIndexInitParams abstractInitParams = VecSimFactory::NewAbstractInitParams(&bf_params, params->primaryIndexParams->logCtx, false); assert(hnsw_index->getInputBlobSize() == abstractInitParams.storedDataSize); - assert(hnsw_params.quantType != VecSimQuant_NONE || - hnsw_index->getStoredDataSize() == abstractInitParams.storedDataSize); + [[maybe_unused]] const size_t expected_stored_size = + hnsw_params.quantType == VecSimQuant_SQ8 + ? HNSWFactory::GetSQ8StoredDataSize( + hnsw_params.metric, hnsw_params.dim, + backend_params.algoParams.hnswParams.quantParams != nullptr) + : abstractInitParams.storedDataSize; + assert(hnsw_index->getStoredDataSize() == expected_stored_size); auto frontendIndex = static_cast *>( BruteForceFactory::NewIndex(&bf_params, abstractInitParams, false)); diff --git a/src/VecSim/index_factories/tiered_factory.h b/src/VecSim/index_factories/tiered_factory.h index 6f0d1f2b6..c37a06436 100644 --- a/src/VecSim/index_factories/tiered_factory.h +++ b/src/VecSim/index_factories/tiered_factory.h @@ -16,6 +16,7 @@ #include "VecSim/algorithms/hnsw/hnsw_tiered.h" #include "VecSim/algorithms/svs/svs_tiered.h" #include "VecSim/algorithms/brute_force/brute_force.h" +#include "VecSim/index_factories/hnsw_factory.h" #include "VecSim/index_factories/factory_utils.h" namespace TieredFactory { @@ -43,9 +44,12 @@ VecSimIndex *NewIndex(const TieredIndexParams *params, HNSWIndexgetInputBlobSize() == abstractInitParams.storedDataSize); - const HNSWParams *hnsw_params = ¶ms->primaryIndexParams->algoParams.hnswParams; - assert(hnsw_params->quantType != VecSimQuant_NONE || - hnsw_index->getStoredDataSize() == abstractInitParams.storedDataSize); + [[maybe_unused]] const size_t expected_stored_size = + hnsw_index->quantType == VecSimQuant_SQ8 + ? HNSWFactory::GetSQ8StoredDataSize(bf_params.metric, bf_params.dim, + !hnsw_index->serializedMeanVector.empty()) + : abstractInitParams.storedDataSize; + assert(hnsw_index->getStoredDataSize() == expected_stored_size); auto frontendIndex = static_cast *>( BruteForceFactory::NewIndex(&bf_params, abstractInitParams, false)); diff --git a/tests/benchmark/bm_common.h b/tests/benchmark/bm_common.h index 6ef4f9fc9..bffda8837 100644 --- a/tests/benchmark/bm_common.h +++ b/tests/benchmark/bm_common.h @@ -111,7 +111,7 @@ void BM_VecSimCommon::TopK_Tiered(benchmark::State &st, unsigned s std::atomic_int iter = 0; auto tiered_index = dynamic_cast *>(GET_INDEX(INDEX_TIERED_HNSW)); - size_t total_iters = 50; + constexpr size_t total_iters = BM_VecSimGeneral::tiered_topk_iterations; VecSimQueryReply *all_results[total_iters]; auto parallel_knn_search = [](AsyncJob *job) { @@ -159,7 +159,7 @@ void BM_VecSimCommon::TopK_Tiered_SQ8(benchmark::State &st) { std::atomic_int iter = 0; auto tiered_index = dynamic_cast *>(GET_INDEX(INDEX_TIERED_HNSW_SQ8)); - size_t total_iters = 50; + constexpr size_t total_iters = BM_VecSimGeneral::tiered_topk_iterations; VecSimQueryReply *all_results[total_iters]; auto parallel_knn_search = [](AsyncJob *job) { @@ -228,5 +228,5 @@ void BM_VecSimCommon::TopK_Tiered_SQ8(benchmark::State &st) { ->Args({200, 100}) \ ->Args({500, 500}) \ ->ArgNames({"ef_runtime", "k"}) \ - ->Iterations(50) \ + ->Iterations(BM_VecSimGeneral::tiered_topk_iterations) \ ->Unit(benchmark::kMillisecond) diff --git a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h index 3d06a9e6d..d31b79e9c 100644 --- a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h +++ b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h @@ -96,4 +96,4 @@ REGISTER_TopK_Tiered(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8)); // Range SQ8 Tiered BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t) (benchmark::State &st) { Range_Tiered_SQ8(st); } -REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t); \ No newline at end of file +REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t); diff --git a/tests/benchmark/bm_vecsim_general.h b/tests/benchmark/bm_vecsim_general.h index 4b9357b23..b0e247514 100644 --- a/tests/benchmark/bm_vecsim_general.h +++ b/tests/benchmark/bm_vecsim_general.h @@ -37,6 +37,8 @@ class BM_VecSimGeneral : public benchmark::Fixture { public: // block_size is public because it is used to define the number of iterations on some test cases static size_t block_size; + // Shared by tiered TopK result buffers and their benchmark registration. + static constexpr size_t tiered_topk_iterations = 50; protected: static size_t dim; diff --git a/tests/benchmark/bm_vecsim_index.h b/tests/benchmark/bm_vecsim_index.h index 18a375c6f..5846aba4f 100644 --- a/tests/benchmark/bm_vecsim_index.h +++ b/tests/benchmark/bm_vecsim_index.h @@ -116,18 +116,19 @@ void BM_VecSimIndex::Initialize() { indices[INDEX_HNSW] = IndexPtr(HNSWFactory::NewIndex(AttachRootPath(hnsw_index_file))); auto *hnsw_index = CastToHNSW(indices[INDEX_HNSW]); - size_t ef_r = 10; - hnsw_index->setEf(ef_r); // Create tiered index from the loaded HNSW index. if (enabled_index_types & IndexTypeFlags::INDEX_MASK_TIERED_HNSW) { BM_VecSimGeneral::mock_thread_pool = new tieredIndexMock(); auto &mock_thread_pool = *BM_VecSimGeneral::mock_thread_pool; + VecSimParams params = { + .algo = VecSimAlgo_HNSWLIB, + .algoParams = {.hnswParams = HNSWParams{}}}; TieredIndexParams tiered_params = { .jobQueue = &mock_thread_pool.jobQ, .jobQueueCtx = mock_thread_pool.ctx, .submitCb = tieredIndexMock::submit_callback, .flatBufferLimit = block_size, - .primaryIndexParams = nullptr, + .primaryIndexParams = ¶ms, .specificParams = {TieredHNSWParams{.swapJobThreshold = 0}}}; auto *tiered_index = TieredFactory::TieredHNSWFactory::NewIndex( @@ -171,11 +172,9 @@ void BM_VecSimIndex::Initialize() { hnsw_sq8_index_file.replace(hnsw_sq8_index_file.find(".hnsw_v3"), std::string(".hnsw_v3").length(), "-sq8.hnsw_v5"); indices[INDEX_HNSW_SQ8] = - IndexPtr(HNSWFactory::NewIndex(AttachRootPath(hnsw_sq8_index_file))); + IndexPtr(HNSWFactory::NewIndex(AttachRootPath(hnsw_sq8_index_file), true)); auto *hnsw_sq8_index = CastToHNSW(indices[INDEX_HNSW_SQ8]); - size_t ef_r = 10; - hnsw_sq8_index->setEf(ef_r); // Create tiered index from the loaded SQ8 HNSW index. if (enabled_index_types & IndexTypeFlags::INDEX_MASK_TIERED_HNSW_SQ8) { diff --git a/tests/unit/test_hnsw_sq8.cpp b/tests/unit/test_hnsw_sq8.cpp index 795dae0fe..2288f02a0 100644 --- a/tests/unit/test_hnsw_sq8.cpp +++ b/tests/unit/test_hnsw_sq8.cpp @@ -9,6 +9,7 @@ #include "gtest/gtest.h" #include "VecSim/algorithms/hnsw/hnsw_single.h" +#include "VecSim/index_factories/hnsw_factory.h" #include "VecSim/types/float16.h" #include "VecSim/types/sq8.h" #include "VecSim/vec_sim.h" @@ -17,6 +18,7 @@ #include #include +#include #include #include #include @@ -182,6 +184,55 @@ TYPED_TEST(HNSWSQ8Test, getDataByLabelReportsNothingForQuantizedStorageLargeDim) TYPED_TEST(HNSWSQ8Test, CreateIndex) { this->create_index_test(); } +TYPED_TEST(HNSWSQ8Test, SerializationRoundTripV5) { + using data_t = typename TestFixture::data_t; + constexpr size_t dim = 8; + HNSWParams params = {.dim = dim, .metric = VecSimMetric_L2, .M = 8, .efConstruction = 40}; + this->SetUp(params); + + for (size_t i = 0; i < 3; ++i) { + ASSERT_EQ(this->GenerateAndAddVector(10 + i, 0.5f + static_cast(i), 0.1f), 1); + } + auto *original = this->CastToHNSW(); + ASSERT_NE(original, nullptr); + + struct TemporaryFile { + std::string path; + ~TemporaryFile() { std::remove(path.c_str()); } + } file{::testing::TempDir() + "hnsw_sq8_" + + std::string(VecSimType_ToString(TypeParam::get_index_type())) + + (TypeParam::with_quant_params ? "_mean" : "_no_mean") + ".hnsw"}; + + original->saveIndex(file.path); + std::unique_ptr loaded( + HNSWFactory::NewIndex(file.path), VecSimIndex_Free); + ASSERT_NE(loaded, nullptr); + auto *restored = dynamic_cast *>(loaded.get()); + ASSERT_NE(restored, nullptr); + EXPECT_EQ(restored->getVersion(), HNSWSerializer::EncodingVersion::V5); + EXPECT_TRUE(restored->checkIntegrity().valid_state); + EXPECT_EQ(restored->quantType, VecSimQuant_SQ8); + EXPECT_TRUE(restored->usesQuantizedStorage()); + EXPECT_EQ(restored->serializedMeanVector, this->quantization_mean); + EXPECT_EQ(restored->getStoredDataSize(), original->getStoredDataSize()); + ASSERT_EQ(VecSimIndex_IndexSize(loaded.get()), VecSimIndex_IndexSize(this->index)); + + std::vector query(dim); + this->GenerateVector(query.data(), 1.5f, 0.1f); + for (size_t i = 0; i < 3; ++i) { + EXPECT_EQ(std::memcmp(original->getDataByInternalId(i), restored->getDataByInternalId(i), + original->getStoredDataSize()), + 0); + EXPECT_FLOAT_EQ(VecSimIndex_GetDistanceFrom_Unsafe(loaded.get(), 10 + i, query.data()), + VecSimIndex_GetDistanceFrom_Unsafe(this->index, 10 + i, query.data())); + } + auto verify = [&](size_t id, double score, size_t) { + EXPECT_EQ(id, 11u); + EXPECT_FLOAT_EQ(score, VecSimIndex_GetDistanceFrom_Unsafe(this->index, id, query.data())); + }; + runTopKSearchTest(loaded.get(), query.data(), 1, verify); +} + TYPED_TEST(HNSWSQ8Test, RejectStandaloneCosine) { HNSWParams params = {.type = TypeParam::get_index_type(), .dim = 4, From 8cc606e7a40d9d924be460f13b65fd5de739ed4e Mon Sep 17 00:00:00 2001 From: Zhang Xiangze Date: Mon, 28 Sep 2026 02:10:14 +0000 Subject: [PATCH 3/3] AddLebel/DeleteLabel tests for Tiered SQ8 index --- .../bm_basics_initialize_fp16.h | 4 +++ .../bm_basics_initialize_fp32.h | 4 +++ tests/benchmark/bm_vecsim_basics.h | 33 +++++++++++++++---- tests/benchmark/bm_vecsim_index.h | 4 +++ .../run_files/bm_basics_multi_fp16.cpp | 3 ++ .../run_files/bm_basics_multi_fp32.cpp | 3 ++ .../run_files/bm_basics_single_fp16.cpp | 3 ++ .../run_files/bm_basics_single_fp32.cpp | 3 ++ 8 files changed, 51 insertions(+), 6 deletions(-) diff --git a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h index d31b79e9c..fcc4e1326 100644 --- a/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h +++ b/tests/benchmark/bm_initialization/bm_basics_initialize_fp16.h @@ -97,3 +97,7 @@ REGISTER_TopK_Tiered(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8)); BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t) (benchmark::State &st) { Range_Tiered_SQ8(st); } REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp16_index_t); + +// SQ8 Tiered add/delete benchmarks +REGISTER_AddLabel(BM_ADD_LABEL, INDEX_TIERED_HNSW_SQ8); +REGISTER_DeleteLabel(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8)); diff --git a/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h b/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h index 12e2d8608..7910f922a 100644 --- a/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h +++ b/tests/benchmark/bm_initialization/bm_basics_initialize_fp32.h @@ -104,3 +104,7 @@ REGISTER_TopK_Tiered(BM_VecSimCommon, BM_FUNC_NAME(TopK, Tiered_SQ8)); BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC_NAME(Range, Tiered_SQ8), fp32_index_t) (benchmark::State &st) { Range_Tiered_SQ8(st); } REGISTER_Range_HNSW(BM_FUNC_NAME(Range, Tiered_SQ8), fp32_index_t); + +// SQ8 Tiered add/delete benchmarks +REGISTER_AddLabel(BM_ADD_LABEL, INDEX_TIERED_HNSW_SQ8); +REGISTER_DeleteLabel(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8)); diff --git a/tests/benchmark/bm_vecsim_basics.h b/tests/benchmark/bm_vecsim_basics.h index 2b67a34cb..a7dd0b0fc 100644 --- a/tests/benchmark/bm_vecsim_basics.h +++ b/tests/benchmark/bm_vecsim_basics.h @@ -38,7 +38,8 @@ class BM_VecSimBasics : public BM_VecSimCommon { // We delete one label in each iteration. For multi index deletes multiple vectors per // iteration, for single index deletes one vector per iteration. template - static void DeleteLabel(algo_t *index, benchmark::State &st); + static void DeleteLabel(algo_t *index, benchmark::State &st, + const VecSimIndexAbstract *data_source = nullptr); // Move one label to a fresh label in each iteration. This only rewrites label bookkeeping - // no vector data is copied and, for HNSW, the graph is untouched - so it is expected to be @@ -104,10 +105,14 @@ void BM_VecSimBasics::AddLabel(benchmark::State &st) { // Note we loop over the new labels and not the internal ids. This way in multi indices BM all // the new vectors added under the same label will be removed in one call. size_t new_label_count = index->indexLabelCount(); + auto cleanup_index = static_cast(st.range(0)); + if (cleanup_index == INDEX_TIERED_HNSW) { + cleanup_index = INDEX_HNSW; + } else if (cleanup_index == INDEX_TIERED_HNSW_SQ8) { + cleanup_index = INDEX_HNSW_SQ8; + } for (size_t label = initial_label_count; label < new_label_count; label++) { - // If index is tiered HNSW, remove directly from the underline HNSW. - VecSimIndex_DeleteVector( - GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0)), label); + VecSimIndex_DeleteVector(GET_INDEX(cleanup_index), label); } assert(VecSimIndex_IndexSize(index) == N_VECTORS); } @@ -165,7 +170,8 @@ void BM_VecSimBasics::AddLabel_AsyncIngest(benchmark::State &st) { template template -void BM_VecSimBasics::DeleteLabel(algo_t *index, benchmark::State &st) { +void BM_VecSimBasics::DeleteLabel( + algo_t *index, benchmark::State &st, const VecSimIndexAbstract *data_source) { // Remove a different vector in every execution. size_t label_to_remove = 0; index->fitMemory(); @@ -177,7 +183,11 @@ void BM_VecSimBasics::DeleteLabel(algo_t *index, benchmark::State st.PauseTiming(); LabelData data(0); // Get label id(s) data. - index->getDataByLabel(label_to_remove, data); + if (data_source) { + data_source->getDataByLabel(label_to_remove, data); + } else { + index->getDataByLabel(label_to_remove, data); + } removed_labels_data.push_back(data); @@ -194,6 +204,7 @@ void BM_VecSimBasics::DeleteLabel(algo_t *index, benchmark::State if (VecSimIndex_BasicInfo(index).algo == VecSimAlgo_TIERED) { dynamic_cast *>(index)->executeReadySwapJobs(); } + assert(removed_vectors_count > 0); st.counters["memory_per_vector"] = benchmark::Counter((double)memory_delta / (double)removed_vectors_count, benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); @@ -507,6 +518,16 @@ void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { VecSimAlgo), \ st); \ } +#define DEFINE_DELETE_LABEL_WITH_DATA_SOURCE(BM_FUNC, INDEX_TYPE, INDEX_NAME, DATA_SOURCE_NAME, \ + DATA_TYPE, DIST_TYPE, VecSimAlgo, DataSourceAlgo) \ + BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, BM_FUNC, INDEX_TYPE)(benchmark::State & st) { \ + DeleteLabel>( \ + BM_VecSimIndex::get_typed_index>( \ + VecSimAlgo), \ + st, \ + BM_VecSimIndex::get_typed_index>( \ + DataSourceAlgo)); \ + } #define REGISTER_DeleteLabel(BM_FUNC) \ BENCHMARK_REGISTER_F(BM_VecSimBasics, BM_FUNC)->UNIT_AND_ITERATIONS diff --git a/tests/benchmark/bm_vecsim_index.h b/tests/benchmark/bm_vecsim_index.h index 5846aba4f..8d872438d 100644 --- a/tests/benchmark/bm_vecsim_index.h +++ b/tests/benchmark/bm_vecsim_index.h @@ -200,6 +200,10 @@ void BM_VecSimIndex::Initialize() { &tiered_params, hnsw_sq8_index); indices[INDEX_TIERED_HNSW_SQ8] = IndexPtr(sq8_tiered_index); + if (!mock_thread_pool.ctx->index_strong_ref) { + mock_thread_pool.ctx->index_strong_ref = + indices[INDEX_TIERED_HNSW_SQ8].get_shared(); + } // Release HNSW_SQ8 ownership since tiered will free it indices[INDEX_HNSW_SQ8].release_ownership(); } diff --git a/tests/benchmark/run_files/bm_basics_multi_fp16.cpp b/tests/benchmark/run_files/bm_basics_multi_fp16.cpp index 8ad11ba32..77b47f1c2 100644 --- a/tests/benchmark/run_files/bm_basics_multi_fp16.cpp +++ b/tests/benchmark/run_files/bm_basics_multi_fp16.cpp @@ -39,6 +39,9 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp16_index_t, HNSWIndex_Mul vecsim_types::float16, float, INDEX_HNSW) DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp16_index_t, TieredHNSWIndex, vecsim_types::float16, float, INDEX_TIERED_HNSW) +DEFINE_DELETE_LABEL_WITH_DATA_SOURCE(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8), fp16_index_t, + TieredHNSWIndex, BruteForceIndex_Multi, vecsim_types::float16, + float, INDEX_TIERED_HNSW_SQ8, INDEX_BF) #include "benchmark/bm_initialization/bm_basics_initialize_fp16.h" BENCHMARK_MAIN(); diff --git a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp index c45d72caa..f9fe67a72 100644 --- a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp @@ -39,6 +39,9 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp32_index_t, HNSWIndex_Mul INDEX_HNSW) DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp32_index_t, TieredHNSWIndex, float, float, INDEX_TIERED_HNSW) +DEFINE_DELETE_LABEL_WITH_DATA_SOURCE(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8), fp32_index_t, + TieredHNSWIndex, BruteForceIndex_Multi, float, float, + INDEX_TIERED_HNSW_SQ8, INDEX_BF) #include "benchmark/bm_initialization/bm_basics_initialize_fp32.h" // Test oscillations at block size boundaries. diff --git a/tests/benchmark/run_files/bm_basics_single_fp16.cpp b/tests/benchmark/run_files/bm_basics_single_fp16.cpp index 19950fffd..2b6baf1d5 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp16.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp16.cpp @@ -36,5 +36,8 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp16_index_t, HNSWIndex_Sin vecsim_types::float16, float, INDEX_HNSW) DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp16_index_t, TieredHNSWIndex, vecsim_types::float16, float, INDEX_TIERED_HNSW) +DEFINE_DELETE_LABEL_WITH_DATA_SOURCE(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8), fp16_index_t, + TieredHNSWIndex, BruteForceIndex_Single, vecsim_types::float16, + float, INDEX_TIERED_HNSW_SQ8, INDEX_BF) #include "benchmark/bm_initialization/bm_basics_initialize_fp16.h" BENCHMARK_MAIN(); diff --git a/tests/benchmark/run_files/bm_basics_single_fp32.cpp b/tests/benchmark/run_files/bm_basics_single_fp32.cpp index d6587bc78..0474337c6 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp32.cpp @@ -36,6 +36,9 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp32_index_t, HNSWIndex_Sin INDEX_HNSW) DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp32_index_t, TieredHNSWIndex, float, float, INDEX_TIERED_HNSW) +DEFINE_DELETE_LABEL_WITH_DATA_SOURCE(BM_FUNC_NAME(DeleteLabel, Tiered_SQ8), fp32_index_t, + TieredHNSWIndex, BruteForceIndex_Single, float, float, + INDEX_TIERED_HNSW_SQ8, INDEX_BF) // Test Oscilations BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, CONCAT_WITH_UNDERSCORE_ARCH(UpdateAtBlockSize, Single),