[REVIEW] New Dataset API Clarifying Ownership - #1846
Conversation
|
/ok to test 5447a4c |
|
/ok to test 17ab09d |
|
NB: I updated the label to |
@achirkin The problem w/ using mdspan/mdarray for this is that it's not carrying along the proper information to either the algorithms nor the user (which is why we created this specialized class for this in the first place!). Two immediate reasons why this API is necessary:
This new API solves both of these problems while leaving the control over the memory ownership entirely in the user's hands. We've discussed this for a long time. We've known this is needed for a long time. it's time to prioritize this and get it done. I agree that an anstract class might make more sense, but ultimately we should not be moving any owneship over to the algorithm (the user should maintain ownership over the class and underlying memory the entire time). |
|
The doc that outlines some of the API design choices can be found in slack. Let me know if there are any parts of the design that can be altered to better suit our users' needs. The following files are test case files I've added and can be ignored for now. They will be removed before the final merge with upstream repo:
|
|
/ok to test 70b6b58 |
achirkin
left a comment
There was a problem hiding this comment.
Sorry for being so late for the second round of reviews! I have a few suggestions for the dataset type hierarchy.
…ets: additional dataset and final extended_dataset to be populated. Extend() function no longer internally does H2D copy. Now, extend() extends graph only. Users are expected to concat original dataset + additional dataset themselves into 1 single extended dataset which they then pass in to extend() function along with a variable called new_start_row indicating at which index the additional dataset starts. Extend() now only accepts device_padded_dataset inputs since it internally calls search kernels that fail on any other dataset type. Users must do H2D copy themselves with the concatenated dataset prior to passing it in to extend()
|
/ok to test c8df0d4 |
…-API # Conflicts: # c/tests/neighbors/ann_cagra_c.cu # cpp/include/cuvs/neighbors/cagra.hpp # cpp/src/neighbors/cagra_search_inst.cu.in # cpp/tests/neighbors/ann_cagra.cuh
Restore java serialize behavior
|
/ok to test 5581ffd |
… API with strided_dataset and build functions that don't take dataset_view. Migrated call sites to new Dataset API
|
/ok to test 8140998 |
…ose call sites over to use new Dataset API
|
/ok to test 62a8da6 |
| #pragma once | ||
|
|
||
| #include <cuvs/core/c_api.h> | ||
| #include <cuvs/neighbors/common.h> |
There was a problem hiding this comment.
This should not be included in here. We shouldn't including things across different namespaces / top level directories.
| return "Index(type=CAGRA, metric=L2" + (", ".join(attr_str)) + ")" | ||
|
|
||
|
|
||
| cdef class Dataset: |
There was a problem hiding this comment.
Why is this in CAGRA? This should be a separate file in core/common to mirror the other common vocab types
| self.dataset.dtype.lanes) | ||
|
|
||
|
|
||
| cdef class PaddedDataset(Dataset): |
There was a problem hiding this comment.
Why? This is just an empty class. what's the purpose of having a separate type at all if it's not going to be any different from the other types other than the name? This doesn't make sense to me.
|
|
||
|
|
||
| cdef class PaddedDatasetView: | ||
| def __cinit__(self): |
There was a problem hiding this comment.
Same with these. If we don't care baout the type, don't bother making a separate type. We gain nothing but bloat and confusion by doing this.
|
|
||
|
|
||
| @auto_sync_resources | ||
| def build(IndexParams index_params, dataset, resources=None): |
There was a problem hiding this comment.
In Python we don't care about views vs owning, so please don't make this distinction here... The user shouldn't need to create a "view" to use CAGRA. We should be able to make that behind the scenes if needed.
cjnolet
left a comment
There was a problem hiding this comment.
Had a bunch of pending review items.
| typedef cuvsDatasetView* cuvsDatasetView_t; | ||
|
|
||
| /** | ||
| * @brief Create an empty owning dataset handle. |
There was a problem hiding this comment.
We can improve the docs here slightly to also mention that the underlying data is not allocated, only the handle.
|
/ok to test 378ac4e |
|
/ok to test 02963e1 |
| * @param[out] layout resolved dataset layout | ||
| * @return cuvsError_t | ||
| */ | ||
| CUVS_EXPORT cuvsError_t cuvsCagraGetDatasetMemTypeAndLayout(DLManagedTensor* dataset, |
There was a problem hiding this comment.
@tarang-jain suggested we temove this and I agree. We don't need to know what type the underlying cagra index holds. Thast should be handled. We should howver, have accessors for the dataset mem type and layout, but that should be in dataset.h.
| * @param[in,out] index cuvsCagraIndex_t CAGRA index | ||
| * @return cuvsError_t | ||
| */ | ||
| CUVS_EXPORT cuvsError_t cuvsCagraExtend(cuvsResources_t res, |
There was a problem hiding this comment.
Need to make sure we have tests for this- especially tests that use the extended_dataset. @HowardHuang1 to make sure we have C tests for this.
| * @param[in] filename the file name for saving the graph | ||
| * @param[in] index CAGRA index | ||
| */ | ||
| CUVS_EXPORT cuvsError_t cuvsCagraSerializeGraph(cuvsResources_t res, |
There was a problem hiding this comment.
Need to mke sure we have tests.
|
/ok to test 09b3334 |
| ASSERT_EQ(cuvsCagraBuild(res, build_params, padded_dataset_owner, owner_built_index), CUVS_SUCCESS); | ||
| ASSERT_EQ(cuvsCagraIndexDestroy(owner_built_index), CUVS_SUCCESS); | ||
| ASSERT_EQ(cuvsCagraUpdateDataset(res, padded_dataset_owner, index), CUVS_SUCCESS); | ||
| ASSERT_EQ(cuvsCagraUpdateDataset(res, padded_dataset_owner, index), CUVS_SUCCESS); |
There was a problem hiding this comment.
What is calling this twice demonstrating? Is that intentional? If so, we should add a comment here as to why, and I'd consider pulling this out into a separate test so that it's immediatley obvious to future eyes why this is called twice.
The reason I flag this specifically is because it's bound to get removed at some point when future eyes come into this file and assume it's a bug in the test.
| host_tensor.dl_tensor.shape = dataset_shape; | ||
|
|
||
| cuvsDataset_t host_view = nullptr; | ||
| ASSERT_EQ(cuvsDatasetMakePaddedView(res, &host_tensor, &host_view), CUVS_SUCCESS); |
There was a problem hiding this comment.
We assert success everywhere but we don't seem to assert any failure cases. We should always test what we want to work.... that means testing for the failures too.
|
|
||
| auto serialized_path = | ||
| "/tmp/cuvs-cagra-host-padded-" + std::to_string(getpid()) + ".bin"; | ||
| ASSERT_EQ(cuvsCagraSerializeGraphAndDataset(res, serialized_path.c_str(), index), CUVS_SUCCESS); |
There was a problem hiding this comment.
This test is testing too much. This is supposed to be testing update. We should have separate tests for serialization and deserialization. The reason for having separate test cases is so that when they fail, it's immediately obvious why "Oh the update broke" vs "oh the update test broke but it seems to be because something happened in the serialization part of the test". The former is immediately obvious, the latter requires a much closer look
There was a problem hiding this comment.
Just making sure I'm clear here- it's okay to have tests that call multiple functions, but that shouldn't be at the expense of having a smaller more targeted test in addition to the larger more integration-centric tests.
| cuvsCagraIndex_t loaded_index; | ||
| ASSERT_EQ(cuvsCagraIndexCreate(&loaded_index), CUVS_SUCCESS); | ||
| cuvsDataset_t loaded_dataset = nullptr; | ||
| ASSERT_EQ(cuvsCagraDeserializeGraphAndDataset( |
There was a problem hiding this comment.
Is the purpose of adding this to the test so that we can create a host padded dataset? Is that the only way to construct this type of dataset? That seems to indicate a bad smell in the usage of the APIs. I think we should be able to specify "I want to create a host padded dataset" if needed.
Overview
Addressing #1574 and #1571.
Replaced strided_dataset with padded_dataset class. Added support all the way up to CAGRA code.
Old class structure (Classes + Inheritance):
New Class Structure (ContainerType Tags + Composition):
Inheritance is removed entirely and all dataset types are on the same level of the inheritance tree.
3 Levels:
Ownership
The index and cagra::build / cagra::index do not own raw vector storage, they only take views.
The old code had a type-erased std::unique_ptr<dataset_view<...>>, i.e. non-owning view handles. The new code uses templates on the index type which determines the type of dataset_view the index holds.
ACE v.s. non-ACE paths on Host
ACE path copies datasets that can't entirely fit in CPU memory in chunks onto GPU memory by calling make_padded_dataset. This is 1x memory on CPU and 1x memory on GPU.
Return types:
Used mainly to maintain lifetime of dataset.
cuvs_cagra_c_api_lifetime_holder
It is a single C++ struct in cagra.cpp that groups the real cagra::index with any extra heap-owned things the C API had to create so the index’s non-owning views stay valid.
Miscellaneous: Extend Serialize Deserialize
Will fill in later
Factories:
Places where make_padded_dataset/view are called internally (not by user):
Host non-ACE path
Tiered CAGRA
Ownership in Downstream Functions:
Improvements:
Breaking Changes for Dataset API:
The following functions are removed since index no longer owns the dataset, index only takes views:
Removed old functions that took mdspan or derivatives of mdspan.
4 cases where index previously owned dataset [all deprecated paths]:
2 edge case build() paths when attach_dataset_on_build == true and a successful dense attach:
Compression Param:
Merge:
These paths have since been removed.
Attach Dataset
Compressed Dataset
Merged Dataset
Deserialize
Helpers
How to attach a compressed dataset onto an uncompressed index?
How to attach a searchable device dataset onto an index built with host build?
a. Utilizes map of host dataset type to device dataset type counterpart
TODOs:
Recent Updates:
Future PRs:
PR#2: Add Support for Compressed Datasets
PR#3: Migrate Rest of Algorithms to use Dataset API