Files
tensorflow--tensorflow/tensorflow/lite/kernels/test_util.cc
T
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

788 lines
28 KiB
C++

/* Copyright 2017 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/kernels/test_util.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <complex>
#include <cstring>
#include <fstream>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/base/casts.h"
#include "absl/base/const_init.h"
#include "absl/base/no_destructor.h"
#include "absl/base/thread_annotations.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/synchronization/mutex.h"
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/allocation.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/core/model.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/delegates/nnapi/acceleration_test_util.h"
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
#include "tensorflow/lite/kernels/acceleration_test_util.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/test_delegate_providers.h"
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/nnapi/nnapi_implementation.h"
#include "tensorflow/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/simple_planner.h"
#include "tensorflow/lite/stderr_reporter.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/string_util.h"
#include "tensorflow/lite/tools/logging.h"
#include "tensorflow/lite/tools/serialization/writer_lib.h"
#include "tensorflow/lite/tools/versioning/op_version.h"
#include "tensorflow/lite/types/fp16.h" // IWYU pragma: keep
#include "tensorflow/lite/version.h"
#include "tsl/platform/logging.h"
namespace tflite {
using ::testing::Eq;
using ::testing::FloatEq;
using ::testing::FloatNear;
using ::testing::Matcher;
namespace {
// An Allocation that owns the memory and will delete it when the Allocation is
// destroyed.
class OwnedMemoryAllocation : public Allocation {
public:
OwnedMemoryAllocation(std::unique_ptr<uint8_t[]> data, size_t size)
: Allocation(DefaultErrorReporter(), tflite::Allocation::Type::kMemory),
data_(std::move(data)),
size_(size) {}
~OwnedMemoryAllocation() override = default;
const void* base() const override { return data_.get(); }
size_t bytes() const override { return size_; }
bool valid() const override { return true; }
private:
std::unique_ptr<uint8_t[]> data_;
size_t size_;
};
// Converts an integer from the sign-and-magnitude representation to
// the biased representation. More precisely, let N be 2 to the
// power of (kBitCount - 1), an integer x is represented by the
// unsigned number x + N.
//
// For instance,
//
// -N + 1 (the most negative number representable using
// sign-and-magnitude) is represented by 1;
// 0 is represented by N; and
// N - 1 (the biggest number representable using
// sign-and-magnitude) is represented by 2N - 1.
//
// Read https://en.wikipedia.org/wiki/Signed_number_representations
// for more details on signed number representations.
uint32_t SignAndMagnitudeToBiased(uint32_t sam) {
constexpr uint32_t kSignBitMask = 1u << 31;
if (kSignBitMask & sam) {
// sam represents a negative number.
return ~sam + 1;
} else {
// sam represents a positive number.
return kSignBitMask | sam;
}
}
// Given two numbers in the sign-and-magnitude representation,
// returns the distance between them as an unsigned number.
uint32_t DistanceBetweenSignAndMagnitudeNumbers(uint32_t sam1, uint32_t sam2) {
uint32_t biased1 = SignAndMagnitudeToBiased(sam1);
uint32_t biased2 = SignAndMagnitudeToBiased(sam2);
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
}
// Returns true if and only if lhs is at most max_ulps ULP's away from rhs.
// In particular, this function:
//
// - returns true if both numbers are NAN.
// - returns false if exact one of numbers is NAN.
// - treats really large numbers as almost equal to infinity.
// - thinks +0.0 and -0.0 are 0 ULP's apart.
bool AlmostEquals(float lhs, float rhs, uint32_t max_ulps) {
if (std::isnan(lhs) || std::isnan(rhs)) {
return std::isnan(lhs) && std::isnan(rhs);
}
return DistanceBetweenSignAndMagnitudeNumbers(
absl::bit_cast<uint32_t>(lhs), absl::bit_cast<uint32_t>(rhs)) <=
max_ulps;
}
MATCHER(Fp16Eq, "") {
// FP16 only has 10 bits precision while FP32 has 23 bits precision. Thus, to
// check if results of FP16 are almost equal, we could check the result is
// within 4 * 2^13 ULPs of FP32, which equals to 4 ULPs of FP16.
constexpr uint32_t fp16_ulps_in_fp32 = 4 * (1 << 13);
float actual = std::get<0>(arg);
float expected = std::get<1>(arg);
// The minimum exponent of FP16 is 2^-14, which means the minimum ULP of FP16
// is 2^-24. Therefore, when expected is less than 2^-14, i.e. a subnormal
// FP16 number, the minimum ULP of FP16 should be used instead of ULP of FP32.
if (std::abs(expected) < 0x1p-14) {
return std::abs(actual - expected) <= 4 * 0x1p-24;
}
return AlmostEquals(actual, expected, fp16_ulps_in_fp32);
}
// Returns the name of the dumped model. The name is in the format of
// DTS-<test_suite_name>-<test_name>-<model_serial>.tflite. The model serial
// number is used to distinguish different models dumped in the same test.
// Returns empty string when there is no test info.
std::string GetDumpedModelName() {
// The mutex is used to ensure thread safety for mutil-threaded tests. Notice
// that it doesn't work for running tests in parallel, which users should
// avoid when dumping models.
static absl::Mutex mutex(absl::kConstInit);
static absl::NoDestructor<std::string> previous_test_name ABSL_GUARDED_BY(
mutex);
static int model_serial ABSL_GUARDED_BY(mutex) = 0;
absl::MutexLock lock(mutex);
const testing::TestInfo* test_info =
::testing::UnitTest::GetInstance()->current_test_info();
if (test_info == nullptr) {
return "";
}
std::string current_test_name =
absl::StrFormat("%s-%s", test_info->test_suite_name(), test_info->name());
// Reset serial number when running a new test.
if (*previous_test_name != current_test_name) {
*previous_test_name = current_test_name;
model_serial = 0;
}
model_serial++;
std::string raw_output_file_name =
absl::StrFormat("DTS-%s-%d.tflite", current_test_name, model_serial);
// Unix file name should not contain "/".
std::string output_file_name =
absl::StrReplaceAll(raw_output_file_name, {{"/", "_"}});
return output_file_name;
}
// Modifies the dumped model as the following:
// 1. Removes optional input tensors in subgraph inputs.
// 2. Adds a signature def to the model.
// 3. Copies input tensors from interpreter to model buffers.
std::unique_ptr<FlatBufferModel> ModifyDumpedModel(
std::unique_ptr<FlatBufferModel> fb_model,
tflite::Interpreter* interpreter) {
std::unique_ptr<ModelT> model(fb_model->GetModel()->UnPack());
auto& graph = model->subgraphs[0];
// Remove optional inputs in subgraph.
std::vector<int> fixed_inputs;
for (int i : graph->inputs) {
if (i < 0 || i >= graph->tensors.size()) {
continue;
}
fixed_inputs.push_back(i);
}
graph->inputs = std::move(fixed_inputs);
// Copy inputs from interpreter to model buffers.
for (int i = 0; i < graph->inputs.size(); ++i) {
int input_idx = graph->inputs[i];
TfLiteTensor* t = interpreter->tensor(input_idx);
if (t == nullptr) {
TFLITE_LOG(INFO) << "input tensor is null";
continue;
}
char* raw_data = GetTensorData<char>(t);
if (raw_data == nullptr) {
TFLITE_LOG(INFO) << "input tensor data is null";
continue;
}
std::vector<uint8_t> data(t->bytes);
memcpy(data.data(), raw_data, t->bytes);
auto buffer = std::make_unique<BufferT>();
buffer->data = data;
model->buffers.push_back(std::move(buffer));
graph->tensors[input_idx]->buffer = model->buffers.size() - 1;
}
// Add a signature def to the model.
auto def = std::make_unique<SignatureDefT>();
def->subgraph_index = 0;
def->signature_key = "serving_default";
for (int i : graph->inputs) {
if (i < 0 || i >= graph->tensors.size()) {
continue;
}
auto map = std::make_unique<TensorMapT>();
map->name = graph->tensors[i]->name;
if (map->name.empty()) {
map->name = absl::StrFormat("input_%d", i);
}
map->tensor_index = i;
def->inputs.push_back(std::move(map));
}
for (int i : graph->outputs) {
if (i < 0 || i >= graph->tensors.size()) {
continue;
}
auto map = std::make_unique<TensorMapT>();
map->name = graph->tensors[i]->name;
if (map->name.empty()) {
map->name = absl::StrFormat("output_%d", i);
}
map->tensor_index = i;
def->outputs.push_back(std::move(map));
}
model->signature_defs.push_back(std::move(def));
// Pack and build a new FlatBufferModel from the modified model.
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<Model> packed_model = Model::Pack(fbb, model.get());
FinishModelBuffer(fbb, packed_model);
auto data = std::make_unique<uint8_t[]>(fbb.GetSize());
memcpy(data.get(), fbb.GetBufferPointer(), fbb.GetSize());
auto allocation =
std::make_unique<OwnedMemoryAllocation>(std::move(data), fbb.GetSize());
return FlatBufferModel::VerifyAndBuildFromAllocation(std::move(allocation));
}
} // namespace
bool AllowFp16PrecisionForFp32() {
return tflite::KernelTestDelegateProviders::Get()->ConstParams().Get<bool>(
tflite::KernelTestDelegateProviders::kAllowFp16PrecisionForFp32);
}
Matcher<std::tuple<float, float>> FloatingPointEq() {
if (AllowFp16PrecisionForFp32()) {
return Fp16Eq();
}
return Eq();
}
Matcher<std::tuple<float, float>> FloatingPointAlmostEq() {
if (AllowFp16PrecisionForFp32()) {
return Fp16Eq();
}
return FloatEq();
}
std::vector<Matcher<std::complex<float>>> ArrayComplex64Near(
const std::vector<std::complex<float>>& values, float max_abs_error) {
std::vector<Matcher<std::complex<float>>> matchers;
matchers.reserve(values.size());
for (const std::complex<float>& v : values) {
matchers.emplace_back(
AllOf(::testing::Property(&std::complex<float>::real,
FloatNear(v.real(), max_abs_error)),
::testing::Property(&std::complex<float>::imag,
FloatNear(v.imag(), max_abs_error))));
}
return matchers;
}
int SingleOpModel::AddInput(const TensorData& t) {
int id = 0;
if (t.per_block_quantization != 0) {
id = AddTensorPerBlockQuant(t);
} else if (t.per_channel_quantization) {
id = AddTensorPerChannelQuant(t);
} else {
id = AddTensor<float>(t, nullptr, 0);
}
inputs_.push_back(id);
return id;
}
int SingleOpModel::AddVariableInput(const TensorData& t) {
int id = 0;
if (t.per_channel_quantization) {
id = AddTensorPerChannelQuant(t);
} else {
id = AddTensor<float>(t, nullptr, 0, true);
}
inputs_.push_back(id);
return id;
}
int SingleOpModel::AddIntermediate(TensorType type,
const std::vector<float>& scale,
const std::vector<int64_t>& zero_point) {
// Currently supports only int16 intermediate types.
int id = tensors_.size();
flatbuffers::Offset<QuantizationParameters> q_params =
CreateQuantizationParameters(builder_, /*min=*/0, /*max=*/0,
builder_.CreateVector<float>(scale),
builder_.CreateVector<int64_t>(zero_point));
std::vector<int> empty;
tensors_.push_back(CreateTensor(builder_, builder_.CreateVector<int>(empty),
type,
/*buffer=*/0,
/*name=*/0, q_params, false));
intermediates_.push_back(id);
return id;
}
int SingleOpModel::AddNullInput() {
int id = kTfLiteOptionalTensor;
inputs_.push_back(id);
return id;
}
int SingleOpModel::AddOutput(const TensorData& t) {
int id = 0;
if (t.per_channel_quantization) {
id = AddTensorPerChannelQuant(t);
} else {
id = AddTensor<float>(t, nullptr, 0);
}
outputs_.push_back(id);
return id;
}
void SingleOpModel::SetBuiltinOp(BuiltinOperator type,
BuiltinOptions builtin_options_type,
flatbuffers::Offset<void> builtin_options) {
opcodes_.push_back(CreateOperatorCode(builder_, type, 0, 0));
operators_.push_back(CreateOperator(
builder_, /*opcode_index=*/0, builder_.CreateVector<int32_t>(inputs_),
builder_.CreateVector<int32_t>(outputs_), builtin_options_type,
builtin_options,
/*custom_options=*/0, CustomOptionsFormat_FLEXBUFFERS, 0,
builder_.CreateVector<int32_t>(intermediates_)));
}
void SingleOpModel::SetBuiltinOp(BuiltinOperator type,
BuiltinOptions2 builtin_options_2_type,
flatbuffers::Offset<void> builtin_options_2) {
opcodes_.push_back(CreateOperatorCode(builder_, type, 0, 0));
operators_.push_back(CreateOperator(
builder_, /*opcode_index=*/0, builder_.CreateVector<int32_t>(inputs_),
builder_.CreateVector<int32_t>(outputs_), tflite::BuiltinOptions_NONE,
/*builtin_options=*/0, /*custom_options=*/0,
CustomOptionsFormat_FLEXBUFFERS, /*mutating_variable_inputs=*/0,
builder_.CreateVector<int32_t>(intermediates_),
/*large_custom_options_offset=*/0,
/*large_custom_options_size=*/0, builtin_options_2_type,
builtin_options_2));
}
void SingleOpModel::SetCustomOp(
const string& name, const std::vector<uint8_t>& custom_option,
const std::function<TfLiteRegistration*()>& registration) {
custom_registrations_[name] = registration;
opcodes_.push_back(
CreateOperatorCodeDirect(builder_, BuiltinOperator_CUSTOM, name.data()));
operators_.push_back(CreateOperator(
builder_, /*opcode_index=*/0, builder_.CreateVector<int32_t>(inputs_),
builder_.CreateVector<int32_t>(outputs_), BuiltinOptions_NONE, 0,
builder_.CreateVector<uint8_t>(custom_option),
CustomOptionsFormat_FLEXBUFFERS));
}
TfLiteStatus SingleOpModel::AllocateTensors() {
TfLiteStatus status = interpreter_->AllocateTensors();
if (status == kTfLiteOk) {
interpreter_->ResetVariableTensors();
}
return status;
}
void SingleOpModel::AllocateAndDelegate(bool apply_delegate) {
// In some rare cases a test may need to postpone modifying the graph with
// a delegate, e.g. if tensors are not fully specified. In such cases the
// test has to explicitly call ApplyDelegate() when necessary.
if (apply_delegate) ApplyDelegate();
CHECK(interpreter_->AllocateTensors() == kTfLiteOk)
<< "Cannot allocate tensors";
interpreter_->ResetVariableTensors();
}
void SingleOpModel::BuildInterpreter(std::vector<std::vector<int>> input_shapes,
int num_threads,
bool allow_fp32_relax_to_fp16,
bool apply_delegate,
bool allocate_and_delegate) {
input_shapes_ = input_shapes;
allow_fp32_relax_to_fp16_ = allow_fp32_relax_to_fp16;
apply_delegate_ = apply_delegate;
allocate_and_delegate_ = allocate_and_delegate;
auto opcodes = builder_.CreateVector(opcodes_);
auto operators = builder_.CreateVector(operators_);
auto tensors = builder_.CreateVector(tensors_);
auto inputs = builder_.CreateVector<int32_t>(inputs_);
auto outputs = builder_.CreateVector<int32_t>(outputs_);
// Create a single subgraph
std::vector<flatbuffers::Offset<SubGraph>> subgraphs;
auto subgraph = CreateSubGraph(builder_, tensors, inputs, outputs, operators);
subgraphs.push_back(subgraph);
auto subgraphs_flatbuffer = builder_.CreateVector(subgraphs);
auto buffers = builder_.CreateVector(buffers_);
auto description = builder_.CreateString("programmatic model");
builder_.Finish(CreateModel(builder_, TFLITE_SCHEMA_VERSION, opcodes,
subgraphs_flatbuffer, description, buffers));
uint8_t* buffer_pointer = builder_.GetBufferPointer();
UpdateOpVersion(buffer_pointer);
bool use_simple_allocator =
tflite::KernelTestDelegateProviders::Get()->ConstParams().Get<bool>(
tflite::KernelTestDelegateProviders::kUseSimpleAllocator);
if (!resolver_) {
if (!bypass_default_delegates_) {
// Check if any delegates are specified via the commandline flags. We also
// assume the intention of the test is to test against a particular
// delegate, hence bypassing applying TfLite default delegates (i.e. the
// XNNPACK delegate).
const auto specified_delegates =
tflite::KernelTestDelegateProviders::Get()->CreateAllDelegates();
if (!specified_delegates.empty()) {
bypass_default_delegates_ = true;
}
}
MutableOpResolver* resolver =
(bypass_default_delegates_ || use_simple_allocator)
? new ops::builtin::BuiltinOpResolverWithoutDefaultDelegates()
: new ops::builtin::BuiltinOpResolver();
for (const auto& reg : custom_registrations_) {
resolver->AddCustom(reg.first.data(), reg.second());
}
resolver_ = std::unique_ptr<OpResolver>(resolver);
}
CHECK(InterpreterBuilder(GetModel(buffer_pointer), *resolver_)(
&interpreter_, num_threads) == kTfLiteOk);
CHECK(interpreter_ != nullptr);
if (use_simple_allocator) {
LOG(INFO) << "Use SimplePlanner.\n";
tflite::Subgraph& primary_subgraph = interpreter_->primary_subgraph();
auto memory_planner = new SimplePlanner(
&primary_subgraph.context_,
std::unique_ptr<GraphInfo>(primary_subgraph.CreateGraphInfo()));
primary_subgraph.memory_planner_.reset(memory_planner);
memory_planner->PlanAllocations();
}
for (size_t i = 0; i < input_shapes.size(); ++i) {
const int input_idx = interpreter_->inputs()[i];
if (input_idx == kTfLiteOptionalTensor) continue;
const auto& shape = input_shapes[i];
if (shape.empty()) continue;
CHECK(interpreter_->ResizeInputTensor(input_idx, shape) == kTfLiteOk);
}
interpreter_->SetAllowFp16PrecisionForFp32(allow_fp32_relax_to_fp16);
if (allocate_and_delegate) {
AllocateAndDelegate(apply_delegate);
}
}
TfLiteStatus SingleOpModel::ApplyDelegate() {
if (delegate_) {
TFLITE_LOG(WARN) << "Having a manually-set TfLite delegate, and bypassing "
"KernelTestDelegateProviders";
SetDelegateApplicationStatus(
interpreter_->ModifyGraphWithDelegate(delegate_.get()));
TF_LITE_ENSURE_STATUS(*GetDelegateApplicationStatus());
last_applied_delegate_ = delegate_.get();
++num_applied_delegates_;
} else {
auto* delegate_providers = tflite::KernelTestDelegateProviders::Get();
// Most TFLite NNAPI delegation tests have been written to run against the
// NNAPI CPU path. We'll enable that for tests. However, need to first check
// if the parameter is present - it will not be if the NNAPI delegate
// provider is not linked into the test.
if (delegate_providers->ConstParams().HasParam("disable_nnapi_cpu")) {
delegate_providers->MutableParams()->Set("disable_nnapi_cpu", false);
}
for (auto& one : delegate_providers->CreateAllDelegates()) {
// The raw ptr always points to the actual TfLiteDegate object.
auto* delegate_raw_ptr = one.delegate.get();
SetDelegateApplicationStatus(
interpreter_->ModifyGraphWithDelegate(std::move(one.delegate)));
TF_LITE_ENSURE_STATUS(*GetDelegateApplicationStatus());
// Note: 'last_applied_delegate_' is always set to the last successfully
// applied one.
last_applied_delegate_ = delegate_raw_ptr;
++num_applied_delegates_;
}
}
return kTfLiteOk;
}
TfLiteStatus SingleOpModel::Invoke() { return interpreter_->Invoke(); }
void SingleOpModel::BuildInterpreter(
std::vector<std::vector<int>> input_shapes) {
BuildInterpreter(input_shapes, /*num_threads=*/-1,
/*allow_fp32_relax_to_fp16=*/false,
/*apply_delegate=*/true, /*allocate_and_delegate=*/true);
}
// static
bool SingleOpModel::GetForceUseNnapi() {
const auto& delegate_params =
tflite::KernelTestDelegateProviders::Get()->ConstParams();
// It's possible this library isn't linked with the nnapi delegate provider
// lib.
return delegate_params.HasParam("use_nnapi") &&
delegate_params.Get<bool>("use_nnapi");
}
int32_t SingleOpModel::GetTensorSize(int index) const {
TfLiteTensor* t = interpreter_->tensor(index);
CHECK(t);
int total_size = 1;
for (int i = 0; i < t->dims->size; ++i) {
total_size *= t->dims->data[i];
}
return total_size;
}
template <>
std::vector<string> SingleOpModel::ExtractVector(int index) const {
TfLiteTensor* tensor_ptr = interpreter_->tensor(index);
CHECK(tensor_ptr != nullptr);
const int num_strings = GetStringCount(tensor_ptr);
std::vector<string> result;
result.reserve(num_strings);
for (int i = 0; i < num_strings; ++i) {
const auto str = GetString(tensor_ptr, i);
result.emplace_back(str.str, str.len);
}
return result;
}
namespace {
// Returns the number of partitions associated, as result of a call to
// ModifyGraphWithDelegate, to the given delegate.
int CountPartitionsDelegatedTo(Subgraph* subgraph,
const TfLiteDelegate* delegate) {
return std::count_if(
subgraph->nodes_and_registration().begin(),
subgraph->nodes_and_registration().end(),
[delegate](
std::pair<TfLiteNode, TfLiteRegistration> node_and_registration) {
return node_and_registration.first.delegate == delegate;
});
}
// Returns the number of partitions associated, as result of a call to
// ModifyGraphWithDelegate, to the given delegate.
int CountPartitionsDelegatedTo(Interpreter* interpreter,
const TfLiteDelegate* delegate) {
int result = 0;
for (int i = 0; i < interpreter->subgraphs_size(); i++) {
Subgraph* subgraph = interpreter->subgraph(i);
result += CountPartitionsDelegatedTo(subgraph, delegate);
}
return result;
}
// Returns the number of nodes that will be executed on the CPU
int CountPartitionsExecutedByCpuKernel(const Interpreter* interpreter) {
int result = 0;
for (int node_idx : interpreter->execution_plan()) {
TfLiteNode node;
TfLiteRegistration reg;
std::tie(node, reg) = *(interpreter->node_and_registration(node_idx));
if (node.delegate == nullptr) {
++result;
}
}
return result;
}
} // namespace
/*static*/ AccelerationValidator* AccelerationValidator::Get() {
static AccelerationValidator* const validator = new AccelerationValidator();
return validator;
}
void AccelerationValidator::AddCallback(Callback callback) {
callbacks_.push_back(std::move(callback));
}
void AccelerationValidator::Validate(const SingleOpModel& model) const {
for (const auto& callback : callbacks_) {
if (callback == nullptr) continue;
callback(model);
}
}
void SingleOpModel::ExpectOpAcceleratedWithNnapi(const std::string& test_id) {
std::optional<NnapiAccelerationTestParams> validation_params =
GetNnapiAccelerationTestParam(test_id);
if (!validation_params.has_value()) {
return;
}
// If we have multiple delegates applied, we would skip this check at the
// moment.
if (num_applied_delegates_ > 1) {
TFLITE_LOG(WARN) << "Skipping ExpectOpAcceleratedWithNnapi as "
<< num_applied_delegates_
<< " delegates have been successfully applied.";
return;
}
TFLITE_LOG(INFO) << "Validating acceleration";
const NnApi* nnapi = NnApiImplementation();
if (nnapi && nnapi->nnapi_exists &&
nnapi->android_sdk_version >=
validation_params.value().MinAndroidSdkVersion()) {
EXPECT_EQ(
CountPartitionsDelegatedTo(interpreter_.get(), last_applied_delegate_),
1)
<< "Expecting operation to be accelerated but cannot find a partition "
"associated to the NNAPI delegate";
EXPECT_GT(num_applied_delegates_, 0) << "No delegates were applied.";
}
}
void SingleOpModel::ValidateAcceleration() {
if (GetForceUseNnapi()) {
ExpectOpAcceleratedWithNnapi(GetCurrentTestId());
}
AccelerationValidator::Get()->Validate(*this);
}
int SingleOpModel::CountOpsExecutedByCpuKernel() {
return CountPartitionsExecutedByCpuKernel(interpreter_.get());
}
int SingleOpModel::CountNumberOfDelegatedPartitions() const {
return CountPartitionsDelegatedTo(interpreter_.get(), last_applied_delegate_);
}
void SingleOpModel::MaybeDumpModel() {
std::string dump_directory(
tflite::KernelTestDelegateProviders::Get()
->ConstParams()
.Get<std::string>(
tflite::KernelTestDelegateProviders::kDumpTFLiteModelDir));
// If no path provided, we don't need to dump the model.
if (dump_directory.empty()) {
return;
}
// If the interpreter is not initialized, there is no model to be dumped.
if (interpreter_ == nullptr) {
TFLITE_LOG(INFO) << "Interpreter is not initialized, skipping model dump.";
return;
}
std::string output_file_name = GetDumpedModelName();
if (output_file_name.empty()) {
return;
}
// Get the model buffer.
std::unique_ptr<uint8_t[]> buffer;
size_t size = 0;
if (ModelWriter(interpreter_.get()).GetBuffer(&buffer, &size) != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to get model buffer";
return;
}
auto model = tflite::FlatBufferModel::BuildFromBuffer(
reinterpret_cast<char*>(buffer.get()), size);
if (model == nullptr) {
TFLITE_LOG(ERROR) << "Failed to build model from buffer";
return;
}
// Modify the model to set the input tensors to the model buffers and add
// signature def.
auto modified_model = ModifyDumpedModel(std::move(model), interpreter_.get());
const Allocation* allocation = modified_model->allocation();
// Save the model to file
std::string output_file_path =
absl::StrCat(dump_directory, "/", output_file_name);
TFLITE_LOG(INFO) << "Saving model to " << output_file_path;
std::ofstream output_file(output_file_path);
output_file.write(reinterpret_cast<const char*>(allocation->base()),
allocation->bytes());
output_file.close();
}
SingleOpModel::~SingleOpModel() {
MaybeDumpModel();
ValidateAcceleration();
}
void MultiOpModel::AddBuiltinOp(
BuiltinOperator type, BuiltinOptions builtin_options_type,
const flatbuffers::Offset<void>& builtin_options,
const std::vector<int32_t>& inputs, const std::vector<int32_t>& outputs) {
opcodes_.push_back(CreateOperatorCode(builder_, type, 0, 0));
const int opcode_index = opcodes_.size() - 1;
operators_.push_back(CreateOperator(
builder_, opcode_index, builder_.CreateVector<int32_t>(inputs),
builder_.CreateVector<int32_t>(outputs), builtin_options_type,
builtin_options,
/*custom_options=*/0, CustomOptionsFormat_FLEXBUFFERS));
}
void MultiOpModel::AddCustomOp(
const string& name, const std::vector<uint8_t>& custom_option,
const std::function<TfLiteRegistration*()>& registration,
const std::vector<int32_t>& inputs, const std::vector<int32_t>& outputs) {
custom_registrations_[name] = registration;
opcodes_.push_back(
CreateOperatorCodeDirect(builder_, BuiltinOperator_CUSTOM, name.data()));
const int opcode_index = opcodes_.size() - 1;
operators_.push_back(CreateOperator(
builder_, opcode_index, builder_.CreateVector<int32_t>(inputs),
builder_.CreateVector<int32_t>(outputs), BuiltinOptions_NONE, 0,
builder_.CreateVector<uint8_t>(custom_option),
CustomOptionsFormat_FLEXBUFFERS));
}
} // namespace tflite