You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by sa...@apache.org on 2020/10/01 16:42:45 UTC

[incubator-mxnet] branch master updated: Add dedup flag to master from #19112 (#19246)

This is an automated email from the ASF dual-hosted git repository.

samskalicky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-mxnet.git


The following commit(s) were added to refs/heads/master by this push:
     new 5dc6cad  Add dedup flag to master from #19112 (#19246)
5dc6cad is described below

commit 5dc6cad954de1971bb1ffb830194924d8fd06bbe
Author: Sam Skalicky <sa...@gmail.com>
AuthorDate: Thu Oct 1 09:40:50 2020 -0700

    Add dedup flag to master from #19112 (#19246)
    
    * initial commit
    
    * update build_subgraph
    
    * added test
    
    * calling test
    
    Co-authored-by: Ubuntu <ub...@ip-172-31-6-220.us-west-2.compute.internal>
---
 src/c_api/c_api_symbolic.cc                        |  5 ++
 src/operator/subgraph/build_subgraph.cc            | 59 +++++++++++++++-------
 .../partitioner/custom_subgraph_property.h         |  7 ++-
 src/operator/subgraph/subgraph_property.h          | 29 ++++++++---
 tests/python/unittest/test_subgraph_op.py          | 32 ++++++++++++
 5 files changed, 101 insertions(+), 31 deletions(-)

diff --git a/src/c_api/c_api_symbolic.cc b/src/c_api/c_api_symbolic.cc
index 6f5f03a..2cb5923 100644
--- a/src/c_api/c_api_symbolic.cc
+++ b/src/c_api/c_api_symbolic.cc
@@ -1410,6 +1410,11 @@ int MXOptimizeForBackend(SymbolHandle sym_handle,
   for (mx_uint i = 0; i < num_options; ++i)
     options_map.emplace(keys[i], vals[i]);
 
+  // set dedup option as attribute on graph to enable dedup during partitioning
+  if (options_map.count("dedup_subgraph") > 0 &&
+      options_map.at("dedup_subgraph").compare("True") == 0)
+    g.attrs["dedup_subgraph"] = std::make_shared<nnvm::any>(std::string("True"));
+
   if (mxnet::op::SubgraphBackendRegistry::Get()->backend_map_.count(backend_name) > 0) {
     // use subgraph backend
     const auto backend = mxnet::op::SubgraphBackendRegistry
diff --git a/src/operator/subgraph/build_subgraph.cc b/src/operator/subgraph/build_subgraph.cc
index 72dd2da..69d1f30 100644
--- a/src/operator/subgraph/build_subgraph.cc
+++ b/src/operator/subgraph/build_subgraph.cc
@@ -539,10 +539,13 @@ void CutGraphInputs(const std::vector<nnvm::NodeEntry*> &input_entries,
                     std::vector<nnvm::NodeEntry> *orig_entries,
                     std::vector<nnvm::NodeEntry> *unique_orig_entries,
                     std::vector<nnvm::NodeEntry*> *unique_input_entries,
-                    const bool skip_var = false) {
+                    const bool skip_var = false,
+                    const bool dedup = false) {
   orig_entries->resize(input_entries.size());
   // map for creating unique var nodes for deduplicating entries from the same node
-  std::unordered_map<std::string, nnvm::NodeEntry> name_count_map;
+  std::unordered_map<std::string, nnvm::NodeEntry> name_map;
+  std::unordered_map<std::string, int> name_count_map;
+
   for (size_t i = 0; i < input_entries.size(); ++i) {
     nnvm::NodeEntry *e = input_entries[i];
     // If the node is a variable itself, we may want to skip the node.
@@ -558,18 +561,24 @@ void CutGraphInputs(const std::vector<nnvm::NodeEntry*> &input_entries,
     CHECK_EQ(output_names.size(), 1U);
     const std::string& var_name = output_names[0];
     // check if this entry is a duplicate
-    auto it = name_count_map.find(var_name);
-    if (name_count_map.end() == it) {
+    if (name_count_map.count(var_name) == 0) {
       // first use of this node as input to subgraph
+      name_count_map.emplace(var_name, 0);
       unique_orig_entries->push_back(*e);
       unique_input_entries->push_back(e);
       nnvm::ObjectPtr n = nnvm::CreateVariableNode(var_name + std::to_string(0));
-      *e = nnvm::NodeEntry{n, 0, 0};
-      // store node for re-use
-      name_count_map.emplace(var_name, *e);
+      name_map.emplace(var_name, nnvm::NodeEntry{n, 0, 0});
     } else {
       // other use of same node as input to subgraph
-      *e = it->second;
+      name_count_map[var_name]++;
+    }
+
+    if (dedup) {
+      *e = name_map[var_name];
+    } else {
+      nnvm::ObjectPtr n = nnvm::CreateVariableNode(
+        var_name + std::to_string(name_count_map[var_name]));
+      *e = nnvm::NodeEntry{n, 0, 0};
     }
   }
 }
@@ -600,13 +609,14 @@ void CreateSubgraphNode(nnvm::Graph* g,
 #if DEBUG_SUBGRAPH
   LOG(INFO) << "Searching for input entries...";
 #endif
+  bool dedup_subgraph = g->HasAttr("dedup_subgraph");
   std::vector<nnvm::NodeEntry*> input_entries;  // nodes that produce inputs to subgraph nodes
   FindInputEntries(*g, simple_nodes, subgraph_nodes, *entry_top_order_map, &input_entries);
   std::vector<nnvm::NodeEntry> orig_input_entries;  // original input entries (dupes)
   std::vector<nnvm::NodeEntry> unique_orig_entries;  // unique original input entries
   std::vector<nnvm::NodeEntry*> unique_input_entries;  // unique modified subgraph inputs
   CutGraphInputs(input_entries, &orig_input_entries, &unique_orig_entries,
-                 &unique_input_entries, false);
+                 &unique_input_entries, false, dedup_subgraph);
 #if DEBUG_SUBGRAPH
   PrintNodeEntries(input_entries);
   LOG(INFO) << "Searching for output entries...";
@@ -621,25 +631,36 @@ void CreateSubgraphNode(nnvm::Graph* g,
   nnvm::NodeEntryEqual node_equal;
   sym.outputs.resize(output_entries.size());
   for (size_t i = 0; i < output_entries.size(); ++i) {
-    if (i == 0) {  // add first entry
-      sym.outputs[idx] = *output_entries[i];
-    } else if (!node_equal(sym.outputs[idx], *output_entries[i])) {  // compare to see if diff
-      // add new entries
-      idx++;
-      sym.outputs[idx] = *output_entries[i];
-    }  // else skip over dupe entries
+    if (dedup_subgraph) {
+      if (i == 0) {  // add first entry
+        sym.outputs[idx] = *output_entries[i];
+      } else if (!node_equal(sym.outputs[idx], *output_entries[i])) {  // compare to see if diff
+        // add new entries
+        idx++;
+        sym.outputs[idx] = *output_entries[i];
+      }  // else skip over dupe entries
+    } else {
+      sym.outputs[i] = *output_entries[i];
+    }
   }
-  sym.outputs.resize(idx+1);
+  if (dedup_subgraph)
+    sym.outputs.resize(idx+1);
 
   const SubgraphPropertyPtr& subg_prop = g->GetAttr<SubgraphPropertyPtr>("subgraph_property");
-  subg_prop->InitSubgraphInputs(&unique_input_entries, &unique_orig_entries);
+  if (dedup_subgraph)
+    subg_prop->InitSubgraphInputs(&unique_input_entries, &unique_orig_entries);
+  else
+    subg_prop->InitSubgraphInputs(&input_entries, &orig_input_entries);
   nnvm::ObjectPtr n = subg_prop->CreateSubgraphNode(sym, subgraph_selector, subgraph_id);
   // CreateSubgraphNode returns NULL if subgraph property determines that subgraph is sub-optimal
   // In that case, subgraph node is not created and graph is not modified
   if (n) {
     // Connect the external nodes to the subgraph node.
     subg_prop->ConnectSubgraphOutputs(n, &output_entries);
-    subg_prop->ConnectSubgraphInputs(n, &unique_input_entries, &unique_orig_entries);
+    if (dedup_subgraph)
+      subg_prop->ConnectSubgraphInputs(n, &unique_input_entries, &unique_orig_entries);
+    else
+      subg_prop->ConnectSubgraphInputs(n, &input_entries, &orig_input_entries);
 
     const auto& indexed_graph = g->indexed_graph();
     for (size_t i = 0; i < n->inputs.size(); ++i) {
diff --git a/src/operator/subgraph/partitioner/custom_subgraph_property.h b/src/operator/subgraph/partitioner/custom_subgraph_property.h
index f2d715b..49d5a8f 100644
--- a/src/operator/subgraph/partitioner/custom_subgraph_property.h
+++ b/src/operator/subgraph/partitioner/custom_subgraph_property.h
@@ -319,9 +319,8 @@ class  CustomSubgraphProperty: public SubgraphProperty {
     opt_vals_.clear();
     options_map_.clear();
     // store options in map in subgraph property to re-use later for reviewSubgraph
-    for (auto& kv : options_map) {
-      options_map_.push_back(kv);
-    }
+    options_map_.insert(options_map.begin(), options_map.end());
+
     // convert options_map_ to char* to pass to backend library
     for (auto& kv : options_map_) {
       opt_keys_.push_back(kv.first.c_str());
@@ -526,7 +525,7 @@ class  CustomSubgraphProperty: public SubgraphProperty {
   mxnet::ext::opCallFree_t call_free_;
   std::unordered_map<std::string, int> supported_nodes;
   std::string subgraph_op_name;
-  std::vector<std::pair<std::string, std::string>> options_map_;
+  std::unordered_map<std::string, std::string> options_map_;
   std::vector<const char*> opt_keys_, opt_vals_;
   std::vector<std::string> in_arg_names, in_aux_names;
   NDArray **in_args_ptr;
diff --git a/src/operator/subgraph/subgraph_property.h b/src/operator/subgraph/subgraph_property.h
index ae3075c..c003567 100644
--- a/src/operator/subgraph/subgraph_property.h
+++ b/src/operator/subgraph/subgraph_property.h
@@ -258,7 +258,7 @@ class SubgraphProperty {
     kAdjust,
   };
 
-  explicit SubgraphProperty(SgPropertyType type = kCreate) : type_(type) {}
+  explicit SubgraphProperty(SgPropertyType type = kCreate) : type_(type), dedup_subgraph(true) {}
 
   /*!
    * \brief The criteria of selecting the subgraph nodes.
@@ -269,7 +269,14 @@ class SubgraphProperty {
   }
 
   virtual void PrePartition(const nnvm::Graph& g,
-    const std::unordered_map<std::string, std::string>& options_map) {}
+    const std::unordered_map<std::string, std::string>& options_map) {
+    if (options_map.count("dedup_subgraph") > 0 &&
+        options_map.at("dedup_subgraph").compare("True") == 0) {
+      dedup_subgraph = true;
+    } else {
+      dedup_subgraph = false;
+    }
+  }
 
   virtual void PostPartition(const nnvm::Graph& g) {}
 
@@ -348,14 +355,19 @@ class SubgraphProperty {
     nnvm::NodeEntry prevNodeEntry;
     uint32_t idx = 0;
     for (size_t i = 0; i < output_entries->size(); ++i) {
-      // increment the output idx for each unique output of the subgraph
-      if (i != 0 && !node_equal(prevNodeEntry, *output_entries->at(i)))
-        idx++;
-      prevNodeEntry = *output_entries->at(i);  // make a copy so we can compare before modifying
-      // change output entry to point to subgraph instead of original node
-      *output_entries->at(i) = nnvm::NodeEntry{subgraph_node, idx, 0};
+      if (dedup_subgraph) {
+        // increment the output idx for each unique output of the subgraph
+        if (i != 0 && !node_equal(prevNodeEntry, *output_entries->at(i)))
+          idx++;
+        prevNodeEntry = *output_entries->at(i);  // make a copy so we can compare before modifying
+        // change output entry to point to subgraph instead of original node
+        *output_entries->at(i) = nnvm::NodeEntry{subgraph_node, idx, 0};
+      } else {
+        *output_entries->at(i) = nnvm::NodeEntry{subgraph_node, static_cast<uint32_t>(i), 0};
+      }
     }
   }
+
   /*!
    * \brief Connect subgraph internal input with external input entries.
    * By default, each input entry will connect in top sorted order.
@@ -417,6 +429,7 @@ class SubgraphProperty {
  protected:
   SgPropertyType type_;
   std::unordered_map<std::string, std::shared_ptr<nnvm::any>> attrs_;
+  bool dedup_subgraph;
 };
 
 using SubgraphPropertyPtr = std::shared_ptr<SubgraphProperty>;
diff --git a/tests/python/unittest/test_subgraph_op.py b/tests/python/unittest/test_subgraph_op.py
index 2974838..375f630 100644
--- a/tests/python/unittest/test_subgraph_op.py
+++ b/tests/python/unittest/test_subgraph_op.py
@@ -384,6 +384,37 @@ def test_subgraph_exe8(sym, subgraph_backend, op_names):
 
 @pytest.mark.parametrize('subgraph_backend', ['default', 'default_v2'])
 @pytest.mark.parametrize('sym,op_names', get_graphs())
+def test_subgraph_exe9(sym, subgraph_backend, op_names):
+    """Call optimize_for to infer shapes, types and dtypes followed by graph partitioning and
+    dedup subgraph, then bind and compare results of the partitioned sym and the original sym."""
+    # bind
+    sym, _, _ = sym
+    arg_shapes, _, aux_shapes = sym.infer_shape()
+    arg_names = sym.list_arguments()
+    aux_names = sym.list_auxiliary_states()
+    arg_dict = {name:mx.nd.random.uniform(shape=shape) for name,shape in zip(arg_names,arg_shapes)}
+    aux_dict = {name:mx.nd.random.uniform(shape=shape) for name,shape in zip(aux_names,aux_shapes)}
+    exe1 = sym._bind(ctx=mx.current_context(), args=arg_dict, aux_states=aux_dict, grad_req='null')
+    exe1.forward()
+
+    # infer shape/type before partition before bind
+    check_call(_LIB.MXSetSubgraphPropertyOpNamesV2(c_str(subgraph_backend), mx_uint(len(op_names)),
+                                                   c_str_array(op_names)))
+    part_sym = sym.optimize_for(subgraph_backend, arg_dict, aux_dict, dedup_subgraph=True)
+    check_call(_LIB.MXRemoveSubgraphPropertyOpNamesV2(c_str(subgraph_backend)))
+
+    exe2 = part_sym._bind(ctx=mx.current_context(), args=arg_dict, aux_states=aux_dict, grad_req='null')
+    exe2.forward()
+
+    # compare outputs
+    outputs1 = exe1.outputs
+    outputs2 = exe2.outputs
+    assert len(outputs1) == len(outputs2)
+    for i in range(len(outputs1)):
+        assert_almost_equal((outputs1[i] - outputs2[i]).abs().sum().asnumpy(), np.zeros(shape=(1,)))
+        
+@pytest.mark.parametrize('subgraph_backend', ['default', 'default_v2'])
+@pytest.mark.parametrize('sym,op_names', get_graphs())
 def test_subgraph_backend_gluon(sym, subgraph_backend, op_names, tmpdir):
     """Call hybridize() to partition the graph, and then compare results of the partitioned
     sym and the original sym. Here do an inference before hybridizing with the subgraph_backend
@@ -505,6 +536,7 @@ if __name__ == "__main__":
             test_subgraph_exe6(sym, subgraph_backend, op_names)
             test_subgraph_exe7(sym, subgraph_backend, op_names)
             test_subgraph_exe8(sym, subgraph_backend, op_names)
+            test_subgraph_exe9(sym, subgraph_backend, op_names)
             test_subgraph_backend_gluon(sym, subgraph_backend, op_names, tmpdir)
             test_subgraph_backend_gluon_ext1(tmpdir)
             test_subgraph_backend_gluon_ext2(tmpdir)