• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include "tensorflow/lite/graph_info.h"
16 
17 #include <algorithm>
18 #include <vector>
19 
20 #include "tensorflow/lite/c/common.h"
21 #include "tensorflow/lite/context_util.h"
22 
23 namespace tflite {
24 namespace {
25 
26 // Helper class that actually performs partitioning by node sub set.
27 // Outputs to a provided `NodeSubset` structure.
28 //
29 // Example usage:
30 // PartitionGraphIntoIndependentNodeSubsetsImpl partitioner(
31 //     info, nodes_to_part, node_subsets);
32 // partitioner.Partition();
33 //
34 // NOTE: Changing the partitioning logic would require a change to
35 // FP16GraphPartitionHelper.
36 // LINT.IfChange
37 class PartitionGraphIntoIndependentNodeSubsetsImpl {
38  public:
PartitionGraphIntoIndependentNodeSubsetsImpl(const GraphInfo * info,const TfLiteIntArray * nodes_to_partition,std::vector<NodeSubset> * node_subsets)39   PartitionGraphIntoIndependentNodeSubsetsImpl(
40       const GraphInfo* info, const TfLiteIntArray* nodes_to_partition,
41       std::vector<NodeSubset>* node_subsets)
42       : info_(info),
43         node_subsets_(node_subsets),
44         node_type_(info_->num_total_nodes(), NodeSubset::kTfNonPartition) {
45     // Populate the node_type_ map.
46     for (auto node_index : TfLiteIntArrayView(nodes_to_partition)) {
47       node_type_[node_index] = NodeSubset::kTfPartition;
48     }
49   }
50 
51   // Actually partition the graph.
Partition()52   void Partition() {
53     // Initialize here to make Partition() re-entrant.
54     node_subsets_->clear();
55     tensor_epochs_.clear();
56     tensor_epochs_.resize(info_->num_tensors(), kEpochAlwaysReady);
57     node_epochs_.clear();
58     node_epochs_.resize(info_->num_execution_nodes(), kEpochNotReady);
59     // Set computed tensors to be kEpochNotReady (initializer set everything to
60     // AlwaysReady).
61     for (int node_index = 0; node_index < info_->num_execution_nodes();
62          node_index++) {
63       const TfLiteNode& node = info_->node(node_index);
64       for (int output_tensor_index : TfLiteIntArrayView(node.outputs)) {
65         tensor_epochs_[output_tensor_index] = kEpochNotReady;
66       }
67     }
68 
69     // Do a graph traversal where each iteration in the loop is an epoch
70     // that corresponds to a node sub set that only contains nodes that are of
71     // the same node_type_.
72     while (true) {
73       BuildNodeSubset();
74       if (node_subsets_->back().nodes.empty()) {
75         node_subsets_->pop_back();
76         break;
77       }
78     }
79 
80     // Mark model outputs as node sub set outputs. All the rest have already
81     // been identified.
82     for (int output_index : info_->outputs()) {
83       int output_epoch = tensor_epochs_[output_index];
84       if (output_epoch == kEpochAlwaysReady) {
85         // This happens when an input of subgraph is also an output of subgraph.
86         continue;
87       }
88       NodeSubset& output_subset = (*node_subsets_)[output_epoch];
89       output_subset.output_tensors.push_back(output_index);
90     }
91     // Make sure every node sub set's inputs and outputs are unique. Since the
92     // list of inputs and outputs is generated in a way that produces
93     // duplicates.
94     for (NodeSubset& node_subset : *node_subsets_) {
95       // Sort and uniquefy using standard library algorithms.
96       auto uniquefy = [](std::vector<int>* items) {
97         std::sort(items->begin(), items->end());
98         auto last = std::unique(items->begin(), items->end());
99         items->erase(last, items->end());
100       };
101       uniquefy(&node_subset.input_tensors);
102       uniquefy(&node_subset.output_tensors);
103     }
104   }
105 
106  private:
107   // Special integer values needed for tensor_epochs_ and node_epochs_.
108   enum {
109     // The node or tensor is not ready to be assigned an epoch. e.g. a node's
110     // inputs have not all been assigned epochs.
111     kEpochNotReady = -1,
112     // Used for tensor_epochs_. This means that the tensor is always ready.
113     // e.g. an input to the whole model or a constant that has no dependencies.
114     kEpochAlwaysReady = -2
115   };
116 
117   // Updates the node at `node_index` in the execution plan and returns true if
118   // it is assigned to an epoch. False is returned if the node is already set to
119   // an epoch, its inputs are not all assigned to epochs, or if it cannot be
120   // assigned to the current epoch since the epoch's node_type doesn't match.
UpdateNode(int node_index)121   bool UpdateNode(int node_index) {
122     const TfLiteNode& node = info_->node(node_index);
123     NodeSubset& current_subset = node_subsets_->back();
124     int current_epoch = node_subsets_->size() - 1;
125     // Check if node is already done.
126     if (node_epochs_[node_index] != kEpochNotReady) {
127       return false;
128     }
129     // See if all dependencies of this node are already assigned to a
130     // node sub set.
131     for (int input_tensor_index : TfLiteIntArrayView(node.inputs)) {
132       if (input_tensor_index != kTfLiteOptionalTensor &&
133           tensor_epochs_[input_tensor_index] == kEpochNotReady) {
134         return false;
135       }
136     }
137 
138     int original_node_idx = info_->node_index(node_index);
139     // When we are starting a new epoch, the first ready node defines
140     // the type of that epoch.
141     if (current_subset.type == NodeSubset::kTfUnexplored) {
142       current_subset.type = node_type_[original_node_idx];
143     }
144     // The node gets assigned to this epoch if it is the same type as
145     // the epoch's assigned type. Note, if this is the current ready
146     // node encountered during this epoch, this condition will be
147     // automatically true.
148     if (current_subset.type == node_type_[original_node_idx]) {
149       node_epochs_[node_index] = current_epoch;
150       current_subset.nodes.push_back(original_node_idx);
151       // All outputs of this node now are assigned to this epoch as
152       // well.
153       for (int output_tensor_index : TfLiteIntArrayView(node.outputs)) {
154         tensor_epochs_[output_tensor_index] = current_epoch;
155       }
156       // Look at our inputs one more time to update that tensor's
157       // epochs' outputs
158       for (int input_tensor_index : TfLiteIntArrayView(node.inputs)) {
159         if (input_tensor_index == kTfLiteOptionalTensor) {
160           continue;
161         }
162         int input_epoch = tensor_epochs_[input_tensor_index];
163         int node_epoch = current_epoch;
164         if (input_epoch != node_epoch) {
165           current_subset.input_tensors.push_back(input_tensor_index);
166           // Set inputs to be outputs of the node sub set where they reside.
167           // the if condition makes sure inputs to the whole computation
168           // are not included (i.e. those initialized to -2 above).
169           if (input_epoch >= 0) {
170             NodeSubset& input_subset = (*node_subsets_)[input_epoch];
171             input_subset.output_tensors.push_back(input_tensor_index);
172           }
173         }
174       }
175       return true;
176     } else {
177       return false;
178     }
179   }
180 
181   // Completely populates the current node_subset by doing graph traversal
BuildNodeSubset()182   void BuildNodeSubset() {
183     node_subsets_->emplace_back(NodeSubset());
184     // loop until no more nodes can be updated.
185     while (true) {
186       bool did_something = false;
187       for (int node_index = 0; node_index < info_->num_execution_nodes();
188            node_index++) {
189         if (UpdateNode(node_index)) {
190           did_something = true;
191         }
192       }
193       if (!did_something) return;
194     }
195   }
196 
197   // Temporary data needed for partitioning.
198   const GraphInfo* info_;
199   // List of node_subsets to populate
200   std::vector<NodeSubset>* node_subsets_;
201   // NOTE: This vector contains a place-holder for *all* nodes in the graph, not
202   // just ones in the execution plan. This is because nodes_to_partition is
203   // passed in as a list of original node indices & not execution plan indices.
204   std::vector<NodeSubset::Type> node_type_;
205   // Maps from tensor index to the epoch in which it is assigned. Also special
206   // negative values of kEpochNotReady if not assigned, kEpochAlwaysReady if it
207   // is an input to the whole model or a constant that has no dependencies.
208   std::vector<int> tensor_epochs_;
209   // Maps from tensor index to the epoch in which it is assigned. Also special
210   // negative values of kEpochNotReady if not assigned.
211   std::vector<int> node_epochs_;
212 };
213 // LINT.ThenChange(//tensorflow/lite/delegates/utils.h)
214 
215 }  // namespace
216 
PartitionGraphIntoIndependentNodeSubsets(const GraphInfo * info,const TfLiteIntArray * nodes_to_partition,std::vector<NodeSubset> * node_subsets)217 TfLiteStatus PartitionGraphIntoIndependentNodeSubsets(
218     const GraphInfo* info, const TfLiteIntArray* nodes_to_partition,
219     std::vector<NodeSubset>* node_subsets) {
220   PartitionGraphIntoIndependentNodeSubsetsImpl(info, nodes_to_partition,
221                                                node_subsets)
222       .Partition();
223   return kTfLiteOk;
224 }
225 
226 }  // namespace tflite
227