blob: 9dab59e0aeb647b820114266766f9066aed014e9 [file] [edit]
/*
* Copyright 2025 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "absl/random/random.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "common/simple_model_builder.h"
#include "common/sync_driver.h"
#include "common/test_util/aligned_tflite_buffer_allocator.h"
#include "common/test_util/fp16_compare.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/utils/experimental/stable_delegate/delegate_loader.h"
namespace tflite::cros::tests {
using TfLiteDelegatePtr = Interpreter::TfLiteDelegatePtr;
TFLiteSettingsT GetDefaultSettings() {
TFLiteSettingsT settings;
settings.stable_delegate_loader_settings =
std::make_unique<StableDelegateLoaderSettingsT>();
settings.stable_delegate_loader_settings->delegate_name =
"mtk_neuron_delegate";
settings.stable_delegate_loader_settings->delegate_path =
"/usr/lib64/libtensorflowlite_mtk_neuron_delegate.so";
settings.mtk_neuron_settings = std::make_unique<tflite::MtkNeuronSettingsT>();
settings.mtk_neuron_settings->allow_fp16_precision_for_fp32 = true;
return settings;
}
TfLiteDelegatePtr GetDelegateFromSettings(TFLiteSettingsT const* settings) {
auto delegate = tflite::delegates::utils::LoadDelegateFromSharedLibrary(
settings->stable_delegate_loader_settings->delegate_path);
flatbuffers::FlatBufferBuilder fbb;
const flatbuffers::Offset<TFLiteSettings> settings_buffer =
TFLiteSettings::Pack(fbb, settings);
fbb.Finish(settings_buffer);
const TFLiteSettings* settings_ptr =
flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer());
return std::unique_ptr<TfLiteOpaqueDelegate, void (*)(TfLiteOpaqueDelegate*)>(
delegate->delegate_plugin->create(settings_ptr),
delegate->delegate_plugin->destroy);
}
void SumTo(SimpleModelBuilder& mb, std::vector<int> shape, std::string prefix,
std::vector<int> inputs, int result, int depth = 1) {
if (inputs.size() == 2) {
mb.AddOperator<TfLiteAddParams>({
.op = kTfLiteBuiltinAdd,
.inputs = inputs,
.outputs = {result},
});
} else {
std::vector<int> sum_results;
for (int i = 1; i < std::ssize(inputs); i += 2) {
sum_results.push_back(mb.AddInternalTensor(SimpleModelBuilder::TensorArgs{
.name = prefix + absl::StrFormat("sum_results_%d_%d", i, depth),
.type = kTfLiteFloat32,
.shape = shape,
}));
mb.AddOperator<TfLiteAddParams>({
.op = kTfLiteBuiltinAdd,
.inputs = {inputs[i - 1], inputs[i]},
.outputs = {sum_results.back()},
});
}
if (inputs.size() % 2 == 1) {
sum_results.push_back(inputs.back());
}
SumTo(mb, shape, prefix, sum_results, result, depth + 1);
}
}
/*
* The model constructed looks like:
* +-----------+
* | input |
* +-----+-----+
* |
* +---------+-------+------+-----------+
* | | | | |
* +----v---+ +---v----+ v +---v----+ +----v---+
* | Conv2d | | Conv2d | ... | Conv2d | | Conv2d |
* +----+---+ +---+----+ | +---+----+ +--+-----+
* | | | | |
* +-v---------v-+ | +-v---------v-+
* | Add | | | Add |
* +-------------+ | +------+------+
* | | |
* +-------+ | +------+
* +-v----v----v-+
* | Add |
* +------+------+
* |
* +---------+-------+------+-----------+
* | | | | |
* +----v---+ +---v----+ v +---v----+ +----v---+
* | Conv2d | | Conv2d | ... | Conv2d | | Conv2d |
* +----+---+ +---+----+ | +---+----+ +--+-----+
* | | | | |
* +-v---------v-+ | +-v---------v-+
* | Add | | | Add |
* +------+------+ | +------+------+
* | | |
* +-------+ | +------+
* | | |
* +-v----v----v-+
* | Add |
* +-------------+
* .
* .
* .
* +---------+-------+------+-----------+
* | | | | |
* +----v---+ +---v----+ v +---v----+ +----v---+
* | Conv2d | | Conv2d | ... | Conv2d | | Conv2d |
* +----+---+ +---+----+ | +---+----+ +--+-----+
* | | | | |
* +-v---------v-+ | +-v---------v-+
* | Add | | | Add |
* +------+------+ | +------+------+
* | | |
* +-------+ | +------+
* | | |
* +-v----v----v-+
* | Output |
* +-------------+
*/
template <int Batch, int Size, int Channels, int Kernel, int Layers, int Width>
std::unique_ptr<FlatBufferModel> GetModel() {
absl::BitGen gen;
SimpleModelBuilder mb;
// Batch, Height, Width, Channels
const std::vector<int> content_tensor_shapes = {Batch, Size, Size, Channels};
auto get_content_tensor_args = [&](std::string name) {
return SimpleModelBuilder::TensorArgs{
.name = name,
.type = kTfLiteFloat32,
.shape = content_tensor_shapes,
};
};
int input = mb.AddInput(get_content_tensor_args("input"));
std::vector<std::vector<int>> weights(Width);
for (int i = 0; i < Width; ++i) {
std::vector<float> weight_values(Channels * Kernel * Kernel * Channels);
for (auto& weight_value : weight_values) {
weight_value = absl::Uniform(gen, 0.0, 1.0);
}
std::vector<uint8_t> weight_data(
reinterpret_cast<const uint8_t*>(weight_values.data()),
reinterpret_cast<const uint8_t*>(weight_values.data()) +
weight_values.size() * sizeof(float));
weights[i].push_back(mb.AddInternalTensor(SimpleModelBuilder::TensorArgs{
.name = absl::StrFormat("weight_%d", i),
.type = kTfLiteFloat32,
// Output Channels, Kernel Height, Kernel Width, Input Channels
.shape = {Channels, Kernel, Kernel, Channels},
.buffer = mb.AddBuffer(weight_data),
}));
}
std::vector<uint8_t> bias_data(Channels * sizeof(float));
int bias = mb.AddInternalTensor(SimpleModelBuilder::TensorArgs{
.name = "bias",
.type = kTfLiteFloat32,
// Output Channels
.shape = {Channels},
.buffer = mb.AddBuffer(bias_data),
});
int output = mb.AddOutput(get_content_tensor_args("output"));
for (int i = 1; i <= Layers; ++i) {
const TfLiteConvParams conv_params = {
.padding = kTfLitePaddingSame,
.stride_width = 1,
.stride_height = 1,
.activation = kTfLiteActNone,
.dilation_width_factor = 1,
.dilation_height_factor = 1,
};
int current_output = (i == Layers)
? output
: mb.AddInternalTensor(get_content_tensor_args(
absl::StrFormat("intermediate_%d", i)));
if (Width == 1) {
mb.AddOperator<TfLiteConvParams>({
.op = kTfLiteBuiltinConv2d,
.inputs = {input, weights[0].back(), bias},
.outputs = {current_output},
.params = conv_params,
});
} else {
std::vector<int> intermediates;
for (int j = 0; j < Width; ++j) {
intermediates.push_back(mb.AddInternalTensor(get_content_tensor_args(
absl::StrFormat("intermediate_%d_%d", i, j))));
mb.AddOperator<TfLiteConvParams>({
.op = kTfLiteBuiltinConv2d,
.inputs = {input, weights[j].back(), bias},
.outputs = {intermediates.back()},
.params = conv_params,
});
}
SumTo(mb, content_tensor_shapes, absl::StrFormat("intermediate_%d_", i),
intermediates, current_output);
}
input = current_output;
}
return mb.Build();
}
std::pair<std::unique_ptr<FlatBufferModel>, int>
GetTimeConsumingModelAndBufferSize() {
// The model with these parameters runs >500ms on Navi.
constexpr int kBatch = 8;
constexpr int kSize = 512;
constexpr int kChannels = 6;
constexpr int kKernel = 15;
constexpr int kLayers = 32;
constexpr int kWidth = 1;
return {GetModel<kBatch, kSize, kChannels, kKernel, kLayers, kWidth>(),
kBatch * kSize * kSize * kChannels};
}
std::vector<float> GetRandomizedFloatData(int size) {
absl::BitGen gen;
std::vector<float> buffer(size);
for (auto& buffer_value : buffer) {
buffer_value = absl::Uniform(gen, 0.0, 1.0);
}
return buffer;
}
TEST(NeuronDelegateTest, InferenceSelfAborted) {
TFLiteSettingsT settings = GetDefaultSettings();
auto [model, buffer_size] = GetTimeConsumingModelAndBufferSize();
ASSERT_NE(model, nullptr);
std::vector<float> input = GetRandomizedFloatData(buffer_size);
// Get the expected output with the given input.
TfLiteDelegatePtr delegate = GetDelegateFromSettings(&settings);
ASSERT_NE(delegate, nullptr);
auto driver = SyncDriver::Create(
std::move(delegate), FlatBufferModel::BuildFromModel(model->GetModel()));
ASSERT_NE(driver, nullptr);
driver->SetInputTensorBuffer(
"input", AllocateTfLiteAlignedTensorBuffer(buffer_size * sizeof(float)));
ASSERT_EQ(driver->SetInputTensorData("input", input), kTfLiteOk);
ASSERT_EQ(driver->AllocateBuffers(), kTfLiteOk);
ASSERT_EQ(driver->Invoke(), kTfLiteOk);
std::vector<float> expected_output =
driver->GetOutputTensorData<float>("output");
// Set the inference abort time to 1ms.
settings.mtk_neuron_settings->inference_abort_time_ms = 1;
// Try inference the model with an abort time.
delegate = GetDelegateFromSettings(&settings);
ASSERT_NE(delegate, nullptr);
driver = SyncDriver::Create(
std::move(delegate), FlatBufferModel::BuildFromModel(model->GetModel()));
ASSERT_NE(driver, nullptr);
driver->SetInputTensorBuffer(
"input", AllocateTfLiteAlignedTensorBuffer(buffer_size * sizeof(float)));
ASSERT_EQ(driver->SetInputTensorData("input", input), kTfLiteOk);
ASSERT_EQ(driver->AllocateBuffers(), kTfLiteOk);
// Check the model inference is properly aborted.
absl::Time before_invoke = absl::Now();
ASSERT_EQ(driver->Invoke(), kTfLiteError);
absl::Time after_invoke = absl::Now();
// Use a larger time duration to accommodate inaccuracy and overheads.
EXPECT_LT(after_invoke - before_invoke, absl::Milliseconds(60));
// The model inference should be aborted, so the output should be different.
std::vector<float> aborted_output =
driver->GetOutputTensorData<float>("output");
ASSERT_EQ(aborted_output.size(), expected_output.size());
EXPECT_THAT(
aborted_output,
testing::Not(testing::ElementsAreArray(ArrayFp16Eq(expected_output))));
}
} // namespace tflite::cros::tests
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}