• 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 <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