Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add vertex permutation #53

Merged
merged 3 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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