-
Notifications
You must be signed in to change notification settings - Fork 345
Transpose #1834
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
Transpose #1834
Changes from all commits
150505d
d58dfbf
80a5d8d
1210608
7557fa1
497ee32
281c2ce
6c97224
6efdffa
ffc944c
152c82c
f177ca4
6cbcdd4
6d370ec
e4bf27d
8097b21
4600bd1
f9a0f57
8a6b5fb
015da7f
74eee0a
92d2b91
5b7d745
ccdfbbb
9a9e5b4
bbb137c
0c257ab
3e4516c
fd4da17
401b98e
6783edb
6d292c4
de3f50c
53f8be9
1c2bc87
72f46d3
e6f9a21
0308c13
35296fc
7f8fa5c
21943c2
f369c06
5a79e94
89c4fc9
fbdcb9f
0e906aa
becac46
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 |
|---|---|---|
|
|
@@ -97,11 +97,56 @@ class graph_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu, std::enab | |
| graph_meta_t<vertex_t, edge_t, multi_gpu> meta, | ||
| bool do_expensive_check = false); | ||
|
|
||
| // return a new renumber_map | ||
| /** | ||
| * @brief Symmetrize this graph. | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Renumber map to recover the original vertex IDs from the renumbered vertex | ||
| * IDs. | ||
| * @param reciprocal If true, an edge is kept only when the reversed edge also exists. If false, | ||
| * keep (and symmetrize) all the edges that appear only in one direction. | ||
| * @return rmm::device_uvector<vertex_t> Return a new renumber map (to recover the original vertex | ||
| * IDs). | ||
| */ | ||
| rmm::device_uvector<vertex_t> symmetrize(raft::handle_t const& handle, | ||
| rmm::device_uvector<vertex_t>&& renumber_map, | ||
| bool reciprocal = false); | ||
|
|
||
| /** | ||
| * @brief Transpose this graph. | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Renumber map to recover the original vertex IDs from the renumbered vertex | ||
| * IDs. | ||
| * @return rmm::device_uvector<vertex_t> Return a new renumber map (to recover the original vertex | ||
| * IDs). | ||
| */ | ||
| rmm::device_uvector<vertex_t> transpose(raft::handle_t const& handle, | ||
| rmm::device_uvector<vertex_t>&& renumber_map); | ||
|
|
||
| /** | ||
| * @brief Transpose the storage format (no change in actual graph). | ||
| * | ||
| * In SG, convert between CSR and CSC. In multi-GPU, currently convert between CSR + DCSR hybrid | ||
| * and CSC + DCSC hybrid (but the internal representation in multi-GPU is subject to change). | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Renumber map to recover the original vertex IDs from the renumbered vertex | ||
| * IDs. | ||
| * @param destroy If true, destroy this graph to free-up memory. | ||
| * @return std::tuple<graph_t<vertex_t, edge_t, weight_t, !store_transposed, multi_gpu>, | ||
| * rmm::device_uvector<vertex_t>> Return a storage transposed graph and a new renumber map (to | ||
| * recover the original vertex IDs for the returned graph). | ||
| */ | ||
| std::tuple<graph_t<vertex_t, edge_t, weight_t, !store_transposed, multi_gpu>, | ||
| rmm::device_uvector<vertex_t>> | ||
| transpose_storage(raft::handle_t const& handle, | ||
|
Collaborator
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. So this variation returns a new graph and a new renumbering map. Same question about multi-column/string renumbering. Also, it's a shame that this function can't be const. Would it be better to have two separate functions, one that is const (where destroy is forced to false) and one that is not const (where destroy is forced to be true)? I suppose it depends upon how we plan on using this.
Contributor
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. Yeah... I can create a const one if necessary; not sure it will be actually necessary. If store_transposed is not a template parameter, this can be a void function changing internal representation, so non-const. If one wants to keep both representations, yeah... having a const might be necessary (but this requires 2x memory so undesirable for large scale analysis).
Collaborator
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. I'm inclined to leave this as is unless/until we discover a need for a const version. I was just initially surprised that it was not... and then realized the |
||
| rmm::device_uvector<vertex_t>&& renumber_map, | ||
| bool destroy = false); | ||
|
|
||
| bool is_weighted() const { return adj_matrix_partition_weights_.has_value(); } | ||
|
|
||
| graph_view_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu> view() const | ||
|
|
@@ -169,7 +214,7 @@ class graph_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu, std::enab | |
| std::optional<rmm::device_uvector<weight_t>>> | ||
| decompress_to_edgelist(raft::handle_t const& handle, | ||
| std::optional<rmm::device_uvector<vertex_t>> const& renumber_map, | ||
| bool destroy); | ||
| bool destroy = false); | ||
|
|
||
| private: | ||
| std::vector<rmm::device_uvector<edge_t>> adj_matrix_partition_offsets_{}; | ||
|
|
@@ -220,12 +265,61 @@ class graph_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu, std::enab | |
| graph_meta_t<vertex_t, edge_t, multi_gpu> meta, | ||
| bool do_expensive_check = false); | ||
|
|
||
| // return a new renumber_map if @p renumber_map is valid | ||
| /** | ||
| * @brief Symmetrize this graph. | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Optional renumber map to recover the original vertex IDs from the | ||
| * renumbered vertex IDs. If @p renuber_map.has_value() is false, this function assumes that | ||
| * vertex IDs are not renumbered. | ||
| * @param reciprocal If true, an edge is kept only when the reversed edge also exists. If false, | ||
| * keep (and symmetrize) all the edges that appear only in one direction. | ||
| * @return rmm::device_uvector<vertex_t> Return a new renumber map (to recover the original vertex | ||
| * IDs) if @p renumber_map.has_value() is true. | ||
| */ | ||
| std::optional<rmm::device_uvector<vertex_t>> symmetrize( | ||
| raft::handle_t const& handle, | ||
| std::optional<rmm::device_uvector<vertex_t>>&& renumber_map, | ||
| bool reciprocal = false); | ||
|
|
||
| /** | ||
| * @brief Transpose this graph. | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Optional renumber map to recover the original vertex IDs from the | ||
| * renumbered vertex IDs. If @p renuber_map.has_value() is false, this function assumes that | ||
| * vertex IDs are not renumbered. | ||
| * @return rmm::device_uvector<vertex_t> Return a new renumber map (to recover the original vertex | ||
| * IDs) if @p renumber_map.has_value() is true. | ||
| */ | ||
| std::optional<rmm::device_uvector<vertex_t>> transpose( | ||
| raft::handle_t const& handle, std::optional<rmm::device_uvector<vertex_t>>&& renumber_map); | ||
|
|
||
| /** | ||
| * @brief Transpose the storage format (no change in actual graph). | ||
| * | ||
| * In SG, convert between CSR and CSC. In multi-GPU, currently convert between CSR + DCSR hybrid | ||
| * and CSC + DCSC hybrid (but the internal representation in multi-GPU is subject to change). | ||
| * | ||
| * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and | ||
| * handles to various CUDA libraries) to run graph algorithms. | ||
| * @param renumber_map Optional renumber map to recover the original vertex IDs from the | ||
| * renumbered vertex IDs. If @p renuber_map.has_value() is false, this function assumes that | ||
| * vertex IDs are not renumbered. | ||
| * @param destroy If true, destroy this graph to free-up memory. | ||
| * @return std::tuple<graph_t<vertex_t, edge_t, weight_t, !store_transposed, multi_gpu>, | ||
| * rmm::device_uvector<vertex_t>> Return a storage transposed graph and a optional new renumber | ||
| * map (to recover the original vertex IDs for the returned graph) The returned optional new | ||
| * renumber map is valid only if @p renumber_map.has_value() is true. | ||
| */ | ||
| std::tuple<graph_t<vertex_t, edge_t, weight_t, !store_transposed, multi_gpu>, | ||
| std::optional<rmm::device_uvector<vertex_t>>> | ||
| transpose_storage(raft::handle_t const& handle, | ||
| std::optional<rmm::device_uvector<vertex_t>>&& renumber_map, | ||
| bool destroy = false); | ||
|
|
||
| bool is_weighted() const { return weights_.has_value(); } | ||
|
|
||
| graph_view_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu> view() const | ||
|
|
@@ -270,7 +364,7 @@ class graph_t<vertex_t, edge_t, weight_t, store_transposed, multi_gpu, std::enab | |
| std::optional<rmm::device_uvector<weight_t>>> | ||
| decompress_to_edgelist(raft::handle_t const& handle, | ||
| std::optional<rmm::device_uvector<vertex_t>> const& renumber_map, | ||
| bool destroy); | ||
| bool destroy = false); | ||
|
|
||
| private: | ||
| friend class cugraph::serializer::serializer_t; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question... this will work for callers that use C++ renumbering.
If we do python renumbering (for multi-column or strings), how would this function work? Would we have to provide a fake renumbering map (where
i = i), and then use those values to look up values when we get back to python?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In SG, renumbering is optional, so you can provide std::nullopt.
In MG, renumbering is mandatory, so this question is relevant.
So, this depends on how the python renumbering is implemented. So, as you know, the C++ renumbering routine renumbers in a specific way to exploit this in optimizations as well. If the python renumbering satisfies all these requirements, we can create an identity mapping renumbering map (as you suggested) or look for a better option.
But if asking the python renumbering to satisfy all the detailed renumbering requirements is not very desirable, we may consider the renumbering process as the following.
Python renumbering
Multi-column AND|OR string IDs => integer IDs
C++ renumbering
Integer IDs to Integer IDs (mainly for optimization and can be mostly transparent to python users)
Then, really not that much changes with/or without python renumbering. This may add some additional overhead, but I guess String/multi-column renumbering is much more expensive than integer-to-integer renumbering and this leads to cleaner software architecture.
We run python renumbering, and then, run analytics as we have integer vertex ID inputs, and unrenumber to strings AND|OR multi-columns only when we need to return results to users.
What do you think and any concerns?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this really depends on how python renumbering is implemented, but if python renumbering just maps multi-column/string IDs to integers, it just needs to find unique IDs, and create a mapping between original IDs to integer IDs.
If python renumbering also sorts vertex IDs within a partition by their degrees (as C++ layer uses this for various optimizations), it needs to compute the degrees for each unique vertex IDs, and I believe this can be done much more efficiently after converting to integer IDs than directly working on multi-column/string IDs.
So, the performance loss may not be really that significant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I was mainly asking for clarification purposes (making sure we all understand the implications - especially @rlratzel whose team will have to use this feature).
I don't have any concerns over what you described. I believe the current python renumbering satisfies all the detailed renumbering requirements today. I suspect that the new string/multi-column renumbering can also satisfy those requirements. We can, while it is being developed, explore whether a two-phase renumbering would be more efficient or result in lower maintain cost at a minimal performance loss. For now we can just use the identity mapping to keep track of the numbering changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for covering this, I don't have any questions beyond what @ChuckHastings already asked.