From 99538a3493057ef48715b3f459c0d891a21676b4 Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Tue, 20 Mar 2018 01:44:45 +0000 Subject: [PATCH 1/8] Take a graph as input. --- include/nnvm/node.h | 3 ++ include/nnvm/op_attr_types.h | 2 ++ src/core/symbolic.cc | 58 ++++++++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/include/nnvm/node.h b/include/nnvm/node.h index 2c7d0ef30..a6e250aec 100644 --- a/include/nnvm/node.h +++ b/include/nnvm/node.h @@ -19,6 +19,8 @@ namespace nnvm { // Forward declare node. class Node; +class Graph; + /*! * \brief we always used NodePtr for a reference pointer * to the node, so this alias can be changed in case. @@ -80,6 +82,7 @@ struct NodeAttrs { * For place holder variable, op == nullptr. */ const Op *op{nullptr}; + std::shared_ptr g; /*! \brief name of the node */ std::string name; /*! \brief The dictionary representation of attributes */ diff --git a/include/nnvm/op_attr_types.h b/include/nnvm/op_attr_types.h index e58e9ceb3..ad2293dde 100644 --- a/include/nnvm/op_attr_types.h +++ b/include/nnvm/op_attr_types.h @@ -202,6 +202,8 @@ using FCorrectLayout = std::function *last_ilayouts, std::vector *olayouts)>; +using FInputGraph = std::function; + } // namespace nnvm #endif // NNVM_OP_ATTR_TYPES_H_ diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index 2a2f5be50..f73a8c6d0 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -267,6 +267,7 @@ void Symbol::Compose(const array_view& args, const std::string& name) { static auto& flist_inputs = Op::GetAttr("FListInputNames"); static auto& fset_attrs = Op::GetAttr("FSetInputVarAttrOnCompose"); + static auto& fgraph = Op::GetAttr("FInputGraph"); // parameter check. for (size_t i = 0; i < args.size(); ++i) { @@ -284,26 +285,51 @@ void Symbol::Compose(const array_view& args, if (IsAtomic(outputs)) { Node* n = outputs[0].node.get(); uint32_t n_req = n->num_inputs(); + FListInputNames name_fn = flist_inputs.get(n->op(), nullptr); + auto arg_names = (name_fn == nullptr) ? std::vector{"data"} : name_fn(n->attrs); + FInputGraph fng = fgraph.get(n->op(), nullptr); + std::vector arg_vec(args.begin(), args.end()); + std::unordered_map kwarg_map(kwargs.begin(), kwargs.end()); + // If one of the input arguments is a graph, we need to remove it from the + // list. + if (fng != nullptr) { + size_t idx = fng(n->attrs); + CHECK(idx < n_req); + const Symbol *sym; + if (idx < arg_vec.size()) { + sym = arg_vec[idx]; + arg_vec.erase(arg_vec.begin() + idx); + } + else { + auto it = kwarg_map.find(arg_names[idx]); + CHECK(it != kwarg_map.end()); + sym = it->second; + kwarg_map.erase(it); + } + arg_names.erase(arg_names.begin() + idx); + n->attrs.g = std::make_shared(); + n->attrs.g->outputs = sym->outputs; + if (n_req != kVarg) + n_req--; + } if (n_req != kVarg) { n->inputs.resize(n_req); - CHECK_LE(args.size(), n_req) + CHECK_LE(arg_vec.size(), n_req) << "Incorrect number of arguments, requires " << n_req - << ", provided " << args.size(); - for (size_t i = 0; i < args.size(); ++i) { - n->inputs[i] = args[i]->outputs[0]; + << ", provided " << arg_vec.size(); + for (size_t i = 0; i < arg_vec.size(); ++i) { + n->inputs[i] = arg_vec[i]->outputs[0]; } // switch to keyword argument matching - if (args.size() != n_req) { - FListInputNames fn = flist_inputs.get(n->op(), nullptr); - auto arg_names = (fn == nullptr) ? std::vector{"data"} : fn(n->attrs); + if (arg_vec.size() != n_req) { if (arg_names.size() != n_req) { LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op()->name; } size_t nmatched = 0; - for (size_t i = args.size(); i < n_req; ++i) { - auto it = kwargs.find(arg_names[i]); - if (it != kwargs.end() && it->first == arg_names[i]) { + for (size_t i = arg_vec.size(); i < n_req; ++i) { + auto it = kwarg_map.find(arg_names[i]); + if (it != kwarg_map.end() && it->first == arg_names[i]) { n->inputs[i] = it->second->outputs[0]; ++nmatched; } else { @@ -314,18 +340,18 @@ void Symbol::Compose(const array_view& args, } } - if (nmatched != kwargs.size()) { + if (nmatched != kwarg_map.size()) { n->inputs.clear(); - std::vector keys = GetKeys(kwargs); - array_view view(dmlc::BeginPtr(arg_names) + args.size(), + std::vector keys = GetKeys(kwarg_map); + array_view view(dmlc::BeginPtr(arg_names) + arg_vec.size(), dmlc::BeginPtr(arg_names) + arg_names.size()); KeywordArgumentMismatch("Symbol.Compose", keys, view); } } } else { - CHECK_EQ(kwargs.size(), 0U) << "Variable length function do not accept kwargs"; - n->inputs.reserve(args.size()); - for (const Symbol* s : args) { + CHECK_EQ(kwarg_map.size(), 0U) << "Variable length function do not accept kwargs"; + n->inputs.reserve(arg_vec.size()); + for (const Symbol* s : arg_vec) { n->inputs.push_back(s->outputs[0]); } } From e886f3b47964d1472073ff05eb37cff2e2a867bf Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Wed, 21 Mar 2018 12:06:45 -0700 Subject: [PATCH 2/8] add ref count to the subgraph. --- src/core/symbolic.cc | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index f73a8c6d0..410c48876 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -261,6 +261,22 @@ std::vector Symbol::ListOutputNames() const { return ret; } +static void SetSubgraph(Node* n, const Symbol *sym) { + n->attrs.g = std::make_shared(); + n->attrs.g->outputs = sym->outputs; + auto &gidx = n->attrs.g->indexed_graph(); + CHECK_GE(gidx.input_nodes().size(), 1) << "The subgraph requires at least 1 input"; + + std::vector ref_count(gidx.num_node_entries(), 0); + for (const auto& i : gidx.input_nodes()) ++ref_count[gidx.entry_id(i, 0)]; + for (const auto& i : gidx.outputs()) ++ref_count[gidx.entry_id(i)]; + for (size_t i = 0; i < gidx.num_nodes(); ++i) { + for (const auto& j : gidx[i].inputs) ++ref_count[gidx.entry_id(j)]; + } + n->attrs.g->attrs["forward_ref_count"] = + std::make_shared(std::move(ref_count)); +} + // compositional logic void Symbol::Compose(const array_view& args, const std::unordered_map& kwargs, @@ -306,11 +322,11 @@ void Symbol::Compose(const array_view& args, sym = it->second; kwarg_map.erase(it); } - arg_names.erase(arg_names.begin() + idx); - n->attrs.g = std::make_shared(); - n->attrs.g->outputs = sym->outputs; + if (n_req != kVarg) n_req--; + arg_names.erase(arg_names.begin() + idx); + SetSubgraph(n, sym); } if (n_req != kVarg) { From b55a1e1b0ed406ce99f6b8ffb277e973de751a5d Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Sat, 7 Apr 2018 01:21:28 +0000 Subject: [PATCH 3/8] handle subgraph correctly. --- src/core/symbolic.cc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index 410c48876..539373cb9 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -285,10 +285,18 @@ void Symbol::Compose(const array_view& args, static auto& fset_attrs = Op::GetAttr("FSetInputVarAttrOnCompose"); static auto& fgraph = Op::GetAttr("FInputGraph"); + Node* n = outputs[0].node.get(); + FInputGraph fng = fgraph.get(n->op(), nullptr); + int garg_idx = -1; + if (fng != nullptr) + garg_idx = fng(n->attrs); + // parameter check. for (size_t i = 0; i < args.size(); ++i) { - CHECK_EQ(args[i]->outputs.size(), 1U) - << "Argument " << i << " is a tuple, single value is required"; + // If the argument isn't a graph, it should have only one output. + if (garg_idx < 0 || (size_t) garg_idx != i) + CHECK_EQ(args[i]->outputs.size(), 1U) + << "Argument " << i << " is a tuple, single value is required"; } for (const auto& kv : kwargs) { CHECK_EQ(kv.second->outputs.size(), 1U) @@ -299,11 +307,9 @@ void Symbol::Compose(const array_view& args, // Atomic functor composition. if (IsAtomic(outputs)) { - Node* n = outputs[0].node.get(); uint32_t n_req = n->num_inputs(); FListInputNames name_fn = flist_inputs.get(n->op(), nullptr); auto arg_names = (name_fn == nullptr) ? std::vector{"data"} : name_fn(n->attrs); - FInputGraph fng = fgraph.get(n->op(), nullptr); std::vector arg_vec(args.begin(), args.end()); std::unordered_map kwarg_map(kwargs.begin(), kwargs.end()); // If one of the input arguments is a graph, we need to remove it from the From 8218f4848f6af6758338a2158f7264c91dcb83a9 Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Mon, 9 Apr 2018 13:07:10 -0700 Subject: [PATCH 4/8] access more than one subgraphs. --- include/nnvm/node.h | 9 ++++- include/nnvm/op_attr_types.h | 11 ++++++ src/core/symbolic.cc | 69 +++++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/include/nnvm/node.h b/include/nnvm/node.h index a6e250aec..c42520236 100644 --- a/include/nnvm/node.h +++ b/include/nnvm/node.h @@ -82,7 +82,6 @@ struct NodeAttrs { * For place holder variable, op == nullptr. */ const Op *op{nullptr}; - std::shared_ptr g; /*! \brief name of the node */ std::string name; /*! \brief The dictionary representation of attributes */ @@ -93,6 +92,14 @@ struct NodeAttrs { * The object can be used to quickly access attributes. */ any parsed; + /*! + * \brief Some operators take graphs as input. These operators include + * control flow operators and high-order functions. + * These graphs don't change when the operators are invoked for different + * mini-batches. In this sense, the subgraphs are kind of similar to + * the parameters and show be kept as node attributes. + */ + std::vector > subgraphs; }; /*! diff --git a/include/nnvm/op_attr_types.h b/include/nnvm/op_attr_types.h index ad2293dde..1dac50cc5 100644 --- a/include/nnvm/op_attr_types.h +++ b/include/nnvm/op_attr_types.h @@ -203,6 +203,17 @@ using FCorrectLayout = std::function *olayouts)>; using FInputGraph = std::function; +/*! + * \brief Get a list of inputs that represent graphs instead of data. + * Normally, input symbols are considered as data to the operator. However, + * control flow operators and high-order functions need to interpret symbols + * as graphs. + * \param attrs The attributes of this node. + * \return a list of input index that are interpreted as symbols by the operator. + * + * \note Register under "FInputGraph". + */ +using FInputGraph = std::function(const NodeAttrs& attrs)>; } // namespace nnvm diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index 539373cb9..cde47fa59 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -261,10 +261,13 @@ std::vector Symbol::ListOutputNames() const { return ret; } +// Here we construct the symbol as an independent graph and attach it to +// the node as a subgraph. static void SetSubgraph(Node* n, const Symbol *sym) { - n->attrs.g = std::make_shared(); - n->attrs.g->outputs = sym->outputs; - auto &gidx = n->attrs.g->indexed_graph(); + auto g = std::make_shared(); + n->attrs.subgraphs.push_back(g); + g->outputs = sym->outputs; + auto &gidx = g->indexed_graph(); CHECK_GE(gidx.input_nodes().size(), 1) << "The subgraph requires at least 1 input"; std::vector ref_count(gidx.num_node_entries(), 0); @@ -273,8 +276,7 @@ static void SetSubgraph(Node* n, const Symbol *sym) { for (size_t i = 0; i < gidx.num_nodes(); ++i) { for (const auto& j : gidx[i].inputs) ++ref_count[gidx.entry_id(j)]; } - n->attrs.g->attrs["forward_ref_count"] = - std::make_shared(std::move(ref_count)); + g->attrs["forward_ref_count"] = std::make_shared(std::move(ref_count)); } // compositional logic @@ -285,21 +287,34 @@ void Symbol::Compose(const array_view& args, static auto& fset_attrs = Op::GetAttr("FSetInputVarAttrOnCompose"); static auto& fgraph = Op::GetAttr("FInputGraph"); + // The arguments that contain graphs. Node* n = outputs[0].node.get(); FInputGraph fng = fgraph.get(n->op(), nullptr); - int garg_idx = -1; + std::vector garg_idx; if (fng != nullptr) garg_idx = fng(n->attrs); + // The names of the arguments that contain graphs. + FListInputNames name_fn = flist_inputs.get(n->op(), nullptr); + auto arg_names = (name_fn == nullptr) ? std::vector{"data"} : name_fn(n->attrs); + std::vector garg_names(garg_idx.size()); + for (size_t i = 0; i < garg_idx.size(); i++) { + size_t idx = garg_idx[i]; + if (idx < arg_names.size()) + garg_names[i] = arg_names[idx]; + } + // parameter check. for (size_t i = 0; i < args.size(); ++i) { // If the argument isn't a graph, it should have only one output. - if (garg_idx < 0 || (size_t) garg_idx != i) + if (garg_idx.empty() || std::find(garg_idx.begin(), garg_idx.end(), i) == garg_idx.end()) CHECK_EQ(args[i]->outputs.size(), 1U) - << "Argument " << i << " is a tuple, single value is required"; + << "Argument " << i << " is a tuple, single value is required"; } for (const auto& kv : kwargs) { - CHECK_EQ(kv.second->outputs.size(), 1U) + if (garg_names.empty() + || std::find(garg_names.begin(), garg_names.end(), kv.first) == garg_names.end()) + CHECK_EQ(kv.second->outputs.size(), 1U) << "Keyword Argument " << kv.first << " is a tuple, single value is required"; } // assign new name @@ -308,31 +323,29 @@ void Symbol::Compose(const array_view& args, // Atomic functor composition. if (IsAtomic(outputs)) { uint32_t n_req = n->num_inputs(); - FListInputNames name_fn = flist_inputs.get(n->op(), nullptr); - auto arg_names = (name_fn == nullptr) ? std::vector{"data"} : name_fn(n->attrs); std::vector arg_vec(args.begin(), args.end()); std::unordered_map kwarg_map(kwargs.begin(), kwargs.end()); // If one of the input arguments is a graph, we need to remove it from the // list. if (fng != nullptr) { - size_t idx = fng(n->attrs); - CHECK(idx < n_req); - const Symbol *sym; - if (idx < arg_vec.size()) { - sym = arg_vec[idx]; - arg_vec.erase(arg_vec.begin() + idx); - } - else { - auto it = kwarg_map.find(arg_names[idx]); - CHECK(it != kwarg_map.end()); - sym = it->second; - kwarg_map.erase(it); - } + std::vector idxes = fng(n->attrs); + for (auto idx : idxes) { + const Symbol *syms; + if (idx < arg_vec.size()) { + sym = arg_vec[idx]; + arg_vec.erase(arg_vec.begin() + idx); + } else { + auto it = kwarg_map.find(arg_names[idx]); + CHECK(it != kwarg_map.end()); + sym = it->second; + kwarg_map.erase(it); + } - if (n_req != kVarg) - n_req--; - arg_names.erase(arg_names.begin() + idx); - SetSubgraph(n, sym); + if (n_req != kVarg) + n_req--; + arg_names.erase(arg_names.begin() + idx); + SetSubgraph(n, sym); + } } if (n_req != kVarg) { From 809cc1d7fd9c510a44f548f4a1b3938fdc870ec6 Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Mon, 9 Apr 2018 21:07:53 +0000 Subject: [PATCH 5/8] Fix a compile error. --- src/core/symbolic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index cde47fa59..1861b2ea1 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -330,7 +330,7 @@ void Symbol::Compose(const array_view& args, if (fng != nullptr) { std::vector idxes = fng(n->attrs); for (auto idx : idxes) { - const Symbol *syms; + const Symbol *sym; if (idx < arg_vec.size()) { sym = arg_vec[idx]; arg_vec.erase(arg_vec.begin() + idx); From 192c05dcfb6c878102c211cbe67d1ff5f1f8892b Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Mon, 30 Apr 2018 18:55:57 +0000 Subject: [PATCH 6/8] store symbols in NodeAttrs. --- include/nnvm/node.h | 3 ++- src/core/symbolic.cc | 20 +------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/include/nnvm/node.h b/include/nnvm/node.h index c42520236..380cac43c 100644 --- a/include/nnvm/node.h +++ b/include/nnvm/node.h @@ -18,6 +18,7 @@ namespace nnvm { // Forward declare node. class Node; +class Symbol; class Graph; @@ -99,7 +100,7 @@ struct NodeAttrs { * mini-batches. In this sense, the subgraphs are kind of similar to * the parameters and show be kept as node attributes. */ - std::vector > subgraphs; + std::vector > subgraphs; }; /*! diff --git a/src/core/symbolic.cc b/src/core/symbolic.cc index 1861b2ea1..927dd2b70 100644 --- a/src/core/symbolic.cc +++ b/src/core/symbolic.cc @@ -261,24 +261,6 @@ std::vector Symbol::ListOutputNames() const { return ret; } -// Here we construct the symbol as an independent graph and attach it to -// the node as a subgraph. -static void SetSubgraph(Node* n, const Symbol *sym) { - auto g = std::make_shared(); - n->attrs.subgraphs.push_back(g); - g->outputs = sym->outputs; - auto &gidx = g->indexed_graph(); - CHECK_GE(gidx.input_nodes().size(), 1) << "The subgraph requires at least 1 input"; - - std::vector ref_count(gidx.num_node_entries(), 0); - for (const auto& i : gidx.input_nodes()) ++ref_count[gidx.entry_id(i, 0)]; - for (const auto& i : gidx.outputs()) ++ref_count[gidx.entry_id(i)]; - for (size_t i = 0; i < gidx.num_nodes(); ++i) { - for (const auto& j : gidx[i].inputs) ++ref_count[gidx.entry_id(j)]; - } - g->attrs["forward_ref_count"] = std::make_shared(std::move(ref_count)); -} - // compositional logic void Symbol::Compose(const array_view& args, const std::unordered_map& kwargs, @@ -344,7 +326,7 @@ void Symbol::Compose(const array_view& args, if (n_req != kVarg) n_req--; arg_names.erase(arg_names.begin() + idx); - SetSubgraph(n, sym); + n->attrs.subgraphs.push_back(std::make_shared(*sym)); } } From c29f9ec86718ee1eacc2ef163977464cd98088d3 Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Thu, 17 May 2018 15:56:28 +0000 Subject: [PATCH 7/8] remove forward declaration of Graph. --- include/nnvm/node.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/nnvm/node.h b/include/nnvm/node.h index 380cac43c..bcfdb95c4 100644 --- a/include/nnvm/node.h +++ b/include/nnvm/node.h @@ -20,8 +20,6 @@ namespace nnvm { class Node; class Symbol; -class Graph; - /*! * \brief we always used NodePtr for a reference pointer * to the node, so this alias can be changed in case. From a83395bae3898f8f33683889fd7decf33e7fed6b Mon Sep 17 00:00:00 2001 From: Da Zheng Date: Fri, 18 May 2018 18:16:38 +0000 Subject: [PATCH 8/8] remove. --- include/nnvm/op_attr_types.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/nnvm/op_attr_types.h b/include/nnvm/op_attr_types.h index 1dac50cc5..b7f6be408 100644 --- a/include/nnvm/op_attr_types.h +++ b/include/nnvm/op_attr_types.h @@ -202,7 +202,6 @@ using FCorrectLayout = std::function *last_ilayouts, std::vector *olayouts)>; -using FInputGraph = std::function; /*! * \brief Get a list of inputs that represent graphs instead of data. * Normally, input symbols are considered as data to the operator. However,