diff --git a/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.dtg.toml b/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.dtg.toml index 6167bffdd2..1e4c929c16 100644 --- a/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.dtg.toml +++ b/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.dtg.toml @@ -11,6 +11,10 @@ features = [ includes = [ "task-spec/dynamic_graph/dynamic_node_invocation.dtg.h", "", + "task-spec/dynamic_graph/dynamic_invocation_id_t.dtg.h", + "task-spec/dynamic_graph/dynamic_value_attrs.dtg.h", + "task-spec/dynamic_graph/dynamic_value_id_t.dtg.h", + "utils/bidict/unordered_bidict.h", ] src_includes = [ @@ -20,3 +24,11 @@ src_includes = [ [[fields]] name = "invocations" type = "std::set<::FlexFlow::DynamicNodeInvocation>" + +[[fields]] +name = "invocation_ids" +type = "::FlexFlow::unordered_bidict<::FlexFlow::dynamic_invocation_id_t, ::FlexFlow::DynamicNodeInvocation>" + +[[fields]] +name = "value_ids" +type = "::FlexFlow::unordered_bidict<::FlexFlow::dynamic_value_id_t, ::FlexFlow::DynamicValueAttrs>" diff --git a/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.h b/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.h index 29339beb3e..81625158b7 100644 --- a/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.h +++ b/lib/task-spec/include/task-spec/dynamic_graph/dynamic_open_dataflow_graph.h @@ -16,6 +16,12 @@ DynamicOpenDataflowGraph make_empty_dynamic_open_dataflow_graph(); void check_dynamic_open_dataflow_graph_is_valid( DynamicOpenDataflowGraph const &); +DynamicOpenDataflowGraph compute_invocation_ids_for_dynamic_open_dataflow_graph( + DynamicOpenDataflowGraph const &); + +DynamicOpenDataflowGraph compute_value_ids_for_dynamic_open_dataflow_graph( + DynamicOpenDataflowGraph const &); + nonnegative_int dynamic_graph_num_nodes(DynamicOpenDataflowGraph const &); bool full_dynamic_graph_satisfies( diff --git a/lib/task-spec/include/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.dtg.toml b/lib/task-spec/include/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.dtg.toml index d5e92e9b3a..a523c01631 100644 --- a/lib/task-spec/include/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.dtg.toml +++ b/lib/task-spec/include/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.dtg.toml @@ -11,6 +11,10 @@ features = [ includes = [ "task-spec/dynamic_graph/serializable_dynamic_node_invocation.dtg.h", "", + "task-spec/dynamic_graph/dynamic_invocation_id_t.dtg.h", + "task-spec/dynamic_graph/serializable_dynamic_value_attrs.dtg.h", + "task-spec/dynamic_graph/dynamic_value_id_t.dtg.h", + "utils/bidict/unordered_bidict.h", ] src_includes = [ @@ -20,3 +24,11 @@ src_includes = [ [[fields]] name = "invocations" type = "std::set<::FlexFlow::SerializableDynamicNodeInvocation>" + +[[fields]] +name = "invocation_ids" +type = "::FlexFlow::unordered_bidict<::FlexFlow::dynamic_invocation_id_t, ::FlexFlow::SerializableDynamicNodeInvocation>" + +[[fields]] +name = "value_ids" +type = "::FlexFlow::unordered_bidict<::FlexFlow::dynamic_value_id_t, ::FlexFlow::SerializableDynamicValueAttrs>" diff --git a/lib/task-spec/src/task-spec/dynamic_graph/dynamic_open_dataflow_graph.cc b/lib/task-spec/src/task-spec/dynamic_graph/dynamic_open_dataflow_graph.cc index ee8af3135b..b8c27c38ce 100644 --- a/lib/task-spec/src/task-spec/dynamic_graph/dynamic_open_dataflow_graph.cc +++ b/lib/task-spec/src/task-spec/dynamic_graph/dynamic_open_dataflow_graph.cc @@ -7,19 +7,26 @@ #include "task-spec/dynamic_graph/dynamic_slot_site.dtg.h" #include "task-spec/dynamic_graph/serializable_dynamic_node_attrs.h" #include "task-spec/dynamic_graph/serializable_dynamic_value_attrs.h" +#include "utils/bidict/algorithms/unordered_bidict_from_map.h" +#include "utils/bidict/unordered_bidict.h" #include "utils/containers/all_of.h" #include "utils/containers/at_idx.h" #include "utils/containers/concat_vectors.h" #include "utils/containers/contains_duplicates.h" #include "utils/containers/contains_value.h" +#include "utils/containers/enumerate.h" #include "utils/containers/filter_values.h" #include "utils/containers/flatmap.h" #include "utils/containers/get_only.h" +#include "utils/containers/invert_map.h" +#include "utils/containers/map_keys.h" #include "utils/containers/multiset_of.h" #include "utils/containers/multiset_union.h" #include "utils/containers/repeat.h" #include "utils/containers/require_all_of.h" +#include "utils/containers/set_of.h" #include "utils/containers/transform.h" +#include "utils/containers/vector_of.h" #include "utils/containers/zip_strict.h" #include "utils/containers/zip_values_strict.h" #include "utils/graph/dataflow_graph/algorithms.h" @@ -39,6 +46,8 @@ namespace FlexFlow { DynamicOpenDataflowGraph make_empty_dynamic_open_dataflow_graph() { return DynamicOpenDataflowGraph{ std::set{}, + unordered_bidict{}, + unordered_bidict{}, }; } @@ -75,6 +84,46 @@ void check_dynamic_open_dataflow_graph_is_valid( labelled_open_kwarg_dataflow_graph_from_dynamic_open_dataflow_graph(g); } +DynamicOpenDataflowGraph compute_invocation_ids_for_dynamic_open_dataflow_graph( + DynamicOpenDataflowGraph const &g) { + unordered_bidict + invocation_ids = unordered_bidict_from_map( + map_keys(enumerate(g.invocations), [](nonnegative_int i) { + return dynamic_invocation_id_t{i}; + })); + + DynamicOpenDataflowGraph result{ + /*invocations=*/g.invocations, + /*invocation_ids=*/invocation_ids, + /*value_ids=*/g.value_ids, + }; + return result; +} + +DynamicOpenDataflowGraph compute_value_ids_for_dynamic_open_dataflow_graph( + DynamicOpenDataflowGraph const &g) { + std::map internal_value_ids = map_keys( + enumerate(dynamic_graph_get_internal_values(g)), [](nonnegative_int i) { + return dynamic_value_id_t{dynamic_internal_value_id_t{i}}; + }); + + std::map external_value_ids = map_keys( + enumerate(dynamic_graph_get_external_values(g)), [](nonnegative_int i) { + return dynamic_value_id_t{dynamic_external_value_id_t{i}}; + }); + + unordered_bidict value_ids = + unordered_bidict_from_map( + binary_merge_disjoint_maps(internal_value_ids, external_value_ids)); + + DynamicOpenDataflowGraph result{ + /*invocations=*/g.invocations, + /*invocation_ids=*/g.invocation_ids, + /*value_ids=*/value_ids, + }; + return result; +} + nonnegative_int dynamic_graph_num_nodes(DynamicOpenDataflowGraph const &g) { return num_elements(get_dynamic_nodes(g)); } @@ -173,68 +222,25 @@ std::set dynamic_invocation_id_t dynamic_graph_get_id_for_invocation( DynamicOpenDataflowGraph const &g, DynamicNodeInvocation const &invocation) { - return dynamic_invocation_id_t{ - nonnegative_int{assert_unwrap(index_of(g.invocations, invocation))}, - }; + return g.invocation_ids.at_r(invocation); } DynamicNodeInvocation dynamic_graph_get_invocation_for_id(DynamicOpenDataflowGraph const &g, dynamic_invocation_id_t const &id) { - return at_idx(g.invocations, id.idx); + return g.invocation_ids.at_l(id); } dynamic_value_id_t dynamic_graph_get_id_for_value(DynamicOpenDataflowGraph const &g, DynamicValueAttrs const &value) { - auto idx_in_set = [](std::set const &s, - DynamicValueAttrs const &v) -> nonnegative_int { - return nonnegative_int{assert_unwrap(index_of(s, v))}; - }; - - { - std::set internal_values = - dynamic_graph_get_internal_values(g); - if (contains(internal_values, value)) { - return dynamic_value_id_t{ - dynamic_internal_value_id_t{ - idx_in_set(internal_values, value), - }, - }; - } - } - - { - std::set external_values = - dynamic_graph_get_external_values(g); - if (contains(external_values, value)) { - return dynamic_value_id_t{ - dynamic_external_value_id_t{ - idx_in_set(external_values, value), - }, - }; - } - } - - PANIC("Could not find id for value {}", value); + return g.value_ids.at_r(value); } DynamicValueAttrs dynamic_graph_get_value_for_id(DynamicOpenDataflowGraph const &g, dynamic_value_id_t const &id) { - return id.visit(overload{ - [&](dynamic_internal_value_id_t const &internal_id) -> DynamicValueAttrs { - std::set internal_values = - dynamic_graph_get_internal_values(g); - - return at_idx(internal_values, internal_id.idx); - }, - [&](dynamic_external_value_id_t const &external_id) -> DynamicValueAttrs { - std::set external_values = - dynamic_graph_get_external_values(g); - - return at_idx(external_values, external_id.idx); - }}); + return g.value_ids.at_l(id); } std::set @@ -455,11 +461,17 @@ DynamicOpenDataflowGraph dynamic_open_dataflow_graph_from_invocation_set( DynamicOpenDataflowGraph result = DynamicOpenDataflowGraph{ invocation_set, + unordered_bidict{}, + unordered_bidict{}, }; check_dynamic_open_dataflow_graph_is_valid(result); - return result; + // note: invocation ids must be computed before value ids, as computing + // value ids requires looking up invocation ids + result = compute_invocation_ids_for_dynamic_open_dataflow_graph(result); + + return compute_value_ids_for_dynamic_open_dataflow_graph(result); } std::pair>(); - bidict, DynamicValueAttrs> + // note: unordered so that the contains_r probe in inputs_have_been_added + // below is a hash lookup rather than a tree descent using + // DynamicValueAttrs::operator< + unordered_bidict, + DynamicValueAttrs> value_map; for (auto const &kv : enumerate(graph_inputs)) { @@ -511,14 +527,6 @@ std::pair bool { - return all_of(values(invocation.inputs), - [&](DynamicValueAttrs const &input) -> bool { - return value_map.contains_r(input); - }); - }; - bidict node_map; std::set to_add = g.invocations; @@ -546,19 +554,60 @@ std::pair invocation_by_idx = + vector_of(g.invocations); + int num_invocations = static_cast(invocation_by_idx.size()); + + std::unordered_map> consumers_of_value; + std::vector num_unavailable_inputs(num_invocations, 0); + + for (int idx = 0; idx < num_invocations; idx++) { + // a value consumed by several slots of one invocation only blocks it once + for (DynamicValueAttrs const &input : + set_of(values(invocation_by_idx.at(idx).inputs))) { + if (!value_map.contains_r(input)) { + num_unavailable_inputs.at(idx)++; + consumers_of_value[input].push_back(idx); } } + } - PANIC("Failed to add any invocations in to_add", to_add); - }; + std::set ready; + for (int idx = 0; idx < num_invocations; idx++) { + if (num_unavailable_inputs.at(idx) == 0) { + ready.insert(idx); + } + } - while (to_add.size() > 0) { - add_next_invocation_to_graph(); + while (!ready.empty()) { + int idx = *ready.begin(); + ready.erase(ready.begin()); + + DynamicNodeInvocation const &invocation = invocation_by_idx.at(idx); + add_invocation_to_graph(invocation); + + for (DynamicValueAttrs const &output : set_of(values(invocation.outputs))) { + for (int consumer_idx : consumers_of_value[output]) { + if (--num_unavailable_inputs.at(consumer_idx) == 0) { + ready.insert(consumer_idx); + } + } + } + } + + // any invocation still pending has an input that is never produced, or + // participates in a cycle + if (!to_add.empty()) { + PANIC("Failed to add any invocations in to_add", to_add); } return std::pair{result, node_map}; diff --git a/lib/task-spec/src/task-spec/dynamic_graph/loss_insertion.cc b/lib/task-spec/src/task-spec/dynamic_graph/loss_insertion.cc index e7c4b34460..80db86edb1 100644 --- a/lib/task-spec/src/task-spec/dynamic_graph/loss_insertion.cc +++ b/lib/task-spec/src/task-spec/dynamic_graph/loss_insertion.cc @@ -84,6 +84,13 @@ LossInsertionResult perform_loss_insertion( DynamicOpenDataflowGraph result = dg; result.invocations.insert(loss_invocation); + + // the newly-inserted invocation invalidates the cached id lookup + // structures, so recompute them (invocation ids first, as computing value + // ids requires looking up invocation ids) + result = compute_value_ids_for_dynamic_open_dataflow_graph( + compute_invocation_ids_for_dynamic_open_dataflow_graph(result)); + return LossInsertionResult{result, label_value, logit_grad_value}; } diff --git a/lib/task-spec/src/task-spec/dynamic_graph/make_dynamic_open_dataflow_graph_from_cg.cc b/lib/task-spec/src/task-spec/dynamic_graph/make_dynamic_open_dataflow_graph_from_cg.cc index 3ce603f5ff..a3acb832be 100644 --- a/lib/task-spec/src/task-spec/dynamic_graph/make_dynamic_open_dataflow_graph_from_cg.cc +++ b/lib/task-spec/src/task-spec/dynamic_graph/make_dynamic_open_dataflow_graph_from_cg.cc @@ -78,7 +78,10 @@ DynamicOpenDataflowGraph result.invocations.emplace(result_inputs, result_attrs, result_outputs); } - return result; + // note: invocation ids must be computed before value ids, as computing + // value ids requires looking up invocation ids + return compute_value_ids_for_dynamic_open_dataflow_graph( + compute_invocation_ids_for_dynamic_open_dataflow_graph(result)); } } // namespace FlexFlow diff --git a/lib/task-spec/src/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.cc b/lib/task-spec/src/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.cc index 2ceb8c1214..f0b6ae162d 100644 --- a/lib/task-spec/src/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.cc +++ b/lib/task-spec/src/task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.cc @@ -1,5 +1,8 @@ #include "task-spec/dynamic_graph/serializable_dynamic_open_dataflow_graph.h" #include "task-spec/dynamic_graph/serializable_dynamic_node_invocation.h" +#include "task-spec/dynamic_graph/serializable_dynamic_value_attrs.h" +#include "utils/bidict/algorithms/unordered_bidict_transform_values.h" +#include "utils/containers/transform.h" namespace FlexFlow { @@ -9,6 +12,12 @@ SerializableDynamicOpenDataflowGraph return SerializableDynamicOpenDataflowGraph{ /*invocations=*/transform(g.invocations, dynamic_node_invocation_to_serializable), + /*invocation_ids=*/ + unordered_bidict_transform_values( + g.invocation_ids, dynamic_node_invocation_to_serializable), + /*value_ids=*/ + unordered_bidict_transform_values(g.value_ids, + dynamic_value_attrs_to_serializable), }; } @@ -17,6 +26,13 @@ DynamicOpenDataflowGraph dynamic_open_dataflow_graph_from_serializable( return DynamicOpenDataflowGraph{ /*invocations=*/transform(serializable.invocations, dynamic_node_invocation_from_serializable), + /*invocation_ids=*/ + unordered_bidict_transform_values( + serializable.invocation_ids, + dynamic_node_invocation_from_serializable), + /*value_ids=*/ + unordered_bidict_transform_values(serializable.value_ids, + dynamic_value_attrs_from_serializable), }; } diff --git a/lib/utils/include/utils/bidict/algorithms/unordered_bidict_from_map.h b/lib/utils/include/utils/bidict/algorithms/unordered_bidict_from_map.h new file mode 100644 index 0000000000..19b3cc23ee --- /dev/null +++ b/lib/utils/include/utils/bidict/algorithms/unordered_bidict_from_map.h @@ -0,0 +1,21 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_ALGORITHMS_UNORDERED_BIDICT_FROM_MAP_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_ALGORITHMS_UNORDERED_BIDICT_FROM_MAP_H + +#include "utils/bidict/unordered_bidict.h" + +namespace FlexFlow { + +template +unordered_bidict unordered_bidict_from_map(std::map const &m) { + return unordered_bidict{m.begin(), m.end()}; +} + +template +unordered_bidict + unordered_bidict_from_map(std::unordered_map const &m) { + return unordered_bidict{m.begin(), m.end()}; +} + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/bidict/algorithms/unordered_bidict_transform_values.h b/lib/utils/include/utils/bidict/algorithms/unordered_bidict_transform_values.h new file mode 100644 index 0000000000..3e6663d669 --- /dev/null +++ b/lib/utils/include/utils/bidict/algorithms/unordered_bidict_transform_values.h @@ -0,0 +1,23 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_ALGORITHMS_UNORDERED_BIDICT_TRANSFORM_VALUES_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_ALGORITHMS_UNORDERED_BIDICT_TRANSFORM_VALUES_H + +#include "utils/bidict/unordered_bidict.h" + +namespace FlexFlow { + +template > +unordered_bidict + unordered_bidict_transform_values(unordered_bidict const &m, F &&f) { + unordered_bidict result; + for (auto const &kv : m) { + result.equate_strict({kv.first, f(kv.second)}); + } + return result; +} + +} // namespace FlexFlow + +#endif diff --git a/lib/utils/include/utils/bidict/unordered_bidict.h b/lib/utils/include/utils/bidict/unordered_bidict.h new file mode 100644 index 0000000000..c71c6c47de --- /dev/null +++ b/lib/utils/include/utils/bidict/unordered_bidict.h @@ -0,0 +1,370 @@ +#ifndef _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_UNORDERED_BIDICT_H +#define _FLEXFLOW_LIB_UTILS_INCLUDE_UTILS_BIDICT_UNORDERED_BIDICT_H + +#include "utils/check_fmtable.h" +#include "utils/containers/contains_key.h" +#include "utils/containers/map_from_unordered.h" +#include "utils/containers/require_same.h" +#include "utils/containers/unordered_keys.h" +#include "utils/containers/unordered_map_from_keys_and_values.h" +#include "utils/containers/unordered_set_of.h" + +#include "utils/containers/values.h" +#include "utils/fmt/unordered_map.h" +#include "utils/hash/unordered_map.h" +#include "utils/json/check_is_json_deserializable.h" +#include "utils/json/check_is_json_serializable.h" +#include +#include +#include +#include +#include +#include + +namespace FlexFlow { + +template +struct unordered_bidict { + unordered_bidict() : fwd_map{}, bwd_map{} {} + + unordered_bidict(std::initializer_list> init) + : unordered_bidict(init.begin(), init.end()) {} + + template + unordered_bidict(InputIt first, InputIt last) { + for (auto it = first; it != last; it++) { + this->equate_strict(it->first, it->second); + } + } + + bool contains(L const &l, R const &r) const { + return this->contains_l(l) && this->at_l(l) == r; + } + + bool contains_l(L const &l) const { + return fwd_map.find(l) != fwd_map.end(); + } + + bool contains_r(R const &r) const { + return bwd_map.find(r) != bwd_map.end(); + } + + void erase_l(L const &l) { + if (this->contains_l(l)) { + R r = this->at_l(l); + this->fwd_map.erase(l); + this->bwd_map.erase(r); + } + } + + void erase_r(R const &r) { + if (this->contains_r(r)) { + L l = this->at_r(r); + this->fwd_map.erase(l); + this->bwd_map.erase(r); + } + } + + void equate(L const &l, R const &r) { + bool contains_l = this->contains_l(l); + bool contains_r = this->contains_r(r); + + if (contains_l != contains_r || (contains_l && this->at_l(l) != r)) { + this->erase_l(l); + this->erase_r(r); + contains_l = contains_r = false; + } + + if (!contains_l) { + ASSERT(!contains_r); + fwd_map.insert({l, r}); + bwd_map.insert({r, l}); + } + } + + void equate(std::pair const &lr) { + this->equate(lr.first, lr.second); + } + + void equate_strict(L const &l, R const &r) { + ASSERT(this->contains_l(l) == this->contains_r(r)); + + if (this->contains_l(l)) { + ASSERT(this->at_l(l) == r); + } else { + fwd_map.insert({l, r}); + bwd_map.insert({r, l}); + } + } + + void equate_strict(std::pair const &lr) { + this->equate_strict(lr.first, lr.second); + } + + bool operator==(unordered_bidict const &other) const { + return require_same((this->fwd_map == other.fwd_map), + (this->bwd_map == other.bwd_map)); + } + + bool operator!=(unordered_bidict const &other) const { + return require_same((this->fwd_map != other.fwd_map), + (this->bwd_map != other.bwd_map)); + } + + R const &at_l(L const &l) const { + ASSERT(contains_key(this->fwd_map, l)); + return fwd_map.at(l); + } + + L const &at_r(R const &r) const { + ASSERT(contains_key(this->bwd_map, r)); + return bwd_map.at(r); + } + + std::unordered_set left_values() const { + return unordered_keys(this->fwd_map); + } + + std::unordered_set right_values() const { + return unordered_keys(this->bwd_map); + } + + std::size_t size() const { + assert(fwd_map.size() == bwd_map.size()); + return fwd_map.size(); + } + + bool empty() const { + return this->size() == 0; + } + + using const_iterator = typename std::unordered_map::const_iterator; + using value_type = std::pair; + using reference = value_type &; + using const_reference = value_type const &; + using key_type = L; + using mapped_type = R; + /* struct const_iterator { */ + /* using iterator_category = std::forward_iterator_tag; */ + /* using difference_type = std::size_t; */ + /* using value_type = std::pair; */ + /* using pointer = std::pair const *; */ + /* using reference = std::pair const &; */ + + /* explicit const_iterator(typename std::map, + * tl::optional>::const_iterator); */ + + /* reference operator*() const { */ + /* this->current = {this->it->first.value(), this->it->second.value()}; */ + /* return this->current.value(); */ + /* } */ + /* pointer operator->() const { */ + /* return &this->operator*(); */ + /* } */ + + /* const_iterator& operator++() { */ + /* ++this->it; */ + /* return *this; */ + /* } */ + /* const_iterator operator++(int) { */ + /* auto tmp = *this; */ + /* ++(*this); */ + /* return tmp; */ + /* } */ + + /* bool operator==(const_iterator const &other) const { */ + /* return this->it == other.it; */ + /* } */ + /* bool operator!=(const_iterator const &other) const { */ + /* return this->it != other.it; */ + /* } */ + /* private: */ + /* mutable tl::optional> current; */ + /* typename std::map, + * tl::optional>::const_iterator it; */ + /* }; */ + + /* const_iterator cbegin() const { */ + /* return const_iterator(this->fwd_map.cbegin()); */ + /* } */ + + /* const_iterator begin() const { */ + /* return this->cbegin(); */ + /* } */ + + /* const_iterator cend() const { */ + /* return const_iterator(this->fwd_map.cend()); */ + /* } */ + + /* const_iterator end() const { */ + /* return this->cend(); */ + /* } */ + + const_iterator cbegin() const { + return this->fwd_map.cbegin(); + } + + const_iterator begin() const { + return this->cbegin(); + } + + const_iterator cend() const { + return this->fwd_map.cend(); + } + + const_iterator end() const { + return this->cend(); + } + + unordered_bidict reversed() const { + return unordered_bidict(bwd_map, fwd_map); + } + + operator std::unordered_map const &() const { + return this->fwd_map; + } + + operator std::map() const { + return map_from_unordered(this->fwd_map); + } + + std::unordered_map const &as_unordered_map() const { + return this->fwd_map; + } + + std::map as_map() const { + return map_from_unordered(this->fwd_map); + } + + std::unordered_map const &l_to_r() const { + return this->fwd_map; + } + + std::unordered_map const &r_to_l() const { + return this->bwd_map; + } + + unordered_bidict(std::unordered_map const &fwd_map, + std::unordered_map const &bwd_map) + : fwd_map(fwd_map), bwd_map(bwd_map) { + this->check_invariants(); + } + + // note: std::unordered_map provides no relational operators, so ordering + // comparisons are performed on the ordered projection of the forward map + bool operator<(unordered_bidict const &other) const { + return this->as_map() < other.as_map(); + } + + bool operator<=(unordered_bidict const &other) const { + return this->as_map() <= other.as_map(); + } + + bool operator>(unordered_bidict const &other) const { + return this->as_map() > other.as_map(); + } + + bool operator>=(unordered_bidict const &other) const { + return this->as_map() >= other.as_map(); + } + +private: + void check_invariants() const { + std::unordered_set fwd_l_vals = unordered_keys(this->fwd_map); + std::unordered_set bwd_l_vals = unordered_set_of(values(this->bwd_map)); + + std::unordered_set bwd_r_vals = unordered_keys(this->bwd_map); + std::unordered_set fwd_r_vals = unordered_set_of(values(this->fwd_map)); + + ASSERT(fwd_l_vals == bwd_l_vals); + ASSERT(fwd_r_vals == bwd_r_vals); + + for (L const &l : fwd_l_vals) { + ASSERT(bwd_map.at(fwd_map.at(l)) == l); + } + } + + friend struct unordered_bidict; + + std::unordered_map fwd_map; + std::unordered_map bwd_map; +}; + +template +std::unordered_map format_as(unordered_bidict const &b) { + return b.as_unordered_map(); +} + +template +std::ostream &operator<<(std::ostream &s, unordered_bidict const &b) { + CHECK_FMTABLE(L); + CHECK_FMTABLE(R); + + return s << fmt::to_string(b); +} + +} // namespace FlexFlow + +namespace nlohmann { + +template +struct adl_serializer<::FlexFlow::unordered_bidict> { + static ::FlexFlow::unordered_bidict from_json(json const &j) { + CHECK_IS_JSON_DESERIALIZABLE(L); + CHECK_IS_JSON_DESERIALIZABLE(R); + + std::unordered_map m = j; + + ::FlexFlow::unordered_bidict b{m.cbegin(), m.cend()}; + + return b; + } + static void to_json(json &j, ::FlexFlow::unordered_bidict const &b) { + CHECK_IS_JSON_SERIALIZABLE(L); + CHECK_IS_JSON_SERIALIZABLE(R); + + // note: serialize via the ordered projection so that the emitted json + // has a deterministic element order (std::unordered_map does not define + // iteration order) + j = b.as_map(); + } +}; + +} // namespace nlohmann + +namespace rc { + +template +struct Arbitrary<::FlexFlow::unordered_bidict> { + static Gen<::FlexFlow::unordered_bidict> arbitrary() { + return gen::map( + gen::withSize([](int size) -> Gen> { + return gen::apply( + [](std::vector const &keys, + std::vector const &values) -> std::unordered_map { + return ::FlexFlow::unordered_map_from_keys_and_values(keys, + values); + }, + gen::unique>(size, gen::arbitrary()), + gen::unique>(size, gen::arbitrary())); + }), + [](std::unordered_map const &m) { + return ::FlexFlow::unordered_bidict{m.cbegin(), m.cend()}; + }); + } +}; + +} // namespace rc + +namespace std { + +template +struct hash<::FlexFlow::unordered_bidict> { + size_t operator()(::FlexFlow::unordered_bidict const &b) const { + return hash>{}(b.as_unordered_map()); + } +}; + +} // namespace std + +#endif diff --git a/lib/utils/test/src/utils/bidict/unordered_bidict.cc b/lib/utils/test/src/utils/bidict/unordered_bidict.cc new file mode 100644 index 0000000000..b3d279ab5c --- /dev/null +++ b/lib/utils/test/src/utils/bidict/unordered_bidict.cc @@ -0,0 +1,240 @@ +#include "utils/bidict/unordered_bidict.h" +#include "test/utils/doctest/check_without_stringify.h" +#include "test/utils/doctest/fmt/unordered_map.h" +#include "test/utils/doctest/fmt/unordered_set.h" +#include "test/utils/doctest/fmt/vector.h" +#include "test/utils/rapidcheck.h" +#include + +using namespace FlexFlow; + +TEST_SUITE(FF_TEST_SUITE) { + TEST_CASE("unordered_bidict") { + unordered_bidict dict; + dict.equate(1, "one"); + dict.equate(2, "two"); + + SUBCASE("L type is the same as R type") { + unordered_bidict bd; + bd.equate(1, 3); + + SUBCASE("unordered_bidict::contains_l") { + CHECK(bd.contains_l(1)); + CHECK_FALSE(bd.contains_l(3)); + } + + SUBCASE("unordered_bidict::contains_r") { + CHECK(bd.contains_r(3)); + CHECK_FALSE(bd.contains_r(1)); + } + } + + SUBCASE("L type is not the same as R type") { + unordered_bidict bd; + bd.equate(1, "one"); + bd.equate(2, "two"); + + SUBCASE("unordered_bidict::contains_l") { + CHECK(bd.contains_l(1)); + CHECK_FALSE(bd.contains_l(3)); + } + + SUBCASE("unordered_bidict::contains_r") { + CHECK(bd.contains_r("one")); + CHECK_FALSE(bd.contains_r("three")); + } + } + + SUBCASE("unordered_bidict::unordered_bidict(std::initializer_list>)") { + unordered_bidict bd{{1, "one"}, {2, "two"}}; + CHECK(bd.contains_l(1)); + CHECK_FALSE(bd.contains_l(3)); + + SUBCASE("invalid mapping") { + CHECK_THROWS( + unordered_bidict{{1, "one"}, {2, "one"}}); + } + } + + SUBCASE("unordered_bidict::unordered_bidict(InputIt)") { + std::vector> pairs = {{1, "one"}, {2, "two"}}; + unordered_bidict bd{pairs.begin(), pairs.end()}; + CHECK(bd.contains_l(1)); + CHECK_FALSE(bd.contains_l(3)); + + SUBCASE("invalid mapping") { + std::vector> bad_pairs = {{1, "one"}, + {2, "one"}}; + CHECK_THROWS(unordered_bidict{bad_pairs.begin(), + bad_pairs.end()}); + } + } + + SUBCASE("unordered_bidict::unordered_bidict(std::unordered_map " + "const &, std::unordered_map const &)") { + std::unordered_map fwd = {{1, "one"}, {2, "two"}}; + std::unordered_map bwd = {{"one", 1}, {"two", 2}}; + unordered_bidict bd{fwd, bwd}; + CHECK(bd.contains_l(1)); + CHECK_FALSE(bd.contains_l(3)); + + SUBCASE("invalid mapping") { + std::unordered_map bad_fwd = {{1, "one"}, {2, "one"}}; + std::unordered_map bad_bwd = {{"one", 1}}; + CHECK_THROWS(unordered_bidict{bad_fwd, bad_bwd}); + } + } + + SUBCASE("unordered_bidict::erase_l") { + dict.erase_l(1); + CHECK(dict.size() == 1); + CHECK_THROWS(dict.at_l(1)); + CHECK(dict.at_r("two") == 2); + } + + SUBCASE("unordered_bidict::erase_r") { + dict.erase_r("one"); + CHECK(dict.size() == 1); + CHECK_THROWS(dict.at_r("one")); + CHECK(dict.at_l(2) == "two"); + } + + SUBCASE("unordered_bidict::equate") { + CHECK(dict.at_l(1) == "one"); + CHECK(dict.at_r("one") == 1); + CHECK(dict.at_l(2) == "two"); + CHECK(dict.at_r("two") == 2); + + dict.equate(1, "three"); + CHECK(dict.at_l(1) == "three"); + CHECK(dict.at_r("three") == 1); + CHECK_THROWS(dict.at_r("one")); + CHECK(dict.at_l(2) == "two"); + CHECK(dict.at_r("two") == 2); + + dict.equate(3, "three"); + CHECK(dict.at_l(3) == "three"); + CHECK(dict.at_r("three") == 3); + CHECK_THROWS(dict.at_l(1)); + CHECK(dict.at_l(2) == "two"); + CHECK(dict.at_r("two") == 2); + } + + SUBCASE("unordered_bidict::equate_strict") { + CHECK_THROWS(dict.equate_strict(1, "three")); + CHECK_THROWS(dict.equate_strict(3, "two")); + + dict.equate_strict(3, "three"); + CHECK(dict.at_l(3) == "three"); + CHECK(dict.at_r("three") == 3); + } + + SUBCASE("unordered_bidict::operator==") { + unordered_bidict bd{{1, "one"}, {2, "two"}}; + unordered_bidict bd2{{1, "one"}, {3, "three"}}; + CHECK(dict == bd); + CHECK_FALSE(dict == bd2); + } + + SUBCASE("unordered_bidict::operator!=") { + unordered_bidict bd{{1, "one"}, {2, "two"}}; + unordered_bidict bd2{{1, "one"}, {3, "three"}}; + CHECK_FALSE(dict != bd); + CHECK(dict != bd2); + } + + SUBCASE("unordered_bidict::at_l") { + CHECK(dict.at_l(1) == "one"); + CHECK_THROWS(dict.at_l(3)); + } + + SUBCASE("unordered_bidict::at_r") { + CHECK(dict.at_r("one") == 1); + CHECK_THROWS(dict.at_r("three")); + } + + SUBCASE("unordered_bidict::left_values") { + CHECK(dict.left_values() == std::unordered_set{1, 2}); + } + + SUBCASE("unordered_bidict::right_values") { + CHECK(dict.right_values() == + std::unordered_set{"one", "two"}); + } + + SUBCASE("unordered_bidict::size") { + CHECK(dict.size() == 2); + } + + SUBCASE("unordered_bidict::empty") { + CHECK_FALSE(dict.empty()); + unordered_bidict empty{}; + CHECK(empty.empty()); + } + + SUBCASE("unordered_bidict::begin") { + // note: std::unordered_map does not define iteration order, so we can + // only check that begin() refers to one of the contained pairs + auto it = dict.begin(); + CHECK(dict.contains(it->first, it->second)); + } + + SUBCASE("unordered_bidict::end") { + auto it = dict.end(); + + CHECK_WITHOUT_STRINGIFY(it == dict.end()); + } + + SUBCASE("unordered_bidict::reversed") { + unordered_bidict reversed_dict = dict.reversed(); + CHECK(reversed_dict.at_l("one") == 1); + CHECK(reversed_dict.at_r(2) == "two"); + } + + SUBCASE("implicitly convert to std::unordered_map") { + std::unordered_map res = dict; + std::unordered_map expected = {{1, "one"}, {2, "two"}}; + CHECK(res == expected); + } + + SUBCASE("fmt::to_string(unordered_bidict)") { + std::string result = fmt::to_string(dict); + std::string correct = fmt::to_string(dict.as_unordered_map()); + CHECK(result == correct); + } + } + + TEST_CASE("adl_serializer>") { + unordered_bidict deserialized = + unordered_bidict{ + {2, "hello"}, + {3, "goodbye"}, + {4, "yes"}, + }; + + nlohmann::json serialized = std::vector>{ + {2, "hello"}, + {3, "goodbye"}, + {4, "yes"}, + }; + + SUBCASE("to_json") { + nlohmann::json result = deserialized; + nlohmann::json correct = serialized; + + CHECK(result == correct); + } + + SUBCASE("from_json") { + unordered_bidict result = serialized; + unordered_bidict correct = deserialized; + + CHECK(result == correct); + } + } + + TEST_CASE("rc::Arbitrary>") { + RC_SUBCASE([](unordered_bidict) {}); + } +}