Repository navigation
Add cuvs-bench faiss_cpu_hnsw_cagra #2944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| #include <stdexcept> | ||
| #include <string> | ||
| #include <type_traits> | ||
| #include <typeinfo> | ||
|
|
||
| namespace { | ||
|
|
||
|
|
@@ -34,6 +35,13 @@ auto parse_metric_type(cuvs::bench::Metric metric) -> faiss::MetricType | |
| throw std::runtime_error("faiss supports only metric type of inner product and L2"); | ||
| } | ||
| } | ||
|
|
||
| auto metric_type_name(faiss::MetricType metric_type) -> std::string | ||
| { | ||
| if (metric_type == faiss::METRIC_INNER_PRODUCT) { return "inner product"; } | ||
| if (metric_type == faiss::METRIC_L2) { return "L2"; } | ||
| return std::to_string(metric_type); | ||
| } | ||
| } // namespace | ||
|
|
||
| namespace cuvs::bench { | ||
|
|
@@ -71,7 +79,7 @@ class faiss_cpu : public algo<T> { | |
| static_assert(std::is_same_v<T, float>, "faiss support only float type"); | ||
| } | ||
|
|
||
| void build(const T* dataset, size_t nrow) final; | ||
| void build(const T* dataset, size_t nrow) override; | ||
|
|
||
| void set_search_param(const search_param_base& param, const void* filter_bitset) override; | ||
|
|
||
|
|
@@ -185,7 +193,22 @@ template <typename T> | |
| template <typename Index> | ||
| void faiss_cpu<T>::load_(const std::string& file) | ||
| { | ||
| index_ = std::shared_ptr<Index>(dynamic_cast<Index*>(faiss::read_index(file.c_str()))); | ||
| std::unique_ptr<faiss::Index> loaded{faiss::read_index(file.c_str())}; | ||
| auto* typed = dynamic_cast<Index*>(loaded.get()); | ||
| if (typed == nullptr) { | ||
| throw std::runtime_error("'" + file + "' does not contain a " + typeid(Index).name() + | ||
| " (found " + typeid(*loaded).name() + ")"); | ||
| } | ||
| if (typed->d != this->dim_) { | ||
| throw std::runtime_error("'" + file + "' has dimension " + std::to_string(typed->d) + | ||
| ", but the dataset has dimension " + std::to_string(this->dim_)); | ||
| } | ||
| if (typed->metric_type != metric_type_) { | ||
| throw std::runtime_error("'" + file + "' uses the " + metric_type_name(typed->metric_type) + | ||
| " metric, but the dataset uses " + metric_type_name(metric_type_)); | ||
| } | ||
| loaded.release(); | ||
| index_.reset(typed); | ||
| } | ||
|
|
||
| template <typename T> | ||
|
|
@@ -267,8 +290,6 @@ class faiss_cpu_ivfrabitq : public faiss_cpu<T> { | |
| } | ||
| }; | ||
|
|
||
| // TODO(snanditale): Enable this in cmake | ||
| // ref: https://github.com/NVIDIA/raft/issues/1876 | ||
| template <typename T> | ||
| class faiss_cpu_ivfsq : public faiss_cpu<T> { | ||
| public: | ||
|
|
@@ -396,4 +417,67 @@ class faiss_cpu_hnsw_flat : public faiss_cpu<T> { | |
| std::shared_ptr<faiss::SearchParameters> search_params_; | ||
| }; | ||
|
|
||
| /** | ||
| * Search-only wrapper for a faiss::IndexHNSWCagra exported by `faiss_gpu_cagra_hnsw`. | ||
| * | ||
| * The index is built and converted on a GPU host; this wrapper only loads and searches it, so it | ||
| * can run in a CPU-only build. | ||
| */ | ||
| template <typename T> | ||
| class faiss_cpu_hnsw_cagra : public faiss_cpu<T> { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class is almost the same as |
||
| public: | ||
| struct search_param : public faiss_cpu<T>::search_param { | ||
| faiss::SearchParametersHNSW p; | ||
| }; | ||
| faiss_cpu_hnsw_cagra(Metric metric, int dim) | ||
| : faiss_cpu<T>(metric, dim, typename faiss_cpu<T>::build_param{}) | ||
| { | ||
| } | ||
|
|
||
| void build(const T*, size_t) override | ||
| { | ||
| throw std::runtime_error( | ||
| "faiss_cpu_hnsw_cagra does not build indexes; build them with faiss_gpu_cagra_hnsw"); | ||
| } | ||
|
|
||
| void set_search_param(const typename algo<T>::search_param& param, | ||
| const void* filter_bitset) override | ||
| { | ||
| if (filter_bitset != nullptr) { throw std::runtime_error("Filtering is not supported yet."); } | ||
| auto sp = static_cast<const typename faiss_cpu_hnsw_cagra<T>::search_param&>(param); | ||
| this->search_params_ = std::make_shared<faiss::SearchParametersHNSW>(sp.p); | ||
| } | ||
|
|
||
| void save(const std::string& file) const override | ||
| { | ||
| this->template save_<faiss::IndexHNSWCagra>(file); | ||
| } | ||
| void load(const std::string& file) override { this->template load_<faiss::IndexHNSWCagra>(file); } | ||
|
|
||
| std::unique_ptr<algo<T>> copy() override | ||
| { | ||
| return std::make_unique<faiss_cpu_hnsw_cagra<T>>(*this); // use copy constructor | ||
| } | ||
|
|
||
| void search(const T* queries, | ||
| int batch_size, | ||
| int k, | ||
| algo_base::index_type* neighbors, | ||
| float* distances) const override | ||
| { | ||
| static_assert(sizeof(size_t) == sizeof(faiss::idx_t), | ||
| "sizes of size_t and faiss::idx_t are different"); | ||
|
|
||
| this->index_->search(batch_size, | ||
| queries, | ||
| k, | ||
| distances, | ||
| reinterpret_cast<faiss::idx_t*>(neighbors), | ||
| search_params_.get()); | ||
| } | ||
|
|
||
| private: | ||
| std::shared_ptr<faiss::SearchParameters> search_params_; | ||
| }; | ||
|
|
||
| } // namespace cuvs::bench | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| # | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
|
|
||
|
|
@@ -21,6 +21,7 @@ | |
| formats (.fbin, .i8bin, .u8bin). | ||
| """ | ||
|
|
||
| import hashlib | ||
| import itertools | ||
| import os | ||
| from typing import Any, Dict, List, Optional | ||
|
|
@@ -153,6 +154,38 @@ def expand_param_grid( | |
| ] | ||
|
|
||
|
|
||
| def get_index_name_prefix(algo: str) -> str: | ||
| """ | ||
| Return the prefix of the names of the indexes ``algo`` searches. | ||
|
|
||
| The prefix is the name of the algorithm that built the indexes, which is | ||
| also the name its build results are stored under. ``faiss_cpu_hnsw_cagra`` | ||
| only searches indexes built by ``faiss_gpu_cagra_hnsw``; every other | ||
| algorithm builds its own indexes. | ||
|
|
||
| ``algo`` may include a group suffix, as in result CSVs for groups other | ||
| than ``base``: ``faiss_cpu_hnsw_cagra_large`` maps to | ||
| ``faiss_gpu_cagra_hnsw_large``. | ||
| """ | ||
| search_algo, build_algo = "faiss_cpu_hnsw_cagra", "faiss_gpu_cagra_hnsw" | ||
| if algo == search_algo or algo.startswith(f"{search_algo}_"): | ||
| return build_algo + algo[len(search_algo) :] | ||
| return algo | ||
|
|
||
|
|
||
| def get_index_filename(index_name: str) -> str: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This solves an existing bug when index names were > 128 characters: Index names of 128 characters or more were saved under Python's hash(), which is randomized per process, so a later --search run (or a different host) could not find the index built by an earlier --build. Long names now use a SHA-256 digest via a shared get_index_filename() helper, so file names are the same on every run and host; names under 128 characters are unchanged. |
||
| """ | ||
| Return the file name an index named ``index_name`` is saved under. | ||
|
|
||
| Names of 128 characters or more are replaced by their SHA-256 digest. The | ||
| digest is the same in every process, so separate build and search runs, | ||
| including runs on different hosts, resolve the same file. | ||
| """ | ||
| if len(index_name) < 128: | ||
| return index_name | ||
| return hashlib.sha256(index_name.encode()).hexdigest() | ||
|
|
||
|
|
||
| def compute_recall( | ||
| neighbors: np.ndarray, groundtruth: np.ndarray, k: int | ||
| ) -> float: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| name: faiss_cpu_hnsw_cagra | ||
| constraints: | ||
| build: cuvs_bench.config.algos.constraints.cuvs_cagra_build | ||
| groups: | ||
| # Build parameters and constraints must match faiss_gpu_cagra_hnsw.yaml so the index names | ||
| # resolve to the files built by faiss_gpu_cagra_hnsw. | ||
| base: | ||
| build: | ||
| graph_degree: [32, 64] | ||
| intermediate_graph_degree: [64, 128] | ||
| cagra_build_algo: ["NN_DESCENT"] | ||
| base_level_only: [false, true] | ||
| search: | ||
| efSearch: [16, 32, 64, 128, 256, 512, 768, 1024] |
Uh oh!
There was an error while loading. Please reload this page.