Home | History | Annotate | Download | only in lite
      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 #include "tensorflow/contrib/lite/arena_planner.h"
     16 
     17 #include <cstdarg>
     18 
     19 #include <gmock/gmock.h>
     20 #include <gtest/gtest.h>
     21 #include "tensorflow/contrib/lite/testing/util.h"
     22 #include "tensorflow/core/platform/logging.h"
     23 
     24 namespace tflite {
     25 namespace {
     26 
     27 // A simple op to be used in tests, as syntactic sugar.
     28 class TestOp {
     29  public:
     30   TestOp(std::initializer_list<int> inputs, std::initializer_list<int> outputs,
     31          std::initializer_list<int> temporaries)
     32       : inputs_(inputs), outputs_(outputs), temporaries_(temporaries) {}
     33 
     34   const std::vector<int>& inputs() const { return inputs_; }
     35   const std::vector<int>& outputs() const { return outputs_; }
     36   const std::vector<int>& temporaries() const { return temporaries_; }
     37 
     38  private:
     39   std::vector<int> inputs_;
     40   std::vector<int> outputs_;
     41   std::vector<int> temporaries_;
     42 };
     43 
     44 // A test graph where inputs are processed by the given nodes to produce
     45 // outputs.
     46 class TestGraph {
     47  public:
     48   TestGraph(std::initializer_list<int> inputs,
     49             std::initializer_list<TestOp> nodes,
     50             std::initializer_list<int> outputs)
     51       : inputs_(inputs), outputs_(outputs) {
     52     int max_tensor_index = 0;
     53 
     54     for (int t : inputs) {
     55       max_tensor_index = std::max(max_tensor_index, t);
     56     }
     57     for (int t : outputs) {
     58       max_tensor_index = std::max(max_tensor_index, t);
     59     }
     60     for (const auto& node : nodes) {
     61       auto int_array = [](const std::vector<int>& x) {
     62         TfLiteIntArray* lite = TfLiteIntArrayCreate(x.size());
     63         for (size_t i = 0; i < x.size(); i++) lite->data[i] = x[i];
     64         return lite;
     65       };
     66 
     67       nodes_.push_back(TfLiteNode());
     68       nodes_.back().inputs = int_array(node.inputs());
     69       for (int t : node.inputs()) {
     70         max_tensor_index = std::max(max_tensor_index, t);
     71       }
     72       nodes_.back().outputs = int_array(node.outputs());
     73       for (int t : node.outputs()) {
     74         max_tensor_index = std::max(max_tensor_index, t);
     75       }
     76       nodes_.back().temporaries = int_array(node.temporaries());
     77       for (int t : node.temporaries()) {
     78         max_tensor_index = std::max(max_tensor_index, t);
     79       }
     80     }
     81 
     82     for (int i = 0; i <= max_tensor_index; ++i) {
     83       tensors_.push_back(TfLiteTensor());
     84       // Set some default values for allocation_type and bytes, which are the
     85       // only fields used by the arena planner.
     86       tensors_.back().allocation_type = kTfLiteArenaRw;
     87       tensors_.back().bytes = (i + 1) * 3;
     88     }
     89   }
     90 
     91   ~TestGraph() {
     92     for (auto node : nodes_) {
     93       TfLiteIntArrayFree(node.inputs);
     94       TfLiteIntArrayFree(node.outputs);
     95       TfLiteIntArrayFree(node.temporaries);
     96     }
     97   }
     98 
     99   const std::vector<TfLiteNode>& nodes() { return nodes_; }
    100   std::vector<TfLiteTensor>* tensors() { return &tensors_; }
    101   const std::vector<int>& inputs() { return inputs_; }
    102   const std::vector<int>& outputs() { return outputs_; }
    103 
    104  private:
    105   std::vector<TfLiteNode> nodes_;
    106   std::vector<TfLiteTensor> tensors_;
    107   std::vector<int> inputs_;
    108   std::vector<int> outputs_;
    109 };
    110 
    111 // The GraphInfo for a TestGraph.
    112 class TestGraphInfo : public GraphInfo {
    113  public:
    114   explicit TestGraphInfo(TestGraph* graph) : graph_(graph) {}
    115 
    116   size_t num_tensors() const override { return graph_->tensors()->size(); }
    117   TfLiteTensor* tensor(size_t index) override {
    118     return &graph_->tensors()->at(index);
    119   }
    120   size_t num_nodes() const override { return graph_->nodes().size(); }
    121   const TfLiteNode& node(size_t index) const override {
    122     return graph_->nodes()[index];
    123   }
    124   const std::vector<int>& inputs() const override { return graph_->inputs(); }
    125   const std::vector<int>& outputs() const override { return graph_->outputs(); }
    126 
    127  private:
    128   TestGraph* graph_;
    129 };
    130 
    131 void ReportError(TfLiteContext* context, const char* format, ...) {
    132   const size_t kBufferSize = 1024;
    133   char temp_buffer[kBufferSize];
    134 
    135   va_list args;
    136   va_start(args, format);
    137   vsnprintf(temp_buffer, kBufferSize, format, args);
    138   va_end(args);
    139 
    140   LOG(INFO) << temp_buffer;
    141 }
    142 
    143 class ArenaPlannerTest : public ::testing::Test {
    144  protected:
    145   void SetGraph(TestGraph* graph) {
    146     graph_ = graph;
    147     context_.ReportError = ReportError;
    148     planner_.reset(new ArenaPlanner(
    149         &context_, std::unique_ptr<GraphInfo>(new TestGraphInfo(graph))));
    150     CHECK(planner_->ResetAllocations() == kTfLiteOk);
    151     CHECK(planner_->PlanAllocations() == kTfLiteOk);
    152   }
    153 
    154   void Execute(int start, int end) {
    155     CHECK(planner_->ExecuteAllocations(start, end) == kTfLiteOk);
    156   }
    157 
    158   // Returns the actual offset of a given tensor, relative to the start of its
    159   // arena.
    160   int64_t GetOffset(int tensor_index) {
    161     const TfLiteTensor& tensor = (*graph_->tensors())[tensor_index];
    162     return reinterpret_cast<int64_t>(tensor.data.raw) -
    163            planner_->BasePointer(tensor.allocation_type);
    164   }
    165 
    166   // Returns the first aligned offset after a given tensor.
    167   int64_t GetOffsetAfter(int tensor_index) {
    168     const TfLiteTensor& tensor = (*graph_->tensors())[tensor_index];
    169     int64_t offset = GetOffset(tensor_index) + tensor.bytes;
    170     // We must make sure the offset is aligned to kDefaultArenaAlignment.
    171     if (offset % 4 != 0) {
    172       offset += 4 - offset % 4;
    173     }
    174     return offset;
    175   };
    176 
    177   TfLiteContext context_;
    178   TestGraph* graph_;
    179   std::unique_ptr<ArenaPlanner> planner_;
    180 };
    181 
    182 TEST_F(ArenaPlannerTest, EmptyGraph) {
    183   TestGraph graph({}, {}, {});
    184   SetGraph(&graph);
    185   Execute(0, 10);
    186 }
    187 
    188 TEST_F(ArenaPlannerTest, GraphWithNoOps) {
    189   TestGraph graph({0, 10}, {}, {5, 11});
    190   SetGraph(&graph);
    191   Execute(0, 10);
    192   EXPECT_EQ(GetOffset(0), 0);
    193   EXPECT_EQ(GetOffset(10), GetOffsetAfter(0));
    194   // The outputs are never allocated because they are not connected to any
    195   // inputs.
    196   EXPECT_TRUE((*graph.tensors())[5].data.raw == nullptr);
    197   EXPECT_TRUE((*graph.tensors())[11].data.raw == nullptr);
    198 }
    199 
    200 TEST_F(ArenaPlannerTest, GraphWithOneOp) {
    201   TestGraph graph({1}, {{{1}, {2}, {}}}, {2});
    202   SetGraph(&graph);
    203   Execute(0, 10);
    204   EXPECT_EQ(GetOffset(1), 0);
    205   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    206 }
    207 
    208 TEST_F(ArenaPlannerTest, ZeroSizedTensors) {
    209   TestGraph graph({1}, {{{1}, {2}, {}}}, {2});
    210   (*graph.tensors())[1].bytes = 0;
    211   SetGraph(&graph);
    212   // TODO(ahentz): this is currently broken because the arena finds two
    213   // allocations with the same offset and returns an error.
    214   ASSERT_FALSE(planner_->ExecuteAllocations(0, 10) == kTfLiteOk);
    215   // EXPECT_EQ(GetOffset(1), 0);
    216   // EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    217 }
    218 
    219 TEST_F(ArenaPlannerTest, SimpleGraph) {
    220   TestGraph graph({0, 1},
    221                   {
    222                       /* in, out, tmp */
    223                       {{0, 1}, {2}, {}},     // First op
    224                       {{2, 0}, {4, 5}, {}},  // Second op
    225                       {{4, 5}, {3}, {}}      // Third op
    226                   },
    227                   {3});
    228   SetGraph(&graph);
    229   Execute(0, 10);
    230 
    231   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +4 +5 -2 -0 +3 -4 -5
    232   EXPECT_EQ(GetOffset(0), 0);
    233   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    234   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    235   EXPECT_EQ(GetOffset(4), GetOffsetAfter(2));
    236   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    237   EXPECT_EQ(GetOffset(3), 0);
    238 }
    239 
    240 TEST_F(ArenaPlannerTest, SimpleGraphWithTemporary) {
    241   TestGraph graph({0, 1},
    242                   {
    243                       /* in, out, tmp */
    244                       {{0, 1}, {2}, {}},   // First op
    245                       {{2, 0}, {4}, {5}},  // Second op, with temporary
    246                       {{4}, {3}, {}}       // Third op
    247                   },
    248                   {3});
    249   SetGraph(&graph);
    250   Execute(0, 10);
    251 
    252   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +5 +4 -2 -0 -5 +3 -4
    253   EXPECT_EQ(GetOffset(0), 0);
    254   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    255   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    256   EXPECT_EQ(GetOffset(5), GetOffsetAfter(2));
    257   EXPECT_EQ(GetOffset(4), GetOffsetAfter(5));
    258   EXPECT_EQ(GetOffset(3), 0);
    259 }
    260 
    261 TEST_F(ArenaPlannerTest, SimpleGraphWithOptionals) {
    262   TestGraph graph({0, -1, 1},
    263                   {
    264                       /* in, out, tmp */
    265                       {{0, 1}, {2}, {}},     // First op
    266                       {{2, 0}, {4, 5}, {}},  // Second op
    267                       {{4, -1, 5}, {3}, {}}  // Third op, with optional
    268                   },
    269                   {3});
    270   SetGraph(&graph);
    271   Execute(0, 10);
    272 
    273   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +4 +5 -2 -0 +3 -4 -5
    274   EXPECT_EQ(GetOffset(0), 0);
    275   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    276   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    277   EXPECT_EQ(GetOffset(4), GetOffsetAfter(2));
    278   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    279   EXPECT_EQ(GetOffset(3), 0);
    280 }
    281 
    282 TEST_F(ArenaPlannerTest, SimpleGraphWithLargeTensor) {
    283   TestGraph graph({0, -1, 1},
    284                   {
    285                       /* in, out, tmp */
    286                       {{0, 1}, {2}, {}},   // First op
    287                       {{2, 0}, {4}, {5}},  // Second op, with temporary
    288                       {{4, -1}, {3}, {}}   // Third op, with optional
    289                   },
    290                   {3});
    291 
    292   // Make #1 very large so its vacancy can be filled with #5 and #4.
    293   (*graph.tensors())[1].bytes = 40;
    294 
    295   SetGraph(&graph);
    296   Execute(0, 10);
    297 
    298   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +5 +4 -2 -0 -5 +3 -4
    299   EXPECT_EQ(GetOffset(0), 0);
    300   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    301   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    302   EXPECT_EQ(GetOffset(5), GetOffsetAfter(0));
    303   EXPECT_EQ(GetOffset(4), GetOffsetAfter(5));
    304   EXPECT_EQ(GetOffset(3), 0);
    305 }
    306 
    307 TEST_F(ArenaPlannerTest, SimpleGraphWithPersistentTensor) {
    308   TestGraph graph({0, -1, 1},
    309                   {
    310                       /* in, out, tmp */
    311                       {{0, 1}, {2}, {}},   // First op
    312                       {{2, 0}, {4}, {5}},  // Second op, with temporary
    313                       {{4, -1}, {3}, {}}   // Third op, with optional
    314                   },
    315                   {3});
    316 
    317   // Make #1 persistent so it goes into its own arena.
    318   (*graph.tensors())[1].allocation_type = kTfLiteArenaRwPersistent;
    319 
    320   SetGraph(&graph);
    321   Execute(0, 10);
    322 
    323   // Make sure #0 and #1 were given different memory locations (because they
    324   // will both have offset=0, in different arenas.)
    325   EXPECT_NE((*graph.tensors())[0].data.raw, (*graph.tensors())[1].data.raw);
    326 
    327   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +5 +4 -2 -0 -5 +3 -4
    328   EXPECT_EQ(GetOffset(0), 0);
    329   EXPECT_EQ(GetOffset(1), 0);
    330   EXPECT_EQ(GetOffset(2), GetOffsetAfter(0));
    331   EXPECT_EQ(GetOffset(5), GetOffsetAfter(2));
    332   EXPECT_EQ(GetOffset(4), GetOffsetAfter(5));
    333   EXPECT_EQ(GetOffset(3), 0);
    334 }
    335 
    336 TEST_F(ArenaPlannerTest, SimpleGraphWithDynamicTensor) {
    337   TestGraph graph({0, -1, 1},
    338                   {
    339                       /* in, out, tmp */
    340                       {{0, 1}, {2}, {}},   // First op
    341                       {{2, 0}, {4}, {5}},  // Second op, with temporary
    342                       {{4, -1}, {3}, {}}   // Third op, with optional
    343                   },
    344                   {3});
    345 
    346   // Make #1 dynaic so it does not get allocated.
    347   (*graph.tensors())[1].allocation_type = kTfLiteDynamic;
    348 
    349   SetGraph(&graph);
    350   Execute(0, 10);
    351 
    352   EXPECT_EQ((*graph.tensors())[1].data.raw, nullptr);
    353 
    354   // Alloc(+) and dealloc(-) order: +0 +1 +2 -1 +5 +4 -2 -0 -5 +3 -4
    355   EXPECT_EQ(GetOffset(0), 0);
    356   EXPECT_EQ(GetOffset(2), GetOffsetAfter(0));
    357   EXPECT_EQ(GetOffset(5), GetOffsetAfter(2));
    358   EXPECT_EQ(GetOffset(4), GetOffsetAfter(5));
    359   EXPECT_EQ(GetOffset(3), 0);
    360 }
    361 
    362 TEST_F(ArenaPlannerTest, LargerGraphAndStepwiseAllocation) {
    363   TestGraph graph({0, 1},
    364                   {
    365                       /* in, out, tmp */
    366                       {{0, 1}, {2, 3}, {}},
    367                       {{2, 0}, {4, 5}, {6}},
    368                       {{1, -1}, {7}, {}},
    369                       {{7, 3}, {8}, {9}},
    370                       {{4, 5, 8}, {10}, {}},
    371                   },
    372                   {10});
    373   SetGraph(&graph);
    374 
    375   auto is_unallocated = [&](int tensor_index) {
    376     return (*graph.tensors())[tensor_index].data.raw == nullptr;
    377   };
    378 
    379   // The allocation plan is made at the beginning and is independent of
    380   // the execution steps. Here's the allocation order:
    381   //   Op0: +0 +1 +2 +3
    382   //   Op1: +6 +4 +5 -6 -0 -2
    383   //   Op2: +7 -1
    384   //   Op3: +9 +8 -9 -3 -7
    385   //   Op4: +10 -4 -5 -8
    386 
    387   Execute(0, 0);
    388   EXPECT_EQ(GetOffset(0), 0);
    389   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    390   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    391   EXPECT_EQ(GetOffset(3), GetOffsetAfter(2));
    392   EXPECT_TRUE(is_unallocated(6));
    393   EXPECT_TRUE(is_unallocated(4));
    394   EXPECT_TRUE(is_unallocated(5));
    395   EXPECT_TRUE(is_unallocated(7));
    396   EXPECT_TRUE(is_unallocated(9));
    397   EXPECT_TRUE(is_unallocated(8));
    398   EXPECT_TRUE(is_unallocated(10));
    399 
    400   Execute(1, 1);
    401   EXPECT_EQ(GetOffset(0), 0);
    402   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    403   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    404   EXPECT_EQ(GetOffset(3), GetOffsetAfter(2));
    405   EXPECT_EQ(GetOffset(6), GetOffsetAfter(3));
    406   EXPECT_EQ(GetOffset(4), GetOffsetAfter(6));
    407   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    408   EXPECT_TRUE(is_unallocated(7));
    409   EXPECT_TRUE(is_unallocated(9));
    410   EXPECT_TRUE(is_unallocated(8));
    411   EXPECT_TRUE(is_unallocated(10));
    412 
    413   Execute(2, 2);
    414   EXPECT_EQ(GetOffset(0), 0);
    415   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    416   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    417   EXPECT_EQ(GetOffset(3), GetOffsetAfter(2));
    418   EXPECT_EQ(GetOffset(6), GetOffsetAfter(3));
    419   EXPECT_EQ(GetOffset(4), GetOffsetAfter(6));
    420   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    421   // Here's an interesting allocation. Even though #6 requires only 21 bytes,
    422   // its deallocation freed up 24 bytes due to the alignment requirements in
    423   // the arena. That means we can fit #7 in the same space!
    424   EXPECT_EQ(GetOffset(7), GetOffsetAfter(3));
    425   EXPECT_TRUE(is_unallocated(9));
    426   EXPECT_TRUE(is_unallocated(8));
    427   EXPECT_TRUE(is_unallocated(10));
    428 
    429   Execute(3, 3);
    430   EXPECT_EQ(GetOffset(0), 0);
    431   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    432   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    433   EXPECT_EQ(GetOffset(3), GetOffsetAfter(2));
    434   EXPECT_EQ(GetOffset(6), GetOffsetAfter(3));
    435   EXPECT_EQ(GetOffset(4), GetOffsetAfter(6));
    436   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    437   EXPECT_EQ(GetOffset(7), GetOffsetAfter(3));
    438   // The deallocation of #0, #1 and #2 freed up 24 bytes but that's not enough
    439   // for #9, so it goes at the end.
    440   EXPECT_EQ(GetOffset(9), GetOffsetAfter(5));
    441   EXPECT_EQ(GetOffset(8), GetOffsetAfter(9));
    442   EXPECT_TRUE(is_unallocated(10));
    443 
    444   Execute(4, 4);
    445   EXPECT_EQ(GetOffset(0), 0);
    446   EXPECT_EQ(GetOffset(1), GetOffsetAfter(0));
    447   EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
    448   EXPECT_EQ(GetOffset(3), GetOffsetAfter(2));
    449   EXPECT_EQ(GetOffset(6), GetOffsetAfter(3));
    450   EXPECT_EQ(GetOffset(4), GetOffsetAfter(6));
    451   EXPECT_EQ(GetOffset(5), GetOffsetAfter(4));
    452   EXPECT_EQ(GetOffset(7), GetOffsetAfter(3));
    453   EXPECT_EQ(GetOffset(9), GetOffsetAfter(5));
    454   EXPECT_EQ(GetOffset(8), GetOffsetAfter(9));
    455   // There's just enough space at the beginning for #10 due to the
    456   // deallocation of #0, #1, #2 and #3 (total 36 bytes, #10 needs
    457   // only 33.)
    458   EXPECT_EQ(GetOffset(10), 0);
    459 }
    460 
    461 }  // namespace
    462 }  // namespace tflite
    463 
    464 int main(int argc, char** argv) {
    465   ::tflite::LogToStderr();
    466   ::testing::InitGoogleTest(&argc, argv);
    467   return RUN_ALL_TESTS();
    468 }
    469