Skip to content

Commit

Permalink
add vertex permutation (#53)
Browse files Browse the repository at this point in the history
* add vertex permutation

* add guard looking for xxhash

* add test file
  • Loading branch information
mschimek authored Nov 13, 2024
1 parent 4e51662 commit b6b73e1
Show file tree
Hide file tree
Showing 9 changed files with 433 additions and 5 deletions.
213 changes: 213 additions & 0 deletions kagen/generators/generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "kagen/edgeweight_generators/voiding_generator.h"
#include "kagen/kagen.h"
#include "kagen/tools/converter.h"
#include "kagen/tools/random_permutation.h"
#include "kagen/vertexweight_generators/default_generator.h"
#include "kagen/vertexweight_generators/uniform_random_generator.h"
#include "kagen/vertexweight_generators/vertex_weight_generator.h"
Expand Down Expand Up @@ -84,6 +85,218 @@ void Generator::GenerateEdgeWeights(EdgeWeightConfig weight_config, MPI_Comm com
}
}

namespace {
template <typename Permutator>
auto ApplyPermutationAndComputeSendBuffersEdgeList(
const Graph& graph, const std::vector<VertexRange>& recv_ranges, Permutator&& permute) {
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

Edgelist edges = graph.edges;
for (auto& [src, dst]: edges) {
src = permute(src);
dst = permute(dst);
}
std::unordered_map<PEID, std::vector<SInt>> send_buffers;
std::unordered_map<PEID, std::vector<SSInt>> edge_weights_send_buffers;

bool has_edge_weights = graph.NumberOfLocalEdges() == 0 || !graph.edge_weights.empty();

for (std::size_t i = 0; i < edges.size(); ++i) {
const auto& [src, dst] = edges[i];
const PEID target_pe = FindPEInRange(src, recv_ranges);
std::vector<SInt>& send_buf = send_buffers[target_pe];
std::vector<SSInt>& weights_send_buf = edge_weights_send_buffers[target_pe];
send_buf.push_back(src);
send_buf.push_back(dst);
// [Permuted_Src_Id, Degree, EdgeWeights]
if (has_edge_weights) {
weights_send_buf.push_back(graph.edge_weights[i]);
}
}
return std::make_tuple(std::move(send_buffers), std::move(edge_weights_send_buffers));
}
template <typename Permutator>
auto ApplyPermutationAndComputeSendBuffersCSR(
const Graph& graph, const std::vector<VertexRange>& recv_ranges, Permutator&& permute) {
AdjncyArray permuted_adjncy = graph.adjncy;

for (auto& edge: permuted_adjncy) {
edge = permute(edge);
}

std::unordered_map<PEID, std::vector<SInt>> send_buffers;
std::unordered_map<PEID, std::vector<SSInt>> edge_weights_send_buffers;

bool has_edge_weights = graph.NumberOfLocalEdges() == 0 || !graph.edge_weights.empty();

for (std::size_t i = 0; i + 1 < graph.xadj.size(); ++i) {
const SInt degree = graph.xadj[i + 1] - graph.xadj[i];
const SInt global_id = graph.vertex_range.first + i;
const SInt permuted_global_id = permute(global_id);
const PEID target_pe = FindPEInRange(permuted_global_id, recv_ranges);
std::vector<SInt>& send_buf = send_buffers[target_pe];
std::vector<SSInt>& weights_send_buf = edge_weights_send_buffers[target_pe];
auto edge_begin_offset = graph.xadj[i];
auto edge_end_offset = graph.xadj[i + 1];
// [Permuted_Src_Id, Degree, [Permuted_Dst_Ids]] with #Permuted_Dst_Ids = Degree
send_buf.push_back(permuted_global_id);
send_buf.push_back(degree);
send_buf.insert(
send_buf.end(), permuted_adjncy.begin() + edge_begin_offset, permuted_adjncy.begin() + edge_end_offset);
// [Permuted_Src_Id, Degree, EdgeWeights]
if (has_edge_weights) {
weights_send_buf.push_back(permuted_global_id);
weights_send_buf.push_back(degree);
weights_send_buf.insert(
weights_send_buf.end(), graph.edge_weights.begin() + edge_begin_offset,
graph.edge_weights.begin() + edge_end_offset);
}
}
return std::make_tuple(std::move(send_buffers), std::move(edge_weights_send_buffers));
}

template <typename Permutator>
auto ApplyPermutationAndComputeSendBuffers(
const Graph& graph, const std::vector<VertexRange>& recv_ranges, Permutator&& permutator) {
switch (graph.representation) {
case GraphRepresentation::EDGE_LIST:
return ApplyPermutationAndComputeSendBuffersEdgeList(
graph, recv_ranges, std::forward<Permutator>(permutator));
case GraphRepresentation::CSR:
return ApplyPermutationAndComputeSendBuffersCSR(graph, recv_ranges, std::forward<Permutator>(permutator));
default:
throw std::runtime_error("Unexpected graph representation type.");
}
}

inline auto ConstructPermutedGraphCSR(
VertexRange recv_range, const std::vector<SInt>& recv_edges, const std::vector<SSInt>& recv_edge_weights) {
std::size_t num_local_vertices = recv_range.second - recv_range.first;
std::vector<SInt> degree_count(num_local_vertices, 0);

// scan received data for degrees
for (std::size_t cur_pos = 0; cur_pos < recv_edges.size();) {
const SInt src_id = recv_edges[cur_pos];
const SInt degree = recv_edges[cur_pos + 1];
degree_count[src_id - recv_range.first] = degree;
// skip edges
cur_pos += 1 + degree + 1;
}
XadjArray xadj(num_local_vertices + 1, 0);
// compute xadj array for received graph
std::exclusive_scan(degree_count.begin(), degree_count.end(), xadj.begin(), SInt{0});
xadj.back() = degree_count.back() + xadj[num_local_vertices - 1];

// compute adjncy for received graph
const std::size_t num_local_edges = xadj.back();
XadjArray adjncy(num_local_edges);
for (std::size_t cur_pos = 0; cur_pos < recv_edges.size();) {
const SInt global_src_id = recv_edges[cur_pos];
const SInt degree = recv_edges[cur_pos + 1];
const SInt local_src_id = global_src_id - recv_range.first;
std::copy_n(recv_edges.begin() + cur_pos + 2, degree, adjncy.begin() + xadj[local_src_id]);
// forward to next received src vertex
cur_pos += 1 + degree + 1;
}
// compute edge weights for received graph
EdgeWeights edge_weights(num_local_edges);
if (!recv_edge_weights.empty()) {
for (std::size_t cur_pos = 0; cur_pos < recv_edge_weights.size();) {
const SInt global_src_id = static_cast<SInt>(recv_edge_weights[cur_pos]);
const SInt degree = static_cast<SInt>(recv_edge_weights[cur_pos + 1]);
const SInt local_src_id = global_src_id - recv_range.first;
std::copy_n(recv_edge_weights.begin() + cur_pos + 2, degree, edge_weights.begin() + xadj[local_src_id]);
// forward to next received src vertex
cur_pos += 1 + degree + 1;
}
}

// TODO handle vertex weights
return std::make_tuple(std::move(xadj), std::move(adjncy), std::move(edge_weights));
}
inline auto ConstructPermutedGraphEdgeList(
VertexRange recv_range, const std::vector<SInt>& recv_edges, const std::vector<SSInt>& recv_edge_weights) {
std::size_t num_local_vertices = recv_range.second - recv_range.first;
std::vector<SInt> degree(num_local_vertices, 0);
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
for (std::size_t i = 0; i < recv_edges.size(); i += 2) {
const auto src = recv_edges[i];
++degree[src - recv_range.first];
}
std::vector<SInt> write_idx(num_local_vertices);
std::exclusive_scan(degree.begin(), degree.end(), write_idx.begin(), SInt{0});
Edgelist edgelist(recv_edges.size() / 2); // edges are sent flat - not as pairs
EdgeWeights edge_weights(recv_edge_weights.size());
for (std::size_t i = 0; i < recv_edges.size(); i += 2) {
const auto src = recv_edges[i];
const auto dst = recv_edges[i + 1];
const auto local_src_id = src - recv_range.first;
const auto idx = write_idx[local_src_id];
edgelist[idx] = std::make_pair(src, dst);
if (!edge_weights.empty()) {
edge_weights[idx] = recv_edge_weights[i / 2];
}
++write_idx[local_src_id];
}
return std::make_tuple(std::move(edgelist), std::move(edge_weights));
}
} // namespace

void Generator::PermuteVertices(const PGeneratorConfig& config, MPI_Comm comm) {
if (!graph_.vertex_weights.empty())
throw std::runtime_error(
"Graph is vertex weight but this is not yet supported by the vertex permutation routine!");

#ifdef KAGEN_XXHASH_FOUND
int size = -1;
int rank = -1;
MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &size);

auto permutator = random_permutation::FeistelPseudoRandomPermutation::buildPermutation(config.n - 1, 0);
auto permute = [&permutator](SInt v) {
return permutator.f(v);
};

// all PE get n / size vertices
// the first n modulo size PEs obtain one additional vertices.
const SInt vertices_per_pe = config.n / size;
const PEID num_pe_with_additional_node = config.n % size;
const bool has_pe_additional_node = rank < num_pe_with_additional_node;
const SInt begin_vertices = std::min(num_pe_with_additional_node, rank) + rank * vertices_per_pe;
const SInt end_vertices = begin_vertices + vertices_per_pe + has_pe_additional_node;

VertexRange recv_range{begin_vertices, end_vertices};
std::vector<VertexRange> recv_ranges = AllgatherVertexRange(recv_range, comm);

auto [send_buffers, edge_weight_send_buffers] = ApplyPermutationAndComputeSendBuffers(graph_, recv_ranges, permute);
auto recv_edges = ExchangeMessageBuffers(std::move(send_buffers), KAGEN_MPI_SINT, comm);
auto recv_edge_weights = ExchangeMessageBuffers(std::move(edge_weight_send_buffers), KAGEN_MPI_SSINT, comm);

switch (desired_representation_) {
case GraphRepresentation::EDGE_LIST: {
auto [permuted_edgelist, permuted_edge_weights] =
ConstructPermutedGraphEdgeList(recv_range, recv_edges, recv_edge_weights);
graph_.edges = std::move(permuted_edgelist);
graph_.edge_weights = std::move(permuted_edge_weights);
break;
}
case GraphRepresentation::CSR: {
auto [permuted_xadj, permuted_adjncy, permuted_edge_weights] =
ConstructPermutedGraphCSR(recv_range, recv_edges, recv_edge_weights);

graph_.xadj = std::move(permuted_xadj);
graph_.adjncy = std::move(permuted_adjncy);
graph_.edge_weights = std::move(permuted_edge_weights);
break;
}
}
SetVertexRange(recv_range);
#endif // KAGEN_XXHASH_FOUND
}

std::unique_ptr<kagen::VertexWeightGenerator>
CreateVertexWeightGenerator(const VertexWeightConfig weight_config, MPI_Comm comm) {
switch (weight_config.generator_type) {
Expand Down
2 changes: 2 additions & 0 deletions kagen/generators/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Generator {

Graph Take();

virtual void PermuteVertices(const PGeneratorConfig& config, MPI_Comm comm);

protected:
virtual void GenerateEdgeList() = 0;

Expand Down
3 changes: 3 additions & 0 deletions kagen/generators/path/path_directed.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class PathDirected : public virtual Generator, private EdgeListOnlyGenerator {
public:
PathDirected(const PGeneratorConfig& config, const PEID rank, const PEID size);

void PermuteVertices(const PGeneratorConfig& /*config*/, MPI_Comm /*comm*/)
override { /* do nothing as paths can be permutet without communication during construction */ }

protected:
void GenerateEdgeList() final;

Expand Down
3 changes: 3 additions & 0 deletions kagen/in_memory_facade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ Graph GenerateInMemory(const PGeneratorConfig& config_template, GraphRepresentat
<< ")" << std::endl;
}
}
if (config.permute) {
generator->PermuteVertices(config, comm);
}

auto graph = generator->Take();

Expand Down
4 changes: 4 additions & 0 deletions kagen/kagen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ void KaGen::EnableAdvancedStatistics() {
config_->quiet = false;
}

void KaGen::EnableVertexPermutation() {
config_->permute = true;
}

void KaGen::ConfigureEdgeWeightGeneration(
EdgeWeightGeneratorType generator, SInt weight_range_begin, SInt weight_range_end) {
config_->edge_weights.generator_type = generator;
Expand Down
6 changes: 6 additions & 0 deletions kagen/kagen.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ class KaGen {

void EnableAdvancedStatistics();

/*!
* If enabled, KaGen will apply a FeistelPermutation to the vertices of the generated graph and rearrange vertices/edges accordingly.
* This will remove locality from the generated graph.
*/
void EnableVertexPermutation();

/*!
* KaGen will generate edge weights according to the given configuration.
*
Expand Down
19 changes: 14 additions & 5 deletions kagen/tools/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ T FloorLog2(const T arg) {
return log2 - 1;
}

inline PEID FindPEInRange(const SInt node, const std::vector<std::pair<SInt, SInt>>& ranges) {
inline PEID FindPEInRange(const SInt node, const std::vector<VertexRange>& ranges) {
for (std::size_t i = 0; i < ranges.size(); ++i) {
const auto& [local_from, local_to] = ranges[i];

Expand All @@ -67,6 +67,16 @@ inline PEID FindPEInRange(const SInt node, const std::vector<std::pair<SInt, SIn
return -1;
}

inline PEID FindPEInRangeWithBinarySearch(const SInt node, const std::vector<VertexRange>& ranges) {
auto it = std::upper_bound(ranges.begin(), ranges.end(), node, [](SInt value, const std::pair<SInt, SInt>& range) {
return range.first <= value && value < range.second;
});
if (it == ranges.end()) {
return -1;
}
return std::distance(ranges.begin(), it);
}

inline std::vector<VertexRange> AllgatherVertexRange(const VertexRange vertex_range, MPI_Comm comm) {
int rank, size;
MPI_Comm_rank(comm, &rank);
Expand Down Expand Up @@ -118,8 +128,7 @@ std::vector<T> ExchangeMessageBuffers(
}

template <typename Comparator = std::less<Edgelist::value_type>>
inline void
SortEdgesAndWeights(Edgelist& edges, EdgeWeights& edge_weights, Comparator cmp = Comparator{}) {
inline void SortEdgesAndWeights(Edgelist& edges, EdgeWeights& edge_weights, Comparator cmp = Comparator{}) {
if (!std::is_sorted(edges.begin(), edges.end(), cmp)) {
const SInt num_local_edges = edges.size();
// If we have edge weights, sort them the same way as the edges
Expand All @@ -145,7 +154,7 @@ inline void RemoveDuplicates(Edgelist& edges, EdgeWeights& edge_weights) {
const SInt num_local_edges = edges.size();
if (!edge_weights.empty()) {
// TODO replace with zip view once C++23 is enabled
using Edge = typename Edgelist::value_type;
using Edge = typename Edgelist::value_type;
using Weight = typename EdgeWeights::value_type;

std::vector<std::pair<Edge, Weight>> edge_weights_zip;
Expand All @@ -159,7 +168,7 @@ inline void RemoveDuplicates(Edgelist& edges, EdgeWeights& edge_weights) {
return lhs.first == rhs.first;
});
edge_weights_zip.erase(it, edge_weights_zip.end());
for (const auto& [edge, weight] : edge_weights_zip) {
for (const auto& [edge, weight]: edge_weights_zip) {
edges.push_back(edge);
edge_weights.push_back(weight);
}
Expand Down
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ kagen_add_test(test_edge_weights
FILES edge_weights/edge_generation_test.cpp
CORES 1 2 4)

kagen_add_test(test_permutation
FILES permutation/permutation_test.cpp
CORES 1 2 4)

Loading

0 comments on commit b6b73e1

Please sign in to comment.