From c8e5769c0c557ebf337b5737f78b9546c3622647 Mon Sep 17 00:00:00 2001 From: lkuffo Date: Mon, 2 Mar 2026 09:44:51 +0100 Subject: [PATCH 01/24] Moving cluster.hpp away --- include/pdx/cluster.hpp | 39 +++++++++++++++++++++++++++++++++++++ include/pdx/ivf_wrapper.hpp | 26 +------------------------ include/pdx/searcher.hpp | 1 + 3 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 include/pdx/cluster.hpp diff --git a/include/pdx/cluster.hpp b/include/pdx/cluster.hpp new file mode 100644 index 0000000..36ff8c9 --- /dev/null +++ b/include/pdx/cluster.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "pdx/common.hpp" +#include "pdx/utils.hpp" +#include +#include +#include +#include +#include +#include + +namespace PDX { + +template +struct Cluster { + using data_t = pdx_data_t; + + Cluster(uint32_t num_embeddings, uint32_t num_dimensions) + : num_embeddings(num_embeddings), num_dimensions(num_dimensions), + indices(new uint32_t[num_embeddings]), + data(new data_t[static_cast(num_embeddings) * num_dimensions]) {} + + ~Cluster() { + delete[] data; + delete[] indices; + } + + uint32_t num_embeddings{}; + const uint32_t num_dimensions{}; + uint32_t* indices = nullptr; + data_t* data = nullptr; + + size_t GetInMemorySizeInBytes() const { + return sizeof(*this) + num_embeddings * sizeof(*indices) + + num_embeddings * static_cast(num_dimensions) * sizeof(*data); + } +}; + +} // namespace PDX diff --git a/include/pdx/ivf_wrapper.hpp b/include/pdx/ivf_wrapper.hpp index dd20d05..26675ac 100644 --- a/include/pdx/ivf_wrapper.hpp +++ b/include/pdx/ivf_wrapper.hpp @@ -2,6 +2,7 @@ #include "pdx/common.hpp" #include "pdx/utils.hpp" +#include "pdx/cluster.hpp" #include #include #include @@ -11,31 +12,6 @@ namespace PDX { -template -struct Cluster { - using data_t = pdx_data_t; - - Cluster(uint32_t num_embeddings, uint32_t num_dimensions) - : num_embeddings(num_embeddings), num_dimensions(num_dimensions), - indices(new uint32_t[num_embeddings]), - data(new data_t[static_cast(num_embeddings) * num_dimensions]) {} - - ~Cluster() { - delete[] data; - delete[] indices; - } - - uint32_t num_embeddings{}; - const uint32_t num_dimensions{}; - uint32_t* indices = nullptr; - data_t* data = nullptr; - - size_t GetInMemorySizeInBytes() const { - return sizeof(*this) + num_embeddings * sizeof(*indices) + - num_embeddings * static_cast(num_dimensions) * sizeof(*data); - } -}; - template class IVF { public: diff --git a/include/pdx/searcher.hpp b/include/pdx/searcher.hpp index 4cf9b4f..5e1ff90 100644 --- a/include/pdx/searcher.hpp +++ b/include/pdx/searcher.hpp @@ -4,6 +4,7 @@ #include "pdx/db_mock/predicate_evaluator.hpp" #include "pdx/distance_computers/base_computers.hpp" #include "pdx/ivf_wrapper.hpp" +#include "pdx/cluster.hpp" #include "pdx/pruners/adsampling.hpp" #include "pdx/quantizers/scalar.hpp" #include "pdx/utils.hpp" From f71e274a0a4e9ae7b71485a163a2b7ae61858a8f Mon Sep 17 00:00:00 2001 From: lkuffo Date: Mon, 2 Mar 2026 21:15:54 +0100 Subject: [PATCH 02/24] First Update API without balancing --- .gitignore | 3 + benchmarks/benchmark_utils.hpp | 2 +- .../DEFAULT/END_TO_END_PDX_ADSAMPLING.csv | 44 +++ include/pdx/cluster.hpp | 354 +++++++++++++++++- include/pdx/index.hpp | 194 +++++++++- include/pdx/ivf_wrapper.hpp | 87 +++-- include/pdx/layout.hpp | 19 +- include/pdx/searcher.hpp | 75 ++-- 8 files changed, 671 insertions(+), 107 deletions(-) create mode 100644 benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv diff --git a/.gitignore b/.gitignore index 780116a..678309f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ __pycache__/ venv .venv /build/ +/.cache/ +/.vscode/ +/.claude/ /cmake-build-debug/ /cmake-build-release/ /cmake-build*/ diff --git a/benchmarks/benchmark_utils.hpp b/benchmarks/benchmark_utils.hpp index 345a8c9..568aa24 100644 --- a/benchmarks/benchmark_utils.hpp +++ b/benchmarks/benchmark_utils.hpp @@ -43,7 +43,7 @@ class TicToc { }; // Raw binary data paths (SuperKMeans convention: data_.bin / data__test.bin) -inline std::string RAW_DATA_DIR = std::string{CMAKE_SOURCE_DIR} + "/../SuperKMeans/benchmarks/data"; +inline std::string RAW_DATA_DIR = std::string{CMAKE_SOURCE_DIR} + "/../../SuperKMeans/benchmarks/data"; inline std::string GROUND_TRUTH_JSON_DIR = std::string{CMAKE_SOURCE_DIR} + "/../SuperKMeans/benchmarks/ground_truth"; diff --git a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv new file mode 100644 index 0000000..edb72c0 --- /dev/null +++ b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv @@ -0,0 +1,44 @@ +dataset,algorithm,avg,max,min,recall,ivf_nprobe,epsilon,knn,n_queries,selectivity,num_measure_runs,avg_all,max_all,min_all +mxbai,end_to_end_pdx_tree_f32,0.312388,0.572459,0.124166,0.963101,64,0,20,1000,0,1,0.3187,0.7785,0.1242 +mxbai,end_to_end_pdx_tree_f32,0.201243,0.364292,0.080375,0.928901,25,0,20,1000,0,1,0.2034,0.437,0.08038 +mxbai,end_to_end_pdx_tree_u8,0.103335,0.162833,0.051875,0.925102,25,0,20,1000,0,1,0.1039,0.198,0.05187 +mxbai,end_to_end_pdx_tree_u8,0.102807,0.163125,0.053541,0.925102,25,0,20,1000,0,1,0.1032,0.1939,0.05354 +mxbai,end_to_end_pdx_u8,0.25533,0.304917,0.215292,0.925902,25,0,20,1000,0,1,0.2555,0.323,0.2153 +mxbai,end_to_end_pdx_tree_u8,1.78948,2.88,1.02325,0.989902,2048,0,20,1000,0,1,1.82,4.42,1.023 +mxbai,end_to_end_pdx_tree_u8,1.26635,2.08404,0.682041,0.988152,1536,0,20,1000,0,1,1.289,3.157,0.682 +mxbai,end_to_end_pdx_tree_u8,0.944368,1.50971,0.506625,0.987902,1024,0,20,1000,0,1,0.9656,2.317,0.5066 +mxbai,end_to_end_pdx_tree_u8,0.58394,0.922833,0.337625,0.986902,512,0,20,1000,0,1,0.5952,1.309,0.3376 +mxbai,end_to_end_pdx_tree_u8,0.484987,0.759958,0.283125,0.985602,384,0,20,1000,0,1,0.4938,1.031,0.2831 +mxbai,end_to_end_pdx_tree_u8,0.373163,0.582291,0.211417,0.982752,256,0,20,1000,0,1,0.3788,0.7913,0.2114 +mxbai,end_to_end_pdx_tree_u8,0.34491,0.543292,0.198625,0.982202,224,0,20,1000,0,1,0.3494,0.7139,0.1986 +mxbai,end_to_end_pdx_tree_u8,0.311604,0.485375,0.181083,0.981002,192,0,20,1000,0,1,0.3168,0.6227,0.1811 +mxbai,end_to_end_pdx_tree_u8,0.278968,0.441541,0.16625,0.979202,160,0,20,1000,0,1,0.2822,0.6052,0.1663 +mxbai,end_to_end_pdx_tree_u8,0.261679,0.41475,0.157209,0.978452,144,0,20,1000,0,1,0.2647,0.5242,0.1572 +mxbai,end_to_end_pdx_tree_u8,0.244016,0.385583,0.141917,0.976252,128,0,20,1000,0,1,0.247,0.5066,0.1419 +mxbai,end_to_end_pdx_tree_u8,0.22442,0.354333,0.120375,0.973902,112,0,20,1000,0,1,0.2268,0.4437,0.1204 +mxbai,end_to_end_pdx_tree_u8,0.20563,0.330042,0.106625,0.969802,96,0,20,1000,0,1,0.2072,0.4077,0.1066 +mxbai,end_to_end_pdx_tree_u8,0.184893,0.294,0.094708,0.965252,80,0,20,1000,0,1,0.1865,0.3626,0.09471 +mxbai,end_to_end_pdx_tree_u8,0.161937,0.254541,0.084459,0.958202,64,0,20,1000,0,1,0.1636,0.306,0.08446 +mxbai,end_to_end_pdx_tree_u8,0.150911,0.238375,0.0825,0.955102,56,0,20,1000,0,1,0.1521,0.2814,0.0825 +mxbai,end_to_end_pdx_tree_u8,0.140925,0.226042,0.074041,0.949852,48,0,20,1000,0,1,0.1417,0.2682,0.07404 +mxbai,end_to_end_pdx_tree_u8,0.125695,0.199042,0.06225,0.945502,40,0,20,1000,0,1,0.1265,0.23,0.06225 +mxbai,end_to_end_pdx_tree_u8,0.112512,0.179292,0.05875,0.937302,32,0,20,1000,0,1,0.1131,0.2147,0.05875 +mxbai,end_to_end_pdx_tree_u8,0.10687,0.172291,0.055833,0.931002,28,0,20,1000,0,1,0.1075,0.2037,0.05583 +mxbai,end_to_end_pdx_tree_u8,0.101617,0.159584,0.050625,0.927652,26,0,20,1000,0,1,0.1023,0.2239,0.05063 +mxbai,end_to_end_pdx_tree_u8,0.0986691,0.158167,0.052458,0.923202,24,0,20,1000,0,1,0.09904,0.1917,0.05246 +mxbai,end_to_end_pdx_tree_u8,0.0934823,0.147375,0.046417,0.918802,22,0,20,1000,0,1,0.09401,0.1832,0.04642 +mxbai,end_to_end_pdx_tree_u8,0.0937227,0.148541,0.046541,0.912902,20,0,20,1000,0,1,0.09427,0.1831,0.04654 +mxbai,end_to_end_pdx_tree_u8,0.0853826,0.136042,0.043584,0.906552,18,0,20,1000,0,1,0.08597,0.1896,0.04358 +mxbai,end_to_end_pdx_tree_u8,0.0814755,0.128833,0.04375,0.898352,16,0,20,1000,0,1,0.08185,0.161,0.04375 +mxbai,end_to_end_pdx_tree_u8,0.0792071,0.125417,0.039292,0.887952,14,0,20,1000,0,1,0.07957,0.1622,0.03929 +mxbai,end_to_end_pdx_tree_u8,0.0709443,0.110667,0.040375,0.874602,12,0,20,1000,0,1,0.07152,0.1331,0.04038 +mxbai,end_to_end_pdx_tree_u8,0.0696334,0.1095,0.036167,0.858952,10,0,20,1000,0,1,0.07046,0.1439,0.03617 +mxbai,end_to_end_pdx_tree_u8,0.0610953,0.0945,0.034541,0.838202,8,0,20,1000,0,1,0.062,0.126,0.03454 +mxbai,end_to_end_pdx_tree_u8,0.0533335,0.081042,0.031125,0.808852,6,0,20,1000,0,1,0.05419,0.1111,0.03112 +mxbai,end_to_end_pdx_tree_u8,0.0463168,0.068792,0.028041,0.759551,4,0,20,1000,0,1,0.04719,0.09954,0.02804 +mxbai,end_to_end_pdx_tree_u8,0.03571,0.051625,0.023875,0.65255,2,0,20,1000,0,1,0.03665,0.08154,0.02388 +mxbai,end_to_end_pdx_tree_u8,0.0290573,0.041791,0.019041,0.5061,1,0,20,1000,0,1,0.0299,0.07675,0.01904 +mxbai,end_to_end_pdx_tree_u8,0.0824076,0.129084,0.045958,0.898352,16,0,20,1000,0,1,0.08282,0.161,0.04596 +mxbai,end_to_end_pdx_tree_u8,0.0821678,0.129875,0.044417,0.898352,16,0,20,1000,0,1,0.08248,0.1599,0.04442 +mxbai,end_to_end_pdx_tree_u8,0.0871179,0.138416,0.047083,0.906552,18,0,20,1000,0,1,0.08765,0.1662,0.04708 +mxbai,end_to_end_pdx_tree_u8,0.0870912,0.138584,0.045584,0.906552,18,0,20,1000,0,1,0.0876,0.2096,0.04558 diff --git a/include/pdx/cluster.hpp b/include/pdx/cluster.hpp index 36ff8c9..f90e883 100644 --- a/include/pdx/cluster.hpp +++ b/include/pdx/cluster.hpp @@ -7,33 +7,377 @@ #include #include #include +#include #include +#include namespace PDX { template struct Cluster { using data_t = pdx_data_t; + using tombstones_t = std::unordered_set; + + constexpr static float CAPACITY_THRESHOLD = 1.1f; // 10% more than the current capacity Cluster(uint32_t num_embeddings, uint32_t num_dimensions) - : num_embeddings(num_embeddings), num_dimensions(num_dimensions), - indices(new uint32_t[num_embeddings]), - data(new data_t[static_cast(num_embeddings) * num_dimensions]) {} + : num_embeddings(num_embeddings), + used_capacity(num_embeddings), + max_capacity(static_cast(num_embeddings * CAPACITY_THRESHOLD)), + min_capacity(static_cast(num_embeddings / CAPACITY_THRESHOLD)), + num_dimensions(num_dimensions), + indices(new uint32_t[max_capacity]), + data(new data_t[static_cast(max_capacity) * num_dimensions]) {} + + Cluster(uint32_t num_embeddings, uint32_t max_capacity, uint32_t num_dimensions) + : num_embeddings(num_embeddings), + used_capacity(num_embeddings), + max_capacity(max_capacity), + min_capacity(static_cast(num_embeddings / CAPACITY_THRESHOLD)), + num_dimensions(num_dimensions), + indices(new uint32_t[max_capacity]), + data(new data_t[static_cast(max_capacity) * num_dimensions]) {} + + Cluster(Cluster&& other) noexcept + : num_embeddings(other.num_embeddings), + used_capacity(other.used_capacity), + max_capacity(other.max_capacity), + min_capacity(other.min_capacity), + num_dimensions(other.num_dimensions), + n_accessed(other.n_accessed), + n_inserted(other.n_inserted), + n_deleted(other.n_deleted), + indices(other.indices), + data(other.data), + tombstones(std::move(other.tombstones)) { + other.indices = nullptr; + other.data = nullptr; + } ~Cluster() { delete[] data; delete[] indices; } - uint32_t num_embeddings{}; + Cluster(const Cluster&) = delete; + Cluster& operator=(const Cluster&) = delete; + Cluster& operator=(Cluster&&) = delete; + + uint32_t num_embeddings{}; // Number of valid embeddings in the cluster, i.e., excluding tombstones + uint32_t used_capacity{}; // Total capacity of the cluster, i.e., including tombstones but excluding empty slots that are not yet used + uint32_t max_capacity{}; + uint32_t min_capacity{}; const uint32_t num_dimensions{}; - uint32_t* indices = nullptr; + std::mutex cluster_mutex; + size_t n_accessed = 0; + size_t n_inserted = 0; + size_t n_deleted = 0; + + uint32_t* indices = nullptr; // !These are row_ids data_t* data = nullptr; + tombstones_t tombstones; // ! Need to have indexes, not row_ids + + void AddTombstone(uint32_t index) { + tombstones.insert(index); + } + + bool HasTombstone(uint32_t index) const { + return tombstones.count(index); + } + + uint32_t PopTombstone() { + auto it = tombstones.begin(); + uint32_t val = *it; + tombstones.erase(it); + return val; + } + + void RemoveTombstone(uint32_t index) { + tombstones.erase(index); + } + + // Returns the index in cluster of the newly appended embedding + uint32_t AppendEmbedding(uint32_t row_id, const data_t* PDX_RESTRICT embedding) { + uint32_t next_free_idx = used_capacity; + bool replaced_tombstone = false; + if (!tombstones.empty()) { + next_free_idx = PopTombstone(); + replaced_tombstone = true; + } + if (next_free_idx >= max_capacity) { + throw std::runtime_error( + "AppendEmbedding: cluster buffer overflow (used_capacity=" + + std::to_string(used_capacity) + ", max_capacity=" + + std::to_string(max_capacity) + ")" + ); + } + InsertEmbedding(next_free_idx, row_id, embedding); + num_embeddings++; + if (!replaced_tombstone) { + used_capacity++; + } + assert(num_embeddings <= used_capacity); + + n_inserted++; + return next_free_idx; + } + + void DeleteEmbedding(uint32_t index_in_cluster) { + AddTombstone(index_in_cluster); + num_embeddings--; + // Used Capacity remains the same because we are not compacting the cluster + // Should we compact directly? Moving the last embedding to the tombstone + // Maybe worth if we are in memory + n_deleted++; + } size_t GetInMemorySizeInBytes() const { return sizeof(*this) + num_embeddings * sizeof(*indices) + num_embeddings * static_cast(num_dimensions) * sizeof(*data); } + + // Gather all embeddings from the PDX layout into a contiguous row-major buffer. + // Assumes no tombstones (call CompactCluster first). + std::unique_ptr GetHorizontalEmbeddingsFromPDXBuffer() const { + std::unique_ptr out(new data_t[static_cast(num_embeddings) * num_dimensions]); + for (uint32_t i = 0; i < num_embeddings; i++) { + ReadEmbeddingFromPDXBuffer(i, out.get() + static_cast(i) * num_dimensions); + } + return out; + } + + // Gather a single embedding from the PDX layout into a row-major buffer. + std::unique_ptr GetHorizontalEmbeddingFromPDXBuffer(uint32_t idx_in_cluster) const { + std::unique_ptr out(new data_t[num_dimensions]); + ReadEmbeddingFromPDXBuffer(idx_in_cluster, out.get()); + return out; + } + + // Writes the valid PDX data row-by-row, stripping stride gaps. + // Assumes no tombstones (call CompactCluster first). + void SavePDXData(std::ostream& out) const { + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t stride = max_capacity; + + if constexpr (Q == Quantization::F32) { + for (uint32_t d = 0; d < vertical_d; d++) { + out.write( + reinterpret_cast(data + d * stride), + sizeof(data_t) * num_embeddings + ); + } + } else { + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + out.write( + reinterpret_cast(data + d * stride), + num_embeddings * U8_INTERLEAVE_SIZE + ); + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + out.write( + reinterpret_cast(data + d * stride), + num_embeddings * remaining + ); + } + } + + const data_t* h_base = data + stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + out.write( + reinterpret_cast(h_base), + sizeof(data_t) * num_embeddings * H_DIM_SIZE + ); + h_base += stride * H_DIM_SIZE; + } + } + + // Reads compact PDX data from ptr and places it into the strided buffer. + // Advances ptr past all read data. + void LoadPDXData(char*& ptr) { + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t stride = max_capacity; + + if constexpr (Q == Quantization::F32) { + for (uint32_t d = 0; d < vertical_d; d++) { + memcpy(data + d * stride, ptr, sizeof(data_t) * num_embeddings); + ptr += sizeof(data_t) * num_embeddings; + } + } else { + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + memcpy(data + d * stride, ptr, num_embeddings * U8_INTERLEAVE_SIZE); + ptr += num_embeddings * U8_INTERLEAVE_SIZE; + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + memcpy(data + d * stride, ptr, num_embeddings * remaining); + ptr += num_embeddings * remaining; + } + } + + data_t* h_base = data + stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + memcpy(h_base, ptr, sizeof(data_t) * num_embeddings * H_DIM_SIZE); + ptr += sizeof(data_t) * num_embeddings * H_DIM_SIZE; + h_base += stride * H_DIM_SIZE; + } + } + + // Caller must hold cluster_mutex + // Returns: vector of (row_id, new_index_in_cluster) for each moved embedding + // TODO(@lkuffo, high): I dont like this while loops too much. Its confusing (but it works) + std::vector> CompactCluster(){ + std::vector> moves; + if (tombstones.empty()){ + return moves; + } + moves.reserve(tombstones.size()); + + // First: shrink past any tombstoned tail positions (no data movement needed) + while (used_capacity > 0 && HasTombstone(used_capacity - 1)) { + RemoveTombstone(used_capacity - 1); + indices[used_capacity - 1] = 0; + used_capacity--; + } + + // Second: fill remaining interior tombstones by moving from the tail + while (!tombstones.empty()) { + uint32_t tombstone_idx = PopTombstone(); + uint32_t last_idx = used_capacity - 1; + CopyEmbeddingInPDXLayout(last_idx, tombstone_idx); + indices[tombstone_idx] = indices[last_idx]; + moves.emplace_back(indices[tombstone_idx], tombstone_idx); + indices[last_idx] = 0; + used_capacity--; + // The new tail might also be a tombstone, drain it + while (used_capacity > 0 && HasTombstone(used_capacity - 1)) { + RemoveTombstone(used_capacity - 1); + indices[used_capacity - 1] = 0; + used_capacity--; + } + } + + assert(num_embeddings == used_capacity); + return moves; + } + +private: + // Gather-reads one embedding from the transposed PDX buffer into a horizontal (row-major) output. + // Reverse of InsertEmbedding. + void ReadEmbeddingFromPDXBuffer(uint32_t idx_in_cluster, data_t* out) const { + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t stride = max_capacity; + + if constexpr (Q == Quantization::F32) { + for (uint32_t d = 0; d < vertical_d; d++) { + out[d] = data[d * stride + idx_in_cluster]; + } + } else { + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + memcpy(out + d, + data + d * stride + static_cast(idx_in_cluster) * U8_INTERLEAVE_SIZE, + U8_INTERLEAVE_SIZE); + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + memcpy(out + d, + data + d * stride + static_cast(idx_in_cluster) * remaining, + remaining); + } + } + + const data_t* h_base = data + stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + memcpy(out + vertical_d + j, + h_base + static_cast(idx_in_cluster) * H_DIM_SIZE, + H_DIM_SIZE * sizeof(data_t)); + h_base += stride * H_DIM_SIZE; + } + } + + // Scatter-writes a horizontal (row-major) embedding into the transposed PDX buffer layout. + // This function assumes thread safety (caller must hold cluster_mutex). + void InsertEmbedding(uint32_t idx_in_cluster, uint32_t row_id, const data_t* embedding) { + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t stride = max_capacity; + + if constexpr (Q == Quantization::F32) { + // Vertical: column-major, one float per dimension row + for (uint32_t d = 0; d < vertical_d; d++) { + data[d * stride + idx_in_cluster] = embedding[d]; + } + } else { + // U8 Vertical: interleaved in groups of U8_INTERLEAVE_SIZE + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + memcpy(data + d * stride + static_cast(idx_in_cluster) * U8_INTERLEAVE_SIZE, + embedding + d, U8_INTERLEAVE_SIZE); + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + memcpy(data + d * stride + static_cast(idx_in_cluster) * remaining, + embedding + d, remaining); + } + } + + // Horizontal: groups of H_DIM_SIZE, row-major within each group + data_t* h_base = data + stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + memcpy(h_base + static_cast(idx_in_cluster) * H_DIM_SIZE, + embedding + vertical_d + j, + H_DIM_SIZE * sizeof(data_t)); + h_base += stride * H_DIM_SIZE; + } + + indices[idx_in_cluster] = row_id; + } + + // Copies an embedding within the PDX buffer from one position to another. + // This function assumes thread safety (caller must hold cluster_mutex). + void CopyEmbeddingInPDXLayout(uint32_t src_idx, uint32_t dst_idx) { + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t stride = max_capacity; + + if constexpr (Q == Quantization::F32) { + for (uint32_t d = 0; d < vertical_d; d++) { + data[d * stride + dst_idx] = data[d * stride + src_idx]; + } + } else { + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + memcpy(data + d * stride + static_cast(dst_idx) * U8_INTERLEAVE_SIZE, + data + d * stride + static_cast(src_idx) * U8_INTERLEAVE_SIZE, + U8_INTERLEAVE_SIZE); + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + memcpy(data + d * stride + static_cast(dst_idx) * remaining, + data + d * stride + static_cast(src_idx) * remaining, + remaining); + } + } + + data_t* h_base = data + stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + memcpy(h_base + static_cast(dst_idx) * H_DIM_SIZE, + h_base + static_cast(src_idx) * H_DIM_SIZE, + H_DIM_SIZE * sizeof(data_t)); + h_base += stride * H_DIM_SIZE; + } + } }; } // namespace PDX diff --git a/include/pdx/index.hpp b/include/pdx/index.hpp index 6e3b933..8037a15 100644 --- a/include/pdx/index.hpp +++ b/include/pdx/index.hpp @@ -155,6 +155,8 @@ void PopulateIVFClusters( } StoreClusterEmbeddings(cluster, ivf, tmp, cluster_size); } + + ivf.ComputeClusterOffsets(); } class IPDXIndex { @@ -168,7 +170,7 @@ class IPDXIndex { ) const = 0; virtual void BuildIndex(const float* embeddings, size_t num_embeddings) = 0; virtual void SetNProbe(uint32_t n_probe) const = 0; - virtual void Save(const std::string& path) const = 0; + virtual void Save(const std::string& path) = 0; virtual void Restore(const std::string& path) = 0; virtual uint32_t GetNumDimensions() const = 0; virtual uint32_t GetNumClusters() const = 0; @@ -181,6 +183,7 @@ template class PDXIndex : public IPDXIndex { public: using embedding_storage_t = PDX::pdx_data_t; + using cluster_t = PDX::Cluster; private: PDXIndexConfig config{}; @@ -196,6 +199,9 @@ class PDXIndex : public IPDXIndex { return PDXIndexType::PDX_U8; } + // Known bug: When the cluster is compacted, row_id_cluster_mapping is not updated, + // Which leads to inconsistency between the cluster's internal state and the mapping. + // This can cause errors in subsequent deletions or filtered searches that rely on the mapping. void BuildRowIdClusterMapping() { size_t total = 0; for (size_t c = 0; c < index.num_clusters; c++) { @@ -211,11 +217,11 @@ class PDXIndex : public IPDXIndex { PDX::PredicateEvaluator CreatePredicateEvaluator(const std::vector& passing_row_ids ) const { - PDX::PredicateEvaluator evaluator(index.num_clusters, row_id_cluster_mapping.size()); + PDX::PredicateEvaluator evaluator(index.num_clusters, index.total_capacity); for (const auto row_id : passing_row_ids) { const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; evaluator.n_passing_tuples[cluster_id]++; - evaluator.selection_vector[searcher->cluster_offsets[cluster_id] + index_in_cluster] = + evaluator.selection_vector[index.cluster_offsets[cluster_id] + index_in_cluster] = 1; } return evaluator; @@ -230,10 +236,17 @@ class PDXIndex : public IPDXIndex { pruner = std::make_unique(config.num_dimensions, config.seed); } - void Save(const std::string& path) const override { + void Save(const std::string& path) override { + // Compact all clusters before saving + for (uint32_t c = 0; c < index.num_clusters; c++) { + auto moves = index.clusters[c].CompactCluster(); + for (const auto& [row_id, new_idx] : moves) { + row_id_cluster_mapping[row_id] = {c, new_idx}; + } + } + std::ofstream out(path, std::ios::binary); - // Index type flag uint8_t type_flag = static_cast(GetIndexType()); out.write(reinterpret_cast(&type_flag), sizeof(uint8_t)); @@ -306,7 +319,14 @@ class PDXIndex : public IPDXIndex { std::vector GetClusterRowIds(uint32_t cluster_id) const override { const auto& cluster = index.clusters[cluster_id]; - return {cluster.indices, cluster.indices + cluster.num_embeddings}; + std::vector row_ids; + row_ids.reserve(cluster.num_embeddings); + for (uint32_t i = 0; i < cluster.used_capacity; i++) { + if (!cluster.HasTombstone(i)) { + row_ids.push_back(cluster.indices[i]); + } + } + return row_ids; } size_t GetInMemorySizeInBytes() const override { @@ -324,10 +344,8 @@ class PDXIndex : public IPDXIndex { size += pruner->num_dimensions * sizeof(uint32_t); // flip_masks } } - // Searcher: cluster_offsets array if (searcher) { size += sizeof(*searcher); - size += index.num_clusters * sizeof(size_t); } // Row ID to cluster mapping size += row_id_cluster_mapping.capacity() * sizeof(std::pair); @@ -415,6 +433,7 @@ template class PDXTreeIndex : public IPDXIndex { public: using embedding_storage_t = PDX::pdx_data_t; + using cluster_t = PDX::Cluster; private: PDXIndexConfig config{}; @@ -446,11 +465,11 @@ class PDXTreeIndex : public IPDXIndex { PDX::PredicateEvaluator CreatePredicateEvaluator(const std::vector& passing_row_ids ) const { - PDX::PredicateEvaluator evaluator(index.num_clusters, row_id_cluster_mapping.size()); + PDX::PredicateEvaluator evaluator(index.num_clusters, index.total_capacity); for (const auto row_id : passing_row_ids) { const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; evaluator.n_passing_tuples[cluster_id]++; - evaluator.selection_vector[searcher->cluster_offsets[cluster_id] + index_in_cluster] = + evaluator.selection_vector[index.cluster_offsets[cluster_id] + index_in_cluster] = 1; } return evaluator; @@ -465,7 +484,19 @@ class PDXTreeIndex : public IPDXIndex { pruner = std::make_unique(config.num_dimensions, config.seed); } - void Save(const std::string& path) const override { + void Save(const std::string& path) override { + // Compact L1 clusters before saving (update row_id_cluster_mapping from moves) + for (uint32_t c = 0; c < index.num_clusters; c++) { + auto moves = index.clusters[c].CompactCluster(); + for (const auto& [row_id, new_idx] : moves) { + row_id_cluster_mapping[row_id] = {c, new_idx}; + } + } + // Compact L0 clusters (no mapping to update for meso-clusters) + for (uint32_t c = 0; c < index.l0.num_clusters; c++) { + index.l0.clusters[c].CompactCluster(); + } + std::ofstream out(path, std::ios::binary); // Index type flag @@ -542,6 +573,134 @@ class PDXTreeIndex : public IPDXIndex { return searcher->FilteredSearch(query_embedding, knn, evaluator); } + // TODO: Concurrent writes always go through a single writer thread + void Append(size_t row_id, const float* PDX_RESTRICT embedding){ + // Row ids must be dense and sequential (next expected = current size of the mapping) + if (row_id != row_id_cluster_mapping.size()) { + throw std::invalid_argument( + "Append: row_id " + std::to_string(row_id) + " is not sequential (expected " + + std::to_string(row_id_cluster_mapping.size()) + ")" + ); + } + + const auto num_dimensions = index.num_dimensions; + const bool normalize = + config.normalize || DistanceMetricRequiresNormalization(config.distance_metric); + + auto preprocessed = NormalizeAndRotate( + embedding, 1, num_dimensions, normalize, *pruner + ); + + // Find nearest centroid for the new embedding + auto n_probe_top_level = GetTopLevelNumClusters(); + // We confidently prune a quarter of the search space + n_probe_top_level = std::max(1, n_probe_top_level / 4); + top_level_searcher->SetNProbe(n_probe_top_level); + std::vector centroid_candidates = top_level_searcher->Search(preprocessed.get(), 1); + uint32_t closest_centroid_idx = centroid_candidates[0].index; + + // Assign to the corresponding cluster + auto& cluster = index.clusters[closest_centroid_idx]; + + // Lock the cluster for the entire append + health check sequence + std::lock_guard lock(cluster.cluster_mutex); + uint32_t index_in_cluster; + if constexpr (Q == U8) { + ScalarQuantizer quantizer(num_dimensions); + auto quantized = std::make_unique(num_dimensions); + quantizer.QuantizeEmbedding(preprocessed.get(), index.quantization_base, index.quantization_scale, quantized.get()); + index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), quantized.get()); + } else { + index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), preprocessed.get()); + } + row_id_cluster_mapping.emplace_back(closest_centroid_idx, index_in_cluster); + index.total_num_embeddings++; + CheckClusterHealth(closest_centroid_idx, cluster); + } + + // TODO: Concurrent writes always go through a single writer thread + void Delete(size_t row_id){ + // Find the cluster and index in cluster for the given row_id + const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; + auto& cluster = index.clusters[cluster_id]; + + // Lock the cluster for the entire delete + health check sequence + std::lock_guard lock(cluster.cluster_mutex); + + cluster.DeleteEmbedding(index_in_cluster); + // As row_ids are assumed to be non-replacable, + // we don't need to remove the entry from row_id_cluster_mapping + index.total_num_embeddings--; + CheckClusterHealth(cluster_id, cluster); + } + + // Caller must hold cluster.cluster_mutex + void CheckClusterHealth(uint32_t cluster_id, cluster_t& cluster) { + if (cluster.used_capacity == cluster.max_capacity) { + if (cluster.num_embeddings < cluster.used_capacity) { + // There are tombstone slots we can reclaim + auto moves = cluster.CompactCluster(); + for (const auto& [row_id, new_idx] : moves) { + row_id_cluster_mapping[row_id] = {cluster_id, new_idx}; + } + } else { + // Cluster is truly full, split it + SplitCluster(cluster); + } + } else if (cluster.num_embeddings == cluster.min_capacity) { + DestroyAndMergeCluster(cluster); + } + } + + // Caller must hold cluster.cluster_mutex + void DestroyAndMergeCluster(cluster_t& cluster){ + cluster.CompactCluster(); + std::unique_ptr cluster_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); + ReassignEmbeddings(cluster, cluster_embeddings.get()); + // Remove the cluster from index.clusters + // Remove the centroid from this cluster from index.centroids + // For this, we can swap with the last cluster and centroid in the respective arrays and pop_back, then update the row_id_cluster_mapping for the affected clusters + + } + + // Caller must hold cluster.cluster_mutex + void SplitCluster(cluster_t& cluster){ + // Split the cluster into two, with the new centroids having a small symmetric perturbation + // Get inspired from: SuperKMeans::SplitCluster in superkmeans.hpp, but we need to adapt it to our data layout and also update the IVF tree structure accordingly + // Replace cluster and centroid from Clusters array with one of the new ones + // Add a new cluster to the cluster array + // For every point in the deleted cluster: + // Is the distance to one of the new centroids smaller than to the deleted centroid? + // --> Then this is the best assignment in the mesocluster + // Most points will go to either of one of the two new centroids. So we just need to compact + // the horizontal buffers for each of these centroids, and call StoreClusterEmbeddings to properly create + // the cluster's buffer + // Then, compute the centroids manually (easy, just average every dimension, we have the horizontal layout) + // And, update the index.l0 structure. The two new clusters are going to the same mesocluster + // In the remaining points (should be few), compact buffer and call REASSIGN + } + + void ReassignEmbeddings(cluster_t& cluster, const embedding_storage_t* cluster_embeddings){ + // Cluster embeddings are assumed to be in the horizontal layout + // Dequantize the cluster_embeddings + // For each embedding in the cluster, find the closest centroid among all clusters in the same mesocluster and reassign + // We need to exclude the deleted cluster's centroid from the candidates when reassigning, otherwise all embeddings will be assigned to the same closest centroid (the one from the deleted cluster) + // This is called after splitting or merging a cluster, so we only need to reassign embeddings in the affected mesocluster + // If coming from SPLIT: + // --> Take the closest splitted centroid as current assignment + // --> Run a GEMM+PRUNING iteration to get the new assignments (you need to call superkmeans API) + // If coming from MERGE: + // --> Run a GEMM iteration to get the new assignments + std::unique_ptr new_assignments(new uint32_t[cluster.num_embeddings]); + // Assume new_assignments is filled already with the new cluster assignments for each embedding in the cluster + for (size_t i = 0; i < cluster.num_embeddings; i++) { + uint32_t new_cluster_id = new_assignments[i]; + uint32_t row_id = cluster.indices[i]; + auto new_index_in_cluster = index.clusters[new_cluster_id].AppendEmbedding(row_id, cluster_embeddings + i * cluster.num_dimensions); + row_id_cluster_mapping[row_id] = {new_cluster_id, new_index_in_cluster}; + } + } + void BuildIndex(const float* const embeddings, const size_t num_embeddings) override { std::vector row_ids(num_embeddings); std::iota(row_ids.begin(), row_ids.end(), 0); @@ -669,7 +828,14 @@ class PDXTreeIndex : public IPDXIndex { std::vector GetClusterRowIds(uint32_t cluster_id) const override { const auto& cluster = index.clusters[cluster_id]; - return {cluster.indices, cluster.indices + cluster.num_embeddings}; + std::vector row_ids; + row_ids.reserve(cluster.num_embeddings); + for (uint32_t i = 0; i < cluster.used_capacity; i++) { + if (!cluster.HasTombstone(i)) { + row_ids.push_back(cluster.indices[i]); + } + } + return row_ids; } uint32_t GetTopLevelNumClusters() const { return index.l0.num_clusters; } @@ -689,15 +855,11 @@ class PDXTreeIndex : public IPDXIndex { size += pruner->num_dimensions * sizeof(uint32_t); // flip_masks } } - // L1 searcher: cluster_offsets array if (searcher) { size += sizeof(*searcher); - size += index.num_clusters * sizeof(size_t); } - // L0 top-level searcher: cluster_offsets array if (top_level_searcher) { size += sizeof(*top_level_searcher); - size += index.l0.num_clusters * sizeof(size_t); } // Row ID to cluster mapping size += row_id_cluster_mapping.capacity() * sizeof(std::pair); diff --git a/include/pdx/ivf_wrapper.hpp b/include/pdx/ivf_wrapper.hpp index 26675ac..bc1ff69 100644 --- a/include/pdx/ivf_wrapper.hpp +++ b/include/pdx/ivf_wrapper.hpp @@ -24,6 +24,9 @@ class IVF { uint32_t num_vertical_dimensions{}; uint32_t num_horizontal_dimensions{}; std::vector clusters; + size_t max_cluster_capacity{0}; + size_t total_capacity{0}; + std::unique_ptr cluster_offsets; bool is_normalized{}; std::vector centroids; @@ -67,6 +70,19 @@ class IVF { clusters.reserve(num_clusters); } + // Compute cluster_offsets, total_capacity, and max_cluster_capacity from current clusters. + // Must be called after all clusters have been created or after structural changes (split/merge). + void ComputeClusterOffsets() { + cluster_offsets.reset(new size_t[num_clusters]); + total_capacity = 0; + max_cluster_capacity = 0; + for (size_t i = 0; i < num_clusters; ++i) { + cluster_offsets[i] = total_capacity; + total_capacity += clusters[i].max_capacity; + max_cluster_capacity = std::max(max_cluster_capacity, static_cast(clusters[i].max_capacity)); + } + } + void Load(char* input) { char* next_value = input; num_dimensions = ((uint32_t*) input)[0]; @@ -76,17 +92,14 @@ class IVF { next_value += sizeof(uint32_t) * 3; num_clusters = ((uint32_t*) next_value)[0]; next_value += sizeof(uint32_t); - auto* nums_embeddings = (uint32_t*) next_value; - next_value += num_clusters * sizeof(uint32_t); + auto* cluster_headers = (uint32_t*) next_value; + next_value += num_clusters * 2 * sizeof(uint32_t); clusters.reserve(num_clusters); for (size_t i = 0; i < num_clusters; ++i) { - clusters.emplace_back(nums_embeddings[i], num_dimensions); - memcpy( - clusters[i].data, - next_value, - sizeof(data_t) * clusters[i].num_embeddings * num_dimensions - ); - next_value += sizeof(data_t) * clusters[i].num_embeddings * num_dimensions; + uint32_t n_emb = cluster_headers[i * 2]; + uint32_t max_cap = cluster_headers[i * 2 + 1]; + clusters.emplace_back(n_emb, max_cap, num_dimensions); + clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < num_clusters; ++i) { memcpy(clusters[i].indices, next_value, sizeof(uint32_t) * clusters[i].num_embeddings); @@ -110,6 +123,7 @@ class IVF { quantization_scale_squared = quantization_scale * quantization_scale; inverse_quantization_scale_squared = 1.0f / quantization_scale_squared; } + ComputeClusterOffsets(); } void Save(std::ostream& out) const { @@ -120,12 +134,10 @@ class IVF { for (size_t i = 0; i < num_clusters; ++i) { out.write(reinterpret_cast(&clusters[i].num_embeddings), sizeof(uint32_t)); + out.write(reinterpret_cast(&clusters[i].max_capacity), sizeof(uint32_t)); } for (size_t i = 0; i < num_clusters; ++i) { - out.write( - reinterpret_cast(clusters[i].data), - sizeof(data_t) * clusters[i].num_embeddings * num_dimensions - ); + clusters[i].SavePDXData(out); } for (size_t i = 0; i < num_clusters; ++i) { out.write( @@ -157,6 +169,7 @@ class IVF { in_memory_size_in_bytes += (clusters.capacity() - clusters.size()) * sizeof(*clusters.data()); in_memory_size_in_bytes += centroids.capacity() * sizeof(*centroids.data()); + in_memory_size_in_bytes += num_clusters * sizeof(size_t); // cluster_offsets return in_memory_size_in_bytes; } }; @@ -218,18 +231,15 @@ class IVFTree : public IVF { l0.num_horizontal_dimensions = h_dims; l0.num_clusters = n_clusters_l0; - auto* nums_embeddings_l0 = (uint32_t*) next_value; - next_value += n_clusters_l0 * sizeof(uint32_t); + auto* l0_headers = (uint32_t*) next_value; + next_value += n_clusters_l0 * 2 * sizeof(uint32_t); l0.clusters.reserve(n_clusters_l0); for (size_t i = 0; i < n_clusters_l0; ++i) { - l0.clusters.emplace_back(nums_embeddings_l0[i], dims); - memcpy( - l0.clusters[i].data, - next_value, - sizeof(float) * l0.clusters[i].num_embeddings * dims - ); - next_value += sizeof(float) * l0.clusters[i].num_embeddings * dims; + uint32_t n_emb = l0_headers[i * 2]; + uint32_t max_cap = l0_headers[i * 2 + 1]; + l0.clusters.emplace_back(n_emb, max_cap, dims); + l0.clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < n_clusters_l0; ++i) { memcpy( @@ -244,18 +254,15 @@ class IVFTree : public IVF { this->num_horizontal_dimensions = h_dims; this->num_clusters = n_clusters_l1; - auto* nums_embeddings_l1 = (uint32_t*) next_value; - next_value += n_clusters_l1 * sizeof(uint32_t); + auto* l1_headers = (uint32_t*) next_value; + next_value += n_clusters_l1 * 2 * sizeof(uint32_t); this->clusters.reserve(n_clusters_l1); for (size_t i = 0; i < n_clusters_l1; ++i) { - this->clusters.emplace_back(nums_embeddings_l1[i], dims); - memcpy( - this->clusters[i].data, - next_value, - sizeof(data_t) * this->clusters[i].num_embeddings * dims - ); - next_value += sizeof(data_t) * this->clusters[i].num_embeddings * dims; + uint32_t n_emb = l1_headers[i * 2]; + uint32_t max_cap = l1_headers[i * 2 + 1]; + this->clusters.emplace_back(n_emb, max_cap, dims); + this->clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < n_clusters_l1; ++i) { memcpy( @@ -286,6 +293,8 @@ class IVFTree : public IVF { this->quantization_scale_squared = this->quantization_scale * this->quantization_scale; this->inverse_quantization_scale_squared = 1.0f / this->quantization_scale_squared; } + l0.ComputeClusterOffsets(); + this->ComputeClusterOffsets(); } void Save(std::ostream& out) const { @@ -306,13 +315,13 @@ class IVFTree : public IVF { out.write( reinterpret_cast(&l0.clusters[i].num_embeddings), sizeof(uint32_t) ); - } - for (size_t i = 0; i < n_clusters_l0; ++i) { out.write( - reinterpret_cast(l0.clusters[i].data), - sizeof(float) * l0.clusters[i].num_embeddings * this->num_dimensions + reinterpret_cast(&l0.clusters[i].max_capacity), sizeof(uint32_t) ); } + for (size_t i = 0; i < n_clusters_l0; ++i) { + l0.clusters[i].SavePDXData(out); + } for (size_t i = 0; i < n_clusters_l0; ++i) { out.write( reinterpret_cast(l0.clusters[i].indices), @@ -325,13 +334,13 @@ class IVFTree : public IVF { out.write( reinterpret_cast(&this->clusters[i].num_embeddings), sizeof(uint32_t) ); - } - for (size_t i = 0; i < this->num_clusters; ++i) { out.write( - reinterpret_cast(this->clusters[i].data), - sizeof(data_t) * this->clusters[i].num_embeddings * this->num_dimensions + reinterpret_cast(&this->clusters[i].max_capacity), sizeof(uint32_t) ); } + for (size_t i = 0; i < this->num_clusters; ++i) { + this->clusters[i].SavePDXData(out); + } for (size_t i = 0; i < this->num_clusters; ++i) { out.write( reinterpret_cast(this->clusters[i].indices), diff --git a/include/pdx/layout.hpp b/include/pdx/layout.hpp index 16bf727..461a88c 100644 --- a/include/pdx/layout.hpp +++ b/include/pdx/layout.hpp @@ -29,17 +29,21 @@ inline void StoreClusterEmbeddings( const auto vertical_d = index.num_vertical_dimensions; const auto horizontal_d = index.num_horizontal_dimensions; + const auto stride = static_cast(cluster.max_capacity); Eigen::Map in(embeddings, num_embeddings, index.num_dimensions); - Eigen::Map out(cluster.data, vertical_d, num_embeddings); + // Vertical block: (vertical_d x num_embeddings) with row stride = max_capacity + Eigen::Map> out( + cluster.data, vertical_d, num_embeddings, Eigen::OuterStride(stride) + ); out.noalias() = in.leftCols(vertical_d).transpose(); - float* horizontal_out = cluster.data + num_embeddings * vertical_d; + float* horizontal_out = cluster.data + stride * vertical_d; for (size_t j = 0; j < horizontal_d; j += PDX::H_DIM_SIZE) { Eigen::Map out_h(horizontal_out, num_embeddings, PDX::H_DIM_SIZE); out_h.noalias() = in.block(0, vertical_d + j, num_embeddings, PDX::H_DIM_SIZE); - horizontal_out += num_embeddings * PDX::H_DIM_SIZE; + horizontal_out += stride * PDX::H_DIM_SIZE; } } @@ -57,29 +61,30 @@ inline void StoreClusterEmbeddings( const auto vertical_d = index.num_vertical_dimensions; const auto horizontal_d = index.num_horizontal_dimensions; + const auto stride = static_cast(cluster.max_capacity); Eigen::Map in(embeddings, num_embeddings, index.num_dimensions); size_t dim = 0; for (; dim + PDX::U8_INTERLEAVE_SIZE <= vertical_d; dim += PDX::U8_INTERLEAVE_SIZE) { Eigen::Map out_v( - cluster.data + dim * num_embeddings, num_embeddings, PDX::U8_INTERLEAVE_SIZE + cluster.data + dim * stride, num_embeddings, PDX::U8_INTERLEAVE_SIZE ); out_v.noalias() = in.block(0, dim, num_embeddings, PDX::U8_INTERLEAVE_SIZE); } if (dim < vertical_d) { auto remaining = static_cast(vertical_d - dim); Eigen::Map out_v( - cluster.data + dim * num_embeddings, num_embeddings, remaining + cluster.data + dim * stride, num_embeddings, remaining ); out_v.noalias() = in.block(0, dim, num_embeddings, remaining); } - uint8_t* horizontal_out = cluster.data + num_embeddings * vertical_d; + uint8_t* horizontal_out = cluster.data + stride * vertical_d; for (size_t j = 0; j < horizontal_d; j += PDX::H_DIM_SIZE) { Eigen::Map out_h(horizontal_out, num_embeddings, PDX::H_DIM_SIZE); out_h.noalias() = in.block(0, vertical_d + j, num_embeddings, PDX::H_DIM_SIZE); - horizontal_out += num_embeddings * PDX::H_DIM_SIZE; + horizontal_out += stride * PDX::H_DIM_SIZE; } } diff --git a/include/pdx/searcher.hpp b/include/pdx/searcher.hpp index 5e1ff90..621f449 100644 --- a/include/pdx/searcher.hpp +++ b/include/pdx/searcher.hpp @@ -37,16 +37,7 @@ class PDXearch { index_t& pdx_data; PDXearch(index_t& data_index, Pruner& pruner) - : quantizer(data_index.num_dimensions), pruner(pruner), pdx_data(data_index), - cluster_offsets(new size_t[data_index.num_clusters]) { - for (size_t i = 0; i < data_index.num_clusters; ++i) { - cluster_offsets[i] = total_embeddings; - total_embeddings += data_index.clusters[i].num_embeddings; - max_cluster_size = std::max( - max_cluster_size, static_cast(data_index.clusters[i].num_embeddings) - ); - } - } + : quantizer(data_index.num_dimensions), pruner(pruner), pdx_data(data_index) {} void SetNProbe(size_t nprobe) { ivf_nprobe = nprobe; } @@ -60,15 +51,10 @@ class PDXearch { ); } - std::unique_ptr cluster_offsets; - protected: float selectivity_threshold = 0.80; size_t ivf_nprobe = 0; - size_t total_embeddings{0}; - size_t max_cluster_size{0}; - // Prioritized list of indices of the clusters to probe. E.g., [0, 2, 1]. std::unique_ptr cluster_indices_in_access_order; size_t cluster_access_order_size = 0; @@ -219,6 +205,7 @@ class PDXearch { const quantized_embedding_t* PDX_RESTRICT query, const data_t* data, const size_t n_vectors, + const size_t buffer_stride, uint32_t k, const uint32_t* vector_indices, uint32_t* pruning_positions, @@ -230,7 +217,7 @@ class PDXearch { query, data, n_vectors, - n_vectors, + buffer_stride, 0, pdx_data.num_vertical_dimensions, pruning_distances, @@ -240,8 +227,8 @@ class PDXearch { horizontal_dimension < pdx_data.num_horizontal_dimensions; horizontal_dimension += H_DIM_SIZE) { for (size_t vector_idx = 0; vector_idx < n_vectors; vector_idx++) { - size_t data_pos = (pdx_data.num_vertical_dimensions * n_vectors) + - (horizontal_dimension * n_vectors) + (vector_idx * H_DIM_SIZE); + size_t data_pos = (pdx_data.num_vertical_dimensions * buffer_stride) + + (horizontal_dimension * buffer_stride) + (vector_idx * H_DIM_SIZE); pruning_distances[vector_idx] += distance_computer_t::Horizontal( query + pdx_data.num_vertical_dimensions + horizontal_dimension, data + data_pos, @@ -281,6 +268,7 @@ class PDXearch { const quantized_embedding_t* PDX_RESTRICT query, const data_t* data, const size_t n_vectors, + const size_t buffer_stride, uint32_t k, const uint32_t* vector_indices, uint32_t* pruning_positions, @@ -301,7 +289,7 @@ class PDXearch { horizontal_dimension < pdx_data.num_horizontal_dimensions; horizontal_dimension += H_DIM_SIZE) { size_t offset_data = - (pdx_data.num_vertical_dimensions * n_vectors) + (horizontal_dimension * n_vectors); + (pdx_data.num_vertical_dimensions * buffer_stride) + (horizontal_dimension * buffer_stride); for (size_t vector_idx = 0; vector_idx < n_vectors_not_pruned; vector_idx++) { size_t v_idx = pruning_positions[vector_idx]; size_t data_pos = offset_data + (v_idx * H_DIM_SIZE); @@ -318,7 +306,7 @@ class PDXearch { query, data, n_vectors, - n_vectors, + buffer_stride, 0, pdx_data.num_vertical_dimensions, pruning_distances, @@ -330,7 +318,7 @@ class PDXearch { query, data, n_vectors_not_pruned, - n_vectors, + buffer_stride, 0, pdx_data.num_vertical_dimensions, pruning_distances, @@ -371,6 +359,7 @@ class PDXearch { const quantized_embedding_t* PDX_RESTRICT query, const data_t* PDX_RESTRICT data, const size_t n_vectors, + const size_t buffer_stride, uint32_t k, float tuples_threshold, uint32_t* pruning_positions, @@ -408,7 +397,7 @@ class PDXearch { query, data, n_vectors, - n_vectors, + buffer_stride, current_dimension_idx, last_dimension_to_fetch, pruning_distances, @@ -430,6 +419,7 @@ class PDXearch { const quantized_embedding_t* PDX_RESTRICT query, const data_t* PDX_RESTRICT data, const size_t n_vectors, + const size_t buffer_stride, uint32_t k, uint32_t* pruning_positions, distance_t* pruning_distances, @@ -454,8 +444,8 @@ class PDXearch { while (pdx_data.num_horizontal_dimensions && n_vectors_not_pruned && current_horizontal_dimension < pdx_data.num_horizontal_dimensions) { cur_n_vectors_not_pruned = n_vectors_not_pruned; - size_t offset_data = (pdx_data.num_vertical_dimensions * n_vectors) + - (current_horizontal_dimension * n_vectors); + size_t offset_data = (pdx_data.num_vertical_dimensions * buffer_stride) + + (current_horizontal_dimension * buffer_stride); for (size_t vector_idx = 0; vector_idx < n_vectors_not_pruned; vector_idx++) { size_t v_idx = pruning_positions[vector_idx]; size_t data_pos = offset_data + (v_idx * H_DIM_SIZE); @@ -496,7 +486,7 @@ class PDXearch { query, data, cur_n_vectors_not_pruned, - n_vectors, + buffer_stride, current_vertical_dimension, last_dimension_to_test_idx, pruning_distances, @@ -580,9 +570,8 @@ class PDXearch { } public: - /****************************************************************** - * Search methods - ******************************************************************/ + // TODO: Mask tombstones with a really high distance to avoid returning them as results. + std::vector Search(const float* PDX_RESTRICT const raw_query, const uint32_t k) { Heap local_heap{}; std::unique_ptr query(new float[pdx_data.num_dimensions]); @@ -629,8 +618,8 @@ class PDXearch { local_prepared_query = query.get(); } - std::unique_ptr pruning_distances(new distance_t[max_cluster_size]); - std::unique_ptr pruning_positions(new uint32_t[max_cluster_size]); + std::unique_ptr pruning_distances(new distance_t[pdx_data.max_cluster_capacity]); + std::unique_ptr pruning_positions(new uint32_t[pdx_data.max_cluster_capacity]); for (size_t cluster_idx = 0; cluster_idx < clusters_to_visit; ++cluster_idx) { distance_t pruning_threshold = std::numeric_limits::max(); @@ -642,12 +631,14 @@ class PDXearch { if (cluster.num_embeddings == 0) { continue; } + cluster.n_accessed++; if (local_heap.size() < k) { // We cannot prune until we fill the heap Start( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, cluster.indices, pruning_positions.get(), @@ -659,7 +650,8 @@ class PDXearch { Warmup( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, selectivity_threshold, pruning_positions.get(), @@ -672,7 +664,8 @@ class PDXearch { Prune( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, pruning_positions.get(), pruning_distances.get(), @@ -736,8 +729,8 @@ class PDXearch { local_prepared_query = query.get(); } - std::unique_ptr pruning_distances(new distance_t[max_cluster_size]); - std::unique_ptr pruning_positions(new uint32_t[max_cluster_size]); + std::unique_ptr pruning_distances(new distance_t[pdx_data.max_cluster_capacity]); + std::unique_ptr pruning_positions(new uint32_t[pdx_data.max_cluster_capacity]); for (size_t cluster_idx = 0; cluster_idx < clusters_to_visit; ++cluster_idx) { distance_t pruning_threshold = std::numeric_limits::max(); @@ -746,7 +739,7 @@ class PDXearch { const size_t current_cluster_idx = local_cluster_order[cluster_idx]; auto [selection_vector, passing_tuples] = predicate_evaluator.GetSelectionVector( - current_cluster_idx, cluster_offsets[current_cluster_idx] + current_cluster_idx, pdx_data.cluster_offsets[current_cluster_idx] ); if (passing_tuples == 0) { continue; @@ -755,12 +748,14 @@ class PDXearch { if (cluster.num_embeddings == 0) { continue; } + cluster.n_accessed++; if (local_heap.size() < k) { // We cannot prune until we fill the heap FilteredStart( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, cluster.indices, pruning_positions.get(), @@ -774,7 +769,8 @@ class PDXearch { Warmup( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, selectivity_threshold, pruning_positions.get(), @@ -789,7 +785,8 @@ class PDXearch { Prune( local_prepared_query, cluster.data, - cluster.num_embeddings, + cluster.used_capacity, + cluster.max_capacity, k, pruning_positions.get(), pruning_distances.get(), From 0317bf094fb1c16d8bc4e57f5cf2284e3899000b Mon Sep 17 00:00:00 2001 From: lkuffo Date: Tue, 3 Mar 2026 14:17:56 +0100 Subject: [PATCH 03/24] Maintenance in-memory API --- benchmarks/pdx_end_to_end.cpp | 4 +- .../DEFAULT/END_TO_END_PDX_ADSAMPLING.csv | 13 + include/pdx/cluster.hpp | 103 ++++- include/pdx/common.hpp | 2 + include/pdx/index.hpp | 419 ++++++++++++++++-- include/pdx/ivf_wrapper.hpp | 11 + include/pdx/profiler.hpp | 314 +++++++++++++ include/pdx/quantizers/scalar.hpp | 12 + include/pdx/searcher.hpp | 41 +- 9 files changed, 859 insertions(+), 60 deletions(-) create mode 100644 include/pdx/profiler.hpp diff --git a/benchmarks/pdx_end_to_end.cpp b/benchmarks/pdx_end_to_end.cpp index 927213f..50e9284 100644 --- a/benchmarks/pdx_end_to_end.cpp +++ b/benchmarks/pdx_end_to_end.cpp @@ -7,12 +7,12 @@ #include #include #include -#include #include #include "benchmark_utils.hpp" #include "pdx/index.hpp" #include "pdx/utils.hpp" +#include "pdx/profiler.hpp" template void RunBenchmark( @@ -106,6 +106,8 @@ void RunBenchmark( runtimes[j + l * NUM_MEASURE_RUNS] = {clock.accum_time}; } } + PDX::Profiler::Get().PrintHierarchical(); + BenchmarkMetadata results_metadata = { dataset, diff --git a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv index edb72c0..8540ba3 100644 --- a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv +++ b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv @@ -42,3 +42,16 @@ mxbai,end_to_end_pdx_tree_u8,0.0824076,0.129084,0.045958,0.898352,16,0,20,1000,0 mxbai,end_to_end_pdx_tree_u8,0.0821678,0.129875,0.044417,0.898352,16,0,20,1000,0,1,0.08248,0.1599,0.04442 mxbai,end_to_end_pdx_tree_u8,0.0871179,0.138416,0.047083,0.906552,18,0,20,1000,0,1,0.08765,0.1662,0.04708 mxbai,end_to_end_pdx_tree_u8,0.0870912,0.138584,0.045584,0.906552,18,0,20,1000,0,1,0.0876,0.2096,0.04558 +mxbai,end_to_end_pdx_tree_f32,3.88106,6.34308,1.93817,0.99715,2048,0,20,1000,0,1,4.006,12.84,1.938 +mxbai,end_to_end_pdx_tree_f32,2.81742,4.80438,1.36175,0.995201,1536,0,20,1000,0,1,2.909,8.991,1.362 +mxbai,end_to_end_pdx_tree_f32,0.191839,0.35425,0.076791,0.927001,24,0,20,1000,0,1,0.1932,0.4313,0.07679 +mxbai,end_to_end_pdx_tree_f32,0.192574,0.3515,0.074,0.928901,25,0,20,1000,0,1,0.1942,0.4208,0.074 +mxbai,end_to_end_pdx_tree_u8,0.113326,0.183791,0.056375,0.925102,25,0,20,1000,0,1,0.1144,0.2119,0.05638 +mxbai,end_to_end_pdx_tree_u8,0.104159,0.161875,0.055042,0.925102,25,0,20,1000,0,1,0.1048,0.2029,0.05504 +mxbai,end_to_end_pdx_tree_u8,0.108595,0.174709,0.055958,0.925102,25,0,20,1000,0,1,0.1099,0.2377,0.05596 +mxbai,end_to_end_pdx_tree_u8,0.0936199,0.150875,0.05,0.898352,16,0,20,1000,0,1,0.09461,0.21,0.05 +mxbai,end_to_end_pdx_tree_u8,0.0841672,0.132166,0.045959,0.898352,16,0,20,1000,0,1,0.08454,0.1625,0.04596 +mxbai,end_to_end_pdx_tree_u8,0.0865836,0.13575,0.047875,0.898352,16,0,20,1000,0,1,0.08714,0.176,0.04788 +mxbai,end_to_end_pdx_u8,0.241949,0.279833,0.204458,0.899202,16,0,20,1000,0,1,0.2424,0.293,0.2045 +mxbai,end_to_end_pdx_tree_f32,0.155693,0.276708,0.065958,0.901551,16,0,20,1000,0,1,0.1568,0.3437,0.06596 +mxbai,end_to_end_pdx_f32,0.306811,0.429333,0.225875,0.902401,16,0,20,1000,0,1,0.3073,0.48,0.2259 diff --git a/include/pdx/cluster.hpp b/include/pdx/cluster.hpp index f90e883..a04c947 100644 --- a/include/pdx/cluster.hpp +++ b/include/pdx/cluster.hpp @@ -2,6 +2,7 @@ #include "pdx/common.hpp" #include "pdx/utils.hpp" +#include "pdx/profiler.hpp" #include #include #include @@ -19,11 +20,12 @@ struct Cluster { using tombstones_t = std::unordered_set; constexpr static float CAPACITY_THRESHOLD = 1.1f; // 10% more than the current capacity + constexpr static uint32_t MIN_MAX_CAPACITY = 100; Cluster(uint32_t num_embeddings, uint32_t num_dimensions) : num_embeddings(num_embeddings), used_capacity(num_embeddings), - max_capacity(static_cast(num_embeddings * CAPACITY_THRESHOLD)), + max_capacity(std::max(static_cast(num_embeddings * CAPACITY_THRESHOLD), MIN_MAX_CAPACITY)), min_capacity(static_cast(num_embeddings / CAPACITY_THRESHOLD)), num_dimensions(num_dimensions), indices(new uint32_t[max_capacity]), @@ -47,6 +49,8 @@ struct Cluster { n_accessed(other.n_accessed), n_inserted(other.n_inserted), n_deleted(other.n_deleted), + id(other.id), + mesocluster_id(other.mesocluster_id), indices(other.indices), data(other.data), tombstones(std::move(other.tombstones)) { @@ -61,7 +65,35 @@ struct Cluster { Cluster(const Cluster&) = delete; Cluster& operator=(const Cluster&) = delete; - Cluster& operator=(Cluster&&) = delete; + + // Move-assignment: transfers data ownership, keeps destination's mutex and const num_dimensions. + // Caller must ensure no concurrent access to *this during assignment. + Cluster& operator=(Cluster&& other) noexcept { + if (this != &other) { + assert(num_dimensions == other.num_dimensions); + delete[] data; + delete[] indices; + + num_embeddings = other.num_embeddings; + used_capacity = other.used_capacity; + max_capacity = other.max_capacity; + min_capacity = other.min_capacity; + // num_dimensions: const and guaranteed same — skip + // cluster_mutex: keep our own — skip + n_accessed = other.n_accessed; + n_inserted = other.n_inserted; + n_deleted = other.n_deleted; + id = other.id; + mesocluster_id = other.mesocluster_id; + indices = other.indices; + data = other.data; + tombstones = std::move(other.tombstones); + + other.indices = nullptr; + other.data = nullptr; + } + return *this; + } uint32_t num_embeddings{}; // Number of valid embeddings in the cluster, i.e., excluding tombstones uint32_t used_capacity{}; // Total capacity of the cluster, i.e., including tombstones but excluding empty slots that are not yet used @@ -72,8 +104,10 @@ struct Cluster { size_t n_accessed = 0; size_t n_inserted = 0; size_t n_deleted = 0; + uint32_t id{}; // Position in IVF::clusters vector + uint32_t mesocluster_id{}; // Which L0 meso-cluster contains this L1 cluster (L1 only) - uint32_t* indices = nullptr; // !These are row_ids + uint32_t* indices = nullptr; // !These are row_ids data_t* data = nullptr; tombstones_t tombstones; // ! Need to have indexes, not row_ids @@ -98,6 +132,7 @@ struct Cluster { // Returns the index in cluster of the newly appended embedding uint32_t AppendEmbedding(uint32_t row_id, const data_t* PDX_RESTRICT embedding) { + PDX_PROFILE_SCOPE("LeafAppend"); uint32_t next_free_idx = used_capacity; bool replaced_tombstone = false; if (!tombstones.empty()) { @@ -123,6 +158,7 @@ struct Cluster { } void DeleteEmbedding(uint32_t index_in_cluster) { + PDX_PROFILE_SCOPE("LeafDelete"); AddTombstone(index_in_cluster); num_embeddings--; // Used Capacity remains the same because we are not compacting the cluster @@ -131,6 +167,65 @@ struct Cluster { n_deleted++; } + // Reallocate internal buffers with a larger max_capacity, copying existing + // PDX-layout data with the new stride. Tombstone positions remain valid. + void GrowCluster(uint32_t new_max_capacity) { + assert(new_max_capacity > max_capacity); + const auto split = GetPDXDimensionSplit(num_dimensions); + const uint32_t vertical_d = split.vertical_dimensions; + const uint32_t horizontal_d = split.horizontal_dimensions; + const size_t old_stride = max_capacity; + const size_t new_stride = new_max_capacity; + + auto* new_data = new data_t[static_cast(new_max_capacity) * num_dimensions]; + auto* new_indices = new uint32_t[new_max_capacity]; + + std::memcpy(new_indices, indices, used_capacity * sizeof(uint32_t)); + + // Vertical block — copy each dimension row from old stride to new stride + if constexpr (Q == Quantization::F32) { + for (uint32_t d = 0; d < vertical_d; d++) { + std::memcpy( + new_data + d * new_stride, + data + d * old_stride, + used_capacity * sizeof(data_t) + ); + } + } else { + uint32_t d = 0; + for (; d + U8_INTERLEAVE_SIZE <= vertical_d; d += U8_INTERLEAVE_SIZE) { + std::memcpy( + new_data + d * new_stride, + data + d * old_stride, + used_capacity * U8_INTERLEAVE_SIZE + ); + } + if (d < vertical_d) { + uint32_t remaining = vertical_d - d; + std::memcpy( + new_data + d * new_stride, + data + d * old_stride, + used_capacity * remaining + ); + } + } + + // Horizontal blocks — copy each H_DIM_SIZE block with new stride + const data_t* old_h = data + old_stride * vertical_d; + data_t* new_h = new_data + new_stride * vertical_d; + for (uint32_t j = 0; j < horizontal_d; j += H_DIM_SIZE) { + std::memcpy(new_h, old_h, used_capacity * H_DIM_SIZE * sizeof(data_t)); + old_h += old_stride * H_DIM_SIZE; + new_h += new_stride * H_DIM_SIZE; + } + + delete[] data; + delete[] indices; + data = new_data; + indices = new_indices; + max_capacity = new_max_capacity; + } + size_t GetInMemorySizeInBytes() const { return sizeof(*this) + num_embeddings * sizeof(*indices) + num_embeddings * static_cast(num_dimensions) * sizeof(*data); @@ -139,6 +234,7 @@ struct Cluster { // Gather all embeddings from the PDX layout into a contiguous row-major buffer. // Assumes no tombstones (call CompactCluster first). std::unique_ptr GetHorizontalEmbeddingsFromPDXBuffer() const { + std::unique_ptr out(new data_t[static_cast(num_embeddings) * num_dimensions]); for (uint32_t i = 0; i < num_embeddings; i++) { ReadEmbeddingFromPDXBuffer(i, out.get() + static_cast(i) * num_dimensions); @@ -148,6 +244,7 @@ struct Cluster { // Gather a single embedding from the PDX layout into a row-major buffer. std::unique_ptr GetHorizontalEmbeddingFromPDXBuffer(uint32_t idx_in_cluster) const { + PDX_PROFILE_SCOPE("DePDXify-One"); std::unique_ptr out(new data_t[num_dimensions]); ReadEmbeddingFromPDXBuffer(idx_in_cluster, out.get()); return out; diff --git a/include/pdx/common.hpp b/include/pdx/common.hpp index 71a9f32..eb27074 100644 --- a/include/pdx/common.hpp +++ b/include/pdx/common.hpp @@ -48,6 +48,8 @@ static constexpr uint32_t DIMENSIONS_FETCHING_SIZES[20] = {16, 16, 32, 32, 64, 64, 64, 128, 128, 128, 128, 256, 256, 512, 1024, 2048, 16384}; +static constexpr float CENTROID_PERTURBATION_EPS = 1.0f / 1024.0f; + static constexpr bool AllFetchingSizesMultipleOfU8InterleaveSize() { for (auto s : DIMENSIONS_FETCHING_SIZES) { if (s % U8_INTERLEAVE_SIZE != 0) { diff --git a/include/pdx/index.hpp b/include/pdx/index.hpp index 8037a15..5cfc578 100644 --- a/include/pdx/index.hpp +++ b/include/pdx/index.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include "pdx/quantizers/scalar.hpp" #include "pdx/searcher.hpp" #include "pdx/utils.hpp" +#include "pdx/profiler.hpp" #include namespace PDX { @@ -117,6 +119,7 @@ void PopulateIVFClusters( // Pre-allocate all clusters sequentially for (size_t cluster_idx = 0; cluster_idx < num_clusters; cluster_idx++) { ivf.clusters.emplace_back(kmeans_result.assignments[cluster_idx].size(), num_dimensions); + ivf.clusters[cluster_idx].id = cluster_idx; } // Per-thread tmp buffers for gather + quantize @@ -217,6 +220,7 @@ class PDXIndex : public IPDXIndex { PDX::PredicateEvaluator CreatePredicateEvaluator(const std::vector& passing_row_ids ) const { + PDX_PROFILE_SCOPE("PredicateEvaluator"); PDX::PredicateEvaluator evaluator(index.num_clusters, index.total_capacity); for (const auto row_id : passing_row_ids) { const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; @@ -434,6 +438,8 @@ class PDXTreeIndex : public IPDXIndex { public: using embedding_storage_t = PDX::pdx_data_t; using cluster_t = PDX::Cluster; + using distance_computer_t = DistanceComputer; + using distance_computer_f32_t = DistanceComputer; private: PDXIndexConfig config{}; @@ -465,6 +471,7 @@ class PDXTreeIndex : public IPDXIndex { PDX::PredicateEvaluator CreatePredicateEvaluator(const std::vector& passing_row_ids ) const { + PDX_PROFILE_SCOPE("PredicateEvaluator"); PDX::PredicateEvaluator evaluator(index.num_clusters, index.total_capacity); for (const auto row_id : passing_row_ids) { const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; @@ -547,6 +554,7 @@ class PDXTreeIndex : public IPDXIndex { } std::vector Search(const float* query_embedding, size_t knn) const override { + PDX_PROFILE_SCOPE("Search"); auto n_probe_top_level = GetTopLevelNumClusters(); // We confidently prune half of the search space if (searcher->GetNProbe() < GetNumClusters() / 2) { @@ -570,11 +578,25 @@ class PDXTreeIndex : public IPDXIndex { const std::vector& passing_row_ids ) const override { auto evaluator = CreatePredicateEvaluator(passing_row_ids); - return searcher->FilteredSearch(query_embedding, knn, evaluator); + { + PDX_PROFILE_SCOPE("FilteredSearch"); + return searcher->FilteredSearch(query_embedding, knn, evaluator); + } + } + + // Ensure index.clusters has room for at least one more element so that + // push_back inside SplitCluster / ReassignEmbeddings→CheckClusterHealth + // will never reallocate. Must be called BEFORE taking any reference or + // lock into the clusters vector. + void EnsureClusterVectorHeadroom() { + if (index.clusters.size() == index.clusters.capacity()) { + index.clusters.reserve(index.clusters.capacity() * 2); + } } // TODO: Concurrent writes always go through a single writer thread void Append(size_t row_id, const float* PDX_RESTRICT embedding){ + PDX_PROFILE_SCOPE("Append"); // Row ids must be dense and sequential (next expected = current size of the mapping) if (row_id != row_id_cluster_mapping.size()) { throw std::invalid_argument( @@ -599,6 +621,9 @@ class PDXTreeIndex : public IPDXIndex { std::vector centroid_candidates = top_level_searcher->Search(preprocessed.get(), 1); uint32_t closest_centroid_idx = centroid_candidates[0].index; + // Ensure the clusters vector won't reallocate while we hold a reference/lock + EnsureClusterVectorHeadroom(); + // Assign to the corresponding cluster auto& cluster = index.clusters[closest_centroid_idx]; @@ -615,89 +640,380 @@ class PDXTreeIndex : public IPDXIndex { } row_id_cluster_mapping.emplace_back(closest_centroid_idx, index_in_cluster); index.total_num_embeddings++; - CheckClusterHealth(closest_centroid_idx, cluster); + CheckClusterHealth(cluster); } // TODO: Concurrent writes always go through a single writer thread void Delete(size_t row_id){ + PDX_PROFILE_SCOPE("Delete"); // Find the cluster and index in cluster for the given row_id const auto& [cluster_id, index_in_cluster] = row_id_cluster_mapping[row_id]; - auto& cluster = index.clusters[cluster_id]; - // Lock the cluster for the entire delete + health check sequence - std::lock_guard lock(cluster.cluster_mutex); + // Ensure the clusters vector won't reallocate while we hold a reference/lock + // (DestroyAndMergeCluster→ReassignEmbeddings→CheckClusterHealth can split) + EnsureClusterVectorHeadroom(); - cluster.DeleteEmbedding(index_in_cluster); + auto& cluster = index.clusters[cluster_id]; + + { + // Lock only for the mutation; unlock before health check which may + // destroy/replace the cluster (DestroyAndMergeCluster pop_back or + // SplitCluster move-assignment). Single-writer is assumed. + std::lock_guard lock(cluster.cluster_mutex); + cluster.DeleteEmbedding(index_in_cluster); + } // As row_ids are assumed to be non-replacable, // we don't need to remove the entry from row_id_cluster_mapping index.total_num_embeddings--; - CheckClusterHealth(cluster_id, cluster); + CheckClusterHealth(cluster); } // Caller must hold cluster.cluster_mutex - void CheckClusterHealth(uint32_t cluster_id, cluster_t& cluster) { + void CheckClusterHealth(cluster_t& cluster) { if (cluster.used_capacity == cluster.max_capacity) { if (cluster.num_embeddings < cluster.used_capacity) { // There are tombstone slots we can reclaim auto moves = cluster.CompactCluster(); for (const auto& [row_id, new_idx] : moves) { - row_id_cluster_mapping[row_id] = {cluster_id, new_idx}; + row_id_cluster_mapping[row_id] = {cluster.id, new_idx}; } } else { - // Cluster is truly full, split it + // Cluster is truly full and compacted, split it SplitCluster(cluster); } - } else if (cluster.num_embeddings == cluster.min_capacity) { + } else if (cluster.num_embeddings <= cluster.min_capacity) { DestroyAndMergeCluster(cluster); } } + // Find the position of an L1 cluster within its known L0 meso-cluster. + uint32_t FindPositionInMesoCluster(uint32_t l1_cluster_id, uint32_t mesocluster_id) const { + auto& l0_cluster = index.l0.clusters[mesocluster_id]; + for (uint32_t p = 0; p < l0_cluster.used_capacity; p++) { + if (!l0_cluster.HasTombstone(p) && l0_cluster.indices[p] == l1_cluster_id) { + return p; + } + } + throw std::runtime_error( + "FindPositionInMesoCluster: L1 cluster " + std::to_string(l1_cluster_id) + + " not found in L0 meso-cluster " + std::to_string(mesocluster_id) + ); + } + // Caller must hold cluster.cluster_mutex - void DestroyAndMergeCluster(cluster_t& cluster){ + void DestroyAndMergeCluster(cluster_t& cluster) { + PDX_PROFILE_SCOPE("Merge"); + const uint32_t cluster_id = cluster.id; cluster.CompactCluster(); - std::unique_ptr cluster_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); - ReassignEmbeddings(cluster, cluster_embeddings.get()); - // Remove the cluster from index.clusters - // Remove the centroid from this cluster from index.centroids - // For this, we can swap with the last cluster and centroid in the respective arrays and pop_back, then update the row_id_cluster_mapping for the affected clusters + auto raw_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); + // We need to dequantize to reassign + std::unique_ptr cluster_embeddings(new float[cluster.num_embeddings * index.num_dimensions]); + if constexpr (Q == U8) { + // Dequantize the embeddings + for (size_t i = 0; i < cluster.num_embeddings; i++) { + searcher->quantizer.DequantizeEmbedding( + raw_embeddings.get() + i * index.num_dimensions, + index.quantization_base, + index.quantization_scale, + cluster_embeddings.get() + i * index.num_dimensions + ); + } + } else { + std::memcpy( + cluster_embeddings.get(), + raw_embeddings.get(), + cluster.num_embeddings * index.num_dimensions * sizeof(float) + ); + } + ReassignEmbeddings(cluster.indices, cluster_embeddings.get(), cluster.num_embeddings, cluster_id); + + const uint32_t num_dims = index.num_dimensions; + uint32_t last_id = index.num_clusters - 1; + + // Remove the deleted cluster's entry from L0 + uint32_t pos_del = FindPositionInMesoCluster(cluster_id, cluster.mesocluster_id); + index.l0.clusters[cluster.mesocluster_id].DeleteEmbedding(pos_del); + index.l0.total_num_embeddings--; + + if (cluster_id != last_id) { + // Swap the last cluster into the deleted position + index.clusters[cluster_id] = std::move(index.clusters[last_id]); + index.clusters[cluster_id].id = cluster_id; + // mesocluster_id stays correct (the moved cluster's meso-cluster didn't change) + + // Move the last centroid into the deleted slot + std::memcpy( + index.centroids.data() + static_cast(cluster_id) * num_dims, + index.centroids.data() + static_cast(last_id) * num_dims, + num_dims * sizeof(float) + ); + + // Update row_id_cluster_mapping for all embeddings in the moved cluster + auto& moved = index.clusters[cluster_id]; + for (uint32_t i = 0; i < moved.used_capacity; i++) { + if (!moved.HasTombstone(i)) { + row_id_cluster_mapping[moved.indices[i]] = {cluster_id, i}; + } + } + + // Update L0: the entry that pointed to last_id now points to cluster_id + uint32_t pos_last = FindPositionInMesoCluster(last_id, moved.mesocluster_id); + index.l0.clusters[moved.mesocluster_id].indices[pos_last] = cluster_id; + } + // Pop the last cluster and shrink centroids + index.clusters.pop_back(); + index.centroids.resize(index.centroids.size() - num_dims); + index.num_clusters--; + + index.ComputeClusterOffsets(); + index.l0.ComputeClusterOffsets(); } // Caller must hold cluster.cluster_mutex + // Assumes cluster is compacted and has no tombstones void SplitCluster(cluster_t& cluster){ + PDX_PROFILE_SCOPE("Split"); + const uint32_t cluster_id = cluster.id; + const uint32_t mc = cluster.mesocluster_id; + std::unique_ptr raw_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); + std::unique_ptr cluster_embeddings(new float[cluster.num_embeddings * index.num_dimensions]); + if constexpr (Q == U8) { + // Dequantize the embeddings + for (size_t i = 0; i < cluster.num_embeddings; i++) { + searcher->quantizer.DequantizeEmbedding( + raw_embeddings.get() + i * index.num_dimensions, + index.quantization_base, + index.quantization_scale, + cluster_embeddings.get() + i * index.num_dimensions + ); + } + } else { + std::memcpy( + cluster_embeddings.get(), + raw_embeddings.get(), + cluster.num_embeddings * index.num_dimensions * sizeof(float) + ); + } + + auto centroid_to_split = index.centroids.data() + cluster_id * index.num_dimensions; + auto centroid_a = std::unique_ptr(new float[index.num_dimensions]); + auto centroid_b = std::unique_ptr(new float[index.num_dimensions]); // Split the cluster into two, with the new centroids having a small symmetric perturbation - // Get inspired from: SuperKMeans::SplitCluster in superkmeans.hpp, but we need to adapt it to our data layout and also update the IVF tree structure accordingly - // Replace cluster and centroid from Clusters array with one of the new ones - // Add a new cluster to the cluster array - // For every point in the deleted cluster: - // Is the distance to one of the new centroids smaller than to the deleted centroid? - // --> Then this is the best assignment in the mesocluster - // Most points will go to either of one of the two new centroids. So we just need to compact - // the horizontal buffers for each of these centroids, and call StoreClusterEmbeddings to properly create - // the cluster's buffer - // Then, compute the centroids manually (easy, just average every dimension, we have the horizontal layout) - // And, update the index.l0 structure. The two new clusters are going to the same mesocluster - // In the remaining points (should be few), compact buffer and call REASSIGN - } - - void ReassignEmbeddings(cluster_t& cluster, const embedding_storage_t* cluster_embeddings){ - // Cluster embeddings are assumed to be in the horizontal layout - // Dequantize the cluster_embeddings - // For each embedding in the cluster, find the closest centroid among all clusters in the same mesocluster and reassign - // We need to exclude the deleted cluster's centroid from the candidates when reassigning, otherwise all embeddings will be assigned to the same closest centroid (the one from the deleted cluster) - // This is called after splitting or merging a cluster, so we only need to reassign embeddings in the affected mesocluster - // If coming from SPLIT: - // --> Take the closest splitted centroid as current assignment - // --> Run a GEMM+PRUNING iteration to get the new assignments (you need to call superkmeans API) - // If coming from MERGE: - // --> Run a GEMM iteration to get the new assignments - std::unique_ptr new_assignments(new uint32_t[cluster.num_embeddings]); - // Assume new_assignments is filled already with the new cluster assignments for each embedding in the cluster + for (size_t j = 0; j < index.num_dimensions; j++) { + if (j % 2 == 0) { + centroid_a[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); + centroid_b[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); + } else { + centroid_a[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); + centroid_b[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); + } + } + // Compute the distances from each point in the cluster to the centroid being split and to the two new centroids + // One could optimize this by precomputing the distances to the centroid being split + // Further optimization: We could do a GEMM + std::vector group_a, group_b, group_rest; + group_a.reserve(cluster.num_embeddings); + group_b.reserve(cluster.num_embeddings); + group_rest.reserve(cluster.num_embeddings); for (size_t i = 0; i < cluster.num_embeddings; i++) { - uint32_t new_cluster_id = new_assignments[i]; - uint32_t row_id = cluster.indices[i]; - auto new_index_in_cluster = index.clusters[new_cluster_id].AppendEmbedding(row_id, cluster_embeddings + i * cluster.num_dimensions); - row_id_cluster_mapping[row_id] = {new_cluster_id, new_index_in_cluster}; + const float* embedding_ptr = cluster_embeddings.get() + i * index.num_dimensions; + float distance_to_centroid_to_split = distance_computer_f32_t::Horizontal( + embedding_ptr, centroid_to_split, index.num_dimensions + ); + float distance_to_a = distance_computer_f32_t::Horizontal( + embedding_ptr, centroid_a.get(), index.num_dimensions + ); + float distance_to_b = distance_computer_f32_t::Horizontal( + embedding_ptr, centroid_b.get(), index.num_dimensions + ); + if (distance_to_a < distance_to_centroid_to_split && distance_to_a < distance_to_b) { + group_a.push_back(i); + } else if (distance_to_b < distance_to_centroid_to_split && distance_to_b < distance_to_a) { + group_b.push_back(i); + } else { + group_rest.push_back(i); + } + } + + // 5. Compute true centroids by averaging assigned float embeddings. + // If a group is empty, fall back to the perturbed centroid. + auto true_centroid_a = std::make_unique(index.num_dimensions); + auto true_centroid_b = std::make_unique(index.num_dimensions); + if (group_a.empty()) { + std::memcpy(true_centroid_a.get(), centroid_a.get(), index.num_dimensions * sizeof(float)); + } else { + std::memset(true_centroid_a.get(), 0, index.num_dimensions * sizeof(float)); + for (uint32_t idx : group_a) { + const float* emb = cluster_embeddings.get() + static_cast(idx) * index.num_dimensions; +#pragma clang loop vectorize(enable) + for (size_t d = 0; d < index.num_dimensions; d++) { + true_centroid_a[d] += emb[d]; + } + } +#pragma clang loop vectorize(enable) + for (size_t d = 0; d < index.num_dimensions; d++) { + true_centroid_a[d] /= static_cast(group_a.size()); + } + } + if (group_b.empty()) { + std::memcpy(true_centroid_b.get(), centroid_b.get(), index.num_dimensions * sizeof(float)); + } else { + std::memset(true_centroid_b.get(), 0, index.num_dimensions * sizeof(float)); + for (uint32_t idx : group_b) { + const float* emb = cluster_embeddings.get() + static_cast(idx) * index.num_dimensions; +#pragma clang loop vectorize(enable) + for (size_t d = 0; d < index.num_dimensions; d++) { + true_centroid_b[d] += emb[d]; + } + } +#pragma clang loop vectorize(enable) + for (size_t d = 0; d < index.num_dimensions; d++) { + true_centroid_b[d] /= static_cast(group_b.size()); + } + } + + // 6. Gather data_t embeddings and indices for each group + auto gather_group = [&](const std::vector& group) + -> std::pair, std::unique_ptr> { + auto embs = std::make_unique( + static_cast(group.size()) * index.num_dimensions + ); + auto ids = std::make_unique(group.size()); + for (size_t i = 0; i < group.size(); i++) { + std::memcpy( + embs.get() + i * index.num_dimensions, + raw_embeddings.get() + static_cast(group[i]) * index.num_dimensions, + index.num_dimensions * sizeof(embedding_storage_t) + ); + ids[i] = cluster.indices[group[i]]; + } + return {std::move(embs), std::move(ids)}; + }; + auto [embs_a, ids_a] = gather_group(group_a); + auto [embs_b, ids_b] = gather_group(group_b); + // Gather rest NOW, before the move-assignment below destroys cluster.indices + auto [embs_rest, ids_rest] = gather_group(group_rest); + + // 7. Create new cluster A (replaces old cluster at cluster_id) + cluster_t new_cluster_a(static_cast(group_a.size()), index.num_dimensions); + new_cluster_a.id = cluster_id; + new_cluster_a.mesocluster_id = mc; + std::memcpy(new_cluster_a.indices, ids_a.get(), group_a.size() * sizeof(uint32_t)); + StoreClusterEmbeddings(new_cluster_a, index, embs_a.get(), group_a.size()); + + // 8. Create new cluster B (will be appended) + uint32_t new_cluster_b_id = index.num_clusters; + cluster_t new_cluster_b(static_cast(group_b.size()), index.num_dimensions); + new_cluster_b.id = new_cluster_b_id; + new_cluster_b.mesocluster_id = mc; + std::memcpy(new_cluster_b.indices, ids_b.get(), group_b.size() * sizeof(uint32_t)); + StoreClusterEmbeddings(new_cluster_b, index, embs_b.get(), group_b.size()); + + // 9. Replace old cluster with A, append B. + // The caller must have reserved capacity before taking a reference/lock + // (see EnsureClusterVectorHeadroom), so push_back will not reallocate. + index.clusters[cluster_id] = std::move(new_cluster_a); + index.clusters.push_back(std::move(new_cluster_b)); + index.num_clusters++; + + // 10. Update centroids: replace old centroid with A's, append B's + std::memcpy( + index.centroids.data() + static_cast(cluster_id) * index.num_dimensions, + true_centroid_a.get(), + index.num_dimensions * sizeof(float) + ); + index.centroids.insert( + index.centroids.end(), + true_centroid_b.get(), + true_centroid_b.get() + index.num_dimensions + ); + + // 11. Update row_id_cluster_mapping + for (size_t i = 0; i < group_a.size(); i++) { + row_id_cluster_mapping[ids_a[i]] = {cluster_id, static_cast(i)}; + } + for (size_t i = 0; i < group_b.size(); i++) { + row_id_cluster_mapping[ids_b[i]] = {new_cluster_b_id, static_cast(i)}; + } + + // 12. Update L0: remove old centroid, add both new centroids + uint32_t pos = FindPositionInMesoCluster(cluster_id, mc); + auto& l0_cluster = index.l0.clusters[mc]; + l0_cluster.DeleteEmbedding(pos); + l0_cluster.CompactCluster(); + // We removed 1 entry and need to add 2 — grow if there's not enough room + if (l0_cluster.used_capacity + 2 > l0_cluster.max_capacity) { + l0_cluster.GrowCluster(std::max(l0_cluster.max_capacity * 2, l0_cluster.used_capacity + 2)); + } + l0_cluster.AppendEmbedding(cluster_id, true_centroid_a.get()); + l0_cluster.AppendEmbedding(new_cluster_b_id, true_centroid_b.get()); + index.l0.total_num_embeddings++; + + // In the remaining points that were not assigned to neither a or b (should be few), + // compact their dequantized embeddings and call REASSIGN + if (!group_rest.empty()) { + auto float_rest = std::make_unique(group_rest.size() * index.num_dimensions); + for (size_t i = 0; i < group_rest.size(); i++) { + std::memcpy( + float_rest.get() + i * index.num_dimensions, + cluster_embeddings.get() + static_cast(group_rest[i]) * index.num_dimensions, + index.num_dimensions * sizeof(float) + ); + } + ReassignEmbeddings(ids_rest.get(), float_rest.get(), static_cast(group_rest.size())); + } + index.ComputeClusterOffsets(); + index.l0.ComputeClusterOffsets(); + } + + // Reassign dequantized (float) embeddings to their closest centroid. + // exclude_cluster_id: skip this cluster during nearest-centroid search (use UINT32_MAX for no exclusion). + void ReassignEmbeddings( + uint32_t* row_ids, + const float* embeddings, + uint32_t num_embeddings, + uint32_t exclude_cluster_id = UINT32_MAX + ) { + PDX_PROFILE_SCOPE("Reassign"); + const uint32_t num_dims = index.num_dimensions; + + for (size_t i = 0; i < num_embeddings; i++) { + const float* emb = embeddings + i * num_dims; + + // Find closest centroid + uint32_t best_cluster = UINT32_MAX; + float best_dist = std::numeric_limits::max(); + for (uint32_t c = 0; c < index.num_clusters; c++) { + if (c == exclude_cluster_id) { + continue; + } + float dist = distance_computer_f32_t::Horizontal( + emb, index.centroids.data() + static_cast(c) * num_dims, num_dims + ); + if (dist < best_dist) { + best_dist = dist; + best_cluster = c; + } + } + + // Quantize back to storage type if needed, then append + uint32_t row_id = row_ids[i]; + uint32_t new_idx; + if constexpr (Q == U8) { + ScalarQuantizer quantizer(num_dims); + auto quantized = std::make_unique(num_dims); + quantizer.QuantizeEmbedding( + emb, index.quantization_base, index.quantization_scale, quantized.get() + ); + new_idx = index.clusters[best_cluster].AppendEmbedding(row_id, quantized.get()); + } else { + new_idx = index.clusters[best_cluster].AppendEmbedding(row_id, emb); + } + row_id_cluster_mapping[row_id] = {best_cluster, new_idx}; + // Health check may split → push_back, so ensure headroom each iteration + EnsureClusterVectorHeadroom(); + CheckClusterHealth(index.clusters[best_cluster]); } } @@ -810,6 +1126,13 @@ class PDXTreeIndex : public IPDXIndex { 1.0f ); + // Set mesocluster_id on each L1 cluster from L0 kmeans assignments + for (uint32_t mc = 0; mc < l0_num_clusters; mc++) { + for (uint32_t l1_id : l0_kmeans_result.assignments[mc]) { + index.clusters[l1_id].mesocluster_id = mc; + } + } + top_level_searcher = std::make_unique>(index.l0, *pruner); BuildRowIdClusterMapping(); } diff --git a/include/pdx/ivf_wrapper.hpp b/include/pdx/ivf_wrapper.hpp index bc1ff69..7355239 100644 --- a/include/pdx/ivf_wrapper.hpp +++ b/include/pdx/ivf_wrapper.hpp @@ -99,6 +99,7 @@ class IVF { uint32_t n_emb = cluster_headers[i * 2]; uint32_t max_cap = cluster_headers[i * 2 + 1]; clusters.emplace_back(n_emb, max_cap, num_dimensions); + clusters[i].id = i; clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < num_clusters; ++i) { @@ -239,6 +240,7 @@ class IVFTree : public IVF { uint32_t n_emb = l0_headers[i * 2]; uint32_t max_cap = l0_headers[i * 2 + 1]; l0.clusters.emplace_back(n_emb, max_cap, dims); + l0.clusters[i].id = i; l0.clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < n_clusters_l0; ++i) { @@ -262,6 +264,7 @@ class IVFTree : public IVF { uint32_t n_emb = l1_headers[i * 2]; uint32_t max_cap = l1_headers[i * 2 + 1]; this->clusters.emplace_back(n_emb, max_cap, dims); + this->clusters[i].id = i; this->clusters[i].LoadPDXData(next_value); } for (size_t i = 0; i < n_clusters_l1; ++i) { @@ -293,6 +296,14 @@ class IVFTree : public IVF { this->quantization_scale_squared = this->quantization_scale * this->quantization_scale; this->inverse_quantization_scale_squared = 1.0f / this->quantization_scale_squared; } + // Set mesocluster_id on L1 clusters by scanning L0 + for (uint32_t mc = 0; mc < n_clusters_l0; mc++) { + auto& l0c = l0.clusters[mc]; + for (uint32_t p = 0; p < l0c.num_embeddings; p++) { + this->clusters[l0c.indices[p]].mesocluster_id = mc; + } + } + l0.ComputeClusterOffsets(); this->ComputeClusterOffsets(); } diff --git a/include/pdx/profiler.hpp b/include/pdx/profiler.hpp new file mode 100644 index 0000000..83365fe --- /dev/null +++ b/include/pdx/profiler.hpp @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// Disclamer: Code produced by Opus 4.5 + +namespace PDX { + +/** + * @brief A centralized profiler for timing code sections. + * + * Usage: + * // Start/stop manually: + * Profiler::Get().Start("my_section"); + * // ... code ... + * Profiler::Get().Stop("my_section"); + * + * // Or use RAII scoped timer: + * { + * PDX_PROFILE_SCOPE("my_section"); + * // ... code automatically timed ... + * } + * + * // Print results: + * Profiler::Get().Print(); + * + * // Reset all timers: + * Profiler::Get().Reset(); + */ +class Profiler { + public: + struct TimerData { + size_t accum_time_ns = 0; // Accumulated time in nanoseconds + size_t call_count = 0; + std::chrono::high_resolution_clock::time_point start; + bool running = false; + }; + + // Get the global profiler instance + static Profiler& Get() { + static Profiler instance; + return instance; + } + + // Start timing a named section + void Start(const std::string& name) { + std::lock_guard lock(mutex_); + auto& timer = timers_[name]; + if (!timer.running) { + timer.start = std::chrono::high_resolution_clock::now(); + timer.running = true; + } + } + + // Stop timing a named section + void Stop(const std::string& name) { + auto end = std::chrono::high_resolution_clock::now(); + std::lock_guard lock(mutex_); + auto it = timers_.find(name); + if (it != timers_.end() && it->second.running) { + it->second.accum_time_ns += + std::chrono::duration_cast(end - it->second.start) + .count(); + it->second.call_count++; + it->second.running = false; + } + } + + // Get accumulated time in seconds for a timer + double GetTimeSeconds(const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = timers_.find(name); + if (it != timers_.end()) { + return it->second.accum_time_ns / 1e9; + } + return 0.0; + } + + // Get accumulated time in nanoseconds for a timer + size_t GetTimeNanos(const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = timers_.find(name); + if (it != timers_.end()) { + return it->second.accum_time_ns; + } + return 0; + } + + // Get call count for a timer + size_t GetCallCount(const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = timers_.find(name); + if (it != timers_.end()) { + return it->second.call_count; + } + return 0; + } + + // Reset a specific timer + void Reset(const std::string& name) { + std::lock_guard lock(mutex_); + auto it = timers_.find(name); + if (it != timers_.end()) { + it->second.accum_time_ns = 0; + it->second.call_count = 0; + it->second.running = false; + } + } + + // Reset all timers + void Reset() { + std::lock_guard lock(mutex_); + timers_.clear(); + } + + // Print all timers with formatting + void Print(std::ostream& os = std::cout) const { + std::lock_guard lock(mutex_); + + // Calculate total time for percentage calculation + size_t total_ns = 0; + for (const auto& [name, data] : timers_) { + total_ns += data.accum_time_ns; + } + + // Collect and sort timer names for consistent output + std::vector names; + names.reserve(timers_.size()); + for (const auto& [name, _] : timers_) { + names.push_back(name); + } + std::sort(names.begin(), names.end()); + + os << std::fixed << std::setprecision(3); + os << "\n========== PROFILER RESULTS ==========\n"; + + for (const auto& name : names) { + const auto& data = timers_.at(name); + double secs = data.accum_time_ns / 1e9; + double pct = total_ns > 0 ? (data.accum_time_ns * 100.0 / total_ns) : 0.0; + + os << std::left << std::setw(35) << name << std::right << std::setw(10) << secs << "s" + << " (" << std::setw(5) << pct << "%)" + << " [" << data.call_count << " calls]" + << "\n"; + } + + os << "---------------------------------------\n"; + os << std::left << std::setw(35) << "TOTAL" << std::right << std::setw(10) + << (static_cast(total_ns) / 1e9) << "s\n"; + os << "=======================================\n"; + } + + // Print a hierarchical view (timers with '/' are grouped) + void PrintHierarchical(std::ostream& os = std::cout) const { + std::lock_guard lock(mutex_); + + // Calculate total time from top-level timers only + size_t total_ns = 0; + for (const auto& [name, data] : timers_) { + if (name.find('/') == std::string::npos) { + total_ns += data.accum_time_ns; + } + } + + os << std::fixed << std::setprecision(3); + os << "\n========== PROFILER RESULTS ==========\n"; + + // Group timers by prefix + std::unordered_map> groups; + std::vector top_level; + + for (const auto& [name, _] : timers_) { + auto pos = name.find('/'); + if (pos != std::string::npos) { + std::string parent = name.substr(0, pos); + groups[parent].push_back(name); + } else { + top_level.push_back(name); + } + } + + // Sort top-level by accumulated time (descending) + std::sort( + top_level.begin(), + top_level.end(), + [this](const std::string& a, const std::string& b) { + return timers_.at(a).accum_time_ns > timers_.at(b).accum_time_ns; + } + ); + + for (const auto& name : top_level) { + const auto& data = timers_.at(name); + double secs = data.accum_time_ns / 1e9; + double pct = total_ns > 0 ? (data.accum_time_ns * 100.0 / total_ns) : 0.0; + + os << std::left << std::setw(40) << name << std::right << std::setw(10) << secs << "s" + << " (" << std::setw(5) << pct << "%)"; + if (data.call_count > 1) { + os << " [" << data.call_count << " calls]"; + } + os << "\n"; + + // Print children (sorted by time descending) + auto it = groups.find(name); + if (it != groups.end()) { + auto& children = it->second; + std::sort( + children.begin(), + children.end(), + [this](const std::string& a, const std::string& b) { + return timers_.at(a).accum_time_ns > timers_.at(b).accum_time_ns; + } + ); + for (const auto& child : children) { + const auto& child_data = timers_.at(child); + double child_secs = child_data.accum_time_ns / 1e9; + double child_pct = + total_ns > 0 ? (child_data.accum_time_ns * 100.0 / total_ns) : 0.0; + std::string short_name = " - " + child.substr(name.length() + 1); + + os << std::left << std::setw(40) << short_name << std::right << std::setw(10) + << child_secs << "s" + << " (" << std::setw(5) << child_pct << "%)"; + if (child_data.call_count > 1) { + os << " [" << child_data.call_count << " calls]"; + } + os << "\n"; + } + } + } + + os << "-------------------------------------------\n"; + os << std::left << std::setw(40) << "TOTAL" << std::right << std::setw(10) + << (static_cast(total_ns) / 1e9) << "s\n"; + os << "===========================================\n"; + } + + // Check if profiling is enabled + bool IsEnabled() const { return enabled_; } + + // Enable/disable profiling globally + void SetEnabled(bool enabled) { enabled_ = enabled; } + + private: + Profiler() = default; + ~Profiler() = default; + Profiler(const Profiler&) = delete; + Profiler& operator=(const Profiler&) = delete; + + mutable std::mutex mutex_; + std::unordered_map timers_; + bool enabled_ = true; +}; + +/** + * @brief RAII scoped timer that automatically starts on construction and stops on destruction. + */ +class ScopedTimer { + public: + explicit ScopedTimer(std::string name) : name_(std::move(name)) { + if (Profiler::Get().IsEnabled()) { + Profiler::Get().Start(name_); + } + } + + ~ScopedTimer() { + if (Profiler::Get().IsEnabled()) { + Profiler::Get().Stop(name_); + } + } + + // Non-copyable, non-movable + ScopedTimer(const ScopedTimer&) = delete; + ScopedTimer& operator=(const ScopedTimer&) = delete; + ScopedTimer(ScopedTimer&&) = delete; + ScopedTimer& operator=(ScopedTimer&&) = delete; + + private: + std::string name_; +}; + +// Convenience macros for profiling +// Helper macros for unique variable name generation +#define PDX_CONCAT_IMPL(x, y) x##y +#define PDX_CONCAT(x, y) PDX_CONCAT_IMPL(x, y) + +// Profiling macros - only enabled when BENCHMARK_TIME is defined +#ifdef BENCHMARK_TIME +// PDX_PROFILE_SCOPE creates a scoped timer with the given name +#define PDX_PROFILE_SCOPE(name) ::PDX::ScopedTimer PDX_CONCAT(_pdx_timer_, __LINE__)(name) + +// PDX_PROFILE_FUNCTION creates a scoped timer with the function name +#define PDX_PROFILE_FUNCTION() PDX_PROFILE_SCOPE(__func__) + +// Manual start/stop macros +#define PDX_PROFILE_START(name) ::PDX::Profiler::Get().Start(name) +#define PDX_PROFILE_STOP(name) ::PDX::Profiler::Get().Stop(name) +#else +// No-op macros when profiling is disabled +#define PDX_PROFILE_SCOPE(name) ((void) 0) +#define PDX_PROFILE_FUNCTION() ((void) 0) +#define PDX_PROFILE_START(name) ((void) 0) +#define PDX_PROFILE_STOP(name) ((void) 0) +#endif + +} // namespace PDX diff --git a/include/pdx/quantizers/scalar.hpp b/include/pdx/quantizers/scalar.hpp index e37ad10..27c6f35 100644 --- a/include/pdx/quantizers/scalar.hpp +++ b/include/pdx/quantizers/scalar.hpp @@ -92,6 +92,18 @@ class ScalarQuantizer : public Quantizer { } } } + + void DequantizeEmbedding( + const quantized_embedding_t* quantized_embedding, + const float quantization_base, + const float quantization_scale, + float* output_embedding + ) { + for (size_t i = 0; i < num_dimensions; ++i) { + output_embedding[i] = + static_cast(quantized_embedding[i]) / quantization_scale + quantization_base; + } + } }; } // namespace PDX diff --git a/include/pdx/searcher.hpp b/include/pdx/searcher.hpp index 621f449..0159960 100644 --- a/include/pdx/searcher.hpp +++ b/include/pdx/searcher.hpp @@ -8,6 +8,7 @@ #include "pdx/pruners/adsampling.hpp" #include "pdx/quantizers/scalar.hpp" #include "pdx/utils.hpp" +#include "pdx/profiler.hpp" #include #include #include @@ -30,6 +31,7 @@ class PDXearch { using quantized_embedding_t = pdx_quantized_embedding_t; using index_t = Index; using cluster_t = Cluster; + using tombstones_t = typename cluster_t::tombstones_t; using distance_computer_t = DistanceComputer; Quantizer quantizer; @@ -164,6 +166,17 @@ class PDXearch { } }; + void MaskDistancesWithTombstones( + const typename cluster_t::tombstones_t& tombstones, + distance_t* pruning_distances + ) { + if (tombstones.empty()) return; + const distance_t mask = std::numeric_limits::max() / 2; + for (uint32_t idx : tombstones) { + pruning_distances[idx] = mask; + } + } + static void GetClustersAccessOrderIVF( const float* PDX_RESTRICT query, const index_t& data, @@ -210,7 +223,8 @@ class PDXearch { const uint32_t* vector_indices, uint32_t* pruning_positions, distance_t* pruning_distances, - std::priority_queue, VectorComparator>& heap + std::priority_queue, VectorComparator>& heap, + const tombstones_t& tombstones ) { ResetPruningDistances(n_vectors, pruning_distances); distance_computer_t::Vertical( @@ -236,6 +250,7 @@ class PDXearch { ); } } + MaskDistancesWithTombstones(tombstones, pruning_distances); size_t max_possible_k = std::min( static_cast(k) - heap.size(), n_vectors @@ -275,7 +290,8 @@ class PDXearch { distance_t* pruning_distances, std::priority_queue, VectorComparator>& heap, uint8_t* selection_vector, - uint32_t passing_tuples + uint32_t passing_tuples, + const tombstones_t& tombstones ) { ResetPruningDistances(n_vectors, pruning_distances); size_t n_vectors_not_pruned = 0; @@ -329,6 +345,7 @@ class PDXearch { size_t max_possible_k = std::min(static_cast(k) - heap.size(), static_cast(passing_tuples)); MaskDistancesWithSelectionVector(n_vectors, pruning_distances, selection_vector); + MaskDistancesWithTombstones(tombstones, pruning_distances); std::unique_ptr indices_sorted(new size_t[n_vectors]); std::iota(indices_sorted.get(), indices_sorted.get() + n_vectors, static_cast(0)); std::partial_sort( @@ -368,6 +385,7 @@ class PDXearch { std::priority_queue, VectorComparator>& heap, uint32_t& current_dimension_idx, size_t& n_vectors_not_pruned, + const tombstones_t& tombstones, uint32_t passing_tuples = 0, uint8_t* selection_vector = nullptr ) { @@ -376,6 +394,7 @@ class PDXearch { size_t tuples_needed_to_exit = static_cast(std::ceil(tuples_threshold * static_cast(n_vectors))); ResetPruningDistances(n_vectors, pruning_distances); + MaskDistancesWithTombstones(tombstones, pruning_distances); uint32_t n_tuples_to_prune = 0; if constexpr (FILTERED) { float selection_percentage = @@ -427,9 +446,11 @@ class PDXearch { std::priority_queue, VectorComparator>& heap, uint32_t& current_dimension_idx, size_t& n_vectors_not_pruned, + const tombstones_t& tombstones, const uint8_t* selection_vector = nullptr ) { GetPruningThreshold(k, heap, pruning_threshold, current_dimension_idx); + MaskDistancesWithTombstones(tombstones, pruning_distances); InitPositionsArray( n_vectors, n_vectors_not_pruned, @@ -570,8 +591,6 @@ class PDXearch { } public: - // TODO: Mask tombstones with a really high distance to avoid returning them as results. - std::vector Search(const float* PDX_RESTRICT const raw_query, const uint32_t k) { Heap local_heap{}; std::unique_ptr query(new float[pdx_data.num_dimensions]); @@ -643,7 +662,8 @@ class PDXearch { cluster.indices, pruning_positions.get(), pruning_distances.get(), - local_heap + local_heap, + cluster.tombstones ); continue; } @@ -659,7 +679,8 @@ class PDXearch { pruning_threshold, local_heap, current_dimension_idx, - n_vectors_not_pruned + n_vectors_not_pruned, + cluster.tombstones ); Prune( local_prepared_query, @@ -672,7 +693,8 @@ class PDXearch { pruning_threshold, local_heap, current_dimension_idx, - n_vectors_not_pruned + n_vectors_not_pruned, + cluster.tombstones ); if (n_vectors_not_pruned) { MergeIntoHeap( @@ -762,7 +784,8 @@ class PDXearch { pruning_distances.get(), local_heap, selection_vector, - passing_tuples + passing_tuples, + cluster.tombstones ); continue; } @@ -779,6 +802,7 @@ class PDXearch { local_heap, current_dimension_idx, n_vectors_not_pruned, + cluster.tombstones, passing_tuples, selection_vector ); @@ -794,6 +818,7 @@ class PDXearch { local_heap, current_dimension_idx, n_vectors_not_pruned, + cluster.tombstones, selection_vector ); if (n_vectors_not_pruned) { From 6de4725401706a6fbae91b1c5affc60c5fe6d34b Mon Sep 17 00:00:00 2001 From: lkuffo Date: Tue, 3 Mar 2026 16:01:14 +0100 Subject: [PATCH 04/24] Maintenance working, now onto optimization of recall --- .gitignore | 1 + benchmarks/CMakeLists.txt | 3 + benchmarks/pdx_insertion.cpp | 204 ++++++++++++++++++ .../DEFAULT/END_TO_END_PDX_ADSAMPLING.csv | 9 + benchmarks/results/DEFAULT/INSERTION_PDX.csv | 21 ++ include/pdx/cluster.hpp | 2 +- include/pdx/index.hpp | 19 +- include/pdx/profiler.hpp | 13 +- tests/CMakeLists.txt | 5 + tests/test_maintenance.cpp | 145 +++++++++++++ 10 files changed, 413 insertions(+), 9 deletions(-) create mode 100644 benchmarks/pdx_insertion.cpp create mode 100644 benchmarks/results/DEFAULT/INSERTION_PDX.csv create mode 100644 tests/test_maintenance.cpp diff --git a/.gitignore b/.gitignore index 678309f..8300d24 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,7 @@ cmake_install.cmake /benchmarks/BenchmarkPDXIVF /benchmarks/BenchmarkFiltered /benchmarks/BenchmarkSpecialFilters +/benchmarks/BenchmarkInsertion # Test binaries (but keep the committed test data) *.bin diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index fa95195..44f088a 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -12,12 +12,14 @@ add_executable(BenchmarkEndToEnd pdx_end_to_end.cpp) add_executable(BenchmarkSerialization pdx_serialization.cpp) add_executable(BenchmarkFiltered pdx_filtered.cpp) add_executable(BenchmarkSpecialFilters pdx_special_filtered.cpp) +add_executable(BenchmarkInsertion pdx_insertion.cpp) target_link_libraries(BenchmarkPDXIVF ${BENCH_COMMON_LIBS}) target_link_libraries(BenchmarkEndToEnd ${BENCH_COMMON_LIBS}) target_link_libraries(BenchmarkSerialization ${BENCH_COMMON_LIBS}) target_link_libraries(BenchmarkFiltered ${BENCH_COMMON_LIBS}) target_link_libraries(BenchmarkSpecialFilters ${BENCH_COMMON_LIBS}) +target_link_libraries(BenchmarkInsertion ${BENCH_COMMON_LIBS}) add_custom_target(benchmarks DEPENDS @@ -26,4 +28,5 @@ add_custom_target(benchmarks BenchmarkSerialization BenchmarkFiltered BenchmarkSpecialFilters + BenchmarkInsertion ) diff --git a/benchmarks/pdx_insertion.cpp b/benchmarks/pdx_insertion.cpp new file mode 100644 index 0000000..c20ee56 --- /dev/null +++ b/benchmarks/pdx_insertion.cpp @@ -0,0 +1,204 @@ +#ifndef BENCHMARK_TIME +#define BENCHMARK_TIME = true +#endif + +#include +#include +#include +#include +#include + +#include "benchmark_utils.hpp" +#include "pdx/index.hpp" +#include "pdx/utils.hpp" +#include "pdx/profiler.hpp" + +template +void RunBenchmark( + const RawDatasetInfo& info, + const std::string& dataset, + const std::string& algorithm, + const float* data, + const float* queries, + const std::vector& nprobes_to_use +) { + const size_t d = info.num_dimensions; + const size_t n = info.num_embeddings; + const size_t n_queries = info.num_queries; + const float proportion_to_build = 0.75f; + uint8_t KNN = BenchmarkUtils::KNN; + size_t NUM_MEASURE_RUNS = BenchmarkUtils::NUM_MEASURE_RUNS; + std::string RESULTS_PATH = BENCHMARK_UTILS.RESULTS_DIR_PATH + "INSERTION_PDX.csv"; + + const size_t n_build = static_cast(n * proportion_to_build); + const size_t n_insert = n - n_build; + + PDX::PDXIndexConfig index_config{ + .num_dimensions = static_cast(d), + .distance_metric = info.distance_metric, + .seed = 42, + .normalize = true, + .sampling_fraction = 1.0f + }; + + // Build index with 75% of the data + TicToc clock; + std::cout << "Building index with " << n_build << " / " << n << " embeddings...\n"; + clock.Reset(); + clock.Tic(); + IndexT pdx_index(index_config); + pdx_index.BuildIndex(data, n_build); + clock.Toc(); + std::cout << "Build time: " << clock.GetMilliseconds() << " ms\n"; + std::cout << "Clusters: " << pdx_index.GetNumClusters() << "\n"; + std::cout << "Index in-memory size: " << std::fixed << std::setprecision(2) + << static_cast(pdx_index.GetInMemorySizeInBytes()) / (1024.0 * 1024.0) + << " MB\n"; + + // Insert remaining 25% + std::cout << "Inserting " << n_insert << " embeddings...\n"; + clock.Reset(); + clock.Tic(); + for (size_t i = 0; i < n_insert; ++i) { + size_t row_id = n_build + i; + pdx_index.Append(row_id, data + row_id * d); + } + clock.Toc(); + std::cout << "Insertion time: " << clock.GetMilliseconds() << " ms\n"; + std::cout << "Avg insertion time: " << clock.GetMilliseconds() / n_insert << " ms/embedding\n"; + std::cout << "Clusters after insertion: " << pdx_index.GetNumClusters() << "\n"; + std::cout << "Index in-memory size after insertion: " << std::fixed << std::setprecision(2) + << static_cast(pdx_index.GetInMemorySizeInBytes()) / (1024.0 * 1024.0) + << " MB\n"; + + PDX::Profiler::Get().PrintHierarchical(); + + // Load ground truth + std::string gt_path = + BenchmarkUtils::GROUND_TRUTH_DATA + info.pdx_dataset_name + "_100_norm"; + auto gt_buffer = MmapFile(gt_path); + uint32_t* int_ground_truth = reinterpret_cast(gt_buffer.get()); + std::cout << "Ground truth loaded: " << gt_path << "\n"; + + for (size_t ivf_nprobe : nprobes_to_use) { + if (pdx_index.GetNumClusters() < ivf_nprobe) + continue; + + pdx_index.SetNProbe(ivf_nprobe); + + // Recall pass + float recalls = 0; + for (size_t l = 0; l < n_queries; ++l) { + auto result = pdx_index.Search(queries + l * d, KNN); + BenchmarkUtils::VerifyResult(recalls, result, KNN, int_ground_truth, l); + } + + // Timing pass + std::vector runtimes; + runtimes.resize(NUM_MEASURE_RUNS * n_queries); + TicToc search_clock; + for (size_t j = 0; j < NUM_MEASURE_RUNS; ++j) { + for (size_t l = 0; l < n_queries; ++l) { + search_clock.Reset(); + search_clock.Tic(); + pdx_index.Search(queries + l * d, KNN); + search_clock.Toc(); + runtimes[j + l * NUM_MEASURE_RUNS] = {search_clock.accum_time}; + } + } + + BenchmarkMetadata results_metadata = { + dataset, + algorithm, + NUM_MEASURE_RUNS, + n_queries, + ivf_nprobe, + KNN, + recalls, + }; + BenchmarkUtils::SaveResults(runtimes, RESULTS_PATH, results_metadata); + } +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " [index_type] [nprobe]\n"; + std::cerr << "Index types: pdx_tree_f32 (default), pdx_tree_u8\n"; + std::cerr << "Available datasets:"; + for (const auto& [name, _] : RAW_DATASET_PARAMS) { + std::cerr << " " << name; + } + std::cerr << "\n"; + return 1; + } + std::string dataset = argv[1]; + std::string index_type = (argc > 2) ? argv[2] : "pdx_tree_f32"; + size_t arg_ivf_nprobe = (argc > 3) ? std::atoi(argv[3]) : 0; + + if (index_type != "pdx_tree_f32" && index_type != "pdx_tree_u8") { + std::cerr << "Error: Only pdx_tree_f32 and pdx_tree_u8 support maintenance (insertion).\n"; + std::cerr << "Got: " << index_type << "\n"; + return 1; + } + + auto it = RAW_DATASET_PARAMS.find(dataset); + if (it == RAW_DATASET_PARAMS.end()) { + std::cerr << "Unknown dataset: " << dataset << "\n"; + return 1; + } + const auto& info = it->second; + const size_t n = info.num_embeddings; + const size_t d = info.num_dimensions; + const size_t n_queries = info.num_queries; + + std::cout << "==> PDX Insertion Benchmark (Build 75% + Insert 25% + Search)\n"; + std::cout << "Dataset: " << dataset << " (n=" << n << ", d=" << d << ")\n"; + std::cout << "Index type: " << index_type << "\n"; + + // Read data + std::string data_path = RAW_DATA_DIR + "/data_" + dataset + ".bin"; + std::string query_path = RAW_DATA_DIR + "/data_" + dataset + "_test.bin"; + + std::vector data(n * d); + { + std::ifstream file(data_path, std::ios::binary); + if (!file) { + std::cerr << "Failed to open " << data_path << "\n"; + return 1; + } + file.read(reinterpret_cast(data.data()), n * d * sizeof(float)); + } + + std::vector queries(n_queries * d); + { + std::ifstream file(query_path, std::ios::binary); + if (!file) { + std::cerr << "Failed to open " << query_path << "\n"; + return 1; + } + file.read(reinterpret_cast(queries.data()), n_queries * d * sizeof(float)); + } + + std::vector nprobes_to_use; + if (arg_ivf_nprobe > 0) { + nprobes_to_use = {arg_ivf_nprobe}; + } else { + nprobes_to_use.assign( + std::begin(BenchmarkUtils::IVF_PROBES), std::end(BenchmarkUtils::IVF_PROBES) + ); + } + + std::string algorithm = "insertion_" + index_type; + + if (index_type == "pdx_tree_f32") { + RunBenchmark( + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use + ); + } else if (index_type == "pdx_tree_u8") { + RunBenchmark( + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use + ); + } + + return 0; +} diff --git a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv index 8540ba3..3a7fb96 100644 --- a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv +++ b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv @@ -55,3 +55,12 @@ mxbai,end_to_end_pdx_tree_u8,0.0865836,0.13575,0.047875,0.898352,16,0,20,1000,0, mxbai,end_to_end_pdx_u8,0.241949,0.279833,0.204458,0.899202,16,0,20,1000,0,1,0.2424,0.293,0.2045 mxbai,end_to_end_pdx_tree_f32,0.155693,0.276708,0.065958,0.901551,16,0,20,1000,0,1,0.1568,0.3437,0.06596 mxbai,end_to_end_pdx_f32,0.306811,0.429333,0.225875,0.902401,16,0,20,1000,0,1,0.3073,0.48,0.2259 +mxbai,end_to_end_pdx_tree_f32,0.161208,0.295208,0.064125,0.901551,16,0,20,1000,0,1,0.1624,0.3614,0.06413 +mxbai,end_to_end_pdx_tree_f32,0.382128,0.765708,0.124584,0.901551,16,0,20,1000,0,1,0.3829,0.7899,0.1246 +mxbai,end_to_end_pdx_tree_f32,0.378389,0.759292,0.12275,0.901551,16,0,20,1000,0,1,0.3792,0.8067,0.1227 +mxbai,end_to_end_pdx_tree_f32,0.353705,0.709667,0.106292,0.901551,16,0,20,1000,0,1,0.355,0.7791,0.1063 +mxbai,end_to_end_pdx_tree_f32,0.171561,0.311167,0.068291,0.901551,16,0,20,1000,0,1,0.1725,0.3818,0.06829 +mxbai,end_to_end_pdx_tree_f32,0.168438,0.304375,0.073333,0.901551,16,0,20,1000,0,1,0.1696,0.3591,0.07333 +mxbai,end_to_end_pdx_tree_f32,0.375045,0.726583,0.11775,0.901551,16,0,20,1000,0,1,0.3767,0.8144,0.1177 +mxbai,end_to_end_pdx_tree_f32,0.169491,0.31325,0.066083,0.901551,16,0,20,1000,0,1,0.1705,0.3802,0.06608 +mxbai,end_to_end_pdx_tree_f32,0.173138,0.319083,0.069584,0.901551,16,0,20,1000,0,1,0.1741,0.4648,0.06958 diff --git a/benchmarks/results/DEFAULT/INSERTION_PDX.csv b/benchmarks/results/DEFAULT/INSERTION_PDX.csv new file mode 100644 index 0000000..e267584 --- /dev/null +++ b/benchmarks/results/DEFAULT/INSERTION_PDX.csv @@ -0,0 +1,21 @@ +dataset,algorithm,avg,max,min,recall,ivf_nprobe,epsilon,knn,n_queries,selectivity,num_measure_runs,avg_all,max_all,min_all +mxbai,insertion_pdx_tree_f32,0.200779,0.359875,0.074333,0.5912,16,0,20,1000,0,1,0.2089,0.5348,0.07433 +mxbai,insertion_pdx_tree_f32,0.159987,0.276375,0.071917,0.761999,16,0,20,1000,0,1,0.1605,0.2989,0.07192 +mxbai,insertion_pdx_tree_f32,0.149417,0.260875,0.070041,0.868152,16,0,20,1000,0,1,0.1506,0.3311,0.07004 +mxbai,insertion_pdx_tree_f32,0.148974,0.260584,0.067458,0.868152,16,0,20,1000,0,1,0.1501,0.3286,0.06746 +mxbai,insertion_pdx_tree_f32,0.144737,0.251875,0.067208,0.868152,16,0,20,1000,0,1,0.1455,0.3145,0.06721 +mxbai,insertion_pdx_tree_f32,0.238282,0.452459,0.09575,0.888752,16,0,20,1000,0,1,0.2412,0.9044,0.09575 +mxbai,insertion_pdx_tree_f32,0.225129,0.431542,0.087875,0.888752,16,0,20,1000,0,1,0.2267,0.4682,0.08787 +mxbai,insertion_pdx_tree_f32,0.265039,0.53375,0.098833,0.891802,16,0,20,1000,0,1,0.2656,0.5567,0.09883 +mxbai,insertion_pdx_tree_f32,0.379143,0.758667,0.119583,0.894402,16,0,20,1000,0,1,0.3809,0.849,0.1196 +mxbai,insertion_pdx_tree_f32,0.383858,0.756,0.115542,0.894402,16,0,20,1000,0,1,0.3855,0.8134,0.1155 +mxbai,insertion_pdx_tree_f32,0.394723,0.802417,0.115542,0.894402,16,0,20,1000,0,1,0.4122,3.506,0.1155 +mxbai,insertion_pdx_tree_f32,0.185603,0.335792,0.07875,0.887452,16,0,20,1000,0,1,0.1869,0.3702,0.07875 +mxbai,insertion_pdx_tree_f32,0.185639,0.347625,0.071042,0.8097,16,0,20,1000,0,1,0.1862,0.3735,0.07104 +mxbai,insertion_pdx_tree_f32,0.158695,0.2855,0.064458,0.902951,16,0,20,1000,0,1,0.159,0.3315,0.06446 +mxbai,insertion_pdx_tree_f32,0.176409,0.318625,0.069041,0.902951,16,0,20,1000,0,1,0.1771,0.3593,0.06904 +mxbai,insertion_pdx_tree_f32,0.152983,0.257708,0.072208,0.803251,16,0,20,1000,0,1,0.1546,0.322,0.07221 +mxbai,insertion_pdx_tree_f32,0.223003,0.385167,0.103666,0.866701,32,0,20,1000,0,1,0.2246,0.4943,0.1037 +mxbai,insertion_pdx_tree_f32,0.321016,0.56525,0.14225,0.911901,64,0,20,1000,0,1,0.3233,0.6596,0.1422 +mxbai,insertion_pdx_tree_f32,0.318446,0.554084,0.143292,0.910601,64,0,20,1000,0,1,0.3212,0.6405,0.1433 +mxbai,insertion_pdx_tree_f32,0.16802,0.286541,0.077667,0.8028,16,0,20,1000,0,1,0.171,0.3723,0.07767 diff --git a/include/pdx/cluster.hpp b/include/pdx/cluster.hpp index a04c947..21e3c59 100644 --- a/include/pdx/cluster.hpp +++ b/include/pdx/cluster.hpp @@ -19,7 +19,7 @@ struct Cluster { using data_t = pdx_data_t; using tombstones_t = std::unordered_set; - constexpr static float CAPACITY_THRESHOLD = 1.1f; // 10% more than the current capacity + constexpr static float CAPACITY_THRESHOLD = 1.2f; // 20% more than the current capacity constexpr static uint32_t MIN_MAX_CAPACITY = 100; Cluster(uint32_t num_embeddings, uint32_t num_dimensions) diff --git a/include/pdx/index.hpp b/include/pdx/index.hpp index 5cfc578..d2d088c 100644 --- a/include/pdx/index.hpp +++ b/include/pdx/index.hpp @@ -555,6 +555,10 @@ class PDXTreeIndex : public IPDXIndex { std::vector Search(const float* query_embedding, size_t knn) const override { PDX_PROFILE_SCOPE("Search"); + auto n_probe = searcher->GetNProbe(); + if (n_probe == 0) { + searcher->SetNProbe(GetNumClusters()); + } auto n_probe_top_level = GetTopLevelNumClusters(); // We confidently prune half of the search space if (searcher->GetNProbe() < GetNumClusters() / 2) { @@ -614,11 +618,12 @@ class PDXTreeIndex : public IPDXIndex { ); // Find nearest centroid for the new embedding + // Pass the RAW embedding to the L0 search — PDXearch::Search internally + // normalizes and rotates the query. Passing `preprocessed` would double-rotate. auto n_probe_top_level = GetTopLevelNumClusters(); - // We confidently prune a quarter of the search space - n_probe_top_level = std::max(1, n_probe_top_level / 4); + n_probe_top_level = std::max(1u, n_probe_top_level / 4); top_level_searcher->SetNProbe(n_probe_top_level); - std::vector centroid_candidates = top_level_searcher->Search(preprocessed.get(), 1); + std::vector centroid_candidates = top_level_searcher->Search(embedding, 1); uint32_t closest_centroid_idx = centroid_candidates[0].index; // Ensure the clusters vector won't reallocate while we hold a reference/lock @@ -818,6 +823,8 @@ class PDXTreeIndex : public IPDXIndex { group_rest.reserve(cluster.num_embeddings); for (size_t i = 0; i < cluster.num_embeddings; i++) { const float* embedding_ptr = cluster_embeddings.get() + i * index.num_dimensions; + // We could avoid this one if we maintain as part of each cluster, the distance + // of each point to its centroid, which would actually be easy to maintain float distance_to_centroid_to_split = distance_computer_f32_t::Horizontal( embedding_ptr, centroid_to_split, index.num_dimensions ); @@ -851,9 +858,10 @@ class PDXTreeIndex : public IPDXIndex { true_centroid_a[d] += emb[d]; } } + auto inv_group_a_size = 1.0f / static_cast(group_a.size()); #pragma clang loop vectorize(enable) for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_a[d] /= static_cast(group_a.size()); + true_centroid_a[d] *= inv_group_a_size; } } if (group_b.empty()) { @@ -867,9 +875,10 @@ class PDXTreeIndex : public IPDXIndex { true_centroid_b[d] += emb[d]; } } + auto inv_group_b_size = 1.0f / static_cast(group_b.size()); #pragma clang loop vectorize(enable) for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_b[d] /= static_cast(group_b.size()); + true_centroid_b[d] *= inv_group_b_size; } } diff --git a/include/pdx/profiler.hpp b/include/pdx/profiler.hpp index 83365fe..df122cd 100644 --- a/include/pdx/profiler.hpp +++ b/include/pdx/profiler.hpp @@ -146,9 +146,11 @@ class Profiler { double secs = data.accum_time_ns / 1e9; double pct = total_ns > 0 ? (data.accum_time_ns * 100.0 / total_ns) : 0.0; + double ms_per_call = + data.call_count > 0 ? (data.accum_time_ns / 1e6 / data.call_count) : 0.0; os << std::left << std::setw(35) << name << std::right << std::setw(10) << secs << "s" << " (" << std::setw(5) << pct << "%)" - << " [" << data.call_count << " calls]" + << " [" << data.call_count << " calls, " << ms_per_call << " ms/call]" << "\n"; } @@ -201,10 +203,12 @@ class Profiler { double secs = data.accum_time_ns / 1e9; double pct = total_ns > 0 ? (data.accum_time_ns * 100.0 / total_ns) : 0.0; + double ms_per_call = + data.call_count > 0 ? (data.accum_time_ns / 1e6 / data.call_count) : 0.0; os << std::left << std::setw(40) << name << std::right << std::setw(10) << secs << "s" << " (" << std::setw(5) << pct << "%)"; if (data.call_count > 1) { - os << " [" << data.call_count << " calls]"; + os << " [" << data.call_count << " calls, " << ms_per_call << " ms/call]"; } os << "\n"; @@ -226,11 +230,14 @@ class Profiler { total_ns > 0 ? (child_data.accum_time_ns * 100.0 / total_ns) : 0.0; std::string short_name = " - " + child.substr(name.length() + 1); + double child_ms_per_call = child_data.call_count > 0 + ? (child_data.accum_time_ns / 1e6 / child_data.call_count) + : 0.0; os << std::left << std::setw(40) << short_name << std::right << std::setw(10) << child_secs << "s" << " (" << std::setw(5) << child_pct << "%)"; if (child_data.call_count > 1) { - os << " [" << child_data.call_count << " calls]"; + os << " [" << child_data.call_count << " calls, " << child_ms_per_call << " ms/call]"; } os << "\n"; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dc8b128..508d6cc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,12 +43,16 @@ target_link_libraries(test_filtered_search.out PRIVATE ${TEST_COMMON_LIBS}) add_executable(test_index_properties.out test_index_properties.cpp) target_link_libraries(test_index_properties.out PRIVATE ${TEST_COMMON_LIBS}) +add_executable(test_maintenance.out test_maintenance.cpp) +target_link_libraries(test_maintenance.out PRIVATE ${TEST_COMMON_LIBS}) + include(GoogleTest) gtest_discover_tests(test_distance_computers.out) gtest_discover_tests(test_search.out) gtest_discover_tests(test_serialization.out) gtest_discover_tests(test_filtered_search.out) gtest_discover_tests(test_index_properties.out) +gtest_discover_tests(test_maintenance.out) add_custom_target(tests DEPENDS @@ -57,4 +61,5 @@ add_custom_target(tests test_serialization.out test_filtered_search.out test_index_properties.out + test_maintenance.out ) diff --git a/tests/test_maintenance.cpp b/tests/test_maintenance.cpp new file mode 100644 index 0000000..03e5117 --- /dev/null +++ b/tests/test_maintenance.cpp @@ -0,0 +1,145 @@ +#undef HAS_FFTW + +#include +#include +#include +#include +#include + +#include "pdx/index.hpp" +#include "test_utils.hpp" + +namespace { + +static constexpr size_t D = 384; + +template +IndexT BuildTreeIndex(const float* data, size_t n, size_t d) { + PDX::PDXIndexConfig config{ + .num_dimensions = static_cast(d), + .distance_metric = PDX::DistanceMetric::L2SQ, + .seed = TestUtils::SEED, + .normalize = true, + .sampling_fraction = 1.0f, + .hierarchical_indexing = true, + }; + IndexT index(config); + index.BuildIndex(data, n); + return index; +} + +// Test 1: Build with N-1 points, insert the last one, search for it +template +void RunInsertSingleAndSearch() { + auto data = TestUtils::LoadTestData(D); + const size_t n_build = TestUtils::N_TRAIN - 1; + const size_t inserted_row_id = n_build; + + auto index = BuildTreeIndex(data.train.data(), n_build, D); + index.Append(inserted_row_id, data.train.data() + inserted_row_id * D); + index.SetNProbe(0); + + auto results = index.Search(data.train.data() + inserted_row_id * D, TestUtils::KNN); + + bool found = false; + for (const auto& r : results) { + if (r.index == static_cast(inserted_row_id)) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Inserted point (row_id=" << inserted_row_id + << ") not found in search results"; +} + +// Test 2: Build with N-10 points, insert 10, filtered search should return all 10 +template +void RunInsertMultipleAndFilteredSearch() { + auto data = TestUtils::LoadTestData(D); + const size_t n_insert = 10; + const size_t n_build = TestUtils::N_TRAIN - n_insert; + + auto index = BuildTreeIndex(data.train.data(), n_build, D); + + std::vector inserted_ids; + for (size_t i = 0; i < n_insert; ++i) { + size_t row_id = n_build + i; + index.Append(row_id, data.train.data() + row_id * D); + inserted_ids.push_back(row_id); + } + + index.SetNProbe(0); + + // Use the first inserted embedding as query + const float* query = data.train.data() + n_build * D; + auto results = index.FilteredSearch(query, n_insert, inserted_ids); + + std::unordered_set result_ids; + for (const auto& r : results) { + result_ids.insert(r.index); + } + + for (size_t id : inserted_ids) { + EXPECT_TRUE(result_ids.count(static_cast(id))) + << "Inserted point (row_id=" << id << ") not found in filtered search results"; + } +} + +// Test 3: Build with N-1 points, insert 1, delete it, search should not find it +template +void RunInsertDeleteAndSearch() { + auto data = TestUtils::LoadTestData(D); + const size_t n_build = TestUtils::N_TRAIN - 1; + const size_t inserted_row_id = n_build; + + auto index = BuildTreeIndex(data.train.data(), n_build, D); + index.Append(inserted_row_id, data.train.data() + inserted_row_id * D); + index.Delete(inserted_row_id); + index.SetNProbe(0); + + auto results = index.Search(data.train.data() + inserted_row_id * D, TestUtils::KNN); + + for (const auto& r : results) { + EXPECT_NE(r.index, static_cast(inserted_row_id)) + << "Deleted point (row_id=" << inserted_row_id + << ") should not appear in search results"; + } +} + +class MaintenanceTest : public ::testing::TestWithParam {}; + +TEST_P(MaintenanceTest, InsertSingleAndSearch) { + std::string index_type = GetParam(); + if (index_type == "pdx_tree_f32") { + RunInsertSingleAndSearch(); + } else { + RunInsertSingleAndSearch(); + } +} + +TEST_P(MaintenanceTest, InsertMultipleAndFilteredSearch) { + std::string index_type = GetParam(); + if (index_type == "pdx_tree_f32") { + RunInsertMultipleAndFilteredSearch(); + } else { + RunInsertMultipleAndFilteredSearch(); + } +} + +TEST_P(MaintenanceTest, InsertDeleteAndSearch) { + std::string index_type = GetParam(); + if (index_type == "pdx_tree_f32") { + RunInsertDeleteAndSearch(); + } else { + RunInsertDeleteAndSearch(); + } +} + +INSTANTIATE_TEST_SUITE_P( + TreeIndexTypes, + MaintenanceTest, + ::testing::Values("pdx_tree_f32", "pdx_tree_u8"), + [](const ::testing::TestParamInfo& info) { return info.param; } +); + +} // namespace From cab7529e2a395527e73027a3724406c7fbe6dade Mon Sep 17 00:00:00 2001 From: lkuffo Date: Wed, 4 Mar 2026 11:40:02 +0100 Subject: [PATCH 05/24] Maintenance API working! Now on to optimizations --- benchmarks/BenchmarkWorkload | Bin 0 -> 592080 bytes benchmarks/CMakeLists.txt | 3 + benchmarks/benchmark_utils.hpp | 7 + benchmarks/pdx_insertion.cpp | 20 +- benchmarks/pdx_workload.cpp | 315 +++++++++++ .../DEFAULT/END_TO_END_PDX_ADSAMPLING.csv | 7 + benchmarks/results/DEFAULT/INSERTION_PDX.csv | 25 + benchmarks/results/DEFAULT/WORKLOAD_PDX.csv | 5 + include/pdx/cluster.hpp | 9 +- include/pdx/index.hpp | 502 ++++++++++++------ 10 files changed, 713 insertions(+), 180 deletions(-) create mode 100755 benchmarks/BenchmarkWorkload create mode 100644 benchmarks/pdx_workload.cpp create mode 100644 benchmarks/results/DEFAULT/WORKLOAD_PDX.csv diff --git a/benchmarks/BenchmarkWorkload b/benchmarks/BenchmarkWorkload new file mode 100755 index 0000000000000000000000000000000000000000..7a786a58c8b515a0155858c34f18ef782f75f2c3 GIT binary patch literal 592080 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

WU)iM{2^+@IoF2Y>$p4CH0lqTfZI7Qs3x`n(&^ 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

((J_n%@)m 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 nprobes_to_use + const std::vector& nprobes_to_use, + const float proportion_to_build ) { const size_t d = info.num_dimensions; const size_t n = info.num_embeddings; const size_t n_queries = info.num_queries; - const float proportion_to_build = 0.75f; uint8_t KNN = BenchmarkUtils::KNN; size_t NUM_MEASURE_RUNS = BenchmarkUtils::NUM_MEASURE_RUNS; std::string RESULTS_PATH = BENCHMARK_UTILS.RESULTS_DIR_PATH + "INSERTION_PDX.csv"; @@ -61,6 +61,7 @@ void RunBenchmark( clock.Tic(); for (size_t i = 0; i < n_insert; ++i) { size_t row_id = n_build + i; + std::cout << "Inserting embedding " << row_id << " / " << n - 1 << "\r" << std::flush; pdx_index.Append(row_id, data + row_id * d); } clock.Toc(); @@ -122,7 +123,7 @@ void RunBenchmark( int main(int argc, char* argv[]) { if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " [index_type] [nprobe]\n"; + std::cerr << "Usage: " << argv[0] << " [index_type] [nprobe] [build_fraction]\n"; std::cerr << "Index types: pdx_tree_f32 (default), pdx_tree_u8\n"; std::cerr << "Available datasets:"; for (const auto& [name, _] : RAW_DATASET_PARAMS) { @@ -134,6 +135,12 @@ int main(int argc, char* argv[]) { std::string dataset = argv[1]; std::string index_type = (argc > 2) ? argv[2] : "pdx_tree_f32"; size_t arg_ivf_nprobe = (argc > 3) ? std::atoi(argv[3]) : 0; + float proportion_to_build = (argc > 4) ? std::atof(argv[4]) : 0.75f; + + if (proportion_to_build <= 0.0f || proportion_to_build >= 1.0f) { + std::cerr << "Error: build_fraction must be in (0, 1). Got: " << proportion_to_build << "\n"; + return 1; + } if (index_type != "pdx_tree_f32" && index_type != "pdx_tree_u8") { std::cerr << "Error: Only pdx_tree_f32 and pdx_tree_u8 support maintenance (insertion).\n"; @@ -151,7 +158,8 @@ int main(int argc, char* argv[]) { const size_t d = info.num_dimensions; const size_t n_queries = info.num_queries; - std::cout << "==> PDX Insertion Benchmark (Build 75% + Insert 25% + Search)\n"; + std::cout << "==> PDX Insertion Benchmark (Build " << static_cast(proportion_to_build * 100) + << "% + Insert " << static_cast((1.0f - proportion_to_build) * 100) << "% + Search)\n"; std::cout << "Dataset: " << dataset << " (n=" << n << ", d=" << d << ")\n"; std::cout << "Index type: " << index_type << "\n"; @@ -192,11 +200,11 @@ int main(int argc, char* argv[]) { if (index_type == "pdx_tree_f32") { RunBenchmark( - info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use, proportion_to_build ); } else if (index_type == "pdx_tree_u8") { RunBenchmark( - info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use, proportion_to_build ); } diff --git a/benchmarks/pdx_workload.cpp b/benchmarks/pdx_workload.cpp new file mode 100644 index 0000000..b6b4e1a --- /dev/null +++ b/benchmarks/pdx_workload.cpp @@ -0,0 +1,315 @@ +#ifndef BENCHMARK_TIME +#define BENCHMARK_TIME = true +#endif + +#include +#include +#include +#include +#include + +#include "benchmark_utils.hpp" +#include "pdx/index.hpp" +#include "pdx/utils.hpp" +#include "pdx/profiler.hpp" + +// ---- Edit workload here ---- +static const std::vector WORKLOAD = { + {StepType::BUILD, 0.50f}, + {StepType::INSERT, 0.50f}, + {StepType::DELETE, 0.20f}, + {StepType::INSERT, 0.20f}, +}; + +template +void RunWorkload( + const RawDatasetInfo& info, + const std::string& dataset, + const std::string& algorithm, + const float* data, + const float* queries, + const std::vector& nprobes_to_use, + const std::vector& workload +) { + const size_t d = info.num_dimensions; + const size_t n = info.num_embeddings; + const size_t n_queries = info.num_queries; + uint8_t KNN = BenchmarkUtils::KNN; + size_t NUM_MEASURE_RUNS = BenchmarkUtils::NUM_MEASURE_RUNS; + std::string RESULTS_PATH = BENCHMARK_UTILS.RESULTS_DIR_PATH + "WORKLOAD_PDX.csv"; + + PDX::PDXIndexConfig index_config{ + .num_dimensions = static_cast(d), + .distance_metric = info.distance_metric, + .seed = 42, + .normalize = true, + .sampling_fraction = 1.0f + }; + + IndexT pdx_index(index_config); + TicToc clock; + + // State tracking: + // - next_row_id: monotonically increasing row_id counter (row_ids are never reused) + // - live_entries: (row_id, data_index) pairs currently in the index (stack for deletes) + // - available_data: data indices freed by deletes, available for re-insertion + // - next_data_cursor: next fresh data index (for inserts when available_data is empty) + // - data_to_row_id: maps data array index → current row_id (for ground truth remapping) + size_t next_row_id = 0; + size_t next_data_cursor = 0; + std::vector> live_entries; // (row_id, data_index) + std::vector available_data; + std::vector data_to_row_id(n); + live_entries.reserve(n); + + // Execute workload steps + for (size_t step_idx = 0; step_idx < workload.size(); ++step_idx) { + const auto& step = workload[step_idx]; + size_t count = static_cast(n * step.proportion); + + switch (step.type) { + case StepType::BUILD: { + std::cout << "\n=== Step " << step_idx << ": BUILD " << count << " embeddings ===\n"; + clock.Reset(); + clock.Tic(); + pdx_index.BuildIndex(data, count); + clock.Toc(); + for (size_t i = 0; i < count; ++i) { + live_entries.push_back({i, i}); + data_to_row_id[i] = static_cast(i); + } + next_row_id = count; + next_data_cursor = count; + std::cout << "Build time: " << clock.GetMilliseconds() << " ms\n"; + break; + } + case StepType::INSERT: { + if (available_data.size() + (n - next_data_cursor) < count) { + std::cerr << "Step " << step_idx << ": INSERT " << count + << " but only " << available_data.size() + (n - next_data_cursor) + << " data points available\n"; + return; + } + std::cout << "\n=== Step " << step_idx << ": INSERT " << count << " embeddings ===\n"; + clock.Reset(); + clock.Tic(); + for (size_t i = 0; i < count; ++i) { + // Pick a data point: reuse freed ones first, then fresh + size_t data_idx; + if (!available_data.empty()) { + data_idx = available_data.back(); + available_data.pop_back(); + } else { + data_idx = next_data_cursor++; + } + size_t row_id = next_row_id++; + std::cout << "Inserting row_id=" << row_id << " (data=" << data_idx << ")\r" << std::flush; + pdx_index.Append(row_id, data + data_idx * d); + live_entries.push_back({row_id, data_idx}); + data_to_row_id[data_idx] = static_cast(row_id); + } + clock.Toc(); + std::cout << "\nInsertion time: " << clock.GetMilliseconds() << " ms\n"; + std::cout << "Avg insertion time: " << clock.GetMilliseconds() / count << " ms/embedding\n"; + break; + } + case StepType::DELETE: { + if (count > live_entries.size()) { + std::cerr << "Step " << step_idx << ": DELETE " << count + << " but only " << live_entries.size() << " live entries\n"; + return; + } + std::cout << "\n=== Step " << step_idx << ": DELETE " << count << " embeddings ===\n"; + clock.Reset(); + clock.Tic(); + for (size_t i = 0; i < count; ++i) { + auto [row_id, data_idx] = live_entries.back(); + live_entries.pop_back(); + std::cout << "Deleting row_id=" << row_id << " (" << i + 1 << "/" << count << ")\r" << std::flush; + pdx_index.Delete(row_id); + available_data.push_back(data_idx); + } + clock.Toc(); + std::cout << "\nDeletion time: " << clock.GetMilliseconds() << " ms\n"; + std::cout << "Avg deletion time: " << clock.GetMilliseconds() / count << " ms/embedding\n"; + break; + } + } + + std::cout << "Clusters: " << pdx_index.GetNumClusters() << "\n"; + std::cout << "Index in-memory size: " << std::fixed << std::setprecision(2) + << static_cast(pdx_index.GetInMemorySizeInBytes()) / (1024.0 * 1024.0) + << " MB\n"; + std::cout << "Live embeddings: " << live_entries.size() << "\n"; + } + + PDX::Profiler::Get().PrintHierarchical(); + + // Load ground truth and remap data indices → current row_ids. + // Ground truth entries are data indices (0..N-1). After deletes + re-inserts, + // some data points have new row_ids. We remap so VerifyResult can compare. + std::string gt_path = + BenchmarkUtils::GROUND_TRUTH_DATA + info.pdx_dataset_name + "_100_norm"; + auto gt_buffer = MmapFile(gt_path); + uint32_t* original_gt = reinterpret_cast(gt_buffer.get()); + + const size_t gt_max_k = BenchmarkUtils::GROUND_TRUTH_MAX_K; + std::vector remapped_gt(n_queries * gt_max_k); + for (size_t q = 0; q < n_queries; ++q) { + for (size_t k = 0; k < gt_max_k; ++k) { + uint32_t data_idx = original_gt[k + q * gt_max_k]; + remapped_gt[k + q * gt_max_k] = data_to_row_id[data_idx]; + } + } + std::cout << "\nGround truth loaded and remapped: " << gt_path << "\n"; + + for (size_t ivf_nprobe : nprobes_to_use) { + if (pdx_index.GetNumClusters() < ivf_nprobe) + continue; + + pdx_index.SetNProbe(ivf_nprobe); + + // Recall pass + float recalls = 0; + for (size_t l = 0; l < n_queries; ++l) { + auto result = pdx_index.Search(queries + l * d, KNN); + BenchmarkUtils::VerifyResult(recalls, result, KNN, remapped_gt.data(), l); + } + + // Timing pass + std::vector runtimes; + runtimes.resize(NUM_MEASURE_RUNS * n_queries); + TicToc search_clock; + for (size_t j = 0; j < NUM_MEASURE_RUNS; ++j) { + for (size_t l = 0; l < n_queries; ++l) { + search_clock.Reset(); + search_clock.Tic(); + pdx_index.Search(queries + l * d, KNN); + search_clock.Toc(); + runtimes[j + l * NUM_MEASURE_RUNS] = {search_clock.accum_time}; + } + } + + BenchmarkMetadata results_metadata = { + dataset, + algorithm, + NUM_MEASURE_RUNS, + n_queries, + ivf_nprobe, + KNN, + recalls, + }; + BenchmarkUtils::SaveResults(runtimes, RESULTS_PATH, results_metadata); + } +} + +int main(int argc, char* argv[]) { + const auto& workload = WORKLOAD; + + // Validate workload: build + inserts - deletes must equal 1.0 + float net_proportion = 0.0f; + for (const auto& step : workload) { + if (step.type == StepType::DELETE) { + net_proportion -= step.proportion; + } else { + net_proportion += step.proportion; + } + } + if (std::abs(net_proportion - 1.0f) > 1e-5f) { + std::cerr << "Error: workload net proportion must equal 1.0 " + << "(build + inserts - deletes), got: " << net_proportion << "\n"; + return 1; + } + + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " [index_type] [nprobe]\n"; + std::cerr << "Index types: pdx_tree_f32 (default), pdx_tree_u8\n"; + std::cerr << "Available datasets:"; + for (const auto& [name, _] : RAW_DATASET_PARAMS) { + std::cerr << " " << name; + } + std::cerr << "\n"; + return 1; + } + std::string dataset = argv[1]; + std::string index_type = (argc > 2) ? argv[2] : "pdx_tree_f32"; + size_t arg_ivf_nprobe = (argc > 3) ? std::atoi(argv[3]) : 0; + + if (index_type != "pdx_tree_f32" && index_type != "pdx_tree_u8") { + std::cerr << "Error: Only pdx_tree_f32 and pdx_tree_u8 support maintenance.\n"; + std::cerr << "Got: " << index_type << "\n"; + return 1; + } + + auto it = RAW_DATASET_PARAMS.find(dataset); + if (it == RAW_DATASET_PARAMS.end()) { + std::cerr << "Unknown dataset: " << dataset << "\n"; + return 1; + } + const auto& info = it->second; + const size_t n = info.num_embeddings; + const size_t d = info.num_dimensions; + const size_t n_queries = info.num_queries; + + // Print workload summary + std::cout << "==> PDX Workload Benchmark\n"; + std::cout << "Dataset: " << dataset << " (n=" << n << ", d=" << d << ")\n"; + std::cout << "Index type: " << index_type << "\n"; + std::cout << "Workload: "; + for (size_t i = 0; i < workload.size(); ++i) { + if (i > 0) std::cout << " -> "; + switch (workload[i].type) { + case StepType::BUILD: std::cout << "build(" << workload[i].proportion << ")"; break; + case StepType::INSERT: std::cout << "insert(" << workload[i].proportion << ")"; break; + case StepType::DELETE: std::cout << "delete(" << workload[i].proportion << ")"; break; + } + } + std::cout << "\n"; + + // Read data + std::string data_path = RAW_DATA_DIR + "/data_" + dataset + ".bin"; + std::string query_path = RAW_DATA_DIR + "/data_" + dataset + "_test.bin"; + + std::vector data(n * d); + { + std::ifstream file(data_path, std::ios::binary); + if (!file) { + std::cerr << "Failed to open " << data_path << "\n"; + return 1; + } + file.read(reinterpret_cast(data.data()), n * d * sizeof(float)); + } + + std::vector queries(n_queries * d); + { + std::ifstream file(query_path, std::ios::binary); + if (!file) { + std::cerr << "Failed to open " << query_path << "\n"; + return 1; + } + file.read(reinterpret_cast(queries.data()), n_queries * d * sizeof(float)); + } + + std::vector nprobes_to_use; + if (arg_ivf_nprobe > 0) { + nprobes_to_use = {arg_ivf_nprobe}; + } else { + nprobes_to_use.assign( + std::begin(BenchmarkUtils::IVF_PROBES), std::end(BenchmarkUtils::IVF_PROBES) + ); + } + + std::string algorithm = "workload_" + index_type; + + if (index_type == "pdx_tree_f32") { + RunWorkload( + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use, workload + ); + } else if (index_type == "pdx_tree_u8") { + RunWorkload( + info, dataset, algorithm, data.data(), queries.data(), nprobes_to_use, workload + ); + } + + return 0; +} diff --git a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv index 3a7fb96..7578c8a 100644 --- a/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv +++ b/benchmarks/results/DEFAULT/END_TO_END_PDX_ADSAMPLING.csv @@ -64,3 +64,10 @@ mxbai,end_to_end_pdx_tree_f32,0.168438,0.304375,0.073333,0.901551,16,0,20,1000,0 mxbai,end_to_end_pdx_tree_f32,0.375045,0.726583,0.11775,0.901551,16,0,20,1000,0,1,0.3767,0.8144,0.1177 mxbai,end_to_end_pdx_tree_f32,0.169491,0.31325,0.066083,0.901551,16,0,20,1000,0,1,0.1705,0.3802,0.06608 mxbai,end_to_end_pdx_tree_f32,0.173138,0.319083,0.069584,0.901551,16,0,20,1000,0,1,0.1741,0.4648,0.06958 +mxbai,end_to_end_pdx_tree_f32,0.192879,0.351,0.07075,0.901551,16,0,20,1000,0,1,0.1953,0.4614,0.07075 +mxbai,end_to_end_pdx_tree_f32,0.221332,0.407292,0.083583,0.916401,20,0,20,1000,0,1,0.2233,0.4746,0.08358 +mxbai,end_to_end_pdx_tree_f32,0.20471,0.392916,0.077333,0.901551,16,0,20,1000,0,1,0.2062,0.4568,0.07733 +mxbai,end_to_end_pdx_tree_f32,0.218574,0.41325,0.0765,0.901551,16,0,20,1000,0,1,0.2205,0.4901,0.0765 +mxbai,end_to_end_pdx_tree_f32,0.292697,0.563417,0.11325,0.937601,30,0,20,1000,0,1,0.2973,0.7519,0.1133 +mxbai,end_to_end_pdx_tree_f32,0.317646,0.61625,0.131042,0.937601,30,0,20,1000,0,1,0.3218,0.7762,0.131 +mxbai,end_to_end_pdx_tree_f32,0.290767,0.568458,0.110125,0.937601,30,0,20,1000,0,1,0.2944,0.7313,0.1101 diff --git a/benchmarks/results/DEFAULT/INSERTION_PDX.csv b/benchmarks/results/DEFAULT/INSERTION_PDX.csv index e267584..207d9f5 100644 --- a/benchmarks/results/DEFAULT/INSERTION_PDX.csv +++ b/benchmarks/results/DEFAULT/INSERTION_PDX.csv @@ -19,3 +19,28 @@ mxbai,insertion_pdx_tree_f32,0.223003,0.385167,0.103666,0.866701,32,0,20,1000,0, mxbai,insertion_pdx_tree_f32,0.321016,0.56525,0.14225,0.911901,64,0,20,1000,0,1,0.3233,0.6596,0.1422 mxbai,insertion_pdx_tree_f32,0.318446,0.554084,0.143292,0.910601,64,0,20,1000,0,1,0.3212,0.6405,0.1433 mxbai,insertion_pdx_tree_f32,0.16802,0.286541,0.077667,0.8028,16,0,20,1000,0,1,0.171,0.3723,0.07767 +mxbai,insertion_pdx_tree_f32,0.160577,0.289708,0.065625,0.902601,16,0,20,1000,0,1,0.1622,0.3361,0.06563 +mxbai,insertion_pdx_tree_f32,0.161674,0.293917,0.06725,0.902951,16,0,20,1000,0,1,0.1621,0.3321,0.06725 +mxbai,insertion_pdx_tree_f32,0.146719,0.245,0.065166,0.8467,16,0,20,1000,0,1,0.1476,0.2912,0.06517 +mxbai,insertion_pdx_tree_f32,0.150566,0.259291,0.06125,0.8467,16,0,20,1000,0,1,0.1511,0.2932,0.06125 +mxbai,insertion_pdx_tree_f32,0.16932,0.281792,0.068417,0.848701,16,0,20,1000,0,1,0.1707,0.3315,0.06842 +mxbai,insertion_pdx_tree_f32,0.162777,0.274042,0.067542,0.8467,16,0,20,1000,0,1,0.1636,0.338,0.06754 +mxbai,insertion_pdx_tree_f32,0.230922,0.401583,0.103333,0.898151,30,0,20,1000,0,1,0.2327,0.4874,0.1033 +mxbai,insertion_pdx_tree_f32,0.203583,0.353042,0.092041,0.897651,30,0,20,1000,0,1,0.2049,0.4108,0.09204 +mxbai,insertion_pdx_tree_f32,0.243083,0.4475,0.097375,0.941251,30,0,20,1000,0,1,0.2457,0.5497,0.09738 +mxbai,insertion_pdx_tree_f32,0.203078,0.351042,0.095542,0.897651,30,0,20,1000,0,1,0.2043,0.4024,0.09554 +mxbai,insertion_pdx_tree_f32,0.245592,0.436709,0.1085,0.875551,30,0,20,1000,0,1,0.2483,0.5219,0.1085 +mxbai,insertion_pdx_tree_f32,0.269391,0.499042,0.111708,0.901001,30,0,20,1000,0,1,0.273,0.6566,0.1117 +mxbai,insertion_pdx_tree_f32,0.286681,0.544875,0.111125,0.941251,30,0,20,1000,0,1,0.2906,0.8645,0.1111 +mxbai,insertion_pdx_tree_f32,0.275169,0.519375,0.10875,0.941752,30,0,20,1000,0,1,0.2799,0.7446,0.1087 +mxbai,insertion_pdx_tree_f32,0.276064,0.507375,0.117958,0.921351,30,0,20,1000,0,1,0.2805,0.7166,0.118 +mxbai,insertion_pdx_tree_f32,0.245297,0.466333,0.09475,0.944651,30,0,20,1000,0,1,0.248,0.6125,0.09475 +mxbai,insertion_pdx_tree_f32,0.259408,0.502458,0.0985,0.944651,30,0,20,1000,0,1,0.2625,0.657,0.0985 +mxbai,insertion_pdx_tree_f32,0.252962,0.481458,0.101958,0.944651,30,0,20,1000,0,1,0.2563,0.6386,0.102 +mxbai,insertion_pdx_tree_f32,0.253303,0.484,0.104875,0.944651,30,0,20,1000,0,1,0.2559,0.6301,0.1049 +mxbai,insertion_pdx_tree_f32,0.247839,0.475708,0.107334,0.931101,30,0,20,1000,0,1,0.2498,0.6067,0.1073 +mxbai,insertion_pdx_tree_f32,0.275729,0.524792,0.11075,0.930951,30,0,20,1000,0,1,0.2795,0.711,0.1108 +mxbai,insertion_pdx_tree_f32,0.253589,0.489958,0.112958,0.931101,30,0,20,1000,0,1,0.2558,0.6193,0.113 +mxbai,insertion_pdx_tree_f32,0.257274,0.489,0.110667,0.939001,30,0,20,1000,0,1,0.2598,0.615,0.1107 +mxbai,insertion_pdx_tree_f32,0.265108,0.500416,0.101375,0.934901,30,0,20,1000,0,1,0.2678,0.604,0.1014 +mxbai,insertion_pdx_tree_f32,0.275068,0.529125,0.109958,0.941101,30,0,20,1000,0,1,0.277,0.5745,0.11 diff --git a/benchmarks/results/DEFAULT/WORKLOAD_PDX.csv b/benchmarks/results/DEFAULT/WORKLOAD_PDX.csv new file mode 100644 index 0000000..a9d78c5 --- /dev/null +++ b/benchmarks/results/DEFAULT/WORKLOAD_PDX.csv @@ -0,0 +1,5 @@ +dataset,algorithm,avg,max,min,recall,ivf_nprobe,epsilon,knn,n_queries,selectivity,num_measure_runs,avg_all,max_all,min_all +mxbai,workload_pdx_tree_f32,0.253887,0.479,0.106416,0.939651,30,0,20,1000,0,1,0.2574,0.5822,0.1064 +mxbai,workload_pdx_tree_f32,0.265593,0.500708,0.102625,0.934901,30,0,20,1000,0,1,0.2685,0.5861,0.1026 +mxbai,workload_pdx_tree_f32,0.274552,0.53125,0.110542,0.941101,30,0,20,1000,0,1,0.2768,0.5679,0.1105 +mxbai,workload_pdx_tree_f32,0.28167,0.530584,0.119375,0.941051,30,0,20,1000,0,1,0.2853,0.5977,0.1194 diff --git a/include/pdx/cluster.hpp b/include/pdx/cluster.hpp index 21e3c59..bb7cd90 100644 --- a/include/pdx/cluster.hpp +++ b/include/pdx/cluster.hpp @@ -19,14 +19,15 @@ struct Cluster { using data_t = pdx_data_t; using tombstones_t = std::unordered_set; - constexpr static float CAPACITY_THRESHOLD = 1.2f; // 20% more than the current capacity - constexpr static uint32_t MIN_MAX_CAPACITY = 100; + constexpr static float CAPACITY_THRESHOLD = 1.3f; // 30% more than the current capacity + constexpr static float MIN_CAPACITY_THRESHOLD = 0.5f; + constexpr static uint32_t MIN_MAX_CAPACITY = 256; Cluster(uint32_t num_embeddings, uint32_t num_dimensions) : num_embeddings(num_embeddings), used_capacity(num_embeddings), max_capacity(std::max(static_cast(num_embeddings * CAPACITY_THRESHOLD), MIN_MAX_CAPACITY)), - min_capacity(static_cast(num_embeddings / CAPACITY_THRESHOLD)), + min_capacity(static_cast(num_embeddings * MIN_CAPACITY_THRESHOLD)), num_dimensions(num_dimensions), indices(new uint32_t[max_capacity]), data(new data_t[static_cast(max_capacity) * num_dimensions]) {} @@ -35,7 +36,7 @@ struct Cluster { : num_embeddings(num_embeddings), used_capacity(num_embeddings), max_capacity(max_capacity), - min_capacity(static_cast(num_embeddings / CAPACITY_THRESHOLD)), + min_capacity(static_cast(num_embeddings * MIN_CAPACITY_THRESHOLD)), num_dimensions(num_dimensions), indices(new uint32_t[max_capacity]), data(new data_t[static_cast(max_capacity) * num_dimensions]) {} diff --git a/include/pdx/index.hpp b/include/pdx/index.hpp index d2d088c..c48e30c 100644 --- a/include/pdx/index.hpp +++ b/include/pdx/index.hpp @@ -632,16 +632,20 @@ class PDXTreeIndex : public IPDXIndex { // Assign to the corresponding cluster auto& cluster = index.clusters[closest_centroid_idx]; - // Lock the cluster for the entire append + health check sequence - std::lock_guard lock(cluster.cluster_mutex); + // Lock only for the mutation; unlock before health check which may + // trigger SplitCluster → ReassignEmbeddings → EnsureClusterVectorHeadroom, + // causing vector reallocation that invalidates the cluster reference. uint32_t index_in_cluster; - if constexpr (Q == U8) { - ScalarQuantizer quantizer(num_dimensions); - auto quantized = std::make_unique(num_dimensions); - quantizer.QuantizeEmbedding(preprocessed.get(), index.quantization_base, index.quantization_scale, quantized.get()); - index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), quantized.get()); - } else { - index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), preprocessed.get()); + { + std::lock_guard lock(cluster.cluster_mutex); + if constexpr (Q == U8) { + ScalarQuantizer quantizer(num_dimensions); + auto quantized = std::make_unique(num_dimensions); + quantizer.QuantizeEmbedding(preprocessed.get(), index.quantization_base, index.quantization_scale, quantized.get()); + index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), quantized.get()); + } else { + index_in_cluster = cluster.AppendEmbedding(static_cast(row_id), preprocessed.get()); + } } row_id_cluster_mapping.emplace_back(closest_centroid_idx, index_in_cluster); index.total_num_embeddings++; @@ -673,8 +677,12 @@ class PDXTreeIndex : public IPDXIndex { CheckClusterHealth(cluster); } - // Caller must hold cluster.cluster_mutex - void CheckClusterHealth(cluster_t& cluster) { + // Caller must hold cluster.cluster_mutex. + // allow_merges: when false, underfull clusters are not merged (only compaction + // and splits are performed). SPFresh triggers merges lazily from the search + // path, not during reassignment — suppressing merges here prevents cascading + // merge ping-pong where two underfull clusters keep destroying each other. + void CheckClusterHealth(cluster_t& cluster, bool allow_merges = true) { if (cluster.used_capacity == cluster.max_capacity) { if (cluster.num_embeddings < cluster.used_capacity) { // There are tombstone slots we can reclaim @@ -686,7 +694,7 @@ class PDXTreeIndex : public IPDXIndex { // Cluster is truly full and compacted, split it SplitCluster(cluster); } - } else if (cluster.num_embeddings <= cluster.min_capacity) { + } else if (allow_merges && cluster.num_embeddings <= cluster.min_capacity) { DestroyAndMergeCluster(cluster); } } @@ -708,52 +716,58 @@ class PDXTreeIndex : public IPDXIndex { // Caller must hold cluster.cluster_mutex void DestroyAndMergeCluster(cluster_t& cluster) { PDX_PROFILE_SCOPE("Merge"); + //std::cout << "Destroy and Merge cluster " << cluster.id << " (num_embeddings=" << cluster.num_embeddings + // << ", used_capacity=" << cluster.used_capacity << ")\n"; const uint32_t cluster_id = cluster.id; + const uint32_t mc = cluster.mesocluster_id; + const uint32_t num_dims = index.num_dimensions; cluster.CompactCluster(); + const uint32_t n_emb = cluster.num_embeddings; auto raw_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); - // We need to dequantize to reassign - std::unique_ptr cluster_embeddings(new float[cluster.num_embeddings * index.num_dimensions]); + + // Copy indices and dequantized embeddings into local buffers BEFORE + // destroying the cluster — after swap-and-pop the cluster object is gone. + std::vector local_indices(cluster.indices, cluster.indices + n_emb); + std::unique_ptr cluster_embeddings(new float[n_emb * num_dims]); if constexpr (Q == U8) { - // Dequantize the embeddings - for (size_t i = 0; i < cluster.num_embeddings; i++) { + for (size_t i = 0; i < n_emb; i++) { searcher->quantizer.DequantizeEmbedding( - raw_embeddings.get() + i * index.num_dimensions, + raw_embeddings.get() + i * num_dims, index.quantization_base, index.quantization_scale, - cluster_embeddings.get() + i * index.num_dimensions + cluster_embeddings.get() + i * num_dims ); } } else { std::memcpy( cluster_embeddings.get(), raw_embeddings.get(), - cluster.num_embeddings * index.num_dimensions * sizeof(float) + n_emb * num_dims * sizeof(float) ); } - ReassignEmbeddings(cluster.indices, cluster_embeddings.get(), cluster.num_embeddings, cluster_id); - const uint32_t num_dims = index.num_dimensions; - uint32_t last_id = index.num_clusters - 1; + // ---- Fully destroy the cluster BEFORE reassignment ---- + // This ensures the dying cluster has no centroid and no slot in + // index.clusters, so no cascading operation (split→group_rest→reassign) + // can accidentally assign new points to it or try to merge it again. - // Remove the deleted cluster's entry from L0 - uint32_t pos_del = FindPositionInMesoCluster(cluster_id, cluster.mesocluster_id); - index.l0.clusters[cluster.mesocluster_id].DeleteEmbedding(pos_del); + // 1. Remove from L0 + uint32_t pos_del = FindPositionInMesoCluster(cluster_id, mc); + index.l0.clusters[mc].DeleteEmbedding(pos_del); index.l0.total_num_embeddings--; + // 2. Swap-and-pop: move last cluster into the dead slot + uint32_t last_id = index.num_clusters - 1; if (cluster_id != last_id) { - // Swap the last cluster into the deleted position index.clusters[cluster_id] = std::move(index.clusters[last_id]); index.clusters[cluster_id].id = cluster_id; - // mesocluster_id stays correct (the moved cluster's meso-cluster didn't change) - // Move the last centroid into the deleted slot std::memcpy( index.centroids.data() + static_cast(cluster_id) * num_dims, index.centroids.data() + static_cast(last_id) * num_dims, num_dims * sizeof(float) ); - // Update row_id_cluster_mapping for all embeddings in the moved cluster auto& moved = index.clusters[cluster_id]; for (uint32_t i = 0; i < moved.used_capacity; i++) { if (!moved.HasTombstone(i)) { @@ -761,18 +775,23 @@ class PDXTreeIndex : public IPDXIndex { } } - // Update L0: the entry that pointed to last_id now points to cluster_id uint32_t pos_last = FindPositionInMesoCluster(last_id, moved.mesocluster_id); index.l0.clusters[moved.mesocluster_id].indices[pos_last] = cluster_id; } - // Pop the last cluster and shrink centroids + // 3. Pop the dead cluster and shrink centroids index.clusters.pop_back(); index.centroids.resize(index.centroids.size() - num_dims); index.num_clusters--; index.ComputeClusterOffsets(); index.l0.ComputeClusterOffsets(); + + // ---- Now reassign the extracted points ---- + // The dead cluster is fully removed: no slot, no centroid, no L0 entry. + // No exclude_cluster_id needed — the slot is occupied by the moved cluster. + ReassignEmbeddings(local_indices.data(), cluster_embeddings.get(), n_emb, + /*exclude_cluster_id=*/UINT32_MAX, /*allow_merges=*/false); } // Caller must hold cluster.cluster_mutex @@ -781,208 +800,351 @@ class PDXTreeIndex : public IPDXIndex { PDX_PROFILE_SCOPE("Split"); const uint32_t cluster_id = cluster.id; const uint32_t mc = cluster.mesocluster_id; + const uint32_t num_dims = index.num_dimensions; + const uint32_t cluster_num_embeddings = cluster.num_embeddings; + + // 1. Extract all embeddings from PDX layout (row-major) std::unique_ptr raw_embeddings = cluster.GetHorizontalEmbeddingsFromPDXBuffer(); - std::unique_ptr cluster_embeddings(new float[cluster.num_embeddings * index.num_dimensions]); + std::unique_ptr cluster_embeddings(new float[cluster.num_embeddings * num_dims]); if constexpr (Q == U8) { - // Dequantize the embeddings for (size_t i = 0; i < cluster.num_embeddings; i++) { searcher->quantizer.DequantizeEmbedding( - raw_embeddings.get() + i * index.num_dimensions, + raw_embeddings.get() + i * num_dims, index.quantization_base, index.quantization_scale, - cluster_embeddings.get() + i * index.num_dimensions + cluster_embeddings.get() + i * num_dims ); } } else { std::memcpy( cluster_embeddings.get(), raw_embeddings.get(), - cluster.num_embeddings * index.num_dimensions * sizeof(float) + cluster.num_embeddings * num_dims * sizeof(float) ); } - auto centroid_to_split = index.centroids.data() + cluster_id * index.num_dimensions; - auto centroid_a = std::unique_ptr(new float[index.num_dimensions]); - auto centroid_b = std::unique_ptr(new float[index.num_dimensions]); - // Split the cluster into two, with the new centroids having a small symmetric perturbation - for (size_t j = 0; j < index.num_dimensions; j++) { - if (j % 2 == 0) { - centroid_a[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); - centroid_b[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); - } else { - centroid_a[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); - centroid_b[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); - } - } - // Compute the distances from each point in the cluster to the centroid being split and to the two new centroids - // One could optimize this by precomputing the distances to the centroid being split - // Further optimization: We could do a GEMM - std::vector group_a, group_b, group_rest; - group_a.reserve(cluster.num_embeddings); - group_b.reserve(cluster.num_embeddings); - group_rest.reserve(cluster.num_embeddings); + // 2. Split using 2-means clustering (balanced split, avoids degenerate perturbation) + auto centroid_to_split = index.centroids.data() + static_cast(cluster_id) * num_dims; + KMeansResult split_result = ComputeKMeans( + cluster_embeddings.get(), + cluster.num_embeddings, + num_dims, + 2, // k=2 + config.distance_metric, + config.seed, + true, // already preprocessed, don't normalize + 1.0f, // use all points (small cluster) + 4, // iterations + false // no hierarchical for 2 clusters + ); + auto centroid_a = std::make_unique(num_dims); + auto centroid_b = std::make_unique(num_dims); + std::memcpy(centroid_a.get(), split_result.centroids.data(), num_dims * sizeof(float)); + std::memcpy(centroid_b.get(), split_result.centroids.data() + num_dims, num_dims * sizeof(float)); + + // [Old perturbation-based split — replaced by 2-means above] + // auto centroid_a = std::make_unique(num_dims); + // auto centroid_b = std::make_unique(num_dims); + // for (size_t j = 0; j < num_dims; j++) { + // if (j % 2 == 0) { + // centroid_a[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); + // centroid_b[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); + // } else { + // centroid_a[j] = centroid_to_split[j] * (1 - CENTROID_PERTURBATION_EPS); + // centroid_b[j] = centroid_to_split[j] * (1 + CENTROID_PERTURBATION_EPS); + // } + // } + + // 3. Partition embeddings using KMeans centroids + SPFresh Condition 1. + // If A or B is closer than the original centroid → assign directly. + // Otherwise the original was closest but is being deleted — check if some + // OTHER existing centroid is even closer → group_rest for global reassignment. + std::vector group_a_idx, group_b_idx, group_rest_idx; + group_a_idx.reserve(split_result.assignments[0].size()); + group_b_idx.reserve(split_result.assignments[1].size()); + // std::cout << "Split cluster " << cluster_id << " into A(num=" << split_result.assignments[0].size() + // << ") and B(num=" << split_result.assignments[1].size() << ")\n"; for (size_t i = 0; i < cluster.num_embeddings; i++) { - const float* embedding_ptr = cluster_embeddings.get() + i * index.num_dimensions; - // We could avoid this one if we maintain as part of each cluster, the distance - // of each point to its centroid, which would actually be easy to maintain + const float* embedding_ptr = cluster_embeddings.get() + i * num_dims; float distance_to_centroid_to_split = distance_computer_f32_t::Horizontal( - embedding_ptr, centroid_to_split, index.num_dimensions + embedding_ptr, centroid_to_split, num_dims ); float distance_to_a = distance_computer_f32_t::Horizontal( - embedding_ptr, centroid_a.get(), index.num_dimensions + embedding_ptr, centroid_a.get(), num_dims ); float distance_to_b = distance_computer_f32_t::Horizontal( - embedding_ptr, centroid_b.get(), index.num_dimensions + embedding_ptr, centroid_b.get(), num_dims ); - if (distance_to_a < distance_to_centroid_to_split && distance_to_a < distance_to_b) { - group_a.push_back(i); - } else if (distance_to_b < distance_to_centroid_to_split && distance_to_b < distance_to_a) { - group_b.push_back(i); - } else { - group_rest.push_back(i); + float min_ab = std::min(distance_to_a, distance_to_b); + + // if (min_ab <= distance_to_centroid_to_split) { + // A or B is at least as close as the original — just pick the closer one + if (distance_to_a <= distance_to_b) { + group_a_idx.push_back(i); + } else { + group_b_idx.push_back(i); + } + // } else { + // // Original was closer than both A and B, but it's being deleted. + // // Check if some other existing centroid is even closer. + // bool closer_elsewhere = false; + // for (uint32_t c = 0; c < index.num_clusters; c++) { + // if (c == cluster_id) continue; + // float d = distance_computer_f32_t::Horizontal( + // embedding_ptr, + // index.centroids.data() + static_cast(c) * num_dims, num_dims + // ); + // if (d < min_ab) { + // closer_elsewhere = true; + // break; + // } + // } + // if (closer_elsewhere) { + // group_rest_idx.push_back(i); + // } else if (distance_to_a <= distance_to_b) { + // group_a_idx.push_back(i); + // } else { + // group_b_idx.push_back(i); + // } + // } + } + + // 4. Gather embeddings and IDs into growable vectors + accumulate centroid sums. + // These vectors will also receive stolen neighbor embeddings before clusters are created. + std::vector embs_a, embs_b; + std::vector ids_a, ids_b; + embs_a.reserve(group_a_idx.size() * num_dims); + embs_b.reserve(group_b_idx.size() * num_dims); + ids_a.reserve(group_a_idx.size()); + ids_b.reserve(group_b_idx.size()); + + auto centroid_sum_a = std::make_unique(num_dims); + auto centroid_sum_b = std::make_unique(num_dims); + std::memset(centroid_sum_a.get(), 0, num_dims * sizeof(float)); + std::memset(centroid_sum_b.get(), 0, num_dims * sizeof(float)); + + for (uint32_t idx : group_a_idx) { + embs_a.insert(embs_a.end(), + raw_embeddings.get() + static_cast(idx) * num_dims, + raw_embeddings.get() + (static_cast(idx) + 1) * num_dims + ); + ids_a.push_back(cluster.indices[idx]); + const float* emb_f = cluster_embeddings.get() + static_cast(idx) * num_dims; + for (size_t d = 0; d < num_dims; d++) { + centroid_sum_a[d] += emb_f[d]; + } + } + for (uint32_t idx : group_b_idx) { + embs_b.insert(embs_b.end(), + raw_embeddings.get() + static_cast(idx) * num_dims, + raw_embeddings.get() + (static_cast(idx) + 1) * num_dims + ); + ids_b.push_back(cluster.indices[idx]); + const float* emb_f = cluster_embeddings.get() + static_cast(idx) * num_dims; + for (size_t d = 0; d < num_dims; d++) { + centroid_sum_b[d] += emb_f[d]; } } - // 5. Compute true centroids by averaging assigned float embeddings. - // If a group is empty, fall back to the perturbed centroid. - auto true_centroid_a = std::make_unique(index.num_dimensions); - auto true_centroid_b = std::make_unique(index.num_dimensions); - if (group_a.empty()) { - std::memcpy(true_centroid_a.get(), centroid_a.get(), index.num_dimensions * sizeof(float)); - } else { - std::memset(true_centroid_a.get(), 0, index.num_dimensions * sizeof(float)); - for (uint32_t idx : group_a) { - const float* emb = cluster_embeddings.get() + static_cast(idx) * index.num_dimensions; -#pragma clang loop vectorize(enable) - for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_a[d] += emb[d]; - } + // 5. Gather group_rest float embeddings and IDs NOW, before the cluster is replaced + auto float_rest = std::make_unique(group_rest_idx.size() * num_dims); + auto ids_rest = std::make_unique(group_rest_idx.size()); + for (size_t i = 0; i < group_rest_idx.size(); i++) { + std::memcpy( + float_rest.get() + i * num_dims, + cluster_embeddings.get() + static_cast(group_rest_idx[i]) * num_dims, + num_dims * sizeof(float) + ); + ids_rest[i] = cluster.indices[group_rest_idx[i]]; + } + + // 6. SPFresh neighbor reassignment using new centroids. + // Clusters A and B don't exist yet — they're just buffers — so there's no + // risk of overflow, cascading splits/merges, or dangling references. + { + PDX_PROFILE_SCOPE("Split/NeighborReassign"); + //std::cout << "Split reassign" << "\n"; + auto& l0_mc = index.l0.clusters[mc]; + std::vector neighbor_ids; + for (uint32_t p = 0; p < l0_mc.used_capacity; p++) { + if (l0_mc.HasTombstone(p)) continue; + uint32_t nid = l0_mc.indices[p]; + if (nid == cluster_id) continue; // cluster B doesn't exist yet + neighbor_ids.push_back(nid); } - auto inv_group_a_size = 1.0f / static_cast(group_a.size()); -#pragma clang loop vectorize(enable) - for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_a[d] *= inv_group_a_size; + + for (uint32_t neighbor_id : neighbor_ids) { + auto& neighbor = index.clusters[neighbor_id]; + const float* neighbor_centroid = index.centroids.data() + + static_cast(neighbor_id) * num_dims; + + //std::cout << "Neighbor " << neighbor_id << "\n"; + // Single-pass: scan embeddings, identify candidates, and immediately + // delete + buffer. DeleteEmbedding only tombstones so data stays readable. + for (uint32_t p = 0; p < neighbor.used_capacity; p++) { + if (neighbor.HasTombstone(p)) continue; + + auto raw_emb = neighbor.GetHorizontalEmbeddingFromPDXBuffer(p); + const float* emb_ptr; + std::unique_ptr emb_f32; + if constexpr (Q == U8) { + emb_f32 = std::make_unique(num_dims); + searcher->quantizer.DequantizeEmbedding( + raw_emb.get(), index.quantization_base, + index.quantization_scale, emb_f32.get() + ); + emb_ptr = emb_f32.get(); + } else { + emb_ptr = raw_emb.get(); + } + + float dist_own = distance_computer_f32_t::Horizontal( + emb_ptr, neighbor_centroid, num_dims + ); + float dist_a = distance_computer_f32_t::Horizontal( + emb_ptr, centroid_a.get(), num_dims + ); + float dist_b = distance_computer_f32_t::Horizontal( + emb_ptr, centroid_b.get(), num_dims + ); + + if (dist_a < dist_own && dist_a <= dist_b) { + uint32_t row_id = neighbor.indices[p]; + neighbor.DeleteEmbedding(p); + embs_a.insert(embs_a.end(), raw_emb.get(), raw_emb.get() + num_dims); + ids_a.push_back(row_id); + for (size_t d = 0; d < num_dims; d++) { + centroid_sum_a[d] += emb_ptr[d]; + } + } else if (dist_b < dist_own && dist_b < dist_a) { + uint32_t row_id = neighbor.indices[p]; + neighbor.DeleteEmbedding(p); + embs_b.insert(embs_b.end(), raw_emb.get(), raw_emb.get() + num_dims); + ids_b.push_back(row_id); + for (size_t d = 0; d < num_dims; d++) { + centroid_sum_b[d] += emb_ptr[d]; + } + } + } } } - if (group_b.empty()) { - std::memcpy(true_centroid_b.get(), centroid_b.get(), index.num_dimensions * sizeof(float)); + + // 7. Compute true centroids from accumulated sums (includes stolen neighbors) + size_t count_a = ids_a.size(); + size_t count_b = ids_b.size(); + auto true_centroid_a = std::make_unique(num_dims); + auto true_centroid_b = std::make_unique(num_dims); + + if (count_a == 0) { + std::memcpy(true_centroid_a.get(), centroid_a.get(), num_dims * sizeof(float)); } else { - std::memset(true_centroid_b.get(), 0, index.num_dimensions * sizeof(float)); - for (uint32_t idx : group_b) { - const float* emb = cluster_embeddings.get() + static_cast(idx) * index.num_dimensions; + float inv = 1.0f / static_cast(count_a); #pragma clang loop vectorize(enable) - for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_b[d] += emb[d]; - } + for (size_t d = 0; d < num_dims; d++) { + true_centroid_a[d] = centroid_sum_a[d] * inv; } - auto inv_group_b_size = 1.0f / static_cast(group_b.size()); + } + if (count_b == 0) { + std::memcpy(true_centroid_b.get(), centroid_b.get(), num_dims * sizeof(float)); + } else { + float inv = 1.0f / static_cast(count_b); #pragma clang loop vectorize(enable) - for (size_t d = 0; d < index.num_dimensions; d++) { - true_centroid_b[d] *= inv_group_b_size; + for (size_t d = 0; d < num_dims; d++) { + true_centroid_b[d] = centroid_sum_b[d] * inv; } } - // 6. Gather data_t embeddings and indices for each group - auto gather_group = [&](const std::vector& group) - -> std::pair, std::unique_ptr> { - auto embs = std::make_unique( - static_cast(group.size()) * index.num_dimensions - ); - auto ids = std::make_unique(group.size()); - for (size_t i = 0; i < group.size(); i++) { - std::memcpy( - embs.get() + i * index.num_dimensions, - raw_embeddings.get() + static_cast(group[i]) * index.num_dimensions, - index.num_dimensions * sizeof(embedding_storage_t) - ); - ids[i] = cluster.indices[group[i]]; - } - return {std::move(embs), std::move(ids)}; - }; - auto [embs_a, ids_a] = gather_group(group_a); - auto [embs_b, ids_b] = gather_group(group_b); - // Gather rest NOW, before the move-assignment below destroys cluster.indices - auto [embs_rest, ids_rest] = gather_group(group_rest); - - // 7. Create new cluster A (replaces old cluster at cluster_id) - cluster_t new_cluster_a(static_cast(group_a.size()), index.num_dimensions); + if (config.normalize){ + Quantizer quantizer(index.num_dimensions); + quantizer.NormalizeQuery( + true_centroid_a.get(), true_centroid_a.get() + ); + quantizer.NormalizeQuery( + true_centroid_b.get(), true_centroid_b.get() + ); + } + + // 8. Create new cluster A (replaces old cluster at cluster_id) + // std::cout << "Split cluster " << cluster_id << " (" << cluster_num_embeddings << " embeddings) into A (count=" << count_a << ") and B (count=" << count_b + // << "), stealing " << (count_a + count_b - split_result.assignments[0].size() - split_result.assignments[1].size()) + // << " neighbors\n"; + cluster_t new_cluster_a(static_cast(count_a), num_dims); new_cluster_a.id = cluster_id; new_cluster_a.mesocluster_id = mc; - std::memcpy(new_cluster_a.indices, ids_a.get(), group_a.size() * sizeof(uint32_t)); - StoreClusterEmbeddings(new_cluster_a, index, embs_a.get(), group_a.size()); + if (count_a > 0) { + std::memcpy(new_cluster_a.indices, ids_a.data(), count_a * sizeof(uint32_t)); + StoreClusterEmbeddings(new_cluster_a, index, embs_a.data(), count_a); + } - // 8. Create new cluster B (will be appended) + // 9. Create new cluster B (will be appended) uint32_t new_cluster_b_id = index.num_clusters; - cluster_t new_cluster_b(static_cast(group_b.size()), index.num_dimensions); + cluster_t new_cluster_b(static_cast(count_b), num_dims); new_cluster_b.id = new_cluster_b_id; new_cluster_b.mesocluster_id = mc; - std::memcpy(new_cluster_b.indices, ids_b.get(), group_b.size() * sizeof(uint32_t)); - StoreClusterEmbeddings(new_cluster_b, index, embs_b.get(), group_b.size()); + if (count_b > 0) { + std::memcpy(new_cluster_b.indices, ids_b.data(), count_b * sizeof(uint32_t)); + StoreClusterEmbeddings(new_cluster_b, index, embs_b.data(), count_b); + } - // 9. Replace old cluster with A, append B. - // The caller must have reserved capacity before taking a reference/lock - // (see EnsureClusterVectorHeadroom), so push_back will not reallocate. + // 10. Replace old cluster with A, append B. + // The caller must have reserved capacity before taking a reference/lock + // (see EnsureClusterVectorHeadroom), so push_back will not reallocate. index.clusters[cluster_id] = std::move(new_cluster_a); index.clusters.push_back(std::move(new_cluster_b)); index.num_clusters++; - // 10. Update centroids: replace old centroid with A's, append B's - std::memcpy( - index.centroids.data() + static_cast(cluster_id) * index.num_dimensions, - true_centroid_a.get(), - index.num_dimensions * sizeof(float) - ); - index.centroids.insert( - index.centroids.end(), - true_centroid_b.get(), - true_centroid_b.get() + index.num_dimensions - ); - - // 11. Update row_id_cluster_mapping - for (size_t i = 0; i < group_a.size(); i++) { - row_id_cluster_mapping[ids_a[i]] = {cluster_id, static_cast(i)}; - } - for (size_t i = 0; i < group_b.size(); i++) { - row_id_cluster_mapping[ids_b[i]] = {new_cluster_b_id, static_cast(i)}; - } - - // 12. Update L0: remove old centroid, add both new centroids + // 11. Update centroids: replace old centroid with A's, append B's + std::memcpy( + index.centroids.data() + static_cast(cluster_id) * num_dims, + true_centroid_a.get(), + num_dims * sizeof(float) + ); + index.centroids.insert( + index.centroids.end(), + true_centroid_b.get(), + true_centroid_b.get() + num_dims + ); + + // 12. Update row_id_cluster_mapping (includes both original and stolen-neighbor points) + for (size_t i = 0; i < count_a; i++) { + row_id_cluster_mapping[ids_a[i]] = {cluster_id, static_cast(i)}; + } + for (size_t i = 0; i < count_b; i++) { + row_id_cluster_mapping[ids_b[i]] = {new_cluster_b_id, static_cast(i)}; + } + + // 13. Update L0: remove old centroid, add both new centroids uint32_t pos = FindPositionInMesoCluster(cluster_id, mc); auto& l0_cluster = index.l0.clusters[mc]; l0_cluster.DeleteEmbedding(pos); l0_cluster.CompactCluster(); - // We removed 1 entry and need to add 2 — grow if there's not enough room if (l0_cluster.used_capacity + 2 > l0_cluster.max_capacity) { l0_cluster.GrowCluster(std::max(l0_cluster.max_capacity * 2, l0_cluster.used_capacity + 2)); } l0_cluster.AppendEmbedding(cluster_id, true_centroid_a.get()); - l0_cluster.AppendEmbedding(new_cluster_b_id, true_centroid_b.get()); - index.l0.total_num_embeddings++; - - // In the remaining points that were not assigned to neither a or b (should be few), - // compact their dequantized embeddings and call REASSIGN - if (!group_rest.empty()) { - auto float_rest = std::make_unique(group_rest.size() * index.num_dimensions); - for (size_t i = 0; i < group_rest.size(); i++) { - std::memcpy( - float_rest.get() + i * index.num_dimensions, - cluster_embeddings.get() + static_cast(group_rest[i]) * index.num_dimensions, - index.num_dimensions * sizeof(float) - ); - } - ReassignEmbeddings(ids_rest.get(), float_rest.get(), static_cast(group_rest.size())); + l0_cluster.AppendEmbedding(new_cluster_b_id, true_centroid_b.get()); + index.l0.total_num_embeddings++; + + // std::cout << "Size of rest: " << group_rest_idx.size() << "\n"; + + // 14. Reassign undecided points (equidistant from both new centroids) + if (!group_rest_idx.empty()) { + ReassignEmbeddings(ids_rest.get(), float_rest.get(), static_cast(group_rest_idx.size())); } + index.ComputeClusterOffsets(); - index.l0.ComputeClusterOffsets(); + index.l0.ComputeClusterOffsets(); } // Reassign dequantized (float) embeddings to their closest centroid. // exclude_cluster_id: skip this cluster during nearest-centroid search (use UINT32_MAX for no exclusion). + // allow_merges: passed to CheckClusterHealth — false suppresses merge cascades. void ReassignEmbeddings( uint32_t* row_ids, const float* embeddings, uint32_t num_embeddings, - uint32_t exclude_cluster_id = UINT32_MAX + uint32_t exclude_cluster_id = UINT32_MAX, + bool allow_merges = true ) { PDX_PROFILE_SCOPE("Reassign"); const uint32_t num_dims = index.num_dimensions; @@ -1022,7 +1184,7 @@ class PDXTreeIndex : public IPDXIndex { row_id_cluster_mapping[row_id] = {best_cluster, new_idx}; // Health check may split → push_back, so ensure headroom each iteration EnsureClusterVectorHeadroom(); - CheckClusterHealth(index.clusters[best_cluster]); + CheckClusterHealth(index.clusters[best_cluster], allow_merges); } } From 25dacec9d597c05ba9d3ab93b4190b54fd73d12f Mon Sep 17 00:00:00 2001 From: lkuffo Date: Wed, 4 Mar 2026 12:54:14 +0100 Subject: [PATCH 06/24] Need to start optimizing --- benchmarks/BenchmarkWorkload | Bin 592080 -> 608592 bytes benchmarks/pdx_workload.cpp | 12 +++-- .../DEFAULT/END_TO_END_PDX_ADSAMPLING.csv | 4 ++ benchmarks/results/DEFAULT/WORKLOAD_PDX.csv | 9 ++++ include/pdx/index.hpp | 50 +++++++++--------- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/benchmarks/BenchmarkWorkload b/benchmarks/BenchmarkWorkload index 7a786a58c8b515a0155858c34f18ef782f75f2c3..fdd99e1e3c8d7bf4b1e47c9b3a1b0cd3abaa59fa 100755 GIT binary patch delta 96552 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