/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include #include #include #include #include #include "tensorflow/lite/core/interpreter.h" #include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/subgraph_test_util.h" #include "tensorflow/lite/profiling/memory_info.h" namespace tflite { using subgraph_test_util::CheckIntTensor; using subgraph_test_util::CheckScalarStringTensor; using subgraph_test_util::CheckStringTensor; using subgraph_test_util::ControlFlowOpTest; using subgraph_test_util::FillIntTensor; using subgraph_test_util::FillScalarStringTensor; namespace { class WhileTest : public ControlFlowOpTest {}; TEST_F(WhileTest, TestWithXNNPACK) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildFloatLessCondSubgraph(interpreter_->subgraph(1), 100); builder_->BuildXNNPACKSubgraph(interpreter_->subgraph(2)); builder_->BuildFloatWhileSubgraph(&interpreter_->primary_subgraph(), 2); const auto opt = TfLiteXNNPackDelegateOptionsDefault(); std::unique_ptr xnnpack_delegate( TfLiteXNNPackDelegateCreate(&opt), TfLiteXNNPackDelegateDelete); interpreter_->primary_subgraph().MarkAsDelegationSkippable(); interpreter_->subgraph(1)->MarkAsDelegationSkippable(); ASSERT_EQ(interpreter_->ModifyGraphWithDelegate(std::move(xnnpack_delegate)), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); float* input0 = GetTensorData(interpreter_->tensor(interpreter_->inputs()[0])); input0[0] = 1; float* input1 = GetTensorData(interpreter_->tensor(interpreter_->inputs()[1])); input1[0] = 1; ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); float* output0_data = GetTensorData(output0); ASSERT_EQ(output0_data[0], 256); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); float* output1_data = GetTensorData(output1); ASSERT_EQ(output1_data[0], 256); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestInputIsOutput) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 3); builder_->BuildInputIsOutputSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 3); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {1}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {1}, {4}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestInputIsOutputButDifferent) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 2); builder_->BuildInputIsDifferentOutputSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 2); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {2}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {5}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {1}, {8}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestFlexOutput) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 2); builder_->BuildFlexOutputSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 2); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {2}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {2, 3}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {2}, {5, 6}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestCounterOnly) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 1); builder_->BuildCounterOnlySubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 1); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestAllCases) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 5); builder_->BuildAllInplaceScenariosSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 5); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[3], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[4], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[3]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[4]), {1}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {1}, {5}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[2]); CheckIntTensor(output2, {6}, {2, 2, 2, 2, 2, 2}); TfLiteTensor* output3 = interpreter_->tensor(interpreter_->outputs()[3]); CheckIntTensor(output3, {6}, {4, 4, 4, 4, 4, 4}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestStaticUnconsumedOutputs) { for (bool dynamic_tensors : {true, false}) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 3); builder_->BuildInputIsOutputSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraphWithUnconsumedOutput( &interpreter_->primary_subgraph(), 3); InterpreterOptions options; if (dynamic_tensors) { options.OptimizeMemoryForLargeTensors(1); interpreter_->ApplyOptions(&options); } ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {2}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {1}, {8}); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {2}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {2, 2}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); CheckIntTensor(output1, {2}, {8, 8}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } } // Test a body subgraph which triggers the reallocation of an inplace output // tensor whose corresponding input has not been consumed yet. This tests that // the input pointer has be updated. TEST_F(WhileTest, TestDynamicOpTriggersAllocationOfUnsedInput) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 2, 3); builder_->BuildDynamicOpTriggersAllocationOfUnsedInputSubgraph( interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 3); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {2}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {3}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {2}, {4, 4}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[2]); CheckIntTensor(output2, {2}, {2, 2}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestStaticInPlace) { const std::vector expected = {6, 10, 15, 21, 28}; for (int i = 0; i < expected.size(); ++i) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), i + 1); builder_->BuildDeepBodySubgraph(interpreter_->subgraph(2)); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {0}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {1}, {i + 2}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output2, {1}, {expected[i]}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } } TEST_F(WhileTest, TestStaticInPlaceLarge) { int size = 10000; interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), 60000); builder_->BuildLargeBodySubgraph(interpreter_->subgraph(2)); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {size}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), std::vector(size, 1)); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {}, {10010 * size}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output2, {size}, std::vector(size, 70014)); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } // The test builds a model that produces the i-th number of // triangular number sequence. TEST_F(WhileTest, TestTriangularNumberSequence) { const std::vector expected = {1, 3, 6, 10, 15, 21, 28}; for (int i = 0; i < expected.size(); ++i) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), i); builder_->BuildAccumulateLoopBodySubgraph(interpreter_->subgraph(2)); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}), kTfLiteOk); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); // Check While BODY inputs are static tensors. auto body_subgraph = interpreter_->subgraph(2); TfLiteTensor* subgraph_input2 = body_subgraph->tensor(body_subgraph->inputs()[1]); EXPECT_EQ(subgraph_input2->allocation_type, kTfLiteCustom); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {1}, {i + 1}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output2, {1}, {expected[i]}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } } TEST_F(WhileTest, TestTriangularNumberSequenceWithShallowCopy) { const std::vector expected = {1, 3, 6, 10, 15, 21, 28}; for (int i = 0; i < expected.size(); ++i) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), i); builder_->BuildAccumulateLoopBodySubgraph(interpreter_->subgraph(2)); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); // Use 4MB inputs to test shallow copy. interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1000000}); // Apply DynamicAllocationForLargeTensors option to enable shallow copy. InterpreterOptions options; options.OptimizeMemoryForLargeTensors(1000000); ASSERT_EQ(interpreter_->ApplyOptions(&options), kTfLiteOk); const size_t initial_mem_usage = profiling::memory::GetMemoryUsage().mem_footprint_kb; ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); // Memory usage shouldn't exceed 9MB (2 x inputs + margin). ASSERT_LE(profiling::memory::GetMemoryUsage().mem_footprint_kb - initial_mem_usage, 9000); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); const std::vector input_vector(1000000, 1); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), input_vector); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); auto body_subgraph = interpreter_->subgraph(2); // While BODY inputs are dynamic tensors with shallow copy. TfLiteTensor* subgraph_input2 = body_subgraph->tensor(body_subgraph->inputs()[1]); ASSERT_EQ(subgraph_input2->allocation_type, kTfLiteCustom); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {1}, {i + 1}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); const std::vector expected2(1000000, expected[i]); CheckIntTensor(output2, {1000000}, expected2); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } } TEST_F(WhileTest, TestPadLoop) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), 4); builder_->BuildPadLoopBodySubgraph(interpreter_->subgraph(2), {1, 2}); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {2}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {5, 7}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {1}, {5}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output2, {14}, {0, 0, 0, 0, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestDynamicBodyWithSharingEarlyExit) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 0, 4); builder_->BuildDynamicIncreasingSizeSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 4); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {3}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {10000}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1, 2, 3}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {1}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {3}, {1, 2, 3}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestDynamicBodyWithSharing) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 4); builder_->BuildDynamicIncreasingSizeSubgraph(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 4); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {3}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1000000}); interpreter_->ResizeInputTensor(interpreter_->inputs()[3], {1000000}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1, 2, 3}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {4}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {18}, {4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[2]); EXPECT_EQ(output2->dims->data[0], 1000000); TfLiteTensor* output3 = interpreter_->tensor(interpreter_->outputs()[3]); EXPECT_EQ(output3->dims->data[0], 1000000); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestDynamicBodyWithSharingAndAliases) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 0, 5); builder_->BuildDynamicBodySubgraphWithAliases(interpreter_->subgraph(2)); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 5); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[3], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[4], {1}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {0}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[3]), {3}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[4]), {4}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {1}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {1}, {11}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[2]); CheckIntTensor(output2, {1}, {12}); TfLiteTensor* output3 = interpreter_->tensor(interpreter_->outputs()[4]); CheckIntTensor(output3, {1}, {13}); TfLiteTensor* output4 = interpreter_->tensor(interpreter_->outputs()[4]); CheckIntTensor(output4, {1}, {13}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestOutputNotConsumed) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 11, 3); builder_->BuildOutputNotConsumedSubgraph(*interpreter_->subgraph(2)); builder_->BuildOutputNotConsumedWhileSubgraph( &interpreter_->primary_subgraph()); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {3}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {3}, {18, 18, 18}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestPadLoopWithSharing) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLargeLessEqualCondSubgraph(interpreter_->subgraph(1), 3, 3); builder_->BuildLargePadSubgraph(interpreter_->subgraph(2), {1, 2}); builder_->BuildMultiInputWhileSubgraph(&interpreter_->primary_subgraph(), 3); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {2}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), {2}); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {3, 4}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output0 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output0, {1}, {5}); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[1]); CheckIntTensor(output1, {5}, {4, 9, 10, 4, 4}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[2]); CheckIntTensor(output2, {8}, {0, 4, 9, 10, 4, 4, 0, 0}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestPadLoopWithShallowCopy) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraph(interpreter_->subgraph(1), 3); builder_->BuildPadLoopBodySubgraph(interpreter_->subgraph(2), {1, 2}); builder_->BuildWhileSubgraph(&interpreter_->primary_subgraph()); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1}); // Use 4MB inputs to test shallow copy. interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1000000}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[0]), {1}); std::vector input_vector(1000000, 0); input_vector[0] = 5; input_vector[1] = 7; FillIntTensor(interpreter_->tensor(interpreter_->inputs()[1]), input_vector); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckIntTensor(output1, {1}, {4}); TfLiteTensor* output2 = interpreter_->tensor(interpreter_->outputs()[1]); std::vector output_vector(1000009, 0); output_vector[3] = 5; output_vector[4] = 7; CheckIntTensor(output2, {1000009}, output_vector); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } TEST_F(WhileTest, TestWhileLoopWithDynamicTensor) { interpreter_ = std::make_unique(); AddSubgraphs(2); builder_->BuildLessEqualCondSubgraphWithDynamicTensor( interpreter_->subgraph(1), 3); builder_->BuildBodySubgraphWithDynamicTensor(interpreter_->subgraph(2)); builder_->BuildWhileSubgraphWithDynamicTensor( &interpreter_->primary_subgraph()); interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {}); interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {}); interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1}); ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk); FillScalarStringTensor(interpreter_->tensor(interpreter_->inputs()[0]), "A"); FillScalarStringTensor(interpreter_->tensor(interpreter_->inputs()[1]), "A"); FillIntTensor(interpreter_->tensor(interpreter_->inputs()[2]), {1}); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); TfLiteTensor* string_output1 = interpreter_->tensor(interpreter_->outputs()[0]); CheckScalarStringTensor(string_output1, "A"); TfLiteTensor* string_output2 = interpreter_->tensor(interpreter_->outputs()[1]); CheckStringTensor(string_output2, {4}, {"A", "A", "A", "A"}); TfLiteTensor* integer_output = interpreter_->tensor(interpreter_->outputs()[2]); CheckIntTensor(integer_output, {1}, {4}); // The extra invocation serves as a regression test: There was a bug that // invoking a while loop with dynamic shaped body makes the interpreter // state uninvokable. ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk); } } // namespace } // namespace tflite