chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
@@ -0,0 +1,178 @@
# Copyright 2022 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.
# ==============================================================================
# A sample delegate that supports addition and subtraction only.
load("//tensorflow/lite:build_def.bzl", "tflite_cc_shared_object", "tflite_copts", "tflite_linkopts_no_undefined")
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "cc_library_with_tflite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library_with_tflite(
name = "sample_stable_delegate",
testonly = True,
srcs = [
"sample_stable_delegate.cc",
],
hdrs = [
"sample_stable_delegate.h",
],
copts = tflite_copts(),
generate_opaque_delegate_target = True,
tflite_deps = [
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/delegates/utils:simple_opaque_delegate",
"//tensorflow/lite/kernels:builtin_ops",
],
deps = [
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
],
)
cc_test(
name = "sample_stable_delegate_test",
srcs = ["sample_stable_delegate_test.cc"],
copts = tflite_copts(),
data = [
"//tensorflow/lite:testdata/add.bin",
"//tensorflow/lite:testdata/sub.bin",
],
deps = [
":sample_stable_delegate_opaque_delegate",
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/kernels:kernel_util",
"@com_google_googletest//:gtest_main",
],
)
cc_library_with_tflite(
name = "sample_stable_delegate_external",
testonly = True,
srcs = [
"sample_stable_delegate_external.cc",
],
copts = tflite_copts(),
generate_opaque_delegate_target = True,
tflite_deps = [
":sample_stable_delegate",
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/delegates/utils:simple_opaque_delegate",
"//tensorflow/lite/delegates/utils/experimental/stable_delegate:stable_delegate_interface",
"//tensorflow/lite/acceleration/configuration/c:delegate_plugin",
"//tensorflow/lite/acceleration/configuration/c:stable_delegate",
],
)
tflite_cc_shared_object(
name = "tensorflowlite_sample_stable_delegate",
testonly = True,
linkopts = tflite_linkopts_no_undefined() + select({
"//tensorflow:windows": [],
"//conditions:default": [
# Expose necessary symbols only.
"-Wl,--version-script,$(location //tensorflow/lite/delegates/utils/experimental/stable_delegate:version_script.lds)",
],
}),
per_os_targets = True,
deps = [
":sample_stable_delegate_external_opaque_delegate",
"//tensorflow/lite/delegates/utils/experimental/stable_delegate:version_script.lds",
],
)
cc_test(
name = "sample_stable_delegate_external_test",
srcs = ["sample_stable_delegate_external_test.cc"],
data = [
":tensorflowlite_sample_stable_delegate",
"//tensorflow/lite:testdata/add.bin",
],
deps = [
":sample_stable_delegate_opaque_delegate",
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/delegates/utils/experimental/stable_delegate:delegate_loader",
"//tensorflow/lite/kernels:kernel_util",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "sample_stable_delegate_kernel_tests",
timeout = "moderate",
args = ["--stable_delegate_settings_file=$(location stable_delegate_settings.json)"],
data = [
"stable_delegate_settings.json",
":tensorflowlite_sample_stable_delegate",
],
# This shard_count value of 30 was chosen because empirically this seems to be near the sweet
# spot that minimizes test time while not wasting resources with unnecessary shards.
# This runs roughly 30% faster than with shard_count = 20, but values beyond this show little
# improvement, e.g. shard_count = 40 gave only a few per cent improvement.
shard_count = 30,
deps = [
"//tensorflow/lite/kernels:combined_all_kernel_tests_lib",
"//tensorflow/lite/kernels:test_main",
],
)
genrule(
name = "gen_stable_delegate_settings",
outs = ["stable_delegate_settings.json"],
cmd = """
echo '
{
"stable_delegate_loader_settings": {
"delegate_path": "tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so"
}
}
' > $@
""",
)
cc_binary(
name = "sample_app_using_stable_delegate",
testonly = True,
srcs = ["sample_app_using_stable_delegate.cc"],
data = [
":tensorflowlite_sample_stable_delegate",
"//tensorflow/lite:testdata/add.bin",
],
deps = [
"//tensorflow/lite:framework_stable",
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/delegates/utils/experimental/stable_delegate:delegate_loader",
"//tensorflow/lite/kernels:builtin_ops",
],
)
@@ -0,0 +1,260 @@
# TensorFlow Lite Sample Stable Delegate
## Description
An example delegate for stable delegate testing that supports only a few
operations: addition, subtraction, multiplication, equality check, and while
loops.
The sample stable delegate implementation uses the stable delegate API, which is
based around `TfLiteOpaqueDelegate`. `TfLiteOpaqueDelegate` is an opaque version
of `TfLiteDelegate`; which allows delegation of nodes to alternative backends.
This is an abstract type that is intended to have the same role as
`TfLiteDelegate` from
[common.h](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/c/common.h),
but without exposing the implementation details of how delegates are
implemented.
`TfLiteOpaqueDelegate`s can be loaded dynamically (see
`sample_stable_delegate_external_test.cc`) and then be supplied to the TFLite
runtime, in the same way as statically linked delegates can.
Note however that open-source TF Lite does not (yet) provide a binary stable
interface between delegates and the TF Lite runtime itself. Therefore any opaque
delegate that is loaded dynamically into TF Lite *must* have been built against
the same version (and commit) that the TF Lite runtime itself has been built at.
Any other configuration can lead to undefined behavior.
## Delegate implementation
The sample stable delegate uses two supporting interfaces
[SimpleOpaqueDelegateInterface and SimpleOpaqueDelegateKernelInterface](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/simple_opaque_delegate.h).
These APIs make it easier to implement an opaque TF Lite delegate, though their
usage is entirely optional.
The `sample_stable_delegate_test` driver (see next section) makes use of the
[TfLiteOpaqueDelegateFactory](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/simple_opaque_delegate.h)
facility, which provides static methods that deal with delegate creation and
deletion.
## Testing
See
[sample_stable_delegate_test.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate_test.cc)
for a standalone test driver that links the sample stable delegate statically
and runs inference on a TF Lite model.
See
[sample_stable_delegate_external_test.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate_external_test.cc)
for a standalone test driver that loads the sample stable delegate dynamically
and runs inference on a TF Lite model.
### Delegate Test Suite
The Delegate Test Suite provides correctness testing for a delegate at the
operation level. It checks whether a delegate produces results that meet the
accuracy thresholds of the supported operations.
Support for stable delegate binaries has been integrated into the Delegate Test
Suite.
#### Run on Android
The following instructions show how to run the test suite on Android.
First, we build the sample stable delegate shared library file,
`libtensorflowlite_sample_stable_delegate.so`, which we will later load
dynamically as part of the test:
```bash
bazel build -c opt --config=android_arm64 //tensorflow/lite/delegates/utils/experimental/sample_stable_delegate:tensorflowlite_sample_stable_delegate
adb push "$(bazel info -c opt --config=android_arm64 bazel-bin)"/tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so /data/local/tmp
```
Next, we create a configuration file for the component that loads the stable
delegate:
```bash
adb shell 'echo "{
\"stable_delegate_loader_settings\": {
\"delegate_path\": \"/data/local/tmp/libtensorflowlite_sample_stable_delegate.so\"
}
// Add concrete delegate settings for the test target delegate.
}
"> /data/local/tmp/stable_delegate_settings.json'
```
We create a configuration file for the delegate test suite to verify that the
models in the specified test cases have been delegated:
```bash
adb shell 'echo "
# The sample stable delegate supports static-sized addition and subtraction operations.
FloatSubOpModel.NoActivation
FloatSubOpModel.VariousInputShapes
FloatAddOpModel.NoActivation
FloatAddOpModel.VariousInputShapes
"> /data/local/tmp/stable_delegate_acceleration_test_config.json'
```
Then, we build the test suite itself:
```bash
bazel build -c opt --config=android_arm64 //tensorflow/lite/delegates/utils/experimental/stable_delegate:stable_delegate_test_suite
adb push "$(bazel info -c opt --config=android_arm64 bazel-bin)"/tensorflow/lite/delegates/utils/experimental/stable_delegate/stable_delegate_test_suite /data/local/tmp
```
Now, we can execute the test suite with providing the settings file:
```bash
adb shell "/data/local/tmp/stable_delegate_test_suite \
--stable_delegate_settings_file=/data/local/tmp/stable_delegate_settings.json \
--acceleration_test_config_path=/data/local/tmp/stable_delegate_acceleration_test_config.json"
```
You can also specify `gunit_filter` to only run a subset of tests. This can be
used to skip non release-blocking test cases (e.g. fp16 precision issues) which
are subject to Android ML team's approval. For example, the following command
would skip TestA, TestB and TestC.
```bash
adb shell "/data/local/tmp/stable_delegate_test_suite \
--stable_delegate_settings_file=/data/local/tmp/stable_delegate_settings.json \
--acceleration_test_config_path=/data/local/tmp/stable_delegate_acceleration_test_config.json \
--gunit_filter=-TestA:TestB:TestC"
```
The test suite will show the following output in console after all tests are
passed:
```
...
[==========] 3338 tests from 349 test suites ran. (24555 ms total)
[ PASSED ] 3338 tests.
```
### Benchmark Tools
#### Delegate Performance Benchmark app
The
[Delegate Performance Benchmark app](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/benchmark/experimental/delegate_performance/android/README.md)
is the recommended tool to test the latency and accuracy of a stable delegate.
#### TF Lite Benchmark Tool
During early development stages of a new stable delegate it can also be useful
to directly load the delegate's shared library file into TF Lite's
`benchmark_model` tool, because this development workflow works on regular linux
desktop machines and also allows users to benchmark any TF Lite model file they
are interested in.
Support for stable delegate binaries has been integrated into TF Lite's
[`benchmark_model`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark)
CLI tool. We can use this tool to test the sample stable delegate with a
provided TF Lite model file.
##### A) Run on a regular linux host
The following instructions show how to run the tool on regular desktop linux
machine.
First, we build the sample stable delegate shared library file,
`libtensorflowlite_sample_stable_delegate.so`, which we will later load
dynamically with the `benchmark_model` tool:
```bash
bazel build -c opt //tensorflow/lite/delegates/utils/experimental/sample_stable_delegate:tensorflowlite_sample_stable_delegate
```
Next, we create a configuration file for the component that loads the stable
delegate:
```bash
echo "{
\"stable_delegate_loader_settings\": {
\"delegate_path\": \"$(bazel info -c opt bazel-bin)/tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so\"
}
// Add concrete delegate settings for the test target delegate.
}
"> stable_delegate_settings.json
```
Then, we build the `benchmark_model` tool itself:
```bash
bazel build -c opt //tensorflow/lite/tools/benchmark:benchmark_model
```
Now, we can execute the benchmark tool. We provide the settings file together
with a TF Lite file that contains ADD operations. We do this because the sample
stable delegate only support ADD, SUB, MUL, EQUAL, and WHILE:
```bash
$(bazel info -c opt bazel-bin)/tensorflow/lite/tools/benchmark/benchmark_model \
--stable_delegate_settings_file=$(pwd)/stable_delegate_settings.json \
--graph=$(pwd)/tensorflow/lite/testdata/add.bin
```
Note that when you make changes to the sample delegate you need to rebuild the
delegate's shared library file, in order for benchmark_model to pick up the new
delegate code.
##### B) Run on Android
The following instructions show how to run the tool on Android.
First, we build the sample stable delegate shared library file,
`libtensorflowlite_sample_stable_delegate.so`, which we will later load
dynamically with the `benchmark_model` tool:
```bash
bazel build -c opt --config=android_arm64 //tensorflow/lite/delegates/utils/experimental/sample_stable_delegate:tensorflowlite_sample_stable_delegate
adb push "$(bazel info -c opt --config=android_arm64 bazel-bin)"/tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so /data/local/tmp
```
Next, we create a configuration file for the component that loads the stable
delegate:
```bash
adb shell 'echo "{
\"stable_delegate_loader_settings\": {
\"delegate_path\": \"/data/local/tmp/libtensorflowlite_sample_stable_delegate.so\"
}
// Add concrete delegate settings for the test target delegate.
}
"> /data/local/tmp/stable_delegate_settings.json'
```
Then, we build the `benchmark_model` tool itself:
```bash
bazel build -c opt --config=android_arm64 //tensorflow/lite/tools/benchmark:benchmark_model
adb push "$(bazel info -c opt --config=android_arm64 bazel-bin)"/tensorflow/lite/tools/benchmark/benchmark_model /data/local/tmp
```
Now, we can execute the benchmark tool. We provide the settings file together
with a TF Lite file that contains ADD operations. We do this because the sample
stable delegate only support ADD, SUB, MUL, EQUAL, and WHILE:
```bash
adb push tensorflow/lite/testdata/add.bin /data/local/tmp/add.bin
adb shell "/data/local/tmp/benchmark_model \
--stable_delegate_settings_file=/data/local/tmp/stable_delegate_settings.json \
--graph=/data/local/tmp/add.bin"
```
## Sample app
To show how to use the sample stable delegate, we have included a sample app
that uses it. You can build and run the sample app as follows:
```
bazel run -c opt \
//tensorflow/lite/delegates/utils/experimental/sample_stable_delegate:sample_app_using_stable_delegate \
tensorflow/lite/testdata/add.tflite
```
@@ -0,0 +1,157 @@
/* Copyright 2023 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.
==============================================================================*/
// An example app that uses the sample stable delegate.
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <memory>
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/c/c_api.h" // For TfLiteTensorByteSize.
#include "tensorflow/lite/c/c_api_types.h" // For kTfLiteOk
#include "tensorflow/lite/delegates/utils/experimental/stable_delegate/delegate_loader.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model_builder.h"
#ifdef NDEBUG
#define CHECK(x) ((void)(x)) // Avoid warnings for otherwise unused variables.
#else
#define CHECK(x) assert(x)
#endif
using tflite::TFLiteSettings;
using tflite::TFLiteSettingsBuilder;
using tflite::delegates::utils::LoadDelegateFromSharedLibrary;
bool EndsWith(const char* whole, const char* suffix) {
size_t whole_length = strlen(whole);
size_t suffix_length = strlen(suffix);
return whole_length >= suffix_length &&
strcmp(whole + whole_length - suffix_length, suffix) == 0;
}
int main(int argc, char* argv[]) {
// It might be nicer style to use absl command-line flags, but here we're
// trying to minimize dependencies to keep the example as simple as possible.
if (argc != 2) {
fprintf(stderr,
"Usage: sample_app_using_stable_delegate <tflite model>\n"
"\n"
"This program runs the model using the sample stable delegate,\n"
"passing in some arbitrary data as input to the model.\n"
"This sample app assumes that the model's inputs and outputs are\n"
"float tensors.\n");
return 1;
}
const char* filename = argv[1];
// Load a model from a file (the filename would typically end with ".tflite").
std::unique_ptr<tflite::FlatBufferModel> model =
tflite::FlatBufferModel::BuildFromFile(filename);
CHECK(model != nullptr);
// Load the example stable delegate plugin from a shared library file.
const TfLiteStableDelegate* stable_delegate_handle =
LoadDelegateFromSharedLibrary(
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so");
CHECK(stable_delegate_handle != nullptr);
// Build a TFLiteSettings flatbuffer.
// The one in this example is an empty flatbuffer,
// but additional delegate-specific parameters could
// be passed to the delegate via this flatbuffer.
flatbuffers::FlatBufferBuilder flatbuffer_builder;
TFLiteSettingsBuilder tflite_settings_builder(flatbuffer_builder);
// Additional delegate-specific parameters can be set here.
// The example stable delegate currently doesn't take any additional
// delegate-specific parameters, but here's an example of what it
// would like like if we wanted to pass additional parameters to the
// Google Edge TPU delegate, which does have additional parameters.
#if 0
tflite::GoogleEdgeTpuSettingsBuilder edgetpu_settings_builder(
flatbuffer_builder);
edgetpu_settings_builder.add_log_verbosity(10);
tflite_settings_builder.add_google_edgetpu_settings(
edgetpu_settings_builder.Finish());
#endif
flatbuffers::Offset<TFLiteSettings> tflite_settings =
tflite_settings_builder.Finish();
flatbuffer_builder.Finish(tflite_settings);
const TFLiteSettings* settings = flatbuffers::GetRoot<TFLiteSettings>(
flatbuffer_builder.GetBufferPointer());
CHECK(settings != nullptr);
// Construct the delegate instance, using the settings flatbuffer.
TfLiteOpaqueDelegate* opaque_delegate =
stable_delegate_handle->delegate_plugin->create(settings);
CHECK(opaque_delegate != nullptr);
// Construct the model interpreter, using the model and the delegate.
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
builder.AddDelegate(opaque_delegate);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);
CHECK(interpreter != nullptr);
// Allocate tensor buffers.
CHECK(interpreter->AllocateTensors() == kTfLiteOk);
// Fill input buffer.
// The only input to the test model is a single tensor of floats.
// We fill it with some arbitrary data.
float* input = interpreter->typed_input_tensor<float>(0);
int64_t num_input_elements =
TfLiteTensorByteSize(interpreter->input_tensor(0)) / sizeof(float);
for (int i = 0; i < num_input_elements; i++) {
input[i] = 111.222 * i; // Some arbitrary input data.
}
// Run inference.
CHECK(interpreter->Invoke() == kTfLiteOk);
// Get output buffer.
// The only output to the test model is a single tensor of floats.
float* output = interpreter->typed_output_tensor<float>(0);
int64_t num_output_elements =
TfLiteTensorByteSize(interpreter->output_tensor(0)) / sizeof(float);
// Print inputs and results of computation.
for (int i = 0; i < num_input_elements; i++) {
printf("input[%d] = %.3f\n", i, input[i]);
}
printf("\n");
for (int i = 0; i < num_output_elements; i++) {
printf("output[%d] = %.3f\n", i, output[i]);
}
// Verify results, if we're using a specific known model.
if (EndsWith(filename, "lite/testdata/add.bin")) {
CHECK(num_input_elements == num_output_elements);
for (int i = 0; i < num_output_elements; i++) {
// The add.bin model computes f(X) = (X + X) + X.
float expected_output_i = input[i] * 3.0;
CHECK(fabs(output[i] - expected_output_i) <= 0.0000001 * fabs(output[i]));
}
printf("SUCCEEDED\n");
}
return 0;
}
@@ -0,0 +1,233 @@
/* Copyright 2022 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/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
#include <memory>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "tensorflow/lite/c/builtin_op_data.h"
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/simple_opaque_delegate.h"
namespace tflite {
namespace example {
namespace {
class SampleStableDelegateKernel : public SimpleOpaqueDelegateKernelInterface {
bool IsExternalTensor(const TfLiteOpaqueTensor* opaque_tensor) const {
return external_tensors_.count(opaque_tensor) != 0;
}
void DeriveExternalTensors() {
for (const TfLiteOpaqueTensor* tensor : node_input_tensors_set_) {
if (node_output_tensors_set_.count(tensor) == 0) {
external_tensors_.insert(tensor);
}
}
for (const TfLiteOpaqueTensor* tensor : node_output_tensors_set_) {
if (node_input_tensors_set_.count(tensor) == 0) {
external_tensors_.insert(tensor);
}
}
}
public:
TfLiteStatus Init(TfLiteOpaqueContext* context,
const TfLiteOpaqueDelegateParams* params) override {
if (params->delegate == nullptr) return kTfLiteDelegateError;
context_ = context;
builtin_code_.resize(params->nodes_to_replace->size);
node_input_tensors_.resize(params->nodes_to_replace->size);
node_output_tensors_.resize(params->nodes_to_replace->size);
for (int i = 0; i < params->nodes_to_replace->size; ++i) {
const int node_index = params->nodes_to_replace->data[i];
TfLiteOpaqueNode* delegated_node = nullptr;
TfLiteOperator* delegated_node_registration = nullptr;
TfLiteOpaqueContextGetNodeAndRegistration(
context, node_index, &delegated_node, &delegated_node_registration);
auto input_tensor1 = TfLiteOpaqueNodeGetInput(context, delegated_node, 0);
node_input_tensors_[i].push_back(input_tensor1);
node_input_tensors_set_.insert(input_tensor1);
auto input_tensor2 = TfLiteOpaqueNodeGetInput(context, delegated_node, 1);
node_input_tensors_[i].push_back(input_tensor2);
node_input_tensors_set_.insert(input_tensor2);
auto output_tensor =
TfLiteOpaqueNodeGetOutput(context, delegated_node, 0);
node_output_tensors_[i] = output_tensor;
node_output_tensors_set_.insert(output_tensor);
builtin_code_[i] =
TfLiteOperatorGetBuiltInCode(delegated_node_registration);
}
// Determine which tensors are external (the TFLite runtime takes care
// of them) so that we know which tensors are 'internal' to this delegate.
// For the internal tensors we need to ensure they have memory allocated to
// store their data, and take care of re-sizing etc.
DeriveExternalTensors();
return kTfLiteOk;
}
TfLiteStatus Prepare(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* delegated_node) override {
if (external_tensors_.empty()) return kTfLiteOk;
const int kTheInputTensorSize =
helpers::CalculateNumElements((*external_tensors_.begin()));
for (std::vector<const TfLiteOpaqueTensor*>& vecs : node_input_tensors_) {
for (const TfLiteOpaqueTensor* tensor : vecs) {
if (IsExternalTensor(tensor)) continue;
std::vector<float>& vec_memory = internal_tensors_memory_[tensor];
vec_memory.resize(kTheInputTensorSize);
}
}
for (const TfLiteOpaqueTensor* tensor : node_output_tensors_) {
if (IsExternalTensor(tensor)) continue;
std::vector<float>& vec_memory = internal_tensors_memory_[tensor];
vec_memory.resize(kTheInputTensorSize);
}
return kTfLiteOk;
}
void ComputeImpl(float* input_1, float* input_2, float* output,
int builtin_code, int number_of_elements) {
for (int i = 0; i < number_of_elements; ++i) {
if (builtin_code == kTfLiteBuiltinAdd) {
output[i] = input_1[i] + input_2[i];
} else {
output[i] = input_1[i] - input_2[i];
}
}
}
float* GetRawDataSource(TfLiteOpaqueContext* context,
const TfLiteOpaqueTensor* tensor) {
if (IsExternalTensor(tensor)) {
return reinterpret_cast<float*>(TfLiteOpaqueTensorData(tensor));
} else {
return internal_tensors_memory_[tensor].data();
}
}
TfLiteStatus Eval(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* delegated_node) override {
for (int i = 0; i < node_input_tensors_.size(); ++i) {
float* input1 = GetRawDataSource(context, node_input_tensors_[i][0]);
float* input2 = GetRawDataSource(context, node_input_tensors_[i][1]);
float* output = GetRawDataSource(context, node_output_tensors_[i]);
// We assume that all input, output and intermediate tensors of the
// delegated subgraph have the same size.
ComputeImpl(input1, input2, output, builtin_code_[i],
helpers::CalculateNumElements(node_output_tensors_[i]));
}
return kTfLiteOk;
}
private:
std::vector<std::vector<const TfLiteOpaqueTensor*>> node_input_tensors_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> node_input_tensors_set_;
std::vector<const TfLiteOpaqueTensor*> node_output_tensors_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> node_output_tensors_set_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> external_tensors_;
absl::flat_hash_map<const TfLiteOpaqueTensor*, std::vector<float>>
internal_tensors_memory_;
TfLiteOpaqueContext* context_;
// Holds the builtin code of the ops.
// builtin_code_[i] is the type of node at index 'i'
std::vector<int> builtin_code_;
};
} // namespace
int helpers::CalculateNumElements(const TfLiteOpaqueTensor* opaque_tensor) {
int total_num_elements = 1;
for (int i = 0; i < TfLiteOpaqueTensorNumDims(opaque_tensor); ++i) {
total_num_elements *= TfLiteOpaqueTensorDim(opaque_tensor, i);
}
return total_num_elements;
}
bool SampleStableDelegate::IsNodeSupportedByDelegate(
const TfLiteOperator* registration_external, const TfLiteOpaqueNode* node,
TfLiteOpaqueContext* context) const {
TfLiteBuiltinOperator builtin_operator =
TfLiteOperatorGetBuiltInCode(registration_external);
void* builtin_data = TfLiteOpaqueNodeGetBuiltinData(node);
if (builtin_operator == kTfLiteBuiltinAdd) {
TfLiteAddParams* params = reinterpret_cast<TfLiteAddParams*>(builtin_data);
if (!params || params->activation != kTfLiteActNone) return false;
} else if (builtin_operator == kTfLiteBuiltinSub) {
TfLiteSubParams* params = reinterpret_cast<TfLiteSubParams*>(builtin_data);
if (!params || params->activation != kTfLiteActNone) return false;
} else {
return false;
}
// The delegate only accepts two inputs with float32 type.
if (TfLiteOpaqueNodeNumberOfInputs(node) != 2) return false;
const TfLiteOpaqueTensor* tensor_1 =
TfLiteOpaqueNodeGetInput(context, node, 0);
const TfLiteOpaqueTensor* tensor_2 =
TfLiteOpaqueNodeGetInput(context, node, 1);
if (!tensor_1 || TfLiteOpaqueTensorType(tensor_1) != kTfLiteFloat32)
return false;
if (!tensor_2 || TfLiteOpaqueTensorType(tensor_2) != kTfLiteFloat32)
return false;
// The delegate doesn't support broadcasting; it requires both inputs to have
// the same shape.
if (TfLiteOpaqueTensorNumDims(tensor_1) !=
TfLiteOpaqueTensorNumDims(tensor_2))
return false;
for (int i = 0; i < TfLiteOpaqueTensorNumDims(tensor_1); ++i) {
if (TfLiteOpaqueTensorDim(tensor_1, i) !=
TfLiteOpaqueTensorDim(tensor_2, i)) {
return false;
}
}
return true;
}
TfLiteStatus SampleStableDelegate::Initialize(TfLiteOpaqueContext* context) {
return kTfLiteOk;
}
const char* SampleStableDelegate::Name() const {
return kSampleStableDelegateName;
}
std::unique_ptr<SimpleOpaqueDelegateKernelInterface>
SampleStableDelegate::CreateDelegateKernelInterface() {
return std::make_unique<SampleStableDelegateKernel>();
}
} // namespace example
} // namespace tflite
@@ -0,0 +1,67 @@
/* Copyright 2022 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_H_
#include <memory>
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/simple_opaque_delegate.h"
namespace tflite {
namespace example {
namespace helpers {
int CalculateNumElements(const TfLiteOpaqueTensor* opaque_tensor);
} // namespace helpers
// LINT.IfChange
static const char kSampleStableDelegateName[] = "google_sample_delegate";
// LINT.ThenChange(Google-internal path)
static const char kSampleStableDelegateVersion[] = "1.0.0";
// A simple delegate that supports only addition and subtraction operations.
// Implements SimpleOpaqueDelegateInterface, and therefore the delegate can be
// easily be adapted to work with the stable TFLite delegate API via
// TfLiteOpaqueDelegateFactory.
class SampleStableDelegate : public SimpleOpaqueDelegateInterface {
public:
// SampleStableDelegate supports float32 input type only.
// Returns true if the inputs of 'node' are two tensors of float32 with the
// same shape and the operation is addition or subtraction (without fused
// activation).
bool IsNodeSupportedByDelegate(const TfLiteOperator* registration_external,
const TfLiteOpaqueNode* node,
TfLiteOpaqueContext* context) const override;
// No-op. The delegate doesn't have extra steps to perform during
// initialization.
TfLiteStatus Initialize(TfLiteOpaqueContext* context) override;
// Returns a name that identifies the delegate.
const char* Name() const override;
// Returns an instance of SampleStableDelegateKernel that implements
// SimpleOpaqueDelegateKernelInterface. SampleStableDelegateKernel describes
// how a subgraph is delegated and the concrete evaluation of both addition
// and subtraction operations to be performed by the delegate.
std::unique_ptr<SimpleOpaqueDelegateKernelInterface>
CreateDelegateKernelInterface() override;
};
} // namespace example
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_H_
@@ -0,0 +1,62 @@
/* Copyright 2022 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 <memory>
#include <utility>
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
#include "tensorflow/lite/delegates/utils/experimental/stable_delegate/stable_delegate_interface.h"
#include "tensorflow/lite/delegates/utils/simple_opaque_delegate.h"
namespace {
TfLiteOpaqueDelegate* SampleStableDelegateCreateFunc(
const void* tflite_settings) {
auto delegate = std::make_unique<tflite::example::SampleStableDelegate>();
return tflite::TfLiteOpaqueDelegateFactory::CreateSimpleDelegate(
std::move(delegate));
}
void SampleStableDelegateDestroyFunc(
TfLiteOpaqueDelegate* sample_stable_delegate) {
tflite::TfLiteOpaqueDelegateFactory::DeleteSimpleDelegate(
sample_stable_delegate);
}
int SampleStableDelegateErrnoFunc(
TfLiteOpaqueDelegate* sample_stable_delegate) {
// no-op
return 0;
}
const TfLiteOpaqueDelegatePlugin sample_stable_delegate_plugin = {
SampleStableDelegateCreateFunc, SampleStableDelegateDestroyFunc,
SampleStableDelegateErrnoFunc};
const TfLiteStableDelegate sample_stable_delegate = {
TFL_STABLE_DELEGATE_ABI_VERSION, tflite::example::kSampleStableDelegateName,
tflite::example::kSampleStableDelegateVersion,
&sample_stable_delegate_plugin};
} // namespace
/**
* A super simple test delegate for testing.
*/
extern "C" const TfLiteStableDelegate TFL_TheStableDelegate =
sample_stable_delegate;
@@ -0,0 +1,130 @@
/* Copyright 2023 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 <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
#include "tensorflow/lite/delegates/utils/experimental/stable_delegate/delegate_loader.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace {
using tflite::TFLiteSettings;
using tflite::TFLiteSettingsBuilder;
using tflite::delegates::utils::LoadDelegateFromSharedLibrary;
TEST(SampleStableDelegate, LoadFromSharedLibraryFile) {
// Load the example stable opaque_delegate that implements the ADD operation
// from a shared library file.
const TfLiteStableDelegate* stable_delegate_handle =
LoadDelegateFromSharedLibrary(
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so");
ASSERT_NE(stable_delegate_handle, nullptr);
EXPECT_STREQ(stable_delegate_handle->delegate_abi_version,
TFL_STABLE_DELEGATE_ABI_VERSION);
EXPECT_STREQ(stable_delegate_handle->delegate_name,
tflite::example::kSampleStableDelegateName);
EXPECT_STREQ(stable_delegate_handle->delegate_version,
tflite::example::kSampleStableDelegateVersion);
ASSERT_NE(stable_delegate_handle->delegate_plugin, nullptr);
}
TEST(SampleStableDelegate, LoadFromSharedLibraryTestFile) {
// Load the example stable opaque_delegate that implements the ADD operation
// from a shared library file.
const TfLiteStableDelegate* stable_delegate_handle =
LoadDelegateFromSharedLibrary(
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate/"
"libtensorflowlite_sample_stable_delegate.so");
ASSERT_NE(stable_delegate_handle, nullptr);
EXPECT_STREQ(stable_delegate_handle->delegate_abi_version,
TFL_STABLE_DELEGATE_ABI_VERSION);
EXPECT_STREQ(stable_delegate_handle->delegate_name,
tflite::example::kSampleStableDelegateName);
EXPECT_STREQ(stable_delegate_handle->delegate_version,
tflite::example::kSampleStableDelegateVersion);
ASSERT_NE(stable_delegate_handle->delegate_plugin, nullptr);
// Build TFLiteSettings flatbuffer and pass into opaque_delegate plugin
// create method.
flatbuffers::FlatBufferBuilder flatbuffer_builder;
TFLiteSettingsBuilder tflite_settings_builder(flatbuffer_builder);
flatbuffers::Offset<TFLiteSettings> tflite_settings =
tflite_settings_builder.Finish();
flatbuffer_builder.Finish(tflite_settings);
const TFLiteSettings* settings = flatbuffers::GetRoot<TFLiteSettings>(
flatbuffer_builder.GetBufferPointer());
TfLiteOpaqueDelegate* opaque_delegate =
stable_delegate_handle->delegate_plugin->create(settings);
ASSERT_NE(opaque_delegate, nullptr);
// Create the model and the interpreter
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/add.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate);
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
// Allocate the tensors and fill the input tensor.
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
const float kTensorCellValue = 3.f;
std::int64_t n = tflite::NumElements(input_tensor);
std::vector<float> input(n, kTensorCellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
input.size() * sizeof(float)),
kTfLiteOk);
// Run the interpreter and read the output tensor.
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'add.bin' model does the following operation ('t_output' denotes the
// single output tensor, and 't_input' denotes the single input tensor):
//
// t_output = t_input + t_input + t_input = t_input * 3
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensorCellValue * 3);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
stable_delegate_handle->delegate_plugin->destroy(opaque_delegate);
}
} // namespace
@@ -0,0 +1,163 @@
/* Copyright 2023 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/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
#include <cstdint>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace {
TEST(SampleStableDelegate, StaticallyLinkedDelegateAndModelWithAdd) {
// Create an instance of the sample stable delegate that implements the ADD
// operation.
tflite::TfLiteOpaqueDelegateUniquePtr opaque_delegate =
tflite::TfLiteOpaqueDelegateFactory::Create(
std::make_unique<tflite::example::SampleStableDelegate>());
ASSERT_NE(opaque_delegate, nullptr);
//
// Create the model and the interpreter
//
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/add.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
//
// Allocate the tensors and fill the input tensor.
//
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
const float kTensorCellValue = 3.f;
int64_t n = tflite::NumElements(input_tensor);
std::vector<float> input(n, kTensorCellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
input.size() * sizeof(float)),
kTfLiteOk);
//
// Run the interpreter and read the output tensor.
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'add.bin' model does the following operation ('t_output' denotes the
// single output tensor, and 't_input' denotes the single input tensor):
//
// t_output = t_input + t_input + t_input = t_input * 3
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensorCellValue * 3);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TEST(SampleStableDelegate, StaticallyLinkedDelegateAndModelWithSub) {
// Create an instance of the sample stable delegate that implements the SUB
// operation.
tflite::TfLiteOpaqueDelegateUniquePtr opaque_delegate =
tflite::TfLiteOpaqueDelegateFactory::Create(
std::make_unique<tflite::example::SampleStableDelegate>());
ASSERT_NE(opaque_delegate, nullptr);
//
// Create the model and the interpreter
//
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/sub.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
//
// Allocate the tensors and fill the input tensor.
//
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor_0 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor_0, nullptr);
const float kTensor0CellValue = 3.f;
int64_t n = tflite::NumElements(input_tensor_0);
std::vector<float> input_0(n, kTensor0CellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor_0, input_0.data(),
input_0.size() * sizeof(float)),
kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor_1 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/1);
ASSERT_NE(input_tensor_1, nullptr);
n = tflite::NumElements(input_tensor_1);
const float kTensor1CellValue = 2.f;
std::vector<float> input_1(n, kTensor1CellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor_1, input_1.data(),
input_1.size() * sizeof(float)),
kTfLiteOk);
//
// Run the interpreter and read the output tensor.
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'sub.bin' model does the following operation ('t_output' denotes the
// single output tensor, and 't_input_0' and 't_input_1' denote the two input
// tensors):
//
// t_output = t_input_0 - t_input_1
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensor0CellValue - kTensor1CellValue);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
} // namespace
@@ -0,0 +1,602 @@
/* Copyright 2022 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/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate_with_control_flow.h"
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "tensorflow/lite/c/builtin_op_data.h"
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/simple_opaque_delegate.h"
namespace tflite {
namespace example {
// This is not an actual subgraph index, but a placeholder index to store
// the node inputs/outputs/builtin_code information for the initial entry
// subgraph layer. For the entry subgraph, the subgraph index is not used
// for computation, so this index is safe to use.
static const int kTopLevelSubgraphIndex = -1;
namespace {
class SampleStableDelegateKernel : public SimpleOpaqueDelegateKernelInterface {
bool IsExternalTensor(const TfLiteOpaqueTensor* opaque_tensor) const {
return external_tensors_.count(opaque_tensor) != 0;
}
void DeriveExternalTensors() {
for (const TfLiteOpaqueTensor* tensor : node_input_tensors_set_) {
if (node_output_tensors_set_.count(tensor) == 0) {
external_tensors_.insert(tensor);
}
}
for (const TfLiteOpaqueTensor* tensor : node_output_tensors_set_) {
if (node_input_tensors_set_.count(tensor) == 0) {
external_tensors_.insert(tensor);
}
}
}
public:
TfLiteStatus Init(TfLiteOpaqueContext* context,
const TfLiteOpaqueDelegateParams* params) override {
if (params->delegate == nullptr) return kTfLiteDelegateError;
context_ = context;
std::vector<int> callee_subgraph_indices;
TfLiteStatus status =
InitSubgraphNodes(context, kTopLevelSubgraphIndex,
params->nodes_to_replace, callee_subgraph_indices);
if (status != kTfLiteOk) return status;
// Determine which tensors are external (the TFLite runtime takes care
// of them) so that we know which tensors are 'internal' to this delegate.
// For the internal tensors we need to ensure they have memory allocated to
// store their data, and take care of re-sizing etc.
DeriveExternalTensors();
return kTfLiteOk;
}
TfLiteStatus InitSubgraphNodes(TfLiteOpaqueContext* context,
int subgraph_index,
const TfLiteIntArray* nodes_to_execute,
std::vector<int>& callee_subgraph_indices) {
node_input_tensors_[subgraph_index].resize(nodes_to_execute->size);
node_output_tensors_[subgraph_index].resize(nodes_to_execute->size);
builtin_codes_[subgraph_index].resize(nodes_to_execute->size);
for (int i = 0; i < nodes_to_execute->size; ++i) {
const int node_index = nodes_to_execute->data[i];
TfLiteOpaqueNode* delegated_node = nullptr;
TfLiteOperator* delegated_node_registration = nullptr;
TfLiteOpaqueContextGetNodeAndRegistration(
context, node_index, &delegated_node, &delegated_node_registration);
builtin_codes_[subgraph_index][i] =
TfLiteOperatorGetBuiltInCode(delegated_node_registration);
for (int n = 0; n < TfLiteOpaqueNodeNumberOfInputs(delegated_node); ++n) {
auto input_tensor =
TfLiteOpaqueNodeGetInput(context, delegated_node, n);
node_input_tensors_[subgraph_index][i].push_back(input_tensor);
if (subgraph_index == kTopLevelSubgraphIndex) {
// node_input_tensors_set_ is used for deriving external tensors. For
// this sample delegate, we only derive external tensors for the
// top-level subgraph, i.e. for any children subgraphs we handle
// tensor memory allocation on our own.
node_input_tensors_set_.insert(input_tensor);
}
}
for (int n = 0; n < TfLiteOpaqueNodeNumberOfOutputs(delegated_node);
++n) {
auto output_tensor =
TfLiteOpaqueNodeGetOutput(context, delegated_node, n);
node_output_tensors_[subgraph_index][i].push_back(output_tensor);
if (subgraph_index == kTopLevelSubgraphIndex) {
// node_output_tensors_set_ is used for deriving external tensors. For
// this sample delegate, we only derive external tensors for the
// top-level subgraph, i.e. for any children subgraphs we handle
// tensor memory allocation on our own.
node_output_tensors_set_.insert(output_tensor);
}
}
if (builtin_codes_[subgraph_index][i] == kTfLiteBuiltinWhile) {
void* builtin_data = TfLiteOpaqueNodeGetBuiltinData(delegated_node);
TfLiteWhileParams* params =
reinterpret_cast<TfLiteWhileParams*>(builtin_data);
control_flow_branch_indices_[subgraph_index][i] = {
params->cond_subgraph_index, params->body_subgraph_index};
for (int branch_index :
control_flow_branch_indices_[subgraph_index][i]) {
callee_subgraph_indices.push_back(branch_index);
TfLiteStatus status;
TfLiteIntArray* execution_plan;
TfLiteOpaqueContext* branch_context;
status = TfLiteOpaqueContextAcquireSubgraphContext(
context, branch_index, &branch_context);
if (status != kTfLiteOk) return status;
status = TfLiteOpaqueContextGetExecutionPlan(branch_context,
&execution_plan);
if (status != kTfLiteOk) return status;
status = InitSubgraphNodes(branch_context, branch_index,
execution_plan, callee_subgraph_indices);
if (status != kTfLiteOk) return status;
// Release the acquired subgraph context.
status =
TfLiteOpaqueContextReleaseSubgraphContext(context, branch_index);
if (status != kTfLiteOk) return status;
}
}
}
return kTfLiteOk;
}
TfLiteStatus Prepare(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* delegated_node) override {
if (external_tensors_.empty()) return kTfLiteOk;
const int kTheInputTensorSize =
helpers::CalculateNumElements((*external_tensors_.begin()));
// For each subgraph
for (auto [_, node_input_tensors] : node_input_tensors_) {
// For each node in the subgraph
for (std::vector<const TfLiteOpaqueTensor*>& vecs : node_input_tensors) {
// For each tensor in the node
for (const TfLiteOpaqueTensor* tensor : vecs) {
if (IsExternalTensor(tensor)) continue;
std::vector<float>& vec_memory =
internal_float_tensors_memory_[tensor];
vec_memory.resize(kTheInputTensorSize);
}
}
}
// For each subgraph
for (auto [subgraph_index, node_output_tensors] : node_output_tensors_) {
// For each node in the subgraph
for (int i = 0; i < node_output_tensors.size(); ++i) {
std::vector<const TfLiteOpaqueTensor*>& vecs = node_output_tensors[i];
// For each tensor in the node
for (int j = 0; j < vecs.size(); ++j) {
const TfLiteOpaqueTensor* tensor = vecs[j];
if (IsExternalTensor(tensor)) break;
if (builtin_codes_[subgraph_index][i] == kTfLiteBuiltinEqual) {
std::vector<int>& vec_memory = internal_int_tensors_memory_[tensor];
vec_memory.resize(kTheInputTensorSize);
} else {
std::vector<float>& vec_memory =
internal_float_tensors_memory_[tensor];
vec_memory.resize(kTheInputTensorSize);
}
}
}
}
return kTfLiteOk;
}
int* GetIntRawDataSource(const TfLiteOpaqueTensor* tensor) {
if (IsExternalTensor(tensor)) {
return reinterpret_cast<int*>(TfLiteOpaqueTensorData(tensor));
} else {
return internal_int_tensors_memory_[tensor].data();
}
}
float* GetFloatRawDataSource(const TfLiteOpaqueTensor* tensor) {
if (IsExternalTensor(tensor)) {
return reinterpret_cast<float*>(TfLiteOpaqueTensorData(tensor));
} else {
return internal_float_tensors_memory_[tensor].data();
}
}
void CopyRawDataSource(const TfLiteOpaqueTensor* from_tensor,
const TfLiteOpaqueTensor* to_tensor) {
float* from_data = GetFloatRawDataSource(from_tensor);
float* to_data = GetFloatRawDataSource(to_tensor);
int number_of_elements = helpers::CalculateNumElements(to_tensor);
memcpy(to_data, from_data, number_of_elements * sizeof(float));
}
TfLiteStatus EvalArithmeticOp(int subgraph_index, int node_index) {
auto node_input_tensors = node_input_tensors_[subgraph_index];
auto node_output_tensors = node_output_tensors_[subgraph_index];
auto builtin_codes = builtin_codes_[subgraph_index];
float* input1 = GetFloatRawDataSource(node_input_tensors[node_index][0]);
float* input2 = GetFloatRawDataSource(node_input_tensors[node_index][1]);
float* output = GetFloatRawDataSource(node_output_tensors[node_index][0]);
int number_of_elements =
helpers::CalculateNumElements(node_output_tensors[node_index][0]);
// We assume that all input, output and intermediate tensors of the
// delegated subgraph have the same size.
for (int i = 0; i < number_of_elements; ++i) {
switch (builtin_codes[node_index]) {
case kTfLiteBuiltinAdd:
output[i] = input1[i] + input2[i];
break;
case kTfLiteBuiltinSub:
output[i] = input1[i] - input2[i];
break;
case kTfLiteBuiltinMul:
output[i] = input1[i] * input2[i];
break;
default:
return kTfLiteDelegateError;
}
}
return kTfLiteOk;
}
TfLiteStatus EvalComparisonOp(int subgraph_index, int node_index) {
auto node_input_tensors = node_input_tensors_[subgraph_index];
auto node_output_tensors = node_output_tensors_[subgraph_index];
auto builtin_codes = builtin_codes_[subgraph_index];
float* input1 = GetFloatRawDataSource(node_input_tensors[node_index][0]);
float* input2 = GetFloatRawDataSource(node_input_tensors[node_index][1]);
int* output = GetIntRawDataSource(node_output_tensors[node_index][0]);
int number_of_elements =
helpers::CalculateNumElements(node_output_tensors[node_index][0]);
// We assume that all input, output and intermediate tensors of the
// delegated subgraph have the same size.
for (int i = 0; i < number_of_elements; ++i) {
switch (builtin_codes[node_index]) {
case kTfLiteBuiltinEqual:
output[i] = input1[i] == input2[i];
break;
default:
return kTfLiteDelegateError;
}
}
return kTfLiteOk;
}
TfLiteStatus EvalWhileOp(int while_subgraph_index, int while_node_index) {
auto branch_indices =
control_flow_branch_indices_[while_subgraph_index][while_node_index];
int cond_subgraph_index = branch_indices[0];
int body_subgraph_index = branch_indices[1];
int last_cond_node_index =
node_output_tensors_[cond_subgraph_index].size() - 1;
int last_body_node_index =
node_output_tensors_[body_subgraph_index].size() - 1;
// 1. Copy while input to cond input.
CopyRawDataSource(
node_input_tensors_[while_subgraph_index][while_node_index][0],
node_input_tensors_[cond_subgraph_index][0][0]);
TfLiteStatus status;
while (true) {
status = EvalSubgraph(cond_subgraph_index);
if (status != kTfLiteOk) return status;
int* cond_output = GetIntRawDataSource(
node_output_tensors_[cond_subgraph_index][last_cond_node_index][0]);
int number_of_elements = helpers::CalculateNumElements(
node_output_tensors_[cond_subgraph_index][last_cond_node_index][0]);
// We assume that all input, output and intermediate tensors of the
// delegated subgraph have the same size.
bool condition = true;
for (int i = 0; i < number_of_elements; ++i) {
if (cond_output[i] == 0) {
condition = false;
break;
}
}
if (!condition) {
// 4. Copy body output to while output.
CopyRawDataSource(
node_output_tensors_[body_subgraph_index][last_body_node_index][0],
node_output_tensors_[while_subgraph_index][while_node_index][0]);
break;
}
// 2. Copy cond input to body input.
CopyRawDataSource(node_input_tensors_[cond_subgraph_index][0][0],
node_input_tensors_[body_subgraph_index][0][0]);
status = EvalSubgraph(body_subgraph_index);
if (status != kTfLiteOk) return status;
// 3. Copy body output to cond input.
CopyRawDataSource(
node_output_tensors_[body_subgraph_index][last_body_node_index][0],
node_input_tensors_[cond_subgraph_index][0][0]);
}
return kTfLiteOk;
}
TfLiteStatus EvalSubgraph(int subgraph_index) {
TfLiteStatus status;
for (int i = 0; i < node_input_tensors_[subgraph_index].size(); ++i) {
status = EvalNode(subgraph_index, i);
if (status != kTfLiteOk) return status;
}
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* delegated_node) override {
return EvalSubgraph(kTopLevelSubgraphIndex);
}
TfLiteStatus EvalNode(int subgraph_index, int node_index) {
TfLiteStatus status;
switch (builtin_codes_[subgraph_index][node_index]) {
case kTfLiteBuiltinAdd:
case kTfLiteBuiltinSub:
case kTfLiteBuiltinMul:
status = EvalArithmeticOp(subgraph_index, node_index);
break;
case kTfLiteBuiltinEqual:
status = EvalComparisonOp(subgraph_index, node_index);
break;
case kTfLiteBuiltinWhile:
status = EvalWhileOp(subgraph_index, node_index);
break;
default:
return kTfLiteDelegateError;
}
if (status != kTfLiteOk) {
return status;
}
return kTfLiteOk;
}
private:
absl::flat_hash_map<int, absl::flat_hash_map<int, std::vector<int>>>
control_flow_branch_indices_;
absl::flat_hash_map<int, std::vector<std::vector<const TfLiteOpaqueTensor*>>>
node_input_tensors_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> node_input_tensors_set_;
absl::flat_hash_map<int, std::vector<std::vector<const TfLiteOpaqueTensor*>>>
node_output_tensors_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> node_output_tensors_set_;
absl::flat_hash_set<const TfLiteOpaqueTensor*> external_tensors_;
absl::flat_hash_map<const TfLiteOpaqueTensor*, std::vector<float>>
internal_float_tensors_memory_;
absl::flat_hash_map<const TfLiteOpaqueTensor*, std::vector<int>>
internal_int_tensors_memory_;
TfLiteOpaqueContext* context_;
// Holds the builtin code of the ops per subgraph.
// builtin_code_[i] is the type of node at index 'i'
absl::flat_hash_map<int, std::vector<int>> builtin_codes_;
};
} // namespace
TfLiteStatus SampleStableDelegate::ComputeCompatibleCalleeSubgraphs(
TfLiteOpaqueContext* opaque_context, int subgraph_index) {
TfLiteStatus status;
TfLiteOpaqueContext* current_context;
status = TfLiteOpaqueContextAcquireSubgraphContext(
opaque_context, subgraph_index, &current_context);
if (status != kTfLiteOk) {
return status;
}
TfLiteIntArray* execution_plan;
status =
TfLiteOpaqueContextGetExecutionPlan(current_context, &execution_plan);
if (status != kTfLiteOk) {
return status;
}
bool is_compatible_subgraph = true;
// The list of TfLite nodes currently in the model.
for (int i = 0; i < execution_plan->size; ++i) {
int node_index = execution_plan->data[i];
TfLiteOpaqueNode* node = nullptr;
TfLiteOperator* registration = nullptr;
status = TfLiteOpaqueContextGetNodeAndRegistration(
current_context, node_index, &node, &registration);
if (status != kTfLiteOk) {
return status;
}
TfLiteBuiltinOperator builtin_operator =
TfLiteOperatorGetBuiltInCode(registration);
if (builtin_operator == kTfLiteBuiltinWhile) {
void* builtin_data = TfLiteOpaqueNodeGetBuiltinData(node);
const auto* op_data =
reinterpret_cast<const TfLiteWhileParams*>(builtin_data);
// Handle WHILE's cond subgraph.
AddCalleeSubgraphToCallerSubgraph(op_data->cond_subgraph_index,
subgraph_index);
ComputeCompatibleCalleeSubgraphs(opaque_context,
op_data->cond_subgraph_index);
// Handle WHILE's body subgraph.
AddCalleeSubgraphToCallerSubgraph(op_data->body_subgraph_index,
subgraph_index);
ComputeCompatibleCalleeSubgraphs(opaque_context,
op_data->body_subgraph_index);
}
if (!IsNodeSupportedByDelegate(registration, node, current_context)) {
is_compatible_subgraph = false;
}
}
if (is_compatible_subgraph) {
AddCompatibleCalleeSubgraph(subgraph_index);
}
// Release the acquired subgraph context.
status =
TfLiteOpaqueContextReleaseSubgraphContext(opaque_context, subgraph_index);
if (status != kTfLiteOk) {
return status;
}
return kTfLiteOk;
}
TfLiteStatus SampleStableDelegate::PrepareControlFlow(
TfLiteOpaqueContext* opaque_context) {
// Walk through all the subgraphs, and collect all the subgraphs called by
// control flow ops (e.g. WHILE or IF) to `callee_subgraph_indices` in the
// topological order.
constexpr int kPrimarySubgraphIndex = 0;
ComputeCompatibleCalleeSubgraphs(opaque_context, kPrimarySubgraphIndex);
// Mark callee subgraphs as "delegation-skippable" that should be skipped by
// interpreter->ModifyGraphWithDelegate().
for (const auto& [caller_subgraph_index, callee_subgraph_indices] :
control_flow_subgraph_tree_) {
if (callee_subgraph_indices.empty()) {
continue;
}
bool callee_subgraphs_all_delegatable = true;
for (int callee_subgraph_index : callee_subgraph_indices) {
if (!IsCompatibleCalleeSubgraph(callee_subgraph_index)) {
callee_subgraphs_all_delegatable = false;
}
}
if (!callee_subgraphs_all_delegatable) {
continue;
}
for (int callee_subgraph_index : callee_subgraph_indices) {
TfLiteStatus status =
TfLiteOpaqueContextMarkSubgraphAsDelegationSkippable(
opaque_context, callee_subgraph_index);
if (status != kTfLiteOk) return status;
}
}
return kTfLiteOk;
}
int helpers::CalculateNumElements(const TfLiteOpaqueTensor* opaque_tensor) {
int total_num_elements = 1;
for (int i = 0; i < TfLiteOpaqueTensorNumDims(opaque_tensor); ++i) {
total_num_elements *= TfLiteOpaqueTensorDim(opaque_tensor, i);
}
return total_num_elements;
}
bool SampleStableDelegate::IsNodeSupportedByDelegate(
const TfLiteOperator* registration_external, const TfLiteOpaqueNode* node,
TfLiteOpaqueContext* context) const {
TfLiteBuiltinOperator builtin_operator =
TfLiteOperatorGetBuiltInCode(registration_external);
void* builtin_data = TfLiteOpaqueNodeGetBuiltinData(node);
// List of supported / unsupported ops.
switch (builtin_operator) {
case kTfLiteBuiltinAdd: {
TfLiteAddParams* params =
reinterpret_cast<TfLiteAddParams*>(builtin_data);
if (!params || params->activation != kTfLiteActNone) return false;
break;
}
case kTfLiteBuiltinSub: {
TfLiteSubParams* params =
reinterpret_cast<TfLiteSubParams*>(builtin_data);
if (!params || params->activation != kTfLiteActNone) return false;
break;
}
case kTfLiteBuiltinMul: {
TfLiteMulParams* params =
reinterpret_cast<TfLiteMulParams*>(builtin_data);
if (!params || params->activation != kTfLiteActNone) return false;
break;
}
case kTfLiteBuiltinEqual:
break;
case kTfLiteBuiltinWhile: {
TfLiteWhileParams* params =
reinterpret_cast<TfLiteWhileParams*>(builtin_data);
if (!params || !IsCompatibleCalleeSubgraph(params->cond_subgraph_index) ||
!IsCompatibleCalleeSubgraph(params->body_subgraph_index)) {
return false;
}
break;
}
default:
// Any unlisted ops are unsupported by default without further check.
return false;
}
// The delegate only accepts two inputs with float32 type for binary ops and
// one input with float32 type for control flow ops.
if (builtin_operator == kTfLiteBuiltinWhile) {
if (TfLiteOpaqueNodeNumberOfInputs(node) != 1) return false;
const TfLiteOpaqueTensor* tensor =
TfLiteOpaqueNodeGetInput(context, node, 0);
if (!tensor || TfLiteOpaqueTensorType(tensor) != kTfLiteFloat32)
return false;
} else {
if (TfLiteOpaqueNodeNumberOfInputs(node) != 2) return false;
const TfLiteOpaqueTensor* tensor_1 =
TfLiteOpaqueNodeGetInput(context, node, 0);
const TfLiteOpaqueTensor* tensor_2 =
TfLiteOpaqueNodeGetInput(context, node, 1);
if (!tensor_1 || TfLiteOpaqueTensorType(tensor_1) != kTfLiteFloat32)
return false;
if (!tensor_2 || TfLiteOpaqueTensorType(tensor_2) != kTfLiteFloat32)
return false;
// The delegate doesn't support broadcasting; it requires both inputs to
// have the same shape.
if (TfLiteOpaqueTensorNumDims(tensor_1) !=
TfLiteOpaqueTensorNumDims(tensor_2))
return false;
for (int i = 0; i < TfLiteOpaqueTensorNumDims(tensor_1); ++i) {
if (TfLiteOpaqueTensorDim(tensor_1, i) !=
TfLiteOpaqueTensorDim(tensor_2, i)) {
return false;
}
}
}
return true;
}
TfLiteStatus SampleStableDelegate::Initialize(TfLiteOpaqueContext* context) {
if (!has_been_initialized_) {
PrepareControlFlow(context);
has_been_initialized_ = true;
}
return kTfLiteOk;
}
const char* SampleStableDelegate::Name() const {
return kSampleStableDelegateName;
}
std::unique_ptr<SimpleOpaqueDelegateKernelInterface>
SampleStableDelegate::CreateDelegateKernelInterface() {
return std::make_unique<SampleStableDelegateKernel>();
}
} // namespace example
} // namespace tflite
@@ -0,0 +1,125 @@
/* Copyright 2022 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_WITH_CONTROL_FLOW_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_WITH_CONTROL_FLOW_H_
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/simple_opaque_delegate.h"
namespace tflite {
namespace example {
namespace helpers {
int CalculateNumElements(const TfLiteOpaqueTensor* opaque_tensor);
} // namespace helpers
// LINT.IfChange
static const char kSampleStableDelegateName[] = "google_sample_delegate";
// LINT.ThenChange(Google-internal path)
static const char kSampleStableDelegateVersion[] = "1.0.0";
// A simple delegate that supports only a few operations:
// addition, subtraction, multiplication, equality checks, and while loops.
// Implements SimpleOpaqueDelegateInterface, and therefore the delegate can be
// easily be adapted to work with the stable TFLite delegate API via
// TfLiteOpaqueDelegateFactory.
class SampleStableDelegate : public SimpleOpaqueDelegateInterface {
public:
// SampleStableDelegate supports float32 input type only.
// Returns true if the inputs of 'node' are two tensors of float32 with the
// same shape and the operation is supported (without fused activation).
bool IsNodeSupportedByDelegate(const TfLiteOperator* registration_external,
const TfLiteOpaqueNode* node,
TfLiteOpaqueContext* context) const override;
// No-op. The delegate doesn't have extra steps to perform during
// initialization.
TfLiteStatus Initialize(TfLiteOpaqueContext* context) override;
// Returns a name that identifies the delegate.
const char* Name() const override;
// Returns an instance of SampleStableDelegateKernel that implements
// SimpleOpaqueDelegateKernelInterface. SampleStableDelegateKernel describes
// how a subgraph is delegated and the concrete evaluation of operations to be
// performed by the delegate.
std::unique_ptr<SimpleOpaqueDelegateKernelInterface>
CreateDelegateKernelInterface() override;
private:
// Computes all the compatible callee subgraphs of control flow ops of the
// subgraph specified with the given index. All the subgraph tree structures
// are stored in control_flow_subgraph_tree_ and any compatible subgraphs are
// added to compatible_callee_subgraph_indices_.
// NOTE: This function is expected to be called recursively to gather all the
// nested control flow subgraphs, and is expected to be called by
// PrepareControlFlow() with the root (subgraph_index = 0).
TfLiteStatus ComputeCompatibleCalleeSubgraphs(
TfLiteOpaqueContext* opaque_context, int subgraph_index);
// Performs any necessary steps for control flow support. For this sample
// delegate, we computes compatible callee subgraphs, releases subgraph
// contexts, and mark compatible callee subgraphs so that we can avoid Calling
// ModifyGraphWithDelegate() on the compatible subgraphs.
TfLiteStatus PrepareControlFlow(TfLiteOpaqueContext* opaque_context);
// Adds a control flow callee subgraph to the parent subgraph in the
// control_flow_subgraph_tree_.
void AddCalleeSubgraphToCallerSubgraph(int callee_subgraph_index,
int caller_subgraph_index) {
control_flow_subgraph_tree_[caller_subgraph_index].insert(
callee_subgraph_index);
}
// Adds a compatible callee subgraph.
void AddCompatibleCalleeSubgraph(int subgraph_index) {
compatible_callee_subgraph_indices_.insert(subgraph_index);
}
// Returns true if `subgraph_index` is of a compatible callee subgraph.
bool IsCompatibleCalleeSubgraph(int subgraph_index) const {
return compatible_callee_subgraph_indices_.contains(subgraph_index);
}
// A map from a parent subgraph index to its control flow callee subgraph
// indices (i.e. called by WHILE op). This information is used for
// constructing a tree of control flow subgraphs and traversing to figure out
// the delegation dependencies.
absl::flat_hash_map<int, absl::flat_hash_set<int>>
control_flow_subgraph_tree_;
// A set of callee subgraph indices (i.e., called by WHILE op) that this
// sample delegate can fully support. We mark all the callee subgraphs of the
// subgraph S (that contains this control flow op) as compatible if all those
// callee subgraphs are fully supported (i.e. contains only the ops fully
// supported by this delegate). For example, a WHILE op contains two callees:
// condition and body subgraphs. If and only if both condition and body
// subgraphs contain only the supported ops, then both subgraphs are marked
// as compatible.
// NOTE: The definition of `compatible` depends on the delegate provider's
// requirements. The definition we provide in this sample delegate is just an
// example for demonstration purpose only.
absl::flat_hash_set<int> compatible_callee_subgraph_indices_;
// If the delegate is already initialized. This is used to avoid duplicate
// PrepareControlFlow() call (i.e. we only want to call PrepareControlFlow()
// in the primary subgraph for our sample delegate).
bool has_been_initialized_ = false;
};
} // namespace example
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_SAMPLE_STABLE_DELEGATE_SAMPLE_STABLE_DELEGATE_WITH_CONTROL_FLOW_H_
@@ -0,0 +1,230 @@
/* Copyright 2023 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/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate_with_control_flow.h"
#include <cstdint>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace {
TEST(SampleStableDelegate, StaticallyLinkedDelegateAndModelWithAdd) {
// Create an instance of the sample stable delegate that implements the ADD
// operation.
tflite::TfLiteOpaqueDelegateUniquePtr opaque_delegate =
tflite::TfLiteOpaqueDelegateFactory::Create(
std::make_unique<tflite::example::SampleStableDelegate>());
ASSERT_NE(opaque_delegate, nullptr);
//
// Create the model and the interpreter
//
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/add.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
//
// Allocate the tensors and fill the input tensor.
//
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
const float kTensorCellValue = 3.f;
int64_t n = tflite::NumElements(input_tensor);
std::vector<float> input(n, kTensorCellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
input.size() * sizeof(float)),
kTfLiteOk);
//
// Run the interpreter and read the output tensor.
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'add.bin' model does the following operation ('t_output' denotes the
// single output tensor, and 't_input' denotes the single input tensor):
//
// t_output = t_input + t_input + t_input = t_input * 3
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensorCellValue * 3);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TEST(SampleStableDelegate, StaticallyLinkedDelegateAndModelWithSub) {
// Create an instance of the sample stable delegate that implements the SUB
// operation.
tflite::TfLiteOpaqueDelegateUniquePtr opaque_delegate =
tflite::TfLiteOpaqueDelegateFactory::Create(
std::make_unique<tflite::example::SampleStableDelegate>());
ASSERT_NE(opaque_delegate, nullptr);
//
// Create the model and the interpreter
//
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/sub.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
//
// Allocate the tensors and fill the input tensor.
//
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor_0 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor_0, nullptr);
const float kTensor0CellValue = 3.f;
int64_t n = tflite::NumElements(input_tensor_0);
std::vector<float> input_0(n, kTensor0CellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor_0, input_0.data(),
input_0.size() * sizeof(float)),
kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor_1 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/1);
ASSERT_NE(input_tensor_1, nullptr);
n = tflite::NumElements(input_tensor_1);
const float kTensor1CellValue = 2.f;
std::vector<float> input_1(n, kTensor1CellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor_1, input_1.data(),
input_1.size() * sizeof(float)),
kTfLiteOk);
//
// Run the interpreter and read the output tensor.
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'sub.bin' model does the following operation ('t_output' denotes the
// single output tensor, and 't_input_0' and 't_input_1' denote the two input
// tensors):
//
// t_output = t_input_0 - t_input_1
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensor0CellValue - kTensor1CellValue);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TEST(SampleStableDelegate, StaticallyLinkedDelegateAndModelWithNestedWhile) {
// Create an instance of the sample stable delegate that implements the WHILE
// operation.
tflite::TfLiteOpaqueDelegateUniquePtr opaque_delegate =
tflite::TfLiteOpaqueDelegateFactory::Create(
std::make_unique<tflite::example::SampleStableDelegate>());
ASSERT_NE(opaque_delegate, nullptr);
//
// Create the model and the interpreter
//
TfLiteModel* model = TfLiteModelCreateFromFile(
"tensorflow/lite/testdata/nested_while.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsAddDelegate(options, opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
//
// Allocate the tensors and fill the input tensor.
//
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
const float kTensorCellValue = 1.f;
int64_t n = tflite::NumElements(input_tensor);
std::vector<float> input(n, kTensorCellValue);
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
input.size() * sizeof(float)),
kTfLiteOk);
//
// Run the interpreter and read the output tensor.
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
// The 'nested_while.bin' model does the following operation ('t_output'
// denotes the single output tensor, and 't_input' denotes the single input
// tensor):
//
// while (t_input * t_input == t_input) {
// while (t_input * t_input == t_input) {
// t_input = t_input + t_input
// }
// }
// t_output = t_input
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensorCellValue * 2);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
} // namespace
@@ -0,0 +1,166 @@
# Copyright 2022 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.
# ==============================================================================
# Utils package for the stable delegate APIs.
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("//tensorflow/lite:build_def.bzl", "tflite_cc_shared_object", "tflite_linkopts_no_undefined")
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "cc_library_with_tflite")
# copybara:uncomment package(default_applicable_licenses = ["//tensorflow:LICENSE"])
cc_library_with_tflite(
name = "delegate_loader",
srcs = ["delegate_loader.cc"],
hdrs = ["delegate_loader.h"],
generate_opaque_delegate_target = True,
linkopts = select({
"//tensorflow:windows": [],
"//conditions:default": ["-ldl"],
}),
tflite_deps = ["//tensorflow/lite/acceleration/configuration/c:stable_delegate"],
visibility = ["//visibility:public"],
deps = [
"//tensorflow/lite/experimental/acceleration/compatibility:android_info",
"//tensorflow/lite/tools:logging",
"@com_google_absl//absl/strings",
],
)
cc_library_with_tflite(
name = "stable_delegate_interface",
hdrs = ["stable_delegate_interface.h"],
generate_opaque_delegate_target = True,
tflite_deps = ["//tensorflow/lite/acceleration/configuration/c:stable_delegate"],
visibility = ["//visibility:public"],
)
tflite_cc_shared_object(
name = "tensorflowlite_stable_xnnpack_delegate",
testonly = True,
srcs = ["stable_xnnpack_delegate.cc"],
defines = ["TFL_STABLE_DELEGATE_COMPILE_LIBRARY"],
linkopts = tflite_linkopts_no_undefined() + select({
"//tensorflow:windows": [],
# iOS linker doesn't support -version-script.
"//tensorflow:ios": [
# TODO(b/260213307): support hiding unnecessary symbols on iOS.
],
"//conditions:default": [
# Expose necessary symbols only.
"-Wl,--version-script,$(location //tensorflow/lite/delegates/utils/experimental/stable_delegate:version_script.lds)",
],
}),
per_os_targets = True,
visibility = ["//visibility:public"],
deps = [
":stable_delegate_interface",
":version_script.lds",
"//tensorflow/lite/acceleration/configuration/c:stable_delegate",
"//tensorflow/lite/acceleration/configuration/c:xnnpack_plugin",
"//tensorflow/lite/c:c_api",
],
)
cc_test(
name = "delegate_loader_test",
srcs = ["delegate_loader_test.cc"],
data = ["//tensorflow/lite/delegates/utils/experimental/sample_stable_delegate:tensorflowlite_sample_stable_delegate"],
deps = [
":delegate_loader",
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"//tensorflow/lite/acceleration/configuration/c:stable_delegate",
"//tensorflow/lite/delegates/utils/experimental/sample_stable_delegate",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "tflite_settings_json_parser",
srcs = [
"tflite_settings_json_parser.cc",
"//tensorflow/lite/acceleration/configuration:configuration_fbs_contents-inl.h",
],
hdrs = ["tflite_settings_json_parser.h"],
visibility = ["//visibility:public"],
deps = [
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"//tensorflow/lite/kernels/internal:compatibility",
"//tensorflow/lite/tools:logging",
"@flatbuffers",
],
)
build_test(
name = "stable_delegate_build_test",
targets = [
":delegate_loader",
":test_xnnpack_settings.json",
":tensorflowlite_stable_xnnpack_delegate",
":tflite_settings_json_parser",
],
)
cc_test(
name = "tflite_settings_json_parser_test",
srcs = ["tflite_settings_json_parser_test.cc"],
data = [
":test_xnnpack_settings.json",
"//tensorflow/lite/tools/delegates/experimental/stable_delegate:test_invalid_settings.json",
],
deps = [
":tflite_settings_json_parser",
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"@com_google_googletest//:gtest_main",
"@flatbuffers",
],
)
cc_library(
name = "kernel_test_main",
testonly = 1,
srcs = ["kernel_test_main.cc"],
visibility = ["//visibility:public"],
deps = [
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/kernels:acceleration_test_util",
"//tensorflow/lite/kernels:acceleration_test_util_internal",
"//tensorflow/lite/kernels:test_delegate_providers_lib",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/kernels:test_util_delegate_providers",
"//tensorflow/lite/testing:util",
"@com_google_googletest//:gtest",
],
)
cc_test(
name = "stable_delegate_test_suite",
size = "medium",
tags = [
"manual",
"notap",
],
deps = [
":kernel_test_main",
"//tensorflow/lite/kernels:combined_all_kernel_tests_lib",
],
)
exports_files(
srcs = [
"test_xnnpack_settings.json",
"version_script.lds",
],
visibility = ["//visibility:public"],
)
@@ -0,0 +1,113 @@
/* Copyright 2022 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/delegates/utils/experimental/stable_delegate/delegate_loader.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <cerrno>
#include <string>
#include "absl/strings/numbers.h"
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/android_info.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace delegates {
namespace utils {
namespace {
void setLibraryPathEnvironmentVariable(const std::string& delegate_path) {
std::string directory_path = "";
// Finds the last '/' character in the file path.
size_t last_slash_index = delegate_path.rfind('/');
// If there is no '/' character, then the file path is a relative path and
// the directory path is empty.
if (last_slash_index != std::string::npos) {
// Extracts the directory path from the file path.
directory_path = delegate_path.substr(0, last_slash_index);
}
if (setenv(kTfLiteLibraryPathEnvironmentVariable, directory_path.c_str(),
/* overwrite= */ 1) != 0) {
// Delegate loading can continue as the environment variable is optional for
// the stable delegate shared library to use.
TFLITE_LOG(WARN) << "Error setting environment variable "
<< kTfLiteLibraryPathEnvironmentVariable
<< " with error: " << strerror(errno);
}
}
} // namespace
using ::tflite::acceleration::AndroidInfo;
using ::tflite::acceleration::RequestAndroidInfo;
const TfLiteStableDelegate* LoadDelegateFromSharedLibrary(
const std::string& delegate_path) {
void* symbol_pointer =
LoadSymbolFromSharedLibrary(delegate_path, kTfLiteStableDelegateSymbol);
if (!symbol_pointer) {
return nullptr;
}
return reinterpret_cast<const TfLiteStableDelegate*>(symbol_pointer);
}
void* LoadSymbolFromSharedLibrary(const std::string& delegate_path,
const std::string& delegate_symbol) {
// TODO(b/268483011): Use android_dlopen_ext to support loading from an offset
// within an apk.
void* delegate_lib_handle = nullptr;
// RTLD_NOW: Ensures that any dynamic linking errors occur early rather than
// crash later.
// RTLD_LOCAL: Symbols are not available to subsequent objects.
int dlopen_flags = RTLD_NOW | RTLD_LOCAL;
int sdk_version;
AndroidInfo android_info;
if (RequestAndroidInfo(&android_info).ok() &&
absl::SimpleAtoi(android_info.android_sdk_version, &sdk_version) &&
sdk_version >= 23) {
// RTLD_NODELETE: Not unload the shared object during dlclose to prevent
// thread specific key leakage. It is supported since Android SDK level 23.
dlopen_flags |= RTLD_NODELETE;
TFLITE_LOG(INFO) << "Android SDK level is " << sdk_version
<< ", using dlopen with RTLD_NODELETE.";
}
// Exports the path of the folder containing the stable delegate shared
// library to the TFLITE_STABLE_DELEGATE_LIBRARY_PATH environment variable.
setLibraryPathEnvironmentVariable(delegate_path);
// On the one hand, the handle would not be closed in production. The resource
// will be released when the process is killed. On the other hand, it may
// cause issues for leak detection in testing.
// TODO(b/268483011): Better support for cleanup the shared library handle.
delegate_lib_handle = dlopen(delegate_path.c_str(), dlopen_flags);
if (!delegate_lib_handle) {
TFLITE_LOG(ERROR) << "Failed to open library " << delegate_path << ": "
<< dlerror();
return nullptr;
}
void* symbol_pointer = dlsym(delegate_lib_handle, delegate_symbol.c_str());
if (!symbol_pointer) {
TFLITE_LOG(ERROR) << "Failed to find " << delegate_symbol
<< " symbol: " << dlerror();
dlclose(delegate_lib_handle);
return nullptr;
}
return symbol_pointer;
}
} // namespace utils
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,51 @@
/* Copyright 2022 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_DELEGATE_LOADER_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_DELEGATE_LOADER_H_
#include <string>
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
namespace tflite {
namespace delegates {
namespace utils {
constexpr char kTfLiteStableDelegateSymbol[] = "TFL_TheStableDelegate";
constexpr char kTfLiteLibraryPathEnvironmentVariable[] =
"TFLITE_STABLE_DELEGATE_LIBRARY_PATH";
// Loads the TFLite delegate shared library and returns the pointer to
// TfLiteStableDelegate (defined in
// tensorflow/lite/acceleration/configuration/c/stable_delegate.h).
// The returned pointer could be null if the delegate shared library cannot be
// opened or the delegate symbol cannot be found.
const TfLiteStableDelegate* LoadDelegateFromSharedLibrary(
const std::string& delegate_path);
// Loads `delegate_symbol` from the delegate shared library and returns a
// pointer to void. It is caller's responsibility to check and cast the pointer
// to other types. The returned pointer could be null if the delegate shared
// library cannot be opened or the delegate symbol cannot be found.
void* LoadSymbolFromSharedLibrary(const std::string& delegate_path,
const std::string& delegate_symbol);
// TODO(b/239825926): Add ABI version check when loading TfLiteStableDelegate.
} // namespace utils
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_DELEGATE_LOADER_H_
@@ -0,0 +1,84 @@
/* Copyright 2022 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/delegates/utils/experimental/stable_delegate/delegate_loader.h"
#include <cstdlib>
#include <gtest/gtest.h>
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
namespace {
using tflite::TFLiteSettings;
using tflite::TFLiteSettingsBuilder;
using tflite::delegates::utils::LoadDelegateFromSharedLibrary;
using tflite::delegates::utils::LoadSymbolFromSharedLibrary;
TEST(TfLiteDelegateLoaderUtilsTest, Simple) {
const TfLiteStableDelegate* stable_delegate_handle =
LoadDelegateFromSharedLibrary(
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate/"
"libtensorflowlite_sample_stable_delegate.so"
);
ASSERT_NE(stable_delegate_handle, nullptr);
EXPECT_STREQ(stable_delegate_handle->delegate_abi_version,
TFL_STABLE_DELEGATE_ABI_VERSION);
EXPECT_STREQ(stable_delegate_handle->delegate_name,
tflite::example::kSampleStableDelegateName);
EXPECT_STREQ(stable_delegate_handle->delegate_version,
tflite::example::kSampleStableDelegateVersion);
EXPECT_NE(stable_delegate_handle->delegate_plugin, nullptr);
EXPECT_STREQ(
getenv(tflite::delegates::utils::kTfLiteLibraryPathEnvironmentVariable),
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate");
// Builds TFLiteSettings flatbuffer and passes into delegate plugin create
// method.
flatbuffers::FlatBufferBuilder flatbuffer_builder;
TFLiteSettingsBuilder tflite_settings_builder(flatbuffer_builder);
flatbuffers::Offset<TFLiteSettings> tflite_settings =
tflite_settings_builder.Finish();
flatbuffer_builder.Finish(tflite_settings);
const TFLiteSettings* settings = flatbuffers::GetRoot<TFLiteSettings>(
flatbuffer_builder.GetBufferPointer());
auto delegate = stable_delegate_handle->delegate_plugin->create(settings);
ASSERT_NE(delegate, nullptr);
EXPECT_EQ(
stable_delegate_handle->delegate_plugin->get_delegate_errno(delegate), 0);
stable_delegate_handle->delegate_plugin->destroy(delegate);
}
TEST(TfLiteDelegateLoaderUtilsTest, WrongSymbolReturnsNullptr) {
void* symbol_pointer = LoadSymbolFromSharedLibrary(
"tensorflow/lite/delegates/utils/experimental/"
"sample_stable_delegate/libtensorflowlite_sample_stable_delegate.so",
"NOT_REAL_SYMBOL");
EXPECT_EQ(symbol_pointer, nullptr);
}
TEST(TfLiteDelegateLoaderUtilsTest, MissingLibReturnsNullptr) {
const TfLiteStableDelegate* stable_delegate_handle =
LoadDelegateFromSharedLibrary("not_real_delegate.so");
EXPECT_EQ(stable_delegate_handle, nullptr);
}
} // namespace
@@ -0,0 +1,165 @@
/* Copyright 2023 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 <cstdlib>
#include <fstream>
#include <iterator>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/kernels/acceleration_test_util.h"
#include "tensorflow/lite/kernels/acceleration_test_util_internal.h"
#include "tensorflow/lite/kernels/test_delegate_providers.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/testing/util.h"
namespace tflite {
namespace {
class DelegateTestSuiteAccelerationTestParams {
public:
static const char* AccelerationTestConfig() {
return acceleration_test_config_->c_str();
}
static void SetAccelerationTestConfig(std::string config) {
*acceleration_test_config_ = config;
}
static void Destroy() { delete acceleration_test_config_; }
static DelegateTestSuiteAccelerationTestParams ParseConfigurationLine(
const std::string& conf_line) {
// No test argument is supported at the moment.
return {};
}
private:
static std::string* acceleration_test_config_;
};
std::string*
DelegateTestSuiteAccelerationTestParams::acceleration_test_config_ =
new std::string(
R"(
## Every Test can be allowlisted or denylisted using a regexp on its test_id
## Test_id
#
# The test_id is test_suite_name / test_name, this differs from the
# name used by the build because of the / separator instead of .
# Parameterized tests names are composed by the base test name / test / ordinal
# the ordinal is the position in the list of parameters generated by the
# cardinal product of all the different parameter sets
# Denylist/Allowlist
# To denylist an element simply add - before the test_id regex
## Rules evaluation
#
# Rules are checked in order, the first matching completes the browsing
# This can be useful to put more specific rules first and generic default
# ones below
## Test Arguments
#
# No test argument is supported at the moment.
# DTS checks all tests by default if no acceleraton test config file is
# provided.
.*
)");
void ValidateAcceleration(const SingleOpModel& model) {
std::string test_id = GetCurrentTestId();
const bool supported =
GetAccelerationTestParam<DelegateTestSuiteAccelerationTestParams>(test_id)
.has_value();
if (!supported) {
// Note that the error `kTfLiteApplicationError` is accepted here.
// We only want to check the delegate is working properly, so an error due
// to incompatibility between the model and the delegate is not considered a
// failure here.
ASSERT_THAT(model.GetDelegateApplicationStatus().value_or(kTfLiteOk),
testing::AnyOf(kTfLiteOk, kTfLiteApplicationError));
return;
} else {
ASSERT_EQ(model.GetDelegateApplicationStatus().value_or(kTfLiteOk),
kTfLiteOk);
}
// If we have multiple delegates applied, we would skip this check at the
// moment.
int num_applied_delegates = model.GetNumberOfAppliedDelegates();
if (num_applied_delegates > 1) {
TFLITE_LOG(WARN) << "Skipping acceleration validation as "
<< num_applied_delegates
<< " delegates have been successfully applied.";
return;
}
TFLITE_LOG(INFO) << "Validating acceleration with the stable delegate";
ASSERT_GT(num_applied_delegates, 0) << "No delegates were applied.";
ASSERT_EQ(model.CountNumberOfDelegatedPartitions(), 1)
<< "Expecting operation to be accelerated but cannot find a partition "
"associated to the stable delegate";
}
bool InitKernelTest(int* argc, char** argv) {
KernelTestDelegateProviders* const delegate_providers =
KernelTestDelegateProviders::Get();
if (!delegate_providers->InitFromCmdlineArgs(
argc, const_cast<const char**>(argv))) {
return false;
}
const auto& delegate_params = delegate_providers->ConstParams();
if (delegate_params.HasParam("stable_delegate_settings_file") &&
!delegate_params.Get<std::string>("stable_delegate_settings_file")
.empty()) {
AccelerationValidator::Get()->AddCallback(ValidateAcceleration);
}
if (delegate_params.HasParam(
KernelTestDelegateProviders::kAccelerationTestConfigPath)) {
std::string acceleration_test_config_path =
delegate_params.Get<std::string>(
KernelTestDelegateProviders::kAccelerationTestConfigPath);
if (acceleration_test_config_path.empty()) {
return true;
}
std::ifstream fp(acceleration_test_config_path);
if (!fp.good()) {
return false;
}
DelegateTestSuiteAccelerationTestParams::SetAccelerationTestConfig(
std::string(std::istreambuf_iterator<char>(fp),
std::istreambuf_iterator<char>()));
}
return true;
}
void DestroyKernelTest() { DelegateTestSuiteAccelerationTestParams::Destroy(); }
} // namespace
} // namespace tflite
int main(int argc, char** argv) {
tflite::LogToStderr();
if (tflite::InitKernelTest(&argc, argv)) {
testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
tflite::DestroyKernelTest();
return ret;
} else {
return EXIT_FAILURE;
}
}
@@ -0,0 +1,59 @@
/* Copyright 2022 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_STABLE_DELEGATE_INTERFACE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_STABLE_DELEGATE_INTERFACE_H_
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
// This header file declares the interface that stable delegate shared
// libraries need to implement. The stable delegate loader will dynamically load
// the shared library.
#ifdef __cplusplus
extern "C" {
#endif
// Define TFL_STABLE_DELEGATE_EXPORT macro to export a stable delegate API
// function properly with a shared library.
#ifdef SWIG
#define TFL_STABLE_DELEGATE_EXPORT
#else // !defined SWIG
#ifdef _WIN32
// On Windows, the TFL_STABLE_DELEGATE_COMPILE_LIBRARY macro should be defined
// when _building_ a stable delegate shared library, but should not be defined
// when _using_ a stable delegate shared library.
#ifdef TFL_STABLE_DELEGATE_COMPILE_LIBRARY
#define TFL_STABLE_DELEGATE_EXPORT __declspec(dllexport)
#else // !defined TFL_STABLE_DELEGATE_COMPILE_LIBRARY
#define TFL_STABLE_DELEGATE_EXPORT __declspec(dllimport)
#endif // !defined TFL_STABLE_DELEGATE_COMPILE_LIBRARY
#else // !defined _WIN32
#define TFL_STABLE_DELEGATE_EXPORT __attribute__((visibility("default")))
#endif // !defined _WIN32
#endif // !defined SWIG
// The variable contains stable delegate metadata and implementation.
//
// The variable is dynamically initialized and it will be used as the entrypoint
// for the stable delegate providers to load the symbols. Don't add other
// initializations, which depend on the sequence of this initialization.
extern TFL_STABLE_DELEGATE_EXPORT const TfLiteStableDelegate
TFL_TheStableDelegate;
#ifdef __cplusplus
} // extern "C"
#endif
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_STABLE_DELEGATE_INTERFACE_H_
@@ -0,0 +1,33 @@
/* Copyright 2022 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/acceleration/configuration/c/xnnpack_plugin.h"
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/delegates/utils/experimental/stable_delegate/stable_delegate_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* A stable delegate wrapper of the XNNPack delegate for testing.
* The built stable delegate binary is used to verify the stable delegate
* providers by checking if the delegate settings are propagated correctly.
*/
extern TFL_STABLE_DELEGATE_EXPORT const TfLiteStableDelegate
TFL_TheStableDelegate = {TFL_STABLE_DELEGATE_ABI_VERSION, "XNNPACKDelegate",
TfLiteVersion(),
TfLiteXnnpackDelegatePluginCApi()};
#ifdef __cplusplus
} // extern "C"
#endif
@@ -0,0 +1,11 @@
// Test only XNNPack delegate settings file.
//
// This file follows the TFLiteSettings structure in the schema of the generated
// flatbuffer, which is based on the proto file:
// tensorflow/lite/acceleration/configuration/configuration.proto
{
"delegate": "XNNPACK",
"xnnpack_settings": {
"num_threads": 5
}
}
@@ -0,0 +1,91 @@
/* Copyright 2022 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/delegates/utils/experimental/stable_delegate/tflite_settings_json_parser.h"
#include <cstdint>
#include <string>
#include "flatbuffers/base.h" // from @flatbuffers
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/idl.h" // from @flatbuffers
#include "flatbuffers/util.h" // from @flatbuffers
#include "flatbuffers/verifier.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_fbs_contents-inl.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace delegates {
namespace utils {
TfLiteSettingsJsonParser::TfLiteSettingsJsonParser() {
TFLITE_DCHECK(parser_.Parse(configuration_fbs_contents) &&
parser_.SetRootType("TFLiteSettings"));
}
const TFLiteSettings* TfLiteSettingsJsonParser::Parse(
const std::string& json_file_path) {
if (!LoadFromJsonFile(json_file_path) || buffer_pointer_ == nullptr) {
return nullptr;
}
flatbuffers::Verifier verifier(buffer_pointer_, buffer_size_);
if (!verifier.VerifyBuffer<TFLiteSettings>()) {
TFLITE_LOG(ERROR) << "Failed to verify the delegate settings buffer ("
<< json_file_path << ").";
buffer_pointer_ = nullptr;
buffer_size_ = 0;
return nullptr;
}
return flatbuffers::GetRoot<TFLiteSettings>(buffer_pointer_);
}
const uint8_t* TfLiteSettingsJsonParser::GetBufferPointer() {
return buffer_pointer_;
}
flatbuffers::uoffset_t TfLiteSettingsJsonParser::GetBufferSize() {
return buffer_size_;
}
bool TfLiteSettingsJsonParser::LoadFromJsonFile(
const std::string& json_file_path) {
buffer_size_ = 0;
buffer_pointer_ = nullptr;
if (json_file_path.empty()) {
TFLITE_LOG(ERROR) << "Invalid JSON file path.";
return false;
}
std::string json_file;
if (!flatbuffers::LoadFile(json_file_path.c_str(), false, &json_file)) {
TFLITE_LOG(ERROR) << "Failed to load the delegate settings file ("
<< json_file_path << ").";
return false;
}
parser_.opts.root_type = "TFLiteSettings";
if (!parser_.Parse(json_file.c_str())) {
TFLITE_LOG(ERROR) << "Failed to parse the delegate settings file ("
<< json_file_path << ").";
return false;
}
buffer_size_ = parser_.builder_.GetSize();
buffer_pointer_ = parser_.builder_.GetBufferPointer();
return true;
}
} // namespace utils
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,69 @@
/* Copyright 2022 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_TFLITE_SETTINGS_JSON_PARSER_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_TFLITE_SETTINGS_JSON_PARSER_H_
#include <string>
#include "flatbuffers/base.h" // from @flatbuffers
#include "flatbuffers/idl.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
namespace tflite {
namespace delegates {
namespace utils {
// This class parses a JSON file to tflite::TFLiteSettings*.
// Note: This class is "thread-compatible", i.e. not thread-safe but also not
// thread-hostile
// <https://web.archive.org/web/20210125044505/https://www.ibm.com/developerworks/java/library/j-jtp09263/index.html>.
// That is, each instance is not thread-safe, but multiple separate instances
// are safely independent.
class TfLiteSettingsJsonParser {
public:
TfLiteSettingsJsonParser();
// Loads TFLiteSettings from a JSON file path. The lifetime of the
// TFLiteSettings object is tied to the lifetime of the
// TfLiteSettingsJsonParser instance.
//
// Returns the pointer to the TFLiteSettings object or nullptr if an error is
// encountered.
const TFLiteSettings* Parse(const std::string& json_file_path);
// Returns the buffer pointer to the loaded TFLiteSettings object or nullptr
// if an error was encountered during loading or the TFLiteSettings object is
// not loaded. The lifetime of the buffer is tied to the lifetime of the
// TfLiteSettingsJsonParser instance.
const uint8_t* GetBufferPointer();
// Returns the buffer size of the loaded TFLiteSettings object or 0 if an
// error was encountered during loading or the TFLiteSettings object is not
// loaded.
flatbuffers::uoffset_t GetBufferSize();
private:
// Parses content inside `json_file_path` into flatbuffer. Returns true if the
// parsing was successful, otherwise the method returns false.
bool LoadFromJsonFile(const std::string& json_file_path);
flatbuffers::Parser parser_;
uint8_t* buffer_pointer_;
flatbuffers::uoffset_t buffer_size_;
};
} // namespace utils
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_EXPERIMENTAL_STABLE_DELEGATE_TFLITE_SETTINGS_JSON_PARSER_H_
@@ -0,0 +1,107 @@
/* Copyright 2022 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/delegates/utils/experimental/stable_delegate/tflite_settings_json_parser.h"
#include <cstdint>
#include <string>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/util.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
namespace {
using tflite::TFLiteSettings;
using tflite::delegates::utils::TfLiteSettingsJsonParser;
TEST(TfLiteSettingsJsonParserTest, SuccessWithValidXNNPackDelegateSettings) {
TfLiteSettingsJsonParser parser;
const TFLiteSettings* tflite_settings = parser.Parse(
"tensorflow/lite/delegates/utils/experimental/"
"stable_delegate/test_xnnpack_settings.json");
EXPECT_NE(parser.GetBufferPointer(), nullptr);
EXPECT_NE(parser.GetBufferSize(), 0);
ASSERT_NE(tflite_settings, nullptr);
EXPECT_EQ(tflite_settings->delegate(), tflite::Delegate_XNNPACK);
ASSERT_NE(tflite_settings->xnnpack_settings(), nullptr);
EXPECT_EQ(tflite_settings->xnnpack_settings()->num_threads(), 5);
}
TEST(TfLiteSettingsJsonParserTest, GetBufferPointerReturnsValidBufferPointers) {
TfLiteSettingsJsonParser parser;
parser.Parse(
"tensorflow/lite/delegates/utils/experimental/"
"stable_delegate/test_xnnpack_settings.json");
const uint8_t* buffer_pointer = parser.GetBufferPointer();
ASSERT_NE(buffer_pointer, nullptr);
ASSERT_NE(parser.GetBufferSize(), 0);
const TFLiteSettings* tflite_settings =
flatbuffers::GetRoot<TFLiteSettings>(buffer_pointer);
ASSERT_NE(tflite_settings, nullptr);
EXPECT_EQ(tflite_settings->delegate(), tflite::Delegate_XNNPACK);
ASSERT_NE(tflite_settings->xnnpack_settings(), nullptr);
EXPECT_EQ(tflite_settings->xnnpack_settings()->num_threads(), 5);
}
// This test passes the path to a JSON file that the content of the file cannot
// be parsed into the TFLiteSettings structure.
TEST(TfLiteSettingsJsonParserTest, FailedToParseInvalidSettings) {
TfLiteSettingsJsonParser parser;
EXPECT_EQ(
parser.Parse("tensorflow/lite/tools/delegates/experimental/"
"stable_delegate/test_invalid_settings.json"),
nullptr);
EXPECT_EQ(parser.GetBufferPointer(), nullptr);
EXPECT_EQ(parser.GetBufferSize(), 0);
}
TEST(TfLiteSettingsJsonParserTest, FailedToParseWithDifferentRootType) {
std::string temp_file_path =
testing::TempDir() + "/test_corrupted_settings.json";
std::string malicious_payload =
"table FakeTFLiteSettings { fake_offset : int32 (id: 0); }\n"
"root_type FakeTFLiteSettings;\n"
"{ \"fake_offset\": 55555555 }\n";
ASSERT_TRUE(flatbuffers::SaveFile(temp_file_path.c_str(),
malicious_payload.c_str(),
malicious_payload.size(), false));
TfLiteSettingsJsonParser parser;
EXPECT_EQ(parser.Parse(temp_file_path), nullptr);
EXPECT_EQ(parser.GetBufferPointer(), nullptr);
EXPECT_EQ(parser.GetBufferSize(), 0);
}
TEST(TfLiteSettingsJsonParserTest, FailedToVerifyDueToTooManyTables) {
std::string temp_file_path =
testing::TempDir() + "/test_too_many_tables.json";
std::string payload;
uint64_t n = 1000000;
payload.reserve(100 + n * 3);
payload += "{\"edgetpu_settings\":{\"inactive_power_configs\":[{}";
for (uint64_t i = 1; i < n; ++i) {
payload += ",{}";
}
payload += "]}}";
ASSERT_TRUE(flatbuffers::SaveFile(temp_file_path.c_str(), payload.c_str(),
payload.size(), false));
TfLiteSettingsJsonParser parser;
EXPECT_EQ(parser.Parse(temp_file_path), nullptr);
EXPECT_EQ(parser.GetBufferPointer(), nullptr);
}
} // namespace
@@ -0,0 +1,28 @@
VERS_1.0 {
# Export the stable delegate API.
global:
# Export stable delegate symbol.
extern "C" {
TFL_TheStableDelegate;
};
# Export operator new/delete. These are defined as weak symbols and we need to keep these
# symbols to ensure that if the main binary overrides these, we use the same version in the
# stable delegate, since data that is allocated by stable delegate may be deallocated elsewhere.
extern "C++" {
# The syntax here is a bit awkward. Here we want to match against a demangled symbol name
# that contains spaces, but we also want to use glob-style pattern matching.
# The linker script syntax doesn't allow spaces in symbol names unless quoted;
# but if you do quote the symbol name, then it doesn't do glob-style pattern matching.
# So we can't use quotes. Instead we just use the wildcard character "?" to match space,
# which is not ideal, since it would also match any other character. But these patterns
# are nevertheless sufficiently unique that they are unlikely to match any symbols other
# than the overloaded global ::operator new and ::operator delete functions.
operator?new*;
operator?delete*;
};
# Hide everything else.
local:
*;
};