blob: 6ff623321e323f41793bda7ab5c3e248de881d84 [file] [log] [blame]
/* Copyright 2018 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 "tensorflow/lite/micro/micro_interpreter.h"
#include <cstdint>
#include "tensorflow/lite/core/api/flatbuffer_conversions.h"
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/compatibility.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_profiler.h"
#include "tensorflow/lite/micro/micro_utils.h"
#include "tensorflow/lite/micro/recording_micro_allocator.h"
#include "tensorflow/lite/micro/test_helpers.h"
#include "tensorflow/lite/micro/testing/micro_test.h"
namespace tflite {
namespace {
class MockProfiler : public MicroProfiler {
public:
MockProfiler() : event_starts_(0), event_ends_(0) {}
uint32_t BeginEvent(const char* tag) override {
event_starts_++;
return 0;
}
void EndEvent(uint32_t event_handle) override { event_ends_++; }
int event_starts() { return event_starts_; }
int event_ends() { return event_ends_; }
private:
int event_starts_;
int event_ends_;
TF_LITE_REMOVE_VIRTUAL_DELETE
};
} // namespace
} // namespace tflite
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(TestInterpreter) {
const tflite::Model* model = tflite::testing::GetSimpleMockModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 2000;
uint8_t allocator_buffer[allocator_buffer_size];
// Create a new scope so that we can test the destructor.
{
tflite::MicroInterpreter interpreter(model, op_resolver, allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(interpreter.AllocateTensors(), kTfLiteOk);
TF_LITE_MICRO_EXPECT_LE(interpreter.arena_used_bytes(), 928 + 100);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(1), interpreter.inputs_size());
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(2), interpreter.outputs_size());
TfLiteTensor* input = interpreter.input(0);
TF_LITE_MICRO_EXPECT_NE(nullptr, input);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input->type);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), input->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, input->data.i32);
input->data.i32[0] = 21;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter.Invoke());
TfLiteTensor* output = interpreter.output(0);
TF_LITE_MICRO_EXPECT_NE(nullptr, output);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output->type);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), output->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, output->data.i32);
TF_LITE_MICRO_EXPECT_EQ(42, output->data.i32[0]);
output = interpreter.output(1);
TF_LITE_MICRO_EXPECT_NE(nullptr, output);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output->type);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), output->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, output->data.i32);
TF_LITE_MICRO_EXPECT_EQ(42, output->data.i32[0]);
}
TF_LITE_MICRO_EXPECT_EQ(tflite::testing::MockCustom::freed_, true);
}
TF_LITE_MICRO_TEST(TestMultiTenantInterpreter) {
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t arena_size = 8192;
uint8_t arena[arena_size];
size_t simple_model_head_usage = 0, complex_model_head_usage = 0;
// Get simple_model_head_usage.
{
tflite::RecordingMicroAllocator* allocator =
tflite::RecordingMicroAllocator::Create(
arena, arena_size, tflite::GetMicroErrorReporter());
const tflite::Model* model0 = tflite::testing::GetSimpleMockModel();
tflite::MicroInterpreter interpreter0(model0, op_resolver, allocator,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter0.AllocateTensors());
simple_model_head_usage =
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes();
TfLiteTensor* input = interpreter0.input(0);
TfLiteTensor* output = interpreter0.output(0);
input->data.i32[0] = 21;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter0.Invoke());
TF_LITE_MICRO_EXPECT_EQ(42, output->data.i32[0]);
}
// Shared allocator for various models.
tflite::RecordingMicroAllocator* allocator =
tflite::RecordingMicroAllocator::Create(arena, arena_size,
tflite::GetMicroErrorReporter());
// Get complex_model_head_usage. No head space reuse since it's the first
// model allocated in the `allocator`.
const tflite::Model* model1 = tflite::testing::GetComplexMockModel();
tflite::MicroInterpreter interpreter1(model1, op_resolver, allocator,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter1.AllocateTensors());
TfLiteTensor* input1 = interpreter1.input(0);
TfLiteTensor* output1 = interpreter1.output(0);
complex_model_head_usage =
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes();
// Allocate simple model from the same `allocator`. Some head space will
// be reused thanks to multi-tenant TFLM support. Also makes sure that
// the output is correct.
const tflite::Model* model2 = tflite::testing::GetSimpleMockModel();
tflite::MicroInterpreter interpreter2(model2, op_resolver, allocator,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter2.AllocateTensors());
TfLiteTensor* input2 = interpreter2.input(0);
TfLiteTensor* output2 = interpreter2.output(0);
// Verify that 1 + 1 < 2.
size_t multi_tenant_head_usage =
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes();
TF_LITE_MICRO_EXPECT_LE(multi_tenant_head_usage,
complex_model_head_usage + simple_model_head_usage);
// Now we have model1 and model2 sharing the same `allocator`.
// Let's make sure that they can produce correct results.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input1->type);
input1->data.i32[0] = 10;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter1.Invoke());
// Output tensor for the first model.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output1->type);
TF_LITE_MICRO_EXPECT_EQ(10, output1->data.i32[0]);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input2->type);
input2->data.i32[0] = 21;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter2.Invoke());
// Output for the second model.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output2->type);
TF_LITE_MICRO_EXPECT_EQ(42, output2->data.i32[0]);
// Allocate another complex model from the `allocator` will not increase
// head space usage.
const tflite::Model* model3 = tflite::testing::GetComplexMockModel();
tflite::MicroInterpreter interpreter3(model3, op_resolver, allocator,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter3.AllocateTensors());
TfLiteTensor* input3 = interpreter3.input(0);
TfLiteTensor* output3 = interpreter3.output(0);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input3->type);
input3->data.i32[0] = 10;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter3.Invoke());
// Output tensor for the third model.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output3->type);
TF_LITE_MICRO_EXPECT_EQ(10, output3->data.i32[0]);
// No increase on the head usage as we're reusing the space.
TF_LITE_MICRO_EXPECT_EQ(
multi_tenant_head_usage,
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes());
}
TF_LITE_MICRO_TEST(TestKernelMemoryPlanning) {
const tflite::Model* model = tflite::testing::GetSimpleStatefulModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 4096;
uint8_t allocator_buffer[allocator_buffer_size];
tflite::RecordingMicroAllocator* allocator =
tflite::RecordingMicroAllocator::Create(allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
// Make sure kernel memory planning works in multi-tenant context.
for (int i = 0; i < 3; i++) {
tflite::MicroInterpreter interpreter(model, op_resolver, allocator,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(interpreter.AllocateTensors(), kTfLiteOk);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(1), interpreter.inputs_size());
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(2), interpreter.outputs_size());
TfLiteTensor* input = interpreter.input(0);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->size);
TF_LITE_MICRO_EXPECT_EQ(3, input->dims->data[0]);
input->data.uint8[0] = 2;
input->data.uint8[1] = 3;
input->data.uint8[2] = 1;
uint8_t expected_median = 2;
{
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter.Invoke());
TfLiteTensor* median = interpreter.output(0);
TF_LITE_MICRO_EXPECT_EQ(expected_median, median->data.uint8[0]);
TfLiteTensor* invoke_count = interpreter.output(1);
TF_LITE_MICRO_EXPECT_EQ(1, invoke_count->data.i32[0]);
}
{
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter.Invoke());
TfLiteTensor* median = interpreter.output(0);
TF_LITE_MICRO_EXPECT_EQ(expected_median, median->data.uint8[0]);
TfLiteTensor* invoke_count = interpreter.output(1);
TF_LITE_MICRO_EXPECT_EQ(2, invoke_count->data.i32[0]);
}
}
}
// The interpreter initialization requires multiple steps and this test case
// ensures that simply creating and destructing an interpreter object is ok.
// b/147830765 has one example of a change that caused trouble for this simple
// case.
TF_LITE_MICRO_TEST(TestIncompleteInitialization) {
const tflite::Model* model = tflite::testing::GetComplexMockModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 2048;
uint8_t allocator_buffer[allocator_buffer_size];
tflite::MicroInterpreter interpreter(model, op_resolver, allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
}
// Test that an interpreter with a supplied profiler correctly calls the
// profiler each time an operator is invoked.
TF_LITE_MICRO_TEST(InterpreterWithProfilerShouldProfileOps) {
const tflite::Model* model = tflite::testing::GetComplexMockModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 2048;
uint8_t allocator_buffer[allocator_buffer_size];
tflite::MockProfiler profiler;
tflite::MicroInterpreter interpreter(
model, op_resolver, allocator_buffer, allocator_buffer_size,
tflite::GetMicroErrorReporter(), &profiler);
TF_LITE_MICRO_EXPECT_EQ(profiler.event_starts(), 0);
TF_LITE_MICRO_EXPECT_EQ(profiler.event_ends(), 0);
TF_LITE_MICRO_EXPECT_EQ(interpreter.AllocateTensors(), kTfLiteOk);
TF_LITE_MICRO_EXPECT_EQ(interpreter.Invoke(), kTfLiteOk);
#ifndef NDEBUG
TF_LITE_MICRO_EXPECT_EQ(profiler.event_starts(), 3);
TF_LITE_MICRO_EXPECT_EQ(profiler.event_ends(), 3);
#else // Profile events will not occur on release builds.
TF_LITE_MICRO_EXPECT_EQ(profiler.event_starts(), 0);
TF_LITE_MICRO_EXPECT_EQ(profiler.event_ends(), 0);
#endif
}
TF_LITE_MICRO_TEST(TestIncompleteInitializationAllocationsWithSmallArena) {
const tflite::Model* model = tflite::testing::GetComplexMockModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 528;
uint8_t allocator_buffer[allocator_buffer_size];
tflite::RecordingMicroAllocator* allocator =
tflite::RecordingMicroAllocator::Create(allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_NE(nullptr, allocator);
tflite::MicroInterpreter interpreter(model, op_resolver, allocator,
tflite::GetMicroErrorReporter());
// Interpreter fails because arena is too small:
TF_LITE_MICRO_EXPECT_EQ(interpreter.Invoke(), kTfLiteError);
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(192),
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes());
// Ensure allocations are zero (ignore tail since some internal structs are
// initialized with this space):
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteEvalTensorData)
.used_bytes);
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteTensorVariableBufferData)
.used_bytes);
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator->GetRecordedAllocation(tflite::RecordedAllocationType::kOpData)
.used_bytes);
}
TF_LITE_MICRO_TEST(TestInterpreterDoesNotAllocateUntilInvoke) {
const tflite::Model* model = tflite::testing::GetComplexMockModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 1024 * 10;
uint8_t allocator_buffer[allocator_buffer_size];
tflite::RecordingMicroAllocator* allocator =
tflite::RecordingMicroAllocator::Create(allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_NE(nullptr, allocator);
tflite::MicroInterpreter interpreter(model, op_resolver, allocator,
tflite::GetMicroErrorReporter());
// Ensure allocations are zero (ignore tail since some internal structs are
// initialized with this space):
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes());
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteTensorVariableBufferData)
.used_bytes);
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteEvalTensorData)
.used_bytes);
TF_LITE_MICRO_EXPECT_EQ(
static_cast<size_t>(0),
allocator->GetRecordedAllocation(tflite::RecordedAllocationType::kOpData)
.used_bytes);
TF_LITE_MICRO_EXPECT_EQ(interpreter.Invoke(), kTfLiteOk);
allocator->PrintAllocations();
// Allocation sizes vary based on platform - check that allocations are now
// non-zero:
TF_LITE_MICRO_EXPECT_GT(
allocator->GetSimpleMemoryAllocator()->GetHeadUsedBytes(),
static_cast<size_t>(0));
TF_LITE_MICRO_EXPECT_GT(
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteEvalTensorData)
.used_bytes,
0);
TF_LITE_MICRO_EXPECT_GT(
allocator
->GetRecordedAllocation(
tflite::RecordedAllocationType::kTfLiteTensorVariableBufferData)
.used_bytes,
static_cast<size_t>(0));
// TODO(b/160160549): This check is mostly meaningless right now because the
// operator creation in our mock models is inconsistent. Revisit what
// this check should be once the mock models are properly created.
TF_LITE_MICRO_EXPECT_EQ(
allocator->GetRecordedAllocation(tflite::RecordedAllocationType::kOpData)
.used_bytes,
static_cast<size_t>(0));
}
TF_LITE_MICRO_TEST(TestInterpreterMultipleInputs) {
const tflite::Model* model = tflite::testing::GetSimpleMultipleInputsModel();
TF_LITE_MICRO_EXPECT_NE(nullptr, model);
tflite::AllOpsResolver op_resolver = tflite::testing::GetOpResolver();
constexpr size_t allocator_buffer_size = 2000;
uint8_t allocator_buffer[allocator_buffer_size];
// Create a new scope so that we can test the destructor.
{
tflite::MicroInterpreter interpreter(model, op_resolver, allocator_buffer,
allocator_buffer_size,
tflite::GetMicroErrorReporter());
TF_LITE_MICRO_EXPECT_EQ(interpreter.AllocateTensors(), kTfLiteOk);
TF_LITE_MICRO_EXPECT_LE(interpreter.arena_used_bytes(), 928 + 100);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(3), interpreter.inputs_size());
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(1), interpreter.outputs_size());
TfLiteTensor* input = interpreter.input(0);
TF_LITE_MICRO_EXPECT_NE(nullptr, input);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input->type);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), input->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, input->data.i32);
input->data.i32[0] = 21;
TfLiteTensor* input1 = interpreter.input(1);
TF_LITE_MICRO_EXPECT_NE(nullptr, input1);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt8, input1->type);
TF_LITE_MICRO_EXPECT_EQ(1, input1->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input1->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(1), input1->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, input1->data.i32);
input1->data.i32[0] = 21;
TfLiteTensor* input2 = interpreter.input(2);
TF_LITE_MICRO_EXPECT_NE(nullptr, input2);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input2->type);
TF_LITE_MICRO_EXPECT_EQ(1, input2->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input2->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), input2->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, input2->data.i32);
input2->data.i32[0] = 24;
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter.Invoke());
TfLiteTensor* output = interpreter.output(0);
TF_LITE_MICRO_EXPECT_NE(nullptr, output);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output->type);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(static_cast<size_t>(4), output->bytes);
TF_LITE_MICRO_EXPECT_NE(nullptr, output->data.i32);
TF_LITE_MICRO_EXPECT_EQ(66, output->data.i32[0]);
}
TF_LITE_MICRO_EXPECT_EQ(tflite::testing::MultipleInputs::freed_, true);
}
TF_LITE_MICRO_TESTS_END