diff --git a/CMakeLists.txt b/CMakeLists.txt index 792126449b..b62c247a47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ option(FF_BUILD_ALL_EXAMPLES "build all examples. Overrides others" OFF) option(FF_BUILD_UNIT_TESTS "build non-operator unit tests" OFF) option(FF_BUILD_SUBSTITUTION_TOOL "build substitution conversion tool" OFF) option(FF_BUILD_VISUALIZATION_TOOL "build substitution visualization tool" ON) +option(FF_BUILD_SP_IZATION_BENCHMARKING "build sp-ization benchmarking" ON) option(FF_BUILD_ARG_PARSER "build command line argument parser" OFF) option(FF_BUILD_BIN_EXPORT_MODEL_ARCH "build export-model-arch utility" ON) diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index 1cd7068cfd..87ff5269c7 100644 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -10,6 +10,10 @@ if(FF_BUILD_VISUALIZATION_TOOL) add_subdirectory(substitution-to-dot) endif() +if(FF_BUILD_SP_IZATION_BENCHMARKING) + add_subdirectory(sp_ization_benchmarking) +endif() + if(FF_BUILD_ARG_PARSER) add_subdirectory(arg_parser) endif() diff --git a/bin/sp_ization_benchmarking/CMakeLists.txt b/bin/sp_ization_benchmarking/CMakeLists.txt new file mode 100644 index 0000000000..a24e84e31d --- /dev/null +++ b/bin/sp_ization_benchmarking/CMakeLists.txt @@ -0,0 +1,9 @@ +ff_add_executable( + NAME + sp-ization-benchmarking + SRC_PATTERNS + *.cc + DEPS + utils + rapidcheck +) diff --git a/bin/sp_ization_benchmarking/distributions.cc b/bin/sp_ization_benchmarking/distributions.cc new file mode 100644 index 0000000000..6c59f58a34 --- /dev/null +++ b/bin/sp_ization_benchmarking/distributions.cc @@ -0,0 +1,55 @@ +#include "distributions.h" + +namespace FlexFlow { + +Constant::Constant(float val) : val(val) {} + +float Constant::operator()() const { + return val; +} + +Uniform::Uniform(float a, float b) : a(a), b(b) {} + +float Uniform::operator()() const { + return a + ((static_cast(std::rand()) / RAND_MAX) * (b - a)); +} + +Bernoulli::Bernoulli(float p) : p(p) {} + +float Bernoulli::operator()() const { + return (Uniform(0, 1)() < p); +} + +Binary::Binary(float a, float b, float p) : a(a), b(b), p(p) {} + +float Binary::operator()() const { + return (Bernoulli(p)() ? a : b); +} + +Chooser::Chooser(std::vector items) : items(items) {} + +float Chooser::operator()() const { + return items[std::rand() % items.size()]; +} + +UniformNoise::UniformNoise(float lower, float upper) + : lower(lower), upper(upper) {} + +float UniformNoise::operator()() const { + return Uniform(lower, upper)(); +} + +float NoNoise::operator()() const { + return 1; +} + +GaussianNoise::GaussianNoise(float mean, float stddev) + : mean(mean), stddev(stddev) {} + +float GaussianNoise::operator()() const { + static std::default_random_engine generator; + static std::normal_distribution distribution(mean, stddev); + return distribution(generator); +} + +} // namespace FlexFlow diff --git a/bin/sp_ization_benchmarking/distributions.h b/bin/sp_ization_benchmarking/distributions.h new file mode 100644 index 0000000000..ea24d55898 --- /dev/null +++ b/bin/sp_ization_benchmarking/distributions.h @@ -0,0 +1,81 @@ +#ifndef _FLEXFLOW_DISTRIBUTIONS_H +#define _FLEXFLOW_DISTRIBUTIONS_H + +#include "utils/graph/node/node.dtg.h" +#include +#include +#include + +namespace FlexFlow { + +struct Constant { + float val; + Constant(float val = 1); + float operator()() const; +}; + +struct Uniform { + float a, b; + Uniform(float a = 0, float b = 1); + float operator()() const; +}; + +struct Bernoulli { + float p; + Bernoulli(float p = 0.5); + float operator()() const; +}; + +struct Binary { + float a, b, p; + Binary(float a = 0, float b = 1, float p = 0.5); + float operator()() const; +}; + +struct Chooser { + std::vector items; + Chooser(std::vector); + float operator()() const; +}; + +struct UniformNoise { + float lower, upper; + UniformNoise(float lower = 0.9, float upper = 1.1); + float operator()() const; +}; + +struct NoNoise { + float operator()() const; +}; + +struct GaussianNoise { + float mean, stddev; + GaussianNoise(float mean = 1, float stddev = .1); + float operator()() const; +}; + +template +std::unordered_map + make_cost_map(std::unordered_set const &nodes, + Dist const &distribution) { + std::unordered_map cost_map; + for (Node const &node : nodes) { + cost_map[node] = distribution(); + } + return cost_map; +} + +template +std::unordered_map + add_noise_to_cost_map(std::unordered_map cost_map, + Noise const &noise) { + std::unordered_map noisy_cost_map; + for (auto const &[node, cost] : cost_map) { + noisy_cost_map[node] = noise() * cost; + } + return noisy_cost_map; +} + +} // namespace FlexFlow + +#endif diff --git a/bin/sp_ization_benchmarking/nasnet_bench_graph_generator.h b/bin/sp_ization_benchmarking/nasnet_bench_graph_generator.h new file mode 100644 index 0000000000..0946794b39 --- /dev/null +++ b/bin/sp_ization_benchmarking/nasnet_bench_graph_generator.h @@ -0,0 +1,126 @@ +// For context, see https://arxiv.org/abs/1902.09635 && +// https://github.com/google-research/nasbench/blob/master/nasbench/api.py + +#include "utils/containers.h" +#include "utils/containers/all_of.h" +#include "utils/containers/repeat.h" +#include "utils/containers/transform.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/digraph/algorithms/materialize_digraph_view.h" +#include "utils/graph/digraph/algorithms/transitive_reduction.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/digraph_generation.h" +#include +#include + +constexpr size_t MIN_NODES = 6; +constexpr size_t MAX_NODES = 8; +constexpr size_t MIN_EDGES = 8; +constexpr size_t MAX_EDGES = 11; +constexpr size_t NUM_CELLS = 9; + +using AdjacencyMatrix = std::vector>; +namespace FlexFlow { +struct NasNetBenchConfig { + AdjacencyMatrix adjacency_matrix; +}; + +bool is_valid_config(NasNetBenchConfig const &config) { + AdjacencyMatrix const &matrix = config.adjacency_matrix; + const size_t size = matrix.size(); + + auto is_valid_size = [](size_t s) { + return s >= MIN_NODES && s <= MAX_NODES; + }; + + auto is_square_matrix = [&](auto const &m) { + return all_of(m, [&](const auto &row) { return row.size() == size; }); + }; + + auto is_upper_triangular = [&](auto const &m) { + for (size_t i = 0; i < size; ++i) { + for (size_t j = 0; j <= i; ++j) { + if (matrix[i][j]) { + return false; + } + } + } + return true; + }; + + return is_valid_size(size) && is_square_matrix(matrix) && + is_upper_triangular(matrix); +} + +bool is_valid_cell(DiGraphView const &g) { + return (is_acyclic(g)) && (get_sources(g).size() == 1) && + (get_sinks(g).size() == 1) && (num_edges(g) <= MAX_EDGES) && + (num_edges(g) >= MIN_EDGES) && (num_edges(g) <= MAX_NODES) && + (num_edges(g) >= MIN_NODES) && + (num_edges(g) > num_nodes(g)); // filter linear cell and diamond cell +} + +NasNetBenchConfig generate_random_config() { + static std::uniform_int_distribution<> size_dist(MIN_NODES, MAX_NODES); + Binary bin = Binary(0, 1); + + size_t num_nodes = Uniform(MIN_NODES, MAX_NODES)(); + std::vector> matrix(num_nodes, + std::vector(num_nodes, false)); + + for (size_t i = 0; i < num_nodes; ++i) { + for (size_t j = i + 1; j < num_nodes; ++j) { + matrix[i][j] = bin(); + } + } + + return {matrix}; +} + +std::optional + maybe_generate_nasnet_bench_cell(NasNetBenchConfig const &config) { + if (!is_valid_config(config)) { + return std::nullopt; + } + + DiGraph g = DiGraph::create(); + std::vector nodes = add_nodes(g, config.adjacency_matrix.size()); + + for (size_t i = 0; i < nodes.size(); ++i) { + for (size_t j = i + 1; j < nodes.size(); ++j) { + if (config.adjacency_matrix[i][j]) { + g.add_edge(DirectedEdge{nodes[i], nodes[j]}); + } + } + } + + g = materialize_digraph_view(transitive_reduction(g)); + + if (!is_valid_cell(g)) { + return std::nullopt; + } + + return g; +} + +DiGraph generate_nasnet_bench_cell() { + while (true) { + NasNetBenchConfig config = generate_random_config(); + std::optional maybe_cell = + maybe_generate_nasnet_bench_cell(config); + if (maybe_cell) { + return maybe_cell.value(); + } + } +} + +DiGraph generate_nasnet_bench_network() { + DiGraph g = series_composition( + transform(repeat(NUM_CELLS, generate_nasnet_bench_cell), + [](auto const cell) -> DiGraphView { return cell; })); + return g; +} +} // namespace FlexFlow diff --git a/bin/sp_ization_benchmarking/sample_graphs.h b/bin/sp_ization_benchmarking/sample_graphs.h new file mode 100644 index 0000000000..709258e502 --- /dev/null +++ b/bin/sp_ization_benchmarking/sample_graphs.h @@ -0,0 +1,351 @@ +#ifndef FLEXFLOW_GRAPH_GENERATION_H +#define FLEXFLOW_GRAPH_GENERATION_H + +#include "distributions.h" +#include "sample_graphs.h" +#include "utils/containers/get_only.h" +#include "utils/containers/transform.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms/is_2_terminal_dag.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/series_parallel/digraph_generation.h" +#include + +namespace FlexFlow { + +std::tuple make_normal_taso_nasnet_cell() { + DiGraph g = DiGraph::create(); + std::vector inputs = add_nodes(g, 2); + std::vector sep = add_nodes(g, 5); + std::vector id = add_nodes(g, 2); + std::vector avg = add_nodes(g, 3); + std::vector add = add_nodes(g, 5); + std::vector concat = add_nodes(g, 1); + + std::vector edges = {DirectedEdge{inputs.at(0), sep.at(1)}, + DirectedEdge{inputs.at(0), id.at(1)}, + DirectedEdge{inputs.at(0), avg.at(1)}, + DirectedEdge{inputs.at(0), avg.at(2)}, + DirectedEdge{inputs.at(0), sep.at(3)}, + DirectedEdge{inputs.at(0), sep.at(4)}, + DirectedEdge{inputs.at(1), sep.at(0)}, + DirectedEdge{inputs.at(1), id.at(0)}, + DirectedEdge{inputs.at(1), avg.at(0)}, + DirectedEdge{inputs.at(1), sep.at(2)}, + DirectedEdge{sep.at(0), add.at(0)}, + DirectedEdge{id.at(0), add.at(0)}, + DirectedEdge{sep.at(1), add.at(1)}, + DirectedEdge{sep.at(2), add.at(1)}, + DirectedEdge{avg.at(0), add.at(2)}, + DirectedEdge{id.at(1), add.at(2)}, + DirectedEdge{avg.at(1), add.at(3)}, + DirectedEdge{avg.at(2), add.at(3)}, + DirectedEdge{sep.at(3), add.at(4)}, + DirectedEdge{sep.at(4), add.at(4)}}; + add_edges(g, edges); + + for (Node const &a : add) { + g.add_edge(DirectedEdge{a, concat.at(0)}); + } + + assert(get_sinks(g).size() == 1); + assert(get_sources(g).size() == 2); + assert(is_acyclic(g)); + return {g, inputs.at(0), inputs.at(1)}; +} + +std::tuple make_reduction_taso_nasnet_cell() { + DiGraph g = DiGraph::create(); + std::vector inputs = add_nodes(g, 2); + std::vector sep = add_nodes(g, 5); + std::vector id = add_nodes(g, 1); + std::vector avg = add_nodes(g, 2); + std::vector max = add_nodes(g, 2); + std::vector add = add_nodes(g, 5); + std::vector concat = add_nodes(g, 1); + + std::vector edges = {DirectedEdge{inputs.at(0), sep.at(0)}, + DirectedEdge{inputs.at(0), sep.at(2)}, + DirectedEdge{inputs.at(0), sep.at(3)}, + DirectedEdge{inputs.at(1), max.at(1)}, + DirectedEdge{inputs.at(1), sep.at(1)}, + DirectedEdge{inputs.at(1), max.at(0)}, + DirectedEdge{inputs.at(1), avg.at(0)}, + DirectedEdge{sep.at(0), add.at(0)}, + DirectedEdge{sep.at(1), add.at(0)}, + DirectedEdge{max.at(0), add.at(1)}, + DirectedEdge{sep.at(2), add.at(1)}, + DirectedEdge{avg.at(0), add.at(2)}, + DirectedEdge{sep.at(3), add.at(2)}, + DirectedEdge{max.at(1), add.at(3)}, + DirectedEdge{sep.at(4), add.at(3)}, + DirectedEdge{avg.at(1), add.at(4)}, + DirectedEdge{id.at(0), add.at(4)}, + DirectedEdge{add.at(0), sep.at(4)}, + DirectedEdge{add.at(0), avg.at(1)}, + DirectedEdge{add.at(1), id.at(0)}, + DirectedEdge{add.at(2), concat.at(0)}, + DirectedEdge{add.at(3), concat.at(0)}, + DirectedEdge{add.at(4), concat.at(0)}}; + + add_edges(g, edges); + + assert(get_sinks(g).size() == 1); + assert(get_sources(g).size() == 2); + assert(is_acyclic(g)); + return {g, inputs.at(0), inputs.at(1)}; +} + +DiGraph make_full_taso_nasnet(size_t num_reduction_cells, size_t N) { + DiGraph g = DiGraph::create(); + Node input = g.add_node(); + std::deque outputting = {input, input, input}; + std::deque inputting; + size_t num_cells = num_reduction_cells + N * (num_reduction_cells + 1); + for (int i = 0; i < num_cells; i++) { + auto [s, earlier_input, later_input] = + (i % (N + 1) == N) ? make_reduction_taso_nasnet_cell() + : make_normal_taso_nasnet_cell(); + Node cell_output = get_only(get_sinks(s)); + std::unordered_map node_map = parallel_extend(g, s); + later_input = node_map.at(later_input); + earlier_input = node_map.at(earlier_input); + cell_output = node_map.at(cell_output); + + outputting.push_back(cell_output); + outputting.push_back(cell_output); + inputting.push_back(earlier_input); + inputting.push_back(later_input); + + Node a = outputting.front(); + Node b = inputting.front(); + inputting.pop_front(); + outputting.pop_front(); + g.add_edge(DirectedEdge{a, b}); + + a = outputting.front(); + b = inputting.front(); + inputting.pop_front(); + outputting.pop_front(); + g.add_edge(DirectedEdge{a, b}); + + assert(is_2_terminal_dag(g)); + assert(inputting.size() == 0); + assert(outputting.size() == 3); + } + return g; +} + +DiGraph make_linear(size_t length) { + DiGraph g = DiGraph::create(); + if (length == 0) { + return g; + } + std::vector nodes = add_nodes(g, length); + + for (size_t i = 0; i < length - 1; ++i) { + g.add_edge(DirectedEdge{nodes.at(i), nodes.at(i + 1)}); + } + + return g; +} + +DiGraph make_rhombus() { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 4); + + std::vector edges = {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}}; + + add_edges(g, edges); + return g; +} + +DiGraph make_diamond() { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + + std::vector edges = { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + }; + + add_edges(g, edges); + return g; +} + +DiGraph make_fully_connected(std::vector layer_sizes) { + DiGraph g = DiGraph::create(); + std::vector> layers = + transform(layer_sizes, [&g](size_t size) { return add_nodes(g, size); }); + + std::vector edges; + + for (size_t i = 0; i < layers.size() - 1; ++i) { + for (Node const &n1 : layers.at(i)) { + for (Node const &n2 : layers.at(i + 1)) { + edges.push_back(DirectedEdge{n1, n2}); + } + } + } + + add_edges(g, edges); + return g; +} + +DiGraph make_parallel_chains(size_t chain_length, size_t chain_num) { + DiGraph g = DiGraph::create(); + assert(chain_length >= 3); + assert(chain_num >= 1); + std::vector> chains; + + for (size_t i = 0; i < chain_num; i++) { + std::vector chain_nodes = add_nodes(g, chain_length - 2); + chains.push_back(chain_nodes); + + for (size_t j = 0; j < chain_length - 3; j++) { + g.add_edge(DirectedEdge{chain_nodes.at(j), chain_nodes.at(j + 1)}); + } + } + + Node source = g.add_node(); + Node sink = g.add_node(); + + for (std::vector const &chain : chains) { + g.add_edge(DirectedEdge{source, chain.front()}); + g.add_edge(DirectedEdge{chain.back(), sink}); + } + + return g; +} + +DiGraph make_sample_dag_1() { + + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 7); + std::vector edges = {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + DirectedEdge{n.at(0), n.at(6)}, + DirectedEdge{n.at(2), n.at(6)}, + DirectedEdge{n.at(6), n.at(5)}}; + add_edges(g, edges); + assert(is_2_terminal_dag(g)); + return g; +} + +DiGraph make_sample_dag_2() { + NOT_IMPLEMENTED(); +} + +DiGraph make_sample_dag_3() { + // Taken by "A New Algorithm for Mapping DAGs to Series-ParallelSplit Form, + // Escribano et Al, 2002" + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 18); + + std::vector edges = { + DirectedEdge{n.at(0), n.at(1)}, DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(2), n.at(10)}, DirectedEdge{n.at(2), n.at(11)}, + DirectedEdge{n.at(2), n.at(12)}, DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(3), n.at(6)}, DirectedEdge{n.at(4), n.at(6)}, + DirectedEdge{n.at(4), n.at(7)}, DirectedEdge{n.at(4), n.at(10)}, + DirectedEdge{n.at(5), n.at(8)}, DirectedEdge{n.at(6), n.at(8)}, + DirectedEdge{n.at(6), n.at(9)}, DirectedEdge{n.at(7), n.at(8)}, + DirectedEdge{n.at(8), n.at(17)}, DirectedEdge{n.at(9), n.at(17)}, + DirectedEdge{n.at(10), n.at(16)}, DirectedEdge{n.at(11), n.at(16)}, + DirectedEdge{n.at(12), n.at(13)}, DirectedEdge{n.at(12), n.at(14)}, + DirectedEdge{n.at(13), n.at(15)}, DirectedEdge{n.at(14), n.at(15)}, + DirectedEdge{n.at(15), n.at(16)}, DirectedEdge{n.at(16), n.at(17)}}; + + add_edges(g, edges); + return g; +} + +DiGraph make_taso_nasnet_cell() { + // From the TASO paper, pg 57 + DiGraph g = DiGraph::create(); + Node root = g.add_node(); + std::vector input = add_nodes(g, 2); + std::vector dwc = add_nodes(g, 5); + std::vector conv = add_nodes(g, 5); + std::vector avg = add_nodes(g, 3); + std::vector add = add_nodes(g, 5); + Node concat = g.add_node(); + + std::vector edges = {DirectedEdge{root, input.at(0)}, + DirectedEdge{root, input.at(1)}, + DirectedEdge{input.at(0), dwc.at(0)}, + DirectedEdge{input.at(0), dwc.at(1)}, + DirectedEdge{input.at(0), avg.at(0)}, + DirectedEdge{input.at(0), avg.at(1)}, + DirectedEdge{input.at(0), avg.at(2)}, + DirectedEdge{input.at(0), dwc.at(2)}, + DirectedEdge{input.at(1), add.at(2)}, + DirectedEdge{input.at(1), dwc.at(3)}, + DirectedEdge{input.at(1), dwc.at(4)}, + DirectedEdge{input.at(1), add.at(4)}, + DirectedEdge{dwc.at(0), conv.at(0)}, + DirectedEdge{dwc.at(1), conv.at(1)}, + DirectedEdge{dwc.at(2), conv.at(2)}, + DirectedEdge{dwc.at(3), conv.at(3)}, + DirectedEdge{dwc.at(4), conv.at(4)}, + DirectedEdge{conv.at(0), add.at(0)}, + DirectedEdge{conv.at(1), add.at(0)}, + DirectedEdge{avg.at(0), add.at(1)}, + DirectedEdge{avg.at(1), add.at(1)}, + DirectedEdge{avg.at(2), add.at(2)}, + DirectedEdge{conv.at(2), add.at(3)}, + DirectedEdge{conv.at(3), add.at(3)}, + DirectedEdge{conv.at(4), add.at(4)}}; + + add_edges(g, edges); + + for (Node const &a : add) { + g.add_edge(DirectedEdge{a, concat}); + } + return g; +} + +DiGraph make_2_terminal_random_dag(size_t num_nodes, float p, size_t step) { + DiGraph g = DiGraph::create(); + Bernoulli sampler = Bernoulli(p); + std::vector n = add_nodes(g, num_nodes - 2); + for (int i = 0; i < n.size(); i++) { + for (int j = i + step + 1; j < n.size(); j++) { + if (sampler()) { + g.add_edge(DirectedEdge{n.at(i), n.at(j)}); + } + } + } + std::unordered_set sinks = get_sinks(g); + std::unordered_set sources = get_sources(g); + Node sink = g.add_node(); + Node source = g.add_node(); + for (Node s : sources) { + g.add_edge(DirectedEdge{source, s}); + } + for (Node s : sinks) { + g.add_edge(DirectedEdge{s, sink}); + } + assert(is_2_terminal_dag(g)); + return g; +} + +} // namespace FlexFlow + +#endif diff --git a/bin/sp_ization_benchmarking/sp_ization_benchmarking.cc b/bin/sp_ization_benchmarking/sp_ization_benchmarking.cc new file mode 100644 index 0000000000..7fd9064688 --- /dev/null +++ b/bin/sp_ization_benchmarking/sp_ization_benchmarking.cc @@ -0,0 +1,585 @@ +/** + * @file sp_ization_benchmarking.cpp + * @brief Benchmarking different SP-ization techniques on various graphs. + * + * @details + * Algorithms: + * - critical_path_preserving_sp_ization_with_coalescing + * - stratum_sync_sp_ization + * - cost_aware_stratum_sync_sp_ization + * Weight distributions: + * - Constant + * - Uniform(0, 1) + * - Binary(0, 100) + * - Chooser({1.0, 25.0, 500.0}) //sample uniformly from the given weights + * Noise distributions: + * - NoNoise + * - GaussianNoise(1, 0.1) + * - UniformNoise(0.8, 1.25) + * Graph types: + * ... + * + * @note To run the benchmark, go to build/normal/bin/sp_ization_benchmarking, + * run make and then ./sp_ization_benchmarking + */ + +#include "distributions.h" +#include "nasnet_bench_graph_generator.h" +#include "sample_graphs.h" +#include "utils/graph/digraph/algorithms/transitive_reduction.h" +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_metrics.h" +#include "utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h" +#include "utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h" +#include +#include +#include +#include + +constexpr size_t REPEAT = 500; + +using namespace FlexFlow; +using Result = std::tuple; +using CombinedResult = std::tuple; + +template +CombinedResult perform_benchmark_given_graph(DiGraphView const &g, + D const &Dist, + N const &Noise = NoNoise(), + size_t repeat = REPEAT) { + Result critical_path_preserving = {0, 0, 0}; + Result barrier_sync = {0, 0, 0}; + Result cost_aware = {0, 0, 0}; + + for (int i = 0; i < repeat; i++) { + auto cost_map = make_cost_map(get_nodes(g), Dist); + + SeriesParallelDecomposition sp1 = + critical_path_preserving_sp_ization_with_coalescing(g); + SeriesParallelDecomposition sp2 = stratum_sync_sp_ization(g); + SeriesParallelDecomposition sp3 = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + auto noisy_cost_map = add_noise_to_cost_map(cost_map, Noise); + + std::get<0>(critical_path_preserving) += + relative_work_increase(g, sp1, noisy_cost_map); + std::get<1>(critical_path_preserving) += + relative_critical_path_cost_increase(g, sp1, noisy_cost_map); + std::get<2>(critical_path_preserving) += + relative_num_dependencies_increase(g, sp1); + + std::get<0>(barrier_sync) += relative_work_increase(g, sp2, noisy_cost_map); + std::get<1>(barrier_sync) += + relative_critical_path_cost_increase(g, sp2, noisy_cost_map); + std::get<2>(barrier_sync) += relative_num_dependencies_increase(g, sp2); + + std::get<0>(cost_aware) += relative_work_increase(g, sp3, noisy_cost_map); + std::get<1>(cost_aware) += + relative_critical_path_cost_increase(g, sp3, noisy_cost_map); + std::get<2>(cost_aware) += relative_num_dependencies_increase(g, sp3); + } + + std::vector results = { + critical_path_preserving, barrier_sync, cost_aware}; + + for (Result &r : results) { + std::get<0>(r) /= repeat; + std::get<1>(r) /= repeat; + std::get<2>(r) /= repeat; + } + + return {results[0], results[1], results[2]}; +} + +template +CombinedResult + perform_benchmark_given_graph_generator(G const &graph_generator, + D const &Dist, + N const &Noise = NoNoise(), + size_t repeat = REPEAT) { + Result critical_path_preserving = {0, 0, 0}; + Result barrier_sync = {0, 0, 0}; + Result cost_aware = {0, 0, 0}; + + for (int i = 0; i < repeat; i++) { + DiGraphView g = graph_generator(); + auto cost_map = make_cost_map(get_nodes(g), Dist); + + SeriesParallelDecomposition sp1 = + critical_path_preserving_sp_ization_with_coalescing(g); + SeriesParallelDecomposition sp2 = stratum_sync_sp_ization(g); + SeriesParallelDecomposition sp3 = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + auto noisy_cost_map = add_noise_to_cost_map(cost_map, Noise); + + std::get<0>(critical_path_preserving) += + relative_work_increase(g, sp1, noisy_cost_map); + std::get<1>(critical_path_preserving) += + relative_critical_path_cost_increase(g, sp1, noisy_cost_map); + std::get<2>(critical_path_preserving) += + relative_num_dependencies_increase(g, sp1); + + std::get<0>(barrier_sync) += relative_work_increase(g, sp2, noisy_cost_map); + std::get<1>(barrier_sync) += + relative_critical_path_cost_increase(g, sp2, noisy_cost_map); + std::get<2>(barrier_sync) += relative_num_dependencies_increase(g, sp2); + + std::get<0>(cost_aware) += relative_work_increase(g, sp3, noisy_cost_map); + std::get<1>(cost_aware) += + relative_critical_path_cost_increase(g, sp3, noisy_cost_map); + std::get<2>(cost_aware) += relative_num_dependencies_increase(g, sp3); + } + + std::vector results = { + critical_path_preserving, barrier_sync, cost_aware}; + + for (Result &r : results) { + std::get<0>(r) /= repeat; + std::get<1>(r) /= repeat; + std::get<2>(r) /= repeat; + } + + return {results[0], results[1], results[2]}; +} + +void output_benchmark(CombinedResult const &combined_result, + std::string const &title) { + auto [path_pres, stratum_sync, cost_aware_stratum_sync] = combined_result; + std::cout << std::fixed << std::setprecision(3); + std::cout << "Benchmark for " << title << std::endl; + std::cout << "Technique | Work-Increase | Critical-Path-Increase | " + "Dependencies-Increase" + << std::endl; + std::cout << "Barrier Sync | " << std::get<0>(stratum_sync) << " | " + << std::get<1>(stratum_sync) << " | " << std::get<2>(stratum_sync) + << std::endl; + std::cout << "Cost Aware B.S. | " << std::get<0>(cost_aware_stratum_sync) + << " | " << std::get<1>(cost_aware_stratum_sync) << " | " + << std::get<2>(cost_aware_stratum_sync) << std::endl; + std::cout << "Path Preserving | " << std::get<0>(path_pres) << " | " + << std::get<1>(path_pres) << " | " << std::get<2>(path_pres) + << std::endl; + std::cout << std::endl; +} + +template +void bench_mark_given_graph(std::string title, + DiGraphView const &g, + D const &Dist, + N const &Noise = NoNoise(), + size_t repeat = REPEAT) { + output_benchmark(perform_benchmark_given_graph(g, Dist, Noise, repeat), + title); +} + +template +void bench_mark_given_graph_generator(std::string title, + G const &generator, + D const &Dist, + N const &Noise = NoNoise(), + size_t repeat = REPEAT) { + output_benchmark( + perform_benchmark_given_graph_generator(generator, Dist, Noise, repeat), + title); +} + +int main() { + { + DiGraph g = make_sample_dag_3(); + bench_mark_given_graph("sample_dag_3, Constant(1)", g, Constant(1)); + bench_mark_given_graph("sample_dag_3, Constant(1), UniformNoise(0.8, 1.25)", + g, + Constant(1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph("sample_dag_3, Constant(1), GaussianNoise(1, 0.1)", + g, + Constant(1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("sample_dag_3, Uniform(0,1)", g, Uniform(0, 1)); + bench_mark_given_graph( + "sample_dag_3, Uniform(0,1), UniformNoise(0.8, 1.25)", + g, + Uniform(0, 1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph("sample_dag_3, Uniform(0,1), GaussianNoise(1, 0.1)", + g, + Uniform(0, 1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("sample_dag_3, Binary(1, 80)", g, Binary(1, 80)); + bench_mark_given_graph( + "sample_dag_3, Binary(1, 80), UniformNoise(0.8, 1.25)", + g, + Binary(1, 80), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph("sample_dag_3, Binary(1, 80), GaussianNoise(1, 0.1)", + g, + Binary(1, 80), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("sample_dag_3, Chooser({1.0, 20.0, 500.0})", + g, + Chooser({1.0, 20.0, 500.0})); + bench_mark_given_graph( + "sample_dag_3, Chooser({1.0, 20.0, 500.0}), UniformNoise(0.8, 1.25)", + g, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "sample_dag_3, Chooser({1.0, 20.0, 500.0}), GaussianNoise(1, 0.1)", + g, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1)); + } + + { + DiGraph g = make_taso_nasnet_cell(); + bench_mark_given_graph("taso_nasnet_cell, Constant(1)", g, Constant(1)); + bench_mark_given_graph( + "taso_nasnet_cell, Constant(1), UniformNoise(0.8, 1.25)", + g, + Constant(1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "taso_nasnet_cell, Constant(1), GaussianNoise(1, 0.1)", + g, + Constant(1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("taso_nasnet_cell, Uniform(0,1)", g, Uniform(0, 1)); + bench_mark_given_graph( + "taso_nasnet_cell, Uniform(0,1), UniformNoise(0.8, 1.25)", + g, + Uniform(0, 1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "taso_nasnet_cell, Uniform(0,1), GaussianNoise(1, 0.1)", + g, + Uniform(0, 1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("taso_nasnet_cell, Binary(1, 80)", g, Binary(1, 80)); + bench_mark_given_graph( + "taso_nasnet_cell, Binary(1, 80), UniformNoise(0.8, 1.25)", + g, + Binary(1, 80), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "taso_nasnet_cell, Binary(1, 80), GaussianNoise(1, 0.1)", + g, + Binary(1, 80), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("taso_nasnet_cell, Chooser({1.0, 20.0, 500.0})", + g, + Chooser({1.0, 20.0, 500.0})); + bench_mark_given_graph("taso_nasnet_cell, Chooser({1.0, 20.0, 500.0}), " + "UniformNoise(0.8, 1.25)", + g, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "taso_nasnet_cell, Chooser({1.0, 20.0, 500.0}), GaussianNoise(1, 0.1)", + g, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1)); + } + + { + DiGraph g = make_parallel_chains(10, 5); + bench_mark_given_graph("parallel_chains, Constant(1)", g, Constant(1)); + bench_mark_given_graph( + "parallel_chains, Constant(1), UniformNoise(0.8, 1.25)", + g, + Constant(1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "parallel_chains, Constant(1), GaussianNoise(1, 0.1)", + g, + Constant(1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("parallel_chains, Uniform(0,1)", g, Uniform(0, 1)); + bench_mark_given_graph( + "parallel_chains, Uniform(0,1), UniformNoise(0.8, 1.25)", + g, + Uniform(0, 1), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "parallel_chains, Uniform(0,1), GaussianNoise(1, 0.1)", + g, + Uniform(0, 1), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("parallel_chains, Binary(1, 80)", g, Binary(1, 80)); + bench_mark_given_graph( + "parallel_chains, Binary(1, 80), UniformNoise(0.8, 1.25)", + g, + Binary(1, 80), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "parallel_chains, Binary(1, 80), GaussianNoise(1, 0.1)", + g, + Binary(1, 80), + GaussianNoise(1, 0.1)); + + bench_mark_given_graph("parallel_chains, Chooser({1.0, 20.0, 500.0})", + g, + Chooser({1.0, 20.0, 500.0})); + bench_mark_given_graph( + "parallel_chains, Chooser({1.0, 20.0, 500.0}), UniformNoise(0.8, 1.25)", + g, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25)); + bench_mark_given_graph( + "parallel_chains, Chooser({1.0, 20.0, 500.0}), GaussianNoise(1, 0.1)", + g, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1)); + } + + { + + auto generate_2_terminal_random_dag = []() { + return make_2_terminal_random_dag(60, .12, 5); + }; + size_t repeat = 100; + bench_mark_given_graph_generator("make_2_terminal_random_dag, Constant(1)", + generate_2_terminal_random_dag, + Constant(1), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Constant(1), UniformNoise(0.8, 1.25)", + generate_2_terminal_random_dag, + Constant(1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Constant(1), GaussianNoise(1, 0.1)", + generate_2_terminal_random_dag, + Constant(1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator("make_2_terminal_random_dag, Uniform(0,1)", + generate_2_terminal_random_dag, + Uniform(0, 1), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Uniform(0,1), UniformNoise(0.8, 1.25)", + generate_2_terminal_random_dag, + Uniform(0, 1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Uniform(0,1), GaussianNoise(1, 0.1)", + generate_2_terminal_random_dag, + Uniform(0, 1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Binary(1, 80)", + generate_2_terminal_random_dag, + Binary(1, 80), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Binary(1, 80), UniformNoise(0.8, 1.25)", + generate_2_terminal_random_dag, + Binary(1, 80), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Binary(1, 80), GaussianNoise(1, 0.1)", + generate_2_terminal_random_dag, + Binary(1, 80), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Chooser({1.0, 20.0, 500.0})", + generate_2_terminal_random_dag, + Chooser({1.0, 20.0, 500.0}), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Chooser({1.0, 20.0, 500.0}), " + "UniformNoise(0.8, 1.25)", + generate_2_terminal_random_dag, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "make_2_terminal_random_dag, Chooser({1.0, 20.0, 500.0}), " + "GaussianNoise(1, 0.1)", + generate_2_terminal_random_dag, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1), + repeat); + } + + { + size_t repeat = 100; + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Constant(1)", + generate_nasnet_bench_network, + Constant(1), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Constant(1), UniformNoise(0.8, 1.25)", + generate_nasnet_bench_network, + Constant(1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Constant(1), GaussianNoise(1, 0.1)", + generate_nasnet_bench_network, + Constant(1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Uniform(0,1)", + generate_nasnet_bench_network, + Uniform(0, 1), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Uniform(0,1), UniformNoise(0.8, 1.25)", + generate_nasnet_bench_network, + Uniform(0, 1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Uniform(0,1), GaussianNoise(1, 0.1)", + generate_nasnet_bench_network, + Uniform(0, 1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Binary(1, 80)", + generate_nasnet_bench_network, + Binary(1, 80), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Binary(1, 80), UniformNoise(0.8, 1.25)", + generate_nasnet_bench_network, + Binary(1, 80), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Binary(1, 80), GaussianNoise(1, 0.1)", + generate_nasnet_bench_network, + Binary(1, 80), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Chooser({1.0, 20.0, 500.0})", + generate_nasnet_bench_network, + Chooser({1.0, 20.0, 500.0}), + NoNoise(), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Chooser({1.0, 20.0, 500.0}), " + "UniformNoise(0.8, 1.25)", + generate_nasnet_bench_network, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph_generator( + "generate_nasnet_bench_network, Chooser({1.0, 20.0, 500.0}), " + "GaussianNoise(1, 0.1)", + generate_nasnet_bench_network, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1), + repeat); + } + + { + size_t repeat = 10; + DiGraph g = make_full_taso_nasnet(1, 1); + bench_mark_given_graph("make_full_taso_nasnet, Constant(1)", + g, + Constant(1), + NoNoise(), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Constant(1), UniformNoise(0.8, 1.25)", + g, + Constant(1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Constant(1), GaussianNoise(1, 0.1)", + g, + Constant(1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph("make_full_taso_nasnet, Uniform(0,1)", + g, + Uniform(0, 1), + NoNoise(), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Uniform(0,1), UniformNoise(0.8, 1.25)", + g, + Uniform(0, 1), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Uniform(0,1), GaussianNoise(1, 0.1)", + g, + Uniform(0, 1), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph("make_full_taso_nasnet, Binary(1, 80)", + g, + Binary(1, 80), + NoNoise(), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Binary(1, 80), UniformNoise(0.8, 1.25)", + g, + Binary(1, 80), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph( + "make_full_taso_nasnet, Binary(1, 80), GaussianNoise(1, 0.1)", + g, + Binary(1, 80), + GaussianNoise(1, 0.1), + repeat); + + bench_mark_given_graph("make_full_taso_nasnet, Chooser({1.0, 20.0, 500.0})", + g, + Chooser({1.0, 20.0, 500.0}), + NoNoise(), + repeat); + bench_mark_given_graph("make_full_taso_nasnet, Chooser({1.0, 20.0, " + "500.0}), UniformNoise(0.8, 1.25)", + g, + Chooser({1.0, 20.0, 500.0}), + UniformNoise(0.8, 1.25), + repeat); + bench_mark_given_graph("make_full_taso_nasnet, Chooser({1.0, 20.0, " + "500.0}), GaussianNoise(1, 0.1)", + g, + Chooser({1.0, 20.0, 500.0}), + GaussianNoise(1, 0.1), + repeat); + } +} diff --git a/lib/utils/include/utils/containers/invert_map.h b/lib/utils/include/utils/containers/invert_map.h new file mode 100644 index 0000000000..6f0c04a189 --- /dev/null +++ b/lib/utils/include/utils/containers/invert_map.h @@ -0,0 +1,21 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_INVERT_MAP_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_INVERT_MAP_H + +#include +#include +#include + +namespace FlexFlow { + +template +std::unordered_map> + invert_map(std::unordered_map const &m) { + std::unordered_map> result; + for (auto const &[key, value] : m) { + result[value].insert(key); + } + return result; +} +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/containers/unordered_set_of.h b/lib/utils/include/utils/containers/unordered_set_of.h index 722ae66d43..22ed323891 100644 --- a/lib/utils/include/utils/containers/unordered_set_of.h +++ b/lib/utils/include/utils/containers/unordered_set_of.h @@ -1,5 +1,5 @@ -#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_UNIQUE_H -#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_UNIQUE_H +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_UNORDERED_SET_OF_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_CONTAINERS_UNORDERED_SET_OF_H #include diff --git a/lib/utils/include/utils/graph/algorithms.h b/lib/utils/include/utils/graph/algorithms.h index 3f170b5652..f130b17421 100644 --- a/lib/utils/include/utils/graph/algorithms.h +++ b/lib/utils/include/utils/graph/algorithms.h @@ -161,8 +161,6 @@ std::vector std::vector get_bfs_ordering(DiGraphView const &, std::unordered_set const &starting_points); -std::vector get_topological_ordering(DiGraphView const &); -std::vector get_unchecked_topological_ordering(DiGraphView const &); std::vector get_edge_topological_ordering(DiGraphView const &); // std::vector diff --git a/lib/utils/include/utils/graph/digraph/algorithms.h b/lib/utils/include/utils/graph/digraph/algorithms.h index 370f181c37..d1f6147383 100644 --- a/lib/utils/include/utils/graph/digraph/algorithms.h +++ b/lib/utils/include/utils/graph/digraph/algorithms.h @@ -6,6 +6,7 @@ namespace FlexFlow { std::unordered_set get_edges(DiGraphView const &); +int num_edges(DiGraphView const &); std::unordered_set get_sources(DiGraphView const &); std::unordered_set get_sinks(DiGraphView const &); diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_ancestors.h b/lib/utils/include/utils/graph/digraph/algorithms/get_ancestors.h new file mode 100644 index 0000000000..1692900216 --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_ancestors.h @@ -0,0 +1,19 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_ANCESTORS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_ANCESTORS_H + +#include "utils/graph/digraph/digraph_view.h" + +namespace FlexFlow { +/** + * @brief Computes the set of all ancestors of a given node `n` in a directed + *graph, which is the set of all nodes `m` for which a directed path from `n` to + *`m` exists. + * + * @note `n` is not considered to be its own descendant, and is thus not + *included in the returned set. + **/ +std::unordered_set get_ancestors(DiGraphView const &g, Node const &n); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_bottlenecks.h b/lib/utils/include/utils/graph/digraph/algorithms/get_bottlenecks.h new file mode 100644 index 0000000000..69eb435144 --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_bottlenecks.h @@ -0,0 +1,23 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_BOTTLENECKS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_BOTTLENECKS_H + +#include "utils/graph/digraph/digraph_view.h" + +namespace FlexFlow { + +/** + * @brief Returns the bottlenecks of the graph. + * + * A bottleneck is a node through which all paths from any sink to any source + must pass. + + * @note + * The graph must be acyclic and singly connected. + * Note that, under the definition of bottleneck, a source / sink is a + bottleneck if and only if it's the unique source / sink of the graph. + */ +std::unordered_set get_bottlenecks(DiGraphView const &g); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_descendants.h b/lib/utils/include/utils/graph/digraph/algorithms/get_descendants.h new file mode 100644 index 0000000000..e73d84e20e --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_descendants.h @@ -0,0 +1,20 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_DESCENDANTS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_DESCENDANTS_H + +#include "utils/graph/digraph/digraph_view.h" + +namespace FlexFlow { + +/** + * @brief Computes the set of all descendants of a given node in a directed + *graph. + * + * @note `starting_node` is not considered to be its own descendant, and is thus + *not included in the returned set. + **/ +std::unordered_set get_descendants(DiGraphView const &g, + Node const &starting_node); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h b/lib/utils/include/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h new file mode 100644 index 0000000000..b67035ee5c --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h @@ -0,0 +1,36 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_LONGEST_PATH_LENGTHS_FROM_ROOT_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_LONGEST_PATH_LENGTHS_FROM_ROOT_H + +#include "utils/graph/digraph/digraph_view.h" +#include + +namespace FlexFlow { + +/** + * @brief Computes the longest path lengths from the root in directed acyclic + * graph. + * + * @return std::unordered_map For each node n, returns the length + * (i.e. number of nodes) of the longest path from the root to n. + * + * @note The root has a path length of 1. g must be acyclic. + */ +std::unordered_map + get_longest_path_lengths_from_root(DiGraphView const &g); + +/** + * @brief Computes the weighted longest path lengths from the root in a directed + * acyclic graph. + * + * @return std::unordered_map For each node n, returns the length + * (i.e. the sum of the weights of all the nodes) of the longest path from the + * root to n. + * + * @note The root has a path length equal to its weight. g must be acyclic. + */ +std::unordered_map get_weighted_longest_path_lengths_from_root( + DiGraphView const &g, std::unordered_map const &node_costs); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_lowest_common_ancestors.h b/lib/utils/include/utils/graph/digraph/algorithms/get_lowest_common_ancestors.h new file mode 100644 index 0000000000..60b0d32ae2 --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_lowest_common_ancestors.h @@ -0,0 +1,41 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_LOWEST_COMMON_ANCESTORS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_LOWEST_COMMON_ANCESTORS_H + +#include "utils/graph/digraph/digraph_view.h" +#include + +namespace FlexFlow { + +/** + * @brief Finds the lowest common ancestor (LCA) of a set of nodes in a directed + * graph. + * + * @details + * Within this function, we consider the set of ancestors of a given node to + * include the node itself, so the lowest common ancestor of a set of nodes can + * be contained in the input set of nodes itself. + * + * For example, consider the following directed graph: + * + * ``` + * digraph { + * 0 -> 1; + * 0 -> 2; + * 1 -> 3; + * 1 -> 4; + * } + * ``` + * + * The lowest common ancestor of nodes 3 and 1 is 1. + * + * @note + * In a Directed Acyclic Graph, a set of nodes can have no LCA, a unique node as + * LCA, or a set of nodes as LCA. + */ +std::optional> + get_lowest_common_ancestors(DiGraphView const &g, + std::unordered_set const &nodes); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.h b/lib/utils/include/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.h new file mode 100644 index 0000000000..db017c11da --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.h @@ -0,0 +1,23 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_TOPOLOGICAL_ORDERING_FROM_STARTING_NODE_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_TOPOLOGICAL_ORDERING_FROM_STARTING_NODE_H + +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/node/node.dtg.h" + +namespace FlexFlow { + +/** + * @brief Returns a topologically ordered vector of nodes, with the topological + * traversal starting from the starting node. + * + * @note Nodes present within the graph that are not reachable by a traversal + * starting from the starting_node will not be included in the returned vector. + * g must be an acyclic graph + */ +std::vector + get_topological_ordering_from_starting_node(DiGraphView const &g, + Node const &starting_node); + +} // namespace FlexFlow + +#endif // _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_GET_TOPOLOGICAL_ORDERING_FROM_STARTING_NODE_H diff --git a/lib/utils/include/utils/graph/digraph/algorithms/is_2_terminal_dag.h b/lib/utils/include/utils/graph/digraph/algorithms/is_2_terminal_dag.h new file mode 100644 index 0000000000..3b588c7984 --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/is_2_terminal_dag.h @@ -0,0 +1,12 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_IS_2_TERMINAL_DAG_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_IS_2_TERMINAL_DAG_H + +#include "utils/graph/digraph/digraph_view.h" + +namespace FlexFlow { + +bool is_2_terminal_dag(DiGraphView const &); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/is_acyclic.h b/lib/utils/include/utils/graph/digraph/algorithms/is_acyclic.h index 909dc3aef4..ce63f75395 100644 --- a/lib/utils/include/utils/graph/digraph/algorithms/is_acyclic.h +++ b/lib/utils/include/utils/graph/digraph/algorithms/is_acyclic.h @@ -5,7 +5,7 @@ namespace FlexFlow { -std::optional is_acyclic(DiGraphView const &); +bool is_acyclic(DiGraphView const &); } // namespace FlexFlow diff --git a/lib/utils/include/utils/graph/digraph/algorithms/is_tree.h b/lib/utils/include/utils/graph/digraph/algorithms/is_tree.h new file mode 100644 index 0000000000..340d54aab4 --- /dev/null +++ b/lib/utils/include/utils/graph/digraph/algorithms/is_tree.h @@ -0,0 +1,12 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_IS_TREE_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_IS_TREE_H + +#include "utils/graph/digraph/digraph_view.h" + +namespace FlexFlow { + +bool is_tree(DiGraphView const &g); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/digraph/algorithms/transitive_reduction.h b/lib/utils/include/utils/graph/digraph/algorithms/transitive_reduction.h index b4cdc62f83..ad11c6388c 100644 --- a/lib/utils/include/utils/graph/digraph/algorithms/transitive_reduction.h +++ b/lib/utils/include/utils/graph/digraph/algorithms/transitive_reduction.h @@ -1,6 +1,7 @@ #ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_TRANSITIVE_REDUCTION_H #define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_DIGRAPH_ALGORITHMS_TRANSITIVE_REDUCTION_H +#include "utils/graph/digraph/digraph.h" #include "utils/graph/digraph/digraph_view.h" namespace FlexFlow { @@ -21,7 +22,7 @@ struct DirectedEdgeMaskView final : public IDiGraphView { std::unordered_set edge_mask; }; -DiGraphView transitive_reduction(DiGraphView const &); +DiGraph transitive_reduction(DiGraphView const &); } // namespace FlexFlow diff --git a/lib/utils/include/utils/graph/series_parallel/digraph_generation.h b/lib/utils/include/utils/graph/series_parallel/digraph_generation.h new file mode 100644 index 0000000000..ec25c21481 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/digraph_generation.h @@ -0,0 +1,34 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIAL_PARALLEL_DIGRAPH_GENERATION_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIAL_PARALLEL_DIGRAPH_GENERATION_H + +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" + +namespace FlexFlow { + +std::unordered_map parallel_extend(DiGraph &g, + DiGraphView const &ext); +std::unordered_map serial_extend(DiGraph &g, + DiGraphView const &ext); +DiGraph series_composition(DiGraphView const &g1, DiGraphView const &g2); +DiGraph parallel_composition(DiGraphView const &g1, DiGraphView const &g2); +DiGraph series_composition(std::vector const &graphs); +DiGraph parallel_composition(std::vector const &graphs); + +/** + * @brief Constructs a directed DiGraph from a series-parallel decomposition. + * + * @details The transformation is performed recursively as follows: + * - Nodes in the decomposition remain the same in the resulting graph. + * - For serial composition between graphs, an all-to-all connection is created + * between the terminal nodes of one graph and the initial nodes of the + * following one. + * - For parallel composition between graphs, the union of the graphs is taken + * without adding any additional edges. + * + */ +DiGraph digraph_from_sp_decomposition(SeriesParallelDecomposition const &sp); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/get_ancestors.h b/lib/utils/include/utils/graph/series_parallel/get_ancestors.h new file mode 100644 index 0000000000..b7ae79bf49 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/get_ancestors.h @@ -0,0 +1,13 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIAL_PARALLLEL_GET_ANCESTORS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIAL_PARALLLEL_GET_ANCESTORS_H + +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" + +namespace FlexFlow { + +std::unordered_set get_ancestors(SeriesParallelDecomposition const &sp, + Node const &starting_node); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/normalize_sp_decomposition.h b/lib/utils/include/utils/graph/series_parallel/normalize_sp_decomposition.h new file mode 100644 index 0000000000..46b60cd636 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/normalize_sp_decomposition.h @@ -0,0 +1,24 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_normalize_sp_decomposition_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_normalize_sp_decomposition_H + +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" + +namespace FlexFlow { + +/** + * @brief Recursively normalizes a SeriesParallelDecomposition. + * + * @details This function performs the following semantic substitutions: + * - Deletes every empty SeriesSplit and ParallelSplit item, e.g., + * S(P(S()), Node(1), Node(2)) -> S(Node(1), Node(2)) + * + * - Replaces SeriesSplit and ParallelSplit of size 1 with their content, e.g., + * S(S(Node(1)), P(Node(2))) -> S(Node(1), Node(2))) + * + */ +SeriesParallelDecomposition + normalize_sp_decomposition(SeriesParallelDecomposition const &sp); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/series_parallel_decomposition.h b/lib/utils/include/utils/graph/series_parallel/series_parallel_decomposition.h index 52d2cb7236..7f510a68a4 100644 --- a/lib/utils/include/utils/graph/series_parallel/series_parallel_decomposition.h +++ b/lib/utils/include/utils/graph/series_parallel/series_parallel_decomposition.h @@ -17,6 +17,26 @@ std::unordered_multiset get_nodes(SeriesSplit const &); std::unordered_multiset get_nodes(ParallelSplit const &); std::unordered_multiset get_nodes(Node const &); +bool is_empty(Node const &node); +bool is_empty(SeriesSplit const &serial); +bool is_empty(ParallelSplit const ¶llel); +bool is_empty(SeriesParallelDecomposition const &sp); + +bool has_no_duplicate_nodes(SeriesParallelDecomposition const &sp); + +/** + * @brief Counts the total number of nodes in a series-parallel decomposition + * @note Nodes that appear multiple times in the decomposition are counted + * multiple times + */ +size_t num_nodes(SeriesParallelDecomposition const &sp); + +SeriesParallelDecomposition series_composition( + std::vector const &sp_compositions); +SeriesParallelDecomposition parallel_composition( + std::unordered_multiset const + &sp_compositions); + } // namespace FlexFlow #endif diff --git a/lib/utils/include/utils/graph/series_parallel/series_parallel_metrics.h b/lib/utils/include/utils/graph/series_parallel/series_parallel_metrics.h new file mode 100644 index 0000000000..c3b5659723 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/series_parallel_metrics.h @@ -0,0 +1,77 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIES_PARALLEL_METRICS_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIES_PARALLEL_METRICS_H + +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include + +namespace FlexFlow { + +/** + * @brief Maps each node to the number of times it appears in the decomposition. + * + */ +std::unordered_map get_node_counter_map(Node const &node); +std::unordered_map + get_node_counter_map(SeriesSplit const &serial); +std::unordered_map + get_node_counter_map(ParallelSplit const ¶llel); +std::unordered_map + get_node_counter_map(SeriesParallelDecomposition const &sp); + +/** + * @brief Calculates the total cumulative cost of all nodes in the + * decomposition. + * + */ +float work_cost(SeriesParallelDecomposition const &sp, + std::unordered_map cost_map); + +float work_cost(DiGraphView const &g, + std::unordered_map const &cost_map); + +/** + * @brief Computes the total number of edges the decomposition has when viewed + * as a DiGraph where Series connections are all to all. + * + */ +int num_dependencies(SeriesParallelDecomposition const &sp); + +int num_dependencies(DiGraphView const &g); + +float critical_path_cost(SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map); + +float critical_path_cost(DiGraphView const &g, + std::unordered_map const &cost_map); + +/** + * @brief Calculates the relative increase in total work cost between the + * original (possibly non-series-parallel) graph and a possible series-parallel + * decomposition of that graph. + */ +float relative_work_increase(DiGraphView const &g, + SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map); + +/** + * @brief Calculates the relative increase in critical path cost between the + * original (possibly non-series-parallel) graph and a possible series-parallel + * decomposition of that graph. + */ +float relative_critical_path_cost_increase( + DiGraphView const &g, + SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map); + +/** + * @brief Calculates the relative increase in the number of dependencies between + * the original (possibly non-series-parallel) graph and a possible + * series-parallel decomposition of that graph. + */ +float relative_num_dependencies_increase(DiGraphView const &g, + SeriesParallelDecomposition const &sp); + +} // namespace FlexFlow + +#endif // _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_GRAPH_SERIES_PARALLEL_METRICS_H diff --git a/lib/utils/include/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h b/lib/utils/include/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h new file mode 100644 index 0000000000..38c27e4685 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h @@ -0,0 +1,103 @@ +#ifndef _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_CRITICAL_PATH_PRESERVING_SP_IZATION_H +#define _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_CRITICAL_PATH_PRESERVING_SP_IZATION_H + +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include + +namespace FlexFlow { + +/** + * @brief Transforms a directed acyclic graph (DAG) into a Serial Parallel (SP) + * graph. The critical path cost is unchanged, and the SP-ization is done solely + * through node (work) duplication. + * + * @details + * The resulting graph, encoded as a SeriesParallelDecomposition, is a tree + * whose critical path is the same as that of the original graph. The tree is + * constructed as follows: + * - Denote SP(n) as the SeriesParallelDecomposition of the subgraph of g whose + * nodes are all the ancestors of n. + * - Denote the predecessors of n as M. + * - Then: + * - SP(n) = S(n, P({SP(m) for m in M})) + * - SP(root) = root + * - SP(sink) = SP(g) + * Where P, S represent parallel, serial composition respectively. + * + * Example: + * + * digraph G { + * n1 -> n2; + * n1 -> n3; + * n2 -> n4; + * n2 -> n5; + * n3 -> n5; + * n5 -> n6; + * n4 -> n6; + * } + * + * becomes + * + * digraph SP { + * n1 -> n2; + * n2 -> n3; + * n3 -> n4; + * n5 -> n6; + * n6 -> n7; + * n7 -> n4; + * n8 -> n9; + * n9 -> n7; + * } + * + * + * @note g must be a 2 terminal (i.e. single source and single sink) directed + * acyclic graph. + */ +SeriesParallelDecomposition + critical_path_preserving_sp_ization(DiGraphView const &g); + +/** + * @brief Transforms a directed acyclic graph (DAG) into a Serial Parallel (SP) + * graph with coalescing. The critical path cost is unchanged, and the + * SP-ization is done solely through node (work) duplication. + * + * @details + * This SP-ization technique, compared to the previous step, adds an additional + * coalescing step during parallel composition to reduce node duplication. The + * recursive formulation is equivalent, but the parallelization performs an + * additional coalescing step, where parallel strands with common heads are + * merged together. Example: P(S(1,2), S(1,3)) -> P(1, S(2,3)). + * + * Example: + * + * digraph G { + * n1 -> n2; + * n1 -> n3; + * n2 -> n4; + * n2 -> n5; + * n3 -> n5; + * n5 -> n6; + * n4 -> n6; + * } + * + * becomes + * + * digraph SP { + * n1 -> n2; + * n2 -> n3; + * n3 -> n4; + * n1 -> n6; + * n6 -> n7; + * n7 -> n4; + * n1 -> n9; + * n9 -> n7; + * } + * + */ +SeriesParallelDecomposition + critical_path_preserving_sp_ization_with_coalescing(DiGraphView const &g); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h b/lib/utils/include/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h new file mode 100644 index 0000000000..79a2351fa3 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h @@ -0,0 +1,27 @@ +#ifndef _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_IS_VALID_SP_IZATION_H +#define _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_IS_VALID_SP_IZATION_H + +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include + +namespace FlexFlow { +/** + * @brief Checks if dependencies are maintained between a directed graph and its + * series-parallel decomposition. + * + * @details This function ensures that the series-parallel decomposition is a + * valid sp-ization of the given directed graph, by checking that dependencies + * are maintained. Dependencies are considered maintained if: + * - Both the directed graph and the series-parallel decomposition contain the + * same set of nodes. + * - For every node in the directed graph, all its ancestors are also ancestors + * within the series-parallel decomposition. + * + */ +bool dependencies_are_maintained(DiGraphView const &g, + SeriesParallelDecomposition const &sp); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/sp_ization/node_role.enum.toml b/lib/utils/include/utils/graph/series_parallel/sp_ization/node_role.enum.toml new file mode 100644 index 0000000000..bdc5940383 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/sp_ization/node_role.enum.toml @@ -0,0 +1,17 @@ +namespace = "FlexFlow" +name = "NodeRole" +features = [ + "hash", + "json", + "rapidcheck", + "fmt", +] + +[[values]] +name = "PURE" + +[[values]] +name = "SYNC" + +[[values]] +name = "DUMMY" diff --git a/lib/utils/include/utils/graph/series_parallel/sp_ization/spanish_algo.h b/lib/utils/include/utils/graph/series_parallel/sp_ization/spanish_algo.h new file mode 100644 index 0000000000..c4a2081a55 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/sp_ization/spanish_algo.h @@ -0,0 +1,27 @@ +#ifndef _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_SP_IZATION_SPANISH_ALGO_H +#define _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_SP_IZATION_SPANISH_ALGO_H + +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/sp_ization/node_role.dtg.h" +#include +namespace FlexFlow { + +DiGraph add_dummy_nodes(DiGraph g, + std::unordered_map &node_roles); + +DiGraph + delete_dummy_nodes(DiGraph g, + std::unordered_map const &node_roles); + +std::unordered_set + get_component(DiGraph const &g, + Node const &node, + std::unordered_map const &depth_map, + std::unordered_map const &node_roles); + +SeriesParallelDecomposition spanish_strata_sync(DiGraph g); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h b/lib/utils/include/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h new file mode 100644 index 0000000000..7d613d7563 --- /dev/null +++ b/lib/utils/include/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h @@ -0,0 +1,69 @@ +#ifndef _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_WORK_PRESERVING_SP_IZATION_H +#define _FLEXFLOW_UTILS_GRAPH_SERIAL_PARALLEL_WORK_PRESERVING_SP_IZATION_H + +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include + +namespace FlexFlow { + +/** + * @brief + * Transforms a directed acyclic graph (DAG) into a Serial Parallel (SP) + * graph. The total number of nodes remains unchanged, and the SP-ization is + * done solely through edge (dependency) duplication. + * @details + * The graph is first partitioned into strata: the i_th stratum contains all the + * nodes whose critical path length has length i. The nodes in a given stratum + * are composed in parallel, and the strata are serially composed in succession. + * + * Example: + * + * + * digraph G { + * n1 -> n2; + * n1 -> n3; + * n2 -> n4; + * n2 -> n5; + * n3 -> n5; + * n5 -> n6; + * n4 -> n6; + * } + * becomes + * + * digraph SP { + * n1 -> n2; + * n1 -> n3; + * n2 -> n4; + * n2 -> n5; + * n3 -> n5; + * n4 -> n6; + * n5 -> n6; + * n4 -> n6; + * } + * + * @note g must be a directed acyclic graph. + **/ +SeriesParallelDecomposition stratum_sync_sp_ization(DiGraphView const &g); + +/** + * @brief + * Transforms a directed acyclic graph (DAG) into a Serial Parallel (SP) + * graph. The total number of nodes remains unchanged, and the SP-ization is + * done solely through edge (dependency) duplication. + * + * @details + * The algorithm operates under the same principles as + * `stratum_sync_sp_ization`: that is, a stratification step where the nodes are + * partitioned into strata, followed by a merging step where the strata are + * joined. The difference concerns the stratification step, which is cost-aware, + * so that the different disjoint subgraphs present within the same strata have + *a similar critical path cost, thus minimizing the overall critical path cost + *of the SP-ized graph. + **/ +SeriesParallelDecomposition cost_aware_stratum_sync_sp_ization( + DiGraphView const &g, std::unordered_map const &cost_map); + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/src/utils/containers/invert_map.cc b/lib/utils/src/utils/containers/invert_map.cc new file mode 100644 index 0000000000..ca7308d4a0 --- /dev/null +++ b/lib/utils/src/utils/containers/invert_map.cc @@ -0,0 +1 @@ +#include "utils/containers/invert_map.h" diff --git a/lib/utils/src/utils/graph/algorithms.cc b/lib/utils/src/utils/graph/algorithms.cc index 6ed41daf43..7a4e972cf3 100644 --- a/lib/utils/src/utils/graph/algorithms.cc +++ b/lib/utils/src/utils/graph/algorithms.cc @@ -13,6 +13,7 @@ #include "utils/graph/digraph/algorithms/get_incoming_edges.h" #include "utils/graph/digraph/algorithms/get_node_with_greatest_topo_rank.h" #include "utils/graph/digraph/algorithms/get_outgoing_edges.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering.h" #include "utils/graph/digraph/directed_edge_query.h" #include "utils/graph/node/algorithms.h" #include "utils/graph/node/node_query.h" diff --git a/lib/utils/src/utils/graph/digraph/algorithms.cc b/lib/utils/src/utils/graph/digraph/algorithms.cc index 8cd685e5c6..78afc994e8 100644 --- a/lib/utils/src/utils/graph/digraph/algorithms.cc +++ b/lib/utils/src/utils/graph/digraph/algorithms.cc @@ -15,6 +15,10 @@ std::unordered_set get_edges(DiGraphView const &g) { return g.query_edges(directed_edge_query_all()); } +int num_edges(DiGraphView const &g) { + return get_edges(g).size(); +} + std::unordered_set get_sinks(DiGraphView const &g) { return get_sources(flipped(g)); } diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_ancestors.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_ancestors.cc new file mode 100644 index 0000000000..96a34e6f0b --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_ancestors.cc @@ -0,0 +1,12 @@ +#include "utils/graph/digraph/algorithms/get_ancestors.h" +#include "utils/graph/digraph/algorithms/flipped.h" +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" + +namespace FlexFlow { +std::unordered_set get_ancestors(DiGraphView const &g, + Node const &starting_node) { + assert(is_acyclic(g)); + return get_descendants(flipped(g), starting_node); +} +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_bottlenecks.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_bottlenecks.cc new file mode 100644 index 0000000000..adef1c39d3 --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_bottlenecks.cc @@ -0,0 +1,34 @@ +#include "utils/graph/digraph/algorithms/get_bottlenecks.h" +#include "utils/containers/get_only.h" +#include "utils/containers/set_difference.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_weakly_connected_components.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/node/algorithms.h" + +namespace FlexFlow { +std::unordered_set get_bottlenecks(DiGraphView const &g) { + std::unordered_set bottlenecks; + assert(is_acyclic(g)); + assert(get_weakly_connected_components(g).size() == + 1); // must be singly connected + + for (Node const &n : get_nodes(g)) { + DiGraphView subgraph = get_subgraph(g, set_difference(get_nodes(g), {n})); + if (get_weakly_connected_components(subgraph).size() == 2) { + bottlenecks.insert(n); + } + } + + if (get_sources(g).size() == 1) { + bottlenecks.insert(get_only(get_sources(g))); + } + + if (get_sinks(g).size() == 1) { + bottlenecks.insert(get_only(get_sinks(g))); + } + + return bottlenecks; +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_descendants.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_descendants.cc new file mode 100644 index 0000000000..0ccaa1bb0a --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_descendants.cc @@ -0,0 +1,35 @@ +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/containers/contains.h" +#include "utils/containers/filter.h" +#include "utils/graph/digraph/algorithms/get_successors.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/node/algorithms.h" + +namespace FlexFlow { +std::unordered_set get_descendants(DiGraphView const &g, + Node const &starting_node) { + assert(is_acyclic(g)); + assert(contains(get_nodes(g), starting_node)); + + std::unordered_set descendants; + std::stack to_visit; + for (Node const &successor : get_successors(g, starting_node)) { + to_visit.push(successor); + } + while (!to_visit.empty()) { + Node current = to_visit.top(); + to_visit.pop(); + descendants.insert(current); + + for (auto const &s : filter(get_successors(g, current), [&](Node const &n) { + return !contains(descendants, n); + })) { + to_visit.push(s); + } + } + + return descendants; +}; + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_dominators_map.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_dominators_map.cc index 3dd9de73f0..13dd0a15c2 100644 --- a/lib/utils/src/utils/graph/digraph/algorithms/get_dominators_map.cc +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_dominators_map.cc @@ -9,6 +9,7 @@ #include "utils/graph/digraph/algorithms/get_topological_ordering.h" #include "utils/graph/node/algorithms.h" #include "utils/hash/unordered_set.h" +#include #include namespace FlexFlow { diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc new file mode 100644 index 0000000000..a682cf1402 --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc @@ -0,0 +1,53 @@ +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/containers/maximum.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include + +namespace FlexFlow { + +std::unordered_map get_weighted_longest_path_lengths_from_root( + DiGraphView const &g, std::unordered_map const &node_costs) { + + assert(is_acyclic(g)); + + std::vector topo_order = get_topological_ordering(g); + std::unordered_map longest_path_lengths; + + for (Node const &n : topo_order) { + std::unordered_set predecessor_path_lengths = + transform(get_predecessors(g, n), [&](Node const &pred) { + return longest_path_lengths.at(pred); + }); + longest_path_lengths[n] = + (predecessor_path_lengths.size() == 0) + ? node_costs.at(n) + : maximum(predecessor_path_lengths) + node_costs.at(n); + } + return longest_path_lengths; +} + +std::unordered_map + get_longest_path_lengths_from_root(DiGraphView const &g) { + + assert(is_acyclic(g)); + + std::vector topo_order = get_topological_ordering(g); + std::unordered_map longest_path_lengths; + + for (Node const &n : topo_order) { + std::unordered_set predecessor_path_lengths = + transform(get_predecessors(g, n), [&](Node const &pred) { + return longest_path_lengths.at(pred); + }); + longest_path_lengths[n] = (predecessor_path_lengths.size() == 0) + ? 1 + : maximum(predecessor_path_lengths) + 1; + } + + return longest_path_lengths; +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc new file mode 100644 index 0000000000..46a8cc3f69 --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc @@ -0,0 +1,40 @@ +#include "utils/containers/intersection.h" +#include "utils/containers/is_subseteq_of.h" +#include "utils/containers/maximum.h" +#include "utils/containers/transform.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_ancestors.h" +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/node/algorithms.h" +#include "utils/hash/unordered_set.h" +#include + +namespace FlexFlow { + +std::optional> + get_lowest_common_ancestors(DiGraphView const &g, + std::unordered_set const &nodes) { + assert(is_acyclic(g)); + assert(is_subseteq_of(nodes, get_nodes(g))); + if (num_nodes(g) == 0 || nodes.size() == 0) { + return std::nullopt; + } + std::unordered_set> ancestors = + transform(nodes, [&](Node const &n) { + return set_union(get_ancestors(g, n), {n}); + }); + std::unordered_set common_ancestors = intersection(ancestors).value(); + if (common_ancestors.empty()) { + return common_ancestors; + } + std::unordered_map depth_levels = + get_longest_path_lengths_from_root(g); + int largest_depth_for_common_ancestors = maximum(transform( + common_ancestors, [&](Node const &n) { return depth_levels.at(n); })); + return filter(common_ancestors, [&](Node const &n) { + return depth_levels.at(n) == largest_depth_for_common_ancestors; + }); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering.cc index 41fe3b67d5..fc0646ce5e 100644 --- a/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering.cc +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering.cc @@ -1,6 +1,8 @@ #include "utils/graph/digraph/algorithms/get_topological_ordering.h" +#include "utils/graph/algorithms.h" #include "utils/graph/digraph/algorithms.h" #include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_successors.h" #include "utils/graph/digraph/algorithms/is_acyclic.h" #include "utils/graph/node/algorithms.h" #include "utils/graph/traversal.h" diff --git a/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.cc b/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.cc new file mode 100644 index 0000000000..40a2fd46ed --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.cc @@ -0,0 +1,29 @@ +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_successors.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/traversal.h" + +namespace FlexFlow { + +static std::vector get_unchecked_topological_ordering_from_starting_node( + DiGraphView const &g, Node const &starting_node) { + + std::unordered_set descendants = get_descendants(g, starting_node); + descendants.insert(starting_node); + return get_topological_ordering(get_subgraph(g, descendants)); +} + +std::vector + get_topological_ordering_from_starting_node(DiGraphView const &g, + Node const &starting_node) { + assert(is_acyclic(g)); + return get_unchecked_topological_ordering_from_starting_node(g, + starting_node); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/is_2_terminal_dag.cc b/lib/utils/src/utils/graph/digraph/algorithms/is_2_terminal_dag.cc new file mode 100644 index 0000000000..84b95a6c38 --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/is_2_terminal_dag.cc @@ -0,0 +1,11 @@ +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" + +namespace FlexFlow { + +bool is_2_terminal_dag(DiGraphView const &g) { + return (is_acyclic(g) && (get_sources(g).size() == 1) && + get_sinks(g).size() == 1); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/is_acyclic.cc b/lib/utils/src/utils/graph/digraph/algorithms/is_acyclic.cc index dd660f193d..c26cf70ebc 100644 --- a/lib/utils/src/utils/graph/digraph/algorithms/is_acyclic.cc +++ b/lib/utils/src/utils/graph/digraph/algorithms/is_acyclic.cc @@ -1,30 +1,52 @@ #include "utils/graph/digraph/algorithms/is_acyclic.h" -#include "utils/graph/digraph/algorithms.h" +#include "utils/containers/generate_map.h" +#include "utils/graph/digraph/algorithms/get_successors.h" #include "utils/graph/node/algorithms.h" -#include "utils/graph/traversal.h" +#include namespace FlexFlow { -std::optional is_acyclic(DiGraphView const &g) { +enum class ExplorationStatus { NOT_EXPLORED, BEING_EXPLORED, FULLY_EXPLORED }; + +bool is_acyclic(DiGraphView const &g) { if (num_nodes(g) == 0) { - return std::nullopt; - } - std::unordered_set sources = get_sources(g); - if (sources.size() == 0) { - return false; + return true; // vacuously true } - auto dfs_view = unchecked_dfs(g, sources); - std::unordered_set seen; - for (unchecked_dfs_iterator it = dfs_view.begin(); it != dfs_view.end(); - it++) { - if (contains(seen, *it)) { - return false; - } else { - seen.insert(*it); + + std::unordered_map status = + generate_map(get_nodes(g), [](Node const &n) { + return ExplorationStatus::NOT_EXPLORED; + }); + + // recursively explore a given node and all its successors: if, while + // exploring, we find a node that was already being explored, then there is a + // cycle + std::function cycle_downstream_from_node = + [&](Node const &n) -> bool { + status[n] = ExplorationStatus::BEING_EXPLORED; + + for (Node const &successor : get_successors(g, n)) { + if (status.at(successor) == ExplorationStatus::NOT_EXPLORED) { + if (cycle_downstream_from_node( + successor)) { // one of the descendants is part of a cycle + return true; + } + } else if (status.at(successor) == ExplorationStatus::BEING_EXPLORED) { + return true; // we're exploring a node we were already exploring: we + // have hit a cycle + } } - } - if (seen != get_nodes(g)) { + + status[n] = ExplorationStatus::FULLY_EXPLORED; return false; + }; + + for (Node const &node : get_nodes(g)) { + if (status.at(node) == ExplorationStatus::NOT_EXPLORED) { + if (cycle_downstream_from_node(node)) { + return false; + } + } } return true; } diff --git a/lib/utils/src/utils/graph/digraph/algorithms/is_tree.cc b/lib/utils/src/utils/graph/digraph/algorithms/is_tree.cc new file mode 100644 index 0000000000..567b55987c --- /dev/null +++ b/lib/utils/src/utils/graph/digraph/algorithms/is_tree.cc @@ -0,0 +1,18 @@ +#include "utils/graph/digraph/algorithms/is_tree.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/undirected/algorithms/get_connected_components.h" + +namespace FlexFlow { + +bool is_tree(DiGraphView const &g) { + assert(num_nodes(g) > 0); + + bool has_single_root = get_sources(g).size() == 1; + bool is_connected = get_connected_components(as_undirected(g)).size() == 1; + bool node_edge_diff_is_1 = (num_nodes(g) - num_edges(g)) == 1; + return has_single_root && is_connected && node_edge_diff_is_1; +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/digraph/algorithms/transitive_reduction.cc b/lib/utils/src/utils/graph/digraph/algorithms/transitive_reduction.cc index 97a2439263..18f5ba34ca 100644 --- a/lib/utils/src/utils/graph/digraph/algorithms/transitive_reduction.cc +++ b/lib/utils/src/utils/graph/digraph/algorithms/transitive_reduction.cc @@ -1,10 +1,14 @@ #include "utils/graph/digraph/algorithms/transitive_reduction.h" #include "utils/bidict/algorithms/bidict_from_enumerating.h" +#include "utils/containers/contains.h" #include "utils/containers/is_subseteq_of.h" #include "utils/containers/vector_of.h" #include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" #include "utils/graph/digraph/algorithms/materialize_digraph_view.h" #include "utils/graph/digraph/algorithms/transitive_closure.h" +#include "utils/graph/digraph/digraph.h" #include "utils/graph/instances/adjacency_digraph.h" #include "utils/graph/node/algorithms.h" @@ -28,7 +32,7 @@ DirectedEdgeMaskView *DirectedEdgeMaskView::clone() const { return new DirectedEdgeMaskView(this->g, this->edge_mask); } -DiGraphView transitive_reduction(DiGraphView const &g) { +DiGraph transitive_reduction(DiGraphView const &g) { // Logic dropped down to raw adjacency matrix for performance. // The version going through the full graph abstraction was // incredibly slow (> minutes) for even moderately sized graphs diff --git a/lib/utils/src/utils/graph/series_parallel/digraph_generation.cc b/lib/utils/src/utils/graph/series_parallel/digraph_generation.cc new file mode 100644 index 0000000000..d34b599583 --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/digraph_generation.cc @@ -0,0 +1,101 @@ +#include "utils/graph/series_parallel/digraph_generation.h" +#include "utils/containers/transform.h" +#include "utils/containers/vector_of.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/materialize_digraph_view.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/series_parallel_splits.h" +#include "utils/variant.h" + +namespace FlexFlow { + +std::unordered_map parallel_extend(DiGraph &g, + DiGraphView const &ext) { + std::unordered_map node_map; + for (Node const &node : get_nodes(ext)) { + node_map.emplace(node, g.add_node()); + } + for (DirectedEdge const &edge : get_edges(ext)) { + g.add_edge(DirectedEdge{node_map.at(edge.src), node_map.at(edge.dst)}); + } + return node_map; +} + +std::unordered_map serial_extend(DiGraph &g, + DiGraphView const &ext) { + std::unordered_set original_sinks = get_sinks(g); + std::unordered_map node_map = parallel_extend(g, ext); + for (Node const &node1 : original_sinks) { + for (Node const &node2 : get_sources(ext)) { + g.add_edge(DirectedEdge{node1, node_map.at(node2)}); + } + } + return node_map; +} + +DiGraph series_composition(DiGraphView const &g1, DiGraphView const &g2) { + DiGraph g = materialize_digraph_view(g1); + serial_extend(g, g2); + return g; +} + +DiGraph parallel_composition(DiGraphView const &g1, DiGraphView const &g2) { + DiGraph g = materialize_digraph_view(g1); + parallel_extend(g, g2); + return g; +} + +DiGraph series_composition(std::vector const &graphs) { + DiGraph g = DiGraph::create(); + for (DiGraphView const &gs : graphs) { + g = materialize_digraph_view(series_composition(g, gs)); + } + return g; +} + +// TODO(@pietro): should be std::unordered_set, but DiGraphs are +// currently non-hashable +DiGraph parallel_composition(std::vector const &graphs) { + DiGraph g = DiGraph::create(); + for (DiGraphView const &gs : graphs) { + g = materialize_digraph_view(parallel_composition(g, gs)); + } + return g; +} + +DiGraph digraph_from_sp_decomposition(Node const &node) { + DiGraph g = DiGraph::create(); + g.add_node(); + return g; +} + +DiGraph digraph_from_sp_decomposition(SeriesSplit const &serial) { + std::vector children = + transform(serial.children, [](auto const &child) { + return widen(child); + }); + return series_composition( + transform(children, [](auto const child) -> DiGraphView { + return digraph_from_sp_decomposition(child); + })); +} + +DiGraph digraph_from_sp_decomposition(ParallelSplit const ¶llel) { + std::vector children = + transform(vector_of(parallel.get_children()), [](auto const &child) { + return widen(child); + }); + return parallel_composition( + transform(children, [](auto const child) -> DiGraphView { + return digraph_from_sp_decomposition(child); + })); +} + +DiGraph digraph_from_sp_decomposition(SeriesParallelDecomposition const &sp) { + return sp.visit( + [](auto const &x) { return digraph_from_sp_decomposition(x); }); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/get_ancestors.cc b/lib/utils/src/utils/graph/series_parallel/get_ancestors.cc new file mode 100644 index 0000000000..d4661e14ff --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/get_ancestors.cc @@ -0,0 +1,78 @@ +#include "utils/graph/series_parallel/get_ancestors.h" +#include "utils/containers/contains.h" +#include "utils/containers/filter.h" +#include "utils/containers/get_only.h" +#include "utils/containers/transform.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.h" +#include "utils/variant.h" +#include + +namespace FlexFlow { + +static bool perform_traversal(SeriesParallelDecomposition const &sp, + Node const &starting_node, + std::unordered_set &ancestors) { + return sp.visit([&](auto const &sp) { + return perform_traversal(sp, starting_node, ancestors); + }); +} + +static bool perform_traversal(SeriesSplit const &serial, + Node const &starting_node, + std::unordered_set &ancestors) { + std::vector children = + transform(serial.children, [](auto const &child) { + return widen(child); + }); + for (SeriesParallelDecomposition const &child : children) { + bool found_starting_node = + perform_traversal(child, starting_node, ancestors); + if (found_starting_node) { + return true; + } + } + return false; +} + +static bool perform_traversal(ParallelSplit const ¶llel, + Node const &starting_node, + std::unordered_set &ancestors) { + std::unordered_multiset children = + transform(parallel.get_children(), [](auto const &child) { + return widen(child); + }); + + if (contains(get_nodes(parallel), starting_node)) { + SeriesParallelDecomposition branch_with_starting_node = get_only( + filter(children, [&](SeriesParallelDecomposition const &child) { + return contains(get_nodes(child), starting_node); + })); + perform_traversal(branch_with_starting_node, starting_node, ancestors); + return true; + } + + for (SeriesParallelDecomposition const &child : children) { + perform_traversal(child, starting_node, ancestors); + } + return false; +} + +static bool perform_traversal(Node const &node, + Node const &starting_node, + std::unordered_set &ancestors) { + if (starting_node != node) { + ancestors.insert(node); + return false; + } + return true; +} + +std::unordered_set get_ancestors(SeriesParallelDecomposition const &sp, + Node const &starting_node) { + assert(contains(get_nodes(sp), starting_node)); + std::unordered_set ancestors; + perform_traversal(sp, starting_node, ancestors); + return ancestors; +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/get_series_parallel_decomposition.cc b/lib/utils/src/utils/graph/series_parallel/get_series_parallel_decomposition.cc index cd29af59a0..d2db9065bc 100644 --- a/lib/utils/src/utils/graph/series_parallel/get_series_parallel_decomposition.cc +++ b/lib/utils/src/utils/graph/series_parallel/get_series_parallel_decomposition.cc @@ -9,6 +9,7 @@ #include "utils/graph/node/algorithms.h" #include "utils/graph/series_parallel/binary_sp_decomposition_tree/binary_sp_decomposition_tree.h" #include "utils/graph/series_parallel/binary_sp_decomposition_tree/nary_sp_tree_from_binary.h" +#include "utils/graph/series_parallel/normalize_sp_decomposition.h" #include "utils/graph/series_parallel/parallel_reduction.h" #include "utils/graph/series_parallel/series_parallel_decomposition.h" #include "utils/graph/series_parallel/series_reduction.h" diff --git a/lib/utils/src/utils/graph/series_parallel/normalize_sp_decomposition.cc b/lib/utils/src/utils/graph/series_parallel/normalize_sp_decomposition.cc new file mode 100644 index 0000000000..19b02e5121 --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/normalize_sp_decomposition.cc @@ -0,0 +1,57 @@ +#include "utils/graph/series_parallel/normalize_sp_decomposition.h" +#include "utils/containers/filter.h" +#include "utils/containers/get_only.h" +#include "utils/containers/transform.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.h" +#include "utils/variant.h" +#include + +namespace FlexFlow { + +template +static auto filter_empty(T const &container) { + return filter(container, [](auto const &child) { + return !is_empty(widen(child)); + }); +} + +SeriesParallelDecomposition normalize_sp_decomposition(Node const &node) { + return SeriesParallelDecomposition(node); +} + +SeriesParallelDecomposition + normalize_sp_decomposition(SeriesSplit const &serial) { + std::vector normalized_children = + transform(filter_empty(serial.children), [](auto const &child) { + return normalize_sp_decomposition( + widen(child)); + }); + + if (normalized_children.size() == 1) { + return get_only(normalized_children); + } + return series_composition(normalized_children); +} + +SeriesParallelDecomposition + normalize_sp_decomposition(ParallelSplit const ¶llel) { + std::unordered_multiset normalized_children = + transform(filter_empty(parallel.get_children()), [](auto const &child) { + return normalize_sp_decomposition( + widen(child)); + }); + + if (normalized_children.size() == 1) { + return get_only(normalized_children); + } + return parallel_composition(normalized_children); +} + +SeriesParallelDecomposition + normalize_sp_decomposition(SeriesParallelDecomposition const &sp) { + return sp.visit( + [](auto const &x) { return normalize_sp_decomposition(x); }); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/series_parallel_decomposition.cc b/lib/utils/src/utils/graph/series_parallel/series_parallel_decomposition.cc index b7a84b871a..c9acee40a4 100644 --- a/lib/utils/src/utils/graph/series_parallel/series_parallel_decomposition.cc +++ b/lib/utils/src/utils/graph/series_parallel/series_parallel_decomposition.cc @@ -1,12 +1,18 @@ #include "utils/graph/series_parallel/series_parallel_decomposition.h" +#include "utils/containers/all_of.h" +#include "utils/containers/extend.h" #include "utils/containers/multiset_union.h" #include "utils/containers/set_union.h" +#include "utils/containers/sum.h" #include "utils/containers/transform.h" #include "utils/containers/unordered_multiset_of.h" +#include "utils/containers/values.h" #include "utils/containers/vector_of.h" #include "utils/graph/series_parallel/intermediate_sp_decomposition_tree.h" +#include "utils/graph/series_parallel/series_parallel_metrics.h" #include "utils/hash/unordered_set.h" #include "utils/variant.h" +#include namespace FlexFlow { @@ -74,4 +80,69 @@ std::unordered_multiset get_nodes(Node const &node) { return {node}; } +bool is_empty(Node const &node) { + return false; +} + +bool is_empty(SeriesSplit const &serial) { + return all_of(serial.children, [](auto const &child) { + return is_empty(widen(child)); + }); +} + +bool is_empty(ParallelSplit const ¶llel) { + return all_of(parallel.get_children(), [](auto const &child) { + return is_empty(widen(child)); + }); +} + +bool is_empty(SeriesParallelDecomposition const &sp) { + return sp.visit([](auto const &t) { return is_empty(t); }); +} + +size_t num_nodes(SeriesParallelDecomposition const &sp) { + return sum(values(get_node_counter_map(sp))); +} + +bool has_no_duplicate_nodes(SeriesParallelDecomposition const &sp) { + return all_of(values(get_node_counter_map(sp)), + [](int count) { return count == 1; }); +} + +SeriesParallelDecomposition series_composition( + std::vector const &sp_compositions) { + std::vector> composition{}; + for (SeriesParallelDecomposition const &sp_comp : sp_compositions) { + if (sp_comp.has()) { + extend(composition, sp_comp.get().children); + } else if (sp_comp.has()) { + composition.push_back(sp_comp.get()); + } else { + assert(sp_comp.has()); + composition.push_back(sp_comp.get()); + } + } + return SeriesParallelDecomposition{SeriesSplit{composition}}; +} + +SeriesParallelDecomposition parallel_composition( + std::unordered_multiset const + &sp_compositions) { + std::unordered_multiset< + std::variant<::FlexFlow::SeriesSplit, ::FlexFlow::Node>> + composition{}; + for (SeriesParallelDecomposition const &sp_comp : sp_compositions) { + if (sp_comp.has()) { + composition = multiset_union(composition, + sp_comp.get().get_children()); + } else if (sp_comp.has()) { + composition.insert(sp_comp.get()); + } else { + assert(sp_comp.has()); + composition.insert(sp_comp.get()); + } + } + return SeriesParallelDecomposition(ParallelSplit{composition}); +} + } // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/series_parallel_metrics.cc b/lib/utils/src/utils/graph/series_parallel/series_parallel_metrics.cc new file mode 100644 index 0000000000..296c307253 --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/series_parallel_metrics.cc @@ -0,0 +1,130 @@ +#include "utils/graph/series_parallel/series_parallel_metrics.h" +#include "utils/containers/maximum.h" +#include "utils/containers/sum.h" +#include "utils/containers/transform.h" +#include "utils/containers/values.h" +#include "utils/containers/vector_of.h" +#include "utils/fmt/unordered_multiset.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/digraph_generation.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/variant.h" +#include +namespace FlexFlow { + +std::unordered_map get_node_counter_map(Node const &node) { + return {{node, 1}}; +} + +std::unordered_map + get_node_counter_map(ParallelSplit const ¶llel) { + std::unordered_map counter; + for (std::variant const &child : parallel.get_children()) { + for (auto const &[node, count] : + get_node_counter_map(widen(child))) { + counter[node] += count; + } + } + return counter; +} + +std::unordered_map + get_node_counter_map(SeriesSplit const &serial) { + std::unordered_map counter; + for (std::variant const &child : serial.children) { + for (auto const &[node, count] : + get_node_counter_map(widen(child))) { + counter[node] += count; + } + } + return counter; +} + +std::unordered_map + get_node_counter_map(SeriesParallelDecomposition const &sp) { + return sp.visit>( + [](auto const &t) { return get_node_counter_map(t); }); +} + +float work_cost(SeriesParallelDecomposition const &sp, + std::unordered_map cost_map) { + auto cost_per_node_group = [&](std::pair const &pair) { + return pair.second * cost_map.at(pair.first); + }; + std::unordered_map counter = get_node_counter_map(sp); + std::vector> pairs(counter.cbegin(), counter.cend()); + return sum(transform(pairs, cost_per_node_group)); +} + +float work_cost(DiGraphView const &g, + std::unordered_map const &cost_map) { + return sum(transform(vector_of(get_nodes(g)), + [&](Node const &node) { return cost_map.at(node); })); +} + +float critical_path_cost(Node const &node, + std::unordered_map const &cost_map) { + return cost_map.at(node); +} + +float critical_path_cost(SeriesSplit const &serial, + std::unordered_map const &cost_map) { + return sum(transform( + serial.children, [&](std::variant const &child) { + return critical_path_cost(widen(child), + cost_map); + })); +} + +float critical_path_cost(ParallelSplit const ¶llel, + std::unordered_map const &cost_map) { + return maximum(transform(parallel.get_children(), + [&](std::variant const &child) { + return critical_path_cost( + widen(child), + cost_map); + })); +} + +float critical_path_cost(SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map) { + return sp.visit( + [&](auto const &t) { return critical_path_cost(t, cost_map); }); +} + +float critical_path_cost(DiGraphView const &g, + std::unordered_map const &cost_map) { + return maximum( + values(get_weighted_longest_path_lengths_from_root(g, cost_map))); +} + +int num_dependencies(SeriesParallelDecomposition const &sp) { + return num_dependencies(digraph_from_sp_decomposition(sp)); +} + +int num_dependencies(DiGraphView const &g) { + return num_edges(g); +} + +float relative_work_increase(DiGraphView const &g, + SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map) { + return work_cost(sp, cost_map) / work_cost(g, cost_map); +} + +float relative_critical_path_cost_increase( + DiGraphView const &g, + SeriesParallelDecomposition const &sp, + std::unordered_map const &cost_map) { + return critical_path_cost(sp, cost_map) / critical_path_cost(g, cost_map); +} + +float relative_num_dependencies_increase( + DiGraphView const &g, SeriesParallelDecomposition const &sp) { + return static_cast(num_dependencies(sp)) / num_dependencies(g); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc b/lib/utils/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc new file mode 100644 index 0000000000..33fd66e2eb --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc @@ -0,0 +1,122 @@ +#include "utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h" +#include "utils/containers/get_only.h" +#include "utils/containers/transform.h" +#include "utils/containers/unordered_multiset_of.h" +#include "utils/containers/vector_of.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering.h" +#include "utils/graph/digraph/algorithms/is_2_terminal_dag.h" +#include "utils/graph/digraph/digraph_view.h" +#include "utils/graph/series_parallel/normalize_sp_decomposition.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.h" +#include "utils/variant.h" +#include + +namespace FlexFlow { + +static SeriesSplit cut_off_head(SeriesSplit const &s) { + assert(s.children.size() > 0); + return SeriesSplit{std::vector>( + s.children.begin() + 1, s.children.end())}; +} + +/* Performs a parallel composition with coalescing, where components with a + * common starting child are merged together + * Example: to parallel compose S(1, 2, 5), S(1, 3, 4): + * without coalescing: P(S(1, 2, 5), S(1, 3, 4)) + * with coalescing: S(1, P( S(2,5), S(3,4) )) + */ +static SeriesParallelDecomposition parallel_composition_with_coalescing( + std::unordered_set const &strands) { + if (strands.size() == 1) { + return SeriesParallelDecomposition(get_only(strands)); + } + + // group strands by their first ("head") node + std::unordered_map, + std::unordered_set> + grouped_strands; + for (SeriesSplit predecessor : filter(strands, [](SeriesSplit const &serial) { + return !is_empty(serial); + })) { + grouped_strands[predecessor.children.at(0)].insert( + cut_off_head(predecessor)); + } + + // recursively coalesce the strands + std::unordered_multiset coalesced_strands; + for (auto const &[head, tails] : grouped_strands) { + SeriesParallelDecomposition parallel_comp = + parallel_composition_with_coalescing(tails); + coalesced_strands.insert(series_composition( + {widen(head), parallel_comp})); + } + + return normalize_sp_decomposition(parallel_composition(coalesced_strands)); +} + +static SeriesParallelDecomposition + critical_path_preserving_sp_ization_unchecked_with_coalescing( + DiGraphView const &g) { + std::unordered_map node_to_sp; + + Node source = get_only(get_sources(g)); + node_to_sp.emplace(source, SeriesSplit{{source}}); + + for (Node const &node : get_topological_ordering(g)) { + if (node == source) { + continue; + } + std::unordered_set predecessors_as_sp = + transform(get_predecessors(g, node), + [&](Node const &p) { return node_to_sp.at(p); }); + + SeriesParallelDecomposition parallel_composed_predecessors = + parallel_composition_with_coalescing(predecessors_as_sp); + SeriesParallelDecomposition sp_decomp = series_composition( + {parallel_composed_predecessors, SeriesParallelDecomposition(node)}); + node_to_sp.emplace(node, sp_decomp.get()); + } + + Node sink = get_only(get_sinks(g)); + return normalize_sp_decomposition( + SeriesParallelDecomposition(node_to_sp.at(sink))); +} + +SeriesParallelDecomposition + critical_path_preserving_sp_ization_with_coalescing(DiGraphView const &g) { + assert(is_2_terminal_dag(g)); + return critical_path_preserving_sp_ization_unchecked_with_coalescing(g); +} + +static SeriesParallelDecomposition + critical_path_preserving_sp_ization_unchecked(DiGraphView const &g) { + std::unordered_map node_to_sp; + + for (Node const &node : get_topological_ordering(g)) { + + std::unordered_multiset predecessors_as_sp = + unordered_multiset_of( + transform(get_predecessors(g, node), + [&](Node const &p) { return node_to_sp.at(p); })); + + SeriesParallelDecomposition sp_decomp = series_composition( + {normalize_sp_decomposition(parallel_composition(predecessors_as_sp)), + SeriesParallelDecomposition(node)}); + + node_to_sp.emplace(node, sp_decomp); + } + + Node sink = get_only(get_sinks(g)); + return node_to_sp.at(sink); +} + +SeriesParallelDecomposition + critical_path_preserving_sp_ization(DiGraphView const &g) { + assert(is_2_terminal_dag(g)); + return critical_path_preserving_sp_ization_unchecked(g); +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc b/lib/utils/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc new file mode 100644 index 0000000000..240e5ae063 --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc @@ -0,0 +1,31 @@ +#include "utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h" +#include "utils/containers/is_subseteq_of.h" +#include "utils/containers/unordered_set_of.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_ancestors.h" +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/digraph_generation.h" +#include "utils/graph/series_parallel/get_ancestors.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.h" + +namespace FlexFlow { + +bool dependencies_are_maintained(DiGraphView const &g, + SeriesParallelDecomposition const &sp) { + assert(has_no_duplicate_nodes(sp)); + if (unordered_set_of(get_nodes(sp)) != get_nodes(g)) { + return false; + } + + for (Node const &n : get_nodes(g)) { + std::unordered_set ancestors_in_g = get_ancestors(g, n); + std::unordered_set ancestors_in_sp = get_ancestors(sp, n); + if (!is_subseteq_of(ancestors_in_g, ancestors_in_sp)) { + return false; + } + } + return true; +} + +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc b/lib/utils/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc new file mode 100644 index 0000000000..d05672672d --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc @@ -0,0 +1,261 @@ +#include "utils/graph/series_parallel/sp_ization/spanish_algo.h" +#include "utils/containers/filter_keys.h" +#include "utils/containers/filtrans.h" +#include "utils/containers/generate_map.h" +#include "utils/containers/get_only.h" +#include "utils/containers/group_by.h" +#include "utils/containers/intersection.h" +#include "utils/containers/map_values.h" +#include "utils/containers/maximum.h" +#include "utils/containers/range.h" +#include "utils/containers/set_union.h" +#include "utils/containers/values.h" +#include "utils/containers/vector_of.h" +#include "utils/fmt/unordered_multiset.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/digraph/algorithms/get_incoming_edges.h" +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/graph/digraph/algorithms/get_lowest_common_ancestors.h" +#include "utils/graph/digraph/algorithms/get_outgoing_edges.h" +#include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_successors.h" +#include "utils/graph/digraph/algorithms/get_weakly_connected_components.h" +#include "utils/graph/digraph/algorithms/is_2_terminal_dag.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/digraph/algorithms/materialize_digraph_view.h" +#include "utils/graph/digraph/algorithms/transitive_reduction.h" +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/digraph/directed_edge.dtg.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/get_series_parallel_decomposition.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/sp_ization/node_role.dtg.h" + +#include +#include +#include + +namespace FlexFlow { + +std::unordered_map get_initial_node_role_map(DiGraph g) { + return generate_map(get_nodes(g), + [&](Node const &n) { return NodeRole::PURE; }); +} + +std::unordered_set + filter_sync_nodes(std::unordered_set const &nodes, + std::unordered_map const &node_roles) { + return filter( + nodes, [&](Node const &n) { return node_roles.at(n) != NodeRole::SYNC; }); +} + +int get_max_depth(DiGraph const &sp, + std::unordered_map const &depth_map) { + return maximum(values(filter_keys( + depth_map, [&](Node const &n) { return contains(get_nodes(sp), n); }))); +} + +DiGraph add_dummy_nodes(DiGraph g, + std::unordered_map &node_roles) { + std::unordered_map depth_map = + get_longest_path_lengths_from_root(g); + for (DirectedEdge const &e : get_edges(g)) { + Node src = e.src; + Node dst = e.dst; + int depth_diff = depth_map.at(dst) - depth_map.at(src); + if (depth_diff > 1) { + g.remove_edge(e); + Node prev_node = src; + Node intermediate_node = Node{0}; + for (int i : range(1, depth_diff)) { + intermediate_node = g.add_node(); + node_roles[intermediate_node] = NodeRole::DUMMY; + g.add_edge(DirectedEdge{prev_node, intermediate_node}); + prev_node = intermediate_node; + } + g.add_edge(DirectedEdge{prev_node, dst}); + } + } + return g; +} + +DiGraph + delete_dummy_nodes(DiGraph g, + std::unordered_map const &node_roles) { + for (Node const &n : get_nodes(g)) { + if (node_roles.at(n) == NodeRole::DUMMY) { + for (Node const &pred : get_predecessors(g, n)) { + for (Node const &succ : get_successors(g, n)) { + g.add_edge(DirectedEdge{pred, succ}); + } + } + remove_node(g, n); + } + } + return g; +} + +DiGraph delete_nodes_of_given_role( + DiGraph g, + NodeRole const &role, + std::unordered_map const &node_roles) { + for (Node const &n : get_nodes(g)) { + if (node_roles.at(n) == role) { + for (Node const &pred : get_predecessors(g, n)) { + for (Node const &succ : get_successors(g, n)) { + g.add_edge(DirectedEdge{pred, succ}); + } + } + remove_node(g, n); + } + } + return g; +} + +std::unordered_set + get_component(DiGraph const &g, + Node const &node, + std::unordered_map const &depth_map, + std::unordered_map const &node_roles) { + + int max_depth = get_max_depth(g, depth_map); + auto is_in_last_2_layers = [&](Node const &n) { + if (node_roles.at(n) == NodeRole::SYNC) { + if (get_successors(g, n).empty()) { + return true; + } + int successors_depth = + get_only(transform(get_successors(g, n), + [&](Node const &n) { return depth_map.at(n); })); + return successors_depth == max_depth; + } else { + return (depth_map.at(n) == max_depth) || + (depth_map.at(n) == max_depth - 1); + } + }; + std::unordered_set last_two_layers_nodes = + filter(get_nodes(g), is_in_last_2_layers); + + DiGraph subgraph = materialize_digraph_view( + get_subgraph(g, last_two_layers_nodes)); + std::unordered_set component = + get_only(filter(get_weakly_connected_components(subgraph), + [&](std::unordered_set const &component) { + return contains(component, node); + })); + std::unordered_set component_without_sync_nodes = + filter_sync_nodes(component, node_roles); + return component_without_sync_nodes; +} + +std::unordered_set + get_forest(DiGraph const &g, + Node const &handle, + std::unordered_set const &component, + std::unordered_map const &node_roles) { + std::unordered_set> subtrees = + transform(get_successors(g, handle), [&](Node const &n) { + return set_union(get_descendants(g, n), {n}); + }); + auto subtrees_overlapping_with_component = + filter(subtrees, [&](std::unordered_set subtree) { + return intersection(subtree, component).size() > 0; + }); + std::unordered_set forest = + set_union(subtrees_overlapping_with_component); + forest.insert(handle); + return filter_sync_nodes(forest, node_roles); +} + +std::pair, std::unordered_set> + get_up_and_down(DiGraph const &g, + std::unordered_set const &forest, + std::unordered_map const &depth_map) { + + int max_depth = get_max_depth(g, depth_map); + std::unordered_map> grouped_by_depth = + group_by(forest, [&](Node const &n) { return depth_map.at(n); }); + return {grouped_by_depth.at(max_depth - 1), grouped_by_depth.at(max_depth)}; +} + +std::unordered_set + edges_to_remove(DiGraph const &g, + std::unordered_set const &up, + std::unordered_set const &down) { + std::unordered_set to_remove; + + for (Node const &u : up) { + to_remove = set_union(to_remove, get_outgoing_edges(g, u)); + } + for (Node const &d : down) { + to_remove = set_union(to_remove, get_incoming_edges(g, d)); + } + + return to_remove; +} + +std::unordered_set + edges_to_add(std::unordered_set const &up, + std::unordered_set const &down, + Node const &sync_node) { + std::unordered_set to_add; + + for (Node const &u : up) { + to_add.insert(DirectedEdge{u, sync_node}); + } + + for (Node const &d : down) { + to_add.insert(DirectedEdge{sync_node, d}); + } + + return to_add; +} + +SeriesParallelDecomposition spanish_strata_sync(DiGraph g) { + assert(is_2_terminal_dag(g)); + assert(is_acyclic(g)); + + std::unordered_map node_roles = get_initial_node_role_map(g); + + g = add_dummy_nodes(g, node_roles); + std::unordered_map depth_map = + get_longest_path_lengths_from_root(g); + + DiGraph sp = DiGraph::create(); + Node root = get_only(get_sources(g)); + sp.add_node_unsafe(root); + size_t sync_node_counter = maximum( + transform(get_nodes(g), [&](Node const &n) { return n.raw_uid; })); + for (Node const &node : get_bfs_ordering(g, {root})) { + if (node == root) { + continue; + } + sp.add_node_unsafe(node); + add_edges(sp, vector_of(get_incoming_edges(g, node))); + std::unordered_set component = + get_component(sp, node, depth_map, node_roles); + Node handle = get_only(get_lowest_common_ancestors(sp, component).value()); + std::unordered_set forest = + get_forest(sp, handle, component, node_roles); + auto [up, down] = get_up_and_down(sp, forest, depth_map); + + for (DirectedEdge const &e : edges_to_remove(sp, up, down)) { + sp.remove_edge(e); + } + + Node sync_node = Node{++sync_node_counter}; + node_roles[sync_node] = NodeRole::SYNC; + sp.add_node_unsafe(sync_node); + for (DirectedEdge const &e : edges_to_add(up, down, sync_node)) { + sp.add_edge(e); + } + } + sp = delete_nodes_of_given_role(sp, NodeRole::DUMMY, node_roles); + sp = transitive_reduction(sp); + sp = delete_nodes_of_given_role(sp, NodeRole::SYNC, node_roles); + return get_series_parallel_decomposition(sp).value(); +} +} // namespace FlexFlow diff --git a/lib/utils/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc b/lib/utils/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc new file mode 100644 index 0000000000..8ebfa7c50c --- /dev/null +++ b/lib/utils/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc @@ -0,0 +1,201 @@ +#include "utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h" +#include "utils/containers/all_of.h" +#include "utils/containers/get_only.h" +#include "utils/containers/invert_map.h" +#include "utils/containers/keys.h" +#include "utils/containers/maximum.h" +#include "utils/containers/sorted.h" +#include "utils/containers/unordered_multiset_of.h" +#include "utils/containers/unordered_set_of.h" +#include "utils/containers/values.h" +#include "utils/fmt/unordered_multiset.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/graph/digraph/algorithms/get_predecessors.h" +#include "utils/graph/digraph/algorithms/get_successors.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering_from_starting_node.h" +#include "utils/graph/digraph/algorithms/is_2_terminal_dag.h" +#include "utils/graph/digraph/algorithms/is_acyclic.h" +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/series_parallel/normalize_sp_decomposition.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.h" +#include "utils/graph/series_parallel/series_parallel_metrics.h" +#include "utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h" +#include "utils/hash/unordered_set.h" +#include "utils/hash/vector.h" +#include +#include + +namespace FlexFlow { + +std::vector> + stratum_split_assuming_unit_cost(DiGraphView const &g) { + std::unordered_map node_to_stratum = + get_longest_path_lengths_from_root(g); + std::vector> result( + maximum(values(node_to_stratum))); + for (auto const &[node, depth] : node_to_stratum) { + result[depth - 1].insert(node); + } + return result; +} + +static SeriesParallelDecomposition naive_stratum_merge( + std::vector> stratum_split) { + std::vector strata = transform( + stratum_split, [](std::unordered_multiset const &stratum_nodes) { + return parallel_composition(transform(stratum_nodes, [](Node const &n) { + return SeriesParallelDecomposition{n}; + })); + }); + return normalize_sp_decomposition(series_composition(strata)); +} + +SeriesParallelDecomposition + stratum_sync_sp_ization_unchecked(DiGraphView const &g) { + + std::vector> stratum_split = + stratum_split_assuming_unit_cost(g); + return naive_stratum_merge(stratum_split); +} + +SeriesParallelDecomposition stratum_sync_sp_ization(DiGraphView const &g) { + assert(is_acyclic(g)); + SeriesParallelDecomposition sp = stratum_sync_sp_ization_unchecked(g); + assert(dependencies_are_maintained(g, sp)); + return sp; +} + +static std::unordered_set get_heads( + DiGraphView const &g, + std::unordered_set> previous_stratum_metanodes, + std::unordered_set explored) { + std::unordered_set previous_stratum_nodes = + set_union(previous_stratum_metanodes); + std::unordered_set candidate_heads = + set_union(values(get_successors(g, previous_stratum_nodes))); + + auto is_valid_head = [&](Node const &n) { + return (!contains(explored, n) && + all_of(get_predecessors(g, n), + [&](Node const &p) { return contains(explored, p); })); + }; + + return filter(candidate_heads, is_valid_head); +} + +// returns a set of filtered topological orderings starting from `heads` such +// that all nodes present in multiple orderings are not included +static std::unordered_set> + get_non_overlapping_topological_orderings( + DiGraphView const &g, std::unordered_set const &heads) { + + std::unordered_set> topo_orderings = + transform(heads, [&](Node const &head) { + return get_topological_ordering_from_starting_node(g, head); + }); + + std::unordered_map node_frequency; + for (std::vector const &ordering : topo_orderings) { + for (Node const &node : ordering) { + node_frequency[node]++; + } + } + + std::unordered_set visitable_nodes = + filter(keys(node_frequency), + [&](Node const &n) { return node_frequency.at(n) == 1; }); + + std::unordered_set> non_overlapping_topo_orderings = + transform(topo_orderings, [&](std::vector const &ordering) { + return filter(ordering, [&](Node const &n) { + return contains(visitable_nodes, n); + }); + }); + + return non_overlapping_topo_orderings; +} + +static std::unordered_set> + get_metanodes(DiGraphView const &g, + std::unordered_set> const &topo_orderings, + float stratum_cost, + std::unordered_map const &cost_map) { + + auto get_metanode = [&](std::vector const &topo_ordering) { + std::unordered_set explored_nodes; + for (Node const &node : topo_ordering) { + float metanode_cost = + critical_path_cost(stratum_sync_sp_ization(get_subgraph( + g, set_union(explored_nodes, {node}))), + cost_map); + if (metanode_cost > stratum_cost * 1.01) { + break; + } + explored_nodes.insert(node); + } + return explored_nodes; + }; + + return transform(topo_orderings, get_metanode); +} + +static std::vector>> + cost_aware_stratum_split(DiGraphView const &g, + std::unordered_map const &cost_map) { + std::vector>> strata; + Node source = get_only(get_sources(g)); + std::unordered_set explored = {source}; + strata.push_back({{source}}); + while (get_nodes(g) != explored) { + + std::unordered_set heads = get_heads(g, strata.back(), explored); + std::unordered_set> non_overlapping_topo_orderings = + get_non_overlapping_topological_orderings(g, heads); + float stratum_cost = maximum( + transform(heads, [&](Node const &n) { return cost_map.at(n); })); + std::unordered_set> metanodes = get_metanodes( + g, non_overlapping_topo_orderings, stratum_cost, cost_map); + strata.push_back(metanodes); + + explored = set_union(explored, set_union(metanodes)); + } + return strata; +} + +SeriesParallelDecomposition cost_aware_stratum_sync_sp_ization_unchecked( + DiGraphView const &g, std::unordered_map const &cost_map) { + + if (get_nodes(g).size() == 1) { + return SeriesParallelDecomposition(get_only(get_nodes(g))); + } + + std::vector> + sp_ized_strata; + for (auto const &stratum : cost_aware_stratum_split(g, cost_map)) { + auto sp_ized_stratum = unordered_multiset_of( + transform(stratum, [&](std::unordered_set const &nodes) { + return cost_aware_stratum_sync_sp_ization_unchecked( + get_subgraph(g, nodes), cost_map); + })); + sp_ized_strata.push_back(sp_ized_stratum); + } + + return normalize_sp_decomposition( + series_composition(transform(sp_ized_strata, parallel_composition))); +} + +SeriesParallelDecomposition cost_aware_stratum_sync_sp_ization( + DiGraphView const &g, std::unordered_map const &cost_map) { + assert(is_acyclic(g)); + + SeriesParallelDecomposition sp = + cost_aware_stratum_sync_sp_ization_unchecked(g, cost_map); + assert(dependencies_are_maintained(g, sp)); + return sp; +} + +} // namespace FlexFlow diff --git a/lib/utils/test/src/test_algorithms.cc b/lib/utils/test/src/test_algorithms.cc new file mode 100644 index 0000000000..2148b13a0c --- /dev/null +++ b/lib/utils/test/src/test_algorithms.cc @@ -0,0 +1,247 @@ +#include "test/utils/doctest.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/construction.h" +#include "utils/graph/hashmap_undirected_graph.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/undirected.h" +#include +#include +#include +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("MultiDiGraph") { + MultiDiGraph g = MultiDiGraph::create(); + std::vector n = add_nodes(g, 4); + std::vector p = add_node_ports(g, 4); + + MultiDiEdge e0{n[3], p[3], n[0], p[0]}; + MultiDiEdge e1{n[2], p[2], n[1], p[0]}; + MultiDiEdge e2{n[3], p[3], n[1], p[1]}; + MultiDiEdge e3{n[3], p[3], n[2], p[2]}; + + std::vector e = {e0, e1, e2, e3}; + + add_edges(g, e); + + CHECK(get_incoming_edges(g, {n[1], n[3]}) == + std::unordered_set{e[0], e[2], e[3]}); + CHECK(get_incoming_edges(g, {n[1]}) == std::unordered_set{}); + CHECK(get_outgoing_edges(g, {n[2], n[3]}) == + std::unordered_set{e[3]}); + std::unordered_map> expected_result = + std::unordered_map>{ + {n[1], {}}, + {n[2], {n[1]}}, + {n[3], {n[0], n[1], n[2]}}, + }; + CHECK(get_predecessors(g, {n[1], n[2], n[3]}) == expected_result); + } + + TEST_CASE("DiGraph") { + DiGraph g = DiGraph::create(); + + std::vector n = add_nodes(g, 4); + std::vector e = { + {n[0], n[3]}, + {n[0], n[1]}, + {n[0], n[2]}, + {n[1], n[2]}, + }; + add_edges(g, e); + + CHECK(get_incoming_edges(g, {n[2], n[3]}) == + std::unordered_set{e[0], e[2], e[3]}); + CHECK(get_outgoing_edges(g, {n[2], n[3]}) == + std::unordered_set{}); + auto expected_result = std::unordered_map>{ + {n[1], {n[0]}}, + {n[2], {n[0], n[1]}}, + {n[3], {n[0]}}, + }; + CHECK(get_predecessors(g, {n[1], n[2], n[3]}) == expected_result); + + SUBCASE("get_imm_dominators") { + std::unordered_map> result = + get_imm_dominators(g); + + std::unordered_map> expected_result = { + {n[2], n[0]}, + {n[1], n[0]}, + {n[3], n[0]}, + {n[0], std::nullopt}, + }; + CHECK(result == expected_result); + } + + SUBCASE("get_dominators") { + std::unordered_map> expected = { + {n[0], {n[0]}}, + {n[1], {n[0], n[1]}}, + {n[2], {n[0], n[2]}}, + {n[3], {n[0], n[3]}}, + }; + CHECK(get_dominators(g) == expected); + } + + SUBCASE("get_sinks") { + auto expected = std::unordered_set{n[2], n[3]}; + CHECK(get_sinks(g) == expected); + } + + SUBCASE("get_bfs") { + std::unordered_set start_points = std::unordered_set{n[0]}; + auto expected = std::vector{n[0], n[2], n[1], n[3]}; + CHECK(get_bfs_ordering(g, start_points) == expected); + } + + SUBCASE("get_predecessors") { + std::unordered_map> expected_result = { + {n[1], {n[0]}}, + {n[2], {n[0], n[1]}}, + }; + CHECK(get_predecessors(g, {n[1], n[2]}) == expected_result); + } + } + + TEST_CASE("traversal") { + DiGraph g = DiGraph::create(); + std::vector const n = add_nodes(g, 5); + std::vector edges = { + {n[0], n[1]}, {n[1], n[2]}, {n[2], n[3]}}; + add_edges(g, edges); + + CHECK(get_sources(g) == std::unordered_set{n[0], n[4]}); + CHECK(get_unchecked_dfs_ordering(g, {n[0]}) == + std::vector{n[0], n[1], n[2], n[3]}); + CHECK(get_bfs_ordering(g, {n[0]}) == + std::vector{n[0], n[1], n[2], n[3]}); + CHECK(is_acyclic(g) == true); + CHECK(get_bfs_ordering(g, {n[4]}) == std::vector{n[4]}); + CHECK(get_dfs_ordering(g, {n[4]}) == std::vector{n[4]}); + + SUBCASE("with root") { + g.add_edge({n[3], n[2]}); + + CHECK(get_dfs_ordering(g, {n[0]}) == + std::vector{n[0], n[1], n[2], n[3]}); + CHECK(is_acyclic(g) == false); + } + + SUBCASE("without root") { + g.add_edge({n[3], n[0]}); + + CHECK(get_dfs_ordering(g, {n[0]}) == + std::vector{n[0], n[1], n[2], n[3]}); + CHECK(is_acyclic(g) == false); + } + SUBCASE("nonlinear") { + g.add_edge({n[1], n[3]}); + CHECK(is_acyclic(g) == true); + } + + SUBCASE("not connected") { + g.remove_edge({n[2], n[3]}); + CHECK(get_dfs_ordering(g, {n[0]}) == std::vector{n[0], n[1], n[2]}); + } + } + + TEST_CASE("bfs") { + DiGraph g = DiGraph::create(); + std::vector const n = add_nodes(g, 7); + + std::vector e = { + {n[0], n[1]}, + {n[0], n[2]}, + {n[1], n[6]}, + {n[2], n[3]}, + {n[3], n[4]}, + {n[4], n[5]}, + {n[5], n[6]}, + {n[6], n[0]}, + }; + + add_edges(g, e); + + std::vector ordering = get_bfs_ordering(g, {n[0]}); + auto CHECK_BEFORE = [&](int l, int r) { + CHECK(index_of(ordering, n[l]).has_value()); + CHECK(index_of(ordering, n[r]).has_value()); + CHECK(index_of(ordering, n[l]).value() < + index_of(ordering, n[r]).value()); + }; + + CHECK(ordering.size() == n.size()); + CHECK_BEFORE(0, 1); + CHECK_BEFORE(0, 2); + + CHECK_BEFORE(1, 3); + CHECK_BEFORE(1, 6); + CHECK_BEFORE(2, 3); + CHECK_BEFORE(2, 6); + + CHECK_BEFORE(3, 4); + CHECK_BEFORE(6, 4); + + CHECK_BEFORE(4, 5); + } + + TEST_CASE("get_topological_ordering") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + std::vector edges = {{n[0], n[1]}, + {n[0], n[2]}, + {n[1], n[5]}, + {n[2], n[3]}, + {n[3], n[4]}, + {n[4], n[5]}}; + add_edges(g, edges); + std::vector ordering = get_topological_ordering(g); + auto CHECK_BEFORE = [&](int l, int r) { + CHECK(index_of(ordering, n[l]).has_value()); + CHECK(index_of(ordering, n[r]).has_value()); + CHECK(index_of(ordering, n[l]) < index_of(ordering, n[r])); + }; + + CHECK(ordering.size() == n.size()); + CHECK_BEFORE(0, 1); + CHECK_BEFORE(0, 2); + CHECK_BEFORE(1, 5); + CHECK_BEFORE(2, 3); + CHECK_BEFORE(3, 4); + CHECK_BEFORE(4, 5); + } + + TEST_CASE("get_connected_components") { + UndirectedGraph g = UndirectedGraph::create(); + std::vector n = add_nodes(g, 4); + std::vector edges = {{n[0], n[1]}, {n[2], n[1]}}; + + add_edges(g, edges); + std::unordered_set> expected_components = { + {n[0], n[1], n[2]}, + {n[3]}, + }; + + CHECK(get_connected_components(g) == expected_components); + } + + TEST_CASE("get_weakly_connected_components") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 4); + + std::vector edges = {{n[0], n[1]}, {n[2], n[1]}}; + + add_edges(g, edges); + std::unordered_set> expected_components = { + {n[0], n[1], n[2]}, + {n[3]}, + }; + + CHECK(get_outgoing_edges(as_digraph(as_undirected(g)), n[0]).size() == 1); + + CHECK(get_weakly_connected_components(g) == expected_components); + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/get_bottlenecks.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/get_bottlenecks.cc new file mode 100644 index 0000000000..1000e715d9 --- /dev/null +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/get_bottlenecks.cc @@ -0,0 +1,76 @@ +#include "utils/graph/digraph/algorithms/get_bottlenecks.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("get_bottlenecks") { + DiGraph g = DiGraph::create(); + + SUBCASE("single node") { + std::vector n = add_nodes(g, 1); + std::unordered_set expected = {n.at(0)}; + CHECK(get_bottlenecks(g) == expected); + } + + SUBCASE("linear graph") { + std::vector n = add_nodes(g, 3); + add_edges( + g, {DirectedEdge{n.at(0), n.at(1)}, DirectedEdge{n.at(1), n.at(2)}}); + + std::unordered_set expected = {n.at(0), n.at(1), n.at(2)}; + CHECK(get_bottlenecks(g) == expected); + } + + SUBCASE("rhombus") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}}); + + std::unordered_set expected = {n.at(0), n.at(3)}; + CHECK(get_bottlenecks(g) == expected); + } + + SUBCASE("two rhombuses in serial") { + std::vector n = add_nodes(g, 6); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}}); + + std::unordered_set expected = {n.at(0), n.at(3), n.at(5)}; + CHECK(get_bottlenecks(g) == expected); + } + + SUBCASE("middle bottleneck") { + std::vector n = add_nodes(g, 5); + add_edges(g, + {DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}}); + + std::unordered_set expected = {}; + CHECK(get_bottlenecks(g) == expected); + } + + SUBCASE("single source, multiple sinks") { + std::vector n = add_nodes(g, 3); + add_edges( + g, {DirectedEdge{n.at(0), n.at(1)}, DirectedEdge{n.at(0), n.at(2)}}); + + std::unordered_set expected = {n.at(0)}; + CHECK(get_bottlenecks(g) == expected); + } + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/get_descendants.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/get_descendants.cc new file mode 100644 index 0000000000..2129c24281 --- /dev/null +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/get_descendants.cc @@ -0,0 +1,128 @@ +#include "utils/graph/digraph/algorithms/get_descendants.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("get_descendants") { + DiGraph g = DiGraph::create(); + + SUBCASE("single node") { + std::vector n = add_nodes(g, 1); + + std::unordered_set correct = {}; + std::unordered_set result = get_descendants(g, n.at(0)); + CHECK(correct == result); + } + + SUBCASE("linear graph") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}}); + + SUBCASE("n.at(0)") { + std::unordered_set correct = {n.at(1), n.at(2), n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(0)); + CHECK(correct == result); + } + + SUBCASE("n.at(1)") { + std::unordered_set correct = {n.at(2), n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(1)); + CHECK(correct == result); + } + + SUBCASE("n.at(2)") { + std::unordered_set correct = {n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(2)); + CHECK(correct == result); + } + + SUBCASE("n.at(3)") { + std::unordered_set correct = {}; + std::unordered_set result = get_descendants(g, n.at(3)); + CHECK(correct == result); + } + } + + SUBCASE("rhombus") { + std::vector n = add_nodes(g, 5); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}, + }); + + SUBCASE("n.at(0)") { + std::unordered_set correct = {n.at(1), n.at(2), n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(0)); + CHECK(correct == result); + } + + SUBCASE("n.at(1)") { + std::unordered_set correct = {n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(1)); + CHECK(correct == result); + } + + SUBCASE("n.at(2)") { + std::unordered_set correct = {n.at(3)}; + std::unordered_set result = get_descendants(g, n.at(2)); + CHECK(correct == result); + } + + SUBCASE("n.at(3)") { + std::unordered_set correct = {}; + std::unordered_set result = get_descendants(g, n.at(3)); + CHECK(correct == result); + } + } + + SUBCASE("disconnected graph") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(3), n.at(4)}, + }); + + SUBCASE("n.at(0)") { + std::unordered_set correct = {n.at(1), n.at(2)}; + std::unordered_set result = get_descendants(g, n.at(0)); + CHECK(correct == result); + } + + SUBCASE("n.at(1)") { + std::unordered_set correct = {n.at(2)}; + std::unordered_set result = get_descendants(g, n.at(1)); + CHECK(correct == result); + } + + SUBCASE("n.at(2)") { + std::unordered_set correct = {}; + std::unordered_set result = get_descendants(g, n.at(2)); + CHECK(correct == result); + } + + SUBCASE("n.at(3)") { + std::unordered_set correct = {n.at(4)}; + std::unordered_set result = get_descendants(g, n.at(3)); + CHECK(correct == result); + } + + SUBCASE("n.at(4)") { + std::unordered_set correct = {}; + std::unordered_set result = get_descendants(g, n.at(4)); + CHECK(correct == result); + } + } + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc new file mode 100644 index 0000000000..c4621c8d6b --- /dev/null +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.cc @@ -0,0 +1,60 @@ +#include "utils/graph/digraph/algorithms/get_longest_path_lengths_from_root.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include + +using namespace ::FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("get_longest_path_lengths_from_root - linear graph") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 5); + std::vector edges = { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[2], n[3]}, + DirectedEdge{n[3], n[4]}, + }; + + add_edges(g, edges); + + std::unordered_map expected_lengths = { + {n[0], 1}, + {n[1], 2}, + {n[2], 3}, + {n[3], 4}, + {n[4], 5}, + }; + + CHECK(get_longest_path_lengths_from_root(g) == expected_lengths); + } + + TEST_CASE("get_longest_path_lengths_from_root - more complex graph") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 7); + std::vector edges = {DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[3]}, + DirectedEdge{n[0], n[4]}, + DirectedEdge{n[0], n[6]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[2], n[3]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[4], n[5]}, + DirectedEdge{n[5], n[6]}}; + + add_edges(g, edges); + + std::unordered_map expected_lengths = { + {n[0], 1}, + {n[1], 2}, + {n[2], 3}, + {n[3], 4}, + {n[4], 2}, + {n[5], 5}, + {n[6], 6}, + }; + + CHECK(get_longest_path_lengths_from_root(g) == expected_lengths); + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc new file mode 100644 index 0000000000..2dbc78fe14 --- /dev/null +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/get_lowest_common_ancestors.cc @@ -0,0 +1,139 @@ +#include "utils/graph/digraph/algorithms/get_lowest_common_ancestors.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("get_lowest_common_ancestors") { + DiGraph g = DiGraph::create(); + + SUBCASE("trees") { + SUBCASE("single node") { + std::vector n = add_nodes(g, 1); + std::unordered_set correct = {n.at(0)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(0)}).value(); + CHECK(correct == result); + } + + SUBCASE("simple tree") { + std::vector n = add_nodes(g, 3); + add_edges( + g, + {DirectedEdge{n.at(0), n.at(1)}, DirectedEdge{n.at(0), n.at(2)}}); + + std::unordered_set correct = {n.at(0)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(1), n.at(2)}).value(); + CHECK(correct == result); + + correct = {n.at(1)}; + result = get_lowest_common_ancestors(g, {n.at(1)}).value(); + CHECK(correct == result); + + correct = {n.at(2)}; + result = get_lowest_common_ancestors(g, {n.at(2)}).value(); + CHECK(correct == result); + } + + SUBCASE("nodes at different heights") { + std::vector n = add_nodes(g, 6); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}}); + + std::unordered_set correct = {n.at(0)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(5), n.at(2)}).value(); + CHECK(correct == result); + + correct = {n.at(3)}; + result = get_lowest_common_ancestors(g, {n.at(5), n.at(3)}).value(); + CHECK(correct == result); + + correct = {n.at(1)}; + result = get_lowest_common_ancestors(g, {n.at(3), n.at(4)}).value(); + CHECK(correct == result); + + correct = {n.at(0)}; + result = get_lowest_common_ancestors( + g, {n.at(1), n.at(2), n.at(3), n.at(4), n.at(5)}) + .value(); + CHECK(correct == result); + } + + SUBCASE("straight path") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}}); + + std::unordered_set correct = {n.at(2)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(2), n.at(3)}).value(); + CHECK(correct == result); + + correct = {n.at(1)}; + result = get_lowest_common_ancestors(g, {n.at(1), n.at(3)}).value(); + CHECK(correct == result); + + correct = {n.at(1)}; + result = + get_lowest_common_ancestors(g, {n.at(1), n.at(2), n.at(3)}).value(); + CHECK(correct == result); + } + } + + SUBCASE("general dags") { + + SUBCASE("no LCA") { + std::vector n = add_nodes(g, 3); + add_edges( + g, + {DirectedEdge{n.at(0), n.at(2)}, DirectedEdge{n.at(1), n.at(2)}}); + + std::unordered_set correct = {}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(0), n.at(1)}).value(); + CHECK(correct == result); + } + + SUBCASE("multiple LCAs") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(1), n.at(3)}}); + + std::unordered_set correct = {n.at(0), n.at(1)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(2), n.at(3)}).value(); + CHECK(correct == result); + } + + SUBCASE("single LCA") { + std::vector n = add_nodes(g, 6); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(1), n.at(5)}}); + + std::unordered_set correct = {n.at(3)}; + std::unordered_set result = + get_lowest_common_ancestors(g, {n.at(4), n.at(5)}).value(); + CHECK(correct == result); + } + } + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/inverse_line_graph/get_inverse_line_graph.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/inverse_line_graph/get_inverse_line_graph.cc index a635658755..6c670ae93a 100644 --- a/lib/utils/test/src/utils/graph/digraph/algorithms/inverse_line_graph/get_inverse_line_graph.cc +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/inverse_line_graph/get_inverse_line_graph.cc @@ -4,6 +4,7 @@ #include "utils/containers/transform.h" #include "utils/graph/algorithms.h" #include "utils/graph/digraph/algorithms/get_successors.h" +#include "utils/graph/digraph/algorithms/get_topological_ordering.h" #include "utils/graph/digraph/algorithms/transitive_reduction.h" #include "utils/graph/instances/adjacency_digraph.h" #include "utils/graph/multidigraph/algorithms/get_directed_edge.h" diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/is_acyclic.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/is_acyclic.cc index e675e6903f..ff8dcf8fa5 100644 --- a/lib/utils/test/src/utils/graph/digraph/algorithms/is_acyclic.cc +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/is_acyclic.cc @@ -9,23 +9,107 @@ using namespace ::FlexFlow; TEST_SUITE(FF_TEST_SUITE) { TEST_CASE("is_acyclic") { DiGraph g = DiGraph::create(); + SUBCASE("empty graph") { + CHECK(is_acyclic(g)); + } - std::vector n = add_nodes(g, 6); + SUBCASE("single node") { + add_nodes(g, 1); + CHECK(is_acyclic(g)); + } - add_edges(g, - { - DirectedEdge{n.at(0), n.at(1)}, - DirectedEdge{n.at(1), n.at(2)}, - DirectedEdge{n.at(1), n.at(3)}, - DirectedEdge{n.at(1), n.at(5)}, - DirectedEdge{n.at(2), n.at(4)}, - DirectedEdge{n.at(3), n.at(1)}, - DirectedEdge{n.at(3), n.at(4)}, - }); + SUBCASE("simple acyclic graph") { + std::vector n = add_nodes(g, 3); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + }); + CHECK(is_acyclic(g)); + } - std::optional correct = false; - std::optional result = is_acyclic(g); + SUBCASE("simple cyclic graph") { + std::vector n = add_nodes(g, 3); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(0)}, + }); + CHECK_FALSE(is_acyclic(g)); + } - CHECK(result == correct); + SUBCASE("2 parallel chains") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + }); + CHECK(is_acyclic(g)); + } + SUBCASE("traversal with root") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(2)}}); + CHECK_FALSE(is_acyclic(g)); + } + + SUBCASE("traversal without root") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(0)}}); + CHECK_FALSE(is_acyclic(g)); + } + + SUBCASE("traversal nonlinear") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(1), n.at(3)}}); + CHECK(is_acyclic(g)); + } + + SUBCASE("complex cyclic graph") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + DirectedEdge{n.at(5), n.at(1)}, + }); + CHECK_FALSE(is_acyclic(g)); + } + + SUBCASE("complex cyclic graph #2") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(1), n.at(5)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(1)}, + DirectedEdge{n.at(3), n.at(4)}, + }); + CHECK_FALSE(is_acyclic(g)); + } } } diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/is_tree.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/is_tree.cc new file mode 100644 index 0000000000..fe42ba1a62 --- /dev/null +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/is_tree.cc @@ -0,0 +1,95 @@ +#include "utils/graph/digraph/algorithms/is_tree.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("is_tree") { + DiGraph g = DiGraph::create(); + + SUBCASE("single node") { + add_nodes(g, 1); + CHECK(is_tree(g)); + } + + SUBCASE("simple tree") { + std::vector n = add_nodes(g, 3); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + }); + CHECK(is_tree(g)); + } + + SUBCASE("simple cycle") { + std::vector n = add_nodes(g, 3); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(0)}, + }); + CHECK_FALSE(is_tree(g)); + } + + SUBCASE("diamond pattern") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}}); + CHECK_FALSE(is_tree(g)); + } + + SUBCASE("dowstream cycle") { + std::vector n = add_nodes(g, 4); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(2)}, + }); + CHECK_FALSE(is_tree(g)); + } + + SUBCASE("multiple roots") { + std::vector n = add_nodes(g, 4); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + }); + CHECK_FALSE(is_tree(g)); + } + + SUBCASE("multiple incoming edges") { + std::vector n = add_nodes(g, 4); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(1), n.at(3)}, + }); + CHECK_FALSE(is_tree(g)); + } + + SUBCASE("crossing") { + std::vector n = add_nodes(g, 4); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(1), n.at(3)}, + }); + CHECK_FALSE(is_tree(g)); + } + } +} diff --git a/lib/utils/test/src/utils/graph/digraph/algorithms/transitive_reduction.cc b/lib/utils/test/src/utils/graph/digraph/algorithms/transitive_reduction.cc index 1f9062a8ed..34a3be7fc4 100644 --- a/lib/utils/test/src/utils/graph/digraph/algorithms/transitive_reduction.cc +++ b/lib/utils/test/src/utils/graph/digraph/algorithms/transitive_reduction.cc @@ -39,6 +39,50 @@ TEST_SUITE(FF_TEST_SUITE) { } } + SUBCASE("linear graph with additional edge") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(0), n.at(3)}, + }); + + DiGraphView result = transitive_reduction(g); + std::unordered_set result_edges = get_edges(result); + std::unordered_set correct_edges = { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + }; + CHECK(result_edges == correct_edges); + } + + SUBCASE("linear graph with 2 additional edges") { + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(4)}, + + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + }); + + DiGraphView result = transitive_reduction(g); + std::unordered_set result_edges = get_edges(result); + std::unordered_set correct_edges = { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + DirectedEdge{n.at(3), n.at(4)}, + }; + CHECK(result_edges == correct_edges); + } + SUBCASE("nontrivial graph") { // from // https://en.wikipedia.org/w/index.php?title=Transitive_reduction&oldid=1226082357#In_directed_acyclic_graphs diff --git a/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/left_associative_binary_sp_tree_from_nary.cc b/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/left_associative_binary_sp_tree_from_nary.cc index fee971e5e0..5ee2e7c224 100644 --- a/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/left_associative_binary_sp_tree_from_nary.cc +++ b/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/left_associative_binary_sp_tree_from_nary.cc @@ -62,7 +62,7 @@ TEST_SUITE(FF_TEST_SUITE) { BinarySPDecompositionTree result = left_associative_binary_sp_tree_from_nary(input); - // we use multiple checks here because SerialParallelDecomposition's + // we use multiple checks here because SeriesParallelDecomposition's // ParallelSplit is unordered, so there are multiple possible // left-associative binary SP trees CHECK(is_binary_sp_tree_left_associative(result)); diff --git a/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/right_associative_binary_sp_tree_from_nary.cc b/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/right_associative_binary_sp_tree_from_nary.cc index 532ff86c90..7b43f52b8f 100644 --- a/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/right_associative_binary_sp_tree_from_nary.cc +++ b/lib/utils/test/src/utils/graph/series_parallel/binary_sp_decomposition_tree/right_associative_binary_sp_tree_from_nary.cc @@ -60,7 +60,7 @@ TEST_SUITE(FF_TEST_SUITE) { BinarySPDecompositionTree result = right_associative_binary_sp_tree_from_nary(input); - // we use multiple checks here because SerialParallelDecomposition's + // we use multiple checks here because SeriesParallelDecomposition's // ParallelSplit is unordered, so there are multiple possible // right-associative binary SP trees CHECK(is_binary_sp_tree_right_associative(result)); diff --git a/lib/utils/test/src/utils/graph/series_parallel/digraph_generation.cc b/lib/utils/test/src/utils/graph/series_parallel/digraph_generation.cc new file mode 100644 index 0000000000..6199aa6ae3 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/digraph_generation.cc @@ -0,0 +1,112 @@ +#include "utils/graph/series_parallel/digraph_generation.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("digraph_from_sp_decomposition") { + SUBCASE("Empty") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition(ParallelSplit{{}}); + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 0); + CHECK(num_edges(result) == 0); + } + + SUBCASE("Complex Empty") { + SeriesParallelDecomposition input = SeriesParallelDecomposition( + ParallelSplit{{SeriesSplit{{}}, SeriesSplit{{ParallelSplit{{}}}}}}); + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 0); + CHECK(num_edges(result) == 0); + } + + SUBCASE("Single Node") { + SeriesParallelDecomposition input = SeriesParallelDecomposition(Node(1)); + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 1); + CHECK(num_edges(result) == 0); + } + + SUBCASE("Simple SeriesSplit") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{SeriesSplit{{Node(1), Node(2), Node(3)}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 3); + CHECK(num_edges(result) == 2); + CHECK(get_sources(result).size() == 1); + CHECK(get_sinks(result).size() == 1); + } + + SUBCASE("Simple ParallelSplit") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + ParallelSplit{{Node(1), Node(2), Node(3)}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 3); + CHECK(num_edges(result) == 0); + CHECK(get_sources(result).size() == 3); + CHECK(get_sinks(result).size() == 3); + } + + SUBCASE("Mixed Serial-Parallel") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + SeriesSplit{{ParallelSplit{{Node(1), Node(2)}}, + ParallelSplit{{Node(3), Node(4)}}}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 4); + CHECK(num_edges(result) == 4); + CHECK(get_sources(result).size() == 2); + CHECK(get_sinks(result).size() == 2); + } + + SUBCASE("Mixed Parallel-Serial") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + ParallelSplit{{SeriesSplit{{Node(1), Node(2)}}, + SeriesSplit{{Node(3), Node(4)}}}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 4); + CHECK(num_edges(result) == 2); + CHECK(get_sources(result).size() == 2); + CHECK(get_sinks(result).size() == 2); + } + + SUBCASE("Rhombus") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + SeriesSplit{{Node(1), ParallelSplit{{Node(2), Node(3)}}, Node(4)}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 4); + CHECK(num_edges(result) == 4); + CHECK(get_sources(result).size() == 1); + CHECK(get_sinks(result).size() == 1); + } + + SUBCASE("Duplicate Nodes") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + SeriesSplit{{Node(1), ParallelSplit{{Node(1), Node(2)}}, Node(1)}}}; + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 4); + CHECK(num_edges(result) == 4); + CHECK(get_sources(result).size() == 1); + CHECK(get_sinks(result).size() == 1); + } + + SUBCASE("Complex Graph") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{SeriesSplit{ + {ParallelSplit{{SeriesSplit{{ParallelSplit{{Node(1), Node(2)}}, + ParallelSplit{{Node(3), Node(4)}}, + Node(5)}}, + SeriesSplit{{Node(6), Node(7)}}}}, + Node(8)}}}; + + DiGraph result = digraph_from_sp_decomposition(input); + CHECK(num_nodes(result) == 8); + CHECK(num_edges(result) == 9); + CHECK(get_sources(result).size() == 3); + CHECK(get_sinks(result).size() == 1); + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/get_ancestors.cc b/lib/utils/test/src/utils/graph/series_parallel/get_ancestors.cc new file mode 100644 index 0000000000..5e4f19a0a2 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/get_ancestors.cc @@ -0,0 +1,78 @@ +#include "utils/graph/series_parallel/get_ancestors.h" +#include "utils/fmt/unordered_set.h" +#include +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("get_ancestors") { + std::vector n = { + Node(0), Node(1), Node(2), Node(3), Node(4), Node(5), Node(6), Node(7)}; + + SUBCASE("Single Node") { + SeriesParallelDecomposition sp = SeriesParallelDecomposition{n.at(0)}; + std::unordered_set correct = {}; + std::unordered_set result = get_ancestors(sp, n.at(0)); + CHECK(correct == result); + } + + SUBCASE("Simple Serial") { + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{SeriesSplit{{n.at(0), n.at(1), n.at(2)}}}; + std::unordered_set correct = {n.at(0), n.at(1)}; + std::unordered_set result = get_ancestors(sp, n.at(2)); + CHECK(correct == result); + } + + SUBCASE("Simple Parallel") { + SeriesParallelDecomposition sp = SeriesParallelDecomposition{ + ParallelSplit{{n.at(0), n.at(1), n.at(2)}}}; + std::unordered_set correct = {}; + std::unordered_set result = get_ancestors(sp, n.at(1)); + CHECK(correct == result); + } + + SUBCASE("Tree") { + SeriesParallelDecomposition sp = SeriesParallelDecomposition{SeriesSplit{ + {n.at(0), + ParallelSplit{{SeriesSplit{{n.at(1), n.at(2)}}, n.at(3)}}}}}; + std::unordered_set correct = {n.at(0), n.at(1)}; + std::unordered_set result = get_ancestors(sp, n.at(2)); + CHECK(correct == result); + } + + SUBCASE("Rhombus") { + SeriesParallelDecomposition sp = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), ParallelSplit{{n.at(1), n.at(2)}}, n.at(3)}}}; + std::unordered_set correct = {n.at(0), n.at(1), n.at(2)}; + std::unordered_set result = get_ancestors(sp, n.at(3)); + CHECK(correct == result); + } + + SUBCASE("Complex Structure") { + SeriesParallelDecomposition sp = SeriesParallelDecomposition{SeriesSplit{ + {n.at(0), + ParallelSplit{ + {SeriesSplit{ + {n.at(1), ParallelSplit{{n.at(2), n.at(3)}}, n.at(4)}}, + SeriesSplit{{n.at(5), n.at(6)}}}}, + n.at(7)}}}; + std::unordered_set correct = {n.at(0), n.at(1), n.at(2), n.at(3)}; + std::unordered_set result = get_ancestors(sp, n.at(4)); + CHECK(correct == result); + + correct = {n.at(0), n.at(1)}; + result = get_ancestors(sp, n.at(3)); + CHECK(correct == result); + + correct = {n.at(0), n.at(5)}; + result = get_ancestors(sp, n.at(6)); + CHECK(correct == result); + + correct = {n.at(0), n.at(1), n.at(2), n.at(3), n.at(4), n.at(5), n.at(6)}; + result = get_ancestors(sp, n.at(7)); + CHECK(correct == result); + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/normalize_sp_decomposition.cc b/lib/utils/test/src/utils/graph/series_parallel/normalize_sp_decomposition.cc new file mode 100644 index 0000000000..dd51f31093 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/normalize_sp_decomposition.cc @@ -0,0 +1,73 @@ +#include "utils/graph/series_parallel/normalize_sp_decomposition.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("normalize_sp_decomposition") { + Node n1 = Node(1); + Node n2 = Node(2); + Node n3 = Node(3); + + SUBCASE("Empty") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{ + SeriesSplit{{ParallelSplit{{}}, ParallelSplit{{}}}}}; + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{{}}}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Node Decomposition") { + SeriesParallelDecomposition input = SeriesParallelDecomposition{n1}; + SeriesParallelDecomposition correct = SeriesParallelDecomposition{n1}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Serial with Single Node") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{SeriesSplit{{n1}}}; + SeriesParallelDecomposition correct = SeriesParallelDecomposition{n1}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Parallel with Single Node") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{ParallelSplit{{n1}}}; + SeriesParallelDecomposition correct = SeriesParallelDecomposition{n1}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Mixed Serial") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{SeriesSplit{{ParallelSplit{{n1}}, n2}}}; + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{{n1, n2}}}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Mixed Parallel") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{ParallelSplit{{SeriesSplit{{n1}}, n2}}}; + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{ParallelSplit{{n1, n2}}}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + + SUBCASE("Nested") { + SeriesParallelDecomposition input = + SeriesParallelDecomposition{ParallelSplit{ + {SeriesSplit{{ParallelSplit{{n1, n2}}}}, n3, SeriesSplit{{}}}}}; + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{ParallelSplit{{n1, n2, n3}}}; + SeriesParallelDecomposition result = normalize_sp_decomposition(input); + CHECK(correct == result); + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/series_parallel_decomposition.cc b/lib/utils/test/src/utils/graph/series_parallel/series_parallel_decomposition.cc index f5766c9fdd..ef5ce51dce 100644 --- a/lib/utils/test/src/utils/graph/series_parallel/series_parallel_decomposition.cc +++ b/lib/utils/test/src/utils/graph/series_parallel/series_parallel_decomposition.cc @@ -157,4 +157,62 @@ TEST_SUITE(FF_TEST_SUITE) { std::unordered_multiset correct = {input}; CHECK(result == correct); } + + TEST_CASE("is_empty(SeriesParallelDecomposition)") { + Node n1{1}; + Node n2{2}; + + SUBCASE("Node Decomposition") { + SeriesParallelDecomposition sp{n1}; + CHECK_FALSE(is_empty(sp)); + } + + SUBCASE("Empty Serial") { + SeriesParallelDecomposition sp{ + SeriesSplit{std::vector>{}}}; + CHECK(is_empty(sp)); + } + + SUBCASE("Empty Parallel") { + SeriesParallelDecomposition sp{ParallelSplit{{}}}; + CHECK(is_empty(sp)); + } + + SUBCASE("Serial with Node") { + SeriesParallelDecomposition sp{SeriesSplit{{n1}}}; + CHECK_FALSE(is_empty(sp)); + } + + SUBCASE("Parallel with Node") { + SeriesParallelDecomposition sp{ParallelSplit{{n1}}}; + CHECK_FALSE(is_empty(sp)); + } + + SUBCASE("Nested Serial") { + SeriesParallelDecomposition sp{SeriesSplit{{ParallelSplit{{}}}}}; + CHECK(is_empty(sp)); + } + + SUBCASE("Nested Parallel") { + SeriesParallelDecomposition sp{ParallelSplit{ + {SeriesSplit{std::vector>{}}}}}; + CHECK(is_empty(sp)); + } + + SUBCASE("Sparse") { + SeriesSplit sp{{ParallelSplit{{}}, + ParallelSplit{{SeriesSplit{ + std::vector>{}}}}}}; + CHECK(is_empty(sp)); + } + + SUBCASE("Sparse with Node") { + SeriesSplit sp{ + {ParallelSplit{{}}, + ParallelSplit{ + {SeriesSplit{std::vector>{}}, + n2}}}}; + CHECK_FALSE(is_empty(sp)); + } + } } diff --git a/lib/utils/test/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc new file mode 100644 index 0000000000..b2a797cb69 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.cc @@ -0,0 +1,309 @@ +#include "utils/graph/series_parallel/sp_ization/critical_path_preserving_sp_ization.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_metrics.h" +#include "utils/graph/series_parallel/series_parallel_splits.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + + TEST_CASE("critical_path_preserving_sp_ization") { + + SUBCASE("Sample Graph #1") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + }); + + std::unordered_map cost_map = {{n.at(0), 3}, + {n.at(1), 2}, + {n.at(2), 1}, + {n.at(3), 1}, + {n.at(4), 1}, + {n.at(5), 5}}; + + CHECK(work_cost(g, cost_map) == 13); + CHECK(critical_path_cost(g, cost_map) == 12); + + SeriesParallelDecomposition sp = critical_path_preserving_sp_ization(g); + + SUBCASE("structure") { + Node sp0 = n.at(0); + SeriesSplit sp1 = SeriesSplit{{sp0, n.at(1)}}; + SeriesSplit sp2 = SeriesSplit{{ParallelSplit{{sp0, sp1}}, n.at(2)}}; + SeriesSplit sp3 = SeriesSplit{{n.at(0), n.at(1), n.at(3)}}; + SeriesSplit sp4 = SeriesSplit{{ParallelSplit{{sp2, sp3}}, n.at(4)}}; + SeriesSplit sp5 = SeriesSplit{{ParallelSplit{{sp3, sp4}}, n.at(5)}}; + SeriesParallelDecomposition correct = SeriesParallelDecomposition{sp5}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = 3 * 4 + 2 * 3 + 1 * 1 + 1 * 2 + 1 * 1 + 5 * 1; + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = critical_path_cost(g, cost_map); + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #2") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(5)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(4), n.at(5)}, + }); + + std::unordered_map cost_map = {{n.at(0), 1}, + {n.at(1), 1}, + {n.at(2), 10}, + {n.at(3), 1}, + {n.at(4), 1}, + {n.at(5), 1}}; + + CHECK(work_cost(g, cost_map) == 15); + CHECK(critical_path_cost(g, cost_map) == 12); + + SeriesParallelDecomposition sp = critical_path_preserving_sp_ization(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{ParallelSplit{ + {SeriesSplit{{n.at(0), n.at(1), n.at(3), n.at(4)}}, + SeriesSplit{{n.at(0), n.at(2)}}}}, + n.at(5)}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = 16; + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = critical_path_cost(g, cost_map); + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + } + + TEST_CASE("critical_path_preserving_sp_ization_with_coalescing") { + + SUBCASE("Sample Graph #1") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}, + }); + + std::unordered_map cost_map = {{n.at(0), 1}, + {n.at(1), 1}, + {n.at(2), 2}, + {n.at(3), 3}, + {n.at(4), 1}, + {n.at(5), 1}}; + + CHECK(work_cost(g, cost_map) == 9); + CHECK(critical_path_cost(g, cost_map) == 7); + + SeriesParallelDecomposition sp = + critical_path_preserving_sp_ization_with_coalescing(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{ + {n.at(0), + n.at(1), + ParallelSplit{ + {SeriesSplit{{ParallelSplit{{n.at(2), n.at(3)}}, n.at(4)}}, + n.at(3)}}, + n.at(5)}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = 12; + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = critical_path_cost(g, cost_map); + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #2") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(5)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(4), n.at(5)}, + }); + + std::unordered_map cost_map = {{n.at(0), 1}, + {n.at(1), 1}, + {n.at(2), 10}, + {n.at(3), 1}, + {n.at(4), 1}, + {n.at(5), 1}}; + + CHECK(work_cost(g, cost_map) == 15); + CHECK(critical_path_cost(g, cost_map) == 12); + + SeriesParallelDecomposition sp = + critical_path_preserving_sp_ization_with_coalescing(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct(SeriesSplit{ + {n.at(0), + ParallelSplit{{SeriesSplit{{n.at(1), n.at(3), n.at(4)}}, n.at(2)}}, + n.at(5)}}); + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = 15; + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = critical_path_cost(g, cost_map); + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #3") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 10); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(5)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(2), n.at(6)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(3), n.at(8)}, + DirectedEdge{n.at(4), n.at(8)}, + DirectedEdge{n.at(5), n.at(7)}, + DirectedEdge{n.at(7), n.at(8)}, + DirectedEdge{n.at(6), n.at(9)}, + DirectedEdge{n.at(8), n.at(9)}}); + + std::unordered_map cost_map = {{n.at(0), 1}, + {n.at(1), 1}, + {n.at(2), 4}, + {n.at(3), 10}, + {n.at(4), 10}, + {n.at(5), 5}, + {n.at(6), 4}, + {n.at(7), 3}, + {n.at(8), 4}, + {n.at(9), 1}}; + + CHECK(work_cost(g, cost_map) == 43); + CHECK(critical_path_cost(g, cost_map) == 26); + + SeriesParallelDecomposition sp = + critical_path_preserving_sp_ization_with_coalescing(g); + + SUBCASE("structure") { + + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{ + {n.at(0), + ParallelSplit{ + {SeriesSplit{{n.at(1), n.at(2), n.at(6)}}, + SeriesSplit{ + {ParallelSplit{ + {SeriesSplit{ + {ParallelSplit{{n.at(1), n.at(3)}}, + ParallelSplit{ + {n.at(4), + SeriesSplit{{n.at(5), n.at(7)}}}}}}, + n.at(3)}}, + n.at(8)}}}}, + n.at(9)}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + }; + SUBCASE("work cost") { + float correct = 54; + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = critical_path_cost(g, cost_map); + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + SUBCASE("Transitive Reduction") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 5); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(0), n.at(4)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + }); + + SeriesParallelDecomposition result = + critical_path_preserving_sp_ization_with_coalescing(g); + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{ + {n.at(0), n.at(1), ParallelSplit{{n.at(2), n.at(3)}}, n.at(4)}}}; + CHECK(result == correct); + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc new file mode 100644 index 0000000000..e3f09253ea --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/dependencies_are_maintained.cc @@ -0,0 +1,125 @@ +#include "utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h" +#include "utils/containers/get_only.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("dependencies_are_maintained") { + DiGraph g = DiGraph::create(); + SUBCASE("Single Node") { + std::vector n = add_nodes(g, 1); + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{SeriesSplit{{n[0]}}}; + CHECK(dependencies_are_maintained(g, sp)); + } + + SUBCASE("SeriesSplit") { + SUBCASE("Valid SP-ization") { + std::vector n = add_nodes(g, 3); + add_edges(g, {DirectedEdge{n[0], n[1]}, DirectedEdge{n[1], n[2]}}); + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{SeriesSplit{{n[0], n[1], n[2]}}}; + CHECK(dependencies_are_maintained(g, sp)); + } + + SUBCASE("Incorrect SP-ization") { + std::vector n = add_nodes(g, 3); + add_edges(g, {DirectedEdge{n[0], n[1]}, DirectedEdge{n[1], n[2]}}); + + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{SeriesSplit{{n[1], n[0], n[2]}}}; + CHECK_FALSE(dependencies_are_maintained(g, sp)); + } + } + + SUBCASE("ParallelSplit") { + SUBCASE("Valid SP-ization") { + std::vector n = add_nodes(g, 3); + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{ParallelSplit{{n[0], n[1], n[2]}}}; + CHECK(dependencies_are_maintained(g, sp)); + } + + SUBCASE("Incorrect SP-ization") { + std::vector n = add_nodes(g, 3); + + SeriesParallelDecomposition sp = + SeriesParallelDecomposition{ParallelSplit{{n[0], n[2]}}}; + CHECK_FALSE(dependencies_are_maintained(g, sp)); + } + } + + SUBCASE("Rhombus") { + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(3)}}); + SUBCASE("Valid SP-izations") { + SeriesParallelDecomposition sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), ParallelSplit{{n.at(1), n.at(2)}}, n.at(3)}}}; + CHECK(dependencies_are_maintained(g, sp_correct)); + + sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), n.at(1), n.at(2), n.at(3)}}}; + CHECK(dependencies_are_maintained(g, sp_correct)); + } + SUBCASE("Invalid SP-ization") { + SeriesParallelDecomposition sp_incorrect = SeriesParallelDecomposition{ + ParallelSplit{{n.at(0), SeriesSplit{{n.at(1), n.at(3)}}, n.at(2)}}}; + CHECK_FALSE(dependencies_are_maintained(g, sp_incorrect)); + } + } + + SUBCASE("Diamond") { + std::vector n = add_nodes(g, 6); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}, + DirectedEdge{n.at(4), n.at(5)}}); + + SUBCASE("Valid SP-izations") { + + SeriesParallelDecomposition sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), + ParallelSplit{{n.at(1), n.at(2)}}, + ParallelSplit{{n.at(3), n.at(4)}}, + n.at(5)}}}; + CHECK(dependencies_are_maintained(g, sp_correct)); + + sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), + n.at(1), + n.at(2), + ParallelSplit{{n.at(3), n.at(4)}}, + n.at(5)}}}; + CHECK(dependencies_are_maintained(g, sp_correct)); + + sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), + ParallelSplit{{n.at(1), n.at(2)}}, + n.at(3), + n.at(4), + n.at(5)}}}; + CHECK(dependencies_are_maintained(g, sp_correct)); + } + + SUBCASE("Invalid SP-izations") { + SeriesParallelDecomposition sp_correct = SeriesParallelDecomposition{ + SeriesSplit{{n.at(0), + ParallelSplit{{n.at(1), n.at(2), n.at(4)}}, + n.at(3), + n.at(5)}}}; + CHECK_FALSE(dependencies_are_maintained(g, sp_correct)); + } + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc new file mode 100644 index 0000000000..c49cd663a7 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/spanish_algo.cc @@ -0,0 +1,384 @@ +#include "utils/graph/series_parallel/sp_ization/spanish_algo.h" +#include "test/utils/doctest/fmt/unordered_multiset.h" +#include "utils/containers/values.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms/get_incoming_edges.h" +#include "utils/graph/digraph/algorithms/get_outgoing_edges.h" +#include "utils/graph/digraph/directed_edge.dtg.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/node/algorithms.h" +#include "utils/graph/node/node.dtg.h" +#include "utils/graph/series_parallel/parallel_split.dtg.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_split.dtg.h" +#include "utils/graph/series_parallel/sp_ization/dependencies_are_maintained.h" +#include "utils/graph/series_parallel/sp_ization/node_role.dtg.h" +#include +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("spanish_algo - subcomponents") { + SUBCASE("add_dummy_nodes") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 4); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(2), n.at(3)}, + }); + std::unordered_map node_types = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::PURE}, + {n.at(2), NodeRole::PURE}, + {n.at(3), NodeRole::PURE}, + }; + + DiGraph result = add_dummy_nodes(g, node_types); + CHECK(get_edges(result).size() == 6); + CHECK(get_nodes(result).size() == 6); + CHECK(get_incoming_edges(g, n.at(3)).size() == 2); + CHECK(get_outgoing_edges(g, n.at(0)).size() == 2); + + CHECK(node_types.size() == 6); + CHECK(values(node_types) == + std::unordered_multiset{NodeRole::PURE, + NodeRole::PURE, + NodeRole::PURE, + NodeRole::PURE, + NodeRole::DUMMY, + NodeRole::DUMMY}); + } + + SUBCASE("delete_dummy_nodes") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 5); + std::vector edges = { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + }; + add_edges(g, edges); + + std::unordered_map node_roles = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::DUMMY}, + {n.at(2), NodeRole::DUMMY}, + {n.at(3), NodeRole::PURE}, + {n.at(4), NodeRole::PURE}, + }; + + DiGraph result = delete_dummy_nodes(g, node_roles); + + CHECK(get_nodes(result) == + std::unordered_set{n.at(0), n.at(3), n.at(4)}); + CHECK(get_edges(result) == + std::unordered_set{DirectedEdge{n.at(0), n.at(4)}, + DirectedEdge{n.at(0), n.at(3)}, + DirectedEdge{n.at(3), n.at(4)}}); + } + SUBCASE("get_component") { + SUBCASE("2 layer graph, single simple component") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}}); + std::unordered_map node_roles = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::SYNC}, + {n.at(2), NodeRole::PURE}, + {n.at(3), NodeRole::PURE}, + }; + std::unordered_map depth_map = { + {n.at(0), 0}, + {n.at(2), 1}, + {n.at(3), 1}, + }; + std::unordered_set correct = {n.at(0), n.at(2), n.at(3)}; + std::unordered_set result = + get_component(g, n.at(2), depth_map, node_roles); + CHECK(correct == result); + } + SUBCASE("2 layer graph, single complex component") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + {DirectedEdge{n.at(0), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(3), n.at(5)}}); + std::unordered_map node_roles = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::PURE}, + {n.at(2), NodeRole::SYNC}, + {n.at(3), NodeRole::SYNC}, + {n.at(4), NodeRole::PURE}, + {n.at(5), NodeRole::PURE}, + }; + std::unordered_map depth_map = { + {n.at(0), 0}, + {n.at(1), 0}, + {n.at(4), 1}, + {n.at(5), 1}, + }; + SUBCASE("n.at(4)'s component") { + std::unordered_set correct = { + n.at(0), n.at(1), n.at(4), n.at(5)}; + std::unordered_set result = + get_component(g, n.at(4), depth_map, node_roles); + CHECK(correct == result); + } + SUBCASE("n.at(5)'s component") { + std::unordered_set correct = { + n.at(0), n.at(1), n.at(4), n.at(5)}; + std::unordered_set result = + get_component(g, n.at(5), depth_map, node_roles); + CHECK(correct == result); + } + } + SUBCASE("3 layer graph, single connected component") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 7); + add_edges(g, + {DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(2), n.at(4)}, + DirectedEdge{n.at(3), n.at(4)}, + DirectedEdge{n.at(4), n.at(5)}, + DirectedEdge{n.at(4), n.at(6)}}); + std::unordered_map node_roles = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::SYNC}, + {n.at(2), NodeRole::PURE}, + {n.at(3), NodeRole::PURE}, + {n.at(4), NodeRole::SYNC}, + {n.at(5), NodeRole::PURE}, + {n.at(6), NodeRole::PURE}}; + + std::unordered_map depth_map = {{n.at(0), 0}, + {n.at(2), 1}, + {n.at(3), 1}, + {n.at(5), 2}, + {n.at(6), 2}}; + SUBCASE("n.at(5)'s component") { + std::unordered_set correct = { + n.at(2), n.at(3), n.at(5), n.at(6)}; + std::unordered_set result = + get_component(g, n.at(5), depth_map, node_roles); + CHECK(correct == result); + } + + SUBCASE("n.at(6)'s component") { + std::unordered_set correct = { + n.at(2), n.at(3), n.at(5), n.at(6)}; + std::unordered_set result = + get_component(g, n.at(6), depth_map, node_roles); + CHECK(correct == result); + } + } + SUBCASE("3 layer graph, multiple weakly connected components") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 10); + add_edges(g, + { + DirectedEdge{n.at(0), n.at(1)}, + DirectedEdge{n.at(1), n.at(2)}, + DirectedEdge{n.at(1), n.at(3)}, + DirectedEdge{n.at(1), n.at(4)}, + DirectedEdge{n.at(2), n.at(5)}, + DirectedEdge{n.at(3), n.at(6)}, + DirectedEdge{n.at(4), n.at(6)}, + DirectedEdge{n.at(5), n.at(7)}, + DirectedEdge{n.at(5), n.at(8)}, + DirectedEdge{n.at(6), n.at(9)}, + }); + std::unordered_map node_roles = { + {n.at(0), NodeRole::PURE}, + {n.at(1), NodeRole::SYNC}, + {n.at(2), NodeRole::PURE}, + {n.at(3), NodeRole::PURE}, + {n.at(4), NodeRole::PURE}, + {n.at(5), NodeRole::SYNC}, + {n.at(6), NodeRole::SYNC}, + {n.at(7), NodeRole::PURE}, + {n.at(8), NodeRole::PURE}, + {n.at(9), NodeRole::PURE}, + }; + + std::unordered_map depth_map = {{n.at(0), 0}, + {n.at(2), 1}, + {n.at(3), 1}, + {n.at(4), 1}, + {n.at(7), 2}, + {n.at(8), 2}, + {n.at(9), 2}}; + SUBCASE("n.at(7)'s component") { + std::unordered_set correct = {n.at(2), n.at(7), n.at(8)}; + std::unordered_set result = + get_component(g, n.at(7), depth_map, node_roles); + CHECK(correct == result); + } + SUBCASE("n.at(8)'s component") { + std::unordered_set correct = {n.at(2), n.at(7), n.at(8)}; + std::unordered_set result = + get_component(g, n.at(8), depth_map, node_roles); + CHECK(correct == result); + } + SUBCASE("n.at(9)'s component") { + std::unordered_set correct = {n.at(3), n.at(4), n.at(9)}; + std::unordered_set result = + get_component(g, n.at(9), depth_map, node_roles); + CHECK(correct == result); + } + } + } + } + + TEST_CASE("spanish_algorithm") { + + SUBCASE("Single Node") { + DiGraph g = DiGraph::create(); + Node n = g.add_node(); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{Node{n}}; + CHECK(sp == correct); + CHECK(dependencies_are_maintained(g, sp)); + } + SUBCASE("Linear Graph") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 3); + add_edges(g, {DirectedEdge{n[0], n[1]}, DirectedEdge{n[1], n[2]}}); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + CHECK(dependencies_are_maintained(g, sp)); + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{{n[0], n[1], n[2]}}}; + CHECK(sp == correct); + } + + SUBCASE("Rhombus") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 4); + add_edges(g, + {DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[3]}}); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], ParallelSplit{{n[1], n[2]}}, n[3]}}}; + + CHECK(dependencies_are_maintained(g, sp)); + CHECK(correct == sp); + } + + SUBCASE("Sample Graph #1") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[4]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[4], n[5]}, + }); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + CHECK(dependencies_are_maintained(g, sp)); + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], n[1], ParallelSplit{{n[2], n[3]}}, n[4], n[5]}}}; + CHECK(sp == correct); + } + + SUBCASE("Diamond without crossing") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[5]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[4], n[5]}, + }); + + SeriesParallelDecomposition sp = spanish_strata_sync(g); + CHECK(dependencies_are_maintained(g, sp)); + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], + ParallelSplit{{SeriesSplit{{n[1], n[3], n[4]}}, n[2]}}, + n[5]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + + SUBCASE("Diamond Graph") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[4]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[4], n[5]}, + }); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + CHECK(dependencies_are_maintained(g, sp)); + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{{n[0], + ParallelSplit{{n[1], n[2]}}, + ParallelSplit{{n[3], n[4]}}, + n[5]}}}; + CHECK(sp == correct); + } + + SUBCASE("Sample Graph #2") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 10); + add_edges(g, + {DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[3]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[5]}, + DirectedEdge{n[1], n[4]}, + DirectedEdge{n[2], n[6]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[3], n[8]}, + DirectedEdge{n[4], n[8]}, + DirectedEdge{n[5], n[7]}, + DirectedEdge{n[7], n[8]}, + DirectedEdge{n[6], n[9]}, + DirectedEdge{n[8], n[9]}}); + SeriesParallelDecomposition sp = spanish_strata_sync(g); + CHECK(dependencies_are_maintained(g, sp)); + + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], + ParallelSplit{{n[1], n[3]}}, + ParallelSplit{ + {SeriesSplit{{n[2], n[6]}}, + SeriesSplit{{ParallelSplit{ + {SeriesSplit{{n[5], n[7]}}, n[4]}}, + n[8]}}}}, + n[9]}}}; + CHECK(sp == correct); + } + } +} diff --git a/lib/utils/test/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc new file mode 100644 index 0000000000..00e44eecc4 --- /dev/null +++ b/lib/utils/test/src/utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.cc @@ -0,0 +1,370 @@ +#include "utils/graph/series_parallel/sp_ization/work_preserving_sp_ization.h" +#include "utils/graph/algorithms.h" +#include "utils/graph/digraph/algorithms.h" +#include "utils/graph/digraph/digraph.h" +#include "utils/graph/instances/adjacency_digraph.h" +#include "utils/graph/series_parallel/series_parallel_decomposition.dtg.h" +#include "utils/graph/series_parallel/series_parallel_metrics.h" +#include "utils/graph/series_parallel/series_parallel_splits.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + + TEST_CASE("work_preserving_sp_ization") { + + SUBCASE("Sample Graph #1") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[4]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[4], n[5]}, + }); + + std::unordered_map cost_map = { + {n[0], 1}, {n[1], 1}, {n[2], 2}, {n[3], 3}, {n[4], 1}, {n[5], 1}}; + + CHECK(work_cost(g, cost_map) == 9); + CHECK(critical_path_cost(g, cost_map) == 7); + + SeriesParallelDecomposition sp = stratum_sync_sp_ization(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], n[1], ParallelSplit{{n[2], n[3]}}, n[4], n[5]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 7; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #2") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[5]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[4], n[5]}, + }); + + std::unordered_map cost_map = { + {n[0], 1}, {n[1], 1}, {n[2], 10}, {n[3], 1}, {n[4], 1}, {n[5], 1}}; + + CHECK(work_cost(g, cost_map) == 15); + CHECK(critical_path_cost(g, cost_map) == 12); + + SeriesParallelDecomposition sp = stratum_sync_sp_ization(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], ParallelSplit{{n[1], n[2]}}, n[3], n[4], n[5]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 14; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #3") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 9); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[3]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[5]}, + DirectedEdge{n[1], n[4]}, + DirectedEdge{n[2], n[6]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[3], n[8]}, + DirectedEdge{n[4], n[8]}, + DirectedEdge{n[5], n[7]}, + DirectedEdge{n[7], n[8]}, + }); + + std::unordered_map cost_map = {{n[0], 1}, + {n[1], 1}, + {n[2], 10}, + {n[3], 10}, + {n[4], 1}, + {n[5], 1}, + {n[6], 10}, + {n[7], 10}, + {n[8], 1}}; + + CHECK(work_cost(g, cost_map) == 45); + CHECK(critical_path_cost(g, cost_map) == 23); + + SeriesParallelDecomposition sp = stratum_sync_sp_ization(g); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], + ParallelSplit{{n[1], n[3]}}, + ParallelSplit{{n[2], n[4], n[5]}}, + ParallelSplit{{n[6], n[7]}}, + n[8]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 32; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + } + + TEST_CASE("cost_aware_stratum_sync_sp_ization") { + + SUBCASE("Sample Graph #1") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[4]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[4], n[5]}, + }); + + std::unordered_map cost_map = { + {n[0], 1}, {n[1], 1}, {n[2], 2}, {n[3], 3}, {n[4], 1}, {n[5], 1}}; + + CHECK(work_cost(g, cost_map) == 9); + CHECK(critical_path_cost(g, cost_map) == 7); + + SeriesParallelDecomposition sp = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], n[1], ParallelSplit{{n[2], n[3]}}, n[4], n[5]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 7; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #2") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 6); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[2]}, + DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[5]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[4], n[5]}, + }); + + std::unordered_map cost_map = { + {n[0], 1}, {n[1], 1}, {n[2], 10}, {n[3], 1}, {n[4], 1}, {n[5], 1}}; + + CHECK(work_cost(g, cost_map) == 15); + CHECK(critical_path_cost(g, cost_map) == 12); + + SeriesParallelDecomposition sp = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], + ParallelSplit{{SeriesSplit{{n[1], n[3], n[4]}}, n[2]}}, + n[5]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 12; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #3") { + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 9); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, + DirectedEdge{n[0], n[3]}, + DirectedEdge{n[1], n[2]}, + DirectedEdge{n[1], n[5]}, + DirectedEdge{n[1], n[4]}, + DirectedEdge{n[2], n[6]}, + DirectedEdge{n[3], n[4]}, + DirectedEdge{n[3], n[5]}, + DirectedEdge{n[3], n[8]}, + DirectedEdge{n[4], n[8]}, + DirectedEdge{n[5], n[7]}, + DirectedEdge{n[7], n[8]}, + }); + + std::unordered_map cost_map = {{n[0], 1}, + {n[1], 1}, + {n[2], 4}, + {n[3], 10}, + {n[4], 10}, + {n[5], 5}, + {n[6], 4}, + {n[7], 3}, + {n[8], 4}}; + + CHECK(work_cost(g, cost_map) == 42); + CHECK(critical_path_cost(g, cost_map) == 25); + + SeriesParallelDecomposition sp = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = SeriesParallelDecomposition{ + SeriesSplit{{n[0], + ParallelSplit{{SeriesSplit{{n[1], n[2], n[6]}}, n[3]}}, + ParallelSplit{{n[4], SeriesSplit{{n[5], n[7]}}}}, + n[8]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 25; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + + SUBCASE("Sample Graph #4") { + + DiGraph g = DiGraph::create(); + std::vector n = add_nodes(g, 15); + add_edges(g, + { + DirectedEdge{n[0], n[1]}, DirectedEdge{n[0], n[5]}, + DirectedEdge{n[1], n[2]}, DirectedEdge{n[1], n[3]}, + DirectedEdge{n[2], n[4]}, DirectedEdge{n[2], n[7]}, + DirectedEdge{n[3], n[4]}, DirectedEdge{n[3], n[6]}, + DirectedEdge{n[4], n[6]}, DirectedEdge{n[5], n[6]}, + DirectedEdge{n[5], n[10]}, DirectedEdge{n[6], n[9]}, + DirectedEdge{n[6], n[11]}, DirectedEdge{n[7], n[8]}, + DirectedEdge{n[8], n[9]}, DirectedEdge{n[8], n[13]}, + DirectedEdge{n[9], n[13]}, DirectedEdge{n[10], n[11]}, + DirectedEdge{n[10], n[12]}, DirectedEdge{n[11], n[14]}, + DirectedEdge{n[12], n[14]}, DirectedEdge{n[13], n[14]}, + }); + + std::unordered_map cost_map = {{n[0], 1}, + {n[1], 1}, + {n[2], 3}, + {n[3], 3}, + {n[4], 1}, + {n[5], 5}, + {n[6], 5}, + {n[7], 1}, + {n[8], 1}, + {n[9], 1}, + {n[10], 3}, + {n[11], 3}, + {n[12], 2}, + {n[13], 1}, + {n[14], 10}}; + + CHECK(work_cost(g, cost_map) == 41); + CHECK(critical_path_cost(g, cost_map) == 24); + + SeriesParallelDecomposition sp = + cost_aware_stratum_sync_sp_ization(g, cost_map); + + SUBCASE("structure") { + SeriesParallelDecomposition correct = + SeriesParallelDecomposition{SeriesSplit{ + {n[0], + ParallelSplit{ + {SeriesSplit{{n[1], ParallelSplit{{n[2], n[3]}}, n[7]}}, + n[5]}}, + ParallelSplit{{n[4], n[8], n[10]}}, + ParallelSplit{{n[6], n[12]}}, + ParallelSplit{{n[11], SeriesSplit{{n[9], n[13]}}}}, + n[14]}}}; + SeriesParallelDecomposition result = sp; + CHECK(correct == result); + } + SUBCASE("work cost") { + float correct = work_cost(g, cost_map); + float result = work_cost(sp, cost_map); + CHECK(correct == result); + } + + SUBCASE("critical path cost") { + float correct = 27; + float result = critical_path_cost(sp, cost_map); + CHECK(correct == result); + } + } + } +}