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