1 /* Copyright 2017 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
16 #include "tensorflow/lite/graph_info.h"
17
18 #include <stddef.h>
19
20 #include <vector>
21
22 #include <gtest/gtest.h>
23 #include "tensorflow/lite/c/common.h"
24 #include "tensorflow/lite/testing/util.h"
25
26 namespace tflite {
27 namespace {
28
29 // Makes a TfLiteIntArray* from std::vector, must free with TfLiteIntFree().
ConvertVector(const std::vector<int> & x)30 TfLiteIntArray* ConvertVector(const std::vector<int>& x) {
31 TfLiteIntArray* lite = TfLiteIntArrayCreate(x.size());
32 for (size_t i = 0; i < x.size(); i++) lite->data[i] = x[i];
33 return lite;
34 }
35
36 // A very simple test graph that supports setting in/out tensors on nodes.
37 class SimpleTestGraph : public GraphInfo {
38 public:
SimpleTestGraph(int node_index_offset=0)39 explicit SimpleTestGraph(int node_index_offset = 0)
40 : node_index_offset_(node_index_offset) {
41 // 'node_index_offset' number of nodes are not present in the execution
42 // plan. (and hence not considered for partitioning)
43 for (int i = 0; i < node_index_offset; ++i) AddNode({}, {});
44 }
45
~SimpleTestGraph()46 ~SimpleTestGraph() override {
47 for (auto& node : nodes_) {
48 TfLiteIntArrayFree(node.inputs);
49 TfLiteIntArrayFree(node.outputs);
50 }
51 }
52
num_total_nodes() const53 size_t num_total_nodes() const override { return nodes_.size(); }
num_execution_nodes() const54 size_t num_execution_nodes() const override {
55 return nodes_.size() - node_index_offset_;
56 }
node(size_t index) const57 const TfLiteNode& node(size_t index) const override {
58 return nodes_[index + node_index_offset_];
59 }
node_index(size_t index) const60 size_t node_index(size_t index) const override {
61 return index + node_index_offset_;
62 }
num_tensors() const63 size_t num_tensors() const override { return tensors_.size(); }
tensor(size_t index)64 TfLiteTensor* tensor(size_t index) override { return &tensors_[index]; }
inputs() const65 const std::vector<int>& inputs() const override { return inputs_; }
outputs() const66 const std::vector<int>& outputs() const override { return outputs_; }
variables() const67 const std::vector<int>& variables() const override { return variables_; }
68
AddNode(const std::vector<int> & inputs,const std::vector<int> & outputs,bool might_have_side_effect=false)69 void AddNode(const std::vector<int>& inputs, const std::vector<int>& outputs,
70 bool might_have_side_effect = false) {
71 nodes_.push_back(TfLiteNode());
72 TfLiteNode& node = nodes_.back();
73 node.inputs = ConvertVector(inputs);
74 node.outputs = ConvertVector(outputs);
75 node.might_have_side_effect = might_have_side_effect;
76 }
77
AddTensors(int count)78 void AddTensors(int count) { tensors_.resize(count + tensors_.size()); }
79
SetInputsAndOutputs(const std::vector<int> & inputs,const std::vector<int> & outputs)80 void SetInputsAndOutputs(const std::vector<int>& inputs,
81 const std::vector<int>& outputs) {
82 inputs_ = inputs;
83 outputs_ = outputs;
84 }
85
86 private:
87 size_t node_index_offset_;
88 std::vector<TfLiteNode> nodes_;
89 std::vector<TfLiteTensor> tensors_;
90 std::vector<int> inputs_;
91 std::vector<int> outputs_;
92 std::vector<int> variables_;
93 };
94
95 // Partition a graph to generate a list of subgraphs. This wraps the API call
96 // we are testing and handles memory management and conversion to
97 // TfLiteIntArray. Populates `subgraphs` with resulting generated subgraphs.
PartitionGraph(const SimpleTestGraph & graph,const std::vector<int> & nodes_to_partition,std::vector<NodeSubset> * subgraphs)98 void PartitionGraph(const SimpleTestGraph& graph,
99 const std::vector<int>& nodes_to_partition,
100 std::vector<NodeSubset>* subgraphs) {
101 TfLiteIntArray* nodes_to_partition_int_array =
102 ConvertVector(nodes_to_partition);
103 PartitionGraphIntoIndependentNodeSubsets(&graph, nodes_to_partition_int_array,
104 subgraphs);
105 TfLiteIntArrayFree(nodes_to_partition_int_array);
106 }
107
108 // Check a generated list of subgraphs against the expected list of subgraphs.
CheckPartitionSubgraphs(const std::vector<NodeSubset> & generated_subgraphs,const std::vector<NodeSubset> & expected_subgraphs)109 void CheckPartitionSubgraphs(
110 const std::vector<NodeSubset>& generated_subgraphs,
111 const std::vector<NodeSubset>& expected_subgraphs) {
112 ASSERT_EQ(generated_subgraphs.size(), expected_subgraphs.size());
113 for (size_t subgraph_index = 0; subgraph_index < generated_subgraphs.size();
114 subgraph_index++) {
115 EXPECT_EQ(generated_subgraphs[subgraph_index].nodes,
116 expected_subgraphs[subgraph_index].nodes);
117 EXPECT_EQ(generated_subgraphs[subgraph_index].input_tensors,
118 expected_subgraphs[subgraph_index].input_tensors);
119 EXPECT_EQ(generated_subgraphs[subgraph_index].output_tensors,
120 expected_subgraphs[subgraph_index].output_tensors);
121 }
122 }
123
124 // Test an empty trivial graph with no partitions.
TEST(PartitionTest,Nodes0PartitionNodes0)125 TEST(PartitionTest, Nodes0PartitionNodes0) {
126 SimpleTestGraph graph;
127 std::vector<int> nodes_to_partition = {};
128 std::vector<NodeSubset> generated_subgraphs;
129 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
130 CheckPartitionSubgraphs(generated_subgraphs, {});
131 }
132
133 // Test a trivial graph with no node and only 1 tensor.
134 // The tensor is input & output of the graph at the same time.
135 // Note: This is a regression test to ensure the partitioning logic
136 // handles this case without crashing.
TEST(PartitionTest,Nodes0PartitionNodes0Tensors1)137 TEST(PartitionTest, Nodes0PartitionNodes0Tensors1) {
138 SimpleTestGraph graph;
139 graph.AddTensors(1);
140 graph.SetInputsAndOutputs({0}, {0});
141 std::vector<int> nodes_to_partition = {};
142 std::vector<NodeSubset> generated_subgraphs;
143 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
144 CheckPartitionSubgraphs(generated_subgraphs, {});
145 }
146
147 // Test a 1 node graph with no partitions.
148 // Input: tensor(0) -> node(0) -> tensor(1), nodes_to_partition=[]
149 // Output: [kTfNoPartition, tensor(0) -> node(0) -> tensor(1)]
TEST(PartitionTest,Nodes1PartitionNodes0)150 TEST(PartitionTest, Nodes1PartitionNodes0) {
151 SimpleTestGraph graph;
152 graph.AddTensors(2);
153 graph.AddNode({0}, {1});
154 graph.SetInputsAndOutputs({0}, {1});
155 std::vector<int> nodes_to_partition = {};
156 std::vector<NodeSubset> generated_subgraphs;
157 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
158
159 NodeSubset expected_subgraph;
160 expected_subgraph.type = NodeSubset::kTfNonPartition;
161 expected_subgraph.nodes = {0};
162 expected_subgraph.input_tensors = {0};
163 expected_subgraph.output_tensors = {1};
164 CheckPartitionSubgraphs(generated_subgraphs, {expected_subgraph});
165 }
166
TEST(PartitionTest,Nodes1PartitionNodes0_WithOffset)167 TEST(PartitionTest, Nodes1PartitionNodes0_WithOffset) {
168 constexpr int node_index_offset = 17;
169 SimpleTestGraph graph(node_index_offset);
170 graph.AddTensors(2);
171 graph.AddNode({0}, {1});
172 graph.SetInputsAndOutputs({0}, {1});
173 std::vector<int> nodes_to_partition = {};
174 std::vector<NodeSubset> generated_subgraphs;
175 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
176
177 NodeSubset expected_subgraph;
178 expected_subgraph.type = NodeSubset::kTfNonPartition;
179 expected_subgraph.nodes = {node_index_offset};
180 expected_subgraph.input_tensors = {0};
181 expected_subgraph.output_tensors = {1};
182 CheckPartitionSubgraphs(generated_subgraphs, {expected_subgraph});
183 }
184
185 // Test a 1 node graph with no inputs that is fully partitioned.
186 // Input: node(0) -> tensor(1), nodes_to_partition=[node0]
187 // Output: [kTfPartition, node(0) -> tensor(1)]
TEST(PartitionTest,Nodes1PartitionNodes0Inputs0)188 TEST(PartitionTest, Nodes1PartitionNodes0Inputs0) {
189 SimpleTestGraph graph;
190 graph.AddTensors(1);
191 graph.AddNode({}, {0});
192 graph.SetInputsAndOutputs({}, {0});
193 std::vector<NodeSubset> generated_subgraphs;
194 std::vector<int> nodes_to_partition = {0};
195 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
196
197 NodeSubset expected_subgraph;
198 expected_subgraph.type = NodeSubset::kTfPartition;
199 expected_subgraph.nodes = {0};
200 expected_subgraph.input_tensors = {};
201 expected_subgraph.output_tensors = {0};
202 CheckPartitionSubgraphs(generated_subgraphs, {expected_subgraph});
203 }
204
205 // Test a 1 node graph that is partitioned completely.
206 // Input: tensor(0) -> node(0) -> tensor(1), nodes_to_partition=[node0]
207 // Output: [kTfPartition, tensor(0) -> node(0) -> tensor(1)]
TEST(PartitionTest,Nodes1PartitionNodes1)208 TEST(PartitionTest, Nodes1PartitionNodes1) {
209 SimpleTestGraph graph;
210 graph.AddTensors(2);
211 graph.AddNode({0}, {1});
212 graph.SetInputsAndOutputs({0}, {1});
213 std::vector<int> nodes_to_partition = {0};
214 std::vector<NodeSubset> generated_subgraphs;
215 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
216
217 NodeSubset expected_subgraph;
218 expected_subgraph.type = NodeSubset::kTfPartition;
219 expected_subgraph.nodes = {0};
220 expected_subgraph.input_tensors = {0};
221 expected_subgraph.output_tensors = {1};
222 CheckPartitionSubgraphs(generated_subgraphs, {expected_subgraph});
223 }
224
225 // Test a 2 node graph where 1 node is partitioned and the other is not.
226 // Input: tensor(0) -> node(0) -> tensor(1) -> node(1) -> tensor(2),
227 // nodes_to_partition = [1]
228 // Output: [kTfNonPartition, tensor(0) -> node(0) -> tensor(1),
229 // kTfPartition, tensor(1) -> node(1), tensor(2)]
TEST(PartitionTest,Nodes2PartitionNodes1)230 TEST(PartitionTest, Nodes2PartitionNodes1) {
231 SimpleTestGraph graph;
232 graph.AddTensors(3);
233 graph.AddNode({0}, {1});
234 graph.AddNode({1}, {2});
235 graph.SetInputsAndOutputs({0}, {2});
236 std::vector<int> nodes_to_partition = {1};
237 std::vector<NodeSubset> generated_subgraphs;
238 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
239
240 NodeSubset expected_subgraph0;
241 expected_subgraph0.type = NodeSubset::kTfPartition;
242 expected_subgraph0.nodes = {0};
243 expected_subgraph0.input_tensors = {0};
244 expected_subgraph0.output_tensors = {1};
245 NodeSubset expected_subgraph1;
246 expected_subgraph1.type = NodeSubset::kTfPartition;
247 expected_subgraph1.nodes = {1};
248 expected_subgraph1.input_tensors = {1};
249 expected_subgraph1.output_tensors = {2};
250 CheckPartitionSubgraphs(generated_subgraphs,
251 {expected_subgraph0, expected_subgraph1});
252 }
253
254 // Same as above, but with node offset to ensure correct handling of original vs
255 // execution plan indices.
TEST(PartitionTest,Nodes2PartitionNodes1_WithOffset)256 TEST(PartitionTest, Nodes2PartitionNodes1_WithOffset) {
257 constexpr int node_index_offset = 17;
258 SimpleTestGraph graph(node_index_offset);
259 graph.AddTensors(3);
260 graph.AddNode({0}, {1});
261 graph.AddNode({1}, {2});
262 graph.SetInputsAndOutputs({0}, {2});
263 std::vector<int> nodes_to_partition = {node_index_offset + 1};
264 std::vector<NodeSubset> generated_subgraphs;
265 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
266
267 NodeSubset expected_subgraph0;
268 expected_subgraph0.type = NodeSubset::kTfPartition;
269 expected_subgraph0.nodes = {node_index_offset + 0};
270 expected_subgraph0.input_tensors = {0};
271 expected_subgraph0.output_tensors = {1};
272 NodeSubset expected_subgraph1;
273 expected_subgraph1.type = NodeSubset::kTfPartition;
274 expected_subgraph1.nodes = {node_index_offset + 1};
275 expected_subgraph1.input_tensors = {1};
276 expected_subgraph1.output_tensors = {2};
277 CheckPartitionSubgraphs(generated_subgraphs,
278 {expected_subgraph0, expected_subgraph1});
279 }
280
281 // Test a 2 node graph where both nodes are fully partitioned.
282 // Input: tensor(0) -> node(0) -> tensor(1) -> node(1) -> tensor(2),
283 // nodes_to_partition = [0, 1]
284 // Output: [kTfPartition, tensor(0) -> node(0) -> node(1) -> tensor(1)]
TEST(PartitionTest,Nodes2PartitionNodes2)285 TEST(PartitionTest, Nodes2PartitionNodes2) {
286 SimpleTestGraph graph;
287 graph.AddTensors(3);
288 graph.AddNode({0}, {1});
289 graph.AddNode({1}, {2});
290 graph.SetInputsAndOutputs({0}, {2});
291 std::vector<int> nodes_to_partition = {0, 1};
292 std::vector<NodeSubset> generated_subgraphs;
293 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
294
295 NodeSubset expected_subgraph0;
296 expected_subgraph0.type = NodeSubset::kTfPartition;
297 expected_subgraph0.nodes = {0, 1};
298 expected_subgraph0.input_tensors = {0};
299 expected_subgraph0.output_tensors = {2};
300 CheckPartitionSubgraphs(generated_subgraphs, {expected_subgraph0});
301 }
302
303 // Test a three node model where we want to partition node 0 and node
304 // 2, but node 0 and node 2 cannot be in the same subgraph since node 2
305 // depends on node 1 which depends on node 0. Thus, we need to produce three
306 // subgraphs.
307 //
308 // Input: tensor(0) -> node(0) -> tensor(1)
309 // tensor(1) -> node(1) -> tensor(2)
310 // [tensor(2), tensor(1)] -> node(2) -> tensor(3)
311 // nodes_to_partition = [0, 2]
312 // Output: [[kTfPartition, tensor(0) -> node(0) -> tensor(1),
313 // [kTfNonPartition, tensor(1) -> node(1) -> tensor(2)],
314 // [kTfPartition, [tensor(2), tensor(1)] -> node(2) -> node(3)]
TEST(PartitionTest,Nodes3PartitionNodes2)315 TEST(PartitionTest, Nodes3PartitionNodes2) {
316 SimpleTestGraph graph;
317 graph.AddTensors(4);
318 graph.AddNode({0}, {1});
319 graph.AddNode({1}, {2});
320 graph.AddNode({1, 2}, {3});
321 graph.SetInputsAndOutputs({0}, {3});
322 std::vector<int> nodes_to_partition = {0, 2};
323 std::vector<NodeSubset> generated_subgraphs;
324 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
325
326 NodeSubset expected_subgraph0;
327 expected_subgraph0.type = NodeSubset::kTfPartition;
328 expected_subgraph0.nodes = {0};
329 expected_subgraph0.input_tensors = {0};
330 expected_subgraph0.output_tensors = {1};
331 NodeSubset expected_subgraph1;
332 expected_subgraph1.type = NodeSubset::kTfNonPartition;
333 expected_subgraph1.nodes = {1};
334 expected_subgraph1.input_tensors = {1};
335 expected_subgraph1.output_tensors = {2};
336 NodeSubset expected_subgraph2;
337 expected_subgraph2.type = NodeSubset::kTfPartition;
338 expected_subgraph2.nodes = {2};
339 expected_subgraph2.input_tensors = {1, 2};
340 expected_subgraph2.output_tensors = {3};
341 CheckPartitionSubgraphs(
342 generated_subgraphs,
343 {expected_subgraph0, expected_subgraph1, expected_subgraph2});
344 }
345
346 // Test correct partition for graph with control dependency.
347 // Graph for test is like
348 // varhandleOp -> ReadVariableOp -> Add -> AssignVariableOp
349 // |_________________________^ ^^
350 // |------------------------->ReadVariableOp -> (Output)
351 // ^^ is control dependency, in this case we don't want to invoke the
352 // last ReadVariableOp before AssignVariableOp finishes executing.
353 // '>' and '^' represents data dependency.
TEST(PartitionTest,Nodes4PartitionNodes3_WithControlDependency)354 TEST(PartitionTest, Nodes4PartitionNodes3_WithControlDependency) {
355 SimpleTestGraph graph;
356 // Construct graph.
357 {
358 graph.AddTensors(5);
359 graph.AddNode({0}, {1}, true);
360 graph.AddNode({1}, {2}, true);
361 graph.AddNode({2}, {3}, false);
362 graph.AddNode({1, 3}, {}, true);
363 graph.AddNode({1}, {4}, true);
364 }
365 graph.SetInputsAndOutputs({0}, {4});
366 std::vector<int> nodes_to_partition = {0, 1, 3, 4};
367 std::vector<NodeSubset> generated_subgraphs;
368 PartitionGraph(graph, nodes_to_partition, &generated_subgraphs);
369
370 NodeSubset expected_subgraph0;
371 expected_subgraph0.type = NodeSubset::kTfPartition;
372 expected_subgraph0.nodes = {0, 1};
373 expected_subgraph0.input_tensors = {0};
374 expected_subgraph0.output_tensors = {1, 2};
375 NodeSubset expected_subgraph1;
376 expected_subgraph1.type = NodeSubset::kTfNonPartition;
377 expected_subgraph1.nodes = {2};
378 expected_subgraph1.input_tensors = {2};
379 expected_subgraph1.output_tensors = {3};
380 NodeSubset expected_subgraph2;
381 expected_subgraph2.type = NodeSubset::kTfPartition;
382 expected_subgraph2.nodes = {3, 4};
383 expected_subgraph2.input_tensors = {1, 3};
384 expected_subgraph2.output_tensors = {4};
385 CheckPartitionSubgraphs(
386 generated_subgraphs,
387 {expected_subgraph0, expected_subgraph1, expected_subgraph2});
388 }
389
390 } // namespace
391 } // namespace tflite
392