You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by re...@apache.org on 2018/08/04 04:41:43 UTC

[incubator-mxnet] 03/04: Make subgraph var node name unique (#11876)

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

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

commit dbf781c916848273aa25a24ae544e9cc5e1c9b26
Author: reminisce <wu...@gmail.com>
AuthorDate: Tue Jul 24 15:46:50 2018 -0700

    Make subgraph var node name unique (#11876)
---
 src/operator/subgraph/partition_graph.cc  |  12 ++-
 tests/python/unittest/test_subgraph_op.py | 144 +++++++++++++-----------------
 2 files changed, 74 insertions(+), 82 deletions(-)

diff --git a/src/operator/subgraph/partition_graph.cc b/src/operator/subgraph/partition_graph.cc
index c935c8f..0546430 100644
--- a/src/operator/subgraph/partition_graph.cc
+++ b/src/operator/subgraph/partition_graph.cc
@@ -410,7 +410,6 @@ void SortEntries(const std::unordered_map<const nnvm::NodeEntry*, size_t>& entry
  * \param entry_top_order_map mapping entry pointer to its top sorted position
  * \param input_entries input entries of the subgraph
  */
-
 void FindInputEntries(const Graph& g,
                       const std::vector<SimpleNodePtr>& simple_nodes,
                       const std::vector<SimpleNode*>& subgraph_nodes,
@@ -517,6 +516,8 @@ void CutGraphInputs(const std::vector<nnvm::NodeEntry*> &input_entries,
                     std::vector<nnvm::NodeEntry> *orig_entries,
                     const bool skip_var = 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, 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.
@@ -529,7 +530,14 @@ void CutGraphInputs(const std::vector<nnvm::NodeEntry*> &input_entries,
     sym.outputs.push_back(*e);
     const auto output_names = sym.ListOutputNames();
     CHECK_EQ(output_names.size(), 1U);
-    nnvm::NodePtr n = nnvm::CreateVariableNode(output_names[0]);
+    const std::string& var_name = output_names[0];
+    auto it = name_count_map.find(var_name);
+    if (name_count_map.end() == it) {
+      name_count_map.emplace(var_name, 0);
+    } else {
+      ++(it->second);
+    }
+    nnvm::NodePtr n = nnvm::CreateVariableNode(var_name + std::to_string(name_count_map[var_name]));
     *e = nnvm::NodeEntry{n, 0, 0};
   }
 }
diff --git a/tests/python/unittest/test_subgraph_op.py b/tests/python/unittest/test_subgraph_op.py
index 0e5c1e0..c3b408b 100644
--- a/tests/python/unittest/test_subgraph_op.py
+++ b/tests/python/unittest/test_subgraph_op.py
@@ -20,122 +20,106 @@ import mxnet as mx
 from mxnet.base import SymbolHandle, check_call, _LIB, mx_uint, c_str_array
 from mxnet.symbol import Symbol
 import numpy as np
+from mxnet.test_utils import assert_almost_equal
 
 
-def test_subgraph():
-    def get_graph():
-        data1 = mx.sym.Variable('data1', shape=(3, 3, 10, 10), dtype=np.float32)
-        data2 = mx.sym.Variable('data2', shape=(1, 0, 2, 2))
-        data3 = mx.sym.sin(data2)
-        conv = mx.sym.Convolution(data=data1, weight=data3, kernel=(2, 2), num_filter=1)
-        rets = []
-        rets.append((conv, []))
-        rets.append((conv, [mx.sym.sin.__name__]))
-        rets.append((conv, [mx.sym.Convolution.__name__]))
-        rets.append((conv, [mx.sym.sin.__name__, mx.sym.Convolution.__name__]))
-        return rets
-
-    for regular_sym, op_names in get_graph():
-        input_names = regular_sym.list_inputs()
-        shapes = regular_sym.infer_shape()
-        types = regular_sym.infer_type()
-        out = SymbolHandle()
-
-        check_call(_LIB.MXPartitionGraph(regular_sym.handle, mx_uint(len(op_names)),
-            c_str_array(op_names), ctypes.byref(out)))
-        subgraph_sym = Symbol(out)
-        assert input_names == subgraph_sym.list_inputs()
-
-        print(subgraph_sym.list_outputs())
-        assert shapes == subgraph_sym.infer_shape()
-        assert types == subgraph_sym.infer_type()
-
-        regular_exec = regular_sym.simple_bind(ctx=mx.cpu(), grad_req='null')
-        subgraph_exec = subgraph_sym.simple_bind(ctx=mx.cpu(), grad_req='null')
-
-        for name in input_names:
-            regular_exec.arg_dict[name][:] = mx.nd.random.normal(
-                    shape=regular_exec.arg_dict[name].shape)
-            subgraph_exec.arg_dict[name][:] = regular_exec.arg_dict[name]
-
-        subgraph_exec.forward()
-        regular_exec.forward()
-        mx.nd.waitall()
-        assert (subgraph_exec.outputs[0] - regular_exec.outputs[0]).abs().sum().asscalar() == 0.0
-
-
-def test_input_name_order():
-    def check_input_order(sym, op_names):
+def test_subgraph_exe():
+    def check_subgraph_exe(sym, op_names):
         out = SymbolHandle()
         check_call(_LIB.MXPartitionGraph(sym.handle, mx_uint(len(op_names)),
                                          c_str_array(op_names), ctypes.byref(out)))
 
-        new_sym = Symbol(out)
-        #print(sym.list_inputs())
-        #print(new_sym.list_inputs())
-        assert new_sym.list_inputs() == sym.list_inputs()
-        assert new_sym.list_arguments() == sym.list_arguments()
-        assert new_sym.list_auxiliary_states() == sym.list_auxiliary_states()
-        #print(new_sym.list_arguments())
-        #print(new_sym.list_auxiliary_states())
-        #print('original outputs: %s' % sym.list_outputs())
-        #print('new sym outputs: %s' % new_sym.list_outputs())
+        partitioned_sym = Symbol(out)
+        assert partitioned_sym.list_inputs() == sym.list_inputs()
+        assert partitioned_sym.list_arguments() == sym.list_arguments()
+        assert partitioned_sym.list_auxiliary_states() == sym.list_auxiliary_states()
+        exe = sym.simple_bind(ctx=mx.cpu(), grad_req='null')
+        partitioned_exe = partitioned_sym.simple_bind(ctx=mx.cpu(), grad_req='null')
+        input_names = sym.list_inputs()
+        for name in input_names:
+            if name in exe.arg_dict:
+                exe.arg_dict[name][:] = mx.nd.random.uniform(shape=exe.arg_dict[name].shape)
+                partitioned_exe.arg_dict[name][:] = exe.arg_dict[name]
+            else:
+                assert name in exe.aux_dict
+                exe.aux_dict[name][:] = mx.nd.random.uniform(shape=exe.aux_dict[name].shape)
+                partitioned_exe.aux_dict[name][:] = exe.aux_dict[name]
+        exe.forward()
+        partitioned_exe.forward()
+        mx.nd.waitall()
+        assert len(exe.outputs) == len(partitioned_exe.outputs)
+        for i in range(len(exe.outputs)):
+            assert_almost_equal((exe.outputs[i] - partitioned_exe.outputs[i]).abs().sum().asnumpy(),
+                                np.zeros(shape=(1,)))
 
     def test_network_structure_1():
-        data1 = mx.sym.var('data1')
+        data1 = mx.sym.var('data1', shape=(2, 3, 10, 10))
         data2 = mx.sym.var('data2')
         conv1 = mx.sym.Convolution(data=data1, weight=data2, no_bias=True, kernel=(2, 2), num_filter=1)
-        conv2 = mx.sym.Convolution(data=data2, weight=data1, no_bias=True, kernel=(2, 2), num_filter=1)
+        conv2 = mx.sym.Convolution(data=data2, no_bias=True, kernel=(1, 1), num_filter=1)
         out = mx.sym.Group([conv1, conv2])
-        check_input_order(out, ['Convolution'])
+        check_subgraph_exe(out, ['Convolution'])
 
     def test_network_structure_2():
-        data1 = mx.sym.var('data1')
-        data2 = mx.sym.var('data2')
-        conv1 = mx.sym.Convolution(data=data1, weight=data2, no_bias=True, kernel=(2, 2), num_filter=1)
-        conv2 = mx.sym.Convolution(data=data2, weight=data1, no_bias=True, kernel=(2, 2), num_filter=1)
-        out = conv1 + conv2
-        check_input_order(out, ['Convolution'])
-        check_input_order(out, ['Convolution', '_Plus', 'elemwise_add', '_plus'])
-
-    def test_network_structure_3():
         # this tests whether the partitioning algorithm can deal with cycles
-        data = mx.sym.var('data')
+        data = mx.sym.var('data', shape=(2, 3, 10, 10))
         ret = mx.sym.exp(data)
         ret1 = mx.sym.cos(ret)
         ret2 = mx.sym.sin(ret)
         ret = ret1 + ret2
-        check_input_order(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus'])
-        check_input_order(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus'])
+        check_subgraph_exe(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus'])
+        check_subgraph_exe(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus'])
 
-    def test_network_structure_4():
+    def test_network_structure_3():
         # this tests whether the partitioned sym can distinguish in_args and aux_states
-        data = mx.sym.var('data')
+        data = mx.sym.var('data', shape=(2, 3, 10, 10))
         ret = mx.sym.exp(data)
         ret1 = mx.sym.cos(ret)
         ret2 = mx.sym.sin(ret)
         ret = ret1 + ret2
         ret = mx.sym.BatchNorm(ret)
         ret = mx.sym.BatchNorm(ret)
-        check_input_order(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus'])
-        check_input_order(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus'])
-        check_input_order(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus', 'BatchNorm'])
-        check_input_order(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus', 'BatchNorm'])
-        check_input_order(ret, ['exp', 'BatchNorm'])
-        check_input_order(ret, ['BatchNorm'])
+        check_subgraph_exe(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus'])
+        check_subgraph_exe(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus'])
+        check_subgraph_exe(ret, ['exp', 'sin', '_Plus', 'elemwise_add', '_plus', 'BatchNorm'])
+        check_subgraph_exe(ret, ['exp', 'cos', '_Plus', 'elemwise_add', '_plus', 'BatchNorm'])
+        check_subgraph_exe(ret, ['exp', 'BatchNorm'])
+        check_subgraph_exe(ret, ['BatchNorm'])
 
-    def test_network_structure_5():
+    def test_network_structure_4():
         # the last op has multiple duplicate outputs
-        data = mx.sym.var('data')
+        data = mx.sym.var('data', shape=(2, 3, 10, 10))
         ret = mx.sym.exp(data)
         ret = mx.sym.Group([ret, ret, ret])
-        check_input_order(ret, ['exp'])
+        check_subgraph_exe(ret, ['exp'])
+
+    def test_network_structure_5():
+        # the subgraph has two duplicate input entries
+        data = mx.sym.var('data', shape=(2, 3, 10, 10))
+        ret = data + data
+        check_subgraph_exe(ret, ['_plus', '_Plus', 'elemwise_add'])
+
+    def test_network_structure_6():
+        def get_graph():
+            data1 = mx.sym.Variable('data1', shape=(3, 3, 10, 10), dtype=np.float32)
+            data2 = mx.sym.Variable('data2', shape=(1, 0, 2, 2))
+            data3 = mx.sym.sin(data2)
+            conv = mx.sym.Convolution(data=data1, weight=data3, kernel=(2, 2), num_filter=1)
+            rets = [(conv, []),
+                    (conv, [mx.sym.sin.__name__]),
+                    (conv, [mx.sym.Convolution.__name__]),
+                    (conv, [mx.sym.sin.__name__, mx.sym.Convolution.__name__])]
+            return rets
+
+        for sym, op_names in get_graph():
+            check_subgraph_exe(sym, op_names)
 
     test_network_structure_1()
     test_network_structure_2()
     test_network_structure_3()
     test_network_structure_4()
     test_network_structure_5()
+    test_network_structure_6()
 
 
 if __name__ == '__main__':