Skip to content
Open
Show file tree
Hide file tree
Changes from 15 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
20 changes: 2 additions & 18 deletions c/include/cuvs/neighbors/cagra.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ typedef struct cuvsIvfPqParams* cuvsIvfPqParams_t;

/**
* Parameters for ACE (Augmented Core Extraction) graph build.
* ACE enables building indices for datasets too large to fit in GPU memory by:
* ACE enables building indexes for datasets too large to fit in GPU memory by:
* 1. Partitioning the dataset in core (closest) and augmented (second-closest)
* partitions using balanced k-means.
* 2. Building sub-indices for each partition independently
* 2. Building sub-indexes for each partition independently
* 3. Concatenating sub-graphs into a final unified index
*/
struct cuvsAceParams {
Expand Down Expand Up @@ -251,22 +251,6 @@ cuvsError_t cuvsAceParamsCreate(cuvsAceParams_t* params);
*/
cuvsError_t cuvsAceParamsDestroy(cuvsAceParams_t params);

/**
* @brief Allocate ACE params, and populate with default values
*
* @param[in] params cuvsAceParams_t to allocate
* @return cuvsError_t
*/
cuvsError_t cuvsAceParamsCreate(cuvsAceParams_t* params);

/**
* @brief De-allocate ACE params
*
* @param[in] params
* @return cuvsError_t
*/
cuvsError_t cuvsAceParamsDestroy(cuvsAceParams_t params);

/**
* @brief Create CAGRA index parameters similar to an HNSW index
*
Expand Down
126 changes: 126 additions & 0 deletions c/include/cuvs/neighbors/hnsw.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,50 @@ enum cuvsHnswHierarchy {
GPU = 2
};

/**
* Parameters for ACE (Augmented Core Extraction) graph build for HNSW.
* ACE enables building indexes for datasets too large to fit in GPU memory by:
* 1. Partitioning the dataset in core and augmented partitions using balanced k-means
* 2. Building sub-indexes for each partition independently
* 3. Concatenating sub-graphs into a final unified index
*/
struct cuvsHnswAceParams {
/**
* Number of partitions for ACE partitioned build.
* Small values might improve recall but potentially degrade performance and
* increase memory usage. 100k - 5M vectors per partition is recommended.
*/
size_t npartitions;
/**
* Directory to store ACE build artifacts (e.g., KNN graph, optimized graph).
* Used when `use_disk` is true or when the graph does not fit in memory.
*/
const char* build_dir;
/**
* Whether to use disk-based storage for ACE build.
* When true, enables disk-based operations for memory-efficient graph construction.
*/
bool use_disk;
};

typedef struct cuvsHnswAceParams* cuvsHnswAceParams_t;

/**
* @brief Allocate HNSW ACE params, and populate with default values
*
* @param[in] params cuvsHnswAceParams_t to allocate
* @return cuvsError_t
*/
cuvsError_t cuvsHnswAceParamsCreate(cuvsHnswAceParams_t* params);

/**
* @brief De-allocate HNSW ACE params
*
* @param[in] params
* @return cuvsError_t
*/
cuvsError_t cuvsHnswAceParamsDestroy(cuvsHnswAceParams_t params);

struct cuvsHnswIndexParams {
/* hierarchy of the hnsw index */
enum cuvsHnswHierarchy hierarchy;
Expand All @@ -49,6 +93,17 @@ struct cuvsHnswIndexParams {
is parallelized with the help of CPU threads.
*/
int num_threads;
/** HNSW M parameter: number of bi-directional links per node (used when building with ACE).
* graph_degree = m * 2, intermediate_graph_degree = m * 3.
*/
size_t M;
/** Distance type for the index. */
cuvsDistanceType metric;
/**
* Optional: specify ACE parameters for building HNSW index using ACE algorithm.
* Set to nullptr for default behavior (from_cagra conversion).
*/
cuvsHnswAceParams_t ace_params;
};

typedef struct cuvsHnswIndexParams* cuvsHnswIndexParams_t;
Expand Down Expand Up @@ -203,6 +258,77 @@ cuvsError_t cuvsHnswFromCagraWithDataset(cuvsResources_t res,
* @}
*/

/**
* @defgroup hnsw_c_index_build Build HNSW index using ACE algorithm
* @{
*/

/**
* @brief Build an HNSW index using ACE (Augmented Core Extraction) algorithm.
*
* ACE enables building HNSW indexes for datasets too large to fit in GPU memory by:
* 1. Partitioning the dataset using balanced k-means into core and augmented partitions
* 2. Building sub-indexes for each partition independently
* 3. Concatenating sub-graphs into a final unified index
*
* NOTE: This function requires CUDA to be available at runtime.
*
* @param[in] res cuvsResources_t opaque C handle
* @param[in] params cuvsHnswIndexParams_t with ACE parameters configured
* @param[in] dataset DLManagedTensor* host dataset to build index from
* @param[out] index cuvsHnswIndex_t to return the built HNSW index
*
* @return cuvsError_t
*
* @code{.c}
* #include <cuvs/core/c_api.h>
* #include <cuvs/neighbors/hnsw.h>
*
* // Create cuvsResources_t
* cuvsResources_t res;
* cuvsResourcesCreate(&res);
*
* // Create ACE parameters
* cuvsHnswAceParams_t ace_params;
* cuvsHnswAceParamsCreate(&ace_params);
* ace_params->npartitions = 4;
* ace_params->use_disk = true;
* ace_params->build_dir = "/tmp/hnsw_ace_build";
*
* // Create index parameters
* cuvsHnswIndexParams_t params;
* cuvsHnswIndexParamsCreate(&params);
* params->hierarchy = GPU;
* params->ace_params = ace_params;
* params->M = 32;
* params->ef_construction = 120;
*
* // Create HNSW index
* cuvsHnswIndex_t hnsw_index;
* cuvsHnswIndexCreate(&hnsw_index);
*
* // Assume dataset is a populated DLManagedTensor with host data
* DLManagedTensor dataset;
*
* // Build the index
* cuvsHnswBuild(res, params, &dataset, hnsw_index);
*
* // Clean up
* cuvsHnswAceParamsDestroy(ace_params);
* cuvsHnswIndexParamsDestroy(params);
* cuvsHnswIndexDestroy(hnsw_index);
* cuvsResourcesDestroy(res);
* @endcode
*/
cuvsError_t cuvsHnswBuild(cuvsResources_t res,
cuvsHnswIndexParams_t params,
DLManagedTensor* dataset,
cuvsHnswIndex_t index);

/**
* @}
*/

/**
* @defgroup hnsw_c_index_extend Extend HNSW index with additional vectors
* @{
Expand Down
75 changes: 73 additions & 2 deletions c/src/neighbors/hnsw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,36 @@

namespace {

template <typename T>
void _build(cuvsResources_t res,
cuvsHnswIndexParams_t params,
DLManagedTensor* dataset_tensor,
cuvsHnswIndex_t hnsw_index)
{
auto res_ptr = reinterpret_cast<raft::resources*>(res);
auto cpp_params = cuvs::neighbors::hnsw::index_params();
cpp_params.hierarchy = static_cast<cuvs::neighbors::hnsw::HnswHierarchy>(params->hierarchy);
cpp_params.ef_construction = params->ef_construction;
cpp_params.num_threads = params->num_threads;
cpp_params.M = params->M;
cpp_params.metric = static_cast<cuvs::distance::DistanceType>(params->metric);

// Configure ACE parameters
RAFT_EXPECTS(params->ace_params != nullptr, "ACE parameters must be set for hnsw::build");
auto ace_params = cuvs::neighbors::hnsw::graph_build_params::ace_params();
ace_params.npartitions = params->ace_params->npartitions;
ace_params.build_dir = params->ace_params->build_dir ? params->ace_params->build_dir : "/tmp/hnsw_ace_build";
ace_params.use_disk = params->ace_params->use_disk;
cpp_params.graph_build_params = ace_params;

using dataset_mdspan_type = raft::host_matrix_view<T const, int64_t, raft::row_major>;
auto dataset_mds = cuvs::core::from_dlpack<dataset_mdspan_type>(dataset_tensor);

auto hnsw_index_unique_ptr = cuvs::neighbors::hnsw::build(*res_ptr, cpp_params, dataset_mds);
auto hnsw_index_ptr = hnsw_index_unique_ptr.release();
hnsw_index->addr = reinterpret_cast<uintptr_t>(hnsw_index_ptr);
}

template <typename T>
void _from_cagra(cuvsResources_t res,
cuvsHnswIndexParams_t params,
Expand Down Expand Up @@ -118,11 +148,29 @@ void* _deserialize(cuvsResources_t res,
}
} // namespace

extern "C" cuvsError_t cuvsHnswAceParamsCreate(cuvsHnswAceParams_t* params)
{
return cuvs::core::translate_exceptions([=] {
*params = new cuvsHnswAceParams{.npartitions = 1,
.build_dir = "/tmp/hnsw_ace_build",
.use_disk = false};
});
}

extern "C" cuvsError_t cuvsHnswAceParamsDestroy(cuvsHnswAceParams_t params)
{
return cuvs::core::translate_exceptions([=] { delete params; });
}

extern "C" cuvsError_t cuvsHnswIndexParamsCreate(cuvsHnswIndexParams_t* params)
{
return cuvs::core::translate_exceptions([=] {
*params = new cuvsHnswIndexParams{
.hierarchy = cuvsHnswHierarchy::NONE, .ef_construction = 200, .num_threads = 0};
*params = new cuvsHnswIndexParams{.hierarchy = cuvsHnswHierarchy::NONE,
.ef_construction = 200,
.num_threads = 0,
.M = 32,
.metric = L2Expanded,
.ace_params = nullptr};
});
}

Expand Down Expand Up @@ -213,6 +261,29 @@ extern "C" cuvsError_t cuvsHnswFromCagraWithDataset(cuvsResources_t res,
});
}

extern "C" cuvsError_t cuvsHnswBuild(cuvsResources_t res,
cuvsHnswIndexParams_t params,
DLManagedTensor* dataset,
cuvsHnswIndex_t index)
{
return cuvs::core::translate_exceptions([=] {
auto dataset_dl = dataset->dl_tensor;
index->dtype = dataset_dl.dtype;

if (dataset_dl.dtype.code == kDLFloat && dataset_dl.dtype.bits == 32) {
_build<float>(res, params, dataset, index);
} else if (dataset_dl.dtype.code == kDLFloat && dataset_dl.dtype.bits == 16) {
_build<half>(res, params, dataset, index);
} else if (dataset_dl.dtype.code == kDLUInt && dataset_dl.dtype.bits == 8) {
_build<uint8_t>(res, params, dataset, index);
} else if (dataset_dl.dtype.code == kDLInt && dataset_dl.dtype.bits == 8) {
_build<int8_t>(res, params, dataset, index);
} else {
RAFT_FAIL("Unsupported dtype: code=%d, bits=%d", dataset_dl.dtype.code, dataset_dl.dtype.bits);
}
});
}

extern "C" cuvsError_t cuvsHnswExtend(cuvsResources_t res,
cuvsHnswExtendParams_t params,
DLManagedTensor* additional_dataset,
Expand Down
Loading
Loading