Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions c/src/neighbors/brute_force.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,13 @@ extern "C" cuvsError_t cuvsBruteForceDeserialize(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_deserialize<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_deserialize<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_deserialize<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_deserialize<half>(res, filename));
} else {
RAFT_FAIL("Unsupported index dtype: %d and bits: %d", index->dtype.code, index->dtype.bits);
}
Expand Down
9 changes: 6 additions & 3 deletions c/src/neighbors/cagra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,10 +884,13 @@ extern "C" cuvsError_t cuvsCagraDeserialize(cuvsResources_t res,

index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->addr = reinterpret_cast<uintptr_t>(_deserialize<float>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_deserialize<float>(res, filename));
index->dtype.code = kDLFloat;
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr = reinterpret_cast<uintptr_t>(_deserialize<half>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_deserialize<half>(res, filename));
index->dtype.code = kDLFloat;
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->addr = reinterpret_cast<uintptr_t>(_deserialize<int8_t>(res, filename));
Expand Down
9 changes: 6 additions & 3 deletions c/src/neighbors/ivf_flat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,13 @@ extern "C" cuvsError_t cuvsIvfFlatDeserialize(cuvsResources_t res,

index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->addr = reinterpret_cast<uintptr_t>(_deserialize<float, int64_t>(res, filename));
index->addr = reinterpret_cast<uintptr_t>(
_deserialize<float, int64_t>(res, filename));
index->dtype.code = kDLFloat;
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr = reinterpret_cast<uintptr_t>(_deserialize<half, int64_t>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->addr = reinterpret_cast<uintptr_t>(
_deserialize<half, int64_t>(res, filename));
index->dtype.code = kDLFloat;
index->dtype.bits = 16;
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
Expand Down
18 changes: 12 additions & 6 deletions c/src/neighbors/mg_cagra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,13 @@ extern "C" cuvsError_t cuvsMultiGpuCagraDeserialize(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->dtype.code = kDLInt;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<int8_t>(res, filename));
Expand Down Expand Up @@ -445,10 +448,13 @@ extern "C" cuvsError_t cuvsMultiGpuCagraDistribute(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_mg_distribute<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_mg_distribute<half>(res, filename));
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->dtype.code = kDLInt;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<int8_t>(res, filename));
Expand Down
18 changes: 12 additions & 6 deletions c/src/neighbors/mg_ivf_flat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,13 @@ extern "C" cuvsError_t cuvsMultiGpuIvfFlatDeserialize(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->dtype.code = kDLInt;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<int8_t>(res, filename));
Expand Down Expand Up @@ -442,10 +445,13 @@ extern "C" cuvsError_t cuvsMultiGpuIvfFlatDistribute(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_mg_distribute<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_mg_distribute<half>(res, filename));
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->dtype.code = kDLInt;
index->addr = reinterpret_cast<uintptr_t>(_mg_distribute<int8_t>(res, filename));
Expand Down
9 changes: 6 additions & 3 deletions c/src/neighbors/mg_ivf_pq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,13 @@ extern "C" cuvsError_t cuvsMultiGpuIvfPqDeserialize(cuvsResources_t res,
index->dtype.bits = dtype.itemsize * 8;
if (dtype.kind == 'f' && dtype.itemsize == 4) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if (dtype.kind == 'e' && dtype.itemsize == 2) {
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<float>(res, filename));
} else if ((dtype.kind == 'f' || dtype.kind == 'e') &&
dtype.itemsize == 2) {
index->dtype.code = kDLFloat;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
index->addr =
reinterpret_cast<uintptr_t>(_mg_deserialize<half>(res, filename));
} else if (dtype.kind == 'i' && dtype.itemsize == 1) {
index->dtype.code = kDLInt;
index->addr = reinterpret_cast<uintptr_t>(_mg_deserialize<int8_t>(res, filename));
Expand Down
24 changes: 23 additions & 1 deletion cpp/bench/ann/src/cuvs/cuvs_cagra_hnswlib.cu
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include "../common/ann_types.hpp"
#include "../common/conf.hpp"
#include "cuvs_ann_bench_param_parser.h"
#include "cuvs_cagra_hnswlib_wrapper.h"

Expand All @@ -27,6 +28,8 @@ auto parse_build_param(const nlohmann::json& conf) ->
hnsw_params.hierarchy = cuvs::neighbors::hnsw::HnswHierarchy::CPU;
} else if (conf.at("hierarchy") == "gpu") {
hnsw_params.hierarchy = cuvs::neighbors::hnsw::HnswHierarchy::GPU;
} else if (conf.at("hierarchy") == "gpu_layered_on_disk") {
hnsw_params.hierarchy = cuvs::neighbors::hnsw::HnswHierarchy::GPU_LAYERED_ON_DISK;
} else {
THROW("Invalid value for hierarchy: %s", conf.at("hierarchy").get<std::string>().c_str());
}
Expand All @@ -36,12 +39,31 @@ auto parse_build_param(const nlohmann::json& conf) ->
if (conf.contains("ef_construction")) {
hnsw_params.ef_construction = conf.at("ef_construction");
}
if (conf.contains("dataset_path")) {
hnsw_params.dataset_path = conf.at("dataset_path");
} else if (hnsw_params.hierarchy == cuvs::neighbors::hnsw::HnswHierarchy::GPU_LAYERED_ON_DISK) {
hnsw_params.dataset_path = configuration::singleton().get_dataset_conf().base_file;
}
if (conf.contains("num_threads")) { hnsw_params.num_threads = conf.at("num_threads"); }

// Reuse the CAGRA wrapper params parser
::parse_build_param<T, IdxT>(conf, cagra_params);

if (conf.contains("M")) { hnsw_params.M = conf.at("M"); }

// ACE / GPU_LAYERED_ON_DISK builds can be fine-tuned from the benchmark config. The library
// auto-selects the build algorithm from `M` and `ef_construction`; here we only forward the
// explicit ACE overrides (if any) onto the new hnsw index params.
auto ace_conf = collect_conf_with_prefix(conf, "ace_");
if (!ace_conf.empty()) {
auto ace_params = cuvs::neighbors::hnsw::graph_build_params::ace_params();
if (ace_conf.contains("npartitions")) { ace_params.npartitions = ace_conf.at("npartitions"); }
if (ace_conf.contains("build_dir")) { ace_params.build_dir = ace_conf.at("build_dir"); }
if (ace_conf.contains("ef_construction")) {
ace_params.ef_construction = ace_conf.at("ef_construction");
}
if (ace_conf.contains("use_disk")) { ace_params.use_disk = ace_conf.at("use_disk"); }
hnsw_params.graph_build_params = ace_params;
}
return param;
}

Expand Down
42 changes: 36 additions & 6 deletions cpp/bench/ann/src/cuvs/cuvs_cagra_hnswlib_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,33 @@
#include <raft/core/logger.hpp>

#include <chrono>
#include <filesystem>
#include <memory>

namespace cuvs::bench {

inline void move_file_overwrite(const std::filesystem::path& src, const std::filesystem::path& dst)
{
std::error_code ec;
if (src == dst ||
(std::filesystem::exists(dst, ec) && std::filesystem::equivalent(src, dst, ec))) {
return;
}
if (!dst.parent_path().empty()) { std::filesystem::create_directories(dst.parent_path()); }
if (std::filesystem::exists(dst, ec)) { std::filesystem::remove(dst, ec); }

std::filesystem::rename(src, dst, ec);
if (ec) {
// Rename fails across filesystems. Fall back to copy followed by removal of the source.
ec.clear();
std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing, ec);
const auto src_str = src.string();
const auto dst_str = dst.string();
RAFT_EXPECTS(!ec, "Failed to move '%s' to '%s'.", src_str.c_str(), dst_str.c_str());
std::filesystem::remove(src, ec);
}
}

template <typename T, typename IdxT>
class cuvs_cagra_hnswlib : public algo<T>, public algo_gpu {
public:
Expand Down Expand Up @@ -101,18 +124,25 @@ void cuvs_cagra_hnswlib<T, IdxT>::set_search_param(const search_param_base& para
template <typename T, typename IdxT>
void cuvs_cagra_hnswlib<T, IdxT>::save(const std::string& file) const
{
if (build_param_.hnsw_index_params.hierarchy ==
cuvs::neighbors::hnsw::HnswHierarchy::GPU_LAYERED_ON_DISK) {
const auto src_artifact = std::filesystem::path(hnsw_index_->file_path());
RAFT_EXPECTS(!src_artifact.empty(), "Layered HNSW artifact path is not available.");
RAFT_EXPECTS(std::filesystem::exists(src_artifact),
"Layered HNSW artifact '%s' does not exist.",
src_artifact.c_str());

move_file_overwrite(src_artifact, std::filesystem::path(file));
return;
}

if (cagra_ace_build_) {
std::string index_filename = hnsw_index_->file_path();
RAFT_EXPECTS(!index_filename.empty(), "HNSW index file path is not available.");
RAFT_EXPECTS(std::filesystem::exists(index_filename),
"Index file '%s' does not exist.",
index_filename.c_str());
if (std::filesystem::exists(file)) { std::filesystem::remove(file); }
// might fail when using 2 different filesystems
std::error_code ec;
std::filesystem::rename(index_filename, file, ec);
RAFT_EXPECTS(
!ec, "Failed to rename index file '%s' to '%s'.", index_filename.c_str(), file.c_str());
move_file_overwrite(std::filesystem::path(index_filename), std::filesystem::path(file));
} else {
cuvs::neighbors::hnsw::serialize(handle_, file, *(hnsw_index_.get()));
}
Expand Down
18 changes: 15 additions & 3 deletions cpp/include/cuvs/neighbors/hnsw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <cstdint>
#include <cuvs/core/export.hpp>
#include <memory>
#include <string>
#include <type_traits>
#include <variant>

Expand All @@ -41,9 +42,10 @@ namespace graph_build_params = cuvs::neighbors::graph_build_params;
* NOTE: When the value is `NONE`, the HNSW index is built as a base-layer-only index.
*/
enum class HnswHierarchy {
NONE, // base-layer-only index
CPU, // full index with CPU-built hierarchy
GPU // full index with GPU-built hierarchy
NONE, // base-layer-only index
CPU, // full index with CPU-built hierarchy
GPU, // full index with GPU-built hierarchy
GPU_LAYERED_ON_DISK // GPU-built hierarchy stored as layered on-disk topology
};

struct index_params : cuvs::neighbors::index_params {
Expand All @@ -64,6 +66,16 @@ struct index_params : cuvs::neighbors::index_params {
*/
size_t M = 32;

/** Local dataset path used by layered HNSW deserialization.
*
* When `hierarchy == HnswHierarchy::GPU_LAYERED_ON_DISK`, the index artifact stores graph
* topology only. `deserialize` loads vectors from this local dataset path to reconstruct an
* in-memory HNSW index.
* Currently supported local dataset formats are `.npy` and ANN benchmark `*.bin` files with a
* `[uint32 rows, uint32 cols]` header.
*/
std::string dataset_path;

/** Parameters to fine tune GPU graph building. By default we select the parameters based on
* dataset shape and HNSW build parameters. You can override these parameters to fine tune the
* graph building process as described in the CAGRA build docs.
Expand Down
13 changes: 4 additions & 9 deletions cpp/include/cuvs/util/file_io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <raft/core/numpy_serializer.hpp>
#include <raft/core/serialize.hpp>

#include <cuvs/util/numpy_dtype.hpp>

#include <algorithm>
#include <cstring>
#include <istream>
Expand Down Expand Up @@ -189,15 +191,8 @@ std::pair<file_descriptor, size_t> create_numpy_file(const std::string& path,
// Open file
file_descriptor fd(path, O_CREAT | O_RDWR | O_TRUNC, 0644);

// Build header
const auto dtype = raft::numpy_serializer::get_numpy_dtype<T>();
const bool fortran_order = false;
const raft::numpy_serializer::header_t header = {dtype, fortran_order, shape};

std::stringstream ss;
raft::numpy_serializer::write_header(ss, header);
std::string header_str = ss.str();
size_t header_size = header_str.size();
const std::string header_str = detail::make_numpy_header_string<T>(shape);
size_t header_size = header_str.size();

// Calculate data size from shape
size_t data_bytes = sizeof(T);
Expand Down
Loading
Loading