From 4d2dd27393ddb7223fc9375e0496c0df981c3954 Mon Sep 17 00:00:00 2001 From: Chuck Hastings <45364586+ChuckHastings@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:12:48 -0400 Subject: [PATCH 01/37] Biased Random Walks and Node2Vec implementation (#4645) Replaces #4499 Closes #4499 @G-Cornett implemented biased random walks and node2vec during his internship. This PR includes his changes to implement those algorithms. Authors: - Chuck Hastings (https://github.com/ChuckHastings) - Garrett Cornett (https://github.com/G-Cornett) Approvers: - Joseph Nke (https://github.com/jnke2016) - Naim (https://github.com/naimnv) - Seunghwa Kang (https://github.com/seunghwak) URL: https://github.com/rapidsai/cugraph/pull/4645 --- cpp/include/cugraph/algorithms.hpp | 12 +- cpp/src/c_api/random_walks.cpp | 34 +- .../sample_and_compute_local_nbr_indices.cuh | 11 +- ...r_v_random_select_transform_outgoing_e.cuh | 6 +- cpp/src/sampling/random_walks_impl.cuh | 897 ++++++++++++++---- cpp/src/sampling/random_walks_mg_v32_e32.cu | 24 +- cpp/src/sampling/random_walks_mg_v32_e64.cu | 24 +- cpp/src/sampling/random_walks_mg_v64_e64.cu | 24 +- cpp/src/sampling/random_walks_sg_v32_e32.cu | 24 +- cpp/src/sampling/random_walks_sg_v32_e64.cu | 24 +- cpp/src/sampling/random_walks_sg_v64_e64.cu | 24 +- cpp/tests/c_api/sg_random_walks_test.c | 89 +- cpp/tests/sampling/mg_random_walks_test.cpp | 20 +- cpp/tests/sampling/random_walks_check.cuh | 2 +- cpp/tests/sampling/sg_random_walks_test.cpp | 64 +- 15 files changed, 899 insertions(+), 380 deletions(-) diff --git a/cpp/include/cugraph/algorithms.hpp b/cpp/include/cugraph/algorithms.hpp index ed42460ed8e..7e5af4ac686 100644 --- a/cpp/include/cugraph/algorithms.hpp +++ b/cpp/include/cugraph/algorithms.hpp @@ -1579,11 +1579,11 @@ std:: template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed = std::numeric_limits::max()); + size_t max_length); /** * @brief returns biased random walks from starting sources, where each path is of given @@ -1623,11 +1623,11 @@ uniform_random_walks(raft::handle_t const& handle, template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed = std::numeric_limits::max()); + size_t max_length); /** * @brief returns biased random walks with node2vec biases from starting sources, @@ -1670,13 +1670,13 @@ biased_random_walks(raft::handle_t const& handle, template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, weight_t p, - weight_t q, - uint64_t seed = std::numeric_limits::max()); + weight_t q); #ifndef NO_CUGRAPH_OPS /** diff --git a/cpp/src/c_api/random_walks.cpp b/cpp/src/c_api/random_walks.cpp index b9a2c8e4f60..705d2108437 100644 --- a/cpp/src/c_api/random_walks.cpp +++ b/cpp/src/c_api/random_walks.cpp @@ -16,6 +16,7 @@ #include "c_api/abstract_functor.hpp" #include "c_api/graph.hpp" +#include "c_api/random.hpp" #include "c_api/resource_handle.hpp" #include "c_api/utils.hpp" @@ -153,10 +154,11 @@ namespace { struct uniform_random_walks_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; + // FIXME: rng_state_ should be passed as a parameter + cugraph::c_api::cugraph_rng_state_t* rng_state_{nullptr}; cugraph::c_api::cugraph_graph_t* graph_{nullptr}; cugraph::c_api::cugraph_type_erased_device_array_view_t const* start_vertices_{nullptr}; size_t max_length_{0}; - size_t seed_{0}; cugraph::c_api::cugraph_random_walk_result_t* result_{nullptr}; uniform_random_walks_functor(cugraph_resource_handle_t const* handle, @@ -222,13 +224,17 @@ struct uniform_random_walks_functor : public cugraph::c_api::abstract_functor { graph_view.local_vertex_partition_range_last(), false); + // FIXME: remove once rng_state passed as parameter + rng_state_ = reinterpret_cast( + new cugraph::c_api::cugraph_rng_state_t{raft::random::RngState{0}}); + auto [paths, weights] = cugraph::uniform_random_walks( handle_, + rng_state_->rng_state_, graph_view, (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt, raft::device_span{start_vertices.data(), start_vertices.size()}, - max_length_, - seed_); + max_length_); // // Need to unrenumber the vertices in the resulting paths @@ -255,11 +261,12 @@ struct uniform_random_walks_functor : public cugraph::c_api::abstract_functor { struct biased_random_walks_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; + // FIXME: rng_state_ should be passed as a parameter + cugraph::c_api::cugraph_rng_state_t* rng_state_{nullptr}; cugraph::c_api::cugraph_graph_t* graph_{nullptr}; cugraph::c_api::cugraph_type_erased_device_array_view_t const* start_vertices_{nullptr}; size_t max_length_{0}; cugraph::c_api::cugraph_random_walk_result_t* result_{nullptr}; - uint64_t seed_{0}; biased_random_walks_functor(cugraph_resource_handle_t const* handle, cugraph_graph_t* graph, @@ -326,13 +333,17 @@ struct biased_random_walks_functor : public cugraph::c_api::abstract_functor { graph_view.local_vertex_partition_range_last(), false); + // FIXME: remove once rng_state passed as parameter + rng_state_ = reinterpret_cast( + new cugraph::c_api::cugraph_rng_state_t{raft::random::RngState{0}}); + auto [paths, weights] = cugraph::biased_random_walks( handle_, + rng_state_->rng_state_, graph_view, edge_weights->view(), raft::device_span{start_vertices.data(), start_vertices.size()}, - max_length_, - seed_); + max_length_); // // Need to unrenumber the vertices in the resulting paths @@ -354,12 +365,13 @@ struct biased_random_walks_functor : public cugraph::c_api::abstract_functor { struct node2vec_random_walks_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; + // FIXME: rng_state_ should be passed as a parameter + cugraph::c_api::cugraph_rng_state_t* rng_state_{nullptr}; cugraph::c_api::cugraph_graph_t* graph_{nullptr}; cugraph::c_api::cugraph_type_erased_device_array_view_t const* start_vertices_{nullptr}; size_t max_length_{0}; double p_{0}; double q_{0}; - uint64_t seed_{0}; cugraph::c_api::cugraph_random_walk_result_t* result_{nullptr}; node2vec_random_walks_functor(cugraph_resource_handle_t const* handle, @@ -431,15 +443,19 @@ struct node2vec_random_walks_functor : public cugraph::c_api::abstract_functor { graph_view.local_vertex_partition_range_last(), false); + // FIXME: remove once rng_state passed as parameter + rng_state_ = reinterpret_cast( + new cugraph::c_api::cugraph_rng_state_t{raft::random::RngState{0}}); + auto [paths, weights] = cugraph::node2vec_random_walks( handle_, + rng_state_->rng_state_, graph_view, (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt, raft::device_span{start_vertices.data(), start_vertices.size()}, max_length_, static_cast(p_), - static_cast(q_), - seed_); + static_cast(q_)); // FIXME: Need to fix invalid_vtx issue here. We can't unrenumber max_vertex_id+1 // properly... diff --git a/cpp/src/prims/detail/sample_and_compute_local_nbr_indices.cuh b/cpp/src/prims/detail/sample_and_compute_local_nbr_indices.cuh index 7d4750c0554..a6a164d36c1 100644 --- a/cpp/src/prims/detail/sample_and_compute_local_nbr_indices.cuh +++ b/cpp/src/prims/detail/sample_and_compute_local_nbr_indices.cuh @@ -392,11 +392,11 @@ compute_unique_keys(raft::handle_t const& handle, cuda::proclaim_return_type( [unique_key_first = get_dataframe_buffer_begin(aggregate_local_frontier_unique_keys) + local_frontier_unique_key_displacements[i], - num_unique_keys = local_frontier_unique_key_sizes[i]] __device__(key_t key) { + unique_key_last = get_dataframe_buffer_begin(aggregate_local_frontier_unique_keys) + + local_frontier_unique_key_displacements[i] + + local_frontier_unique_key_sizes[i]] __device__(key_t key) { return static_cast(thrust::distance( - unique_key_first, - thrust::lower_bound( - thrust::seq, unique_key_first, unique_key_first + num_unique_keys, key))); + unique_key_first, thrust::find(thrust::seq, unique_key_first, unique_key_last, key))); })); } @@ -1759,8 +1759,7 @@ biased_sample_and_compute_local_nbr_indices( std::optional> key_indices{std::nullopt}; std::vector local_frontier_sample_offsets{}; if (with_replacement) { - // computet segmented inclusive sums (one segment per seed) - + // compute segmented inclusive sums (one segment per seed) auto unique_key_first = thrust::make_transform_iterator( thrust::make_counting_iterator(size_t{0}), cuda::proclaim_return_type( diff --git a/cpp/src/prims/per_v_random_select_transform_outgoing_e.cuh b/cpp/src/prims/per_v_random_select_transform_outgoing_e.cuh index 03514e52e6e..9d0f711d106 100644 --- a/cpp/src/prims/per_v_random_select_transform_outgoing_e.cuh +++ b/cpp/src/prims/per_v_random_select_transform_outgoing_e.cuh @@ -351,7 +351,7 @@ per_v_random_select_transform_e(raft::handle_t const& handle, uniform_sample_and_compute_local_nbr_indices( handle, graph_view, - (minor_comm_size > 1) ? get_dataframe_buffer_begin(*aggregate_local_frontier) + (minor_comm_size > 1) ? get_dataframe_buffer_cbegin(*aggregate_local_frontier) : frontier.begin(), local_frontier_displacements, local_frontier_sizes, @@ -363,7 +363,7 @@ per_v_random_select_transform_e(raft::handle_t const& handle, biased_sample_and_compute_local_nbr_indices( handle, graph_view, - (minor_comm_size > 1) ? get_dataframe_buffer_begin(*aggregate_local_frontier) + (minor_comm_size > 1) ? get_dataframe_buffer_cbegin(*aggregate_local_frontier) : frontier.begin(), edge_bias_src_value_input, edge_bias_dst_value_input, @@ -392,7 +392,7 @@ per_v_random_select_transform_e(raft::handle_t const& handle, graph_view.local_edge_partition_view(i)); auto edge_partition_frontier_key_first = - ((minor_comm_size > 1) ? get_dataframe_buffer_begin(*aggregate_local_frontier) + ((minor_comm_size > 1) ? get_dataframe_buffer_cbegin(*aggregate_local_frontier) : frontier.begin()) + local_frontier_displacements[i]; auto edge_partition_sample_local_nbr_index_first = diff --git a/cpp/src/sampling/random_walks_impl.cuh b/cpp/src/sampling/random_walks_impl.cuh index d582893d756..6c10fc473f3 100644 --- a/cpp/src/sampling/random_walks_impl.cuh +++ b/cpp/src/sampling/random_walks_impl.cuh @@ -17,7 +17,10 @@ #pragma once #include "detail/graph_partition_utils.cuh" +#include "prims/detail/nbr_intersection.cuh" #include "prims/per_v_random_select_transform_outgoing_e.cuh" +#include "prims/property_op_utils.cuh" +#include "prims/update_edge_src_dst_property.cuh" #include "prims/vertex_frontier.cuh" #include @@ -25,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -46,13 +50,6 @@ namespace cugraph { namespace detail { -inline uint64_t get_current_time_nanoseconds() -{ - auto cur = std::chrono::steady_clock::now(); - return static_cast( - std::chrono::duration_cast(cur.time_since_epoch()).count()); -} - template struct sample_edges_op_t { template @@ -70,21 +67,129 @@ struct sample_edges_op_t { } }; +template +struct biased_random_walk_e_bias_op_t { + __device__ bias_t + operator()(vertex_t, vertex_t, bias_t src_out_weight_sum, thrust::nullopt_t, bias_t weight) const + { + return weight / src_out_weight_sum; + } +}; + +template +struct biased_sample_edges_op_t { + __device__ thrust::tuple operator()( + vertex_t, vertex_t dst, weight_t, thrust::nullopt_t, weight_t weight) const + { + return thrust::make_tuple(dst, weight); + } +}; + +template +struct node2vec_random_walk_e_bias_op_t { + bias_t p_{}; + bias_t q_{}; + raft::device_span intersection_offsets_{}; + raft::device_span intersection_indices_{}; + raft::device_span current_vertices_{}; + raft::device_span prev_vertices_{}; + + // Unweighted Bias Operator + template + __device__ std::enable_if_t, bias_t> operator()( + thrust::tuple tagged_src, + vertex_t dst, + thrust::nullopt_t, + thrust::nullopt_t, + thrust::nullopt_t) const + { + // Check tag (prev vert) for destination + if (dst == thrust::get<1>(tagged_src)) { return 1.0 / p_; } + // Search zipped vertices for tagged src + auto lower_itr = thrust::lower_bound( + thrust::seq, + thrust::make_zip_iterator(current_vertices_.begin(), prev_vertices_.begin()), + thrust::make_zip_iterator(current_vertices_.end(), prev_vertices_.end()), + tagged_src); + auto low_idx = thrust::distance( + thrust::make_zip_iterator(current_vertices_.begin(), prev_vertices_.begin()), lower_itr); + auto intersection_index_first = intersection_indices_.begin() + intersection_offsets_[low_idx]; + auto intersection_index_last = + intersection_indices_.begin() + intersection_offsets_[low_idx + 1]; + auto itr = + thrust::lower_bound(thrust::seq, intersection_index_first, intersection_index_last, dst); + return (itr != intersection_index_last && *itr == dst) ? 1.0 : 1.0 / q_; + } + + // Weighted Bias Operator + template + __device__ std::enable_if_t, bias_t> operator()( + thrust::tuple tagged_src, + vertex_t dst, + thrust::nullopt_t, + thrust::nullopt_t, + W) const + { + // Check tag (prev vert) for destination + if (dst == thrust::get<1>(tagged_src)) { return 1.0 / p_; } + // Search zipped vertices for tagged src + auto lower_itr = thrust::lower_bound( + thrust::seq, + thrust::make_zip_iterator(current_vertices_.begin(), prev_vertices_.begin()), + thrust::make_zip_iterator(current_vertices_.end(), prev_vertices_.end()), + tagged_src); + auto low_idx = thrust::distance( + thrust::make_zip_iterator(current_vertices_.begin(), prev_vertices_.begin()), lower_itr); + auto intersection_index_first = intersection_indices_.begin() + intersection_offsets_[low_idx]; + auto intersection_index_last = + intersection_indices_.begin() + intersection_offsets_[low_idx + 1]; + auto itr = + thrust::lower_bound(thrust::seq, intersection_index_first, intersection_index_last, dst); + return (itr != intersection_index_last && *itr == dst) ? 1.0 : 1.0 / q_; + } +}; + +template +struct node2vec_sample_edges_op_t { + template + __device__ std::enable_if_t, vertex_t> operator()( + thrust::tuple tagged_src, + vertex_t dst, + thrust::nullopt_t, + thrust::nullopt_t, + thrust::nullopt_t) const + { + return dst; + } + + template + __device__ std::enable_if_t, thrust::tuple> operator()( + thrust::tuple tagged_src, + vertex_t dst, + thrust::nullopt_t, + thrust::nullopt_t, + W w) const + { + return thrust::make_tuple(dst, w); + } +}; + template struct uniform_selector { - raft::random::RngState rng_state_; - - uniform_selector(uint64_t seed) : rng_state_(seed) {} + raft::random::RngState& rng_state_; + static constexpr bool is_second_order_ = false; template std::tuple, + std::optional>, std::optional>> follow_random_edge( raft::handle_t const& handle, GraphViewType const& graph_view, std::optional> edge_weight_view, - rmm::device_uvector const& current_vertices) + rmm::device_uvector&& current_vertices, + std::optional>&& previous_vertices) { using vertex_t = typename GraphViewType::vertex_type; @@ -133,30 +238,67 @@ struct uniform_selector { minors = std::move(sample_e_op_results); } - return std::make_tuple(std::move(minors), std::move(weights)); + return std::make_tuple(std::move(minors), std::move(previous_vertices), std::move(weights)); } }; template struct biased_selector { - uint64_t seed_{0}; + raft::random::RngState& rng_state_; + static constexpr bool is_second_order_ = false; template std::tuple, + std::optional>, std::optional>> follow_random_edge( raft::handle_t const& handle, GraphViewType const& graph_view, std::optional> edge_weight_view, - rmm::device_uvector const& current_vertices) + rmm::device_uvector&& current_vertices, + std::optional>&& previous_vertices) { - // To do biased sampling, I need out_weights instead of out_degrees. - // Then I generate a random float between [0, out_weights[v]). Then - // instead of making a decision based on the index I need to find - // upper_bound (or is it lower_bound) of the random number and - // the cumulative weight. - CUGRAPH_FAIL("biased sampling not implemented"); + // Create vertex frontier + using vertex_t = typename GraphViewType::vertex_type; + + using tag_t = void; + + cugraph::vertex_frontier_t vertex_frontier( + handle, 1); + + vertex_frontier.bucket(0).insert(current_vertices.begin(), current_vertices.end()); + + auto vertex_weight_sum = compute_out_weight_sums(handle, graph_view, *edge_weight_view); + edge_src_property_t edge_src_out_weight_sums(handle, graph_view); + update_edge_src_property(handle, + graph_view, + vertex_frontier.bucket(0).begin(), + vertex_frontier.bucket(0).end(), + vertex_weight_sum.data(), + edge_src_out_weight_sums.mutable_view()); + auto [sample_offsets, sample_e_op_results] = cugraph::per_v_random_select_transform_outgoing_e( + handle, + graph_view, + vertex_frontier.bucket(0), + edge_src_out_weight_sums.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + *edge_weight_view, + biased_random_walk_e_bias_op_t{}, + edge_src_out_weight_sums.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + *edge_weight_view, + biased_sample_edges_op_t{}, + rng_state_, + size_t{1}, + true, + std::make_optional( + thrust::make_tuple(vertex_t{cugraph::invalid_vertex_id::value}, weight_t{0.0}))); + + // Return results + return std::make_tuple(std::move(std::get<0>(sample_e_op_results)), + std::move(previous_vertices), + std::move(std::get<1>(sample_e_op_results))); } }; @@ -164,26 +306,232 @@ template struct node2vec_selector { weight_t p_; weight_t q_; - uint64_t seed_{0}; + raft::random::RngState& rng_state_; + static constexpr bool is_second_order_ = true; template std::tuple, + std::optional>, std::optional>> follow_random_edge( raft::handle_t const& handle, GraphViewType const& graph_view, std::optional> edge_weight_view, - rmm::device_uvector const& current_vertices) + rmm::device_uvector&& current_vertices, + std::optional>&& previous_vertices) { - // To do node2vec, I need the following: - // 1) transform_reduce_dst_nbr_intersection_of_e_endpoints_by_v to compute the sum of the - // node2vec style weights - // 2) Generate a random number between [0, output_from_trdnioeebv[v]) - // 3) a sampling value that lets me pick the correct edge based on the same computation - // (essentially weighted sampling, but with a function that computes the weight rather - // than just using the edge weights) - CUGRAPH_FAIL("node2vec not implemented"); + // Create vertex frontier + using vertex_t = typename GraphViewType::vertex_type; + + using tag_t = vertex_t; + + // Zip previous and current vertices for nbr_intersection() + auto intersection_pairs = + thrust::make_zip_iterator(current_vertices.begin(), (*previous_vertices).begin()); + + auto [intersection_offsets, intersection_indices] = + detail::nbr_intersection(handle, + graph_view, + cugraph::edge_dummy_property_t{}.view(), + intersection_pairs, + intersection_pairs + current_vertices.size(), + std::array{true, true}, + false); + + rmm::device_uvector intersection_counts(size_t{0}, handle.get_stream()); + rmm::device_uvector aggregate_offsets(size_t{0}, handle.get_stream()); + rmm::device_uvector aggregate_currents(size_t{0}, handle.get_stream()); + rmm::device_uvector aggregate_previous(size_t{0}, handle.get_stream()); + rmm::device_uvector aggregate_indices(size_t{0}, handle.get_stream()); + + // Aggregate intersection data across minor comm + if constexpr (GraphViewType::is_multi_gpu) { + intersection_counts.resize(intersection_offsets.size(), handle.get_stream()); + thrust::adjacent_difference(handle.get_thrust_policy(), + intersection_offsets.begin(), + intersection_offsets.end(), + intersection_counts.begin()); + + auto recv_counts = cugraph::host_scalar_allgather( + handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + current_vertices.size(), + handle.get_stream()); + + std::vector displacements(recv_counts.size()); + std::exclusive_scan(recv_counts.begin(), recv_counts.end(), displacements.begin(), size_t{0}); + + aggregate_offsets.resize(displacements.back() + recv_counts.back() + 1, handle.get_stream()); + aggregate_offsets.set_element_to_zero_async(aggregate_offsets.size() - 1, + handle.get_stream()); + + cugraph::device_allgatherv(handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + intersection_counts.begin() + 1, + aggregate_offsets.begin(), + recv_counts, + displacements, + handle.get_stream()); + + thrust::exclusive_scan(handle.get_thrust_policy(), + aggregate_offsets.begin(), + aggregate_offsets.end(), + aggregate_offsets.begin()); + + aggregate_currents.resize(displacements.back() + recv_counts.back(), handle.get_stream()); + + cugraph::device_allgatherv(handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + current_vertices.begin(), + aggregate_currents.begin(), + recv_counts, + displacements, + handle.get_stream()); + + aggregate_previous.resize(displacements.back() + recv_counts.back(), handle.get_stream()); + + cugraph::device_allgatherv(handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + (*previous_vertices).begin(), + aggregate_previous.begin(), + recv_counts, + displacements, + handle.get_stream()); + + recv_counts = cugraph::host_scalar_allgather( + handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + intersection_offsets.back_element(handle.get_stream()), + handle.get_stream()); + + displacements.resize(recv_counts.size()); + std::exclusive_scan(recv_counts.begin(), recv_counts.end(), displacements.begin(), size_t{0}); + + aggregate_indices.resize(displacements.back() + recv_counts.back(), handle.get_stream()); + + cugraph::device_allgatherv(handle.get_subcomm(cugraph::partition_manager::minor_comm_name()), + intersection_indices.begin(), + aggregate_indices.begin(), + recv_counts, + displacements, + handle.get_stream()); + } + + cugraph::vertex_frontier_t vertex_frontier( + handle, 1); + vertex_frontier.bucket(0).insert( + thrust::make_zip_iterator(current_vertices.begin(), (*previous_vertices).begin()), + thrust::make_zip_iterator(current_vertices.end(), (*previous_vertices).end())); + + // Create data structs for results + rmm::device_uvector minors(0, handle.get_stream()); + std::optional> weights{std::nullopt}; + + if (edge_weight_view) { + auto [sample_offsets, sample_e_op_results] = + cugraph::per_v_random_select_transform_outgoing_e( + handle, + graph_view, + vertex_frontier.bucket(0), + cugraph::edge_src_dummy_property_t{}.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + *edge_weight_view, + GraphViewType::is_multi_gpu + ? node2vec_random_walk_e_bias_op_t{p_, + q_, + raft::device_span( + aggregate_offsets.data(), + aggregate_offsets.size()), + raft::device_span( + aggregate_indices.data(), + aggregate_indices.size()), + raft::device_span( + aggregate_currents.data(), + aggregate_currents.size()), + raft::device_span( + aggregate_previous.data(), + aggregate_previous.size())} + : node2vec_random_walk_e_bias_op_t{p_, + q_, + raft::device_span( + intersection_offsets.data(), + intersection_offsets.size()), + raft::device_span( + intersection_indices.data(), + intersection_indices.size()), + raft::device_span< + vertex_t const>(current_vertices.data(), + current_vertices.size()), + raft::device_span( + (*previous_vertices).data(), + (*previous_vertices).size())}, + cugraph::edge_src_dummy_property_t{}.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + *edge_weight_view, + node2vec_sample_edges_op_t{}, + rng_state_, + size_t{1}, + true, + std::make_optional(thrust::make_tuple( + vertex_t{cugraph::invalid_vertex_id::value}, weight_t{0.0}))); + minors = std::move(std::get<0>(sample_e_op_results)); + weights = std::move(std::get<1>(sample_e_op_results)); + } else { + auto [sample_offsets, sample_e_op_results] = + cugraph::per_v_random_select_transform_outgoing_e( + handle, + graph_view, + vertex_frontier.bucket(0), + cugraph::edge_src_dummy_property_t{}.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + cugraph::edge_dummy_property_t{}.view(), + GraphViewType::is_multi_gpu + ? node2vec_random_walk_e_bias_op_t{p_, + q_, + raft::device_span( + aggregate_offsets.data(), + aggregate_offsets.size()), + raft::device_span( + aggregate_indices.data(), + aggregate_indices.size()), + raft::device_span( + aggregate_currents.data(), + aggregate_currents.size()), + raft::device_span( + aggregate_previous.data(), + aggregate_previous.size())} + : node2vec_random_walk_e_bias_op_t{p_, + q_, + raft::device_span( + intersection_offsets.data(), + intersection_offsets.size()), + raft::device_span( + intersection_indices.data(), + intersection_indices.size()), + raft::device_span< + vertex_t const>(current_vertices.data(), + current_vertices.size()), + raft::device_span( + (*previous_vertices).data(), + (*previous_vertices).size())}, + cugraph::edge_src_dummy_property_t{}.view(), + cugraph::edge_dst_dummy_property_t{}.view(), + cugraph::edge_dummy_property_t{}.view(), + node2vec_sample_edges_op_t{}, + rng_state_, + size_t{1}, + true, + std::make_optional(vertex_t{cugraph::invalid_vertex_id::value})); + minors = std::move(sample_e_op_results); + } + + *previous_vertices = std::move(current_vertices); + + return std::make_tuple(std::move(minors), std::move(previous_vertices), std::move(weights)); } }; @@ -221,6 +569,16 @@ random_walk_impl(raft::handle_t const& handle, ? std::make_optional>(0, handle.get_stream()) : std::nullopt; + auto previous_vertices = (random_selector.is_second_order_) + ? std::make_optional>( + current_vertices.size(), handle.get_stream()) + : std::nullopt; + if (previous_vertices) { + raft::copy((*previous_vertices).data(), + start_vertices.data(), + start_vertices.size(), + handle.get_stream()); + } raft::copy( current_vertices.data(), start_vertices.data(), start_vertices.size(), handle.get_stream()); detail::sequence_fill( @@ -255,25 +613,73 @@ random_walk_impl(raft::handle_t const& handle, auto& minor_comm = handle.get_subcomm(cugraph::partition_manager::minor_comm_name()); auto const minor_comm_size = minor_comm.get_size(); - // Shuffle vertices to correct GPU to compute random indices - std::forward_as_tuple(std::tie(current_vertices, current_gpu, current_position), - std::ignore) = - cugraph::groupby_gpu_id_and_shuffle_values( - handle.get_comms(), + if (previous_vertices) { + std::forward_as_tuple( + std::tie(current_vertices, current_gpu, current_position, previous_vertices), + std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + thrust::make_zip_iterator(current_vertices.begin(), + current_gpu.begin(), + current_position.begin(), + previous_vertices->begin()), + thrust::make_zip_iterator(current_vertices.end(), + current_gpu.end(), + current_position.end(), + previous_vertices->end()), + [key_func = + cugraph::detail::compute_gpu_id_from_int_vertex_t{ + {vertex_partition_range_lasts.begin(), vertex_partition_range_lasts.size()}, + major_comm_size, + minor_comm_size}] __device__(auto val) { return key_func(thrust::get<0>(val)); }, + handle.get_stream()); + } else { + // Shuffle vertices to correct GPU to compute random indices + std::forward_as_tuple(std::tie(current_vertices, current_gpu, current_position), + std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + thrust::make_zip_iterator( + current_vertices.begin(), current_gpu.begin(), current_position.begin()), + thrust::make_zip_iterator( + current_vertices.end(), current_gpu.end(), current_position.end()), + [key_func = + cugraph::detail::compute_gpu_id_from_int_vertex_t{ + {vertex_partition_range_lasts.begin(), vertex_partition_range_lasts.size()}, + major_comm_size, + minor_comm_size}] __device__(auto val) { return key_func(thrust::get<0>(val)); }, + handle.get_stream()); + } + } + + // Sort for nbr_intersection, must sort all together + if (previous_vertices) { + if constexpr (multi_gpu) { + thrust::sort(handle.get_thrust_policy(), + thrust::make_zip_iterator(current_vertices.begin(), + (*previous_vertices).begin(), + current_position.begin(), + current_gpu.begin()), + thrust::make_zip_iterator(current_vertices.end(), + (*previous_vertices).end(), + current_position.end(), + current_gpu.end())); + } else { + thrust::sort( + handle.get_thrust_policy(), thrust::make_zip_iterator( - current_vertices.begin(), current_gpu.begin(), current_position.begin()), + current_vertices.begin(), (*previous_vertices).begin(), current_position.begin()), thrust::make_zip_iterator( - current_vertices.end(), current_gpu.end(), current_position.end()), - [key_func = - cugraph::detail::compute_gpu_id_from_int_vertex_t{ - {vertex_partition_range_lasts.begin(), vertex_partition_range_lasts.size()}, - major_comm_size, - minor_comm_size}] __device__(auto val) { return key_func(thrust::get<0>(val)); }, - handle.get_stream()); + current_vertices.end(), (*previous_vertices).end(), current_position.end())); + } } - std::tie(current_vertices, new_weights) = - random_selector.follow_random_edge(handle, graph_view, edge_weight_view, current_vertices); + std::tie(current_vertices, previous_vertices, new_weights) = + random_selector.follow_random_edge(handle, + graph_view, + edge_weight_view, + std::move(current_vertices), + std::move(previous_vertices)); // FIXME: remove_if has a 32-bit overflow issue // (https://github.com/NVIDIA/thrust/issues/1302) Seems unlikely here (the goal of @@ -281,164 +687,244 @@ random_walk_impl(raft::handle_t const& handle, CUGRAPH_EXPECTS( current_vertices.size() < static_cast(std::numeric_limits::max()), "remove_if will fail, current_vertices.size() is too large"); - + size_t compacted_length{0}; if constexpr (multi_gpu) { if (result_weights) { - auto input_iter = thrust::make_zip_iterator(current_vertices.begin(), - new_weights->begin(), - current_gpu.begin(), - current_position.begin()); - - auto compacted_length = thrust::distance( - input_iter, - thrust::remove_if(handle.get_thrust_policy(), - input_iter, - input_iter + current_vertices.size(), - current_vertices.begin(), - [] __device__(auto dst) { - return (dst == cugraph::invalid_vertex_id::value); - })); - - current_vertices.resize(compacted_length, handle.get_stream()); - new_weights->resize(compacted_length, handle.get_stream()); - current_gpu.resize(compacted_length, handle.get_stream()); - current_position.resize(compacted_length, handle.get_stream()); - - // Shuffle back to original GPU - auto current_iter = thrust::make_zip_iterator(current_vertices.begin(), + if (previous_vertices) { + auto input_iter = thrust::make_zip_iterator(current_vertices.begin(), + new_weights->begin(), + current_gpu.begin(), + current_position.begin(), + previous_vertices->begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } else { + auto input_iter = thrust::make_zip_iterator(current_vertices.begin(), new_weights->begin(), current_gpu.begin(), current_position.begin()); - std::forward_as_tuple( - std::tie(current_vertices, *new_weights, current_gpu, current_position), std::ignore) = - cugraph::groupby_gpu_id_and_shuffle_values( - handle.get_comms(), - current_iter, - current_iter + current_vertices.size(), - [] __device__(auto val) { return thrust::get<2>(val); }, - handle.get_stream()); - - thrust::for_each( - handle.get_thrust_policy(), - thrust::make_zip_iterator( - current_vertices.begin(), new_weights->begin(), current_position.begin()), - thrust::make_zip_iterator( - current_vertices.end(), new_weights->end(), current_position.end()), - [result_verts = result_vertices.data(), - result_wgts = result_weights->data(), - level, - max_length] __device__(auto tuple) { - vertex_t v = thrust::get<0>(tuple); - weight_t w = thrust::get<1>(tuple); - size_t pos = thrust::get<2>(tuple); - result_verts[pos * (max_length + 1) + level + 1] = v; - result_wgts[pos * max_length + level] = w; - }); + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } } else { - auto input_iter = thrust::make_zip_iterator( - current_vertices.begin(), current_gpu.begin(), current_position.begin()); - - auto compacted_length = thrust::distance( - input_iter, - thrust::remove_if(handle.get_thrust_policy(), - input_iter, - input_iter + current_vertices.size(), - current_vertices.begin(), - [] __device__(auto dst) { - return (dst == cugraph::invalid_vertex_id::value); - })); - - current_vertices.resize(compacted_length, handle.get_stream()); - current_gpu.resize(compacted_length, handle.get_stream()); - current_position.resize(compacted_length, handle.get_stream()); - - // Shuffle back to original GPU - auto current_iter = thrust::make_zip_iterator( - current_vertices.begin(), current_gpu.begin(), current_position.begin()); - - std::forward_as_tuple(std::tie(current_vertices, current_gpu, current_position), - std::ignore) = - cugraph::groupby_gpu_id_and_shuffle_values( - handle.get_comms(), - current_iter, - current_iter + current_vertices.size(), - [] __device__(auto val) { return thrust::get<1>(val); }, - handle.get_stream()); - - thrust::for_each( - handle.get_thrust_policy(), - thrust::make_zip_iterator(current_vertices.begin(), current_position.begin()), - thrust::make_zip_iterator(current_vertices.end(), current_position.end()), - [result_verts = result_vertices.data(), level, max_length] __device__(auto tuple) { - vertex_t v = thrust::get<0>(tuple); - size_t pos = thrust::get<1>(tuple); - result_verts[pos * (max_length + 1) + level + 1] = v; - }); + if (previous_vertices) { + auto input_iter = thrust::make_zip_iterator(current_vertices.begin(), + current_gpu.begin(), + current_position.begin(), + previous_vertices->begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } else { + auto input_iter = thrust::make_zip_iterator( + current_vertices.begin(), current_gpu.begin(), current_position.begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } } } else { if (result_weights) { - auto input_iter = thrust::make_zip_iterator( - current_vertices.begin(), new_weights->begin(), current_position.begin()); - - auto compacted_length = thrust::distance( - input_iter, - thrust::remove_if(handle.get_thrust_policy(), - input_iter, - input_iter + current_vertices.size(), - current_vertices.begin(), - [] __device__(auto dst) { - return (dst == cugraph::invalid_vertex_id::value); - })); - - current_vertices.resize(compacted_length, handle.get_stream()); - new_weights->resize(compacted_length, handle.get_stream()); - current_position.resize(compacted_length, handle.get_stream()); - - thrust::for_each( - handle.get_thrust_policy(), - thrust::make_zip_iterator( - current_vertices.begin(), new_weights->begin(), current_position.begin()), - thrust::make_zip_iterator( - current_vertices.end(), new_weights->end(), current_position.end()), - [result_verts = result_vertices.data(), - result_wgts = result_weights->data(), - level, - max_length] __device__(auto tuple) { - vertex_t v = thrust::get<0>(tuple); - weight_t w = thrust::get<1>(tuple); - size_t pos = thrust::get<2>(tuple); - result_verts[pos * (max_length + 1) + level + 1] = v; - result_wgts[pos * max_length + level] = w; - }); + if (previous_vertices) { + auto input_iter = thrust::make_zip_iterator(current_vertices.begin(), + new_weights->begin(), + current_position.begin(), + previous_vertices->begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } else { + auto input_iter = thrust::make_zip_iterator( + current_vertices.begin(), new_weights->begin(), current_position.begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } } else { - auto input_iter = - thrust::make_zip_iterator(current_vertices.begin(), current_position.begin()); - - auto compacted_length = thrust::distance( - input_iter, - thrust::remove_if(handle.get_thrust_policy(), - input_iter, - input_iter + current_vertices.size(), - current_vertices.begin(), - [] __device__(auto dst) { - return (dst == cugraph::invalid_vertex_id::value); - })); - - current_vertices.resize(compacted_length, handle.get_stream()); - current_position.resize(compacted_length, handle.get_stream()); - - thrust::for_each( - handle.get_thrust_policy(), - thrust::make_zip_iterator(current_vertices.begin(), current_position.begin()), - thrust::make_zip_iterator(current_vertices.end(), current_position.end()), - [result_verts = result_vertices.data(), level, max_length] __device__(auto tuple) { - vertex_t v = thrust::get<0>(tuple); - size_t pos = thrust::get<1>(tuple); - result_verts[pos * (max_length + 1) + level + 1] = v; - }); + if (previous_vertices) { + auto input_iter = thrust::make_zip_iterator( + current_vertices.begin(), current_position.begin(), previous_vertices->begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } else { + auto input_iter = + thrust::make_zip_iterator(current_vertices.begin(), current_position.begin()); + + compacted_length = thrust::distance( + input_iter, + thrust::remove_if(handle.get_thrust_policy(), + input_iter, + input_iter + current_vertices.size(), + current_vertices.begin(), + [] __device__(auto dst) { + return (dst == cugraph::invalid_vertex_id::value); + })); + } + } + } + + // Moved out of if statements to cut down on code duplication + current_vertices.resize(compacted_length, handle.get_stream()); + current_vertices.shrink_to_fit(handle.get_stream()); + current_position.resize(compacted_length, handle.get_stream()); + current_position.shrink_to_fit(handle.get_stream()); + if (result_weights) { + new_weights->resize(compacted_length, handle.get_stream()); + new_weights->shrink_to_fit(handle.get_stream()); + } + if (previous_vertices) { + previous_vertices->resize(compacted_length, handle.get_stream()); + previous_vertices->shrink_to_fit(handle.get_stream()); + } + if constexpr (multi_gpu) { + current_gpu.resize(compacted_length, handle.get_stream()); + current_gpu.shrink_to_fit(handle.get_stream()); + + // Shuffle back to original GPU + if (previous_vertices) { + if (result_weights) { + auto current_iter = thrust::make_zip_iterator(current_vertices.begin(), + new_weights->begin(), + current_gpu.begin(), + current_position.begin(), + previous_vertices->begin()); + + std::forward_as_tuple( + std::tie( + current_vertices, *new_weights, current_gpu, current_position, *previous_vertices), + std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + current_iter, + current_iter + current_vertices.size(), + [] __device__(auto val) { return thrust::get<2>(val); }, + handle.get_stream()); + } else { + auto current_iter = thrust::make_zip_iterator(current_vertices.begin(), + current_gpu.begin(), + current_position.begin(), + previous_vertices->begin()); + + std::forward_as_tuple( + std::tie(current_vertices, current_gpu, current_position, *previous_vertices), + std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + current_iter, + current_iter + current_vertices.size(), + [] __device__(auto val) { return thrust::get<1>(val); }, + handle.get_stream()); + } + } else { + if (result_weights) { + auto current_iter = thrust::make_zip_iterator(current_vertices.begin(), + new_weights->begin(), + current_gpu.begin(), + current_position.begin()); + + std::forward_as_tuple( + std::tie(current_vertices, *new_weights, current_gpu, current_position), std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + current_iter, + current_iter + current_vertices.size(), + [] __device__(auto val) { return thrust::get<2>(val); }, + handle.get_stream()); + } else { + auto current_iter = thrust::make_zip_iterator( + current_vertices.begin(), current_gpu.begin(), current_position.begin()); + + std::forward_as_tuple(std::tie(current_vertices, current_gpu, current_position), + std::ignore) = + cugraph::groupby_gpu_id_and_shuffle_values( + handle.get_comms(), + current_iter, + current_iter + current_vertices.size(), + [] __device__(auto val) { return thrust::get<1>(val); }, + handle.get_stream()); + } } } + + if (result_weights) { + thrust::for_each(handle.get_thrust_policy(), + thrust::make_zip_iterator( + current_vertices.begin(), new_weights->begin(), current_position.begin()), + thrust::make_zip_iterator( + current_vertices.end(), new_weights->end(), current_position.end()), + [result_verts = result_vertices.data(), + result_wgts = result_weights->data(), + level, + max_length] __device__(auto tuple) { + vertex_t v = thrust::get<0>(tuple); + weight_t w = thrust::get<1>(tuple); + size_t pos = thrust::get<2>(tuple); + result_verts[pos * (max_length + 1) + level + 1] = v; + result_wgts[pos * max_length + level] = w; + }); + } else { + thrust::for_each( + handle.get_thrust_policy(), + thrust::make_zip_iterator(current_vertices.begin(), current_position.begin()), + thrust::make_zip_iterator(current_vertices.end(), current_position.end()), + [result_verts = result_vertices.data(), level, max_length] __device__(auto tuple) { + vertex_t v = thrust::get<0>(tuple); + size_t pos = thrust::get<1>(tuple); + result_verts[pos * (max_length + 1) + level + 1] = v; + }); + } } return std::make_tuple(std::move(result_vertices), std::move(result_weights)); @@ -449,11 +935,11 @@ random_walk_impl(raft::handle_t const& handle, template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed) + size_t max_length) { CUGRAPH_EXPECTS(!graph_view.has_edge_mask(), "unimplemented."); @@ -462,18 +948,17 @@ uniform_random_walks(raft::handle_t const& handle, edge_weight_view, start_vertices, max_length, - detail::uniform_selector( - (seed == 0 ? detail::get_current_time_nanoseconds() : seed))); + detail::uniform_selector{rng_state}); } template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed) + size_t max_length) { CUGRAPH_EXPECTS(!graph_view.has_edge_mask(), "unimplemented."); @@ -483,30 +968,28 @@ biased_random_walks(raft::handle_t const& handle, std::optional>{edge_weight_view}, start_vertices, max_length, - detail::biased_selector{(seed == 0 ? detail::get_current_time_nanoseconds() : seed)}); + detail::biased_selector{rng_state}); } template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, weight_t p, - weight_t q, - uint64_t seed) + weight_t q) { CUGRAPH_EXPECTS(!graph_view.has_edge_mask(), "unimplemented."); - return detail::random_walk_impl( - handle, - graph_view, - edge_weight_view, - start_vertices, - max_length, - detail::node2vec_selector{ - p, q, (seed == 0 ? detail::get_current_time_nanoseconds() : seed)}); + return detail::random_walk_impl(handle, + graph_view, + edge_weight_view, + start_vertices, + max_length, + detail::node2vec_selector{p, q, rng_state}); } } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_mg_v32_e32.cu b/cpp/src/sampling/random_walks_mg_v32_e32.cu index 421d3e9c818..abe5386da1c 100644 --- a/cpp/src/sampling/random_walks_mg_v32_e32.cu +++ b/cpp/src/sampling/random_walks_mg_v32_e32.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_mg_v32_e64.cu b/cpp/src/sampling/random_walks_mg_v32_e64.cu index d38af65a505..b1bf1a19b77 100644 --- a/cpp/src/sampling/random_walks_mg_v32_e64.cu +++ b/cpp/src/sampling/random_walks_mg_v32_e64.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_mg_v64_e64.cu b/cpp/src/sampling/random_walks_mg_v64_e64.cu index 9dedc893242..13cc899e50d 100644 --- a/cpp/src/sampling/random_walks_mg_v64_e64.cu +++ b/cpp/src/sampling/random_walks_mg_v64_e64.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_sg_v32_e32.cu b/cpp/src/sampling/random_walks_sg_v32_e32.cu index 7b64d107250..383917c0248 100644 --- a/cpp/src/sampling/random_walks_sg_v32_e32.cu +++ b/cpp/src/sampling/random_walks_sg_v32_e32.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_sg_v32_e64.cu b/cpp/src/sampling/random_walks_sg_v32_e64.cu index d9ea09f36ef..98d2bb02d88 100644 --- a/cpp/src/sampling/random_walks_sg_v32_e64.cu +++ b/cpp/src/sampling/random_walks_sg_v32_e64.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/src/sampling/random_walks_sg_v64_e64.cu b/cpp/src/sampling/random_walks_sg_v64_e64.cu index 0b9be107276..c139acec4b7 100644 --- a/cpp/src/sampling/random_walks_sg_v64_e64.cu +++ b/cpp/src/sampling/random_walks_sg_v64_e64.cu @@ -22,54 +22,54 @@ namespace cugraph { template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> uniform_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> biased_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, edge_property_view_t edge_weight_view, raft::device_span start_vertices, - size_t max_length, - uint64_t seed); + size_t max_length); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, float p, - float q, - uint64_t seed); + float q); template std::tuple, std::optional>> node2vec_random_walks(raft::handle_t const& handle, + raft::random::RngState& rng_state, graph_view_t const& graph_view, std::optional> edge_weight_view, raft::device_span start_vertices, size_t max_length, double p, - double q, - uint64_t seed); + double q); } // namespace cugraph diff --git a/cpp/tests/c_api/sg_random_walks_test.c b/cpp/tests/c_api/sg_random_walks_test.c index 14108d91c04..a4a77b5775a 100644 --- a/cpp/tests/c_api/sg_random_walks_test.c +++ b/cpp/tests/c_api/sg_random_walks_test.c @@ -192,9 +192,6 @@ int generic_biased_random_walks_test(vertex_t* h_src, ret_code = cugraph_biased_random_walks(handle, graph, d_start_view, max_depth, &result, &ret_error); -#if 1 - TEST_ASSERT(test_ret_value, ret_code != CUGRAPH_SUCCESS, "biased_random_walks should have failed") -#else TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "biased_random_walks failed."); @@ -208,10 +205,10 @@ int generic_biased_random_walks_test(vertex_t* h_src, size_t wgts_size = cugraph_type_erased_device_array_view_size(wgts); vertex_t h_result_verts[verts_size]; - vertex_t h_result_wgts[wgts_size]; + weight_t h_result_wgts[wgts_size]; - ret_code = - cugraph_type_erased_device_array_view_copy_to_host(handle, (byte_t*)h_verts, verts, &ret_error); + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_verts, verts, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); ret_code = cugraph_type_erased_device_array_view_copy_to_host( @@ -231,23 +228,35 @@ int generic_biased_random_walks_test(vertex_t* h_src, M[h_src[i]][h_dst[i]] = h_wgt[i]; TEST_ASSERT(test_ret_value, - cugraph_random_walk_result_get_max_path_length() == max_depth, + cugraph_random_walk_result_get_max_path_length(result) == max_depth, "path length does not match"); for (int i = 0; (i < num_starts) && (test_ret_value == 0); ++i) { - TEST_ASSERT(test_ret_value, - M[h_start[i]][h_result_verts[i * (max_depth + 1)]] == h_result_wgts[i * max_depth], - "biased_random_walks got edge that doesn't exist"); - for (size_t j = 1; j < cugraph_random_walk_result_get_max_path_length(); ++j) - TEST_ASSERT( - test_ret_value, - M[h_start[i * (max_depth + 1) + j - 1]][h_result_verts[i * (max_depth + 1) + j]] == - h_result_wgts[i * max_depth + j - 1], - "biased_random_walks got edge that doesn't exist"); + TEST_ASSERT( + test_ret_value, h_start[i] == h_result_verts[i * (max_depth + 1)], "start of path not found"); + for (size_t j = 0; j < max_depth; ++j) { + int src_index = i * (max_depth + 1) + j; + int dst_index = src_index + 1; + if (h_result_verts[dst_index] < 0) { + if (h_result_verts[src_index] >= 0) { + int departing_count = 0; + for (int k = 0; k < num_vertices; ++k) { + if (M[h_result_verts[src_index]][k] >= 0) departing_count++; + } + TEST_ASSERT(test_ret_value, + departing_count == 0, + "biased_random_walks found no edge when an edge exists"); + } + } else { + TEST_ASSERT(test_ret_value, + M[h_result_verts[src_index]][h_result_verts[dst_index]] == + h_result_wgts[i * max_depth + j], + "biased_random_walks got edge that doesn't exist"); + } + } } cugraph_random_walk_result_free(result); -#endif cugraph_sg_graph_free(graph); cugraph_free_resource_handle(handle); @@ -302,10 +311,6 @@ int generic_node2vec_random_walks_test(vertex_t* h_src, ret_code = cugraph_node2vec_random_walks( handle, graph, d_start_view, max_depth, p, q, &result, &ret_error); -#if 1 - TEST_ASSERT( - test_ret_value, ret_code != CUGRAPH_SUCCESS, "node2vec_random_walks should have failed") -#else TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "node2vec_random_walks failed."); @@ -319,10 +324,10 @@ int generic_node2vec_random_walks_test(vertex_t* h_src, size_t wgts_size = cugraph_type_erased_device_array_view_size(wgts); vertex_t h_result_verts[verts_size]; - vertex_t h_result_wgts[wgts_size]; + weight_t h_result_wgts[wgts_size]; - ret_code = - cugraph_type_erased_device_array_view_copy_to_host(handle, (byte_t*)h_verts, verts, &ret_error); + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_verts, verts, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); ret_code = cugraph_type_erased_device_array_view_copy_to_host( @@ -342,23 +347,35 @@ int generic_node2vec_random_walks_test(vertex_t* h_src, M[h_src[i]][h_dst[i]] = h_wgt[i]; TEST_ASSERT(test_ret_value, - cugraph_random_walk_result_get_max_path_length() == max_depth, + cugraph_random_walk_result_get_max_path_length(result) == max_depth, "path length does not match"); for (int i = 0; (i < num_starts) && (test_ret_value == 0); ++i) { - TEST_ASSERT(test_ret_value, - M[h_start[i]][h_result_verts[i * (max_depth + 1)]] == h_result_wgts[i * max_depth], - "node2vec_random_walks got edge that doesn't exist"); - for (size_t j = 1; j < max_depth; ++j) - TEST_ASSERT( - test_ret_value, - M[h_start[i * (max_depth + 1) + j - 1]][h_result_verts[i * (max_depth + 1) + j]] == - h_result_wgts[i * max_depth + j - 1], - "node2vec_random_walks got edge that doesn't exist"); + TEST_ASSERT( + test_ret_value, h_start[i] == h_result_verts[i * (max_depth + 1)], "start of path not found"); + for (size_t j = 0; j < max_depth; ++j) { + int src_index = i * (max_depth + 1) + j; + int dst_index = src_index + 1; + if (h_result_verts[dst_index] < 0) { + if (h_result_verts[src_index] >= 0) { + int departing_count = 0; + for (int k = 0; k < num_vertices; ++k) { + if (M[h_result_verts[src_index]][k] >= 0) departing_count++; + } + TEST_ASSERT(test_ret_value, + departing_count == 0, + "node2vec_random_walks found no edge when an edge exists"); + } + } else { + TEST_ASSERT(test_ret_value, + M[h_result_verts[src_index]][h_result_verts[dst_index]] == + h_result_wgts[i * max_depth + j], + "node2vec_random_walks got edge that doesn't exist"); + } + } } cugraph_random_walk_result_free(result); -#endif cugraph_sg_graph_free(graph); cugraph_free_resource_handle(handle); @@ -390,7 +407,7 @@ int test_biased_random_walks() vertex_t src[] = {0, 1, 1, 2, 2, 2, 3, 4}; vertex_t dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; - weight_t wgt[] = {0, 1, 2, 3, 4, 5, 6, 7}; + weight_t wgt[] = {1, 2, 3, 4, 5, 6, 7, 8}; vertex_t start[] = {2, 2}; return generic_biased_random_walks_test( diff --git a/cpp/tests/sampling/mg_random_walks_test.cpp b/cpp/tests/sampling/mg_random_walks_test.cpp index c2ad5c37e9e..e2415c08e60 100644 --- a/cpp/tests/sampling/mg_random_walks_test.cpp +++ b/cpp/tests/sampling/mg_random_walks_test.cpp @@ -44,8 +44,10 @@ struct UniformRandomWalks_Usecase { raft::device_span start_vertices, size_t max_depth) { + raft::random::RngState rng_state(static_cast(handle.get_comms().get_rank())); + return cugraph::uniform_random_walks( - handle, graph_view, edge_weight_view, start_vertices, max_depth, seed); + handle, rng_state, graph_view, edge_weight_view, start_vertices, max_depth); } bool expect_throw() { return false; } @@ -66,12 +68,13 @@ struct BiasedRandomWalks_Usecase { { CUGRAPH_EXPECTS(edge_weight_view.has_value(), "Biased random walk requires edge weights."); + raft::random::RngState rng_state(static_cast(handle.get_comms().get_rank())); + return cugraph::biased_random_walks( - handle, graph_view, *edge_weight_view, start_vertices, max_depth, seed); + handle, rng_state, graph_view, *edge_weight_view, start_vertices, max_depth); } - // FIXME: Not currently implemented - bool expect_throw() { return true; } + bool expect_throw() { return !test_weighted; } }; struct Node2VecRandomWalks_Usecase { @@ -89,18 +92,19 @@ struct Node2VecRandomWalks_Usecase { raft::device_span start_vertices, size_t max_depth) { + raft::random::RngState rng_state(static_cast(handle.get_comms().get_rank())); + return cugraph::node2vec_random_walks(handle, + rng_state, graph_view, edge_weight_view, start_vertices, max_depth, static_cast(p), - static_cast(q), - seed); + static_cast(q)); } - // FIXME: Not currently implemented - bool expect_throw() { return true; } + bool expect_throw() { return false; } }; template diff --git a/cpp/tests/sampling/random_walks_check.cuh b/cpp/tests/sampling/random_walks_check.cuh index 0fd73b5bba7..380b97a5b84 100644 --- a/cpp/tests/sampling/random_walks_check.cuh +++ b/cpp/tests/sampling/random_walks_check.cuh @@ -108,7 +108,7 @@ void random_walks_validate( (int)d, (float)w); } else { - printf("edge (%d,%d) NOT FOUND\n", (int)s, (int)d); + printf("edge (%d,%d), weight %g NOT FOUND\n", (int)s, (int)d, (float)w); } return 1; diff --git a/cpp/tests/sampling/sg_random_walks_test.cpp b/cpp/tests/sampling/sg_random_walks_test.cpp index 7409c2ab758..4bcfebc6d51 100644 --- a/cpp/tests/sampling/sg_random_walks_test.cpp +++ b/cpp/tests/sampling/sg_random_walks_test.cpp @@ -40,8 +40,10 @@ struct UniformRandomWalks_Usecase { raft::device_span start_vertices, size_t num_paths) { + raft::random::RngState rng_state(0); + return cugraph::uniform_random_walks( - handle, graph_view, edge_weight_view, start_vertices, num_paths, seed); + handle, rng_state, graph_view, edge_weight_view, start_vertices, num_paths); } bool expect_throw() { return false; } @@ -62,12 +64,13 @@ struct BiasedRandomWalks_Usecase { { CUGRAPH_EXPECTS(edge_weight_view.has_value(), "Biased random walk requires edge weights."); + raft::random::RngState rng_state(0); + return cugraph::biased_random_walks( - handle, graph_view, *edge_weight_view, start_vertices, num_paths, seed); + handle, rng_state, graph_view, *edge_weight_view, start_vertices, num_paths); } - // FIXME: Not currently implemented - bool expect_throw() { return true; } + bool expect_throw() { return !test_weighted; } }; struct Node2VecRandomWalks_Usecase { @@ -85,18 +88,19 @@ struct Node2VecRandomWalks_Usecase { raft::device_span start_vertices, size_t num_paths) { + raft::random::RngState rng_state(0); + return cugraph::node2vec_random_walks(handle, + rng_state, graph_view, edge_weight_view, start_vertices, num_paths, static_cast(p), - static_cast(q), - seed); + static_cast(q)); } - // FIXME: Not currently implemented - bool expect_throw() { return true; } + bool expect_throw() { return false; } }; template @@ -197,9 +201,6 @@ using Tests_Node2VecRandomWalks_File = using Tests_Node2VecRandomWalks_Rmat = Tests_RandomWalks>; -#if 0 -// FIXME: We should use these tests, gtest-1.11.0 makes it a runtime error -// to define and not instantiate these. TEST_P(Tests_UniformRandomWalks_File, Initialize_i32_i32_f) { run_current_test( @@ -211,7 +212,6 @@ TEST_P(Tests_UniformRandomWalks_Rmat, Initialize_i32_i32_f) run_current_test( override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -#endif TEST_P(Tests_BiasedRandomWalks_File, Initialize_i32_i32_f) { @@ -237,19 +237,12 @@ TEST_P(Tests_Node2VecRandomWalks_Rmat, Initialize_i32_i32_f) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -#if 0 -// FIXME: Not sure why these are failing, but we're refactoring anyway. INSTANTIATE_TEST_SUITE_P( simple_test, Tests_UniformRandomWalks_File, - ::testing::Combine( - ::testing::Values(UniformRandomWalks_Usecase{false, 0, true}, - UniformRandomWalks_Usecase{true, 0, true}), - ::testing::Values(cugraph::test::File_Usecase("test/datasets/karate.mtx"), - cugraph::test::File_Usecase("test/datasets/web-Google.mtx"), - cugraph::test::File_Usecase("test/datasets/ljournal-2008.mtx"), - cugraph::test::File_Usecase("test/datasets/webbase-1M.mtx")))); -#endif + ::testing::Combine(::testing::Values(UniformRandomWalks_Usecase{false, 0, true}, + UniformRandomWalks_Usecase{true, 0, true}), + ::testing::Values(cugraph::test::File_Usecase("test/datasets/karate.mtx")))); INSTANTIATE_TEST_SUITE_P( file_test, @@ -265,6 +258,16 @@ INSTANTIATE_TEST_SUITE_P( Node2VecRandomWalks_Usecase{4, 8, true, 0, true}), ::testing::Values(cugraph::test::File_Usecase("test/datasets/karate.mtx")))); +INSTANTIATE_TEST_SUITE_P( + file_large_test, + Tests_UniformRandomWalks_File, + ::testing::Combine( + ::testing::Values(UniformRandomWalks_Usecase{false, 0, true}, + UniformRandomWalks_Usecase{true, 0, true}), + ::testing::Values(cugraph::test::File_Usecase("test/datasets/web-Google.mtx"), + cugraph::test::File_Usecase("test/datasets/ljournal-2008.mtx"), + cugraph::test::File_Usecase("test/datasets/webbase-1M.mtx")))); + INSTANTIATE_TEST_SUITE_P( file_large_test, Tests_BiasedRandomWalks_File, @@ -285,23 +288,20 @@ INSTANTIATE_TEST_SUITE_P( cugraph::test::File_Usecase("test/datasets/ljournal-2008.mtx"), cugraph::test::File_Usecase("test/datasets/webbase-1M.mtx")))); -#if 0 -// FIXME: Not sure why these are failing, but we're refactoring anyway. INSTANTIATE_TEST_SUITE_P( rmat_small_test, Tests_UniformRandomWalks_Rmat, - ::testing::Combine(::testing::Values(UniformRandomWalks_Usecase{false, 0, true}, - UniformRandomWalks_Usecase{true, 0, true}), - ::testing::Values(cugraph::test::Rmat_Usecase( - 10, 16, 0.57, 0.19, 0.19, 0, false, false)))); + ::testing::Combine( + ::testing::Values(UniformRandomWalks_Usecase{false, 0, true}, + UniformRandomWalks_Usecase{true, 0, true}), + ::testing::Values(cugraph::test::Rmat_Usecase(10, 16, 0.57, 0.19, 0.19, 0, false, false)))); INSTANTIATE_TEST_SUITE_P( rmat_benchmark_test, Tests_UniformRandomWalks_Rmat, - ::testing::Combine(::testing::Values(UniformRandomWalks_Usecase{true, 0, false}), - ::testing::Values(cugraph::test::Rmat_Usecase( - 20, 32, 0.57, 0.19, 0.19, 0, false, false)))); -#endif + ::testing::Combine( + ::testing::Values(UniformRandomWalks_Usecase{true, 0, false}), + ::testing::Values(cugraph::test::Rmat_Usecase(20, 32, 0.57, 0.19, 0.19, 0, false, false)))); INSTANTIATE_TEST_SUITE_P( rmat_small_test, From 467d818a67405adcfda01c57d43c303fbcd1789e Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Wed, 18 Sep 2024 05:12:05 -0500 Subject: [PATCH 02/37] Update flake8 to 7.1.1. (#4652) We need to update flake8 to fix a false-positive that appears with older flake8 versions on Python 3.12. Authors: - Bradley Dice (https://github.com/bdice) Approvers: - James Lamb (https://github.com/jameslamb) - Alex Barghi (https://github.com/alexbarghi-nv) - Kyle Edwards (https://github.com/KyleFromNVIDIA) URL: https://github.com/rapidsai/cugraph/pull/4652 --- .pre-commit-config.yaml | 4 ++-- .../cugraph-dgl/python-script/ogbn_mag_benchmark.py | 4 ++-- python/cugraph-dgl/cugraph_dgl/nn/conv/base.py | 4 ++-- .../examples/graphsage/node-classification-dask.py | 4 +++- .../examples/graphsage/node-classification.py | 4 +++- .../examples/multi_trainer_MG_example/model.py | 2 +- .../examples/multi_trainer_MG_example/workflow_mnmg.py | 2 +- .../examples/multi_trainer_MG_example/workflow_snmg.py | 2 +- .../cugraph-pyg/cugraph_pyg/data/dask_graph_store.py | 2 +- python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py | 2 +- python/cugraph-service/tests/test_e2e.py | 6 +++--- python/cugraph-service/tests/test_mg_e2e.py | 6 +++--- python/cugraph/cugraph/tests/generators/test_rmat.py | 4 +++- .../cugraph/cugraph/tests/generators/test_rmat_mg.py | 6 ++++-- .../cugraph/tests/sampling/test_bulk_sampler.py | 2 +- .../cugraph/cugraph/tests/structure/test_graph_mg.py | 4 ++-- python/cugraph/cugraph/tests/traversal/test_sssp.py | 4 ++-- python/cugraph/cugraph/traversal/sssp.py | 10 ++++++---- 18 files changed, 41 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5b351478fa9..8ff284210b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: files: ^(python/.*|benchmarks/.*)$ exclude: ^python/nx-cugraph/ - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 7.1.1 hooks: - id: flake8 args: ["--config=.flake8"] @@ -34,7 +34,7 @@ repos: hooks: - id: yesqa additional_dependencies: - - flake8==6.0.0 + - flake8==7.1.1 - repo: https://github.com/pre-commit/mirrors-clang-format rev: v16.0.6 hooks: diff --git a/benchmarks/cugraph-dgl/python-script/ogbn_mag_benchmark.py b/benchmarks/cugraph-dgl/python-script/ogbn_mag_benchmark.py index 539fe333b1e..55ff0043e30 100644 --- a/benchmarks/cugraph-dgl/python-script/ogbn_mag_benchmark.py +++ b/benchmarks/cugraph-dgl/python-script/ogbn_mag_benchmark.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -126,4 +126,4 @@ def sampling_func(g, seed_nodes, labels, train_loader): st = time.time() sampling_func(g, subset_split_idx["train"], labels, train_loader) et = time.time() - print(f"Sampling time taken = {et-st} s") + print(f"Sampling time taken = {et - st} s") diff --git a/python/cugraph-dgl/cugraph_dgl/nn/conv/base.py b/python/cugraph-dgl/cugraph_dgl/nn/conv/base.py index d2460f814c9..fcd5a26aee6 100644 --- a/python/cugraph-dgl/cugraph_dgl/nn/conv/base.py +++ b/python/cugraph-dgl/cugraph_dgl/nn/conv/base.py @@ -129,7 +129,7 @@ def __init__( if csrc_ids is not None: if csrc_ids.numel() != self._num_src_nodes + 1: raise RuntimeError( - f"Size mismatch for 'csrc_ids': expected ({size[0]+1},), " + f"Size mismatch for 'csrc_ids': expected ({size[0] + 1},), " f"but got {tuple(csrc_ids.size())}" ) csrc_ids = csrc_ids.contiguous() @@ -137,7 +137,7 @@ def __init__( if cdst_ids is not None: if cdst_ids.numel() != self._num_dst_nodes + 1: raise RuntimeError( - f"Size mismatch for 'cdst_ids': expected ({size[1]+1},), " + f"Size mismatch for 'cdst_ids': expected ({size[1] + 1},), " f"but got {tuple(cdst_ids.size())}" ) cdst_ids = cdst_ids.contiguous() diff --git a/python/cugraph-dgl/examples/graphsage/node-classification-dask.py b/python/cugraph-dgl/examples/graphsage/node-classification-dask.py index 992669e4284..0481f9566bc 100644 --- a/python/cugraph-dgl/examples/graphsage/node-classification-dask.py +++ b/python/cugraph-dgl/examples/graphsage/node-classification-dask.py @@ -205,7 +205,9 @@ def train(args, device, g, dataset, model): et = time.time() - print(f"Time taken for epoch {epoch} with batch_size {batch_size} = {et-st} s") + print( + f"Time taken for epoch {epoch} with batch_size {batch_size} = {et - st} s" + ) acc = evaluate(model, g, val_dataloader) print( "Epoch {:05d} | Loss {:.4f} | Accuracy {:.4f} ".format( diff --git a/python/cugraph-dgl/examples/graphsage/node-classification.py b/python/cugraph-dgl/examples/graphsage/node-classification.py index 2b8b687efab..56ac41c09b4 100644 --- a/python/cugraph-dgl/examples/graphsage/node-classification.py +++ b/python/cugraph-dgl/examples/graphsage/node-classification.py @@ -215,7 +215,9 @@ def train(args, device, g, dataset, model, directory): et = time.time() - print(f"Time taken for epoch {epoch} with batch_size {batch_size} = {et-st} s") + print( + f"Time taken for epoch {epoch} with batch_size {batch_size} = {et - st} s" + ) acc = evaluate(model, g, val_dataloader) print( "Epoch {:05d} | Loss {:.4f} | Accuracy {:.4f} ".format( diff --git a/python/cugraph-dgl/examples/multi_trainer_MG_example/model.py b/python/cugraph-dgl/examples/multi_trainer_MG_example/model.py index d3aad2ab309..3e0c0454905 100644 --- a/python/cugraph-dgl/examples/multi_trainer_MG_example/model.py +++ b/python/cugraph-dgl/examples/multi_trainer_MG_example/model.py @@ -134,7 +134,7 @@ def train_model(model, g, opt, train_dataloader, num_epochs, rank, val_nid): et = time.time() print( f"Total time taken for num_epochs {num_epochs} " - f"with batch_size {train_dataloader._batch_size} = {et-st} s on rank ={rank}" + f"with batch_size {train_dataloader._batch_size} = {et - st} s on rank ={rank}" ) if rank == 0: val_acc = layerwise_infer(g, val_nid, model, 1024 * 5, "cuda") diff --git a/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_mnmg.py b/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_mnmg.py index b1878b37d4e..11afe466014 100644 --- a/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_mnmg.py +++ b/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_mnmg.py @@ -234,7 +234,7 @@ def run_workflow( torch.distributed.barrier() total_et = time.time() print( - f"Total time taken on n_epochs {n_epochs} = {total_et-total_st} s", + f"Total time taken on n_epochs {n_epochs} = {total_et - total_st} s", f"measured by worker = {global_rank}", ) diff --git a/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_snmg.py b/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_snmg.py index da5c2b4d64e..001d7fb82dc 100644 --- a/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_snmg.py +++ b/python/cugraph-dgl/examples/multi_trainer_MG_example/workflow_snmg.py @@ -207,7 +207,7 @@ def run_workflow(rank, world_size, cugraph_id, dataset, temp_dir): torch.distributed.barrier() total_et = time.time() print( - f"Total time taken on n_epochs {n_epochs} = {total_et-total_st} s", + f"Total time taken on n_epochs {n_epochs} = {total_et - total_st} s", f"measured by worker = {rank}", ) diff --git a/python/cugraph-pyg/cugraph_pyg/data/dask_graph_store.py b/python/cugraph-pyg/cugraph_pyg/data/dask_graph_store.py index c805cd496c8..6195f3118a4 100644 --- a/python/cugraph-pyg/cugraph_pyg/data/dask_graph_store.py +++ b/python/cugraph-pyg/cugraph_pyg/data/dask_graph_store.py @@ -150,7 +150,7 @@ def is_set(self, key): if key not in self.__dataclass_fields__: raise KeyError(key) attr = getattr(self, key) - return type(attr) != _field_status or attr != _field_status.UNSET + return type(attr) is not _field_status or attr != _field_status.UNSET def is_fully_specified(self): """ diff --git a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py index b299fc2a1a1..09d874bd87d 100644 --- a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py +++ b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py @@ -66,7 +66,7 @@ def train(epoch: int): torch.cuda.synchronize() print( f"Average Training Iteration Time (s/iter): \ - {(time.perf_counter() - start_avg_time)/(i-warmup_steps):.6f}" + {(time.perf_counter() - start_avg_time) / (i - warmup_steps):.6f}" ) diff --git a/python/cugraph-service/tests/test_e2e.py b/python/cugraph-service/tests/test_e2e.py index c9b3d24f20e..3079a2423c7 100644 --- a/python/cugraph-service/tests/test_e2e.py +++ b/python/cugraph-service/tests/test_e2e.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -307,8 +307,8 @@ def test_load_call_unload_extension(client, extension1): assert len(results) == 2 assert len(results[0]) == 33 assert len(results[1]) == 21 - assert type(results[0][0]) == int - assert type(results[1][0]) == float + assert type(results[0][0]) is int + assert type(results[1][0]) is float assert results[0][0] == 9 assert results[1][0] == 9.0 diff --git a/python/cugraph-service/tests/test_mg_e2e.py b/python/cugraph-service/tests/test_mg_e2e.py index 39c1195151d..5526593aee0 100644 --- a/python/cugraph-service/tests/test_mg_e2e.py +++ b/python/cugraph-service/tests/test_mg_e2e.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -413,8 +413,8 @@ def test_call_extension_result_on_device( assert len(results) == 2 assert len(results[0]) == array1_len assert len(results[1]) == array2_len - assert type(results[0][0]) == int - assert type(results[1][0]) == float + assert type(results[0][0]) is int + assert type(results[1][0]) is float assert results[0][0] == 9 assert results[1][0] == 9.0 else: diff --git a/python/cugraph/cugraph/tests/generators/test_rmat.py b/python/cugraph/cugraph/tests/generators/test_rmat.py index 1cee0461686..87cbe636fdc 100644 --- a/python/cugraph/cugraph/tests/generators/test_rmat.py +++ b/python/cugraph/cugraph/tests/generators/test_rmat.py @@ -27,7 +27,9 @@ _scale_values = [2, 4, 16] _scale_test_ids = [f"scale={x}" for x in _scale_values] _graph_types = [cugraph.Graph, None, int] -_graph_test_ids = [f"create_using={getattr(x,'__name__',str(x))}" for x in _graph_types] +_graph_test_ids = [ + f"create_using={getattr(x, '__name__', str(x))}" for x in _graph_types +] _clip_and_flip = [False, True] _clip_and_flip_test_ids = [f"clip_and_flip={x}" for x in _clip_and_flip] _scramble_vertex_ids = [False, True] diff --git a/python/cugraph/cugraph/tests/generators/test_rmat_mg.py b/python/cugraph/cugraph/tests/generators/test_rmat_mg.py index 0e1808d2f80..44a6b3a2fc1 100644 --- a/python/cugraph/cugraph/tests/generators/test_rmat_mg.py +++ b/python/cugraph/cugraph/tests/generators/test_rmat_mg.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2023, NVIDIA CORPORATION. +# Copyright (c) 2021-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -34,7 +34,9 @@ _scale_values = [2, 4, 16] _scale_test_ids = [f"scale={x}" for x in _scale_values] _graph_types = [cugraph.Graph, None, int] -_graph_test_ids = [f"create_using={getattr(x,'__name__',str(x))}" for x in _graph_types] +_graph_test_ids = [ + f"create_using={getattr(x, '__name__', str(x))}" for x in _graph_types +] def _call_rmat(scale, num_edges, create_using, mg=True): diff --git a/python/cugraph/cugraph/tests/sampling/test_bulk_sampler.py b/python/cugraph/cugraph/tests/sampling/test_bulk_sampler.py index 65bcce78771..3c5d6428001 100644 --- a/python/cugraph/cugraph/tests/sampling/test_bulk_sampler.py +++ b/python/cugraph/cugraph/tests/sampling/test_bulk_sampler.py @@ -119,7 +119,7 @@ def test_bulk_sampler_remainder(scratch_dir): assert b in recovered_samples["batch_id"].values_host.tolist() for x in range(0, 6, 2): - subdir = f"{x}-{x+1}" + subdir = f"{x}-{x + 1}" df = cudf.read_parquet(os.path.join(samples_path, f"batch={subdir}.parquet")) assert ((df.batch_id == x) | (df.batch_id == (x + 1))).all() diff --git a/python/cugraph/cugraph/tests/structure/test_graph_mg.py b/python/cugraph/cugraph/tests/structure/test_graph_mg.py index cba61731e9a..f2cc1583f93 100644 --- a/python/cugraph/cugraph/tests/structure/test_graph_mg.py +++ b/python/cugraph/cugraph/tests/structure/test_graph_mg.py @@ -303,7 +303,7 @@ def test_mg_graph_serializable(dask_client, input_combo): G = input_combo["MGGraph"] dask_client.publish_dataset(shared_g=G) shared_g = dask_client.get_dataset("shared_g") - assert type(shared_g) == type(G) + assert type(shared_g) is type(G) assert G.number_of_vertices() == shared_g.number_of_vertices() assert G.number_of_edges() == shared_g.number_of_edges() # cleanup @@ -314,7 +314,7 @@ def test_mg_graph_serializable(dask_client, input_combo): def test_mg_graph_copy(): G = cugraph.MultiGraph(directed=True) G_c = copy.deepcopy(G) - assert type(G) == type(G_c) + assert type(G) is type(G_c) @pytest.mark.mg diff --git a/python/cugraph/cugraph/tests/traversal/test_sssp.py b/python/cugraph/cugraph/tests/traversal/test_sssp.py index 58288e022e8..ceb6040275d 100644 --- a/python/cugraph/cugraph/tests/traversal/test_sssp.py +++ b/python/cugraph/cugraph/tests/traversal/test_sssp.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2023, NVIDIA CORPORATION. +# Copyright (c) 2019-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -486,7 +486,7 @@ def test_scipy_api_compat(): distances = cugraph.shortest_path( input_coo_matrix, source=0, return_predecessors=False ) - assert type(distances) != tuple + assert type(distances) is not tuple with pytest.raises(ValueError): cugraph.shortest_path(input_coo_matrix, source=0, unweighted=False) diff --git a/python/cugraph/cugraph/traversal/sssp.py b/python/cugraph/cugraph/traversal/sssp.py index 5ab97e60390..bb98b5a9a29 100644 --- a/python/cugraph/cugraph/traversal/sssp.py +++ b/python/cugraph/cugraph/traversal/sssp.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2023, NVIDIA CORPORATION. +# Copyright (c) 2019-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -36,7 +36,7 @@ def _ensure_args( # checks common to all input types if (method is not None) and (method != "auto"): raise ValueError("only 'auto' is currently accepted for method") - if (indices is not None) and (type(indices) == list): + if (indices is not None) and (type(indices) is list): raise ValueError("indices currently cannot be a list-like type") if (indices is not None) and (source is not None): raise TypeError("cannot specify both 'source' and 'indices'") @@ -70,9 +70,11 @@ def _ensure_args( # Check for non-Graph-type inputs else: - if (directed is not None) and (type(directed) != bool): + if (directed is not None) and (type(directed) is not bool): raise ValueError("'directed' must be a bool") - if (return_predecessors is not None) and (type(return_predecessors) != bool): + if (return_predecessors is not None) and ( + type(return_predecessors) is not bool + ): raise ValueError("'return_predecessors' must be a bool") if (unweighted is not None) and (unweighted is not True): raise ValueError("'unweighted' currently must be True if " "specified") From eb8963ab40f6ec7f8260e5e74dae5c7421b455d9 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 18 Sep 2024 14:42:15 -0500 Subject: [PATCH 03/37] Use CI workflow branch 'branch-24.10' again (#4654) All RAPIDS libraries have been updated with Python 3.12 support, so Python 3.12 changes have been merged into `branch-24.10` of `shared-workflows`: https://github.com/rapidsai/shared-workflows/pull/213 This updates GitHub Actions configs here to that branch. --- .github/workflows/build.yaml | 32 +++++++++++++------------- .github/workflows/pr.yaml | 44 ++++++++++++++++++------------------ .github/workflows/test.yaml | 18 +++++++-------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3ad8aef5820..bc489ffd3f0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -38,7 +38,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -47,7 +47,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -57,7 +57,7 @@ jobs: if: github.ref_type == 'branch' needs: python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -69,7 +69,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-pylibcugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -83,7 +83,7 @@ jobs: wheel-publish-pylibcugraph: needs: wheel-build-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -93,7 +93,7 @@ jobs: wheel-build-cugraph: needs: wheel-publish-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -106,7 +106,7 @@ jobs: wheel-publish-cugraph: needs: wheel-build-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -116,7 +116,7 @@ jobs: wheel-build-nx-cugraph: needs: wheel-publish-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -126,7 +126,7 @@ jobs: wheel-publish-nx-cugraph: needs: wheel-build-nx-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -136,7 +136,7 @@ jobs: wheel-build-cugraph-dgl: needs: wheel-publish-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -146,7 +146,7 @@ jobs: wheel-publish-cugraph-dgl: needs: wheel-build-cugraph-dgl secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -156,7 +156,7 @@ jobs: wheel-build-cugraph-pyg: needs: wheel-publish-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -166,7 +166,7 @@ jobs: wheel-publish-cugraph-pyg: needs: wheel-build-cugraph-pyg secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -175,7 +175,7 @@ jobs: package-name: cugraph-pyg wheel-build-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -185,7 +185,7 @@ jobs: wheel-publish-cugraph-equivariant: needs: wheel-build-cugraph-equivariant secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index d25a0b81b13..dacd9a93399 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -34,29 +34,29 @@ jobs: - wheel-tests-cugraph-equivariant - devcontainer secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.10 checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.10 with: enable_check_generated_files: false conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.10 with: build_type: pull-request node_type: cpu32 conda-cpp-tests: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.10 with: build_type: pull-request conda-cpp-checks: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.10 with: build_type: pull-request enable_check_symbols: true @@ -64,19 +64,19 @@ jobs: conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.10 with: build_type: pull-request conda-python-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.10 with: build_type: pull-request conda-notebook-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -86,7 +86,7 @@ jobs: docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -96,7 +96,7 @@ jobs: wheel-build-pylibcugraph: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_pylibcugraph.sh @@ -107,14 +107,14 @@ jobs: wheel-tests-pylibcugraph: needs: wheel-build-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_pylibcugraph.sh wheel-build-cugraph: needs: wheel-tests-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_cugraph.sh @@ -124,35 +124,35 @@ jobs: wheel-tests-cugraph: needs: wheel-build-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_cugraph.sh wheel-build-nx-cugraph: needs: wheel-tests-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_nx-cugraph.sh wheel-tests-nx-cugraph: needs: wheel-build-nx-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_nx-cugraph.sh wheel-build-cugraph-dgl: needs: wheel-tests-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_cugraph-dgl.sh wheel-tests-cugraph-dgl: needs: wheel-build-cugraph-dgl secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_cugraph-dgl.sh @@ -160,35 +160,35 @@ jobs: wheel-build-cugraph-pyg: needs: wheel-tests-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_cugraph-pyg.sh wheel-tests-cugraph-pyg: needs: wheel-build-cugraph-pyg secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_cugraph-pyg.sh matrix_filter: map(select(.ARCH == "amd64")) wheel-build-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 with: build_type: pull-request script: ci/build_wheel_cugraph-equivariant.sh wheel-tests-cugraph-equivariant: needs: wheel-build-cugraph-equivariant secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: pull-request script: ci/test_wheel_cugraph-equivariant.sh matrix_filter: map(select(.ARCH == "amd64")) devcontainer: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.10 with: arch: '["amd64"]' cuda: '["12.5"]' diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 32447711811..957d29ce72b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ on: jobs: conda-cpp-checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -26,7 +26,7 @@ jobs: symbol_exclusions: (cugraph::ops|hornet|void writeEdgeCountsKernel|void markUniqueOffsetsKernel) conda-cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -34,7 +34,7 @@ jobs: sha: ${{ inputs.sha }} conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -42,7 +42,7 @@ jobs: sha: ${{ inputs.sha }} wheel-tests-pylibcugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -51,7 +51,7 @@ jobs: script: ci/test_wheel_pylibcugraph.sh wheel-tests-cugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -60,7 +60,7 @@ jobs: script: ci/test_wheel_cugraph.sh wheel-tests-nx-cugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -69,7 +69,7 @@ jobs: script: ci/test_wheel_nx-cugraph.sh wheel-tests-cugraph-dgl: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -79,7 +79,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) wheel-tests-cugraph-pyg: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} @@ -89,7 +89,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) wheel-tests-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 with: build_type: nightly branch: ${{ inputs.branch }} From 8b1a437275b2a0c4477afec4cc41eb3281b12517 Mon Sep 17 00:00:00 2001 From: Mike McCarty Date: Thu, 19 Sep 2024 01:43:32 -0400 Subject: [PATCH 04/37] Recommending `miniforge` for conda install (#4650) Recommending `miniforge` for conda install in installation docs. Authors: - Mike McCarty (https://github.com/mmccarty) - Bradley Dice (https://github.com/bdice) - Alex Barghi (https://github.com/alexbarghi-nv) Approvers: - Rick Ratzel (https://github.com/rlratzel) - Bradley Dice (https://github.com/bdice) URL: https://github.com/rapidsai/cugraph/pull/4650 --- .../notebooks/get_node_storage.ipynb | 2 +- .../heterogeneous_dataloader_benchmark.ipynb | 2 +- .../homogenous_dataloader_benchmark.ipynb | 6 +- .../cugraph/notebooks/feature_storage.ipynb | 2 +- .../source/installation/getting_cugraph.md | 2 +- .../installation/getting_wholegraph.md | 2 +- notebooks/demo/mg_pagerank.ipynb | 280 +++++++++--------- 7 files changed, 148 insertions(+), 148 deletions(-) diff --git a/benchmarks/cugraph-dgl/notebooks/get_node_storage.ipynb b/benchmarks/cugraph-dgl/notebooks/get_node_storage.ipynb index 95b456c7812..4681c8ec825 100644 --- a/benchmarks/cugraph-dgl/notebooks/get_node_storage.ipynb +++ b/benchmarks/cugraph-dgl/notebooks/get_node_storage.ipynb @@ -18,7 +18,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } diff --git a/benchmarks/cugraph-dgl/notebooks/heterogeneous_dataloader_benchmark.ipynb b/benchmarks/cugraph-dgl/notebooks/heterogeneous_dataloader_benchmark.ipynb index d3b054bb0ee..2c4a934827a 100644 --- a/benchmarks/cugraph-dgl/notebooks/heterogeneous_dataloader_benchmark.ipynb +++ b/benchmarks/cugraph-dgl/notebooks/heterogeneous_dataloader_benchmark.ipynb @@ -176,7 +176,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/dgl/dataloading/dataloader.py:859: DGLWarning: Dataloader CPU affinity opt is not enabled, consider switching it on (see enable_cpu_affinity() or CPU best practices for DGL [https://docs.dgl.ai/tutorials/cpu/cpu_best_practises.html])\n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/dgl/dataloading/dataloader.py:859: DGLWarning: Dataloader CPU affinity opt is not enabled, consider switching it on (see enable_cpu_affinity() or CPU best practices for DGL [https://docs.dgl.ai/tutorials/cpu/cpu_best_practises.html])\n", " dgl_warning(f'Dataloader CPU affinity opt is not enabled, consider switching it on '\n" ] }, diff --git a/benchmarks/cugraph-dgl/notebooks/homogenous_dataloader_benchmark.ipynb b/benchmarks/cugraph-dgl/notebooks/homogenous_dataloader_benchmark.ipynb index ea1e9b34965..ecd111dabdf 100644 --- a/benchmarks/cugraph-dgl/notebooks/homogenous_dataloader_benchmark.ipynb +++ b/benchmarks/cugraph-dgl/notebooks/homogenous_dataloader_benchmark.ipynb @@ -26,7 +26,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } @@ -190,7 +190,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/dgl/dataloading/dataloader.py:859: DGLWarning: Dataloader CPU affinity opt is not enabled, consider switching it on (see enable_cpu_affinity() or CPU best practices for DGL [https://docs.dgl.ai/tutorials/cpu/cpu_best_practises.html])\n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/dgl/dataloading/dataloader.py:859: DGLWarning: Dataloader CPU affinity opt is not enabled, consider switching it on (see enable_cpu_affinity() or CPU best practices for DGL [https://docs.dgl.ai/tutorials/cpu/cpu_best_practises.html])\n", " dgl_warning(f'Dataloader CPU affinity opt is not enabled, consider switching it on '\n" ] }, @@ -278,7 +278,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/distributed/worker.py:2988: UserWarning: Large object of size 1.42 MiB detected in task graph: \n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/distributed/worker.py:2988: UserWarning: Large object of size 1.42 MiB detected in task graph: \n", " [b'\\xad\\xd1\\xe3\\x9c\\x96\\x83O\\xb3\\xba1\\x86\\x94\\xb6\\ ... =int32), False]\n", "Consider scattering large objects ahead of time\n", "with client.scatter to reduce scheduler burden and \n", diff --git a/benchmarks/cugraph/notebooks/feature_storage.ipynb b/benchmarks/cugraph/notebooks/feature_storage.ipynb index 7413ac00cde..440d76fbdb4 100644 --- a/benchmarks/cugraph/notebooks/feature_storage.ipynb +++ b/benchmarks/cugraph/notebooks/feature_storage.ipynb @@ -18,7 +18,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/datasets/vjawa/miniconda3/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + "/datasets/vjawa/miniforge/envs/all_cuda-115_arch-x86_64/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } diff --git a/docs/cugraph/source/installation/getting_cugraph.md b/docs/cugraph/source/installation/getting_cugraph.md index 41ec9a67e1f..01bc9e379c9 100644 --- a/docs/cugraph/source/installation/getting_cugraph.md +++ b/docs/cugraph/source/installation/getting_cugraph.md @@ -21,7 +21,7 @@ The RAPIDS Docker containers contain all RAPIDS packages, including all from cuG ## Conda -It is easy to install cuGraph using conda. You can get a minimal conda installation with [Miniconda](https://conda.io/miniconda.html) or get the full installation with [Anaconda](https://www.anaconda.com/download). +It is easy to install cuGraph using conda. You can get a minimal conda installation with [miniforge](https://github.com/conda-forge/miniforge). cuGraph Conda packages * cugraph - this will also import: diff --git a/docs/cugraph/source/wholegraph/installation/getting_wholegraph.md b/docs/cugraph/source/wholegraph/installation/getting_wholegraph.md index 57314dcd426..80c666d6593 100644 --- a/docs/cugraph/source/wholegraph/installation/getting_wholegraph.md +++ b/docs/cugraph/source/wholegraph/installation/getting_wholegraph.md @@ -21,7 +21,7 @@ The RAPIDS Docker containers (as of Release 23.10) contain all RAPIDS packages, ## Conda -It is easy to install WholeGraph using conda. You can get a minimal conda installation with [Miniconda](https://conda.io/miniconda.html) or get the full installation with [Anaconda](https://www.anaconda.com/download). +It is easy to install WholeGraph using conda. You can get a minimal conda installation with [miniforge](https://github.com/conda-forge/miniforge). WholeGraph conda packages * libwholegraph diff --git a/notebooks/demo/mg_pagerank.ipynb b/notebooks/demo/mg_pagerank.ipynb index bb333048450..e3314f80b3c 100644 --- a/notebooks/demo/mg_pagerank.ipynb +++ b/notebooks/demo/mg_pagerank.ipynb @@ -219,250 +219,250 @@ "text": [ "2023-05-12 09:25:01,974 - distributed.sizeof - WARNING - Sizeof calculation failed. Defaulting to 0.95 MiB\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", " return sizeof(obj)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", " return meth(arg, *args, **kwargs)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", " + df._index.memory_usage()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", " if self.levels:\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", " self._compute_levels_and_codes()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", " code, cats = cudf.Series._from_data({None: col}).factorize()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", " return cudf.core.algorithms.factorize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", " labels = values._column._label_encoding(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", " order = order.take(left_gather_map, check_bounds=False).argsort()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", " return self.as_frame()._get_sorted_inds(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", " return libcudf.sort.order_by(to_sort, ascending, na_position)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", " File \"sort.pyx\", line 141, in cudf._lib.sort.order_by\n", - "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniconda3/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", + "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniforge/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", "2023-05-12 09:25:01,976 - distributed.sizeof - WARNING - Sizeof calculation failed. Defaulting to 0.95 MiB\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", " return sizeof(obj)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", " return meth(arg, *args, **kwargs)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", " + df._index.memory_usage()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", " if self.levels:\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", " self._compute_levels_and_codes()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", " code, cats = cudf.Series._from_data({None: col}).factorize()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", " return cudf.core.algorithms.factorize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", " labels = values._column._label_encoding(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", " order = order.take(left_gather_map, check_bounds=False).argsort()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", " return self.as_frame()._get_sorted_inds(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", " return libcudf.sort.order_by(to_sort, ascending, na_position)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", " File \"sort.pyx\", line 141, in cudf._lib.sort.order_by\n", - "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniconda3/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", + "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniforge/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", "2023-05-12 09:25:03,767 - distributed.sizeof - WARNING - Sizeof calculation failed. Defaulting to 0.95 MiB\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", " return sizeof(obj)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", " return meth(arg, *args, **kwargs)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", " + df._index.memory_usage()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", " if self.levels:\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", " self._compute_levels_and_codes()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", " code, cats = cudf.Series._from_data({None: col}).factorize()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", " return cudf.core.algorithms.factorize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", " labels = values._column._label_encoding(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", " order = order.take(left_gather_map, check_bounds=False).argsort()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", " return self.as_frame()._get_sorted_inds(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", " return libcudf.sort.order_by(to_sort, ascending, na_position)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", " File \"sort.pyx\", line 141, in cudf._lib.sort.order_by\n", - "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniconda3/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", + "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniforge/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", "2023-05-12 09:25:03,768 - distributed.sizeof - WARNING - Sizeof calculation failed. Defaulting to 0.95 MiB\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/sizeof.py\", line 17, in safe_sizeof\n", " return sizeof(obj)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/utils.py\", line 642, in __call__\n", " return meth(arg, *args, **kwargs)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask_cudf/backends.py\", line 430, in sizeof_cudf_dataframe\n", " + df._index.memory_usage()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 1594, in memory_usage\n", " if self.levels:\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 605, in levels\n", " self._compute_levels_and_codes()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/multiindex.py\", line 748, in _compute_levels_and_codes\n", " code, cats = cudf.Series._from_data({None: col}).factorize()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/single_column_frame.py\", line 311, in factorize\n", " return cudf.core.algorithms.factorize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/algorithms.py\", line 138, in factorize\n", " labels = values._column._label_encoding(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1385, in _label_encoding\n", " order = order.take(left_gather_map, check_bounds=False).argsort()\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1101, in argsort\n", " return self.as_frame()._get_sorted_inds(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 1572, in _get_sorted_inds\n", " return libcudf.sort.order_by(to_sort, ascending, na_position)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", " File \"sort.pyx\", line 141, in cudf._lib.sort.order_by\n", - "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniconda3/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", + "MemoryError: std::bad_alloc: out_of_memory: CUDA error at: /home/dacosta/miniforge/envs/cugraph_0411/include/rmm/mr/device/cuda_memory_resource.hpp\n", "2023-05-12 09:25:03,820 - distributed.worker - ERROR - Could not deserialize task ('len-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d', 1)\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2923, in loads_function\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2923, in loads_function\n", " result = cache_loads[bytes_object]\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/collections.py\", line 24, in __getitem__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/collections.py\", line 24, in __getitem__\n", " value = super().__getitem__(key)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/collections/__init__.py\", line 1106, in __getitem__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/collections/__init__.py\", line 1106, in __getitem__\n", " raise KeyError(key)\n", "KeyError: b'\\x80\\x05\\x95>\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x11dask.optimization\\x94\\x8c\\x10SubgraphCallable\\x94\\x93\\x94(}\\x94(\\x8cKlen-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d\\x94\\x8cZassign-getitem-len-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d\\x94\\x8c*rename-01db283bd79fee66f232920c8dc6b55e_.0\\x94\\x8c;getitem-to_frame-rename-01db283bd79fee66f232920c8dc6b55e_.0\\x94\\x8c+getitem-3499fd71ac25ebbc1a06991edea6067c_.0\\x94\\x8c\\t_operator\\x94\\x8c\\x07getitem\\x94\\x93\\x94\\x8c/reset_index-f4c18304ca92859ccd09f44cf89b4b43_.0\\x94\\x8c\\x13__dask_blockwise__1\\x94\\x87\\x94h\\x0c(\\x8c\\ndask.utils\\x94\\x8c\\x05apply\\x94\\x93\\x94h\\x0f\\x8c\\x0cmethodcaller\\x94\\x93\\x94\\x8c\\x0breset_index\\x94\\x85\\x94R\\x94]\\x94\\x8c\\x13__dask_blockwise__5\\x94a\\x8c\\x08builtins\\x94\\x8c\\x04dict\\x94\\x93\\x94]\\x94]\\x94(\\x8c\\x04drop\\x94\\x89ea\\x86\\x94t\\x94h\\x07(h\\x11\\x8c\\x13dask.dataframe.core\\x94\\x8c\\x11apply_and_enforce\\x94\\x93\\x94]\\x94((h\\x11h#]\\x94h\\x0bh\\x0c\\x8c\\x13__dask_blockwise__0\\x94\\x87\\x94ah\\x1b]\\x94(]\\x94(\\x8c\\x05_func\\x94h\\x13\\x8c\\x08to_frame\\x94\\x85\\x94R\\x94e]\\x94(\\x8c\\x05_meta\\x94\\x8c\\x08builtins\\x94\\x8c\\x07getattr\\x94\\x93\\x94\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94\\x8c\\x10host_deserialize\\x94\\x86\\x94R\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94C0\\x80\\x04\\x95%\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94.\\x94\\x8c\\x0ccolumn_names\\x94C\\x14\\x80\\x04\\x95\\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x03src\\x94\\x85\\x94.\\x94\\x8c\\x07columns\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94C=\\x80\\x04\\x952\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x1acudf.core.column.numerical\\x94\\x8c\\x0fNumericalColumn\\x94\\x93\\x94.\\x94\\x8c\\x05dtype\\x94CB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i4\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94\\x8c\\x18dtype-is-cudf-serialized\\x94\\x89\\x8c\\x04data\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94CI\\x80\\x04\\x95>\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x14SpillableBufferSlice\\x94\\x93\\x94.\\x94\\x8c\\x0bframe_count\\x94K\\x01u\\x8c\\x04mask\\x94}\\x94(hGCD\\x80\\x04\\x959\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x0fSpillableBuffer\\x94\\x93\\x94.\\x94hIK\\x01u\\x8c\\x04size\\x94K\\x00hIK\\x02u\\x85\\x94\\x8c\\x05index\\x94}\\x94(\\x8c\\x0cindex_column\\x94}\\x94(\\x8c\\x05start\\x94K\\x00\\x8c\\x04stop\\x94K\\x00\\x8c\\x04step\\x94K\\x01u\\x8c\\x04name\\x94C\\x04\\x80\\x04N.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i8\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94\\x8c\\x0ftype-serialized\\x94C-\\x80\\x04\\x95\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0fcudf.core.index\\x94\\x8c\\nRangeIndex\\x94\\x93\\x94.\\x94hIK\\x00u\\x8c\\x11index_frame_count\\x94K\\x00\\x8c\\x07is-cuda\\x94]\\x94(\\x88\\x88e\\x8c\\x07lengths\\x94]\\x94(K\\x00K\\x00e\\x8c\\twriteable\\x94NN\\x86\\x94u]\\x94(\\x8c\\x12numpy.core.numeric\\x94\\x8c\\x0b_frombuffer\\x94\\x93\\x94(C\\x00\\x94\\x8c\\x05numpy\\x94hB\\x93\\x94\\x8c\\x02u1\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01|\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94bK\\x00\\x85\\x94\\x8c\\x01C\\x94t\\x94R\\x94he(C\\x00\\x94hkK\\x00\\x85\\x94hot\\x94R\\x94e\\x86\\x94R\\x94ee\\x86\\x94t\\x94\\x8c\\x13__dask_blockwise__2\\x94eh\\x1b]\\x94(]\\x94(h*h\\x13\\x8c\\x06rename\\x94\\x85\\x94R\\x94e]\\x94(h/h2h5h6\\x86\\x94R\\x94}\\x94(h:C0\\x80\\x04\\x95%\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94.\\x94h}\\x94(h@C=\\x80\\x04\\x952\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x1acudf.core.column.numerical\\x94\\x8c\\x0fNumericalColumn\\x94\\x93\\x94.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i4\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94hD\\x89hE}\\x94(hGCI\\x80\\x04\\x95>\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x14SpillableBufferSlice\\x94\\x93\\x94.\\x94hIK\\x01uhMK\\x00hIK\\x01u\\x85\\x94hO}\\x94(hQ}\\x94(hSK\\x00hTK\\x00hUK\\x01uhVC\\x04\\x80\\x04N.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i8\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94hYC-\\x80\\x04\\x95\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0fcudf.core.index\\x94\\x8c\\nRangeIndex\\x94\\x93\\x94.\\x94hIK\\x00uh[K\\x00h\\\\]\\x94\\x88ah^]\\x94K\\x00ah`N\\x85\\x94u]\\x94he(C\\x00\\x94hkK\\x00\\x85\\x94hot\\x94R\\x94a\\x86\\x94R\\x94e]\\x94(h>h\\x1b]\\x94]\\x94(\\x8c\\x03src\\x94h\\x9eea\\x86\\x94ee\\x86\\x94t\\x94h\\x05(h\\x11h!\\x8c\\x10_reduction_chunk\\x94\\x93\\x94]\\x94h\\x0b(\\x8c\\x16dask.dataframe.methods\\x94\\x8c\\x06assign\\x94\\x93\\x94h\\x06h\\rh\\x08t\\x94h&\\x87\\x94ah\\x1b]\\x94]\\x94(\\x8c\\taca_chunk\\x94h0\\x8c\\x03len\\x94\\x93\\x94ea\\x86\\x94t\\x94\\x8c\\x13__dask_blockwise__0\\x94h\\x9e\\x8c\\x13__dask_blockwise__1\\x94\\x8c\\x03dst\\x94\\x8c\\x13__dask_blockwise__2\\x94N\\x8c\\x13__dask_blockwise__3\\x94\\x8c)to_frame-804980ae30b71d28f0a6bd3d5b7610f9\\x94\\x8c\\x13__dask_blockwise__4\\x94\\x8c(getitem-15414b72be12e28054238b44933937ab\\x94\\x8c\\x13__dask_blockwise__6\\x94\\x8c3cudf-aggregate-agg-c50c2d97de169ca4f41e43a92a042630\\x94uh\\x04\\x8c\\x13__dask_blockwise__5\\x94\\x85\\x94\\x8c6subgraph_callable-b4ca530e-8895-432e-b553-40a7b5892ab2\\x94t\\x94R\\x94.'\n", "\n", "During handling of the above exception, another exception occurred:\n", "\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2244, in execute\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2244, in execute\n", " function, args, kwargs = await self._maybe_deserialize_task(ts)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2216, in _maybe_deserialize_task\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2216, in _maybe_deserialize_task\n", " function, args, kwargs = _deserialize(*ts.run_spec)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2937, in _deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2937, in _deserialize\n", " function = loads_function(function)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2925, in loads_function\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2925, in loads_function\n", " result = pickle.loads(bytes_object)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py\", line 96, in loads\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py\", line 96, in loads\n", " return pickle.loads(x)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 176, in host_deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 176, in host_deserialize\n", " obj = cls.device_deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 130, in device_deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 130, in device_deserialize\n", " return typ.deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py\", line 1019, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py\", line 1019, in deserialize\n", " obj = super().deserialize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 106, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 106, in deserialize\n", " columns = deserialize_columns(header[\"columns\"], frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 2450, in deserialize_columns\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 2450, in deserialize_columns\n", " colobj = col_typ.deserialize(meta, frames[:col_frame_count])\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1216, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1216, in deserialize\n", " data, frames = unpack(header[\"data\"], frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1204, in unpack\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1204, in unpack\n", " obj = klass.deserialize(header, frames[:count])\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 574, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 574, in deserialize\n", " return SpillableBuffer.deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py\", line 335, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py\", line 335, in deserialize\n", " return cls._from_device_memory(frame)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 235, in _from_device_memory\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 235, in _from_device_memory\n", " ret._finalize_init(ptr_desc={\"type\": \"gpu\"}, exposed=exposed)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 206, in _finalize_init\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 206, in _finalize_init\n", " raise ValueError(\n", "ValueError: cannot create without a global spill manager\n", "2023-05-12 09:25:03,817 - distributed.worker - ERROR - Could not deserialize task ('len-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d', 0)\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2923, in loads_function\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2923, in loads_function\n", " result = cache_loads[bytes_object]\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/collections.py\", line 24, in __getitem__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/collections.py\", line 24, in __getitem__\n", " value = super().__getitem__(key)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/collections/__init__.py\", line 1106, in __getitem__\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/collections/__init__.py\", line 1106, in __getitem__\n", " raise KeyError(key)\n", "KeyError: b'\\x80\\x05\\x95>\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x11dask.optimization\\x94\\x8c\\x10SubgraphCallable\\x94\\x93\\x94(}\\x94(\\x8cKlen-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d\\x94\\x8cZassign-getitem-len-chunk-319fe46af5510615b2fae86c6e732896-841a12bf4568ebb80eb2030cc4d9651d\\x94\\x8c*rename-01db283bd79fee66f232920c8dc6b55e_.0\\x94\\x8c;getitem-to_frame-rename-01db283bd79fee66f232920c8dc6b55e_.0\\x94\\x8c+getitem-3499fd71ac25ebbc1a06991edea6067c_.0\\x94\\x8c\\t_operator\\x94\\x8c\\x07getitem\\x94\\x93\\x94\\x8c/reset_index-f4c18304ca92859ccd09f44cf89b4b43_.0\\x94\\x8c\\x13__dask_blockwise__1\\x94\\x87\\x94h\\x0c(\\x8c\\ndask.utils\\x94\\x8c\\x05apply\\x94\\x93\\x94h\\x0f\\x8c\\x0cmethodcaller\\x94\\x93\\x94\\x8c\\x0breset_index\\x94\\x85\\x94R\\x94]\\x94\\x8c\\x13__dask_blockwise__5\\x94a\\x8c\\x08builtins\\x94\\x8c\\x04dict\\x94\\x93\\x94]\\x94]\\x94(\\x8c\\x04drop\\x94\\x89ea\\x86\\x94t\\x94h\\x07(h\\x11\\x8c\\x13dask.dataframe.core\\x94\\x8c\\x11apply_and_enforce\\x94\\x93\\x94]\\x94((h\\x11h#]\\x94h\\x0bh\\x0c\\x8c\\x13__dask_blockwise__0\\x94\\x87\\x94ah\\x1b]\\x94(]\\x94(\\x8c\\x05_func\\x94h\\x13\\x8c\\x08to_frame\\x94\\x85\\x94R\\x94e]\\x94(\\x8c\\x05_meta\\x94\\x8c\\x08builtins\\x94\\x8c\\x07getattr\\x94\\x93\\x94\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94\\x8c\\x10host_deserialize\\x94\\x86\\x94R\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94C0\\x80\\x04\\x95%\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94.\\x94\\x8c\\x0ccolumn_names\\x94C\\x14\\x80\\x04\\x95\\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x03src\\x94\\x85\\x94.\\x94\\x8c\\x07columns\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94C=\\x80\\x04\\x952\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x1acudf.core.column.numerical\\x94\\x8c\\x0fNumericalColumn\\x94\\x93\\x94.\\x94\\x8c\\x05dtype\\x94CB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i4\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94\\x8c\\x18dtype-is-cudf-serialized\\x94\\x89\\x8c\\x04data\\x94}\\x94(\\x8c\\x0ftype-serialized\\x94CI\\x80\\x04\\x95>\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x14SpillableBufferSlice\\x94\\x93\\x94.\\x94\\x8c\\x0bframe_count\\x94K\\x01u\\x8c\\x04mask\\x94}\\x94(hGCD\\x80\\x04\\x959\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x0fSpillableBuffer\\x94\\x93\\x94.\\x94hIK\\x01u\\x8c\\x04size\\x94K\\x00hIK\\x02u\\x85\\x94\\x8c\\x05index\\x94}\\x94(\\x8c\\x0cindex_column\\x94}\\x94(\\x8c\\x05start\\x94K\\x00\\x8c\\x04stop\\x94K\\x00\\x8c\\x04step\\x94K\\x01u\\x8c\\x04name\\x94C\\x04\\x80\\x04N.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i8\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94\\x8c\\x0ftype-serialized\\x94C-\\x80\\x04\\x95\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0fcudf.core.index\\x94\\x8c\\nRangeIndex\\x94\\x93\\x94.\\x94hIK\\x00u\\x8c\\x11index_frame_count\\x94K\\x00\\x8c\\x07is-cuda\\x94]\\x94(\\x88\\x88e\\x8c\\x07lengths\\x94]\\x94(K\\x00K\\x00e\\x8c\\twriteable\\x94NN\\x86\\x94u]\\x94(\\x8c\\x12numpy.core.numeric\\x94\\x8c\\x0b_frombuffer\\x94\\x93\\x94(C\\x00\\x94\\x8c\\x05numpy\\x94hB\\x93\\x94\\x8c\\x02u1\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01|\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94bK\\x00\\x85\\x94\\x8c\\x01C\\x94t\\x94R\\x94he(C\\x00\\x94hkK\\x00\\x85\\x94hot\\x94R\\x94e\\x86\\x94R\\x94ee\\x86\\x94t\\x94\\x8c\\x13__dask_blockwise__2\\x94eh\\x1b]\\x94(]\\x94(h*h\\x13\\x8c\\x06rename\\x94\\x85\\x94R\\x94e]\\x94(h/h2h5h6\\x86\\x94R\\x94}\\x94(h:C0\\x80\\x04\\x95%\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x13cudf.core.dataframe\\x94\\x8c\\tDataFrame\\x94\\x93\\x94.\\x94h}\\x94(h@C=\\x80\\x04\\x952\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x1acudf.core.column.numerical\\x94\\x8c\\x0fNumericalColumn\\x94\\x93\\x94.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i4\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94hD\\x89hE}\\x94(hGCI\\x80\\x04\\x95>\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c!cudf.core.buffer.spillable_buffer\\x94\\x8c\\x14SpillableBufferSlice\\x94\\x93\\x94.\\x94hIK\\x01uhMK\\x00hIK\\x01u\\x85\\x94hO}\\x94(hQ}\\x94(hSK\\x00hTK\\x00hUK\\x01uhVC\\x04\\x80\\x04N.\\x94hBCB\\x80\\x04\\x957\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x05numpy\\x94\\x8c\\x05dtype\\x94\\x93\\x94\\x8c\\x02i8\\x94\\x89\\x88\\x87\\x94R\\x94(K\\x03\\x8c\\x01<\\x94NNNJ\\xff\\xff\\xff\\xffJ\\xff\\xff\\xff\\xffK\\x00t\\x94b.\\x94hYC-\\x80\\x04\\x95\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0fcudf.core.index\\x94\\x8c\\nRangeIndex\\x94\\x93\\x94.\\x94hIK\\x00uh[K\\x00h\\\\]\\x94\\x88ah^]\\x94K\\x00ah`N\\x85\\x94u]\\x94he(C\\x00\\x94hkK\\x00\\x85\\x94hot\\x94R\\x94a\\x86\\x94R\\x94e]\\x94(h>h\\x1b]\\x94]\\x94(\\x8c\\x03src\\x94h\\x9eea\\x86\\x94ee\\x86\\x94t\\x94h\\x05(h\\x11h!\\x8c\\x10_reduction_chunk\\x94\\x93\\x94]\\x94h\\x0b(\\x8c\\x16dask.dataframe.methods\\x94\\x8c\\x06assign\\x94\\x93\\x94h\\x06h\\rh\\x08t\\x94h&\\x87\\x94ah\\x1b]\\x94]\\x94(\\x8c\\taca_chunk\\x94h0\\x8c\\x03len\\x94\\x93\\x94ea\\x86\\x94t\\x94\\x8c\\x13__dask_blockwise__0\\x94h\\x9e\\x8c\\x13__dask_blockwise__1\\x94\\x8c\\x03dst\\x94\\x8c\\x13__dask_blockwise__2\\x94N\\x8c\\x13__dask_blockwise__3\\x94\\x8c)to_frame-804980ae30b71d28f0a6bd3d5b7610f9\\x94\\x8c\\x13__dask_blockwise__4\\x94\\x8c(getitem-15414b72be12e28054238b44933937ab\\x94\\x8c\\x13__dask_blockwise__6\\x94\\x8c3cudf-aggregate-agg-c50c2d97de169ca4f41e43a92a042630\\x94uh\\x04\\x8c\\x13__dask_blockwise__5\\x94\\x85\\x94\\x8c6subgraph_callable-b4ca530e-8895-432e-b553-40a7b5892ab2\\x94t\\x94R\\x94.'\n", "\n", "During handling of the above exception, another exception occurred:\n", "\n", "Traceback (most recent call last):\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2244, in execute\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2244, in execute\n", " function, args, kwargs = await self._maybe_deserialize_task(ts)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2216, in _maybe_deserialize_task\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2216, in _maybe_deserialize_task\n", " function, args, kwargs = _deserialize(*ts.run_spec)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py\", line 79, in inner\n", " return func(*args, **kwds)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2937, in _deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2937, in _deserialize\n", " function = loads_function(function)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2925, in loads_function\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py\", line 2925, in loads_function\n", " result = pickle.loads(bytes_object)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py\", line 96, in loads\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py\", line 96, in loads\n", " return pickle.loads(x)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 176, in host_deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 176, in host_deserialize\n", " obj = cls.device_deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 130, in device_deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py\", line 130, in device_deserialize\n", " return typ.deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py\", line 1019, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py\", line 1019, in deserialize\n", " obj = super().deserialize(\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 106, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py\", line 106, in deserialize\n", " columns = deserialize_columns(header[\"columns\"], frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 2450, in deserialize_columns\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 2450, in deserialize_columns\n", " colobj = col_typ.deserialize(meta, frames[:col_frame_count])\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1216, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1216, in deserialize\n", " data, frames = unpack(header[\"data\"], frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1204, in unpack\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py\", line 1204, in unpack\n", " obj = klass.deserialize(header, frames[:count])\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 574, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 574, in deserialize\n", " return SpillableBuffer.deserialize(header, frames)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py\", line 335, in deserialize\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py\", line 335, in deserialize\n", " return cls._from_device_memory(frame)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 235, in _from_device_memory\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 235, in _from_device_memory\n", " ret._finalize_init(ptr_desc={\"type\": \"gpu\"}, exposed=exposed)\n", - " File \"/home/dacosta/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 206, in _finalize_init\n", + " File \"/home/dacosta/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py\", line 206, in _finalize_init\n", " raise ValueError(\n", "ValueError: cannot create without a global spill manager\n" ] @@ -475,34 +475,34 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[6], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m# Create a directed graph using the source (src) and destination (dst) vertex pairs from the Dataframe \u001b[39;00m\n\u001b[1;32m 2\u001b[0m G \u001b[39m=\u001b[39m cugraph\u001b[39m.\u001b[39mGraph(directed\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[0;32m----> 3\u001b[0m G\u001b[39m.\u001b[39;49mfrom_dask_cudf_edgelist(e_list, source\u001b[39m=\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39msrc\u001b[39;49m\u001b[39m'\u001b[39;49m, destination\u001b[39m=\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mdst\u001b[39;49m\u001b[39m'\u001b[39;49m)\n\u001b[1;32m 5\u001b[0m \u001b[39m# Print time\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39mRead, load and renumber: \u001b[39m\u001b[39m\"\u001b[39m, time\u001b[39m.\u001b[39mtime()\u001b[39m-\u001b[39mt_start, \u001b[39m\"\u001b[39m\u001b[39ms\u001b[39m\u001b[39m\"\u001b[39m)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cugraph/structure/graph_classes.py:309\u001b[0m, in \u001b[0;36mGraph.from_dask_cudf_edgelist\u001b[0;34m(self, input_ddf, source, destination, edge_attr, renumber, store_transposed, legacy_renum_only)\u001b[0m\n\u001b[1;32m 307\u001b[0m \u001b[39melif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_Impl\u001b[39m.\u001b[39medgelist \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[1;32m 308\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mRuntimeError\u001b[39;00m(\u001b[39m\"\u001b[39m\u001b[39mGraph already has values\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[0;32m--> 309\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_Impl\u001b[39m.\u001b[39;49m_simpleDistributedGraphImpl__from_edgelist(\n\u001b[1;32m 310\u001b[0m input_ddf,\n\u001b[1;32m 311\u001b[0m source,\n\u001b[1;32m 312\u001b[0m destination,\n\u001b[1;32m 313\u001b[0m edge_attr,\n\u001b[1;32m 314\u001b[0m renumber,\n\u001b[1;32m 315\u001b[0m store_transposed,\n\u001b[1;32m 316\u001b[0m legacy_renum_only,\n\u001b[1;32m 317\u001b[0m )\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cugraph/structure/graph_implementation/simpleDistributedGraph.py:272\u001b[0m, in \u001b[0;36msimpleDistributedGraphImpl.__from_edgelist\u001b[0;34m(self, input_ddf, source, destination, edge_attr, renumber, store_transposed, legacy_renum_only)\u001b[0m\n\u001b[1;32m 268\u001b[0m dst_col_name \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mrenumber_map\u001b[39m.\u001b[39mrenumbered_dst_col_name\n\u001b[1;32m 270\u001b[0m ddf \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39medgelist\u001b[39m.\u001b[39medgelist_df\n\u001b[0;32m--> 272\u001b[0m num_edges \u001b[39m=\u001b[39m \u001b[39mlen\u001b[39;49m(ddf)\n\u001b[1;32m 273\u001b[0m edge_data \u001b[39m=\u001b[39m get_distributed_data(ddf)\n\u001b[1;32m 275\u001b[0m graph_props \u001b[39m=\u001b[39m GraphProperties(\n\u001b[1;32m 276\u001b[0m is_multigraph\u001b[39m=\u001b[39m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mproperties\u001b[39m.\u001b[39mmulti_edge,\n\u001b[1;32m 277\u001b[0m is_symmetric\u001b[39m=\u001b[39m\u001b[39mnot\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mproperties\u001b[39m.\u001b[39mdirected,\n\u001b[1;32m 278\u001b[0m )\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/dataframe/core.py:4775\u001b[0m, in \u001b[0;36mDataFrame.__len__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 4773\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39m\u001b[39m__len__\u001b[39m()\n\u001b[1;32m 4774\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 4775\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mlen\u001b[39;49m(s)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/dataframe/core.py:843\u001b[0m, in \u001b[0;36m_Frame.__len__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__len__\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[1;32m 841\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mreduction(\n\u001b[1;32m 842\u001b[0m \u001b[39mlen\u001b[39;49m, np\u001b[39m.\u001b[39;49msum, token\u001b[39m=\u001b[39;49m\u001b[39m\"\u001b[39;49m\u001b[39mlen\u001b[39;49m\u001b[39m\"\u001b[39;49m, meta\u001b[39m=\u001b[39;49m\u001b[39mint\u001b[39;49m, split_every\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m\n\u001b[0;32m--> 843\u001b[0m )\u001b[39m.\u001b[39;49mcompute()\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/base.py:314\u001b[0m, in \u001b[0;36mDaskMethodsMixin.compute\u001b[0;34m(self, **kwargs)\u001b[0m\n\u001b[1;32m 290\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mcompute\u001b[39m(\u001b[39mself\u001b[39m, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs):\n\u001b[1;32m 291\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"Compute this dask collection\u001b[39;00m\n\u001b[1;32m 292\u001b[0m \n\u001b[1;32m 293\u001b[0m \u001b[39m This turns a lazy Dask collection into its in-memory equivalent.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[39m dask.base.compute\u001b[39;00m\n\u001b[1;32m 313\u001b[0m \u001b[39m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 314\u001b[0m (result,) \u001b[39m=\u001b[39m compute(\u001b[39mself\u001b[39;49m, traverse\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 315\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/dask/base.py:599\u001b[0m, in \u001b[0;36mcompute\u001b[0;34m(traverse, optimize_graph, scheduler, get, *args, **kwargs)\u001b[0m\n\u001b[1;32m 596\u001b[0m keys\u001b[39m.\u001b[39mappend(x\u001b[39m.\u001b[39m__dask_keys__())\n\u001b[1;32m 597\u001b[0m postcomputes\u001b[39m.\u001b[39mappend(x\u001b[39m.\u001b[39m__dask_postcompute__())\n\u001b[0;32m--> 599\u001b[0m results \u001b[39m=\u001b[39m schedule(dsk, keys, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 600\u001b[0m \u001b[39mreturn\u001b[39;00m repack([f(r, \u001b[39m*\u001b[39ma) \u001b[39mfor\u001b[39;00m r, (f, a) \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(results, postcomputes)])\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:3186\u001b[0m, in \u001b[0;36mClient.get\u001b[0;34m(self, dsk, keys, workers, allow_other_workers, resources, sync, asynchronous, direct, retries, priority, fifo_timeout, actors, **kwargs)\u001b[0m\n\u001b[1;32m 3184\u001b[0m should_rejoin \u001b[39m=\u001b[39m \u001b[39mFalse\u001b[39;00m\n\u001b[1;32m 3185\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m-> 3186\u001b[0m results \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mgather(packed, asynchronous\u001b[39m=\u001b[39;49masynchronous, direct\u001b[39m=\u001b[39;49mdirect)\n\u001b[1;32m 3187\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 3188\u001b[0m \u001b[39mfor\u001b[39;00m f \u001b[39min\u001b[39;00m futures\u001b[39m.\u001b[39mvalues():\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:2345\u001b[0m, in \u001b[0;36mClient.gather\u001b[0;34m(self, futures, errors, direct, asynchronous)\u001b[0m\n\u001b[1;32m 2343\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 2344\u001b[0m local_worker \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[0;32m-> 2345\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49msync(\n\u001b[1;32m 2346\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_gather,\n\u001b[1;32m 2347\u001b[0m futures,\n\u001b[1;32m 2348\u001b[0m errors\u001b[39m=\u001b[39;49merrors,\n\u001b[1;32m 2349\u001b[0m direct\u001b[39m=\u001b[39;49mdirect,\n\u001b[1;32m 2350\u001b[0m local_worker\u001b[39m=\u001b[39;49mlocal_worker,\n\u001b[1;32m 2351\u001b[0m asynchronous\u001b[39m=\u001b[39;49masynchronous,\n\u001b[1;32m 2352\u001b[0m )\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:349\u001b[0m, in \u001b[0;36mSyncMethodMixin.sync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[39mreturn\u001b[39;00m future\n\u001b[1;32m 348\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 349\u001b[0m \u001b[39mreturn\u001b[39;00m sync(\n\u001b[1;32m 350\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mloop, func, \u001b[39m*\u001b[39;49margs, callback_timeout\u001b[39m=\u001b[39;49mcallback_timeout, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs\n\u001b[1;32m 351\u001b[0m )\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:416\u001b[0m, in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[39mif\u001b[39;00m error:\n\u001b[1;32m 415\u001b[0m typ, exc, tb \u001b[39m=\u001b[39m error\n\u001b[0;32m--> 416\u001b[0m \u001b[39mraise\u001b[39;00m exc\u001b[39m.\u001b[39mwith_traceback(tb)\n\u001b[1;32m 417\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 418\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:389\u001b[0m, in \u001b[0;36msync..f\u001b[0;34m()\u001b[0m\n\u001b[1;32m 387\u001b[0m future \u001b[39m=\u001b[39m wait_for(future, callback_timeout)\n\u001b[1;32m 388\u001b[0m future \u001b[39m=\u001b[39m asyncio\u001b[39m.\u001b[39mensure_future(future)\n\u001b[0;32m--> 389\u001b[0m result \u001b[39m=\u001b[39m \u001b[39myield\u001b[39;00m future\n\u001b[1;32m 390\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 391\u001b[0m error \u001b[39m=\u001b[39m sys\u001b[39m.\u001b[39mexc_info()\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/tornado/gen.py:769\u001b[0m, in \u001b[0;36mRunner.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 766\u001b[0m exc_info \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[1;32m 768\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 769\u001b[0m value \u001b[39m=\u001b[39m future\u001b[39m.\u001b[39;49mresult()\n\u001b[1;32m 770\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 771\u001b[0m exc_info \u001b[39m=\u001b[39m sys\u001b[39m.\u001b[39mexc_info()\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:2208\u001b[0m, in \u001b[0;36mClient._gather\u001b[0;34m(self, futures, errors, direct, local_worker)\u001b[0m\n\u001b[1;32m 2206\u001b[0m exc \u001b[39m=\u001b[39m CancelledError(key)\n\u001b[1;32m 2207\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 2208\u001b[0m \u001b[39mraise\u001b[39;00m exception\u001b[39m.\u001b[39mwith_traceback(traceback)\n\u001b[1;32m 2209\u001b[0m \u001b[39mraise\u001b[39;00m exc\n\u001b[1;32m 2210\u001b[0m \u001b[39mif\u001b[39;00m errors \u001b[39m==\u001b[39m \u001b[39m\"\u001b[39m\u001b[39mskip\u001b[39m\u001b[39m\"\u001b[39m:\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/contextlib.py:79\u001b[0m, in \u001b[0;36minner\u001b[0;34m()\u001b[0m\n\u001b[1;32m 76\u001b[0m \u001b[39m@wraps\u001b[39m(func)\n\u001b[1;32m 77\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39minner\u001b[39m(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwds):\n\u001b[1;32m 78\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_recreate_cm():\n\u001b[0;32m---> 79\u001b[0m \u001b[39mreturn\u001b[39;00m func(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwds)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py:2937\u001b[0m, in \u001b[0;36m_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2934\u001b[0m \u001b[39m# Some objects require threadlocal state during deserialization, e.g. to\u001b[39;00m\n\u001b[1;32m 2935\u001b[0m \u001b[39m# detect the current worker\u001b[39;00m\n\u001b[1;32m 2936\u001b[0m \u001b[39mif\u001b[39;00m function \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m-> 2937\u001b[0m function \u001b[39m=\u001b[39m loads_function(function)\n\u001b[1;32m 2938\u001b[0m \u001b[39mif\u001b[39;00m args \u001b[39mand\u001b[39;00m \u001b[39misinstance\u001b[39m(args, \u001b[39mbytes\u001b[39m):\n\u001b[1;32m 2939\u001b[0m args \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(args)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py:2925\u001b[0m, in \u001b[0;36mloads_function\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2923\u001b[0m result \u001b[39m=\u001b[39m cache_loads[bytes_object]\n\u001b[1;32m 2924\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n\u001b[0;32m-> 2925\u001b[0m result \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(bytes_object)\n\u001b[1;32m 2926\u001b[0m cache_loads[bytes_object] \u001b[39m=\u001b[39m result\n\u001b[1;32m 2927\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py:96\u001b[0m, in \u001b[0;36mloads\u001b[0;34m()\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[39mreturn\u001b[39;00m pickle\u001b[39m.\u001b[39mloads(x, buffers\u001b[39m=\u001b[39mbuffers)\n\u001b[1;32m 95\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m---> 96\u001b[0m \u001b[39mreturn\u001b[39;00m pickle\u001b[39m.\u001b[39mloads(x)\n\u001b[1;32m 97\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 98\u001b[0m logger\u001b[39m.\u001b[39minfo(\u001b[39m\"\u001b[39m\u001b[39mFailed to deserialize \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m\"\u001b[39m, x[:\u001b[39m10000\u001b[39m], exc_info\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py:176\u001b[0m, in \u001b[0;36mhost_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 154\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"Perform device-side deserialization tasks.\u001b[39;00m\n\u001b[1;32m 155\u001b[0m \n\u001b[1;32m 156\u001b[0m \u001b[39mParameters\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 170\u001b[0m \u001b[39m:meta private:\u001b[39;00m\n\u001b[1;32m 171\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 172\u001b[0m frames \u001b[39m=\u001b[39m [\n\u001b[1;32m 173\u001b[0m cudf\u001b[39m.\u001b[39mcore\u001b[39m.\u001b[39mbuffer\u001b[39m.\u001b[39mas_buffer(f) \u001b[39mif\u001b[39;00m c \u001b[39melse\u001b[39;00m f\n\u001b[1;32m 174\u001b[0m \u001b[39mfor\u001b[39;00m c, f \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(header[\u001b[39m\"\u001b[39m\u001b[39mis-cuda\u001b[39m\u001b[39m\"\u001b[39m], \u001b[39mmap\u001b[39m(\u001b[39mmemoryview\u001b[39m, frames))\n\u001b[1;32m 175\u001b[0m ]\n\u001b[0;32m--> 176\u001b[0m obj \u001b[39m=\u001b[39m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39mdevice_deserialize(header, frames)\n\u001b[1;32m 177\u001b[0m \u001b[39mreturn\u001b[39;00m obj\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py:130\u001b[0m, in \u001b[0;36mdevice_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 125\u001b[0m typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 126\u001b[0m frames \u001b[39m=\u001b[39m [\n\u001b[1;32m 127\u001b[0m cudf\u001b[39m.\u001b[39mcore\u001b[39m.\u001b[39mbuffer\u001b[39m.\u001b[39mas_buffer(f) \u001b[39mif\u001b[39;00m c \u001b[39melse\u001b[39;00m \u001b[39mmemoryview\u001b[39m(f)\n\u001b[1;32m 128\u001b[0m \u001b[39mfor\u001b[39;00m c, f \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(header[\u001b[39m\"\u001b[39m\u001b[39mis-cuda\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 129\u001b[0m ]\n\u001b[0;32m--> 130\u001b[0m \u001b[39mreturn\u001b[39;00m typ\u001b[39m.\u001b[39mdeserialize(header, frames)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py:1019\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1016\u001b[0m \u001b[39m@classmethod\u001b[39m\n\u001b[1;32m 1017\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mdeserialize\u001b[39m(\u001b[39mcls\u001b[39m, header, frames):\n\u001b[1;32m 1018\u001b[0m index_nframes \u001b[39m=\u001b[39m header[\u001b[39m\"\u001b[39m\u001b[39mindex_frame_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[0;32m-> 1019\u001b[0m obj \u001b[39m=\u001b[39m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39mdeserialize(\n\u001b[1;32m 1020\u001b[0m header, frames[header[\u001b[39m\"\u001b[39m\u001b[39mindex_frame_count\u001b[39m\u001b[39m\"\u001b[39m] :]\n\u001b[1;32m 1021\u001b[0m )\n\u001b[1;32m 1023\u001b[0m idx_typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mindex\u001b[39m\u001b[39m\"\u001b[39m][\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 1024\u001b[0m index \u001b[39m=\u001b[39m idx_typ\u001b[39m.\u001b[39mdeserialize(header[\u001b[39m\"\u001b[39m\u001b[39mindex\u001b[39m\u001b[39m\"\u001b[39m], frames[:index_nframes])\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py:106\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 104\u001b[0m cls_deserialize \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 105\u001b[0m column_names \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mcolumn_names\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m--> 106\u001b[0m columns \u001b[39m=\u001b[39m deserialize_columns(header[\u001b[39m\"\u001b[39m\u001b[39mcolumns\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 107\u001b[0m \u001b[39mreturn\u001b[39;00m cls_deserialize\u001b[39m.\u001b[39m_from_data(\u001b[39mdict\u001b[39m(\u001b[39mzip\u001b[39m(column_names, columns)))\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:2450\u001b[0m, in \u001b[0;36mdeserialize_columns\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2448\u001b[0m col_frame_count \u001b[39m=\u001b[39m meta[\u001b[39m\"\u001b[39m\u001b[39mframe_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[1;32m 2449\u001b[0m col_typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(meta[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m-> 2450\u001b[0m colobj \u001b[39m=\u001b[39m col_typ\u001b[39m.\u001b[39mdeserialize(meta, frames[:col_frame_count])\n\u001b[1;32m 2451\u001b[0m columns\u001b[39m.\u001b[39mappend(colobj)\n\u001b[1;32m 2452\u001b[0m \u001b[39m# Advance frames\u001b[39;00m\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:1216\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1214\u001b[0m dtype \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mdtype\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 1215\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m\"\u001b[39m\u001b[39mdata\u001b[39m\u001b[39m\"\u001b[39m \u001b[39min\u001b[39;00m header:\n\u001b[0;32m-> 1216\u001b[0m data, frames \u001b[39m=\u001b[39m unpack(header[\u001b[39m\"\u001b[39m\u001b[39mdata\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 1217\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 1218\u001b[0m data \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:1204\u001b[0m, in \u001b[0;36munpack\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1202\u001b[0m count \u001b[39m=\u001b[39m header[\u001b[39m\"\u001b[39m\u001b[39mframe_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[1;32m 1203\u001b[0m klass \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m-> 1204\u001b[0m obj \u001b[39m=\u001b[39m klass\u001b[39m.\u001b[39mdeserialize(header, frames[:count])\n\u001b[1;32m 1205\u001b[0m \u001b[39mreturn\u001b[39;00m obj, frames[count:]\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:574\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 567\u001b[0m \u001b[39m@classmethod\u001b[39m\n\u001b[1;32m 568\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mdeserialize\u001b[39m(\u001b[39mcls\u001b[39m, header: \u001b[39mdict\u001b[39m, frames: \u001b[39mlist\u001b[39m):\n\u001b[1;32m 569\u001b[0m \u001b[39m# TODO: because of the hack in `SpillableBuffer.serialize()` where\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 572\u001b[0m \u001b[39m# deserialize into `SpillableBufferSlice` when the frames hasn't been\u001b[39;00m\n\u001b[1;32m 573\u001b[0m \u001b[39m# copied.\u001b[39;00m\n\u001b[0;32m--> 574\u001b[0m \u001b[39mreturn\u001b[39;00m SpillableBuffer\u001b[39m.\u001b[39mdeserialize(header, frames)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py:335\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 332\u001b[0m \u001b[39mreturn\u001b[39;00m frame \u001b[39m# The frame is already deserialized\u001b[39;00m\n\u001b[1;32m 334\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mhasattr\u001b[39m(frame, \u001b[39m\"\u001b[39m\u001b[39m__cuda_array_interface__\u001b[39m\u001b[39m\"\u001b[39m):\n\u001b[0;32m--> 335\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39m_from_device_memory(frame)\n\u001b[1;32m 336\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39m_from_host_memory(frame)\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:235\u001b[0m, in \u001b[0;36m_from_device_memory\u001b[0;34m()\u001b[0m\n\u001b[1;32m 218\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"Create a spillabe buffer from device memory.\u001b[39;00m\n\u001b[1;32m 219\u001b[0m \n\u001b[1;32m 220\u001b[0m \u001b[39mNo data is being copied.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 232\u001b[0m \u001b[39m Buffer representing the same device memory as `data`\u001b[39;00m\n\u001b[1;32m 233\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 234\u001b[0m ret \u001b[39m=\u001b[39m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39m_from_device_memory(data)\n\u001b[0;32m--> 235\u001b[0m ret\u001b[39m.\u001b[39m_finalize_init(ptr_desc\u001b[39m=\u001b[39m{\u001b[39m\"\u001b[39m\u001b[39mtype\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39m\"\u001b[39m\u001b[39mgpu\u001b[39m\u001b[39m\"\u001b[39m}, exposed\u001b[39m=\u001b[39mexposed)\n\u001b[1;32m 236\u001b[0m \u001b[39mreturn\u001b[39;00m ret\n", - "File \u001b[0;32m~/miniconda3/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:206\u001b[0m, in \u001b[0;36m_finalize_init\u001b[0;34m()\u001b[0m\n\u001b[1;32m 204\u001b[0m manager \u001b[39m=\u001b[39m get_global_manager()\n\u001b[1;32m 205\u001b[0m \u001b[39mif\u001b[39;00m manager \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m--> 206\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mValueError\u001b[39;00m(\n\u001b[1;32m 207\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mcannot create \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m\u001b[39m__class__\u001b[39m\u001b[39m}\u001b[39;00m\u001b[39m without \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[1;32m 208\u001b[0m \u001b[39m\"\u001b[39m\u001b[39ma global spill manager\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[1;32m 209\u001b[0m )\n\u001b[1;32m 211\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_manager \u001b[39m=\u001b[39m manager\n\u001b[1;32m 212\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_manager\u001b[39m.\u001b[39madd(\u001b[39mself\u001b[39m)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cugraph/structure/graph_classes.py:309\u001b[0m, in \u001b[0;36mGraph.from_dask_cudf_edgelist\u001b[0;34m(self, input_ddf, source, destination, edge_attr, renumber, store_transposed, legacy_renum_only)\u001b[0m\n\u001b[1;32m 307\u001b[0m \u001b[39melif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_Impl\u001b[39m.\u001b[39medgelist \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[1;32m 308\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mRuntimeError\u001b[39;00m(\u001b[39m\"\u001b[39m\u001b[39mGraph already has values\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[0;32m--> 309\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_Impl\u001b[39m.\u001b[39;49m_simpleDistributedGraphImpl__from_edgelist(\n\u001b[1;32m 310\u001b[0m input_ddf,\n\u001b[1;32m 311\u001b[0m source,\n\u001b[1;32m 312\u001b[0m destination,\n\u001b[1;32m 313\u001b[0m edge_attr,\n\u001b[1;32m 314\u001b[0m renumber,\n\u001b[1;32m 315\u001b[0m store_transposed,\n\u001b[1;32m 316\u001b[0m legacy_renum_only,\n\u001b[1;32m 317\u001b[0m )\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cugraph/structure/graph_implementation/simpleDistributedGraph.py:272\u001b[0m, in \u001b[0;36msimpleDistributedGraphImpl.__from_edgelist\u001b[0;34m(self, input_ddf, source, destination, edge_attr, renumber, store_transposed, legacy_renum_only)\u001b[0m\n\u001b[1;32m 268\u001b[0m dst_col_name \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mrenumber_map\u001b[39m.\u001b[39mrenumbered_dst_col_name\n\u001b[1;32m 270\u001b[0m ddf \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39medgelist\u001b[39m.\u001b[39medgelist_df\n\u001b[0;32m--> 272\u001b[0m num_edges \u001b[39m=\u001b[39m \u001b[39mlen\u001b[39;49m(ddf)\n\u001b[1;32m 273\u001b[0m edge_data \u001b[39m=\u001b[39m get_distributed_data(ddf)\n\u001b[1;32m 275\u001b[0m graph_props \u001b[39m=\u001b[39m GraphProperties(\n\u001b[1;32m 276\u001b[0m is_multigraph\u001b[39m=\u001b[39m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mproperties\u001b[39m.\u001b[39mmulti_edge,\n\u001b[1;32m 277\u001b[0m is_symmetric\u001b[39m=\u001b[39m\u001b[39mnot\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mproperties\u001b[39m.\u001b[39mdirected,\n\u001b[1;32m 278\u001b[0m )\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/dataframe/core.py:4775\u001b[0m, in \u001b[0;36mDataFrame.__len__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 4773\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39m\u001b[39m__len__\u001b[39m()\n\u001b[1;32m 4774\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 4775\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mlen\u001b[39;49m(s)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/dataframe/core.py:843\u001b[0m, in \u001b[0;36m_Frame.__len__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__len__\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[1;32m 841\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mreduction(\n\u001b[1;32m 842\u001b[0m \u001b[39mlen\u001b[39;49m, np\u001b[39m.\u001b[39;49msum, token\u001b[39m=\u001b[39;49m\u001b[39m\"\u001b[39;49m\u001b[39mlen\u001b[39;49m\u001b[39m\"\u001b[39;49m, meta\u001b[39m=\u001b[39;49m\u001b[39mint\u001b[39;49m, split_every\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m\n\u001b[0;32m--> 843\u001b[0m )\u001b[39m.\u001b[39;49mcompute()\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/base.py:314\u001b[0m, in \u001b[0;36mDaskMethodsMixin.compute\u001b[0;34m(self, **kwargs)\u001b[0m\n\u001b[1;32m 290\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mcompute\u001b[39m(\u001b[39mself\u001b[39m, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs):\n\u001b[1;32m 291\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"Compute this dask collection\u001b[39;00m\n\u001b[1;32m 292\u001b[0m \n\u001b[1;32m 293\u001b[0m \u001b[39m This turns a lazy Dask collection into its in-memory equivalent.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[39m dask.base.compute\u001b[39;00m\n\u001b[1;32m 313\u001b[0m \u001b[39m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 314\u001b[0m (result,) \u001b[39m=\u001b[39m compute(\u001b[39mself\u001b[39;49m, traverse\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 315\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/dask/base.py:599\u001b[0m, in \u001b[0;36mcompute\u001b[0;34m(traverse, optimize_graph, scheduler, get, *args, **kwargs)\u001b[0m\n\u001b[1;32m 596\u001b[0m keys\u001b[39m.\u001b[39mappend(x\u001b[39m.\u001b[39m__dask_keys__())\n\u001b[1;32m 597\u001b[0m postcomputes\u001b[39m.\u001b[39mappend(x\u001b[39m.\u001b[39m__dask_postcompute__())\n\u001b[0;32m--> 599\u001b[0m results \u001b[39m=\u001b[39m schedule(dsk, keys, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 600\u001b[0m \u001b[39mreturn\u001b[39;00m repack([f(r, \u001b[39m*\u001b[39ma) \u001b[39mfor\u001b[39;00m r, (f, a) \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(results, postcomputes)])\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:3186\u001b[0m, in \u001b[0;36mClient.get\u001b[0;34m(self, dsk, keys, workers, allow_other_workers, resources, sync, asynchronous, direct, retries, priority, fifo_timeout, actors, **kwargs)\u001b[0m\n\u001b[1;32m 3184\u001b[0m should_rejoin \u001b[39m=\u001b[39m \u001b[39mFalse\u001b[39;00m\n\u001b[1;32m 3185\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m-> 3186\u001b[0m results \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mgather(packed, asynchronous\u001b[39m=\u001b[39;49masynchronous, direct\u001b[39m=\u001b[39;49mdirect)\n\u001b[1;32m 3187\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 3188\u001b[0m \u001b[39mfor\u001b[39;00m f \u001b[39min\u001b[39;00m futures\u001b[39m.\u001b[39mvalues():\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:2345\u001b[0m, in \u001b[0;36mClient.gather\u001b[0;34m(self, futures, errors, direct, asynchronous)\u001b[0m\n\u001b[1;32m 2343\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 2344\u001b[0m local_worker \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[0;32m-> 2345\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49msync(\n\u001b[1;32m 2346\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_gather,\n\u001b[1;32m 2347\u001b[0m futures,\n\u001b[1;32m 2348\u001b[0m errors\u001b[39m=\u001b[39;49merrors,\n\u001b[1;32m 2349\u001b[0m direct\u001b[39m=\u001b[39;49mdirect,\n\u001b[1;32m 2350\u001b[0m local_worker\u001b[39m=\u001b[39;49mlocal_worker,\n\u001b[1;32m 2351\u001b[0m asynchronous\u001b[39m=\u001b[39;49masynchronous,\n\u001b[1;32m 2352\u001b[0m )\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:349\u001b[0m, in \u001b[0;36mSyncMethodMixin.sync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[39mreturn\u001b[39;00m future\n\u001b[1;32m 348\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 349\u001b[0m \u001b[39mreturn\u001b[39;00m sync(\n\u001b[1;32m 350\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mloop, func, \u001b[39m*\u001b[39;49margs, callback_timeout\u001b[39m=\u001b[39;49mcallback_timeout, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs\n\u001b[1;32m 351\u001b[0m )\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:416\u001b[0m, in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[39mif\u001b[39;00m error:\n\u001b[1;32m 415\u001b[0m typ, exc, tb \u001b[39m=\u001b[39m error\n\u001b[0;32m--> 416\u001b[0m \u001b[39mraise\u001b[39;00m exc\u001b[39m.\u001b[39mwith_traceback(tb)\n\u001b[1;32m 417\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 418\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/utils.py:389\u001b[0m, in \u001b[0;36msync..f\u001b[0;34m()\u001b[0m\n\u001b[1;32m 387\u001b[0m future \u001b[39m=\u001b[39m wait_for(future, callback_timeout)\n\u001b[1;32m 388\u001b[0m future \u001b[39m=\u001b[39m asyncio\u001b[39m.\u001b[39mensure_future(future)\n\u001b[0;32m--> 389\u001b[0m result \u001b[39m=\u001b[39m \u001b[39myield\u001b[39;00m future\n\u001b[1;32m 390\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 391\u001b[0m error \u001b[39m=\u001b[39m sys\u001b[39m.\u001b[39mexc_info()\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/tornado/gen.py:769\u001b[0m, in \u001b[0;36mRunner.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 766\u001b[0m exc_info \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[1;32m 768\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 769\u001b[0m value \u001b[39m=\u001b[39m future\u001b[39m.\u001b[39;49mresult()\n\u001b[1;32m 770\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 771\u001b[0m exc_info \u001b[39m=\u001b[39m sys\u001b[39m.\u001b[39mexc_info()\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/client.py:2208\u001b[0m, in \u001b[0;36mClient._gather\u001b[0;34m(self, futures, errors, direct, local_worker)\u001b[0m\n\u001b[1;32m 2206\u001b[0m exc \u001b[39m=\u001b[39m CancelledError(key)\n\u001b[1;32m 2207\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 2208\u001b[0m \u001b[39mraise\u001b[39;00m exception\u001b[39m.\u001b[39mwith_traceback(traceback)\n\u001b[1;32m 2209\u001b[0m \u001b[39mraise\u001b[39;00m exc\n\u001b[1;32m 2210\u001b[0m \u001b[39mif\u001b[39;00m errors \u001b[39m==\u001b[39m \u001b[39m\"\u001b[39m\u001b[39mskip\u001b[39m\u001b[39m\"\u001b[39m:\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/contextlib.py:79\u001b[0m, in \u001b[0;36minner\u001b[0;34m()\u001b[0m\n\u001b[1;32m 76\u001b[0m \u001b[39m@wraps\u001b[39m(func)\n\u001b[1;32m 77\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39minner\u001b[39m(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwds):\n\u001b[1;32m 78\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_recreate_cm():\n\u001b[0;32m---> 79\u001b[0m \u001b[39mreturn\u001b[39;00m func(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwds)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py:2937\u001b[0m, in \u001b[0;36m_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2934\u001b[0m \u001b[39m# Some objects require threadlocal state during deserialization, e.g. to\u001b[39;00m\n\u001b[1;32m 2935\u001b[0m \u001b[39m# detect the current worker\u001b[39;00m\n\u001b[1;32m 2936\u001b[0m \u001b[39mif\u001b[39;00m function \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m-> 2937\u001b[0m function \u001b[39m=\u001b[39m loads_function(function)\n\u001b[1;32m 2938\u001b[0m \u001b[39mif\u001b[39;00m args \u001b[39mand\u001b[39;00m \u001b[39misinstance\u001b[39m(args, \u001b[39mbytes\u001b[39m):\n\u001b[1;32m 2939\u001b[0m args \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(args)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/worker.py:2925\u001b[0m, in \u001b[0;36mloads_function\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2923\u001b[0m result \u001b[39m=\u001b[39m cache_loads[bytes_object]\n\u001b[1;32m 2924\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n\u001b[0;32m-> 2925\u001b[0m result \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(bytes_object)\n\u001b[1;32m 2926\u001b[0m cache_loads[bytes_object] \u001b[39m=\u001b[39m result\n\u001b[1;32m 2927\u001b[0m \u001b[39mreturn\u001b[39;00m result\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/distributed/protocol/pickle.py:96\u001b[0m, in \u001b[0;36mloads\u001b[0;34m()\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[39mreturn\u001b[39;00m pickle\u001b[39m.\u001b[39mloads(x, buffers\u001b[39m=\u001b[39mbuffers)\n\u001b[1;32m 95\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m---> 96\u001b[0m \u001b[39mreturn\u001b[39;00m pickle\u001b[39m.\u001b[39mloads(x)\n\u001b[1;32m 97\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m:\n\u001b[1;32m 98\u001b[0m logger\u001b[39m.\u001b[39minfo(\u001b[39m\"\u001b[39m\u001b[39mFailed to deserialize \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m\"\u001b[39m, x[:\u001b[39m10000\u001b[39m], exc_info\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py:176\u001b[0m, in \u001b[0;36mhost_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 154\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"Perform device-side deserialization tasks.\u001b[39;00m\n\u001b[1;32m 155\u001b[0m \n\u001b[1;32m 156\u001b[0m \u001b[39mParameters\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 170\u001b[0m \u001b[39m:meta private:\u001b[39;00m\n\u001b[1;32m 171\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 172\u001b[0m frames \u001b[39m=\u001b[39m [\n\u001b[1;32m 173\u001b[0m cudf\u001b[39m.\u001b[39mcore\u001b[39m.\u001b[39mbuffer\u001b[39m.\u001b[39mas_buffer(f) \u001b[39mif\u001b[39;00m c \u001b[39melse\u001b[39;00m f\n\u001b[1;32m 174\u001b[0m \u001b[39mfor\u001b[39;00m c, f \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(header[\u001b[39m\"\u001b[39m\u001b[39mis-cuda\u001b[39m\u001b[39m\"\u001b[39m], \u001b[39mmap\u001b[39m(\u001b[39mmemoryview\u001b[39m, frames))\n\u001b[1;32m 175\u001b[0m ]\n\u001b[0;32m--> 176\u001b[0m obj \u001b[39m=\u001b[39m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39mdevice_deserialize(header, frames)\n\u001b[1;32m 177\u001b[0m \u001b[39mreturn\u001b[39;00m obj\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/abc.py:130\u001b[0m, in \u001b[0;36mdevice_deserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 125\u001b[0m typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 126\u001b[0m frames \u001b[39m=\u001b[39m [\n\u001b[1;32m 127\u001b[0m cudf\u001b[39m.\u001b[39mcore\u001b[39m.\u001b[39mbuffer\u001b[39m.\u001b[39mas_buffer(f) \u001b[39mif\u001b[39;00m c \u001b[39melse\u001b[39;00m \u001b[39mmemoryview\u001b[39m(f)\n\u001b[1;32m 128\u001b[0m \u001b[39mfor\u001b[39;00m c, f \u001b[39min\u001b[39;00m \u001b[39mzip\u001b[39m(header[\u001b[39m\"\u001b[39m\u001b[39mis-cuda\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 129\u001b[0m ]\n\u001b[0;32m--> 130\u001b[0m \u001b[39mreturn\u001b[39;00m typ\u001b[39m.\u001b[39mdeserialize(header, frames)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/dataframe.py:1019\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1016\u001b[0m \u001b[39m@classmethod\u001b[39m\n\u001b[1;32m 1017\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mdeserialize\u001b[39m(\u001b[39mcls\u001b[39m, header, frames):\n\u001b[1;32m 1018\u001b[0m index_nframes \u001b[39m=\u001b[39m header[\u001b[39m\"\u001b[39m\u001b[39mindex_frame_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[0;32m-> 1019\u001b[0m obj \u001b[39m=\u001b[39m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39mdeserialize(\n\u001b[1;32m 1020\u001b[0m header, frames[header[\u001b[39m\"\u001b[39m\u001b[39mindex_frame_count\u001b[39m\u001b[39m\"\u001b[39m] :]\n\u001b[1;32m 1021\u001b[0m )\n\u001b[1;32m 1023\u001b[0m idx_typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mindex\u001b[39m\u001b[39m\"\u001b[39m][\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 1024\u001b[0m index \u001b[39m=\u001b[39m idx_typ\u001b[39m.\u001b[39mdeserialize(header[\u001b[39m\"\u001b[39m\u001b[39mindex\u001b[39m\u001b[39m\"\u001b[39m], frames[:index_nframes])\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/frame.py:106\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 104\u001b[0m cls_deserialize \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 105\u001b[0m column_names \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mcolumn_names\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m--> 106\u001b[0m columns \u001b[39m=\u001b[39m deserialize_columns(header[\u001b[39m\"\u001b[39m\u001b[39mcolumns\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 107\u001b[0m \u001b[39mreturn\u001b[39;00m cls_deserialize\u001b[39m.\u001b[39m_from_data(\u001b[39mdict\u001b[39m(\u001b[39mzip\u001b[39m(column_names, columns)))\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:2450\u001b[0m, in \u001b[0;36mdeserialize_columns\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2448\u001b[0m col_frame_count \u001b[39m=\u001b[39m meta[\u001b[39m\"\u001b[39m\u001b[39mframe_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[1;32m 2449\u001b[0m col_typ \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(meta[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m-> 2450\u001b[0m colobj \u001b[39m=\u001b[39m col_typ\u001b[39m.\u001b[39mdeserialize(meta, frames[:col_frame_count])\n\u001b[1;32m 2451\u001b[0m columns\u001b[39m.\u001b[39mappend(colobj)\n\u001b[1;32m 2452\u001b[0m \u001b[39m# Advance frames\u001b[39;00m\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:1216\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1214\u001b[0m dtype \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mdtype\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[1;32m 1215\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m\"\u001b[39m\u001b[39mdata\u001b[39m\u001b[39m\"\u001b[39m \u001b[39min\u001b[39;00m header:\n\u001b[0;32m-> 1216\u001b[0m data, frames \u001b[39m=\u001b[39m unpack(header[\u001b[39m\"\u001b[39m\u001b[39mdata\u001b[39m\u001b[39m\"\u001b[39m], frames)\n\u001b[1;32m 1217\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 1218\u001b[0m data \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/column/column.py:1204\u001b[0m, in \u001b[0;36munpack\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1202\u001b[0m count \u001b[39m=\u001b[39m header[\u001b[39m\"\u001b[39m\u001b[39mframe_count\u001b[39m\u001b[39m\"\u001b[39m]\n\u001b[1;32m 1203\u001b[0m klass \u001b[39m=\u001b[39m pickle\u001b[39m.\u001b[39mloads(header[\u001b[39m\"\u001b[39m\u001b[39mtype-serialized\u001b[39m\u001b[39m\"\u001b[39m])\n\u001b[0;32m-> 1204\u001b[0m obj \u001b[39m=\u001b[39m klass\u001b[39m.\u001b[39mdeserialize(header, frames[:count])\n\u001b[1;32m 1205\u001b[0m \u001b[39mreturn\u001b[39;00m obj, frames[count:]\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:574\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 567\u001b[0m \u001b[39m@classmethod\u001b[39m\n\u001b[1;32m 568\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mdeserialize\u001b[39m(\u001b[39mcls\u001b[39m, header: \u001b[39mdict\u001b[39m, frames: \u001b[39mlist\u001b[39m):\n\u001b[1;32m 569\u001b[0m \u001b[39m# TODO: because of the hack in `SpillableBuffer.serialize()` where\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 572\u001b[0m \u001b[39m# deserialize into `SpillableBufferSlice` when the frames hasn't been\u001b[39;00m\n\u001b[1;32m 573\u001b[0m \u001b[39m# copied.\u001b[39;00m\n\u001b[0;32m--> 574\u001b[0m \u001b[39mreturn\u001b[39;00m SpillableBuffer\u001b[39m.\u001b[39mdeserialize(header, frames)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/buffer.py:335\u001b[0m, in \u001b[0;36mdeserialize\u001b[0;34m()\u001b[0m\n\u001b[1;32m 332\u001b[0m \u001b[39mreturn\u001b[39;00m frame \u001b[39m# The frame is already deserialized\u001b[39;00m\n\u001b[1;32m 334\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mhasattr\u001b[39m(frame, \u001b[39m\"\u001b[39m\u001b[39m__cuda_array_interface__\u001b[39m\u001b[39m\"\u001b[39m):\n\u001b[0;32m--> 335\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39m_from_device_memory(frame)\n\u001b[1;32m 336\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39m_from_host_memory(frame)\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:235\u001b[0m, in \u001b[0;36m_from_device_memory\u001b[0;34m()\u001b[0m\n\u001b[1;32m 218\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"Create a spillabe buffer from device memory.\u001b[39;00m\n\u001b[1;32m 219\u001b[0m \n\u001b[1;32m 220\u001b[0m \u001b[39mNo data is being copied.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 232\u001b[0m \u001b[39m Buffer representing the same device memory as `data`\u001b[39;00m\n\u001b[1;32m 233\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 234\u001b[0m ret \u001b[39m=\u001b[39m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39m_from_device_memory(data)\n\u001b[0;32m--> 235\u001b[0m ret\u001b[39m.\u001b[39m_finalize_init(ptr_desc\u001b[39m=\u001b[39m{\u001b[39m\"\u001b[39m\u001b[39mtype\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39m\"\u001b[39m\u001b[39mgpu\u001b[39m\u001b[39m\"\u001b[39m}, exposed\u001b[39m=\u001b[39mexposed)\n\u001b[1;32m 236\u001b[0m \u001b[39mreturn\u001b[39;00m ret\n", + "File \u001b[0;32m~/miniforge/envs/cugraph_0411/lib/python3.10/site-packages/cudf/core/buffer/spillable_buffer.py:206\u001b[0m, in \u001b[0;36m_finalize_init\u001b[0;34m()\u001b[0m\n\u001b[1;32m 204\u001b[0m manager \u001b[39m=\u001b[39m get_global_manager()\n\u001b[1;32m 205\u001b[0m \u001b[39mif\u001b[39;00m manager \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m--> 206\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mValueError\u001b[39;00m(\n\u001b[1;32m 207\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mcannot create \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m\u001b[39m__class__\u001b[39m\u001b[39m}\u001b[39;00m\u001b[39m without \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[1;32m 208\u001b[0m \u001b[39m\"\u001b[39m\u001b[39ma global spill manager\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[1;32m 209\u001b[0m )\n\u001b[1;32m 211\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_manager \u001b[39m=\u001b[39m manager\n\u001b[1;32m 212\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_manager\u001b[39m.\u001b[39madd(\u001b[39mself\u001b[39m)\n", "\u001b[0;31mValueError\u001b[0m: cannot create without a global spill manager" ] } From fd714aab5b9d192d8b85214cefed1ab76ea5f5f1 Mon Sep 17 00:00:00 2001 From: Ray Douglass Date: Thu, 19 Sep 2024 12:06:39 -0400 Subject: [PATCH 05/37] DOC v24.12 Updates [skip ci] --- .../cuda11.8-conda/devcontainer.json | 6 +- .devcontainer/cuda11.8-pip/devcontainer.json | 8 +- .../cuda12.5-conda/devcontainer.json | 6 +- .devcontainer/cuda12.5-pip/devcontainer.json | 8 +- .github/workflows/build.yaml | 36 ++++----- .github/workflows/pr.yaml | 48 +++++------ .github/workflows/test.yaml | 18 ++--- VERSION | 2 +- .../all_cuda-118_arch-x86_64.yaml | 30 +++---- .../all_cuda-125_arch-x86_64.yaml | 30 +++---- .../cugraph-service/conda_build_config.yaml | 2 +- conda/recipes/cugraph/conda_build_config.yaml | 2 +- .../pylibcugraph/conda_build_config.yaml | 2 +- dependencies.yaml | 80 +++++++++---------- notebooks/README.md | 8 +- .../conda/cugraph_dgl_dev_cuda-118.yaml | 4 +- python/cugraph-dgl/pyproject.toml | 6 +- python/cugraph-equivariant/pyproject.toml | 2 +- .../conda/cugraph_pyg_dev_cuda-118.yaml | 4 +- python/cugraph-pyg/pyproject.toml | 6 +- python/cugraph-service/server/pyproject.toml | 16 ++-- python/cugraph/pyproject.toml | 24 +++--- python/nx-cugraph/pyproject.toml | 2 +- python/pylibcugraph/pyproject.toml | 10 +-- 24 files changed, 180 insertions(+), 180 deletions(-) diff --git a/.devcontainer/cuda11.8-conda/devcontainer.json b/.devcontainer/cuda11.8-conda/devcontainer.json index 9c83535b771..f5886540252 100644 --- a/.devcontainer/cuda11.8-conda/devcontainer.json +++ b/.devcontainer/cuda11.8-conda/devcontainer.json @@ -5,17 +5,17 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.10-cpp-cuda11.8-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.12-cpp-cuda11.8-mambaforge-ubuntu22.04" } }, "runArgs": [ "--rm", "--name", - "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.10-cuda11.8-conda" + "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.12-cuda11.8-conda" ], "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.10": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.12": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index a559be18077..270bfa239ad 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -5,24 +5,24 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.10-cpp-cuda11.8-ucx1.15.0-openmpi-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.12-cpp-cuda11.8-ucx1.15.0-openmpi-ubuntu22.04" } }, "runArgs": [ "--rm", "--name", - "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.10-cuda11.8-pip" + "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.12-cuda11.8-pip" ], "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/cuda:24.10": { + "ghcr.io/rapidsai/devcontainers/features/cuda:24.12": { "version": "11.8", "installcuBLAS": true, "installcuSOLVER": true, "installcuRAND": true, "installcuSPARSE": true }, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.10": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.12": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/cuda", diff --git a/.devcontainer/cuda12.5-conda/devcontainer.json b/.devcontainer/cuda12.5-conda/devcontainer.json index ca10c04edee..e31428e4b0c 100644 --- a/.devcontainer/cuda12.5-conda/devcontainer.json +++ b/.devcontainer/cuda12.5-conda/devcontainer.json @@ -5,17 +5,17 @@ "args": { "CUDA": "12.5", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.10-cpp-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.12-cpp-mambaforge-ubuntu22.04" } }, "runArgs": [ "--rm", "--name", - "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.10-cuda12.5-conda" + "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.12-cuda12.5-conda" ], "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.10": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.12": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" diff --git a/.devcontainer/cuda12.5-pip/devcontainer.json b/.devcontainer/cuda12.5-pip/devcontainer.json index 6e2bf45700a..835274999ba 100644 --- a/.devcontainer/cuda12.5-pip/devcontainer.json +++ b/.devcontainer/cuda12.5-pip/devcontainer.json @@ -5,24 +5,24 @@ "args": { "CUDA": "12.5", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.10-cpp-cuda12.5-ucx1.17.0-openmpi-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.12-cpp-cuda12.5-ucx1.17.0-openmpi-ubuntu22.04" } }, "runArgs": [ "--rm", "--name", - "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.10-cuda12.5-pip" + "${localEnv:USER:anon}-rapids-${localWorkspaceFolderBasename}-24.12-cuda12.5-pip" ], "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/cuda:24.10": { + "ghcr.io/rapidsai/devcontainers/features/cuda:24.12": { "version": "12.5", "installcuBLAS": true, "installcuSOLVER": true, "installcuRAND": true, "installcuSPARSE": true }, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.10": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.12": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/cuda", diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bc489ffd3f0..b272fb43e35 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -38,7 +38,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -47,7 +47,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -57,7 +57,7 @@ jobs: if: github.ref_type == 'branch' needs: python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -69,7 +69,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-pylibcugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -77,13 +77,13 @@ jobs: date: ${{ inputs.date }} script: ci/build_wheel_pylibcugraph.sh extra-repo: rapidsai/cugraph-ops - extra-repo-sha: branch-24.10 + extra-repo-sha: branch-24.12 extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY node_type: cpu32 wheel-publish-pylibcugraph: needs: wheel-build-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -93,7 +93,7 @@ jobs: wheel-build-cugraph: needs: wheel-publish-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -101,12 +101,12 @@ jobs: date: ${{ inputs.date }} script: ci/build_wheel_cugraph.sh extra-repo: rapidsai/cugraph-ops - extra-repo-sha: branch-24.10 + extra-repo-sha: branch-24.12 extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY wheel-publish-cugraph: needs: wheel-build-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -116,7 +116,7 @@ jobs: wheel-build-nx-cugraph: needs: wheel-publish-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -126,7 +126,7 @@ jobs: wheel-publish-nx-cugraph: needs: wheel-build-nx-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -136,7 +136,7 @@ jobs: wheel-build-cugraph-dgl: needs: wheel-publish-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -146,7 +146,7 @@ jobs: wheel-publish-cugraph-dgl: needs: wheel-build-cugraph-dgl secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -156,7 +156,7 @@ jobs: wheel-build-cugraph-pyg: needs: wheel-publish-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -166,7 +166,7 @@ jobs: wheel-publish-cugraph-pyg: needs: wheel-build-cugraph-pyg secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -175,7 +175,7 @@ jobs: package-name: cugraph-pyg wheel-build-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -185,7 +185,7 @@ jobs: wheel-publish-cugraph-equivariant: needs: wheel-build-cugraph-equivariant secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.12 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index dacd9a93399..2220aa8aad6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -34,29 +34,29 @@ jobs: - wheel-tests-cugraph-equivariant - devcontainer secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.12 checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.12 with: enable_check_generated_files: false conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.12 with: build_type: pull-request node_type: cpu32 conda-cpp-tests: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.12 with: build_type: pull-request conda-cpp-checks: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.12 with: build_type: pull-request enable_check_symbols: true @@ -64,19 +64,19 @@ jobs: conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.12 with: build_type: pull-request conda-python-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.12 with: build_type: pull-request conda-notebook-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -86,7 +86,7 @@ jobs: docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -96,63 +96,63 @@ jobs: wheel-build-pylibcugraph: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_pylibcugraph.sh extra-repo: rapidsai/cugraph-ops - extra-repo-sha: branch-24.10 + extra-repo-sha: branch-24.12 extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY node_type: cpu32 wheel-tests-pylibcugraph: needs: wheel-build-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_pylibcugraph.sh wheel-build-cugraph: needs: wheel-tests-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_cugraph.sh extra-repo: rapidsai/cugraph-ops - extra-repo-sha: branch-24.10 + extra-repo-sha: branch-24.12 extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY wheel-tests-cugraph: needs: wheel-build-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_cugraph.sh wheel-build-nx-cugraph: needs: wheel-tests-pylibcugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_nx-cugraph.sh wheel-tests-nx-cugraph: needs: wheel-build-nx-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_nx-cugraph.sh wheel-build-cugraph-dgl: needs: wheel-tests-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_cugraph-dgl.sh wheel-tests-cugraph-dgl: needs: wheel-build-cugraph-dgl secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_cugraph-dgl.sh @@ -160,35 +160,35 @@ jobs: wheel-build-cugraph-pyg: needs: wheel-tests-cugraph secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_cugraph-pyg.sh wheel-tests-cugraph-pyg: needs: wheel-build-cugraph-pyg secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_cugraph-pyg.sh matrix_filter: map(select(.ARCH == "amd64")) wheel-build-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.12 with: build_type: pull-request script: ci/build_wheel_cugraph-equivariant.sh wheel-tests-cugraph-equivariant: needs: wheel-build-cugraph-equivariant secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: pull-request script: ci/test_wheel_cugraph-equivariant.sh matrix_filter: map(select(.ARCH == "amd64")) devcontainer: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.12 with: arch: '["amd64"]' cuda: '["12.5"]' diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 957d29ce72b..5fbdd276bd6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ on: jobs: conda-cpp-checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -26,7 +26,7 @@ jobs: symbol_exclusions: (cugraph::ops|hornet|void writeEdgeCountsKernel|void markUniqueOffsetsKernel) conda-cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -34,7 +34,7 @@ jobs: sha: ${{ inputs.sha }} conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -42,7 +42,7 @@ jobs: sha: ${{ inputs.sha }} wheel-tests-pylibcugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -51,7 +51,7 @@ jobs: script: ci/test_wheel_pylibcugraph.sh wheel-tests-cugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -60,7 +60,7 @@ jobs: script: ci/test_wheel_cugraph.sh wheel-tests-nx-cugraph: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -69,7 +69,7 @@ jobs: script: ci/test_wheel_nx-cugraph.sh wheel-tests-cugraph-dgl: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -79,7 +79,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) wheel-tests-cugraph-pyg: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} @@ -89,7 +89,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) wheel-tests-cugraph-equivariant: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.10 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 with: build_type: nightly branch: ${{ inputs.branch }} diff --git a/VERSION b/VERSION index 7c7ba04436f..af28c42b528 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -24.10.00 +24.12.00 diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 18cca40c320..b95343be301 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -16,22 +16,22 @@ dependencies: - cuda-nvtx - cuda-version=11.8 - cudatoolkit -- cudf==24.10.*,>=0.0.0a0 +- cudf==24.12.*,>=0.0.0a0 - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 -- dask-cuda==24.10.*,>=0.0.0a0 -- dask-cudf==24.10.*,>=0.0.0a0 +- dask-cuda==24.12.*,>=0.0.0a0 +- dask-cudf==24.12.*,>=0.0.0a0 - doxygen - fsspec>=0.6.0 - gcc_linux-64=11.* - graphviz - ipython -- libcudf==24.10.*,>=0.0.0a0 -- libcugraphops==24.10.*,>=0.0.0a0 -- libraft-headers==24.10.*,>=0.0.0a0 -- libraft==24.10.*,>=0.0.0a0 -- librmm==24.10.*,>=0.0.0a0 +- libcudf==24.12.*,>=0.0.0a0 +- libcugraphops==24.12.*,>=0.0.0a0 +- libraft-headers==24.12.*,>=0.0.0a0 +- libraft==24.12.*,>=0.0.0a0 +- librmm==24.12.*,>=0.0.0a0 - nbsphinx - nccl>=2.9.9 - networkx>=2.5.1 @@ -49,9 +49,9 @@ dependencies: - pre-commit - pydantic - pydata-sphinx-theme -- pylibcugraphops==24.10.*,>=0.0.0a0 -- pylibraft==24.10.*,>=0.0.0a0 -- pylibwholegraph==24.10.*,>=0.0.0a0 +- pylibcugraphops==24.12.*,>=0.0.0a0 +- pylibraft==24.12.*,>=0.0.0a0 +- pylibwholegraph==24.12.*,>=0.0.0a0 - pytest - pytest-benchmark - pytest-cov @@ -59,12 +59,12 @@ dependencies: - pytest-xdist - python-louvain - pytorch>=2.0,<2.2.0a0 -- raft-dask==24.10.*,>=0.0.0a0 +- raft-dask==24.12.*,>=0.0.0a0 - rapids-build-backend>=0.3.1,<0.4.0.dev0 -- rapids-dask-dependency==24.10.*,>=0.0.0a0 +- rapids-dask-dependency==24.12.*,>=0.0.0a0 - recommonmark - requests -- rmm==24.10.*,>=0.0.0a0 +- rmm==24.12.*,>=0.0.0a0 - scikit-build-core>=0.10.0 - scikit-learn>=0.23.1 - scipy @@ -77,7 +77,7 @@ dependencies: - torchdata - torchmetrics - ucx-proc=*=gpu -- ucx-py==0.40.*,>=0.0.0a0 +- ucx-py==0.41.*,>=0.0.0a0 - wget - wheel name: all_cuda-118_arch-x86_64 diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index ef20371e0f5..ae152cebacf 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -18,26 +18,26 @@ dependencies: - cuda-nvtx-dev - cuda-profiler-api - cuda-version=12.5 -- cudf==24.10.*,>=0.0.0a0 +- cudf==24.12.*,>=0.0.0a0 - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 -- dask-cuda==24.10.*,>=0.0.0a0 -- dask-cudf==24.10.*,>=0.0.0a0 +- dask-cuda==24.12.*,>=0.0.0a0 +- dask-cudf==24.12.*,>=0.0.0a0 - doxygen - fsspec>=0.6.0 - gcc_linux-64=11.* - graphviz - ipython - libcublas-dev -- libcudf==24.10.*,>=0.0.0a0 -- libcugraphops==24.10.*,>=0.0.0a0 +- libcudf==24.12.*,>=0.0.0a0 +- libcugraphops==24.12.*,>=0.0.0a0 - libcurand-dev - libcusolver-dev - libcusparse-dev -- libraft-headers==24.10.*,>=0.0.0a0 -- libraft==24.10.*,>=0.0.0a0 -- librmm==24.10.*,>=0.0.0a0 +- libraft-headers==24.12.*,>=0.0.0a0 +- libraft==24.12.*,>=0.0.0a0 +- librmm==24.12.*,>=0.0.0a0 - nbsphinx - nccl>=2.9.9 - networkx>=2.5.1 @@ -54,9 +54,9 @@ dependencies: - pre-commit - pydantic - pydata-sphinx-theme -- pylibcugraphops==24.10.*,>=0.0.0a0 -- pylibraft==24.10.*,>=0.0.0a0 -- pylibwholegraph==24.10.*,>=0.0.0a0 +- pylibcugraphops==24.12.*,>=0.0.0a0 +- pylibraft==24.12.*,>=0.0.0a0 +- pylibwholegraph==24.12.*,>=0.0.0a0 - pytest - pytest-benchmark - pytest-cov @@ -64,12 +64,12 @@ dependencies: - pytest-xdist - python-louvain - pytorch>=2.0,<2.2.0a0 -- raft-dask==24.10.*,>=0.0.0a0 +- raft-dask==24.12.*,>=0.0.0a0 - rapids-build-backend>=0.3.1,<0.4.0.dev0 -- rapids-dask-dependency==24.10.*,>=0.0.0a0 +- rapids-dask-dependency==24.12.*,>=0.0.0a0 - recommonmark - requests -- rmm==24.10.*,>=0.0.0a0 +- rmm==24.12.*,>=0.0.0a0 - scikit-build-core>=0.10.0 - scikit-learn>=0.23.1 - scipy @@ -82,7 +82,7 @@ dependencies: - torchdata - torchmetrics - ucx-proc=*=gpu -- ucx-py==0.40.*,>=0.0.0a0 +- ucx-py==0.41.*,>=0.0.0a0 - wget - wheel name: all_cuda-125_arch-x86_64 diff --git a/conda/recipes/cugraph-service/conda_build_config.yaml b/conda/recipes/cugraph-service/conda_build_config.yaml index 2ac251ab10a..67ed3e26b0e 100644 --- a/conda/recipes/cugraph-service/conda_build_config.yaml +++ b/conda/recipes/cugraph-service/conda_build_config.yaml @@ -1,2 +1,2 @@ ucx_py_version: - - "0.40.*" + - "0.41.*" diff --git a/conda/recipes/cugraph/conda_build_config.yaml b/conda/recipes/cugraph/conda_build_config.yaml index 2525441f92d..10f2e15c550 100644 --- a/conda/recipes/cugraph/conda_build_config.yaml +++ b/conda/recipes/cugraph/conda_build_config.yaml @@ -20,4 +20,4 @@ c_stdlib_version: - "2.17" ucx_py_version: - - "0.40.*" + - "0.41.*" diff --git a/conda/recipes/pylibcugraph/conda_build_config.yaml b/conda/recipes/pylibcugraph/conda_build_config.yaml index 2525441f92d..10f2e15c550 100644 --- a/conda/recipes/pylibcugraph/conda_build_config.yaml +++ b/conda/recipes/pylibcugraph/conda_build_config.yaml @@ -20,4 +20,4 @@ c_stdlib_version: - "2.17" ucx_py_version: - - "0.40.*" + - "0.41.*" diff --git a/dependencies.yaml b/dependencies.yaml index 2c8335868ba..091f2db4548 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -426,12 +426,12 @@ dependencies: packages: - c-compiler - cxx-compiler - - libcudf==24.10.*,>=0.0.0a0 + - libcudf==24.12.*,>=0.0.0a0 # Deprecate libcugraphops - - libcugraphops==24.10.*,>=0.0.0a0 - - libraft-headers==24.10.*,>=0.0.0a0 - - libraft==24.10.*,>=0.0.0a0 - - librmm==24.10.*,>=0.0.0a0 + - libcugraphops==24.12.*,>=0.0.0a0 + - libraft-headers==24.12.*,>=0.0.0a0 + - libraft==24.12.*,>=0.0.0a0 + - librmm==24.12.*,>=0.0.0a0 - openmpi # Required for building cpp-mgtests (multi-GPU tests) specific: - output_types: [conda] @@ -521,8 +521,8 @@ dependencies: common: - output_types: [conda, pyproject] packages: - - &dask rapids-dask-dependency==24.10.*,>=0.0.0a0 - - &dask_cuda dask-cuda==24.10.*,>=0.0.0a0 + - &dask rapids-dask-dependency==24.12.*,>=0.0.0a0 + - &dask_cuda dask-cuda==24.12.*,>=0.0.0a0 - &numba numba>=0.57 - &numpy numpy>=1.23,<2.0a0 - output_types: conda @@ -532,7 +532,7 @@ dependencies: - requests - nccl>=2.9.9 - ucx-proc=*=gpu - - &ucx_py_unsuffixed ucx-py==0.40.*,>=0.0.0a0 + - &ucx_py_unsuffixed ucx-py==0.41.*,>=0.0.0a0 - output_types: pyproject packages: # cudf uses fsspec but is protocol independent. cugraph @@ -545,12 +545,12 @@ dependencies: cuda: "11.*" cuda_suffixed: "true" packages: - - &ucx_py_cu11 ucx-py-cu11==0.40.*,>=0.0.0a0 + - &ucx_py_cu11 ucx-py-cu11==0.41.*,>=0.0.0a0 - matrix: cuda: "12.*" cuda_suffixed: "true" packages: - - &ucx_py_cu12 ucx-py-cu12==0.40.*,>=0.0.0a0 + - &ucx_py_cu12 ucx-py-cu12==0.41.*,>=0.0.0a0 - matrix: packages: - *ucx_py_unsuffixed @@ -573,15 +573,15 @@ dependencies: cuda: "11.*" cuda_suffixed: "true" packages: - - &cugraph_cu11 cugraph-cu11==24.10.*,>=0.0.0a0 + - &cugraph_cu11 cugraph-cu11==24.12.*,>=0.0.0a0 - matrix: cuda: "12.*" cuda_suffixed: "true" packages: - - &cugraph_cu12 cugraph-cu12==24.10.*,>=0.0.0a0 + - &cugraph_cu12 cugraph-cu12==24.12.*,>=0.0.0a0 - matrix: packages: - - &cugraph_unsuffixed cugraph==24.10.*,>=0.0.0a0 + - &cugraph_unsuffixed cugraph==24.12.*,>=0.0.0a0 python_run_cugraph_pyg: common: - output_types: [conda, pyproject] @@ -629,19 +629,19 @@ dependencies: cuda_suffixed: "true" packages: - *cugraph_cu11 - - cugraph-service-client-cu11==24.10.*,>=0.0.0a0 + - cugraph-service-client-cu11==24.12.*,>=0.0.0a0 - *ucx_py_cu11 - matrix: cuda: "12.*" cuda_suffixed: "true" packages: - *cugraph_cu12 - - cugraph-service-client-cu12==24.10.*,>=0.0.0a0 + - cugraph-service-client-cu12==24.12.*,>=0.0.0a0 - *ucx_py_cu12 - matrix: packages: - *cugraph_unsuffixed - - cugraph-service-client==24.10.*,>=0.0.0a0 + - cugraph-service-client==24.12.*,>=0.0.0a0 - *ucx_py_unsuffixed test_cpp: common: @@ -677,7 +677,7 @@ dependencies: - scikit-learn>=0.23.1 - output_types: [conda] packages: - - &pylibwholegraph_unsuffixed pylibwholegraph==24.10.*,>=0.0.0a0 + - &pylibwholegraph_unsuffixed pylibwholegraph==24.12.*,>=0.0.0a0 - *thrift test_python_pylibcugraph: common: @@ -759,19 +759,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - pylibwholegraph-cu12==24.10.*,>=0.0.0a0 + - pylibwholegraph-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - pylibwholegraph-cu11==24.10.*,>=0.0.0a0 + - pylibwholegraph-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*pylibwholegraph_unsuffixed]} depends_on_rmm: common: - output_types: conda packages: - - &rmm_unsuffixed rmm==24.10.*,>=0.0.0a0 + - &rmm_unsuffixed rmm==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -784,19 +784,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - rmm-cu12==24.10.*,>=0.0.0a0 + - rmm-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - rmm-cu11==24.10.*,>=0.0.0a0 + - rmm-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*rmm_unsuffixed]} depends_on_cudf: common: - output_types: conda packages: - - &cudf_unsuffixed cudf==24.10.*,>=0.0.0a0 + - &cudf_unsuffixed cudf==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -809,19 +809,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - cudf-cu12==24.10.*,>=0.0.0a0 + - cudf-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - cudf-cu11==24.10.*,>=0.0.0a0 + - cudf-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*cudf_unsuffixed]} depends_on_dask_cudf: common: - output_types: conda packages: - - &dask_cudf_unsuffixed dask-cudf==24.10.*,>=0.0.0a0 + - &dask_cudf_unsuffixed dask-cudf==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -834,19 +834,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - dask-cudf-cu12==24.10.*,>=0.0.0a0 + - dask-cudf-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - dask-cudf-cu11==24.10.*,>=0.0.0a0 + - dask-cudf-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*dask_cudf_unsuffixed]} depends_on_pylibraft: common: - output_types: conda packages: - - &pylibraft_unsuffixed pylibraft==24.10.*,>=0.0.0a0 + - &pylibraft_unsuffixed pylibraft==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -859,19 +859,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - pylibraft-cu12==24.10.*,>=0.0.0a0 + - pylibraft-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - pylibraft-cu11==24.10.*,>=0.0.0a0 + - pylibraft-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*pylibraft_unsuffixed]} depends_on_raft_dask: common: - output_types: conda packages: - - &raft_dask_unsuffixed raft-dask==24.10.*,>=0.0.0a0 + - &raft_dask_unsuffixed raft-dask==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -884,19 +884,19 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - raft-dask-cu12==24.10.*,>=0.0.0a0 + - raft-dask-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - raft-dask-cu11==24.10.*,>=0.0.0a0 + - raft-dask-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*raft_dask_unsuffixed]} depends_on_pylibcugraph: common: - output_types: conda packages: - - &pylibcugraph_unsuffixed pylibcugraph==24.10.*,>=0.0.0a0 + - &pylibcugraph_unsuffixed pylibcugraph==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -909,12 +909,12 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - pylibcugraph-cu12==24.10.*,>=0.0.0a0 + - pylibcugraph-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - pylibcugraph-cu11==24.10.*,>=0.0.0a0 + - pylibcugraph-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*pylibcugraph_unsuffixed]} # deprecate pylibcugraphops @@ -922,7 +922,7 @@ dependencies: common: - output_types: conda packages: - - &pylibcugraphops_unsuffixed pylibcugraphops==24.10.*,>=0.0.0a0 + - &pylibcugraphops_unsuffixed pylibcugraphops==24.12.*,>=0.0.0a0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -935,12 +935,12 @@ dependencies: cuda: "12.*" cuda_suffixed: "true" packages: - - pylibcugraphops-cu12==24.10.*,>=0.0.0a0 + - pylibcugraphops-cu12==24.12.*,>=0.0.0a0 - matrix: cuda: "11.*" cuda_suffixed: "true" packages: - - pylibcugraphops-cu11==24.10.*,>=0.0.0a0 + - pylibcugraphops-cu11==24.12.*,>=0.0.0a0 - {matrix: null, packages: [*pylibcugraphops_unsuffixed]} depends_on_cupy: diff --git a/notebooks/README.md b/notebooks/README.md index a8f094c340b..f0d0a25b9dd 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -66,13 +66,13 @@ The easiest way to run the notebooks is to get the latest [rapidsai/notebooks](h For example, get the latest (as of writing the document) nightly image (`a` after the version number indicates that an image is nightly) with cuda 12.0 using ```sh -docker pull rapidsai/notebooks:24.10a-cuda12.0-py3.10 +docker pull rapidsai/notebooks:24.12a-cuda12.0-py3.10 ``` And, then run a container based on the image using ```sh -docker run --rm -it --pull always --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 -p 8888:8888 rapidsai/notebooks:24.10a-cuda12.0-py3.10 +docker run --rm -it --pull always --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 -p 8888:8888 rapidsai/notebooks:24.12a-cuda12.0-py3.10 ``` You are all set. Run and edit cugraph notebooks from a browser at url http://127.0.0.1:8888/lab/tree/cugraph/cugraph_benchmarks @@ -88,8 +88,8 @@ ssh -L 127.0.0.1:8888:127.0.0.1:8888 [USER_NAME@][REMOTE_HOST_NAME or REMOTE_HO and then run the container in your remote machine. ```sh -docker pull rapidsai/notebooks:24.10a-cuda12.0-py3.10 -docker run --rm -it --pull always --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 -p 8888:8888 rapidsai/notebooks:24.10a-cuda12.0-py3.10 +docker pull rapidsai/notebooks:24.12a-cuda12.0-py3.10 +docker run --rm -it --pull always --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 -p 8888:8888 rapidsai/notebooks:24.12a-cuda12.0-py3.10 ``` You can run and edit cugraph notebooks at url http://127.0.0.1:8888/lab/tree/cugraph/cugraph_benchmarks as if they are running locally. diff --git a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml index ea30b652286..4e6bdc20232 100644 --- a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml +++ b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml @@ -9,11 +9,11 @@ channels: - conda-forge - nvidia dependencies: -- cugraph==24.10.*,>=0.0.0a0 +- cugraph==24.12.*,>=0.0.0a0 - dgl>=1.1.0.cu* - pandas - pre-commit -- pylibcugraphops==24.10.*,>=0.0.0a0 +- pylibcugraphops==24.12.*,>=0.0.0a0 - pytest - pytest-benchmark - pytest-cov diff --git a/python/cugraph-dgl/pyproject.toml b/python/cugraph-dgl/pyproject.toml index 0cfeb10822a..c1044efd7e7 100644 --- a/python/cugraph-dgl/pyproject.toml +++ b/python/cugraph-dgl/pyproject.toml @@ -24,16 +24,16 @@ classifiers = [ "Programming Language :: Python", ] dependencies = [ - "cugraph==24.10.*,>=0.0.0a0", + "cugraph==24.12.*,>=0.0.0a0", "numba>=0.57", "numpy>=1.23,<2.0a0", - "pylibcugraphops==24.10.*,>=0.0.0a0", + "pylibcugraphops==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.optional-dependencies] test = [ "pandas", - "pylibwholegraph==24.10.*,>=0.0.0a0", + "pylibwholegraph==24.12.*,>=0.0.0a0", "pytest", "pytest-benchmark", "pytest-cov", diff --git a/python/cugraph-equivariant/pyproject.toml b/python/cugraph-equivariant/pyproject.toml index 2cc25885b84..7713e89ac20 100644 --- a/python/cugraph-equivariant/pyproject.toml +++ b/python/cugraph-equivariant/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] dependencies = [ - "pylibcugraphops==24.10.*,>=0.0.0a0", + "pylibcugraphops==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.urls] diff --git a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml index bd1ca33af70..6a0523f1c2d 100644 --- a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml +++ b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml @@ -9,11 +9,11 @@ channels: - conda-forge - nvidia dependencies: -- cugraph==24.10.*,>=0.0.0a0 +- cugraph==24.12.*,>=0.0.0a0 - pandas - pre-commit - pyg>=2.5,<2.6 -- pylibcugraphops==24.10.*,>=0.0.0a0 +- pylibcugraphops==24.12.*,>=0.0.0a0 - pytest - pytest-benchmark - pytest-cov diff --git a/python/cugraph-pyg/pyproject.toml b/python/cugraph-pyg/pyproject.toml index d206d6001cc..e3fb0eee98f 100644 --- a/python/cugraph-pyg/pyproject.toml +++ b/python/cugraph-pyg/pyproject.toml @@ -29,10 +29,10 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] dependencies = [ - "cugraph==24.10.*,>=0.0.0a0", + "cugraph==24.12.*,>=0.0.0a0", "numba>=0.57", "numpy>=1.23,<2.0a0", - "pylibcugraphops==24.10.*,>=0.0.0a0", + "pylibcugraphops==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.urls] @@ -42,7 +42,7 @@ Documentation = "https://docs.rapids.ai/api/cugraph/stable/" [project.optional-dependencies] test = [ "pandas", - "pylibwholegraph==24.10.*,>=0.0.0a0", + "pylibwholegraph==24.12.*,>=0.0.0a0", "pytest", "pytest-benchmark", "pytest-cov", diff --git a/python/cugraph-service/server/pyproject.toml b/python/cugraph-service/server/pyproject.toml index b9789c201d2..c850397b6fc 100644 --- a/python/cugraph-service/server/pyproject.toml +++ b/python/cugraph-service/server/pyproject.toml @@ -20,18 +20,18 @@ authors = [ license = { text = "Apache 2.0" } requires-python = ">=3.10" dependencies = [ - "cudf==24.10.*,>=0.0.0a0", - "cugraph-service-client==24.10.*,>=0.0.0a0", - "cugraph==24.10.*,>=0.0.0a0", + "cudf==24.12.*,>=0.0.0a0", + "cugraph-service-client==24.12.*,>=0.0.0a0", + "cugraph==24.12.*,>=0.0.0a0", "cupy-cuda11x>=12.0.0", - "dask-cuda==24.10.*,>=0.0.0a0", - "dask-cudf==24.10.*,>=0.0.0a0", + "dask-cuda==24.12.*,>=0.0.0a0", + "dask-cudf==24.12.*,>=0.0.0a0", "numba>=0.57", "numpy>=1.23,<2.0a0", - "rapids-dask-dependency==24.10.*,>=0.0.0a0", - "rmm==24.10.*,>=0.0.0a0", + "rapids-dask-dependency==24.12.*,>=0.0.0a0", + "rmm==24.12.*,>=0.0.0a0", "thriftpy2!=0.5.0,!=0.5.1", - "ucx-py==0.40.*,>=0.0.0a0", + "ucx-py==0.41.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", diff --git a/python/cugraph/pyproject.toml b/python/cugraph/pyproject.toml index 31721c8a568..142d0bcd5fa 100644 --- a/python/cugraph/pyproject.toml +++ b/python/cugraph/pyproject.toml @@ -23,18 +23,18 @@ authors = [ license = { text = "Apache 2.0" } requires-python = ">=3.10" dependencies = [ - "cudf==24.10.*,>=0.0.0a0", + "cudf==24.12.*,>=0.0.0a0", "cupy-cuda11x>=12.0.0", - "dask-cuda==24.10.*,>=0.0.0a0", - "dask-cudf==24.10.*,>=0.0.0a0", + "dask-cuda==24.12.*,>=0.0.0a0", + "dask-cudf==24.12.*,>=0.0.0a0", "fsspec[http]>=0.6.0", "numba>=0.57", "numpy>=1.23,<2.0a0", - "pylibcugraph==24.10.*,>=0.0.0a0", - "raft-dask==24.10.*,>=0.0.0a0", - "rapids-dask-dependency==24.10.*,>=0.0.0a0", - "rmm==24.10.*,>=0.0.0a0", - "ucx-py==0.40.*,>=0.0.0a0", + "pylibcugraph==24.12.*,>=0.0.0a0", + "raft-dask==24.12.*,>=0.0.0a0", + "rapids-dask-dependency==24.12.*,>=0.0.0a0", + "rmm==24.12.*,>=0.0.0a0", + "ucx-py==0.41.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", @@ -49,7 +49,7 @@ test = [ "networkx>=2.5.1", "numpy>=1.23,<2.0a0", "pandas", - "pylibwholegraph==24.10.*,>=0.0.0a0", + "pylibwholegraph==24.12.*,>=0.0.0a0", "pytest", "pytest-benchmark", "pytest-cov", @@ -82,9 +82,9 @@ build-backend = "scikit_build_core.build" requires = [ "cmake>=3.26.4,!=3.30.0", "ninja", - "pylibcugraph==24.10.*,>=0.0.0a0", - "pylibraft==24.10.*,>=0.0.0a0", - "rmm==24.10.*,>=0.0.0a0", + "pylibcugraph==24.12.*,>=0.0.0a0", + "pylibraft==24.12.*,>=0.0.0a0", + "rmm==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. dependencies-file = "../../dependencies.yaml" matrix-entry = "cuda_suffixed=true" diff --git a/python/nx-cugraph/pyproject.toml b/python/nx-cugraph/pyproject.toml index e7b4ea44dd8..239d894f5b7 100644 --- a/python/nx-cugraph/pyproject.toml +++ b/python/nx-cugraph/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "cupy-cuda11x>=12.0.0", "networkx>=3.0", "numpy>=1.23,<2.0a0", - "pylibcugraph==24.10.*,>=0.0.0a0", + "pylibcugraph==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.optional-dependencies] diff --git a/python/pylibcugraph/pyproject.toml b/python/pylibcugraph/pyproject.toml index 92c417f0372..98bbe255e3e 100644 --- a/python/pylibcugraph/pyproject.toml +++ b/python/pylibcugraph/pyproject.toml @@ -27,8 +27,8 @@ dependencies = [ "nvidia-curand", "nvidia-cusolver", "nvidia-cusparse", - "pylibraft==24.10.*,>=0.0.0a0", - "rmm==24.10.*,>=0.0.0a0", + "pylibraft==24.12.*,>=0.0.0a0", + "rmm==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", @@ -40,7 +40,7 @@ classifiers = [ [project.optional-dependencies] test = [ - "cudf==24.10.*,>=0.0.0a0", + "cudf==24.12.*,>=0.0.0a0", "numpy>=1.23,<2.0a0", "pandas", "pytest", @@ -74,7 +74,7 @@ dependencies-file = "../../dependencies.yaml" requires = [ "cmake>=3.26.4,!=3.30.0", "ninja", - "pylibraft==24.10.*,>=0.0.0a0", - "rmm==24.10.*,>=0.0.0a0", + "pylibraft==24.12.*,>=0.0.0a0", + "rmm==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true" From 7e058e2961b5b8be05877282d26051f36dd73c53 Mon Sep 17 00:00:00 2001 From: Rick Ratzel <3039903+rlratzel@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:12:30 -0500 Subject: [PATCH 06/37] Drops duplicate edges in non-MultiGraph PLC `SGGraph` instances (#4658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Graph input with duplicate edges intended for `Graph`/`DiGraph` instances resulted in internal PLC `SGGraph` instances with duplicate edges, which were effectively treated as MultiGraphs and caused incorrect results from algorithms like `pagerank`. This PR sets the `drop_multi_edges` PLC `SGGraph` ctor option to have PLC remove duplicate edges on `SGGraph` creation. The overhead to drop duplicate edges for non-MultiGraphs is negligible, and in the case of a large test graph (wikipedia data, 37.5M nodes, 464.5M edges) resulted in an overall _speedup_ for pagerank going from 12.2 seconds to 10.7 seconds on my workstation, likely due to fewer edges to process a minor slowdown from 10.5s to 10.7s. _edit: after several re-runs, the pagerank runtime before the change settled to 10.5, and the runtime after the change was typically 10.7._ A test was added that uses pagerank to ensure Graphs vs. MultiGraphs are handled correctly and duplicate edges are dropped as needed. The results when run without `drop_multi_edges` set: ``` > assert actual_pr_for_G == approx(expected_pr_for_G) E assert {0: 0.0875795...7955580949783} == approx({0: 0....32 ± 1.8e-07}) E E comparison failed. Mismatched elements: 4 / 4: E Max absolute difference: 0.08785887916592061 E Max relative difference: 0.5007959662968462 E Index | Obtained | Expected E 0 | 0.08757955580949783 | 0.17543839772251532 ± 1.8e-07 E 1 | 0.41242048144340515 | 0.32456160227748454 ± 3.2e-07 E 2 | 0.41242048144340515 | 0.32456160227748454 ± 3.2e-07 E 3 | 0.08757955580949783 | 0.17543839772251532 ± 1.8e-07 ``` The same test passes when run with the changes in this PR to set `drop_multi_edges`. Authors: - Rick Ratzel (https://github.com/rlratzel) Approvers: - Erik Welch (https://github.com/eriknw) URL: https://github.com/rapidsai/cugraph/pull/4658 --- python/nx-cugraph/nx_cugraph/classes/graph.py | 9 +++++ .../nx_cugraph/tests/test_pagerank.py | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 python/nx-cugraph/nx_cugraph/tests/test_pagerank.py diff --git a/python/nx-cugraph/nx_cugraph/classes/graph.py b/python/nx-cugraph/nx_cugraph/classes/graph.py index 7425eacb2b4..7c01365c0ac 100644 --- a/python/nx-cugraph/nx_cugraph/classes/graph.py +++ b/python/nx-cugraph/nx_cugraph/classes/graph.py @@ -689,6 +689,14 @@ def _get_plc_graph( src_indices = src_indices.astype(index_dtype) dst_indices = dst_indices.astype(index_dtype) + # This sets drop_multi_edges=True for non-multigraph input, which means + # the data in self.src_indices and self.dst_indices may not be + # identical to that contained in the returned pcl.SGGraph (the returned + # SGGraph may have fewer edges since duplicates are dropped). Ideally + # self.src_indices and self.dst_indices would be updated to have + # duplicate edges removed for non-multigraph instances, but that + # requires additional code which would be redundant and likely not as + # performant as the code in PLC. return plc.SGGraph( resource_handle=plc.ResourceHandle(), graph_properties=plc.GraphProperties( @@ -702,6 +710,7 @@ def _get_plc_graph( renumber=False, do_expensive_check=False, vertices_array=self._node_ids, + drop_multi_edges=not self.is_multigraph(), ) def _sort_edge_indices(self, primary="src"): diff --git a/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py b/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py new file mode 100644 index 00000000000..0b437df2d2f --- /dev/null +++ b/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py @@ -0,0 +1,36 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import networkx as nx +import pandas as pd +from pytest import approx + + +def test_pagerank_multigraph(): + """ + Ensures correct differences between pagerank results for Graphs + vs. MultiGraphs generated using from_pandas_edgelist() + """ + df = pd.DataFrame({"source": [0, 1, 1, 1, 1, 1, 1, 2], + "target": [1, 2, 2, 2, 2, 2, 2, 3]}) + expected_pr_for_G = nx.pagerank(nx.from_pandas_edgelist(df)) + expected_pr_for_MultiG = nx.pagerank( + nx.from_pandas_edgelist(df, create_using=nx.MultiGraph)) + + G = nx.from_pandas_edgelist(df, backend="cugraph") + actual_pr_for_G = nx.pagerank(G, backend="cugraph") + + MultiG = nx.from_pandas_edgelist(df, create_using=nx.MultiGraph, backend="cugraph") + actual_pr_for_MultiG = nx.pagerank(MultiG, backend="cugraph") + + assert actual_pr_for_G == approx(expected_pr_for_G) + assert actual_pr_for_MultiG == approx(expected_pr_for_MultiG) From 607ee5bcdc5fa75f097d5739169e9d2911106378 Mon Sep 17 00:00:00 2001 From: Jake Awe <50372925+AyodeAwe@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:11:08 -0500 Subject: [PATCH 07/37] Update update-version.sh to use packaging lib (#4664) This PR updates the update-version.sh script to use the packaging library, given that setuptools is no longer included by default in Python 3.12. --- ci/release/update-version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 36ab7252117..5859ebde953 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -45,8 +45,8 @@ function sed_runner() { echo "${NEXT_FULL_TAG}" > VERSION # Need to distutils-normalize the original version -NEXT_SHORT_TAG_PEP440=$(python -c "from setuptools.extern import packaging; print(packaging.version.Version('${NEXT_SHORT_TAG}'))") -NEXT_UCXX_SHORT_TAG_PEP440=$(python -c "from setuptools.extern import packaging; print(packaging.version.Version('${NEXT_UCXX_SHORT_TAG}'))") +NEXT_SHORT_TAG_PEP440=$(python -c "from packaging.version import Version; print(Version('${NEXT_SHORT_TAG}'))") +NEXT_UCXX_SHORT_TAG_PEP440=$(python -c "from packaging.version import Version; print(Version('${NEXT_UCXX_SHORT_TAG}'))") DEPENDENCIES=( cudf From 59217ad009ad3febd13629b9eebb5681e47e3ab0 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Tue, 24 Sep 2024 17:20:07 -0500 Subject: [PATCH 08/37] nx-cugraph: Updates nxcg.Graph classes for API-compatibility with NetworkX Graph classes, needed for zero code change graph generators (#4629) This is an alternative approach to #4558 for enabling GPU-accelerated NetworkX to "just work". It has similarities to #4558. I opted to make separate classes such as `ZeroGraph`, which I think makes for cleaner separation and gives us and users more control. There are a few lingering TODOs and code comments to tidy up, but I don't think there are any show-stoppers. I have not updated methods (such as `number_of_nodes`) to optimistically try to use GPU if possible, b/c this is not strictly necessary, but we should update these soon. I have run NetworkX tests with these classes using https://github.com/networkx/networkx/pull/7585 and https://github.com/networkx/networkx/pull/7600. We need the behavior in 7585, and 7600 is only useful for testing. There are only 3 new failing tests, and 3 tests that hang (I'll run them overnight to see if they finish). Here's a test summary: ``` 5548 passed, 24 skipped, 16 xfailed, 25 xpassed ``` Note that 25 tests that were failing now pass. I have not investigated test failures, xfails, or xpasses yet. I would like to add tests too. We rely heavily on the networkx cache. I think this is preferred. It is late for me. I will describe and show how and why this works later. I opted for `zero=` and `ZeroGraph`, because I find them delightful! Renaming is trivial if other terms are preferred. CC @quasiben Authors: - Erik Welch (https://github.com/eriknw) Approvers: - Bradley Dice (https://github.com/bdice) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4629 --- ci/run_nx_cugraph_pytests.sh | 3 +- ci/test_python.sh | 2 +- ci/test_wheel.sh | 1 + .../all_cuda-118_arch-x86_64.yaml | 1 - .../all_cuda-125_arch-x86_64.yaml | 1 - dependencies.yaml | 1 - python/nx-cugraph/_nx_cugraph/__init__.py | 17 +- python/nx-cugraph/lint.yaml | 16 +- python/nx-cugraph/nx_cugraph/__init__.py | 14 +- .../algorithms/bipartite/generators.py | 3 +- .../algorithms/community/louvain.py | 6 +- .../nx-cugraph/nx_cugraph/algorithms/core.py | 7 +- .../algorithms/link_analysis/hits_alg.py | 3 +- .../nx_cugraph/algorithms/operators/unary.py | 12 +- .../algorithms/shortest_paths/generic.py | 5 +- .../algorithms/shortest_paths/unweighted.py | 5 +- .../traversal/breadth_first_search.py | 13 +- .../nx-cugraph/nx_cugraph/classes/__init__.py | 10 +- .../nx-cugraph/nx_cugraph/classes/digraph.py | 89 +++- python/nx-cugraph/nx_cugraph/classes/graph.py | 399 +++++++++++++++--- .../nx_cugraph/classes/multidigraph.py | 35 +- .../nx_cugraph/classes/multigraph.py | 152 ++++--- python/nx-cugraph/nx_cugraph/convert.py | 132 +++++- .../nx-cugraph/nx_cugraph/convert_matrix.py | 8 +- .../nx_cugraph/generators/_utils.py | 16 +- .../nx_cugraph/generators/classic.py | 7 +- .../nx_cugraph/generators/community.py | 7 +- .../nx-cugraph/nx_cugraph/generators/ego.py | 11 +- .../nx-cugraph/nx_cugraph/generators/small.py | 10 +- .../nx_cugraph/generators/social.py | 29 +- python/nx-cugraph/nx_cugraph/interface.py | 248 ++++++----- python/nx-cugraph/nx_cugraph/relabel.py | 17 +- .../nx-cugraph/nx_cugraph/tests/test_bfs.py | 5 +- .../nx_cugraph/tests/test_classes.py | 77 ++++ .../nx_cugraph/tests/test_cluster.py | 5 +- .../nx_cugraph/tests/test_convert.py | 3 - .../nx_cugraph/tests/test_ego_graph.py | 36 +- .../nx_cugraph/tests/test_generators.py | 42 +- .../nx_cugraph/tests/test_graph_methods.py | 4 +- .../nx_cugraph/tests/test_match_api.py | 3 - .../nx_cugraph/tests/test_multigraph.py | 6 +- .../nx_cugraph/tests/test_pagerank.py | 20 +- .../nx_cugraph/tests/testing_utils.py | 2 +- .../nx-cugraph/nx_cugraph/utils/decorators.py | 21 +- python/nx-cugraph/nx_cugraph/utils/misc.py | 36 +- python/nx-cugraph/pyproject.toml | 4 +- python/nx-cugraph/run_nx_tests.sh | 4 + 47 files changed, 1196 insertions(+), 352 deletions(-) create mode 100644 python/nx-cugraph/nx_cugraph/tests/test_classes.py diff --git a/ci/run_nx_cugraph_pytests.sh b/ci/run_nx_cugraph_pytests.sh index b0caffd0a0f..0e309d1e2d4 100755 --- a/ci/run_nx_cugraph_pytests.sh +++ b/ci/run_nx_cugraph_pytests.sh @@ -6,4 +6,5 @@ set -euo pipefail # Support invoking run_nx_cugraph_pytests.sh outside the script directory cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../python/nx-cugraph/nx_cugraph -pytest --capture=no --cache-clear --benchmark-disable "$@" tests +NX_CUGRAPH_USE_COMPAT_GRAPHS=False pytest --capture=no --cache-clear --benchmark-disable "$@" tests +NX_CUGRAPH_USE_COMPAT_GRAPHS=True pytest --capture=no --cache-clear --benchmark-disable "$@" tests diff --git a/ci/test_python.sh b/ci/test_python.sh index e8c8272e8d6..810284b8c97 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -108,7 +108,7 @@ echo "nx-cugraph coverage from networkx tests: $_coverage" echo $_coverage | awk '{ if ($NF == "0.0%") exit 1 }' # Ensure all algorithms were called by comparing covered lines to function lines. # Run our tests again (they're fast enough) to add their coverage, then create coverage.json -pytest \ +NX_CUGRAPH_USE_COMPAT_GRAPHS=False pytest \ --pyargs nx_cugraph \ --config-file=../pyproject.toml \ --cov-config=../pyproject.toml \ diff --git a/ci/test_wheel.sh b/ci/test_wheel.sh index 158704e08d1..e3690dfde6e 100755 --- a/ci/test_wheel.sh +++ b/ci/test_wheel.sh @@ -37,6 +37,7 @@ else DASK_DISTRIBUTED__SCHEDULER__WORKER_TTL="1000s" \ DASK_DISTRIBUTED__COMM__TIMEOUTS__CONNECT="1000s" \ DASK_CUDA_WAIT_WORKERS_MIN_TIMEOUT="1000s" \ + NX_CUGRAPH_USE_COMPAT_GRAPHS=False \ python -m pytest \ -v \ --import-mode=append \ diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 18cca40c320..533f23cd7ac 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -44,7 +44,6 @@ dependencies: - nvcc_linux-64=11.8 - ogb - openmpi -- packaging>=21 - pandas - pre-commit - pydantic diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index ef20371e0f5..084a6adfd31 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -49,7 +49,6 @@ dependencies: - numpydoc - ogb - openmpi -- packaging>=21 - pandas - pre-commit - pydantic diff --git a/dependencies.yaml b/dependencies.yaml index 2c8335868ba..76048be2010 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -688,7 +688,6 @@ dependencies: common: - output_types: [conda, pyproject] packages: - - packaging>=21 # not needed by nx-cugraph tests, but is required for running networkx tests - pytest-mpl cugraph_dgl_dev: diff --git a/python/nx-cugraph/_nx_cugraph/__init__.py b/python/nx-cugraph/_nx_cugraph/__init__.py index 41c18c27ecf..428d266dd2e 100644 --- a/python/nx-cugraph/_nx_cugraph/__init__.py +++ b/python/nx-cugraph/_nx_cugraph/__init__.py @@ -22,6 +22,7 @@ $ python _nx_cugraph/__init__.py """ +import os from _nx_cugraph._version import __version__ @@ -293,12 +294,20 @@ def get_info(): for key in info_keys: del d[key] + + d["default_config"] = { + "use_compat_graphs": os.environ.get("NX_CUGRAPH_USE_COMPAT_GRAPHS", "true") + .strip() + .lower() + == "true", + } return d -def _check_networkx_version(): - import warnings +def _check_networkx_version() -> tuple[int, int]: + """Check the version of networkx and return ``(major, minor)`` version tuple.""" import re + import warnings import networkx as nx @@ -321,6 +330,10 @@ def _check_networkx_version(): f"{nx.__version__}. Please upgrade (or fix) your Python environment." ) + nxver_major = int(version_major) + nxver_minor = int(re.match(r"^\d+", version_minor).group()) + return (nxver_major, nxver_minor) + if __name__ == "__main__": from pathlib import Path diff --git a/python/nx-cugraph/lint.yaml b/python/nx-cugraph/lint.yaml index b2184a185c4..dab2ea70ef1 100644 --- a/python/nx-cugraph/lint.yaml +++ b/python/nx-cugraph/lint.yaml @@ -26,7 +26,7 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.18 + rev: v0.19 hooks: - id: validate-pyproject name: Validate pyproject.toml @@ -40,29 +40,29 @@ repos: hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade args: [--py310-plus] - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black # - id: black-jupyter - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.4 + rev: v0.6.7 hooks: - id: ruff args: [--fix-only, --show-fixes] # --unsafe-fixes] - repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 args: ['--per-file-ignores=_nx_cugraph/__init__.py:E501', '--extend-ignore=B020,SIM105'] # Why is this necessary? additional_dependencies: &flake8_dependencies # These versions need updated manually - - flake8==7.1.0 - - flake8-bugbear==24.4.26 + - flake8==7.1.1 + - flake8-bugbear==24.8.19 - flake8-simplify==0.21.0 - repo: https://github.com/asottile/yesqa rev: v1.5.0 @@ -77,7 +77,7 @@ repos: additional_dependencies: [tomli] files: ^(nx_cugraph|docs)/ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.4 + rev: v0.6.7 hooks: - id: ruff - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/python/nx-cugraph/nx_cugraph/__init__.py b/python/nx-cugraph/nx_cugraph/__init__.py index 542256fa781..4404e57f645 100644 --- a/python/nx-cugraph/nx_cugraph/__init__.py +++ b/python/nx-cugraph/nx_cugraph/__init__.py @@ -12,6 +12,11 @@ # limitations under the License. from networkx.exception import * +from _nx_cugraph._version import __git_commit__, __version__ +from _nx_cugraph import _check_networkx_version + +_nxver: tuple[int, int] = _check_networkx_version() + from . import utils from . import classes @@ -32,7 +37,10 @@ from . import algorithms from .algorithms import * -from _nx_cugraph._version import __git_commit__, __version__ -from _nx_cugraph import _check_networkx_version +from .interface import BackendInterface -_check_networkx_version() +BackendInterface.Graph = classes.Graph +BackendInterface.DiGraph = classes.DiGraph +BackendInterface.MultiGraph = classes.MultiGraph +BackendInterface.MultiDiGraph = classes.MultiDiGraph +del BackendInterface diff --git a/python/nx-cugraph/nx_cugraph/algorithms/bipartite/generators.py b/python/nx-cugraph/nx_cugraph/algorithms/bipartite/generators.py index 60276b7d41b..214970235c6 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/bipartite/generators.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/bipartite/generators.py @@ -16,6 +16,7 @@ import networkx as nx import numpy as np +from nx_cugraph import _nxver from nx_cugraph.generators._utils import _create_using_class, _number_and_nodes from nx_cugraph.utils import index_dtype, networkx_algorithm @@ -48,7 +49,7 @@ def complete_bipartite_graph(n1, n2, create_using=None): nodes.extend(range(n2) if nodes2 is None else nodes2) if len(set(nodes)) != len(nodes): raise nx.NetworkXError("Inputs n1 and n2 must contain distinct nodes") - if nx.__version__[:3] <= "3.3": + if _nxver <= (3, 3): name = f"complete_bipartite_graph({orig_n1}, {orig_n2})" else: name = f"complete_bipartite_graph({n1}, {n2})" diff --git a/python/nx-cugraph/nx_cugraph/algorithms/community/louvain.py b/python/nx-cugraph/nx_cugraph/algorithms/community/louvain.py index ea1318060e0..52c512c454d 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/community/louvain.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/community/louvain.py @@ -12,9 +12,9 @@ # limitations under the License. import warnings -import networkx as nx import pylibcugraph as plc +from nx_cugraph import _nxver from nx_cugraph.convert import _to_undirected_graph from nx_cugraph.utils import ( _dtype_param, @@ -27,7 +27,7 @@ __all__ = ["louvain_communities"] # max_level argument was added to NetworkX 3.3 -if nx.__version__[:3] <= "3.2": +if _nxver <= (3, 2): _max_level_param = { "max_level : int, optional": ( "Upper limit of the number of macro-iterations (max: 500)." @@ -81,7 +81,7 @@ def _louvain_communities( node_ids, clusters, modularity = plc.louvain( resource_handle=plc.ResourceHandle(), graph=G._get_plc_graph(weight, 1, dtype), - max_level=max_level, # TODO: add this parameter to NetworkX + max_level=max_level, threshold=threshold, resolution=resolution, do_expensive_check=False, diff --git a/python/nx-cugraph/nx_cugraph/algorithms/core.py b/python/nx-cugraph/nx_cugraph/algorithms/core.py index 8eb9a9946e7..e69ee88a17c 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/core.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/core.py @@ -15,6 +15,7 @@ import pylibcugraph as plc import nx_cugraph as nxcg +from nx_cugraph import _nxver from nx_cugraph.convert import _to_undirected_graph from nx_cugraph.utils import ( _get_int_dtype, @@ -58,9 +59,12 @@ def _(G): @networkx_algorithm(is_incomplete=True, version_added="23.12", _plc="k_truss_subgraph") def k_truss(G, k): if is_nx := isinstance(G, nx.Graph): + is_compat_graph = isinstance(G, nxcg.Graph) G = nxcg.from_networkx(G, preserve_all_attrs=True) + else: + is_compat_graph = False if nxcg.number_of_selfloops(G) > 0: - if nx.__version__[:3] <= "3.2": + if _nxver <= (3, 2): exc_class = nx.NetworkXError else: exc_class = nx.NetworkXNotImplemented @@ -128,6 +132,7 @@ def k_truss(G, k): node_values, node_masks, key_to_id=key_to_id, + use_compat_graph=is_compat_graph, ) new_graph.graph.update(G.graph) return new_graph diff --git a/python/nx-cugraph/nx_cugraph/algorithms/link_analysis/hits_alg.py b/python/nx-cugraph/nx_cugraph/algorithms/link_analysis/hits_alg.py index e529b83ab1a..cc59fd5eb64 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/link_analysis/hits_alg.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/link_analysis/hits_alg.py @@ -15,6 +15,7 @@ import numpy as np import pylibcugraph as plc +from nx_cugraph import _nxver from nx_cugraph.convert import _to_graph from nx_cugraph.utils import ( _dtype_param, @@ -53,7 +54,7 @@ def hits( if nstart is not None: nstart = G._dict_to_nodearray(nstart, 0, dtype) if max_iter <= 0: - if nx.__version__[:3] <= "3.2": + if _nxver <= (3, 2): raise ValueError("`maxiter` must be a positive integer.") raise nx.PowerIterationFailedConvergence(max_iter) try: diff --git a/python/nx-cugraph/nx_cugraph/algorithms/operators/unary.py b/python/nx-cugraph/nx_cugraph/algorithms/operators/unary.py index f53b3458949..75dc5fbc706 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/operators/unary.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/operators/unary.py @@ -23,6 +23,7 @@ @networkx_algorithm(version_added="24.02") def complement(G): + is_compat_graph = isinstance(G, nxcg.Graph) G = _to_graph(G) N = G._N # Upcast to int64 so indices don't overflow. @@ -43,6 +44,7 @@ def complement(G): src_indices.astype(index_dtype), dst_indices.astype(index_dtype), key_to_id=G.key_to_id, + use_compat_graph=is_compat_graph, ) @@ -51,10 +53,16 @@ def reverse(G, copy=True): if not G.is_directed(): raise nx.NetworkXError("Cannot reverse an undirected graph.") if isinstance(G, nx.Graph): - if not copy: + is_compat_graph = isinstance(G, nxcg.Graph) + if not copy and not is_compat_graph: raise RuntimeError( "Using `copy=False` is invalid when using a NetworkX graph " "as input to `nx_cugraph.reverse`" ) G = nxcg.from_networkx(G, preserve_all_attrs=True) - return G.reverse(copy=copy) + else: + is_compat_graph = False + rv = G.reverse(copy=copy) + if is_compat_graph: + return rv._to_compat_graph() + return rv diff --git a/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/generic.py b/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/generic.py index 7d6d77f34a4..ab3c7214303 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/generic.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/generic.py @@ -14,6 +14,7 @@ import numpy as np import nx_cugraph as nxcg +from nx_cugraph import _nxver from nx_cugraph.convert import _to_graph from nx_cugraph.utils import _dtype_param, _get_float_dtype, networkx_algorithm @@ -57,7 +58,7 @@ def shortest_path( paths = nxcg.all_pairs_dijkstra_path(G, weight=weight, dtype=dtype) else: # method == 'bellman-ford': paths = nxcg.all_pairs_bellman_ford_path(G, weight=weight, dtype=dtype) - if nx.__version__[:3] <= "3.4": + if _nxver <= (3, 4): paths = dict(paths) # To target elif method == "unweighted": @@ -129,7 +130,7 @@ def shortest_path_length( # To target elif method == "unweighted": lengths = nxcg.single_target_shortest_path_length(G, target) - if nx.__version__[:3] <= "3.4": + if _nxver <= (3, 4): lengths = dict(lengths) elif method == "dijkstra": lengths = nxcg.single_source_dijkstra_path_length( diff --git a/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/unweighted.py b/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/unweighted.py index 0e98c366e4a..e9c515632ca 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/unweighted.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/shortest_paths/unweighted.py @@ -17,6 +17,7 @@ import numpy as np import pylibcugraph as plc +from nx_cugraph import _nxver from nx_cugraph.convert import _to_graph from nx_cugraph.utils import _groupby, index_dtype, networkx_algorithm @@ -43,7 +44,7 @@ def single_source_shortest_path_length(G, source, cutoff=None): def single_target_shortest_path_length(G, target, cutoff=None): G = _to_graph(G) rv = _bfs(G, target, cutoff, "Target", return_type="length") - if nx.__version__[:3] <= "3.4": + if _nxver <= (3, 4): return iter(rv.items()) return rv @@ -61,7 +62,7 @@ def bidirectional_shortest_path(G, source, target): # TODO PERF: do bidirectional traversal in core G = _to_graph(G) if source not in G or target not in G: - if nx.__version__[:3] <= "3.3": + if _nxver <= (3, 3): raise nx.NodeNotFound( f"Either source {source} or target {target} is not in G" ) diff --git a/python/nx-cugraph/nx_cugraph/algorithms/traversal/breadth_first_search.py b/python/nx-cugraph/nx_cugraph/algorithms/traversal/breadth_first_search.py index 5e4466d7d33..72d0079cf0c 100644 --- a/python/nx-cugraph/nx_cugraph/algorithms/traversal/breadth_first_search.py +++ b/python/nx-cugraph/nx_cugraph/algorithms/traversal/breadth_first_search.py @@ -18,6 +18,7 @@ import pylibcugraph as plc import nx_cugraph as nxcg +from nx_cugraph import _nxver from nx_cugraph.convert import _to_graph from nx_cugraph.utils import _groupby, index_dtype, networkx_algorithm @@ -57,7 +58,7 @@ def _bfs(G, source, *, depth_limit=None, reverse=False): return distances[mask], predecessors[mask], node_ids[mask] -if nx.__version__[:3] <= "3.3": +if _nxver <= (3, 3): @networkx_algorithm(is_incomplete=True, version_added="24.02", _plc="bfs") def generic_bfs_edges( @@ -132,13 +133,15 @@ def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): raise NotImplementedError( "sort_neighbors argument in bfs_tree is not currently supported" ) + is_compat_graph = isinstance(G, nxcg.Graph) G = _check_G_and_source(G, source) if depth_limit is not None and depth_limit < 1: - return nxcg.DiGraph.from_coo( + return nxcg.CudaDiGraph.from_coo( 1, cp.array([], dtype=index_dtype), cp.array([], dtype=index_dtype), id_to_key=[source], + use_compat_graph=is_compat_graph, ) distances, predecessors, node_ids = _bfs( @@ -148,11 +151,12 @@ def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): reverse=reverse, ) if predecessors.size == 0: - return nxcg.DiGraph.from_coo( + return nxcg.CudaDiGraph.from_coo( 1, cp.array([], dtype=index_dtype), cp.array([], dtype=index_dtype), id_to_key=[source], + use_compat_graph=is_compat_graph, ) # TODO: create renumbering helper function(s) unique_node_ids = cp.unique(cp.hstack((predecessors, node_ids))) @@ -170,11 +174,12 @@ def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): old_index: new_index for new_index, old_index in enumerate(unique_node_ids.tolist()) } - return nxcg.DiGraph.from_coo( + return nxcg.CudaDiGraph.from_coo( unique_node_ids.size, src_indices, dst_indices, key_to_id=key_to_id, + use_compat_graph=is_compat_graph, ) diff --git a/python/nx-cugraph/nx_cugraph/classes/__init__.py b/python/nx-cugraph/nx_cugraph/classes/__init__.py index 19a5357da55..71168e5364f 100644 --- a/python/nx-cugraph/nx_cugraph/classes/__init__.py +++ b/python/nx-cugraph/nx_cugraph/classes/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,9 +10,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from .graph import Graph -from .digraph import DiGraph -from .multigraph import MultiGraph -from .multidigraph import MultiDiGraph +from .graph import CudaGraph, Graph +from .digraph import CudaDiGraph, DiGraph +from .multigraph import CudaMultiGraph, MultiGraph +from .multidigraph import CudaMultiDiGraph, MultiDiGraph from .function import * diff --git a/python/nx-cugraph/nx_cugraph/classes/digraph.py b/python/nx-cugraph/nx_cugraph/classes/digraph.py index e5cfb8f6815..178bf44f16e 100644 --- a/python/nx-cugraph/nx_cugraph/classes/digraph.py +++ b/python/nx-cugraph/nx_cugraph/classes/digraph.py @@ -18,34 +18,108 @@ import cupy as cp import networkx as nx import numpy as np +from networkx.classes.digraph import ( + _CachedPropertyResetterAdjAndSucc, + _CachedPropertyResetterPred, +) import nx_cugraph as nxcg from ..utils import index_dtype -from .graph import Graph +from .graph import CudaGraph, Graph if TYPE_CHECKING: # pragma: no cover from nx_cugraph.typing import AttrKey -__all__ = ["DiGraph"] +__all__ = ["CudaDiGraph", "DiGraph"] networkx_api = nxcg.utils.decorators.networkx_class(nx.DiGraph) -class DiGraph(Graph): - ################# - # Class methods # - ################# +class DiGraph(nx.DiGraph, Graph): + _nx_attrs = ("_node", "_adj", "_succ", "_pred") + + name = Graph.name + _node = Graph._node + + @property + @networkx_api + def _adj(self): + if (adj := self.__dict__["_adj"]) is None: + self._reify_networkx() + adj = self.__dict__["_adj"] + return adj + + @_adj.setter + def _adj(self, val): + self._prepare_setter() + _CachedPropertyResetterAdjAndSucc.__set__(None, self, val) + if cache := getattr(self, "__networkx_cache__", None): + cache.clear() + + @property + @networkx_api + def _succ(self): + if (succ := self.__dict__["_succ"]) is None: + self._reify_networkx() + succ = self.__dict__["_succ"] + return succ + + @_succ.setter + def _succ(self, val): + self._prepare_setter() + _CachedPropertyResetterAdjAndSucc.__set__(None, self, val) + if cache := getattr(self, "__networkx_cache__", None): + cache.clear() + + @property + @networkx_api + def _pred(self): + if (pred := self.__dict__["_pred"]) is None: + self._reify_networkx() + pred = self.__dict__["_pred"] + return pred + + @_pred.setter + def _pred(self, val): + self._prepare_setter() + _CachedPropertyResetterPred.__set__(None, self, val) + if cache := getattr(self, "__networkx_cache__", None): + cache.clear() @classmethod @networkx_api def is_directed(cls) -> bool: return True + @classmethod + @networkx_api + def is_multigraph(cls) -> bool: + return False + + @classmethod + def to_cudagraph_class(cls) -> type[CudaDiGraph]: + return CudaDiGraph + @classmethod def to_networkx_class(cls) -> type[nx.DiGraph]: return nx.DiGraph + +class CudaDiGraph(CudaGraph): + ################# + # Class methods # + ################# + + is_directed = classmethod(DiGraph.is_directed.__func__) + is_multigraph = classmethod(DiGraph.is_multigraph.__func__) + to_cudagraph_class = classmethod(DiGraph.to_cudagraph_class.__func__) + to_networkx_class = classmethod(DiGraph.to_networkx_class.__func__) + + @classmethod + def _to_compat_graph_class(cls) -> type[DiGraph]: + return DiGraph + @networkx_api def size(self, weight: AttrKey | None = None) -> int: if weight is not None: @@ -57,7 +131,7 @@ def size(self, weight: AttrKey | None = None) -> int: ########################## @networkx_api - def reverse(self, copy: bool = True) -> DiGraph: + def reverse(self, copy: bool = True) -> CudaDiGraph: return self._copy(not copy, self.__class__, reverse=True) @networkx_api @@ -162,6 +236,7 @@ def to_undirected(self, reciprocal=False, as_view=False): node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=False, ) if as_view: rv.graph = self.graph diff --git a/python/nx-cugraph/nx_cugraph/classes/graph.py b/python/nx-cugraph/nx_cugraph/classes/graph.py index 7c01365c0ac..cfe1e1c87e9 100644 --- a/python/nx-cugraph/nx_cugraph/classes/graph.py +++ b/python/nx-cugraph/nx_cugraph/classes/graph.py @@ -20,8 +20,13 @@ import networkx as nx import numpy as np import pylibcugraph as plc +from networkx.classes.graph import ( + _CachedPropertyResetterAdj, + _CachedPropertyResetterNode, +) import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import index_dtype @@ -40,57 +45,246 @@ any_ndarray, ) -__all__ = ["Graph"] +__all__ = ["CudaGraph", "Graph"] networkx_api = nxcg.utils.decorators.networkx_class(nx.Graph) +# The "everything" cache key is an internal implementation detail of NetworkX +# that may change between releases. +if _nxver < (3, 4): + _CACHE_KEY = ( + True, # Include all edge values + True, # Include all node values + True, # Include `.graph` attributes + ) +else: + _CACHE_KEY = ( + True, # Include all edge values + True, # Include all node values + # `.graph` attributes are always included now + ) + +# Use to indicate when a full conversion to GPU failed so we don't try again. +_CANT_CONVERT_TO_GPU = "_CANT_CONVERT_TO_GPU" + + +# `collections.UserDict` was the preferred way to subclass dict, but now +# subclassing dict directly is much better supported and should work here. +# This class should only be necessary if the user clears the cache manually. +class _GraphCache(dict): + """Cache that ensures Graph will reify into a NetworkX graph when cleared.""" + + _graph: Graph -class Graph: + def __init__(self, graph: Graph): + self._graph = graph + + def clear(self) -> None: + self._graph._reify_networkx() + super().clear() + + +class Graph(nx.Graph): # Tell networkx to dispatch calls with this object to nx-cugraph __networkx_backend__: ClassVar[str] = "cugraph" # nx >=3.2 __networkx_plugin__: ClassVar[str] = "cugraph" # nx <3.2 + # Core attributes of NetowkrX graphs that will be copied and cleared as appropriate. + # These attributes comprise the edge and node data model for NetworkX graphs. + _nx_attrs = ("_node", "_adj") + # Allow networkx dispatch machinery to cache conversions. # This means we should clear the cache if we ever mutate the object! - __networkx_cache__: dict | None + __networkx_cache__: _GraphCache | None # networkx properties graph: dict - graph_attr_dict_factory: ClassVar[type] = dict + # Should we declare type annotations for the rest? + + # Properties that trigger copying to the CPU + def _prepare_setter(self): + """Be careful when setting private attributes which may be used during init.""" + if ( + # If not present, then this must be in init + any(attr not in self.__dict__ for attr in self._nx_attrs) + # Already on the CPU + or not any(self.__dict__[attr] is None for attr in self._nx_attrs) + ): + return + if self._is_on_gpu: + # Copy from GPU to CPU + self._reify_networkx() + return + # Default values + for attr in self._nx_attrs: + if self.__dict__[attr] is None: + if attr == "_succ": + self.__dict__[attr] = self.__dict__["_adj"] + else: + self.__dict__[attr] = {} - # Not networkx properties - # We store edge data in COO format with {src,dst}_indices and edge_values. - src_indices: cp.ndarray[IndexValue] - dst_indices: cp.ndarray[IndexValue] - edge_values: dict[AttrKey, cp.ndarray[EdgeValue]] - edge_masks: dict[AttrKey, cp.ndarray[bool]] - node_values: dict[AttrKey, any_ndarray[NodeValue]] - node_masks: dict[AttrKey, any_ndarray[bool]] - key_to_id: dict[NodeKey, IndexValue] | None - _id_to_key: list[NodeKey] | None - _N: int - _node_ids: cp.ndarray[IndexValue] | None # holds plc.SGGraph.vertices_array data + @property + @networkx_api + def _node(self): + if (node := self.__dict__["_node"]) is None: + self._reify_networkx() + node = self.__dict__["_node"] + return node + + @_node.setter + def _node(self, val): + self._prepare_setter() + _CachedPropertyResetterNode.__set__(None, self, val) + if cache := getattr(self, "__networkx_cache__", None): + cache.clear() - # Used by graph._get_plc_graph - _plc_type_map: ClassVar[dict[np.dtype, np.dtype]] = { - # signed int - np.dtype(np.int8): np.dtype(np.float32), - np.dtype(np.int16): np.dtype(np.float32), - np.dtype(np.int32): np.dtype(np.float64), - np.dtype(np.int64): np.dtype(np.float64), # raise if abs(x) > 2**53 - # unsigned int - np.dtype(np.uint8): np.dtype(np.float32), - np.dtype(np.uint16): np.dtype(np.float32), - np.dtype(np.uint32): np.dtype(np.float64), - np.dtype(np.uint64): np.dtype(np.float64), # raise if x > 2**53 - # other - np.dtype(np.bool_): np.dtype(np.float32), - np.dtype(np.float16): np.dtype(np.float32), - } - _plc_allowed_edge_types: ClassVar[set[np.dtype]] = { - np.dtype(np.float32), - np.dtype(np.float64), - } + @property + @networkx_api + def _adj(self): + if (adj := self.__dict__["_adj"]) is None: + self._reify_networkx() + adj = self.__dict__["_adj"] + return adj + + @_adj.setter + def _adj(self, val): + self._prepare_setter() + _CachedPropertyResetterAdj.__set__(None, self, val) + if cache := getattr(self, "__networkx_cache__", None): + cache.clear() + + @property + def _is_on_gpu(self) -> bool: + """Whether the full graph is on device (in the cache). + + This returns False when only a subset of the graph (such as only + edge indices and edge attribute) is on device. + + The graph may be on host (CPU) and device (GPU) at the same time. + """ + cache = getattr(self, "__networkx_cache__", None) + if not cache: + return False + return _CACHE_KEY in cache.get("backends", {}).get("cugraph", {}) + + @property + def _is_on_cpu(self) -> bool: + """Whether the graph is on host as a NetworkX graph. + + This means the core data structures that comprise a NetworkX graph + (such as ``G._node`` and ``G._adj``) are present. + + The graph may be on host (CPU) and device (GPU) at the same time. + """ + return self.__dict__["_node"] is not None + + @property + def _cudagraph(self): + """Return the full ``CudaGraph`` on device, computing if necessary, or None.""" + nx_cache = getattr(self, "__networkx_cache__", None) + if nx_cache is None: + nx_cache = {} + elif _CANT_CONVERT_TO_GPU in nx_cache: + return None + cache = nx_cache.setdefault("backends", {}).setdefault("cugraph", {}) + if (Gcg := cache.get(_CACHE_KEY)) is not None: + if isinstance(Gcg, Graph): + # This shouldn't happen during normal use, but be extra-careful anyway + return Gcg._cudagraph + return Gcg + if self.__dict__["_node"] is None: + raise RuntimeError( + f"{type(self).__name__} cannot be converted to the GPU, because it is " + "not on the CPU! This is not supposed to be possible. If you believe " + "you have found a bug, please report a minimum reproducible example to " + "https://github.com/rapidsai/cugraph/issues/new/choose" + ) + try: + Gcg = nxcg.from_networkx( + self, preserve_edge_attrs=True, preserve_node_attrs=True + ) + except Exception: + # Should we warn that the full graph can't be on GPU? + nx_cache[_CANT_CONVERT_TO_GPU] = True + return None + Gcg.graph = self.graph + cache[_CACHE_KEY] = Gcg + return Gcg + + @_cudagraph.setter + def _cudagraph(self, val, *, clear_cpu=True): + """Set the full ``CudaGraph`` for this graph, or remove from device if None.""" + if (cache := getattr(self, "__networkx_cache__", None)) is None: + # Should we warn? + return + # TODO: pay close attention to when we should clear the cache, since + # this may or may not be a mutation. + cache = cache.setdefault("backends", {}).setdefault("cugraph", {}) + if val is None: + cache.pop(_CACHE_KEY, None) + else: + self.graph = val.graph + cache[_CACHE_KEY] = val + if clear_cpu: + for key in self._nx_attrs: + self.__dict__[key] = None + + @nx.Graph.name.setter + def name(self, s): + # Don't clear the cache when setting the name, since `.graph` is shared. + # There is a very small risk here for the cache to become (slightly) + # insconsistent if graphs from other backends are cached. + self.graph["name"] = s + + @classmethod + @networkx_api + def is_directed(cls) -> bool: + return False + + @classmethod + @networkx_api + def is_multigraph(cls) -> bool: + return False + + @classmethod + def to_cudagraph_class(cls) -> type[CudaGraph]: + return CudaGraph + + @classmethod + @networkx_api + def to_directed_class(cls) -> type[nxcg.DiGraph]: + return nxcg.DiGraph + + @classmethod + def to_networkx_class(cls) -> type[nx.Graph]: + return nx.Graph + + @classmethod + @networkx_api + def to_undirected_class(cls) -> type[Graph]: + return Graph + + def __init__(self, incoming_graph_data=None, **attr): + super().__init__(incoming_graph_data, **attr) + self.__networkx_cache__ = _GraphCache(self) + + def _reify_networkx(self) -> None: + """Copy graph to host (CPU) if necessary.""" + if self.__dict__["_node"] is None: + # After we make this into an nx graph, we rely on the cache being correct + Gcg = self._cudagraph + G = nxcg.to_networkx(Gcg) + for key in self._nx_attrs: + self.__dict__[key] = G.__dict__[key] + + def _become(self, other: Graph): + if self.__class__ is not other.__class__: + raise TypeError( + "Attempting to update graph inplace with graph of different type!" + ) + # Begin with the simplest implementation; do we need to do more? + self.__dict__.update(other.__dict__) + return self #################### # Creation methods # @@ -109,9 +303,10 @@ def from_coo( *, key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: - new_graph = object.__new__(cls) + ) -> Graph | CudaGraph: + new_graph = object.__new__(cls.to_cudagraph_class()) new_graph.__networkx_cache__ = {} new_graph.src_indices = src_indices new_graph.dst_indices = dst_indices @@ -173,7 +368,8 @@ def from_coo( isolates = nxcg.algorithms.isolate._isolates(new_graph) if len(isolates) > 0: new_graph._node_ids = cp.arange(new_graph._N, dtype=index_dtype) - + if use_compat_graph or use_compat_graph is None and issubclass(cls, Graph): + new_graph = new_graph._to_compat_graph() return new_graph @classmethod @@ -188,8 +384,9 @@ def from_csr( *, key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: + ) -> Graph | CudaGraph: N = indptr.size - 1 src_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead @@ -205,6 +402,7 @@ def from_csr( node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=use_compat_graph, **attr, ) @@ -220,8 +418,9 @@ def from_csc( *, key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: + ) -> Graph | CudaGraph: N = indptr.size - 1 dst_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead @@ -237,6 +436,7 @@ def from_csc( node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=use_compat_graph, **attr, ) @@ -254,8 +454,9 @@ def from_dcsr( *, key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: + ) -> Graph | CudaGraph: src_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead np.repeat(compressed_srcs.get(), cp.diff(indptr).get()) @@ -270,6 +471,7 @@ def from_dcsr( node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=use_compat_graph, **attr, ) @@ -287,8 +489,9 @@ def from_dcsc( *, key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: + ) -> Graph | CudaGraph: dst_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead np.repeat(compressed_dsts.get(), cp.diff(indptr).get()) @@ -303,13 +506,75 @@ def from_dcsc( node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=use_compat_graph, **attr, ) - def __new__(cls, incoming_graph_data=None, **attr) -> Graph: + +class CudaGraph: + # Tell networkx to dispatch calls with this object to nx-cugraph + __networkx_backend__: ClassVar[str] = "cugraph" # nx >=3.2 + __networkx_plugin__: ClassVar[str] = "cugraph" # nx <3.2 + + # Allow networkx dispatch machinery to cache conversions. + # This means we should clear the cache if we ever mutate the object! + __networkx_cache__: dict | None + + # networkx properties + graph: dict + graph_attr_dict_factory: ClassVar[type] = dict + + # Not networkx properties + # We store edge data in COO format with {src,dst}_indices and edge_values. + src_indices: cp.ndarray[IndexValue] + dst_indices: cp.ndarray[IndexValue] + edge_values: dict[AttrKey, cp.ndarray[EdgeValue]] + edge_masks: dict[AttrKey, cp.ndarray[bool]] + node_values: dict[AttrKey, any_ndarray[NodeValue]] + node_masks: dict[AttrKey, any_ndarray[bool]] + key_to_id: dict[NodeKey, IndexValue] | None + _id_to_key: list[NodeKey] | None + _N: int + _node_ids: cp.ndarray[IndexValue] | None # holds plc.SGGraph.vertices_array data + + # Used by graph._get_plc_graph + _plc_type_map: ClassVar[dict[np.dtype, np.dtype]] = { + # signed int + np.dtype(np.int8): np.dtype(np.float32), + np.dtype(np.int16): np.dtype(np.float32), + np.dtype(np.int32): np.dtype(np.float64), + np.dtype(np.int64): np.dtype(np.float64), # raise if abs(x) > 2**53 + # unsigned int + np.dtype(np.uint8): np.dtype(np.float32), + np.dtype(np.uint16): np.dtype(np.float32), + np.dtype(np.uint32): np.dtype(np.float64), + np.dtype(np.uint64): np.dtype(np.float64), # raise if x > 2**53 + # other + np.dtype(np.bool_): np.dtype(np.float32), + np.dtype(np.float16): np.dtype(np.float32), + } + _plc_allowed_edge_types: ClassVar[set[np.dtype]] = { + np.dtype(np.float32), + np.dtype(np.float64), + } + + #################### + # Creation methods # + #################### + + from_coo = classmethod(Graph.from_coo.__func__) + from_csr = classmethod(Graph.from_csr.__func__) + from_csc = classmethod(Graph.from_csc.__func__) + from_dcsr = classmethod(Graph.from_dcsr.__func__) + from_dcsc = classmethod(Graph.from_dcsc.__func__) + + def __new__(cls, incoming_graph_data=None, **attr) -> CudaGraph: if incoming_graph_data is None: new_graph = cls.from_coo( - 0, cp.empty(0, index_dtype), cp.empty(0, index_dtype) + 0, + cp.empty(0, index_dtype), + cp.empty(0, index_dtype), + use_compat_graph=False, ) elif incoming_graph_data.__class__ is cls: new_graph = incoming_graph_data.copy() @@ -318,34 +583,30 @@ def __new__(cls, incoming_graph_data=None, **attr) -> Graph: else: raise NotImplementedError new_graph.graph.update(attr) + # We could return Graph here (if configured), but let's not for now return new_graph ################# # Class methods # ################# - @classmethod - @networkx_api - def is_directed(cls) -> bool: - return False + is_directed = classmethod(Graph.is_directed.__func__) + is_multigraph = classmethod(Graph.is_multigraph.__func__) + to_cudagraph_class = classmethod(Graph.to_cudagraph_class.__func__) + to_networkx_class = classmethod(Graph.to_networkx_class.__func__) @classmethod @networkx_api - def is_multigraph(cls) -> bool: - return False + def to_directed_class(cls) -> type[nxcg.CudaDiGraph]: + return nxcg.CudaDiGraph @classmethod @networkx_api - def to_directed_class(cls) -> type[nxcg.DiGraph]: - return nxcg.DiGraph - - @classmethod - def to_networkx_class(cls) -> type[nx.Graph]: - return nx.Graph + def to_undirected_class(cls) -> type[CudaGraph]: + return CudaGraph @classmethod - @networkx_api - def to_undirected_class(cls) -> type[Graph]: + def _to_compat_graph_class(cls) -> type[Graph]: return Graph ############## @@ -438,7 +699,7 @@ def clear_edges(self) -> None: cache.clear() @networkx_api - def copy(self, as_view: bool = False) -> Graph: + def copy(self, as_view: bool = False) -> CudaGraph: # Does shallow copy in networkx return self._copy(as_view, self.__class__) @@ -534,14 +795,19 @@ def size(self, weight: AttrKey | None = None) -> int: return int(cp.count_nonzero(self.src_indices <= self.dst_indices)) @networkx_api - def to_directed(self, as_view: bool = False) -> nxcg.DiGraph: + def to_directed(self, as_view: bool = False) -> nxcg.CudaDiGraph: return self._copy(as_view, self.to_directed_class()) @networkx_api - def to_undirected(self, as_view: bool = False) -> Graph: + def to_undirected(self, as_view: bool = False) -> CudaGraph: # Does deep copy in networkx return self._copy(as_view, self.to_undirected_class()) + def _to_compat_graph(self) -> Graph: + rv = self._to_compat_graph_class()() + rv._cudagraph = self + return rv + # Not implemented... # adj, adjacency, add_edge, add_edges_from, add_node, # add_nodes_from, add_weighted_edges_from, degree, @@ -552,8 +818,8 @@ def to_undirected(self, as_view: bool = False) -> Graph: # Private methods # ################### - def _copy(self, as_view: bool, cls: type[Graph], reverse: bool = False): - # DRY warning: see also MultiGraph._copy + def _copy(self, as_view: bool, cls: type[CudaGraph], reverse: bool = False): + # DRY warning: see also CudaMultiGraph._copy src_indices = self.src_indices dst_indices = self.dst_indices edge_values = self.edge_values @@ -593,6 +859,7 @@ def _copy(self, as_view: bool, cls: type[Graph], reverse: bool = False): node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=False, ) if as_view: rv.graph = self.graph @@ -714,7 +981,7 @@ def _get_plc_graph( ) def _sort_edge_indices(self, primary="src"): - # DRY warning: see also MultiGraph._sort_edge_indices + # DRY warning: see also CudaMultiGraph._sort_edge_indices if primary == "src": stacked = cp.vstack((self.dst_indices, self.src_indices)) elif primary == "dst": @@ -736,7 +1003,7 @@ def _sort_edge_indices(self, primary="src"): {key: val[indices] for key, val in self.edge_masks.items()} ) - def _become(self, other: Graph): + def _become(self, other: CudaGraph): if self.__class__ is not other.__class__: raise TypeError( "Attempting to update graph inplace with graph of different type!" diff --git a/python/nx-cugraph/nx_cugraph/classes/multidigraph.py b/python/nx-cugraph/nx_cugraph/classes/multidigraph.py index 2e7a55a9eb1..5a6595567d2 100644 --- a/python/nx-cugraph/nx_cugraph/classes/multidigraph.py +++ b/python/nx-cugraph/nx_cugraph/classes/multidigraph.py @@ -16,24 +16,51 @@ import nx_cugraph as nxcg -from .digraph import DiGraph -from .multigraph import MultiGraph +from .digraph import CudaDiGraph, DiGraph +from .graph import Graph +from .multigraph import CudaMultiGraph, MultiGraph -__all__ = ["MultiDiGraph"] +__all__ = ["CudaMultiDiGraph", "MultiDiGraph"] networkx_api = nxcg.utils.decorators.networkx_class(nx.MultiDiGraph) -class MultiDiGraph(MultiGraph, DiGraph): +class MultiDiGraph(nx.MultiDiGraph, MultiGraph, DiGraph): + name = Graph.name + _node = Graph._node + _adj = DiGraph._adj + _succ = DiGraph._succ + _pred = DiGraph._pred + @classmethod @networkx_api def is_directed(cls) -> bool: return True + @classmethod + @networkx_api + def is_multigraph(cls) -> bool: + return True + + @classmethod + def to_cudagraph_class(cls) -> type[CudaMultiDiGraph]: + return CudaMultiDiGraph + @classmethod def to_networkx_class(cls) -> type[nx.MultiDiGraph]: return nx.MultiDiGraph + +class CudaMultiDiGraph(CudaMultiGraph, CudaDiGraph): + is_directed = classmethod(MultiDiGraph.is_directed.__func__) + is_multigraph = classmethod(MultiDiGraph.is_multigraph.__func__) + to_cudagraph_class = classmethod(MultiDiGraph.to_cudagraph_class.__func__) + to_networkx_class = classmethod(MultiDiGraph.to_networkx_class.__func__) + + @classmethod + def _to_compat_graph_class(cls) -> type[MultiDiGraph]: + return MultiDiGraph + ########################## # NetworkX graph methods # ########################## diff --git a/python/nx-cugraph/nx_cugraph/classes/multigraph.py b/python/nx-cugraph/nx_cugraph/classes/multigraph.py index 23d9faa8734..c8c8f1dfb00 100644 --- a/python/nx-cugraph/nx_cugraph/classes/multigraph.py +++ b/python/nx-cugraph/nx_cugraph/classes/multigraph.py @@ -22,7 +22,7 @@ import nx_cugraph as nxcg from ..utils import index_dtype -from .graph import Graph +from .graph import CudaGraph, Graph, _GraphCache if TYPE_CHECKING: from nx_cugraph.typing import ( @@ -34,32 +34,47 @@ NodeValue, any_ndarray, ) -__all__ = ["MultiGraph"] +__all__ = ["MultiGraph", "CudaMultiGraph"] networkx_api = nxcg.utils.decorators.networkx_class(nx.MultiGraph) -class MultiGraph(Graph): - # networkx properties - edge_key_dict_factory: ClassVar[type] = dict +class MultiGraph(nx.MultiGraph, Graph): + name = Graph.name + _node = Graph._node + _adj = Graph._adj - # Not networkx properties + @classmethod + @networkx_api + def is_directed(cls) -> bool: + return False - # In a MultiGraph, each edge has a unique `(src, dst, key)` key. - # By default, `key` is 0 if possible, else 1, else 2, etc. - # This key can be any hashable Python object in NetworkX. - # We don't use a dict for our data structure here, because - # that would require a `(src, dst, key)` key. - # Instead, we keep `edge_keys` and/or `edge_indices`. - # `edge_keys` is the list of Python objects for each edge. - # `edge_indices` is for the common case of default multiedge keys, - # in which case we can store it as a cupy array. - # `edge_indices` is generally preferred. It is possible to provide - # both where edge_indices is the default and edge_keys is anything. - # It is also possible for them both to be None, which means the - # default edge indices has not yet been calculated. - edge_indices: cp.ndarray[IndexValue] | None - edge_keys: list[EdgeKey] | None + @classmethod + @networkx_api + def is_multigraph(cls) -> bool: + return True + + @classmethod + def to_cudagraph_class(cls) -> type[CudaMultiGraph]: + return CudaMultiGraph + + @classmethod + @networkx_api + def to_directed_class(cls) -> type[nxcg.MultiDiGraph]: + return nxcg.MultiDiGraph + + @classmethod + def to_networkx_class(cls) -> type[nx.MultiGraph]: + return nx.MultiGraph + + @classmethod + @networkx_api + def to_undirected_class(cls) -> type[MultiGraph]: + return MultiGraph + + def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr): + super().__init__(incoming_graph_data, multigraph_input, **attr) + self.__networkx_cache__ = _GraphCache(self) #################### # Creation methods # @@ -80,9 +95,10 @@ def from_coo( key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, edge_keys: list[EdgeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> MultiGraph: - new_graph = super().from_coo( + ) -> MultiGraph | CudaMultiGraph: + new_graph = super(cls.to_undirected_class(), cls).from_coo( N, src_indices, dst_indices, @@ -92,6 +108,7 @@ def from_coo( node_masks, key_to_id=key_to_id, id_to_key=id_to_key, + use_compat_graph=False, **attr, ) new_graph.edge_indices = edge_indices @@ -102,6 +119,8 @@ def from_coo( and len(new_graph.edge_keys) != src_indices.size ): raise ValueError + if use_compat_graph or use_compat_graph is None and issubclass(cls, Graph): + new_graph = new_graph._to_compat_graph() return new_graph @classmethod @@ -118,8 +137,9 @@ def from_csr( key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, edge_keys: list[EdgeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> MultiGraph: + ) -> MultiGraph | CudaMultiGraph: N = indptr.size - 1 src_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead @@ -137,6 +157,7 @@ def from_csr( key_to_id=key_to_id, id_to_key=id_to_key, edge_keys=edge_keys, + use_compat_graph=use_compat_graph, **attr, ) @@ -154,8 +175,9 @@ def from_csc( key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, edge_keys: list[EdgeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> MultiGraph: + ) -> MultiGraph | CudaMultiGraph: N = indptr.size - 1 dst_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead @@ -173,6 +195,7 @@ def from_csc( key_to_id=key_to_id, id_to_key=id_to_key, edge_keys=edge_keys, + use_compat_graph=use_compat_graph, **attr, ) @@ -192,8 +215,9 @@ def from_dcsr( key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, edge_keys: list[EdgeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> MultiGraph: + ) -> MultiGraph | CudaMultiGraph: src_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead np.repeat(compressed_srcs.get(), cp.diff(indptr).get()) @@ -210,6 +234,7 @@ def from_dcsr( key_to_id=key_to_id, id_to_key=id_to_key, edge_keys=edge_keys, + use_compat_graph=use_compat_graph, **attr, ) @@ -229,8 +254,9 @@ def from_dcsc( key_to_id: dict[NodeKey, IndexValue] | None = None, id_to_key: list[NodeKey] | None = None, edge_keys: list[EdgeKey] | None = None, + use_compat_graph: bool | None = None, **attr, - ) -> Graph: + ) -> MultiGraph | CudaGraph: dst_indices = cp.array( # cp.repeat is slow to use here, so use numpy instead np.repeat(compressed_dsts.get(), cp.diff(indptr).get()) @@ -247,12 +273,46 @@ def from_dcsc( key_to_id=key_to_id, id_to_key=id_to_key, edge_keys=edge_keys, + use_compat_graph=use_compat_graph, **attr, ) + +class CudaMultiGraph(CudaGraph): + # networkx properties + edge_key_dict_factory: ClassVar[type] = dict + + # Not networkx properties + + # In a MultiGraph, each edge has a unique `(src, dst, key)` key. + # By default, `key` is 0 if possible, else 1, else 2, etc. + # This key can be any hashable Python object in NetworkX. + # We don't use a dict for our data structure here, because + # that would require a `(src, dst, key)` key. + # Instead, we keep `edge_keys` and/or `edge_indices`. + # `edge_keys` is the list of Python objects for each edge. + # `edge_indices` is for the common case of default multiedge keys, + # in which case we can store it as a cupy array. + # `edge_indices` is generally preferred. It is possible to provide + # both where edge_indices is the default and edge_keys is anything. + # It is also possible for them both to be None, which means the + # default edge indices has not yet been calculated. + edge_indices: cp.ndarray[IndexValue] | None + edge_keys: list[EdgeKey] | None + + #################### + # Creation methods # + #################### + + from_coo = classmethod(MultiGraph.from_coo.__func__) + from_csr = classmethod(MultiGraph.from_csr.__func__) + from_csc = classmethod(MultiGraph.from_csc.__func__) + from_dcsr = classmethod(MultiGraph.from_dcsr.__func__) + from_dcsc = classmethod(MultiGraph.from_dcsc.__func__) + def __new__( cls, incoming_graph_data=None, multigraph_input=None, **attr - ) -> MultiGraph: + ) -> CudaMultiGraph: if isinstance(incoming_graph_data, dict) and multigraph_input is not False: new_graph = nxcg.from_networkx( nx.MultiGraph(incoming_graph_data, multigraph_input=multigraph_input), @@ -267,28 +327,23 @@ def __new__( # Class methods # ################# - @classmethod - @networkx_api - def is_directed(cls) -> bool: - return False + is_directed = classmethod(MultiGraph.is_directed.__func__) + is_multigraph = classmethod(MultiGraph.is_multigraph.__func__) + to_cudagraph_class = classmethod(MultiGraph.to_cudagraph_class.__func__) + to_networkx_class = classmethod(MultiGraph.to_networkx_class.__func__) @classmethod @networkx_api - def is_multigraph(cls) -> bool: - return True + def to_directed_class(cls) -> type[nxcg.CudaMultiDiGraph]: + return nxcg.CudaMultiDiGraph @classmethod @networkx_api - def to_directed_class(cls) -> type[nxcg.MultiDiGraph]: - return nxcg.MultiDiGraph - - @classmethod - def to_networkx_class(cls) -> type[nx.MultiGraph]: - return nx.MultiGraph + def to_undirected_class(cls) -> type[CudaMultiGraph]: + return CudaMultiGraph @classmethod - @networkx_api - def to_undirected_class(cls) -> type[MultiGraph]: + def _to_compat_graph_class(cls) -> type[MultiGraph]: return MultiGraph ########################## @@ -308,7 +363,7 @@ def clear_edges(self) -> None: self.edge_keys = None @networkx_api - def copy(self, as_view: bool = False) -> MultiGraph: + def copy(self, as_view: bool = False) -> CudaMultiGraph: # Does shallow copy in networkx return self._copy(as_view, self.__class__) @@ -391,11 +446,11 @@ def has_edge(self, u: NodeKey, v: NodeKey, key: EdgeKey | None = None) -> bool: return any(edge_keys[i] == key for i in indices.tolist()) @networkx_api - def to_directed(self, as_view: bool = False) -> nxcg.MultiDiGraph: + def to_directed(self, as_view: bool = False) -> nxcg.CudaMultiDiGraph: return self._copy(as_view, self.to_directed_class()) @networkx_api - def to_undirected(self, as_view: bool = False) -> MultiGraph: + def to_undirected(self, as_view: bool = False) -> CudaMultiGraph: # Does deep copy in networkx return self._copy(as_view, self.to_undirected_class()) @@ -403,8 +458,8 @@ def to_undirected(self, as_view: bool = False) -> MultiGraph: # Private methods # ################### - def _copy(self, as_view: bool, cls: type[Graph], reverse: bool = False): - # DRY warning: see also Graph._copy + def _copy(self, as_view: bool, cls: type[CudaGraph], reverse: bool = False): + # DRY warning: see also CudaGraph._copy src_indices = self.src_indices dst_indices = self.dst_indices edge_indices = self.edge_indices @@ -451,6 +506,7 @@ def _copy(self, as_view: bool, cls: type[Graph], reverse: bool = False): key_to_id=key_to_id, id_to_key=id_to_key, edge_keys=edge_keys, + use_compat_graph=False, ) if as_view: rv.graph = self.graph @@ -460,7 +516,7 @@ def _copy(self, as_view: bool, cls: type[Graph], reverse: bool = False): return rv def _sort_edge_indices(self, primary="src"): - # DRY warning: see also Graph._sort_edge_indices + # DRY warning: see also CudaGraph._sort_edge_indices if self.edge_indices is None and self.edge_keys is None: return super()._sort_edge_indices(primary=primary) if primary == "src": diff --git a/python/nx-cugraph/nx_cugraph/convert.py b/python/nx-cugraph/nx_cugraph/convert.py index 56d16d837d7..a872f13ac70 100644 --- a/python/nx-cugraph/nx_cugraph/convert.py +++ b/python/nx-cugraph/nx_cugraph/convert.py @@ -12,6 +12,7 @@ # limitations under the License. from __future__ import annotations +import functools import itertools import operator as op from collections import Counter, defaultdict @@ -23,9 +24,13 @@ import numpy as np import nx_cugraph as nxcg +from nx_cugraph import _nxver from .utils import index_dtype, networkx_algorithm -from .utils.misc import pairwise +from .utils.misc import _And_NotImplementedError, pairwise + +if _nxver >= (3, 4): + from networkx.utils.backends import _get_cache_key, _get_from_cache, _set_to_cache if TYPE_CHECKING: # pragma: no cover from nx_cugraph.typing import AttrKey, Dtype, EdgeValue, NodeValue, any_ndarray @@ -60,6 +65,27 @@ def _iterate_values(graph, adj, is_dicts, func): return func(it), False +# Consider adding this to `utils` if it is useful elsewhere +def _fallback_decorator(func): + """Catch and convert exceptions to ``NotImplementedError``; use as a decorator. + + ``nx.NetworkXError`` are raised without being converted. This allows + falling back to other backends if, for example, conversion to GPU failed. + """ + + @functools.wraps(func) + def inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except nx.NetworkXError: + raise + except Exception as exc: + raise _And_NotImplementedError(exc) from exc + + return inner + + +@_fallback_decorator def from_networkx( graph: nx.Graph, edge_attrs: AttrKey | dict[AttrKey, EdgeValue | None] | None = None, @@ -74,7 +100,8 @@ def from_networkx( as_directed: bool = False, name: str | None = None, graph_name: str | None = None, -) -> nxcg.Graph: + use_compat_graph: bool | None = False, +) -> nxcg.Graph | nxcg.CudaGraph: """Convert a networkx graph to nx_cugraph graph; can convert all attributes. Parameters @@ -114,10 +141,16 @@ def from_networkx( The name of the algorithm when dispatched from networkx. graph_name : str, optional The name of the graph argument geing converted when dispatched from networkx. + use_compat_graph : bool or None, default False + Indicate whether to return a graph that is compatible with NetworkX graph. + For example, ``nx_cugraph.Graph`` can be used as a NetworkX graph and can + reside in host (CPU) or device (GPU) memory. The default is False, which + will return e.g. ``nx_cugraph.CudaGraph`` that only resides on device (GPU) + and is not fully compatible as a NetworkX graph. Returns ------- - nx_cugraph.Graph + nx_cugraph.Graph or nx_cugraph.CudaGraph Notes ----- @@ -145,6 +178,41 @@ def from_networkx( graph = G else: raise TypeError(f"Expected networkx.Graph; got {type(graph)}") + elif isinstance(graph, nxcg.Graph): + if ( + use_compat_graph + # Use compat graphs by default + or use_compat_graph is None + and (_nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs) + ): + return graph + if graph._is_on_gpu: + return graph._cudagraph + if not graph._is_on_cpu: + raise RuntimeError( + f"{type(graph).__name__} cannot be converted to the GPU, because it is " + "not on the CPU! This is not supposed to be possible. If you believe " + "you have found a bug, please report a minimum reproducible example to " + "https://github.com/rapidsai/cugraph/issues/new/choose" + ) + if _nxver >= (3, 4): + cache_key = _get_cache_key( + edge_attrs=edge_attrs, + node_attrs=node_attrs, + preserve_edge_attrs=preserve_edge_attrs, + preserve_node_attrs=preserve_node_attrs, + preserve_graph_attrs=preserve_graph_attrs, + ) + cache = getattr(graph, "__networkx_cache__", None) + if cache is not None: + cache = cache.setdefault("backends", {}).setdefault("cugraph", {}) + compat_key, rv = _get_from_cache(cache, cache_key) + if rv is not None: + if isinstance(rv, nxcg.Graph): + # This shouldn't happen during normal use, but be extra-careful + rv = rv._cudagraph + if rv is not None: + return rv if preserve_all_attrs: preserve_edge_attrs = True @@ -165,7 +233,12 @@ def from_networkx( else: node_attrs = {node_attrs: None} - if graph.__class__ in {nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph}: + if graph.__class__ in { + nx.Graph, + nx.DiGraph, + nx.MultiGraph, + nx.MultiDiGraph, + } or isinstance(graph, nxcg.Graph): # This is a NetworkX private attribute, but is much faster to use adj = graph._adj else: @@ -455,9 +528,9 @@ def func(it, edge_attr=edge_attr, dtype=dtype): # if vals.ndim > 1: ... if graph.is_multigraph(): if graph.is_directed() or as_directed: - klass = nxcg.MultiDiGraph + klass = nxcg.CudaMultiDiGraph else: - klass = nxcg.MultiGraph + klass = nxcg.CudaMultiGraph rv = klass.from_coo( N, src_indices, @@ -469,12 +542,13 @@ def func(it, edge_attr=edge_attr, dtype=dtype): node_masks, key_to_id=key_to_id, edge_keys=edge_keys, + use_compat_graph=False, ) else: if graph.is_directed() or as_directed: - klass = nxcg.DiGraph + klass = nxcg.CudaDiGraph else: - klass = nxcg.Graph + klass = nxcg.CudaGraph rv = klass.from_coo( N, src_indices, @@ -484,9 +558,22 @@ def func(it, edge_attr=edge_attr, dtype=dtype): node_values, node_masks, key_to_id=key_to_id, + use_compat_graph=False, ) if preserve_graph_attrs: rv.graph.update(graph.graph) # deepcopy? + if _nxver >= (3, 4) and isinstance(graph, nxcg.Graph) and cache is not None: + # Make sure this conversion is added to the cache, and make all of + # our graphs share the same `.graph` attribute for consistency. + rv.graph = graph.graph + _set_to_cache(cache, cache_key, rv) + if ( + use_compat_graph + # Use compat graphs by default + or use_compat_graph is None + and (_nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs) + ): + return rv._to_compat_graph() return rv @@ -535,14 +622,16 @@ def _iter_attr_dicts( return full_dicts -def to_networkx(G: nxcg.Graph, *, sort_edges: bool = False) -> nx.Graph: +def to_networkx( + G: nxcg.Graph | nxcg.CudaGraph, *, sort_edges: bool = False +) -> nx.Graph: """Convert a nx_cugraph graph to networkx graph. All edge and node attributes and ``G.graph`` properties are converted. Parameters ---------- - G : nx_cugraph.Graph + G : nx_cugraph.Graph or nx_cugraph.CudaGraph sort_edges : bool, default False Whether to sort the edge data of the input graph by (src, dst) indices before converting. This can be useful to convert to networkx graphs @@ -557,6 +646,9 @@ def to_networkx(G: nxcg.Graph, *, sort_edges: bool = False) -> nx.Graph: -------- from_networkx : The opposite; convert networkx graph to nx_cugraph graph """ + if isinstance(G, nxcg.Graph): + # These graphs are already NetworkX graphs :) + return G rv = G.to_networkx_class()() id_to_key = G.id_to_key if sort_edges: @@ -623,13 +715,13 @@ def _to_graph( edge_attr: AttrKey | None = None, edge_default: EdgeValue | None = 1, edge_dtype: Dtype | None = None, -) -> nxcg.Graph | nxcg.DiGraph: +) -> nxcg.CudaGraph | nxcg.CudaDiGraph: """Ensure that input type is a nx_cugraph graph, and convert if necessary. Directed and undirected graphs are both allowed. This is an internal utility function and may change or be removed. """ - if isinstance(G, nxcg.Graph): + if isinstance(G, nxcg.CudaGraph): return G if isinstance(G, nx.Graph): return from_networkx( @@ -644,15 +736,15 @@ def _to_directed_graph( edge_attr: AttrKey | None = None, edge_default: EdgeValue | None = 1, edge_dtype: Dtype | None = None, -) -> nxcg.DiGraph: - """Ensure that input type is a nx_cugraph DiGraph, and convert if necessary. +) -> nxcg.CudaDiGraph: + """Ensure that input type is a nx_cugraph CudaDiGraph, and convert if necessary. Undirected graphs will be converted to directed. This is an internal utility function and may change or be removed. """ - if isinstance(G, nxcg.DiGraph): + if isinstance(G, nxcg.CudaDiGraph): return G - if isinstance(G, nxcg.Graph): + if isinstance(G, nxcg.CudaGraph): return G.to_directed() if isinstance(G, nx.Graph): return from_networkx( @@ -670,13 +762,13 @@ def _to_undirected_graph( edge_attr: AttrKey | None = None, edge_default: EdgeValue | None = 1, edge_dtype: Dtype | None = None, -) -> nxcg.Graph: - """Ensure that input type is a nx_cugraph Graph, and convert if necessary. +) -> nxcg.CudaGraph: + """Ensure that input type is a nx_cugraph CudaGraph, and convert if necessary. Only undirected graphs are allowed. Directed graphs will raise ValueError. This is an internal utility function and may change or be removed. """ - if isinstance(G, nxcg.Graph): + if isinstance(G, nxcg.CudaGraph): if G.is_directed(): raise ValueError("Only undirected graphs supported; got a directed graph") return G @@ -688,7 +780,7 @@ def _to_undirected_graph( raise TypeError -@networkx_algorithm(version_added="24.08") +@networkx_algorithm(version_added="24.08", fallback=True) def from_dict_of_lists(d, create_using=None): from .generators._utils import _create_using_class diff --git a/python/nx-cugraph/nx_cugraph/convert_matrix.py b/python/nx-cugraph/nx_cugraph/convert_matrix.py index 38139b913cf..54975902861 100644 --- a/python/nx-cugraph/nx_cugraph/convert_matrix.py +++ b/python/nx-cugraph/nx_cugraph/convert_matrix.py @@ -14,6 +14,8 @@ import networkx as nx import numpy as np +from nx_cugraph import _nxver + from .generators._utils import _create_using_class from .utils import _cp_iscopied_asarray, index_dtype, networkx_algorithm @@ -24,7 +26,7 @@ # Value columns with string dtype is not supported -@networkx_algorithm(is_incomplete=True, version_added="23.12") +@networkx_algorithm(is_incomplete=True, version_added="23.12", fallback=True) def from_pandas_edgelist( df, source="source", @@ -138,7 +140,7 @@ def from_pandas_edgelist( and ( # In nx <= 3.3, `edge_key` was ignored if `edge_attr` is None edge_attr is not None - or nx.__version__[:3] > "3.3" + or _nxver > (3, 3) ) ): try: @@ -161,7 +163,7 @@ def from_pandas_edgelist( return G -@networkx_algorithm(version_added="23.12") +@networkx_algorithm(version_added="23.12", fallback=True) def from_scipy_sparse_array( A, parallel_edges=False, create_using=None, edge_attribute="weight" ): diff --git a/python/nx-cugraph/nx_cugraph/generators/_utils.py b/python/nx-cugraph/nx_cugraph/generators/_utils.py index e38ace5b28d..bc9ab84bdad 100644 --- a/python/nx-cugraph/nx_cugraph/generators/_utils.py +++ b/python/nx-cugraph/nx_cugraph/generators/_utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,6 +16,7 @@ import networkx as nx import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import index_dtype @@ -74,7 +75,7 @@ def _common_small_graph(n, nodes, create_using, *, allow_directed=True): return G -def _create_using_class(create_using, *, default=nxcg.Graph): +def _create_using_class(create_using, *, default=nx.Graph): """Handle ``create_using`` argument and return a Graph type from nx_cugraph.""" inplace = False if create_using is None: @@ -85,16 +86,17 @@ def _create_using_class(create_using, *, default=nxcg.Graph): create_using, "is_multigraph" ): raise TypeError("create_using is not a valid graph type or instance") - elif not isinstance(create_using, nxcg.Graph): + elif not isinstance(create_using, (nxcg.Graph, nxcg.CudaGraph)): raise NotImplementedError( f"create_using with object of type {type(create_using)} is not supported " - "by the cugraph backend; only nx_cugraph.Graph objects are allowed." + "by the cugraph backend; only nx_cugraph.Graph or nx_cugraph.CudaGraph " + "objects are allowed." ) else: inplace = True G = create_using G.clear() - if not isinstance(G, nxcg.Graph): + if not isinstance(G, (nxcg.Graph, nxcg.CudaGraph)): if G.is_multigraph(): if G.is_directed(): graph_class = nxcg.MultiDiGraph @@ -104,10 +106,12 @@ def _create_using_class(create_using, *, default=nxcg.Graph): graph_class = nxcg.DiGraph else: graph_class = nxcg.Graph + if _nxver >= (3, 3) and not nx.config.backends.cugraph.use_compat_graphs: + graph_class = graph_class.to_cudagraph_class() if G.__class__ not in {nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph}: raise NotImplementedError( f"create_using with type {type(G)} is not supported by the cugraph " - "backend; only standard networkx or nx_cugraph Graph objects are " + "backend; only standard networkx or nx_cugraph graph objects are " "allowed (but not customized subclasses derived from them)." ) else: diff --git a/python/nx-cugraph/nx_cugraph/generators/classic.py b/python/nx-cugraph/nx_cugraph/generators/classic.py index a548beea34f..cfcb2a3afec 100644 --- a/python/nx-cugraph/nx_cugraph/generators/classic.py +++ b/python/nx-cugraph/nx_cugraph/generators/classic.py @@ -18,6 +18,7 @@ import numpy as np import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import _get_int_dtype, index_dtype, networkx_algorithm from ._utils import ( @@ -102,7 +103,9 @@ def complete_graph(n, create_using=None): @networkx_algorithm(version_added="23.12") def complete_multipartite_graph(*subset_sizes): if not subset_sizes: - return nxcg.Graph() + if _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs: + return nxcg.Graph() + return nxcg.CudaGraph() try: subset_sizes = [_ensure_int(size) for size in subset_sizes] except TypeError: @@ -139,6 +142,8 @@ def complete_multipartite_graph(*subset_sizes): dst_indices, node_values={"subset": subsets_array}, id_to_key=nodes, + use_compat_graph=_nxver < (3, 3) + or nx.config.backends.cugraph.use_compat_graphs, ) diff --git a/python/nx-cugraph/nx_cugraph/generators/community.py b/python/nx-cugraph/nx_cugraph/generators/community.py index 9b0e0848de9..4e5063cc345 100644 --- a/python/nx-cugraph/nx_cugraph/generators/community.py +++ b/python/nx-cugraph/nx_cugraph/generators/community.py @@ -11,8 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import cupy as cp +import networkx as nx import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import networkx_algorithm from ._utils import ( @@ -42,4 +44,7 @@ def caveman_graph(l, k): # noqa: E741 dst_cliques.extend(dst_clique + i * k for i in range(1, l)) src_indices = cp.hstack(src_cliques) dst_indices = cp.hstack(dst_cliques) - return nxcg.Graph.from_coo(l * k, src_indices, dst_indices) + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( + l * k, src_indices, dst_indices, use_compat_graph=use_compat_graph + ) diff --git a/python/nx-cugraph/nx_cugraph/generators/ego.py b/python/nx-cugraph/nx_cugraph/generators/ego.py index 66c9c8b95ee..9a91fa0b6c3 100644 --- a/python/nx-cugraph/nx_cugraph/generators/ego.py +++ b/python/nx-cugraph/nx_cugraph/generators/ego.py @@ -32,7 +32,10 @@ def ego_graph( ): """Weighted ego_graph with negative cycles is not yet supported. `NotImplementedError` will be raised if there are negative `distance` edge weights.""" # noqa: E501 if isinstance(G, nx.Graph): + is_compat_graph = isinstance(G, nxcg.Graph) G = nxcg.from_networkx(G, preserve_all_attrs=True) + else: + is_compat_graph = False if n not in G: if distance is None: raise nx.NodeNotFound(f"Source {n} is not in G") @@ -100,7 +103,10 @@ def ego_graph( node_mask &= node_ids != src_index node_ids = node_ids[node_mask] if node_ids.size == G._N: - return G.copy() + rv = G.copy() + if is_compat_graph: + return rv._to_compat_graph() + return rv # TODO: create renumbering helper function(s) node_ids.sort() # TODO: is this ever necessary? Keep for safety node_values = {key: val[node_ids] for key, val in G.node_values.items()} @@ -137,6 +143,7 @@ def ego_graph( "node_values": node_values, "node_masks": node_masks, "key_to_id": key_to_id, + "use_compat_graph": False, } if G.is_multigraph(): if G.edge_keys is not None: @@ -147,6 +154,8 @@ def ego_graph( kwargs["edge_indices"] = G.edge_indices[edge_mask] rv = G.__class__.from_coo(**kwargs) rv.graph.update(G.graph) + if is_compat_graph: + return rv._to_compat_graph() return rv diff --git a/python/nx-cugraph/nx_cugraph/generators/small.py b/python/nx-cugraph/nx_cugraph/generators/small.py index 45487571cda..d0c03cb7dd4 100644 --- a/python/nx-cugraph/nx_cugraph/generators/small.py +++ b/python/nx-cugraph/nx_cugraph/generators/small.py @@ -14,6 +14,7 @@ import networkx as nx import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import index_dtype, networkx_algorithm from ._utils import _IS_NX32_OR_LESS, _create_using_class @@ -449,7 +450,14 @@ def pappus_graph(): index_dtype, ) # fmt: on - return nxcg.Graph.from_coo(18, src_indices, dst_indices, name="Pappus Graph") + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( + 18, + src_indices, + dst_indices, + name="Pappus Graph", + use_compat_graph=use_compat_graph, + ) @networkx_algorithm(version_added="23.12") diff --git a/python/nx-cugraph/nx_cugraph/generators/social.py b/python/nx-cugraph/nx_cugraph/generators/social.py index 07e82c63fbf..09d405e7561 100644 --- a/python/nx-cugraph/nx_cugraph/generators/social.py +++ b/python/nx-cugraph/nx_cugraph/generators/social.py @@ -11,9 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. import cupy as cp +import networkx as nx import numpy as np import nx_cugraph as nxcg +from nx_cugraph import _nxver from ..utils import index_dtype, networkx_algorithm @@ -77,7 +79,8 @@ def davis_southern_women_graph(): "E13", "E14", ] # fmt: on - return nxcg.Graph.from_coo( + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( 32, src_indices, dst_indices, @@ -85,6 +88,7 @@ def davis_southern_women_graph(): id_to_key=women + events, top=women, bottom=events, + use_compat_graph=use_compat_graph, ) @@ -111,7 +115,14 @@ def florentine_families_graph(): "Salviati", "Strozzi", "Tornabuoni" ] # fmt: on - return nxcg.Graph.from_coo(15, src_indices, dst_indices, id_to_key=nodes) + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( + 15, + src_indices, + dst_indices, + id_to_key=nodes, + use_compat_graph=use_compat_graph, + ) @networkx_algorithm(version_added="23.12") @@ -165,13 +176,15 @@ def karate_club_graph(): "Officer", "Officer", "Officer", "Officer", "Officer", "Officer", ]) # fmt: on - return nxcg.Graph.from_coo( + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( 34, src_indices, dst_indices, edge_values={"weight": weights}, node_values={"club": clubs}, name="Zachary's Karate Club", + use_compat_graph=use_compat_graph, ) @@ -289,6 +302,12 @@ def les_miserables_graph(): "Zephine", ] # fmt: on - return nxcg.Graph.from_coo( - 77, src_indices, dst_indices, edge_values={"weight": weights}, id_to_key=nodes + use_compat_graph = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + return nxcg.CudaGraph.from_coo( + 77, + src_indices, + dst_indices, + edge_values={"weight": weights}, + id_to_key=nodes, + use_compat_graph=use_compat_graph, ) diff --git a/python/nx-cugraph/nx_cugraph/interface.py b/python/nx-cugraph/nx_cugraph/interface.py index 4007230efa9..1a3d08409a2 100644 --- a/python/nx-cugraph/nx_cugraph/interface.py +++ b/python/nx-cugraph/nx_cugraph/interface.py @@ -18,6 +18,7 @@ import networkx as nx import nx_cugraph as nxcg +from nx_cugraph import _nxver class BackendInterface: @@ -32,11 +33,19 @@ def convert_from_nx(graph, *args, edge_attrs=None, weight=None, **kwargs): "edge_attrs and weight arguments should not both be given" ) edge_attrs = {weight: 1} - return nxcg.from_networkx(graph, *args, edge_attrs=edge_attrs, **kwargs) + return nxcg.from_networkx( + graph, + *args, + edge_attrs=edge_attrs, + use_compat_graph=_nxver < (3, 3) + or nx.config.backends.cugraph.use_compat_graphs, + **kwargs, + ) @staticmethod def convert_to_nx(obj, *, name: str | None = None): - if isinstance(obj, nxcg.Graph): + if isinstance(obj, nxcg.CudaGraph): + # Observe that this does not try to convert Graph! return nxcg.to_networkx(obj) return obj @@ -62,19 +71,32 @@ def key(testpath): return (testname, frozenset({classname, filename})) return (testname, frozenset({filename})) + use_compat_graph = ( + _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + ) + fallback = use_compat_graph or nx.utils.backends._dispatchable._fallback_to_nx + # Reasons for xfailing + # For nx version <= 3.1 no_weights = "weighted implementation not currently supported" no_multigraph = "multigraphs not currently supported" + # For nx version <= 3.2 + nx_cugraph_in_test_setup = ( + "nx-cugraph Graph is incompatible in test setup in nx versions < 3.3" + ) + # For all versions louvain_different = "Louvain may be different due to RNG" - no_string_dtype = "string edge values not currently supported" sssp_path_different = "sssp may choose a different valid path" + tuple_elements_preferred = "elements are tuples instead of lists" + no_mixed_dtypes_for_nodes = ( + # This one is tricky b/c we don't raise; all dtypes are treated as str + "mixed dtypes (str, int, float) for single node property not supported" + ) + # These shouldn't fail if using Graph or falling back to networkx + no_string_dtype = "string edge values not currently supported" no_object_dtype_for_edges = ( "Edges don't support object dtype (lists, strings, etc.)" ) - tuple_elements_preferred = "elements are tuples instead of lists" - nx_cugraph_in_test_setup = ( - "nx-cugraph Graph is incompatible in test setup in nx versions < 3.3" - ) xfail = { # This is removed while strongly_connected_components() is not @@ -98,38 +120,6 @@ def key(testpath): "test_cycles.py:TestMinimumCycleBasis." "test_gh6787_and_edge_attribute_names" ): sssp_path_different, - key( - "test_graph_hashing.py:test_isomorphic_edge_attr" - ): no_object_dtype_for_edges, - key( - "test_graph_hashing.py:test_isomorphic_edge_attr_and_node_attr" - ): no_object_dtype_for_edges, - key( - "test_graph_hashing.py:test_isomorphic_edge_attr_subgraph_hash" - ): no_object_dtype_for_edges, - key( - "test_graph_hashing.py:" - "test_isomorphic_edge_attr_and_node_attr_subgraph_hash" - ): no_object_dtype_for_edges, - key( - "test_summarization.py:TestSNAPNoEdgeTypes.test_summary_graph" - ): no_object_dtype_for_edges, - key( - "test_summarization.py:TestSNAPUndirected.test_summary_graph" - ): no_object_dtype_for_edges, - key( - "test_summarization.py:TestSNAPDirected.test_summary_graph" - ): no_object_dtype_for_edges, - key("test_gexf.py:TestGEXF.test_relabel"): no_object_dtype_for_edges, - key( - "test_gml.py:TestGraph.test_parse_gml_cytoscape_bug" - ): no_object_dtype_for_edges, - key("test_gml.py:TestGraph.test_parse_gml"): no_object_dtype_for_edges, - key("test_gml.py:TestGraph.test_read_gml"): no_object_dtype_for_edges, - key("test_gml.py:TestGraph.test_data_types"): no_object_dtype_for_edges, - key( - "test_gml.py:TestPropertyLists.test_reading_graph_with_list_property" - ): no_object_dtype_for_edges, key( "test_relabel.py:" "test_relabel_preserve_node_order_partial_mapping_with_copy_false" @@ -138,48 +128,107 @@ def key(testpath): "test_gml.py:" "TestPropertyLists.test_reading_graph_with_single_element_list_property" ): tuple_elements_preferred, - key( - "test_relabel.py:" - "TestRelabel.test_relabel_multidigraph_inout_merge_nodes" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multigraph_merge_inplace" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multidigraph_merge_inplace" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multidigraph_inout_copy" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multigraph_merge_copy" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multidigraph_merge_copy" - ): no_string_dtype, - key( - "test_relabel.py:TestRelabel.test_relabel_multigraph_nonnumeric_key" - ): no_string_dtype, - key("test_contraction.py:test_multigraph_path"): no_object_dtype_for_edges, - key( - "test_contraction.py:test_directed_multigraph_path" - ): no_object_dtype_for_edges, - key( - "test_contraction.py:test_multigraph_blockmodel" - ): no_object_dtype_for_edges, - key( - "test_summarization.py:TestSNAPUndirectedMulti.test_summary_graph" - ): no_string_dtype, - key( - "test_summarization.py:TestSNAPDirectedMulti.test_summary_graph" - ): no_string_dtype, } + if not fallback: + xfail.update( + { + key( + "test_graph_hashing.py:test_isomorphic_edge_attr" + ): no_object_dtype_for_edges, + key( + "test_graph_hashing.py:test_isomorphic_edge_attr_and_node_attr" + ): no_object_dtype_for_edges, + key( + "test_graph_hashing.py:test_isomorphic_edge_attr_subgraph_hash" + ): no_object_dtype_for_edges, + key( + "test_graph_hashing.py:" + "test_isomorphic_edge_attr_and_node_attr_subgraph_hash" + ): no_object_dtype_for_edges, + key( + "test_summarization.py:TestSNAPNoEdgeTypes.test_summary_graph" + ): no_object_dtype_for_edges, + key( + "test_summarization.py:TestSNAPUndirected.test_summary_graph" + ): no_object_dtype_for_edges, + key( + "test_summarization.py:TestSNAPDirected.test_summary_graph" + ): no_object_dtype_for_edges, + key( + "test_gexf.py:TestGEXF.test_relabel" + ): no_object_dtype_for_edges, + key( + "test_gml.py:TestGraph.test_parse_gml_cytoscape_bug" + ): no_object_dtype_for_edges, + key( + "test_gml.py:TestGraph.test_parse_gml" + ): no_object_dtype_for_edges, + key( + "test_gml.py:TestGraph.test_read_gml" + ): no_object_dtype_for_edges, + key( + "test_gml.py:TestGraph.test_data_types" + ): no_object_dtype_for_edges, + key( + "test_gml.py:" + "TestPropertyLists.test_reading_graph_with_list_property" + ): no_object_dtype_for_edges, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multidigraph_inout_merge_nodes" + ): no_string_dtype, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multigraph_merge_inplace" + ): no_string_dtype, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multidigraph_merge_inplace" + ): no_string_dtype, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multidigraph_inout_copy" + ): no_string_dtype, + key( + "test_relabel.py:TestRelabel.test_relabel_multigraph_merge_copy" + ): no_string_dtype, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multidigraph_merge_copy" + ): no_string_dtype, + key( + "test_relabel.py:" + "TestRelabel.test_relabel_multigraph_nonnumeric_key" + ): no_string_dtype, + key( + "test_contraction.py:test_multigraph_path" + ): no_object_dtype_for_edges, + key( + "test_contraction.py:test_directed_multigraph_path" + ): no_object_dtype_for_edges, + key( + "test_contraction.py:test_multigraph_blockmodel" + ): no_object_dtype_for_edges, + key( + "test_summarization.py:" + "TestSNAPUndirectedMulti.test_summary_graph" + ): no_string_dtype, + key( + "test_summarization.py:TestSNAPDirectedMulti.test_summary_graph" + ): no_string_dtype, + } + ) + else: + xfail.update( + { + key( + "test_gml.py:" + "TestPropertyLists.test_reading_graph_with_list_property" + ): no_mixed_dtypes_for_nodes, + } + ) - from packaging.version import parse - - nxver = parse(nx.__version__) - - if nxver.major == 3 and nxver.minor <= 2: + if _nxver <= (3, 2): xfail.update( { # NetworkX versions prior to 3.2.1 have tests written to @@ -216,7 +265,7 @@ def key(testpath): } ) - if nxver.major == 3 and nxver.minor <= 1: + if _nxver <= (3, 1): # MAINT: networkx 3.0, 3.1 # NetworkX 3.2 added the ability to "fallback to nx" if backend algorithms # raise NotImplementedError or `can_run` returns False. The tests below @@ -332,24 +381,25 @@ def key(testpath): xfail[key("test_louvain.py:test_threshold")] = ( "Louvain does not support seed parameter" ) - if nxver.major == 3 and nxver.minor >= 2: - xfail.update( - { - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_multi_attr_incl_target" - ): no_string_dtype, - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_multidigraph_and_edge_attr" - ): no_string_dtype, - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_int_attr_name" - ): no_string_dtype, - } - ) - if nxver.minor == 2: + if _nxver >= (3, 2): + if not fallback: + xfail.update( + { + key( + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_multi_attr_incl_target" + ): no_string_dtype, + key( + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_multidigraph_and_edge_attr" + ): no_string_dtype, + key( + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_int_attr_name" + ): no_string_dtype, + } + ) + if _nxver[1] == 2: different_iteration_order = "Different graph data iteration order" xfail.update( { @@ -366,7 +416,7 @@ def key(testpath): ): different_iteration_order, } ) - elif nxver.minor >= 3: + elif _nxver[1] >= 3: xfail.update( { key("test_louvain.py:test_max_level"): louvain_different, diff --git a/python/nx-cugraph/nx_cugraph/relabel.py b/python/nx-cugraph/nx_cugraph/relabel.py index 20d1337a99c..e38e18c779e 100644 --- a/python/nx-cugraph/nx_cugraph/relabel.py +++ b/python/nx-cugraph/nx_cugraph/relabel.py @@ -29,13 +29,18 @@ @networkx_algorithm(version_added="24.08") def relabel_nodes(G, mapping, copy=True): + G_orig = G if isinstance(G, nx.Graph): - if not copy: + is_compat_graph = isinstance(G, nxcg.Graph) + if not copy and not is_compat_graph: raise RuntimeError( "Using `copy=False` is invalid when using a NetworkX graph " "as input to `nx_cugraph.relabel_nodes`" ) G = nxcg.from_networkx(G, preserve_all_attrs=True) + else: + is_compat_graph = False + it = range(G._N) if G.key_to_id is None else G.id_to_key if callable(mapping): previd_to_key = [mapping(node) for node in it] @@ -225,12 +230,13 @@ def relabel_nodes(G, mapping, copy=True): node_masks=node_masks, id_to_key=newid_to_key, key_to_id=key_to_newid, + use_compat_graph=is_compat_graph, **extra_kwargs, ) rv.graph.update(G.graph) if not copy: - G._become(rv) - return G + G_orig._become(rv) + return G_orig return rv @@ -241,7 +247,10 @@ def convert_node_labels_to_integers( if ordering not in {"default", "sorted", "increasing degree", "decreasing degree"}: raise nx.NetworkXError(f"Unknown node ordering: {ordering}") if isinstance(G, nx.Graph): + is_compat_graph = isinstance(G, nxcg.Graph) G = nxcg.from_networkx(G, preserve_all_attrs=True) + else: + is_compat_graph = False G = G.copy() if label_attribute is not None: prev_vals = G.id_to_key @@ -279,4 +288,6 @@ def convert_node_labels_to_integers( key_to_id = G.key_to_id G.key_to_id = {i: key_to_id[n] for i, (d, n) in enumerate(pairs, first_label)} G._id_to_key = id_to_key + if is_compat_graph: + return G._to_compat_graph() return G diff --git a/python/nx-cugraph/nx_cugraph/tests/test_bfs.py b/python/nx-cugraph/nx_cugraph/tests/test_bfs.py index c2b22e98949..ad2c62c1fb9 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_bfs.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_bfs.py @@ -12,11 +12,10 @@ # limitations under the License. import networkx as nx import pytest -from packaging.version import parse -nxver = parse(nx.__version__) +from nx_cugraph import _nxver -if nxver.major == 3 and nxver.minor < 2: +if _nxver < (3, 2): pytest.skip("Need NetworkX >=3.2 to test clustering", allow_module_level=True) diff --git a/python/nx-cugraph/nx_cugraph/tests/test_classes.py b/python/nx-cugraph/nx_cugraph/tests/test_classes.py new file mode 100644 index 00000000000..0ac238b3558 --- /dev/null +++ b/python/nx-cugraph/nx_cugraph/tests/test_classes.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import nx_cugraph as nxcg + + +def test_class_to_class(): + """Basic sanity checks to ensure metadata relating graph classes are accurate.""" + for prefix in ["", "Cuda"]: + for suffix in ["Graph", "DiGraph", "MultiGraph", "MultiDiGraph"]: + cls_name = f"{prefix}{suffix}" + cls = getattr(nxcg, cls_name) + assert cls.__name__ == cls_name + G = cls() + assert cls is G.__class__ + # cudagraph + val = cls.to_cudagraph_class() + val2 = G.to_cudagraph_class() + assert val is val2 + assert val.__name__ == f"Cuda{suffix}" + assert val.__module__.startswith("nx_cugraph") + assert cls.is_directed() == G.is_directed() == val.is_directed() + assert cls.is_multigraph() == G.is_multigraph() == val.is_multigraph() + # networkx + val = cls.to_networkx_class() + val2 = G.to_networkx_class() + assert val is val2 + assert val.__name__ == suffix + assert val.__module__.startswith("networkx") + val = val() + assert cls.is_directed() == G.is_directed() == val.is_directed() + assert cls.is_multigraph() == G.is_multigraph() == val.is_multigraph() + # directed + val = cls.to_directed_class() + val2 = G.to_directed_class() + assert val is val2 + assert val.__module__.startswith("nx_cugraph") + assert val.is_directed() + assert cls.is_multigraph() == G.is_multigraph() == val.is_multigraph() + if "Di" in suffix: + assert val is cls + else: + assert "Di" in val.__name__ + assert prefix in val.__name__ + assert cls.to_undirected_class() is cls + # undirected + val = cls.to_undirected_class() + val2 = G.to_undirected_class() + assert val is val2 + assert val.__module__.startswith("nx_cugraph") + assert not val.is_directed() + assert cls.is_multigraph() == G.is_multigraph() == val.is_multigraph() + if "Di" not in suffix: + assert val is cls + else: + assert "Di" not in val.__name__ + assert prefix in val.__name__ + assert cls.to_directed_class() is cls + # "zero" + if prefix == "Cuda": + val = cls._to_compat_graph_class() + val2 = G._to_compat_graph_class() + assert val is val2 + assert val.__name__ == suffix + assert val.__module__.startswith("nx_cugraph") + assert val.to_cudagraph_class() is cls + assert cls.is_directed() == G.is_directed() == val.is_directed() + assert cls.is_multigraph() == G.is_multigraph() == val.is_multigraph() diff --git a/python/nx-cugraph/nx_cugraph/tests/test_cluster.py b/python/nx-cugraph/nx_cugraph/tests/test_cluster.py index ad4770f1ab8..fd8e1b3cf13 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_cluster.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_cluster.py @@ -12,11 +12,10 @@ # limitations under the License. import networkx as nx import pytest -from packaging.version import parse -nxver = parse(nx.__version__) +from nx_cugraph import _nxver -if nxver.major == 3 and nxver.minor < 2: +if _nxver < (3, 2): pytest.skip("Need NetworkX >=3.2 to test clustering", allow_module_level=True) diff --git a/python/nx-cugraph/nx_cugraph/tests/test_convert.py b/python/nx-cugraph/nx_cugraph/tests/test_convert.py index 634b28e961c..3d109af8a74 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_convert.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_convert.py @@ -13,13 +13,10 @@ import cupy as cp import networkx as nx import pytest -from packaging.version import parse import nx_cugraph as nxcg from nx_cugraph import interface -nxver = parse(nx.__version__) - @pytest.mark.parametrize( "graph_class", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph] diff --git a/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py b/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py index 5474f9d79e3..0697a744e85 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py @@ -12,16 +12,13 @@ # limitations under the License. import networkx as nx import pytest -from packaging.version import parse import nx_cugraph as nxcg +from nx_cugraph import _nxver from .testing_utils import assert_graphs_equal -nxver = parse(nx.__version__) - - -if nxver.major == 3 and nxver.minor < 2: +if _nxver < (3, 2): pytest.skip("Need NetworkX >=3.2 to test ego_graph", allow_module_level=True) @@ -49,7 +46,12 @@ def test_ego_graph_cycle_graph( kwargs = {"radius": radius, "center": center, "undirected": undirected} Hnx = nx.ego_graph(Gnx, n, **kwargs) Hcg = nx.ego_graph(Gnx, n, **kwargs, backend="cugraph") + use_compat_graphs = _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs + assert_graphs_equal(Hnx, Hcg._cudagraph if use_compat_graphs else Hcg) + Hcg = nx.ego_graph(Gcg, n, **kwargs) assert_graphs_equal(Hnx, Hcg) + Hcg = nx.ego_graph(Gcg._to_compat_graph(), n, **kwargs) + assert_graphs_equal(Hnx, Hcg._cudagraph) with pytest.raises(nx.NodeNotFound, match="not in G"): nx.ego_graph(Gnx, -1, **kwargs) with pytest.raises(nx.NodeNotFound, match="not in G"): @@ -61,20 +63,36 @@ def test_ego_graph_cycle_graph( kwargs["distance"] = "weight" H2nx = nx.ego_graph(Gnx, n, **kwargs) - is_nx32 = nxver.major == 3 and nxver.minor == 2 + is_nx32 = _nxver[:2] == (3, 2) if undirected and Gnx.is_directed() and Gnx.is_multigraph(): if is_nx32: # `should_run` was added in nx 3.3 match = "Weighted ego_graph with undirected=True not implemented" + elif _nxver >= (3, 4): + match = "not implemented by 'cugraph'" else: match = "not implemented by cugraph" - with pytest.raises(RuntimeError, match=match): + with pytest.raises( + RuntimeError if _nxver < (3, 4) else NotImplementedError, match=match + ): nx.ego_graph(Gnx, n, **kwargs, backend="cugraph") with pytest.raises(NotImplementedError, match="ego_graph"): - nx.ego_graph(Gcg, n, **kwargs) + nx.ego_graph(Gcg, n, **kwargs, backend="cugraph") + if _nxver < (3, 4): + with pytest.raises(NotImplementedError, match="ego_graph"): + nx.ego_graph(Gcg, n, **kwargs) + else: + # This is an interesting case. `nxcg.ego_graph` is not implemented for + # these arguments, so it falls back to networkx. Hence, as it is currently + # implemented, the input graph is `nxcg.CudaGraph`, but the output graph + # is `nx.Graph`. Should networkx convert back to "cugraph" backend? + # TODO: make fallback to networkx configurable. + H2cg = nx.ego_graph(Gcg, n, **kwargs) + assert type(H2nx) is type(H2cg) + assert_graphs_equal(H2nx, nxcg.from_networkx(H2cg, preserve_all_attrs=True)) else: H2cg = nx.ego_graph(Gnx, n, **kwargs, backend="cugraph") - assert_graphs_equal(H2nx, H2cg) + assert_graphs_equal(H2nx, H2cg._cudagraph if use_compat_graphs else H2cg) with pytest.raises(nx.NodeNotFound, match="not found in graph"): nx.ego_graph(Gnx, -1, **kwargs) with pytest.raises(nx.NodeNotFound, match="not found in graph"): diff --git a/python/nx-cugraph/nx_cugraph/tests/test_generators.py b/python/nx-cugraph/nx_cugraph/tests/test_generators.py index c751b0fe2b3..5c405f1c93b 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_generators.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_generators.py @@ -13,25 +13,24 @@ import networkx as nx import numpy as np import pytest -from packaging.version import parse import nx_cugraph as nxcg +from nx_cugraph import _nxver from .testing_utils import assert_graphs_equal -nxver = parse(nx.__version__) - - -if nxver.major == 3 and nxver.minor < 2: +if _nxver < (3, 2): pytest.skip("Need NetworkX >=3.2 to test generators", allow_module_level=True) def compare(name, create_using, *args, is_vanilla=False): exc1 = exc2 = None func = getattr(nx, name) - if isinstance(create_using, nxcg.Graph): + if isinstance(create_using, nxcg.CudaGraph): nx_create_using = nxcg.to_networkx(create_using) - elif isinstance(create_using, type) and issubclass(create_using, nxcg.Graph): + elif isinstance(create_using, type) and issubclass( + create_using, (nxcg.Graph, nxcg.CudaGraph) + ): nx_create_using = create_using.to_networkx_class() elif isinstance(create_using, nx.Graph): nx_create_using = create_using.copy() @@ -61,8 +60,27 @@ def compare(name, create_using, *args, is_vanilla=False): exc2 = exc if exc1 is not None or exc2 is not None: assert type(exc1) is type(exc2) + return + if isinstance(Gcg, nxcg.Graph): + # If the graph is empty, it may be on host, otherwise it should be on device + if len(G): + assert Gcg._is_on_gpu + assert not Gcg._is_on_cpu + assert_graphs_equal(G, Gcg._cudagraph) else: assert_graphs_equal(G, Gcg) + # Ensure the output type is correct + if is_vanilla: + if _nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs: + assert isinstance(Gcg, nxcg.Graph) + else: + assert isinstance(Gcg, nxcg.CudaGraph) + elif isinstance(create_using, type) and issubclass( + create_using, (nxcg.Graph, nxcg.CudaGraph) + ): + assert type(Gcg) is create_using + elif isinstance(create_using, (nxcg.Graph, nxcg.CudaGraph)): + assert type(Gcg) is type(create_using) N = list(range(-1, 5)) @@ -76,6 +94,10 @@ def compare(name, create_using, *args, is_vanilla=False): nxcg.DiGraph, nxcg.MultiGraph, nxcg.MultiDiGraph, + nxcg.CudaGraph, + nxcg.CudaDiGraph, + nxcg.CudaMultiGraph, + nxcg.CudaMultiDiGraph, # These raise NotImplementedError # nx.Graph(), # nx.DiGraph(), @@ -85,6 +107,10 @@ def compare(name, create_using, *args, is_vanilla=False): nxcg.DiGraph(), nxcg.MultiGraph(), nxcg.MultiDiGraph(), + nxcg.CudaGraph(), + nxcg.CudaDiGraph(), + nxcg.CudaMultiGraph(), + nxcg.CudaMultiDiGraph(), None, object, # Bad input 7, # Bad input @@ -158,7 +184,7 @@ def compare(name, create_using, *args, is_vanilla=False): @pytest.mark.parametrize("create_using", COMPLETE_CREATE_USING) def test_generator_noarg(name, create_using): print(name, create_using, type(create_using)) - if isinstance(create_using, nxcg.Graph) and name in { + if isinstance(create_using, nxcg.CudaGraph) and name in { # fmt: off "bull_graph", "chvatal_graph", "cubical_graph", "diamond_graph", "house_graph", "house_x_graph", "icosahedral_graph", "krackhardt_kite_graph", diff --git a/python/nx-cugraph/nx_cugraph/tests/test_graph_methods.py b/python/nx-cugraph/nx_cugraph/tests/test_graph_methods.py index 3120995a2b2..40a361b1084 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_graph_methods.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_graph_methods.py @@ -47,7 +47,7 @@ def _create_Gs(): @pytest.mark.parametrize("Gnx", _create_Gs()) @pytest.mark.parametrize("reciprocal", [False, True]) def test_to_undirected_directed(Gnx, reciprocal): - Gcg = nxcg.DiGraph(Gnx) + Gcg = nxcg.CudaDiGraph(Gnx) assert_graphs_equal(Gnx, Gcg) Hnx1 = Gnx.to_undirected(reciprocal=reciprocal) Hcg1 = Gcg.to_undirected(reciprocal=reciprocal) @@ -62,6 +62,6 @@ def test_multidigraph_to_undirected(): Gnx.add_edge(0, 1) Gnx.add_edge(0, 1) Gnx.add_edge(1, 0) - Gcg = nxcg.MultiDiGraph(Gnx) + Gcg = nxcg.CudaMultiDiGraph(Gnx) with pytest.raises(NotImplementedError): Gcg.to_undirected() diff --git a/python/nx-cugraph/nx_cugraph/tests/test_match_api.py b/python/nx-cugraph/nx_cugraph/tests/test_match_api.py index 176b531a6e7..1a61c69b3e7 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_match_api.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_match_api.py @@ -14,13 +14,10 @@ import inspect import networkx as nx -from packaging.version import parse import nx_cugraph as nxcg from nx_cugraph.utils import networkx_algorithm -nxver = parse(nx.__version__) - def test_match_signature_and_names(): """Simple test to ensure our signatures and basic module layout match networkx.""" diff --git a/python/nx-cugraph/nx_cugraph/tests/test_multigraph.py b/python/nx-cugraph/nx_cugraph/tests/test_multigraph.py index a8f189a4745..9208eea09f2 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_multigraph.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_multigraph.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -26,7 +26,7 @@ def test_get_edge_data(test_nxcugraph): G.add_edge(0, 3) G.add_edge(0, 3) if test_nxcugraph: - G = nxcg.MultiGraph(G) + G = nxcg.CudaMultiGraph(G) default = object() assert G.get_edge_data(0, 0, default=default) is default assert G.get_edge_data("a", "b", default=default) is default @@ -60,7 +60,7 @@ def test_get_edge_data(test_nxcugraph): G = nx.MultiGraph() G.add_edge(0, 1) if test_nxcugraph: - G = nxcg.MultiGraph(G) + G = nxcg.CudaMultiGraph(G) assert G.get_edge_data(0, 1, default=default) == {0: {}} assert G.get_edge_data(0, 1, 0, default=default) == {} assert G.get_edge_data(0, 1, 1, default=default) is default diff --git a/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py b/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py index 0b437df2d2f..252f9e6bbb8 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_pagerank.py @@ -12,19 +12,23 @@ # limitations under the License. import networkx as nx import pandas as pd -from pytest import approx +import pytest def test_pagerank_multigraph(): """ - Ensures correct differences between pagerank results for Graphs - vs. MultiGraphs generated using from_pandas_edgelist() + Ensures correct pagerank for Graphs and MultiGraphs when using from_pandas_edgelist. + + PageRank for MultiGraph should give different result compared to Graph; when using + a Graph, the duplicate edges should be dropped. """ - df = pd.DataFrame({"source": [0, 1, 1, 1, 1, 1, 1, 2], - "target": [1, 2, 2, 2, 2, 2, 2, 3]}) + df = pd.DataFrame( + {"source": [0, 1, 1, 1, 1, 1, 1, 2], "target": [1, 2, 2, 2, 2, 2, 2, 3]} + ) expected_pr_for_G = nx.pagerank(nx.from_pandas_edgelist(df)) expected_pr_for_MultiG = nx.pagerank( - nx.from_pandas_edgelist(df, create_using=nx.MultiGraph)) + nx.from_pandas_edgelist(df, create_using=nx.MultiGraph) + ) G = nx.from_pandas_edgelist(df, backend="cugraph") actual_pr_for_G = nx.pagerank(G, backend="cugraph") @@ -32,5 +36,5 @@ def test_pagerank_multigraph(): MultiG = nx.from_pandas_edgelist(df, create_using=nx.MultiGraph, backend="cugraph") actual_pr_for_MultiG = nx.pagerank(MultiG, backend="cugraph") - assert actual_pr_for_G == approx(expected_pr_for_G) - assert actual_pr_for_MultiG == approx(expected_pr_for_MultiG) + assert actual_pr_for_G == pytest.approx(expected_pr_for_G) + assert actual_pr_for_MultiG == pytest.approx(expected_pr_for_MultiG) diff --git a/python/nx-cugraph/nx_cugraph/tests/testing_utils.py b/python/nx-cugraph/nx_cugraph/tests/testing_utils.py index 529a96efd81..50836acf55f 100644 --- a/python/nx-cugraph/nx_cugraph/tests/testing_utils.py +++ b/python/nx-cugraph/nx_cugraph/tests/testing_utils.py @@ -17,7 +17,7 @@ def assert_graphs_equal(Gnx, Gcg): assert isinstance(Gnx, nx.Graph) - assert isinstance(Gcg, nxcg.Graph) + assert isinstance(Gcg, nxcg.CudaGraph) assert (a := Gnx.number_of_nodes()) == (b := Gcg.number_of_nodes()), (a, b) assert (a := Gnx.number_of_edges()) == (b := Gcg.number_of_edges()), (a, b) assert (a := Gnx.is_directed()) == (b := Gcg.is_directed()), (a, b) diff --git a/python/nx-cugraph/nx_cugraph/utils/decorators.py b/python/nx-cugraph/nx_cugraph/utils/decorators.py index 3c5de4f2936..16486996ba0 100644 --- a/python/nx-cugraph/nx_cugraph/utils/decorators.py +++ b/python/nx-cugraph/nx_cugraph/utils/decorators.py @@ -16,10 +16,14 @@ from textwrap import dedent import networkx as nx +from networkx import NetworkXError from networkx.utils.decorators import nodes_or_number, not_implemented_for +from nx_cugraph import _nxver from nx_cugraph.interface import BackendInterface +from .misc import _And_NotImplementedError + try: from networkx.utils.backends import _registered_algorithms except ModuleNotFoundError: @@ -44,6 +48,7 @@ class networkx_algorithm: version_added: str is_incomplete: bool is_different: bool + _fallback: bool _plc_names: set[str] | None def __new__( @@ -59,6 +64,7 @@ def __new__( version_added: str, # Required is_incomplete: bool = False, # See self.extra_doc for details if True is_different: bool = False, # See self.extra_doc for details if True + fallback: bool = False, # Change non-nx exceptions to NotImplementedError _plc: str | set[str] | None = None, # Hidden from user, may be removed someday ): if func is None: @@ -70,10 +76,11 @@ def __new__( version_added=version_added, is_incomplete=is_incomplete, is_different=is_different, + fallback=fallback, _plc=_plc, ) instance = object.__new__(cls) - if nodes_or_number is not None and nx.__version__[:3] > "3.2": + if nodes_or_number is not None and _nxver > (3, 2): func = nx.utils.decorators.nodes_or_number(nodes_or_number)(func) # update_wrapper sets __wrapped__, which will be used for the signature update_wrapper(instance, func) @@ -100,6 +107,7 @@ def __new__( instance.version_added = version_added instance.is_incomplete = is_incomplete instance.is_different = is_different + instance.fallback = fallback # The docstring on our function is added to the NetworkX docstring. instance.extra_doc = ( dedent(func.__doc__.lstrip("\n").rstrip()) if func.__doc__ else None @@ -113,7 +121,7 @@ def __new__( # Set methods so they are in __dict__ instance._can_run = instance._can_run instance._should_run = instance._should_run - if nodes_or_number is not None and nx.__version__[:3] <= "3.2": + if nodes_or_number is not None and _nxver <= (3, 2): instance = nx.utils.decorators.nodes_or_number(nodes_or_number)(instance) return instance @@ -136,7 +144,14 @@ def _should_run(self, func): self.should_run = func def __call__(self, /, *args, **kwargs): - return self.__wrapped__(*args, **kwargs) + if not self.fallback: + return self.__wrapped__(*args, **kwargs) + try: + return self.__wrapped__(*args, **kwargs) + except NetworkXError: + raise + except Exception as exc: + raise _And_NotImplementedError(exc) from exc def __reduce__(self): return _restore_networkx_dispatched, (self.name,) diff --git a/python/nx-cugraph/nx_cugraph/utils/misc.py b/python/nx-cugraph/nx_cugraph/utils/misc.py index 8526524f1de..01c25dd5983 100644 --- a/python/nx-cugraph/nx_cugraph/utils/misc.py +++ b/python/nx-cugraph/nx_cugraph/utils/misc.py @@ -194,7 +194,7 @@ def _get_int_dtype( def _get_float_dtype( - dtype: Dtype, *, graph: nxcg.Graph | None = None, weight: EdgeKey | None = None + dtype: Dtype, *, graph: nxcg.CudaGraph | None = None, weight: EdgeKey | None = None ): """Promote dtype to float32 or float64 as appropriate.""" if dtype is None: @@ -238,3 +238,37 @@ def _cp_iscopied_asarray(a, *args, orig_object=None, **kwargs): ): return False, arr return True, arr + + +class _And_NotImplementedError(NotImplementedError): + """Additionally make an exception a ``NotImplementedError``. + + For example: + + >>> try: + ... raise _And_NotImplementedError(KeyError("missing")) + ... except KeyError: + ... pass + + or + + >>> try: + ... raise _And_NotImplementedError(KeyError("missing")) + ... except NotImplementedError: + ... pass + + """ + + def __new__(cls, exc): + exc_type = type(exc) + if issubclass(exc_type, NotImplementedError): + new_type = exc_type + else: + new_type = type( + f"{exc_type.__name__}{cls.__name__}", + (exc_type, NotImplementedError), + {}, + ) + instance = NotImplementedError.__new__(new_type) + instance.__init__(*exc.args) + return instance diff --git a/python/nx-cugraph/pyproject.toml b/python/nx-cugraph/pyproject.toml index e7b4ea44dd8..98de089a92c 100644 --- a/python/nx-cugraph/pyproject.toml +++ b/python/nx-cugraph/pyproject.toml @@ -40,7 +40,6 @@ dependencies = [ [project.optional-dependencies] test = [ - "packaging>=21", "pandas", "pytest", "pytest-benchmark", @@ -170,6 +169,7 @@ external = [ ] ignore = [ # Would be nice to fix these + "B905", # `zip()` without an explicit `strict=` parameter (Note: possible since py39 was dropped; we should do this!) "D100", # Missing docstring in public module "D101", # Missing docstring in public class "D102", # Missing docstring in public method @@ -215,6 +215,7 @@ ignore = [ "SIM105", # Use contextlib.suppress(...) instead of try-except-pass (Note: try-except-pass is much faster) "SIM108", # Use ternary operator ... instead of if-else-block (Note: if-else better for coverage and sometimes clearer) "TRY003", # Avoid specifying long messages outside the exception class (Note: why?) + "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` (Note: tuple is faster for now) # Ignored categories "C90", # mccabe (Too strict, but maybe we should make things less complex) @@ -241,6 +242,7 @@ ignore = [ # Allow assert, print, RNG, and no docstring "nx_cugraph/**/tests/*py" = ["S101", "S311", "T201", "D103", "D100"] "_nx_cugraph/__init__.py" = ["E501"] +"nx_cugraph/__init__.py" = ["E402"] # Allow module level import not at top of file "nx_cugraph/algorithms/**/*py" = ["D205", "D401"] # Allow flexible docstrings for algorithms "nx_cugraph/generators/**/*py" = ["D205", "D401"] # Allow flexible docstrings for generators "nx_cugraph/interface.py" = ["D401"] # Flexible docstrings diff --git a/python/nx-cugraph/run_nx_tests.sh b/python/nx-cugraph/run_nx_tests.sh index bceec53b7d5..5fb173cf939 100755 --- a/python/nx-cugraph/run_nx_tests.sh +++ b/python/nx-cugraph/run_nx_tests.sh @@ -18,6 +18,10 @@ # testing takes longer. Without it, tests will xfail when encountering a # function that we don't implement. # +# NX_CUGRAPH_USE_COMPAT_GRAPHS, {"True", "False"}, default is "True" +# Whether to use `nxcg.Graph` as the nx_cugraph backend graph. +# A Graph should be a compatible NetworkX graph, so fewer tests should fail. +# # Coverage of `nx_cugraph.algorithms` is reported and is a good sanity check # that algorithms run. From 27f2256eede2949c04a94136500ec6c1b7fbbc29 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 24 Sep 2024 23:25:18 -0500 Subject: [PATCH 09/37] bump NCCL floor to 2.18.1.1, include nccl.h where it's needed (#4661) Contributes to https://github.com/rapidsai/build-planning/issues/102 Some RAPIDS libraries are using `ncclCommSplit()`, which was introduced in `nccl==2.18.1.1`. This is part of a series of PRs across RAPIDS updating libraries' pins to `nccl>=2.18.1.1` to ensure they get a new-enough version that supports that. Authors: - James Lamb (https://github.com/jameslamb) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Vyas Ramasubramani (https://github.com/vyasr) - https://github.com/jakirkham URL: https://github.com/rapidsai/cugraph/pull/4661 --- conda/environments/all_cuda-118_arch-x86_64.yaml | 2 +- conda/environments/all_cuda-125_arch-x86_64.yaml | 2 +- conda/recipes/libcugraph/conda_build_config.yaml | 2 +- cpp/include/cugraph/mtmg/instance_manager.hpp | 2 ++ cpp/include/cugraph/mtmg/resource_manager.hpp | 2 ++ cpp/tests/mtmg/multi_node_threaded_test.cu | 1 + dependencies.yaml | 2 +- 7 files changed, 9 insertions(+), 4 deletions(-) diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 533f23cd7ac..fd91edd8adc 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -33,7 +33,7 @@ dependencies: - libraft==24.10.*,>=0.0.0a0 - librmm==24.10.*,>=0.0.0a0 - nbsphinx -- nccl>=2.9.9 +- nccl>=2.18.1.1 - networkx>=2.5.1 - networkx>=3.0 - ninja diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index 084a6adfd31..19da750601b 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -39,7 +39,7 @@ dependencies: - libraft==24.10.*,>=0.0.0a0 - librmm==24.10.*,>=0.0.0a0 - nbsphinx -- nccl>=2.9.9 +- nccl>=2.18.1.1 - networkx>=2.5.1 - networkx>=3.0 - ninja diff --git a/conda/recipes/libcugraph/conda_build_config.yaml b/conda/recipes/libcugraph/conda_build_config.yaml index 26aa428d7f5..6b50d0aad23 100644 --- a/conda/recipes/libcugraph/conda_build_config.yaml +++ b/conda/recipes/libcugraph/conda_build_config.yaml @@ -17,7 +17,7 @@ doxygen_version: - ">=1.8.11" nccl_version: - - ">=2.9.9" + - ">=2.18.1.1" c_stdlib: - sysroot diff --git a/cpp/include/cugraph/mtmg/instance_manager.hpp b/cpp/include/cugraph/mtmg/instance_manager.hpp index a2111804997..759635b4a34 100644 --- a/cpp/include/cugraph/mtmg/instance_manager.hpp +++ b/cpp/include/cugraph/mtmg/instance_manager.hpp @@ -20,6 +20,8 @@ #include +#include + #include namespace cugraph { diff --git a/cpp/include/cugraph/mtmg/resource_manager.hpp b/cpp/include/cugraph/mtmg/resource_manager.hpp index a9e4b81f894..e9d25c4576b 100644 --- a/cpp/include/cugraph/mtmg/resource_manager.hpp +++ b/cpp/include/cugraph/mtmg/resource_manager.hpp @@ -27,6 +27,8 @@ #include #include +#include + #include namespace cugraph { diff --git a/cpp/tests/mtmg/multi_node_threaded_test.cu b/cpp/tests/mtmg/multi_node_threaded_test.cu index 06ccd4a7fa1..374c432aac5 100644 --- a/cpp/tests/mtmg/multi_node_threaded_test.cu +++ b/cpp/tests/mtmg/multi_node_threaded_test.cu @@ -39,6 +39,7 @@ #include #include +#include #include #include diff --git a/dependencies.yaml b/dependencies.yaml index 76048be2010..cd34fcf2f70 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -530,7 +530,7 @@ dependencies: - aiohttp - fsspec>=0.6.0 - requests - - nccl>=2.9.9 + - nccl>=2.18.1.1 - ucx-proc=*=gpu - &ucx_py_unsuffixed ucx-py==0.40.*,>=0.0.0a0 - output_types: pyproject From 87987af74d65e9dedeaea96a9ea4ac7c3c068615 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Wed, 25 Sep 2024 11:09:46 -0400 Subject: [PATCH 10/37] Install mg test executables (#4656) `rapids_test_add()` does not install the executables we need, because it's checking the value of `${MPIEXEC_EXECUTABLE}` and seeing that it's not a target. Install the target separately. Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Bradley Dice (https://github.com/bdice) - Chuck Hastings (https://github.com/ChuckHastings) URL: https://github.com/rapidsai/cugraph/pull/4656 --- cpp/tests/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 09b1431e33b..3752e823659 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -220,6 +220,7 @@ function(ConfigureTestMG CMAKE_TEST_NAME) GPUS ${GPU_COUNT} PERCENT 100 INSTALL_COMPONENT_SET testing_mg + INSTALL_TARGET ${CMAKE_TEST_NAME} ) set_tests_properties(${CMAKE_TEST_NAME} PROPERTIES LABELS "CUGRAPH_MG") @@ -302,6 +303,7 @@ function(ConfigureCTestMG CMAKE_TEST_NAME) GPUS ${GPU_COUNT} PERCENT 100 INSTALL_COMPONENT_SET testing_mg + INSTALL_TARGET ${CMAKE_TEST_NAME} ) set_tests_properties(${CMAKE_TEST_NAME} PROPERTIES LABELS "CUGRAPH_C_MG") From 1e5b3287e628a5cf8e60da14c25174baf0e5cc1a Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:21:35 -0400 Subject: [PATCH 11/37] Fix `cit-patents` Dataset for `nx-cugraph` Benchmark (#4666) Replace `_` with `-` Authors: - Ralph Liu (https://github.com/nv-rliu) Approvers: - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4666 --- benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh index a1d32474e5f..3059e3d4bdf 100755 --- a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh +++ b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh @@ -30,7 +30,7 @@ algos=" datasets=" netscience email_Eu_core - cit_patents + cit-patents hollywood soc-livejournal " From 36c190ac8378527ac1e7445eb3773068f9179dea Mon Sep 17 00:00:00 2001 From: Alex Barghi <105237337+alexbarghi-nv@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:42:32 -0400 Subject: [PATCH 12/37] [FEA] Biased Sampling in cuGraph-DGL (#4595) Adds support for biased sampling to cuGraph-DGL. Resolves rapidsai/cugraph-gnn#25 Merge after #4583, #4586, #4607 Authors: - Alex Barghi (https://github.com/alexbarghi-nv) - Ralph Liu (https://github.com/nv-rliu) - Seunghwa Kang (https://github.com/seunghwak) Approvers: - Ray Douglass (https://github.com/raydouglass) - Tingyu Wang (https://github.com/tingyu66) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4595 --- .../dataloading/neighbor_sampler.py | 19 ++- python/cugraph-dgl/cugraph_dgl/graph.py | 148 ++++++++++-------- .../tests/dataloading/test_dataloader.py | 55 ++++++- .../tests/dataloading/test_dataloader_mg.py | 81 +++++++++- .../cugraph/gnn/data_loading/dist_sampler.py | 2 +- .../pylibcugraph/biased_neighbor_sample.pyx | 2 +- .../pylibcugraph/uniform_neighbor_sample.pyx | 2 +- 7 files changed, 224 insertions(+), 85 deletions(-) diff --git a/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py b/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py index 87d111adcba..4ec513cbf9b 100644 --- a/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py +++ b/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py @@ -18,7 +18,7 @@ from typing import Sequence, Optional, Union, List, Tuple, Iterator -from cugraph.gnn import UniformNeighborSampler, DistSampleWriter +from cugraph.gnn import UniformNeighborSampler, BiasedNeighborSampler, DistSampleWriter from cugraph.utilities.utils import import_optional import cugraph_dgl @@ -93,7 +93,6 @@ def __init__( If provided, the probability of each neighbor being sampled is proportional to the edge feature with the given name. Mutually exclusive with mask. - Currently unsupported. mask: str Optional. If proivided, only neighbors where the edge mask @@ -133,10 +132,6 @@ def __init__( raise NotImplementedError( "Edge masking is currently unsupported by cuGraph-DGL" ) - if prob: - raise NotImplementedError( - "Edge masking is currently unsupported by cuGraph-DGL" - ) if prefetch_edge_feats: warnings.warn("'prefetch_edge_feats' is ignored by cuGraph-DGL") if prefetch_node_feats: @@ -146,6 +141,8 @@ def __init__( if fused: warnings.warn("'fused' is ignored by cuGraph-DGL") + self.__prob_attr = prob + self.fanouts = fanouts_per_layer reverse_fanouts = fanouts_per_layer.copy() reverse_fanouts.reverse() @@ -180,8 +177,14 @@ def sample( format=kwargs.pop("format", "parquet"), ) - ds = UniformNeighborSampler( - g._graph(self.edge_dir), + sampling_clx = ( + UniformNeighborSampler + if self.__prob_attr is None + else BiasedNeighborSampler + ) + + ds = sampling_clx( + g._graph(self.edge_dir, prob_attr=self.__prob_attr), writer, compression="CSR", fanout=self._reversed_fanout_vals, diff --git a/python/cugraph-dgl/cugraph_dgl/graph.py b/python/cugraph-dgl/cugraph_dgl/graph.py index 011ab736d00..138e645838a 100644 --- a/python/cugraph-dgl/cugraph_dgl/graph.py +++ b/python/cugraph-dgl/cugraph_dgl/graph.py @@ -312,7 +312,7 @@ def add_edges( self.__graph = None self.__vertex_offsets = None - def num_nodes(self, ntype: str = None) -> int: + def num_nodes(self, ntype: Optional[str] = None) -> int: """ Returns the number of nodes of ntype, or if ntype is not provided, the total number of nodes in the graph. @@ -322,7 +322,7 @@ def num_nodes(self, ntype: str = None) -> int: return self.__num_nodes_dict[ntype] - def number_of_nodes(self, ntype: str = None) -> int: + def number_of_nodes(self, ntype: Optional[str] = None) -> int: """ Alias for num_nodes. """ @@ -381,7 +381,7 @@ def _vertex_offsets(self) -> Dict[str, int]: return dict(self.__vertex_offsets) - def __get_edgelist(self) -> Dict[str, "torch.Tensor"]: + def __get_edgelist(self, prob_attr=None) -> Dict[str, "torch.Tensor"]: """ This function always returns src/dst labels with respect to the out direction. @@ -431,63 +431,71 @@ def __get_edgelist(self) -> Dict[str, "torch.Tensor"]: ) ) + num_edges_t = torch.tensor( + [self.__edge_indices[et].shape[1] for et in sorted_keys], device="cuda" + ) + if self.is_multi_gpu: rank = torch.distributed.get_rank() world_size = torch.distributed.get_world_size() - num_edges_t = torch.tensor( - [self.__edge_indices[et].shape[1] for et in sorted_keys], device="cuda" - ) num_edges_all_t = torch.empty( world_size, num_edges_t.numel(), dtype=torch.int64, device="cuda" ) torch.distributed.all_gather_into_tensor(num_edges_all_t, num_edges_t) - if rank > 0: - start_offsets = num_edges_all_t[:rank].T.sum(axis=1) - edge_id_array = torch.concat( + start_offsets = num_edges_all_t[:rank].T.sum(axis=1) + + else: + rank = 0 + start_offsets = torch.zeros( + (len(sorted_keys),), dtype=torch.int64, device="cuda" + ) + num_edges_all_t = num_edges_t.reshape((1, num_edges_t.numel())) + + # Use pinned memory here for fast access to CPU/WG storage + edge_id_array_per_type = [ + torch.arange( + start_offsets[i], + start_offsets[i] + num_edges_all_t[rank][i], + dtype=torch.int64, + device="cpu", + ).pin_memory() + for i in range(len(sorted_keys)) + ] + + # Retrieve the weights from the appropriate feature(s) + # DGL implicitly requires all edge types use the same + # feature name. + if prob_attr is None: + weights = None + else: + if len(sorted_keys) > 1: + weights = torch.concat( [ - torch.arange( - start_offsets[i], - start_offsets[i] + num_edges_all_t[rank][i], - dtype=torch.int64, - device="cuda", - ) - for i in range(len(sorted_keys)) + self.edata[prob_attr][sorted_keys[i]][ix] + for i, ix in enumerate(edge_id_array_per_type) ] ) else: - edge_id_array = torch.concat( - [ - torch.arange( - self.__edge_indices[et].shape[1], - dtype=torch.int64, - device="cuda", - ) - for et in sorted_keys - ] - ) + weights = self.edata[prob_attr][edge_id_array_per_type[0]] - else: - # single GPU - edge_id_array = torch.concat( - [ - torch.arange( - self.__edge_indices[et].shape[1], - dtype=torch.int64, - device="cuda", - ) - for et in sorted_keys - ] - ) + # Safe to move this to cuda because the consumer will always + # move it to cuda if it isn't already there. + edge_id_array = torch.concat(edge_id_array_per_type).cuda() - return { + edgelist_dict = { "src": edge_index[0], "dst": edge_index[1], "etp": edge_type_array, "eid": edge_id_array, } + if weights is not None: + edgelist_dict["wgt"] = weights + + return edgelist_dict + @property def is_homogeneous(self): return len(self.__num_edges_dict) <= 1 and len(self.__num_nodes_dict) <= 1 @@ -508,7 +516,9 @@ def _resource_handle(self): return self.__handle def _graph( - self, direction: str + self, + direction: str, + prob_attr: Optional[str] = None, ) -> Union[pylibcugraph.SGGraph, pylibcugraph.MGGraph]: """ Gets the pylibcugraph Graph object with edges pointing in the given direction @@ -522,12 +532,16 @@ def _graph( is_multigraph=True, is_symmetric=False ) - if self.__graph is not None and self.__graph[1] != direction: - self.__graph = None + if self.__graph is not None: + if ( + self.__graph["direction"] != direction + or self.__graph["prob_attr"] != prob_attr + ): + self.__graph = None if self.__graph is None: src_col, dst_col = ("src", "dst") if direction == "out" else ("dst", "src") - edgelist_dict = self.__get_edgelist() + edgelist_dict = self.__get_edgelist(prob_attr=prob_attr) if self.is_multi_gpu: rank = torch.distributed.get_rank() @@ -536,33 +550,35 @@ def _graph( vertices_array = cupy.arange(self.num_nodes(), dtype="int64") vertices_array = cupy.array_split(vertices_array, world_size)[rank] - self.__graph = ( - pylibcugraph.MGGraph( - self._resource_handle, - graph_properties, - [cupy.asarray(edgelist_dict[src_col]).astype("int64")], - [cupy.asarray(edgelist_dict[dst_col]).astype("int64")], - vertices_array=[vertices_array], - edge_id_array=[cupy.asarray(edgelist_dict["eid"])], - edge_type_array=[cupy.asarray(edgelist_dict["etp"])], - ), - direction, + graph = pylibcugraph.MGGraph( + self._resource_handle, + graph_properties, + [cupy.asarray(edgelist_dict[src_col]).astype("int64")], + [cupy.asarray(edgelist_dict[dst_col]).astype("int64")], + vertices_array=[vertices_array], + edge_id_array=[cupy.asarray(edgelist_dict["eid"])], + edge_type_array=[cupy.asarray(edgelist_dict["etp"])], + weight_array=[cupy.asarray(edgelist_dict["wgt"])] + if "wgt" in edgelist_dict + else None, ) else: - self.__graph = ( - pylibcugraph.SGGraph( - self._resource_handle, - graph_properties, - cupy.asarray(edgelist_dict[src_col]).astype("int64"), - cupy.asarray(edgelist_dict[dst_col]).astype("int64"), - vertices_array=cupy.arange(self.num_nodes(), dtype="int64"), - edge_id_array=cupy.asarray(edgelist_dict["eid"]), - edge_type_array=cupy.asarray(edgelist_dict["etp"]), - ), - direction, + graph = pylibcugraph.SGGraph( + self._resource_handle, + graph_properties, + cupy.asarray(edgelist_dict[src_col]).astype("int64"), + cupy.asarray(edgelist_dict[dst_col]).astype("int64"), + vertices_array=cupy.arange(self.num_nodes(), dtype="int64"), + edge_id_array=cupy.asarray(edgelist_dict["eid"]), + edge_type_array=cupy.asarray(edgelist_dict["etp"]), + weight_array=cupy.asarray(edgelist_dict["wgt"]) + if "wgt" in edgelist_dict + else None, ) - return self.__graph[0] + self.__graph = {"graph": graph, "direction": direction, "prob_attr": prob_attr} + + return self.__graph["graph"] def _has_n_emb(self, ntype: str, emb_name: str) -> bool: return (ntype, emb_name) in self.__ndata_storage diff --git a/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader.py b/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader.py index ef47875463d..419ec7790a9 100644 --- a/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader.py +++ b/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. + import cugraph_dgl.dataloading import pytest @@ -48,9 +49,12 @@ def test_dataloader_basic_homogeneous(): assert len(out_t) <= 2 -def sample_dgl_graphs(g, train_nid, fanouts, batch_size=1): +def sample_dgl_graphs(g, train_nid, fanouts, batch_size=1, prob_attr=None): # Single fanout to match cugraph - sampler = dgl.dataloading.NeighborSampler(fanouts) + sampler = dgl.dataloading.NeighborSampler( + fanouts, + prob=prob_attr, + ) dataloader = dgl.dataloading.DataLoader( g, train_nid, @@ -71,8 +75,13 @@ def sample_dgl_graphs(g, train_nid, fanouts, batch_size=1): return dgl_output -def sample_cugraph_dgl_graphs(cugraph_g, train_nid, fanouts, batch_size=1): - sampler = cugraph_dgl.dataloading.NeighborSampler(fanouts) +def sample_cugraph_dgl_graphs( + cugraph_g, train_nid, fanouts, batch_size=1, prob_attr=None +): + sampler = cugraph_dgl.dataloading.NeighborSampler( + fanouts, + prob=prob_attr, + ) dataloader = cugraph_dgl.dataloading.FutureDataLoader( cugraph_g, @@ -126,3 +135,41 @@ def test_same_homogeneousgraph_results(ix, batch_size): dgl_output[0]["blocks"][0].num_edges() == cugraph_output[0]["blocks"][0].num_edges() ) + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.skipif(isinstance(dgl, MissingModule), reason="dgl not available") +def test_dataloader_biased_homogeneous(): + src = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8]) + dst = torch.tensor([0, 0, 0, 0, 1, 1, 1, 1]) + wgt = torch.tensor([1, 1, 2, 0, 0, 0, 2, 1], dtype=torch.float32) + + train_nid = torch.tensor([0, 1]) + # Create a heterograph with 3 node types and 3 edges types. + dgl_g = dgl.graph((src, dst)) + dgl_g.edata["wgt"] = wgt + + cugraph_g = cugraph_dgl.Graph(is_multi_gpu=False) + cugraph_g.add_nodes(9) + cugraph_g.add_edges(u=src, v=dst, data={"wgt": wgt}) + + dgl_output = sample_dgl_graphs(dgl_g, train_nid, [4], batch_size=2, prob_attr="wgt") + cugraph_output = sample_cugraph_dgl_graphs( + cugraph_g, train_nid, [4], batch_size=2, prob_attr="wgt" + ) + + cugraph_output_nodes = cugraph_output[0]["output_nodes"].cpu().numpy() + dgl_output_nodes = dgl_output[0]["output_nodes"].cpu().numpy() + + np.testing.assert_array_equal( + np.sort(cugraph_output_nodes), np.sort(dgl_output_nodes) + ) + assert ( + dgl_output[0]["blocks"][0].num_dst_nodes() + == cugraph_output[0]["blocks"][0].num_dst_nodes() + ) + assert ( + dgl_output[0]["blocks"][0].num_edges() + == cugraph_output[0]["blocks"][0].num_edges() + ) + assert 5 == cugraph_output[0]["blocks"][0].num_edges() diff --git a/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader_mg.py b/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader_mg.py index b32233f16a6..061f4fa2077 100644 --- a/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader_mg.py +++ b/python/cugraph-dgl/cugraph_dgl/tests/dataloading/test_dataloader_mg.py @@ -82,9 +82,18 @@ def test_dataloader_basic_homogeneous(): ) -def sample_dgl_graphs(g, train_nid, fanouts, batch_size=1): +def sample_dgl_graphs( + g, + train_nid, + fanouts, + batch_size=1, + prob_attr=None, +): # Single fanout to match cugraph - sampler = dgl.dataloading.NeighborSampler(fanouts) + sampler = dgl.dataloading.NeighborSampler( + fanouts, + prob=prob_attr, + ) dataloader = dgl.dataloading.DataLoader( g, train_nid, @@ -105,8 +114,17 @@ def sample_dgl_graphs(g, train_nid, fanouts, batch_size=1): return dgl_output -def sample_cugraph_dgl_graphs(cugraph_g, train_nid, fanouts, batch_size=1): - sampler = cugraph_dgl.dataloading.NeighborSampler(fanouts) +def sample_cugraph_dgl_graphs( + cugraph_g, + train_nid, + fanouts, + batch_size=1, + prob_attr=None, +): + sampler = cugraph_dgl.dataloading.NeighborSampler( + fanouts, + prob=prob_attr, + ) dataloader = cugraph_dgl.dataloading.FutureDataLoader( cugraph_g, @@ -179,3 +197,58 @@ def test_same_homogeneousgraph_results_mg(ix, batch_size): args=(world_size, uid, ix, batch_size), nprocs=world_size, ) + + +def run_test_dataloader_biased_homogeneous(rank, world_size, uid): + init_pytorch_worker(rank, world_size, uid, True) + + src = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8]) + (rank * 9) + dst = torch.tensor([0, 0, 0, 0, 1, 1, 1, 1]) + (rank * 9) + wgt = torch.tensor( + [0.1, 0.1, 0.2, 0, 0, 0, 0.2, 0.1] * world_size, dtype=torch.float32 + ) + + train_nid = torch.tensor([0, 1]) + (rank * 9) + # Create a heterograph with 3 node types and 3 edge types. + dgl_g = dgl.graph((src, dst)) + dgl_g.edata["wgt"] = wgt[:8] + + cugraph_g = cugraph_dgl.Graph(is_multi_gpu=True) + cugraph_g.add_nodes(9 * world_size) + cugraph_g.add_edges(u=src, v=dst, data={"wgt": wgt}) + + dgl_output = sample_dgl_graphs(dgl_g, train_nid, [4], batch_size=2, prob_attr="wgt") + cugraph_output = sample_cugraph_dgl_graphs( + cugraph_g, train_nid, [4], batch_size=2, prob_attr="wgt" + ) + + cugraph_output_nodes = cugraph_output[0]["output_nodes"].cpu().numpy() + dgl_output_nodes = dgl_output[0]["output_nodes"].cpu().numpy() + + np.testing.assert_array_equal( + np.sort(cugraph_output_nodes), np.sort(dgl_output_nodes) + ) + assert ( + dgl_output[0]["blocks"][0].num_dst_nodes() + == cugraph_output[0]["blocks"][0].num_dst_nodes() + ) + assert ( + dgl_output[0]["blocks"][0].num_edges() + == cugraph_output[0]["blocks"][0].num_edges() + ) + + assert 5 == cugraph_output[0]["blocks"][0].num_edges() + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.skipif(isinstance(dgl, MissingModule), reason="dgl not available") +def test_dataloader_biased_homogeneous_mg(): + uid = cugraph_comms_create_unique_id() + # Limit the number of GPUs this test is run with + world_size = torch.cuda.device_count() + + torch.multiprocessing.spawn( + run_test_dataloader_biased_homogeneous, + args=(world_size, uid), + nprocs=world_size, + ) diff --git a/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py b/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py index a49139961fd..52ffd8fadfd 100644 --- a/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py +++ b/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py @@ -776,7 +776,7 @@ def sample_batches( label_to_output_comm_rank=cupy.asarray(label_to_output_comm_rank), h_fan_out=np.array(self.__fanout, dtype="int32"), with_replacement=self.__with_replacement, - do_expensive_check=True, + do_expensive_check=False, with_edge_properties=True, random_state=random_state + rank, prior_sources_behavior=self.__prior_sources_behavior, diff --git a/python/pylibcugraph/pylibcugraph/biased_neighbor_sample.pyx b/python/pylibcugraph/pylibcugraph/biased_neighbor_sample.pyx index 77f1f04c394..2dd138d5d06 100644 --- a/python/pylibcugraph/pylibcugraph/biased_neighbor_sample.pyx +++ b/python/pylibcugraph/pylibcugraph/biased_neighbor_sample.pyx @@ -118,7 +118,7 @@ def biased_neighbor_sample(ResourceHandle resource_handle, Device array containing the list of starting vertices for sampling. h_fan_out: numpy array type - Host array containing the brancing out (fan-out) degrees per + Host array containing the branching out (fan-out) degrees per starting vertex for each hop level. with_replacement: bool diff --git a/python/pylibcugraph/pylibcugraph/uniform_neighbor_sample.pyx b/python/pylibcugraph/pylibcugraph/uniform_neighbor_sample.pyx index c25c9119985..f3e2336d8f6 100644 --- a/python/pylibcugraph/pylibcugraph/uniform_neighbor_sample.pyx +++ b/python/pylibcugraph/pylibcugraph/uniform_neighbor_sample.pyx @@ -117,7 +117,7 @@ def uniform_neighbor_sample(ResourceHandle resource_handle, Device array containing the list of starting vertices for sampling. h_fan_out: numpy array type - Host array containing the brancing out (fan-out) degrees per + Host array containing the branching out (fan-out) degrees per starting vertex for each hop level. with_replacement: bool From 54b03da6a2121be837eb87500f51c7c578a1b246 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Fri, 27 Sep 2024 11:30:11 -0500 Subject: [PATCH 13/37] reduce pip verbosity in wheel builds (#4651) Proposes reducing the verbosity of `pip wheel` called in wheel builds from `-vvv` to `-v`. This eliminates the 1000s of lines like this in CI logs: ![image](https://github.com/user-attachments/assets/4dc2b024-c9f1-4618-9c94-8166c6aa9a13) to hopefully make it easier to view those logs interactively Authors: - James Lamb (https://github.com/jameslamb) Approvers: - Vyas Ramasubramani (https://github.com/vyasr) URL: https://github.com/rapidsai/cugraph/pull/4651 --- ci/build_wheel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build_wheel.sh b/ci/build_wheel.sh index 1976d8ff46f..f3979ab3049 100755 --- a/ci/build_wheel.sh +++ b/ci/build_wheel.sh @@ -17,7 +17,7 @@ cd "${package_dir}" python -m pip wheel \ -w dist \ - -vvv \ + -v \ --no-deps \ --disable-pip-version-check \ --extra-index-url https://pypi.nvidia.com \ From 0f4fe8f92340861c14eacc3298e52ec5513b6c12 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 30 Sep 2024 15:42:56 +0200 Subject: [PATCH 14/37] Remove NumPy <2 pin (#4615) This PR removes the NumPy<2 pin which is expected to work for RAPIDS projects once CuPy 13.3.0 is released (CuPy 13.2.0 had some issues preventing the use with NumPy 2). Authors: - Sebastian Berg (https://github.com/seberg) - https://github.com/jakirkham - Rick Ratzel (https://github.com/rlratzel) - Alex Barghi (https://github.com/alexbarghi-nv) - James Lamb (https://github.com/jameslamb) - Philip Hyunsu Cho (https://github.com/hcho3) Approvers: - Alex Barghi (https://github.com/alexbarghi-nv) - James Lamb (https://github.com/jameslamb) URL: https://github.com/rapidsai/cugraph/pull/4615 --- ci/test_python.sh | 6 +++--- ci/test_wheel_cugraph-dgl.sh | 12 +----------- ci/test_wheel_cugraph-pyg.sh | 6 +++--- conda/environments/all_cuda-118_arch-x86_64.yaml | 6 +++--- conda/environments/all_cuda-125_arch-x86_64.yaml | 6 +++--- conda/recipes/cugraph-dgl/meta.yaml | 4 ++-- conda/recipes/cugraph-pyg/meta.yaml | 4 ++-- conda/recipes/cugraph-service/meta.yaml | 2 +- conda/recipes/libcugraph/conda_build_config.yaml | 2 +- dependencies.yaml | 14 ++++++++------ .../conda/cugraph_dgl_dev_cuda-118.yaml | 2 +- python/cugraph-dgl/pyproject.toml | 4 ++-- .../conda/cugraph_pyg_dev_cuda-118.yaml | 2 +- python/cugraph-pyg/pyproject.toml | 4 ++-- python/cugraph-service/server/pyproject.toml | 4 ++-- .../tests/data_store/test_property_graph.py | 14 ++++++++------ python/cugraph/pyproject.toml | 4 ++-- python/nx-cugraph/pyproject.toml | 2 +- python/pylibcugraph/pyproject.toml | 2 +- 19 files changed, 47 insertions(+), 53 deletions(-) diff --git a/ci/test_python.sh b/ci/test_python.sh index 810284b8c97..f21a06cf061 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -159,7 +159,7 @@ if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then cugraph \ cugraph-dgl \ 'dgl>=1.1.0.cu*,<=2.0.0.cu*' \ - 'pytorch>=2.0' \ + 'pytorch>=2.3,<2.4' \ 'cuda-version=11.8' rapids-print-env @@ -198,10 +198,10 @@ if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then # TODO re-enable logic once CUDA 12 is testable #if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then CONDA_CUDA_VERSION="11.8" - PYG_URL="https://data.pyg.org/whl/torch-2.1.0+cu118.html" + PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu118.html" #else # CONDA_CUDA_VERSION="12.1" - # PYG_URL="https://data.pyg.org/whl/torch-2.1.0+cu121.html" + # PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu121.html" #fi # Will automatically install built dependencies of cuGraph-PyG diff --git a/ci/test_wheel_cugraph-dgl.sh b/ci/test_wheel_cugraph-dgl.sh index 564b46cb07e..9b79cb17fe4 100755 --- a/ci/test_wheel_cugraph-dgl.sh +++ b/ci/test_wheel_cugraph-dgl.sh @@ -32,18 +32,8 @@ fi PYTORCH_URL="https://download.pytorch.org/whl/cu${PYTORCH_CUDA_VER}" DGL_URL="https://data.dgl.ai/wheels/cu${PYTORCH_CUDA_VER}/repo.html" -# Starting from 2.2, PyTorch wheels depend on nvidia-nccl-cuxx>=2.19 wheel and -# dynamically link to NCCL. RAPIDS CUDA 11 CI images have an older NCCL version that -# might shadow the newer NCCL required by PyTorch during import (when importing -# `cupy` before `torch`). -if [[ "${NCCL_VERSION}" < "2.19" ]]; then - PYTORCH_VER="2.1.0" -else - PYTORCH_VER="2.3.0" -fi - rapids-logger "Installing PyTorch and DGL" -rapids-retry python -m pip install "torch==${PYTORCH_VER}" --index-url ${PYTORCH_URL} +rapids-retry python -m pip install torch==2.3.0 --index-url ${PYTORCH_URL} rapids-retry python -m pip install dgl==2.0.0 --find-links ${DGL_URL} python -m pytest python/cugraph-dgl/tests diff --git a/ci/test_wheel_cugraph-pyg.sh b/ci/test_wheel_cugraph-pyg.sh index c55ae033344..8f4b16a2dec 100755 --- a/ci/test_wheel_cugraph-pyg.sh +++ b/ci/test_wheel_cugraph-pyg.sh @@ -29,13 +29,13 @@ export CI_RUN=1 if [[ "${CUDA_VERSION}" == "11.8.0" ]]; then PYTORCH_URL="https://download.pytorch.org/whl/cu118" - PYG_URL="https://data.pyg.org/whl/torch-2.1.0+cu118.html" + PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu118.html" else PYTORCH_URL="https://download.pytorch.org/whl/cu121" - PYG_URL="https://data.pyg.org/whl/torch-2.1.0+cu121.html" + PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu121.html" fi rapids-logger "Installing PyTorch and PyG dependencies" -rapids-retry python -m pip install torch==2.1.0 --index-url ${PYTORCH_URL} +rapids-retry python -m pip install torch==2.3.0 --index-url ${PYTORCH_URL} rapids-retry python -m pip install "torch-geometric>=2.5,<2.6" rapids-retry python -m pip install \ ogb \ diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index fd91edd8adc..7ae576e8288 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -33,13 +33,13 @@ dependencies: - libraft==24.10.*,>=0.0.0a0 - librmm==24.10.*,>=0.0.0a0 - nbsphinx -- nccl>=2.18.1.1 +- nccl>=2.19 - networkx>=2.5.1 - networkx>=3.0 - ninja - notebook>=0.5.0 - numba>=0.57 -- numpy>=1.23,<2.0a0 +- numpy>=1.23,<3.0a0 - numpydoc - nvcc_linux-64=11.8 - ogb @@ -57,7 +57,7 @@ dependencies: - pytest-mpl - pytest-xdist - python-louvain -- pytorch>=2.0,<2.2.0a0 +- pytorch>=2.3,<2.4.0a0 - raft-dask==24.10.*,>=0.0.0a0 - rapids-build-backend>=0.3.1,<0.4.0.dev0 - rapids-dask-dependency==24.10.*,>=0.0.0a0 diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index 19da750601b..1fb04cae081 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -39,13 +39,13 @@ dependencies: - libraft==24.10.*,>=0.0.0a0 - librmm==24.10.*,>=0.0.0a0 - nbsphinx -- nccl>=2.18.1.1 +- nccl>=2.19 - networkx>=2.5.1 - networkx>=3.0 - ninja - notebook>=0.5.0 - numba>=0.57 -- numpy>=1.23,<2.0a0 +- numpy>=1.23,<3.0a0 - numpydoc - ogb - openmpi @@ -62,7 +62,7 @@ dependencies: - pytest-mpl - pytest-xdist - python-louvain -- pytorch>=2.0,<2.2.0a0 +- pytorch>=2.3,<2.4.0a0 - raft-dask==24.10.*,>=0.0.0a0 - rapids-build-backend>=0.3.1,<0.4.0.dev0 - rapids-dask-dependency==24.10.*,>=0.0.0a0 diff --git a/conda/recipes/cugraph-dgl/meta.yaml b/conda/recipes/cugraph-dgl/meta.yaml index d1cf6fcd9e9..c80ca6890a8 100644 --- a/conda/recipes/cugraph-dgl/meta.yaml +++ b/conda/recipes/cugraph-dgl/meta.yaml @@ -27,11 +27,11 @@ requirements: - cugraph ={{ version }} - dgl >=1.1.0.cu* - numba >=0.57 - - numpy >=1.23,<2.0a0 + - numpy >=1.23,<3.0a0 - pylibcugraphops ={{ minor_version }} - tensordict >=0.1.2 - python - - pytorch >=2.0 + - pytorch >=2.3,<2.4.0a0 - cupy >=12.0.0 tests: diff --git a/conda/recipes/cugraph-pyg/meta.yaml b/conda/recipes/cugraph-pyg/meta.yaml index 2e1788ac0c6..38d4a3d7d15 100644 --- a/conda/recipes/cugraph-pyg/meta.yaml +++ b/conda/recipes/cugraph-pyg/meta.yaml @@ -29,9 +29,9 @@ requirements: run: - rapids-dask-dependency ={{ minor_version }} - numba >=0.57 - - numpy >=1.23,<2.0a0 + - numpy >=1.23,<3.0a0 - python - - pytorch >=2.0 + - pytorch >=2.3,<2.4.0a0 - cupy >=12.0.0 - cugraph ={{ version }} - pylibcugraphops ={{ minor_version }} diff --git a/conda/recipes/cugraph-service/meta.yaml b/conda/recipes/cugraph-service/meta.yaml index c1027582c78..7df7573e2d0 100644 --- a/conda/recipes/cugraph-service/meta.yaml +++ b/conda/recipes/cugraph-service/meta.yaml @@ -63,7 +63,7 @@ outputs: - dask-cuda ={{ minor_version }} - dask-cudf ={{ minor_version }} - numba >=0.57 - - numpy >=1.23,<2.0a0 + - numpy >=1.23,<3.0a0 - python - rapids-dask-dependency ={{ minor_version }} - thriftpy2 >=0.4.15,!=0.5.0,!=0.5.1 diff --git a/conda/recipes/libcugraph/conda_build_config.yaml b/conda/recipes/libcugraph/conda_build_config.yaml index 6b50d0aad23..55bd635c330 100644 --- a/conda/recipes/libcugraph/conda_build_config.yaml +++ b/conda/recipes/libcugraph/conda_build_config.yaml @@ -17,7 +17,7 @@ doxygen_version: - ">=1.8.11" nccl_version: - - ">=2.18.1.1" + - ">=2.19" c_stdlib: - sysroot diff --git a/dependencies.yaml b/dependencies.yaml index cd34fcf2f70..4da61cb00ad 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -524,13 +524,13 @@ dependencies: - &dask rapids-dask-dependency==24.10.*,>=0.0.0a0 - &dask_cuda dask-cuda==24.10.*,>=0.0.0a0 - &numba numba>=0.57 - - &numpy numpy>=1.23,<2.0a0 + - &numpy numpy>=1.23,<3.0a0 - output_types: conda packages: - aiohttp - fsspec>=0.6.0 - requests - - nccl>=2.18.1.1 + - nccl>=2.19 - ucx-proc=*=gpu - &ucx_py_unsuffixed ucx-py==0.40.*,>=0.0.0a0 - output_types: pyproject @@ -695,7 +695,9 @@ dependencies: - output_types: [conda] packages: - *cugraph_unsuffixed - - pytorch>=2.0 + # ceiling could be removed when this is fixed: + # https://github.com/conda-forge/pytorch-cpu-feedstock/issues/254 + - &pytorch_conda pytorch>=2.3,<2.4.0a0 - pytorch-cuda==11.8 - &tensordict tensordict>=0.1.2 - dgl>=1.1.0.cu* @@ -704,7 +706,7 @@ dependencies: - output_types: [conda] packages: - *cugraph_unsuffixed - - pytorch>=2.0 + - *pytorch_conda - pytorch-cuda==11.8 - *tensordict - pyg>=2.5,<2.6 @@ -713,7 +715,7 @@ dependencies: common: - output_types: [conda] packages: - - &pytorch_unsuffixed pytorch>=2.0,<2.2.0a0 + - *pytorch_conda - torchdata - pydantic - ogb @@ -733,7 +735,7 @@ dependencies: matrices: - matrix: {cuda: "12.*"} packages: - - &pytorch_pip torch>=2.0,<2.2.0a0 + - &pytorch_pip torch>=2.3,<2.4.0a0 - *tensordict - matrix: {cuda: "11.*"} packages: diff --git a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml index ea30b652286..bbb6a5082f6 100644 --- a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml +++ b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml @@ -19,7 +19,7 @@ dependencies: - pytest-cov - pytest-xdist - pytorch-cuda==11.8 -- pytorch>=2.0 +- pytorch>=2.3,<2.4.0a0 - scipy - tensordict>=0.1.2 name: cugraph_dgl_dev_cuda-118 diff --git a/python/cugraph-dgl/pyproject.toml b/python/cugraph-dgl/pyproject.toml index 0cfeb10822a..e6c411c0eec 100644 --- a/python/cugraph-dgl/pyproject.toml +++ b/python/cugraph-dgl/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "cugraph==24.10.*,>=0.0.0a0", "numba>=0.57", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pylibcugraphops==24.10.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. @@ -40,7 +40,7 @@ test = [ "pytest-xdist", "scipy", "tensordict>=0.1.2", - "torch>=2.0,<2.2.0a0", + "torch>=2.3,<2.4.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.urls] diff --git a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml index bd1ca33af70..d9afd52b9b7 100644 --- a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml +++ b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml @@ -19,7 +19,7 @@ dependencies: - pytest-cov - pytest-xdist - pytorch-cuda==11.8 -- pytorch>=2.0 +- pytorch>=2.3,<2.4.0a0 - scipy - tensordict>=0.1.2 name: cugraph_pyg_dev_cuda-118 diff --git a/python/cugraph-pyg/pyproject.toml b/python/cugraph-pyg/pyproject.toml index d206d6001cc..244391444f7 100644 --- a/python/cugraph-pyg/pyproject.toml +++ b/python/cugraph-pyg/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ dependencies = [ "cugraph==24.10.*,>=0.0.0a0", "numba>=0.57", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pylibcugraphops==24.10.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. @@ -49,7 +49,7 @@ test = [ "pytest-xdist", "scipy", "tensordict>=0.1.2", - "torch>=2.0,<2.2.0a0", + "torch>=2.3,<2.4.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [tool.setuptools] diff --git a/python/cugraph-service/server/pyproject.toml b/python/cugraph-service/server/pyproject.toml index b9789c201d2..6d34c82d90e 100644 --- a/python/cugraph-service/server/pyproject.toml +++ b/python/cugraph-service/server/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "dask-cuda==24.10.*,>=0.0.0a0", "dask-cudf==24.10.*,>=0.0.0a0", "numba>=0.57", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "rapids-dask-dependency==24.10.*,>=0.0.0a0", "rmm==24.10.*,>=0.0.0a0", "thriftpy2!=0.5.0,!=0.5.1", @@ -47,7 +47,7 @@ cugraph-service-server = "cugraph_service_server.__main__:main" [project.optional-dependencies] test = [ "networkx>=2.5.1", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pandas", "pytest", "pytest-benchmark", diff --git a/python/cugraph/cugraph/tests/data_store/test_property_graph.py b/python/cugraph/cugraph/tests/data_store/test_property_graph.py index da5608e0193..50f08cdf3d0 100644 --- a/python/cugraph/cugraph/tests/data_store/test_property_graph.py +++ b/python/cugraph/cugraph/tests/data_store/test_property_graph.py @@ -2576,9 +2576,10 @@ def bench_extract_subgraph_for_rmat(gpubenchmark, rmat_PropertyGraph): scn = PropertyGraph.src_col_name dcn = PropertyGraph.dst_col_name - verts = [] - for i in range(0, 10000, 10): - verts.append(generated_df["src"].iloc[i]) + # Build a query string to extract a graph with only specific edges based on + # the integer vertex IDs. Other edge and/or vertex properties can be + # included in the query as well. + verts = [int(generated_df["src"].iloc[i]) for i in range(0, 10000, 10)] selected_edges = pG.select_edges(f"{scn}.isin({verts}) | {dcn}.isin({verts})") gpubenchmark( @@ -2618,9 +2619,10 @@ def bench_extract_subgraph_for_rmat_detect_duplicate_edges( scn = PropertyGraph.src_col_name dcn = PropertyGraph.dst_col_name - verts = [] - for i in range(0, 10000, 10): - verts.append(generated_df["src"].iloc[i]) + # Build a query string to extract a graph with only specific edges based on + # the integer vertex IDs. Other edge and/or vertex properties can be + # included in the query as well. + verts = [int(generated_df["src"].iloc[i]) for i in range(0, 10000, 10)] selected_edges = pG.select_edges(f"{scn}.isin({verts}) | {dcn}.isin({verts})") diff --git a/python/cugraph/pyproject.toml b/python/cugraph/pyproject.toml index 31721c8a568..1b672cb4807 100644 --- a/python/cugraph/pyproject.toml +++ b/python/cugraph/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "dask-cudf==24.10.*,>=0.0.0a0", "fsspec[http]>=0.6.0", "numba>=0.57", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pylibcugraph==24.10.*,>=0.0.0a0", "raft-dask==24.10.*,>=0.0.0a0", "rapids-dask-dependency==24.10.*,>=0.0.0a0", @@ -47,7 +47,7 @@ classifiers = [ [project.optional-dependencies] test = [ "networkx>=2.5.1", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pandas", "pylibwholegraph==24.10.*,>=0.0.0a0", "pytest", diff --git a/python/nx-cugraph/pyproject.toml b/python/nx-cugraph/pyproject.toml index 98de089a92c..cf4b98afe77 100644 --- a/python/nx-cugraph/pyproject.toml +++ b/python/nx-cugraph/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ dependencies = [ "cupy-cuda11x>=12.0.0", "networkx>=3.0", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pylibcugraph==24.10.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. diff --git a/python/pylibcugraph/pyproject.toml b/python/pylibcugraph/pyproject.toml index 92c417f0372..cf935d37b2c 100644 --- a/python/pylibcugraph/pyproject.toml +++ b/python/pylibcugraph/pyproject.toml @@ -41,7 +41,7 @@ classifiers = [ [project.optional-dependencies] test = [ "cudf==24.10.*,>=0.0.0a0", - "numpy>=1.23,<2.0a0", + "numpy>=1.23,<3.0a0", "pandas", "pytest", "pytest-benchmark", From 4dfd3b393cc5d2c2da479186efed97aab1bb9de0 Mon Sep 17 00:00:00 2001 From: Alex Barghi <105237337+alexbarghi-nv@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:52:04 -0400 Subject: [PATCH 15/37] Support Negative Sampling in pylibcugraph and cuGraph-PyG (#4660) Adds support for negative sampling in `pylibcugraph` and `cugraph-pyg`. Closes rapidsai/cugraph-gnn#39 Merge after #4641 Authors: - Alex Barghi (https://github.com/alexbarghi-nv) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4660 --- cpp/src/c_api/graph_generators.cpp | 21 +- .../dataloading/neighbor_sampler.py | 6 +- .../cugraph_dgl/dataloading/sampler.py | 15 +- python/cugraph-dgl/cugraph_dgl/graph.py | 3 - python/cugraph-pyg/cugraph_pyg/__init__.py | 7 +- .../cugraph_pyg/data/graph_store.py | 17 +- .../cugraph_pyg/examples/gcn_dist_mnmg.py | 24 +- .../cugraph_pyg/examples/gcn_dist_sg.py | 24 +- .../cugraph_pyg/examples/gcn_dist_snmg.py | 23 +- .../examples/rgcn_link_class_mnmg.py | 418 ++++++++++ .../examples/rgcn_link_class_sg.py | 219 +++++ .../examples/rgcn_link_class_snmg.py | 320 ++++++++ .../cugraph_pyg/loader/__init__.py | 3 + .../cugraph_pyg/loader/link_loader.py | 205 +++++ .../loader/link_neighbor_loader.py | 243 ++++++ .../cugraph_pyg/loader/neighbor_loader.py | 28 +- .../cugraph_pyg/loader/node_loader.py | 12 +- .../cugraph_pyg/sampler/sampler.py | 184 ++++- .../cugraph_pyg/sampler/sampler_utils.py | 102 ++- .../tests/loader/test_neighbor_loader.py | 108 +++ .../tests/loader/test_neighbor_loader_mg.py | 185 ++++- .../cugraph/gnn/data_loading/__init__.py | 5 +- .../gnn/data_loading/bulk_sampler_io.py | 6 +- .../gnn/data_loading/dist_io/__init__.py | 16 + .../gnn/data_loading/dist_io/reader.py | 144 ++++ .../gnn/data_loading/dist_io/writer.py | 321 ++++++++ .../cugraph/gnn/data_loading/dist_sampler.py | 766 +++++++++--------- .../tests/sampling/test_dist_sampler.py | 60 +- .../tests/sampling/test_dist_sampler_mg.py | 80 ++ .../pylibcugraph/pylibcugraph/CMakeLists.txt | 1 + python/pylibcugraph/pylibcugraph/__init__.py | 2 + .../_cugraph_c/sampling_algorithms.pxd | 2 +- .../internal_types/CMakeLists.txt | 3 +- .../pylibcugraph/internal_types/coo.pxd | 28 + .../pylibcugraph/internal_types/coo.pyx | 96 +++ .../pylibcugraph/negative_sampling.pyx | 184 +++++ 36 files changed, 3415 insertions(+), 466 deletions(-) create mode 100644 python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_mnmg.py create mode 100644 python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_sg.py create mode 100644 python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_snmg.py create mode 100644 python/cugraph-pyg/cugraph_pyg/loader/link_loader.py create mode 100644 python/cugraph-pyg/cugraph_pyg/loader/link_neighbor_loader.py create mode 100644 python/cugraph/cugraph/gnn/data_loading/dist_io/__init__.py create mode 100644 python/cugraph/cugraph/gnn/data_loading/dist_io/reader.py create mode 100644 python/cugraph/cugraph/gnn/data_loading/dist_io/writer.py create mode 100644 python/pylibcugraph/pylibcugraph/internal_types/coo.pxd create mode 100644 python/pylibcugraph/pylibcugraph/internal_types/coo.pyx create mode 100644 python/pylibcugraph/pylibcugraph/negative_sampling.pyx diff --git a/cpp/src/c_api/graph_generators.cpp b/cpp/src/c_api/graph_generators.cpp index 7601f1508f9..a58a4d5db35 100644 --- a/cpp/src/c_api/graph_generators.cpp +++ b/cpp/src/c_api/graph_generators.cpp @@ -124,32 +124,41 @@ cugraph_error_code_t cugraph_generate_rmat_edgelists( extern "C" cugraph_type_erased_device_array_view_t* cugraph_coo_get_sources(cugraph_coo_t* coo) { auto internal_pointer = reinterpret_cast(coo); - return reinterpret_cast(internal_pointer->src_->view()); + return (internal_pointer->src_) ? reinterpret_cast( + internal_pointer->src_->view()) + : nullptr; } extern "C" cugraph_type_erased_device_array_view_t* cugraph_coo_get_destinations(cugraph_coo_t* coo) { auto internal_pointer = reinterpret_cast(coo); - return reinterpret_cast(internal_pointer->dst_->view()); + return (internal_pointer->dst_) ? reinterpret_cast( + internal_pointer->dst_->view()) + : nullptr; } extern "C" cugraph_type_erased_device_array_view_t* cugraph_coo_get_edge_weights(cugraph_coo_t* coo) { auto internal_pointer = reinterpret_cast(coo); - return reinterpret_cast(internal_pointer->wgt_->view()); + return (internal_pointer->wgt_) ? reinterpret_cast( + internal_pointer->wgt_->view()) + : nullptr; } extern "C" cugraph_type_erased_device_array_view_t* cugraph_coo_get_edge_id(cugraph_coo_t* coo) { auto internal_pointer = reinterpret_cast(coo); - return reinterpret_cast(internal_pointer->id_->view()); + return (internal_pointer->id_) ? reinterpret_cast( + internal_pointer->id_->view()) + : nullptr; } extern "C" cugraph_type_erased_device_array_view_t* cugraph_coo_get_edge_type(cugraph_coo_t* coo) { auto internal_pointer = reinterpret_cast(coo); - return reinterpret_cast( - internal_pointer->type_->view()); + return (internal_pointer->type_) ? reinterpret_cast( + internal_pointer->type_->view()) + : nullptr; } extern "C" size_t cugraph_coo_list_size(const cugraph_coo_list_t* coo_list) diff --git a/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py b/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py index 4ec513cbf9b..ecc51006995 100644 --- a/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py +++ b/python/cugraph-dgl/cugraph_dgl/dataloading/neighbor_sampler.py @@ -197,10 +197,8 @@ def sample( if g.is_homogeneous: indices = torch.concat(list(indices)) - ds.sample_from_nodes(indices.long(), batch_size=batch_size) - return HomogeneousSampleReader( - ds.get_reader(), self.output_format, self.edge_dir - ) + reader = ds.sample_from_nodes(indices.long(), batch_size=batch_size) + return HomogeneousSampleReader(reader, self.output_format, self.edge_dir) raise ValueError( "Sampling heterogeneous graphs is currently" diff --git a/python/cugraph-dgl/cugraph_dgl/dataloading/sampler.py b/python/cugraph-dgl/cugraph_dgl/dataloading/sampler.py index 731ec1b8d6f..7ea608e7e53 100644 --- a/python/cugraph-dgl/cugraph_dgl/dataloading/sampler.py +++ b/python/cugraph-dgl/cugraph_dgl/dataloading/sampler.py @@ -20,7 +20,6 @@ create_homogeneous_sampled_graphs_from_tensors_csc, ) -from cugraph.gnn import DistSampleReader from cugraph.utilities.utils import import_optional @@ -33,14 +32,18 @@ class SampleReader: Iterator that processes results from the cuGraph distributed sampler. """ - def __init__(self, base_reader: DistSampleReader, output_format: str = "dgl.Block"): + def __init__( + self, + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]], + output_format: str = "dgl.Block", + ): """ Constructs a new SampleReader. Parameters ---------- - base_reader: DistSampleReader - The reader responsible for loading saved samples produced by + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] + The iterator responsible for loading saved samples produced by the cuGraph distributed sampler. """ self.__output_format = output_format @@ -83,7 +86,7 @@ class HomogeneousSampleReader(SampleReader): def __init__( self, - base_reader: DistSampleReader, + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]], output_format: str = "dgl.Block", edge_dir="in", ): @@ -92,7 +95,7 @@ def __init__( Parameters ---------- - base_reader: DistSampleReader + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] The reader responsible for loading saved samples produced by the cuGraph distributed sampler. output_format: str diff --git a/python/cugraph-dgl/cugraph_dgl/graph.py b/python/cugraph-dgl/cugraph_dgl/graph.py index 138e645838a..88b93656fa8 100644 --- a/python/cugraph-dgl/cugraph_dgl/graph.py +++ b/python/cugraph-dgl/cugraph_dgl/graph.py @@ -620,9 +620,6 @@ def _get_n_emb( ) try: - print( - u, - ) return self.__ndata_storage[ntype, emb_name].fetch( _cast_to_torch_tensor(u), "cuda" ) diff --git a/python/cugraph-pyg/cugraph_pyg/__init__.py b/python/cugraph-pyg/cugraph_pyg/__init__.py index 719751c966a..e566e6e9fdd 100644 --- a/python/cugraph-pyg/cugraph_pyg/__init__.py +++ b/python/cugraph-pyg/cugraph_pyg/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2023, NVIDIA CORPORATION. +# Copyright (c) 2019-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,3 +12,8 @@ # limitations under the License. from cugraph_pyg._version import __git_commit__, __version__ + +import cugraph_pyg.data +import cugraph_pyg.loader +import cugraph_pyg.sampler +import cugraph_pyg.nn diff --git a/python/cugraph-pyg/cugraph_pyg/data/graph_store.py b/python/cugraph-pyg/cugraph_pyg/data/graph_store.py index 93ea5700c50..c47dda5eaa5 100644 --- a/python/cugraph-pyg/cugraph_pyg/data/graph_store.py +++ b/python/cugraph-pyg/cugraph_pyg/data/graph_store.py @@ -205,13 +205,18 @@ def _num_vertices(self) -> Dict[str, int]: else edge_attr.size[1] ) else: - if edge_attr.edge_type[0] not in num_vertices: + if edge_attr.edge_type[0] != edge_attr.edge_type[2]: + if edge_attr.edge_type[0] not in num_vertices: + num_vertices[edge_attr.edge_type[0]] = int( + self.__edge_indices[edge_attr.edge_type][0].max() + 1 + ) + if edge_attr.edge_type[2] not in num_vertices: + num_vertices[edge_attr.edge_type[1]] = int( + self.__edge_indices[edge_attr.edge_type][1].max() + 1 + ) + elif edge_attr.edge_type[0] not in num_vertices: num_vertices[edge_attr.edge_type[0]] = int( - self.__edge_indices[edge_attr.edge_type][0].max() + 1 - ) - if edge_attr.edge_type[2] not in num_vertices: - num_vertices[edge_attr.edge_type[1]] = int( - self.__edge_indices[edge_attr.edge_type][1].max() + 1 + self.__edge_indices[edge_attr.edge_type].max() + 1 ) if self.is_multi_gpu: diff --git a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_mnmg.py b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_mnmg.py index 7002d7ebded..127ca809d91 100644 --- a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_mnmg.py +++ b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_mnmg.py @@ -185,6 +185,8 @@ def run_train( wall_clock_start, tempdir=None, num_layers=3, + in_memory=False, + seeds_per_call=-1, ): optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=0.0005) @@ -196,20 +198,23 @@ def run_train( from cugraph_pyg.loader import NeighborLoader ix_train = split_idx["train"].cuda() - train_path = os.path.join(tempdir, f"train_{global_rank}") - os.mkdir(train_path) + train_path = None if in_memory else os.path.join(tempdir, f"train_{global_rank}") + if train_path: + os.mkdir(train_path) train_loader = NeighborLoader( data, input_nodes=ix_train, directory=train_path, shuffle=True, drop_last=True, + local_seeds_per_call=seeds_per_call if seeds_per_call > 0 else None, **kwargs, ) ix_test = split_idx["test"].cuda() - test_path = os.path.join(tempdir, f"test_{global_rank}") - os.mkdir(test_path) + test_path = None if in_memory else os.path.join(tempdir, f"test_{global_rank}") + if test_path: + os.mkdir(test_path) test_loader = NeighborLoader( data, input_nodes=ix_test, @@ -221,14 +226,16 @@ def run_train( ) ix_valid = split_idx["valid"].cuda() - valid_path = os.path.join(tempdir, f"valid_{global_rank}") - os.mkdir(valid_path) + valid_path = None if in_memory else os.path.join(tempdir, f"valid_{global_rank}") + if valid_path: + os.mkdir(valid_path) valid_loader = NeighborLoader( data, input_nodes=ix_valid, directory=valid_path, shuffle=True, drop_last=True, + local_seeds_per_call=seeds_per_call if seeds_per_call > 0 else None, **kwargs, ) @@ -347,6 +354,9 @@ def parse_args(): parser.add_argument("--skip_partition", action="store_true") parser.add_argument("--wg_mem_type", type=str, default="distributed") + parser.add_argument("--in_memory", action="store_true", default=False) + parser.add_argument("--seeds_per_call", type=int, default=-1) + return parser.parse_args() @@ -429,6 +439,8 @@ def parse_args(): wall_clock_start, tempdir, args.num_layers, + args.in_memory, + args.seeds_per_call, ) else: warnings.warn("This script should be run with 'torchrun`. Exiting.") diff --git a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py index 09d874bd87d..0f9c39bf04d 100644 --- a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py +++ b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_sg.py @@ -91,10 +91,20 @@ def test(loader: NeighborLoader, val_steps: Optional[int] = None): def create_loader( - data, num_neighbors, input_nodes, replace, batch_size, samples_dir, stage_name + data, + num_neighbors, + input_nodes, + replace, + batch_size, + samples_dir, + stage_name, + local_seeds_per_call, ): - directory = os.path.join(samples_dir, stage_name) - os.mkdir(directory) + if samples_dir is not None: + directory = os.path.join(samples_dir, stage_name) + os.mkdir(directory) + else: + directory = None return NeighborLoader( data, num_neighbors=num_neighbors, @@ -102,6 +112,7 @@ def create_loader( replace=replace, batch_size=batch_size, directory=directory, + local_seeds_per_call=local_seeds_per_call, ) @@ -147,6 +158,8 @@ def parse_args(): parser.add_argument("--tempdir_root", type=str, default=None) parser.add_argument("--dataset_root", type=str, default="dataset") parser.add_argument("--dataset", type=str, default="ogbn-products") + parser.add_argument("--in_memory", action="store_true", default=False) + parser.add_argument("--seeds_per_call", type=int, default=-1) return parser.parse_args() @@ -170,7 +183,10 @@ def parse_args(): "num_neighbors": [args.fan_out] * args.num_layers, "replace": False, "batch_size": args.batch_size, - "samples_dir": samples_dir, + "samples_dir": None if args.in_memory else samples_dir, + "local_seeds_per_call": None + if args.seeds_per_call <= 0 + else args.seeds_per_call, } train_loader = create_loader( diff --git a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_snmg.py b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_snmg.py index b1bb0240e71..73efbc92a24 100644 --- a/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_snmg.py +++ b/python/cugraph-pyg/cugraph_pyg/examples/gcn_dist_snmg.py @@ -86,6 +86,8 @@ def run_train( wall_clock_start, tempdir=None, num_layers=3, + in_memory=False, + seeds_per_call=-1, ): init_pytorch_worker( @@ -119,20 +121,23 @@ def run_train( dist.barrier() ix_train = torch.tensor_split(split_idx["train"], world_size)[rank].cuda() - train_path = os.path.join(tempdir, f"train_{rank}") - os.mkdir(train_path) + train_path = None if in_memory else os.path.join(tempdir, f"train_{rank}") + if train_path: + os.mkdir(train_path) train_loader = NeighborLoader( (feature_store, graph_store), input_nodes=ix_train, directory=train_path, shuffle=True, drop_last=True, + local_seeds_per_call=seeds_per_call if seeds_per_call > 0 else None, **kwargs, ) ix_test = torch.tensor_split(split_idx["test"], world_size)[rank].cuda() - test_path = os.path.join(tempdir, f"test_{rank}") - os.mkdir(test_path) + test_path = None if in_memory else os.path.join(tempdir, f"test_{rank}") + if test_path: + os.mkdir(test_path) test_loader = NeighborLoader( (feature_store, graph_store), input_nodes=ix_test, @@ -144,14 +149,16 @@ def run_train( ) ix_valid = torch.tensor_split(split_idx["valid"], world_size)[rank].cuda() - valid_path = os.path.join(tempdir, f"valid_{rank}") - os.mkdir(valid_path) + valid_path = None if in_memory else os.path.join(tempdir, f"valid_{rank}") + if valid_path: + os.mkdir(valid_path) valid_loader = NeighborLoader( (feature_store, graph_store), input_nodes=ix_valid, directory=valid_path, shuffle=True, drop_last=True, + local_seeds_per_call=seeds_per_call if seeds_per_call > 0 else None, **kwargs, ) @@ -269,6 +276,8 @@ def run_train( parser.add_argument("--tempdir_root", type=str, default=None) parser.add_argument("--dataset_root", type=str, default="dataset") parser.add_argument("--dataset", type=str, default="ogbn-products") + parser.add_argument("--in_memory", action="store_true", default=False) + parser.add_argument("--seeds_per_call", type=int, default=-1) parser.add_argument( "--n_devices", @@ -322,6 +331,8 @@ def run_train( wall_clock_start, tempdir, args.num_layers, + args.in_memory, + args.seeds_per_call, ), nprocs=world_size, join=True, diff --git a/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_mnmg.py b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_mnmg.py new file mode 100644 index 00000000000..5c75e01e6f5 --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_mnmg.py @@ -0,0 +1,418 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This example illustrates link classification using the ogbl-wikikg2 dataset. + +import os +import json +import argparse +import warnings + +import torch + +import torch.nn.functional as F +from torch.nn import Parameter +from torch_geometric.nn import FastRGCNConv, GAE +from torch.nn.parallel import DistributedDataParallel + +from ogb.linkproppred import PygLinkPropPredDataset + +import cugraph_pyg + +from cugraph.gnn import ( + cugraph_comms_init, + cugraph_comms_create_unique_id, + cugraph_comms_shutdown, +) + +from pylibwholegraph.torch.initialize import ( + init as wm_init, + finalize as wm_finalize, +) + + +# Enable cudf spilling to save gpu memory +from cugraph.testing.mg_utils import enable_spilling + +# Ensures that a CUDA context is not created on import of rapids. +# Allows pytorch to create the context instead +os.environ["RAPIDS_NO_INITIALIZE"] = "1" + + +def init_pytorch_worker(global_rank, local_rank, world_size, uid): + import rmm + + rmm.reinitialize(devices=[local_rank], pool_allocator=True, managed_memory=True) + + import cupy + from rmm.allocators.cupy import rmm_cupy_allocator + + cupy.cuda.set_allocator(rmm_cupy_allocator) + + cugraph_comms_init( + global_rank, + world_size, + uid, + local_rank, + ) + + wm_init(global_rank, world_size, local_rank, torch.cuda.device_count()) + + enable_spilling() + + +class RGCNEncoder(torch.nn.Module): + def __init__(self, num_nodes, hidden_channels, num_relations, num_bases=30): + super().__init__() + self.node_emb = Parameter(torch.empty(num_nodes, hidden_channels)) + self.conv1 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.conv2 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.reset_parameters() + + def reset_parameters(self): + torch.nn.init.xavier_uniform_(self.node_emb) + self.conv1.reset_parameters() + self.conv2.reset_parameters() + + def forward(self, edge_index, edge_type): + x = self.node_emb + x = self.conv1(x, edge_index, edge_type).relu_() + x = F.dropout(x, p=0.2, training=self.training) + x = self.conv2(x, edge_index, edge_type) + return x + + +def train(epoch, model, optimizer, train_loader, edge_feature_store, num_steps=None): + model.train() + optimizer.zero_grad() + + for i, batch in enumerate(train_loader): + r = edge_feature_store[("n", "e", "n"), "rel"][batch.e_id].flatten().cuda() + z = model.encode(batch.edge_index, r) + + loss = model.recon_loss(z, batch.edge_index) + loss.backward() + optimizer.step() + + if i % 10 == 0: + print( + f"Epoch: {epoch:02d}, Iteration: {i:02d}, Loss: {loss:.4f}", flush=True + ) + if num_steps and i == num_steps: + break + + +def test(stage, epoch, model, loader, num_steps=None): + # TODO support ROC-AUC metric + # Predict probabilities of future edges + model.eval() + + rr = 0.0 + for i, (h, h_neg, t, t_neg, r) in enumerate(loader): + if num_steps and i >= num_steps: + break + + ei = torch.concatenate( + [ + torch.stack([h, t]).cuda(), + torch.stack([h_neg.flatten(), t_neg.flatten()]).cuda(), + ], + dim=-1, + ) + + r = torch.concatenate([r, torch.repeat_interleave(r, h_neg.shape[-1])]).cuda() + + z = model.encode(ei, r) + q = model.decode(z, ei) + + _, ix = torch.sort(q, descending=True) + rr += 1.0 / (1.0 + ix[0]) + + print(f"epoch {epoch:02d} {stage} mrr:", rr / i, flush=True) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--hidden_channels", type=int, default=128) + parser.add_argument("--num_layers", type=int, default=1) + parser.add_argument("--lr", type=float, default=0.001) + parser.add_argument("--epochs", type=int, default=4) + parser.add_argument("--batch_size", type=int, default=16384) + parser.add_argument("--num_neg", type=int, default=500) + parser.add_argument("--num_pos", type=int, default=-1) + parser.add_argument("--fan_out", type=int, default=10) + parser.add_argument("--dataset", type=str, default="ogbl-wikikg2") + parser.add_argument("--dataset_root", type=str, default="dataset") + parser.add_argument("--seeds_per_call", type=int, default=-1) + parser.add_argument("--n_devices", type=int, default=-1) + parser.add_argument("--skip_partition", action="store_true") + + return parser.parse_args() + + +def run_train(rank, world_size, model, data, edge_feature_store, meta, splits, args): + model = model.to(rank) + model = GAE(DistributedDataParallel(model, device_ids=[rank])) + optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) + + eli = torch.stack([splits["train"]["head"], splits["train"]["tail"]]) + + train_loader = cugraph_pyg.loader.LinkNeighborLoader( + data, + [args.fan_out] * args.num_layers, + edge_label_index=eli, + local_seeds_per_call=args.seeds_per_call if args.seeds_per_call > 0 else None, + batch_size=args.batch_size, + shuffle=True, + drop_last=True, + ) + + def get_eval_loader(stage: str): + head = splits[stage]["head"] + tail = splits[stage]["tail"] + + head_neg = splits[stage]["head_neg"][:, : args.num_neg] + tail_neg = splits[stage]["tail_neg"][:, : args.num_neg] + + rel = splits[stage]["relation"] + + return torch.utils.data.DataLoader( + torch.utils.data.TensorDataset( + head.pin_memory(), + head_neg.pin_memory(), + tail.pin_memory(), + tail_neg.pin_memory(), + rel.pin_memory(), + ), + batch_size=1, + shuffle=False, + drop_last=True, + ) + + test_loader = get_eval_loader("test") + valid_loader = get_eval_loader("valid") + + num_train_steps = (args.num_pos // args.batch_size) if args.num_pos > 0 else 100 + + for epoch in range(1, 1 + args.epochs): + train( + epoch, + model, + optimizer, + train_loader, + edge_feature_store, + num_steps=num_train_steps, + ) + test("validation", epoch, model, valid_loader, num_steps=1024) + + test("test", epoch, model, test_loader, num_steps=1024) + + wm_finalize() + cugraph_comms_shutdown() + + +def partition_data( + data, splits, meta, edge_path, rel_path, pos_path, neg_path, meta_path +): + # Split and save edge index + os.makedirs( + edge_path, + exist_ok=True, + ) + for (r, e) in enumerate(torch.tensor_split(data.edge_index, world_size, dim=1)): + rank_path = os.path.join(edge_path, f"rank={r}.pt") + torch.save( + e.clone(), + rank_path, + ) + + # Split and save edge reltypes + os.makedirs( + rel_path, + exist_ok=True, + ) + for (r, f) in enumerate(torch.tensor_split(data.edge_reltype, world_size)): + rank_path = os.path.join(rel_path, f"rank={r}.pt") + torch.save( + f.clone(), + rank_path, + ) + + # Split and save positive edges + os.makedirs( + pos_path, + exist_ok=True, + ) + for stage in ["train", "test", "valid"]: + for (r, n) in enumerate( + torch.tensor_split( + torch.stack([splits[stage]["head"], splits[stage]["tail"]]), + world_size, + dim=-1, + ) + ): + rank_path = os.path.join(pos_path, f"rank={r}_{stage}.pt") + torch.save( + n.clone(), + rank_path, + ) + + # Split and save negative edges + os.makedirs( + neg_path, + exist_ok=True, + ) + for stage in ["test", "valid"]: + for (r, n) in enumerate( + torch.tensor_split( + torch.stack([splits[stage]["head_neg"], splits[stage]["tail_neg"]]), + world_size, + dim=1, + ) + ): + rank_path = os.path.join(neg_path, f"rank={r}_{stage}.pt") + torch.save(n.clone(), rank_path) + for (r, n) in enumerate( + torch.tensor_split(splits[stage]["relation"], world_size, dim=-1) + ): + print(n) + rank_path = os.path.join(neg_path, f"rank={r}_{stage}_relation.pt") + torch.save(n.clone(), rank_path) + + with open(meta_path, "w") as f: + json.dump(meta, f) + + +def load_partitioned_data(rank, edge_path, rel_path, pos_path, neg_path, meta_path): + from cugraph_pyg.data import GraphStore, WholeFeatureStore, TensorDictFeatureStore + + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() + edge_feature_store = WholeFeatureStore() + + # Load edge index + graph_store[("n", "e", "n"), "coo"] = torch.load( + os.path.join(edge_path, f"rank={rank}.pt") + ) + + # Load edge rel type + edge_feature_store[("n", "e", "n"), "rel"] = torch.load( + os.path.join(rel_path, f"rank={rank}.pt") + ) + + splits = {} + + # Load positive edges + for stage in ["train", "test", "valid"]: + head, tail = torch.load(os.path.join(pos_path, f"rank={rank}_{stage}.pt")) + splits[stage] = { + "head": head, + "tail": tail, + } + + # Load negative edges + for stage in ["test", "valid"]: + head_neg, tail_neg = torch.load( + os.path.join(neg_path, f"rank={rank}_{stage}.pt") + ) + relation = torch.load( + os.path.join(neg_path, f"rank={rank}_{stage}_relation.pt") + ) + splits[stage]["head_neg"] = head_neg + splits[stage]["tail_neg"] = tail_neg + splits[stage]["relation"] = relation + + with open(meta_path, "r") as f: + meta = json.load(f) + + return (feature_store, graph_store), edge_feature_store, splits, meta + + +if __name__ == "__main__": + args = parse_args() + + if "LOCAL_RANK" in os.environ: + torch.distributed.init_process_group("nccl") + world_size = torch.distributed.get_world_size() + global_rank = torch.distributed.get_rank() + local_rank = int(os.environ["LOCAL_RANK"]) + device = torch.device(local_rank) + + # Create the uid needed for cuGraph comms + if global_rank == 0: + cugraph_id = [cugraph_comms_create_unique_id()] + else: + cugraph_id = [None] + torch.distributed.broadcast_object_list(cugraph_id, src=0, device=device) + cugraph_id = cugraph_id[0] + + init_pytorch_worker(global_rank, local_rank, world_size, cugraph_id) + + # Split the data + edge_path = os.path.join(args.dataset_root, args.dataset + "_eix_part") + rel_path = os.path.join(args.dataset_root, args.dataset + "_rel_part") + pos_path = os.path.join(args.dataset_root, args.dataset + "_e_pos_part") + neg_path = os.path.join(args.dataset_root, args.dataset + "_e_neg_part") + meta_path = os.path.join(args.dataset_root, args.dataset + "_meta.json") + + if not args.skip_partition and global_rank == 0: + data = PygLinkPropPredDataset(args.dataset, root=args.dataset_root) + dataset = data[0] + + splits = data.get_edge_split() + + meta = {} + meta["num_nodes"] = int(dataset.num_nodes) + meta["num_rels"] = int(dataset.edge_reltype.max()) + 1 + + partition_data( + dataset, + splits, + meta, + edge_path=edge_path, + rel_path=rel_path, + pos_path=pos_path, + neg_path=neg_path, + meta_path=meta_path, + ) + del data + del dataset + del splits + torch.distributed.barrier() + + # Load partitions + data, edge_feature_store, splits, meta = load_partitioned_data( + rank=global_rank, + edge_path=edge_path, + rel_path=rel_path, + pos_path=pos_path, + neg_path=neg_path, + meta_path=meta_path, + ) + torch.distributed.barrier() + + model = RGCNEncoder( + meta["num_nodes"], + hidden_channels=args.hidden_channels, + num_relations=meta["num_rels"], + ) + + run_train( + global_rank, world_size, model, data, edge_feature_store, meta, splits, args + ) + else: + warnings.warn("This script should be run with 'torchrun`. Exiting.") diff --git a/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_sg.py b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_sg.py new file mode 100644 index 00000000000..67d7eecc7c2 --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_sg.py @@ -0,0 +1,219 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This example illustrates link classification using the ogbl-wikikg2 dataset. + +import argparse + +from typing import Tuple, Dict, Any + +import torch +import cupy + +import rmm +from rmm.allocators.cupy import rmm_cupy_allocator +from rmm.allocators.torch import rmm_torch_allocator + +# Must change allocators immediately upon import +# or else other imports will cause memory to be +# allocated and prevent changing the allocator +rmm.reinitialize(devices=[0], pool_allocator=True, managed_memory=True) +cupy.cuda.set_allocator(rmm_cupy_allocator) +torch.cuda.memory.change_current_allocator(rmm_torch_allocator) + +import torch.nn.functional as F # noqa: E402 +from torch.nn import Parameter # noqa: E402 +from torch_geometric.nn import FastRGCNConv, GAE # noqa: E402 +import torch_geometric # noqa: E402 +import cugraph_pyg # noqa: E402 + +# Enable cudf spilling to save gpu memory +from cugraph.testing.mg_utils import enable_spilling # noqa: E402 + +enable_spilling() + + +class RGCNEncoder(torch.nn.Module): + def __init__(self, num_nodes, hidden_channels, num_relations, num_bases=30): + super().__init__() + self.node_emb = Parameter(torch.empty(num_nodes, hidden_channels)) + self.conv1 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.conv2 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.reset_parameters() + + def reset_parameters(self): + torch.nn.init.xavier_uniform_(self.node_emb) + self.conv1.reset_parameters() + self.conv2.reset_parameters() + + def forward(self, edge_index, edge_type): + x = self.node_emb + x = self.conv1(x, edge_index, edge_type).relu_() + x = F.dropout(x, p=0.2, training=self.training) + x = self.conv2(x, edge_index, edge_type) + return x + + +def load_data( + dataset_str, dataset_root: str +) -> Tuple[ + Tuple["torch_geometric.data.FeatureStore", "torch_geometric.data.GraphStore"], + "torch_geometric.data.FeatureStore", + Dict[str, Dict[str, "torch.Tensor"]], + Dict[str, Any], +]: + from ogb.linkproppred import PygLinkPropPredDataset + + data = PygLinkPropPredDataset(dataset_str, root=dataset_root) + dataset = data[0] + + splits = data.get_edge_split() + + from cugraph_pyg.data import GraphStore, TensorDictFeatureStore + + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() + edge_feature_store = TensorDictFeatureStore() + meta = {} + + graph_store[("n", "e", "n"), "coo"] = dataset.edge_index + edge_feature_store[("n", "e", "n"), "rel"] = dataset.edge_reltype.pin_memory() + meta["num_nodes"] = dataset.num_nodes + meta["num_rels"] = dataset.edge_reltype.max() + 1 + + return (feature_store, graph_store), edge_feature_store, splits, meta + + +def train(epoch, model, optimizer, train_loader, edge_feature_store): + model.train() + optimizer.zero_grad() + + for i, batch in enumerate(train_loader): + r = edge_feature_store[("n", "e", "n"), "rel"][batch.e_id].flatten().cuda() + z = model.encode(batch.edge_index, r) + + loss = model.recon_loss(z, batch.edge_index) + loss.backward() + optimizer.step() + + if i % 10 == 0: + print(f"Epoch: {epoch:02d}, Iteration: {i:02d}, Loss: {loss:.4f}") + if i == 100: + break + + +def test(stage, epoch, model, loader, num_steps=None): + # TODO support ROC-AUC metric + # Predict probabilities of future edges + model.eval() + + rr = 0.0 + for i, (h, h_neg, t, t_neg, r) in enumerate(loader): + if num_steps and i >= num_steps: + break + + ei = torch.concatenate( + [ + torch.stack([h, t]).cuda(), + torch.stack([h_neg.flatten(), t_neg.flatten()]).cuda(), + ], + dim=-1, + ) + + r = torch.concatenate([r, torch.repeat_interleave(r, h_neg.shape[-1])]).cuda() + + z = model.encode(ei, r) + q = model.decode(z, ei) + + _, ix = torch.sort(q, descending=True) + rr += 1.0 / (1.0 + ix[0]) + + print(f"epoch {epoch:02d} {stage} mrr:", rr / i) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--hidden_channels", type=int, default=128) + parser.add_argument("--num_layers", type=int, default=1) + parser.add_argument("--lr", type=float, default=0.001) + parser.add_argument("--epochs", type=int, default=4) + parser.add_argument("--batch_size", type=int, default=16384) + parser.add_argument("--num_neg", type=int, default=500) + parser.add_argument("--fan_out", type=int, default=10) + parser.add_argument("--dataset", type=str, default="ogbl-wikikg2") + parser.add_argument("--dataset_root", type=str, default="dataset") + parser.add_argument("--seeds_per_call", type=int, default=-1) + + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + + data, edge_feature_store, splits, meta = load_data(args.dataset, args.dataset_root) + + model = GAE( + RGCNEncoder( + meta["num_nodes"], + hidden_channels=args.hidden_channels, + num_relations=meta["num_rels"], + ) + ).cuda() + optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) + + train_loader = cugraph_pyg.loader.LinkNeighborLoader( + data, + [args.fan_out] * args.num_layers, + edge_label_index=torch.stack( + [splits["train"]["head"], splits["train"]["tail"]] + ), + local_seeds_per_call=args.seeds_per_call if args.seeds_per_call > 0 else None, + batch_size=args.batch_size, + shuffle=True, + drop_last=True, + ) + + def get_eval_loader(stage: str): + head = splits[stage]["head"] + tail = splits[stage]["tail"] + + head_neg = splits[stage]["head_neg"][:, : args.num_neg] + tail_neg = splits[stage]["tail_neg"][:, : args.num_neg] + + rel = splits[stage]["relation"] + + return torch.utils.data.DataLoader( + torch.utils.data.TensorDataset( + head.pin_memory(), + head_neg.pin_memory(), + tail.pin_memory(), + tail_neg.pin_memory(), + rel.pin_memory(), + ), + batch_size=1, + shuffle=False, + drop_last=True, + ) + + test_loader = get_eval_loader("test") + valid_loader = get_eval_loader("valid") + + for epoch in range(1, 1 + args.epochs): + train(epoch, model, optimizer, train_loader, edge_feature_store) + test("validation", epoch, model, valid_loader, num_steps=1024) + + test("test", epoch, model, test_loader, num_steps=1024) diff --git a/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_snmg.py b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_snmg.py new file mode 100644 index 00000000000..2c0ae53a08e --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/examples/rgcn_link_class_snmg.py @@ -0,0 +1,320 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This example illustrates link classification using the ogbl-wikikg2 dataset. + +import os +import argparse +import warnings + +from typing import Tuple, Any + +import torch + +import torch.nn.functional as F +from torch.nn import Parameter +from torch_geometric.nn import FastRGCNConv, GAE +from torch.nn.parallel import DistributedDataParallel + +import torch_geometric +import cugraph_pyg + +from cugraph.gnn import ( + cugraph_comms_init, + cugraph_comms_create_unique_id, + cugraph_comms_shutdown, +) + +from pylibwholegraph.torch.initialize import ( + init as wm_init, + finalize as wm_finalize, +) + + +# Enable cudf spilling to save gpu memory +from cugraph.testing.mg_utils import enable_spilling + +# Ensures that a CUDA context is not created on import of rapids. +# Allows pytorch to create the context instead +os.environ["RAPIDS_NO_INITIALIZE"] = "1" + + +def init_pytorch_worker(rank, world_size, uid): + import rmm + + rmm.reinitialize(devices=[rank], pool_allocator=True, managed_memory=True) + + import cupy + from rmm.allocators.cupy import rmm_cupy_allocator + + cupy.cuda.set_allocator(rmm_cupy_allocator) + + cugraph_comms_init( + rank, + world_size, + uid, + rank, + ) + + wm_init(rank, world_size, rank, world_size) + + os.environ["MASTER_ADDR"] = "localhost" + os.environ["MASTER_PORT"] = "12355" + torch.distributed.init_process_group( + "nccl", + rank=rank, + world_size=world_size, + ) + + enable_spilling() + + +class RGCNEncoder(torch.nn.Module): + def __init__(self, num_nodes, hidden_channels, num_relations, num_bases=30): + super().__init__() + self.node_emb = Parameter(torch.empty(num_nodes, hidden_channels)) + self.conv1 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.conv2 = FastRGCNConv( + hidden_channels, hidden_channels, num_relations, num_bases=num_bases + ) + self.reset_parameters() + + def reset_parameters(self): + torch.nn.init.xavier_uniform_(self.node_emb) + self.conv1.reset_parameters() + self.conv2.reset_parameters() + + def forward(self, edge_index, edge_type): + x = self.node_emb + x = self.conv1(x, edge_index, edge_type).relu_() + x = F.dropout(x, p=0.2, training=self.training) + x = self.conv2(x, edge_index, edge_type) + return x + + +def load_data( + rank: int, + world_size: int, + data: Any, +) -> Tuple[ + Tuple["torch_geometric.data.FeatureStore", "torch_geometric.data.GraphStore"], + "torch_geometric.data.FeatureStore", +]: + from cugraph_pyg.data import GraphStore, WholeFeatureStore, TensorDictFeatureStore + + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() # empty fs required by PyG + edge_feature_store = WholeFeatureStore() + + graph_store[("n", "e", "n"), "coo"] = torch.tensor_split( + data.edge_index.cuda(), world_size, dim=1 + )[rank] + + edge_feature_store[("n", "e", "n"), "rel"] = torch.tensor_split( + data.edge_reltype.cuda(), + world_size, + )[rank] + + return (feature_store, graph_store), edge_feature_store + + +def train(epoch, model, optimizer, train_loader, edge_feature_store, num_steps=None): + model.train() + optimizer.zero_grad() + + for i, batch in enumerate(train_loader): + r = edge_feature_store[("n", "e", "n"), "rel"][batch.e_id].flatten().cuda() + z = model.encode(batch.edge_index, r) + + loss = model.recon_loss(z, batch.edge_index) + loss.backward() + optimizer.step() + + if i % 10 == 0: + print( + f"Epoch: {epoch:02d}, Iteration: {i:02d}, Loss: {loss:.4f}", flush=True + ) + if num_steps and i == num_steps: + break + + +def test(stage, epoch, model, loader, num_steps=None): + # TODO support ROC-AUC metric + # Predict probabilities of future edges + model.eval() + + rr = 0.0 + for i, (h, h_neg, t, t_neg, r) in enumerate(loader): + if num_steps and i >= num_steps: + break + + ei = torch.concatenate( + [ + torch.stack([h, t]).cuda(), + torch.stack([h_neg.flatten(), t_neg.flatten()]).cuda(), + ], + dim=-1, + ) + + r = torch.concatenate([r, torch.repeat_interleave(r, h_neg.shape[-1])]).cuda() + + z = model.encode(ei, r) + q = model.decode(z, ei) + + _, ix = torch.sort(q, descending=True) + rr += 1.0 / (1.0 + ix[0]) + + print(f"epoch {epoch:02d} {stage} mrr:", rr / i, flush=True) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--hidden_channels", type=int, default=128) + parser.add_argument("--num_layers", type=int, default=1) + parser.add_argument("--lr", type=float, default=0.001) + parser.add_argument("--epochs", type=int, default=4) + parser.add_argument("--batch_size", type=int, default=16384) + parser.add_argument("--num_neg", type=int, default=500) + parser.add_argument("--num_pos", type=int, default=-1) + parser.add_argument("--fan_out", type=int, default=10) + parser.add_argument("--dataset", type=str, default="ogbl-wikikg2") + parser.add_argument("--dataset_root", type=str, default="dataset") + parser.add_argument("--seeds_per_call", type=int, default=-1) + parser.add_argument("--n_devices", type=int, default=-1) + + return parser.parse_args() + + +def run_train(rank, world_size, uid, model, data, meta, splits, args): + init_pytorch_worker( + rank, + world_size, + uid, + ) + + model = model.to(rank) + model = GAE(DistributedDataParallel(model, device_ids=[rank])) + optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) + + data, edge_feature_store = load_data(rank, world_size, data) + + eli = torch.stack( + [ + torch.tensor_split(splits["train"]["head"], world_size)[rank], + torch.tensor_split(splits["train"]["tail"], world_size)[rank], + ] + ) + + train_loader = cugraph_pyg.loader.LinkNeighborLoader( + data, + [args.fan_out] * args.num_layers, + edge_label_index=eli, + local_seeds_per_call=args.seeds_per_call if args.seeds_per_call > 0 else None, + batch_size=args.batch_size, + shuffle=True, + drop_last=True, + ) + + def get_eval_loader(stage: str): + head = torch.tensor_split(splits[stage]["head"], world_size)[rank] + tail = torch.tensor_split(splits[stage]["tail"], world_size)[rank] + + head_neg = torch.tensor_split( + splits[stage]["head_neg"][:, : args.num_neg], world_size + )[rank] + tail_neg = torch.tensor_split( + splits[stage]["tail_neg"][:, : args.num_neg], world_size + )[rank] + + rel = torch.tensor_split(splits[stage]["relation"], world_size)[rank] + + return torch.utils.data.DataLoader( + torch.utils.data.TensorDataset( + head.pin_memory(), + head_neg.pin_memory(), + tail.pin_memory(), + tail_neg.pin_memory(), + rel.pin_memory(), + ), + batch_size=1, + shuffle=False, + drop_last=True, + ) + + test_loader = get_eval_loader("test") + valid_loader = get_eval_loader("valid") + + num_train_steps = (args.num_pos // args.batch_size) if args.num_pos > 0 else 100 + + for epoch in range(1, 1 + args.epochs): + train( + epoch, + model, + optimizer, + train_loader, + edge_feature_store, + num_steps=num_train_steps, + ) + test("validation", epoch, model, valid_loader, num_steps=1024) + + test("test", epoch, model, test_loader, num_steps=1024) + + wm_finalize() + cugraph_comms_shutdown() + + +if __name__ == "__main__": + if "CI_RUN" in os.environ and os.environ["CI_RUN"] == "1": + warnings.warn("Skipping SMNG example in CI due to memory limit") + else: + args = parse_args() + + # change the allocator before any allocations are made + from rmm.allocators.torch import rmm_torch_allocator + + torch.cuda.memory.change_current_allocator(rmm_torch_allocator) + + # import ogb here to stop it from creating a context and breaking pytorch/rmm + from ogb.linkproppred import PygLinkPropPredDataset + + data = PygLinkPropPredDataset(args.dataset, root=args.dataset_root) + dataset = data[0] + + splits = data.get_edge_split() + + meta = {} + meta["num_nodes"] = dataset.num_nodes + meta["num_rels"] = dataset.edge_reltype.max() + 1 + + model = RGCNEncoder( + meta["num_nodes"], + hidden_channels=args.hidden_channels, + num_relations=meta["num_rels"], + ) + + print("Data =", data) + if args.n_devices == -1: + world_size = torch.cuda.device_count() + else: + world_size = args.n_devices + print("Using", world_size, "GPUs...") + + uid = cugraph_comms_create_unique_id() + torch.multiprocessing.spawn( + run_train, + (world_size, uid, model, data, meta, splits, args), + nprocs=world_size, + join=True, + ) diff --git a/python/cugraph-pyg/cugraph_pyg/loader/__init__.py b/python/cugraph-pyg/cugraph_pyg/loader/__init__.py index cad66aaa183..c804b3d1f97 100644 --- a/python/cugraph-pyg/cugraph_pyg/loader/__init__.py +++ b/python/cugraph-pyg/cugraph_pyg/loader/__init__.py @@ -16,6 +16,9 @@ from cugraph_pyg.loader.node_loader import NodeLoader from cugraph_pyg.loader.neighbor_loader import NeighborLoader +from cugraph_pyg.loader.link_loader import LinkLoader +from cugraph_pyg.loader.link_neighbor_loader import LinkNeighborLoader + from cugraph_pyg.loader.dask_node_loader import DaskNeighborLoader from cugraph_pyg.loader.dask_node_loader import BulkSampleLoader diff --git a/python/cugraph-pyg/cugraph_pyg/loader/link_loader.py b/python/cugraph-pyg/cugraph_pyg/loader/link_loader.py new file mode 100644 index 00000000000..77e2ac4f99d --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/loader/link_loader.py @@ -0,0 +1,205 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings + +import cugraph_pyg +from typing import Union, Tuple, Callable, Optional + +from cugraph.utilities.utils import import_optional + +torch_geometric = import_optional("torch_geometric") +torch = import_optional("torch") + + +class LinkLoader: + """ + Duck-typed version of torch_geometric.loader.LinkLoader. + Loads samples from batches of input nodes using a + `~cugraph_pyg.sampler.BaseSampler.sample_from_edges` + function. + """ + + def __init__( + self, + data: Union[ + "torch_geometric.data.Data", + "torch_geometric.data.HeteroData", + Tuple[ + "torch_geometric.data.FeatureStore", "torch_geometric.data.GraphStore" + ], + ], + link_sampler: "cugraph_pyg.sampler.BaseSampler", + edge_label_index: "torch_geometric.typing.InputEdges" = None, + edge_label: "torch_geometric.typing.OptTensor" = None, + edge_label_time: "torch_geometric.typing.OptTensor" = None, + neg_sampling: Optional["torch_geometric.sampler.NegativeSampling"] = None, + neg_sampling_ratio: Optional[Union[int, float]] = None, + transform: Optional[Callable] = None, + transform_sampler_output: Optional[Callable] = None, + filter_per_worker: Optional[bool] = None, + custom_cls: Optional["torch_geometric.data.HeteroData"] = None, + input_id: "torch_geometric.typing.OptTensor" = None, + batch_size: int = 1, # refers to number of edges in batch + shuffle: bool = False, + drop_last: bool = False, + **kwargs, + ): + """ + Parameters + ---------- + data: Data, HeteroData, or Tuple[FeatureStore, GraphStore] + See torch_geometric.loader.NodeLoader. + link_sampler: BaseSampler + See torch_geometric.loader.LinkLoader. + edge_label_index: InputEdges + See torch_geometric.loader.LinkLoader. + edge_label: OptTensor + See torch_geometric.loader.LinkLoader. + edge_label_time: OptTensor + See torch_geometric.loader.LinkLoader. + neg_sampling: Optional[NegativeSampling] + Type of negative sampling to perform, if desired. + See torch_geometric.loader.LinkLoader. + neg_sampling_ratio: Optional[Union[int, float]] + Negative sampling ratio. Affects how many negative + samples are generated. + See torch_geometric.loader.LinkLoader. + transform: Callable (optional, default=None) + This argument currently has no effect. + transform_sampler_output: Callable (optional, default=None) + This argument currently has no effect. + filter_per_worker: bool (optional, default=False) + This argument currently has no effect. + custom_cls: HeteroData + This argument currently has no effect. This loader will + always return a Data or HeteroData object. + input_id: OptTensor + See torch_geometric.loader.LinkLoader. + + """ + if not isinstance(data, (list, tuple)) or not isinstance( + data[1], cugraph_pyg.data.GraphStore + ): + # Will eventually automatically convert these objects to cuGraph objects. + raise NotImplementedError("Currently can't accept non-cugraph graphs") + + if not isinstance(link_sampler, cugraph_pyg.sampler.BaseSampler): + raise NotImplementedError("Must provide a cuGraph sampler") + + if edge_label_time is not None: + raise ValueError("Temporal sampling is currently unsupported") + + if filter_per_worker: + warnings.warn("filter_per_worker is currently ignored") + + if custom_cls is not None: + warnings.warn("custom_cls is currently ignored") + + if transform is not None: + warnings.warn("transform is currently ignored.") + + if transform_sampler_output is not None: + warnings.warn("transform_sampler_output is currently ignored.") + + if neg_sampling_ratio is not None: + warnings.warn( + "The 'neg_sampling_ratio' argument is deprecated in PyG" + " and is not supported in cuGraph-PyG." + ) + + neg_sampling = torch_geometric.sampler.NegativeSampling.cast(neg_sampling) + + ( + input_type, + edge_label_index, + ) = torch_geometric.loader.utils.get_edge_label_index( + data, + (None, edge_label_index), + ) + + self.__input_data = torch_geometric.sampler.EdgeSamplerInput( + input_id=torch.arange( + edge_label_index[0].numel(), dtype=torch.int64, device="cuda" + ) + if input_id is None + else input_id, + row=edge_label_index[0], + col=edge_label_index[1], + label=edge_label, + time=edge_label_time, + input_type=input_type, + ) + + # Edge label check from torch_geometric.loader.LinkLoader + if ( + neg_sampling is not None + and neg_sampling.is_binary() + and edge_label is not None + and edge_label.min() == 0 + ): + edge_label = edge_label + 1 + + if ( + neg_sampling is not None + and neg_sampling.is_triplet() + and edge_label is not None + ): + raise ValueError( + "'edge_label' needs to be undefined for " + "'triplet'-based negative sampling. Please use " + "`src_index`, `dst_pos_index` and " + "`neg_pos_index` of the returned mini-batch " + "instead to differentiate between positive and " + "negative samples." + ) + + self.__data = data + + self.__link_sampler = link_sampler + self.__neg_sampling = neg_sampling + + self.__batch_size = batch_size + self.__shuffle = shuffle + self.__drop_last = drop_last + + def __iter__(self): + if self.__shuffle: + perm = torch.randperm(self.__input_data.row.numel()) + else: + perm = torch.arange(self.__input_data.row.numel()) + + if self.__drop_last: + d = perm.numel() % self.__batch_size + perm = perm[:-d] + + input_data = torch_geometric.sampler.EdgeSamplerInput( + input_id=self.__input_data.input_id[perm], + row=self.__input_data.row[perm], + col=self.__input_data.col[perm], + label=None + if self.__input_data.label is None + else self.__input_data.label[perm], + time=None + if self.__input_data.time is None + else self.__input_data.time[perm], + input_type=self.__input_data.input_type, + ) + + return cugraph_pyg.sampler.SampleIterator( + self.__data, + self.__link_sampler.sample_from_edges( + input_data, + neg_sampling=self.__neg_sampling, + ), + ) diff --git a/python/cugraph-pyg/cugraph_pyg/loader/link_neighbor_loader.py b/python/cugraph-pyg/cugraph_pyg/loader/link_neighbor_loader.py new file mode 100644 index 00000000000..080565368c4 --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/loader/link_neighbor_loader.py @@ -0,0 +1,243 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings + +from typing import Union, Tuple, Optional, Callable, List, Dict + +import cugraph_pyg +from cugraph_pyg.loader import LinkLoader +from cugraph_pyg.sampler import BaseSampler + +from cugraph.gnn import NeighborSampler, DistSampleWriter +from cugraph.utilities.utils import import_optional + +torch_geometric = import_optional("torch_geometric") + + +class LinkNeighborLoader(LinkLoader): + """ + Duck-typed version of torch_geometric.loader.LinkNeighborLoader + + Link loader that implements the neighbor sampling + algorithm used in GraphSAGE. + """ + + def __init__( + self, + data: Union[ + "torch_geometric.data.Data", + "torch_geometric.data.HeteroData", + Tuple[ + "torch_geometric.data.FeatureStore", "torch_geometric.data.GraphStore" + ], + ], + num_neighbors: Union[ + List[int], Dict["torch_geometric.typing.EdgeType", List[int]] + ], + edge_label_index: "torch_geometric.typing.InputEdges" = None, + edge_label: "torch_geometric.typing.OptTensor" = None, + edge_label_time: "torch_geometric.typing.OptTensor" = None, + replace: bool = False, + subgraph_type: Union[ + "torch_geometric.typing.SubgraphType", str + ] = "directional", + disjoint: bool = False, + temporal_strategy: str = "uniform", + neg_sampling: Optional["torch_geometric.sampler.NegativeSampling"] = None, + neg_sampling_ratio: Optional[Union[int, float]] = None, + time_attr: Optional[str] = None, + weight_attr: Optional[str] = None, + transform: Optional[Callable] = None, + transform_sampler_output: Optional[Callable] = None, + is_sorted: bool = False, + filter_per_worker: Optional[bool] = None, + neighbor_sampler: Optional["torch_geometric.sampler.NeighborSampler"] = None, + directed: bool = True, # Deprecated. + batch_size: int = 16, # Refers to number of edges per batch. + directory: Optional[str] = None, + batches_per_partition=256, + format: str = "parquet", + compression: Optional[str] = None, + local_seeds_per_call: Optional[int] = None, + **kwargs, + ): + """ + data: Data, HeteroData, or Tuple[FeatureStore, GraphStore] + See torch_geometric.loader.LinkNeighborLoader. + num_neighbors: List[int] or Dict[EdgeType, List[int]] + Fanout values. + See torch_geometric.loader.LinkNeighborLoader. + edge_label_index: InputEdges + Input edges for sampling. + See torch_geometric.loader.LinkNeighborLoader. + edge_label: OptTensor + Labels for input edges. + See torch_geometric.loader.LinkNeighborLoader. + edge_label_time: OptTensor + Time attribute for input edges. + See torch_geometric.loader.LinkNeighborLoader. + replace: bool (optional, default=False) + Whether to sample with replacement. + See torch_geometric.loader.LinkNeighborLoader. + subgraph_type: Union[SubgraphType, str] (optional, default='directional') + The type of subgraph to return. + Currently only 'directional' is supported. + See torch_geometric.loader.LinkNeighborLoader. + disjoint: bool (optional, default=False) + Whether to perform disjoint sampling. + Currently unsupported. + See torch_geometric.loader.LinkNeighborLoader. + temporal_strategy: str (optional, default='uniform') + Currently only 'uniform' is suppported. + See torch_geometric.loader.LinkNeighborLoader. + time_attr: str (optional, default=None) + Used for temporal sampling. + See torch_geometric.loader.LinkNeighborLoader. + weight_attr: str (optional, default=None) + Used for biased sampling. + See torch_geometric.loader.LinkNeighborLoader. + transform: Callable (optional, default=None) + See torch_geometric.loader.LinkNeighborLoader. + transform_sampler_output: Callable (optional, default=None) + See torch_geometric.loader.LinkNeighborLoader. + is_sorted: bool (optional, default=False) + Ignored by cuGraph. + See torch_geometric.loader.LinkNeighborLoader. + filter_per_worker: bool (optional, default=False) + Currently ignored by cuGraph, but this may + change once in-memory sampling is implemented. + See torch_geometric.loader.LinkNeighborLoader. + neighbor_sampler: torch_geometric.sampler.NeighborSampler + (optional, default=None) + Not supported by cuGraph. + See torch_geometric.loader.LinkNeighborLoader. + directed: bool (optional, default=True) + Deprecated. + See torch_geometric.loader.LinkNeighborLoader. + batch_size: int (optional, default=16) + The number of input nodes per output minibatch. + See torch.utils.dataloader. + directory: str (optional, default=None) + The directory where samples will be temporarily stored, + if spilling samples to disk. If None, this loader + will perform buffered in-memory sampling. + If writing to disk, setting this argument + to a tempfile.TemporaryDirectory with a context + manager is a good option but depending on the filesystem, + you may want to choose an alternative location with fast I/O + intead. + See cugraph.gnn.DistSampleWriter. + batches_per_partition: int (optional, default=256) + The number of batches per partition if writing samples to + disk. Manually tuning this parameter is not recommended + but reducing it may help conserve GPU memory. + See cugraph.gnn.DistSampleWriter. + format: str (optional, default='parquet') + If writing samples to disk, they will be written in this + file format. + See cugraph.gnn.DistSampleWriter. + compression: str (optional, default=None) + The compression type to use if writing samples to disk. + If not provided, it is automatically chosen. + local_seeds_per_call: int (optional, default=None) + The number of seeds to process within a single sampling call. + Manually tuning this parameter is not recommended but reducing + it may conserve GPU memory. The total number of seeds processed + per sampling call is equal to the sum of this parameter across + all workers. If not provided, it will be automatically + calculated. + See cugraph.gnn.DistSampler. + **kwargs + Other keyword arguments passed to the superclass. + """ + + subgraph_type = torch_geometric.sampler.base.SubgraphType(subgraph_type) + + if not directed: + subgraph_type = torch_geometric.sampler.base.SubgraphType.induced + warnings.warn( + "The 'directed' argument is deprecated. " + "Use subgraph_type='induced' instead." + ) + if subgraph_type != torch_geometric.sampler.base.SubgraphType.directional: + raise ValueError("Only directional subgraphs are currently supported") + if disjoint: + raise ValueError("Disjoint sampling is currently unsupported") + if temporal_strategy != "uniform": + warnings.warn("Only the uniform temporal strategy is currently supported") + if neighbor_sampler is not None: + raise ValueError("Passing a neighbor sampler is currently unsupported") + if time_attr is not None: + raise ValueError("Temporal sampling is currently unsupported") + if is_sorted: + warnings.warn("The 'is_sorted' argument is ignored by cuGraph.") + if not isinstance(data, (list, tuple)) or not isinstance( + data[1], cugraph_pyg.data.GraphStore + ): + # Will eventually automatically convert these objects to cuGraph objects. + raise NotImplementedError("Currently can't accept non-cugraph graphs") + + if compression is None: + compression = "CSR" + elif compression not in ["CSR", "COO"]: + raise ValueError("Invalid value for compression (expected 'CSR' or 'COO')") + + writer = ( + None + if directory is None + else DistSampleWriter( + directory=directory, + batches_per_partition=batches_per_partition, + format=format, + ) + ) + + feature_store, graph_store = data + + if weight_attr is not None: + graph_store._set_weight_attr((feature_store, weight_attr)) + + sampler = BaseSampler( + NeighborSampler( + graph_store._graph, + writer, + retain_original_seeds=True, + fanout=num_neighbors, + prior_sources_behavior="exclude", + deduplicate_sources=True, + compression=compression, + compress_per_hop=False, + with_replacement=replace, + local_seeds_per_call=local_seeds_per_call, + biased=(weight_attr is not None), + ), + (feature_store, graph_store), + batch_size=batch_size, + ) + # TODO add heterogeneous support and pass graph_store._vertex_offsets + + super().__init__( + (feature_store, graph_store), + sampler, + edge_label_index=edge_label_index, + edge_label=edge_label, + edge_label_time=edge_label_time, + neg_sampling=neg_sampling, + neg_sampling_ratio=neg_sampling_ratio, + transform=transform, + transform_sampler_output=transform_sampler_output, + filter_per_worker=filter_per_worker, + batch_size=batch_size, + **kwargs, + ) diff --git a/python/cugraph-pyg/cugraph_pyg/loader/neighbor_loader.py b/python/cugraph-pyg/cugraph_pyg/loader/neighbor_loader.py index 7f12bbb3fe6..1da2c6dc381 100644 --- a/python/cugraph-pyg/cugraph_pyg/loader/neighbor_loader.py +++ b/python/cugraph-pyg/cugraph_pyg/loader/neighbor_loader.py @@ -12,7 +12,6 @@ # limitations under the License. import warnings -import tempfile from typing import Union, Tuple, Optional, Callable, List, Dict @@ -123,14 +122,14 @@ def __init__( The number of input nodes per output minibatch. See torch.utils.dataloader. directory: str (optional, default=None) - The directory where samples will be temporarily stored. - It is recommend that this be set by the user, usually - setting it to a tempfile.TemporaryDirectory with a context + The directory where samples will be temporarily stored, + if spilling samples to disk. If None, this loader + will perform buffered in-memory sampling. + If writing to disk, setting this argument + to a tempfile.TemporaryDirectory with a context manager is a good option but depending on the filesystem, you may want to choose an alternative location with fast I/O intead. - If not set, this will create a TemporaryDirectory that will - persist until this object is garbage collected. See cugraph.gnn.DistSampleWriter. batches_per_partition: int (optional, default=256) The number of batches per partition if writing samples to @@ -182,20 +181,19 @@ def __init__( # Will eventually automatically convert these objects to cuGraph objects. raise NotImplementedError("Currently can't accept non-cugraph graphs") - if directory is None: - warnings.warn("Setting a directory to store samples is recommended.") - self._tempdir = tempfile.TemporaryDirectory() - directory = self._tempdir.name - if compression is None: compression = "CSR" elif compression not in ["CSR", "COO"]: raise ValueError("Invalid value for compression (expected 'CSR' or 'COO')") - writer = DistSampleWriter( - directory=directory, - batches_per_partition=batches_per_partition, - format=format, + writer = ( + None + if directory is None + else DistSampleWriter( + directory=directory, + batches_per_partition=batches_per_partition, + format=format, + ) ) feature_store, graph_store = data diff --git a/python/cugraph-pyg/cugraph_pyg/loader/node_loader.py b/python/cugraph-pyg/cugraph_pyg/loader/node_loader.py index 49923783d6b..4b236f75885 100644 --- a/python/cugraph-pyg/cugraph_pyg/loader/node_loader.py +++ b/python/cugraph-pyg/cugraph_pyg/loader/node_loader.py @@ -110,8 +110,10 @@ def __init__( input_id, ) - self.__input_data = torch_geometric.loader.node_loader.NodeSamplerInput( - input_id=input_id, + self.__input_data = torch_geometric.sampler.NodeSamplerInput( + input_id=torch.arange(len(input_nodes), dtype=torch.int64, device="cuda") + if input_id is None + else input_id, node=input_nodes, time=None, input_type=input_type, @@ -135,10 +137,8 @@ def __iter__(self): d = perm.numel() % self.__batch_size perm = perm[:-d] - input_data = torch_geometric.loader.node_loader.NodeSamplerInput( - input_id=None - if self.__input_data.input_id is None - else self.__input_data.input_id[perm], + input_data = torch_geometric.sampler.NodeSamplerInput( + input_id=self.__input_data.input_id[perm], node=self.__input_data.node[perm], time=None if self.__input_data.time is None diff --git a/python/cugraph-pyg/cugraph_pyg/sampler/sampler.py b/python/cugraph-pyg/cugraph_pyg/sampler/sampler.py index 268e9ffebbd..bc3d4fd8d3c 100644 --- a/python/cugraph-pyg/cugraph_pyg/sampler/sampler.py +++ b/python/cugraph-pyg/cugraph_pyg/sampler/sampler.py @@ -14,9 +14,9 @@ from typing import Optional, Iterator, Union, Dict, Tuple from cugraph.utilities.utils import import_optional -from cugraph.gnn import DistSampler, DistSampleReader +from cugraph.gnn import DistSampler -from .sampler_utils import filter_cugraph_pyg_store +from .sampler_utils import filter_cugraph_pyg_store, neg_sample, neg_cat torch = import_optional("torch") torch_geometric = import_optional("torch_geometric") @@ -60,7 +60,12 @@ def __next__(self): next_sample = next(self.__output_iter) if isinstance(next_sample, torch_geometric.sampler.SamplerOutput): sz = next_sample.edge.numel() - if sz == next_sample.col.numel(): + if sz == next_sample.col.numel() and ( + next_sample.node.numel() > next_sample.col[-1] + ): + # This will only trigger on very small batches and will have minimal + # performance impact. If COO output is removed, then this condition + # can be avoided. col = next_sample.col else: col = torch_geometric.edge_index.ptr2index( @@ -101,10 +106,20 @@ def __next__(self): data.num_sampled_nodes = next_sample.num_sampled_nodes data.num_sampled_edges = next_sample.num_sampled_edges - data.input_id = data.batch - data.seed_time = None + data.input_id = next_sample.metadata[0] data.batch_size = data.input_id.size(0) + if len(next_sample.metadata) == 2: + data.seed_time = next_sample.metadata[1] + elif len(next_sample.metadata) == 4: + ( + data.edge_label_index, + data.edge_label, + data.seed_time, + ) = next_sample.metadata[1:] + else: + raise ValueError("Invalid metadata") + elif isinstance(next_sample, torch_geometric.sampler.HeteroSamplerOutput): col = {} for edge_type, col_idx in next_sample.col: @@ -152,13 +167,15 @@ class SampleReader: Iterator that processes results from the cuGraph distributed sampler. """ - def __init__(self, base_reader: DistSampleReader): + def __init__( + self, base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] + ): """ Constructs a new SampleReader. Parameters ---------- - base_reader: DistSampleReader + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] The reader responsible for loading saved samples produced by the cuGraph distributed sampler. """ @@ -173,6 +190,9 @@ def __next__(self): self.__base_reader ) + self.__raw_sample_data["input_offsets"] -= self.__raw_sample_data[ + "input_offsets" + ][0].clone() self.__raw_sample_data["label_hop_offsets"] -= self.__raw_sample_data[ "label_hop_offsets" ][0].clone() @@ -202,14 +222,16 @@ class HomogeneousSampleReader(SampleReader): produced by the cuGraph distributed sampler. """ - def __init__(self, base_reader: DistSampleReader): + def __init__( + self, base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] + ): """ Constructs a new HomogeneousSampleReader Parameters ---------- - base_reader: DistSampleReader - The reader responsible for loading saved samples produced by + base_reader: Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]] + The iterator responsible for loading saved samples produced by the cuGraph distributed sampler. """ super().__init__(base_reader) @@ -262,6 +284,52 @@ def __decode_csc(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): [num_seeds, num_sampled_nodes_hops.diff(prepend=num_seeds)] ) + input_index = raw_sample_data["input_index"][ + raw_sample_data["input_offsets"][index] : raw_sample_data["input_offsets"][ + index + 1 + ] + ] + + num_seeds = input_index.numel() + input_index = input_index[input_index >= 0] + + num_pos = input_index.numel() + num_neg = num_seeds - num_pos + if num_neg > 0: + edge_label = torch.concat( + [ + torch.full((num_pos,), 1.0), + torch.full((num_neg,), 0.0), + ] + ) + else: + edge_label = None + + edge_inverse = ( + ( + raw_sample_data["edge_inverse"][ + (raw_sample_data["input_offsets"][index] * 2) : ( + raw_sample_data["input_offsets"][index + 1] * 2 + ) + ] + ) + if "edge_inverse" in raw_sample_data + else None + ) + + if edge_inverse is None: + metadata = ( + input_index, + None, # TODO this will eventually include time + ) + else: + metadata = ( + input_index, + edge_inverse.view(2, -1), + edge_label, + None, # TODO this will eventually include time + ) + return torch_geometric.sampler.SamplerOutput( node=renumber_map.cpu(), row=minors, @@ -270,6 +338,7 @@ def __decode_csc(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): batch=renumber_map[:num_seeds], num_sampled_nodes=num_sampled_nodes.cpu(), num_sampled_edges=num_sampled_edges.cpu(), + metadata=metadata, ) def __decode_coo(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): @@ -315,6 +384,37 @@ def __decode_coo(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): [num_seeds, num_sampled_nodes_hops.diff(prepend=num_seeds)] ) + input_index = raw_sample_data["input_index"][ + raw_sample_data["input_offsets"][index] : raw_sample_data["input_offsets"][ + index + 1 + ] + ] + + edge_inverse = ( + ( + raw_sample_data["edge_inverse"][ + (raw_sample_data["input_offsets"][index] * 2) : ( + raw_sample_data["input_offsets"][index + 1] * 2 + ) + ] + ) + if "edge_inverse" in raw_sample_data + else None + ) + + if edge_inverse is None: + metadata = ( + input_index, + None, # TODO this will eventually include time + ) + else: + metadata = ( + input_index, + edge_inverse.view(2, -1), + None, + None, # TODO this will eventually include time + ) + return torch_geometric.sampler.SamplerOutput( node=renumber_map.cpu(), row=minors, @@ -323,6 +423,7 @@ def __decode_coo(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): batch=renumber_map[:num_seeds], num_sampled_nodes=num_sampled_nodes, num_sampled_edges=num_sampled_edges, + metadata=metadata, ) def _decode(self, raw_sample_data: Dict[str, "torch.Tensor"], index: int): @@ -353,8 +454,8 @@ def sample_from_nodes( "torch_geometric.sampler.SamplerOutput", ] ]: - self.__sampler.sample_from_nodes( - index.node, batch_size=self.__batch_size, **kwargs + reader = self.__sampler.sample_from_nodes( + index.node, batch_size=self.__batch_size, input_id=index.input_id, **kwargs ) edge_attrs = self.__graph_store.get_all_edge_attrs() @@ -362,7 +463,7 @@ def sample_from_nodes( len(edge_attrs) == 1 and edge_attrs[0].edge_type[0] == edge_attrs[0].edge_type[2] ): - return HomogeneousSampleReader(self.__sampler.get_reader()) + return HomogeneousSampleReader(reader) else: # TODO implement heterogeneous sampling raise NotImplementedError( @@ -381,4 +482,59 @@ def sample_from_edges( "torch_geometric.sampler.SamplerOutput", ] ]: - raise NotImplementedError("Edge sampling is currently unimplemented.") + src = index.row + dst = index.col + input_id = index.input_id + neg_batch_size = 0 + if neg_sampling: + # Sample every negative subset at once. + # TODO handle temporal sampling (node_time) + src_neg, dst_neg = neg_sample( + self.__graph_store, + index.row, + index.col, + self.__batch_size, + neg_sampling, + None, # src_time, + None, # src_node_time, + ) + if neg_sampling.is_binary(): + src, _ = neg_cat(src.cuda(), src_neg, self.__batch_size) + else: + # triplet, cat dst to src so length is the same; will + # result in the same set of unique vertices + src, _ = neg_cat(src.cuda(), dst_neg, self.__batch_size) + dst, neg_batch_size = neg_cat(dst.cuda(), dst_neg, self.__batch_size) + + # Concatenate -1s so the input id tensor lines up and can + # be processed by the dist sampler. + # When loading the output batch, '-1' will be dropped. + input_id, _ = neg_cat( + input_id, + torch.full( + (dst_neg.numel(),), -1, dtype=torch.int64, device=input_id.device + ), + self.__batch_size, + ) + + # TODO for temporal sampling, node times have to be + # adjusted here. + reader = self.__sampler.sample_from_edges( + torch.stack([src, dst]), # reverse of usual convention + input_id=input_id, + batch_size=self.__batch_size + neg_batch_size, + **kwargs, + ) + + edge_attrs = self.__graph_store.get_all_edge_attrs() + if ( + len(edge_attrs) == 1 + and edge_attrs[0].edge_type[0] == edge_attrs[0].edge_type[2] + ): + return HomogeneousSampleReader(reader) + else: + # TODO implement heterogeneous sampling + raise NotImplementedError( + "Sampling heterogeneous graphs is currently" + " unsupported in the non-dask API" + ) diff --git a/python/cugraph-pyg/cugraph_pyg/sampler/sampler_utils.py b/python/cugraph-pyg/cugraph_pyg/sampler/sampler_utils.py index dba7c146b01..b3d56ef9992 100644 --- a/python/cugraph-pyg/cugraph_pyg/sampler/sampler_utils.py +++ b/python/cugraph-pyg/cugraph_pyg/sampler/sampler_utils.py @@ -14,10 +14,14 @@ from typing import Sequence, Dict, Tuple -from cugraph_pyg.data import DaskGraphStore +from math import ceil + +from cugraph_pyg.data import GraphStore, DaskGraphStore from cugraph.utilities.utils import import_optional import cudf +import cupy +import pylibcugraph dask_cudf = import_optional("dask_cudf") torch_geometric = import_optional("torch_geometric") @@ -429,3 +433,99 @@ def filter_cugraph_pyg_store( data[attr.attr_name] = tensors[i] return data + + +def neg_sample( + graph_store: GraphStore, + seed_src: "torch.Tensor", + seed_dst: "torch.Tensor", + batch_size: int, + neg_sampling: "torch_geometric.sampler.NegativeSampling", + time: "torch.Tensor", + node_time: "torch.Tensor", +) -> Tuple["torch.Tensor", "torch.Tensor"]: + try: + # Compatibility for PyG 2.5 + src_weight = neg_sampling.src_weight + dst_weight = neg_sampling.dst_weight + except AttributeError: + src_weight = neg_sampling.weight + dst_weight = neg_sampling.weight + unweighted = src_weight is None and dst_weight is None + + # Require at least one negative edge per batch + num_neg = max( + int(ceil(neg_sampling.amount * seed_src.numel())), + int(ceil(seed_src.numel() / batch_size)), + ) + + if graph_store.is_multi_gpu: + num_neg_global = torch.tensor([num_neg], device="cuda") + torch.distributed.all_reduce(num_neg_global, op=torch.distributed.ReduceOp.SUM) + num_neg = int(num_neg_global) + else: + num_neg_global = num_neg + + if node_time is None: + result_dict = pylibcugraph.negative_sampling( + graph_store._resource_handle, + graph_store._graph, + num_neg_global, + vertices=None + if unweighted + else cupy.arange(src_weight.numel(), dtype="int64"), + src_bias=None if src_weight is None else cupy.asarray(src_weight), + dst_bias=None if dst_weight is None else cupy.asarray(dst_weight), + remove_duplicates=False, + remove_false_negatives=False, + exact_number_of_samples=True, + do_expensive_check=False, + ) + + src_neg = torch.as_tensor(result_dict["sources"], device="cuda")[:num_neg] + dst_neg = torch.as_tensor(result_dict["destinations"], device="cuda")[:num_neg] + + # TODO modifiy the C API so this condition is impossible + if src_neg.numel() < num_neg: + num_gen = num_neg - src_neg.numel() + src_neg = torch.concat( + [ + src_neg, + torch.randint( + 0, src_neg.max(), (num_gen,), device="cuda", dtype=torch.int64 + ), + ] + ) + dst_neg = torch.concat( + [ + dst_neg, + torch.randint( + 0, dst_neg.max(), (num_gen,), device="cuda", dtype=torch.int64 + ), + ] + ) + return src_neg, dst_neg + raise NotImplementedError( + "Temporal negative sampling is currently unimplemented in cuGraph-PyG" + ) + + +def neg_cat( + seed_pos: "torch.Tensor", seed_neg: "torch.Tensor", pos_batch_size: int +) -> Tuple["torch.Tensor", int]: + num_seeds = seed_pos.numel() + num_batches = int(ceil(num_seeds / pos_batch_size)) + neg_batch_size = int(ceil(seed_neg.numel() / num_batches)) + + batch_pos_offsets = torch.full((num_batches,), pos_batch_size).cumsum(-1)[:-1] + seed_pos_splits = torch.tensor_split(seed_pos, batch_pos_offsets) + + batch_neg_offsets = torch.full((num_batches,), neg_batch_size).cumsum(-1)[:-1] + seed_neg_splits = torch.tensor_split(seed_neg, batch_neg_offsets) + + return ( + torch.concatenate( + [torch.concatenate(s) for s in zip(seed_pos_splits, seed_neg_splits)] + ), + neg_batch_size, + ) diff --git a/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader.py b/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader.py index c4ad941de7a..8ee18a826f7 100644 --- a/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader.py +++ b/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader.py @@ -16,6 +16,7 @@ from cugraph.datasets import karate from cugraph.utilities.utils import import_optional, MissingModule +import cugraph_pyg from cugraph_pyg.data import TensorDictFeatureStore, GraphStore from cugraph_pyg.loader import NeighborLoader @@ -86,3 +87,110 @@ def test_neighbor_loader_biased(): assert out.edge_index.shape[1] == 2 assert (out.edge_index.cpu() == torch.tensor([[3, 4], [1, 2]])).all() + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.sg +@pytest.mark.parametrize("num_nodes", [10, 25]) +@pytest.mark.parametrize("num_edges", [64, 128]) +@pytest.mark.parametrize("batch_size", [2, 4]) +@pytest.mark.parametrize("select_edges", [16, 32]) +@pytest.mark.parametrize("depth", [1, 3]) +@pytest.mark.parametrize("num_neighbors", [1, 4]) +def test_link_neighbor_loader_basic( + num_nodes, num_edges, batch_size, select_edges, num_neighbors, depth +): + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() + + eix = torch.randperm(num_edges)[:select_edges] + graph_store[("n", "e", "n"), "coo"] = torch.stack( + [ + torch.randint(0, num_nodes, (num_edges,)), + torch.randint(0, num_nodes, (num_edges,)), + ] + ) + + elx = graph_store[("n", "e", "n"), "coo"][:, eix] + loader = cugraph_pyg.loader.LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[num_neighbors] * depth, + edge_label_index=elx, + batch_size=batch_size, + shuffle=False, + ) + + elx = torch.tensor_split(elx, eix.numel() // batch_size, dim=1) + for i, batch in enumerate(loader): + assert ( + batch.input_id.cpu() == torch.arange(i * batch_size, (i + 1) * batch_size) + ).all() + assert (elx[i] == batch.n_id[batch.edge_label_index.cpu()]).all() + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.sg +@pytest.mark.parametrize("batch_size", [1, 2]) +def test_link_neighbor_loader_negative_sampling_basic(batch_size): + num_edges = 62 + num_nodes = 19 + select_edges = 17 + + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() + + eix = torch.randperm(num_edges)[:select_edges] + graph_store[("n", "e", "n"), "coo"] = torch.stack( + [ + torch.randint(0, num_nodes, (num_edges,)), + torch.randint(0, num_nodes, (num_edges,)), + ] + ) + + elx = graph_store[("n", "e", "n"), "coo"][:, eix] + loader = cugraph_pyg.loader.LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[3, 3, 3], + edge_label_index=elx, + batch_size=batch_size, + neg_sampling="binary", + shuffle=False, + ) + + elx = torch.tensor_split(elx, eix.numel() // batch_size, dim=1) + for i, batch in enumerate(loader): + assert batch.edge_label[0] == 1.0 + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.sg +@pytest.mark.parametrize("batch_size", [1, 2]) +def test_link_neighbor_loader_negative_sampling_uneven(batch_size): + num_edges = 62 + num_nodes = 19 + select_edges = 17 + + graph_store = GraphStore() + feature_store = TensorDictFeatureStore() + + eix = torch.randperm(num_edges)[:select_edges] + graph_store[("n", "e", "n"), "coo"] = torch.stack( + [ + torch.randint(0, num_nodes, (num_edges,)), + torch.randint(0, num_nodes, (num_edges,)), + ] + ) + + elx = graph_store[("n", "e", "n"), "coo"][:, eix] + loader = cugraph_pyg.loader.LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[3, 3, 3], + edge_label_index=elx, + batch_size=batch_size, + neg_sampling=torch_geometric.sampler.NegativeSampling("binary", amount=0.1), + shuffle=False, + ) + + elx = torch.tensor_split(elx, eix.numel() // batch_size, dim=1) + for i, batch in enumerate(loader): + assert batch.edge_label[0] == 1.0 diff --git a/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader_mg.py b/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader_mg.py index b8089bb901d..d1dee01a508 100644 --- a/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader_mg.py +++ b/python/cugraph-pyg/cugraph_pyg/tests/loader/test_neighbor_loader_mg.py @@ -19,7 +19,7 @@ from cugraph.utilities.utils import import_optional, MissingModule from cugraph_pyg.data import TensorDictFeatureStore, GraphStore -from cugraph_pyg.loader import NeighborLoader +from cugraph_pyg.loader import NeighborLoader, LinkNeighborLoader from cugraph.gnn import ( cugraph_comms_init, @@ -96,6 +96,7 @@ def run_test_neighbor_loader_mg(rank, uid, world_size, specify_size): cugraph_comms_shutdown() +@pytest.mark.skip(reason="deleteme") @pytest.mark.parametrize("specify_size", [True, False]) @pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") @pytest.mark.mg @@ -165,6 +166,7 @@ def run_test_neighbor_loader_biased_mg(rank, uid, world_size): cugraph_comms_shutdown() +@pytest.mark.skip(reason="deleteme") @pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") @pytest.mark.mg def test_neighbor_loader_biased_mg(): @@ -179,3 +181,184 @@ def test_neighbor_loader_biased_mg(): ), nprocs=world_size, ) + + +def run_test_link_neighbor_loader_basic_mg( + rank, + uid, + world_size, + num_nodes: int, + num_edges: int, + select_edges: int, + batch_size: int, + num_neighbors: int, + depth: int, +): + init_pytorch_worker(rank, world_size, uid) + + graph_store = GraphStore(is_multi_gpu=True) + feature_store = TensorDictFeatureStore() + + eix = torch.randperm(num_edges)[:select_edges] + graph_store[("n", "e", "n"), "coo"] = torch.stack( + [ + torch.randint(0, num_nodes, (num_edges,)), + torch.randint(0, num_nodes, (num_edges,)), + ] + ) + + elx = graph_store[("n", "e", "n"), "coo"][:, eix] + loader = LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[num_neighbors] * depth, + edge_label_index=elx, + batch_size=batch_size, + shuffle=False, + ) + + elx = torch.tensor_split(elx, eix.numel() // batch_size, dim=1) + for i, batch in enumerate(loader): + assert ( + batch.input_id.cpu() == torch.arange(i * batch_size, (i + 1) * batch_size) + ).all() + assert (elx[i] == batch.n_id[batch.edge_label_index.cpu()]).all() + + cugraph_comms_shutdown() + + +@pytest.mark.skip(reason="deleteme") +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.mg +@pytest.mark.parametrize("select_edges", [64, 128]) +@pytest.mark.parametrize("batch_size", [2, 4]) +@pytest.mark.parametrize("depth", [1, 3]) +def test_link_neighbor_loader_basic_mg(select_edges, batch_size, depth): + num_nodes = 25 + num_edges = 128 + num_neighbors = 2 + + uid = cugraph_comms_create_unique_id() + world_size = torch.cuda.device_count() + + torch.multiprocessing.spawn( + run_test_link_neighbor_loader_basic_mg, + args=( + uid, + world_size, + num_nodes, + num_edges, + select_edges, + batch_size, + num_neighbors, + depth, + ), + nprocs=world_size, + ) + + +def run_test_link_neighbor_loader_uneven_mg(rank, uid, world_size, edge_index): + init_pytorch_worker(rank, world_size, uid) + + graph_store = GraphStore(is_multi_gpu=True) + feature_store = TensorDictFeatureStore() + + batch_size = 1 + graph_store[("n", "e", "n"), "coo"] = torch.tensor_split( + edge_index, world_size, dim=-1 + )[rank] + + elx = graph_store[("n", "e", "n"), "coo"] # select all edges on each worker + loader = LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[2, 2, 2], + edge_label_index=elx, + batch_size=batch_size, + shuffle=False, + ) + + for i, batch in enumerate(loader): + assert ( + batch.input_id.cpu() == torch.arange(i * batch_size, (i + 1) * batch_size) + ).all() + + assert (elx[:, [i]] == batch.n_id[batch.edge_label_index.cpu()]).all() + + cugraph_comms_shutdown() + + +@pytest.mark.skip(reason="deleteme") +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.mg +def test_link_neighbor_loader_uneven_mg(): + edge_index = torch.tensor( + [ + [0, 1, 3, 4, 7], + [1, 0, 8, 9, 12], + ] + ) + + uid = cugraph_comms_create_unique_id() + world_size = torch.cuda.device_count() + + torch.multiprocessing.spawn( + run_test_link_neighbor_loader_uneven_mg, + args=( + uid, + world_size, + edge_index, + ), + nprocs=world_size, + ) + + +def run_test_link_neighbor_loader_negative_sampling_basic_mg( + rank, world_size, uid, batch_size +): + num_edges = 62 * world_size + num_nodes = 19 * world_size + select_edges = 17 + + init_pytorch_worker(rank, world_size, uid) + + graph_store = GraphStore(is_multi_gpu=True) + feature_store = TensorDictFeatureStore() + + eix = torch.randperm(num_edges)[:select_edges] + graph_store[("n", "e", "n"), "coo"] = torch.stack( + [ + torch.randint(0, num_nodes, (num_edges,)), + torch.randint(0, num_nodes, (num_edges,)), + ] + ) + + elx = graph_store[("n", "e", "n"), "coo"][:, eix] + loader = LinkNeighborLoader( + (feature_store, graph_store), + num_neighbors=[3, 3, 3], + edge_label_index=elx, + batch_size=batch_size, + neg_sampling="binary", + shuffle=False, + ) + + elx = torch.tensor_split(elx, eix.numel() // batch_size, dim=1) + for i, batch in enumerate(loader): + assert batch.edge_label[0] == 1.0 + + +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.mg +@pytest.mark.parametrize("batch_size", [1, 2]) +def test_link_neighbor_loader_negative_sampling_basic_mg(batch_size): + uid = cugraph_comms_create_unique_id() + world_size = torch.cuda.device_count() + + torch.multiprocessing.spawn( + run_test_link_neighbor_loader_negative_sampling_basic_mg, + args=( + world_size, + uid, + batch_size, + ), + nprocs=world_size, + ) diff --git a/python/cugraph/cugraph/gnn/data_loading/__init__.py b/python/cugraph/cugraph/gnn/data_loading/__init__.py index 9e2c81ec749..25f58be88aa 100644 --- a/python/cugraph/cugraph/gnn/data_loading/__init__.py +++ b/python/cugraph/cugraph/gnn/data_loading/__init__.py @@ -14,9 +14,12 @@ from cugraph.gnn.data_loading.bulk_sampler import BulkSampler from cugraph.gnn.data_loading.dist_sampler import ( DistSampler, + NeighborSampler, +) +from cugraph.gnn.data_loading.dist_io import ( DistSampleWriter, DistSampleReader, - NeighborSampler, + BufferedSampleReader, ) diff --git a/python/cugraph/cugraph/gnn/data_loading/bulk_sampler_io.py b/python/cugraph/cugraph/gnn/data_loading/bulk_sampler_io.py index 6abbd82647b..222fb49a836 100644 --- a/python/cugraph/cugraph/gnn/data_loading/bulk_sampler_io.py +++ b/python/cugraph/cugraph/gnn/data_loading/bulk_sampler_io.py @@ -33,10 +33,12 @@ def create_df_from_disjoint_series(series_list: List[cudf.Series]): def create_df_from_disjoint_arrays(array_dict: Dict[str, cupy.array]): + series_dict = {} for k in list(array_dict.keys()): - array_dict[k] = cudf.Series(array_dict[k], name=k) + if array_dict[k] is not None: + series_dict[k] = cudf.Series(array_dict[k], name=k) - return create_df_from_disjoint_series(list(array_dict.values())) + return create_df_from_disjoint_series(list(series_dict.values())) def _write_samples_to_parquet_csr( diff --git a/python/cugraph/cugraph/gnn/data_loading/dist_io/__init__.py b/python/cugraph/cugraph/gnn/data_loading/dist_io/__init__.py new file mode 100644 index 00000000000..29bb5489be2 --- /dev/null +++ b/python/cugraph/cugraph/gnn/data_loading/dist_io/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .reader import BufferedSampleReader, DistSampleReader +from .writer import DistSampleWriter diff --git a/python/cugraph/cugraph/gnn/data_loading/dist_io/reader.py b/python/cugraph/cugraph/gnn/data_loading/dist_io/reader.py new file mode 100644 index 00000000000..69f909e7a8d --- /dev/null +++ b/python/cugraph/cugraph/gnn/data_loading/dist_io/reader.py @@ -0,0 +1,144 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import os +import re + +import cudf + +from typing import Callable, Iterator, Tuple, Dict, Optional + +from cugraph.utilities.utils import import_optional, MissingModule + +# Prevent PyTorch from being imported and causing an OOM error +torch = MissingModule("torch") + + +class DistSampleReader: + def __init__( + self, + directory: str, + *, + format: str = "parquet", + rank: Optional[int] = None, + filelist=None, + ): + torch = import_optional("torch") + + self.__format = format + self.__directory = directory + + if format != "parquet": + raise ValueError("Invalid format (currently supported: 'parquet')") + + if filelist is None: + files = os.listdir(directory) + ex = re.compile(r"batch\=([0-9]+)\.([0-9]+)\-([0-9]+)\.([0-9]+)\.parquet") + filematch = [ex.match(f) for f in files] + filematch = [f for f in filematch if f] + + if rank is not None: + filematch = [f for f in filematch if int(f[1]) == rank] + + batch_count = sum([int(f[4]) - int(f[2]) + 1 for f in filematch]) + filematch = sorted(filematch, key=lambda f: int(f[2]), reverse=True) + + self.__files = filematch + else: + self.__files = list(filelist) + + if rank is None: + self.__batch_count = batch_count + else: + # TODO maybe remove this in favor of warning users that they are + # probably going to cause a hang, instead of attempting to resolve + # the hang for them by dropping batches. + batch_count = torch.tensor([batch_count], device="cuda") + torch.distributed.all_reduce(batch_count, torch.distributed.ReduceOp.MIN) + self.__batch_count = int(batch_count) + + def __iter__(self): + return self + + def __next__(self) -> Tuple[Dict[str, "torch.Tensor"], int, int]: + torch = import_optional("torch") + + if len(self.__files) > 0: + f = self.__files.pop() + fname = f[0] + start_inclusive = int(f[2]) + end_inclusive = int(f[4]) + + if (end_inclusive - start_inclusive + 1) > self.__batch_count: + end_inclusive = start_inclusive + self.__batch_count - 1 + self.__batch_count = 0 + else: + self.__batch_count -= end_inclusive - start_inclusive + 1 + + df = cudf.read_parquet(os.path.join(self.__directory, fname)) + tensors = {} + for col in list(df.columns): + s = df[col].dropna() + if len(s) > 0: + tensors[col] = torch.as_tensor(s, device="cuda") + df.drop(col, axis=1, inplace=True) + + return tensors, start_inclusive, end_inclusive + + raise StopIteration + + +class BufferedSampleReader: + def __init__( + self, + nodes_call_groups: list["torch.Tensor"], + sample_fn: Callable[..., Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]], + *args, + **kwargs, + ): + self.__sample_args = args + self.__sample_kwargs = kwargs + + self.__nodes_call_groups = iter(nodes_call_groups) + self.__sample_fn = sample_fn + self.__current_call_id = 0 + self.__current_reader = None + + def __next__(self) -> Tuple[Dict[str, "torch.Tensor"], int, int]: + new_reader = False + + if self.__current_reader is None: + new_reader = True + else: + try: + out = next(self.__current_reader) + except StopIteration: + new_reader = True + + if new_reader: + # Will trigger StopIteration if there are no more call groups + self.__current_reader = self.__sample_fn( + self.__current_call_id, + next(self.__nodes_call_groups), + *self.__sample_args, + **self.__sample_kwargs, + ) + + self.__current_call_id += 1 + out = next(self.__current_reader) + + return out + + def __iter__(self) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: + return self diff --git a/python/cugraph/cugraph/gnn/data_loading/dist_io/writer.py b/python/cugraph/cugraph/gnn/data_loading/dist_io/writer.py new file mode 100644 index 00000000000..f8ad4719a76 --- /dev/null +++ b/python/cugraph/cugraph/gnn/data_loading/dist_io/writer.py @@ -0,0 +1,321 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from math import ceil + + +import cupy + +from cugraph.utilities.utils import MissingModule +from cugraph.gnn.data_loading.dist_io import DistSampleReader + +from cugraph.gnn.data_loading.bulk_sampler_io import create_df_from_disjoint_arrays + +from typing import Iterator, Tuple, Dict + +torch = MissingModule("torch") + + +class DistSampleWriter: + def __init__( + self, + directory: str, + *, + batches_per_partition: int = 256, + format: str = "parquet", + ): + """ + Parameters + ---------- + directory: str (required) + The directory where samples will be written. This + writer can only write to disk. + batches_per_partition: int (optional, default=256) + The number of batches to write in a single file. + format: str (optional, default='parquet') + The file format of the output files containing the + sampled minibatches. Currently, only parquet format + is supported. + """ + if format != "parquet": + raise ValueError("Invalid format (currently supported: 'parquet')") + + self.__format = format + self.__directory = directory + self.__batches_per_partition = batches_per_partition + + @property + def _format(self): + return self.__format + + @property + def _directory(self): + return self.__directory + + @property + def _batches_per_partition(self): + return self.__batches_per_partition + + def get_reader( + self, rank: int + ) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: + """ + Returns an iterator over sampled data. + """ + + # currently only disk reading is supported + return DistSampleReader(self._directory, format=self._format, rank=rank) + + def __write_minibatches_coo(self, minibatch_dict): + has_edge_ids = minibatch_dict["edge_id"] is not None + has_edge_types = minibatch_dict["edge_type"] is not None + has_weights = minibatch_dict["weight"] is not None + + if minibatch_dict["renumber_map"] is None: + raise ValueError( + "Distributed sampling without renumbering is not supported" + ) + + # Quit if there are no batches to write. + if len(minibatch_dict["batch_id"]) == 0: + return + + fanout_length = (len(minibatch_dict["label_hop_offsets"]) - 1) // len( + minibatch_dict["batch_id"] + ) + + for p in range( + 0, int(ceil(len(minibatch_dict["batch_id"]) / self.__batches_per_partition)) + ): + partition_start = p * (self.__batches_per_partition) + partition_end = (p + 1) * (self.__batches_per_partition) + + label_hop_offsets_array_p = minibatch_dict["label_hop_offsets"][ + partition_start * fanout_length : partition_end * fanout_length + 1 + ] + + batch_id_array_p = minibatch_dict["batch_id"][partition_start:partition_end] + start_batch_id = batch_id_array_p[0] + + input_offsets_p = minibatch_dict["input_offsets"][ + partition_start : (partition_end + 1) + ] + input_index_p = minibatch_dict["input_index"][ + input_offsets_p[0] : input_offsets_p[-1] + ] + edge_inverse_p = ( + minibatch_dict["edge_inverse"][ + (input_offsets_p[0] * 2) : (input_offsets_p[-1] * 2) + ] + if "edge_inverse" in minibatch_dict + else None + ) + + start_ix, end_ix = label_hop_offsets_array_p[[0, -1]] + majors_array_p = minibatch_dict["majors"][start_ix:end_ix] + minors_array_p = minibatch_dict["minors"][start_ix:end_ix] + edge_id_array_p = ( + minibatch_dict["edge_id"][start_ix:end_ix] + if has_edge_ids + else cupy.array([], dtype="int64") + ) + edge_type_array_p = ( + minibatch_dict["edge_type"][start_ix:end_ix] + if has_edge_types + else cupy.array([], dtype="int32") + ) + weight_array_p = ( + minibatch_dict["weight"][start_ix:end_ix] + if has_weights + else cupy.array([], dtype="float32") + ) + + # create the renumber map offsets + renumber_map_offsets_array_p = minibatch_dict["renumber_map_offsets"][ + partition_start : partition_end + 1 + ] + + renumber_map_start_ix, renumber_map_end_ix = renumber_map_offsets_array_p[ + [0, -1] + ] + + renumber_map_array_p = minibatch_dict["renumber_map"][ + renumber_map_start_ix:renumber_map_end_ix + ] + + results_dataframe_p = create_df_from_disjoint_arrays( + { + "majors": majors_array_p, + "minors": minors_array_p, + "map": renumber_map_array_p, + "label_hop_offsets": label_hop_offsets_array_p, + "weight": weight_array_p, + "edge_id": edge_id_array_p, + "edge_type": edge_type_array_p, + "renumber_map_offsets": renumber_map_offsets_array_p, + "input_index": input_index_p, + "input_offsets": input_offsets_p, + "edge_inverse": edge_inverse_p, + } + ) + + end_batch_id = start_batch_id + len(batch_id_array_p) - 1 + rank = minibatch_dict["rank"] if "rank" in minibatch_dict else 0 + + full_output_path = os.path.join( + self.__directory, + f"batch={rank:05d}.{start_batch_id:08d}-" + f"{rank:05d}.{end_batch_id:08d}.parquet", + ) + + results_dataframe_p.to_parquet( + full_output_path, + compression=None, + index=False, + force_nullable_schema=True, + ) + + def __write_minibatches_csr(self, minibatch_dict): + has_edge_ids = minibatch_dict["edge_id"] is not None + has_edge_types = minibatch_dict["edge_type"] is not None + has_weights = minibatch_dict["weight"] is not None + + if minibatch_dict["renumber_map"] is None: + raise ValueError( + "Distributed sampling without renumbering is not supported" + ) + + # Quit if there are no batches to write. + if len(minibatch_dict["batch_id"]) == 0: + return + + fanout_length = (len(minibatch_dict["label_hop_offsets"]) - 1) // len( + minibatch_dict["batch_id"] + ) + + for p in range( + 0, int(ceil(len(minibatch_dict["batch_id"]) / self.__batches_per_partition)) + ): + partition_start = p * (self.__batches_per_partition) + partition_end = (p + 1) * (self.__batches_per_partition) + + label_hop_offsets_array_p = minibatch_dict["label_hop_offsets"][ + partition_start * fanout_length : partition_end * fanout_length + 1 + ] + + batch_id_array_p = minibatch_dict["batch_id"][partition_start:partition_end] + start_batch_id = batch_id_array_p[0] + + input_offsets_p = minibatch_dict["input_offsets"][ + partition_start : (partition_end + 1) + ] + input_index_p = minibatch_dict["input_index"][ + input_offsets_p[0] : input_offsets_p[-1] + ] + edge_inverse_p = ( + minibatch_dict["edge_inverse"][ + (input_offsets_p[0] * 2) : (input_offsets_p[-1] * 2) + ] + if "edge_inverse" in minibatch_dict + else None + ) + + # major offsets and minors + ( + major_offsets_start_incl, + major_offsets_end_incl, + ) = label_hop_offsets_array_p[[0, -1]] + + start_ix, end_ix = minibatch_dict["major_offsets"][ + [major_offsets_start_incl, major_offsets_end_incl] + ] + + major_offsets_array_p = minibatch_dict["major_offsets"][ + major_offsets_start_incl : major_offsets_end_incl + 1 + ] + + minors_array_p = minibatch_dict["minors"][start_ix:end_ix] + edge_id_array_p = ( + minibatch_dict["edge_id"][start_ix:end_ix] + if has_edge_ids + else cupy.array([], dtype="int64") + ) + edge_type_array_p = ( + minibatch_dict["edge_type"][start_ix:end_ix] + if has_edge_types + else cupy.array([], dtype="int32") + ) + weight_array_p = ( + minibatch_dict["weight"][start_ix:end_ix] + if has_weights + else cupy.array([], dtype="float32") + ) + + # create the renumber map offsets + renumber_map_offsets_array_p = minibatch_dict["renumber_map_offsets"][ + partition_start : partition_end + 1 + ] + + renumber_map_start_ix, renumber_map_end_ix = renumber_map_offsets_array_p[ + [0, -1] + ] + + renumber_map_array_p = minibatch_dict["renumber_map"][ + renumber_map_start_ix:renumber_map_end_ix + ] + + results_dataframe_p = create_df_from_disjoint_arrays( + { + "major_offsets": major_offsets_array_p, + "minors": minors_array_p, + "map": renumber_map_array_p, + "label_hop_offsets": label_hop_offsets_array_p, + "weight": weight_array_p, + "edge_id": edge_id_array_p, + "edge_type": edge_type_array_p, + "renumber_map_offsets": renumber_map_offsets_array_p, + "input_index": input_index_p, + "input_offsets": input_offsets_p, + "edge_inverse": edge_inverse_p, + } + ) + + end_batch_id = start_batch_id + len(batch_id_array_p) - 1 + rank = minibatch_dict["rank"] if "rank" in minibatch_dict else 0 + + full_output_path = os.path.join( + self.__directory, + f"batch={rank:05d}.{start_batch_id:08d}-" + f"{rank:05d}.{end_batch_id:08d}.parquet", + ) + + results_dataframe_p.to_parquet( + full_output_path, + compression=None, + index=False, + force_nullable_schema=True, + ) + + def write_minibatches(self, minibatch_dict): + if (minibatch_dict["majors"] is not None) and ( + minibatch_dict["minors"] is not None + ): + self.__write_minibatches_coo(minibatch_dict) + elif (minibatch_dict["major_offsets"] is not None) and ( + minibatch_dict["minors"] is not None + ): + self.__write_minibatches_csr(minibatch_dict) + else: + raise ValueError("invalid columns") diff --git a/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py b/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py index 52ffd8fadfd..0ff38741e1a 100644 --- a/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py +++ b/python/cugraph/cugraph/gnn/data_loading/dist_sampler.py @@ -11,8 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import re import warnings from math import ceil from functools import reduce @@ -27,348 +25,19 @@ from cugraph.utilities.utils import import_optional, MissingModule from cugraph.gnn.comms import cugraph_comms_get_raft_handle -from cugraph.gnn.data_loading.bulk_sampler_io import create_df_from_disjoint_arrays + +from cugraph.gnn.data_loading.dist_io import BufferedSampleReader +from cugraph.gnn.data_loading.dist_io import DistSampleWriter torch = MissingModule("torch") TensorType = Union["torch.Tensor", cupy.ndarray, cudf.Series] -class DistSampleReader: - def __init__( - self, - directory: str, - *, - format: str = "parquet", - rank: Optional[int] = None, - filelist=None, - ): - torch = import_optional("torch") - - self.__format = format - self.__directory = directory - - if format != "parquet": - raise ValueError("Invalid format (currently supported: 'parquet')") - - if filelist is None: - files = os.listdir(directory) - ex = re.compile(r"batch\=([0-9]+)\.([0-9]+)\-([0-9]+)\.([0-9]+)\.parquet") - filematch = [ex.match(f) for f in files] - filematch = [f for f in filematch if f] - - if rank is not None: - filematch = [f for f in filematch if int(f[1]) == rank] - - batch_count = sum([int(f[4]) - int(f[2]) + 1 for f in filematch]) - filematch = sorted(filematch, key=lambda f: int(f[2]), reverse=True) - - self.__files = filematch - else: - self.__files = list(filelist) - - if rank is None: - self.__batch_count = batch_count - else: - batch_count = torch.tensor([batch_count], device="cuda") - torch.distributed.all_reduce(batch_count, torch.distributed.ReduceOp.MIN) - self.__batch_count = int(batch_count) - - def __iter__(self): - return self - - def __next__(self): - torch = import_optional("torch") - - if len(self.__files) > 0: - f = self.__files.pop() - fname = f[0] - start_inclusive = int(f[2]) - end_inclusive = int(f[4]) - - if (end_inclusive - start_inclusive + 1) > self.__batch_count: - end_inclusive = start_inclusive + self.__batch_count - 1 - self.__batch_count = 0 - else: - self.__batch_count -= end_inclusive - start_inclusive + 1 - - df = cudf.read_parquet(os.path.join(self.__directory, fname)) - tensors = {} - for col in list(df.columns): - s = df[col].dropna() - if len(s) > 0: - tensors[col] = torch.as_tensor(s, device="cuda") - df.drop(col, axis=1, inplace=True) - - return tensors, start_inclusive, end_inclusive - - raise StopIteration - - -class DistSampleWriter: - def __init__( - self, - directory: str, - *, - batches_per_partition: int = 256, - format: str = "parquet", - ): - """ - Parameters - ---------- - directory: str (required) - The directory where samples will be written. This - writer can only write to disk. - batches_per_partition: int (optional, default=256) - The number of batches to write in a single file. - format: str (optional, default='parquet') - The file format of the output files containing the - sampled minibatches. Currently, only parquet format - is supported. - """ - if format != "parquet": - raise ValueError("Invalid format (currently supported: 'parquet')") - - self.__format = format - self.__directory = directory - self.__batches_per_partition = batches_per_partition - - @property - def _format(self): - return self.__format - - @property - def _directory(self): - return self.__directory - - @property - def _batches_per_partition(self): - return self.__batches_per_partition - - def get_reader( - self, rank: int - ) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: - """ - Returns an iterator over sampled data. - """ - - # currently only disk reading is supported - return DistSampleReader(self._directory, format=self._format, rank=rank) - - def __write_minibatches_coo(self, minibatch_dict): - has_edge_ids = minibatch_dict["edge_id"] is not None - has_edge_types = minibatch_dict["edge_type"] is not None - has_weights = minibatch_dict["weight"] is not None - - if minibatch_dict["renumber_map"] is None: - raise ValueError( - "Distributed sampling without renumbering is not supported" - ) - - # Quit if there are no batches to write. - if len(minibatch_dict["batch_id"]) == 0: - return - - fanout_length = (len(minibatch_dict["label_hop_offsets"]) - 1) // len( - minibatch_dict["batch_id"] - ) - rank_batch_offset = minibatch_dict["batch_id"][0] - - for p in range( - 0, int(ceil(len(minibatch_dict["batch_id"]) / self.__batches_per_partition)) - ): - partition_start = p * (self.__batches_per_partition) - partition_end = (p + 1) * (self.__batches_per_partition) - - label_hop_offsets_array_p = minibatch_dict["label_hop_offsets"][ - partition_start * fanout_length : partition_end * fanout_length + 1 - ] - - batch_id_array_p = minibatch_dict["batch_id"][partition_start:partition_end] - start_batch_id = batch_id_array_p[0] - rank_batch_offset - - start_ix, end_ix = label_hop_offsets_array_p[[0, -1]] - majors_array_p = minibatch_dict["majors"][start_ix:end_ix] - minors_array_p = minibatch_dict["minors"][start_ix:end_ix] - edge_id_array_p = ( - minibatch_dict["edge_id"][start_ix:end_ix] - if has_edge_ids - else cupy.array([], dtype="int64") - ) - edge_type_array_p = ( - minibatch_dict["edge_type"][start_ix:end_ix] - if has_edge_types - else cupy.array([], dtype="int32") - ) - weight_array_p = ( - minibatch_dict["weight"][start_ix:end_ix] - if has_weights - else cupy.array([], dtype="float32") - ) - - # create the renumber map offsets - renumber_map_offsets_array_p = minibatch_dict["renumber_map_offsets"][ - partition_start : partition_end + 1 - ] - - renumber_map_start_ix, renumber_map_end_ix = renumber_map_offsets_array_p[ - [0, -1] - ] - - renumber_map_array_p = minibatch_dict["renumber_map"][ - renumber_map_start_ix:renumber_map_end_ix - ] - - results_dataframe_p = create_df_from_disjoint_arrays( - { - "majors": majors_array_p, - "minors": minors_array_p, - "map": renumber_map_array_p, - "label_hop_offsets": label_hop_offsets_array_p, - "weight": weight_array_p, - "edge_id": edge_id_array_p, - "edge_type": edge_type_array_p, - "renumber_map_offsets": renumber_map_offsets_array_p, - } - ) - - end_batch_id = start_batch_id + len(batch_id_array_p) - 1 - rank = minibatch_dict["rank"] if "rank" in minibatch_dict else 0 - - full_output_path = os.path.join( - self.__directory, - f"batch={rank:05d}.{start_batch_id:08d}-" - f"{rank:05d}.{end_batch_id:08d}.parquet", - ) - - results_dataframe_p.to_parquet( - full_output_path, - compression=None, - index=False, - force_nullable_schema=True, - ) - - def __write_minibatches_csr(self, minibatch_dict): - has_edge_ids = minibatch_dict["edge_id"] is not None - has_edge_types = minibatch_dict["edge_type"] is not None - has_weights = minibatch_dict["weight"] is not None - - if minibatch_dict["renumber_map"] is None: - raise ValueError( - "Distributed sampling without renumbering is not supported" - ) - - # Quit if there are no batches to write. - if len(minibatch_dict["batch_id"]) == 0: - return - - fanout_length = (len(minibatch_dict["label_hop_offsets"]) - 1) // len( - minibatch_dict["batch_id"] - ) - - for p in range( - 0, int(ceil(len(minibatch_dict["batch_id"]) / self.__batches_per_partition)) - ): - partition_start = p * (self.__batches_per_partition) - partition_end = (p + 1) * (self.__batches_per_partition) - - label_hop_offsets_array_p = minibatch_dict["label_hop_offsets"][ - partition_start * fanout_length : partition_end * fanout_length + 1 - ] - - batch_id_array_p = minibatch_dict["batch_id"][partition_start:partition_end] - start_batch_id = batch_id_array_p[0] - - # major offsets and minors - ( - major_offsets_start_incl, - major_offsets_end_incl, - ) = label_hop_offsets_array_p[[0, -1]] - - start_ix, end_ix = minibatch_dict["major_offsets"][ - [major_offsets_start_incl, major_offsets_end_incl] - ] - - major_offsets_array_p = minibatch_dict["major_offsets"][ - major_offsets_start_incl : major_offsets_end_incl + 1 - ] - - minors_array_p = minibatch_dict["minors"][start_ix:end_ix] - edge_id_array_p = ( - minibatch_dict["edge_id"][start_ix:end_ix] - if has_edge_ids - else cupy.array([], dtype="int64") - ) - edge_type_array_p = ( - minibatch_dict["edge_type"][start_ix:end_ix] - if has_edge_types - else cupy.array([], dtype="int32") - ) - weight_array_p = ( - minibatch_dict["weight"][start_ix:end_ix] - if has_weights - else cupy.array([], dtype="float32") - ) - - # create the renumber map offsets - renumber_map_offsets_array_p = minibatch_dict["renumber_map_offsets"][ - partition_start : partition_end + 1 - ] - - renumber_map_start_ix, renumber_map_end_ix = renumber_map_offsets_array_p[ - [0, -1] - ] - - renumber_map_array_p = minibatch_dict["renumber_map"][ - renumber_map_start_ix:renumber_map_end_ix - ] - - results_dataframe_p = create_df_from_disjoint_arrays( - { - "major_offsets": major_offsets_array_p, - "minors": minors_array_p, - "map": renumber_map_array_p, - "label_hop_offsets": label_hop_offsets_array_p, - "weight": weight_array_p, - "edge_id": edge_id_array_p, - "edge_type": edge_type_array_p, - "renumber_map_offsets": renumber_map_offsets_array_p, - } - ) - - end_batch_id = start_batch_id + len(batch_id_array_p) - 1 - rank = minibatch_dict["rank"] if "rank" in minibatch_dict else 0 - - full_output_path = os.path.join( - self.__directory, - f"batch={rank:05d}.{start_batch_id:08d}-" - f"{rank:05d}.{end_batch_id:08d}.parquet", - ) - - results_dataframe_p.to_parquet( - full_output_path, - compression=None, - index=False, - force_nullable_schema=True, - ) - - def write_minibatches(self, minibatch_dict): - if (minibatch_dict["majors"] is not None) and ( - minibatch_dict["minors"] is not None - ): - self.__write_minibatches_coo(minibatch_dict) - elif (minibatch_dict["major_offsets"] is not None) and ( - minibatch_dict["minors"] is not None - ): - self.__write_minibatches_csr(minibatch_dict) - else: - raise ValueError("invalid columns") - - class DistSampler: def __init__( self, graph: Union[pylibcugraph.SGGraph, pylibcugraph.MGGraph], - writer: DistSampleWriter, + writer: Optional[DistSampleWriter], local_seeds_per_call: int, retain_original_seeds: bool = False, ): @@ -379,7 +48,8 @@ def __init__( The pylibcugraph graph object that will be sampled. writer: DistSampleWriter (required) The writer responsible for writing samples to disk - or, in the future, device or host memory. + or; if None, then samples will be written to memory + instead. local_seeds_per_call: int The number of seeds on this rank this sampler will process in a single sampling call. Batches will @@ -402,14 +72,6 @@ def __init__( self.__handle = None self.__retain_original_seeds = retain_original_seeds - def get_reader(self) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: - """ - Returns an iterator over sampled data. - """ - torch = import_optional("torch") - rank = torch.distributed.get_rank() if self.is_multi_gpu else None - return self.__writer.get_reader(rank) - def sample_batches( self, seeds: TensorType, @@ -564,6 +226,108 @@ def get_start_batch_offset( else: return 0, input_size_is_equal + def __sample_from_nodes_func( + self, + call_id: int, + current_seeds_and_ix: Tuple["torch.Tensor", "torch.Tensor"], + batch_id_start: int, + batch_size: int, + batches_per_call: int, + random_state: int, + assume_equal_input_size: bool, + ) -> Union[None, Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]]: + torch = import_optional("torch") + + current_seeds, current_ix = current_seeds_and_ix + + current_batches = torch.arange( + batch_id_start + call_id * batches_per_call, + batch_id_start + + call_id * batches_per_call + + int(ceil(len(current_seeds))) + + 1, + device="cuda", + dtype=torch.int32, + ) + + current_batches = current_batches.repeat_interleave(batch_size)[ + : len(current_seeds) + ] + + # do qr division to get the number of batch_size batches and the + # size of the last batch + num_full, last_count = divmod(len(current_seeds), batch_size) + input_offsets = torch.concatenate( + [ + torch.tensor([0], device="cuda", dtype=torch.int64), + torch.full((num_full,), batch_size, device="cuda", dtype=torch.int64), + torch.tensor([last_count], device="cuda", dtype=torch.int64) + if last_count > 0 + else torch.tensor([], device="cuda", dtype=torch.int64), + ] + ).cumsum(-1) + + minibatch_dict = self.sample_batches( + seeds=current_seeds, + batch_ids=current_batches, + random_state=random_state, + assume_equal_input_size=assume_equal_input_size, + ) + minibatch_dict["input_index"] = current_ix.cuda() + minibatch_dict["input_offsets"] = input_offsets + + if self.__writer is None: + # rename renumber_map -> map to match unbuffered format + minibatch_dict["map"] = minibatch_dict["renumber_map"] + del minibatch_dict["renumber_map"] + minibatch_dict = { + k: torch.as_tensor(v, device="cuda") + for k, v in minibatch_dict.items() + if v is not None + } + + return iter([(minibatch_dict, current_batches[0], current_batches[-1])]) + else: + self.__writer.write_minibatches(minibatch_dict) + return None + + def __get_call_groups( + self, + seeds: TensorType, + input_id: TensorType, + seeds_per_call: int, + assume_equal_input_size: bool = False, + ): + torch = import_optional("torch") + + # Split the input seeds into call groups. Each call group + # corresponds to one sampling call. A call group contains + # many batches. + seeds_call_groups = torch.split(seeds, seeds_per_call, dim=-1) + index_call_groups = torch.split(input_id, seeds_per_call, dim=-1) + + # Need to add empties to the list of call groups to handle the case + # where not all ranks have the same number of call groups. This + # prevents a hang since we need all ranks to make the same number + # of calls. + if not assume_equal_input_size: + num_call_groups = torch.tensor( + [len(seeds_call_groups)], device="cuda", dtype=torch.int32 + ) + torch.distributed.all_reduce( + num_call_groups, op=torch.distributed.ReduceOp.MAX + ) + seeds_call_groups = list(seeds_call_groups) + ( + [torch.tensor([], dtype=seeds.dtype, device="cuda")] + * (int(num_call_groups) - len(seeds_call_groups)) + ) + index_call_groups = list(index_call_groups) + ( + [torch.tensor([], dtype=torch.int64, device=input_id.device)] + * (int(num_call_groups) - len(index_call_groups)) + ) + + return seeds_call_groups, index_call_groups + def sample_from_nodes( self, nodes: TensorType, @@ -571,7 +335,8 @@ def sample_from_nodes( batch_size: int = 16, random_state: int = 62, assume_equal_input_size: bool = False, - ): + input_id: Optional[TensorType] = None, + ) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: """ Performs node-based sampling. Accepts a list of seed nodes, and batch size. Splits the seed list into batches, then divides the batches into call groups @@ -587,64 +352,301 @@ def sample_from_nodes( The size of each batch. random_state: int The random seed to use for sampling. + assume_equal_input_size: bool + Whether the inputs across workers should be assumed to be equal in + dimension. Skips some checks if True. + input_id: Optional[TensorType] + Input ids corresponding to the original batch tensor, if it + was permuted prior to calling this function. If present, + will be saved with the samples. """ torch = import_optional("torch") nodes = torch.as_tensor(nodes, device="cuda") + num_seeds = nodes.numel() batches_per_call = self._local_seeds_per_call // batch_size actual_seeds_per_call = batches_per_call * batch_size - # Split the input seeds into call groups. Each call group - # corresponds to one sampling call. A call group contains - # many batches. - num_seeds = len(nodes) - nodes_call_groups = torch.split(nodes, actual_seeds_per_call) + if input_id is None: + input_id = torch.arange(num_seeds, dtype=torch.int64, device="cpu") local_num_batches = int(ceil(num_seeds / batch_size)) batch_id_start, input_size_is_equal = self.get_start_batch_offset( local_num_batches, assume_equal_input_size=assume_equal_input_size ) - # Need to add empties to the list of call groups to handle the case - # where not all nodes have the same number of call groups. This - # prevents a hang since we need all ranks to make the same number - # of calls. - if not input_size_is_equal: - num_call_groups = torch.tensor( - [len(nodes_call_groups)], device="cuda", dtype=torch.int32 - ) - torch.distributed.all_reduce( - num_call_groups, op=torch.distributed.ReduceOp.MAX + nodes_call_groups, index_call_groups = self.__get_call_groups( + nodes, + input_id, + actual_seeds_per_call, + assume_equal_input_size=input_size_is_equal, + ) + + sample_args = ( + batch_id_start, + batch_size, + batches_per_call, + random_state, + input_size_is_equal, + ) + + if self.__writer is None: + # Buffered sampling + return BufferedSampleReader( + zip(nodes_call_groups, index_call_groups), + self.__sample_from_nodes_func, + *sample_args, ) - nodes_call_groups = list(nodes_call_groups) + ( - [torch.tensor([], dtype=nodes.dtype, device="cuda")] - * (int(num_call_groups) - len(nodes_call_groups)) + else: + # Unbuffered sampling + for i, current_seeds_and_ix in enumerate( + zip(nodes_call_groups, index_call_groups) + ): + self.__sample_from_nodes_func( + i, + current_seeds_and_ix, + *sample_args, + ) + + # Return a reader that points to the stored samples + rank = torch.distributed.get_rank() if self.is_multi_gpu else None + return self.__writer.get_reader(rank) + + def __sample_from_edges_func( + self, + call_id: int, + current_seeds_and_ix: Tuple["torch.Tensor", "torch.Tensor"], + batch_id_start: int, + batch_size: int, + batches_per_call: int, + random_state: int, + assume_equal_input_size: bool, + ) -> Union[None, Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]]: + torch = import_optional("torch") + + current_seeds, current_ix = current_seeds_and_ix + num_seed_edges = current_ix.numel() + + # The index gets stored as-is regardless of what makes it into + # the final batch and in what order. + # do qr division to get the number of batch_size batches and the + # size of the last batch + num_whole_batches, last_count = divmod(num_seed_edges, batch_size) + input_offsets = torch.concatenate( + [ + torch.tensor([0], device="cuda", dtype=torch.int64), + torch.full( + (num_whole_batches,), batch_size, device="cuda", dtype=torch.int64 + ), + torch.tensor([last_count], device="cuda", dtype=torch.int64) + if last_count > 0 + else torch.tensor([], device="cuda", dtype=torch.int64), + ] + ).cumsum(-1) + + current_seeds, leftover_seeds = ( + current_seeds[:, : (batch_size * num_whole_batches)], + current_seeds[:, (batch_size * num_whole_batches) :], + ) + + # For input edges, we need to translate this into unique vertices + # for each batch. + # We start by reorganizing the seed and index tensors so we can + # determine the unique vertices. This results in the expected + # src-to-dst concatenation for each batch + current_seeds = torch.concat( + [ + current_seeds[0].reshape((-1, batch_size)), + current_seeds[1].reshape((-1, batch_size)), + ], + axis=-1, + ) + + # The returned unique values must be sorted or else the inverse won't line up + # In the future this may be a good target for a C++ function + # Each element is a tuple of (unique, index, inverse) + # The seeds must be presorted with a stable sort prior to calling + # unique_consecutive in order to support negative sampling. This is + # because if we put positive edges after negative ones, then we may + # inadvertently turn a true positive into a false negative. + y = ( + torch.sort( + t, + stable=True, ) + for t in current_seeds + ) + z = ((v, torch.sort(i)[1]) for v, i in y) - # Make a call to sample_batches for each call group - for i, current_seeds in enumerate(nodes_call_groups): - current_batches = torch.arange( - batch_id_start + i * batches_per_call, - batch_id_start - + i * batches_per_call - + int(ceil(len(current_seeds))) - + 1, - device="cuda", - dtype=torch.int32, + u = [ + ( + torch.unique_consecutive( + t, + return_inverse=True, + ), + i, ) + for t, i in z + ] - current_batches = current_batches.repeat_interleave(batch_size)[ - : len(current_seeds) + if len(u) > 0: + current_seeds = torch.concat([a[0] for a, _ in u]) + current_inv = torch.concat([a[1][i] for a, i in u]) + current_batches = torch.concat( + [ + torch.full( + (a[0].numel(),), + i + batch_id_start + (call_id * batches_per_call), + device="cuda", + dtype=torch.int32, + ) + for i, (a, _) in enumerate(u) + ] + ) + else: + current_seeds = torch.tensor([], device="cuda", dtype=torch.int64) + current_inv = torch.tensor([], device="cuda", dtype=torch.int64) + current_batches = torch.tensor([], device="cuda", dtype=torch.int32) + del u + + # Join with the leftovers + leftover_seeds, lyi = torch.sort( + leftover_seeds.flatten(), + stable=True, + ) + lz = torch.sort(lyi)[1] + leftover_seeds, lui = leftover_seeds.unique_consecutive(return_inverse=True) + leftover_inv = lui[lz] + + current_seeds = torch.concat([current_seeds, leftover_seeds]) + current_inv = torch.concat([current_inv, leftover_inv]) + current_batches = torch.concat( + [ + current_batches, + torch.full( + (leftover_seeds.numel(),), + (current_batches[-1] + 1) if current_batches.numel() > 0 else 0, + device="cuda", + dtype=torch.int32, + ), ] + ) + del leftover_seeds + del lz + del lui + + minibatch_dict = self.sample_batches( + seeds=current_seeds, + batch_ids=current_batches, + random_state=random_state, + assume_equal_input_size=assume_equal_input_size, + ) + minibatch_dict["input_index"] = current_ix.cuda() + minibatch_dict["input_offsets"] = input_offsets + minibatch_dict[ + "edge_inverse" + ] = current_inv # (2 * batch_size) entries per batch + + if self.__writer is None: + # rename renumber_map -> map to match unbuffered format + minibatch_dict["map"] = minibatch_dict["renumber_map"] + del minibatch_dict["renumber_map"] + minibatch_dict = { + k: torch.as_tensor(v, device="cuda") + for k, v in minibatch_dict.items() + if v is not None + } + + return iter([(minibatch_dict, current_batches[0], current_batches[-1])]) + else: + self.__writer.write_minibatches(minibatch_dict) + return None - minibatch_dict = self.sample_batches( - seeds=current_seeds, - batch_ids=current_batches, - random_state=random_state, - assume_equal_input_size=input_size_is_equal, + def sample_from_edges( + self, + edges: TensorType, + *, + batch_size: int = 16, + random_state: int = 62, + assume_equal_input_size: bool = False, + input_id: Optional[TensorType] = None, + ) -> Iterator[Tuple[Dict[str, "torch.Tensor"], int, int]]: + """ + Performs sampling starting from seed edges. + + Parameters + ---------- + edges: TensorType + 2 x (# edges) tensor of edges to sample from. + Standard src/dst format. This will be converted + to a list of seed nodes. + batch_size: int + The size of each batch. + random_state: int + The random seed to use for sampling. + assume_equal_input_size: bool + Whether this function should assume that inputs + are equal across ranks. Skips some potentially + slow steps if True. + input_id: Optional[TensorType] + Input ids corresponding to the original batch tensor, if it + was permuted prior to calling this function. If present, + will be saved with the samples. + """ + + torch = import_optional("torch") + + edges = torch.as_tensor(edges, device="cuda") + num_seed_edges = edges.shape[-1] + + batches_per_call = self._local_seeds_per_call // batch_size + actual_seed_edges_per_call = batches_per_call * batch_size + + if input_id is None: + input_id = torch.arange(len(edges), dtype=torch.int64, device="cpu") + + local_num_batches = int(ceil(num_seed_edges / batch_size)) + batch_id_start, input_size_is_equal = self.get_start_batch_offset( + local_num_batches, assume_equal_input_size=assume_equal_input_size + ) + + edges_call_groups, index_call_groups = self.__get_call_groups( + edges, + input_id, + actual_seed_edges_per_call, + assume_equal_input_size=input_size_is_equal, + ) + + sample_args = ( + batch_id_start, + batch_size, + batches_per_call, + random_state, + input_size_is_equal, + ) + + if self.__writer is None: + # Buffered sampling + return BufferedSampleReader( + zip(edges_call_groups, index_call_groups), + self.__sample_from_edges_func, + *sample_args, ) - self.__writer.write_minibatches(minibatch_dict) + else: + # Unbuffered sampling + for i, current_seeds_and_ix in enumerate( + zip(edges_call_groups, index_call_groups) + ): + self.__sample_from_edges_func( + i, + current_seeds_and_ix, + *sample_args, + ) + + # Return a reader that points to the stored samples + rank = torch.distributed.get_rank() if self.is_multi_gpu else None + return self.__writer.get_reader(rank) @property def is_multi_gpu(self): @@ -709,6 +711,8 @@ def __init__( # sampling. So setting the function here is safe. In the future, # if libcugraph allows setting a new attribute, this API might # change. + # TODO allow func to be a call to a future remote sampling API + # if the provided graph is in another process (rapidsai/cugraph#4623). self.__func = ( pylibcugraph.biased_neighbor_sample if biased diff --git a/python/cugraph/cugraph/tests/sampling/test_dist_sampler.py b/python/cugraph/cugraph/tests/sampling/test_dist_sampler.py index 70b20e7baec..64db0232fb1 100644 --- a/python/cugraph/cugraph/tests/sampling/test_dist_sampler.py +++ b/python/cugraph/cugraph/tests/sampling/test_dist_sampler.py @@ -20,6 +20,7 @@ from cugraph.datasets import karate from cugraph.gnn import UniformNeighborSampler, DistSampleWriter +from cugraph.gnn.data_loading.bulk_sampler_io import create_df_from_disjoint_arrays from pylibcugraph import SGGraph, ResourceHandle, GraphProperties @@ -41,7 +42,7 @@ @pytest.fixture -def karate_graph(): +def karate_graph() -> SGGraph: el = karate.get_edgelist().reset_index().rename(columns={"index": "eid"}) G = SGGraph( ResourceHandle(), @@ -101,3 +102,60 @@ def test_dist_sampler_simple( assert original_el.dst.iloc[edge_id.iloc[i]] == dst.iloc[i] shutil.rmtree(samples_path) + + +@pytest.mark.sg +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.parametrize("seeds_per_call", [4, 5, 10]) +@pytest.mark.parametrize("compression", ["COO", "CSR"]) +def test_dist_sampler_buffered_in_memory( + scratch_dir: str, karate_graph: SGGraph, seeds_per_call: int, compression: str +): + G = karate_graph + + samples_path = os.path.join(scratch_dir, "test_bulk_sampler_buffered_in_memory") + create_directory_with_overwrite(samples_path) + + seeds = cupy.arange(10, dtype="int64") + + unbuffered_sampler = UniformNeighborSampler( + G, + writer=DistSampleWriter(samples_path), + local_seeds_per_call=seeds_per_call, + compression=compression, + ) + + buffered_sampler = UniformNeighborSampler( + G, + writer=None, + local_seeds_per_call=seeds_per_call, + compression=compression, + ) + + unbuffered_results = unbuffered_sampler.sample_from_nodes( + seeds, + batch_size=4, + ) + + unbuffered_results = [ + (create_df_from_disjoint_arrays(r[0]), r[1], r[2]) for r in unbuffered_results + ] + + buffered_results = buffered_sampler.sample_from_nodes(seeds, batch_size=4) + buffered_results = [ + (create_df_from_disjoint_arrays(r[0]), r[1], r[2]) for r in buffered_results + ] + + assert len(buffered_results) == len(unbuffered_results) + + for k in range(len(buffered_results)): + br, bs, be = buffered_results[k] + ur, us, ue = unbuffered_results[k] + + assert bs == us + assert be == ue + + for col in ur.columns: + assert (br[col].dropna() == ur[col].dropna()).all() + + shutil.rmtree(samples_path) diff --git a/python/cugraph/cugraph/tests/sampling/test_dist_sampler_mg.py b/python/cugraph/cugraph/tests/sampling/test_dist_sampler_mg.py index a1c32938994..5bb541d6cf3 100644 --- a/python/cugraph/cugraph/tests/sampling/test_dist_sampler_mg.py +++ b/python/cugraph/cugraph/tests/sampling/test_dist_sampler_mg.py @@ -18,6 +18,8 @@ import cupy import cudf +from typing import Any + from cugraph.datasets import karate from cugraph.gnn import ( UniformNeighborSampler, @@ -27,6 +29,7 @@ cugraph_comms_init, cugraph_comms_shutdown, ) +from cugraph.gnn.data_loading.bulk_sampler_io import create_df_from_disjoint_arrays from pylibcugraph import MGGraph, ResourceHandle, GraphProperties from cugraph.utilities.utils import ( @@ -235,3 +238,80 @@ def test_dist_sampler_uneven(scratch_dir, batch_size, fanout, seeds_per_call): assert original_el.dst.iloc[edge_id.iloc[i]] == dst.iloc[i] shutil.rmtree(samples_path) + + +def run_test_dist_sampler_buffered_in_memory( + rank: int, + world_size: int, + uid: Any, + samples_path: str, + seeds_per_call: int, + compression: str, +): + init_pytorch(rank, world_size) + cugraph_comms_init(rank, world_size, uid, device=rank) + + G = karate_mg_graph(rank, world_size) + + num_seeds = 8 + seeds = cupy.random.randint(0, 34, num_seeds, dtype="int64") + + unbuffered_sampler = UniformNeighborSampler( + G, + writer=DistSampleWriter(samples_path), + local_seeds_per_call=seeds_per_call, + compression=compression, + ) + + buffered_sampler = UniformNeighborSampler( + G, + writer=None, + local_seeds_per_call=seeds_per_call, + compression=compression, + ) + + unbuffered_results = unbuffered_sampler.sample_from_nodes( + seeds, + batch_size=4, + ) + + unbuffered_results = [ + (create_df_from_disjoint_arrays(r[0]), r[1], r[2]) for r in unbuffered_results + ] + + buffered_results = buffered_sampler.sample_from_nodes(seeds, batch_size=4) + buffered_results = [ + (create_df_from_disjoint_arrays(r[0]), r[1], r[2]) for r in buffered_results + ] + + assert len(buffered_results) == len(unbuffered_results) + + for k in range(len(buffered_results)): + br, bs, be = buffered_results[k] + ur, us, ue = unbuffered_results[k] + + assert bs == us + assert be == ue + + for col in ur.columns: + assert (br[col].dropna() == ur[col].dropna()).all() + + +@pytest.mark.mg +@pytest.mark.skipif(isinstance(torch, MissingModule), reason="torch not available") +@pytest.mark.parametrize("seeds_per_call", [4, 5, 10]) +@pytest.mark.parametrize("compression", ["COO", "CSR"]) +def test_dist_sampler_buffered_in_memory(scratch_dir, seeds_per_call, compression): + uid = cugraph_comms_create_unique_id() + + samples_path = os.path.join(scratch_dir, "test_bulk_sampler_buffered_in_memory_mg") + create_directory_with_overwrite(samples_path) + + world_size = torch.cuda.device_count() + torch.multiprocessing.spawn( + run_test_dist_sampler_buffered_in_memory, + args=(world_size, uid, samples_path, seeds_per_call, compression), + nprocs=world_size, + ) + + shutil.rmtree(samples_path) diff --git a/python/pylibcugraph/pylibcugraph/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/CMakeLists.txt index 514caeac6aa..9f1b9924336 100644 --- a/python/pylibcugraph/pylibcugraph/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/CMakeLists.txt @@ -55,6 +55,7 @@ set(cython_sources two_hop_neighbors.pyx uniform_neighbor_sample.pyx biased_neighbor_sample.pyx + negative_sampling.pyx uniform_random_walks.pyx utils.pyx weakly_connected_components.pyx diff --git a/python/pylibcugraph/pylibcugraph/__init__.py b/python/pylibcugraph/pylibcugraph/__init__.py index 8a8923827b8..26fa3f64ddd 100644 --- a/python/pylibcugraph/pylibcugraph/__init__.py +++ b/python/pylibcugraph/pylibcugraph/__init__.py @@ -41,6 +41,8 @@ from pylibcugraph.biased_neighbor_sample import biased_neighbor_sample +from pylibcugraph.negative_sampling import negative_sampling + from pylibcugraph.core_number import core_number from pylibcugraph.k_core import k_core diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/sampling_algorithms.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/sampling_algorithms.pxd index 4a707db03c5..c982b12665a 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/sampling_algorithms.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/sampling_algorithms.pxd @@ -112,10 +112,10 @@ cdef extern from "cugraph_c/sampling_algorithms.h": const cugraph_resource_handle_t* handle, cugraph_rng_state_t* rng_state, cugraph_graph_t* graph, - size_t num_samples, const cugraph_type_erased_device_array_view_t* vertices, const cugraph_type_erased_device_array_view_t* src_bias, const cugraph_type_erased_device_array_view_t* dst_bias, + size_t num_samples, bool_t remove_duplicates, bool_t remove_false_negatives, bool_t exact_number_of_samples, diff --git a/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt index 1ca169c5869..22f07939db0 100644 --- a/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (c) 2022, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ set(cython_sources sampling_result.pyx + coo.pyx ) set(linked_libraries cugraph::cugraph;cugraph::cugraph_c) diff --git a/python/pylibcugraph/pylibcugraph/internal_types/coo.pxd b/python/pylibcugraph/pylibcugraph/internal_types/coo.pxd new file mode 100644 index 00000000000..129b0be4dbe --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/internal_types/coo.pxd @@ -0,0 +1,28 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + + +from pylibcugraph._cugraph_c.coo cimport ( + cugraph_coo_t, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, +) + +cdef class COO: + cdef cugraph_coo_t* c_coo_ptr + cdef set_ptr(self, cugraph_coo_t* ptr) + cdef get_array(self, cugraph_type_erased_device_array_view_t* ptr) diff --git a/python/pylibcugraph/pylibcugraph/internal_types/coo.pyx b/python/pylibcugraph/pylibcugraph/internal_types/coo.pyx new file mode 100644 index 00000000000..64d10c22eaf --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/internal_types/coo.pyx @@ -0,0 +1,96 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.coo cimport ( + cugraph_coo_t, + cugraph_coo_free, + cugraph_coo_get_sources, + cugraph_coo_get_destinations, + cugraph_coo_get_edge_weights, + cugraph_coo_get_edge_id, + cugraph_coo_get_edge_type, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, +) +from pylibcugraph.utils cimport create_cupy_array_view_for_device_ptr + +cdef class COO: + """ + Cython interface to a cugraph_coo_t pointer. Instances of this + call will take ownership of the pointer and free it under standard python + GC rules (ie. when all references to it are no longer present). + + This class provides methods to return non-owning cupy ndarrays for the + corresponding array members. Returning these cupy arrays increments the ref + count on the COO instances from which the cupy arrays are + referencing. + """ + def __cinit__(self): + # This COO instance owns sample_result_ptr now. It will be + # freed when this instance is deleted (see __dealloc__()) + self.c_coo_ptr = NULL + + def __dealloc__(self): + if self.c_coo_ptr is not NULL: + cugraph_coo_free(self.c_coo_ptr) + + cdef set_ptr(self, cugraph_coo_t* ptr): + self.c_coo_ptr = ptr + + cdef get_array(self, cugraph_type_erased_device_array_view_t* ptr): + if ptr is NULL: + return None + + return create_cupy_array_view_for_device_ptr( + ptr, + self, + ) + + def get_sources(self): + if self.c_coo_ptr is NULL: + raise ValueError("pointer not set, must call set_ptr() with a " + "non-NULL value first.") + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_coo_get_sources(self.c_coo_ptr) + return self.get_array(ptr) + + def get_destinations(self): + if self.c_coo_ptr is NULL: + raise ValueError("pointer not set, must call set_ptr() with a " + "non-NULL value first.") + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_coo_get_destinations(self.c_coo_ptr) + return self.get_array(ptr) + + def get_edge_ids(self): + if self.c_coo_ptr is NULL: + raise ValueError("pointer not set, must call set_ptr() with a " + "non-NULL value first.") + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_coo_get_edge_id(self.c_coo_ptr) + return self.get_array(ptr) + + def get_edge_types(self): + if self.c_coo_ptr is NULL: + raise ValueError("pointer not set, must call set_ptr() with a " + "non-NULL value first.") + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_coo_get_edge_type(self.c_coo_ptr) + return self.get_array(ptr) + + def get_edge_weights(self): + if self.c_coo_ptr is NULL: + raise ValueError("pointer not set, must call set_ptr() with a " + "non-NULL value first.") + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_coo_get_edge_weights(self.c_coo_ptr) + return self.get_array(ptr) diff --git a/python/pylibcugraph/pylibcugraph/negative_sampling.pyx b/python/pylibcugraph/pylibcugraph/negative_sampling.pyx new file mode 100644 index 00000000000..610cfa90ccf --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/negative_sampling.pyx @@ -0,0 +1,184 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport uintptr_t + +from pylibcugraph._cugraph_c.resource_handle cimport ( + cugraph_resource_handle_t, + bool_t, +) +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, + cugraph_type_erased_device_array_view_create, + cugraph_type_erased_device_array_view_free, + cugraph_type_erased_host_array_view_t, + cugraph_type_erased_host_array_view_create, + cugraph_type_erased_host_array_view_free, +) +from pylibcugraph.resource_handle cimport ( + ResourceHandle, +) +from pylibcugraph.graphs cimport ( + _GPUGraph, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) +from pylibcugraph._cugraph_c.sampling_algorithms cimport ( + cugraph_negative_sampling, +) +from pylibcugraph._cugraph_c.coo cimport ( + cugraph_coo_t, +) +from pylibcugraph.internal_types.coo cimport ( + COO, +) +from pylibcugraph.utils cimport ( + assert_success, + assert_CAI_type, + create_cugraph_type_erased_device_array_view_from_py_obj, +) +from pylibcugraph._cugraph_c.random cimport ( + cugraph_rng_state_t +) +from pylibcugraph.random cimport ( + CuGraphRandomState +) + +def negative_sampling(ResourceHandle resource_handle, + _GPUGraph graph, + size_t num_samples, + random_state=None, + vertices=None, + src_bias=None, + dst_bias=None, + remove_duplicates=False, + remove_false_negatives=False, + exact_number_of_samples=False, + do_expensive_check=False): + """ + Performs negative sampling, which is essentially a form of graph generation. + + By setting vertices, src_bias, and dst_bias, this function can perform + biased negative sampling. + + Parameters + ---------- + resource_handle: ResourceHandle + Handle to the underlying device and host resources needed for + referencing data and running algorithms. + input_graph: SGGraph or MGGraph + The stored cuGraph graph to create negative samples for. + num_samples: int + The number of negative edges to generate for each positive edge. + random_state: int (Optional) + Random state to use when generating samples. Optional argument, + defaults to a hash of process id, time, and hostname. + (See pylibcugraph.random.CuGraphRandomState) + vertices: device array type (Optional) + Vertex ids corresponding to the src/dst biases, if provided. + Ignored if src/dst biases are not provided. + src_bias: device array type (Optional) + Probability per edge that a vertex is selected as a source vertex. + Does not have to be normalized. Uses a uniform distribution if + not provided. + dst_bias: device array type (Optional) + Probability per edge that a vertex is selected as a destination vertex. + Does not have to be normalized. Uses a uniform distribution if + not provided. + remove_duplicates: bool (Optional) + Whether to remove duplicate edges from the generated edgelist. + Defaults to False (does not remove duplicates). + remove_false_negatives: bool (Optional) + Whether to remove false negatives from the generated edgelist. + Defaults to False (does not check for and remove false negatives). + exact_number_of_samples: bool (Optional) + Whether to manually regenerate samples until the desired number + as specified by num_samples has been generated. + Defaults to False (does not regenerate if enough samples are not + produced in the initial round). + do_expensive_check: bool (Optional) + Whether to perform an expensive error check at the C++ level. + Defaults to False (no error check). + + Returns + ------- + dict[str, cupy.ndarray] + Generated edges in COO format. + """ + + assert_CAI_type(vertices, "vertices", True) + assert_CAI_type(src_bias, "src_bias", True) + assert_CAI_type(dst_bias, "dst_bias", True) + + cdef cugraph_resource_handle_t* c_resource_handle_ptr = ( + resource_handle.c_resource_handle_ptr + ) + + cdef cugraph_graph_t* c_graph_ptr = graph.c_graph_ptr + + cdef bool_t c_remove_duplicates = remove_duplicates + cdef bool_t c_remove_false_negatives = remove_false_negatives + cdef bool_t c_exact_number_of_samples = exact_number_of_samples + cdef bool_t c_do_expensive_check = do_expensive_check + + cg_rng_state = CuGraphRandomState(resource_handle, random_state) + + cdef cugraph_rng_state_t* rng_state_ptr = \ + cg_rng_state.rng_state_ptr + + cdef cugraph_type_erased_device_array_view_t* vertices_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj(vertices) + cdef cugraph_type_erased_device_array_view_t* src_bias_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj(src_bias) + cdef cugraph_type_erased_device_array_view_t* dst_bias_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj(dst_bias) + + cdef cugraph_coo_t* result_ptr + cdef cugraph_error_t* err_ptr + cdef cugraph_error_code_t error_code + + error_code = cugraph_negative_sampling( + c_resource_handle_ptr, + rng_state_ptr, + c_graph_ptr, + vertices_ptr, + src_bias_ptr, + dst_bias_ptr, + num_samples, + c_remove_duplicates, + c_remove_false_negatives, + c_exact_number_of_samples, + c_do_expensive_check, + &result_ptr, + &err_ptr, + ) + assert_success(error_code, err_ptr, "cugraph_negative_sampling") + + coo = COO() + coo.set_ptr(result_ptr) + + return { + 'sources': coo.get_sources(), + 'destinations': coo.get_destinations(), + 'edge_id': coo.get_edge_ids(), + 'edge_type': coo.get_edge_types(), + 'weight': coo.get_edge_weights(), + } From 9b107b928b60903037aa1baf5d82f0d6047a3705 Mon Sep 17 00:00:00 2001 From: GALI PREM SAGAR Date: Mon, 30 Sep 2024 14:28:42 -0500 Subject: [PATCH 16/37] Swtch traceback to `--native` in `cugraph` (#4663) In cudf we have observed a ~10% speed up of pytest suite execution by switching pytest traceback to `--native`: ``` currently: 102474 passed, 2117 skipped, 902 xfailed in 892.16s (0:14:52) --tb=short: 102474 passed, 2117 skipped, 902 xfailed in 898.99s (0:14:58) --tb=no: 102474 passed, 2117 skipped, 902 xfailed in 815.98s (0:13:35) --tb=native: 102474 passed, 2117 skipped, 902 xfailed in 820.92s (0:13:40) ``` This PR makes similar change to `cugraph` repo. xref: https://github.com/rapidsai/cudf/pull/16851 Authors: - GALI PREM SAGAR (https://github.com/galipremsagar) Approvers: - Brad Rees (https://github.com/BradReesWork) URL: https://github.com/rapidsai/cugraph/pull/4663 --- benchmarks/pytest.ini | 1 + .../cugraph-equivariant/cugraph_equivariant/tests/pytest.ini | 4 ++++ python/cugraph-pyg/cugraph_pyg/tests/pytest.ini | 4 ++++ python/cugraph-pyg/pytest.ini | 1 + python/cugraph-service/pytest.ini | 1 + python/cugraph-service/tests/pytest.ini | 4 ++++ python/cugraph/pytest.ini | 1 + python/nx-cugraph/nx_cugraph/tests/pytest.ini | 4 ++++ python/pylibcugraph/pytest.ini | 2 ++ 9 files changed, 22 insertions(+) create mode 100644 python/cugraph-equivariant/cugraph_equivariant/tests/pytest.ini create mode 100644 python/cugraph-pyg/cugraph_pyg/tests/pytest.ini create mode 100644 python/cugraph-service/tests/pytest.ini create mode 100644 python/nx-cugraph/nx_cugraph/tests/pytest.ini diff --git a/benchmarks/pytest.ini b/benchmarks/pytest.ini index fe7fc31b6d6..d692b78de37 100644 --- a/benchmarks/pytest.ini +++ b/benchmarks/pytest.ini @@ -8,6 +8,7 @@ testpaths = addopts = --benchmark-columns="min, max, mean, stddev, outliers" + --tb=native markers = managedmem_on: RMM managed memory enabled diff --git a/python/cugraph-equivariant/cugraph_equivariant/tests/pytest.ini b/python/cugraph-equivariant/cugraph_equivariant/tests/pytest.ini new file mode 100644 index 00000000000..7b0a9f29fb1 --- /dev/null +++ b/python/cugraph-equivariant/cugraph_equivariant/tests/pytest.ini @@ -0,0 +1,4 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +[pytest] +addopts = --tb=native diff --git a/python/cugraph-pyg/cugraph_pyg/tests/pytest.ini b/python/cugraph-pyg/cugraph_pyg/tests/pytest.ini new file mode 100644 index 00000000000..7b0a9f29fb1 --- /dev/null +++ b/python/cugraph-pyg/cugraph_pyg/tests/pytest.ini @@ -0,0 +1,4 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +[pytest] +addopts = --tb=native diff --git a/python/cugraph-pyg/pytest.ini b/python/cugraph-pyg/pytest.ini index db99a54ae49..07c4ffa0958 100644 --- a/python/cugraph-pyg/pytest.ini +++ b/python/cugraph-pyg/pytest.ini @@ -17,6 +17,7 @@ addopts = --benchmark-max-time=0 --benchmark-min-rounds=1 --benchmark-columns="mean, rounds" + --tb=native ## do not run slow tests/benchmarks by default -m "not slow" diff --git a/python/cugraph-service/pytest.ini b/python/cugraph-service/pytest.ini index 6a0dd36ecec..f2ba9175f82 100644 --- a/python/cugraph-service/pytest.ini +++ b/python/cugraph-service/pytest.ini @@ -16,6 +16,7 @@ addopts = --benchmark-warmup=off --benchmark-max-time=0 --benchmark-min-rounds=1 --benchmark-columns="min, max, mean, rounds" + --tb=native ## for use with rapids-pytest-benchmark plugin #--benchmark-gpu-disable ## for use with pytest-cov plugin diff --git a/python/cugraph-service/tests/pytest.ini b/python/cugraph-service/tests/pytest.ini new file mode 100644 index 00000000000..7b0a9f29fb1 --- /dev/null +++ b/python/cugraph-service/tests/pytest.ini @@ -0,0 +1,4 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +[pytest] +addopts = --tb=native diff --git a/python/cugraph/pytest.ini b/python/cugraph/pytest.ini index 675a6cf8fde..bca148538d9 100644 --- a/python/cugraph/pytest.ini +++ b/python/cugraph/pytest.ini @@ -17,6 +17,7 @@ addopts = --benchmark-max-time=0 --benchmark-min-rounds=1 --benchmark-columns="mean, rounds" + --tb=native ## do not run the slow tests/benchmarks by default -m "not slow" ## for use with rapids-pytest-benchmark plugin diff --git a/python/nx-cugraph/nx_cugraph/tests/pytest.ini b/python/nx-cugraph/nx_cugraph/tests/pytest.ini new file mode 100644 index 00000000000..7b0a9f29fb1 --- /dev/null +++ b/python/nx-cugraph/nx_cugraph/tests/pytest.ini @@ -0,0 +1,4 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +[pytest] +addopts = --tb=native diff --git a/python/pylibcugraph/pytest.ini b/python/pylibcugraph/pytest.ini index 573628de680..d5ade9f4836 100644 --- a/python/pylibcugraph/pytest.ini +++ b/python/pylibcugraph/pytest.ini @@ -14,3 +14,5 @@ [pytest] markers = cugraph_ops: Tests requiring cugraph-ops + +addopts = --tb=native From 0edb4c30649ff4ccf25541e8849b8512c5818547 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Tue, 1 Oct 2024 10:22:35 -0400 Subject: [PATCH 17/37] Prune workflows based on changed files (#4634) Only run tests based on things that have actually changed. For example, if only Python files have changed, we don't need to run the C++ tests. Contributes to https://github.com/rapidsai/build-planning/issues/94 Depends on https://github.com/rapidsai/shared-workflows/pull/239 Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Robert Maynard (https://github.com/robertmaynard) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cugraph/pull/4634 --- .github/workflows/pr.yaml | 67 +++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2220aa8aad6..b0a1308237e 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -12,6 +12,7 @@ concurrency: jobs: pr-builder: needs: + - changed-files - checks - conda-cpp-build - conda-cpp-tests @@ -35,6 +36,45 @@ jobs: - devcontainer secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.12 + if: always() + with: + needs: ${{ toJSON(needs) }} + changed-files: + secrets: inherit + uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@branch-24.12 + with: + files_yaml: | + test_cpp: + - '**' + - '!.devcontainers/**' + - '!CONTRIBUTING.md' + - '!README.md' + - '!docs/**' + - '!img/**' + - '!mg_utils/**' + - '!notebooks/**' + - '!python/**' + - '!readme_pages/**' + # TODO: Remove this before merging + - '!.github/**' + test_notebooks: + - '**' + - '!.devcontainers/**' + - '!CONTRIBUTING.md' + - '!README.md' + - '!docs/**' + # TODO: Remove this before merging + - '!.github/**' + test_python: + - '**' + - '!.devcontainers/**' + - '!CONTRIBUTING.md' + - '!README.md' + - '!docs/**' + - '!img/**' + - '!notebooks/**' + # TODO: Remove this before merging + - '!.github/**' checks: secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.12 @@ -48,9 +88,10 @@ jobs: build_type: pull-request node_type: cpu32 conda-cpp-tests: - needs: conda-cpp-build + needs: [conda-cpp-build, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp with: build_type: pull-request conda-cpp-checks: @@ -68,15 +109,17 @@ jobs: with: build_type: pull-request conda-python-tests: - needs: conda-python-build + needs: [conda-python-build, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request conda-notebook-tests: - needs: conda-python-build + needs: [conda-python-build, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_notebooks with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -105,9 +148,10 @@ jobs: extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY node_type: cpu32 wheel-tests-pylibcugraph: - needs: wheel-build-pylibcugraph + needs: [wheel-build-pylibcugraph, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_pylibcugraph.sh @@ -122,9 +166,10 @@ jobs: extra-repo-sha: branch-24.12 extra-repo-deploy-key: CUGRAPH_OPS_SSH_PRIVATE_DEPLOY_KEY wheel-tests-cugraph: - needs: wheel-build-cugraph + needs: [wheel-build-cugraph, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_cugraph.sh @@ -136,9 +181,10 @@ jobs: build_type: pull-request script: ci/build_wheel_nx-cugraph.sh wheel-tests-nx-cugraph: - needs: wheel-build-nx-cugraph + needs: [wheel-build-nx-cugraph, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_nx-cugraph.sh @@ -150,9 +196,10 @@ jobs: build_type: pull-request script: ci/build_wheel_cugraph-dgl.sh wheel-tests-cugraph-dgl: - needs: wheel-build-cugraph-dgl + needs: [wheel-build-cugraph-dgl, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_cugraph-dgl.sh @@ -165,9 +212,10 @@ jobs: build_type: pull-request script: ci/build_wheel_cugraph-pyg.sh wheel-tests-cugraph-pyg: - needs: wheel-build-cugraph-pyg + needs: [wheel-build-cugraph-pyg, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_cugraph-pyg.sh @@ -179,9 +227,10 @@ jobs: build_type: pull-request script: ci/build_wheel_cugraph-equivariant.sh wheel-tests-cugraph-equivariant: - needs: wheel-build-cugraph-equivariant + needs: [wheel-build-cugraph-equivariant, changed-files] secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.12 + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: build_type: pull-request script: ci/test_wheel_cugraph-equivariant.sh From a936327c02d08c219591ac3c419724b8b5cd646d Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:51:33 -0400 Subject: [PATCH 18/37] Add `nx-cugraph` Docs Pages (#4669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/rapidsai/graph_dl/issues/606 and [another issue] ## Proposed Changes In preparation for GA release, this PR adds a landing page for `nx-cugraph` in the cugraph API documentation site. The new pages can be viewed by clicking `nx-cugraph` in the navigation bar at the top of the page. ### New pages nx-cugraph └─ How it works └─ Supported Algorithms └─ Getting Started └─ Benchmarks └─ FAQ ## Notes for Reviewers - In order to build and test these docs, I modified the `build.sh` file to use `sphinx-autobuild`. ```bash 122 123 cd ${REPODIR}/docs/cugraph-docs 124 #make html 125 sphinx-autobuild source build/html 126 fi 127 ``` - For now, I believe the best way to view these changes is to clone the PR branch, then run `build.sh` in order to host the webserver locally.. --------- Co-authored-by: Don Acosta <97529984+acostadon@users.noreply.github.com> Co-authored-by: rlratzel Co-authored-by: Erik Welch --- docs/cugraph/source/_static/bc_benchmark.png | Bin 0 -> 139273 bytes docs/cugraph/source/_static/colab.png | Bin 0 -> 76512 bytes .../source/_static/nxcg-execution-diagram.jpg | Bin 0 -> 170702 bytes docs/cugraph/source/nx_cugraph/benchmarks.md | 28 ++ docs/cugraph/source/nx_cugraph/faqs.md | 5 + .../cugraph/source/nx_cugraph/how-it-works.md | 114 ++++++ docs/cugraph/source/nx_cugraph/index.rst | 49 ++- .../cugraph/source/nx_cugraph/installation.md | 50 +++ docs/cugraph/source/nx_cugraph/nx_cugraph.md | 57 +-- .../nx_cugraph/supported-algorithms.rst | 354 ++++++++++++++++++ python/nx-cugraph/_nx_cugraph/__init__.py | 2 +- 11 files changed, 599 insertions(+), 60 deletions(-) create mode 100644 docs/cugraph/source/_static/bc_benchmark.png create mode 100644 docs/cugraph/source/_static/colab.png create mode 100644 docs/cugraph/source/_static/nxcg-execution-diagram.jpg create mode 100644 docs/cugraph/source/nx_cugraph/benchmarks.md create mode 100644 docs/cugraph/source/nx_cugraph/faqs.md create mode 100644 docs/cugraph/source/nx_cugraph/how-it-works.md create mode 100644 docs/cugraph/source/nx_cugraph/installation.md create mode 100644 docs/cugraph/source/nx_cugraph/supported-algorithms.rst diff --git a/docs/cugraph/source/_static/bc_benchmark.png b/docs/cugraph/source/_static/bc_benchmark.png new file mode 100644 index 0000000000000000000000000000000000000000..9e385c97e996221f8698c324dcbafe24bf73ecc4 GIT binary patch literal 139273 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�{@}BJE*EU*tu*(hf9pJAgMSOj^ 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 XNkQE&=yoGrJcd0JTG$+?4JMuk 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 z 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

)e}NHDeyWt!@MDS$W$^tH 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 V2RcelJcSfPD{J2>ykQaX{{TGiEWQ8$ literal 0 HcmV?d00001 diff --git a/docs/cugraph/source/_static/nxcg-execution-diagram.jpg b/docs/cugraph/source/_static/nxcg-execution-diagram.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48136289af9b46dfaf14bbec43fbce3cfbe3736b GIT binary patch literal 170702 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 z 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 + +![bench-image](../_static/bc_benchmark.png) + +

Results from running this Benchmark
+ + +## Reproducing Benchmarks + +Below are the steps to reproduce the results on your workstation. These are documented in this [README](https://github.com/rapidsai/cugraph/blob/HEAD/benchmarks/nx-cugraph/pytest-based). + +1. Clone the latest + +2. Follow the instructions to build an environment + +3. Activate the environment + +4. Install the latest `nx-cugraph` by following the [guide](installation.md) + +5. Follow the instructions written in the README here: `cugraph/benchmarks/nx-cugraph/pytest-based/` diff --git a/docs/cugraph/source/nx_cugraph/faqs.md b/docs/cugraph/source/nx_cugraph/faqs.md new file mode 100644 index 00000000000..dee943d1908 --- /dev/null +++ b/docs/cugraph/source/nx_cugraph/faqs.md @@ -0,0 +1,5 @@ +# FAQ + + > **1. Is `nx-cugraph` able to run across multiple GPUs?** + +nx-cugraph currently does not support multi-GPU. Multi-GPU support may be added to a future release of nx-cugraph, but consider [cugraph](https://docs.rapids.ai/api/cugraph/stable) for multi-GPU accelerated graph analytics in Python today. diff --git a/docs/cugraph/source/nx_cugraph/how-it-works.md b/docs/cugraph/source/nx_cugraph/how-it-works.md new file mode 100644 index 00000000000..f9dc5af67ac --- /dev/null +++ b/docs/cugraph/source/nx_cugraph/how-it-works.md @@ -0,0 +1,114 @@ +# How it Works + +NetworkX has the ability to **dispatch function calls to separately-installed third-party backends**. + +NetworkX backends let users experience improved performance and/or additional functionality without changing their NetworkX Python code. Examples include backends that provide algorithm acceleration using GPUs, parallel processing, graph database integration, and more. + +While NetworkX is a pure-Python implementation with minimal to no dependencies, backends may be written in other languages and require specialized hardware and/or OS support, additional software dependencies, or even separate services. Installation instructions vary based on the backend, and additional information can be found from the individual backend project pages listed in the NetworkX Backend Gallery. + + +![nxcg-execution-flow](../_static/nxcg-execution-diagram.jpg) + +## Enabling nx-cugraph + +NetworkX will use nx-cugraph as the graph analytics backend if any of the +following are used: + +### `NETWORKX_BACKEND_PRIORITY` environment variable. + +The `NETWORKX_BACKEND_PRIORITY` environment variable can be used to have NetworkX automatically dispatch to specified backends. This variable can be set to a single backend name, or a comma-separated list of backends ordered using the priority which NetworkX should try. If a NetworkX function is called that nx-cugraph supports, NetworkX will redirect the function call to nx-cugraph automatically, or fall back to the next backend in the list if provided, or run using the default NetworkX implementation. See [NetworkX Backends and Configs](https://networkx.org/documentation/stable/reference/backends.html). + +For example, this setting will have NetworkX use nx-cugraph for any function called by the script supported by nx-cugraph, and the default NetworkX implementation for all others. +``` +bash> NETWORKX_BACKEND_PRIORITY=cugraph python my_networkx_script.py +``` + +This example will have NetworkX use nx-cugraph for functions it supports, then try other_backend if nx-cugraph does not support them, and finally the default NetworkX implementation if not supported by either backend: +``` +bash> NETWORKX_BACKEND_PRIORITY="cugraph,other_backend" python my_networkx_script.py +``` + +### `backend=` keyword argument + +To explicitly specify a particular backend for an API, use the `backend=` +keyword argument. This argument takes precedence over the +`NETWORKX_BACKEND_PRIORITY` environment variable. This requires anyone +running code that uses the `backend=` keyword argument to have the specified +backend installed. + +Example: +```python +nx.betweenness_centrality(cit_patents_graph, k=k, backend="cugraph") +``` + +### Type-based dispatching + +NetworkX also supports automatically dispatching to backends associated with +specific graph types. Like the `backend=` keyword argument example above, this +requires the user to write code for a specific backend, and therefore requires +the backend to be installed, but has the advantage of ensuring a particular +behavior without the potential for runtime conversions. + +To use type-based dispatching with nx-cugraph, the user must import the backend +directly in their code to access the utilities provided to create a Graph +instance specifically for the nx-cugraph backend. + +Example: +```python +import networkx as nx +import nx_cugraph as nxcg + +G = nx.Graph() +... +nxcg_G = nxcg.from_networkx(G) # conversion happens once here +nx.betweenness_centrality(nxcg_G, k=1000) # nxcg Graph type causes cugraph backend + # to be used, no conversion necessary +``` + +## Command Line Example + +--- + +Create `bc_demo.ipy` and paste the code below. + +```python +import pandas as pd +import networkx as nx + +url = "https://data.rapids.ai/cugraph/datasets/cit-Patents.csv" +df = pd.read_csv(url, sep=" ", names=["src", "dst"], dtype="int32") +G = nx.from_pandas_edgelist(df, source="src", target="dst") + +%time result = nx.betweenness_centrality(G, k=10) +``` +Run the command: +``` +user@machine:/# ipython bc_demo.ipy +``` + +You will observe a run time of approximately 7 minutes...more or less depending on your CPU. + +Run the command again, this time specifying cugraph as the NetworkX backend. +``` +user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy +``` +This run will be much faster, typically around 20 seconds depending on your GPU. +``` +user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy +``` +There is also an option to cache the graph conversion to GPU. This can dramatically improve performance when running multiple algorithms on the same graph. Caching is enabled by default for NetworkX versions 3.4 and later, but if using an older version, set "NETWORKX_CACHE_CONVERTED_GRAPHS=True" +``` +NETWORKX_BACKEND_PRIORITY=cugraph NETWORKX_CACHE_CONVERTED_GRAPHS=True ipython bc_demo.ipy +``` + +When running Python interactively, the cugraph backend can be specified as an argument in the algorithm call. + +For example: +``` +nx.betweenness_centrality(cit_patents_graph, k=k, backend="cugraph") +``` + + +The latest list of algorithms supported by nx-cugraph can be found [here](https://github.com/rapidsai/cugraph/blob/HEAD/python/nx-cugraph/README.md#algorithms) or in the next section. + +--- diff --git a/docs/cugraph/source/nx_cugraph/index.rst b/docs/cugraph/source/nx_cugraph/index.rst index ef6f51601ab..110300c1836 100644 --- a/docs/cugraph/source/nx_cugraph/index.rst +++ b/docs/cugraph/source/nx_cugraph/index.rst @@ -1,9 +1,48 @@ -=============================== -nxCugraph as a NetworkX Backend -=============================== +nx-cugraph +----------- +nx-cugraph is a `NetworkX backend `_ that provides **GPU acceleration** to many popular NetworkX algorithms. + +By simply `installing and enabling nx-cugraph `_, users can see significant speedup on workflows where performance is hindered by the default NetworkX implementation. With ``nx-cugraph``, users can have GPU-based, large-scale performance **without** changing their familiar and easy-to-use NetworkX code. + +.. code-block:: python + + import pandas as pd + import networkx as nx + + url = "https://data.rapids.ai/cugraph/datasets/cit-Patents.csv" + df = pd.read_csv(url, sep=" ", names=["src", "dst"], dtype="int32") + G = nx.from_pandas_edgelist(df, source="src", target="dst") + + %time result = nx.betweenness_centrality(G, k=10) + +.. figure:: ../_static/colab.png + :width: 200px + :target: https://nvda.ws/4drM4re + + Try it on Google Colab! + + ++------------------------------------------------------------------------------------------------------------------------+ +| **Zero Code Change Acceleration** | +| | +| Just ``nx.config.backend_priority=["cugraph"]`` in Jupyter, or set ``NETWORKX_BACKEND_PRIORITY=cugraph`` in the shell. | ++------------------------------------------------------------------------------------------------------------------------+ +| **Run the same code on CPU or GPU** | +| | +| Nothing changes, not even your `import` statements, when going from CPU to GPU. | ++------------------------------------------------------------------------------------------------------------------------+ + + +``nx-cugraph`` is now Generally Available (GA) as part of the ``RAPIDS`` package. See `RAPIDS +Quick Start `_ to get up-and-running with ``nx-cugraph``. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 + :caption: Contents: - nx_cugraph.md + how-it-works + supported-algorithms + installation + benchmarks + faqs diff --git a/docs/cugraph/source/nx_cugraph/installation.md b/docs/cugraph/source/nx_cugraph/installation.md new file mode 100644 index 00000000000..8d221f16fec --- /dev/null +++ b/docs/cugraph/source/nx_cugraph/installation.md @@ -0,0 +1,50 @@ +# Getting Started + +This guide describes how to install ``nx-cugraph`` and use it in your workflows. + + +## System Requirements + +`nx-cugraph` requires the following: + + - **Volta architecture or later NVIDIA GPU, with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+** + - **[CUDA](https://docs.nvidia.com/cuda/index.html) 11.2, 11.4, 11.5, 11.8, 12.0, 12.2, or 12.5** + - **Python >= 3.10** + - **[NetworkX](https://networkx.org/documentation/stable/install.html#) >= 3.0 (version 3.2 or higher recommended)** + +More details about system requirements can be found in the [RAPIDS System Requirements Documentation](https://docs.rapids.ai/install#system-req). + +## Installing nx-cugraph + +Read the [RAPIDS Quick Start Guide](https://docs.rapids.ai/install) to learn more about installing all RAPIDS libraries. + +`nx-cugraph` can be installed using conda or pip. It is included in the RAPIDS metapackage, or can be installed separately. + +### Conda +**Nightly version** +```bash +conda install -c rapidsai-nightly -c conda-forge -c nvidia nx-cugraph +``` + +**Stable version** +```bash +conda install -c rapidsai -c conda-forge -c nvidia nx-cugraph +``` + +### pip +**Nightly version** +```bash +pip install nx-cugraph-cu11 --extra-index-url https://pypi.anaconda.org/rapidsai-wheels-nightly/simple +``` + +**Stable version** +```bash +pip install nx-cugraph-cu11 --extra-index-url https://pypi.nvidia.com +``` + +
+ +**Note:** + - The `pip install` examples above are for CUDA 11. To install for CUDA 12, replace `-cu11` with `-cu12` + +
diff --git a/docs/cugraph/source/nx_cugraph/nx_cugraph.md b/docs/cugraph/source/nx_cugraph/nx_cugraph.md index 75a30b0be5c..900362a6e2b 100644 --- a/docs/cugraph/source/nx_cugraph/nx_cugraph.md +++ b/docs/cugraph/source/nx_cugraph/nx_cugraph.md @@ -1,18 +1,10 @@ ### nx_cugraph -nx-cugraph is a [NetworkX -backend]() that provides GPU acceleration to many popular NetworkX algorithms. - -By simply [installing and enabling nx-cugraph](), users can see significant speedup on workflows where performance is hindered by the default NetworkX implementation. With nx-cugraph, users can have GPU-based, large-scale performance without changing their familiar and easy-to-use NetworkX code. - -Let's look at some examples of algorithm speedups comparing NetworkX with and without GPU acceleration using nx-cugraph. - -Each chart has three measurements. -* NX - default NetworkX, no GPU acceleration -* nx-cugraph - GPU-accelerated NetworkX using nx-cugraph. This involves an internal conversion/transfer of graph data from CPU to GPU memory -* nx-cugraph (preconvert) - GPU-accelerated NetworkX using nx-cugraph with the graph data pre-converted/transferred to GPU +`nx-cugraph` is a [networkX backend]() that accelerates many popular NetworkX functions using cuGraph and NVIDIA GPUs. +Users simply [install and enable nx-cugraph](installation.md) to experience GPU speedups. +Lets look at some examples of algorithm speedups comparing CPU based NetworkX to dispatched versions run on GPU with nx_cugraph. ![Ancestors](../images/ancestors.png) ![BFS Tree](../images/bfs_tree.png) @@ -22,46 +14,3 @@ Each chart has three measurements. ![Pagerank](../images/pagerank.png) ![Single Source Shortest Path](../images/sssp.png) ![Weakly Connected Components](../images/wcc.png) - -### Command line example -Open bc_demo.ipy and paste the code below. - -``` -import pandas as pd -import networkx as nx - -url = "https://data.rapids.ai/cugraph/datasets/cit-Patents.csv" -df = pd.read_csv(url, sep=" ", names=["src", "dst"], dtype="int32") -G = nx.from_pandas_edgelist(df, source="src", target="dst") - -%time result = nx.betweenness_centrality(G, k=10) -``` -Run the command: -``` -user@machine:/# ipython bc_demo.ipy -``` - -You will observe a run time of approximately 7 minutes...more or less depending on your cpu. - -Run the command again, this time specifying cugraph as the NetworkX backend. -``` -user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy -``` -This run will be much faster, typically around 20 seconds depending on your GPU. -``` -user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy -``` -There is also an option to cache the graph conversion to GPU. This can dramatically improve performance when running multiple algorithms on the same graph. -``` -NETWORKX_BACKEND_PRIORITY=cugraph NETWORKX_CACHE_CONVERTED_GRAPHS=True ipython bc_demo.ipy -``` - -When running Python interactively, the cugraph backend can be specified as an argument in the algorithm call. - -For example: -``` -nx.betweenness_centrality(cit_patents_graph, k=k, backend="cugraph") -``` - - -The latest list of algorithms supported by nx-cugraph can be found [here](https://github.com/rapidsai/cugraph/blob/main/python/nx-cugraph/README.md#algorithms). diff --git a/docs/cugraph/source/nx_cugraph/supported-algorithms.rst b/docs/cugraph/source/nx_cugraph/supported-algorithms.rst new file mode 100644 index 00000000000..b21ef7bb668 --- /dev/null +++ b/docs/cugraph/source/nx_cugraph/supported-algorithms.rst @@ -0,0 +1,354 @@ +Supported Algorithms +===================== + +The nx-cugraph backend to NetworkX connects +`pylibcugraph <../../readme_pages/pylibcugraph.md>`_ (cuGraph's low-level Python +interface to its CUDA-based graph analytics library) and +`CuPy `_ (a GPU-accelerated array library) to NetworkX's +familiar and easy-to-use API. + +Below is the list of algorithms that are currently supported in nx-cugraph. + + +Algorithms +---------- + ++-----------------------------+ +| **Centrality** | ++=============================+ +| betweenness_centrality | ++-----------------------------+ +| edge_betweenness_centrality | ++-----------------------------+ +| degree_centrality | ++-----------------------------+ +| in_degree_centrality | ++-----------------------------+ +| out_degree_centrality | ++-----------------------------+ +| eigenvector_centrality | ++-----------------------------+ +| katz_centrality | ++-----------------------------+ + ++---------------------+ +| **Cluster** | ++=====================+ +| average_clustering | ++---------------------+ +| clustering | ++---------------------+ +| transitivity | ++---------------------+ +| triangles | ++---------------------+ + ++--------------------------+ +| **Community** | ++==========================+ +| louvain_communities | ++--------------------------+ + ++--------------------------+ +| **Bipartite** | ++==========================+ +| complete_bipartite_graph | ++--------------------------+ + ++------------------------------------+ +| **Components** | ++====================================+ +| connected_components | ++------------------------------------+ +| is_connected | ++------------------------------------+ +| node_connected_component | ++------------------------------------+ +| number_connected_components | ++------------------------------------+ +| weakly_connected | ++------------------------------------+ +| is_weakly_connected | ++------------------------------------+ +| number_weakly_connected_components | ++------------------------------------+ +| weakly_connected_components | ++------------------------------------+ + ++-------------+ +| **Core** | ++=============+ +| core_number | ++-------------+ +| k_truss | ++-------------+ + ++-------------+ +| **DAG** | ++=============+ +| ancestors | ++-------------+ +| descendants | ++-------------+ + ++--------------------+ +| **Isolate** | ++====================+ +| is_isolate | ++--------------------+ +| isolates | ++--------------------+ +| number_of_isolates | ++--------------------+ + ++-------------------+ +| **Link analysis** | ++===================+ +| hits | ++-------------------+ +| pagerank | ++-------------------+ + ++----------------+ +| **Operators** | ++================+ +| complement | ++----------------+ +| reverse | ++----------------+ + ++----------------------+ +| **Reciprocity** | ++======================+ +| overall_reciprocity | ++----------------------+ +| reciprocity | ++----------------------+ + ++---------------------------------------+ +| **Shortest Paths** | ++=======================================+ +| has_path | ++---------------------------------------+ +| shortest_path | ++---------------------------------------+ +| shortest_path_length | ++---------------------------------------+ +| all_pairs_shortest_path | ++---------------------------------------+ +| all_pairs_shortest_path_length | ++---------------------------------------+ +| bidirectional_shortest_path | ++---------------------------------------+ +| single_source_shortest_path | ++---------------------------------------+ +| single_source_shortest_path_length | ++---------------------------------------+ +| single_target_shortest_path | ++---------------------------------------+ +| single_target_shortest_path_length | ++---------------------------------------+ +| all_pairs_bellman_ford_path | ++---------------------------------------+ +| all_pairs_bellman_ford_path_length | ++---------------------------------------+ +| all_pairs_dijkstra | ++---------------------------------------+ +| all_pairs_dijkstra_path | ++---------------------------------------+ +| all_pairs_dijkstra_path_length | ++---------------------------------------+ +| bellman_ford_path | ++---------------------------------------+ +| bellman_ford_path_length | ++---------------------------------------+ +| dijkstra_path | ++---------------------------------------+ +| dijkstra_path_length | ++---------------------------------------+ +| single_source_bellman_ford | ++---------------------------------------+ +| single_source_bellman_ford_path | ++---------------------------------------+ +| single_source_bellman_ford_path_length| ++---------------------------------------+ +| single_source_dijkstra | ++---------------------------------------+ +| single_source_dijkstra_path | ++---------------------------------------+ +| single_source_dijkstra_path_length | ++---------------------------------------+ + ++---------------------------+ +| **Traversal** | ++===========================+ +| bfs_edges | ++---------------------------+ +| bfs_layers | ++---------------------------+ +| bfs_predecessors | ++---------------------------+ +| bfs_successors | ++---------------------------+ +| bfs_tree | ++---------------------------+ +| descendants_at_distance | ++---------------------------+ +| generic_bfs_edges | ++---------------------------+ + ++---------------------+ +| **Tree** | ++=====================+ +| is_arborescence | ++---------------------+ +| is_branching | ++---------------------+ +| is_forest | ++---------------------+ +| is_tree | ++---------------------+ + +Generators +------------ + ++-------------------------------+ +| **Classic** | ++===============================+ +| barbell_graph | ++-------------------------------+ +| circular_ladder_graph | ++-------------------------------+ +| complete_graph | ++-------------------------------+ +| complete_multipartite_graph | ++-------------------------------+ +| cycle_graph | ++-------------------------------+ +| empty_graph | ++-------------------------------+ +| ladder_graph | ++-------------------------------+ +| lollipop_graph | ++-------------------------------+ +| null_graph | ++-------------------------------+ +| path_graph | ++-------------------------------+ +| star_graph | ++-------------------------------+ +| tadpole_graph | ++-------------------------------+ +| trivial_graph | ++-------------------------------+ +| turan_graph | ++-------------------------------+ +| wheel_graph | ++-------------------------------+ + ++-----------------+ +| **Classic** | ++=================+ +| caveman_graph | ++-----------------+ + ++------------+ +| **Ego** | ++============+ +| ego_graph | ++------------+ + ++------------------------------+ +| **small** | ++==============================+ +| bull_graph | ++------------------------------+ +| chvatal_graph | ++------------------------------+ +| cubical_graph | ++------------------------------+ +| desargues_graph | ++------------------------------+ +| diamond_graph | ++------------------------------+ +| dodecahedral_graph | ++------------------------------+ +| frucht_graph | ++------------------------------+ +| heawood_graph | ++------------------------------+ +| house_graph | ++------------------------------+ +| house_x_graph | ++------------------------------+ +| icosahedral_graph | ++------------------------------+ +| krackhardt_kite_graph | ++------------------------------+ +| moebius_kantor_graph | ++------------------------------+ +| octahedral_graph | ++------------------------------+ +| pappus_graph | ++------------------------------+ +| petersen_graph | ++------------------------------+ +| sedgewick_maze_graph | ++------------------------------+ +| tetrahedral_graph | ++------------------------------+ +| truncated_cube_graph | ++------------------------------+ +| truncated_tetrahedron_graph | ++------------------------------+ +| tutte_graph | ++------------------------------+ + ++-------------------------------+ +| **Social** | ++===============================+ +| davis_southern_women_graph | ++-------------------------------+ +| florentine_families_graph | ++-------------------------------+ +| karate_club_graph | ++-------------------------------+ +| les_miserables_graph | ++-------------------------------+ + +Other +------- + ++-------------------------+ +| **Classes** | ++=========================+ +| is_negatively_weighted | ++-------------------------+ + ++----------------------+ +| **Convert** | ++======================+ +| from_dict_of_lists | ++----------------------+ +| to_dict_of_lists | ++----------------------+ + ++--------------------------+ +| **Convert Matrix** | ++==========================+ +| from_pandas_edgelist | ++--------------------------+ +| from_scipy_sparse_array | ++--------------------------+ + ++-----------------------------------+ +| **Relabel** | ++===================================+ +| convert_node_labels_to_integers | ++-----------------------------------+ +| relabel_nodes | ++-----------------------------------+ + + +To request nx-cugraph backend support for a NetworkX API that is not listed +above, visit the `cuGraph GitHub repo `_. diff --git a/python/nx-cugraph/_nx_cugraph/__init__.py b/python/nx-cugraph/_nx_cugraph/__init__.py index 428d266dd2e..a5e45979fe2 100644 --- a/python/nx-cugraph/_nx_cugraph/__init__.py +++ b/python/nx-cugraph/_nx_cugraph/__init__.py @@ -36,7 +36,7 @@ "backend_name": "cugraph", "project": "nx-cugraph", "package": "nx_cugraph", - "url": f"https://github.com/rapidsai/cugraph/tree/branch-{_version_major:0>2}.{_version_minor:0>2}/python/nx-cugraph", + "url": f"https://rapids.ai/nx-cugraph", "short_summary": "GPU-accelerated backend.", # "description": "TODO", "functions": { From f8b9ac375b21a1c2787a2a148b34eab756b01e3f Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:51:54 -0400 Subject: [PATCH 19/37] Add `nx-cugraph` introduction notebook to repo (#4677) ## Proposed Changes This PR adds an introduction notebook to the `notebooks/demo` directory of the repository. Click the link to view the files directory in the dev branch. * [The introduction to `nx-cugraph` notebook](https://github.com/rapidsai/cugraph/blob/1a70b7ad588bcc1dc6f65af8fe07149ed8083ef0/notebooks/demo/accelerating_networkx.ipynb) --------- Co-authored-by: Erik Welch --- notebooks/demo/accelerating_networkx.ipynb | 614 +++++++++++++++++++++ 1 file changed, 614 insertions(+) create mode 100644 notebooks/demo/accelerating_networkx.ipynb diff --git a/notebooks/demo/accelerating_networkx.ipynb b/notebooks/demo/accelerating_networkx.ipynb new file mode 100644 index 00000000000..1a6c6cfb3f6 --- /dev/null +++ b/notebooks/demo/accelerating_networkx.ipynb @@ -0,0 +1,614 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "R2cpVp2WdOsp" + }, + "source": [ + "# NetworkX - Easy Graph Analytics\n", + "\n", + "NetworkX is the most popular library for graph analytics available in Python, or quite possibly any language. To illustrate this, NetworkX was downloaded more than 71 million times in September of 2024 alone, which is roughly 71 times more than the next most popular graph analytics library! [*](https://en.wikipedia.org/wiki/NetworkX) NetworkX has earned this popularity from its very easy-to-use API, the wealth of documentation and examples available, the large (and friendly) community behind it, and its easy installation which requires nothing more than Python.\n", + "\n", + "However, NetworkX users are familiar with the tradeoff that comes with those benefits. The pure-Python implementation often results in poor performance when graph data starts to reach larger scales, limiting the usefulness of the library for many real-world problems.\n", + "\n", + "# Accelerated NetworkX - Easy (and fast!) Graph Analytics\n", + "\n", + "To address the performance problem, NetworkX 3.0 introduced a mechanism to dispatch algorithm calls to alternate implementations. The NetworkX Python API remains the same but NetworkX will use more capable algorithm implementations provided by one or more backends. This approach means users don't have to give up NetworkX -or even change their code- in order to take advantage of GPU performance." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xkg10FrNThrK" + }, + "source": [ + "# Let's Get the Environment Setup\n", + "This notebook will demonstrate NetworkX both with and without GPU acceleration provided by the `nx-cugraph` backend.\n", + "\n", + "`nx-cugraph` is available as a package installable using `pip`, `conda`, and [from source](https://github.com/rapidsai/nx-cugraph). Before importing `networkx`, lets install `nx-cugraph` so it can be registered as an available backend by NetworkX when needed. We'll use `pip` to install.\n", + "\n", + "NOTES:\n", + "* `nx-cugraph` requires a compatible NVIDIA GPU, NVIDIA CUDA and associated drivers, and a supported OS. Details about these and other installation prerequisites can be seen [here](https://docs.rapids.ai/install#system-req).\n", + "* The `nx-cugraph` package is currently hosted by NVIDIA and therefore the `--extra-index-url` option must be used.\n", + "* `nx-cugraph` is supported on specific 11.x and 12.x CUDA versions, and the major version number must be known in order to install the correct build (this is determined automatically when using `conda`).\n", + "\n", + "To find the CUDA major version on your system, run the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NMFwzc1I95BS" + }, + "outputs": [], + "source": [ + "!nvcc --version" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i91Yj-yZ-nGS" + }, + "source": [ + "From the above output we can see we're using CUDA 12.x so we'll be installing `nx-cugraph-cu12`. If we were using CUDA 11.x, the package name would be `nx-cugraph-cu11`. We'll also be adding `https://pypi.nvidia.com` as an `--extra-index-url`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "mYYN9EpnWphu" + }, + "outputs": [], + "source": [ + "!pip install nx-cugraph-cu12 --extra-index-url=https://pypi.nvidia.com" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0h1K-7tI_AZH" + }, + "source": [ + "Of course, we'll also be using `networkx`, which is already provided in the Colab environment. This notebook will be using features added in version 3.3, so we'll import it here to verify we have a compatible version." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YTV0ZTME2tV6" + }, + "outputs": [], + "source": [ + "import networkx as nx\n", + "nx.__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UiZKOa3WC7be" + }, + "source": [ + "# Let's Start with Something Simple\n", + "\n", + "To begin, we'll compare NetworkX results without a backend to results of the same algorithm using the `nx-cugraph` backend on a small graph. `nx.karate_club_graph()` returns an instance of the famous example graph consisting of 34 nodes and 78 edges from Zachary's paper, described [here](https://en.wikipedia.org/wiki/Zachary%27s_karate_club)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3atL3tI0frYm" + }, + "source": [ + "## Betweenness Centrality\n", + "[Betweenness Centrality](https://en.wikipedia.org/wiki/Betweenness_centrality) is a graph algorithm that computes a centrality score for each node (`v`) based on how many of the shortest paths between pairs of nodes in the graph pass through `v`. A higher centrality score represents a node that \"connects\" other nodes in a network more than that of a node with a lower score.\n", + "\n", + "First, let's create a NetworkX Graph instance of the the Karate Club graph and inspect it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JSw7EZ46-kRu" + }, + "outputs": [], + "source": [ + "G = nx.karate_club_graph()\n", + "G.number_of_nodes(), G.number_of_edges()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_-E17u2gKgbC" + }, + "source": [ + "Next, let's run betweenness centrality and save the results. Because the Karate Club graph is so small, this should not take long." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "qjxXXKJhKQ4s" + }, + "outputs": [], + "source": [ + "%%time\n", + "nx_bc_results = nx.betweenness_centrality(G)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ClrR3z9XMfLr" + }, + "source": [ + "Now, let's run the same algorithm on the same data using the `nx-cugraph` backend.\n", + "\n", + "There are several ways to instruct NetworkX to use a particular backend instead of the default implementation. Here, we will use the `config` API, which was added in NetworkX version 3.3.\n", + "\n", + "The following two lines set the backend to \"cugraph\" and enable graph conversion caching.\n", + "\n", + "Some notes:\n", + "* The standard convention for NetworkX backends is to name the package with a `nx-` prefix to denote that these are packages intended to be used with NetworkX, but the `nx-` prefix is not included when referring to them in NetworkX API calls. Here, `nx-cugraph` is the name of the backend package, and `\"cugraph\"` is the name NetworkX will use to refer to it.\n", + "* NetworkX can use multiple backends! `nx.config.backend_priority` is a list that can contain several backends, ordered based on priority. If a backend in the list cannot run a particular algorithm (either because it isn't supported in the backend, the algorithm doesn't support a particular option, or some other reason), NetworkX will try the next backend in the list. If no specified backend is able to run the algorithm, NetworkX will fall back to the default implementation.\n", + "* Many backends have their own data structures for representing an input graph, often optimized for that backend's implementation. Prior to running a backend algorithm, NetworkX will have the backend convert the standard NetworkX Graph instance to the backend-specific type. This conversion can be expensive, and rather than repeat it as part of each algorithm call, NetworkX can cache the conversion so it can be skipped on future calls if the graph doesn't change. This caching can save significant time and improve overall performance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oFHwNqqsNsqS" + }, + "outputs": [], + "source": [ + "nx.config.backend_priority=[\"cugraph\"] # NETWORKX_BACKEND_PRIORITY=cugraph\n", + "nx.config.cache_converted_graphs=True # NETWORKX_CACHE_CONVERTED_GRAPHS=True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HrUeWRRQRzFP" + }, + "outputs": [], + "source": [ + "%%time\n", + "nxcg_bc_results = nx.betweenness_centrality(G)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "z1hxut3GTj5A" + }, + "source": [ + "You may have noticed that using the `nx-cugraph` backend resulted in a slightly slower execution time. This is not surprising when working with a graph this small, since the overhead of converting the graph for the first time and launching the algorithm kernel on the GPU is actually significantly more than the computation time itself. We'll see later that this overhead is negligible when compared to the time saved when running on a GPU for larger graphs.\n", + "\n", + "Since we've enabled graph conversion caching, we can see that if we re-run the same call the execution time is noticeably shorter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7a0XvpUOr9Ju" + }, + "outputs": [], + "source": [ + "%%time\n", + "nxcg_bc_results = nx.betweenness_centrality(G)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ppjE5J5RscOe" + }, + "source": [ + "Notice the warning above about using the cache. This will only be raised **once** per graph instance (it can also be easily disabled), but its purpose is to point out that the cache should not be used if the Graph object will have its attribute dictionary modified directly. In this case and many others, we won't be modifying the dictionaries directly. Instead, we will use APIs such as `nx.set_node_attributes` which properly clear the cache, so it's safe for us to use the cache. Because of that, we'll disable the warning so we don't see it on other graphs in this session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Namb5JLvwS-q" + }, + "outputs": [], + "source": [ + "import warnings\n", + "warnings.filterwarnings(\"ignore\", message=\"Using cached graph for 'cugraph' backend\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BzGAphcILFsT" + }, + "source": [ + "Smaller graphs are also easy to visualize with NetworkX's plotting utilities. The flexibility of NetworkX's `Graph` instances make it trivial to add the betweenness centrality scores back to the graph object as node attributes. This will allow us to use those values for the visualization.\n", + "\n", + "In this case, we'll create new attributes for each node called \"nx_bc\" for the default NetworkX results, and \"nxcg_bc\" for the nx-cugraph results. We'll use those values to assign the color for each node and plot two graphs side-by-side. This will make it easy to visually validate that the nodes with the higher centrality scores for both implementations match and do indeed appear to be more \"central\" to other nodes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1coV6ZfcUoqI" + }, + "outputs": [], + "source": [ + "nx.set_node_attributes(G, nx_bc_results, \"nx_bc\")\n", + "nx.set_node_attributes(G, nxcg_bc_results, \"nxcg_bc\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Sba2iYJgLoN2" + }, + "outputs": [], + "source": [ + "# Configure plot size and layout/position for each node\n", + "import matplotlib.pyplot as plt\n", + "plt.rcParams['figure.figsize'] = [12, 8]\n", + "pos = nx.spring_layout(G)\n", + "\n", + "# Assign colors for each set of betweenness centrality results\n", + "nx_colors = [G.nodes[n][\"nx_bc\"] for n in G.nodes()]\n", + "nxcg_colors = [G.nodes[n][\"nxcg_bc\"] for n in G.nodes()]\n", + "\n", + "# Plot the graph and color each node corresponding to NetworkX betweenness centrality values\n", + "plt.subplot(1, 2, 1)\n", + "nx.draw(G, pos=pos, with_labels=True, node_color=nx_colors)\n", + "\n", + "# Plot the graph and color each node corresponding to nx-cugraph betweenness centrality values\n", + "plt.subplot(1, 2, 2)\n", + "nx.draw(G, pos=pos, with_labels=True, node_color=nxcg_colors)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dJXH4Zn5VNSg" + }, + "source": [ + "As we can see, the same two nodes (`0` and `33`) are the two most central in both graphs, followed by `2`, `31`, and `32`.\n", + "\n", + "## PageRank\n", + "Another popular algorithm is [PageRank](https://en.wikipedia.org/wiki/PageRank). PageRank also assigns scores to each node, but these scores are based on analyzing links to each node to determine relative \"importance\" within the graph.\n", + "\n", + "Let's update the config to use the default NetworkX implementation and run `nx.pagerank`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9CdYNk62E1v_" + }, + "outputs": [], + "source": [ + "nx.config.backend_priority=[]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Jo39YxVmYolq" + }, + "outputs": [], + "source": [ + "%%time\n", + "nx_pr_results = nx.pagerank(G)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sV6dM8ToZDiC" + }, + "source": [ + "We could set `nx.config.backend_priority` again to list `\"cugraph\"` as the backend, but let's instead show how the `backend` kwarg can be used to override the priority list and force a specific backend to be used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oMSvQVGKY0rn" + }, + "outputs": [], + "source": [ + "%%time\n", + "nxcg_pr_results = nx.pagerank(G, backend=\"cugraph\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ZGux_8xFZneI" + }, + "source": [ + "In this example, instead of plotting the graph to show that the results are identical, we can compare them directly using the saved values from both runs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "RcmtdFy4Zw7p" + }, + "outputs": [], + "source": [ + "sorted(nx_pr_results) == sorted(nxcg_pr_results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mefjUEAnZ4pq" + }, + "source": [ + "# Working with Bigger Data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yLY-yl6PuNYo" + }, + "source": [ + "Now we'll look at a larger dataset from https://snap.stanford.edu/data/cit-Patents.html which contains citations across different U.S. patents granted from January 1, 1963 to December 30, 1999. The dataset represents 16.5M citations (edges) between 3.77M patents (nodes).\n", + "\n", + "This will demonstrate that data of this size starts to push the limits of the default pure-Python NetworkX implementation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "lyYF0LbtFwjh" + }, + "outputs": [], + "source": [ + "# The locale encoding may have been modified from the plots above, reset here to run shell commands\n", + "import locale\n", + "locale.getpreferredencoding = lambda: \"UTF-8\"\n", + "!wget https://data.rapids.ai/cugraph/datasets/cit-Patents.csv # Skip if cit-Patents.csv already exists.\n", + "# !wget https://snap.stanford.edu/data/cit-Patents.txt.gz # Skip if cit-Patents.txt.gz already exists." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "kjGINYphQSQ2" + }, + "outputs": [], + "source": [ + "%load_ext cudf.pandas\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "iV4DieGZOalc" + }, + "outputs": [], + "source": [ + "%%time\n", + "df = pd.read_csv(\"cit-Patents.csv\",\n", + " sep=\" \",\n", + " names=[\"src\", \"dst\"],\n", + " dtype=\"int32\",\n", + ")\n", + "# df = pd.read_csv(\"cit-Patents.txt.gz\",\n", + "# compression=\"gzip\",\n", + "# skiprows=4,\n", + "# sep=\"\\t\",\n", + "# names=[\"src\", \"dst\"],\n", + "# dtype=\"int32\",\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PREA67u4eKat" + }, + "outputs": [], + "source": [ + "%%time\n", + "G = nx.from_pandas_edgelist(df, source=\"src\", target=\"dst\")\n", + "G.number_of_nodes(), G.number_of_edges()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NcsUxBqpu4zY" + }, + "source": [ + "By default, `nx.betweenness_centrality` will perform an all-pairs shortest path analysis when determining the centrality scores for each node. However, due to the much larger size of this graph, determining the shortest path for all pairs of nodes in the graph is not feasible. Instead, we'll use the parameter `k` to limit the number of shortest path computations used for determining the centrality scores, at the expense of accuracy. As we'll see when using a dataset this size with `nx.betweenness_centrality`, we have to limit `k` to `1` which is not practical but is sufficient here for demonstration purposes (since anything larger than `1` will result in many minutes of execution time)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gNDWbj3kAk3j" + }, + "outputs": [], + "source": [ + "%%time\n", + "bc_results = nx.betweenness_centrality(G, k=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NB8xmxMd1PlX" + }, + "source": [ + "Now we'll configure NetworkX to use the `nx-cugraph` backend (again, using the name convention that drops the package name's `nx-` prefix) and run the same call. Because this is a Graph that `nx-cugraph` hasn't seen before, the runtime will include the time to convert and cache a GPU-based graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "xUYNG1xhvbWc" + }, + "outputs": [], + "source": [ + "nx.config.backend_priority = [\"cugraph\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cmK8ZuQGvfPo" + }, + "outputs": [], + "source": [ + "%%time\n", + "bc_results = nx.betweenness_centrality(G, k=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vdHb1YXP15TZ" + }, + "source": [ + "Let's run betweenness centrality again, now with a more useful number of samples by setting `k=100`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fKjIrzL-vrGS" + }, + "outputs": [], + "source": [ + "%%time\n", + "bc_results = nx.betweenness_centrality(G, k=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QeMcrAX2HZSM" + }, + "source": [ + "Let's also run pagerank on the same dataset to compare." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gR8ID6ekHgHt" + }, + "outputs": [], + "source": [ + "nx.config.backend_priority = []" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rTFuvX5wb_c1" + }, + "outputs": [], + "source": [ + "%%time\n", + "nx_pr_results = nx.pagerank(G)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8sJx9aeJV9hv" + }, + "outputs": [], + "source": [ + "%%time\n", + "nxcg_pr_results = nx.pagerank(G, backend=\"cugraph\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wGOVQ6ZyY4Ih" + }, + "outputs": [], + "source": [ + "sorted(nx_pr_results) == sorted(nxcg_pr_results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k2DfAaZaDIBj" + }, + "source": [ + "---\n", + "\n", + "Information on the U.S. Patent Citation Network dataset used in this notebook is as follows:\n", + "
Authors: Jure Leskovec and Andrej Krevl\n", + "
Title: SNAP Datasets, Stanford Large Network Dataset Collection\n", + "
URL: http://snap.stanford.edu/data\n", + "
Date: June 2014\n", + "
\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From e6be36761b5d304f9d412878e9a40c422200631d Mon Sep 17 00:00:00 2001 From: Don Acosta <97529984+acostadon@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:19:20 -0400 Subject: [PATCH 20/37] Implementing some of the VDR feedback (#4674) Adding some content and navigation options per VDR, resolves https://github.com/rapidsai/graph_dl/issues/594 resolves https://github.com/rapidsai/graph_dl/issues/593 --------- Co-authored-by: rlratzel --- docs/cugraph/source/index.rst | 102 ++++++++++++++++++++------------ docs/cugraph/source/top_toc.rst | 13 ++++ 2 files changed, 76 insertions(+), 39 deletions(-) create mode 100644 docs/cugraph/source/top_toc.rst diff --git a/docs/cugraph/source/index.rst b/docs/cugraph/source/index.rst index 9ea9e4d65cf..259a36b8fd6 100644 --- a/docs/cugraph/source/index.rst +++ b/docs/cugraph/source/index.rst @@ -1,58 +1,82 @@ RAPIDS Graph documentation ========================== + .. image:: images/cugraph_logo_2.png :width: 600 -*Making graph analytics fast and easy regardless of scale* - - -.. list-table:: RAPIDS Graph covers a range of graph libraries and packages, that includes: - :widths: 25 25 25 - :header-rows: 1 - - * - Core - - GNN - - Extension - * - :abbr:`cugraph (Python wrapper with lots of convenience functions)` - - :abbr:`cugraph-ops (GNN aggregators and operators)` - - :abbr:`cugraph-service (Graph-as-a-service provides both Client and Server packages)` - * - :abbr:`pylibcugraph (light-weight Python wrapper with no guard rails)` - - :abbr:`cugraph-dgl (Accelerated extensions for use with the DGL framework)` - - - * - :abbr:`libcugraph (C++ API)` - - :abbr:`cugraph-pyg (Accelerated extensions for use with the PyG framework)` - - - * - :abbr:`libcugraph_etl (C++ renumbering function for strings)` - - :abbr:`wholegraph (Shared memory-based GPU-accelerated GNN training)` - - -.. -| ~~~~~~~~~~~~ Introduction ~~~~~~~~~~~~ cuGraph is a library of graph algorithms that seamlessly integrates into the RAPIDS data science ecosystem and allows the data scientist to easily call -graph algorithms using data stored in GPU DataFrames, NetworkX Graphs, or -even CuPy or SciPy sparse Matrices. +graph algorithms using data stored in GPU DataFrames, NetworkX Graphs, or even +CuPy or SciPy sparse Matrices. Our major integration effort with NetworkX +allows for **zero code change** GPU acceleration through the use of the +nx-cugraph backend. NetworkX and the nx-cugraph backend offer a seamless +transition to GPU accelerated graph analytics for NetworkX users with access to +a supported GPU. + +Getting started with cuGraph + +Required hardware/software for cuGraph and `RAPIDS `_ + * NVIDIA GPU, Volta architecture or later, with `compute capability 7.0+`_ + * CUDA 11.2-11.8, 12.0-12.5 + * Python version 3.10, 3.11, or 3.12 + * NetworkX version 3.0 or newer in order to use use the nx-cuGraph backend. NetworkX version 3.4 or newer is recommended. (`see below <#cugraph-using-networkx-code>`). + +Installation +The latest RAPIDS System Requirements documentation is located `here `_. + +This includes several ways to set up cuGraph + +* From Unix + + * `Conda `_ + * `Docker `_ + * `pip `_ + + +**Note: Windows use of RAPIDS depends on prior installation of** `WSL2 `_. + +* From Windows -Note: We are redoing all of our documents, please be patient as we update -the docs and links + * `Conda `_ + * `Docker `_ + * `pip `_ -| + +cuGraph Using NetworkX Code + +cuGraph is now available as a NetworkX backend using `nx-cugraph `_. +nx-cugraph offers NetworkX users a **zero code change** option to accelerate +their existing NetworkX code using an NVIDIA GPU and cuGraph. + + + Cugraph API Example + + .. code-block:: python + + import cugraph + import cudf + + # Create an instance of the popular Zachary Karate Club graph + from cugraph.datasets import karate + G = karate.get_graph() + + # Call cugraph.degree_centrality + vertex_bc = cugraph.degree_centrality(G) + +There are several resources containing cuGraph examples, `the cuGraph notebook repository `_ +has many examples of loading graph data and running algorithms in Jupyter notebooks. +The `cuGraph test code _` contain python scripts setting up and calling cuGraph algorithms. +A simple example of `testing the degree centrality algorithm `_ +is a good place to start. Some of these show `multi-GPU tests/examples `_ with larger data sets as well. .. toctree:: :maxdepth: 2 - :caption: Contents: - - basics/index - nx_cugraph/index - installation/index - tutorials/index - graph_support/index - wholegraph/index - references/index - api_docs/index + + top_toc Indices and tables ================== diff --git a/docs/cugraph/source/top_toc.rst b/docs/cugraph/source/top_toc.rst new file mode 100644 index 00000000000..8e31e70ca78 --- /dev/null +++ b/docs/cugraph/source/top_toc.rst @@ -0,0 +1,13 @@ +.. toctree:: + :maxdepth: 2 + :caption: cuGraph documentation Contents: + :name: top_toc + + basics/index + nx_cugraph/index + installation/index + tutorials/index + graph_support/index + wholegraph/index + references/index + api_docs/index From 5fad4356729482ae5d4843a6e74330f3aa81a59c Mon Sep 17 00:00:00 2001 From: Joseph Nke <76006812+jnke2016@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:56:46 +0100 Subject: [PATCH 21/37] Refactor the python function symmetrizing the edgelist (#4649) This PR updates the python API to symmetrize the edge list through the CAPI for PLC algorithms. This PR also deprecates the legacy python function symmetrizing the edge list closes #4588 --- cpp/include/cugraph_c/graph.h | 14 +- cpp/src/c_api/graph_mg.cpp | 30 ++++ cpp/src/c_api/graph_sg.cpp | 61 ++++++++ cpp/tests/c_api/create_graph_test.c | 6 + cpp/tests/c_api/mg_create_graph_test.c | 3 + .../cugraph/structure/graph_classes.py | 27 +++- .../simpleDistributedGraph.py | 56 +++++--- .../graph_implementation/simpleGraph.py | 131 +++++++++++++----- .../cugraph/cugraph/structure/hypergraph.py | 27 ++++ .../cugraph/structure/property_graph.py | 28 ++++ .../cugraph/cugraph/structure/symmetrize.py | 5 + .../tests/sampling/test_random_walks_mg.py | 15 +- .../sampling/test_uniform_neighbor_sample.py | 28 +++- .../test_uniform_neighbor_sample_mg.py | 10 ++ .../cugraph/tests/structure/test_graph.py | 13 ++ .../tests/structure/test_multigraph.py | 4 +- .../cugraph/tests/utils/test_dataset.py | 24 ++++ .../cugraph/cugraph/utilities/nx_factory.py | 41 +++++- python/cugraph/pytest.ini | 1 + .../pylibcugraph/_cugraph_c/graph.pxd | 3 + python/pylibcugraph/pylibcugraph/graphs.pyx | 21 ++- 21 files changed, 477 insertions(+), 71 deletions(-) diff --git a/cpp/include/cugraph_c/graph.h b/cpp/include/cugraph_c/graph.h index 00fce0493a3..d812b503778 100644 --- a/cpp/include/cugraph_c/graph.h +++ b/cpp/include/cugraph_c/graph.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * Copyright (c) 2021-2024, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,6 +105,8 @@ cugraph_error_code_t cugraph_sg_graph_create( weights, * or take the maximum weight), the caller should remove specific edges themselves and not rely * on this flag. + * @param [in] symmetrize If true, symmetrize the edgelist. The symmetrization of edges + * with edge_ids and/or edge_type_ids is currently not supported. * @param [in] do_expensive_check If true, do expensive checks to validate the input data * is consistent with software assumptions. If false bypass these checks. * @param [out] graph A pointer to the graph object @@ -126,6 +128,7 @@ cugraph_error_code_t cugraph_graph_create_sg( bool_t renumber, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error); @@ -150,6 +153,8 @@ cugraph_error_code_t cugraph_graph_create_sg( * If false, do not renumber. Renumbering enables some significant optimizations within * the graph primitives library, so it is strongly encouraged. Renumbering is required if * the vertices are not sequential integer values from 0 to num_vertices. + * @param [in] symmetrize If true, symmetrize the edgelist. The symmetrization of edges + * with edge_ids and/or edge_type_ids is currently not supported. * @param [in] do_expensive_check If true, do expensive checks to validate the input data * is consistent with software assumptions. If false bypass these checks. * @param [out] graph A pointer to the graph object @@ -168,6 +173,7 @@ cugraph_error_code_t cugraph_sg_graph_create_from_csr( const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error); @@ -190,6 +196,8 @@ cugraph_error_code_t cugraph_sg_graph_create_from_csr( * If false, do not renumber. Renumbering enables some significant optimizations within * the graph primitives library, so it is strongly encouraged. Renumbering is required if * the vertices are not sequential integer values from 0 to num_vertices. + * @param [in] symmetrize If true, symmetrize the edgelist. The symmetrization of edges + * with edge_ids and/or edge_type_ids is currently not supported. * @param [in] do_expensive_check If true, do expensive checks to validate the input data * is consistent with software assumptions. If false bypass these checks. * @param [out] graph A pointer to the graph object @@ -208,6 +216,7 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error); @@ -289,6 +298,8 @@ cugraph_error_code_t cugraph_mg_graph_create( * Note that setting this flag will arbitrarily select one instance of a multi edge to be the * edge that survives. If the edges have properties that should be honored (e.g. sum the * weights, or take the maximum weight), the caller should do that on not rely on this flag. + * @param [in] symmetrize If true, symmetrize the edgelist. The symmetrization of edges + * with edge_ids and/or edge_type_ids is currently not supported. * @param [in] do_expensive_check If true, do expensive checks to validate the input data * is consistent with software assumptions. If false bypass these checks. * @param [out] graph A pointer to the graph object @@ -309,6 +320,7 @@ cugraph_error_code_t cugraph_graph_create_mg( size_t num_arrays, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error); diff --git a/cpp/src/c_api/graph_mg.cpp b/cpp/src/c_api/graph_mg.cpp index cc4acd31743..fc8014a5dd8 100644 --- a/cpp/src/c_api/graph_mg.cpp +++ b/cpp/src/c_api/graph_mg.cpp @@ -71,6 +71,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { bool_t renumber_; bool_t drop_self_loops_; bool_t drop_multi_edges_; + bool_t symmetrize_; bool_t do_expensive_check_; cugraph::c_api::cugraph_graph_t* result_{}; @@ -91,6 +92,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { bool_t renumber, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t do_expensive_check) : abstract_functor(), properties_(properties), @@ -109,6 +111,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { renumber_(renumber), drop_self_loops_(drop_self_loops), drop_multi_edges_(drop_multi_edges), + symmetrize_(symmetrize), do_expensive_check_(do_expensive_check) { } @@ -224,6 +227,22 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { : false); } + if (symmetrize_) { + if (edgelist_edge_ids || edgelist_edge_types) { + // Currently doesn't support the symmetrization of edgelist with edge_ids and edge_types + unsupported(); + } + + // Symmetrize the edgelist + std::tie(edgelist_srcs, edgelist_dsts, edgelist_weights) = + cugraph::symmetrize_edgelist( + handle_, + std::move(edgelist_srcs), + std::move(edgelist_dsts), + std::move(edgelist_weights), + false); + } + std::tie(*graph, new_edge_weights, new_edge_ids, new_edge_types, new_number_map) = cugraph::create_graph_from_edgelisttype_; } + if (symmetrize == TRUE) { + CAPI_EXPECTS((properties->is_symmetric == TRUE), + CUGRAPH_INVALID_INPUT, + "Invalid input arguments: The graph property must be symmetric if 'symmetrize' " + "is set to True.", + *error); + } + CAPI_EXPECTS(p_src[i]->type_ == vertex_type, CUGRAPH_INVALID_INPUT, "Invalid input arguments: all vertex types must match", @@ -488,6 +516,7 @@ extern "C" cugraph_error_code_t cugraph_graph_create_mg( bool_t::TRUE, drop_self_loops, drop_multi_edges, + symmetrize, do_expensive_check); try { @@ -534,6 +563,7 @@ extern "C" cugraph_error_code_t cugraph_mg_graph_create( 1, FALSE, FALSE, + FALSE, do_expensive_check, graph, error); diff --git a/cpp/src/c_api/graph_sg.cpp b/cpp/src/c_api/graph_sg.cpp index ff71471a8d0..f6ea8e4142e 100644 --- a/cpp/src/c_api/graph_sg.cpp +++ b/cpp/src/c_api/graph_sg.cpp @@ -43,6 +43,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { bool_t renumber_; bool_t drop_self_loops_; bool_t drop_multi_edges_; + bool_t symmetrize_; bool_t do_expensive_check_; cugraph_data_type_id_t edge_type_; cugraph::c_api::cugraph_graph_t* result_{}; @@ -58,6 +59,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { bool_t renumber, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t do_expensive_check, cugraph_data_type_id_t edge_type) : abstract_functor(), @@ -72,6 +74,7 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { renumber_(renumber), drop_self_loops_(drop_self_loops), drop_multi_edges_(drop_multi_edges), + symmetrize_(symmetrize), do_expensive_check_(do_expensive_check), edge_type_(edge_type) { @@ -207,6 +210,22 @@ struct create_graph_functor : public cugraph::c_api::abstract_functor { : false); } + if (symmetrize_) { + if (edgelist_edge_ids || edgelist_edge_types) { + // Currently doesn't support the symmetrization with edge_ids and edge_types + unsupported(); + } + + // Symmetrize the edgelist + std::tie(edgelist_srcs, edgelist_dsts, edgelist_weights) = + cugraph::symmetrize_edgelist( + handle_, + std::move(edgelist_srcs), + std::move(edgelist_dsts), + std::move(edgelist_weights), + false); + } + std::tie(*graph, new_edge_weights, new_edge_ids, new_edge_types, new_number_map) = cugraph::create_graph_from_edgelist, edge_type_id_t>(handle_); + if (symmetrize_) { + if (edgelist_edge_ids || edgelist_edge_types) { + // Currently doesn't support the symmetrization with edge_ids and edge_types + unsupported(); + } + + // Symmetrize the edgelist + std::tie(edgelist_srcs, edgelist_dsts, edgelist_weights) = + cugraph::symmetrize_edgelist( + handle_, + std::move(edgelist_srcs), + std::move(edgelist_dsts), + std::move(edgelist_weights), + false); + } + std::tie(*graph, new_edge_weights, new_edge_ids, new_edge_types, new_number_map) = cugraph::create_graph_from_edgelist(edge_type_ids); + if (symmetrize == TRUE) { + CAPI_EXPECTS((properties->is_symmetric == TRUE), + CUGRAPH_INVALID_INPUT, + "Invalid input arguments: The graph property must be symmetric if 'symmetrize' is " + "set to True.", + *error); + } + CAPI_EXPECTS(p_src->size_ == p_dst->size_, CUGRAPH_INVALID_INPUT, "Invalid input arguments: src size != dst size.", @@ -606,6 +653,7 @@ extern "C" cugraph_error_code_t cugraph_graph_create_sg( renumber, drop_self_loops, drop_multi_edges, + symmetrize, do_expensive_check, edge_type); @@ -658,6 +706,7 @@ extern "C" cugraph_error_code_t cugraph_sg_graph_create( renumber, FALSE, FALSE, + FALSE, do_expensive_check, graph, error); @@ -673,6 +722,7 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error) @@ -707,6 +757,14 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( weight_type = cugraph_data_type_id_t::FLOAT32; } + if (symmetrize == TRUE) { + CAPI_EXPECTS((properties->is_symmetric == TRUE), + CUGRAPH_INVALID_INPUT, + "Invalid input arguments: The graph property must be symmetric if 'symmetrize' is " + "set to True.", + *error); + } + CAPI_EXPECTS( (edge_type_ids == nullptr && edge_ids == nullptr) || (edge_type_ids != nullptr && edge_ids != nullptr), @@ -735,6 +793,7 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( p_edge_ids, p_edge_type_ids, renumber, + FALSE, // symmetrize do_expensive_check); try { @@ -770,6 +829,7 @@ cugraph_error_code_t cugraph_sg_graph_create_from_csr( const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error) @@ -783,6 +843,7 @@ cugraph_error_code_t cugraph_sg_graph_create_from_csr( edge_type_ids, store_transposed, renumber, + symmetrize, do_expensive_check, graph, error); diff --git a/cpp/tests/c_api/create_graph_test.c b/cpp/tests/c_api/create_graph_test.c index 41b8691e79c..104787e4c7b 100644 --- a/cpp/tests/c_api/create_graph_test.c +++ b/cpp/tests/c_api/create_graph_test.c @@ -104,6 +104,7 @@ int test_create_sg_graph_simple() FALSE, FALSE, FALSE, + FALSE, &graph, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -213,6 +214,7 @@ int test_create_sg_graph_csr() FALSE, FALSE, FALSE, + FALSE, &graph, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -408,6 +410,7 @@ int test_create_sg_graph_symmetric_error() FALSE, FALSE, FALSE, + FALSE, TRUE, &graph, &ret_error); @@ -526,6 +529,7 @@ int test_create_sg_graph_with_isolated_vertices() FALSE, FALSE, FALSE, + FALSE, &graph, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -675,6 +679,7 @@ int test_create_sg_graph_csr_with_isolated() FALSE, FALSE, FALSE, + FALSE, &graph, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -840,6 +845,7 @@ int test_create_sg_graph_with_isolated_vertices_multi_input() TRUE, TRUE, FALSE, + FALSE, &graph, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); diff --git a/cpp/tests/c_api/mg_create_graph_test.c b/cpp/tests/c_api/mg_create_graph_test.c index dd817881325..12579f26d06 100644 --- a/cpp/tests/c_api/mg_create_graph_test.c +++ b/cpp/tests/c_api/mg_create_graph_test.c @@ -109,6 +109,7 @@ int test_create_mg_graph_simple(const cugraph_resource_handle_t* handle) 1, FALSE, FALSE, + FALSE, TRUE, &graph, &ret_error); @@ -251,6 +252,7 @@ int test_create_mg_graph_multiple_edge_lists(const cugraph_resource_handle_t* ha num_local_arrays, FALSE, FALSE, + FALSE, TRUE, &graph, &ret_error); @@ -446,6 +448,7 @@ int test_create_mg_graph_multiple_edge_lists_multi_edge(const cugraph_resource_h num_local_arrays, TRUE, TRUE, + FALSE, TRUE, &graph, &ret_error); diff --git a/python/cugraph/cugraph/structure/graph_classes.py b/python/cugraph/cugraph/structure/graph_classes.py index e90c0576f55..84234f7e904 100644 --- a/python/cugraph/cugraph/structure/graph_classes.py +++ b/python/cugraph/cugraph/structure/graph_classes.py @@ -116,6 +116,7 @@ def from_cudf_edgelist( renumber=True, store_transposed=False, legacy_renum_only=False, + symmetrize=None, ): """ Initialize a graph from the edge list. It is an error to call this @@ -174,6 +175,15 @@ def from_cudf_edgelist( This parameter is deprecated and will be removed. + symmetrize: bool, optional (default=None) + If True, symmetrize the edge list for an undirected graph. Setting + this flag to True for a directed graph returns an error. The default + behavior symmetrizes the edges if the graph is undirected. This flag + cannot be set to True if the edgelist contains edge IDs or edge Types. + If the incoming edgelist is intended for an undirected graph and it is + known to be symmetric, this flag can be set to False to skip the + symmetrization step for better performance. + Examples -------- >>> df = cudf.read_csv(datasets_path / 'karate.csv', delimiter=' ', @@ -201,6 +211,7 @@ def from_cudf_edgelist( renumber=renumber, store_transposed=store_transposed, legacy_renum_only=legacy_renum_only, + symmetrize=symmetrize, ) def from_cudf_adjlist( @@ -210,6 +221,7 @@ def from_cudf_adjlist( value_col=None, renumber=True, store_transposed=False, + symmetrize=None, ): """ Initialize a graph from the adjacency list. It is an error to call this @@ -247,6 +259,14 @@ def from_cudf_adjlist( store_transposed : bool, optional (default=False) If True, stores the transpose of the adjacency matrix. Required for certain algorithms. + symmetrize: bool, optional (default=None) + If True, symmetrize the edge list for an undirected graph. Setting + this flag to True for a directed graph returns an error. The default + behavior symmetrizes the edges if the graph is undirected. This flag + cannot be set to True if the edgelist contains edge IDs or edge Types. + If the incoming edgelist is intended for an undirected graph and it is + known to be symmetric, this flag can be set to False to skip the + symmetrization step for better performance. Examples -------- @@ -268,7 +288,12 @@ def from_cudf_adjlist( raise RuntimeError("Graph is already initialized") elif self._Impl.edgelist is not None or self._Impl.adjlist is not None: raise RuntimeError("Graph already has values") - self._Impl._simpleGraphImpl__from_adjlist(offset_col, index_col, value_col) + self._Impl._simpleGraphImpl__from_adjlist( + offset_col=offset_col, + index_col=index_col, + value_col=value_col, + symmetrize=symmetrize, + ) def from_dask_cudf_edgelist( self, diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 7f3f7e83e59..83dad234287 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -34,7 +34,6 @@ ) from cugraph.structure.number_map import NumberMap -from cugraph.structure.symmetrize import symmetrize from cugraph.dask.common.part_utils import ( persist_dask_df_equal_parts_per_worker, ) @@ -98,6 +97,7 @@ def _make_plc_graph( edge_id_type, edge_type_id, drop_multi_edges, + symmetrize, ): weights = None edge_ids = None @@ -151,6 +151,7 @@ def _make_plc_graph( else ([cudf.Series(dtype=edge_type_id)] if edge_type_id else None), num_arrays=num_arrays, store_transposed=store_transposed, + symmetrize=symmetrize, do_expensive_check=False, drop_multi_edges=drop_multi_edges, ) @@ -172,6 +173,7 @@ def __from_edgelist( renumber=True, store_transposed=False, legacy_renum_only=False, + symmetrize=None, ): if not isinstance(input_ddf, dask_cudf.DataFrame): raise TypeError("input should be a dask_cudf dataFrame") @@ -184,6 +186,35 @@ def __from_edgelist( ].dtype not in [np.int32, np.int64]: raise ValueError("set renumber to True for non integer columns ids") + if self.properties.directed and symmetrize: + raise ValueError( + "The edgelist can only be symmetrized for undirected graphs." + ) + + if self.properties.directed: + if symmetrize: + raise ValueError( + "The edgelist can only be symmetrized for undirected graphs." + ) + else: + if symmetrize or symmetrize is None: + unsupported = False + if edge_id is not None or edge_type is not None: + unsupported = True + if isinstance(edge_attr, list): + if len(edge_attr) > 1: + unsupported = True + if unsupported: + raise ValueError( + "Edge list containing Edge Ids or Types can't be symmetrized. " + "If the edges are already symmetric, set the 'symmetrize' " + "flag to False" + ) + + if symmetrize is None: + # default behavior + symmetrize = not self.properties.directed + s_col = source d_col = destination if not isinstance(s_col, list): @@ -266,27 +297,11 @@ def __from_edgelist( ddf_columns += value_col_names input_ddf = input_ddf[ddf_columns] - if len(value_col_names) == 0: - source_col, dest_col = symmetrize( - input_ddf, - source, - destination, - multi=True, # Deprecated parameter - symmetrize=not self.properties.directed, - ) - value_col = None - else: - source_col, dest_col, value_col = symmetrize( - input_ddf, - source, - destination, - value_col_names, - multi=True, # Deprecated parameter - symmetrize=not self.properties.directed, - ) - # Create a dask_cudf dataframe from the cudf series # or dataframe objects obtained from symmetrization + source_col = input_ddf[source] + dest_col = input_ddf[destination] + value_col = input_ddf[value_col_names] if isinstance(source_col, dask_cudf.Series): frames = [ source_col.to_frame(name=source), @@ -370,6 +385,7 @@ def __from_edgelist( self.edge_id_type, self.edge_type_id_type, not self.properties.multi_edge, + not self.properties.directed, ) for w, edata in persisted_keys_d.items() } diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index bc5cca67c2e..858b114ebdc 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -13,7 +13,7 @@ from cugraph.structure import graph_primtypes_wrapper from cugraph.structure.replicate_edgelist import replicate_cudf_dataframe -from cugraph.structure.symmetrize import symmetrize +from cugraph.structure.symmetrize import symmetrize as symmetrize_df from cugraph.structure.number_map import NumberMap import cugraph.dask.common.mg_utils as mg_utils import cudf @@ -134,6 +134,7 @@ def __from_edgelist( renumber=True, legacy_renum_only=False, store_transposed=False, + symmetrize=None, ): if legacy_renum_only: warning_msg = ( @@ -143,6 +144,35 @@ def __from_edgelist( warning_msg, ) + if self.properties.directed and symmetrize: + raise ValueError( + "The edgelist can only be symmetrized for undirected graphs." + ) + + if self.properties.directed: + if symmetrize: + raise ValueError( + "The edgelist can only be symmetrized for undirected graphs." + ) + else: + if symmetrize or symmetrize is None: + unsupported = False + if edge_id is not None or edge_type is not None: + unsupported = True + if isinstance(edge_attr, list): + if len(edge_attr) > 1: + unsupported = True + if unsupported: + raise ValueError( + "Edge list containing Edge Ids or Types can't be symmetrized. " + "If the edges are already symmetric, set the 'symmetrize' " + "flag to False" + ) + + if symmetrize is None: + # default behavior + symmetrize = not self.properties.directed + # Verify column names present in input DataFrame s_col = source d_col = destination @@ -264,45 +294,27 @@ def __from_edgelist( ) raise ValueError("set renumber to True for non integer columns ids") - # The dataframe will be symmetrized iff the graph is undirected - # otherwise the inital dataframe will be returned. Duplicated edges - # will be dropped unless the graph is a MultiGraph(Not Implemented yet) - # TODO: Update Symmetrize to work on Graph and/or DataFrame + # The dataframe will be symmetrized iff the graph is undirected with the + # symmetrize flag set to None or True otherwise, the inital dataframe will + # be returned. If set to False, the API will assume that the edges are already + # symmetric. Duplicated edges will be dropped unless the graph is a + # MultiGraph(Not Implemented yet) + if edge_attr is not None: - source_col, dest_col, value_col = symmetrize( - elist, - source, - destination, - edge_attr, - multi=self.properties.multi_edge, # Deprecated parameter - symmetrize=not self.properties.directed, - ) + value_col = { + self.edgeWeightCol: elist[weight] if weight in edge_attr else None, + self.edgeIdCol: elist[edge_id] if edge_id in edge_attr else None, + self.edgeTypeCol: elist[edge_type] if edge_type in edge_attr else None, + } - if isinstance(value_col, cudf.DataFrame): - value_dict = {} - for i in value_col.columns: - value_dict[i] = value_col[i] - value_col = value_dict else: value_col = None - source_col, dest_col = symmetrize( - elist, - source, - destination, - multi=self.properties.multi_edge, # Deprecated parameter - symmetrize=not self.properties.directed, - ) - - if isinstance(value_col, dict): - value_col = { - self.edgeWeightCol: value_col[weight] if weight in value_col else None, - self.edgeIdCol: value_col[edge_id] if edge_id in value_col else None, - self.edgeTypeCol: value_col[edge_type] - if edge_type in value_col - else None, - } - self.edgelist = simpleGraphImpl.EdgeList(source_col, dest_col, value_col) + # FIXME: if the user calls self.edgelist.edgelist_df after creating a + # symmetric graph, return the symmetric edgelist? + self.edgelist = simpleGraphImpl.EdgeList( + elist[source], elist[destination], value_col + ) if self.batch_enabled: self._replicate_edgelist() @@ -312,6 +324,7 @@ def __from_edgelist( store_transposed=store_transposed, renumber=renumber, drop_multi_edges=not self.properties.multi_edge, + symmetrize=symmetrize, ) def to_pandas_edgelist( @@ -549,13 +562,23 @@ def __from_adjlist( value_col=None, renumber=True, store_transposed=False, + symmetrize=None, ): self.adjlist = simpleGraphImpl.AdjList(offset_col, index_col, value_col) + + if self.properties.directed and symmetrize: + raise ValueError("The edges can only be symmetrized for undirected graphs.") + if value_col is not None: self.properties.weighted = True self._make_plc_graph( - value_col=value_col, store_transposed=store_transposed, renumber=renumber + value_col=value_col, + store_transposed=store_transposed, + renumber=renumber, + symmetrize=not self.properties.directed + if symmetrize is None + else symmetrize, ) if self.batch_enabled: @@ -1146,6 +1169,7 @@ def _make_plc_graph( store_transposed: bool = False, renumber: bool = True, drop_multi_edges: bool = False, + symmetrize: bool = False, ): """ Parameters @@ -1164,6 +1188,8 @@ def _make_plc_graph( int32 or int64 type. drop_multi_edges: bool (default=False) Whether to drop multi edges + symmetrize: bool (default=False) + Whether to symmetrize """ if value_col is None: @@ -1228,6 +1254,7 @@ def _make_plc_graph( do_expensive_check=True, input_array_format=input_array_format, drop_multi_edges=drop_multi_edges, + symmetrize=symmetrize, ) def to_directed(self, DiG, store_transposed=False): @@ -1253,12 +1280,18 @@ def to_directed(self, DiG, store_transposed=False): DiG._make_plc_graph(value_col, store_transposed) def to_undirected(self, G, store_transposed=False): + """ Return an undirected copy of the graph. Note: This will discard any edge ids or edge types but will preserve edge weights if present. """ + # FIXME: Update this function to not call the deprecated + # symmetrize function. + # 1) Import the C++ function that symmetrize a graph + # 2) decompress the edgelist to update 'simpleGraphImpl.EdgeList' + # Doesn't work for edgelists with edge_ids and edge_types. G.properties.renumbered = self.properties.renumbered G.renumber_map = self.renumber_map if self.properties.directed is False: @@ -1268,14 +1301,14 @@ def to_undirected(self, G, store_transposed=False): else: df = self.edgelist.edgelist_df if self.edgelist.weights: - source_col, dest_col, value_col = symmetrize( + source_col, dest_col, value_col = symmetrize_df( df, simpleGraphImpl.srcCol, simpleGraphImpl.dstCol, simpleGraphImpl.edgeWeightCol, ) else: - source_col, dest_col = symmetrize( + source_col, dest_col = symmetrize_df( df, simpleGraphImpl.srcCol, simpleGraphImpl.dstCol ) value_col = None @@ -1310,6 +1343,28 @@ def has_edge(self, u, v): v = tmp["id"][1] df = self.edgelist.edgelist_df + + if self.edgelist.weights: + # FIXME: Update this function to not call the deprecated + # symmetrize function. + source_col, dest_col, value_col = symmetrize_df( + df, + simpleGraphImpl.srcCol, + simpleGraphImpl.dstCol, + simpleGraphImpl.edgeWeightCol, + symmetrize=not self.properties.directed, + ) + else: + source_col, dest_col = symmetrize_df( + df, + simpleGraphImpl.srcCol, + simpleGraphImpl.dstCol, + symmetrize=not self.properties.directed, + ) + value_col = None + + self.edgelist = simpleGraphImpl.EdgeList(source_col, dest_col, value_col) + return ( (df[simpleGraphImpl.srcCol] == u) & (df[simpleGraphImpl.dstCol] == v) ).any() diff --git a/python/cugraph/cugraph/structure/hypergraph.py b/python/cugraph/cugraph/structure/hypergraph.py index bdc98333da0..55e6bbcca3d 100644 --- a/python/cugraph/cugraph/structure/hypergraph.py +++ b/python/cugraph/cugraph/structure/hypergraph.py @@ -37,6 +37,7 @@ import cudf import numpy as np from cugraph.structure.graph_classes import Graph +from cugraph.structure.symmetrize import symmetrize def hypergraph( @@ -277,6 +278,32 @@ def hypergraph( renumber=True, ) + df = cudf.DataFrame() + + # Need to refactor this code as it uses the + # deprecated symmetrize call. + if "weights" in graph.edgelist.edgelist_df: + source_col, dest_col, value_col = symmetrize( + graph.edgelist.edgelist_df, + "src", + "dst", + "weights", + symmetrize=not graph.is_directed(), + ) + + df["src"] = source_col + df["dst"] = dest_col + df["weights"] = value_col + else: + source_col, dest_col = symmetrize( + graph.edgelist.edgelist_df, "src", "dst", symmetrize=not graph.is_directed() + ) + + df["src"] = source_col + df["dst"] = dest_col + + graph.edgelist.edgelist_df = df + return { "nodes": nodes, "edges": edges, diff --git a/python/cugraph/cugraph/structure/property_graph.py b/python/cugraph/cugraph/structure/property_graph.py index 53c1bf778c7..5f55a15888a 100644 --- a/python/cugraph/cugraph/structure/property_graph.py +++ b/python/cugraph/cugraph/structure/property_graph.py @@ -15,6 +15,7 @@ import numpy as np import cugraph +from cugraph.structure.symmetrize import symmetrize from cugraph.utilities.utils import ( import_optional, MissingModule, @@ -2005,6 +2006,33 @@ def edge_props_to_graph( else: G.from_pandas_edgelist(edge_prop_df.reset_index(), **create_args) + # FIXME: Property_graph does not fully leverage the PLC API yet. + # It still relies on the edges being symmetrized by the deprecated + # symmetrize function. + + # Symmetrize the internal representation of the edgelists + + if edge_attr is not None: + source_col, dest_col, value_col = symmetrize( + G.edgelist.edgelist_df, + "src", + "dst", + "weights", + symmetrize=not G.is_directed(), + ) + else: + source_col, dest_col = symmetrize( + G.edgelist.edgelist_df, "src", "dst", symmetrize=not G.is_directed() + ) + + renumbered_edge_prop_df = cudf.DataFrame() + renumbered_edge_prop_df["src"] = source_col + renumbered_edge_prop_df["dst"] = dest_col + if edge_attr: + renumbered_edge_prop_df["weights"] = value_col + + G.edgelist.edgelist_df = renumbered_edge_prop_df + if add_edge_data: # Set the edge_data on the resulting Graph to a DataFrame # containing the edges and the edge ID for each. Edge IDs are diff --git a/python/cugraph/cugraph/structure/symmetrize.py b/python/cugraph/cugraph/structure/symmetrize.py index 3e46d81b6ff..b59661b1cd4 100644 --- a/python/cugraph/cugraph/structure/symmetrize.py +++ b/python/cugraph/cugraph/structure/symmetrize.py @@ -257,6 +257,11 @@ def symmetrize( >>> df['values'] = cudf.Series(M['2']) >>> src, dst, val = symmetrize(df, 'sources', 'destinations', 'values', multi=True) """ + warnings.warn( + "This method is deprecated and will no longer be supported. The symmetrization " + "of the edges are only supported by setting the 'symmetrize' flag to 'True'", + FutureWarning, + ) # FIXME: Redundant check that should be done at the graph creation if "edge_id" in input_df.columns and symmetrize: diff --git a/python/cugraph/cugraph/tests/sampling/test_random_walks_mg.py b/python/cugraph/cugraph/tests/sampling/test_random_walks_mg.py index 2db3c6f5907..34eeb2902f8 100644 --- a/python/cugraph/cugraph/tests/sampling/test_random_walks_mg.py +++ b/python/cugraph/cugraph/tests/sampling/test_random_walks_mg.py @@ -19,8 +19,10 @@ import cugraph import dask_cudf import cugraph.dask as dcg +import cudf from cugraph.testing import SMALL_DATASETS from cugraph.datasets import karate_asymmetric +from cugraph.structure.symmetrize import symmetrize from pylibcugraph.testing.utils import gen_fixture_params_product @@ -205,4 +207,15 @@ def input_graph(request): def test_dask_mg_random_walks(dask_client, input_graph): path_data, seeds, max_depth = calc_random_walks(input_graph) df_G = input_graph.input_df.compute().reset_index(drop=True) - check_random_walks(input_graph, path_data, seeds, max_depth, df_G) + + # FIXME: leverages the deprecated symmetrize call + source_col, dest_col, value_col = symmetrize( + df_G, "src", "dst", "value", symmetrize=not input_graph.is_directed() + ) + + df = cudf.DataFrame() + df["src"] = source_col + df["dst"] = dest_col + df["value"] = value_col + + check_random_walks(input_graph, path_data, seeds, max_depth, df) diff --git a/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample.py b/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample.py index 304ead6fea9..ad0dbe77f7d 100644 --- a/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample.py +++ b/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample.py @@ -21,6 +21,7 @@ from cugraph import uniform_neighbor_sample from cugraph.testing import UNDIRECTED_DATASETS from cugraph.datasets import email_Eu_core, small_tree +from cugraph.structure.symmetrize import symmetrize from pylibcugraph.testing.utils import gen_fixture_params_product @@ -148,6 +149,15 @@ def test_uniform_neighbor_sample_simple(input_combo): # should be 'None' if the datasets was never renumbered input_df = G.edgelist.edgelist_df + # FIXME: Uses the deprecated implementation of symmetrize. + source_col, dest_col = symmetrize( + input_df, "src", "dst", symmetrize=not G.is_directed() + ) + + input_df = cudf.DataFrame() + input_df["src"] = source_col + input_df["dst"] = dest_col + result_nbr = uniform_neighbor_sample( G, input_combo["start_list"], @@ -235,6 +245,19 @@ def test_uniform_neighbor_sample_tree(directed): G = cugraph.Graph(directed=directed) G.from_cudf_edgelist(df, "src", "dst", "value") + # FIXME: Uses the deprecated implementation of symmetrize. + source_col, dest_col, value_col = symmetrize( + G.edgelist.edgelist_df, "src", "dst", "weights", symmetrize=not G.is_directed() + ) + + # Retrieve the input dataframe. + # input_df != df if 'directed = False' because df will be symmetrized + # internally. + input_df = cudf.DataFrame() + input_df["src"] = source_col + input_df["dst"] = dest_col + input_df["value"] = value_col + # # Make sure the old C++ renumbering was skipped because: # 1) Pylibcugraph already does renumbering @@ -245,11 +268,6 @@ def test_uniform_neighbor_sample_tree(directed): assert G.renumbered is False - # Retrieve the input dataframe. - # input_df != df if 'directed = False' because df will be symmetrized - # internally. - input_df = G.edgelist.edgelist_df - # TODO: Incomplete, include more testing for tree graph as well as # for larger graphs start_list = cudf.Series([0, 0], dtype="int32") diff --git a/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample_mg.py b/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample_mg.py index c65535f98a2..4a85b49a66e 100644 --- a/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample_mg.py +++ b/python/cugraph/cugraph/tests/sampling/test_uniform_neighbor_sample_mg.py @@ -27,6 +27,7 @@ from cugraph.dask import uniform_neighbor_sample from cugraph.dask.common.mg_utils import is_single_gpu from cugraph.structure.symmetrize import _memory_efficient_drop_duplicates +from cugraph.structure.symmetrize import symmetrize_ddf from cugraph.datasets import email_Eu_core, small_tree from pylibcugraph.testing.utils import gen_fixture_params_product @@ -144,6 +145,10 @@ def test_mg_uniform_neighbor_sample_simple(dask_client, input_combo): input_df, vertex_col_name, len(workers) ) + input_df = symmetrize_ddf( + input_df, src_name="src", dst_name="dst", symmetrize=not dg.is_directed() + ) + result_nbr = uniform_neighbor_sample( dg, input_combo["start_list"], @@ -247,6 +252,11 @@ def test_mg_uniform_neighbor_sample_tree(dask_client, directed): # input_df != ddf if 'directed = False' because ddf will be symmetrized # internally. input_df = G.input_df + + input_df = symmetrize_ddf( + input_df, src_name="src", dst_name="dst", symmetrize=not G.is_directed() + ) + join = result_nbr.merge( input_df, left_on=[*result_nbr.columns[:2]], right_on=[*input_df.columns[:2]] ) diff --git a/python/cugraph/cugraph/tests/structure/test_graph.py b/python/cugraph/cugraph/tests/structure/test_graph.py index c0524fcfe77..48a0b257b12 100644 --- a/python/cugraph/cugraph/tests/structure/test_graph.py +++ b/python/cugraph/cugraph/tests/structure/test_graph.py @@ -25,6 +25,7 @@ from cugraph.testing import utils from cudf.testing import assert_series_equal from cudf.testing.testing import assert_frame_equal +from cugraph.structure.symmetrize import symmetrize # MG import dask_cudf @@ -534,6 +535,18 @@ def test_to_directed(graph_file): # cugraph add_edge_list G = cugraph.Graph() G.from_cudf_edgelist(cu_M, source="0", destination="1") + + # FIXME: Uses the deprecated implementation of symmetrize. + source_col, dest_col = symmetrize( + G.edgelist.edgelist_df, "src", "dst", symmetrize=not G.is_directed() + ) + + input_df = cudf.DataFrame() + input_df["src"] = source_col + input_df["dst"] = dest_col + + G.edgelist.edgelist_df = input_df + Gnx = nx.from_pandas_edgelist(M, source="0", target="1", create_using=nx.Graph()) DiG = G.to_directed() diff --git a/python/cugraph/cugraph/tests/structure/test_multigraph.py b/python/cugraph/cugraph/tests/structure/test_multigraph.py index a9ea617fdb8..e245894b479 100644 --- a/python/cugraph/cugraph/tests/structure/test_multigraph.py +++ b/python/cugraph/cugraph/tests/structure/test_multigraph.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2023, NVIDIA CORPORATION. +# Copyright (c) 2020-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -76,7 +76,7 @@ def test_Graph_from_MultiGraph(graph_file): G = cugraph.Graph(GM) Gnx = nx.Graph(GnxM) - assert Gnx.number_of_edges() == G.number_of_edges() + assert Gnx.number_of_edges() == G.number_of_edges(directed_edges=True) GdM = graph_file.get_graph(create_using=cugraph.MultiGraph(directed=True)) GnxdM = nx.from_pandas_edgelist( nxM, diff --git a/python/cugraph/cugraph/tests/utils/test_dataset.py b/python/cugraph/cugraph/tests/utils/test_dataset.py index a52b99dabfe..3873cd1c3e4 100644 --- a/python/cugraph/cugraph/tests/utils/test_dataset.py +++ b/python/cugraph/cugraph/tests/utils/test_dataset.py @@ -26,6 +26,7 @@ from cugraph.dask.common.mg_utils import is_single_gpu from cugraph.datasets import karate from cugraph.structure import Graph +from cugraph.structure.symmetrize import symmetrize from cugraph.testing import ( RAPIDS_DATASET_ROOT_DIR_PATH, ALL_DATASETS, @@ -379,6 +380,29 @@ def test_node_and_edge_count(dataset): download=True, create_using=Graph(directed=dataset_is_directed) ) + df = cudf.DataFrame() + if "weights" in G.edgelist.edgelist_df: + source_col, dest_col, value_col = symmetrize( + G.edgelist.edgelist_df, + "src", + "dst", + "weights", + symmetrize=not G.is_directed(), + ) + + df["src"] = source_col + df["dst"] = dest_col + df["weights"] = value_col + else: + source_col, dest_col = symmetrize( + G.edgelist.edgelist_df, "src", "dst", symmetrize=not G.is_directed() + ) + + df["src"] = source_col + df["dst"] = dest_col + + G.edgelist.edgelist_df = df + assert G.number_of_nodes() == dataset.metadata["number_of_nodes"] assert G.number_of_edges() == dataset.metadata["number_of_edges"] diff --git a/python/cugraph/cugraph/utilities/nx_factory.py b/python/cugraph/cugraph/utilities/nx_factory.py index d07d17978d7..794fb33a7a1 100644 --- a/python/cugraph/cugraph/utilities/nx_factory.py +++ b/python/cugraph/cugraph/utilities/nx_factory.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2023, NVIDIA CORPORATION. +# Copyright (c) 2020-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -24,6 +24,8 @@ from cudf import from_pandas from cudf.api.types import is_integer_dtype +from cugraph.structure.symmetrize import symmetrize + # nx will be a MissingModule instance if NetworkX is not installed (any # attribute access on a MissingModule instance results in a RuntimeError). nx = import_optional("networkx") @@ -129,6 +131,17 @@ def convert_from_nx( if is_weighted is False: _gdf = convert_unweighted_to_gdf(nxG, vertex_type) + # FIXME: The legacy algorithms do not support the PLC graph + # hence, the symmetrization cannot be performed at the graph + # creation. Use the deprecated 'symmetrize' function for now. + source_col, dest_col = symmetrize( + _gdf, "src", "dst", symmetrize=not G.is_directed() + ) + + _gdf = cudf.DataFrame() + + _gdf["src"] = source_col + _gdf["dst"] = dest_col G.from_cudf_edgelist( _gdf, source="src", @@ -140,6 +153,18 @@ def convert_from_nx( else: if weight is None: _gdf = convert_weighted_unnamed_to_gdf(nxG, vertex_type) + # FIXME: The legacy algorithms do not support the PLC graph + # hence, the symmetrization cannot be performed at the graph + # creation. Use the deprecated 'symmetrize' function for now. + source_col, dest_col, value_col = symmetrize( + _gdf, "src", "target", "weight", symmetrize=not G.is_directed() + ) + + _gdf = cudf.DataFrame() + + _gdf["src"] = source_col + _gdf["target"] = dest_col + _gdf["weight"] = value_col G.from_cudf_edgelist( _gdf, source="source", @@ -148,8 +173,22 @@ def convert_from_nx( renumber=do_renumber, store_transposed=store_transposed, ) + else: _gdf = convert_weighted_named_to_gdf(nxG, weight, vertex_type) + # FIXME: The legacy algorithms do not support the PLC graph + # hence, the symmetrization cannot be performed at the graph + # creation. Use the deprecated 'symmetrize' function for now. + source_col, dest_col, value_col = symmetrize( + _gdf, "src", "dst", "weight", symmetrize=not G.is_directed() + ) + + _gdf = cudf.DataFrame() + + _gdf["src"] = source_col + _gdf["dst"] = dest_col + _gdf["weight"] = value_col + G.from_cudf_edgelist( _gdf, source="src", diff --git a/python/cugraph/pytest.ini b/python/cugraph/pytest.ini index bca148538d9..2f01a0cc51b 100644 --- a/python/cugraph/pytest.ini +++ b/python/cugraph/pytest.ini @@ -71,3 +71,4 @@ filterwarnings = ignore:This function is deprecated. Batched support for multiple vertices:DeprecationWarning # Called via dask. Not obviously addressable in cugraph. ignore:The behavior of array concatenation with empty entries is deprecated:FutureWarning + ignore:This method is deprecated and will no longer be supported. The symmetrization:FutureWarning diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd index 4247bcc1b2a..497607860bd 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd @@ -67,6 +67,7 @@ cdef extern from "cugraph_c/graph.h": bool_t renumber, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t check, cugraph_graph_t** graph, cugraph_error_t** error) @@ -117,6 +118,7 @@ cdef extern from "cugraph_c/graph.h": const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, + bool_t symmetrize, bool_t check, cugraph_graph_t** graph, cugraph_error_t** error @@ -173,6 +175,7 @@ cdef extern from "cugraph_c/graph.h": size_t num_arrays, bool_t drop_self_loops, bool_t drop_multi_edges, + bool_t symmetrize, bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error) diff --git a/python/pylibcugraph/pylibcugraph/graphs.pyx b/python/pylibcugraph/pylibcugraph/graphs.pyx index def47390ce5..6eda0a83d3e 100644 --- a/python/pylibcugraph/pylibcugraph/graphs.pyx +++ b/python/pylibcugraph/pylibcugraph/graphs.pyx @@ -123,9 +123,17 @@ cdef class SGGraph(_GPUGraph): drop_self_loops : bool, optional (default='False') If true, drop any self loops that exist in the provided edge list. + Not supported for CSR graph. + drop_multi_edges: bool, optional (default='False') If true, drop any multi edges that exist in the provided edge list + Not supported for CSR graph. + + symmetrize: bool, optional (default='False') + If true, symmetrize the edge list + + Examples --------- >>> import pylibcugraph, cupy, numpy @@ -155,7 +163,8 @@ cdef class SGGraph(_GPUGraph): input_array_format="COO", vertices_array=None, drop_self_loops=False, - drop_multi_edges=False): + drop_multi_edges=False, + symmetrize=False): # FIXME: add tests for these if not(isinstance(store_transposed, (int, bool))): @@ -217,6 +226,7 @@ cdef class SGGraph(_GPUGraph): renumber, drop_self_loops, drop_multi_edges, + symmetrize, do_expensive_check, &(self.c_graph_ptr), &error_ptr) @@ -234,6 +244,7 @@ cdef class SGGraph(_GPUGraph): edge_type_view_ptr, store_transposed, renumber, + symmetrize, # drop_self_loops, #FIXME: Not supported yet # drop_multi_edges, #FIXME: Not supported yet do_expensive_check, @@ -325,6 +336,10 @@ cdef class MGGraph(_GPUGraph): drop_multi_edges: bool, optional (default='False') If true, drop any multi edges that exist in the provided edge list + + symmetrize: bool, optional (default='False') + If true, symmetrize the edge list + """ def __cinit__(self, ResourceHandle resource_handle, @@ -339,7 +354,8 @@ cdef class MGGraph(_GPUGraph): vertices_array=None, size_t num_arrays=1, # default value to not break users drop_self_loops=False, - drop_multi_edges=False): + drop_multi_edges=False, + symmetrize=False): if not(isinstance(store_transposed, (int, bool))): raise TypeError("expected int or bool for store_transposed, got " @@ -465,6 +481,7 @@ cdef class MGGraph(_GPUGraph): num_arrays, drop_self_loops, drop_multi_edges, + symmetrize, do_expensive_check, &(self.c_graph_ptr), &error_ptr) From 3789b70e1d6c40971d1d74249ccbd5221e707046 Mon Sep 17 00:00:00 2001 From: Alex Barghi <105237337+alexbarghi-nv@users.noreply.github.com> Date: Sun, 6 Oct 2024 20:55:08 -0400 Subject: [PATCH 22/37] Constrain versions of PyTorch and CI artifacts in CI Runs, upgrade to dgl 2.4 (#4690) We were pulling the wrong packages because the PyTorch version constraint wasn't tight enough. Hopefully these sorts of issues will be resolved in the `cugraph-gnn` repository going forward, where we can pin a specific pytorch version for testing. Authors: - Alex Barghi (https://github.com/alexbarghi-nv) - James Lamb (https://github.com/jameslamb) Approvers: - Ray Douglass (https://github.com/raydouglass) - https://github.com/jakirkham - Brad Rees (https://github.com/BradReesWork) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4690 --- ci/build_docs.sh | 34 ++++++++-------- ci/build_python.sh | 3 +- ci/test_cpp.sh | 6 ++- ci/test_notebooks.sh | 6 ++- ci/test_python.sh | 39 ++++++++----------- ci/test_wheel_cugraph-dgl.sh | 4 +- .../all_cuda-118_arch-x86_64.yaml | 3 +- .../all_cuda-125_arch-x86_64.yaml | 3 +- conda/recipes/cugraph-dgl/meta.yaml | 2 +- conda/recipes/cugraph-pyg/meta.yaml | 2 +- dependencies.yaml | 7 ++-- .../source/graph_support/DGL_support.md | 9 +++-- .../wholegraph/installation/container.md | 3 +- python/cugraph-dgl/README.md | 9 +++-- .../conda/cugraph_dgl_dev_cuda-118.yaml | 5 +-- .../conda/cugraph_pyg_dev_cuda-118.yaml | 5 +-- 16 files changed, 72 insertions(+), 68 deletions(-) diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 55235c6ebb9..01c573c96ca 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -6,6 +6,10 @@ set -euo pipefail rapids-logger "Create test conda environment" . /opt/conda/etc/profile.d/conda.sh +export RAPIDS_VERSION="$(rapids-version)" +export RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" +export RAPIDS_VERSION_NUMBER="$RAPIDS_VERSION_MAJOR_MINOR" + rapids-dependency-file-generator \ --output conda \ --file-key docs \ @@ -22,35 +26,31 @@ PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then CONDA_CUDA_VERSION="11.8" - DGL_CHANNEL="dglteam/label/cu118" + DGL_CHANNEL="dglteam/label/th23_cu118" else CONDA_CUDA_VERSION="12.1" - DGL_CHANNEL="dglteam/label/cu121" + DGL_CHANNEL="dglteam/label/th23_cu121" fi rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ --channel conda-forge \ - --channel pyg \ --channel nvidia \ --channel "${DGL_CHANNEL}" \ - libcugraph \ - pylibcugraph \ - cugraph \ - cugraph-pyg \ - cugraph-dgl \ - cugraph-service-server \ - cugraph-service-client \ - libcugraph_etl \ - pylibcugraphops \ - pylibwholegraph \ - pytorch \ + "libcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-pyg=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-dgl=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-service-server=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-service-client=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "libcugraph_etl=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraphops=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibwholegraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pytorch>=2.3,<2.4" \ "cuda-version=${CONDA_CUDA_VERSION}" -export RAPIDS_VERSION="$(rapids-version)" -export RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" -export RAPIDS_VERSION_NUMBER="$RAPIDS_VERSION_MAJOR_MINOR" export RAPIDS_DOCS_DIR="$(mktemp -d)" for PROJECT in libcugraphops libwholegraph; do diff --git a/ci/build_python.sh b/ci/build_python.sh index 1ebc38b058b..c94cc2a0fce 100755 --- a/ci/build_python.sh +++ b/ci/build_python.sh @@ -61,7 +61,6 @@ if [[ ${RAPIDS_CUDA_MAJOR} == "11" ]]; then --no-test \ --channel "${CPP_CHANNEL}" \ --channel "${RAPIDS_CONDA_BLD_OUTPUT_DIR}" \ - --channel pyg \ --channel pytorch \ --channel pytorch-nightly \ conda/recipes/cugraph-pyg @@ -71,7 +70,7 @@ if [[ ${RAPIDS_CUDA_MAJOR} == "11" ]]; then --no-test \ --channel "${CPP_CHANNEL}" \ --channel "${RAPIDS_CONDA_BLD_OUTPUT_DIR}" \ - --channel dglteam \ + --channel dglteam/label/th23_cu118 \ --channel pytorch \ --channel pytorch-nightly \ conda/recipes/cugraph-dgl diff --git a/ci/test_cpp.sh b/ci/test_cpp.sh index 6c14870164e..fb9ab1f5e4e 100755 --- a/ci/test_cpp.sh +++ b/ci/test_cpp.sh @@ -8,6 +8,8 @@ cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../ . /opt/conda/etc/profile.d/conda.sh +RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" + rapids-logger "Generate C++ testing dependencies" rapids-dependency-file-generator \ --output conda \ @@ -30,7 +32,9 @@ rapids-print-env rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ - libcugraph libcugraph_etl libcugraph-tests + "libcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "libcugraph_etl=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "libcugraph-tests=${RAPIDS_VERSION_MAJOR_MINOR}.*" rapids-logger "Check GPU usage" nvidia-smi diff --git a/ci/test_notebooks.sh b/ci/test_notebooks.sh index 31ec56074f0..b22671b48dc 100755 --- a/ci/test_notebooks.sh +++ b/ci/test_notebooks.sh @@ -5,6 +5,8 @@ set -Eeuo pipefail . /opt/conda/etc/profile.d/conda.sh +RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" + rapids-logger "Generate notebook testing dependencies" rapids-dependency-file-generator \ --output conda \ @@ -27,7 +29,9 @@ PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - libcugraph pylibcugraph cugraph + "libcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" NBTEST="$(realpath "$(dirname "$0")/utils/nbtest.sh")" NOTEBOOK_LIST="$(realpath "$(dirname "$0")/notebook_list.py")" diff --git a/ci/test_python.sh b/ci/test_python.sh index f21a06cf061..29b4c7be190 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -8,6 +8,8 @@ cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../ . /opt/conda/etc/profile.d/conda.sh +RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" + rapids-logger "Generate Python testing dependencies" rapids-dependency-file-generator \ --output conda \ @@ -34,12 +36,12 @@ rapids-print-env rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - libcugraph \ - pylibcugraph \ - cugraph \ - nx-cugraph \ - cugraph-service-server \ - cugraph-service-client + "libcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "nx-cugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-service-server=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-service-client=${RAPIDS_VERSION_MAJOR_MINOR}.*" rapids-logger "Check GPU usage" nvidia-smi @@ -151,14 +153,13 @@ if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ --channel conda-forge \ - --channel dglteam/label/cu118 \ + --channel dglteam/label/th23_cu118 \ --channel nvidia \ - libcugraph \ - pylibcugraph \ - pylibcugraphops \ - cugraph \ - cugraph-dgl \ - 'dgl>=1.1.0.cu*,<=2.0.0.cu*' \ + "libcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pylibcugraphops=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "cugraph-dgl=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ 'pytorch>=2.3,<2.4' \ 'cuda-version=11.8' @@ -208,16 +209,10 @@ if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - --channel pyg \ - "cugraph-pyg" \ + "cugraph-pyg=${RAPIDS_VERSION_MAJOR_MINOR}.*" \ + "pytorch>=2.3,<2.4" \ "ogb" - pip install \ - pyg_lib \ - torch_scatter \ - torch_sparse \ - -f ${PYG_URL} - rapids-print-env rapids-logger "pytest cugraph_pyg (single GPU)" @@ -253,7 +248,7 @@ if [[ "${RAPIDS_CUDA_VERSION}" == "11.8.0" ]]; then --channel "${PYTHON_CHANNEL}" \ --channel conda-forge \ --channel nvidia \ - cugraph-equivariant + "cugraph-equivariant=${RAPIDS_VERSION_MAJOR_MINOR}.*" pip install e3nn==0.5.1 rapids-print-env diff --git a/ci/test_wheel_cugraph-dgl.sh b/ci/test_wheel_cugraph-dgl.sh index 9b79cb17fe4..688c58026bd 100755 --- a/ci/test_wheel_cugraph-dgl.sh +++ b/ci/test_wheel_cugraph-dgl.sh @@ -30,10 +30,10 @@ else PYTORCH_CUDA_VER=$PKG_CUDA_VER fi PYTORCH_URL="https://download.pytorch.org/whl/cu${PYTORCH_CUDA_VER}" -DGL_URL="https://data.dgl.ai/wheels/cu${PYTORCH_CUDA_VER}/repo.html" +DGL_URL="https://data.dgl.ai/wheels/torch-2.3/cu${PYTORCH_CUDA_VER}/repo.html" rapids-logger "Installing PyTorch and DGL" rapids-retry python -m pip install torch==2.3.0 --index-url ${PYTORCH_URL} -rapids-retry python -m pip install dgl==2.0.0 --find-links ${DGL_URL} +rapids-retry python -m pip install dgl==2.4.0 --find-links ${DGL_URL} python -m pytest python/cugraph-dgl/tests diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 7ae576e8288..239da7eb724 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -4,8 +4,7 @@ channels: - rapidsai - rapidsai-nightly - dask/label/dev -- pyg -- dglteam/label/cu118 +- dglteam/label/th23_cu118 - conda-forge - nvidia dependencies: diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index 1fb04cae081..c975d891a1b 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -4,8 +4,7 @@ channels: - rapidsai - rapidsai-nightly - dask/label/dev -- pyg -- dglteam/label/cu118 +- dglteam/label/th23_cu118 - conda-forge - nvidia dependencies: diff --git a/conda/recipes/cugraph-dgl/meta.yaml b/conda/recipes/cugraph-dgl/meta.yaml index c80ca6890a8..0383fc8adf8 100644 --- a/conda/recipes/cugraph-dgl/meta.yaml +++ b/conda/recipes/cugraph-dgl/meta.yaml @@ -25,7 +25,7 @@ requirements: - setuptools>=61.0.0 run: - cugraph ={{ version }} - - dgl >=1.1.0.cu* + - dgl >=2.4.0.th23.cu* - numba >=0.57 - numpy >=1.23,<3.0a0 - pylibcugraphops ={{ minor_version }} diff --git a/conda/recipes/cugraph-pyg/meta.yaml b/conda/recipes/cugraph-pyg/meta.yaml index 38d4a3d7d15..7d3e503e23a 100644 --- a/conda/recipes/cugraph-pyg/meta.yaml +++ b/conda/recipes/cugraph-pyg/meta.yaml @@ -36,7 +36,7 @@ requirements: - cugraph ={{ version }} - pylibcugraphops ={{ minor_version }} - tensordict >=0.1.2 - - pyg >=2.5,<2.6 + - pytorch_geometric >=2.5,<2.6 tests: imports: diff --git a/dependencies.yaml b/dependencies.yaml index 4da61cb00ad..b2f03ed3b9f 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -323,8 +323,7 @@ channels: - rapidsai - rapidsai-nightly - dask/label/dev - - pyg - - dglteam/label/cu118 + - dglteam/label/th23_cu118 - conda-forge - nvidia dependencies: @@ -700,7 +699,7 @@ dependencies: - &pytorch_conda pytorch>=2.3,<2.4.0a0 - pytorch-cuda==11.8 - &tensordict tensordict>=0.1.2 - - dgl>=1.1.0.cu* + - dgl>=2.4.0.cu* cugraph_pyg_dev: common: - output_types: [conda] @@ -709,7 +708,7 @@ dependencies: - *pytorch_conda - pytorch-cuda==11.8 - *tensordict - - pyg>=2.5,<2.6 + - pytorch_geometric>=2.5,<2.6 depends_on_pytorch: common: diff --git a/docs/cugraph/source/graph_support/DGL_support.md b/docs/cugraph/source/graph_support/DGL_support.md index ba9a28e3170..7d32a9efe37 100644 --- a/docs/cugraph/source/graph_support/DGL_support.md +++ b/docs/cugraph/source/graph_support/DGL_support.md @@ -8,9 +8,12 @@ Install and update cugraph-dgl and the required dependencies using the command: -``` -conda install mamba -n base -c conda-forge -mamba install cugraph-dgl -c rapidsai-nightly -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam +```shell +# CUDA 11 +conda install -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam/label/th23_cu118 cugraph-dgl + +# CUDA 12 +conda install -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam/label/th23_cu121 cugraph-dgl ``` ## Build from Source diff --git a/docs/cugraph/source/wholegraph/installation/container.md b/docs/cugraph/source/wholegraph/installation/container.md index 3a2c627c56a..6aac53cf88f 100644 --- a/docs/cugraph/source/wholegraph/installation/container.md +++ b/docs/cugraph/source/wholegraph/installation/container.md @@ -24,6 +24,7 @@ RUN pip3 install Cython setuputils3 scikit-build nanobind pytest-forked pytest To run GNN applications, you may also need cuGraphOps, DGL and/or PyG libraries to run the GNN layers. You may refer to [DGL](https://www.dgl.ai/pages/start.html) or [PyG](https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html) For example, to install DGL, you may need to add: + ```dockerfile -RUN pip3 install dgl -f https://data.dgl.ai/wheels/cu118/repo.html +RUN pip3 install dgl -f https://data.dgl.ai/wheels/torch-2.3/cu118/repo.html ``` diff --git a/python/cugraph-dgl/README.md b/python/cugraph-dgl/README.md index ac4cb2f6253..013d4fe5e2e 100644 --- a/python/cugraph-dgl/README.md +++ b/python/cugraph-dgl/README.md @@ -8,9 +8,12 @@ Install and update cugraph-dgl and the required dependencies using the command: -``` -conda install mamba -n base -c conda-forge -mamba install cugraph-dgl -c rapidsai-nightly -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam +```shell +# CUDA 11 +conda install -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam/label/th23_cu118 cugraph-dgl + +# CUDA 12 +conda install -c rapidsai -c pytorch -c conda-forge -c nvidia -c dglteam/label/th23_cu121 cugraph-dgl ``` ## Build from Source diff --git a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml index bbb6a5082f6..3f30e521218 100644 --- a/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml +++ b/python/cugraph-dgl/conda/cugraph_dgl_dev_cuda-118.yaml @@ -4,13 +4,12 @@ channels: - rapidsai - rapidsai-nightly - dask/label/dev -- pyg -- dglteam/label/cu118 +- dglteam/label/th23_cu118 - conda-forge - nvidia dependencies: - cugraph==24.10.*,>=0.0.0a0 -- dgl>=1.1.0.cu* +- dgl>=2.4.0.cu* - pandas - pre-commit - pylibcugraphops==24.10.*,>=0.0.0a0 diff --git a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml index d9afd52b9b7..6b798f3dc88 100644 --- a/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml +++ b/python/cugraph-pyg/conda/cugraph_pyg_dev_cuda-118.yaml @@ -4,15 +4,13 @@ channels: - rapidsai - rapidsai-nightly - dask/label/dev -- pyg -- dglteam/label/cu118 +- dglteam/label/th23_cu118 - conda-forge - nvidia dependencies: - cugraph==24.10.*,>=0.0.0a0 - pandas - pre-commit -- pyg>=2.5,<2.6 - pylibcugraphops==24.10.*,>=0.0.0a0 - pytest - pytest-benchmark @@ -20,6 +18,7 @@ dependencies: - pytest-xdist - pytorch-cuda==11.8 - pytorch>=2.3,<2.4.0a0 +- pytorch_geometric>=2.5,<2.6 - scipy - tensordict>=0.1.2 name: cugraph_pyg_dev_cuda-118 From 773301761439e49126a9c8444c8270d3922213b7 Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:45:59 -0400 Subject: [PATCH 23/37] Updates docs to describe nx-cugraph based on latest updates for 24.10 (#4694) ## Proposed Changes This PR contains critical fixes to the cugraph docs pages. --------- Co-authored-by: acostadon Co-authored-by: rlratzel Co-authored-by: James Lamb --- .../source/basics/cugraph_cascading.md | 53 ----- .../basics/{cugraph_intro.md => index.md} | 5 +- docs/cugraph/source/basics/index.rst | 11 -- docs/cugraph/source/basics/nx_transition.rst | 181 ------------------ docs/cugraph/source/index.rst | 69 ++++--- docs/cugraph/source/nx_cugraph/benchmarks.md | 10 +- docs/cugraph/source/nx_cugraph/faqs.md | 5 - .../cugraph/source/nx_cugraph/how-it-works.md | 63 +++--- docs/cugraph/source/nx_cugraph/index.rst | 44 +++-- .../cugraph/source/nx_cugraph/installation.md | 6 +- .../nx_cugraph/supported-algorithms.rst | 69 +++---- docs/cugraph/source/top_toc.rst | 13 -- readme_pages/pylibcugraph.md | 2 +- 13 files changed, 149 insertions(+), 382 deletions(-) delete mode 100644 docs/cugraph/source/basics/cugraph_cascading.md rename docs/cugraph/source/basics/{cugraph_intro.md => index.md} (99%) delete mode 100644 docs/cugraph/source/basics/index.rst delete mode 100644 docs/cugraph/source/basics/nx_transition.rst delete mode 100644 docs/cugraph/source/nx_cugraph/faqs.md delete mode 100644 docs/cugraph/source/top_toc.rst diff --git a/docs/cugraph/source/basics/cugraph_cascading.md b/docs/cugraph/source/basics/cugraph_cascading.md deleted file mode 100644 index bad3d7fa6a8..00000000000 --- a/docs/cugraph/source/basics/cugraph_cascading.md +++ /dev/null @@ -1,53 +0,0 @@ - -# Method Cascading and cuGraph - -BLUF: cuGraph does not support method cascading - -[Method Cascading](https://en.wikipedia.org/wiki/Method_cascading) is a popular, and useful, functional programming concept and is a great way to make code more readable. Python supports method cascading ... _for the most part_. There are a number of Python built-in classes that do not support cascading. - -An example, from cuDF, is a sequence of method calls for loading data and then finding the largest values from a subset of the data (yes there are other ways this could be done): - -``` -gdf = cudf.from_pandas(df).query(‘val > 200’).nlargest(‘va’3) -``` - -cuGraph does not support method cascading for two main reasons: (1) the object-oriented nature of the Graph data object leverages in-place methods, and (2) the fact that algorithms operate on graphs rather than graphs running algorithms. - -## Graph Data Objects -cuGraph follows an object-oriented design for the Graph objects. Users create a Graph and can then add data to object, but every add method call returns `None`. - -_Why Inplace methods?_
-cuGraph focuses on the big graph problems where there are 10s of millions to trillions of edges (Giga bytes to Terabytes of data). At that scale, creating a copy of the data becomes memory inefficient. - -_Why not return `self` rather than `None`?_
-It would be simple to modify the methods to return `self` rather than `None`, however it opens the methods to misinterpretation. Consider the following code: - -``` -# cascade flow - makes sense -G = cugraph.Graph().from_cudf_edgelist(df) - -# non-cascaded code can be confusing -G = cugraph.Graph() -G2 = G.from_cudf_edgelist(df) -G3 = G.from_cudf_edgelist(df2) -``` -The confusion with the non-cascade code is that G, G1, and G3 are all the same object with the same data. Users could be confused since it is not obvious that changing G3 would also change both G2 and G. To prevent confusion, cuGraph has opted to not return `self`. - -_Why not add a flag "return_self" to the methods?_
-``` -# cascade flow - makes sense -G = cugraph.Graph().from_cudf_edgelist(df, return_self=True) -``` -The fact that a developer would explicitly add a "return_self" flag to the method indicates that the developer is aware that the method returns None. It is just as easy for the developer to use a non-cascading workflow. - -### Algorithms -Algorithms operate on graph objects. -``` -cugraph.pagerank(G) and not G.pagerank() -``` -This pattern allows cuGraph to maintain a particular object-oriented model, where Graph objects simply maintain graph data, and algorithm functions operate independently on Graph objects. While this model has benefits that simplify the overall design and its usability in the majority of use cases, it does mean that the developer cannot cascade graph creation into an algorithm call. - -``` -# will not work -G = cugraph.Graph().from_cudf_edgelist(df).pagerank() -``` diff --git a/docs/cugraph/source/basics/cugraph_intro.md b/docs/cugraph/source/basics/index.md similarity index 99% rename from docs/cugraph/source/basics/cugraph_intro.md rename to docs/cugraph/source/basics/index.md index 7ad2825604a..36aad5166bc 100644 --- a/docs/cugraph/source/basics/cugraph_intro.md +++ b/docs/cugraph/source/basics/index.md @@ -1,5 +1,5 @@ - # cuGraph Introduction + The Data Scientist has a collection of techniques within their proverbial toolbox. Data engineering, statistical analysis, and machine learning are among the most commonly known. However, there @@ -20,8 +20,8 @@ into the RAPIDS data science ecosystem and allows the data scientist to easily call graph algorithms using data stored in a GPU DataFrame, NetworkX Graphs, or even CuPy or SciPy sparse Matrix. - ## Vision + The vision of RAPIDS cuGraph is to ___make graph analysis ubiquitous to the point that users just think in terms of analysis and not technologies or frameworks___. This is a goal that many of us on the cuGraph team have been @@ -48,7 +48,6 @@ high-speed ETL, statistics, and machine learning. To make things even better, RAPIDS and DASK allows cuGraph to scale to multiple GPUs to support multi-billion edge graphs. - ## Terminology cuGraph is a collection of GPU accelerated graph algorithms and graph utility diff --git a/docs/cugraph/source/basics/index.rst b/docs/cugraph/source/basics/index.rst deleted file mode 100644 index 7bba301b657..00000000000 --- a/docs/cugraph/source/basics/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -====== -Basics -====== - - -.. toctree:: - :maxdepth: 2 - - cugraph_intro - nx_transition - cugraph_cascading diff --git a/docs/cugraph/source/basics/nx_transition.rst b/docs/cugraph/source/basics/nx_transition.rst deleted file mode 100644 index 9da2fe9b49e..00000000000 --- a/docs/cugraph/source/basics/nx_transition.rst +++ /dev/null @@ -1,181 +0,0 @@ -************************************** -NetworkX by calling cuGraph Algorithms -************************************** - - -*Note: this is a work in progress and will be updatred and changed as we better flesh out -compatibility issues* - -Latest Update -############# - -Last Update: March 7th, 2024 -Release: 24.04 - -**CuGraph is now a registered backend for networkX. This is described in the following blog: -`Accelerating NetworkX on NVIDIA GPUs for High Performance Graph Analytics -`_ - - -Easy Path – Use NetworkX Graph Objects, Accelerated Algorithms -############################################################## - -Rather than updating all of your existing code, simply update the calls to -graph algorithms by replacing the module name. This allows all the complicated -ETL code to be unchanged while still seeing significate performance -improvements. Again this will be deprecated since networkX dispatching to nx_cugraph -has many advantages. - - -.. image:: ../images/Nx_Cg_1.png - :width: 600 - -It is that easy. All algorithms in cuGraph support a NetworkX graph object as -input and match the NetworkX API list of arguments. - -Currently, cuGraph accepts both NetworkX Graph and DiGraph objects. We will be -adding support for Bipartite graph and Multigraph over the next few releases. - -Differences in Algorithms -########################## - -Since cuGraph currently does not support attribute rich graphs, those -algorithms that return simple scores (centrality, clustering, etc.) best match -the NetworkX process. Algorithms that return a subgraph will do so without -any additional attributes on the nodes or edges. - -Algorithms that exactly match -***************************** - -+-------------------------------+------------------------+ -| Algorithm | Differences | -+===============================+========================+ -| Core Number | None | -+-------------------------------+------------------------+ -| HITS | None | -+-------------------------------+------------------------+ -| PageRank | None | -+-------------------------------+------------------------+ -| Personal PageRank | None | -+-------------------------------+------------------------+ -| Strongly Connected Components | None | -+-------------------------------+------------------------+ -| Weakly Connected Components | None | -+-------------------------------+------------------------+ - -| - - - -Algorithms that do not copy over additional attributes -************************************************************************ - -+-------------------------------+-------------------------------------+ -| Algorithm | Differences | -+===============================+=====================================+ -| K-Truss | Does not copy over attributes | -+-------------------------------+-------------------------------------+ -| K-Core | Does not copy over attributes | -+-------------------------------+-------------------------------------+ -| Subgraph Extraction | Does not copy over attributes | -+-------------------------------+-------------------------------------+ - -| - - -Algorithms not in NetworkX -************************** - -+--------------------------------------+----------------------------+ -| Algorithm | Differences | -+======================================+============================+ -| Ensemble Clustering for Graphs (ECG) | Currently not in NetworkX | -+--------------------------------------+----------------------------+ -| Force Atlas 2 | Currently not in NetworkX | -+--------------------------------------+----------------------------+ -| Leiden | Currently not in NetworkX | -+--------------------------------------+----------------------------+ -| Louvain | Currently not in NetworkX | -+--------------------------------------+----------------------------+ -| Overlap coefficient | Currently not in NetworkX | -+--------------------------------------+----------------------------+ -| Spectral Clustering | Currently not in NetworkX | -+--------------------------------------+----------------------------+ - -| - - -Algorithm where not all arguments are supported -*********************************************** - -+----------------------------+-------------------------------------------------+ -| Algorithm | Differences | -+============================+=================================================+ -|Betweenness Centrality | weight is currently not supported – ignored | -| | endpoints is currently not supported – ignored | -+----------------------------+-------------------------------------------------+ -|Edge Betweenness Centrality | weight is currently not supported – ignored | -+----------------------------+-------------------------------------------------+ -| Katz Centrality | beta is currently not supported – ignored | -| | max_iter defaults to 100 versus 1000 | -+----------------------------+-------------------------------------------------+ - -| - -Algorithms where the results are different -****************************************** - - -For example, the NetworkX traversal algorithms typically return a generator -rather than a dictionary. - - -+----------------------------+-------------------------------------------------+ -| Algorithm | Differences | -+============================+=================================================+ -| Triangle Counting | this algorithm simply returns the total number | -| | of triangle and not the number per vertex | -| | (on roadmap to update) | -+----------------------------+-------------------------------------------------+ -| Jaccard coefficient | Currently we only do a 1-hop computation rather | -| | than an all-pairs. Fix is on roadmap | -+----------------------------+-------------------------------------------------+ -| Breadth First Search (BFS) | Returns a Pandas DataFrame with: | -| | [vertex][distance][predecessor] | -+----------------------------+-------------------------------------------------+ -| Single Source | Returns a Pandas DataFrame with: | -| Shortest Path (SSSP) | [vertex][distance][predecessor] | -+----------------------------+-------------------------------------------------+ - -| - -Graph Building -############## - -The biggest difference between NetworkX and cuGraph is with how Graph objects -are built. NetworkX, for the most part, stores graph data in a dictionary. -That structure allows easy insertion of new records. Consider the following -code for building a NetworkX Graph:: - - # Read the node data - df = pd.read_csv( data_file) - - # Construct graph from edge list. - G = nx.DiGraph() - - for row in df.iterrows(): - G.add_edge( - row[1]["1"], row[1]["2"], count=row[1]["3"] - ) - - -The code block is perfectly fine for NetworkX. However, the process of iterating over the dataframe and adding one node at a time is problematic for GPUs and something that we try and avoid. cuGraph stores data in columns (i.e. arrays). Resizing an array requires allocating a new array one element larger, copying the data, and adding the new value. That is not very efficient. - -If your code follows the above model of inserting one element at a time, the we suggest either rewriting that code or using it as is within NetworkX and just accelerating the algorithms with cuGraph. - -Now, if your code bulk loads the data from Pandas, then RAPIDS can accelerate that process by orders of magnitude. - -.. image:: ../images/Nx_Cg_2.png - :width: 600 - -The above cuGraph code will create cuGraph.Graph object and not a NetworkX.Graph object. diff --git a/docs/cugraph/source/index.rst b/docs/cugraph/source/index.rst index 259a36b8fd6..0db1860b2b9 100644 --- a/docs/cugraph/source/index.rst +++ b/docs/cugraph/source/index.rst @@ -10,23 +10,33 @@ Introduction ~~~~~~~~~~~~ cuGraph is a library of graph algorithms that seamlessly integrates into the RAPIDS data science ecosystem and allows the data scientist to easily call -graph algorithms using data stored in GPU DataFrames, NetworkX Graphs, or even -CuPy or SciPy sparse Matrices. Our major integration effort with NetworkX -allows for **zero code change** GPU acceleration through the use of the -nx-cugraph backend. NetworkX and the nx-cugraph backend offer a seamless -transition to GPU accelerated graph analytics for NetworkX users with access to -a supported GPU. +graph algorithms using data stored in cuDF/Pandas DataFrames or CuPy/SciPy +sparse matrices. +--------------------------- +cuGraph Using NetworkX Code +--------------------------- + +cuGraph is now available as a NetworkX backend using `nx-cugraph `_. +Our major integration effort with NetworkX offers NetworkX users a **zero code change** option to accelerate +their existing NetworkX code using an NVIDIA GPU and cuGraph. + +Check out `zero code change accelerated NetworkX `_. If you would like to continue using standard cuGraph, then continue down below. + +---------------------------- Getting started with cuGraph +---------------------------- Required hardware/software for cuGraph and `RAPIDS `_ - * NVIDIA GPU, Volta architecture or later, with `compute capability 7.0+`_ + * NVIDIA GPU, Volta architecture or later, with `compute capability 7.0+ `_ * CUDA 11.2-11.8, 12.0-12.5 * Python version 3.10, 3.11, or 3.12 - * NetworkX version 3.0 or newer in order to use use the nx-cuGraph backend. NetworkX version 3.4 or newer is recommended. (`see below <#cugraph-using-networkx-code>`). +++++++++++++ Installation -The latest RAPIDS System Requirements documentation is located `here `_. +++++++++++++ + +Please see the latest `RAPIDS System Requirements documentation `_. This includes several ways to set up cuGraph @@ -41,17 +51,9 @@ This includes several ways to set up cuGraph * From Windows - * `Conda `_ - * `Docker `_ - * `pip `_ - - -cuGraph Using NetworkX Code - -cuGraph is now available as a NetworkX backend using `nx-cugraph `_. -nx-cugraph offers NetworkX users a **zero code change** option to accelerate -their existing NetworkX code using an NVIDIA GPU and cuGraph. - + * `Conda `_ + * `Docker `_ + * `pip `_ Cugraph API Example @@ -67,19 +69,32 @@ their existing NetworkX code using an NVIDIA GPU and cuGraph. # Call cugraph.degree_centrality vertex_bc = cugraph.degree_centrality(G) -There are several resources containing cuGraph examples, `the cuGraph notebook repository `_ -has many examples of loading graph data and running algorithms in Jupyter notebooks. -The `cuGraph test code _` contain python scripts setting up and calling cuGraph algorithms. -A simple example of `testing the degree centrality algorithm `_ -is a good place to start. Some of these show `multi-GPU tests/examples `_ with larger data sets as well. + There are several resources containing cuGraph examples, the cuGraph `notebook repository `_ has many examples of loading graph data and running algorithms in Jupyter notebooks. + The cuGraph `test code `_ contains script examples of setting up and calling cuGraph algorithms. + + A simple example of `testing the degree centrality algorithm `_ is a good place to start. There are also `multi-GPU examples `_ with larger data sets as well. + +---- + +~~~~~~~~~~~~~~~~~ +Table of Contents +~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 2 - top_toc + basics/index + nx_cugraph/index + installation/index + tutorials/index + graph_support/index + wholegraph/index + references/index + api_docs/index +~~~~~~~~~~~~~~~~~~ Indices and tables -================== +~~~~~~~~~~~~~~~~~~ * :ref:`genindex` * :ref:`search` diff --git a/docs/cugraph/source/nx_cugraph/benchmarks.md b/docs/cugraph/source/nx_cugraph/benchmarks.md index 31d5e5b09eb..45085c133a9 100644 --- a/docs/cugraph/source/nx_cugraph/benchmarks.md +++ b/docs/cugraph/source/nx_cugraph/benchmarks.md @@ -15,14 +15,12 @@ class="title-ref"> ## Reproducing Benchmarks -Below are the steps to reproduce the results on your workstation. These are documented in this [README](https://github.com/rapidsai/cugraph/blob/HEAD/benchmarks/nx-cugraph/pytest-based). +Below are the steps to reproduce the results on your own. 1. Clone the latest -2. Follow the instructions to build an environment +2. Follow the instructions to build and activate an environment -3. Activate the environment +4. Install the latest `nx-cugraph` by following the [Installation Guide](installation.md) -4. Install the latest `nx-cugraph` by following the [guide](installation.md) - -5. Follow the instructions written in the README here: `cugraph/benchmarks/nx-cugraph/pytest-based/` +5. Follow the instructions written in the README [here](https://github.com/rapidsai/cugraph/blob/HEAD/benchmarks/nx-cugraph/pytest-based) diff --git a/docs/cugraph/source/nx_cugraph/faqs.md b/docs/cugraph/source/nx_cugraph/faqs.md deleted file mode 100644 index dee943d1908..00000000000 --- a/docs/cugraph/source/nx_cugraph/faqs.md +++ /dev/null @@ -1,5 +0,0 @@ -# FAQ - - > **1. Is `nx-cugraph` able to run across multiple GPUs?** - -nx-cugraph currently does not support multi-GPU. Multi-GPU support may be added to a future release of nx-cugraph, but consider [cugraph](https://docs.rapids.ai/api/cugraph/stable) for multi-GPU accelerated graph analytics in Python today. diff --git a/docs/cugraph/source/nx_cugraph/how-it-works.md b/docs/cugraph/source/nx_cugraph/how-it-works.md index f9dc5af67ac..5696688d1b5 100644 --- a/docs/cugraph/source/nx_cugraph/how-it-works.md +++ b/docs/cugraph/source/nx_cugraph/how-it-works.md @@ -4,35 +4,29 @@ NetworkX has the ability to **dispatch function calls to separately-installed th NetworkX backends let users experience improved performance and/or additional functionality without changing their NetworkX Python code. Examples include backends that provide algorithm acceleration using GPUs, parallel processing, graph database integration, and more. -While NetworkX is a pure-Python implementation with minimal to no dependencies, backends may be written in other languages and require specialized hardware and/or OS support, additional software dependencies, or even separate services. Installation instructions vary based on the backend, and additional information can be found from the individual backend project pages listed in the NetworkX Backend Gallery. - +While NetworkX is a pure-Python implementation, backends may be written to use other libraries and even specialized hardware. `nx-cugraph` is a NetworkX backend that uses RAPIDS cuGraph and NVIDIA GPUs to significantly improve NetworkX performance. ![nxcg-execution-flow](../_static/nxcg-execution-diagram.jpg) ## Enabling nx-cugraph -NetworkX will use nx-cugraph as the graph analytics backend if any of the -following are used: +It is recommended to use `networkx>=3.4` for optimal zero code change performance, but `nx-cugraph` will also work with `networkx 3.0+`. -### `NETWORKX_BACKEND_PRIORITY` environment variable. +NetworkX will use `nx-cugraph` as the backend if any of the following are used: -The `NETWORKX_BACKEND_PRIORITY` environment variable can be used to have NetworkX automatically dispatch to specified backends. This variable can be set to a single backend name, or a comma-separated list of backends ordered using the priority which NetworkX should try. If a NetworkX function is called that nx-cugraph supports, NetworkX will redirect the function call to nx-cugraph automatically, or fall back to the next backend in the list if provided, or run using the default NetworkX implementation. See [NetworkX Backends and Configs](https://networkx.org/documentation/stable/reference/backends.html). +### `NX_CUGRAPH_AUTOCONFIG` environment variable. -For example, this setting will have NetworkX use nx-cugraph for any function called by the script supported by nx-cugraph, and the default NetworkX implementation for all others. -``` -bash> NETWORKX_BACKEND_PRIORITY=cugraph python my_networkx_script.py -``` +The `NX_CUGRAPH_AUTOCONFIG` environment variable can be used to configure NetworkX for full zero code change acceleration using `nx-cugraph`. If a NetworkX function is called that `nx-cugraph` supports, NetworkX will redirect the function call to `nx-cugraph` automatically, or fall back to either another backend if enabled or the default NetworkX implementation. See the [NetworkX documentation on backends](https://networkx.org/documentation/stable/reference/backends.html) for configuring NetworkX manually. -This example will have NetworkX use nx-cugraph for functions it supports, then try other_backend if nx-cugraph does not support them, and finally the default NetworkX implementation if not supported by either backend: ``` -bash> NETWORKX_BACKEND_PRIORITY="cugraph,other_backend" python my_networkx_script.py +bash> NX_CUGRAPH_AUTOCONFIG=True python my_networkx_script.py ``` ### `backend=` keyword argument To explicitly specify a particular backend for an API, use the `backend=` keyword argument. This argument takes precedence over the -`NETWORKX_BACKEND_PRIORITY` environment variable. This requires anyone +`NX_CUGRAPH_AUTOCONFIG` environment variable. This requires anyone running code that uses the `backend=` keyword argument to have the specified backend installed. @@ -49,9 +43,9 @@ requires the user to write code for a specific backend, and therefore requires the backend to be installed, but has the advantage of ensuring a particular behavior without the potential for runtime conversions. -To use type-based dispatching with nx-cugraph, the user must import the backend +To use type-based dispatching with `nx-cugraph`, the user must import the backend directly in their code to access the utilities provided to create a Graph -instance specifically for the nx-cugraph backend. +instance specifically for the `nx-cugraph` backend. Example: ```python @@ -59,7 +53,10 @@ import networkx as nx import nx_cugraph as nxcg G = nx.Graph() -... + +# populate the graph +# ... + nxcg_G = nxcg.from_networkx(G) # conversion happens once here nx.betweenness_centrality(nxcg_G, k=1000) # nxcg Graph type causes cugraph backend # to be used, no conversion necessary @@ -84,31 +81,33 @@ G = nx.from_pandas_edgelist(df, source="src", target="dst") Run the command: ``` user@machine:/# ipython bc_demo.ipy + +CPU times: user 7min 36s, sys: 5.22 s, total: 7min 41s +Wall time: 7min 41s ``` You will observe a run time of approximately 7 minutes...more or less depending on your CPU. Run the command again, this time specifying cugraph as the NetworkX backend. +```bash +user@machine:/# NX_CUGRAPH_AUTOCONFIG=True ipython bc_demo.ipy + +CPU times: user 4.14 s, sys: 1.13 s, total: 5.27 s +Wall time: 5.32 s ``` -user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy -``` -This run will be much faster, typically around 20 seconds depending on your GPU. -``` -user@machine:/# NETWORKX_BACKEND_PRIORITY=cugraph ipython bc_demo.ipy -``` -There is also an option to cache the graph conversion to GPU. This can dramatically improve performance when running multiple algorithms on the same graph. Caching is enabled by default for NetworkX versions 3.4 and later, but if using an older version, set "NETWORKX_CACHE_CONVERTED_GRAPHS=True" -``` -NETWORKX_BACKEND_PRIORITY=cugraph NETWORKX_CACHE_CONVERTED_GRAPHS=True ipython bc_demo.ipy -``` +This run will be much faster, typically around 5 seconds depending on your GPU. -When running Python interactively, the cugraph backend can be specified as an argument in the algorithm call. +
-For example: -``` -nx.betweenness_centrality(cit_patents_graph, k=k, backend="cugraph") -``` +*Note, the examples above were run using the following specs*: +    *NetworkX 3.4*
+    *nx-cugraph 24.10*
+    *CPU: Intel(R) Xeon(R) Gold 6128 CPU @ 3.40GHz 45GB RAM*
+    *GPU: NVIDIA Quadro RTX 8000 80GB RAM*
-The latest list of algorithms supported by nx-cugraph can be found [here](https://github.com/rapidsai/cugraph/blob/HEAD/python/nx-cugraph/README.md#algorithms) or in the next section. +
--- + +The latest list of algorithms supported by `nx-cugraph` can be found in [GitHub](https://github.com/rapidsai/cugraph/blob/HEAD/python/nx-cugraph/README.md#algorithms), or in the [Supported Algorithms Section](supported-algorithms.md). diff --git a/docs/cugraph/source/nx_cugraph/index.rst b/docs/cugraph/source/nx_cugraph/index.rst index 110300c1836..730958a5b73 100644 --- a/docs/cugraph/source/nx_cugraph/index.rst +++ b/docs/cugraph/source/nx_cugraph/index.rst @@ -1,9 +1,13 @@ nx-cugraph ----------- -nx-cugraph is a `NetworkX backend `_ that provides **GPU acceleration** to many popular NetworkX algorithms. +``nx-cugraph`` is a NetworkX backend that provides **GPU acceleration** to many popular NetworkX algorithms. -By simply `installing and enabling nx-cugraph `_, users can see significant speedup on workflows where performance is hindered by the default NetworkX implementation. With ``nx-cugraph``, users can have GPU-based, large-scale performance **without** changing their familiar and easy-to-use NetworkX code. +By simply `installing and enabling nx-cugraph `_, users can see significant speedup on workflows where performance is hindered by the default NetworkX implementation. + +Users can have GPU-based, large-scale performance **without** changing their familiar and easy-to-use NetworkX code. + +.. centered:: Timed result from running the following code snippet (called ``demo.ipy``, showing NetworkX with vs. without ``nx-cugraph``) .. code-block:: python @@ -16,6 +20,21 @@ By simply `installing and enabling nx-cugraph `_ to get up-and-running with ``nx-c :caption: Contents: how-it-works - supported-algorithms installation + supported-algorithms benchmarks - faqs diff --git a/docs/cugraph/source/nx_cugraph/installation.md b/docs/cugraph/source/nx_cugraph/installation.md index 8d221f16fec..a816801d001 100644 --- a/docs/cugraph/source/nx_cugraph/installation.md +++ b/docs/cugraph/source/nx_cugraph/installation.md @@ -1,4 +1,4 @@ -# Getting Started +# Installing nx-cugraph This guide describes how to install ``nx-cugraph`` and use it in your workflows. @@ -10,11 +10,11 @@ This guide describes how to install ``nx-cugraph`` and use it in your workflows. - **Volta architecture or later NVIDIA GPU, with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+** - **[CUDA](https://docs.nvidia.com/cuda/index.html) 11.2, 11.4, 11.5, 11.8, 12.0, 12.2, or 12.5** - **Python >= 3.10** - - **[NetworkX](https://networkx.org/documentation/stable/install.html#) >= 3.0 (version 3.2 or higher recommended)** + - **[NetworkX](https://networkx.org/documentation/stable/install.html#) >= 3.0 (version 3.4 or higher recommended)** More details about system requirements can be found in the [RAPIDS System Requirements Documentation](https://docs.rapids.ai/install#system-req). -## Installing nx-cugraph +## Installing Packages Read the [RAPIDS Quick Start Guide](https://docs.rapids.ai/install) to learn more about installing all RAPIDS libraries. diff --git a/docs/cugraph/source/nx_cugraph/supported-algorithms.rst b/docs/cugraph/source/nx_cugraph/supported-algorithms.rst index b21ef7bb668..8f57c02b240 100644 --- a/docs/cugraph/source/nx_cugraph/supported-algorithms.rst +++ b/docs/cugraph/source/nx_cugraph/supported-algorithms.rst @@ -2,7 +2,7 @@ Supported Algorithms ===================== The nx-cugraph backend to NetworkX connects -`pylibcugraph <../../readme_pages/pylibcugraph.md>`_ (cuGraph's low-level Python +`pylibcugraph `_ (cuGraph's low-level Python interface to its CUDA-based graph analytics library) and `CuPy `_ (a GPU-accelerated array library) to NetworkX's familiar and easy-to-use API. @@ -209,6 +209,40 @@ Algorithms | is_tree | +---------------------+ + +Utilities +------- + ++-------------------------+ +| **Classes** | ++=========================+ +| is_negatively_weighted | ++-------------------------+ + ++----------------------+ +| **Convert** | ++======================+ +| from_dict_of_lists | ++----------------------+ +| to_dict_of_lists | ++----------------------+ + ++--------------------------+ +| **Convert Matrix** | ++==========================+ +| from_pandas_edgelist | ++--------------------------+ +| from_scipy_sparse_array | ++--------------------------+ + ++-----------------------------------+ +| **Relabel** | ++===================================+ +| convert_node_labels_to_integers | ++-----------------------------------+ +| relabel_nodes | ++-----------------------------------+ + Generators ------------ @@ -316,39 +350,6 @@ Generators | les_miserables_graph | +-------------------------------+ -Other -------- - -+-------------------------+ -| **Classes** | -+=========================+ -| is_negatively_weighted | -+-------------------------+ - -+----------------------+ -| **Convert** | -+======================+ -| from_dict_of_lists | -+----------------------+ -| to_dict_of_lists | -+----------------------+ - -+--------------------------+ -| **Convert Matrix** | -+==========================+ -| from_pandas_edgelist | -+--------------------------+ -| from_scipy_sparse_array | -+--------------------------+ - -+-----------------------------------+ -| **Relabel** | -+===================================+ -| convert_node_labels_to_integers | -+-----------------------------------+ -| relabel_nodes | -+-----------------------------------+ - To request nx-cugraph backend support for a NetworkX API that is not listed above, visit the `cuGraph GitHub repo `_. diff --git a/docs/cugraph/source/top_toc.rst b/docs/cugraph/source/top_toc.rst deleted file mode 100644 index 8e31e70ca78..00000000000 --- a/docs/cugraph/source/top_toc.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. toctree:: - :maxdepth: 2 - :caption: cuGraph documentation Contents: - :name: top_toc - - basics/index - nx_cugraph/index - installation/index - tutorials/index - graph_support/index - wholegraph/index - references/index - api_docs/index diff --git a/readme_pages/pylibcugraph.md b/readme_pages/pylibcugraph.md index 3bb552141e9..fcb5a624931 100644 --- a/readme_pages/pylibcugraph.md +++ b/readme_pages/pylibcugraph.md @@ -4,7 +4,7 @@


-CuGraph pylibcugraph +cuGraph pylibcugraph

Part of [RAPIDS](https://rapids.ai) cuGraph, pylibcugraph is a wrapper around the cuGraph C API. It is aimed more at integrators instead of algorithm writers or end users like Data Scientists. Most of the cuGraph python API uses pylibcugraph to efficiently run algorithms by removing much of the overhead of the python-centric implementation, relying more on cython instead. Pylibcugraph is intended for applications that require a tighter integration with cuGraph at the Python layer with fewer dependencies. From acf502987f3279e0638da904ea099c5cbcbea21c Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Mon, 7 Oct 2024 09:53:07 -0500 Subject: [PATCH 24/37] `nx-cugraph`: add `NX_CUGRAPH_AUTOCONFIG=True` env var to enable full zero-code change (#4685) This is for convenience and sets or updates `NETWORKX` environment variables. Do we like `NX_CUGRAPH` as the env var name? What should we consider a true value: "True" (case-insensitive) or a non-empty value? This works with the latest dev version of NetworkX. I have not yet tried it with older NetworkX versions. Authors: - Erik Welch (https://github.com/eriknw) - https://github.com/jakirkham Approvers: - Rick Ratzel (https://github.com/rlratzel) - Ralph Liu (https://github.com/nv-rliu) URL: https://github.com/rapidsai/cugraph/pull/4685 --- python/nx-cugraph/_nx_cugraph/__init__.py | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/python/nx-cugraph/_nx_cugraph/__init__.py b/python/nx-cugraph/_nx_cugraph/__init__.py index a5e45979fe2..fc0bea47180 100644 --- a/python/nx-cugraph/_nx_cugraph/__init__.py +++ b/python/nx-cugraph/_nx_cugraph/__init__.py @@ -301,6 +301,45 @@ def get_info(): .lower() == "true", } + + # Enable zero-code change usage with a simple environment variable + # by setting or updating other NETWORKX environment variables. + if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true": + from itertools import chain + + def update_env_var(varname): + """Add "cugraph" to a list of backend names environment variable.""" + if varname not in os.environ: + os.environ[varname] = "cugraph" + return + string = os.environ[varname] + vals = [ + stripped for x in string.strip().split(",") if (stripped := x.strip()) + ] + if "cugraph" not in vals: + # Should we append or prepend? Let's be first! + os.environ[varname] = ",".join(chain(["cugraph"], vals)) + + # Automatically convert NetworkX Graphs to nx-cugraph for algorithms + if (varname := "NETWORKX_BACKEND_PRIORITY_ALGOS") in os.environ: + # "*_ALGOS" is given priority in NetworkX >=3.4 + update_env_var(varname) + # But update this too to "just work" if users mix env vars and nx versions + os.environ["NETWORKX_BACKEND_PRIORITY"] = os.environ[varname] + else: + update_env_var("NETWORKX_BACKEND_PRIORITY") + # And for older NetworkX versions + update_env_var("NETWORKX_AUTOMATIC_BACKENDS") # For NetworkX 3.2 + update_env_var("NETWORKX_GRAPH_CONVERT") # For NetworkX 3.0 and 3.1 + # Automatically create nx-cugraph Graph from graph generators + update_env_var("NETWORKX_BACKEND_PRIORITY_GENERATORS") + # Run default NetworkX implementation (in >=3.4) if not implemented by nx-cugraph + if (varname := "NETWORKX_FALLBACK_TO_NX") not in os.environ: + os.environ[varname] = "true" + # Cache graph conversions (default is False in NetworkX 3.2 + if (varname := "NETWORKX_CACHE_CONVERTED_GRAPHS") not in os.environ: + os.environ[varname] = "true" + return d From add69b8e2a63f4158172f78461f2f9703cbe8eaf Mon Sep 17 00:00:00 2001 From: Ray Douglass Date: Wed, 9 Oct 2024 09:38:38 -0400 Subject: [PATCH 25/37] Update Changelog [skip ci] --- CHANGELOG.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 689a214751f..d934273d0a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,80 @@ +# cugraph 24.10.00 (9 Oct 2024) + +## 🚨 Breaking Changes + +- Add Additional Check for `NetworkX` Release Candidate Versions ([#4613](https://github.com/rapidsai/cugraph/pull/4613)) [@nv-rliu](https://github.com/nv-rliu) +- Updates to `cugraph.hypergraph` (Duplicate Col Labels Bug) ([#4610](https://github.com/rapidsai/cugraph/pull/4610)) [@nv-rliu](https://github.com/nv-rliu) +- Heterogeneous renumbering implementation ([#4602](https://github.com/rapidsai/cugraph/pull/4602)) [@seunghwak](https://github.com/seunghwak) +- [FEA] DGL Examples ([#4583](https://github.com/rapidsai/cugraph/pull/4583)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) + +## 🐛 Bug Fixes + +- Updates docs to describe nx-cugraph based on latest updates for 24.10 ([#4694](https://github.com/rapidsai/cugraph/pull/4694)) [@nv-rliu](https://github.com/nv-rliu) +- Constrain versions of PyTorch and CI artifacts in CI Runs, upgrade to dgl 2.4 ([#4690](https://github.com/rapidsai/cugraph/pull/4690)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) +- Drops duplicate edges in non-MultiGraph PLC `SGGraph` instances ([#4658](https://github.com/rapidsai/cugraph/pull/4658)) [@rlratzel](https://github.com/rlratzel) +- Install mg test executables ([#4656](https://github.com/rapidsai/cugraph/pull/4656)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- Fix build strings in nx-cugraph ([#4639](https://github.com/rapidsai/cugraph/pull/4639)) [@bdice](https://github.com/bdice) +- Set CUDA_STATIC_MATH_LIBRARIES in Python builds ([#4612](https://github.com/rapidsai/cugraph/pull/4612)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- Updates to `cugraph.hypergraph` (Duplicate Col Labels Bug) ([#4610](https://github.com/rapidsai/cugraph/pull/4610)) [@nv-rliu](https://github.com/nv-rliu) +- Biased sampling primitive bug fix ([#4607](https://github.com/rapidsai/cugraph/pull/4607)) [@seunghwak](https://github.com/seunghwak) +- Fix `test_property_graph_mg` Usage of Util Function ([#4600](https://github.com/rapidsai/cugraph/pull/4600)) [@nv-rliu](https://github.com/nv-rliu) +- Re-configure benchmarking devices & add markers to `bench_cugraph_uniform_neighbor_sample` ([#4561](https://github.com/rapidsai/cugraph/pull/4561)) [@nv-rliu](https://github.com/nv-rliu) + +## 📖 Documentation + +- Implementing some of the VDR feedback ([#4674](https://github.com/rapidsai/cugraph/pull/4674)) [@acostadon](https://github.com/acostadon) +- Add `nx-cugraph` Docs Pages ([#4669](https://github.com/rapidsai/cugraph/pull/4669)) [@nv-rliu](https://github.com/nv-rliu) +- Recommending `miniforge` for conda install ([#4650](https://github.com/rapidsai/cugraph/pull/4650)) [@mmccarty](https://github.com/mmccarty) + +## 🚀 New Features + +- Add `nx-cugraph` introduction notebook to repo ([#4677](https://github.com/rapidsai/cugraph/pull/4677)) [@nv-rliu](https://github.com/nv-rliu) +- Support Negative Sampling in pylibcugraph and cuGraph-PyG ([#4660](https://github.com/rapidsai/cugraph/pull/4660)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) +- Heterogeneous renumbering implementation ([#4602](https://github.com/rapidsai/cugraph/pull/4602)) [@seunghwak](https://github.com/seunghwak) +- [FEA] Biased Sampling in cuGraph-DGL ([#4595](https://github.com/rapidsai/cugraph/pull/4595)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) +- [FEA] Biased Sampling in cuGraph-PyG ([#4586](https://github.com/rapidsai/cugraph/pull/4586)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) +- [FEA] DGL Examples ([#4583](https://github.com/rapidsai/cugraph/pull/4583)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) + +## 🛠️ Improvements + +- `nx-cugraph`: add `NX_CUGRAPH_AUTOCONFIG=True` env var to enable full zero-code change ([#4685](https://github.com/rapidsai/cugraph/pull/4685)) [@eriknw](https://github.com/eriknw) +- Fix `cit-patents` Dataset for `nx-cugraph` Benchmark ([#4666](https://github.com/rapidsai/cugraph/pull/4666)) [@nv-rliu](https://github.com/nv-rliu) +- Update update-version.sh to use packaging lib ([#4664](https://github.com/rapidsai/cugraph/pull/4664)) [@AyodeAwe](https://github.com/AyodeAwe) +- Swtch traceback to `--native` in `cugraph` ([#4663](https://github.com/rapidsai/cugraph/pull/4663)) [@galipremsagar](https://github.com/galipremsagar) +- bump NCCL floor to 2.18.1.1, include nccl.h where it's needed ([#4661](https://github.com/rapidsai/cugraph/pull/4661)) [@jameslamb](https://github.com/jameslamb) +- Use CI workflow branch 'branch-24.10' again ([#4654](https://github.com/rapidsai/cugraph/pull/4654)) [@jameslamb](https://github.com/jameslamb) +- Update flake8 to 7.1.1. ([#4652](https://github.com/rapidsai/cugraph/pull/4652)) [@bdice](https://github.com/bdice) +- reduce pip verbosity in wheel builds ([#4651](https://github.com/rapidsai/cugraph/pull/4651)) [@jameslamb](https://github.com/jameslamb) +- Refactor the python function symmetrizing the edgelist ([#4649](https://github.com/rapidsai/cugraph/pull/4649)) [@jnke2016](https://github.com/jnke2016) +- Add `--cpu-only` or `--gpu-only` Arguments to `nx-cugraph` Benchmark ([#4648](https://github.com/rapidsai/cugraph/pull/4648)) [@nv-rliu](https://github.com/nv-rliu) +- Add support for Python 3.12 ([#4647](https://github.com/rapidsai/cugraph/pull/4647)) [@jameslamb](https://github.com/jameslamb) +- Biased Random Walks and Node2Vec implementation ([#4645](https://github.com/rapidsai/cugraph/pull/4645)) [@ChuckHastings](https://github.com/ChuckHastings) +- update a few more Python references to Python 3.10 ([#4637](https://github.com/rapidsai/cugraph/pull/4637)) [@jameslamb](https://github.com/jameslamb) +- Negative Sampling test needs whole GPU ([#4636](https://github.com/rapidsai/cugraph/pull/4636)) [@ChuckHastings](https://github.com/ChuckHastings) +- Update rapidsai/pre-commit-hooks ([#4633](https://github.com/rapidsai/cugraph/pull/4633)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- Update examples to build with latest changes to cugraph ([#4632](https://github.com/rapidsai/cugraph/pull/4632)) [@ChuckHastings](https://github.com/ChuckHastings) +- Remove Warnings and Timeout from `bench_cugraph_uniform_neighbor_sample.py` ([#4631](https://github.com/rapidsai/cugraph/pull/4631)) [@nv-rliu](https://github.com/nv-rliu) +- Update edge triangle count to call a non detail primitive ([#4630](https://github.com/rapidsai/cugraph/pull/4630)) [@jnke2016](https://github.com/jnke2016) +- nx-cugraph: Updates nxcg.Graph classes for API-compatibility with NetworkX Graph classes, needed for zero code change graph generators ([#4629](https://github.com/rapidsai/cugraph/pull/4629)) [@eriknw](https://github.com/eriknw) +- Drop Python 3.9 support ([#4625](https://github.com/rapidsai/cugraph/pull/4625)) [@jameslamb](https://github.com/jameslamb) +- Download fewer datasets for C/C++ unit tests ([#4624](https://github.com/rapidsai/cugraph/pull/4624)) [@ChuckHastings](https://github.com/ChuckHastings) +- Use CUDA math wheels ([#4621](https://github.com/rapidsai/cugraph/pull/4621)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- Fix ListColumn constructor argument ([#4620](https://github.com/rapidsai/cugraph/pull/4620)) [@mroeschke](https://github.com/mroeschke) +- Use CategoricalColumn instead of build_categorical_column ([#4618](https://github.com/rapidsai/cugraph/pull/4618)) [@mroeschke](https://github.com/mroeschke) +- Add `nx-cugraph` Benchmarking Scripts ([#4616](https://github.com/rapidsai/cugraph/pull/4616)) [@nv-rliu](https://github.com/nv-rliu) +- Remove NumPy <2 pin ([#4615](https://github.com/rapidsai/cugraph/pull/4615)) [@seberg](https://github.com/seberg) +- Add Additional Check for `NetworkX` Release Candidate Versions ([#4613](https://github.com/rapidsai/cugraph/pull/4613)) [@nv-rliu](https://github.com/nv-rliu) +- Remove a bunch of legacy code that's no longer used ([#4609](https://github.com/rapidsai/cugraph/pull/4609)) [@ChuckHastings](https://github.com/ChuckHastings) +- Update pre-commit hooks ([#4605](https://github.com/rapidsai/cugraph/pull/4605)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- Improve update-version.sh ([#4599](https://github.com/rapidsai/cugraph/pull/4599)) [@bdice](https://github.com/bdice) +- Use tool.scikit-build.cmake.version, set scikit-build-core minimum-version ([#4597](https://github.com/rapidsai/cugraph/pull/4597)) [@jameslamb](https://github.com/jameslamb) +- Migrate get_sampling_index function from cugraph-ops to cugraph ([#4594](https://github.com/rapidsai/cugraph/pull/4594)) [@ChuckHastings](https://github.com/ChuckHastings) +- Merge branch-24.08 into branch-24.10 ([#4565](https://github.com/rapidsai/cugraph/pull/4565)) [@jameslamb](https://github.com/jameslamb) +- Fix ucx-py version, use UCX 1.17.0 in pip devcontainers ([#4562](https://github.com/rapidsai/cugraph/pull/4562)) [@bdice](https://github.com/bdice) +- Use stream_allocator_adaptor constructor instead of factory. ([#4557](https://github.com/rapidsai/cugraph/pull/4557)) [@bdice](https://github.com/bdice) +- Add an Explanatory Error Message for uint Types ([#4556](https://github.com/rapidsai/cugraph/pull/4556)) [@alexbarghi-nv](https://github.com/alexbarghi-nv) +- Define and Implement C++ API for negative sampling ([#4523](https://github.com/rapidsai/cugraph/pull/4523)) [@ChuckHastings](https://github.com/ChuckHastings) + # cugraph 24.08.00 (7 Aug 2024) ## 🚨 Breaking Changes From 6f4689afa0f9473ef1a7cd805508371513aaf396 Mon Sep 17 00:00:00 2001 From: Matthew Murray <41342305+Matt711@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:29:01 -0400 Subject: [PATCH 26/37] Update all rmm imports to use pylibrmm/librmm (#4671) This PR updates all the RMM imports to use pylibrmm/librmm now that `rmm._lib` is deprecated . It should be merged after [rmm/1676](https://github.com/rapidsai/rmm/pull/1676). Authors: - Matthew Murray (https://github.com/Matt711) Approvers: - Brad Rees (https://github.com/BradReesWork) - Alex Barghi (https://github.com/alexbarghi-nv) URL: https://github.com/rapidsai/cugraph/pull/4671 --- python/cugraph/cugraph/structure/graph_primtypes.pxd | 4 ++-- python/cugraph/cugraph/structure/graph_primtypes.pyx | 4 ++-- python/cugraph/cugraph/structure/graph_utilities.pxd | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_primtypes.pxd b/python/cugraph/cugraph/structure/graph_primtypes.pxd index eaf552195da..f547db5c463 100644 --- a/python/cugraph/cugraph/structure/graph_primtypes.pxd +++ b/python/cugraph/cugraph/structure/graph_primtypes.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2023, NVIDIA CORPORATION. +# Copyright (c) 2019-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,7 +21,7 @@ from libcpp.memory cimport unique_ptr from libcpp.utility cimport pair from libcpp.vector cimport vector from pylibraft.common.handle cimport * -from rmm._lib.device_buffer cimport device_buffer +from rmm.librmm.device_buffer cimport device_buffer cdef extern from "cugraph/legacy/graph.hpp" namespace "cugraph::legacy": diff --git a/python/cugraph/cugraph/structure/graph_primtypes.pyx b/python/cugraph/cugraph/structure/graph_primtypes.pyx index 10f3871e157..063790a33a4 100644 --- a/python/cugraph/cugraph/structure/graph_primtypes.pyx +++ b/python/cugraph/cugraph/structure/graph_primtypes.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2023, NVIDIA CORPORATION. +# Copyright (c) 2020-2024, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,7 +20,7 @@ import numpy as np from libc.stdint cimport uintptr_t from libcpp.utility cimport move -from rmm._lib.device_buffer cimport DeviceBuffer +from rmm.pylibrmm.device_buffer cimport DeviceBuffer from cudf.core.buffer import as_buffer import cudf diff --git a/python/cugraph/cugraph/structure/graph_utilities.pxd b/python/cugraph/cugraph/structure/graph_utilities.pxd index 39e2cdbbff5..5612990c452 100644 --- a/python/cugraph/cugraph/structure/graph_utilities.pxd +++ b/python/cugraph/cugraph/structure/graph_utilities.pxd @@ -21,7 +21,7 @@ from libcpp.memory cimport unique_ptr from libcpp.utility cimport pair from libcpp.vector cimport vector -from rmm._lib.device_buffer cimport device_buffer +from rmm.librmm.device_buffer cimport device_buffer from pylibraft.common.handle cimport handle_t From a4a6b83bf1945061fab927437df7538bf1e8eb4f Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 10 Oct 2024 13:55:26 -0500 Subject: [PATCH 27/37] combine pip install calls in wheel-testing scripts (#4701) ## Summary Follow-up to #4690. Proposes consolidating stuff like this in CI scripts: ```shell pip install A pip install B pip install C ``` Into this: ```shell pip install A B C ``` ## Benefits of these changes Reduces the risk of creating a broken environment with incompatible packages. Unlike `conda`, `pip` does not evaluate the requirements of all installed packages when you run `pip` install. Installing `torch` and `cugraph-dgl` at the same time, for example, gives us a chance to find out about packaging issues like *"`cugraph-dgl` and `torch` have conflicting requirements on `{other_package}`"* at CI time. Similar change from `cudf`: https://github.com/rapidsai/cudf/pull/16575 Authors: - James Lamb (https://github.com/jameslamb) Approvers: - Kyle Edwards (https://github.com/KyleFromNVIDIA) - Alex Barghi (https://github.com/alexbarghi-nv) URL: https://github.com/rapidsai/cugraph/pull/4701 --- ci/test_wheel.sh | 1 - ci/test_wheel_cugraph-dgl.sh | 25 +++++++-------- ci/test_wheel_cugraph-equivariant.sh | 19 ++++++----- ci/test_wheel_cugraph-pyg.sh | 47 ++++++++++++++-------------- ci/test_wheel_cugraph.sh | 4 +-- ci/test_wheel_nx-cugraph.sh | 2 +- ci/test_wheel_pylibcugraph.sh | 4 +-- 7 files changed, 49 insertions(+), 53 deletions(-) diff --git a/ci/test_wheel.sh b/ci/test_wheel.sh index e3690dfde6e..dfba25bbe1a 100755 --- a/ci/test_wheel.sh +++ b/ci/test_wheel.sh @@ -4,7 +4,6 @@ set -eoxu pipefail package_name=$1 -package_dir=$2 python_package_name=$(echo ${package_name}|sed 's/-/_/g') diff --git a/ci/test_wheel_cugraph-dgl.sh b/ci/test_wheel_cugraph-dgl.sh index 688c58026bd..d7558d43b6d 100755 --- a/ci/test_wheel_cugraph-dgl.sh +++ b/ci/test_wheel_cugraph-dgl.sh @@ -4,24 +4,16 @@ set -eoxu pipefail package_name="cugraph-dgl" -package_dir="python/cugraph-dgl" - -python_package_name=$(echo ${package_name}|sed 's/-/_/g') mkdir -p ./dist RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" -# Download wheels built during this job. +# Download the pylibcugraph, cugraph, and cugraph-dgl built in the previous step RAPIDS_PY_WHEEL_NAME="pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-deps RAPIDS_PY_WHEEL_NAME="cugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-deps -python -m pip install ./local-deps/*.whl - -# use 'ls' to expand wildcard before adding `[extra]` requires for pip RAPIDS_PY_WHEEL_NAME="${package_name}_${RAPIDS_PY_CUDA_SUFFIX}" RAPIDS_PY_WHEEL_PURE="1" rapids-download-wheels-from-s3 ./dist -# pip creates wheels using python package names -python -m pip install $(ls ./dist/${python_package_name}*.whl)[test] - +# determine pytorch and DGL sources PKG_CUDA_VER="$(echo ${CUDA_VERSION} | cut -d '.' -f1,2 | tr -d '.')" PKG_CUDA_VER_MAJOR=${PKG_CUDA_VER:0:2} if [[ "${PKG_CUDA_VER_MAJOR}" == "12" ]]; then @@ -32,8 +24,15 @@ fi PYTORCH_URL="https://download.pytorch.org/whl/cu${PYTORCH_CUDA_VER}" DGL_URL="https://data.dgl.ai/wheels/torch-2.3/cu${PYTORCH_CUDA_VER}/repo.html" -rapids-logger "Installing PyTorch and DGL" -rapids-retry python -m pip install torch==2.3.0 --index-url ${PYTORCH_URL} -rapids-retry python -m pip install dgl==2.4.0 --find-links ${DGL_URL} +# echo to expand wildcard before adding `[extra]` requires for pip +python -m pip install \ + -v \ + --extra-index-url "${PYTORCH_URL}" \ + --find-links "${DGL_URL}" \ + "$(echo ./local-deps/pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}*.whl)" \ + "$(echo ./local-deps/cugraph_${RAPIDS_PY_CUDA_SUFFIX}*.whl)" \ + "$(echo ./dist/cugraph_dgl_${RAPIDS_PY_CUDA_SUFFIX}*.whl)[test]" \ + 'dgl==2.4.0' \ + 'torch>=2.3.0,<2.4' python -m pytest python/cugraph-dgl/tests diff --git a/ci/test_wheel_cugraph-equivariant.sh b/ci/test_wheel_cugraph-equivariant.sh index cb952055f06..3be1d578964 100755 --- a/ci/test_wheel_cugraph-equivariant.sh +++ b/ci/test_wheel_cugraph-equivariant.sh @@ -4,19 +4,14 @@ set -eoxu pipefail package_name="cugraph-equivariant" -package_dir="python/cugraph-equivariant" - -python_package_name=$(echo ${package_name}|sed 's/-/_/g') mkdir -p ./dist RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" -# use 'ls' to expand wildcard before adding `[extra]` requires for pip +# Download the cugraph-equivariant built in the previous step RAPIDS_PY_WHEEL_NAME="${package_name}_${RAPIDS_PY_CUDA_SUFFIX}" RAPIDS_PY_WHEEL_PURE="1" rapids-download-wheels-from-s3 ./dist -# pip creates wheels using python package names -python -m pip install $(ls ./dist/${python_package_name}*.whl)[test] - +# determine pytorch source PKG_CUDA_VER="$(echo ${CUDA_VERSION} | cut -d '.' -f1,2 | tr -d '.')" PKG_CUDA_VER_MAJOR=${PKG_CUDA_VER:0:2} if [[ "${PKG_CUDA_VER_MAJOR}" == "12" ]]; then @@ -26,8 +21,12 @@ else fi PYTORCH_URL="https://download.pytorch.org/whl/cu${PYTORCH_CUDA_VER}" -rapids-logger "Installing PyTorch and e3nn" -rapids-retry python -m pip install torch --index-url ${PYTORCH_URL} -rapids-retry python -m pip install e3nn +# echo to expand wildcard before adding `[extra]` requires for pip +python -m pip install \ + -v \ + --extra-index-url "${PYTORCH_URL}" \ + "$(echo ./dist/cugraph_equivariant_${RAPIDS_PY_CUDA_SUFFIX}*.whl)[test]" \ + 'e3nn' \ + 'torch>=2.3.0,<2.4' python -m pytest python/cugraph-equivariant/cugraph_equivariant/tests diff --git a/ci/test_wheel_cugraph-pyg.sh b/ci/test_wheel_cugraph-pyg.sh index 8f4b16a2dec..2f508ee830b 100755 --- a/ci/test_wheel_cugraph-pyg.sh +++ b/ci/test_wheel_cugraph-pyg.sh @@ -4,29 +4,16 @@ set -eoxu pipefail package_name="cugraph-pyg" -package_dir="python/cugraph-pyg" - -python_package_name=$(echo ${package_name}|sed 's/-/_/g') mkdir -p ./dist RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" -# Download wheels built during this job. +# Download the pylibcugraph, cugraph, and cugraph-pyg built in the previous step RAPIDS_PY_WHEEL_NAME="pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-deps RAPIDS_PY_WHEEL_NAME="cugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-deps -python -m pip install ./local-deps/*.whl - -# use 'ls' to expand wildcard before adding `[extra]` requires for pip RAPIDS_PY_WHEEL_NAME="${package_name}_${RAPIDS_PY_CUDA_SUFFIX}" RAPIDS_PY_WHEEL_PURE="1" rapids-download-wheels-from-s3 ./dist -# pip creates wheels using python package names -python -m pip install $(ls ./dist/${python_package_name}*.whl)[test] - -# RAPIDS_DATASET_ROOT_DIR is used by test scripts -export RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)" - -# Used to skip certain examples in CI due to memory limitations -export CI_RUN=1 +# determine pytorch and pyg sources if [[ "${CUDA_VERSION}" == "11.8.0" ]]; then PYTORCH_URL="https://download.pytorch.org/whl/cu118" PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu118.html" @@ -34,15 +21,27 @@ else PYTORCH_URL="https://download.pytorch.org/whl/cu121" PYG_URL="https://data.pyg.org/whl/torch-2.3.0+cu121.html" fi -rapids-logger "Installing PyTorch and PyG dependencies" -rapids-retry python -m pip install torch==2.3.0 --index-url ${PYTORCH_URL} -rapids-retry python -m pip install "torch-geometric>=2.5,<2.6" -rapids-retry python -m pip install \ - ogb \ - pyg_lib \ - torch_scatter \ - torch_sparse \ - -f ${PYG_URL} + +# echo to expand wildcard before adding `[extra]` requires for pip +python -m pip install \ + -v \ + --extra-index-url "${PYTORCH_URL}" \ + --find-links "${PYG_URL}" \ + "$(echo ./local-deps/pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}*.whl)" \ + "$(echo ./local-deps/cugraph_${RAPIDS_PY_CUDA_SUFFIX}*.whl)" \ + "$(echo ./dist/cugraph_pyg_${RAPIDS_PY_CUDA_SUFFIX}*.whl)[test]" \ + 'ogb' \ + 'pyg_lib' \ + 'torch>=2.3.0,<2.4' \ + 'torch-geometric>=2.5,<2.6' \ + 'torch_scatter' \ + 'torch_sparse' + +# RAPIDS_DATASET_ROOT_DIR is used by test scripts +export RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)" + +# Used to skip certain examples in CI due to memory limitations +export CI_RUN=1 rapids-logger "pytest cugraph-pyg (single GPU)" pushd python/cugraph-pyg/cugraph_pyg diff --git a/ci/test_wheel_cugraph.sh b/ci/test_wheel_cugraph.sh index d351ea21624..295cec7cb10 100755 --- a/ci/test_wheel_cugraph.sh +++ b/ci/test_wheel_cugraph.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. set -eoxu pipefail @@ -8,4 +8,4 @@ RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" RAPIDS_PY_WHEEL_NAME="pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-pylibcugraph-dep python -m pip install --no-deps ./local-pylibcugraph-dep/pylibcugraph*.whl -./ci/test_wheel.sh cugraph python/cugraph +./ci/test_wheel.sh cugraph diff --git a/ci/test_wheel_nx-cugraph.sh b/ci/test_wheel_nx-cugraph.sh index b5adfbcb9d3..024169ae698 100755 --- a/ci/test_wheel_nx-cugraph.sh +++ b/ci/test_wheel_nx-cugraph.sh @@ -8,4 +8,4 @@ RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" RAPIDS_PY_WHEEL_NAME="pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./local-deps python -m pip install ./local-deps/*.whl -./ci/test_wheel.sh nx-cugraph python/nx-cugraph +./ci/test_wheel.sh nx-cugraph diff --git a/ci/test_wheel_pylibcugraph.sh b/ci/test_wheel_pylibcugraph.sh index d04cb358d21..ddc9976308b 100755 --- a/ci/test_wheel_pylibcugraph.sh +++ b/ci/test_wheel_pylibcugraph.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. set -eoxu pipefail -./ci/test_wheel.sh pylibcugraph python/pylibcugraph +./ci/test_wheel.sh pylibcugraph From 4bb54942898d3fcd55ed8370c4013618bb39841b Mon Sep 17 00:00:00 2001 From: Yunsong Wang Date: Mon, 14 Oct 2024 09:46:25 -0700 Subject: [PATCH 28/37] Clean up the use of cuco hash functions (#4707) This PR improves the use of cuco hashers by replacing detail APIs with public ones and updating device code to use `cuda::std::byte` instead of `std::byte`. Authors: - Yunsong Wang (https://github.com/PointKernel) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Seunghwa Kang (https://github.com/seunghwak) URL: https://github.com/rapidsai/cugraph/pull/4707 --- cpp/src/detail/graph_partition_utils.cuh | 14 +++++++------- cpp/src/structure/remove_multi_edges_impl.cuh | 5 +++-- cpp/tests/prims/mg_count_if_v.cu | 2 +- ...mg_per_v_pair_transform_dst_nbr_intersection.cu | 2 +- ...pair_transform_dst_nbr_weighted_intersection.cu | 2 +- cpp/tests/prims/mg_transform_reduce_v.cu | 2 +- cpp/tests/utilities/property_generator_kernels.cuh | 4 ++-- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/cpp/src/detail/graph_partition_utils.cuh b/cpp/src/detail/graph_partition_utils.cuh index 00931780266..b10d2e788f4 100644 --- a/cpp/src/detail/graph_partition_utils.cuh +++ b/cpp/src/detail/graph_partition_utils.cuh @@ -43,7 +43,7 @@ struct compute_gpu_id_from_ext_vertex_t { __host__ __device__ int operator()(vertex_t v) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; auto vertex_partition_id = static_cast(hash_func(v) % comm_size); return partition_manager::compute_global_comm_rank_from_vertex_partition_id( major_comm_size, minor_comm_size, vertex_partition_id); @@ -58,7 +58,7 @@ struct compute_gpu_id_from_ext_edge_id_t { __host__ __device__ int operator()(edge_t e) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; auto vertex_partition_id = static_cast(hash_func(e) % comm_size); return partition_manager::compute_global_comm_rank_from_vertex_partition_id( major_comm_size, minor_comm_size, vertex_partition_id); @@ -88,7 +88,7 @@ struct compute_vertex_partition_id_from_ext_vertex_t { __host__ __device__ int operator()(vertex_t v) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return hash_func(v) % comm_size; } }; @@ -114,7 +114,7 @@ struct compute_gpu_id_from_ext_edge_endpoints_t { __host__ __device__ int operator()(vertex_t major, vertex_t minor) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; auto major_vertex_partition_id = static_cast(hash_func(major) % comm_size); auto minor_vertex_partition_id = static_cast(hash_func(minor) % comm_size); auto major_comm_rank = major_vertex_partition_id % major_comm_size; @@ -126,7 +126,7 @@ struct compute_gpu_id_from_ext_edge_endpoints_t { __host__ __device__ int operator()( thrust::tuple pair /* major, minor */) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; auto major_vertex_partition_id = static_cast(hash_func(thrust::get<0>(pair)) % comm_size); auto minor_vertex_partition_id = static_cast(hash_func(thrust::get<1>(pair)) % comm_size); auto major_comm_rank = major_vertex_partition_id % major_comm_size; @@ -192,7 +192,7 @@ struct compute_edge_partition_id_from_ext_edge_endpoints_t { __host__ __device__ int operator()(vertex_t major, vertex_t minor) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return (hash_func(major) % comm_size) * minor_comm_size + (hash_func(minor) % comm_size) / major_comm_size; } @@ -200,7 +200,7 @@ struct compute_edge_partition_id_from_ext_edge_endpoints_t { __host__ __device__ int operator()( thrust::tuple pair /* major, minor */) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return (hash_func(thrust::get<0>(pair)) % comm_size) * minor_comm_size + (hash_func(thrust::get<1>(pair)) % comm_size) / major_comm_size; } diff --git a/cpp/src/structure/remove_multi_edges_impl.cuh b/cpp/src/structure/remove_multi_edges_impl.cuh index ce83fdcb66a..7e266ab2caf 100644 --- a/cpp/src/structure/remove_multi_edges_impl.cuh +++ b/cpp/src/structure/remove_multi_edges_impl.cuh @@ -28,6 +28,7 @@ #include +#include #include #include #include @@ -53,8 +54,8 @@ struct hash_src_dst_pair { vertex_t pair[2]; pair[0] = thrust::get<0>(t); pair[1] = thrust::get<1>(t); - cuco::detail::MurmurHash3_32 hash_func{}; - return hash_func.compute_hash(reinterpret_cast(pair), 2 * sizeof(vertex_t)) % + cuco::murmurhash3_32 hash_func{}; + return hash_func.compute_hash(reinterpret_cast(pair), 2 * sizeof(vertex_t)) % num_groups; } }; diff --git a/cpp/tests/prims/mg_count_if_v.cu b/cpp/tests/prims/mg_count_if_v.cu index 56550027936..19ec285109c 100644 --- a/cpp/tests/prims/mg_count_if_v.cu +++ b/cpp/tests/prims/mg_count_if_v.cu @@ -48,7 +48,7 @@ struct test_predicate { test_predicate(int mod_count) : mod(mod_count) {} __device__ bool operator()(vertex_t, const vertex_t& val) { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return (0 == (hash_func(val) % mod)); } }; diff --git a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu index fc6369ec721..4025d4d1b1d 100644 --- a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu +++ b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu @@ -137,7 +137,7 @@ class Tests_MGPerVPairTransformDstNbrIntersection cugraph::get_dataframe_buffer_begin(mg_vertex_pair_buffer), cugraph::get_dataframe_buffer_end(mg_vertex_pair_buffer), [comm_rank, num_vertices = mg_graph_view.number_of_vertices()] __device__(size_t i) { - cuco::detail::MurmurHash3_32 + cuco::murmurhash3_32 hash_func{}; // use hash_func to generate arbitrary vertex pairs auto v0 = static_cast(hash_func(i + comm_rank) % num_vertices); auto v1 = static_cast(hash_func(i + num_vertices + comm_rank) % num_vertices); diff --git a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu index 06a23880d81..8af187554e1 100644 --- a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu +++ b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu @@ -163,7 +163,7 @@ class Tests_MGPerVPairTransformDstNbrIntersection cugraph::get_dataframe_buffer_begin(mg_vertex_pair_buffer), cugraph::get_dataframe_buffer_end(mg_vertex_pair_buffer), [comm_rank, num_vertices = mg_graph_view.number_of_vertices()] __device__(size_t i) { - cuco::detail::MurmurHash3_32 + cuco::murmurhash3_32 hash_func{}; // use hash_func to generate arbitrary vertex pairs auto v0 = static_cast(hash_func(i + comm_rank) % num_vertices); auto v1 = static_cast(hash_func(i + num_vertices + comm_rank) % num_vertices); diff --git a/cpp/tests/prims/mg_transform_reduce_v.cu b/cpp/tests/prims/mg_transform_reduce_v.cu index 0e6d71094bd..9e9bee89d67 100644 --- a/cpp/tests/prims/mg_transform_reduce_v.cu +++ b/cpp/tests/prims/mg_transform_reduce_v.cu @@ -53,7 +53,7 @@ struct v_op_t { __device__ auto operator()(vertex_t, vertex_t val) const { - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return cugraph::test::detail::make_property_value(hash_func(val) % mod); } }; diff --git a/cpp/tests/utilities/property_generator_kernels.cuh b/cpp/tests/utilities/property_generator_kernels.cuh index d8d5cc420fd..78b22e0dac2 100644 --- a/cpp/tests/utilities/property_generator_kernels.cuh +++ b/cpp/tests/utilities/property_generator_kernels.cuh @@ -60,7 +60,7 @@ struct vertex_property_transform { { static_assert(cugraph::is_thrust_tuple_of_arithmetic::value || std::is_arithmetic_v); - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return make_property_value(hash_func(v) % mod); } }; @@ -74,7 +74,7 @@ struct edge_property_transform { { static_assert(cugraph::is_thrust_tuple_of_arithmetic::value || std::is_arithmetic_v); - cuco::detail::MurmurHash3_32 hash_func{}; + cuco::murmurhash3_32 hash_func{}; return make_property_value(hash_func(src + dst) % mod); } }; From 21fe9bfdf9ead952b30f114eadf6d39edde3edea Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Tue, 15 Oct 2024 05:55:05 -0500 Subject: [PATCH 29/37] Update nx-cugraph to NetworkX 3.4 (#4717) This run `make` in `python/nx-cugraph` directory and update the pre-commit hook. Also fixes `ego_graph` tests to check `nx.config.fallback_to_nx` configuration. Authors: - Erik Welch (https://github.com/eriknw) Approvers: - Rick Ratzel (https://github.com/rlratzel) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cugraph/pull/4717 --- .pre-commit-config.yaml | 4 ++-- python/nx-cugraph/_nx_cugraph/__init__.py | 4 ++-- python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ff284210b7..3687562b48e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,7 +68,7 @@ repos: types: [python] language: python pass_filenames: false - additional_dependencies: ["networkx>=3.3"] + additional_dependencies: ["networkx>=3.4"] - repo: local hooks: - id: nx-cugraph-readme-update @@ -78,4 +78,4 @@ repos: types_or: [python, markdown] language: python pass_filenames: false - additional_dependencies: ["networkx>=3.3"] + additional_dependencies: ["networkx>=3.4"] diff --git a/python/nx-cugraph/_nx_cugraph/__init__.py b/python/nx-cugraph/_nx_cugraph/__init__.py index fc0bea47180..9feeda568a6 100644 --- a/python/nx-cugraph/_nx_cugraph/__init__.py +++ b/python/nx-cugraph/_nx_cugraph/__init__.py @@ -36,7 +36,7 @@ "backend_name": "cugraph", "project": "nx-cugraph", "package": "nx_cugraph", - "url": f"https://rapids.ai/nx-cugraph", + "url": "https://rapids.ai/nx-cugraph", "short_summary": "GPU-accelerated backend.", # "description": "TODO", "functions": { @@ -180,7 +180,7 @@ "ego_graph": "Weighted ego_graph with negative cycles is not yet supported. `NotImplementedError` will be raised if there are negative `distance` edge weights.", "eigenvector_centrality": "`nstart` parameter is not used, but it is checked for validity.", "from_pandas_edgelist": "cudf.DataFrame inputs also supported; value columns with str is unsuppported.", - "generic_bfs_edges": "`neighbors` and `sort_neighbors` parameters are not yet supported.", + "generic_bfs_edges": "`neighbors` parameter is not yet supported.", "katz_centrality": "`nstart` isn't used (but is checked), and `normalized=False` is not supported.", "louvain_communities": "`seed` parameter is currently ignored, and self-loops are not yet supported.", "pagerank": "`dangling` parameter is not supported, but it is checked for validity.", diff --git a/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py b/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py index 0697a744e85..f3d0a8d3767 100644 --- a/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py +++ b/python/nx-cugraph/nx_cugraph/tests/test_ego_graph.py @@ -78,7 +78,7 @@ def test_ego_graph_cycle_graph( nx.ego_graph(Gnx, n, **kwargs, backend="cugraph") with pytest.raises(NotImplementedError, match="ego_graph"): nx.ego_graph(Gcg, n, **kwargs, backend="cugraph") - if _nxver < (3, 4): + if _nxver < (3, 4) or not nx.config.fallback_to_nx: with pytest.raises(NotImplementedError, match="ego_graph"): nx.ego_graph(Gcg, n, **kwargs) else: @@ -86,7 +86,6 @@ def test_ego_graph_cycle_graph( # these arguments, so it falls back to networkx. Hence, as it is currently # implemented, the input graph is `nxcg.CudaGraph`, but the output graph # is `nx.Graph`. Should networkx convert back to "cugraph" backend? - # TODO: make fallback to networkx configurable. H2cg = nx.ego_graph(Gcg, n, **kwargs) assert type(H2nx) is type(H2cg) assert_graphs_equal(H2nx, nxcg.from_networkx(H2cg, preserve_all_attrs=True)) From 88b841f9c4fa40e1ec5f38b53f5eaec265e7ba16 Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:51:46 -0400 Subject: [PATCH 30/37] Update `nx-cugraph` README with New Env Variable (#4705) This PR updates the nx-cugraph README with the new `NX_CUGRAPH_AUTOCONFIG` variable. --------- Co-authored-by: Rick Ratzel <3039903+rlratzel@users.noreply.github.com> --- python/nx-cugraph/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/python/nx-cugraph/README.md b/python/nx-cugraph/README.md index c3ca0b880a9..8cc3a5d90df 100644 --- a/python/nx-cugraph/README.md +++ b/python/nx-cugraph/README.md @@ -1,7 +1,7 @@ # nx-cugraph ## Description -[RAPIDS](https://rapids.ai) nx-cugraph is a [backend to NetworkX](https://networkx.org/documentation/stable/reference/utils.html#backends) +[RAPIDS](https://rapids.ai) nx-cugraph is a [backend to NetworkX](https://networkx.org/documentation/stable/backends.html) to run supported algorithms with GPU acceleration. ## System Requirements @@ -10,7 +10,7 @@ nx-cugraph requires the following: * NVIDIA GPU, Volta architecture or later, with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+ * CUDA 11.2, 11.4, 11.5, 11.8, 12.0, 12.2, or 12.5 * Python version 3.10, 3.11, or 3.12 - * NetworkX >= version 3.0 (version 3.2 or higher recommended) + * NetworkX >= version 3.0 (version 3.4 or higher recommended) More details about system requirements can be found in the [RAPIDS System Requirements documentation](https://docs.rapids.ai/install#system-req). @@ -45,18 +45,20 @@ Notes: NetworkX will use nx-cugraph as the graph analytics backend if any of the following are used: -### `NETWORKX_AUTOMATIC_BACKENDS` environment variable. -The `NETWORKX_AUTOMATIC_BACKENDS` environment variable can be used to have NetworkX automatically dispatch to specified backends an API is called that the backend supports. -Set `NETWORKX_AUTOMATIC_BACKENDS=cugraph` to use nx-cugraph to GPU accelerate supported APIs with no code changes. +### `NX_CUGRAPH_AUTOCONFIG` environment variable. +By setting `NX_CUGRAPH_AUTOCONFIG=True`, NetworkX will automatically dispatch algorithm calls to nx-cugraph (if the backend is supported). This allows users to GPU accelerate their code with zero code change. + +Read more on [Networkx Backends and How They Work](https://networkx.org/documentation/stable/reference/backends.html). + Example: ``` -bash> NETWORKX_AUTOMATIC_BACKENDS=cugraph python my_networkx_script.py +bash> NX_CUGRAPH_AUTOCONFIG=True python my_networkx_script.py ``` ### `backend=` keyword argument To explicitly specify a particular backend for an API, use the `backend=` keyword argument. This argument takes precedence over the -`NETWORKX_AUTOMATIC_BACKENDS` environment variable. This requires anyone +`NX_CUGRAPH_AUTOCONFIG` environment variable. This requires anyone running code that uses the `backend=` keyword argument to have the specified backend installed. From 0d0d28a6be886b37cd349736adcc25c37d360e94 Mon Sep 17 00:00:00 2001 From: Chuck Hastings <45364586+ChuckHastings@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:44:43 -0400 Subject: [PATCH 31/37] Remove deprecated C API functions for graph creation/graph free (#4718) Prep work for temporal, cleaning up some old functions that we deprecated a while back. Authors: - Chuck Hastings (https://github.com/ChuckHastings) Approvers: - Seunghwa Kang (https://github.com/seunghwak) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4718 --- cpp/include/cugraph_c/graph.h | 146 ------------------ cpp/src/c_api/graph_mg.cpp | 37 ----- cpp/src/c_api/graph_sg.cpp | 64 -------- cpp/tests/c_api/betweenness_centrality_test.c | 2 +- cpp/tests/c_api/bfs_test.c | 2 +- cpp/tests/c_api/biased_neighbor_sample_test.c | 4 +- cpp/tests/c_api/core_number_test.c | 2 +- cpp/tests/c_api/create_graph_test.c | 4 +- cpp/tests/c_api/ecg_test.c | 2 +- .../c_api/edge_betweenness_centrality_test.c | 2 +- cpp/tests/c_api/egonet_test.c | 2 +- cpp/tests/c_api/eigenvector_centrality_test.c | 2 +- cpp/tests/c_api/extract_paths_test.c | 2 +- cpp/tests/c_api/hits_test.c | 2 +- cpp/tests/c_api/k_core_test.c | 2 +- cpp/tests/c_api/k_truss_test.c | 2 +- cpp/tests/c_api/katz_test.c | 2 +- cpp/tests/c_api/legacy_spectral_test.c | 4 +- cpp/tests/c_api/leiden_test.c | 2 +- cpp/tests/c_api/louvain_test.c | 2 +- .../c_api/mg_betweenness_centrality_test.c | 2 +- cpp/tests/c_api/mg_bfs_test.c | 2 +- .../c_api/mg_biased_neighbor_sample_test.c | 8 +- cpp/tests/c_api/mg_core_number_test.c | 2 +- cpp/tests/c_api/mg_ecg_test.c | 2 +- .../mg_edge_betweenness_centrality_test.c | 2 +- cpp/tests/c_api/mg_egonet_test.c | 2 +- .../c_api/mg_eigenvector_centrality_test.c | 2 +- cpp/tests/c_api/mg_hits_test.c | 2 +- cpp/tests/c_api/mg_induced_subgraph_test.c | 2 +- cpp/tests/c_api/mg_k_core_test.c | 2 +- cpp/tests/c_api/mg_k_truss_test.c | 3 +- cpp/tests/c_api/mg_katz_test.c | 2 +- cpp/tests/c_api/mg_leiden_test.c | 2 +- cpp/tests/c_api/mg_lookup_src_dst_test.c | 2 +- cpp/tests/c_api/mg_louvain_test.c | 2 +- cpp/tests/c_api/mg_negative_sampling_test.c | 2 +- cpp/tests/c_api/mg_pagerank_test.c | 8 +- cpp/tests/c_api/mg_random_walks_test.c | 6 +- cpp/tests/c_api/mg_similarity_test.c | 2 +- cpp/tests/c_api/mg_sssp_test.c | 4 +- .../mg_strongly_connected_components_test.c | 2 +- cpp/tests/c_api/mg_test_utils.cpp | 68 +++++--- cpp/tests/c_api/mg_triangle_count_test.c | 2 +- cpp/tests/c_api/mg_two_hop_neighbors_test.c | 2 +- .../c_api/mg_uniform_neighbor_sample_test.c | 8 +- .../mg_weakly_connected_components_test.c | 2 +- cpp/tests/c_api/negative_sampling_test.c | 2 +- cpp/tests/c_api/pagerank_test.c | 8 +- cpp/tests/c_api/sg_random_walks_test.c | 6 +- cpp/tests/c_api/similarity_test.c | 4 +- cpp/tests/c_api/sssp_test.c | 4 +- .../strongly_connected_components_test.c | 2 +- cpp/tests/c_api/test_utils.cpp | 18 ++- cpp/tests/c_api/triangle_count_test.c | 2 +- cpp/tests/c_api/two_hop_neighbors_test.c | 2 +- .../c_api/uniform_neighbor_sample_test.c | 10 +- .../c_api/weakly_connected_components_test.c | 2 +- .../pylibcugraph/_cugraph_c/graph.pxd | 87 +---------- python/pylibcugraph/pylibcugraph/graphs.pyx | 10 +- 60 files changed, 145 insertions(+), 444 deletions(-) diff --git a/cpp/include/cugraph_c/graph.h b/cpp/include/cugraph_c/graph.h index d812b503778..c5ce9c9fbeb 100644 --- a/cpp/include/cugraph_c/graph.h +++ b/cpp/include/cugraph_c/graph.h @@ -35,48 +35,6 @@ typedef struct { bool_t is_multigraph; } cugraph_graph_properties_t; -/** - * @brief Construct an SG graph - * - * @deprecated This API will be deleted, use cugraph_graph_create_sg instead - * - * @param [in] handle Handle for accessing resources - * @param [in] properties Properties of the constructed graph - * @param [in] src Device array containing the source vertex ids. - * @param [in] dst Device array containing the destination vertex ids - * @param [in] weights Device array containing the edge weights. Note that an unweighted - * graph can be created by passing weights == NULL. - * @param [in] edge_ids Device array containing the edge ids for each edge. Optional - argument that can be NULL if edge ids are not used. - * @param [in] edge_type_ids Device array containing the edge types for each edge. Optional - argument that can be NULL if edge types are not used. - * @param [in] store_transposed If true create the graph initially in transposed format - * @param [in] renumber If true, renumber vertices to make an efficient data structure. - * If false, do not renumber. Renumbering enables some significant optimizations within - * the graph primitives library, so it is strongly encouraged. Renumbering is required if - * the vertices are not sequential integer values from 0 to num_vertices. - * @param [in] do_expensive_check If true, do expensive checks to validate the input data - * is consistent with software assumptions. If false bypass these checks. - * @param [out] graph A pointer to the graph object - * @param [out] error Pointer to an error object storing details of any error. Will - * be populated if error code is not CUGRAPH_SUCCESS - * - * @return error code - */ -cugraph_error_code_t cugraph_sg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - bool_t renumber, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error); - /** * @brief Construct an SG graph * @@ -133,51 +91,6 @@ cugraph_error_code_t cugraph_graph_create_sg( cugraph_graph_t** graph, cugraph_error_t** error); -/** - * @brief Construct an SG graph from a CSR input - * - * @deprecated This API will be deleted, use cugraph_graph_create_sg_from_csr instead - * - * @param [in] handle Handle for accessing resources - * @param [in] properties Properties of the constructed graph - * @param [in] offsets Device array containing the CSR offsets array - * @param [in] indices Device array containing the destination vertex ids - * @param [in] weights Device array containing the edge weights. Note that an unweighted - * graph can be created by passing weights == NULL. - * @param [in] edge_ids Device array containing the edge ids for each edge. Optional - argument that can be NULL if edge ids are not used. - * @param [in] edge_type_ids Device array containing the edge types for each edge. Optional - argument that can be NULL if edge types are not used. - * @param [in] store_transposed If true create the graph initially in transposed format - * @param [in] renumber If true, renumber vertices to make an efficient data structure. - * If false, do not renumber. Renumbering enables some significant optimizations within - * the graph primitives library, so it is strongly encouraged. Renumbering is required if - * the vertices are not sequential integer values from 0 to num_vertices. - * @param [in] symmetrize If true, symmetrize the edgelist. The symmetrization of edges - * with edge_ids and/or edge_type_ids is currently not supported. - * @param [in] do_expensive_check If true, do expensive checks to validate the input data - * is consistent with software assumptions. If false bypass these checks. - * @param [out] graph A pointer to the graph object - * @param [out] error Pointer to an error object storing details of any error. Will - * be populated if error code is not CUGRAPH_SUCCESS - * - * @return error code - */ -cugraph_error_code_t cugraph_sg_graph_create_from_csr( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* offsets, - const cugraph_type_erased_device_array_view_t* indices, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - bool_t renumber, - bool_t symmetrize, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error); - /** * @brief Construct an SG graph from a CSR input * @@ -221,47 +134,6 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( cugraph_graph_t** graph, cugraph_error_t** error); -/** - * @brief Construct an MG graph - * - * @deprecated This API will be deleted, use cugraph_graph_create_mg instead - * - * @param [in] handle Handle for accessing resources - * @param [in] properties Properties of the constructed graph - * @param [in] src Device array containing the source vertex ids - * @param [in] dst Device array containing the destination vertex ids - * @param [in] weights Device array containing the edge weights. Note that an unweighted - * graph can be created by passing weights == NULL. If a weighted - * graph is to be created, the weights device array should be created - * on each rank, but the pointer can be NULL and the size 0 - * if there are no inputs provided by this rank - * @param [in] edge_ids Device array containing the edge ids for each edge. Optional - argument that can be NULL if edge ids are not used. - * @param [in] edge_type_ids Device array containing the edge types for each edge. Optional - argument that can be NULL if edge types are not used. - * @param [in] store_transposed If true create the graph initially in transposed format - * @param [in] num_edges Number of edges - * @param [in] do_expensive_check If true, do expensive checks to validate the input data - * is consistent with software assumptions. If false bypass these checks. - * @param [out] graph A pointer to the graph object - * @param [out] error Pointer to an error object storing details of any error. Will - * be populated if error code is not CUGRAPH_SUCCESS - * @return error code - */ -cugraph_error_code_t cugraph_mg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - size_t num_edges, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error); - /** * @brief Construct an MG graph * @@ -332,24 +204,6 @@ cugraph_error_code_t cugraph_graph_create_mg( */ void cugraph_graph_free(cugraph_graph_t* graph); -/** - * @brief Destroy an SG graph - * - * @deprecated This API will be deleted, use cugraph_graph_free instead - * - * @param [in] graph A pointer to the graph object to destroy - */ -void cugraph_sg_graph_free(cugraph_graph_t* graph); - -/** - * @brief Destroy an MG graph - * - * @deprecated This API will be deleted, use cugraph_graph_free instead - * - * @param [in] graph A pointer to the graph object to destroy - */ -void cugraph_mg_graph_free(cugraph_graph_t* graph); - /** * @brief Create a data mask * diff --git a/cpp/src/c_api/graph_mg.cpp b/cpp/src/c_api/graph_mg.cpp index fc8014a5dd8..2057448dbe5 100644 --- a/cpp/src/c_api/graph_mg.cpp +++ b/cpp/src/c_api/graph_mg.cpp @@ -536,40 +536,3 @@ extern "C" cugraph_error_code_t cugraph_graph_create_mg( return CUGRAPH_SUCCESS; } - -extern "C" cugraph_error_code_t cugraph_mg_graph_create( - cugraph_resource_handle_t const* handle, - cugraph_graph_properties_t const* properties, - cugraph_type_erased_device_array_view_t const* src, - cugraph_type_erased_device_array_view_t const* dst, - cugraph_type_erased_device_array_view_t const* weights, - cugraph_type_erased_device_array_view_t const* edge_ids, - cugraph_type_erased_device_array_view_t const* edge_type_ids, - bool_t store_transposed, - size_t num_edges, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error) -{ - return cugraph_graph_create_mg(handle, - properties, - NULL, - &src, - &dst, - (weights == nullptr) ? nullptr : &weights, - (edge_ids == nullptr) ? nullptr : &edge_ids, - (edge_type_ids == nullptr) ? nullptr : &edge_type_ids, - store_transposed, - 1, - FALSE, - FALSE, - FALSE, - do_expensive_check, - graph, - error); -} - -extern "C" void cugraph_mg_graph_free(cugraph_graph_t* ptr_graph) -{ - if (ptr_graph != NULL) { cugraph_graph_free(ptr_graph); } -} diff --git a/cpp/src/c_api/graph_sg.cpp b/cpp/src/c_api/graph_sg.cpp index f6ea8e4142e..ea598b902ae 100644 --- a/cpp/src/c_api/graph_sg.cpp +++ b/cpp/src/c_api/graph_sg.cpp @@ -680,38 +680,6 @@ extern "C" cugraph_error_code_t cugraph_graph_create_sg( return CUGRAPH_SUCCESS; } -extern "C" cugraph_error_code_t cugraph_sg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - bool_t renumber, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error) -{ - return cugraph_graph_create_sg(handle, - properties, - NULL, - src, - dst, - weights, - edge_ids, - edge_type_ids, - store_transposed, - renumber, - FALSE, - FALSE, - FALSE, - do_expensive_check, - graph, - error); -} - cugraph_error_code_t cugraph_graph_create_sg_from_csr( const cugraph_resource_handle_t* handle, const cugraph_graph_properties_t* properties, @@ -819,36 +787,6 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( return CUGRAPH_SUCCESS; } -cugraph_error_code_t cugraph_sg_graph_create_from_csr( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* offsets, - const cugraph_type_erased_device_array_view_t* indices, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - bool_t renumber, - bool_t symmetrize, - bool_t do_expensive_check, - cugraph_graph_t** graph, - cugraph_error_t** error) -{ - return cugraph_graph_create_sg_from_csr(handle, - properties, - offsets, - indices, - weights, - edge_ids, - edge_type_ids, - store_transposed, - renumber, - symmetrize, - do_expensive_check, - graph, - error); -} - extern "C" void cugraph_graph_free(cugraph_graph_t* ptr_graph) { if (ptr_graph != NULL) { @@ -871,5 +809,3 @@ extern "C" void cugraph_graph_free(cugraph_graph_t* ptr_graph) delete internal_pointer; } } - -extern "C" void cugraph_sg_graph_free(cugraph_graph_t* ptr_graph) { cugraph_graph_free(ptr_graph); } diff --git a/cpp/tests/c_api/betweenness_centrality_test.c b/cpp/tests/c_api/betweenness_centrality_test.c index cc37a10798e..7fec78eaffd 100644 --- a/cpp/tests/c_api/betweenness_centrality_test.c +++ b/cpp/tests/c_api/betweenness_centrality_test.c @@ -122,7 +122,7 @@ int generic_betweenness_centrality_test(vertex_t* h_src, cugraph_type_erased_device_array_view_free(seeds_view); cugraph_type_erased_device_array_free(seeds); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/bfs_test.c b/cpp/tests/c_api/bfs_test.c index 8feb426a853..b61051d8f90 100644 --- a/cpp/tests/c_api/bfs_test.c +++ b/cpp/tests/c_api/bfs_test.c @@ -109,7 +109,7 @@ int generic_bfs_test(vertex_t* h_src, cugraph_type_erased_device_array_free(p_sources); cugraph_paths_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/biased_neighbor_sample_test.c b/cpp/tests/c_api/biased_neighbor_sample_test.c index fe80514c825..0ccb236f7fc 100644 --- a/cpp/tests/c_api/biased_neighbor_sample_test.c +++ b/cpp/tests/c_api/biased_neighbor_sample_test.c @@ -461,7 +461,7 @@ int generic_biased_neighbor_sample_test(const cugraph_resource_handle_t* handle, cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } @@ -680,7 +680,7 @@ int test_biased_neighbor_sample_with_labels(const cugraph_resource_handle_t* han cugraph_sampling_options_free(sampling_options); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } diff --git a/cpp/tests/c_api/core_number_test.c b/cpp/tests/c_api/core_number_test.c index ca0bbf7134f..c9c85694db5 100644 --- a/cpp/tests/c_api/core_number_test.c +++ b/cpp/tests/c_api/core_number_test.c @@ -81,7 +81,7 @@ int generic_core_number_test(vertex_t* h_src, } cugraph_core_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/create_graph_test.c b/cpp/tests/c_api/create_graph_test.c index 104787e4c7b..a07f31fce57 100644 --- a/cpp/tests/c_api/create_graph_test.c +++ b/cpp/tests/c_api/create_graph_test.c @@ -204,7 +204,7 @@ int test_create_sg_graph_csr() handle, wgt_view, (byte_t*)h_wgt, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_sg_graph_create_from_csr(handle, + ret_code = cugraph_graph_create_sg_from_csr(handle, &properties, offsets_view, indices_view, @@ -669,7 +669,7 @@ int test_create_sg_graph_csr_with_isolated() handle, wgt_view, (byte_t*)h_wgt, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_sg_graph_create_from_csr(handle, + ret_code = cugraph_graph_create_sg_from_csr(handle, &properties, offsets_view, indices_view, diff --git a/cpp/tests/c_api/ecg_test.c b/cpp/tests/c_api/ecg_test.c index 4d4dd64572f..4f4c29b97b9 100644 --- a/cpp/tests/c_api/ecg_test.c +++ b/cpp/tests/c_api/ecg_test.c @@ -121,7 +121,7 @@ int generic_ecg_test(vertex_t* h_src, cugraph_hierarchical_clustering_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/edge_betweenness_centrality_test.c b/cpp/tests/c_api/edge_betweenness_centrality_test.c index 95b875b21af..8b9167778d9 100644 --- a/cpp/tests/c_api/edge_betweenness_centrality_test.c +++ b/cpp/tests/c_api/edge_betweenness_centrality_test.c @@ -128,7 +128,7 @@ int generic_edge_betweenness_centrality_test(vertex_t* h_src, } cugraph_edge_centrality_result_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/egonet_test.c b/cpp/tests/c_api/egonet_test.c index b4d27935ce1..1bde50afbad 100644 --- a/cpp/tests/c_api/egonet_test.c +++ b/cpp/tests/c_api/egonet_test.c @@ -165,7 +165,7 @@ int generic_egonet_test(vertex_t* h_src, cugraph_induced_subgraph_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(resource_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/eigenvector_centrality_test.c b/cpp/tests/c_api/eigenvector_centrality_test.c index ded505aeedc..d42745917d2 100644 --- a/cpp/tests/c_api/eigenvector_centrality_test.c +++ b/cpp/tests/c_api/eigenvector_centrality_test.c @@ -83,7 +83,7 @@ int generic_eigenvector_centrality_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/extract_paths_test.c b/cpp/tests/c_api/extract_paths_test.c index 5b87c3151c7..46e23112742 100644 --- a/cpp/tests/c_api/extract_paths_test.c +++ b/cpp/tests/c_api/extract_paths_test.c @@ -124,7 +124,7 @@ int generic_bfs_test_with_extract_paths(vertex_t* h_src, cugraph_type_erased_device_array_free(p_destinations); cugraph_extract_paths_result_free(p_extract_paths_result); cugraph_paths_result_free(p_paths_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/hits_test.c b/cpp/tests/c_api/hits_test.c index a9dbbb4f224..db63b0366b5 100644 --- a/cpp/tests/c_api/hits_test.c +++ b/cpp/tests/c_api/hits_test.c @@ -153,7 +153,7 @@ int generic_hits_test(vertex_t* h_src, } cugraph_hits_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/k_core_test.c b/cpp/tests/c_api/k_core_test.c index 81c1973d051..c60001fc8e7 100644 --- a/cpp/tests/c_api/k_core_test.c +++ b/cpp/tests/c_api/k_core_test.c @@ -142,7 +142,7 @@ int generic_k_core_test(vertex_t* h_src, cugraph_k_core_result_free(k_core_result); cugraph_core_result_free(core_result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(resource_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/k_truss_test.c b/cpp/tests/c_api/k_truss_test.c index 9aa90f66480..1ebacfead9f 100644 --- a/cpp/tests/c_api/k_truss_test.c +++ b/cpp/tests/c_api/k_truss_test.c @@ -150,7 +150,7 @@ int generic_k_truss_test(vertex_t* h_src, cugraph_induced_subgraph_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(resource_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/katz_test.c b/cpp/tests/c_api/katz_test.c index 4ee61b2b806..33bb6a5a9af 100644 --- a/cpp/tests/c_api/katz_test.c +++ b/cpp/tests/c_api/katz_test.c @@ -93,7 +93,7 @@ int generic_katz_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/legacy_spectral_test.c b/cpp/tests/c_api/legacy_spectral_test.c index ecb61e47ae6..ad695dd0883 100644 --- a/cpp/tests/c_api/legacy_spectral_test.c +++ b/cpp/tests/c_api/legacy_spectral_test.c @@ -141,7 +141,7 @@ int generic_spectral_test(vertex_t* h_src, cugraph_clustering_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); @@ -272,7 +272,7 @@ int generic_balanced_cut_test(vertex_t* h_src, cugraph_clustering_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/leiden_test.c b/cpp/tests/c_api/leiden_test.c index df206ebd1ed..57e6e921cce 100644 --- a/cpp/tests/c_api/leiden_test.c +++ b/cpp/tests/c_api/leiden_test.c @@ -114,7 +114,7 @@ int generic_leiden_test(vertex_t* h_src, cugraph_hierarchical_clustering_result_free(p_result); } - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/louvain_test.c b/cpp/tests/c_api/louvain_test.c index 41d777545b2..c083dbaa676 100644 --- a/cpp/tests/c_api/louvain_test.c +++ b/cpp/tests/c_api/louvain_test.c @@ -114,7 +114,7 @@ int generic_louvain_test(vertex_t* h_src, cugraph_hierarchical_clustering_result_free(p_result); } - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/mg_betweenness_centrality_test.c b/cpp/tests/c_api/mg_betweenness_centrality_test.c index 6a3cb69dd57..6f45792200e 100644 --- a/cpp/tests/c_api/mg_betweenness_centrality_test.c +++ b/cpp/tests/c_api/mg_betweenness_centrality_test.c @@ -118,7 +118,7 @@ int generic_betweenness_centrality_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_view_free(seeds_view); cugraph_type_erased_device_array_free(seeds); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_bfs_test.c b/cpp/tests/c_api/mg_bfs_test.c index 026d8f0796c..db24a180774 100644 --- a/cpp/tests/c_api/mg_bfs_test.c +++ b/cpp/tests/c_api/mg_bfs_test.c @@ -106,7 +106,7 @@ int generic_bfs_test(const cugraph_resource_handle_t* p_handle, } cugraph_paths_result_free(paths_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_biased_neighbor_sample_test.c b/cpp/tests/c_api/mg_biased_neighbor_sample_test.c index ce96fc6f5f7..ead86f22ad3 100644 --- a/cpp/tests/c_api/mg_biased_neighbor_sample_test.c +++ b/cpp/tests/c_api/mg_biased_neighbor_sample_test.c @@ -432,7 +432,7 @@ int generic_biased_neighbor_sample_test(const cugraph_resource_handle_t* handle, cugraph_sample_result_free(result); #endif - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } @@ -678,7 +678,7 @@ int test_biased_neighbor_from_alex(const cugraph_resource_handle_t* handle) cugraph_sample_result_free(result); cugraph_type_erased_host_array_view_free(h_fan_out_view); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); cugraph_sampling_options_free(sampling_options); @@ -941,7 +941,7 @@ int test_biased_neighbor_sample_alex_bug(const cugraph_resource_handle_t* handle cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } @@ -1221,7 +1221,7 @@ int test_biased_neighbor_sample_sort_by_hop(const cugraph_resource_handle_t* han cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } diff --git a/cpp/tests/c_api/mg_core_number_test.c b/cpp/tests/c_api/mg_core_number_test.c index 68c69a83c4f..a588c1997b1 100644 --- a/cpp/tests/c_api/mg_core_number_test.c +++ b/cpp/tests/c_api/mg_core_number_test.c @@ -79,7 +79,7 @@ int generic_core_number_test(const cugraph_resource_handle_t* p_handle, } cugraph_core_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_ecg_test.c b/cpp/tests/c_api/mg_ecg_test.c index b14ebda2959..626cb6025f5 100644 --- a/cpp/tests/c_api/mg_ecg_test.c +++ b/cpp/tests/c_api/mg_ecg_test.c @@ -100,7 +100,7 @@ int generic_ecg_test(const cugraph_resource_handle_t* handle, cugraph_hierarchical_clustering_result_free(result); } - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_edge_betweenness_centrality_test.c b/cpp/tests/c_api/mg_edge_betweenness_centrality_test.c index 319d4fb789b..cc3cc4c94c5 100644 --- a/cpp/tests/c_api/mg_edge_betweenness_centrality_test.c +++ b/cpp/tests/c_api/mg_edge_betweenness_centrality_test.c @@ -130,7 +130,7 @@ int generic_edge_betweenness_centrality_test(const cugraph_resource_handle_t* ha } cugraph_edge_centrality_result_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_egonet_test.c b/cpp/tests/c_api/mg_egonet_test.c index 6a308102ebe..41328f1f908 100644 --- a/cpp/tests/c_api/mg_egonet_test.c +++ b/cpp/tests/c_api/mg_egonet_test.c @@ -161,7 +161,7 @@ int generic_egonet_test(const cugraph_resource_handle_t* resource_handle, cugraph_induced_subgraph_result_free(result); } - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_eigenvector_centrality_test.c b/cpp/tests/c_api/mg_eigenvector_centrality_test.c index 37bf2afca3c..cdf2387fdb3 100644 --- a/cpp/tests/c_api/mg_eigenvector_centrality_test.c +++ b/cpp/tests/c_api/mg_eigenvector_centrality_test.c @@ -84,7 +84,7 @@ int generic_eigenvector_centrality_test(const cugraph_resource_handle_t* handle, } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_hits_test.c b/cpp/tests/c_api/mg_hits_test.c index 3e10bfc05d6..201718417a7 100644 --- a/cpp/tests/c_api/mg_hits_test.c +++ b/cpp/tests/c_api/mg_hits_test.c @@ -154,7 +154,7 @@ int generic_hits_test(const cugraph_resource_handle_t* p_handle, } cugraph_hits_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_induced_subgraph_test.c b/cpp/tests/c_api/mg_induced_subgraph_test.c index 018b308688e..39eefb9258c 100644 --- a/cpp/tests/c_api/mg_induced_subgraph_test.c +++ b/cpp/tests/c_api/mg_induced_subgraph_test.c @@ -153,7 +153,7 @@ int generic_induced_subgraph_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_free(subgraph_offsets); cugraph_type_erased_device_array_free(subgraph_vertices); cugraph_induced_subgraph_result_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } diff --git a/cpp/tests/c_api/mg_k_core_test.c b/cpp/tests/c_api/mg_k_core_test.c index 1218475481b..45aa1283679 100644 --- a/cpp/tests/c_api/mg_k_core_test.c +++ b/cpp/tests/c_api/mg_k_core_test.c @@ -110,7 +110,7 @@ int generic_k_core_test(const cugraph_resource_handle_t* resource_handle, cugraph_k_core_result_free(k_core_result); cugraph_core_result_free(core_result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_k_truss_test.c b/cpp/tests/c_api/mg_k_truss_test.c index e406eb330a7..ed385c6ca76 100644 --- a/cpp/tests/c_api/mg_k_truss_test.c +++ b/cpp/tests/c_api/mg_k_truss_test.c @@ -16,6 +16,7 @@ #include "mg_test_utils.h" /* RUN_TEST */ +#include #include #include @@ -99,7 +100,7 @@ int generic_k_truss_test(const cugraph_resource_handle_t* handle, } cugraph_induced_subgraph_result_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } diff --git a/cpp/tests/c_api/mg_katz_test.c b/cpp/tests/c_api/mg_katz_test.c index 2efb5d50539..6d000c335da 100644 --- a/cpp/tests/c_api/mg_katz_test.c +++ b/cpp/tests/c_api/mg_katz_test.c @@ -94,7 +94,7 @@ int generic_katz_test(const cugraph_resource_handle_t* handle, } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_leiden_test.c b/cpp/tests/c_api/mg_leiden_test.c index 72719b4d515..231c021c467 100644 --- a/cpp/tests/c_api/mg_leiden_test.c +++ b/cpp/tests/c_api/mg_leiden_test.c @@ -105,7 +105,7 @@ int generic_leiden_test(const cugraph_resource_handle_t* p_handle, cugraph_hierarchical_clustering_result_free(p_result); } - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_lookup_src_dst_test.c b/cpp/tests/c_api/mg_lookup_src_dst_test.c index 0cffffeb425..8c85d245acc 100644 --- a/cpp/tests/c_api/mg_lookup_src_dst_test.c +++ b/cpp/tests/c_api/mg_lookup_src_dst_test.c @@ -206,7 +206,7 @@ int generic_lookup_src_dst_test(const cugraph_resource_handle_t* handle, cugraph_lookup_result_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_louvain_test.c b/cpp/tests/c_api/mg_louvain_test.c index 858783614e5..54331fd8c66 100644 --- a/cpp/tests/c_api/mg_louvain_test.c +++ b/cpp/tests/c_api/mg_louvain_test.c @@ -96,7 +96,7 @@ int generic_louvain_test(const cugraph_resource_handle_t* p_handle, cugraph_hierarchical_clustering_result_free(p_result); } - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_negative_sampling_test.c b/cpp/tests/c_api/mg_negative_sampling_test.c index 3289206d8db..274bad35dfb 100644 --- a/cpp/tests/c_api/mg_negative_sampling_test.c +++ b/cpp/tests/c_api/mg_negative_sampling_test.c @@ -203,7 +203,7 @@ int generic_negative_sampling_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_free(d_src_bias); cugraph_type_erased_device_array_free(d_dst_bias); cugraph_coo_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } diff --git a/cpp/tests/c_api/mg_pagerank_test.c b/cpp/tests/c_api/mg_pagerank_test.c index 8bd02e70181..4616db3f704 100644 --- a/cpp/tests/c_api/mg_pagerank_test.c +++ b/cpp/tests/c_api/mg_pagerank_test.c @@ -94,7 +94,7 @@ int generic_pagerank_test(const cugraph_resource_handle_t* handle, } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; @@ -169,7 +169,7 @@ int generic_pagerank_nonconverging_test(const cugraph_resource_handle_t* handle, } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; @@ -278,7 +278,7 @@ int generic_personalized_pagerank_test(const cugraph_resource_handle_t* handle, } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; @@ -387,7 +387,7 @@ int generic_personalized_pagerank_nonconverging_test(const cugraph_resource_hand } cugraph_centrality_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_random_walks_test.c b/cpp/tests/c_api/mg_random_walks_test.c index 1c3e4ac0d38..13252e0f1dc 100644 --- a/cpp/tests/c_api/mg_random_walks_test.c +++ b/cpp/tests/c_api/mg_random_walks_test.c @@ -130,7 +130,7 @@ int generic_uniform_random_walks_test(const cugraph_resource_handle_t* handle, } cugraph_random_walk_result_free(result); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; @@ -232,7 +232,7 @@ int generic_biased_random_walks_test(const cugraph_resource_handle_t* handle, cugraph_random_walk_result_free(result); #endif - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; @@ -338,7 +338,7 @@ int generic_node2vec_random_walks_test(const cugraph_resource_handle_t* handle, cugraph_random_walk_result_free(result); #endif - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_similarity_test.c b/cpp/tests/c_api/mg_similarity_test.c index 486ca34aaca..637f66e40ed 100644 --- a/cpp/tests/c_api/mg_similarity_test.c +++ b/cpp/tests/c_api/mg_similarity_test.c @@ -189,7 +189,7 @@ int generic_similarity_test(const cugraph_resource_handle_t* handle, if (result != NULL) cugraph_similarity_result_free(result); if (vertex_pairs != NULL) cugraph_vertex_pairs_free(vertex_pairs); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_sssp_test.c b/cpp/tests/c_api/mg_sssp_test.c index 1158885aed2..3103b7331f4 100644 --- a/cpp/tests/c_api/mg_sssp_test.c +++ b/cpp/tests/c_api/mg_sssp_test.c @@ -94,7 +94,7 @@ int generic_sssp_test(const cugraph_resource_handle_t* p_handle, cugraph_type_erased_device_array_view_free(distances); cugraph_type_erased_device_array_view_free(predecessors); cugraph_paths_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; @@ -167,7 +167,7 @@ int generic_sssp_test_double(const cugraph_resource_handle_t* p_handle, cugraph_type_erased_device_array_view_free(distances); cugraph_type_erased_device_array_view_free(predecessors); cugraph_paths_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_strongly_connected_components_test.c b/cpp/tests/c_api/mg_strongly_connected_components_test.c index 38b2691c784..95777cc86fe 100644 --- a/cpp/tests/c_api/mg_strongly_connected_components_test.c +++ b/cpp/tests/c_api/mg_strongly_connected_components_test.c @@ -100,7 +100,7 @@ int generic_scc_test(const cugraph_resource_handle_t* handle, cugraph_labeling_result_free(p_result); #endif - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_test_utils.cpp b/cpp/tests/c_api/mg_test_utils.cpp index 58c5e59c16f..98b64014726 100644 --- a/cpp/tests/c_api/mg_test_utils.cpp +++ b/cpp/tests/c_api/mg_test_utils.cpp @@ -191,16 +191,20 @@ extern "C" int create_mg_test_graph(const cugraph_resource_handle_t* handle, handle, wgt_view, (byte_t*)h_wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_mg_graph_create(handle, + ret_code = cugraph_graph_create_mg(handle, &properties, - src_view, - dst_view, - wgt_view, + NULL, + &src_view, + &dst_view, + &wgt_view, NULL, NULL, store_transposed, original_num_edges, // UNUSED FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -285,16 +289,20 @@ extern "C" int create_mg_test_graph_double(const cugraph_resource_handle_t* hand handle, wgt_view, (byte_t*)h_wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_mg_graph_create(handle, + ret_code = cugraph_graph_create_mg(handle, &properties, - src_view, - dst_view, - wgt_view, + NULL, + &src_view, + &dst_view, + &wgt_view, NULL, NULL, store_transposed, original_num_edges, // UNUSED FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -373,16 +381,20 @@ extern "C" int create_mg_test_graph_with_edge_ids(const cugraph_resource_handle_ handle, idx_view, (byte_t*)h_idx, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_mg_graph_create(handle, + ret_code = cugraph_graph_create_mg(handle, &properties, - src_view, - dst_view, NULL, - idx_view, + &src_view, + &dst_view, + NULL, + &idx_view, NULL, store_transposed, original_num_edges, // UNUSED FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -496,16 +508,20 @@ extern "C" int create_mg_test_graph_with_properties(const cugraph_resource_handl TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); } - ret_code = cugraph_mg_graph_create(handle, + ret_code = cugraph_graph_create_mg(handle, &properties, - src_view, - dst_view, - wgt_view, - idx_view, - type_view, + NULL, + &src_view, + &dst_view, + &wgt_view, + &idx_view, + &type_view, store_transposed, original_num_edges, // UNUSED FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -625,16 +641,20 @@ int create_mg_test_graph_new(const cugraph_resource_handle_t* handle, TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "edge_id copy_from_host failed."); } - ret_code = cugraph_mg_graph_create(handle, + ret_code = cugraph_graph_create_mg(handle, &properties, - src_view, - dst_view, - wgt_view, - edge_id_view, - edge_type_view, + NULL, + &src_view, + &dst_view, + &wgt_view, + &edge_id_view, + &edge_type_view, store_transposed, original_num_edges, // UNUSED FALSE, + FALSE, + FALSE, + FALSE, graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); diff --git a/cpp/tests/c_api/mg_triangle_count_test.c b/cpp/tests/c_api/mg_triangle_count_test.c index d6eec6aad25..149c42046f5 100644 --- a/cpp/tests/c_api/mg_triangle_count_test.c +++ b/cpp/tests/c_api/mg_triangle_count_test.c @@ -105,7 +105,7 @@ int generic_triangle_count_test(const cugraph_resource_handle_t* handle, cugraph_triangle_count_result_free(p_result); } - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_two_hop_neighbors_test.c b/cpp/tests/c_api/mg_two_hop_neighbors_test.c index 056da2bcc45..31d5d535c8d 100644 --- a/cpp/tests/c_api/mg_two_hop_neighbors_test.c +++ b/cpp/tests/c_api/mg_two_hop_neighbors_test.c @@ -110,7 +110,7 @@ int generic_two_hop_nbr_test(const cugraph_resource_handle_t* resource_handle, cugraph_vertex_pairs_free(result); cugraph_type_erased_device_array_view_free(start_vertices_view); cugraph_type_erased_device_array_free(start_vertices); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/mg_uniform_neighbor_sample_test.c b/cpp/tests/c_api/mg_uniform_neighbor_sample_test.c index 3d8fb02ed46..b046b453c1c 100644 --- a/cpp/tests/c_api/mg_uniform_neighbor_sample_test.c +++ b/cpp/tests/c_api/mg_uniform_neighbor_sample_test.c @@ -431,7 +431,7 @@ int generic_uniform_neighbor_sample_test(const cugraph_resource_handle_t* handle cugraph_sample_result_free(result); #endif - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } @@ -676,7 +676,7 @@ int test_uniform_neighbor_from_alex(const cugraph_resource_handle_t* handle) cugraph_sample_result_free(result); cugraph_type_erased_host_array_view_free(h_fan_out_view); - cugraph_mg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); cugraph_sampling_options_free(sampling_options); @@ -938,7 +938,7 @@ int test_uniform_neighbor_sample_alex_bug(const cugraph_resource_handle_t* handl cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } @@ -1217,7 +1217,7 @@ int test_uniform_neighbor_sample_sort_by_hop(const cugraph_resource_handle_t* ha cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } diff --git a/cpp/tests/c_api/mg_weakly_connected_components_test.c b/cpp/tests/c_api/mg_weakly_connected_components_test.c index 3410c6dfd69..9b9dcfd6d5a 100644 --- a/cpp/tests/c_api/mg_weakly_connected_components_test.c +++ b/cpp/tests/c_api/mg_weakly_connected_components_test.c @@ -93,7 +93,7 @@ int generic_wcc_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_view_free(components); cugraph_type_erased_device_array_view_free(vertices); cugraph_labeling_result_free(p_result); - cugraph_mg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_error_free(ret_error); return test_ret_value; diff --git a/cpp/tests/c_api/negative_sampling_test.c b/cpp/tests/c_api/negative_sampling_test.c index 5e8d3f7e765..52360d622dd 100644 --- a/cpp/tests/c_api/negative_sampling_test.c +++ b/cpp/tests/c_api/negative_sampling_test.c @@ -194,7 +194,7 @@ int generic_negative_sampling_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_free(d_vertices); cugraph_type_erased_device_array_free(d_src_bias); cugraph_coo_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } diff --git a/cpp/tests/c_api/pagerank_test.c b/cpp/tests/c_api/pagerank_test.c index 0d55852dbb2..b0caef58eee 100644 --- a/cpp/tests/c_api/pagerank_test.c +++ b/cpp/tests/c_api/pagerank_test.c @@ -93,7 +93,7 @@ int generic_pagerank_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); @@ -168,7 +168,7 @@ int generic_pagerank_nonconverging_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); @@ -277,7 +277,7 @@ int generic_personalized_pagerank_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); @@ -386,7 +386,7 @@ int generic_personalized_pagerank_nonconverging_test(vertex_t* h_src, } cugraph_centrality_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/sg_random_walks_test.c b/cpp/tests/c_api/sg_random_walks_test.c index a4a77b5775a..05d77a0b3b2 100644 --- a/cpp/tests/c_api/sg_random_walks_test.c +++ b/cpp/tests/c_api/sg_random_walks_test.c @@ -141,7 +141,7 @@ int generic_uniform_random_walks_test(vertex_t* h_src, } cugraph_random_walk_result_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); @@ -258,7 +258,7 @@ int generic_biased_random_walks_test(vertex_t* h_src, cugraph_random_walk_result_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); @@ -377,7 +377,7 @@ int generic_node2vec_random_walks_test(vertex_t* h_src, cugraph_random_walk_result_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/similarity_test.c b/cpp/tests/c_api/similarity_test.c index 70e0cb6fb95..ac4dff850fa 100644 --- a/cpp/tests/c_api/similarity_test.c +++ b/cpp/tests/c_api/similarity_test.c @@ -128,7 +128,7 @@ int generic_similarity_test(vertex_t* h_src, if (result != NULL) cugraph_similarity_result_free(result); if (vertex_pairs != NULL) cugraph_vertex_pairs_free(vertex_pairs); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); @@ -238,7 +238,7 @@ int generic_all_pairs_similarity_test(vertex_t* h_src, } if (result != NULL) cugraph_similarity_result_free(result); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/sssp_test.c b/cpp/tests/c_api/sssp_test.c index 48039fcb43c..290542b1f9d 100644 --- a/cpp/tests/c_api/sssp_test.c +++ b/cpp/tests/c_api/sssp_test.c @@ -94,7 +94,7 @@ int generic_sssp_test(vertex_t* h_src, cugraph_type_erased_device_array_view_free(distances); cugraph_type_erased_device_array_view_free(predecessors); cugraph_paths_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); @@ -168,7 +168,7 @@ int generic_sssp_test_double(vertex_t* h_src, cugraph_type_erased_device_array_view_free(distances); cugraph_type_erased_device_array_view_free(predecessors); cugraph_paths_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/strongly_connected_components_test.c b/cpp/tests/c_api/strongly_connected_components_test.c index 24dd24c3fcd..0706c33a0bb 100644 --- a/cpp/tests/c_api/strongly_connected_components_test.c +++ b/cpp/tests/c_api/strongly_connected_components_test.c @@ -95,7 +95,7 @@ int generic_scc_test(vertex_t* h_src, cugraph_type_erased_device_array_view_free(vertices); cugraph_labeling_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/test_utils.cpp b/cpp/tests/c_api/test_utils.cpp index 193251917a4..2df62345784 100644 --- a/cpp/tests/c_api/test_utils.cpp +++ b/cpp/tests/c_api/test_utils.cpp @@ -98,8 +98,9 @@ extern "C" int create_test_graph(const cugraph_resource_handle_t* p_handle, p_handle, wgt_view, (byte_t*)h_wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_sg_graph_create(p_handle, + ret_code = cugraph_graph_create_sg(p_handle, &properties, + nullptr, src_view, dst_view, wgt_view, @@ -108,6 +109,9 @@ extern "C" int create_test_graph(const cugraph_resource_handle_t* p_handle, store_transposed, renumber, FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -180,8 +184,9 @@ extern "C" int create_test_graph_double(const cugraph_resource_handle_t* p_handl p_handle, wgt_view, (byte_t*)h_wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_sg_graph_create(p_handle, + ret_code = cugraph_graph_create_sg(p_handle, &properties, + nullptr, src_view, dst_view, wgt_view, @@ -190,6 +195,9 @@ extern "C" int create_test_graph_double(const cugraph_resource_handle_t* p_handl store_transposed, renumber, FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -355,8 +363,9 @@ int create_sg_test_graph(const cugraph_resource_handle_t* handle, TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "edge_id copy_from_host failed."); } - ret_code = cugraph_sg_graph_create(handle, + ret_code = cugraph_graph_create_sg(handle, &properties, + nullptr, src_view, dst_view, wgt_view, @@ -365,6 +374,9 @@ int create_sg_test_graph(const cugraph_resource_handle_t* handle, store_transposed, renumber, FALSE, + FALSE, + FALSE, + FALSE, graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); diff --git a/cpp/tests/c_api/triangle_count_test.c b/cpp/tests/c_api/triangle_count_test.c index 5929e3d6560..383cc335941 100644 --- a/cpp/tests/c_api/triangle_count_test.c +++ b/cpp/tests/c_api/triangle_count_test.c @@ -102,7 +102,7 @@ int generic_triangle_count_test(vertex_t* h_src, cugraph_triangle_count_result_free(p_result); } - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/two_hop_neighbors_test.c b/cpp/tests/c_api/two_hop_neighbors_test.c index bc95db3932b..44ccfd11622 100644 --- a/cpp/tests/c_api/two_hop_neighbors_test.c +++ b/cpp/tests/c_api/two_hop_neighbors_test.c @@ -117,7 +117,7 @@ int generic_two_hop_nbr_test(vertex_t* h_src, cugraph_vertex_pairs_free(result); cugraph_type_erased_device_array_view_free(start_vertices_view); cugraph_type_erased_device_array_free(start_vertices); - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_free_resource_handle(resource_handle); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/uniform_neighbor_sample_test.c b/cpp/tests/c_api/uniform_neighbor_sample_test.c index 451dbca51a7..404e38627ae 100644 --- a/cpp/tests/c_api/uniform_neighbor_sample_test.c +++ b/cpp/tests/c_api/uniform_neighbor_sample_test.c @@ -460,7 +460,7 @@ int generic_uniform_neighbor_sample_test(const cugraph_resource_handle_t* handle cugraph_sample_result_free(result); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); return test_ret_value; } @@ -528,8 +528,9 @@ int create_test_graph_with_edge_ids(const cugraph_resource_handle_t* p_handle, ret_code = cugraph_type_erased_device_array_view_as_type(ids, weight_tid, &wgt_view, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt cast from ids failed."); - ret_code = cugraph_sg_graph_create(p_handle, + ret_code = cugraph_graph_create_sg(p_handle, &properties, + NULL, src_view, dst_view, wgt_view, @@ -538,6 +539,9 @@ int create_test_graph_with_edge_ids(const cugraph_resource_handle_t* p_handle, store_transposed, renumber, FALSE, + FALSE, + FALSE, + FALSE, p_graph, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); @@ -766,7 +770,7 @@ int test_uniform_neighbor_sample_with_labels(const cugraph_resource_handle_t* ha cugraph_sampling_options_free(sampling_options); #endif - cugraph_sg_graph_free(graph); + cugraph_graph_free(graph); cugraph_error_free(ret_error); } diff --git a/cpp/tests/c_api/weakly_connected_components_test.c b/cpp/tests/c_api/weakly_connected_components_test.c index ee1a6b9066b..04fdcbc8930 100644 --- a/cpp/tests/c_api/weakly_connected_components_test.c +++ b/cpp/tests/c_api/weakly_connected_components_test.c @@ -93,7 +93,7 @@ int generic_wcc_test(vertex_t* h_src, cugraph_type_erased_device_array_view_free(components); cugraph_type_erased_device_array_view_free(vertices); cugraph_labeling_result_free(p_result); - cugraph_sg_graph_free(p_graph); + cugraph_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd index 497607860bd..5bbe5bc4a87 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd @@ -37,21 +37,6 @@ cdef extern from "cugraph_c/graph.h": bool_t is_symmetric bool_t is_multigraph - cdef cugraph_error_code_t \ - cugraph_sg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_types, - bool_t store_transposed, - bool_t renumber, - bool_t check, - cugraph_graph_t** graph, - cugraph_error_t** error) - # Supports isolated vertices cdef cugraph_error_code_t \ cugraph_graph_create_sg( @@ -72,12 +57,6 @@ cdef extern from "cugraph_c/graph.h": cugraph_graph_t** graph, cugraph_error_t** error) - # This may get renamed to cugraph_graph_free() - cdef void \ - cugraph_sg_graph_free( - cugraph_graph_t* graph - ) - # FIXME: Might want to delete 'cugraph_sg_graph_free' and replace # 'cugraph_mg_graph_free' by 'cugraph_graph_free' cdef void \ @@ -85,45 +64,6 @@ cdef extern from "cugraph_c/graph.h": cugraph_graph_t* graph ) - cdef cugraph_error_code_t \ - cugraph_mg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_types, - bool_t store_transposed, - size_t num_edges, - bool_t check, - cugraph_graph_t** graph, - cugraph_error_t** error - ) - - # This may get renamed to or replaced with cugraph_graph_free() - cdef void \ - cugraph_mg_graph_free( - cugraph_graph_t* graph - ) - - cdef cugraph_error_code_t \ - cugraph_sg_graph_create_from_csr( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* offsets, - const cugraph_type_erased_device_array_view_t* indices, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - bool_t renumber, - bool_t symmetrize, - bool_t check, - cugraph_graph_t** graph, - cugraph_error_t** error - ) - cdef cugraph_error_code_t \ cugraph_graph_create_sg_from_csr( const cugraph_resource_handle_t* handle, @@ -135,27 +75,7 @@ cdef extern from "cugraph_c/graph.h": const cugraph_type_erased_device_array_view_t* edge_type_ids, bool_t store_transposed, bool_t renumber, - bool_t check, - cugraph_graph_t** graph, - cugraph_error_t** error - ) - - cdef void \ - cugraph_sg_graph_free( - cugraph_graph_t* graph - ) - - cdef cugraph_error_code_t \ - cugraph_mg_graph_create( - const cugraph_resource_handle_t* handle, - const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_view_t* src, - const cugraph_type_erased_device_array_view_t* dst, - const cugraph_type_erased_device_array_view_t* weights, - const cugraph_type_erased_device_array_view_t* edge_ids, - const cugraph_type_erased_device_array_view_t* edge_type_ids, - bool_t store_transposed, - size_t num_edges, + bool_t symmetrize, bool_t check, cugraph_graph_t** graph, cugraph_error_t** error @@ -179,8 +99,3 @@ cdef extern from "cugraph_c/graph.h": bool_t do_expensive_check, cugraph_graph_t** graph, cugraph_error_t** error) - - cdef void \ - cugraph_mg_graph_free( - cugraph_graph_t* graph - ) diff --git a/python/pylibcugraph/pylibcugraph/graphs.pyx b/python/pylibcugraph/pylibcugraph/graphs.pyx index 6eda0a83d3e..ba32eb5b641 100644 --- a/python/pylibcugraph/pylibcugraph/graphs.pyx +++ b/python/pylibcugraph/pylibcugraph/graphs.pyx @@ -26,12 +26,8 @@ from pylibcugraph._cugraph_c.array cimport ( from pylibcugraph._cugraph_c.graph cimport ( cugraph_graph_create_sg, cugraph_graph_create_mg, - cugraph_sg_graph_create_from_csr, #FIXME: Remove this once - # 'cugraph_graph_create_sg_from_csr' is exposed cugraph_graph_create_sg_from_csr, - cugraph_sg_graph_free, #FIXME: Remove this cugraph_graph_free, - cugraph_mg_graph_free, #FIXME: Remove this ) from pylibcugraph.resource_handle cimport ( ResourceHandle, @@ -234,7 +230,7 @@ cdef class SGGraph(_GPUGraph): "cugraph_graph_create_sg()") elif input_array_format == "CSR": - error_code = cugraph_sg_graph_create_from_csr( + error_code = cugraph_graph_create_sg_from_csr( resource_handle.c_resource_handle_ptr, &(graph_properties.c_graph_properties), srcs_or_offsets_view_ptr, @@ -270,7 +266,7 @@ cdef class SGGraph(_GPUGraph): def __dealloc__(self): if self.c_graph_ptr is not NULL: - cugraph_sg_graph_free(self.c_graph_ptr) + cugraph_graph_free(self.c_graph_ptr) cdef class MGGraph(_GPUGraph): @@ -503,4 +499,4 @@ cdef class MGGraph(_GPUGraph): def __dealloc__(self): if self.c_graph_ptr is not NULL: - cugraph_mg_graph_free(self.c_graph_ptr) + cugraph_graph_free(self.c_graph_ptr) From 0c81f15364697e6d922aced223808c6c78de098e Mon Sep 17 00:00:00 2001 From: Rick Ratzel <3039903+rlratzel@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:09:52 -0500 Subject: [PATCH 32/37] Adds new dataset for benchmarking in the 100k node 1M edge range, adds additional k-values for BC benchmarks (#4726) Adds new dataset for benchmarking in the 100k node 1M edge range. This also updates the benchmark fixture to download the dataset instead of requiring a separate script. This also includes changes from #4721 to add additional k-values for BC benchmarks to run. The addition was done with an inline yaml file and a followup PR will be done to properly add this dataset to the cugraph.datasets API. The inline yaml was done in order to allow benchmarks to run on a system with an existing cugraph installation by simply copying this bench_algos.py, rather than require an updated cugraph install with the new dataset metadata. Authors: - Rick Ratzel (https://github.com/rlratzel) - Ralph Liu (https://github.com/nv-rliu) Approvers: - Ralph Liu (https://github.com/nv-rliu) - Don Acosta (https://github.com/acostadon) URL: https://github.com/rapidsai/cugraph/pull/4726 --- benchmarks/nx-cugraph/pytest-based/README.md | 4 +- .../nx-cugraph/pytest-based/bench_algos.py | 53 +++++++++++++------ .../create_results_summary_page.py | 2 + .../pytest-based/get_graph_bench_dataset.py | 35 ------------ .../pytest-based/run-main-benchmarks.sh | 20 ++++--- 5 files changed, 56 insertions(+), 58 deletions(-) delete mode 100644 benchmarks/nx-cugraph/pytest-based/get_graph_bench_dataset.py diff --git a/benchmarks/nx-cugraph/pytest-based/README.md b/benchmarks/nx-cugraph/pytest-based/README.md index 781550fa560..5d2406bfcd5 100644 --- a/benchmarks/nx-cugraph/pytest-based/README.md +++ b/benchmarks/nx-cugraph/pytest-based/README.md @@ -21,7 +21,9 @@ Our current benchmarks provide the following datasets: #### 1. `run-main-benchmarks.sh` This script allows users to run a small set of commonly-used algorithms across multiple datasets and backends. All results are stored inside a sub-directory (`logs/`) and output files are named based on the combination of parameters for that benchmark. -NOTE: If running with all algorithms and datasets using NetworkX without an accelerated backend, this script may take a few hours to finish running. +NOTE: + - If running with all algorithms and datasets using NetworkX without an accelerated backend, this script may take a few hours to finish running. + - The `betweenness_centrality` benchmark will run with values `[10, 20, 50, 100, 500, 1000]` by default. You can specify only specific k-values to be run by editing `bc_k_values` (line 46) to be passed as a [pytest keyword object](https://docs.pytest.org/en/6.2.x/usage.html#specifying-tests-selecting-tests). **Usage:** - Run with `--cpu-only`: diff --git a/benchmarks/nx-cugraph/pytest-based/bench_algos.py b/benchmarks/nx-cugraph/pytest-based/bench_algos.py index f88d93c3f17..8852ed2a875 100644 --- a/benchmarks/nx-cugraph/pytest-based/bench_algos.py +++ b/benchmarks/nx-cugraph/pytest-based/bench_algos.py @@ -37,6 +37,40 @@ iterations = 1 warmup_rounds = 1 +# FIXME: Add this to cugraph.datasets. This is done here so these benchmarks +# can be run without requiring an updated cugraph install. This temporarily +# adds a dataset based on an Amazon product co-purchasing network. +amazon0302_metadata = """ +name: amazon0302 +description: + Network was collected by crawling Amazon website. It is based on Customers Who Bought This Item Also Bought feature of the Amazon website. If a product i is frequently co-purchased with product j, the graph contains a directed edge from i to j. The data was collected in March 02 2003. +author: J. Leskovec, L. Adamic and B. Adamic +refs: J. Leskovec, L. Adamic and B. Adamic. The Dynamics of Viral Marketing. ACM Transactions on the Web (ACM TWEB), 1(1), 2007. +delim: "\t" +header: 3 +col_names: + - FromNodeId + - ToNodeId +col_types: + - int32 + - int32 +has_loop: false +is_directed: true +is_multigraph: false +is_symmetric: false +number_of_edges: 1234877 +number_of_nodes: 262111 +url: https://snap.stanford.edu/data/amazon0302.txt.gz +""" +amazon0302_metadata_file_name = datasets.default_download_dir.path / "amazon0302.yaml" +if not amazon0302_metadata_file_name.exists(): + amazon0302_metadata_file_name.parent.mkdir(parents=True, exist_ok=True) + with open(amazon0302_metadata_file_name, "w") as f: + f.write(amazon0302_metadata) + +amazon0302_dataset = datasets.Dataset(amazon0302_metadata_file_name) +amazon0302_dataset.metadata["file_type"] = ".gz" + dataset_param_values = [ # name: karate, nodes: 34, edges: 156 pytest.param(datasets.karate, marks=[pytest.mark.small, pytest.mark.undirected]), @@ -46,6 +80,8 @@ pytest.param( datasets.email_Eu_core, marks=[pytest.mark.small, pytest.mark.directed] ), + # name: amazon0302, nodes: 262111, edges: 1234877 + pytest.param(amazon0302_dataset, marks=[pytest.mark.medium, pytest.mark.directed]), # name: cit-Patents, nodes: 3774768, edges: 16518948 pytest.param( datasets.cit_patents, marks=[pytest.mark.medium, pytest.mark.directed] @@ -113,19 +149,7 @@ def nx_graph_from_dataset(dataset_obj): """ create_using = nx.DiGraph if dataset_obj.metadata["is_directed"] else nx.Graph names = dataset_obj.metadata["col_names"] - dtypes = dataset_obj.metadata["col_types"] - if isinstance(dataset_obj.metadata["header"], int): - header = dataset_obj.metadata["header"] - else: - header = None - - pandas_edgelist = pd.read_csv( - dataset_obj.get_path(), - delimiter=dataset_obj.metadata["delim"], - names=names, - dtype=dict(zip(names, dtypes)), - header=header, - ) + pandas_edgelist = dataset_obj.get_edgelist(download=True, reader="pandas") G = nx.from_pandas_edgelist( pandas_edgelist, source=names[0], target=names[1], create_using=create_using ) @@ -272,7 +296,7 @@ def bench_from_networkx(benchmark, graph_obj): # normalized_param_values = [True, False] normalized_param_values = [True] -k_param_values = [10, 100, 1000] +k_param_values = [10, 20, 50, 100, 500, 1000] @pytest.mark.parametrize( @@ -281,7 +305,6 @@ def bench_from_networkx(benchmark, graph_obj): @pytest.mark.parametrize("k", k_param_values, ids=lambda k: f"{k=}") def bench_betweenness_centrality(benchmark, graph_obj, backend_wrapper, normalized, k): G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper) - if k > G.number_of_nodes(): pytest.skip(reason=f"{k=} > {G.number_of_nodes()=}") diff --git a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py b/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py index f1cc4b06ccc..df4031e0f61 100644 --- a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py +++ b/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py @@ -166,6 +166,7 @@ def get_system_info(): ordered_datasets = [ "netscience", "email_Eu_core", + "amazon0302", "cit-patents", "hollywood", "soc-livejournal1", @@ -174,6 +175,7 @@ def get_system_info(): dataset_meta = { "netscience": ["1,461", "5,484", "Yes"], "email_Eu_core": ["1,005", "25,571", "Yes"], + "amazon0302": ["262,111", "1,234,877", "Yes"], "cit-patents": ["3,774,768", "16,518,948", "Yes"], "hollywood": ["1,139,905", "57,515,616", "No"], "soc-livejournal1": ["4,847,571", "68,993,773", "Yes"], diff --git a/benchmarks/nx-cugraph/pytest-based/get_graph_bench_dataset.py b/benchmarks/nx-cugraph/pytest-based/get_graph_bench_dataset.py deleted file mode 100644 index 5a0a15da8ee..00000000000 --- a/benchmarks/nx-cugraph/pytest-based/get_graph_bench_dataset.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2024, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Checks if a particular dataset has been downloaded inside the datasets dir -(RAPIDS_DATAEST_ROOT_DIR). If not, the file will be downloaded using the -datasets API. - -Positional Arguments: - 1) dataset name (e.g. 'email_Eu_core', 'cit-patents') - available datasets can be found here: `python/cugraph/cugraph/datasets/__init__.py` -""" - -import sys - -import cugraph.datasets as cgds - - -if __name__ == "__main__": - # download and store dataset (csv) by using the Datasets API - dataset = sys.argv[1].replace("-", "_") - dataset_obj = getattr(cgds, dataset) - - if not dataset_obj.get_path().exists(): - dataset_obj.get_edgelist(download=True) diff --git a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh index 3059e3d4bdf..6c674a5e428 100755 --- a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh +++ b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh @@ -14,7 +14,7 @@ # location to store datasets used for benchmarking -export RAPIDS_DATASET_ROOT_DIR=/datasets/cugraph +export RAPIDS_DATASET_ROOT_DIR=${RAPIDS_DATASET_ROOT_DIR:-/datasets/cugraph} mkdir -p logs # list of algos, datasets, and back-ends to use in combinations @@ -30,6 +30,7 @@ algos=" datasets=" netscience email_Eu_core + amazon0302 cit-patents hollywood soc-livejournal @@ -40,6 +41,11 @@ backends=" None cugraph-preconverted " + +# edit this directly to for pytest +# e.g. -k "and not 100 and not 1000" +bc_k_values="" + # check for --cpu-only or --gpu-only args if [[ "$#" -eq 1 ]]; then case $1 in @@ -58,15 +64,15 @@ fi for algo in $algos; do for dataset in $datasets; do - # this script can be used to download benchmarking datasets by name via cugraph.datasets - python get_graph_bench_dataset.py $dataset for backend in $backends; do name="${backend}__${algo}__${dataset}" echo "Running: $backend, $dataset, bench_$algo" - # command to preproduce test - # echo "RUNNING: \"pytest -sv -k \"$backend and $dataset and bench_$algo and not 1000\" --benchmark-json=\"logs/${name}.json\" bench_algos.py" - pytest -sv \ - -k "$backend and $dataset and bench_$algo and not 1000" \ + + # uncomment to get command for reproducing test + # echo "RUNNING: \"pytest -sv -k \"$backend and $dataset and bench_$algo $bc_k_values\" --benchmark-json=\"logs/${name}.json\" bench_algos.py" + + pytest -sv --co \ + -k "$backend and $dataset and bench_$algo $bc_k_values" \ --benchmark-json="logs/${name}.json" \ bench_algos.py 2>&1 | tee "logs/${name}.out" done From fde2fb4bef20038ca1a1f341209c36004ff4a8a2 Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:18:07 -0400 Subject: [PATCH 33/37] Remove `--collect-only` Option Left by Accident (#4727) Short PR. Removes a flag preventing benchmarks from being run in a script. --- benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh index 6c674a5e428..73c85000b0f 100755 --- a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh +++ b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh @@ -71,7 +71,7 @@ for algo in $algos; do # uncomment to get command for reproducing test # echo "RUNNING: \"pytest -sv -k \"$backend and $dataset and bench_$algo $bc_k_values\" --benchmark-json=\"logs/${name}.json\" bench_algos.py" - pytest -sv --co \ + pytest -sv \ -k "$backend and $dataset and bench_$algo $bc_k_values" \ --benchmark-json="logs/${name}.json" \ bench_algos.py 2>&1 | tee "logs/${name}.out" From 69d6601ccb7363fd08ec10c1fadb7a2c4cffc15a Mon Sep 17 00:00:00 2001 From: Ralph Liu <137829296+nv-rliu@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:11:45 -0400 Subject: [PATCH 34/37] Add support for storing results for all k-values (#4728) Another small PR for nx-cugraph benchmark logging. Authors: - Ralph Liu (https://github.com/nv-rliu) Approvers: - Rick Ratzel (https://github.com/rlratzel) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cugraph/pull/4728 --- .../nx-cugraph/pytest-based/create_results_summary_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py b/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py index df4031e0f61..e4aff10f0a5 100644 --- a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py +++ b/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py @@ -157,7 +157,7 @@ def get_system_info(): dataset_patt = re.compile(".*ds=([\w-]+).*") backend_patt = re.compile(".*backend=(\w+).*") - k_patt = re.compile(".*k=(10*).*") + k_patt = re.compile(".*k=(\d+).*") # Organize all benchmark runs by the following hierarchy: algo -> backend -> dataset benchmarks = get_all_benchmark_info() From 2ac5586c8939bc371e3637f8e3e5a972b02bf0ef Mon Sep 17 00:00:00 2001 From: Chuck Hastings <45364586+ChuckHastings@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:21:46 -0400 Subject: [PATCH 35/37] Don't compile int32_t/int64_t vertex_t/edge_t combinations (#4720) To reduce compile time and library size... we're dropping pre-compiled support for vertex_t as int32_t and edge_t as int64_t. This is a small edge case in memory utilization. The data structures and primitives would still support such a configuration, but we're not compiling it into libcugraph.so with this PR. Seems to improve C++ compile time by 25% on my dgx18 and a 30% reduction in the size of libcugraph.so. Authors: - Chuck Hastings (https://github.com/ChuckHastings) Approvers: - Seunghwa Kang (https://github.com/seunghwak) URL: https://github.com/rapidsai/cugraph/pull/4720 --- cpp/CMakeLists.txt | 106 --------- .../cugraph/utilities/graph_traits.hpp | 2 +- cpp/src/c_api/graph_helper_mg.cu | 22 -- cpp/src/c_api/graph_helper_sg.cu | 24 -- cpp/src/c_api/graph_mg.cpp | 16 +- cpp/src/c_api/graph_sg.cpp | 14 +- .../betweenness_centrality_mg_v32_e64.cu | 58 ----- .../betweenness_centrality_sg_v32_e64.cu | 58 ----- .../eigenvector_centrality_mg_v32_e64.cu | 40 ---- .../eigenvector_centrality_sg_v32_e64.cu | 40 ---- .../centrality/katz_centrality_mg_v32_e64.cu | 50 ---- .../centrality/katz_centrality_sg_v32_e64.cu | 50 ---- .../approx_weighted_matching_mg_v32_e64.cu | 30 --- .../approx_weighted_matching_sg_v32_e64.cu | 30 --- .../detail/common_methods_mg_v32_e64.cu | 118 ---------- .../detail/common_methods_sg_v32_e64.cu | 118 ---------- .../maximal_independent_moves_mg_v32_e64.cu | 28 --- .../maximal_independent_moves_sg_v32_e64.cu | 27 --- cpp/src/community/detail/refine_mg_v32_e64.cu | 66 ------ cpp/src/community/detail/refine_sg_v32_e64.cu | 66 ------ cpp/src/community/ecg_mg_v32_e64.cu | 44 ---- cpp/src/community/ecg_sg_v32_e64.cu | 44 ---- .../edge_triangle_count_mg_v32_e64.cu | 26 --- .../edge_triangle_count_sg_v32_e64.cu | 26 --- cpp/src/community/egonet_mg_v32_e64.cu | 66 ------ cpp/src/community/egonet_sg_v32_e64.cu | 66 ------ cpp/src/community/k_truss_mg_v32_e64.cu | 41 ---- cpp/src/community/k_truss_sg_v32_e64.cu | 41 ---- cpp/src/community/leiden_mg_v32_e64.cu | 58 ----- cpp/src/community/leiden_sg_v32_e64.cu | 58 ----- cpp/src/community/louvain_mg_v32_e64.cu | 57 ----- cpp/src/community/louvain_sg_v32_e64.cu | 57 ----- .../community/triangle_count_mg_v32_e64.cu | 27 --- .../community/triangle_count_sg_v32_e64.cu | 27 --- cpp/src/components/mis_mg_v32_e64.cu | 24 -- cpp/src/components/mis_sg_v32_e64.cu | 25 -- .../components/vertex_coloring_mg_v32_e64.cu | 25 -- .../components/vertex_coloring_sg_v32_e64.cu | 25 -- .../weakly_connected_components_mg_v32_e64.cu | 29 --- .../weakly_connected_components_sg_v32_e64.cu | 29 --- cpp/src/cores/core_number_mg_v32_e64.cu | 31 --- cpp/src/cores/core_number_sg_v32_e64.cu | 31 --- cpp/src/cores/k_core_mg_v32_e64.cu | 45 ---- cpp/src/cores/k_core_sg_v32_e64.cu | 45 ---- .../collect_local_vertex_values_mg_v32_e32.cu | 12 +- .../collect_local_vertex_values_mg_v32_e64.cu | 51 ---- .../collect_local_vertex_values_sg_v32_e32.cu | 12 +- .../collect_local_vertex_values_sg_v32_e64.cu | 51 ---- .../detail/groupby_and_count_mg_v32_e64.cu | 56 ----- cpp/src/link_analysis/hits_mg_v32_e64.cu | 44 ---- cpp/src/link_analysis/hits_sg_v32_e64.cu | 44 ---- cpp/src/link_analysis/pagerank_mg_v32_e64.cu | 75 ------ cpp/src/link_analysis/pagerank_sg_v32_e64.cu | 75 ------ cpp/src/link_prediction/cosine_mg_v32_e64.cu | 54 ----- cpp/src/link_prediction/cosine_sg_v32_e64.cu | 54 ----- cpp/src/link_prediction/jaccard_mg_v32_e64.cu | 54 ----- cpp/src/link_prediction/jaccard_sg_v32_e64.cu | 54 ----- cpp/src/link_prediction/overlap_mg_v32_e64.cu | 54 ----- cpp/src/link_prediction/overlap_sg_v32_e64.cu | 54 ----- .../link_prediction/sorensen_mg_v32_e64.cu | 54 ----- .../link_prediction/sorensen_sg_v32_e64.cu | 54 ----- cpp/src/lookup/lookup_src_dst_mg_v32_e64.cu | 43 ---- cpp/src/lookup/lookup_src_dst_sg_v32_e64.cu | 43 ---- .../check_edge_bias_values_mg_v32_e64.cu | 33 --- .../check_edge_bias_values_sg_v32_e64.cu | 33 --- .../gather_one_hop_edgelist_mg_v32_e64.cu | 55 ----- .../gather_one_hop_edgelist_sg_v32_e64.cu | 55 ----- .../detail/sample_edges_mg_v32_e64.cu | 59 ----- .../detail/sample_edges_sg_v32_e64.cu | 59 ----- .../shuffle_and_organize_output_mg_v32_e64.cu | 63 ----- .../sampling/negative_sampling_mg_v32_e64.cu | 48 ---- .../sampling/negative_sampling_sg_v32_e64.cu | 48 ---- .../sampling/neighbor_sampling_mg_v32_e64.cpp | 130 ----------- .../sampling/neighbor_sampling_sg_v32_e64.cpp | 130 ----------- cpp/src/sampling/random_walks_mg_v32_e64.cu | 75 ------ cpp/src/sampling/random_walks_old_sg.cu | 24 +- .../sampling/random_walks_old_sg_v32_e64.cu | 54 ----- cpp/src/sampling/random_walks_sg_v32_e64.cu | 75 ------ .../sampling_post_processing_sg_v32_e64.cu | 219 ------------------ cpp/src/structure/coarsen_graph_mg_v32_e64.cu | 66 ------ cpp/src/structure/coarsen_graph_sg_v32_e64.cu | 66 ------ .../create_graph_from_edgelist_mg_v32_e64.cu | 189 --------------- .../create_graph_from_edgelist_sg_v32_e64.cu | 190 --------------- .../decompress_to_edgelist_mg_v32_e64.cu | 78 ------- .../decompress_to_edgelist_sg_v32_e64.cu | 78 ------- cpp/src/structure/graph_mg_v32_e64.cu | 25 -- cpp/src/structure/graph_sg_v32_e64.cu | 25 -- cpp/src/structure/graph_view_mg_v32_e64.cu | 24 -- cpp/src/structure/graph_view_sg_v32_e64.cu | 24 -- .../graph_weight_utils_mg_v32_e64.cu | 124 ---------- .../graph_weight_utils_sg_v32_e64.cu | 125 ---------- .../structure/induced_subgraph_mg_v32_e64.cu | 46 ---- .../structure/induced_subgraph_sg_v32_e64.cu | 46 ---- .../remove_multi_edges_sg_v32_e64.cu | 46 ---- .../structure/remove_self_loops_sg_v32_e64.cu | 44 ---- .../structure/renumber_edgelist_mg_v32_e64.cu | 33 --- .../structure/renumber_edgelist_sg_v32_e64.cu | 31 --- .../select_random_vertices_mg_v32_e64.cu | 41 ---- .../select_random_vertices_sg_v32_e64.cu | 41 ---- .../structure/symmetrize_graph_mg_v32_e64.cu | 71 ------ .../structure/symmetrize_graph_sg_v32_e64.cu | 73 ------ .../structure/transpose_graph_mg_v32_e64.cu | 67 ------ .../structure/transpose_graph_sg_v32_e64.cu | 69 ------ .../transpose_graph_storage_mg_v32_e64.cu | 67 ------ .../transpose_graph_storage_sg_v32_e64.cu | 69 ------ cpp/src/traversal/bfs_mg_v32_e64.cu | 33 --- cpp/src/traversal/bfs_sg_v32_e64.cu | 33 --- .../traversal/extract_bfs_paths_mg_v32_e64.cu | 31 --- .../traversal/extract_bfs_paths_sg_v32_e64.cu | 31 --- cpp/src/traversal/k_hop_nbrs_mg_v32_e64.cu | 29 --- cpp/src/traversal/k_hop_nbrs_sg_v32_e64.cu | 29 --- .../od_shortest_distances_sg_v32_e64.cu | 40 ---- cpp/src/traversal/sssp_mg_v32_e64.cu | 40 ---- cpp/src/traversal/sssp_sg_v32_e64.cu | 40 ---- .../shuffle_vertex_pairs_mg_v32_e64.cu | 120 ---------- .../betweenness_centrality_test.cpp | 6 - .../edge_betweenness_centrality_test.cpp | 6 - .../eigenvector_centrality_test.cpp | 7 - cpp/tests/centrality/katz_centrality_test.cpp | 7 - .../mg_betweenness_centrality_test.cpp | 6 - .../mg_edge_betweenness_centrality_test.cpp | 6 - .../mg_eigenvector_centrality_test.cpp | 7 - .../centrality/mg_katz_centrality_test.cpp | 7 - cpp/tests/community/mg_ecg_test.cpp | 6 - .../community/mg_edge_triangle_count_test.cpp | 7 - cpp/tests/community/mg_egonet_test.cu | 6 - cpp/tests/community/mg_k_truss_test.cpp | 7 - cpp/tests/community/mg_leiden_test.cpp | 6 - cpp/tests/community/mg_louvain_test.cpp | 6 - .../community/mg_triangle_count_test.cpp | 7 - .../community/mg_weighted_matching_test.cpp | 12 - cpp/tests/community/triangle_count_test.cpp | 5 - .../community/weighted_matching_test.cpp | 12 - cpp/tests/components/mg_mis_test.cu | 12 - .../components/mg_vertex_coloring_test.cu | 12 - .../mg_weakly_connected_components_test.cpp | 7 - cpp/tests/components/mis_test.cu | 12 - cpp/tests/components/vertex_coloring_test.cu | 12 - .../weakly_connected_components_test.cpp | 7 - cpp/tests/cores/core_number_test.cpp | 7 - cpp/tests/cores/k_core_test.cpp | 5 - cpp/tests/cores/k_core_validate.cu | 10 - cpp/tests/cores/mg_core_number_test.cpp | 7 - cpp/tests/cores/mg_k_core_test.cpp | 5 - cpp/tests/link_analysis/hits_test.cpp | 6 - cpp/tests/link_analysis/mg_hits_test.cpp | 7 - cpp/tests/link_analysis/mg_pagerank_test.cpp | 7 - cpp/tests/link_analysis/pagerank_test.cpp | 6 - .../link_prediction/mg_similarity_test.cpp | 26 --- .../mg_weighted_similarity_test.cpp | 26 --- cpp/tests/link_prediction/similarity_test.cu | 24 -- .../weighted_similarity_test.cpp | 24 -- cpp/tests/lookup/lookup_src_dst_test.cpp | 12 - cpp/tests/lookup/mg_lookup_src_dst_test.cpp | 12 - cpp/tests/prims/mg_count_if_e.cu | 32 --- cpp/tests/prims/mg_count_if_v.cu | 14 -- cpp/tests/prims/mg_extract_transform_e.cu | 14 -- ...extract_transform_v_frontier_outgoing_e.cu | 15 -- ...r_v_pair_transform_dst_nbr_intersection.cu | 8 - ...transform_dst_nbr_weighted_intersection.cu | 8 - ...er_v_random_select_transform_outgoing_e.cu | 16 -- ...rm_reduce_dst_key_aggregated_outgoing_e.cu | 18 -- ..._v_transform_reduce_incoming_outgoing_e.cu | 34 --- cpp/tests/prims/mg_reduce_v.cu | 14 -- cpp/tests/prims/mg_transform_e.cu | 48 ---- ...st_nbr_intersection_of_e_endpoints_by_v.cu | 8 - cpp/tests/prims/mg_transform_reduce_e.cu | 32 --- .../mg_transform_reduce_e_by_src_dst_key.cu | 32 --- cpp/tests/prims/mg_transform_reduce_v.cu | 32 --- ...reduce_v_frontier_outgoing_e_by_src_dst.cu | 15 -- .../sampling/biased_neighbor_sampling.cpp | 13 -- .../sampling/mg_biased_neighbor_sampling.cpp | 6 - .../sampling/mg_uniform_neighbor_sampling.cpp | 6 - cpp/tests/sampling/negative_sampling.cpp | 35 --- ...ing_heterogeneous_post_processing_test.cpp | 5 - .../sampling_post_processing_test.cpp | 5 - .../sampling/uniform_neighbor_sampling.cpp | 12 - cpp/tests/structure/coarsen_graph_test.cpp | 12 - .../count_self_loops_and_multi_edges_test.cpp | 7 - ...has_edge_and_compute_multiplicity_test.cpp | 7 - cpp/tests/structure/mg_coarsen_graph_test.cpp | 14 -- ..._count_self_loops_and_multi_edges_test.cpp | 7 - ...has_edge_and_compute_multiplicity_test.cpp | 7 - .../structure/mg_induced_subgraph_test.cu | 6 - .../mg_select_random_vertices_test.cpp | 6 - cpp/tests/structure/mg_symmetrize_test.cpp | 14 -- .../structure/mg_transpose_storage_test.cpp | 14 -- cpp/tests/structure/mg_transpose_test.cpp | 14 -- cpp/tests/structure/symmetrize_test.cpp | 14 -- .../structure/transpose_storage_test.cpp | 14 -- cpp/tests/structure/transpose_test.cpp | 14 -- cpp/tests/structure/weight_sum_test.cpp | 12 - cpp/tests/traversal/bfs_test.cpp | 7 - cpp/tests/traversal/extract_bfs_paths_test.cu | 7 - cpp/tests/traversal/k_hop_nbrs_test.cpp | 7 - cpp/tests/traversal/mg_bfs_test.cpp | 7 - .../traversal/mg_extract_bfs_paths_test.cu | 7 - cpp/tests/traversal/mg_k_hop_nbrs_test.cpp | 7 - cpp/tests/traversal/mg_sssp_test.cpp | 7 - .../traversal/od_shortest_distances_test.cpp | 7 - cpp/tests/traversal/sssp_test.cpp | 7 - .../utilities/conversion_utilities_mg.cu | 172 -------------- .../utilities/conversion_utilities_sg.cu | 120 ---------- cpp/tests/utilities/csv_file_utilities.cu | 80 ------- cpp/tests/utilities/debug_utilities_mg.cpp | 12 - cpp/tests/utilities/debug_utilities_sg.cpp | 12 - .../utilities/matrix_market_file_utilities.cu | 80 ------- .../property_generator_utilities_mg.cu | 12 - .../property_generator_utilities_sg.cu | 12 - 209 files changed, 39 insertions(+), 7672 deletions(-) delete mode 100644 cpp/src/centrality/betweenness_centrality_mg_v32_e64.cu delete mode 100644 cpp/src/centrality/betweenness_centrality_sg_v32_e64.cu delete mode 100644 cpp/src/centrality/eigenvector_centrality_mg_v32_e64.cu delete mode 100644 cpp/src/centrality/eigenvector_centrality_sg_v32_e64.cu delete mode 100644 cpp/src/centrality/katz_centrality_mg_v32_e64.cu delete mode 100644 cpp/src/centrality/katz_centrality_sg_v32_e64.cu delete mode 100644 cpp/src/community/approx_weighted_matching_mg_v32_e64.cu delete mode 100644 cpp/src/community/approx_weighted_matching_sg_v32_e64.cu delete mode 100644 cpp/src/community/detail/common_methods_mg_v32_e64.cu delete mode 100644 cpp/src/community/detail/common_methods_sg_v32_e64.cu delete mode 100644 cpp/src/community/detail/maximal_independent_moves_mg_v32_e64.cu delete mode 100644 cpp/src/community/detail/maximal_independent_moves_sg_v32_e64.cu delete mode 100644 cpp/src/community/detail/refine_mg_v32_e64.cu delete mode 100644 cpp/src/community/detail/refine_sg_v32_e64.cu delete mode 100644 cpp/src/community/ecg_mg_v32_e64.cu delete mode 100644 cpp/src/community/ecg_sg_v32_e64.cu delete mode 100644 cpp/src/community/edge_triangle_count_mg_v32_e64.cu delete mode 100644 cpp/src/community/edge_triangle_count_sg_v32_e64.cu delete mode 100644 cpp/src/community/egonet_mg_v32_e64.cu delete mode 100644 cpp/src/community/egonet_sg_v32_e64.cu delete mode 100644 cpp/src/community/k_truss_mg_v32_e64.cu delete mode 100644 cpp/src/community/k_truss_sg_v32_e64.cu delete mode 100644 cpp/src/community/leiden_mg_v32_e64.cu delete mode 100644 cpp/src/community/leiden_sg_v32_e64.cu delete mode 100644 cpp/src/community/louvain_mg_v32_e64.cu delete mode 100644 cpp/src/community/louvain_sg_v32_e64.cu delete mode 100644 cpp/src/community/triangle_count_mg_v32_e64.cu delete mode 100644 cpp/src/community/triangle_count_sg_v32_e64.cu delete mode 100644 cpp/src/components/mis_mg_v32_e64.cu delete mode 100644 cpp/src/components/mis_sg_v32_e64.cu delete mode 100644 cpp/src/components/vertex_coloring_mg_v32_e64.cu delete mode 100644 cpp/src/components/vertex_coloring_sg_v32_e64.cu delete mode 100644 cpp/src/components/weakly_connected_components_mg_v32_e64.cu delete mode 100644 cpp/src/components/weakly_connected_components_sg_v32_e64.cu delete mode 100644 cpp/src/cores/core_number_mg_v32_e64.cu delete mode 100644 cpp/src/cores/core_number_sg_v32_e64.cu delete mode 100644 cpp/src/cores/k_core_mg_v32_e64.cu delete mode 100644 cpp/src/cores/k_core_sg_v32_e64.cu delete mode 100644 cpp/src/detail/collect_local_vertex_values_mg_v32_e64.cu delete mode 100644 cpp/src/detail/collect_local_vertex_values_sg_v32_e64.cu delete mode 100644 cpp/src/detail/groupby_and_count_mg_v32_e64.cu delete mode 100644 cpp/src/link_analysis/hits_mg_v32_e64.cu delete mode 100644 cpp/src/link_analysis/hits_sg_v32_e64.cu delete mode 100644 cpp/src/link_analysis/pagerank_mg_v32_e64.cu delete mode 100644 cpp/src/link_analysis/pagerank_sg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/cosine_mg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/cosine_sg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/jaccard_mg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/jaccard_sg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/overlap_mg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/overlap_sg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/sorensen_mg_v32_e64.cu delete mode 100644 cpp/src/link_prediction/sorensen_sg_v32_e64.cu delete mode 100644 cpp/src/lookup/lookup_src_dst_mg_v32_e64.cu delete mode 100644 cpp/src/lookup/lookup_src_dst_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/check_edge_bias_values_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/check_edge_bias_values_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/gather_one_hop_edgelist_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/gather_one_hop_edgelist_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/sample_edges_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/sample_edges_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/detail/shuffle_and_organize_output_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/negative_sampling_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/negative_sampling_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/neighbor_sampling_mg_v32_e64.cpp delete mode 100644 cpp/src/sampling/neighbor_sampling_sg_v32_e64.cpp delete mode 100644 cpp/src/sampling/random_walks_mg_v32_e64.cu delete mode 100644 cpp/src/sampling/random_walks_old_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/random_walks_sg_v32_e64.cu delete mode 100644 cpp/src/sampling/sampling_post_processing_sg_v32_e64.cu delete mode 100644 cpp/src/structure/coarsen_graph_mg_v32_e64.cu delete mode 100644 cpp/src/structure/coarsen_graph_sg_v32_e64.cu delete mode 100644 cpp/src/structure/create_graph_from_edgelist_mg_v32_e64.cu delete mode 100644 cpp/src/structure/create_graph_from_edgelist_sg_v32_e64.cu delete mode 100644 cpp/src/structure/decompress_to_edgelist_mg_v32_e64.cu delete mode 100644 cpp/src/structure/decompress_to_edgelist_sg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_mg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_sg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_view_mg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_view_sg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_weight_utils_mg_v32_e64.cu delete mode 100644 cpp/src/structure/graph_weight_utils_sg_v32_e64.cu delete mode 100644 cpp/src/structure/induced_subgraph_mg_v32_e64.cu delete mode 100644 cpp/src/structure/induced_subgraph_sg_v32_e64.cu delete mode 100644 cpp/src/structure/remove_multi_edges_sg_v32_e64.cu delete mode 100644 cpp/src/structure/remove_self_loops_sg_v32_e64.cu delete mode 100644 cpp/src/structure/renumber_edgelist_mg_v32_e64.cu delete mode 100644 cpp/src/structure/renumber_edgelist_sg_v32_e64.cu delete mode 100644 cpp/src/structure/select_random_vertices_mg_v32_e64.cu delete mode 100644 cpp/src/structure/select_random_vertices_sg_v32_e64.cu delete mode 100644 cpp/src/structure/symmetrize_graph_mg_v32_e64.cu delete mode 100644 cpp/src/structure/symmetrize_graph_sg_v32_e64.cu delete mode 100644 cpp/src/structure/transpose_graph_mg_v32_e64.cu delete mode 100644 cpp/src/structure/transpose_graph_sg_v32_e64.cu delete mode 100644 cpp/src/structure/transpose_graph_storage_mg_v32_e64.cu delete mode 100644 cpp/src/structure/transpose_graph_storage_sg_v32_e64.cu delete mode 100644 cpp/src/traversal/bfs_mg_v32_e64.cu delete mode 100644 cpp/src/traversal/bfs_sg_v32_e64.cu delete mode 100644 cpp/src/traversal/extract_bfs_paths_mg_v32_e64.cu delete mode 100644 cpp/src/traversal/extract_bfs_paths_sg_v32_e64.cu delete mode 100644 cpp/src/traversal/k_hop_nbrs_mg_v32_e64.cu delete mode 100644 cpp/src/traversal/k_hop_nbrs_sg_v32_e64.cu delete mode 100644 cpp/src/traversal/od_shortest_distances_sg_v32_e64.cu delete mode 100644 cpp/src/traversal/sssp_mg_v32_e64.cu delete mode 100644 cpp/src/traversal/sssp_sg_v32_e64.cu delete mode 100644 cpp/src/utilities/shuffle_vertex_pairs_mg_v32_e64.cu diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index b8eaba9d575..fc3dbb57e1f 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -175,56 +175,40 @@ set(CUGRAPH_SOURCES src/detail/permute_range_v32.cu src/detail/permute_range_v64.cu src/utilities/shuffle_vertex_pairs_mg_v32_e32.cu - src/utilities/shuffle_vertex_pairs_mg_v32_e64.cu src/utilities/shuffle_vertex_pairs_mg_v64_e64.cu src/detail/collect_local_vertex_values_sg_v32_e32.cu - src/detail/collect_local_vertex_values_sg_v32_e64.cu src/detail/collect_local_vertex_values_sg_v64_e64.cu src/detail/collect_local_vertex_values_mg_v32_e32.cu - src/detail/collect_local_vertex_values_mg_v32_e64.cu src/detail/collect_local_vertex_values_mg_v64_e64.cu src/detail/groupby_and_count_mg_v32_e32.cu - src/detail/groupby_and_count_mg_v32_e64.cu src/detail/groupby_and_count_mg_v64_e64.cu src/detail/collect_comm_wrapper_mg_v32_e32.cu src/detail/collect_comm_wrapper_mg_v64_e64.cu src/sampling/random_walks_mg_v64_e64.cu src/sampling/random_walks_mg_v32_e32.cu - src/sampling/random_walks_mg_v32_e64.cu src/community/detail/common_methods_mg_v64_e64.cu src/community/detail/common_methods_mg_v32_e32.cu - src/community/detail/common_methods_mg_v32_e64.cu src/community/detail/common_methods_sg_v64_e64.cu src/community/detail/common_methods_sg_v32_e32.cu - src/community/detail/common_methods_sg_v32_e64.cu src/community/detail/refine_sg_v64_e64.cu src/community/detail/refine_sg_v32_e32.cu - src/community/detail/refine_sg_v32_e64.cu src/community/detail/refine_mg_v64_e64.cu src/community/detail/refine_mg_v32_e32.cu - src/community/detail/refine_mg_v32_e64.cu src/community/edge_triangle_count_sg_v64_e64.cu src/community/edge_triangle_count_sg_v32_e32.cu - src/community/edge_triangle_count_sg_v32_e64.cu src/community/edge_triangle_count_mg_v64_e64.cu src/community/edge_triangle_count_mg_v32_e32.cu - src/community/edge_triangle_count_mg_v32_e64.cu src/community/detail/maximal_independent_moves_sg_v64_e64.cu src/community/detail/maximal_independent_moves_sg_v32_e32.cu - src/community/detail/maximal_independent_moves_sg_v32_e64.cu src/community/detail/maximal_independent_moves_mg_v64_e64.cu src/community/detail/maximal_independent_moves_mg_v32_e32.cu - src/community/detail/maximal_independent_moves_mg_v32_e64.cu src/detail/utility_wrappers_32.cu src/detail/utility_wrappers_64.cu src/structure/graph_view_mg_v64_e64.cu src/structure/graph_view_mg_v32_e32.cu - src/structure/graph_view_mg_v32_e64.cu src/structure/remove_self_loops_sg_v32_e32.cu - src/structure/remove_self_loops_sg_v32_e64.cu src/structure/remove_self_loops_sg_v64_e64.cu src/structure/remove_multi_edges_sg_v32_e32.cu - src/structure/remove_multi_edges_sg_v32_e64.cu src/structure/remove_multi_edges_sg_v64_e64.cu src/utilities/path_retrieval_sg_v32_e32.cu src/utilities/path_retrieval_sg_v64_e64.cu @@ -232,127 +216,89 @@ set(CUGRAPH_SOURCES src/linear_assignment/legacy/hungarian.cu src/link_prediction/jaccard_sg_v64_e64.cu src/link_prediction/jaccard_sg_v32_e32.cu - src/link_prediction/jaccard_sg_v32_e64.cu src/link_prediction/sorensen_sg_v64_e64.cu src/link_prediction/sorensen_sg_v32_e32.cu - src/link_prediction/sorensen_sg_v32_e64.cu src/link_prediction/overlap_sg_v64_e64.cu src/link_prediction/overlap_sg_v32_e32.cu - src/link_prediction/overlap_sg_v32_e64.cu src/link_prediction/cosine_sg_v64_e64.cu src/link_prediction/cosine_sg_v32_e32.cu - src/link_prediction/cosine_sg_v32_e64.cu src/link_prediction/jaccard_mg_v64_e64.cu src/link_prediction/jaccard_mg_v32_e32.cu - src/link_prediction/jaccard_mg_v32_e64.cu src/link_prediction/sorensen_mg_v64_e64.cu src/link_prediction/sorensen_mg_v32_e32.cu - src/link_prediction/sorensen_mg_v32_e64.cu src/link_prediction/overlap_mg_v64_e64.cu src/link_prediction/overlap_mg_v32_e32.cu - src/link_prediction/overlap_mg_v32_e64.cu src/link_prediction/cosine_mg_v64_e64.cu src/link_prediction/cosine_mg_v32_e32.cu - src/link_prediction/cosine_mg_v32_e64.cu src/layout/legacy/force_atlas2.cu src/converters/legacy/COOtoCSR.cu src/community/legacy/spectral_clustering.cu src/community/louvain_sg_v64_e64.cu src/community/louvain_sg_v32_e32.cu - src/community/louvain_sg_v32_e64.cu src/community/louvain_mg_v64_e64.cu src/community/louvain_mg_v32_e32.cu - src/community/louvain_mg_v32_e64.cu src/community/leiden_sg_v64_e64.cu src/community/leiden_sg_v32_e32.cu - src/community/leiden_sg_v32_e64.cu src/community/leiden_mg_v64_e64.cu src/community/leiden_mg_v32_e32.cu - src/community/leiden_mg_v32_e64.cu src/community/ecg_sg_v64_e64.cu src/community/ecg_sg_v32_e32.cu - src/community/ecg_sg_v32_e64.cu src/community/ecg_mg_v64_e64.cu src/community/ecg_mg_v32_e32.cu - src/community/ecg_mg_v32_e64.cu src/community/egonet_sg_v64_e64.cu src/community/egonet_sg_v32_e32.cu - src/community/egonet_sg_v32_e64.cu src/community/egonet_mg_v64_e64.cu src/community/egonet_mg_v32_e32.cu - src/community/egonet_mg_v32_e64.cu src/community/k_truss_sg_v64_e64.cu src/community/k_truss_sg_v32_e32.cu - src/community/k_truss_sg_v32_e64.cu src/community/k_truss_mg_v64_e64.cu src/community/k_truss_mg_v32_e32.cu - src/community/k_truss_mg_v32_e64.cu src/lookup/lookup_src_dst_mg_v32_e32.cu - src/lookup/lookup_src_dst_mg_v32_e64.cu src/lookup/lookup_src_dst_mg_v64_e64.cu src/lookup/lookup_src_dst_sg_v32_e32.cu - src/lookup/lookup_src_dst_sg_v32_e64.cu src/lookup/lookup_src_dst_sg_v64_e64.cu src/sampling/random_walks_old_sg_v32_e32.cu - src/sampling/random_walks_old_sg_v32_e64.cu src/sampling/random_walks_old_sg_v64_e64.cu src/sampling/random_walks_sg_v64_e64.cu src/sampling/random_walks_sg_v32_e32.cu - src/sampling/random_walks_sg_v32_e64.cu src/sampling/detail/prepare_next_frontier_sg_v64_e64.cu src/sampling/detail/prepare_next_frontier_sg_v32_e32.cu src/sampling/detail/prepare_next_frontier_mg_v64_e64.cu src/sampling/detail/prepare_next_frontier_mg_v32_e32.cu src/sampling/detail/gather_one_hop_edgelist_sg_v64_e64.cu src/sampling/detail/gather_one_hop_edgelist_sg_v32_e32.cu - src/sampling/detail/gather_one_hop_edgelist_sg_v32_e64.cu src/sampling/detail/gather_one_hop_edgelist_mg_v64_e64.cu src/sampling/detail/gather_one_hop_edgelist_mg_v32_e32.cu - src/sampling/detail/gather_one_hop_edgelist_mg_v32_e64.cu src/sampling/detail/remove_visited_vertices_from_frontier_sg_v32_e32.cu src/sampling/detail/remove_visited_vertices_from_frontier_sg_v64_e64.cu src/sampling/detail/check_edge_bias_values_sg_v64_e64.cu src/sampling/detail/check_edge_bias_values_sg_v32_e32.cu - src/sampling/detail/check_edge_bias_values_sg_v32_e64.cu src/sampling/detail/check_edge_bias_values_mg_v64_e64.cu src/sampling/detail/check_edge_bias_values_mg_v32_e32.cu - src/sampling/detail/check_edge_bias_values_mg_v32_e64.cu src/sampling/detail/sample_edges_sg_v64_e64.cu src/sampling/detail/sample_edges_sg_v32_e32.cu - src/sampling/detail/sample_edges_sg_v32_e64.cu src/sampling/detail/sample_edges_mg_v64_e64.cu src/sampling/detail/sample_edges_mg_v32_e32.cu - src/sampling/detail/sample_edges_mg_v32_e64.cu src/sampling/detail/shuffle_and_organize_output_mg_v64_e64.cu src/sampling/detail/shuffle_and_organize_output_mg_v32_e32.cu - src/sampling/detail/shuffle_and_organize_output_mg_v32_e64.cu - src/sampling/neighbor_sampling_mg_v32_e64.cpp src/sampling/neighbor_sampling_mg_v32_e32.cpp src/sampling/neighbor_sampling_mg_v64_e64.cpp - src/sampling/neighbor_sampling_sg_v32_e64.cpp src/sampling/neighbor_sampling_sg_v32_e32.cpp src/sampling/neighbor_sampling_sg_v64_e64.cpp - src/sampling/negative_sampling_sg_v32_e64.cu src/sampling/negative_sampling_sg_v32_e32.cu src/sampling/negative_sampling_sg_v64_e64.cu - src/sampling/negative_sampling_mg_v32_e64.cu src/sampling/negative_sampling_mg_v32_e32.cu src/sampling/negative_sampling_mg_v64_e64.cu src/sampling/sampling_post_processing_sg_v64_e64.cu src/sampling/sampling_post_processing_sg_v32_e32.cu - src/sampling/sampling_post_processing_sg_v32_e64.cu src/cores/core_number_sg_v64_e64.cu src/cores/core_number_sg_v32_e32.cu - src/cores/core_number_sg_v32_e64.cu src/cores/core_number_mg_v64_e64.cu src/cores/core_number_mg_v32_e32.cu - src/cores/core_number_mg_v32_e64.cu src/cores/k_core_sg_v64_e64.cu src/cores/k_core_sg_v32_e32.cu - src/cores/k_core_sg_v32_e64.cu src/cores/k_core_mg_v64_e64.cu src/cores/k_core_mg_v32_e32.cu - src/cores/k_core_mg_v32_e64.cu src/components/legacy/connectivity.cu src/generators/generate_rmat_edgelist_sg_v32_e32.cu src/generators/generate_rmat_edgelist_sg_v64_e64.cu @@ -366,55 +312,38 @@ set(CUGRAPH_SOURCES src/generators/erdos_renyi_generator_sg_v64_e64.cu src/structure/graph_sg_v64_e64.cu src/structure/graph_sg_v32_e32.cu - src/structure/graph_sg_v32_e64.cu src/structure/graph_mg_v64_e64.cu src/structure/graph_mg_v32_e32.cu - src/structure/graph_mg_v32_e64.cu src/structure/graph_view_sg_v64_e64.cu src/structure/graph_view_sg_v32_e32.cu - src/structure/graph_view_sg_v32_e64.cu src/structure/decompress_to_edgelist_sg_v64_e64.cu src/structure/decompress_to_edgelist_sg_v32_e32.cu - src/structure/decompress_to_edgelist_sg_v32_e64.cu src/structure/decompress_to_edgelist_mg_v64_e64.cu src/structure/decompress_to_edgelist_mg_v32_e32.cu - src/structure/decompress_to_edgelist_mg_v32_e64.cu src/structure/symmetrize_graph_sg_v64_e64.cu src/structure/symmetrize_graph_sg_v32_e32.cu - src/structure/symmetrize_graph_sg_v32_e64.cu src/structure/symmetrize_graph_mg_v64_e64.cu src/structure/symmetrize_graph_mg_v32_e32.cu - src/structure/symmetrize_graph_mg_v32_e64.cu src/structure/transpose_graph_sg_v64_e64.cu src/structure/transpose_graph_sg_v32_e32.cu - src/structure/transpose_graph_sg_v32_e64.cu src/structure/transpose_graph_mg_v64_e64.cu src/structure/transpose_graph_mg_v32_e32.cu - src/structure/transpose_graph_mg_v32_e64.cu src/structure/transpose_graph_storage_sg_v64_e64.cu src/structure/transpose_graph_storage_sg_v32_e32.cu - src/structure/transpose_graph_storage_sg_v32_e64.cu src/structure/transpose_graph_storage_mg_v64_e64.cu src/structure/transpose_graph_storage_mg_v32_e32.cu - src/structure/transpose_graph_storage_mg_v32_e64.cu src/structure/coarsen_graph_sg_v64_e64.cu src/structure/coarsen_graph_sg_v32_e32.cu - src/structure/coarsen_graph_sg_v32_e64.cu src/structure/coarsen_graph_mg_v64_e64.cu src/structure/coarsen_graph_mg_v32_e32.cu - src/structure/coarsen_graph_mg_v32_e64.cu src/structure/graph_weight_utils_mg_v64_e64.cu src/structure/graph_weight_utils_mg_v32_e32.cu - src/structure/graph_weight_utils_mg_v32_e64.cu src/structure/graph_weight_utils_sg_v64_e64.cu src/structure/graph_weight_utils_sg_v32_e32.cu - src/structure/graph_weight_utils_sg_v32_e64.cu src/structure/renumber_edgelist_sg_v64_e64.cu src/structure/renumber_edgelist_sg_v32_e32.cu - src/structure/renumber_edgelist_sg_v32_e64.cu src/structure/renumber_edgelist_mg_v64_e64.cu src/structure/renumber_edgelist_mg_v32_e32.cu - src/structure/renumber_edgelist_mg_v32_e64.cu src/structure/renumber_utils_sg_v64_e64.cu src/structure/renumber_utils_sg_v32_e32.cu src/structure/renumber_utils_mg_v64_e64.cu @@ -425,115 +354,80 @@ set(CUGRAPH_SOURCES src/structure/relabel_mg_v32_e32.cu src/structure/induced_subgraph_sg_v64_e64.cu src/structure/induced_subgraph_sg_v32_e32.cu - src/structure/induced_subgraph_sg_v32_e64.cu src/structure/induced_subgraph_mg_v64_e64.cu src/structure/induced_subgraph_mg_v32_e32.cu - src/structure/induced_subgraph_mg_v32_e64.cu src/structure/select_random_vertices_sg_v64_e64.cu src/structure/select_random_vertices_sg_v32_e32.cu - src/structure/select_random_vertices_sg_v32_e64.cu src/structure/select_random_vertices_mg_v64_e64.cu src/structure/select_random_vertices_mg_v32_e32.cu - src/structure/select_random_vertices_mg_v32_e64.cu src/traversal/extract_bfs_paths_sg_v64_e64.cu src/traversal/extract_bfs_paths_sg_v32_e32.cu - src/traversal/extract_bfs_paths_sg_v32_e64.cu src/traversal/extract_bfs_paths_mg_v64_e64.cu src/traversal/extract_bfs_paths_mg_v32_e32.cu - src/traversal/extract_bfs_paths_mg_v32_e64.cu src/traversal/bfs_sg_v64_e64.cu src/traversal/bfs_sg_v32_e32.cu - src/traversal/bfs_sg_v32_e64.cu src/traversal/bfs_mg_v64_e64.cu src/traversal/bfs_mg_v32_e32.cu - src/traversal/bfs_mg_v32_e64.cu src/traversal/sssp_sg_v64_e64.cu src/traversal/sssp_sg_v32_e32.cu - src/traversal/sssp_sg_v32_e64.cu src/traversal/od_shortest_distances_sg_v64_e64.cu src/traversal/od_shortest_distances_sg_v32_e32.cu - src/traversal/od_shortest_distances_sg_v32_e64.cu src/traversal/sssp_mg_v64_e64.cu src/traversal/sssp_mg_v32_e32.cu - src/traversal/sssp_mg_v32_e64.cu src/link_analysis/hits_sg_v64_e64.cu src/link_analysis/hits_sg_v32_e32.cu - src/link_analysis/hits_sg_v32_e64.cu src/link_analysis/hits_mg_v64_e64.cu src/link_analysis/hits_mg_v32_e32.cu - src/link_analysis/hits_mg_v32_e64.cu src/link_analysis/pagerank_sg_v64_e64.cu src/link_analysis/pagerank_sg_v32_e32.cu - src/link_analysis/pagerank_sg_v32_e64.cu src/link_analysis/pagerank_mg_v64_e64.cu src/link_analysis/pagerank_mg_v32_e32.cu - src/link_analysis/pagerank_mg_v32_e64.cu src/centrality/katz_centrality_sg_v64_e64.cu src/centrality/katz_centrality_sg_v32_e32.cu - src/centrality/katz_centrality_sg_v32_e64.cu src/centrality/katz_centrality_mg_v64_e64.cu src/centrality/katz_centrality_mg_v32_e32.cu - src/centrality/katz_centrality_mg_v32_e64.cu src/centrality/eigenvector_centrality_sg_v64_e64.cu src/centrality/eigenvector_centrality_sg_v32_e32.cu - src/centrality/eigenvector_centrality_sg_v32_e64.cu src/centrality/eigenvector_centrality_mg_v64_e64.cu src/centrality/eigenvector_centrality_mg_v32_e32.cu - src/centrality/eigenvector_centrality_mg_v32_e64.cu src/centrality/betweenness_centrality_sg_v64_e64.cu src/centrality/betweenness_centrality_sg_v32_e32.cu - src/centrality/betweenness_centrality_sg_v32_e64.cu src/centrality/betweenness_centrality_mg_v64_e64.cu src/centrality/betweenness_centrality_mg_v32_e32.cu - src/centrality/betweenness_centrality_mg_v32_e64.cu src/tree/legacy/mst.cu src/from_cugraph_ops/sampling_index.cu src/components/weakly_connected_components_sg_v64_e64.cu src/components/weakly_connected_components_sg_v32_e32.cu - src/components/weakly_connected_components_sg_v32_e64.cu src/components/weakly_connected_components_mg_v64_e64.cu src/components/weakly_connected_components_mg_v32_e32.cu - src/components/weakly_connected_components_mg_v32_e64.cu src/components/mis_sg_v64_e64.cu src/components/mis_sg_v32_e32.cu - src/components/mis_sg_v32_e64.cu src/components/mis_mg_v64_e64.cu src/components/mis_mg_v32_e32.cu - src/components/mis_mg_v32_e64.cu src/components/vertex_coloring_sg_v64_e64.cu src/components/vertex_coloring_sg_v32_e32.cu - src/components/vertex_coloring_sg_v32_e64.cu src/components/vertex_coloring_mg_v64_e64.cu src/components/vertex_coloring_mg_v32_e32.cu - src/components/vertex_coloring_mg_v32_e64.cu src/structure/create_graph_from_edgelist_sg_v64_e64.cu src/structure/create_graph_from_edgelist_sg_v32_e32.cu - src/structure/create_graph_from_edgelist_sg_v32_e64.cu src/structure/create_graph_from_edgelist_mg_v64_e64.cu src/structure/create_graph_from_edgelist_mg_v32_e32.cu - src/structure/create_graph_from_edgelist_mg_v32_e64.cu src/structure/symmetrize_edgelist_sg_v64_e64.cu src/structure/symmetrize_edgelist_sg_v32_e32.cu src/structure/symmetrize_edgelist_mg_v64_e64.cu src/structure/symmetrize_edgelist_mg_v32_e32.cu src/community/triangle_count_sg_v64_e64.cu src/community/triangle_count_sg_v32_e32.cu - src/community/triangle_count_sg_v32_e64.cu src/community/triangle_count_mg_v64_e64.cu src/community/triangle_count_mg_v32_e32.cu - src/community/triangle_count_mg_v32_e64.cu src/community/approx_weighted_matching_sg_v64_e64.cu src/community/approx_weighted_matching_sg_v32_e32.cu - src/community/approx_weighted_matching_sg_v32_e64.cu src/community/approx_weighted_matching_mg_v64_e64.cu src/community/approx_weighted_matching_mg_v32_e32.cu - src/community/approx_weighted_matching_mg_v32_e64.cu src/traversal/k_hop_nbrs_sg_v64_e64.cu src/traversal/k_hop_nbrs_sg_v32_e32.cu - src/traversal/k_hop_nbrs_sg_v32_e64.cu src/traversal/k_hop_nbrs_mg_v64_e64.cu src/traversal/k_hop_nbrs_mg_v32_e32.cu - src/traversal/k_hop_nbrs_mg_v32_e64.cu src/mtmg/vertex_result_sg_v32_e32.cu src/mtmg/vertex_result_sg_v64_e64.cu src/mtmg/vertex_result_mg_v32_e32.cu diff --git a/cpp/include/cugraph/utilities/graph_traits.hpp b/cpp/include/cugraph/utilities/graph_traits.hpp index bd46c9d4fc1..f0ae0b4279d 100644 --- a/cpp/include/cugraph/utilities/graph_traits.hpp +++ b/cpp/include/cugraph/utilities/graph_traits.hpp @@ -44,7 +44,7 @@ template struct is_vertex_edge_combo { static constexpr bool value = is_one_of::value && is_one_of::value && - (sizeof(vertex_t) <= sizeof(edge_t)); + (sizeof(vertex_t) == sizeof(edge_t)); }; // meta-function that constrains diff --git a/cpp/src/c_api/graph_helper_mg.cu b/cpp/src/c_api/graph_helper_mg.cu index 353d3d90de8..704968066ad 100644 --- a/cpp/src/c_api/graph_helper_mg.cu +++ b/cpp/src/c_api/graph_helper_mg.cu @@ -25,12 +25,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, float constant_value); -template edge_property_t, float> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - float constant_value); - template edge_property_t, float> create_constant_edge_property( raft::handle_t const& handle, @@ -42,11 +36,6 @@ create_constant_edge_property(raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, float constant_value); -template edge_property_t, float> -create_constant_edge_property(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - float constant_value); - template edge_property_t, float> create_constant_edge_property(raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, @@ -58,12 +47,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, double constant_value); -template edge_property_t, double> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - double constant_value); - template edge_property_t, double> create_constant_edge_property( raft::handle_t const& handle, @@ -75,11 +58,6 @@ create_constant_edge_property(raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, double constant_value); -template edge_property_t, double> -create_constant_edge_property(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - double constant_value); - template edge_property_t, double> create_constant_edge_property(raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, diff --git a/cpp/src/c_api/graph_helper_sg.cu b/cpp/src/c_api/graph_helper_sg.cu index 86efa0d7bed..5426a2294e1 100644 --- a/cpp/src/c_api/graph_helper_sg.cu +++ b/cpp/src/c_api/graph_helper_sg.cu @@ -48,12 +48,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, float constant_value); -template edge_property_t, float> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - float constant_value); - template edge_property_t, float> create_constant_edge_property( raft::handle_t const& handle, @@ -66,12 +60,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, float constant_value); -template edge_property_t, float> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - float constant_value); - template edge_property_t, float> create_constant_edge_property( raft::handle_t const& handle, @@ -84,12 +72,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, double constant_value); -template edge_property_t, double> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - double constant_value); - template edge_property_t, double> create_constant_edge_property( raft::handle_t const& handle, @@ -102,12 +84,6 @@ create_constant_edge_property( cugraph::graph_view_t const& graph_view, double constant_value); -template edge_property_t, double> -create_constant_edge_property( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - double constant_value); - template edge_property_t, double> create_constant_edge_property( raft::handle_t const& handle, diff --git a/cpp/src/c_api/graph_mg.cpp b/cpp/src/c_api/graph_mg.cpp index 2057448dbe5..c63528a9180 100644 --- a/cpp/src/c_api/graph_mg.cpp +++ b/cpp/src/c_api/graph_mg.cpp @@ -402,6 +402,14 @@ extern "C" cugraph_error_code_t cugraph_graph_create_mg( raft::comms::op_t::SUM, p_handle->handle_->get_stream()); + cugraph_data_type_id_t edge_type{vertex_type}; + + if (vertex_type == cugraph_data_type_id_t::INT32) + CAPI_EXPECTS(num_edges < int32_threshold, + CUGRAPH_INVALID_INPUT, + "Number of edges won't fit in 32-bit integer, using 32-bit type", + *error); + auto vertex_types = cugraph::host_scalar_allgather( p_handle->handle_->get_comms(), static_cast(vertex_type), p_handle->handle_->get_stream()); @@ -434,14 +442,6 @@ extern "C" cugraph_error_code_t cugraph_graph_create_mg( "different weight type used on different GPUs", *error); - cugraph_data_type_id_t edge_type; - - if (num_edges < int32_threshold) { - edge_type = static_cast(vertex_types[0]); - } else { - edge_type = cugraph_data_type_id_t::INT64; - } - if (weight_type == cugraph_data_type_id_t::NTYPES) { weight_type = cugraph_data_type_id_t::FLOAT32; } diff --git a/cpp/src/c_api/graph_sg.cpp b/cpp/src/c_api/graph_sg.cpp index ea598b902ae..e7ebbc2d319 100644 --- a/cpp/src/c_api/graph_sg.cpp +++ b/cpp/src/c_api/graph_sg.cpp @@ -609,14 +609,14 @@ extern "C" cugraph_error_code_t cugraph_graph_create_sg( "Invalid input arguments: src size != weights size.", *error); - cugraph_data_type_id_t edge_type; - cugraph_data_type_id_t weight_type; + if (p_src->type_ == cugraph_data_type_id_t::INT32) + CAPI_EXPECTS(p_src->size_ < int32_threshold, + CUGRAPH_INVALID_INPUT, + "Number of edges won't fit in 32-bit integer, using 32-bit type", + *error); - if (p_src->size_ < int32_threshold) { - edge_type = p_src->type_; - } else { - edge_type = cugraph_data_type_id_t::INT64; - } + cugraph_data_type_id_t edge_type = p_src->type_; + cugraph_data_type_id_t weight_type; if (weights != nullptr) { weight_type = p_weights->type_; diff --git a/cpp/src/centrality/betweenness_centrality_mg_v32_e64.cu b/cpp/src/centrality/betweenness_centrality_mg_v32_e64.cu deleted file mode 100644 index e90ae93b407..00000000000 --- a/cpp/src/centrality/betweenness_centrality_mg_v32_e64.cu +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/betweenness_centrality_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template rmm::device_uvector betweenness_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const include_endpoints, - bool do_expensive_check); - -template rmm::device_uvector betweenness_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const include_endpoints, - bool do_expensive_check); - -template edge_property_t, float> -edge_betweenness_centrality( - const raft::handle_t& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const do_expensive_check); - -template edge_property_t, double> -edge_betweenness_centrality( - const raft::handle_t& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/centrality/betweenness_centrality_sg_v32_e64.cu b/cpp/src/centrality/betweenness_centrality_sg_v32_e64.cu deleted file mode 100644 index 21175047fe9..00000000000 --- a/cpp/src/centrality/betweenness_centrality_sg_v32_e64.cu +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/betweenness_centrality_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template rmm::device_uvector betweenness_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const include_endpoints, - bool do_expensive_check); - -template rmm::device_uvector betweenness_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const include_endpoints, - bool do_expensive_check); - -template edge_property_t, float> -edge_betweenness_centrality( - const raft::handle_t& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const do_expensive_check); - -template edge_property_t, double> -edge_betweenness_centrality( - const raft::handle_t& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - bool const normalized, - bool const do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/centrality/eigenvector_centrality_mg_v32_e64.cu b/cpp/src/centrality/eigenvector_centrality_mg_v32_e64.cu deleted file mode 100644 index 77d74310c8e..00000000000 --- a/cpp/src/centrality/eigenvector_centrality_mg_v32_e64.cu +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/eigenvector_centrality_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template rmm::device_uvector eigenvector_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> initial_centralities, - float epsilon, - size_t max_iterations, - bool do_expensive_check); - -template rmm::device_uvector eigenvector_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> initial_centralities, - double epsilon, - size_t max_iterations, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/centrality/eigenvector_centrality_sg_v32_e64.cu b/cpp/src/centrality/eigenvector_centrality_sg_v32_e64.cu deleted file mode 100644 index c0efa7a59c1..00000000000 --- a/cpp/src/centrality/eigenvector_centrality_sg_v32_e64.cu +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/eigenvector_centrality_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template rmm::device_uvector eigenvector_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> initial_centralities, - float epsilon, - size_t max_iterations, - bool do_expensive_check); - -template rmm::device_uvector eigenvector_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> initial_centralities, - double epsilon, - size_t max_iterations, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/centrality/katz_centrality_mg_v32_e64.cu b/cpp/src/centrality/katz_centrality_mg_v32_e64.cu deleted file mode 100644 index 22efe9b5606..00000000000 --- a/cpp/src/centrality/katz_centrality_mg_v32_e64.cu +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/katz_centrality_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template void katz_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - float const* betas, - float* katz_centralities, - float alpha, - float beta, - float epsilon, - size_t max_iterations, - bool has_initial_guess, - bool normalize, - bool do_expensive_check); - -template void katz_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - double const* betas, - double* katz_centralities, - double alpha, - double beta, - double epsilon, - size_t max_iterations, - bool has_initial_guess, - bool normalize, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/centrality/katz_centrality_sg_v32_e64.cu b/cpp/src/centrality/katz_centrality_sg_v32_e64.cu deleted file mode 100644 index 6d977c14825..00000000000 --- a/cpp/src/centrality/katz_centrality_sg_v32_e64.cu +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "centrality/katz_centrality_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template void katz_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - float const* betas, - float* katz_centralities, - float alpha, - float beta, - float epsilon, - size_t max_iterations, - bool has_initial_guess, - bool normalize, - bool do_expensive_check); - -template void katz_centrality( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - double const* betas, - double* katz_centralities, - double alpha, - double beta, - double epsilon, - size_t max_iterations, - bool has_initial_guess, - bool normalize, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/approx_weighted_matching_mg_v32_e64.cu b/cpp/src/community/approx_weighted_matching_mg_v32_e64.cu deleted file mode 100644 index 431634ab18a..00000000000 --- a/cpp/src/community/approx_weighted_matching_mg_v32_e64.cu +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "approx_weighted_matching_impl.cuh" - -namespace cugraph { - -template std::tuple, float> approximate_weighted_matching( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template std::tuple, double> approximate_weighted_matching( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -} // namespace cugraph diff --git a/cpp/src/community/approx_weighted_matching_sg_v32_e64.cu b/cpp/src/community/approx_weighted_matching_sg_v32_e64.cu deleted file mode 100644 index f9bab14c29f..00000000000 --- a/cpp/src/community/approx_weighted_matching_sg_v32_e64.cu +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "approx_weighted_matching_impl.cuh" - -namespace cugraph { - -template std::tuple, float> approximate_weighted_matching( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template std::tuple, double> approximate_weighted_matching( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -} // namespace cugraph diff --git a/cpp/src/community/detail/common_methods_mg_v32_e64.cu b/cpp/src/community/detail/common_methods_mg_v32_e64.cu deleted file mode 100644 index 3958683b4ad..00000000000 --- a/cpp/src/community/detail/common_methods_mg_v32_e64.cu +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/detail/common_methods.cuh" - -namespace cugraph { -namespace detail { - -template float compute_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - rmm::device_uvector const& next_clusters, - rmm::device_uvector const& cluster_weights, - float total_edge_weight, - float resolution); - -template double compute_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - rmm::device_uvector const& next_clusters, - rmm::device_uvector const& cluster_weights, - double total_edge_weight, - double resolution); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>> -graph_contraction(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weights, - raft::device_span labels); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>> -graph_contraction(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weights, - raft::device_span labels); - -template rmm::device_uvector update_clustering_by_delta_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - float total_edge_weight, - float resolution, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, float> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template rmm::device_uvector update_clustering_by_delta_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - double total_edge_weight, - double resolution, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, double> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template std::tuple, rmm::device_uvector> -compute_cluster_keys_and_values( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - rmm::device_uvector const& next_clusters_v, - edge_src_property_t, int32_t> const& - src_clusters_cache); - -template std::tuple, rmm::device_uvector> -compute_cluster_keys_and_values( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - rmm::device_uvector const& next_clusters_v, - edge_src_property_t, int32_t> const& - src_clusters_cache); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/community/detail/common_methods_sg_v32_e64.cu b/cpp/src/community/detail/common_methods_sg_v32_e64.cu deleted file mode 100644 index 7be4b179fa4..00000000000 --- a/cpp/src/community/detail/common_methods_sg_v32_e64.cu +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/detail/common_methods.cuh" - -namespace cugraph { -namespace detail { - -template float compute_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - rmm::device_uvector const& next_clusters, - rmm::device_uvector const& cluster_weights, - float total_edge_weight, - float resolution); - -template double compute_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - rmm::device_uvector const& next_clusters, - rmm::device_uvector const& cluster_weights, - double total_edge_weight, - double resolution); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>> -graph_contraction(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weights, - raft::device_span labels); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>> -graph_contraction(raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weights, - raft::device_span labels); - -template rmm::device_uvector update_clustering_by_delta_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - float total_edge_weight, - float resolution, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, float> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template rmm::device_uvector update_clustering_by_delta_modularity( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - double total_edge_weight, - double resolution, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, double> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template std::tuple, rmm::device_uvector> -compute_cluster_keys_and_values( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - rmm::device_uvector const& next_clusters_v, - edge_src_property_t, int32_t> const& - src_clusters_cache); - -template std::tuple, rmm::device_uvector> -compute_cluster_keys_and_values( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - rmm::device_uvector const& next_clusters_v, - edge_src_property_t, int32_t> const& - src_clusters_cache); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/community/detail/maximal_independent_moves_mg_v32_e64.cu b/cpp/src/community/detail/maximal_independent_moves_mg_v32_e64.cu deleted file mode 100644 index 596f7fa0a11..00000000000 --- a/cpp/src/community/detail/maximal_independent_moves_mg_v32_e64.cu +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "maximal_independent_moves.cuh" - -namespace cugraph { -namespace detail { - -template rmm::device_uvector maximal_independent_moves( - raft::handle_t const& handle, - graph_view_t const& decision_graph_view, - raft::random::RngState& rng_state); - -} // namespace detail - -} // namespace cugraph diff --git a/cpp/src/community/detail/maximal_independent_moves_sg_v32_e64.cu b/cpp/src/community/detail/maximal_independent_moves_sg_v32_e64.cu deleted file mode 100644 index 2ae195faf01..00000000000 --- a/cpp/src/community/detail/maximal_independent_moves_sg_v32_e64.cu +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "maximal_independent_moves.cuh" - -namespace cugraph { -namespace detail { - -template rmm::device_uvector maximal_independent_moves( - raft::handle_t const& handle, - graph_view_t const& decision_graph_view, - raft::random::RngState& rng_state); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/community/detail/refine_mg_v32_e64.cu b/cpp/src/community/detail/refine_mg_v32_e64.cu deleted file mode 100644 index 16c0fde2991..00000000000 --- a/cpp/src/community/detail/refine_mg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/detail/refine_impl.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - std::pair, rmm::device_uvector>> -refine_clustering( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - float total_edge_weight, - float resolution, - float theta, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, float> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template std::tuple, - std::pair, rmm::device_uvector>> -refine_clustering( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - double total_edge_weight, - double resolution, - double theta, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, double> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/community/detail/refine_sg_v32_e64.cu b/cpp/src/community/detail/refine_sg_v32_e64.cu deleted file mode 100644 index f011bb2ebe0..00000000000 --- a/cpp/src/community/detail/refine_sg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/detail/refine_impl.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - std::pair, rmm::device_uvector>> -refine_clustering( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - float total_edge_weight, - float resolution, - float theta, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, float> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -template std::tuple, - std::pair, rmm::device_uvector>> -refine_clustering( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - double total_edge_weight, - double resolution, - double theta, - rmm::device_uvector const& vertex_weights_v, - rmm::device_uvector&& cluster_keys_v, - rmm::device_uvector&& cluster_weights_v, - rmm::device_uvector&& next_clusters_v, - edge_src_property_t, double> const& - src_vertex_weights_cache, - edge_src_property_t, int32_t> const& - src_clusters_cache, - edge_dst_property_t, int32_t> const& - dst_clusters_cache, - bool up_down); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/community/ecg_mg_v32_e64.cu b/cpp/src/community/ecg_mg_v32_e64.cu deleted file mode 100644 index 6e9dde68dea..00000000000 --- a/cpp/src/community/ecg_mg_v32_e64.cu +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/ecg_impl.cuh" - -namespace cugraph { -template std::tuple, size_t, float> ecg( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - - float min_weight, - size_t ensemble_size, - size_t max_level, - float threshold, - float resolution); - -template std::tuple, size_t, double> ecg( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - - double min_weight, - size_t ensemble_size, - size_t max_level, - double threshold, - double resolution); - -} // namespace cugraph diff --git a/cpp/src/community/ecg_sg_v32_e64.cu b/cpp/src/community/ecg_sg_v32_e64.cu deleted file mode 100644 index 9fa1a2429f1..00000000000 --- a/cpp/src/community/ecg_sg_v32_e64.cu +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/ecg_impl.cuh" - -namespace cugraph { -template std::tuple, size_t, float> ecg( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - - float min_weight, - size_t ensemble_size, - size_t max_level, - float threshold, - float resolution); - -template std::tuple, size_t, double> ecg( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - - double min_weight, - size_t ensemble_size, - size_t max_level, - double threshold, - double resolution); - -} // namespace cugraph diff --git a/cpp/src/community/edge_triangle_count_mg_v32_e64.cu b/cpp/src/community/edge_triangle_count_mg_v32_e64.cu deleted file mode 100644 index adab2d1fede..00000000000 --- a/cpp/src/community/edge_triangle_count_mg_v32_e64.cu +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/edge_triangle_count_impl.cuh" - -namespace cugraph { - -// SG instantiation -template edge_property_t, int64_t> edge_triangle_count( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/edge_triangle_count_sg_v32_e64.cu b/cpp/src/community/edge_triangle_count_sg_v32_e64.cu deleted file mode 100644 index 24a8de868e0..00000000000 --- a/cpp/src/community/edge_triangle_count_sg_v32_e64.cu +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/edge_triangle_count_impl.cuh" - -namespace cugraph { - -// SG instantiation -template edge_property_t, int64_t> edge_triangle_count( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/egonet_mg_v32_e64.cu b/cpp/src/community/egonet_mg_v32_e64.cu deleted file mode 100644 index f9e27b6cf26..00000000000 --- a/cpp/src/community/egonet_mg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/egonet_impl.cuh" - -namespace cugraph { - -// MG FP32 - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> - -extract_ego(raft::handle_t const&, - graph_view_t const&, - std::optional>, - int32_t*, - int32_t, - int32_t); -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - raft::device_span source_vertex, - int32_t radius, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const&, - graph_view_t const&, - std::optional>, - int32_t*, - int32_t, - int32_t); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - raft::device_span source_vertex, - int32_t radius, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/egonet_sg_v32_e64.cu b/cpp/src/community/egonet_sg_v32_e64.cu deleted file mode 100644 index 4bb79120445..00000000000 --- a/cpp/src/community/egonet_sg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "community/egonet_impl.cuh" - -namespace cugraph { - -// SG FP32 - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - int32_t* source_vertex, - int32_t n_subgraphs, - int32_t radius); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - raft::device_span source_vertex, - int32_t radius, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - int32_t* source_vertex, - int32_t n_subgraphs, - int32_t radius); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_ego(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional>, - raft::device_span source_vertex, - int32_t radius, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/k_truss_mg_v32_e64.cu b/cpp/src/community/k_truss_mg_v32_e64.cu deleted file mode 100644 index b07f9382612..00000000000 --- a/cpp/src/community/k_truss_mg_v32_e64.cu +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/k_truss_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_truss(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int64_t k, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_truss(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int64_t k, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/k_truss_sg_v32_e64.cu b/cpp/src/community/k_truss_sg_v32_e64.cu deleted file mode 100644 index 87a86d3f95d..00000000000 --- a/cpp/src/community/k_truss_sg_v32_e64.cu +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/k_truss_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_truss(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int64_t k, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_truss(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int64_t k, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/leiden_mg_v32_e64.cu b/cpp/src/community/leiden_mg_v32_e64.cu deleted file mode 100644 index bce08617feb..00000000000 --- a/cpp/src/community/leiden_mg_v32_e64.cu +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/leiden_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::pair>, float> leiden( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t max_level, - float resolution, - float theta); - -template std::pair>, double> leiden( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t max_level, - double resolution, - double theta); - -template std::pair leiden(raft::handle_t const&, - raft::random::RngState&, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - float, - float); -template std::pair leiden( - raft::handle_t const&, - raft::random::RngState&, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - double, - double); -} // namespace cugraph diff --git a/cpp/src/community/leiden_sg_v32_e64.cu b/cpp/src/community/leiden_sg_v32_e64.cu deleted file mode 100644 index 518b2c0b0e0..00000000000 --- a/cpp/src/community/leiden_sg_v32_e64.cu +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/leiden_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::pair>, float> leiden( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t max_level, - float resolution, - float theta); - -template std::pair>, double> leiden( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t max_level, - double resolution, - double theta); - -template std::pair leiden(raft::handle_t const&, - raft::random::RngState&, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - float, - float); -template std::pair leiden( - raft::handle_t const&, - raft::random::RngState&, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - double, - double); -} // namespace cugraph diff --git a/cpp/src/community/louvain_mg_v32_e64.cu b/cpp/src/community/louvain_mg_v32_e64.cu deleted file mode 100644 index f25102af2a2..00000000000 --- a/cpp/src/community/louvain_mg_v32_e64.cu +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/louvain_impl.cuh" - -namespace cugraph { - -// Explicit template instantations - -template std::pair>, float> louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - size_t, - float, - float); -template std::pair>, double> louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - size_t, - double, - double); -template std::pair louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - float, - float); -template std::pair louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - double, - double); -} // namespace cugraph diff --git a/cpp/src/community/louvain_sg_v32_e64.cu b/cpp/src/community/louvain_sg_v32_e64.cu deleted file mode 100644 index fc972bdee61..00000000000 --- a/cpp/src/community/louvain_sg_v32_e64.cu +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/louvain_impl.cuh" - -namespace cugraph { - -// Explicit template instantations - -template std::pair>, float> louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - size_t, - float, - float); -template std::pair>, double> louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - size_t, - double, - double); -template std::pair louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - float, - float); -template std::pair louvain( - raft::handle_t const&, - std::optional>, - graph_view_t const&, - std::optional>, - int32_t*, - size_t, - double, - double); -} // namespace cugraph diff --git a/cpp/src/community/triangle_count_mg_v32_e64.cu b/cpp/src/community/triangle_count_mg_v32_e64.cu deleted file mode 100644 index 50ccc074f63..00000000000 --- a/cpp/src/community/triangle_count_mg_v32_e64.cu +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/triangle_count_impl.cuh" - -namespace cugraph { - -template void triangle_count(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> vertices, - raft::device_span counts, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/community/triangle_count_sg_v32_e64.cu b/cpp/src/community/triangle_count_sg_v32_e64.cu deleted file mode 100644 index 6f7c3a87298..00000000000 --- a/cpp/src/community/triangle_count_sg_v32_e64.cu +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "community/triangle_count_impl.cuh" - -namespace cugraph { - -template void triangle_count(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> vertices, - raft::device_span counts, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/components/mis_mg_v32_e64.cu b/cpp/src/components/mis_mg_v32_e64.cu deleted file mode 100644 index 793d170b433..00000000000 --- a/cpp/src/components/mis_mg_v32_e64.cu +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "mis_impl.cuh" -namespace cugraph { - -template rmm::device_uvector maximal_independent_set( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::random::RngState& rng_state); - -} // namespace cugraph diff --git a/cpp/src/components/mis_sg_v32_e64.cu b/cpp/src/components/mis_sg_v32_e64.cu deleted file mode 100644 index a3a29cb751b..00000000000 --- a/cpp/src/components/mis_sg_v32_e64.cu +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "mis_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector maximal_independent_set( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::random::RngState& rng_state); - -} // namespace cugraph diff --git a/cpp/src/components/vertex_coloring_mg_v32_e64.cu b/cpp/src/components/vertex_coloring_mg_v32_e64.cu deleted file mode 100644 index 04facb7cda8..00000000000 --- a/cpp/src/components/vertex_coloring_mg_v32_e64.cu +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "vertex_coloring_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector vertex_coloring( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::random::RngState& rng_state); - -} // namespace cugraph diff --git a/cpp/src/components/vertex_coloring_sg_v32_e64.cu b/cpp/src/components/vertex_coloring_sg_v32_e64.cu deleted file mode 100644 index bf510a1f571..00000000000 --- a/cpp/src/components/vertex_coloring_sg_v32_e64.cu +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "vertex_coloring_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector vertex_coloring( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::random::RngState& rng_state); - -} // namespace cugraph diff --git a/cpp/src/components/weakly_connected_components_mg_v32_e64.cu b/cpp/src/components/weakly_connected_components_mg_v32_e64.cu deleted file mode 100644 index edbc8cc5eb5..00000000000 --- a/cpp/src/components/weakly_connected_components_mg_v32_e64.cu +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "components/weakly_connected_components_impl.cuh" - -namespace cugraph { - -// MG instantiations - -template void weakly_connected_components( - raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t* components, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/components/weakly_connected_components_sg_v32_e64.cu b/cpp/src/components/weakly_connected_components_sg_v32_e64.cu deleted file mode 100644 index 813d2711674..00000000000 --- a/cpp/src/components/weakly_connected_components_sg_v32_e64.cu +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "components/weakly_connected_components_impl.cuh" - -namespace cugraph { - -// SG instantiations - -template void weakly_connected_components( - raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t* components, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/cores/core_number_mg_v32_e64.cu b/cpp/src/cores/core_number_mg_v32_e64.cu deleted file mode 100644 index f41f9cd8a17..00000000000 --- a/cpp/src/cores/core_number_mg_v32_e64.cu +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cores/core_number_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template void core_number(raft::handle_t const& handle, - graph_view_t const& graph_view, - int64_t* core_numbers, - k_core_degree_type_t degree_type, - size_t k_first, - size_t k_last, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/cores/core_number_sg_v32_e64.cu b/cpp/src/cores/core_number_sg_v32_e64.cu deleted file mode 100644 index 63a6d0069a3..00000000000 --- a/cpp/src/cores/core_number_sg_v32_e64.cu +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cores/core_number_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template void core_number(raft::handle_t const& handle, - graph_view_t const& graph_view, - int64_t* core_numbers, - k_core_degree_type_t degree_type, - size_t k_first, - size_t k_last, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/cores/k_core_mg_v32_e64.cu b/cpp/src/cores/k_core_mg_v32_e64.cu deleted file mode 100644 index b2329997cb6..00000000000 --- a/cpp/src/cores/k_core_mg_v32_e64.cu +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cores/k_core_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_core(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t k, - std::optional degree_type, - std::optional> core_numbers, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_core(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t k, - std::optional degree_type, - std::optional> core_numbers, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/cores/k_core_sg_v32_e64.cu b/cpp/src/cores/k_core_sg_v32_e64.cu deleted file mode 100644 index 642f6673168..00000000000 --- a/cpp/src/cores/k_core_sg_v32_e64.cu +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cores/k_core_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_core(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t k, - std::optional degree_type, - std::optional> core_numbers, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>> -k_core(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - size_t k, - std::optional degree_type, - std::optional> core_numbers, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/detail/collect_local_vertex_values_mg_v32_e32.cu b/cpp/src/detail/collect_local_vertex_values_mg_v32_e32.cu index 3b6059a0ce8..c0c186e867e 100644 --- a/cpp/src/detail/collect_local_vertex_values_mg_v32_e32.cu +++ b/cpp/src/detail/collect_local_vertex_values_mg_v32_e32.cu @@ -15,7 +15,6 @@ */ #include "detail/collect_local_vertex_values.cuh" -#include "detail/graph_partition_utils.cuh" #include #include @@ -47,5 +46,16 @@ collect_local_vertex_values_from_ext_vertex_value_pairs( int32_t default_value, bool do_expensive_check); +template rmm::device_uvector +collect_local_vertex_values_from_ext_vertex_value_pairs( + raft::handle_t const& handle, + rmm::device_uvector&& d_vertices, + rmm::device_uvector&& d_values, + rmm::device_uvector const& number_map, + int32_t local_vertex_first, + int32_t local_vertex_last, + double default_value, + bool do_expensive_check); + } // namespace detail } // namespace cugraph diff --git a/cpp/src/detail/collect_local_vertex_values_mg_v32_e64.cu b/cpp/src/detail/collect_local_vertex_values_mg_v32_e64.cu deleted file mode 100644 index e33b6fe7a68..00000000000 --- a/cpp/src/detail/collect_local_vertex_values_mg_v32_e64.cu +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "detail/collect_local_vertex_values.cuh" -#include "detail/graph_partition_utils.cuh" - -#include -#include - -#include - -namespace cugraph { -namespace detail { - -template rmm::device_uvector -collect_local_vertex_values_from_ext_vertex_value_pairs( - raft::handle_t const& handle, - rmm::device_uvector&& d_vertices, - rmm::device_uvector&& d_values, - rmm::device_uvector const& number_map, - int32_t local_vertex_first, - int32_t local_vertex_last, - double default_value, - bool do_expensive_check); - -template rmm::device_uvector -collect_local_vertex_values_from_ext_vertex_value_pairs( - raft::handle_t const& handle, - rmm::device_uvector&& d_vertices, - rmm::device_uvector&& d_values, - rmm::device_uvector const& number_map, - int32_t local_vertex_first, - int32_t local_vertex_last, - int64_t default_value, - bool do_expensive_check); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/detail/collect_local_vertex_values_sg_v32_e32.cu b/cpp/src/detail/collect_local_vertex_values_sg_v32_e32.cu index c544d009aef..ac4c612b74a 100644 --- a/cpp/src/detail/collect_local_vertex_values_sg_v32_e32.cu +++ b/cpp/src/detail/collect_local_vertex_values_sg_v32_e32.cu @@ -15,7 +15,6 @@ */ #include "detail/collect_local_vertex_values.cuh" -#include "detail/graph_partition_utils.cuh" #include #include @@ -47,5 +46,16 @@ collect_local_vertex_values_from_ext_vertex_value_pairs int32_t default_value, bool do_expensive_check); +template rmm::device_uvector +collect_local_vertex_values_from_ext_vertex_value_pairs( + raft::handle_t const& handle, + rmm::device_uvector&& d_vertices, + rmm::device_uvector&& d_values, + rmm::device_uvector const& number_map, + int32_t local_vertex_first, + int32_t local_vertex_last, + double default_value, + bool do_expensive_check); + } // namespace detail } // namespace cugraph diff --git a/cpp/src/detail/collect_local_vertex_values_sg_v32_e64.cu b/cpp/src/detail/collect_local_vertex_values_sg_v32_e64.cu deleted file mode 100644 index 99817a70e61..00000000000 --- a/cpp/src/detail/collect_local_vertex_values_sg_v32_e64.cu +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "detail/collect_local_vertex_values.cuh" -#include "detail/graph_partition_utils.cuh" - -#include -#include - -#include - -namespace cugraph { -namespace detail { - -template rmm::device_uvector -collect_local_vertex_values_from_ext_vertex_value_pairs( - raft::handle_t const& handle, - rmm::device_uvector&& d_vertices, - rmm::device_uvector&& d_values, - rmm::device_uvector const& number_map, - int32_t local_vertex_first, - int32_t local_vertex_last, - double default_value, - bool do_expensive_check); - -template rmm::device_uvector -collect_local_vertex_values_from_ext_vertex_value_pairs( - raft::handle_t const& handle, - rmm::device_uvector&& d_vertices, - rmm::device_uvector&& d_values, - rmm::device_uvector const& number_map, - int32_t local_vertex_first, - int32_t local_vertex_last, - int64_t default_value, - bool do_expensive_check); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/detail/groupby_and_count_mg_v32_e64.cu b/cpp/src/detail/groupby_and_count_mg_v32_e64.cu deleted file mode 100644 index ec9cbc89aa2..00000000000 --- a/cpp/src/detail/groupby_and_count_mg_v32_e64.cu +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "detail/graph_partition_utils.cuh" -#include "detail/groupby_and_count.cuh" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -namespace cugraph { -namespace detail { -template rmm::device_uvector groupby_and_count_edgelist_by_local_partition_id( - raft::handle_t const& handle, - rmm::device_uvector& d_edgelist_majors, - rmm::device_uvector& d_edgelist_minors, - std::optional>& d_edgelist_weights, - std::optional>& d_edgelist_edge_ids, - std::optional>& d_edgelist_edge_types, - bool groupby_and_counts_local_partition); - -template rmm::device_uvector groupby_and_count_edgelist_by_local_partition_id( - raft::handle_t const& handle, - rmm::device_uvector& d_edgelist_majors, - rmm::device_uvector& d_edgelist_minors, - std::optional>& d_edgelist_weights, - std::optional>& d_edgelist_edge_ids, - std::optional>& d_edgelist_edge_types, - bool groupby_and_counts_local_partition); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/link_analysis/hits_mg_v32_e64.cu b/cpp/src/link_analysis/hits_mg_v32_e64.cu deleted file mode 100644 index a0a99f61822..00000000000 --- a/cpp/src/link_analysis/hits_mg_v32_e64.cu +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "link_analysis/hits_impl.cuh" - -namespace cugraph { - -// MG instantiation -template std::tuple hits( - raft::handle_t const& handle, - graph_view_t const& graph_view, - float* const hubs, - float* const authorities, - float epsilon, - size_t max_iterations, - bool has_initial_hubs_guess, - bool normalize, - bool do_expensive_check); - -template std::tuple hits( - raft::handle_t const& handle, - graph_view_t const& graph_view, - double* const hubs, - double* const authorities, - double epsilon, - size_t max_iterations, - bool has_initial_hubs_guess, - bool normalize, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_analysis/hits_sg_v32_e64.cu b/cpp/src/link_analysis/hits_sg_v32_e64.cu deleted file mode 100644 index 10eb4579f15..00000000000 --- a/cpp/src/link_analysis/hits_sg_v32_e64.cu +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "link_analysis/hits_impl.cuh" - -namespace cugraph { - -// SG instantiation -template std::tuple hits( - raft::handle_t const& handle, - graph_view_t const& graph_view, - float* const hubs, - float* const authorities, - float epsilon, - size_t max_iterations, - bool has_initial_hubs_guess, - bool normalize, - bool do_expensive_check); - -template std::tuple hits( - raft::handle_t const& handle, - graph_view_t const& graph_view, - double* const hubs, - double* const authorities, - double epsilon, - size_t max_iterations, - bool has_initial_hubs_guess, - bool normalize, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_analysis/pagerank_mg_v32_e64.cu b/cpp/src/link_analysis/pagerank_mg_v32_e64.cu deleted file mode 100644 index c3a3b1b6f52..00000000000 --- a/cpp/src/link_analysis/pagerank_mg_v32_e64.cu +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_analysis/pagerank_impl.cuh" - -namespace cugraph { - -// MG instantiation -template void pagerank(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional precomputed_vertex_out_weight_sums, - std::optional personalization_vertices, - std::optional personalization_values, - std::optional personalization_vector_size, - float* pageranks, - float alpha, - float epsilon, - size_t max_iterations, - bool has_initial_guess, - bool do_expensive_check); - -template void pagerank(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional precomputed_vertex_out_weight_sums, - std::optional personalization_vertices, - std::optional personalization_values, - std::optional personalization_vector_size, - double* pageranks, - double alpha, - double epsilon, - size_t max_iterations, - bool has_initial_guess, - bool do_expensive_check); - -template std::tuple, centrality_algorithm_metadata_t> pagerank( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> precomputed_vertex_out_weight_sums, - std::optional, raft::device_span>> - personalization, - std::optional> initial_pageranks, - float alpha, - float epsilon, - size_t max_iterations, - bool do_expensive_check); - -template std::tuple, centrality_algorithm_metadata_t> pagerank( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> precomputed_vertex_out_weight_sums, - std::optional, raft::device_span>> - personalization, - std::optional> initial_pageranks, - double alpha, - double epsilon, - size_t max_iterations, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_analysis/pagerank_sg_v32_e64.cu b/cpp/src/link_analysis/pagerank_sg_v32_e64.cu deleted file mode 100644 index 0e515a55024..00000000000 --- a/cpp/src/link_analysis/pagerank_sg_v32_e64.cu +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_analysis/pagerank_impl.cuh" - -namespace cugraph { - -// SG instantiation -template void pagerank(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional precomputed_vertex_out_weight_sums, - std::optional personalization_vertices, - std::optional personalization_values, - std::optional personalization_vector_size, - float* pageranks, - float alpha, - float epsilon, - size_t max_iterations, - bool has_initial_guess, - bool do_expensive_check); - -template void pagerank(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional precomputed_vertex_out_weight_sums, - std::optional personalization_vertices, - std::optional personalization_values, - std::optional personalization_vector_size, - double* pageranks, - double alpha, - double epsilon, - size_t max_iterations, - bool has_initial_guess, - bool do_expensive_check); - -template std::tuple, centrality_algorithm_metadata_t> pagerank( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> precomputed_vertex_out_weight_sums, - std::optional, raft::device_span>> - personalization, - std::optional> initial_pageranks, - float alpha, - float epsilon, - size_t max_iterations, - bool do_expensive_check); - -template std::tuple, centrality_algorithm_metadata_t> pagerank( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> precomputed_vertex_out_weight_sums, - std::optional, raft::device_span>> - personalization, - std::optional> initial_pageranks, - double alpha, - double epsilon, - size_t max_iterations, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/cosine_mg_v32_e64.cu b/cpp/src/link_prediction/cosine_mg_v32_e64.cu deleted file mode 100644 index c6efa6b8c30..00000000000 --- a/cpp/src/link_prediction/cosine_mg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/cosine_similarity_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector cosine_similarity_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector cosine_similarity_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - cosine_similarity_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - cosine_similarity_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/cosine_sg_v32_e64.cu b/cpp/src/link_prediction/cosine_sg_v32_e64.cu deleted file mode 100644 index 80a3b31faab..00000000000 --- a/cpp/src/link_prediction/cosine_sg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/cosine_similarity_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector cosine_similarity_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector cosine_similarity_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - cosine_similarity_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - cosine_similarity_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/jaccard_mg_v32_e64.cu b/cpp/src/link_prediction/jaccard_mg_v32_e64.cu deleted file mode 100644 index a359458a3c0..00000000000 --- a/cpp/src/link_prediction/jaccard_mg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/jaccard_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector jaccard_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector jaccard_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - jaccard_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - jaccard_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/jaccard_sg_v32_e64.cu b/cpp/src/link_prediction/jaccard_sg_v32_e64.cu deleted file mode 100644 index eaa01f13c5e..00000000000 --- a/cpp/src/link_prediction/jaccard_sg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/jaccard_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector jaccard_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector jaccard_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - jaccard_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - jaccard_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/overlap_mg_v32_e64.cu b/cpp/src/link_prediction/overlap_mg_v32_e64.cu deleted file mode 100644 index c493a50a5db..00000000000 --- a/cpp/src/link_prediction/overlap_mg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/overlap_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector overlap_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector overlap_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - overlap_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - overlap_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/overlap_sg_v32_e64.cu b/cpp/src/link_prediction/overlap_sg_v32_e64.cu deleted file mode 100644 index 31adbcf0238..00000000000 --- a/cpp/src/link_prediction/overlap_sg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/overlap_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector overlap_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector overlap_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - overlap_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - overlap_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/sorensen_mg_v32_e64.cu b/cpp/src/link_prediction/sorensen_mg_v32_e64.cu deleted file mode 100644 index 9c5f5cc9716..00000000000 --- a/cpp/src/link_prediction/sorensen_mg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/sorensen_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector sorensen_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector sorensen_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - sorensen_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - sorensen_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/link_prediction/sorensen_sg_v32_e64.cu b/cpp/src/link_prediction/sorensen_sg_v32_e64.cu deleted file mode 100644 index 20cd5e621e2..00000000000 --- a/cpp/src/link_prediction/sorensen_sg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "link_prediction/sorensen_impl.cuh" - -namespace cugraph { - -template rmm::device_uvector sorensen_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template rmm::device_uvector sorensen_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::tuple, raft::device_span> vertex_pairs, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - sorensen_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - sorensen_all_pairs_coefficients( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> vertices, - std::optional topk, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/lookup/lookup_src_dst_mg_v32_e64.cu b/cpp/src/lookup/lookup_src_dst_mg_v32_e64.cu deleted file mode 100644 index 4e120f49f10..00000000000 --- a/cpp/src/lookup/lookup_src_dst_mg_v32_e64.cu +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "lookup/lookup_src_dst_impl.cuh" - -namespace cugraph { - -template class lookup_container_t; - -template lookup_container_t build_edge_id_and_type_to_src_dst_lookup_map( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_id_view, - edge_property_view_t edge_type_view); - -template std::tuple, rmm::device_uvector> -lookup_endpoints_from_edge_ids_and_single_type( - raft::handle_t const& handle, - lookup_container_t const& search_container, - raft::device_span edge_ids_to_lookup, - int32_t edge_type_to_lookup); - -template std::tuple, rmm::device_uvector> -lookup_endpoints_from_edge_ids_and_types( - raft::handle_t const& handle, - lookup_container_t const& search_container, - raft::device_span edge_ids_to_lookup, - raft::device_span edge_types_to_lookup); - -} // namespace cugraph diff --git a/cpp/src/lookup/lookup_src_dst_sg_v32_e64.cu b/cpp/src/lookup/lookup_src_dst_sg_v32_e64.cu deleted file mode 100644 index 46b62e05ed8..00000000000 --- a/cpp/src/lookup/lookup_src_dst_sg_v32_e64.cu +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "lookup/lookup_src_dst_impl.cuh" - -namespace cugraph { - -template class lookup_container_t; - -template lookup_container_t build_edge_id_and_type_to_src_dst_lookup_map( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_id_view, - edge_property_view_t edge_type_view); - -template std::tuple, rmm::device_uvector> -lookup_endpoints_from_edge_ids_and_single_type( - raft::handle_t const& handle, - lookup_container_t const& search_container, - raft::device_span edge_ids_to_lookup, - int32_t edge_type_to_lookup); - -template std::tuple, rmm::device_uvector> -lookup_endpoints_from_edge_ids_and_types( - raft::handle_t const& handle, - lookup_container_t const& search_container, - raft::device_span edge_ids_to_lookup, - raft::device_span edge_types_to_lookup); - -} // namespace cugraph diff --git a/cpp/src/sampling/detail/check_edge_bias_values_mg_v32_e64.cu b/cpp/src/sampling/detail/check_edge_bias_values_mg_v32_e64.cu deleted file mode 100644 index b8b3564fee7..00000000000 --- a/cpp/src/sampling/detail/check_edge_bias_values_mg_v32_e64.cu +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/check_edge_bias_values.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple check_edge_bias_values( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_bias_view); - -template std::tuple check_edge_bias_values( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_bias_view); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/check_edge_bias_values_sg_v32_e64.cu b/cpp/src/sampling/detail/check_edge_bias_values_sg_v32_e64.cu deleted file mode 100644 index c8c28a5ad04..00000000000 --- a/cpp/src/sampling/detail/check_edge_bias_values_sg_v32_e64.cu +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/check_edge_bias_values.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple check_edge_bias_values( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_bias_view); - -template std::tuple check_edge_bias_values( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_bias_view); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/gather_one_hop_edgelist_mg_v32_e64.cu b/cpp/src/sampling/detail/gather_one_hop_edgelist_mg_v32_e64.cu deleted file mode 100644 index d7dd08b4cd5..00000000000 --- a/cpp/src/sampling/detail/gather_one_hop_edgelist_mg_v32_e64.cu +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/gather_one_hop_edgelist_impl.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -gather_one_hop_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - raft::device_span active_majors, - std::optional> active_major_labels, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -gather_one_hop_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - raft::device_span active_majors, - std::optional> active_major_labels, - bool do_expensive_check); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/gather_one_hop_edgelist_sg_v32_e64.cu b/cpp/src/sampling/detail/gather_one_hop_edgelist_sg_v32_e64.cu deleted file mode 100644 index 2a151b4d4e6..00000000000 --- a/cpp/src/sampling/detail/gather_one_hop_edgelist_sg_v32_e64.cu +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/gather_one_hop_edgelist_impl.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -gather_one_hop_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - raft::device_span active_majors, - std::optional> active_major_labels, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -gather_one_hop_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - raft::device_span active_majors, - std::optional> active_major_labels, - bool do_expensive_check); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/sample_edges_mg_v32_e64.cu b/cpp/src/sampling/detail/sample_edges_mg_v32_e64.cu deleted file mode 100644 index 4628840499f..00000000000 --- a/cpp/src/sampling/detail/sample_edges_mg_v32_e64.cu +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/sample_edges.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sample_edges(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - std::optional> edge_bias_view, - raft::random::RngState& rng_state, - raft::device_span active_majors, - std::optional> active_major_labels, - size_t fanout, - bool with_replacement); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sample_edges(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - std::optional> edge_bias_view, - raft::random::RngState& rng_state, - raft::device_span active_majors, - std::optional> active_major_labels, - size_t fanout, - bool with_replacement); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/sample_edges_sg_v32_e64.cu b/cpp/src/sampling/detail/sample_edges_sg_v32_e64.cu deleted file mode 100644 index b1d664782f2..00000000000 --- a/cpp/src/sampling/detail/sample_edges_sg_v32_e64.cu +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/sample_edges.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sample_edges(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - std::optional> edge_bias_view, - raft::random::RngState& rng_state, - raft::device_span active_majors, - std::optional> active_major_labels, - size_t fanout, - bool with_replacement); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sample_edges(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_edge_type_view, - std::optional> edge_bias_view, - raft::random::RngState& rng_state, - raft::device_span active_majors, - std::optional> active_major_labels, - size_t fanout, - bool with_replacement); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/detail/shuffle_and_organize_output_mg_v32_e64.cu b/cpp/src/sampling/detail/shuffle_and_organize_output_mg_v32_e64.cu deleted file mode 100644 index 50fc3910aca..00000000000 --- a/cpp/src/sampling/detail/shuffle_and_organize_output_mg_v32_e64.cu +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/detail/shuffle_and_organize_output_impl.cuh" - -namespace cugraph { -namespace detail { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -shuffle_and_organize_output( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types, - std::optional>&& hops, - std::optional>&& labels, - std::optional, raft::device_span>> - label_to_output_comm_rank); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -shuffle_and_organize_output( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types, - std::optional>&& hops, - std::optional>&& labels, - std::optional, raft::device_span>> - label_to_output_comm_rank); - -} // namespace detail -} // namespace cugraph diff --git a/cpp/src/sampling/negative_sampling_mg_v32_e64.cu b/cpp/src/sampling/negative_sampling_mg_v32_e64.cu deleted file mode 100644 index af4c28c0f1a..00000000000 --- a/cpp/src/sampling/negative_sampling_mg_v32_e64.cu +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "negative_sampling_impl.cuh" - -#include -#include - -namespace cugraph { - -template std::tuple, rmm::device_uvector> negative_sampling( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> src_bias, - std::optional> dst_bias, - size_t num_samples, - bool remove_duplicates, - bool remove_existing_edges, - bool exact_number_of_samples, - bool do_expensive_check); - -template std::tuple, rmm::device_uvector> negative_sampling( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> src_bias, - std::optional> dst_bias, - size_t num_samples, - bool remove_duplicates, - bool remove_existing_edges, - bool exact_number_of_samples, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/sampling/negative_sampling_sg_v32_e64.cu b/cpp/src/sampling/negative_sampling_sg_v32_e64.cu deleted file mode 100644 index c66c31a4258..00000000000 --- a/cpp/src/sampling/negative_sampling_sg_v32_e64.cu +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "negative_sampling_impl.cuh" - -#include -#include - -namespace cugraph { - -template std::tuple, rmm::device_uvector> negative_sampling( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> src_bias, - std::optional> dst_bias, - size_t num_samples, - bool remove_duplicates, - bool remove_existing_edges, - bool exact_number_of_samples, - bool do_expensive_check); - -template std::tuple, rmm::device_uvector> negative_sampling( - raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> src_bias, - std::optional> dst_bias, - size_t num_samples, - bool remove_duplicates, - bool remove_existing_edges, - bool exact_number_of_samples, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/sampling/neighbor_sampling_mg_v32_e64.cpp b/cpp/src/sampling/neighbor_sampling_mg_v32_e64.cpp deleted file mode 100644 index c37b353ae1c..00000000000 --- a/cpp/src/sampling/neighbor_sampling_mg_v32_e64.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "neighbor_sampling_impl.hpp" - -#include -#include - -namespace cugraph { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -uniform_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -uniform_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -biased_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - edge_property_view_t edge_bias_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -biased_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - edge_property_view_t edge_bias_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/sampling/neighbor_sampling_sg_v32_e64.cpp b/cpp/src/sampling/neighbor_sampling_sg_v32_e64.cpp deleted file mode 100644 index 7ab0a8782ec..00000000000 --- a/cpp/src/sampling/neighbor_sampling_sg_v32_e64.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "neighbor_sampling_impl.hpp" - -#include -#include - -namespace cugraph { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -uniform_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -uniform_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -biased_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - edge_property_view_t edge_bias_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -biased_neighbor_sample( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - edge_property_view_t edge_bias_view, - raft::device_span starting_vertices, - std::optional> starting_vertex_labels, - std::optional, raft::device_span>> - label_to_output_comm_rank, - raft::host_span fan_out, - raft::random::RngState& rng_state, - bool return_hops, - bool with_replacement, - prior_sources_behavior_t prior_sources_behavior, - bool dedupe_sources, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/sampling/random_walks_mg_v32_e64.cu b/cpp/src/sampling/random_walks_mg_v32_e64.cu deleted file mode 100644 index b1bf1a19b77..00000000000 --- a/cpp/src/sampling/random_walks_mg_v32_e64.cu +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/random_walks_impl.cuh" - -#include - -namespace cugraph { - -template std::tuple, std::optional>> -uniform_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -uniform_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -biased_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -biased_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -node2vec_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length, - float p, - float q); - -template std::tuple, std::optional>> -node2vec_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length, - double p, - double q); - -} // namespace cugraph diff --git a/cpp/src/sampling/random_walks_old_sg.cu b/cpp/src/sampling/random_walks_old_sg.cu index 0e612163715..d5079231f51 100644 --- a/cpp/src/sampling/random_walks_old_sg.cu +++ b/cpp/src/sampling/random_walks_old_sg.cu @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, NVIDIA CORPORATION. + * Copyright (c) 2021-2024, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,17 +33,6 @@ template std:: bool use_padding, std::unique_ptr sampling_strategy); -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - random_walks(raft::handle_t const& handle, - graph_view_t const& gview, - std::optional> edge_weight_view, - int32_t const* ptr_d_start, - int64_t num_paths, - int64_t max_depth, - bool use_padding, - std::unique_ptr sampling_strategy); - template std:: tuple, rmm::device_uvector, rmm::device_uvector> random_walks(raft::handle_t const& handle, @@ -68,17 +57,6 @@ template std:: bool use_padding, std::unique_ptr sampling_strategy); -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - random_walks(raft::handle_t const& handle, - graph_view_t const& gview, - std::optional> edge_weight_view, - int32_t const* ptr_d_start, - int64_t num_paths, - int64_t max_depth, - bool use_padding, - std::unique_ptr sampling_strategy); - template std:: tuple, rmm::device_uvector, rmm::device_uvector> random_walks(raft::handle_t const& handle, diff --git a/cpp/src/sampling/random_walks_old_sg_v32_e64.cu b/cpp/src/sampling/random_walks_old_sg_v32_e64.cu deleted file mode 100644 index 7dfbf964587..00000000000 --- a/cpp/src/sampling/random_walks_old_sg_v32_e64.cu +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Andrei Schaffer, aschaffer@nvidia.com -// -#include "random_walks.cuh" - -namespace cugraph { -// template explicit instantiation directives (EIDir's): - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - random_walks(raft::handle_t const& handle, - graph_view_t const& gview, - std::optional> edge_weight_view, - int32_t const* ptr_d_start, - int64_t num_paths, - int64_t max_depth, - bool use_padding, - std::unique_ptr sampling_strategy); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - random_walks(raft::handle_t const& handle, - graph_view_t const& gview, - std::optional> edge_weight_view, - int32_t const* ptr_d_start, - int64_t num_paths, - int64_t max_depth, - bool use_padding, - std::unique_ptr sampling_strategy); - -template std:: - tuple, rmm::device_uvector, rmm::device_uvector> - convert_paths_to_coo(raft::handle_t const& handle, - int64_t coalesced_sz_v, - int64_t num_paths, - rmm::device_buffer&& d_coalesced_v, - rmm::device_buffer&& d_sizes); - -} // namespace cugraph diff --git a/cpp/src/sampling/random_walks_sg_v32_e64.cu b/cpp/src/sampling/random_walks_sg_v32_e64.cu deleted file mode 100644 index 98d2bb02d88..00000000000 --- a/cpp/src/sampling/random_walks_sg_v32_e64.cu +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling/random_walks_impl.cuh" - -#include - -namespace cugraph { - -template std::tuple, std::optional>> -uniform_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -uniform_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -biased_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -biased_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span start_vertices, - size_t max_length); - -template std::tuple, std::optional>> -node2vec_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length, - float p, - float q); - -template std::tuple, std::optional>> -node2vec_random_walks(raft::handle_t const& handle, - raft::random::RngState& rng_state, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span start_vertices, - size_t max_length, - double p, - double q); - -} // namespace cugraph diff --git a/cpp/src/sampling/sampling_post_processing_sg_v32_e64.cu b/cpp/src/sampling/sampling_post_processing_sg_v32_e64.cu deleted file mode 100644 index 7001dcfdaf3..00000000000 --- a/cpp/src/sampling/sampling_post_processing_sg_v32_e64.cu +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sampling_post_processing_impl.cuh" - -#include - -namespace cugraph { - -template std::tuple>, - rmm::device_uvector, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - std::optional>> -renumber_and_compress_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool compress_per_hop, - bool doubly_compress, - bool do_expensive_check); - -template std::tuple>, - rmm::device_uvector, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - std::optional>> -renumber_and_compress_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool compress_per_hop, - bool doubly_compress, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - std::optional>> -renumber_and_sort_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - std::optional>> -renumber_and_sort_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - rmm::device_uvector, - std::optional>, - std::optional>> -heterogeneous_renumber_and_sort_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - raft::device_span vertex_type_offsets, - size_t num_labels, - size_t num_hops, - size_t num_vertex_types, - size_t num_edge_types, - bool src_is_major, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - rmm::device_uvector, - rmm::device_uvector, - std::optional>, - std::optional>> -heterogeneous_renumber_and_sort_sampled_edgelist( - raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> seed_vertices, - std::optional> seed_vertex_label_offsets, - std::optional> edgelist_label_offsets, - raft::device_span vertex_type_offsets, - size_t num_labels, - size_t num_hops, - size_t num_vertex_types, - size_t num_edge_types, - bool src_is_major, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sort_sampled_edgelist(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::optional>> -sort_sampled_edgelist(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - std::optional>&& edgelist_hops, - std::optional> edgelist_label_offsets, - size_t num_labels, - size_t num_hops, - bool src_is_major, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/coarsen_graph_mg_v32_e64.cu b/cpp/src/structure/coarsen_graph_mg_v32_e64.cu deleted file mode 100644 index c41d1071304..00000000000 --- a/cpp/src/structure/coarsen_graph_mg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/coarsen_graph_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/coarsen_graph_sg_v32_e64.cu b/cpp/src/structure/coarsen_graph_sg_v32_e64.cu deleted file mode 100644 index 317bd5a3588..00000000000 --- a/cpp/src/structure/coarsen_graph_sg_v32_e64.cu +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/coarsen_graph_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -coarsen_graph(raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - int32_t const* labels, - bool renumber, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/create_graph_from_edgelist_mg_v32_e64.cu b/cpp/src/structure/create_graph_from_edgelist_mg_v32_e64.cu deleted file mode 100644 index 380d3474292..00000000000 --- a/cpp/src/structure/create_graph_from_edgelist_mg_v32_e64.cu +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/create_graph_from_edgelist_impl.cuh" - -namespace cugraph { - -// explicit instantiations - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); -} // namespace cugraph diff --git a/cpp/src/structure/create_graph_from_edgelist_sg_v32_e64.cu b/cpp/src/structure/create_graph_from_edgelist_sg_v32_e64.cu deleted file mode 100644 index 71bd74c1a44..00000000000 --- a/cpp/src/structure/create_graph_from_edgelist_sg_v32_e64.cu +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/create_graph_from_edgelist_impl.cuh" - -namespace cugraph { - -// explicit instantiations - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, float>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -template std::tuple< - cugraph::graph_t, - std::optional< - cugraph::edge_property_t, double>>, - std::optional< - cugraph::edge_property_t, int64_t>>, - std::optional< - cugraph::edge_property_t, int32_t>>, - std::optional>> -create_graph_from_edgelist( - raft::handle_t const& handle, - std::optional>&& vertices, - std::vector>&& edgelist_srcs, - std::vector>&& edgelist_dsts, - std::optional>>&& edgelist_weights, - std::optional>>&& edgelist_edge_ids, - std::optional>>&& edgelist_edge_types, - graph_properties_t graph_properties, - bool renumber, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/decompress_to_edgelist_mg_v32_e64.cu b/cpp/src/structure/decompress_to_edgelist_mg_v32_e64.cu deleted file mode 100644 index 082b058c3ae..00000000000 --- a/cpp/src/structure/decompress_to_edgelist_mg_v32_e64.cu +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/decompress_to_edgelist_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/decompress_to_edgelist_sg_v32_e64.cu b/cpp/src/structure/decompress_to_edgelist_sg_v32_e64.cu deleted file mode 100644 index 035bea15e1c..00000000000 --- a/cpp/src/structure/decompress_to_edgelist_sg_v32_e64.cu +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/decompress_to_edgelist_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -decompress_to_edgelist( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> edge_type_view, - std::optional> renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/graph_mg_v32_e64.cu b/cpp/src/structure/graph_mg_v32_e64.cu deleted file mode 100644 index 154ae2971dc..00000000000 --- a/cpp/src/structure/graph_mg_v32_e64.cu +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template class graph_t; -template class graph_t; - -} // namespace cugraph diff --git a/cpp/src/structure/graph_sg_v32_e64.cu b/cpp/src/structure/graph_sg_v32_e64.cu deleted file mode 100644 index 51f19cfc910..00000000000 --- a/cpp/src/structure/graph_sg_v32_e64.cu +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template class graph_t; -template class graph_t; - -} // namespace cugraph diff --git a/cpp/src/structure/graph_view_mg_v32_e64.cu b/cpp/src/structure/graph_view_mg_v32_e64.cu deleted file mode 100644 index 1c6edc53706..00000000000 --- a/cpp/src/structure/graph_view_mg_v32_e64.cu +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_view_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template class graph_view_t; -template class graph_view_t; -} // namespace cugraph diff --git a/cpp/src/structure/graph_view_sg_v32_e64.cu b/cpp/src/structure/graph_view_sg_v32_e64.cu deleted file mode 100644 index 12b140e34a6..00000000000 --- a/cpp/src/structure/graph_view_sg_v32_e64.cu +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_view_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template class graph_view_t; -template class graph_view_t; -} // namespace cugraph diff --git a/cpp/src/structure/graph_weight_utils_mg_v32_e64.cu b/cpp/src/structure/graph_weight_utils_mg_v32_e64.cu deleted file mode 100644 index a1fe84b2b85..00000000000 --- a/cpp/src/structure/graph_weight_utils_mg_v32_e64.cu +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_weight_utils_impl.cuh" - -namespace cugraph { - -// SG instantiation - -// compute_in_weight_sums - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -} // namespace cugraph diff --git a/cpp/src/structure/graph_weight_utils_sg_v32_e64.cu b/cpp/src/structure/graph_weight_utils_sg_v32_e64.cu deleted file mode 100644 index 4d4ce7097bd..00000000000 --- a/cpp/src/structure/graph_weight_utils_sg_v32_e64.cu +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/graph_weight_utils_impl.cuh" - -namespace cugraph { - -// SG instantiation - -// compute_in_weight_sums - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_in_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector -compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template rmm::device_uvector compute_out_weight_sums( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_in_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_max_out_weight_sum( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template float compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -template double compute_total_edge_weight( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view); - -} // namespace cugraph diff --git a/cpp/src/structure/induced_subgraph_mg_v32_e64.cu b/cpp/src/structure/induced_subgraph_mg_v32_e64.cu deleted file mode 100644 index e83d101898f..00000000000 --- a/cpp/src/structure/induced_subgraph_mg_v32_e64.cu +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/induced_subgraph_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_induced_subgraphs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span subgraph_offsets, - raft::device_span subgraph_vertices, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_induced_subgraphs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span subgraph_offsets, - raft::device_span subgraph_vertices, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/induced_subgraph_sg_v32_e64.cu b/cpp/src/structure/induced_subgraph_sg_v32_e64.cu deleted file mode 100644 index 6a4c08f9682..00000000000 --- a/cpp/src/structure/induced_subgraph_sg_v32_e64.cu +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/induced_subgraph_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_induced_subgraphs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span subgraph_offsets, - raft::device_span subgraph_vertices, - bool do_expensive_check); - -template std::tuple, - rmm::device_uvector, - std::optional>, - rmm::device_uvector> -extract_induced_subgraphs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - raft::device_span subgraph_offsets, - raft::device_span subgraph_vertices, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/remove_multi_edges_sg_v32_e64.cu b/cpp/src/structure/remove_multi_edges_sg_v32_e64.cu deleted file mode 100644 index 03b5a634158..00000000000 --- a/cpp/src/structure/remove_multi_edges_sg_v32_e64.cu +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/remove_multi_edges_impl.cuh" - -namespace cugraph { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -remove_multi_edges(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - bool keep_min_value_edge); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -remove_multi_edges(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types, - bool keep_min_value_edge); - -} // namespace cugraph diff --git a/cpp/src/structure/remove_self_loops_sg_v32_e64.cu b/cpp/src/structure/remove_self_loops_sg_v32_e64.cu deleted file mode 100644 index 0244e75353d..00000000000 --- a/cpp/src/structure/remove_self_loops_sg_v32_e64.cu +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/remove_self_loops_impl.cuh" - -namespace cugraph { - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -remove_self_loops(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>> -remove_self_loops(raft::handle_t const& handle, - rmm::device_uvector&& edgelist_srcs, - rmm::device_uvector&& edgelist_dsts, - std::optional>&& edgelist_weights, - std::optional>&& edgelist_edge_ids, - std::optional>&& edgelist_edge_types); - -} // namespace cugraph diff --git a/cpp/src/structure/renumber_edgelist_mg_v32_e64.cu b/cpp/src/structure/renumber_edgelist_mg_v32_e64.cu deleted file mode 100644 index 6d1414f918c..00000000000 --- a/cpp/src/structure/renumber_edgelist_mg_v32_e64.cu +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/renumber_edgelist_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, renumber_meta_t> -renumber_edgelist( - raft::handle_t const& handle, - std::optional>&& local_vertices, - std::vector const& edgelist_srcs /* [INOUT] */, - std::vector const& edgelist_dsts /* [INOUT] */, - std::vector const& edgelist_edge_counts, - std::optional>> const& edgelist_intra_partition_segment_offsets, - bool store_transposed, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/renumber_edgelist_sg_v32_e64.cu b/cpp/src/structure/renumber_edgelist_sg_v32_e64.cu deleted file mode 100644 index 5214ec15f3e..00000000000 --- a/cpp/src/structure/renumber_edgelist_sg_v32_e64.cu +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/renumber_edgelist_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, renumber_meta_t> -renumber_edgelist(raft::handle_t const& handle, - std::optional>&& vertices, - int32_t* edgelist_srcs /* [INOUT] */, - int32_t* edgelist_dsts /* [INOUT] */, - int64_t num_edgelist_edges, - bool store_transposed, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/select_random_vertices_mg_v32_e64.cu b/cpp/src/structure/select_random_vertices_mg_v32_e64.cu deleted file mode 100644 index 16b5243bcd4..00000000000 --- a/cpp/src/structure/select_random_vertices_mg_v32_e64.cu +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "structure/select_random_vertices_impl.hpp" - -namespace cugraph { - -template rmm::device_uvector select_random_vertices( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> given_set, - raft::random::RngState& rng_state, - size_t select_count, - bool with_replacement, - bool sort_vertices, - bool do_expensive_check); - -template rmm::device_uvector select_random_vertices( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> given_set, - raft::random::RngState& rng_state, - size_t select_count, - bool with_replacement, - bool sort_vertices, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/select_random_vertices_sg_v32_e64.cu b/cpp/src/structure/select_random_vertices_sg_v32_e64.cu deleted file mode 100644 index 21842ba76f8..00000000000 --- a/cpp/src/structure/select_random_vertices_sg_v32_e64.cu +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "structure/select_random_vertices_impl.hpp" - -namespace cugraph { - -template rmm::device_uvector select_random_vertices( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> given_set, - raft::random::RngState& rng_state, - size_t select_count, - bool with_replacement, - bool sort_vertices, - bool do_expensive_check); - -template rmm::device_uvector select_random_vertices( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> given_set, - raft::random::RngState& rng_state, - size_t select_count, - bool with_replacement, - bool sort_vertices, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/symmetrize_graph_mg_v32_e64.cu b/cpp/src/structure/symmetrize_graph_mg_v32_e64.cu deleted file mode 100644 index db077e676bd..00000000000 --- a/cpp/src/structure/symmetrize_graph_mg_v32_e64.cu +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/symmetrize_graph_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/symmetrize_graph_sg_v32_e64.cu b/cpp/src/structure/symmetrize_graph_sg_v32_e64.cu deleted file mode 100644 index a819207874d..00000000000 --- a/cpp/src/structure/symmetrize_graph_sg_v32_e64.cu +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/symmetrize_graph_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& - edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -symmetrize_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool reciprocal, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/transpose_graph_mg_v32_e64.cu b/cpp/src/structure/transpose_graph_mg_v32_e64.cu deleted file mode 100644 index 6bf9b9e4011..00000000000 --- a/cpp/src/structure/transpose_graph_mg_v32_e64.cu +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/transpose_graph_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/transpose_graph_sg_v32_e64.cu b/cpp/src/structure/transpose_graph_sg_v32_e64.cu deleted file mode 100644 index af64aa7b20b..00000000000 --- a/cpp/src/structure/transpose_graph_sg_v32_e64.cu +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/transpose_graph_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/transpose_graph_storage_mg_v32_e64.cu b/cpp/src/structure/transpose_graph_storage_mg_v32_e64.cu deleted file mode 100644 index 15488e2ba17..00000000000 --- a/cpp/src/structure/transpose_graph_storage_mg_v32_e64.cu +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/transpose_graph_storage_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/structure/transpose_graph_storage_sg_v32_e64.cu b/cpp/src/structure/transpose_graph_storage_sg_v32_e64.cu deleted file mode 100644 index ac8787582dd..00000000000 --- a/cpp/src/structure/transpose_graph_storage_sg_v32_e64.cu +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "structure/transpose_graph_storage_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, float>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, float>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -template std::tuple< - graph_t, - std::optional, double>>, - std::optional>> -transpose_graph_storage( - raft::handle_t const& handle, - graph_t&& graph, - std::optional, double>>&& - edge_weights, - std::optional>&& renumber_map, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/bfs_mg_v32_e64.cu b/cpp/src/traversal/bfs_mg_v32_e64.cu deleted file mode 100644 index 5fde01e0fcc..00000000000 --- a/cpp/src/traversal/bfs_mg_v32_e64.cu +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "traversal/bfs_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template void bfs(raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t* distances, - int32_t* predecessors, - int32_t const* sources, - size_t n_sources, - bool direction_optimizing, - int32_t depth_limit, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/bfs_sg_v32_e64.cu b/cpp/src/traversal/bfs_sg_v32_e64.cu deleted file mode 100644 index 3a6a7ef01de..00000000000 --- a/cpp/src/traversal/bfs_sg_v32_e64.cu +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "traversal/bfs_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template void bfs(raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t* distances, - int32_t* predecessors, - int32_t const* sources, - size_t n_sources, - bool direction_optimizing, - int32_t depth_limit, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/extract_bfs_paths_mg_v32_e64.cu b/cpp/src/traversal/extract_bfs_paths_mg_v32_e64.cu deleted file mode 100644 index 00aacc50c47..00000000000 --- a/cpp/src/traversal/extract_bfs_paths_mg_v32_e64.cu +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "traversal/extract_bfs_paths_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, int32_t> extract_bfs_paths( - raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t const* distances, - int32_t const* predecessors, - int32_t const* destinations, - size_t n_destinations); - -} // namespace cugraph diff --git a/cpp/src/traversal/extract_bfs_paths_sg_v32_e64.cu b/cpp/src/traversal/extract_bfs_paths_sg_v32_e64.cu deleted file mode 100644 index 369ae9efa1a..00000000000 --- a/cpp/src/traversal/extract_bfs_paths_sg_v32_e64.cu +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "traversal/extract_bfs_paths_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template std::tuple, int32_t> extract_bfs_paths( - raft::handle_t const& handle, - graph_view_t const& graph_view, - int32_t const* distances, - int32_t const* predecessors, - int32_t const* destinations, - size_t n_destinations); - -} // namespace cugraph diff --git a/cpp/src/traversal/k_hop_nbrs_mg_v32_e64.cu b/cpp/src/traversal/k_hop_nbrs_mg_v32_e64.cu deleted file mode 100644 index 31049883166..00000000000 --- a/cpp/src/traversal/k_hop_nbrs_mg_v32_e64.cu +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "traversal/k_hop_nbrs_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, rmm::device_uvector> k_hop_nbrs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::device_span start_vertices, - size_t k, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/k_hop_nbrs_sg_v32_e64.cu b/cpp/src/traversal/k_hop_nbrs_sg_v32_e64.cu deleted file mode 100644 index cd47f81361f..00000000000 --- a/cpp/src/traversal/k_hop_nbrs_sg_v32_e64.cu +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "traversal/k_hop_nbrs_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template std::tuple, rmm::device_uvector> k_hop_nbrs( - raft::handle_t const& handle, - graph_view_t const& graph_view, - raft::device_span start_vertices, - size_t k, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/od_shortest_distances_sg_v32_e64.cu b/cpp/src/traversal/od_shortest_distances_sg_v32_e64.cu deleted file mode 100644 index 880bc775589..00000000000 --- a/cpp/src/traversal/od_shortest_distances_sg_v32_e64.cu +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "traversal/od_shortest_distances_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template rmm::device_uvector od_shortest_distances( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span origins, - raft::device_span destinations, - float cutoff, - bool do_expensive_check); - -template rmm::device_uvector od_shortest_distances( - raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - raft::device_span origins, - raft::device_span destinations, - double cutoff, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/sssp_mg_v32_e64.cu b/cpp/src/traversal/sssp_mg_v32_e64.cu deleted file mode 100644 index 382b31cebf9..00000000000 --- a/cpp/src/traversal/sssp_mg_v32_e64.cu +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "traversal/sssp_impl.cuh" - -namespace cugraph { - -// MG instantiation - -template void sssp(raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - float* distances, - int32_t* predecessors, - int32_t source_vertex, - float cutoff, - bool do_expensive_check); - -template void sssp(raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - double* distances, - int32_t* predecessors, - int32_t source_vertex, - double cutoff, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/traversal/sssp_sg_v32_e64.cu b/cpp/src/traversal/sssp_sg_v32_e64.cu deleted file mode 100644 index b02d43cfaf2..00000000000 --- a/cpp/src/traversal/sssp_sg_v32_e64.cu +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "traversal/sssp_impl.cuh" - -namespace cugraph { - -// SG instantiation - -template void sssp(raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - float* distances, - int32_t* predecessors, - int32_t source_vertex, - float cutoff, - bool do_expensive_check); - -template void sssp(raft::handle_t const& handle, - graph_view_t const& graph_view, - edge_property_view_t edge_weight_view, - double* distances, - int32_t* predecessors, - int32_t source_vertex, - double cutoff, - bool do_expensive_check); - -} // namespace cugraph diff --git a/cpp/src/utilities/shuffle_vertex_pairs_mg_v32_e64.cu b/cpp/src/utilities/shuffle_vertex_pairs_mg_v32_e64.cu deleted file mode 100644 index d79b2379224..00000000000 --- a/cpp/src/utilities/shuffle_vertex_pairs_mg_v32_e64.cu +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2021-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "detail/graph_partition_utils.cuh" -#include "shuffle_vertex_pairs.cuh" - -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace cugraph { - -namespace detail { -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_ext_vertex_pairs_with_values_to_local_gpu_by_edge_partitioning( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_ext_vertex_pairs_with_values_to_local_gpu_by_edge_partitioning( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_int_vertex_pairs_with_values_to_local_gpu_by_edge_partitioning( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types, - std::vector const& vertex_partition_range_lasts); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_int_vertex_pairs_with_values_to_local_gpu_by_edge_partitioning( - raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types, - std::vector const& vertex_partition_range_lasts); - -} // namespace detail - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_external_edges(raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types); - -template std::tuple, - rmm::device_uvector, - std::optional>, - std::optional>, - std::optional>, - std::vector> -shuffle_external_edges(raft::handle_t const& handle, - rmm::device_uvector&& majors, - rmm::device_uvector&& minors, - std::optional>&& weights, - std::optional>&& edge_ids, - std::optional>&& edge_types); - -} // namespace cugraph diff --git a/cpp/tests/centrality/betweenness_centrality_test.cpp b/cpp/tests/centrality/betweenness_centrality_test.cpp index 671e70bd40c..42f619efa2b 100644 --- a/cpp/tests/centrality/betweenness_centrality_test.cpp +++ b/cpp/tests/centrality/betweenness_centrality_test.cpp @@ -182,12 +182,6 @@ TEST_P(Tests_BetweennessCentrality_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_BetweennessCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_BetweennessCentrality_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/centrality/edge_betweenness_centrality_test.cpp b/cpp/tests/centrality/edge_betweenness_centrality_test.cpp index 4f9cdbe6458..9ba840bde90 100644 --- a/cpp/tests/centrality/edge_betweenness_centrality_test.cpp +++ b/cpp/tests/centrality/edge_betweenness_centrality_test.cpp @@ -205,12 +205,6 @@ TEST_P(Tests_EdgeBetweennessCentrality_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_EdgeBetweennessCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_EdgeBetweennessCentrality_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/centrality/eigenvector_centrality_test.cpp b/cpp/tests/centrality/eigenvector_centrality_test.cpp index 9e232888e77..f9bccd6256b 100644 --- a/cpp/tests/centrality/eigenvector_centrality_test.cpp +++ b/cpp/tests/centrality/eigenvector_centrality_test.cpp @@ -254,13 +254,6 @@ TEST_P(Tests_EigenvectorCentrality_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_EigenvectorCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_EigenvectorCentrality_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/centrality/katz_centrality_test.cpp b/cpp/tests/centrality/katz_centrality_test.cpp index fc5aead5d90..a6365a37c70 100644 --- a/cpp/tests/centrality/katz_centrality_test.cpp +++ b/cpp/tests/centrality/katz_centrality_test.cpp @@ -258,13 +258,6 @@ TEST_P(Tests_KatzCentrality_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_KatzCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_KatzCentrality_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/centrality/mg_betweenness_centrality_test.cpp b/cpp/tests/centrality/mg_betweenness_centrality_test.cpp index 65a1098033b..35f6a5157ff 100644 --- a/cpp/tests/centrality/mg_betweenness_centrality_test.cpp +++ b/cpp/tests/centrality/mg_betweenness_centrality_test.cpp @@ -205,12 +205,6 @@ TEST_P(Tests_MGBetweennessCentrality_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGBetweennessCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGBetweennessCentrality_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/centrality/mg_edge_betweenness_centrality_test.cpp b/cpp/tests/centrality/mg_edge_betweenness_centrality_test.cpp index 8dbeb076c00..ff100a33e40 100644 --- a/cpp/tests/centrality/mg_edge_betweenness_centrality_test.cpp +++ b/cpp/tests/centrality/mg_edge_betweenness_centrality_test.cpp @@ -209,12 +209,6 @@ TEST_P(Tests_MGEdgeBetweennessCentrality_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGEdgeBetweennessCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGEdgeBetweennessCentrality_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/centrality/mg_eigenvector_centrality_test.cpp b/cpp/tests/centrality/mg_eigenvector_centrality_test.cpp index 39406e05d6c..0e2a0e37c1b 100644 --- a/cpp/tests/centrality/mg_eigenvector_centrality_test.cpp +++ b/cpp/tests/centrality/mg_eigenvector_centrality_test.cpp @@ -238,13 +238,6 @@ TEST_P(Tests_MGEigenvectorCentrality_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGEigenvectorCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGEigenvectorCentrality_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/centrality/mg_katz_centrality_test.cpp b/cpp/tests/centrality/mg_katz_centrality_test.cpp index 15a4089fa1b..5ccade18c18 100644 --- a/cpp/tests/centrality/mg_katz_centrality_test.cpp +++ b/cpp/tests/centrality/mg_katz_centrality_test.cpp @@ -229,13 +229,6 @@ TEST_P(Tests_MGKatzCentrality_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGKatzCentrality_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGKatzCentrality_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/community/mg_ecg_test.cpp b/cpp/tests/community/mg_ecg_test.cpp index c99f83fa2e8..14d1697744e 100644 --- a/cpp/tests/community/mg_ecg_test.cpp +++ b/cpp/tests/community/mg_ecg_test.cpp @@ -182,12 +182,6 @@ TEST_P(Tests_MGEcg_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGEcg_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGEcg_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/community/mg_edge_triangle_count_test.cpp b/cpp/tests/community/mg_edge_triangle_count_test.cpp index 89bdf870ccd..5f00d4bbc5c 100644 --- a/cpp/tests/community/mg_edge_triangle_count_test.cpp +++ b/cpp/tests/community/mg_edge_triangle_count_test.cpp @@ -206,13 +206,6 @@ TEST_P(Tests_MGEdgeTriangleCount_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGEdgeTriangleCount_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGEdgeTriangleCount_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/community/mg_egonet_test.cu b/cpp/tests/community/mg_egonet_test.cu index ac363df3ec5..130e01e8df9 100644 --- a/cpp/tests/community/mg_egonet_test.cu +++ b/cpp/tests/community/mg_egonet_test.cu @@ -268,12 +268,6 @@ TEST_P(Tests_MGEgonet_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGEgonet_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGEgonet_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/community/mg_k_truss_test.cpp b/cpp/tests/community/mg_k_truss_test.cpp index a1624949007..d3463e73a6f 100644 --- a/cpp/tests/community/mg_k_truss_test.cpp +++ b/cpp/tests/community/mg_k_truss_test.cpp @@ -241,13 +241,6 @@ TEST_P(Tests_MGKTruss_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGKTruss_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGKTruss_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/community/mg_leiden_test.cpp b/cpp/tests/community/mg_leiden_test.cpp index 65f4827ba06..6949ac8d170 100644 --- a/cpp/tests/community/mg_leiden_test.cpp +++ b/cpp/tests/community/mg_leiden_test.cpp @@ -214,12 +214,6 @@ TEST_P(Tests_MGLeiden_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGLeiden_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGLeiden_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/community/mg_louvain_test.cpp b/cpp/tests/community/mg_louvain_test.cpp index 106ad2562f7..4aebd26c256 100644 --- a/cpp/tests/community/mg_louvain_test.cpp +++ b/cpp/tests/community/mg_louvain_test.cpp @@ -252,12 +252,6 @@ TEST_P(Tests_MGLouvain_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGLouvain_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGLouvain_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/community/mg_triangle_count_test.cpp b/cpp/tests/community/mg_triangle_count_test.cpp index 932ff5050f1..b541933ca4d 100644 --- a/cpp/tests/community/mg_triangle_count_test.cpp +++ b/cpp/tests/community/mg_triangle_count_test.cpp @@ -246,13 +246,6 @@ TEST_P(Tests_MGTriangleCount_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTriangleCount_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTriangleCount_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/community/mg_weighted_matching_test.cpp b/cpp/tests/community/mg_weighted_matching_test.cpp index 5a150bbc1f7..4e57450ace7 100644 --- a/cpp/tests/community/mg_weighted_matching_test.cpp +++ b/cpp/tests/community/mg_weighted_matching_test.cpp @@ -188,12 +188,6 @@ TEST_P(Tests_MGWeightedMatching_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGWeightedMatching_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGWeightedMatching_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -206,12 +200,6 @@ TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/community/triangle_count_test.cpp b/cpp/tests/community/triangle_count_test.cpp index b4f4b87943a..c35ab4e6f4d 100644 --- a/cpp/tests/community/triangle_count_test.cpp +++ b/cpp/tests/community/triangle_count_test.cpp @@ -263,11 +263,6 @@ TEST_P(Tests_TriangleCount_Rmat, CheckInt32Int32) run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_TriangleCount_File, CheckInt32Int64) -{ - run_current_test(override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_TriangleCount_Rmat, CheckInt64Int64) { run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); diff --git a/cpp/tests/community/weighted_matching_test.cpp b/cpp/tests/community/weighted_matching_test.cpp index 436273c3be3..ddaa85a14db 100644 --- a/cpp/tests/community/weighted_matching_test.cpp +++ b/cpp/tests/community/weighted_matching_test.cpp @@ -123,12 +123,6 @@ TEST_P(Tests_SGWeightedMatching_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGWeightedMatching_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGWeightedMatching_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -141,12 +135,6 @@ TEST_P(Tests_SGWeightedMatching_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGWeightedMatching_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGWeightedMatching_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/components/mg_mis_test.cu b/cpp/tests/components/mg_mis_test.cu index 9c50be3fa28..0696a6a1db1 100644 --- a/cpp/tests/components/mg_mis_test.cu +++ b/cpp/tests/components/mg_mis_test.cu @@ -213,12 +213,6 @@ TEST_P(Tests_MGMaximalIndependentSet_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGMaximalIndependentSet_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGMaximalIndependentSet_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -231,12 +225,6 @@ TEST_P(Tests_MGMaximalIndependentSet_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGMaximalIndependentSet_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGMaximalIndependentSet_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/components/mg_vertex_coloring_test.cu b/cpp/tests/components/mg_vertex_coloring_test.cu index 14e15df502f..17327e35c97 100644 --- a/cpp/tests/components/mg_vertex_coloring_test.cu +++ b/cpp/tests/components/mg_vertex_coloring_test.cu @@ -214,12 +214,6 @@ TEST_P(Tests_MGGraphColoring_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGGraphColoring_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGGraphColoring_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -232,12 +226,6 @@ TEST_P(Tests_MGGraphColoring_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGGraphColoring_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGGraphColoring_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/components/mg_weakly_connected_components_test.cpp b/cpp/tests/components/mg_weakly_connected_components_test.cpp index 368fea68877..bb3bc826a71 100644 --- a/cpp/tests/components/mg_weakly_connected_components_test.cpp +++ b/cpp/tests/components/mg_weakly_connected_components_test.cpp @@ -195,13 +195,6 @@ TEST_P(Tests_MGWeaklyConnectedComponents_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGWeaklyConnectedComponents_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGWeaklyConnectedComponents_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/components/mis_test.cu b/cpp/tests/components/mis_test.cu index f98aa1fe0c7..d7871f32082 100644 --- a/cpp/tests/components/mis_test.cu +++ b/cpp/tests/components/mis_test.cu @@ -179,12 +179,6 @@ TEST_P(Tests_SGMaximalIndependentSet_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGMaximalIndependentSet_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGMaximalIndependentSet_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -197,12 +191,6 @@ TEST_P(Tests_SGMaximalIndependentSet_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGMaximalIndependentSet_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGMaximalIndependentSet_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/components/vertex_coloring_test.cu b/cpp/tests/components/vertex_coloring_test.cu index c7c37c6ad56..fed64f272d7 100644 --- a/cpp/tests/components/vertex_coloring_test.cu +++ b/cpp/tests/components/vertex_coloring_test.cu @@ -182,12 +182,6 @@ TEST_P(Tests_SGGraphColoring_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGGraphColoring_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGGraphColoring_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -200,12 +194,6 @@ TEST_P(Tests_SGGraphColoring_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGGraphColoring_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGGraphColoring_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/components/weakly_connected_components_test.cpp b/cpp/tests/components/weakly_connected_components_test.cpp index 7b909c6f594..db8eeefe511 100644 --- a/cpp/tests/components/weakly_connected_components_test.cpp +++ b/cpp/tests/components/weakly_connected_components_test.cpp @@ -213,13 +213,6 @@ TEST_P(Tests_WeaklyConnectedComponents_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_WeaklyConnectedComponents_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_WeaklyConnectedComponents_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/cores/core_number_test.cpp b/cpp/tests/cores/core_number_test.cpp index ca0174202c2..937bdb95241 100644 --- a/cpp/tests/cores/core_number_test.cpp +++ b/cpp/tests/cores/core_number_test.cpp @@ -331,13 +331,6 @@ TEST_P(Tests_CoreNumber_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_CoreNumber_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_CoreNumber_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/cores/k_core_test.cpp b/cpp/tests/cores/k_core_test.cpp index 064b7862842..5dbafe206dc 100644 --- a/cpp/tests/cores/k_core_test.cpp +++ b/cpp/tests/cores/k_core_test.cpp @@ -134,11 +134,6 @@ TEST_P(Tests_KCore_Rmat, CheckInt32Int32) run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_KCore_Rmat, CheckInt32Int64) -{ - run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_KCore_Rmat, CheckInt64Int64) { run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); diff --git a/cpp/tests/cores/k_core_validate.cu b/cpp/tests/cores/k_core_validate.cu index 53c97dd466b..4645af95a91 100644 --- a/cpp/tests/cores/k_core_validate.cu +++ b/cpp/tests/cores/k_core_validate.cu @@ -99,16 +99,6 @@ template void check_correctness( std::optional>> const& subgraph, size_t k); -template void check_correctness( - raft::handle_t const& handle, - graph_view_t const& graph_view, - std::optional> edge_weight_view, - rmm::device_uvector const& core_numbers, - std::tuple, - rmm::device_uvector, - std::optional>> const& subgraph, - size_t k); - template void check_correctness( raft::handle_t const& handle, graph_view_t const& graph_view, diff --git a/cpp/tests/cores/mg_core_number_test.cpp b/cpp/tests/cores/mg_core_number_test.cpp index f8294d81fdf..f6b73be7b09 100644 --- a/cpp/tests/cores/mg_core_number_test.cpp +++ b/cpp/tests/cores/mg_core_number_test.cpp @@ -205,13 +205,6 @@ TEST_P(Tests_MGCoreNumber_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCoreNumber_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCoreNumber_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/cores/mg_k_core_test.cpp b/cpp/tests/cores/mg_k_core_test.cpp index 28bc445bda8..8ebbff6f5a4 100644 --- a/cpp/tests/cores/mg_k_core_test.cpp +++ b/cpp/tests/cores/mg_k_core_test.cpp @@ -208,11 +208,6 @@ TEST_P(Tests_MGKCore_Rmat, CheckInt32Int32) run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGKCore_Rmat, CheckInt32Int64) -{ - run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGKCore_Rmat, CheckInt64Int64) { run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); diff --git a/cpp/tests/link_analysis/hits_test.cpp b/cpp/tests/link_analysis/hits_test.cpp index 89bc0ca34c5..0017f4652ba 100644 --- a/cpp/tests/link_analysis/hits_test.cpp +++ b/cpp/tests/link_analysis/hits_test.cpp @@ -316,12 +316,6 @@ TEST_P(Tests_Hits_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_Hits_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_Hits_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/link_analysis/mg_hits_test.cpp b/cpp/tests/link_analysis/mg_hits_test.cpp index 8a13204a5aa..eb2a9bcd721 100644 --- a/cpp/tests/link_analysis/mg_hits_test.cpp +++ b/cpp/tests/link_analysis/mg_hits_test.cpp @@ -266,13 +266,6 @@ TEST_P(Tests_MGHits_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGHits_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGHits_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/link_analysis/mg_pagerank_test.cpp b/cpp/tests/link_analysis/mg_pagerank_test.cpp index a9e3a10b2ae..f6ca6b03192 100644 --- a/cpp/tests/link_analysis/mg_pagerank_test.cpp +++ b/cpp/tests/link_analysis/mg_pagerank_test.cpp @@ -283,13 +283,6 @@ TEST_P(Tests_MGPageRank_Rmat, CheckInt32Int32FloatFloat) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPageRank_Rmat, CheckInt32Int64FloatFloat) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPageRank_Rmat, CheckInt64Int64FloatFloat) { auto param = GetParam(); diff --git a/cpp/tests/link_analysis/pagerank_test.cpp b/cpp/tests/link_analysis/pagerank_test.cpp index 196476d6756..b2bc501218a 100644 --- a/cpp/tests/link_analysis/pagerank_test.cpp +++ b/cpp/tests/link_analysis/pagerank_test.cpp @@ -369,12 +369,6 @@ TEST_P(Tests_PageRank_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_PageRank_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_PageRank_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/link_prediction/mg_similarity_test.cpp b/cpp/tests/link_prediction/mg_similarity_test.cpp index 3bcabb6b6df..302248fe516 100644 --- a/cpp/tests/link_prediction/mg_similarity_test.cpp +++ b/cpp/tests/link_prediction/mg_similarity_test.cpp @@ -190,13 +190,6 @@ TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int32FloatFloatJaccard) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); } -TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int64FloatFloatJaccard) -{ - auto param = GetParam(); - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); -} - TEST_P(Tests_MGSimilarity_Rmat, CheckInt64Int64FloatFloatJaccard) { auto param = GetParam(); @@ -216,12 +209,6 @@ TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int32FloatSorensen) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); } -TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int64FloatSorensen) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); -} - TEST_P(Tests_MGSimilarity_Rmat, CheckInt64Int64FloatSorensen) { run_current_test( @@ -240,12 +227,6 @@ TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int32FloatOverlap) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); } -TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int64FloatOverlap) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); -} - TEST_P(Tests_MGSimilarity_Rmat, CheckInt64Int64FloatOverlap) { run_current_test( @@ -266,13 +247,6 @@ TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int32FloatFloatCosine) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); } -TEST_P(Tests_MGSimilarity_Rmat, CheckInt32Int64FloatFloatCosine) -{ - auto param = GetParam(); - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); -} - TEST_P(Tests_MGSimilarity_Rmat, CheckInt64Int64FloatFloatCosine) { auto param = GetParam(); diff --git a/cpp/tests/link_prediction/mg_weighted_similarity_test.cpp b/cpp/tests/link_prediction/mg_weighted_similarity_test.cpp index 730a3ac8f08..2076d4ff79e 100644 --- a/cpp/tests/link_prediction/mg_weighted_similarity_test.cpp +++ b/cpp/tests/link_prediction/mg_weighted_similarity_test.cpp @@ -200,13 +200,6 @@ TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int32FloatFloatJaccard) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); } -TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int64FloatFloatJaccard) -{ - auto param = GetParam(); - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); -} - TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt64Int64FloatFloatJaccard) { auto param = GetParam(); @@ -226,12 +219,6 @@ TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int32FloatSorensen) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); } -TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int64FloatSorensen) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); -} - TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt64Int64FloatSorensen) { run_current_test( @@ -250,12 +237,6 @@ TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int32FloatOverlap) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); } -TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int64FloatOverlap) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); -} - TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt64Int64FloatOverlap) { run_current_test( @@ -276,13 +257,6 @@ TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int32FloatFloatCosine) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); } -TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt32Int64FloatFloatCosine) -{ - auto param = GetParam(); - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); -} - TEST_P(Tests_MGWeightedSimilarity_Rmat, CheckInt64Int64FloatFloatCosine) { auto param = GetParam(); diff --git a/cpp/tests/link_prediction/similarity_test.cu b/cpp/tests/link_prediction/similarity_test.cu index 5547c4bd0c0..ec6db102830 100644 --- a/cpp/tests/link_prediction/similarity_test.cu +++ b/cpp/tests/link_prediction/similarity_test.cu @@ -223,12 +223,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatJaccard) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatJaccard) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatJaccard) { run_current_test( @@ -247,12 +241,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatSorensen) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatSorensen) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatSorensen) { run_current_test( @@ -271,12 +259,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatOverlap) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatOverlap) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatOverlap) { run_current_test( @@ -295,12 +277,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatCosine) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatCosine) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatCosine) { run_current_test( diff --git a/cpp/tests/link_prediction/weighted_similarity_test.cpp b/cpp/tests/link_prediction/weighted_similarity_test.cpp index 2450e7d6376..cfd1171eedb 100644 --- a/cpp/tests/link_prediction/weighted_similarity_test.cpp +++ b/cpp/tests/link_prediction/weighted_similarity_test.cpp @@ -233,12 +233,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatJaccard) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatJaccard) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_jaccard_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatJaccard) { run_current_test( @@ -257,12 +251,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatSorensen) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatSorensen) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_sorensen_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatSorensen) { run_current_test( @@ -281,12 +269,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatOverlap) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatOverlap) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_overlap_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatOverlap) { run_current_test( @@ -305,12 +287,6 @@ TEST_P(Tests_Similarity_Rmat, CheckInt32Int32FloatCosine) override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); } -TEST_P(Tests_Similarity_Rmat, CheckInt32Int64FloatCosine) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam()), cugraph::test::test_cosine_t{}); -} - TEST_P(Tests_Similarity_Rmat, CheckInt64Int64FloatCosine) { run_current_test( diff --git a/cpp/tests/lookup/lookup_src_dst_test.cpp b/cpp/tests/lookup/lookup_src_dst_test.cpp index 87f4958f740..5f23af6dfa3 100644 --- a/cpp/tests/lookup/lookup_src_dst_test.cpp +++ b/cpp/tests/lookup/lookup_src_dst_test.cpp @@ -250,12 +250,6 @@ TEST_P(Tests_SGLookupEdgeSrcDst_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGLookupEdgeSrcDst_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGLookupEdgeSrcDst_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -268,12 +262,6 @@ TEST_P(Tests_SGLookupEdgeSrcDst_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SGLookupEdgeSrcDst_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SGLookupEdgeSrcDst_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/lookup/mg_lookup_src_dst_test.cpp b/cpp/tests/lookup/mg_lookup_src_dst_test.cpp index 8d31b0ca157..7136f620919 100644 --- a/cpp/tests/lookup/mg_lookup_src_dst_test.cpp +++ b/cpp/tests/lookup/mg_lookup_src_dst_test.cpp @@ -293,12 +293,6 @@ TEST_P(Tests_MGLookupEdgeSrcDst_File, CheckInt32Int32FloatFloat) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGLookupEdgeSrcDst_File, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGLookupEdgeSrcDst_File, CheckInt64Int64FloatFloat) { run_current_test( @@ -311,12 +305,6 @@ TEST_P(Tests_MGLookupEdgeSrcDst_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGLookupEdgeSrcDst_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGLookupEdgeSrcDst_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/prims/mg_count_if_e.cu b/cpp/tests/prims/mg_count_if_e.cu index 88494132ac7..63a785fb182 100644 --- a/cpp/tests/prims/mg_count_if_e.cu +++ b/cpp/tests/prims/mg_count_if_e.cu @@ -212,14 +212,6 @@ TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int32FloatTupleIntFloatTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { auto param = GetParam(); @@ -243,14 +235,6 @@ TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int32FloatTupleIntFloatTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { auto param = GetParam(); @@ -273,14 +257,6 @@ TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int32FloatTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfE_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -303,14 +279,6 @@ TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int32FloatTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfE_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfE_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_count_if_v.cu b/cpp/tests/prims/mg_count_if_v.cu index 19ec285109c..0d399f52acd 100644 --- a/cpp/tests/prims/mg_count_if_v.cu +++ b/cpp/tests/prims/mg_count_if_v.cu @@ -168,13 +168,6 @@ TEST_P(Tests_MGCountIfV_Rmat, CheckInt32Int32FloatTransposeFalse) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfV_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfV_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -195,13 +188,6 @@ TEST_P(Tests_MGCountIfV_Rmat, CheckInt32Int32FloatTransposeTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountIfV_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountIfV_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_extract_transform_e.cu b/cpp/tests/prims/mg_extract_transform_e.cu index e45f10c3175..d3d6524cbdb 100644 --- a/cpp/tests/prims/mg_extract_transform_e.cu +++ b/cpp/tests/prims/mg_extract_transform_e.cu @@ -334,20 +334,6 @@ TEST_P(Tests_MGExtractTransformE_Rmat, CheckInt32Int32FloatInt32TupleFloatInt32) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGExtractTransformE_File, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test(std::get<0>(param), std::get<1>(param)); -} - -TEST_P(Tests_MGExtractTransformE_Rmat, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGExtractTransformE_File, CheckInt64Int64FloatInt32Int32) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_extract_transform_v_frontier_outgoing_e.cu b/cpp/tests/prims/mg_extract_transform_v_frontier_outgoing_e.cu index 03f64e422c9..a8393d84e43 100644 --- a/cpp/tests/prims/mg_extract_transform_v_frontier_outgoing_e.cu +++ b/cpp/tests/prims/mg_extract_transform_v_frontier_outgoing_e.cu @@ -435,21 +435,6 @@ TEST_P(Tests_MGExtractTransformVFrontierOutgoingE_Rmat, CheckInt32Int32FloatInt3 cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGExtractTransformVFrontierOutgoingE_File, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test(std::get<0>(param), - std::get<1>(param)); -} - -TEST_P(Tests_MGExtractTransformVFrontierOutgoingE_Rmat, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGExtractTransformVFrontierOutgoingE_File, CheckInt64Int64FloatInt32Int32) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu index 4025d4d1b1d..48c308746f1 100644 --- a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu +++ b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_intersection.cu @@ -291,14 +291,6 @@ TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt32Int32Float) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu index 8af187554e1..4e12cf608cb 100644 --- a/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu +++ b/cpp/tests/prims/mg_per_v_pair_transform_dst_nbr_weighted_intersection.cu @@ -337,14 +337,6 @@ TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt32Int32Float) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVPairTransformDstNbrIntersection_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_per_v_random_select_transform_outgoing_e.cu b/cpp/tests/prims/mg_per_v_random_select_transform_outgoing_e.cu index f698701eb08..386fce24a87 100644 --- a/cpp/tests/prims/mg_per_v_random_select_transform_outgoing_e.cu +++ b/cpp/tests/prims/mg_per_v_random_select_transform_outgoing_e.cu @@ -565,14 +565,6 @@ TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt32Int32FloatTupl cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt32Int64FloatTupleIntFloat) -{ - auto param = GetParam(); - run_current_test>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt64Int64FloatTupleIntFloat) { auto param = GetParam(); @@ -595,14 +587,6 @@ TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt32Int32Float) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVRandomSelectTransformOutgoingE_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_per_v_transform_reduce_dst_key_aggregated_outgoing_e.cu b/cpp/tests/prims/mg_per_v_transform_reduce_dst_key_aggregated_outgoing_e.cu index e08baa610e5..3dd256544b4 100644 --- a/cpp/tests/prims/mg_per_v_transform_reduce_dst_key_aggregated_outgoing_e.cu +++ b/cpp/tests/prims/mg_per_v_transform_reduce_dst_key_aggregated_outgoing_e.cu @@ -513,15 +513,6 @@ TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, - CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { @@ -547,15 +538,6 @@ TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, - CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceDstKeyAggregatedOutgoingE_Rmat, CheckInt64Int64FloatTransposeFalse) { diff --git a/cpp/tests/prims/mg_per_v_transform_reduce_incoming_outgoing_e.cu b/cpp/tests/prims/mg_per_v_transform_reduce_incoming_outgoing_e.cu index eb350ddb435..41830b3017c 100644 --- a/cpp/tests/prims/mg_per_v_transform_reduce_incoming_outgoing_e.cu +++ b/cpp/tests/prims/mg_per_v_transform_reduce_incoming_outgoing_e.cu @@ -494,15 +494,6 @@ TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, - CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { @@ -529,15 +520,6 @@ TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, - CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { @@ -561,14 +543,6 @@ TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt32Int32FloatTr cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -591,14 +565,6 @@ TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt32Int32FloatTr cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGPerVTransformReduceIncomingOutgoingE_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_reduce_v.cu b/cpp/tests/prims/mg_reduce_v.cu index 0f53adcc71c..ebd557da004 100644 --- a/cpp/tests/prims/mg_reduce_v.cu +++ b/cpp/tests/prims/mg_reduce_v.cu @@ -270,13 +270,6 @@ TEST_P(Tests_MGReduceV_Rmat, CheckInt32Int32FloatTransposeFalse) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGReduceV_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGReduceV_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -297,13 +290,6 @@ TEST_P(Tests_MGReduceV_Rmat, CheckInt32Int32FloatTransposeTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGReduceV_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGReduceV_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_e.cu b/cpp/tests/prims/mg_transform_e.cu index 2f131b4d54f..3984c7cd86b 100644 --- a/cpp/tests/prims/mg_transform_e.cu +++ b/cpp/tests/prims/mg_transform_e.cu @@ -278,14 +278,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatTupleIntFloatTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { auto param = GetParam(); @@ -309,14 +301,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatTupleIntFloatTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { auto param = GetParam(); @@ -339,14 +323,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatIntTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatIntTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatIntTransposeFalse) { auto param = GetParam(); @@ -369,14 +345,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatIntTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatIntTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatIntTransposeTrue) { auto param = GetParam(); @@ -399,14 +367,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatBoolTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatBoolTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatBoolTransposeFalse) { auto param = GetParam(); @@ -429,14 +389,6 @@ TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int32FloatBoolTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformE_Rmat, CheckInt32Int64FloatBoolTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformE_Rmat, CheckInt64Int64FloatBoolTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_reduce_dst_nbr_intersection_of_e_endpoints_by_v.cu b/cpp/tests/prims/mg_transform_reduce_dst_nbr_intersection_of_e_endpoints_by_v.cu index 8eee3d5a6d5..cb86a96d78b 100644 --- a/cpp/tests/prims/mg_transform_reduce_dst_nbr_intersection_of_e_endpoints_by_v.cu +++ b/cpp/tests/prims/mg_transform_reduce_dst_nbr_intersection_of_e_endpoints_by_v.cu @@ -246,14 +246,6 @@ TEST_P(Tests_MGTransformReduceDstNbrIntersectionOfEEndpointsByV_Rmat, CheckInt32 cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceDstNbrIntersectionOfEEndpointsByV_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceDstNbrIntersectionOfEEndpointsByV_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_reduce_e.cu b/cpp/tests/prims/mg_transform_reduce_e.cu index ff8bd42f1ad..e290f05e9e4 100644 --- a/cpp/tests/prims/mg_transform_reduce_e.cu +++ b/cpp/tests/prims/mg_transform_reduce_e.cu @@ -228,14 +228,6 @@ TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int32FloatTupleIntFloatTranspose cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { auto param = GetParam(); @@ -259,14 +251,6 @@ TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int32FloatTupleIntFloatTranspose cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { auto param = GetParam(); @@ -289,14 +273,6 @@ TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int32FloatTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -319,14 +295,6 @@ TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int32FloatTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceE_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_reduce_e_by_src_dst_key.cu b/cpp/tests/prims/mg_transform_reduce_e_by_src_dst_key.cu index c7bb51e3635..b050e314a15 100644 --- a/cpp/tests/prims/mg_transform_reduce_e_by_src_dst_key.cu +++ b/cpp/tests/prims/mg_transform_reduce_e_by_src_dst_key.cu @@ -354,14 +354,6 @@ TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int32FloatTupleIntFlo cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { auto param = GetParam(); @@ -385,14 +377,6 @@ TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int32FloatTupleIntFlo cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { auto param = GetParam(); @@ -415,14 +399,6 @@ TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int32FloatTransposeFa cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -445,14 +421,6 @@ TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int32FloatTransposeTr cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceEBySrcDstKey_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_reduce_v.cu b/cpp/tests/prims/mg_transform_reduce_v.cu index 9e9bee89d67..1e5cb7207b1 100644 --- a/cpp/tests/prims/mg_transform_reduce_v.cu +++ b/cpp/tests/prims/mg_transform_reduce_v.cu @@ -246,14 +246,6 @@ TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int32FloatTupleIntFloatTranspose cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test, false>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeFalse) { auto param = GetParam(); @@ -277,14 +269,6 @@ TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int32FloatTupleIntFloatTranspose cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int64FloatTupleIntFloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test, true>( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt64Int64FloatTupleIntFloatTransposeTrue) { auto param = GetParam(); @@ -307,14 +291,6 @@ TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int32FloatTransposeFalse) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); @@ -337,14 +313,6 @@ TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int32FloatTransposeTrue) cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceV_Rmat, CheckInt64Int64FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/prims/mg_transform_reduce_v_frontier_outgoing_e_by_src_dst.cu b/cpp/tests/prims/mg_transform_reduce_v_frontier_outgoing_e_by_src_dst.cu index 5947dd9a560..9b7e24856fe 100644 --- a/cpp/tests/prims/mg_transform_reduce_v_frontier_outgoing_e_by_src_dst.cu +++ b/cpp/tests/prims/mg_transform_reduce_v_frontier_outgoing_e_by_src_dst.cu @@ -688,21 +688,6 @@ TEST_P(Tests_MGTransformReduceVFrontierOutgoingEBySrcDst_Rmat, cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransformReduceVFrontierOutgoingEBySrcDst_File, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test(std::get<0>(param), - std::get<1>(param)); -} - -TEST_P(Tests_MGTransformReduceVFrontierOutgoingEBySrcDst_Rmat, CheckInt32Int64FloatInt32Int32) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), - cugraph::test::override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransformReduceVFrontierOutgoingEBySrcDst_File, CheckInt64Int64FloatInt32Int32) { auto param = GetParam(); diff --git a/cpp/tests/sampling/biased_neighbor_sampling.cpp b/cpp/tests/sampling/biased_neighbor_sampling.cpp index 0b7307bef7d..6e63d18383e 100644 --- a/cpp/tests/sampling/biased_neighbor_sampling.cpp +++ b/cpp/tests/sampling/biased_neighbor_sampling.cpp @@ -223,13 +223,6 @@ TEST_P(Tests_Biased_Neighbor_Sampling_File, CheckInt32Int32Float) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_Biased_Neighbor_Sampling_File, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_Biased_Neighbor_Sampling_File, CheckInt64Int64Float) { auto param = GetParam(); @@ -243,12 +236,6 @@ TEST_P(Tests_Biased_Neighbor_Sampling_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_Biased_Neighbor_Sampling_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_Biased_Neighbor_Sampling_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/sampling/mg_biased_neighbor_sampling.cpp b/cpp/tests/sampling/mg_biased_neighbor_sampling.cpp index 8f08320aac8..d39e1725b63 100644 --- a/cpp/tests/sampling/mg_biased_neighbor_sampling.cpp +++ b/cpp/tests/sampling/mg_biased_neighbor_sampling.cpp @@ -298,12 +298,6 @@ TEST_P(Tests_MGBiased_Neighbor_Sampling_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGBiased_Neighbor_Sampling_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGBiased_Neighbor_Sampling_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/sampling/mg_uniform_neighbor_sampling.cpp b/cpp/tests/sampling/mg_uniform_neighbor_sampling.cpp index 23f1da40cf4..0dd63e4fb06 100644 --- a/cpp/tests/sampling/mg_uniform_neighbor_sampling.cpp +++ b/cpp/tests/sampling/mg_uniform_neighbor_sampling.cpp @@ -320,12 +320,6 @@ TEST_P(Tests_MGUniform_Neighbor_Sampling_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGUniform_Neighbor_Sampling_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGUniform_Neighbor_Sampling_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/sampling/negative_sampling.cpp b/cpp/tests/sampling/negative_sampling.cpp index ba929c63e9b..8906d011376 100644 --- a/cpp/tests/sampling/negative_sampling.cpp +++ b/cpp/tests/sampling/negative_sampling.cpp @@ -213,18 +213,12 @@ class Tests_Negative_Sampling : public ::testing::TestWithParam using Tests_Negative_Sampling_File_i32_i32_float = Tests_Negative_Sampling; -using Tests_Negative_Sampling_File_i32_i64_float = - Tests_Negative_Sampling; - using Tests_Negative_Sampling_File_i64_i64_float = Tests_Negative_Sampling; using Tests_Negative_Sampling_Rmat_i32_i32_float = Tests_Negative_Sampling; -using Tests_Negative_Sampling_Rmat_i32_i64_float = - Tests_Negative_Sampling; - using Tests_Negative_Sampling_Rmat_i64_i64_float = Tests_Negative_Sampling; @@ -255,12 +249,6 @@ TEST_P(Tests_Negative_Sampling_File_i32_i32_float, CheckInt32Int32Float) run_all_tests(this); } -TEST_P(Tests_Negative_Sampling_File_i32_i64_float, CheckInt32Int64Float) -{ - load_graph(override_File_Usecase_with_cmd_line_arguments(GetParam())); - run_all_tests(this); -} - TEST_P(Tests_Negative_Sampling_File_i64_i64_float, CheckInt64Int64Float) { load_graph(override_File_Usecase_with_cmd_line_arguments(GetParam())); @@ -273,12 +261,6 @@ TEST_P(Tests_Negative_Sampling_Rmat_i32_i32_float, CheckInt32Int32Float) run_all_tests(this); } -TEST_P(Tests_Negative_Sampling_Rmat_i32_i64_float, CheckInt32Int64Float) -{ - load_graph(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); - run_all_tests(this); -} - TEST_P(Tests_Negative_Sampling_Rmat_i64_i64_float, CheckInt64Int64Float) { load_graph(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); @@ -297,18 +279,6 @@ INSTANTIATE_TEST_SUITE_P( cugraph::test::File_Usecase("test/datasets/ljournal-2008.mtx"), cugraph::test::File_Usecase("test/datasets/webbase-1M.mtx"))); -INSTANTIATE_TEST_SUITE_P( - file_test, - Tests_Negative_Sampling_File_i32_i64_float, - ::testing::Values(cugraph::test::File_Usecase("test/datasets/karate.mtx"))); - -INSTANTIATE_TEST_SUITE_P( - file_large_test, - Tests_Negative_Sampling_File_i32_i64_float, - ::testing::Values(cugraph::test::File_Usecase("test/datasets/web-Google.mtx"), - cugraph::test::File_Usecase("test/datasets/ljournal-2008.mtx"), - cugraph::test::File_Usecase("test/datasets/webbase-1M.mtx"))); - INSTANTIATE_TEST_SUITE_P( file_test, Tests_Negative_Sampling_File_i64_i64_float, @@ -326,11 +296,6 @@ INSTANTIATE_TEST_SUITE_P( Tests_Negative_Sampling_Rmat_i32_i32_float, ::testing::Values(cugraph::test::Rmat_Usecase(10, 16, 0.57, 0.19, 0.19, 0, false, false, 0))); -INSTANTIATE_TEST_SUITE_P( - rmat_small_test, - Tests_Negative_Sampling_Rmat_i32_i64_float, - ::testing::Values(cugraph::test::Rmat_Usecase(10, 16, 0.57, 0.19, 0.19, 0, false, false, 0))); - INSTANTIATE_TEST_SUITE_P( rmat_small_test, Tests_Negative_Sampling_Rmat_i64_i64_float, diff --git a/cpp/tests/sampling/sampling_heterogeneous_post_processing_test.cpp b/cpp/tests/sampling/sampling_heterogeneous_post_processing_test.cpp index 2b2049dc8db..4d58bfc31e6 100644 --- a/cpp/tests/sampling/sampling_heterogeneous_post_processing_test.cpp +++ b/cpp/tests/sampling/sampling_heterogeneous_post_processing_test.cpp @@ -578,11 +578,6 @@ TEST_P(Tests_SamplingHeterogeneousPostProcessing_Rmat, CheckInt32Int32) run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SamplingHeterogeneousPostProcessing_Rmat, CheckInt32Int64) -{ - run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SamplingHeterogeneousPostProcessing_Rmat, CheckInt64Int64) { run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); diff --git a/cpp/tests/sampling/sampling_post_processing_test.cpp b/cpp/tests/sampling/sampling_post_processing_test.cpp index b262794d26d..c1b99b99b0a 100644 --- a/cpp/tests/sampling/sampling_post_processing_test.cpp +++ b/cpp/tests/sampling/sampling_post_processing_test.cpp @@ -896,11 +896,6 @@ TEST_P(Tests_SamplingPostProcessing_Rmat, CheckInt32Int32) run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_SamplingPostProcessing_Rmat, CheckInt32Int64) -{ - run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_SamplingPostProcessing_Rmat, CheckInt64Int64) { run_current_test(override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); diff --git a/cpp/tests/sampling/uniform_neighbor_sampling.cpp b/cpp/tests/sampling/uniform_neighbor_sampling.cpp index e348780e14b..87524b03acf 100644 --- a/cpp/tests/sampling/uniform_neighbor_sampling.cpp +++ b/cpp/tests/sampling/uniform_neighbor_sampling.cpp @@ -240,12 +240,6 @@ TEST_P(Tests_Uniform_Neighbor_Sampling_File, CheckInt32Int32Float) override_File_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_Uniform_Neighbor_Sampling_File, CheckInt32Int64Float) -{ - run_current_test( - override_File_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_Uniform_Neighbor_Sampling_File, CheckInt64Int64Float) { run_current_test( @@ -258,12 +252,6 @@ TEST_P(Tests_Uniform_Neighbor_Sampling_Rmat, CheckInt32Int32Float) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_Uniform_Neighbor_Sampling_Rmat, CheckInt32Int64Float) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_Uniform_Neighbor_Sampling_Rmat, CheckInt64Int64Float) { run_current_test( diff --git a/cpp/tests/structure/coarsen_graph_test.cpp b/cpp/tests/structure/coarsen_graph_test.cpp index 9225156f7f3..417dd4f3e05 100644 --- a/cpp/tests/structure/coarsen_graph_test.cpp +++ b/cpp/tests/structure/coarsen_graph_test.cpp @@ -366,18 +366,6 @@ TEST_P(Tests_CoarsenGraph_Rmat, CheckInt32Int32FloatTransposeTrue) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_CoarsenGraph_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - -TEST_P(Tests_CoarsenGraph_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_CoarsenGraph_Rmat, CheckInt64Int64FloatTransposeFalse) { run_current_test( diff --git a/cpp/tests/structure/count_self_loops_and_multi_edges_test.cpp b/cpp/tests/structure/count_self_loops_and_multi_edges_test.cpp index 0055ee1315e..178909037b1 100644 --- a/cpp/tests/structure/count_self_loops_and_multi_edges_test.cpp +++ b/cpp/tests/structure/count_self_loops_and_multi_edges_test.cpp @@ -177,13 +177,6 @@ TEST_P(Tests_CountSelfLoopsAndMultiEdges_Rmat, CheckInt32Int32FloatTransposeFals std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_CountSelfLoopsAndMultiEdges_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_CountSelfLoopsAndMultiEdges_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/has_edge_and_compute_multiplicity_test.cpp b/cpp/tests/structure/has_edge_and_compute_multiplicity_test.cpp index 322d7915504..9c2576cfe6c 100644 --- a/cpp/tests/structure/has_edge_and_compute_multiplicity_test.cpp +++ b/cpp/tests/structure/has_edge_and_compute_multiplicity_test.cpp @@ -223,13 +223,6 @@ TEST_P(Tests_HasEdgeAndComputeMultiplicity_Rmat, CheckInt32Int32FloatTransposeFa std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_HasEdgeAndComputeMultiplicity_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_HasEdgeAndComputeMultiplicity_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_coarsen_graph_test.cpp b/cpp/tests/structure/mg_coarsen_graph_test.cpp index 471773d71bd..deb4c287183 100644 --- a/cpp/tests/structure/mg_coarsen_graph_test.cpp +++ b/cpp/tests/structure/mg_coarsen_graph_test.cpp @@ -436,20 +436,6 @@ TEST_P(Tests_MGCoarsenGraph_Rmat, CheckInt32Int32FloatTransposeFalse) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCoarsenGraph_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_MGCoarsenGraph_Rmat, CheckInt64Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCoarsenGraph_File, CheckInt32Int32FloatTransposeTrue) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_count_self_loops_and_multi_edges_test.cpp b/cpp/tests/structure/mg_count_self_loops_and_multi_edges_test.cpp index 61f40049e31..8b78e16c404 100644 --- a/cpp/tests/structure/mg_count_self_loops_and_multi_edges_test.cpp +++ b/cpp/tests/structure/mg_count_self_loops_and_multi_edges_test.cpp @@ -180,13 +180,6 @@ TEST_P(Tests_MGCountSelfLoopsAndMultiEdges_Rmat, CheckInt32Int32FloatTransposeFa std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGCountSelfLoopsAndMultiEdges_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGCountSelfLoopsAndMultiEdges_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_has_edge_and_compute_multiplicity_test.cpp b/cpp/tests/structure/mg_has_edge_and_compute_multiplicity_test.cpp index b8ad06dd18b..9da8dfeb595 100644 --- a/cpp/tests/structure/mg_has_edge_and_compute_multiplicity_test.cpp +++ b/cpp/tests/structure/mg_has_edge_and_compute_multiplicity_test.cpp @@ -278,13 +278,6 @@ TEST_P(Tests_MGHasEdgeAndComputeMultiplicity_Rmat, CheckInt32Int32FloatTranspose std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGHasEdgeAndComputeMultiplicity_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGHasEdgeAndComputeMultiplicity_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_induced_subgraph_test.cu b/cpp/tests/structure/mg_induced_subgraph_test.cu index fd636e8c4c9..2958686c945 100644 --- a/cpp/tests/structure/mg_induced_subgraph_test.cu +++ b/cpp/tests/structure/mg_induced_subgraph_test.cu @@ -288,12 +288,6 @@ TEST_P(Tests_MGInducedSubgraph_Rmat, CheckInt32Int32) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGInducedSubgraph_Rmat, CheckInt32Int64) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGInducedSubgraph_Rmat, CheckInt64Int64) { run_current_test( diff --git a/cpp/tests/structure/mg_select_random_vertices_test.cpp b/cpp/tests/structure/mg_select_random_vertices_test.cpp index a33e2ca1813..d59bb8e0f63 100644 --- a/cpp/tests/structure/mg_select_random_vertices_test.cpp +++ b/cpp/tests/structure/mg_select_random_vertices_test.cpp @@ -225,12 +225,6 @@ TEST_P(Tests_MGSelectRandomVertices_Rmat, CheckInt32Int32FloatFloat) override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); } -TEST_P(Tests_MGSelectRandomVertices_Rmat, CheckInt32Int64FloatFloat) -{ - run_current_test( - override_Rmat_Usecase_with_cmd_line_arguments(GetParam())); -} - TEST_P(Tests_MGSelectRandomVertices_Rmat, CheckInt64Int64FloatFloat) { run_current_test( diff --git a/cpp/tests/structure/mg_symmetrize_test.cpp b/cpp/tests/structure/mg_symmetrize_test.cpp index 7d83b482ccf..ebdec278db1 100644 --- a/cpp/tests/structure/mg_symmetrize_test.cpp +++ b/cpp/tests/structure/mg_symmetrize_test.cpp @@ -262,20 +262,6 @@ TEST_P(Tests_MGSymmetrize_Rmat, CheckInt32Int32FloatTransposedTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGSymmetrize_Rmat, CheckInt32Int64FloatTransposedFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_MGSymmetrize_Rmat, CheckInt32Int64FloatTransposedTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGSymmetrize_Rmat, CheckInt64Int64FloatTransposedFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_transpose_storage_test.cpp b/cpp/tests/structure/mg_transpose_storage_test.cpp index c43561a4251..41c50b396fb 100644 --- a/cpp/tests/structure/mg_transpose_storage_test.cpp +++ b/cpp/tests/structure/mg_transpose_storage_test.cpp @@ -261,20 +261,6 @@ TEST_P(Tests_MGTransposeStorage_Rmat, CheckInt32Int32FloatTransposeStoragedTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTransposeStorage_Rmat, CheckInt32Int64FloatTransposeStoragedFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_MGTransposeStorage_Rmat, CheckInt32Int64FloatTransposeStoragedTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTransposeStorage_Rmat, CheckInt64Int64FloatTransposeStoragedFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/mg_transpose_test.cpp b/cpp/tests/structure/mg_transpose_test.cpp index a1a816e6f4b..28f5c000d3d 100644 --- a/cpp/tests/structure/mg_transpose_test.cpp +++ b/cpp/tests/structure/mg_transpose_test.cpp @@ -257,20 +257,6 @@ TEST_P(Tests_MGTranspose_Rmat, CheckInt32Int32FloatTransposedTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGTranspose_Rmat, CheckInt32Int64FloatTransposedFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_MGTranspose_Rmat, CheckInt32Int64FloatTransposedTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGTranspose_Rmat, CheckInt64Int64FloatTransposedFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/symmetrize_test.cpp b/cpp/tests/structure/symmetrize_test.cpp index bf5e28e472f..5c9d76df117 100644 --- a/cpp/tests/structure/symmetrize_test.cpp +++ b/cpp/tests/structure/symmetrize_test.cpp @@ -434,20 +434,6 @@ TEST_P(Tests_Symmetrize_Rmat, CheckInt32Int32FloatTransposeTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_Symmetrize_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_Symmetrize_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_Symmetrize_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/transpose_storage_test.cpp b/cpp/tests/structure/transpose_storage_test.cpp index d14f975b382..86c58fbe6b8 100644 --- a/cpp/tests/structure/transpose_storage_test.cpp +++ b/cpp/tests/structure/transpose_storage_test.cpp @@ -214,20 +214,6 @@ TEST_P(Tests_TransposeStorage_Rmat, CheckInt32Int32FloatTransposeTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_TransposeStorage_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_TransposeStorage_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_TransposeStorage_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/transpose_test.cpp b/cpp/tests/structure/transpose_test.cpp index 4b666d72814..e8c3b31f932 100644 --- a/cpp/tests/structure/transpose_test.cpp +++ b/cpp/tests/structure/transpose_test.cpp @@ -198,20 +198,6 @@ TEST_P(Tests_Transpose_Rmat, CheckInt32Int32FloatTransposeTrue) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_Transpose_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - -TEST_P(Tests_Transpose_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_Transpose_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = GetParam(); diff --git a/cpp/tests/structure/weight_sum_test.cpp b/cpp/tests/structure/weight_sum_test.cpp index 6f600e69431..5affd93073b 100644 --- a/cpp/tests/structure/weight_sum_test.cpp +++ b/cpp/tests/structure/weight_sum_test.cpp @@ -217,18 +217,6 @@ TEST_P(Tests_WeightSum_Rmat, CheckInt32Int32FloatTransposeTrue) run_current_test(std::get<0>(param), std::get<1>(param)); } -TEST_P(Tests_WeightSum_Rmat, CheckInt32Int64FloatTransposeFalse) -{ - auto param = override_Rmat_Usecase_with_cmd_line_arguments(GetParam()); - run_current_test(std::get<0>(param), std::get<1>(param)); -} - -TEST_P(Tests_WeightSum_Rmat, CheckInt32Int64FloatTransposeTrue) -{ - auto param = override_Rmat_Usecase_with_cmd_line_arguments(GetParam()); - run_current_test(std::get<0>(param), std::get<1>(param)); -} - TEST_P(Tests_WeightSum_Rmat, CheckInt64Int64FloatTransposeFalse) { auto param = override_Rmat_Usecase_with_cmd_line_arguments(GetParam()); diff --git a/cpp/tests/traversal/bfs_test.cpp b/cpp/tests/traversal/bfs_test.cpp index c5041378d78..b17b9aee66b 100644 --- a/cpp/tests/traversal/bfs_test.cpp +++ b/cpp/tests/traversal/bfs_test.cpp @@ -275,13 +275,6 @@ TEST_P(Tests_BFS_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_BFS_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_BFS_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/extract_bfs_paths_test.cu b/cpp/tests/traversal/extract_bfs_paths_test.cu index 89f87503494..62b654df233 100644 --- a/cpp/tests/traversal/extract_bfs_paths_test.cu +++ b/cpp/tests/traversal/extract_bfs_paths_test.cu @@ -215,13 +215,6 @@ TEST_P(Tests_ExtractBfsPaths_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_ExtractBfsPaths_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_ExtractBfsPaths_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/k_hop_nbrs_test.cpp b/cpp/tests/traversal/k_hop_nbrs_test.cpp index e385f05b0c7..326921552db 100644 --- a/cpp/tests/traversal/k_hop_nbrs_test.cpp +++ b/cpp/tests/traversal/k_hop_nbrs_test.cpp @@ -256,13 +256,6 @@ TEST_P(Tests_KHopNbrs_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_KHopNbrs_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_KHopNbrs_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/mg_bfs_test.cpp b/cpp/tests/traversal/mg_bfs_test.cpp index c294c6d0091..4d4b83e275b 100644 --- a/cpp/tests/traversal/mg_bfs_test.cpp +++ b/cpp/tests/traversal/mg_bfs_test.cpp @@ -294,13 +294,6 @@ TEST_P(Tests_MGBFS_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGBFS_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGBFS_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/mg_extract_bfs_paths_test.cu b/cpp/tests/traversal/mg_extract_bfs_paths_test.cu index 476a6ffab8f..1ef5c282c1c 100644 --- a/cpp/tests/traversal/mg_extract_bfs_paths_test.cu +++ b/cpp/tests/traversal/mg_extract_bfs_paths_test.cu @@ -298,13 +298,6 @@ TEST_P(Tests_MGExtractBFSPaths_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGExtractBFSPaths_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGExtractBFSPaths_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/mg_k_hop_nbrs_test.cpp b/cpp/tests/traversal/mg_k_hop_nbrs_test.cpp index 311d1f55b9c..4e4b7d38ccd 100644 --- a/cpp/tests/traversal/mg_k_hop_nbrs_test.cpp +++ b/cpp/tests/traversal/mg_k_hop_nbrs_test.cpp @@ -254,13 +254,6 @@ TEST_P(Tests_MGKHopNbrs_Rmat, CheckInt32Int32) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGKHopNbrs_Rmat, CheckInt32Int64) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGKHopNbrs_Rmat, CheckInt64Int64) { auto param = GetParam(); diff --git a/cpp/tests/traversal/mg_sssp_test.cpp b/cpp/tests/traversal/mg_sssp_test.cpp index 9ad16d1c947..bc3f5870f6c 100644 --- a/cpp/tests/traversal/mg_sssp_test.cpp +++ b/cpp/tests/traversal/mg_sssp_test.cpp @@ -285,13 +285,6 @@ TEST_P(Tests_MGSSSP_Rmat, CheckInt32Int32Float) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_MGSSSP_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_MGSSSP_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/traversal/od_shortest_distances_test.cpp b/cpp/tests/traversal/od_shortest_distances_test.cpp index fe9fe99e784..aba85e2b425 100644 --- a/cpp/tests/traversal/od_shortest_distances_test.cpp +++ b/cpp/tests/traversal/od_shortest_distances_test.cpp @@ -235,13 +235,6 @@ TEST_P(Tests_ODShortestDistances_Rmat, CheckInt32Int32Float) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_ODShortestDistances_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_ODShortestDistances_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/traversal/sssp_test.cpp b/cpp/tests/traversal/sssp_test.cpp index 3eff1a8e106..9180ec94da5 100644 --- a/cpp/tests/traversal/sssp_test.cpp +++ b/cpp/tests/traversal/sssp_test.cpp @@ -271,13 +271,6 @@ TEST_P(Tests_SSSP_Rmat, CheckInt32Int32Float) std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); } -TEST_P(Tests_SSSP_Rmat, CheckInt32Int64Float) -{ - auto param = GetParam(); - run_current_test( - std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param))); -} - TEST_P(Tests_SSSP_Rmat, CheckInt64Int64Float) { auto param = GetParam(); diff --git a/cpp/tests/utilities/conversion_utilities_mg.cu b/cpp/tests/utilities/conversion_utilities_mg.cu index cb4703ec89b..6c5db5b6c57 100644 --- a/cpp/tests/utilities/conversion_utilities_mg.cu +++ b/cpp/tests/utilities/conversion_utilities_mg.cu @@ -25,13 +25,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -46,13 +39,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -67,13 +53,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -88,13 +67,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -111,15 +83,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -138,15 +101,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -165,15 +119,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -192,15 +137,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -217,13 +153,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -238,13 +167,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -259,13 +181,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -280,13 +195,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -301,13 +209,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -322,13 +223,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -343,13 +237,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -364,13 +251,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -391,19 +271,6 @@ mg_graph_to_sg_graph( std::optional> renumber_map, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional, int64_t>>, - std::optional>> -mg_graph_to_sg_graph( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> renumber_map, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, float>>, @@ -430,19 +297,6 @@ mg_graph_to_sg_graph( std::optional> renumber_map, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional, int64_t>>, - std::optional>> -mg_graph_to_sg_graph( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> renumber_map, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, double>>, @@ -469,19 +323,6 @@ mg_graph_to_sg_graph( std::optional> renumber_map, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional, int64_t>>, - std::optional>> -mg_graph_to_sg_graph( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> renumber_map, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, float>>, @@ -508,19 +349,6 @@ mg_graph_to_sg_graph( std::optional> renumber_map, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional, int64_t>>, - std::optional>> -mg_graph_to_sg_graph( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> edge_id_view, - std::optional> renumber_map, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, double>>, diff --git a/cpp/tests/utilities/conversion_utilities_sg.cu b/cpp/tests/utilities/conversion_utilities_sg.cu index 450594807d1..c97881b895a 100644 --- a/cpp/tests/utilities/conversion_utilities_sg.cu +++ b/cpp/tests/utilities/conversion_utilities_sg.cu @@ -25,13 +25,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -46,13 +39,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -67,13 +53,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -88,13 +67,6 @@ graph_to_host_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_coo( raft::handle_t const& handle, @@ -111,15 +83,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -138,15 +101,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -165,15 +119,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -192,15 +137,6 @@ graph_to_device_coo( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, - rmm::device_uvector, - std::optional>> -graph_to_device_coo( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, rmm::device_uvector, std::optional>> @@ -217,13 +153,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -238,13 +167,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -259,13 +181,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -280,13 +195,6 @@ graph_to_host_csr( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csr( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csr( raft::handle_t const& handle, @@ -301,13 +209,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -322,13 +223,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -343,13 +237,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, @@ -364,13 +251,6 @@ graph_to_host_csc( std::optional> edge_weight_view, std::optional> renumber_map); -template std::tuple, std::vector, std::optional>> -graph_to_host_csc( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template std::tuple, std::vector, std::optional>> graph_to_host_csc( raft::handle_t const& handle, diff --git a/cpp/tests/utilities/csv_file_utilities.cu b/cpp/tests/utilities/csv_file_utilities.cu index 08b1e37af8a..e7be9ff3133 100644 --- a/cpp/tests/utilities/csv_file_utilities.cu +++ b/cpp/tests/utilities/csv_file_utilities.cu @@ -406,86 +406,6 @@ read_graph_from_csv_file( bool test_weighted, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_csv_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, float>>, diff --git a/cpp/tests/utilities/debug_utilities_mg.cpp b/cpp/tests/utilities/debug_utilities_mg.cpp index 3423189a8ad..e160976d648 100644 --- a/cpp/tests/utilities/debug_utilities_mg.cpp +++ b/cpp/tests/utilities/debug_utilities_mg.cpp @@ -30,18 +30,6 @@ template void print_edges( std::optional> edge_weight_view, std::optional> renumber_map); -template void print_edges( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - -template void print_edges( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template void print_edges( raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, diff --git a/cpp/tests/utilities/debug_utilities_sg.cpp b/cpp/tests/utilities/debug_utilities_sg.cpp index 622943b3c08..a5984c92397 100644 --- a/cpp/tests/utilities/debug_utilities_sg.cpp +++ b/cpp/tests/utilities/debug_utilities_sg.cpp @@ -30,18 +30,6 @@ template void print_edges( std::optional> edge_weight_view, std::optional> renumber_map); -template void print_edges( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - -template void print_edges( - raft::handle_t const& handle, - cugraph::graph_view_t const& graph_view, - std::optional> edge_weight_view, - std::optional> renumber_map); - template void print_edges( raft::handle_t const& handle, cugraph::graph_view_t const& graph_view, diff --git a/cpp/tests/utilities/matrix_market_file_utilities.cu b/cpp/tests/utilities/matrix_market_file_utilities.cu index 302533113ee..1ca31a1ee6b 100644 --- a/cpp/tests/utilities/matrix_market_file_utilities.cu +++ b/cpp/tests/utilities/matrix_market_file_utilities.cu @@ -567,86 +567,6 @@ read_graph_from_matrix_market_file( bool test_weighted, bool renumber); -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, float>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - -template std::tuple< - cugraph::graph_t, - std::optional, double>>, - std::optional>> -read_graph_from_matrix_market_file( - raft::handle_t const& handle, - std::string const& graph_file_full_path, - bool test_weighted, - bool renumber); - template std::tuple< cugraph::graph_t, std::optional, float>>, diff --git a/cpp/tests/utilities/property_generator_utilities_mg.cu b/cpp/tests/utilities/property_generator_utilities_mg.cu index f283050dca8..cd208684bec 100644 --- a/cpp/tests/utilities/property_generator_utilities_mg.cu +++ b/cpp/tests/utilities/property_generator_utilities_mg.cu @@ -30,18 +30,6 @@ template struct generate, in template struct generate, thrust::tuple>; -template struct generate, bool>; -template struct generate, int32_t>; -template struct generate, int64_t>; -template struct generate, - thrust::tuple>; - -template struct generate, bool>; -template struct generate, int32_t>; -template struct generate, int64_t>; -template struct generate, - thrust::tuple>; - template struct generate, bool>; template struct generate, int32_t>; template struct generate, int64_t>; diff --git a/cpp/tests/utilities/property_generator_utilities_sg.cu b/cpp/tests/utilities/property_generator_utilities_sg.cu index dd1907a9f1d..e824dc2b5d2 100644 --- a/cpp/tests/utilities/property_generator_utilities_sg.cu +++ b/cpp/tests/utilities/property_generator_utilities_sg.cu @@ -30,18 +30,6 @@ template struct generate, i template struct generate, thrust::tuple>; -template struct generate, bool>; -template struct generate, int32_t>; -template struct generate, int64_t>; -template struct generate, - thrust::tuple>; - -template struct generate, bool>; -template struct generate, int32_t>; -template struct generate, int64_t>; -template struct generate, - thrust::tuple>; - template struct generate, bool>; template struct generate, int32_t>; template struct generate, int64_t>; From 27f8ce1d8c1e4a24e3b15e12fea7a43e5f7566b8 Mon Sep 17 00:00:00 2001 From: Alex Barghi <105237337+alexbarghi-nv@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:40:20 -0400 Subject: [PATCH 36/37] [FEA] Support Edge ID Lookup in PyLibcuGraph (#4687) Support Edge ID lookup in `pylibcugraph`. Also fixes some bugs in the C API (i.e. lookup table not being cleaned up correctly, container being incorrectly dereferenced as graph). Verified in rapidsai/cugraph-gnn#50 Authors: - Alex Barghi (https://github.com/alexbarghi-nv) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Seunghwa Kang (https://github.com/seunghwak) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4687 --- cpp/include/cugraph_c/lookup_src_dst.h | 8 ++ cpp/src/c_api/lookup_src_dst.cpp | 45 ++++--- .../pylibcugraph/pylibcugraph/CMakeLists.txt | 1 + python/pylibcugraph/pylibcugraph/__init__.py | 2 + .../_cugraph_c/lookup_src_dst.pxd | 2 + .../pylibcugraph/edge_id_lookup_table.pxd | 34 ++++++ .../pylibcugraph/edge_id_lookup_table.pyx | 114 ++++++++++++++++++ .../internal_types/CMakeLists.txt | 1 + .../internal_types/edge_id_lookup_result.pxd | 30 +++++ .../internal_types/edge_id_lookup_result.pyx | 63 ++++++++++ .../pylibcugraph/tests/test_lookup_table.py | 80 ++++++++++++ 11 files changed, 364 insertions(+), 16 deletions(-) create mode 100644 python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pxd create mode 100644 python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pyx create mode 100644 python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pxd create mode 100644 python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pyx create mode 100644 python/pylibcugraph/pylibcugraph/tests/test_lookup_table.py diff --git a/cpp/include/cugraph_c/lookup_src_dst.h b/cpp/include/cugraph_c/lookup_src_dst.h index f4d63572e82..64051743981 100644 --- a/cpp/include/cugraph_c/lookup_src_dst.h +++ b/cpp/include/cugraph_c/lookup_src_dst.h @@ -136,6 +136,14 @@ cugraph_type_erased_device_array_view_t* cugraph_lookup_result_get_dsts( */ void cugraph_lookup_result_free(cugraph_lookup_result_t* result); +/** + * @ingroup samplingC + * @brief Free a sampling lookup map + * + * @param [in] container The sampling lookup map (a.k.a. container). + */ +void cugraph_lookup_container_free(cugraph_lookup_container_t* container); + #ifdef __cplusplus } #endif diff --git a/cpp/src/c_api/lookup_src_dst.cpp b/cpp/src/c_api/lookup_src_dst.cpp index 1be2137ef2f..3b87791ac50 100644 --- a/cpp/src/c_api/lookup_src_dst.cpp +++ b/cpp/src/c_api/lookup_src_dst.cpp @@ -307,23 +307,26 @@ extern "C" cugraph_error_code_t cugraph_lookup_endpoints_from_edge_ids_and_types { CAPI_EXPECTS( reinterpret_cast(graph)->vertex_type_ == - reinterpret_cast(lookup_container)->vertex_type_, + reinterpret_cast(lookup_container) + ->vertex_type_, CUGRAPH_INVALID_INPUT, "vertex type of graph and lookup_container must match", *error); CAPI_EXPECTS( reinterpret_cast(graph)->edge_type_ == - reinterpret_cast(lookup_container)->edge_type_, + reinterpret_cast(lookup_container) + ->edge_type_, CUGRAPH_INVALID_INPUT, "edge type of graph and lookup_container must match", *error); - CAPI_EXPECTS(reinterpret_cast(graph)->edge_type_id_type_ == - reinterpret_cast(lookup_container) - ->edge_type_id_type_, - CUGRAPH_INVALID_INPUT, - "edge type id type of graph and lookup_container must match", - *error); + CAPI_EXPECTS( + reinterpret_cast(graph)->edge_type_id_type_ == + reinterpret_cast(lookup_container) + ->edge_type_id_type_, + CUGRAPH_INVALID_INPUT, + "edge type id type of graph and lookup_container must match", + *error); lookup_using_edge_ids_and_types_functor functor( handle, graph, lookup_container, edge_ids_to_lookup, edge_types_to_lookup); @@ -341,23 +344,26 @@ extern "C" cugraph_error_code_t cugraph_lookup_endpoints_from_edge_ids_and_singl { CAPI_EXPECTS( reinterpret_cast(graph)->vertex_type_ == - reinterpret_cast(lookup_container)->vertex_type_, + reinterpret_cast(lookup_container) + ->vertex_type_, CUGRAPH_INVALID_INPUT, "vertex type of graph and lookup_container must match", *error); CAPI_EXPECTS( reinterpret_cast(graph)->edge_type_ == - reinterpret_cast(lookup_container)->edge_type_, + reinterpret_cast(lookup_container) + ->edge_type_, CUGRAPH_INVALID_INPUT, "edge type of graph and lookup_container must match", *error); - CAPI_EXPECTS(reinterpret_cast(graph)->edge_type_id_type_ == - reinterpret_cast(lookup_container) - ->edge_type_id_type_, - CUGRAPH_INVALID_INPUT, - "edge type id type of graph and lookup_container must match", - *error); + CAPI_EXPECTS( + reinterpret_cast(graph)->edge_type_id_type_ == + reinterpret_cast(lookup_container) + ->edge_type_id_type_, + CUGRAPH_INVALID_INPUT, + "edge type id type of graph and lookup_container must match", + *error); lookup_using_edge_ids_of_single_type_functor functor( handle, graph, lookup_container, edge_ids_to_lookup, edge_type_to_lookup); @@ -387,3 +393,10 @@ extern "C" void cugraph_lookup_result_free(cugraph_lookup_result_t* result) delete internal_pointer->dsts_; delete internal_pointer; } + +extern "C" void cugraph_lookup_container_free(cugraph_lookup_container_t* container) +{ + auto internal_ptr = reinterpret_cast(container); + // The graph should presumably own the other structures. + delete internal_ptr; +} diff --git a/python/pylibcugraph/pylibcugraph/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/CMakeLists.txt index 9f1b9924336..3a53c7d16c3 100644 --- a/python/pylibcugraph/pylibcugraph/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/CMakeLists.txt @@ -65,6 +65,7 @@ set(cython_sources all_pairs_sorensen_coefficients.pyx all_pairs_overlap_coefficients.pyx all_pairs_cosine_coefficients.pyx + edge_id_lookup_table.pyx ) set(linked_libraries cugraph::cugraph;cugraph::cugraph_c) diff --git a/python/pylibcugraph/pylibcugraph/__init__.py b/python/pylibcugraph/pylibcugraph/__init__.py index 26fa3f64ddd..9c04a528fd8 100644 --- a/python/pylibcugraph/pylibcugraph/__init__.py +++ b/python/pylibcugraph/pylibcugraph/__init__.py @@ -21,6 +21,8 @@ from pylibcugraph.graph_properties import GraphProperties +from pylibcugraph.edge_id_lookup_table import EdgeIdLookupTable + from pylibcugraph.eigenvector_centrality import eigenvector_centrality from pylibcugraph.katz_centrality import katz_centrality diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/lookup_src_dst.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/lookup_src_dst.pxd index 710ca7d113b..e8a2bbf47ae 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/lookup_src_dst.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/lookup_src_dst.pxd @@ -70,3 +70,5 @@ cdef extern from "cugraph_c/lookup_src_dst.h": const cugraph_lookup_result_t* result) cdef void cugraph_lookup_result_free(cugraph_lookup_result_t* result) + + cdef void cugraph_lookup_container_free(cugraph_lookup_container_t* container) diff --git a/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pxd b/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pxd new file mode 100644 index 00000000000..9bbd19963a7 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pxd @@ -0,0 +1,34 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) +from pylibcugraph._cugraph_c.lookup_src_dst cimport ( + cugraph_lookup_container_t, +) +from pylibcugraph.resource_handle cimport ( + ResourceHandle, +) +from pylibcugraph.graphs cimport ( + _GPUGraph, +) + +cdef class EdgeIdLookupTable: + cdef ResourceHandle handle, + cdef _GPUGraph graph, + cdef cugraph_lookup_container_t* lookup_container_c_ptr diff --git a/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pyx b/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pyx new file mode 100644 index 00000000000..49ccdbdd168 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/edge_id_lookup_table.pyx @@ -0,0 +1,114 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.resource_handle cimport ( + cugraph_resource_handle_t, +) +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, + cugraph_type_erased_device_array_view_create, + cugraph_type_erased_device_array_view_free, + cugraph_type_erased_host_array_view_t, + cugraph_type_erased_host_array_view_create, + cugraph_type_erased_host_array_view_free, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) +from pylibcugraph._cugraph_c.lookup_src_dst cimport ( + cugraph_lookup_container_t, + cugraph_build_edge_id_and_type_to_src_dst_lookup_map, + cugraph_lookup_container_free, + cugraph_lookup_endpoints_from_edge_ids_and_single_type, + cugraph_lookup_result_t, +) +from pylibcugraph.utils cimport ( + assert_success, + assert_CAI_type, + assert_AI_type, + get_c_type_from_numpy_type, + create_cugraph_type_erased_device_array_view_from_py_obj +) +from pylibcugraph.resource_handle cimport ( + ResourceHandle, +) +from pylibcugraph.graphs cimport ( + _GPUGraph, +) +from pylibcugraph.internal_types.edge_id_lookup_result cimport ( + EdgeIdLookupResult, +) + +cdef class EdgeIdLookupTable: + def __cinit__(self, ResourceHandle resource_handle, _GPUGraph graph): + self.handle = resource_handle + self.graph = graph + + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_build_edge_id_and_type_to_src_dst_lookup_map( + self.handle.c_resource_handle_ptr, + self.graph.c_graph_ptr, + &self.lookup_container_c_ptr, + &error_ptr, + ) + + assert_success(error_code, error_ptr, "cugraph_build_edge_id_and_type_to_src_dst_lookup_map") + + def __dealloc__(self): + if self.lookup_container_c_ptr is not NULL: + cugraph_lookup_container_free(self.lookup_container_c_ptr) + + def lookup_vertex_ids( + self, + edge_ids, + int edge_type + ): + """ + For a single edge type, finds the source and destination vertex ids corresponding + to the provided edge ids. + """ + + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + cdef cugraph_lookup_result_t* result_ptr + + cdef cugraph_type_erased_device_array_view_t* edge_ids_c_ptr + edge_ids_c_ptr = create_cugraph_type_erased_device_array_view_from_py_obj(edge_ids) + + error_code = cugraph_lookup_endpoints_from_edge_ids_and_single_type( + self.handle.c_resource_handle_ptr, + self.graph.c_graph_ptr, + self.lookup_container_c_ptr, + edge_ids_c_ptr, + edge_type, + &result_ptr, + &error_ptr, + ) + + assert_success(error_code, error_ptr, "cugraph_lookup_endpoints_from_edge_ids_and_single_type") + + lr = EdgeIdLookupResult() + lr.set_ptr((result_ptr)) + return { + 'sources': lr.get_sources(), + 'destinations': lr.get_destinations(), + } diff --git a/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt index 22f07939db0..1b0d6ec71a4 100644 --- a/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/internal_types/CMakeLists.txt @@ -15,6 +15,7 @@ set(cython_sources sampling_result.pyx coo.pyx + edge_id_lookup_result.pyx ) set(linked_libraries cugraph::cugraph;cugraph::cugraph_c) diff --git a/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pxd b/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pxd new file mode 100644 index 00000000000..68dd2362a00 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pxd @@ -0,0 +1,30 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + + +from pylibcugraph._cugraph_c.lookup_src_dst cimport ( + cugraph_lookup_result_t +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, +) + +cdef class EdgeIdLookupResult: + cdef cugraph_lookup_result_t* result_c_ptr + + cdef get_array(self, cugraph_type_erased_device_array_view_t* ptr) + + cdef set_ptr(self, cugraph_lookup_result_t* ptr) diff --git a/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pyx b/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pyx new file mode 100644 index 00000000000..5f7165ce988 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/internal_types/edge_id_lookup_result.pyx @@ -0,0 +1,63 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.lookup_src_dst cimport ( + cugraph_lookup_result_t, + cugraph_lookup_result_free, + cugraph_lookup_result_get_dsts, + cugraph_lookup_result_get_srcs, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, +) +from pylibcugraph.utils cimport ( + create_cupy_array_view_for_device_ptr, +) + +cdef class EdgeIdLookupResult: + def __cinit__(self): + """ + Sets this object as the owner of the given pointer. + """ + self.result_c_ptr = NULL + + cdef set_ptr(self, cugraph_lookup_result_t* ptr): + self.result_c_ptr = ptr + + def __dealloc__(self): + if self.result_c_ptr is not NULL: + cugraph_lookup_result_free(self.result_c_ptr) + + cdef get_array(self, cugraph_type_erased_device_array_view_t* ptr): + if ptr is NULL: + return None + + return create_cupy_array_view_for_device_ptr( + ptr, + self, + ) + + def get_sources(self): + if self.result_c_ptr is NULL: + return None + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_lookup_result_get_srcs(self.result_c_ptr) + return self.get_array(ptr) + + def get_destinations(self): + if self.result_c_ptr is NULL: + return None + cdef cugraph_type_erased_device_array_view_t* ptr = cugraph_lookup_result_get_dsts(self.result_c_ptr) + return self.get_array(ptr) diff --git a/python/pylibcugraph/pylibcugraph/tests/test_lookup_table.py b/python/pylibcugraph/pylibcugraph/tests/test_lookup_table.py new file mode 100644 index 00000000000..2910a5f8d4d --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/tests/test_lookup_table.py @@ -0,0 +1,80 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cupy + +from pylibcugraph import ( + SGGraph, + ResourceHandle, + GraphProperties, + EdgeIdLookupTable, +) + + +# ============================================================================= +# Pytest fixtures +# ============================================================================= +# fixtures used in this test module are defined in conftest.py + + +# ============================================================================= +# Tests +# ============================================================================= + + +def test_lookup_table(): + # Vertex id array + vtcs = cupy.arange(6, dtype="int64") + + # Edge ids are unique per edge type and start from 0 + # Each edge type has the same src/dst vertex type here, + # just as it would in a GNN application. + srcs = cupy.array([0, 1, 5, 4, 3, 2, 2, 0, 5, 4, 4, 5]) + dsts = cupy.array([1, 5, 0, 3, 2, 1, 3, 3, 2, 3, 1, 4]) + etps = cupy.array([0, 2, 6, 7, 4, 3, 4, 1, 7, 7, 6, 8], dtype="int32") + eids = cupy.array([0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 1, 0]) + + wgts = cupy.ones((len(srcs),), dtype="float32") + + graph = SGGraph( + resource_handle=ResourceHandle(), + graph_properties=GraphProperties(is_symmetric=False, is_multigraph=True), + src_or_offset_array=srcs, + dst_or_index_array=dsts, + vertices_array=vtcs, + weight_array=wgts, + edge_id_array=eids, + edge_type_array=etps, + store_transposed=False, + renumber=False, + do_expensive_check=True, + ) + + table = EdgeIdLookupTable(ResourceHandle(), graph) + + assert table is not None + + found_edges = table.lookup_vertex_ids(cupy.array([0, 1, 2, 3, 4]), 7) + assert (found_edges["sources"] == cupy.array([4, 5, 4, -1, -1])).all() + assert (found_edges["destinations"] == cupy.array([3, 2, 3, -1, -1])).all() + + found_edges = table.lookup_vertex_ids(cupy.array([0]), 5) + assert (found_edges["sources"] == cupy.array([-1])).all() + assert (found_edges["destinations"] == cupy.array([-1])).all() + + found_edges = table.lookup_vertex_ids(cupy.array([3, 1, 0, 5]), 6) + assert (found_edges["sources"] == cupy.array([-1, 4, 5, -1])).all() + assert (found_edges["destinations"] == cupy.array([-1, 1, 0, -1])).all() + + # call __dealloc__() + del table From a9192a8575ee7e79d0b41d59382884cc7dfeabc4 Mon Sep 17 00:00:00 2001 From: Joseph Nke <76006812+jnke2016@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:55:02 +0100 Subject: [PATCH 37/37] Symmetrize edgelist when creating a CSR graph (#4716) This PR allows the edge list to be symmetrized when creating a graph from a CSR representation. closes #4693 Authors: - Joseph Nke (https://github.com/jnke2016) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/4716 --- cpp/src/c_api/graph_sg.cpp | 2 +- .../cugraph/tests/structure/test_graph.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/cpp/src/c_api/graph_sg.cpp b/cpp/src/c_api/graph_sg.cpp index e7ebbc2d319..e57d6b5bb14 100644 --- a/cpp/src/c_api/graph_sg.cpp +++ b/cpp/src/c_api/graph_sg.cpp @@ -761,7 +761,7 @@ cugraph_error_code_t cugraph_graph_create_sg_from_csr( p_edge_ids, p_edge_type_ids, renumber, - FALSE, // symmetrize + symmetrize, do_expensive_check); try { diff --git a/python/cugraph/cugraph/tests/structure/test_graph.py b/python/cugraph/cugraph/tests/structure/test_graph.py index 48a0b257b12..b3e517100e1 100644 --- a/python/cugraph/cugraph/tests/structure/test_graph.py +++ b/python/cugraph/cugraph/tests/structure/test_graph.py @@ -26,6 +26,7 @@ from cudf.testing import assert_series_equal from cudf.testing.testing import assert_frame_equal from cugraph.structure.symmetrize import symmetrize +from cugraph.datasets import karate_asymmetric # MG import dask_cudf @@ -204,6 +205,37 @@ def test_add_adj_list_to_edge_list(graph_file): compare_series(destinations_cu, destinations_exp) +@pytest.mark.sg +def test_create_undirected_graph_from_asymmetric_adj_list(): + # karate_asymmetric.get_path() + Mnx = utils.read_csv_for_nx(karate_asymmetric.get_path()) + N = max(max(Mnx["0"]), max(Mnx["1"])) + 1 + Mcsr = scipy.sparse.csr_matrix((Mnx.weight, (Mnx["0"], Mnx["1"])), shape=(N, N)) + + offsets = cudf.Series(Mcsr.indptr) + indices = cudf.Series(Mcsr.indices) + + G = cugraph.Graph(directed=False) + + with pytest.raises(Exception): + # Ifan undirected graph is created with 'symmetrize' set to False, the + # edgelist provided by the user must be symmetric. + G.from_cudf_adjlist(offsets, indices, None, symmetrize=False) + + G = cugraph.Graph(directed=False) + G.from_cudf_adjlist(offsets, indices, None, symmetrize=True) + + # FIXME: Since we have no mechanism to access the symmetrized edgelist + # from the graph_view_t, assert that the edgelist size is unchanged. Once + # exposing 'decompress_to_edgelist', ensure that + # G.number_of_edges() == 2 * karate_asymmetric.get_edgelist()? + assert G.number_of_edges() == len(karate_asymmetric.get_edgelist()) + + # FIXME: Once 'decompress_to_edgelist' is exposed to the + # python API, ensure that the derived edgelist is symmetric + # if symmetrize = True. + + # Test @pytest.mark.sg @pytest.mark.parametrize("graph_file", utils.DATASETS)