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