From 78a8f37921c505c32640eaa9e11a767595bf7142 Mon Sep 17 00:00:00 2001 From: Daniel Seemaier Date: Tue, 30 Apr 2024 16:35:52 +0200 Subject: [PATCH] refactor: move to the same interface as in the Shm code --- kaminpar-dist/coarsening/clusterer.h | 44 ++++ .../coarsening/clustering/clusterer.h | 39 ---- .../clustering/lp/global_lp_clusterer.cc | 42 ++-- .../clustering/lp/global_lp_clusterer.h | 8 +- .../clustering/lp/local_lp_clusterer.cc | 59 ++--- .../clustering/lp/local_lp_clusterer.h | 13 +- .../coarsening/clustering/noop_clusterer.h | 41 +--- .../coarsening/global_cluster_coarsener.cc | 205 ++++++++++++++++++ .../coarsening/global_cluster_coarsener.h | 62 ++++++ .../local_then_global_cluster_coarsener.cc | 205 ++++++++++++++++++ .../local_then_global_cluster_coarsener.h | 63 ++++++ kaminpar-dist/distributed_label_propagation.h | 111 +++------- kaminpar-shm/coarsening/clusterer.h | 4 +- 13 files changed, 680 insertions(+), 216 deletions(-) create mode 100644 kaminpar-dist/coarsening/clusterer.h delete mode 100644 kaminpar-dist/coarsening/clustering/clusterer.h create mode 100644 kaminpar-dist/coarsening/global_cluster_coarsener.cc create mode 100644 kaminpar-dist/coarsening/global_cluster_coarsener.h create mode 100644 kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc create mode 100644 kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h diff --git a/kaminpar-dist/coarsening/clusterer.h b/kaminpar-dist/coarsening/clusterer.h new file mode 100644 index 00000000..5466f1ac --- /dev/null +++ b/kaminpar-dist/coarsening/clusterer.h @@ -0,0 +1,44 @@ +/******************************************************************************* + * Interface for clustering algorithms. + * + * @file: clusterer.h + * @author: Daniel Seemaier + * @date: 29.09.21 + ******************************************************************************/ +#pragma once + +#include "kaminpar-dist/datastructures/distributed_graph.h" +#include "kaminpar-dist/dkaminpar.h" + +#include "kaminpar-common/datastructures/static_array.h" + +namespace kaminpar::dist { +class Clusterer { +public: + Clusterer() = default; + + Clusterer(const Clusterer &) = delete; + Clusterer &operator=(const Clusterer &) = delete; + + Clusterer(Clusterer &&) noexcept = default; + Clusterer &operator=(Clusterer &&) noexcept = default; + + virtual ~Clusterer() = default; + + // + // Optional options + // + + virtual void set_communities(const StaticArray & /* communities */) {} + virtual void clear_communities() {} + + virtual void set_max_cluster_weight(GlobalNodeWeight /* weight */) {} + + // + // Clustering function + // + + virtual void cluster(StaticArray &clustering, const DistributedGraph &graph) = 0; +}; +} // namespace kaminpar::dist + diff --git a/kaminpar-dist/coarsening/clustering/clusterer.h b/kaminpar-dist/coarsening/clustering/clusterer.h deleted file mode 100644 index 48b5d029..00000000 --- a/kaminpar-dist/coarsening/clustering/clusterer.h +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Interface for clustering algorithms. - * - * @file: clusterer.h - * @author: Daniel Seemaier - * @date: 29.09.21 - ******************************************************************************/ -#pragma once - -#include "kaminpar-dist/datastructures/distributed_graph.h" -#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" -#include "kaminpar-dist/dkaminpar.h" - -#include "kaminpar-common/datastructures/noinit_vector.h" - -namespace kaminpar::dist { -template class Clusterer { -public: - using ClusterArray = NoinitVector; - - virtual ~Clusterer() = default; - - virtual void initialize(const DistributedGraph &graph) = 0; - - virtual ClusterArray & - cluster(const DistributedGraph &graph, GlobalNodeWeight max_cluster_weight) = 0; -}; - -using GlobalClusterer = Clusterer; - -class LocalClusterer : public Clusterer { -public: - virtual ClusterArray & - cluster(const DistributedGraph &graph, GlobalNodeWeight max_cluster_weight) = 0; - - virtual ClusterArray & - cluster(const DistributedPartitionedGraph &p_graph, GlobalNodeWeight max_cluster_weight) = 0; -}; -} // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.cc b/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.cc index 1cf875bf..ddbc9596 100644 --- a/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.cc +++ b/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.cc @@ -42,7 +42,7 @@ struct UnorderedRatingMap { return std::numeric_limits::max(); } - void resize(const std::size_t /* capacity */) {} + void resize(std::size_t /* capacity */) {} google::dense_hash_map map{}; }; @@ -62,17 +62,16 @@ struct GlobalLPClusteringConfig : public LabelPropagationConfig { class GlobalLPClusteringImpl final : public ChunkRandomdLabelPropagation, - public NonatomicOwnedClusterVector { + public NonatomicClusterVectorRef { SET_DEBUG(false); using Base = ChunkRandomdLabelPropagation; - using ClusterBase = NonatomicOwnedClusterVector; + using ClusterBase = NonatomicClusterVectorRef; using WeightDeltaMap = growt::GlobalNodeIDMap; public: explicit GlobalLPClusteringImpl(const Context &ctx) - : ClusterBase{ctx.partition.graph->total_n}, - _ctx(ctx), + : _ctx(ctx), _c_ctx(ctx.coarsening), _changed_label(ctx.partition.graph->n), _cluster_weights(ctx.partition.graph->total_n - ctx.partition.graph->n), @@ -113,15 +112,16 @@ class GlobalLPClusteringImpl final TIMER_BARRIER(graph.communicator()); } - auto & - compute_clustering(const DistributedGraph &graph, const GlobalNodeWeight max_cluster_weight) { + void set_max_cluster_weight(const GlobalNodeWeight weight) { + _max_cluster_weight = weight; + } + + void compute_clustering(StaticArray &clustering, const DistributedGraph &graph) { TIMER_BARRIER(graph.communicator()); SCOPED_TIMER("Label propagation"); - _max_cluster_weight = max_cluster_weight; - - // Ensure that the clustering algorithm was properly initialized - KASSERT(_graph == &graph, "must call initialize() before compute_clustering()", assert::always); + init_clusters_ref(clustering); + initialize(graph); const int num_chunks = _c_ctx.global_lp.chunks.compute(_ctx.parallel); @@ -135,8 +135,6 @@ class GlobalLPClusteringImpl final break; } } - - return clusters(); } void set_max_num_iterations(const int max_num_iterations) { @@ -260,7 +258,7 @@ class GlobalLPClusteringImpl final void move_node(const NodeID lu, const ClusterID gcluster) { KASSERT(lu < _changed_label.size()); _changed_label[lu] = this->cluster(lu); - NonatomicOwnedClusterVector::move_node(lu, gcluster); + NonatomicClusterVectorRef::move_node(lu, gcluster); // Detect if a node was moved back to its original cluster if (_c_ctx.global_lp.prevent_cyclic_moves && gcluster == initial_cluster(lu)) { @@ -347,8 +345,6 @@ class GlobalLPClusteringImpl final } void allocate(const DistributedGraph &graph) { - ensure_cluster_size(graph.total_n()); - const NodeID allocated_num_active_nodes = _changed_label.size(); if (allocated_num_active_nodes < graph.n()) { @@ -568,7 +564,7 @@ class GlobalLPClusteringImpl final weight_delta_handle.find(old_gcluster + 1) == weight_delta_handle.end()) { change_cluster_weight(old_gcluster, -weight, true); } - NonatomicOwnedClusterVector::move_node(lnode, new_gcluster); + NonatomicClusterVectorRef::move_node(lnode, new_gcluster); if (!should_sync_cluster_weights() || weight_delta_handle.find(new_gcluster + 1) == weight_delta_handle.end()) { change_cluster_weight(new_gcluster, weight, false); @@ -610,7 +606,7 @@ class GlobalLPClusteringImpl final if (current != kInvalidNodeID && current_weight + u_weight <= max_cluster_weight(u_cluster)) { change_cluster_weight(current_cluster, u_weight, true); - NonatomicOwnedClusterVector::move_node(u, current_cluster); + NonatomicClusterVectorRef::move_node(u, current_cluster); current_weight += u_weight; } else { current = u; @@ -679,13 +675,13 @@ GlobalLPClusterer::GlobalLPClusterer(const Context &ctx) GlobalLPClusterer::~GlobalLPClusterer() = default; -void GlobalLPClusterer::initialize(const DistributedGraph &graph) { - _impl->initialize(graph); +void GlobalLPClusterer::set_max_cluster_weight(const GlobalNodeWeight weight) { + _impl->set_max_cluster_weight(weight); } -GlobalLPClusterer::ClusterArray &GlobalLPClusterer::cluster( - const DistributedGraph &graph, const GlobalNodeWeight max_cluster_weight +void GlobalLPClusterer::cluster( + StaticArray &clustering, const DistributedGraph &graph ) { - return _impl->compute_clustering(graph, max_cluster_weight); + _impl->compute_clustering(clustering, graph); } } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.h b/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.h index e4481f5b..e02aa7b5 100644 --- a/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.h +++ b/kaminpar-dist/coarsening/clustering/lp/global_lp_clusterer.h @@ -7,12 +7,12 @@ ******************************************************************************/ #pragma once -#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/clusterer.h" #include "kaminpar-dist/context.h" #include "kaminpar-dist/datastructures/distributed_graph.h" namespace kaminpar::dist { -class GlobalLPClusterer : public Clusterer { +class GlobalLPClusterer : public Clusterer { public: explicit GlobalLPClusterer(const Context &ctx); @@ -24,9 +24,9 @@ class GlobalLPClusterer : public Clusterer { ~GlobalLPClusterer() override; - void initialize(const DistributedGraph &graph) final; + void set_max_cluster_weight(GlobalNodeWeight weight) final; - ClusterArray &cluster(const DistributedGraph &graph, GlobalNodeWeight max_cluster_weight) final; + void cluster(StaticArray &clustering, const DistributedGraph &graph) final; private: std::unique_ptr _impl; diff --git a/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.cc b/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.cc index 3a4e279a..30f4e81d 100644 --- a/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.cc +++ b/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.cc @@ -21,20 +21,19 @@ struct LocalLPClusteringConfig : public LabelPropagationConfig { class LocalLPClusteringImpl final : public ChunkRandomdLabelPropagation, - public NonatomicOwnedClusterVector, + public NonatomicClusterVectorRef, public OwnedRelaxedClusterWeightVector { SET_DEBUG(false); using Base = ChunkRandomdLabelPropagation; - using ClusterBase = NonatomicOwnedClusterVector; + using ClusterBase = NonatomicClusterVectorRef; using ClusterWeightBase = OwnedRelaxedClusterWeightVector; public: LocalLPClusteringImpl(const NodeID max_n, const CoarseningContext &c_ctx) - : ClusterBase(max_n), - ClusterWeightBase{max_n}, - _ignore_ghost_nodes(c_ctx.local_lp.ignore_ghost_nodes), + : _ignore_ghost_nodes(c_ctx.local_lp.ignore_ghost_nodes), _keep_ghost_clusters(c_ctx.local_lp.keep_ghost_clusters) { + allocate_cluster_weights(max_n); allocate(max_n, max_n); set_max_num_iterations(c_ctx.local_lp.num_iterations); set_max_degree(c_ctx.local_lp.active_high_degree_threshold); @@ -45,9 +44,13 @@ class LocalLPClusteringImpl final Base::initialize(&graph, graph.n()); } - auto & - compute_clustering(const DistributedGraph &graph, const GlobalNodeWeight max_cluster_weight) { + void set_max_cluster_weight(const GlobalNodeWeight max_cluster_weight) { _max_cluster_weight = max_cluster_weight; + } + + void compute_clustering(StaticArray &clustering, const DistributedGraph &graph) { + init_clusters_ref(clustering); + initialize(graph); // initialize ghost nodes if (!_ignore_ghost_nodes) { @@ -89,15 +92,6 @@ class LocalLPClusteringImpl final }); } } - - return clusters(); - } - - auto &compute_clustering( - const DistributedPartitionedGraph &p_graph, const GlobalNodeWeight max_cluster_weight - ) { - _partition = p_graph.partition().data(); - return compute_clustering(p_graph.graph(), max_cluster_weight); } void set_max_num_iterations(const std::size_t max_num_iterations) { @@ -147,7 +141,7 @@ class LocalLPClusteringImpl final } using Base::_graph; - NodeWeight _max_cluster_weight; + NodeWeight _max_cluster_weight = std::numeric_limits::max(); std::size_t _max_num_iterations; bool _ignore_ghost_nodes; bool _keep_ghost_clusters; @@ -160,27 +154,36 @@ class LocalLPClusteringImpl final // LocalLPClusterer::LocalLPClusterer(const Context &ctx) - : _impl{std::make_unique( + : _impl(std::make_unique( ctx.coarsening.local_lp.ignore_ghost_nodes ? ctx.partition.graph->n : ctx.partition.graph->total_n, ctx.coarsening - )} {} + )) {} LocalLPClusterer::~LocalLPClusterer() = default; -void LocalLPClusterer::initialize(const DistributedGraph &graph) { - _impl->initialize(graph); +void LocalLPClusterer::set_communities(const StaticArray &communities) { + _impl->_partition = communities.data(); } -LocalLPClusterer::ClusterArray &LocalLPClusterer::cluster( - const DistributedGraph &graph, const GlobalNodeWeight max_cluster_weight -) { - return _impl->compute_clustering(graph, max_cluster_weight); +void LocalLPClusterer::clear_communities() { + _impl->_partition = nullptr; +} + +void LocalLPClusterer::set_max_cluster_weight(GlobalNodeWeight weight) { + _impl->set_max_cluster_weight(weight); } -LocalLPClusterer::ClusterArray &LocalLPClusterer::cluster( - const DistributedPartitionedGraph &p_graph, const GlobalNodeWeight max_cluster_weight +void LocalLPClusterer::cluster( + StaticArray &global_clustering, const DistributedGraph &p_graph ) { - return _impl->compute_clustering(p_graph, max_cluster_weight); + static_assert(sizeof(GlobalNodeID) % sizeof(NodeID) == 0, "Size mismatch"); + GlobalNodeID *raw_global_clustering = global_clustering.data(); + NodeID *raw_local_clustering = reinterpret_cast(raw_global_clustering); + StaticArray local_clustering( + sizeof(GlobalNodeID) / sizeof(NodeID) * global_clustering.size(), raw_local_clustering + ); + + return _impl->compute_clustering(local_clustering, p_graph); } } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.h b/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.h index 631b77bf..f19baba0 100644 --- a/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.h +++ b/kaminpar-dist/coarsening/clustering/lp/local_lp_clusterer.h @@ -8,13 +8,12 @@ ******************************************************************************/ #pragma once -#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/clusterer.h" #include "kaminpar-dist/context.h" #include "kaminpar-dist/datastructures/distributed_graph.h" -#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" namespace kaminpar::dist { -class LocalLPClusterer : public LocalClusterer { +class LocalLPClusterer : public Clusterer { public: explicit LocalLPClusterer(const Context &ctx); @@ -26,12 +25,12 @@ class LocalLPClusterer : public LocalClusterer { ~LocalLPClusterer() override; - void initialize(const DistributedGraph &graph) final; + void set_communities(const StaticArray &communities) final; + void clear_communities() final; - ClusterArray &cluster(const DistributedGraph &graph, GlobalNodeWeight max_cluster_weight) final; + void set_max_cluster_weight(GlobalNodeWeight weight) final; - ClusterArray & - cluster(const DistributedPartitionedGraph &graph, GlobalNodeWeight max_cluster_weight) final; + void cluster(StaticArray &clustering, const DistributedGraph &graph) final; private: std::unique_ptr _impl; diff --git a/kaminpar-dist/coarsening/clustering/noop_clusterer.h b/kaminpar-dist/coarsening/clustering/noop_clusterer.h index 35089475..255e4e96 100644 --- a/kaminpar-dist/coarsening/clustering/noop_clusterer.h +++ b/kaminpar-dist/coarsening/clustering/noop_clusterer.h @@ -7,43 +7,16 @@ ******************************************************************************/ #pragma once -#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/clusterer.h" #include "kaminpar-dist/context.h" +#include "kaminpar-dist/datastructures/distributed_graph.h" -namespace kaminpar::dist { -class GlobalNoopClustering : public Clusterer { - using ClusterArray = typename Clusterer::ClusterArray; - -public: - explicit GlobalNoopClustering(const Context &) {} - - void initialize(const DistributedGraph &) final {} - - ClusterArray &cluster(const DistributedGraph &, GlobalNodeWeight) final { - return _empty_clustering; - } - -protected: - ClusterArray _empty_clustering; -}; - -class LocalNoopClustering : public LocalClusterer { - using ClusterArray = typename Clusterer::ClusterArray; +#include "kaminpar-common/datastructures/static_array.h" +namespace kaminpar::dist { +class NoopClustering : public Clusterer { public: - explicit LocalNoopClustering(const Context &) {} - - void initialize(const DistributedGraph &) final {} - - ClusterArray &cluster(const DistributedGraph &, GlobalNodeWeight) final { - return _empty_clustering; - } - - ClusterArray &cluster(const DistributedPartitionedGraph &, GlobalNodeWeight) final { - return _empty_clustering; - } - -private: - ClusterArray _empty_clustering; + void cluster(StaticArray & /* clustering */, const DistributedGraph & /* graph */) + final {} }; } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/global_cluster_coarsener.cc b/kaminpar-dist/coarsening/global_cluster_coarsener.cc new file mode 100644 index 00000000..96fbbc64 --- /dev/null +++ b/kaminpar-dist/coarsening/global_cluster_coarsener.cc @@ -0,0 +1,205 @@ +/******************************************************************************* + * Builds and manages a hierarchy of coarse graphs. + * + * @file: coarsener.cc + * @author: Daniel Seemaier + * @date: 28.04.2022 + ******************************************************************************/ +#include "kaminpar-dist/coarsening/coarsener.h" + +#include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" +#include "kaminpar-dist/coarsening/contraction/local_cluster_contraction.h" +#include "kaminpar-dist/datastructures/distributed_graph.h" +#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" +#include "kaminpar-dist/factories.h" + +#include "kaminpar-shm/coarsening/max_cluster_weights.h" + +#include "kaminpar-common/logger.h" + +namespace kaminpar::dist { +SET_DEBUG(false); + +Coarsener::Coarsener(const DistributedGraph &input_graph, const Context &input_ctx) + : _input_graph(input_graph), + _input_ctx(input_ctx), + _global_clusterer(factory::create_global_clusterer(_input_ctx)), + _local_clusterer(factory::create_local_clusterer(_input_ctx)) {} + +const DistributedGraph *Coarsener::coarsen_once() { + return coarsen_once(max_cluster_weight()); +} + +const DistributedGraph *Coarsener::coarsen_once_local(const GlobalNodeWeight max_cluster_weight) { + DBG << "Coarsen graph using local clustering algorithm ..."; + const DistributedGraph *graph = coarsest(); + + _local_clusterer->initialize(*graph); + auto &clustering = _local_clusterer->cluster(*graph, max_cluster_weight); + if (clustering.empty()) { + DBG << "... converged with empty clustering"; + return graph; + } + + ScalableVector> legacy_clustering(clustering.begin(), clustering.end()); + auto [c_graph, mapping, m_ctx] = contract_local_clustering(*graph, legacy_clustering); + KASSERT(debug::validate_graph(c_graph), "", assert::heavy); + DBG << "Reduced number of nodes from " << graph->global_n() << " to " << c_graph.global_n(); + + if (!has_converged(*graph, c_graph)) { + DBG << "... success"; + + _graph_hierarchy.push_back(std::move(c_graph)); + _local_mapping_hierarchy.push_back(std::move(mapping)); + return coarsest(); + } + + DBG << "... converged due to insufficient shrinkage"; + return graph; +} + +const DistributedGraph *Coarsener::coarsen_once_global(const GlobalNodeWeight max_cluster_weight) { + DBG << "Coarsen graph using global clustering algorithm ..."; + + const DistributedGraph *graph = coarsest(); + + // Call global clustering algorithm + _global_clusterer->initialize(*graph); + auto &clustering = + _global_clusterer->cluster(*graph, static_cast(max_cluster_weight)); + + bool empty = clustering.empty(); + MPI_Allreduce(MPI_IN_PLACE, &empty, 1, MPI_CXX_BOOL, MPI_LAND, graph->communicator()); + if (empty) { // Empty --> converged + DBG << "... converged with empty clustering"; + return graph; + } + + // Construct the coarse graph + auto result = contract_clustering(*graph, clustering, _input_ctx.coarsening); + + KASSERT(debug::validate_graph(result.graph), "", assert::heavy); + DBG << "Reduced number of nodes from " << graph->global_n() << " to " << result.graph.global_n(); + + // Only keep graph if coarsening has not converged yet + if (!has_converged(*graph, result.graph)) { + DBG << "... success"; + + _graph_hierarchy.push_back(std::move(result.graph)); + _global_mapping_hierarchy.push_back(std::move(result.mapping)); + _node_migration_history.push_back(std::move(result.migration)); + + return coarsest(); + } + + DBG << "... converged due to insufficient shrinkage"; + return graph; +} + +const DistributedGraph *Coarsener::coarsen_once(const GlobalNodeWeight max_cluster_weight) { + const DistributedGraph *graph = coarsest(); + + if (level() >= _input_ctx.coarsening.max_global_clustering_levels) { + return graph; + } else if (level() == _input_ctx.coarsening.max_local_clustering_levels) { + _local_clustering_converged = true; + } + + if (!_local_clustering_converged) { + const DistributedGraph *c_graph = coarsen_once_local(max_cluster_weight); + if (c_graph == graph) { + _local_clustering_converged = true; + // no return -> try global clustering right away + } else { + return c_graph; + } + } + + return coarsen_once_global(max_cluster_weight); +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once(DistributedPartitionedGraph &&p_graph) { + KASSERT(coarsest() == &p_graph.graph(), "expected graph partition of current coarsest graph"); + KASSERT(!_global_mapping_hierarchy.empty() || !_local_mapping_hierarchy.empty()); + + if (!_global_mapping_hierarchy.empty()) { + return uncoarsen_once_global(std::move(p_graph)); + } + + return uncoarsen_once_local(std::move(p_graph)); +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once_local(DistributedPartitionedGraph &&p_graph) { + KASSERT(!_local_mapping_hierarchy.empty(), "", assert::light); + + auto block_weights = p_graph.take_block_weights(); + const DistributedGraph *new_coarsest = nth_coarsest(1); + const auto &mapping = _local_mapping_hierarchy.back(); + + StaticArray partition(new_coarsest->total_n()); + new_coarsest->pfor_all_nodes([&](const NodeID u) { partition[u] = p_graph.block(mapping[u]); }); + const BlockID k = p_graph.k(); + + _local_mapping_hierarchy.pop_back(); + _graph_hierarchy.pop_back(); + + return {coarsest(), k, std::move(partition), std::move(block_weights)}; +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once_global(DistributedPartitionedGraph &&p_graph +) { + const DistributedGraph *new_coarsest = nth_coarsest(1); + + p_graph = project_partition( + *new_coarsest, + std::move(p_graph), + _global_mapping_hierarchy.back(), + _node_migration_history.back() + ); + + KASSERT( + debug::validate_partition(p_graph), + "invalid partition after projection to finer graph", + assert::heavy + ); + + _graph_hierarchy.pop_back(); + _global_mapping_hierarchy.pop_back(); + _node_migration_history.pop_back(); + + // if pop_back() on _graph_hierarchy caused a reallocation, the graph pointer + // in p_graph dangles + p_graph.UNSAFE_set_graph(coarsest()); + + return std::move(p_graph); +} + +bool Coarsener::has_converged(const DistributedGraph &before, const DistributedGraph &after) const { + return 1.0 * after.global_n() / before.global_n() >= 0.95; +} + +const DistributedGraph *Coarsener::coarsest() const { + return nth_coarsest(0); +} + +std::size_t Coarsener::level() const { + return _graph_hierarchy.size(); +} + +const DistributedGraph *Coarsener::nth_coarsest(const std::size_t n) const { + return _graph_hierarchy.size() > n ? &_graph_hierarchy[_graph_hierarchy.size() - n - 1] + : &_input_graph; +} + +GlobalNodeWeight Coarsener::max_cluster_weight() const { + const auto *graph = coarsest(); + + return shm::compute_max_cluster_weight( + _input_ctx.coarsening, + _input_ctx.partition, + graph->global_n(), + graph->global_total_node_weight() + ); +} +} // namespace kaminpar::dist + diff --git a/kaminpar-dist/coarsening/global_cluster_coarsener.h b/kaminpar-dist/coarsening/global_cluster_coarsener.h new file mode 100644 index 00000000..5ed6a3d2 --- /dev/null +++ b/kaminpar-dist/coarsening/global_cluster_coarsener.h @@ -0,0 +1,62 @@ +/******************************************************************************* + * Builds and manages a hierarchy of coarse graphs. + * + * @file: coarsener.h + * @author: Daniel Seemaier + * @date: 28.04.2022 + ******************************************************************************/ +#pragma once + +#include + +#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" +#include "kaminpar-dist/context.h" +#include "kaminpar-dist/datastructures/distributed_graph.h" +#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" +#include "kaminpar-dist/dkaminpar.h" + +#include "kaminpar-common/datastructures/scalable_vector.h" + +namespace kaminpar::dist { +class Coarsener { +public: + Coarsener(const DistributedGraph &input_graph, const Context &input_ctx); + + const DistributedGraph *coarsen_once(); + + const DistributedGraph *coarsen_once(GlobalNodeWeight max_cluster_weight); + + DistributedPartitionedGraph uncoarsen_once(DistributedPartitionedGraph &&p_graph); + + GlobalNodeWeight max_cluster_weight() const; + const DistributedGraph *coarsest() const; + std::size_t level() const; + +private: + const DistributedGraph *coarsen_once_local(GlobalNodeWeight max_cluster_weight); + const DistributedGraph *coarsen_once_global(GlobalNodeWeight max_cluster_weight); + + DistributedPartitionedGraph uncoarsen_once_local(DistributedPartitionedGraph &&p_graph); + DistributedPartitionedGraph uncoarsen_once_global(DistributedPartitionedGraph &&p_graph); + + const DistributedGraph *nth_coarsest(std::size_t n) const; + + bool has_converged(const DistributedGraph &before, const DistributedGraph &after) const; + + const DistributedGraph &_input_graph; + const Context &_input_ctx; + + std::unique_ptr _global_clusterer; + std::unique_ptr _local_clusterer; + + std::vector _graph_hierarchy; + std::vector _global_mapping_hierarchy; //< produced by global clustering algorithm + std::vector _node_migration_history; + std::vector> + _local_mapping_hierarchy; //< produced by local clustering_algorithm + + bool _local_clustering_converged = false; +}; +} // namespace kaminpar::dist + diff --git a/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc new file mode 100644 index 00000000..96fbbc64 --- /dev/null +++ b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc @@ -0,0 +1,205 @@ +/******************************************************************************* + * Builds and manages a hierarchy of coarse graphs. + * + * @file: coarsener.cc + * @author: Daniel Seemaier + * @date: 28.04.2022 + ******************************************************************************/ +#include "kaminpar-dist/coarsening/coarsener.h" + +#include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" +#include "kaminpar-dist/coarsening/contraction/local_cluster_contraction.h" +#include "kaminpar-dist/datastructures/distributed_graph.h" +#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" +#include "kaminpar-dist/factories.h" + +#include "kaminpar-shm/coarsening/max_cluster_weights.h" + +#include "kaminpar-common/logger.h" + +namespace kaminpar::dist { +SET_DEBUG(false); + +Coarsener::Coarsener(const DistributedGraph &input_graph, const Context &input_ctx) + : _input_graph(input_graph), + _input_ctx(input_ctx), + _global_clusterer(factory::create_global_clusterer(_input_ctx)), + _local_clusterer(factory::create_local_clusterer(_input_ctx)) {} + +const DistributedGraph *Coarsener::coarsen_once() { + return coarsen_once(max_cluster_weight()); +} + +const DistributedGraph *Coarsener::coarsen_once_local(const GlobalNodeWeight max_cluster_weight) { + DBG << "Coarsen graph using local clustering algorithm ..."; + const DistributedGraph *graph = coarsest(); + + _local_clusterer->initialize(*graph); + auto &clustering = _local_clusterer->cluster(*graph, max_cluster_weight); + if (clustering.empty()) { + DBG << "... converged with empty clustering"; + return graph; + } + + ScalableVector> legacy_clustering(clustering.begin(), clustering.end()); + auto [c_graph, mapping, m_ctx] = contract_local_clustering(*graph, legacy_clustering); + KASSERT(debug::validate_graph(c_graph), "", assert::heavy); + DBG << "Reduced number of nodes from " << graph->global_n() << " to " << c_graph.global_n(); + + if (!has_converged(*graph, c_graph)) { + DBG << "... success"; + + _graph_hierarchy.push_back(std::move(c_graph)); + _local_mapping_hierarchy.push_back(std::move(mapping)); + return coarsest(); + } + + DBG << "... converged due to insufficient shrinkage"; + return graph; +} + +const DistributedGraph *Coarsener::coarsen_once_global(const GlobalNodeWeight max_cluster_weight) { + DBG << "Coarsen graph using global clustering algorithm ..."; + + const DistributedGraph *graph = coarsest(); + + // Call global clustering algorithm + _global_clusterer->initialize(*graph); + auto &clustering = + _global_clusterer->cluster(*graph, static_cast(max_cluster_weight)); + + bool empty = clustering.empty(); + MPI_Allreduce(MPI_IN_PLACE, &empty, 1, MPI_CXX_BOOL, MPI_LAND, graph->communicator()); + if (empty) { // Empty --> converged + DBG << "... converged with empty clustering"; + return graph; + } + + // Construct the coarse graph + auto result = contract_clustering(*graph, clustering, _input_ctx.coarsening); + + KASSERT(debug::validate_graph(result.graph), "", assert::heavy); + DBG << "Reduced number of nodes from " << graph->global_n() << " to " << result.graph.global_n(); + + // Only keep graph if coarsening has not converged yet + if (!has_converged(*graph, result.graph)) { + DBG << "... success"; + + _graph_hierarchy.push_back(std::move(result.graph)); + _global_mapping_hierarchy.push_back(std::move(result.mapping)); + _node_migration_history.push_back(std::move(result.migration)); + + return coarsest(); + } + + DBG << "... converged due to insufficient shrinkage"; + return graph; +} + +const DistributedGraph *Coarsener::coarsen_once(const GlobalNodeWeight max_cluster_weight) { + const DistributedGraph *graph = coarsest(); + + if (level() >= _input_ctx.coarsening.max_global_clustering_levels) { + return graph; + } else if (level() == _input_ctx.coarsening.max_local_clustering_levels) { + _local_clustering_converged = true; + } + + if (!_local_clustering_converged) { + const DistributedGraph *c_graph = coarsen_once_local(max_cluster_weight); + if (c_graph == graph) { + _local_clustering_converged = true; + // no return -> try global clustering right away + } else { + return c_graph; + } + } + + return coarsen_once_global(max_cluster_weight); +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once(DistributedPartitionedGraph &&p_graph) { + KASSERT(coarsest() == &p_graph.graph(), "expected graph partition of current coarsest graph"); + KASSERT(!_global_mapping_hierarchy.empty() || !_local_mapping_hierarchy.empty()); + + if (!_global_mapping_hierarchy.empty()) { + return uncoarsen_once_global(std::move(p_graph)); + } + + return uncoarsen_once_local(std::move(p_graph)); +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once_local(DistributedPartitionedGraph &&p_graph) { + KASSERT(!_local_mapping_hierarchy.empty(), "", assert::light); + + auto block_weights = p_graph.take_block_weights(); + const DistributedGraph *new_coarsest = nth_coarsest(1); + const auto &mapping = _local_mapping_hierarchy.back(); + + StaticArray partition(new_coarsest->total_n()); + new_coarsest->pfor_all_nodes([&](const NodeID u) { partition[u] = p_graph.block(mapping[u]); }); + const BlockID k = p_graph.k(); + + _local_mapping_hierarchy.pop_back(); + _graph_hierarchy.pop_back(); + + return {coarsest(), k, std::move(partition), std::move(block_weights)}; +} + +DistributedPartitionedGraph Coarsener::uncoarsen_once_global(DistributedPartitionedGraph &&p_graph +) { + const DistributedGraph *new_coarsest = nth_coarsest(1); + + p_graph = project_partition( + *new_coarsest, + std::move(p_graph), + _global_mapping_hierarchy.back(), + _node_migration_history.back() + ); + + KASSERT( + debug::validate_partition(p_graph), + "invalid partition after projection to finer graph", + assert::heavy + ); + + _graph_hierarchy.pop_back(); + _global_mapping_hierarchy.pop_back(); + _node_migration_history.pop_back(); + + // if pop_back() on _graph_hierarchy caused a reallocation, the graph pointer + // in p_graph dangles + p_graph.UNSAFE_set_graph(coarsest()); + + return std::move(p_graph); +} + +bool Coarsener::has_converged(const DistributedGraph &before, const DistributedGraph &after) const { + return 1.0 * after.global_n() / before.global_n() >= 0.95; +} + +const DistributedGraph *Coarsener::coarsest() const { + return nth_coarsest(0); +} + +std::size_t Coarsener::level() const { + return _graph_hierarchy.size(); +} + +const DistributedGraph *Coarsener::nth_coarsest(const std::size_t n) const { + return _graph_hierarchy.size() > n ? &_graph_hierarchy[_graph_hierarchy.size() - n - 1] + : &_input_graph; +} + +GlobalNodeWeight Coarsener::max_cluster_weight() const { + const auto *graph = coarsest(); + + return shm::compute_max_cluster_weight( + _input_ctx.coarsening, + _input_ctx.partition, + graph->global_n(), + graph->global_total_node_weight() + ); +} +} // namespace kaminpar::dist + diff --git a/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h new file mode 100644 index 00000000..5b551e26 --- /dev/null +++ b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h @@ -0,0 +1,63 @@ +/******************************************************************************* + * Builds and manages a hierarchy of coarse graphs. + * + * @file: coarsener.h + * @author: Daniel Seemaier + * @date: 28.04.2022 + ******************************************************************************/ +#pragma once + +#include + +#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" +#include "kaminpar-dist/context.h" +#include "kaminpar-dist/datastructures/distributed_graph.h" +#include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" +#include "kaminpar-dist/dkaminpar.h" + +#include "kaminpar-common/datastructures/scalable_vector.h" + +namespace kaminpar::dist { +class Coarsener { +public: + Coarsener(const DistributedGraph &input_graph, const Context &input_ctx); + + const DistributedGraph *coarsen_once(); + + const DistributedGraph *coarsen_once(GlobalNodeWeight max_cluster_weight); + + DistributedPartitionedGraph uncoarsen_once(DistributedPartitionedGraph &&p_graph); + + GlobalNodeWeight max_cluster_weight() const; + const DistributedGraph *coarsest() const; + std::size_t level() const; + +private: + const DistributedGraph *coarsen_once_local(GlobalNodeWeight max_cluster_weight); + const DistributedGraph *coarsen_once_global(GlobalNodeWeight max_cluster_weight); + + DistributedPartitionedGraph uncoarsen_once_local(DistributedPartitionedGraph &&p_graph); + DistributedPartitionedGraph uncoarsen_once_global(DistributedPartitionedGraph &&p_graph); + + const DistributedGraph *nth_coarsest(std::size_t n) const; + + bool has_converged(const DistributedGraph &before, const DistributedGraph &after) const; + + const DistributedGraph &_input_graph; + const Context &_input_ctx; + + std::unique_ptr _global_clusterer; + std::unique_ptr _local_clusterer; + + std::vector _graph_hierarchy; + std::vector _global_mapping_hierarchy; //< produced by global clustering algorithm + std::vector _node_migration_history; + std::vector> + _local_mapping_hierarchy; //< produced by local clustering_algorithm + + bool _local_clustering_converged = false; +}; +} // namespace kaminpar::dist + + diff --git a/kaminpar-dist/distributed_label_propagation.h b/kaminpar-dist/distributed_label_propagation.h index 623cd1c9..22c6989b 100644 --- a/kaminpar-dist/distributed_label_propagation.h +++ b/kaminpar-dist/distributed_label_propagation.h @@ -1198,85 +1198,14 @@ class ChunkRandomdLabelPropagation : public LabelPropagation { std::vector _buckets; }; -template class NonatomicOwnedClusterVector { -public: - explicit NonatomicOwnedClusterVector(const NodeID max_num_nodes) : _clusters(max_num_nodes) { - tbb::parallel_for(0, max_num_nodes, [&](const NodeID u) { _clusters[u] = 0; }); - } - - [[nodiscard]] auto &&take_clusters() { - return std::move(_clusters); - } - - [[nodiscard]] auto &clusters() { - return _clusters; - } - - void init_cluster(const NodeID node, const ClusterID cluster) { - move_node(node, cluster); - } - - [[nodiscard]] ClusterID cluster(const NodeID node) { - KASSERT(node < _clusters.size()); - return __atomic_load_n(&_clusters[node], __ATOMIC_RELAXED); - } - - void move_node(const NodeID node, const ClusterID cluster) { - KASSERT(node < _clusters.size()); - __atomic_store_n(&_clusters[node], cluster, __ATOMIC_RELAXED); - } - - void ensure_cluster_size(const NodeID max_num_nodes) { - if (_clusters.size() < max_num_nodes) { - _clusters.resize(max_num_nodes); - } - } - -private: - NoinitVector _clusters; -}; - -template class OwnedClusterVector { +template class OwnedRelaxedClusterWeightVector { public: - explicit OwnedClusterVector(const NodeID max_num_nodes) : _clusters(max_num_nodes) {} - - [[nodiscard]] auto &&take_clusters() { - return std::move(_clusters); - } - - [[nodiscard]] auto &clusters() { - return _clusters; - } - - void init_cluster(const NodeID node, const ClusterID cluster) { - _clusters[node] = cluster; - } - - [[nodiscard]] ClusterID cluster(const NodeID node) { - KASSERT(node < _clusters.size()); - return _clusters[node]; - } - - void move_node(const NodeID node, const ClusterID cluster) { - KASSERT(node < _clusters.size()); - _clusters[node] = cluster; - } - - void ensure_cluster_size(const NodeID max_num_nodes) { - if (_clusters.size() < max_num_nodes) { - _clusters.resize(max_num_nodes); + void allocate_cluster_weights(const ClusterID num_clusters) { + if (_cluster_weights.size() < num_clusters) { + _cluster_weights.resize(num_clusters); } } -private: - ScalableVector> _clusters; -}; - -template class OwnedRelaxedClusterWeightVector { -public: - explicit OwnedRelaxedClusterWeightVector(const ClusterID max_num_clusters) - : _cluster_weights(max_num_clusters) {} - auto &&take_cluster_weights() { return std::move(_cluster_weights); } @@ -1286,7 +1215,7 @@ template class OwnedRelaxedClusterW } ClusterWeight cluster_weight(const ClusterID cluster) { - return _cluster_weights[cluster]; + return __atomic_load_n(&_cluster_weights[cluster], __ATOMIC_RELAXED); } bool move_cluster_weight( @@ -1296,14 +1225,38 @@ template class OwnedRelaxedClusterW const ClusterWeight max_weight ) { if (_cluster_weights[new_cluster] + delta <= max_weight) { - _cluster_weights[new_cluster].fetch_add(delta, std::memory_order_relaxed); - _cluster_weights[old_cluster].fetch_sub(delta, std::memory_order_relaxed); + __atomic_fetch_add(&_cluster_weights[new_cluster], delta, __ATOMIC_RELAXED); + __atomic_fetch_sub(&_cluster_weights[old_cluster], delta, __ATOMIC_RELAXED); return true; } return false; } private: - ScalableVector> _cluster_weights; + StaticArray _cluster_weights; +}; + +template class NonatomicClusterVectorRef { +public: + void init_clusters_ref(StaticArray &clustering) { + _clusters = &clustering; + } + + void init_cluster(const NodeID node, const ClusterID cluster) { + move_node(node, cluster); + } + + [[nodiscard]] ClusterID cluster(const NodeID node) { + KASSERT(node < _clusters->size()); + return __atomic_load_n(&_clusters->at(node), __ATOMIC_RELAXED); + } + + void move_node(const NodeID node, const ClusterID cluster) { + KASSERT(node < _clusters->size()); + __atomic_store_n(&_clusters->at(node), cluster, __ATOMIC_RELAXED); + } + +private: + StaticArray *_clusters = nullptr; }; } // namespace kaminpar::dist diff --git a/kaminpar-shm/coarsening/clusterer.h b/kaminpar-shm/coarsening/clusterer.h index 857bc029..59bccf1a 100644 --- a/kaminpar-shm/coarsening/clusterer.h +++ b/kaminpar-shm/coarsening/clusterer.h @@ -29,8 +29,8 @@ class Clusterer { // Optional options // - virtual void set_max_cluster_weight(const NodeWeight /* weight */) {} - virtual void set_desired_cluster_count(const NodeID /* count */) {} + virtual void set_max_cluster_weight(NodeWeight /* weight */) {} + virtual void set_desired_cluster_count(NodeID /* count */) {} // // Clustering function