Skip to content

Commit 2beefce

Browse files
committed
feat(rust): add IVF-SQ bindings
Wraps the IVF-SQ C API (added in v26.06) in the Rust crate, mirroring the cagra module structure: IndexParams and SearchParams builders, Index with build / search / search_with_filter (cuvsFilter bitset) / extend / serialize / deserialize, and the NLists/Dim/Size getters. cuvsIvfSqIndexGetCenters is deliberately not wrapped, matching cagra and ivf_pq. rust/cuvs-sys/src/bindings.rs regenerated (additive). 9 GPU tests: params builders, build + self-neighbor search, repeated search, getters, extend from an empty index, bitset-filtered search (verifies exclusion), serialize/deserialize round-trip (re-verifies search on the loaded index), interior-NUL path rejection.
1 parent 78135be commit 2beefce

6 files changed

Lines changed: 870 additions & 0 deletions

File tree

rust/cuvs-sys/src/bindings.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,6 +1765,182 @@ unsafe extern "C" {
17651765
index: cuvsIvfFlatIndex_t,
17661766
) -> cuvsError_t;
17671767
}
1768+
#[doc = " @defgroup ivf_sq_c_index_params IVF-SQ index build parameters\n @{\n/\n/**\n @brief Supplemental parameters to build IVF-SQ Index\n"]
1769+
#[repr(C)]
1770+
#[derive(Debug, Copy, Clone)]
1771+
pub struct cuvsIvfSqIndexParams {
1772+
#[doc = " Distance type."]
1773+
pub metric: cuvsDistanceType,
1774+
#[doc = " The argument used by some distance metrics."]
1775+
pub metric_arg: f32,
1776+
#[doc = " Whether to add the dataset content to the index, i.e.:\n\n - `true` means the index is filled with the dataset vectors and ready to search after calling\n `build`.\n - `false` means `build` only trains the underlying model (e.g. quantizer or clustering), but\n the index is left empty; you'd need to call `extend` on the index afterwards to populate it."]
1777+
pub add_data_on_build: bool,
1778+
#[doc = " The number of inverted lists (clusters)"]
1779+
pub n_lists: u32,
1780+
#[doc = " The number of iterations searching for kmeans centers (index building)."]
1781+
pub kmeans_n_iters: u32,
1782+
#[doc = " The number of data vectors per cluster to use during iterative kmeans building.\n The index uses at most `n_lists * max_train_points_per_cluster` rows for training."]
1783+
pub max_train_points_per_cluster: u32,
1784+
#[doc = " By default, the algorithm allocates more space than necessary for individual clusters\n (`list_data`). This allows to amortize the cost of memory allocation and reduce the number of\n data copies during repeated calls to `extend` (extending the database).\n\n The alternative is the conservative allocation behavior; when enabled, the algorithm always\n allocates the minimum amount of memory required to store the given number of records. Set this\n flag to `true` if you prefer to use as little GPU memory for the database as possible."]
1785+
pub conservative_memory_allocation: bool,
1786+
}
1787+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
1788+
const _: () = {
1789+
["Size of cuvsIvfSqIndexParams"][::std::mem::size_of::<cuvsIvfSqIndexParams>() - 28usize];
1790+
["Alignment of cuvsIvfSqIndexParams"][::std::mem::align_of::<cuvsIvfSqIndexParams>() - 4usize];
1791+
["Offset of field: cuvsIvfSqIndexParams::metric"]
1792+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, metric) - 0usize];
1793+
["Offset of field: cuvsIvfSqIndexParams::metric_arg"]
1794+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, metric_arg) - 4usize];
1795+
["Offset of field: cuvsIvfSqIndexParams::add_data_on_build"]
1796+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, add_data_on_build) - 8usize];
1797+
["Offset of field: cuvsIvfSqIndexParams::n_lists"]
1798+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, n_lists) - 12usize];
1799+
["Offset of field: cuvsIvfSqIndexParams::kmeans_n_iters"]
1800+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, kmeans_n_iters) - 16usize];
1801+
["Offset of field: cuvsIvfSqIndexParams::max_train_points_per_cluster"]
1802+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, max_train_points_per_cluster) - 20usize];
1803+
["Offset of field: cuvsIvfSqIndexParams::conservative_memory_allocation"]
1804+
[::std::mem::offset_of!(cuvsIvfSqIndexParams, conservative_memory_allocation) - 24usize];
1805+
};
1806+
pub type cuvsIvfSqIndexParams_t = *mut cuvsIvfSqIndexParams;
1807+
unsafe extern "C" {
1808+
#[must_use]
1809+
#[doc = " @brief Allocate IVF-SQ Index params, and populate with default values\n\n @param[in] index_params cuvsIvfSqIndexParams_t to allocate\n @return cuvsError_t"]
1810+
pub fn cuvsIvfSqIndexParamsCreate(index_params: *mut cuvsIvfSqIndexParams_t) -> cuvsError_t;
1811+
}
1812+
unsafe extern "C" {
1813+
#[must_use]
1814+
#[doc = " @brief De-allocate IVF-SQ Index params\n\n @param[in] index_params\n @return cuvsError_t"]
1815+
pub fn cuvsIvfSqIndexParamsDestroy(index_params: cuvsIvfSqIndexParams_t) -> cuvsError_t;
1816+
}
1817+
#[doc = " @defgroup ivf_sq_c_search_params IVF-SQ index search parameters\n @{\n/\n/**\n @brief Supplemental parameters to search IVF-SQ index\n"]
1818+
#[repr(C)]
1819+
#[derive(Debug, Copy, Clone)]
1820+
pub struct cuvsIvfSqSearchParams {
1821+
#[doc = " The number of clusters to search."]
1822+
pub n_probes: u32,
1823+
}
1824+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
1825+
const _: () = {
1826+
["Size of cuvsIvfSqSearchParams"][::std::mem::size_of::<cuvsIvfSqSearchParams>() - 4usize];
1827+
["Alignment of cuvsIvfSqSearchParams"]
1828+
[::std::mem::align_of::<cuvsIvfSqSearchParams>() - 4usize];
1829+
["Offset of field: cuvsIvfSqSearchParams::n_probes"]
1830+
[::std::mem::offset_of!(cuvsIvfSqSearchParams, n_probes) - 0usize];
1831+
};
1832+
pub type cuvsIvfSqSearchParams_t = *mut cuvsIvfSqSearchParams;
1833+
unsafe extern "C" {
1834+
#[must_use]
1835+
#[doc = " @brief Allocate IVF-SQ search params, and populate with default values\n\n @param[in] params cuvsIvfSqSearchParams_t to allocate\n @return cuvsError_t"]
1836+
pub fn cuvsIvfSqSearchParamsCreate(params: *mut cuvsIvfSqSearchParams_t) -> cuvsError_t;
1837+
}
1838+
unsafe extern "C" {
1839+
#[must_use]
1840+
#[doc = " @brief De-allocate IVF-SQ search params\n\n @param[in] params\n @return cuvsError_t"]
1841+
pub fn cuvsIvfSqSearchParamsDestroy(params: cuvsIvfSqSearchParams_t) -> cuvsError_t;
1842+
}
1843+
#[doc = " @defgroup ivf_sq_c_index IVF-SQ index\n @{\n/\n/**\n @brief Struct to hold address of cuvs::neighbors::ivf_sq::index and its active trained dtype\n"]
1844+
#[repr(C)]
1845+
#[derive(Debug, Copy, Clone)]
1846+
pub struct cuvsIvfSqIndex {
1847+
pub addr: usize,
1848+
pub dtype: DLDataType,
1849+
}
1850+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
1851+
const _: () = {
1852+
["Size of cuvsIvfSqIndex"][::std::mem::size_of::<cuvsIvfSqIndex>() - 16usize];
1853+
["Alignment of cuvsIvfSqIndex"][::std::mem::align_of::<cuvsIvfSqIndex>() - 8usize];
1854+
["Offset of field: cuvsIvfSqIndex::addr"]
1855+
[::std::mem::offset_of!(cuvsIvfSqIndex, addr) - 0usize];
1856+
["Offset of field: cuvsIvfSqIndex::dtype"]
1857+
[::std::mem::offset_of!(cuvsIvfSqIndex, dtype) - 8usize];
1858+
};
1859+
pub type cuvsIvfSqIndex_t = *mut cuvsIvfSqIndex;
1860+
unsafe extern "C" {
1861+
#[must_use]
1862+
#[doc = " @brief Allocate IVF-SQ index\n\n @param[in] index cuvsIvfSqIndex_t to allocate\n @return cuvsError_t"]
1863+
pub fn cuvsIvfSqIndexCreate(index: *mut cuvsIvfSqIndex_t) -> cuvsError_t;
1864+
}
1865+
unsafe extern "C" {
1866+
#[must_use]
1867+
#[doc = " @brief De-allocate IVF-SQ index\n\n @param[in] index cuvsIvfSqIndex_t to de-allocate"]
1868+
pub fn cuvsIvfSqIndexDestroy(index: cuvsIvfSqIndex_t) -> cuvsError_t;
1869+
}
1870+
unsafe extern "C" {
1871+
#[must_use]
1872+
#[doc = " Get the number of clusters/inverted lists"]
1873+
pub fn cuvsIvfSqIndexGetNLists(index: cuvsIvfSqIndex_t, n_lists: *mut i64) -> cuvsError_t;
1874+
}
1875+
unsafe extern "C" {
1876+
#[must_use]
1877+
#[doc = " Get the dimensionality of the data"]
1878+
pub fn cuvsIvfSqIndexGetDim(index: cuvsIvfSqIndex_t, dim: *mut i64) -> cuvsError_t;
1879+
}
1880+
unsafe extern "C" {
1881+
#[must_use]
1882+
#[doc = " Get the size of the index"]
1883+
pub fn cuvsIvfSqIndexGetSize(index: cuvsIvfSqIndex_t, size: *mut i64) -> cuvsError_t;
1884+
}
1885+
unsafe extern "C" {
1886+
#[must_use]
1887+
#[doc = " @brief Get the cluster centers corresponding to the lists [n_lists, dim]\n\n @param[in] index cuvsIvfSqIndex_t Built Ivf-SQ Index\n @param[out] centers Preallocated array on host or device memory to store output, [n_lists, dim]\n @return cuvsError_t"]
1888+
pub fn cuvsIvfSqIndexGetCenters(
1889+
index: cuvsIvfSqIndex_t,
1890+
centers: *mut DLManagedTensor,
1891+
) -> cuvsError_t;
1892+
}
1893+
unsafe extern "C" {
1894+
#[must_use]
1895+
#[doc = " @defgroup ivf_sq_c_index_build IVF-SQ index build\n @{\n/\n/**\n @brief Build an IVF-SQ index with a `DLManagedTensor` which has underlying\n `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`,\n or `kDLCPU`. Also, acceptable underlying types are:\n 1. `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32`\n 2. `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 16`\n\n @code {.c}\n #include <cuvs/core/c_api.h>\n #include <cuvs/neighbors/ivf_sq.h>\n\n // Create cuvsResources_t\n cuvsResources_t res;\n cuvsError_t res_create_status = cuvsResourcesCreate(&res);\n\n // Assume a populated `DLManagedTensor` type here\n DLManagedTensor dataset;\n\n // Create default index params\n cuvsIvfSqIndexParams_t index_params;\n cuvsError_t params_create_status = cuvsIvfSqIndexParamsCreate(&index_params);\n\n // Create IVF-SQ index\n cuvsIvfSqIndex_t index;\n cuvsError_t index_create_status = cuvsIvfSqIndexCreate(&index);\n\n // Build the IVF-SQ Index\n cuvsError_t build_status = cuvsIvfSqBuild(res, index_params, &dataset, index);\n\n // de-allocate `index_params`, `index` and `res`\n cuvsError_t params_destroy_status = cuvsIvfSqIndexParamsDestroy(index_params);\n cuvsError_t index_destroy_status = cuvsIvfSqIndexDestroy(index);\n cuvsError_t res_destroy_status = cuvsResourcesDestroy(res);\n @endcode\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] index_params cuvsIvfSqIndexParams_t used to build IVF-SQ index\n @param[in] dataset DLManagedTensor* training dataset\n @param[out] index cuvsIvfSqIndex_t Newly built IVF-SQ index\n @return cuvsError_t"]
1896+
pub fn cuvsIvfSqBuild(
1897+
res: cuvsResources_t,
1898+
index_params: cuvsIvfSqIndexParams_t,
1899+
dataset: *mut DLManagedTensor,
1900+
index: cuvsIvfSqIndex_t,
1901+
) -> cuvsError_t;
1902+
}
1903+
unsafe extern "C" {
1904+
#[must_use]
1905+
#[doc = " @defgroup ivf_sq_c_index_search IVF-SQ index search\n @{\n/\n/**\n @brief Search an IVF-SQ index with a `DLManagedTensor` which has underlying\n `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`.\n Types for input are:\n 1. `queries`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` or 16\n 2. `neighbors`: `kDLDataType.code == kDLInt` and `kDLDataType.bits = 64`\n 3. `distances`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32`\n\n @code {.c}\n #include <cuvs/core/c_api.h>\n #include <cuvs/neighbors/ivf_sq.h>\n\n // Create cuvsResources_t\n cuvsResources_t res;\n cuvsError_t res_create_status = cuvsResourcesCreate(&res);\n\n // Assume a populated `DLManagedTensor` type here\n DLManagedTensor queries;\n DLManagedTensor neighbors;\n DLManagedTensor distances;\n\n // Create default search params\n cuvsIvfSqSearchParams_t search_params;\n cuvsError_t params_create_status = cuvsIvfSqSearchParamsCreate(&search_params);\n\n // Search the `index` built using `cuvsIvfSqBuild`\n cuvsError_t search_status = cuvsIvfSqSearch(\n res, search_params, index, &queries, &neighbors, &distances, (cuvsFilter){});\n\n // de-allocate `search_params` and `res`\n cuvsError_t params_destroy_status = cuvsIvfSqSearchParamsDestroy(search_params);\n cuvsError_t res_destroy_status = cuvsResourcesDestroy(res);\n @endcode\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] search_params cuvsIvfSqSearchParams_t used to search IVF-SQ index\n @param[in] index ivfSqIndex which has been returned by `cuvsIvfSqBuild`\n @param[in] queries DLManagedTensor* queries dataset to search\n @param[out] neighbors DLManagedTensor* output `k` neighbors for queries\n @param[out] distances DLManagedTensor* output `k` distances for queries\n @param[in] filter cuvsFilter input filter that can be used\n to filter queries and neighbors based on the given bitset."]
1906+
pub fn cuvsIvfSqSearch(
1907+
res: cuvsResources_t,
1908+
search_params: cuvsIvfSqSearchParams_t,
1909+
index: cuvsIvfSqIndex_t,
1910+
queries: *mut DLManagedTensor,
1911+
neighbors: *mut DLManagedTensor,
1912+
distances: *mut DLManagedTensor,
1913+
filter: cuvsFilter,
1914+
) -> cuvsError_t;
1915+
}
1916+
unsafe extern "C" {
1917+
#[must_use]
1918+
#[doc = " @defgroup ivf_sq_c_index_serialize IVF-SQ C-API serialize functions\n @{\n/\n/**\n Save the index to file.\n\n Experimental, both the API and the serialization format are subject to change.\n\n @code{.c}\n #include <cuvs/neighbors/ivf_sq.h>\n\n // Create cuvsResources_t\n cuvsResources_t res;\n cuvsError_t res_create_status = cuvsResourcesCreate(&res);\n\n // create an index with `cuvsIvfSqBuild`\n cuvsIvfSqSerialize(res, \"/path/to/index\", index);\n @endcode\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] filename the file name for saving the index\n @param[in] index IVF-SQ index"]
1919+
pub fn cuvsIvfSqSerialize(
1920+
res: cuvsResources_t,
1921+
filename: *const ::std::os::raw::c_char,
1922+
index: cuvsIvfSqIndex_t,
1923+
) -> cuvsError_t;
1924+
}
1925+
unsafe extern "C" {
1926+
#[must_use]
1927+
#[doc = " Load index from file.\n\n Experimental, both the API and the serialization format are subject to change.\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] filename the name of the file that stores the index\n @param[out] index IVF-SQ index loaded from disk"]
1928+
pub fn cuvsIvfSqDeserialize(
1929+
res: cuvsResources_t,
1930+
filename: *const ::std::os::raw::c_char,
1931+
index: cuvsIvfSqIndex_t,
1932+
) -> cuvsError_t;
1933+
}
1934+
unsafe extern "C" {
1935+
#[must_use]
1936+
#[doc = " @defgroup ivf_sq_c_index_extend IVF-SQ index extend\n @{\n/\n/**\n @brief Extend the index with the new data.\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] new_vectors DLManagedTensor* the new vectors to add to the index\n @param[in] new_indices DLManagedTensor* vector of new indices for the new vectors. If the index\n is empty, this can be NULL to imply a continuous range `[0...n_rows)`.\n @param[inout] index IVF-SQ index to be extended\n @return cuvsError_t"]
1937+
pub fn cuvsIvfSqExtend(
1938+
res: cuvsResources_t,
1939+
new_vectors: *mut DLManagedTensor,
1940+
new_indices: *mut DLManagedTensor,
1941+
index: cuvsIvfSqIndex_t,
1942+
) -> cuvsError_t;
1943+
}
17681944
unsafe extern "C" {
17691945
#[must_use]
17701946
#[doc = " @defgroup ann_refine_c Approximate Nearest Neighbors Refinement C-API\n @{\n/\n/**\n @brief Refine nearest neighbor search.\n\n Refinement is an operation that follows an approximate NN search. The approximate search has\n already selected n_candidates neighbor candidates for each query. We narrow it down to k\n neighbors. For each query, we calculate the exact distance between the query and its\n n_candidates neighbor candidate, and select the k nearest ones.\n\n @param[in] res cuvsResources_t opaque C handle\n @param[in] dataset device matrix that stores the dataset [n_rows, dims]\n @param[in] queries device matrix of the queries [n_queris, dims]\n @param[in] candidates indices of candidate vectors [n_queries, n_candidates], where\n n_candidates >= k\n @param[in] metric distance metric to use. Euclidean (L2) is used by default\n @param[out] indices device matrix that stores the refined indices [n_queries, k]\n @param[out] distances device matrix that stores the refined distances [n_queries, k]"]

0 commit comments

Comments
 (0)