| #include "caffe2/transforms/common_subexpression_elimination.h" |
| |
| #include "caffe2/core/common.h" |
| #include "caffe2/core/net.h" |
| #include "caffe2/proto/caffe2_pb.h" |
| |
| #include <c10/util/irange.h> |
| |
| namespace caffe2 { |
| |
| using transform::Graph; |
| using transform::Node; |
| |
| // Checks if the node at model_idx and the node at candidate_idx are |
| // "common subexpressions". That is, do they have the same function, and |
| // take in the exact same input. If so, then their function is duplicated. |
| bool are_nodes_common(const Graph& g, int model_idx, int candidate_idx) { |
| // We need the candidate operator to match this model_op. |
| const Node& model_node = g.node(model_idx); |
| const Node& candidate_node = g.node(candidate_idx); |
| |
| // Types need to match. |
| if (model_node.op.type() != candidate_node.op.type()) { |
| return false; |
| } |
| // Arguments need to match. |
| if (!MatchArguments(model_node.op, candidate_node.op)) { |
| return false; |
| } |
| // Inputs need to match. |
| if (model_node.op.input_size() != candidate_node.op.input_size()) { |
| return false; |
| } |
| // If any input_blob name is different, this is not okay. |
| for (int i = 0; i < model_node.op.input_size(); i++) { |
| if (candidate_node.op.input(i) != model_node.op.input(i)) { |
| return false; |
| } |
| } |
| // Now, we also need to check that each blob comes from the same parent, or |
| // if they are external (isn't in parents). This is equivalent to a |
| // map equality (since parent edges can only contain up to one blob). |
| if (model_node.parents.size() != candidate_node.parents.size() || |
| !std::equal( |
| model_node.parents.begin(), |
| model_node.parents.end(), |
| candidate_node.parents.begin())) { |
| return false; |
| } |
| |
| // Output size have to match too. |
| if (model_node.op.output_size() != candidate_node.op.output_size()) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool CommonSubexpressionEliminationTransform::PatternRule( |
| const Graph& g, |
| const std::vector<int>& subgraph, |
| int idx) { |
| if (subgraph.size() == 0) { |
| if (IsAllowed(g.node(idx).op.type())) |
| return true; |
| return false; |
| } |
| return are_nodes_common(g, subgraph.at(0), idx); |
| } |
| |
| // As long as we have matched more than 2 ops, it is worth eliminating. |
| bool CommonSubexpressionEliminationTransform::ValidatorRule( |
| const Graph& /*g*/, |
| const std::vector<int>& subgraph) { |
| if (subgraph.size() >= 2) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool CommonSubexpressionEliminationTransform::ReplaceRule( |
| const std::vector<int>& subgraph, |
| Graph* g_ptr) { |
| CHECK(g_ptr); |
| auto& g = *g_ptr; |
| |
| // We're gonna make a new node, with the same input as all of the ones in |
| // subgraph, but with their combined children. |
| int new_idx = g.size(); |
| OperatorDef new_op = g.node(subgraph[0]).op; |
| // We will need to rename the output blobs. |
| new_op.clear_output(); |
| for (const auto& blob : g.node(subgraph[0]).op.output()) { |
| new_op.add_output("transform/" + blob); |
| } |
| |
| // Need to set up the parents. |
| const auto& new_op_parents = g.node(subgraph[0]).parents; |
| |
| for (auto& parent : new_op_parents) { |
| int parent_idx = parent.first; |
| |
| // Make the parents acknowledge us as its new child. |
| g.node(parent_idx).children[new_idx] = new_op_parents.at(parent_idx); |
| |
| // Make the parents disown all our outdated siblings. |
| for (const auto i : c10::irange(subgraph.size())) { |
| g.node(parent_idx).children.erase(subgraph[i]); |
| } |
| } |
| |
| // Add the node now. |
| g.push_node( |
| Node(new_op, true, new_op_parents, std::map<int, std::vector<string>>())); |
| |
| // Now, we need to populate the child edges. |
| for (const int x : subgraph) { |
| // Figure out what the subgraph's node's blobs correspond to in new_op |
| // This is easy, since their indices match. |
| std::map<string, string> output_renamings; |
| for (int i = 0; i < new_op.output_size(); i++) { |
| output_renamings[g.node(x).op.output(i)] = g.node(new_idx).op.output(i); |
| } |
| |
| // Now, time to add the old node's children to new_op |
| for (auto& child : g.node(x).children) { |
| int child_idx = child.first; |
| std::vector<string> blobs = child.second; |
| |
| // rename the old blobs, and use them for our new edge. |
| for (string& blob : blobs) { |
| blob = output_renamings.at(blob); |
| } |
| |
| // create this new edge |
| g.node(new_idx).children[child_idx] = blobs; |
| g.node(child_idx).parents[new_idx] = blobs; |
| |
| // delete the old edge |
| g.node(child_idx).parents.erase(x); |
| |
| // need to rename the inputs of the children too. |
| for (int i = 0; i < g.node(child_idx).op.input_size(); i++) { |
| string blob = g.node(child_idx).op.input(i); |
| if (output_renamings.count(blob) > 0) { |
| g.node(child_idx).op.set_input(i, output_renamings.at(blob)); |
| } |
| } |
| } |
| } |
| |
| g.DeactivateSubgraph(subgraph); |
| |
| return true; |
| } |
| |
| REGISTER_TRANSFORM( |
| CommonSubexpressionElimination, |
| CommonSubexpressionEliminationTransform); |
| |
| } // namespace caffe2 |