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
+146
View File
@@ -0,0 +1,146 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
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(
name = "simple_delegate",
srcs = [
"simple_delegate.cc",
],
hdrs = [
"simple_delegate.h",
],
compatible_with = get_compatible_with_portable(),
deps = [
"//tensorflow/lite:array",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates:utils",
"//tensorflow/lite/kernels/internal:compatibility",
],
)
cc_test(
name = "simple_delegate_test",
srcs = ["simple_delegate_test.cc"],
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/core/kernels:builtin_ops",
"//tensorflow/lite/delegates/utils/dummy_delegate",
"@com_google_googletest//:gtest_main",
],
)
cc_library_with_tflite(
name = "simple_opaque_delegate",
srcs = ["simple_opaque_delegate.cc"],
hdrs = ["simple_opaque_delegate.h"],
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",
],
deps = [
"//tensorflow/lite:array",
"//tensorflow/lite:builtin_ops",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite:util",
"//tensorflow/lite/kernels/internal:compatibility",
],
)
filegroup(
name = "c_api_test_builtin_op_models",
testonly = 1,
srcs = [
"//tensorflow/lite:testdata/add.bin",
"//tensorflow/lite:testdata/conv_huge_im2col.bin",
"//tensorflow/lite:testdata/multi_add.bin",
],
)
cc_test(
name = "simple_opaque_delegate_test",
srcs = ["simple_opaque_delegate_test.cc"],
data = [":c_api_test_builtin_op_models"],
deps = [
":simple_opaque_delegate",
"//tensorflow/lite:builtin_ops",
"//tensorflow/lite:framework_stable",
"//tensorflow/lite/c:c_api",
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/delegates:delegate_test_util",
"//tensorflow/lite/delegates/utils/experimental/sample_stable_delegate",
"//tensorflow/lite/kernels:builtin_ops",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels/internal:compatibility",
"@com_google_googletest//:gtest_main",
],
)
cc_library_with_tflite(
name = "async_type_helpers",
srcs = ["async_type_helpers.cc"],
hdrs = ["async_type_helpers.h"],
tflite_deps = [
":ret_macros",
"//tensorflow/lite/async/interop/c:attribute_map",
"//tensorflow/lite/async/interop/c:constants",
"//tensorflow/lite/async/interop/c:types",
],
)
cc_library_with_tflite(
name = "utils",
srcs = ["utils.cc"],
hdrs = ["utils.h"],
tflite_deps = [
"//tensorflow/lite/c:c_api_experimental",
"//tensorflow/lite/c:c_api_types",
":ret_macros",
],
deps = [
"//tensorflow/lite:array",
"//tensorflow/lite:minimal_logging",
"@com_google_absl//absl/status",
],
)
cc_library_with_tflite(
name = "ret_macros",
srcs = [],
hdrs = ["ret_macros.h"],
tflite_deps = ["//tensorflow/lite/c:c_api_types"],
deps = [
"//tensorflow/lite:minimal_logging",
],
)
cc_library_with_tflite(
name = "sync_fence",
srcs = ["sync_fence.cc"],
hdrs = ["sync_fence.h"],
tflite_deps = [
":ret_macros",
],
deps = [
"//tensorflow/lite:minimal_logging",
"@com_google_absl//absl/types:span",
],
)
@@ -0,0 +1,176 @@
/* 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/async_type_helpers.h"
#include <cstring>
#include "tensorflow/lite/async/interop/c/attribute_map.h"
#include "tensorflow/lite/async/interop/c/constants.h"
#include "tensorflow/lite/async/interop/c/types.h"
#include "tensorflow/lite/delegates/utils/ret_macros.h"
// TODO(b/191883048): Cleanup this file when refactoring the attribute map
// accessors.
namespace tflite::delegates::utils {
namespace {
BufferType BufferTypeFromString(const char* buffer_type) {
if (buffer_type == nullptr) {
return BufferType::kUnknown;
}
if (std::strcmp(buffer_type, kBufferTypeAHardwareBufferBlob) == 0) {
return BufferType::kAHardwareBufferBlob;
}
return BufferType::kUnknown;
}
const char* StringFromBufferType(BufferType buffer_type) {
switch (buffer_type) {
case BufferType::kAHardwareBufferBlob:
return kBufferTypeAHardwareBufferBlob;
case BufferType::kUnknown:
return "<unknown buffer type>";
}
}
SyncType SyncTypeFromString(const char* sync_type) {
if (sync_type == nullptr) {
return SyncType::kUnknown;
}
if (std::strcmp(sync_type, kTfLiteSyncTypeNoSyncObj) == 0) {
return SyncType::kNoSyncObj;
}
if (std::strcmp(sync_type, kSyncTypeSyncFenceFd) == 0) {
return SyncType::kSyncFenceFd;
}
return SyncType::kUnknown;
}
const char* StringFromSyncType(SyncType sync_type) {
switch (sync_type) {
case SyncType::kNoSyncObj:
return kTfLiteSyncTypeNoSyncObj;
case SyncType::kSyncFenceFd:
return kSyncTypeSyncFenceFd;
case SyncType::kUnknown:
return "<unknown sync type>";
}
}
} // namespace
BufferAttributes ReadBufferAttrs(const TfLiteAttributeMap* attr_map) {
TFLITE_ABORT_CHECK(TfLiteAttributeMapIsBufferAttributeMap(attr_map),
""); // Crash OK
BufferAttributes attrs{};
const char* buffer_type = nullptr;
if (TfLiteAttributeMapGetStringBufferAttr(
attr_map, kTfLiteBufferAttrKeyResourceTypeName, &buffer_type)) {
attrs.buffer_type = BufferTypeFromString(buffer_type);
}
size_t alignment = 0;
if (TfLiteAttributeMapGetSizeTBufferAttr(
attr_map, kTfLiteBufferAttrKeyAlignment, &alignment)) {
attrs.alignment = alignment;
}
size_t padding = 0;
if (TfLiteAttributeMapGetSizeTBufferAttr(
attr_map, kTfLiteBufferAttrKeyPadding, &padding)) {
attrs.padding = padding;
}
size_t offset = 0;
if (TfLiteAttributeMapGetSizeTBufferAttr(attr_map, kTfLiteBufferAttrKeyOffset,
&offset)) {
attrs.offset = offset;
}
size_t size = 0;
if (TfLiteAttributeMapGetSizeTBufferAttr(attr_map, kTfLiteBufferAttrKeySize,
&size)) {
attrs.size = size;
}
return attrs;
}
BufferAttributes ReadBufferAttrs(const ScopedTfLiteAttrMap& attr_map) {
return ReadBufferAttrs(attr_map.get());
}
void WriteBufferAttrs(const BufferAttributes& attrs,
TfLiteAttributeMap* attr_map) {
TFLITE_ABORT_CHECK(TfLiteAttributeMapIsBufferAttributeMap(attr_map),
""); // Crash OK
if (attrs.buffer_type) {
TfLiteAttributeMapSetStringBufferAttr(
attr_map, kTfLiteBufferAttrKeyResourceTypeName,
StringFromBufferType(attrs.buffer_type.value()));
}
if (attrs.alignment) {
TfLiteAttributeMapSetSizeTBufferAttr(
attr_map, kTfLiteBufferAttrKeyAlignment, attrs.alignment.value());
}
if (attrs.padding) {
TfLiteAttributeMapSetSizeTBufferAttr(attr_map, kTfLiteBufferAttrKeyPadding,
attrs.padding.value());
}
if (attrs.offset) {
TfLiteAttributeMapSetSizeTBufferAttr(attr_map, kTfLiteBufferAttrKeyOffset,
attrs.offset.value());
}
if (attrs.size) {
TfLiteAttributeMapSetSizeTBufferAttr(attr_map, kTfLiteBufferAttrKeySize,
attrs.size.value());
}
}
ScopedTfLiteAttrMap WriteBufferAttrs(const BufferAttributes& attrs) {
auto attr_map = CreateScopedTfLiteAttrMap(kTfLiteAttrMapTypeBuffer);
WriteBufferAttrs(attrs, attr_map.get());
return attr_map;
}
SyncAttributes ReadSyncAttrs(const TfLiteAttributeMap* attr_map) {
TFLITE_ABORT_CHECK(TfLiteAttributeMapIsSyncAttributeMap(attr_map),
""); // Crash OK
SyncAttributes attrs{};
const char* sync_type = nullptr;
if (TfLiteAttributeMapGetStringSyncAttr(
attr_map, kTfLiteSynchronizationAttrKeyObjectTypeName, &sync_type)) {
attrs.sync_type = SyncTypeFromString(sync_type);
}
return attrs;
}
SyncAttributes ReadSyncAttrs(const ScopedTfLiteAttrMap& attr_map) {
return ReadSyncAttrs(attr_map.get());
}
void WriteSyncAttrs(const SyncAttributes& attrs, TfLiteAttributeMap* attr_map) {
TFLITE_ABORT_CHECK(TfLiteAttributeMapIsSyncAttributeMap(attr_map),
""); // Crash OK
if (attrs.sync_type) {
TfLiteAttributeMapSetStringSyncAttr(
attr_map, kTfLiteSynchronizationAttrKeyObjectTypeName,
StringFromSyncType(attrs.sync_type.value()));
}
}
ScopedTfLiteAttrMap WriteSyncAttrs(const SyncAttributes& attrs) {
auto attr_map = CreateScopedTfLiteAttrMap(kTfLiteAttrMapTypeSync);
WriteSyncAttrs(attrs, attr_map.get());
return attr_map;
}
} // namespace tflite::delegates::utils
@@ -0,0 +1,96 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_ASYNC_TYPE_HELPERS_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_ASYNC_TYPE_HELPERS_H_
#include <memory>
#include <optional>
#include "tensorflow/lite/async/interop/c/attribute_map.h"
#include "tensorflow/lite/async/interop/c/types.h"
namespace tflite::delegates::utils {
constexpr char kBufferTypeAHardwareBufferBlob[] = "ahardware_buffer_blob";
constexpr char kSyncTypeSyncFenceFd[] = "sync_fence_fd";
// RAII wrapper of TfLiteAttributeMap.
using ScopedTfLiteAttrMap =
std::unique_ptr<TfLiteAttributeMap, decltype(&TfLiteAttributeMapDelete)>;
inline ScopedTfLiteAttrMap CreateScopedTfLiteAttrMap(TfLiteAttrMapType type) {
return ScopedTfLiteAttrMap(TfLiteAttributeMapCreate(type),
TfLiteAttributeMapDelete);
}
// RAII wrapper of TfLiteBackendBuffer.
using ScopedTfLiteBackendBuffer =
std::unique_ptr<TfLiteBackendBuffer, decltype(&TfLiteBackendBufferDelete)>;
inline ScopedTfLiteBackendBuffer CreateScopedTfLiteBackendBuffer() {
return ScopedTfLiteBackendBuffer(TfLiteBackendBufferCreate(),
TfLiteBackendBufferDelete);
}
// RAII wrapper of TfLiteSynchronization.
using ScopedTfLiteSynchronization =
std::unique_ptr<TfLiteSynchronization,
decltype(&TfLiteSynchronizationDelete)>;
inline ScopedTfLiteSynchronization CreateScopedTfLiteSynchronization() {
return ScopedTfLiteSynchronization(TfLiteSynchronizationCreate(),
TfLiteSynchronizationDelete);
}
enum class BufferType { kUnknown, kAHardwareBufferBlob };
struct BufferAttributes {
std::optional<BufferType> buffer_type;
std::optional<size_t> alignment;
std::optional<size_t> padding;
std::optional<size_t> offset;
std::optional<size_t> size;
};
// Converts TfLiteAttributeMap to BufferAttributes.
// Crashes if the input attr map is not a buffer attr map.
BufferAttributes ReadBufferAttrs(const TfLiteAttributeMap* attr_map);
BufferAttributes ReadBufferAttrs(const ScopedTfLiteAttrMap& attr_map);
// Converts BufferAttributes to TfLiteAttributeMap.
// Crashes if the input mutable attr map is not a buffer attr map.
void WriteBufferAttrs(const BufferAttributes& attrs,
TfLiteAttributeMap* attr_map);
ScopedTfLiteAttrMap WriteBufferAttrs(const BufferAttributes& attrs);
enum class SyncType { kUnknown, kNoSyncObj, kSyncFenceFd };
struct SyncAttributes {
std::optional<SyncType> sync_type;
};
// Converts TfLiteAttributeMap to SyncAttributes.
// Crashes if the input attr map is not a sync attr map.
SyncAttributes ReadSyncAttrs(const TfLiteAttributeMap* attr_map);
SyncAttributes ReadSyncAttrs(const ScopedTfLiteAttrMap& attr_map);
// Converts SyncAttributes to TfLiteAttributeMap.
// Crashes if the input mutable attr map is not a sync attr map.
void WriteSyncAttrs(const SyncAttributes& attrs, TfLiteAttributeMap* attr_map);
ScopedTfLiteAttrMap WriteSyncAttrs(const SyncAttributes& attrs);
} // namespace tflite::delegates::utils
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_ASYNC_TYPE_HELPERS_H_
@@ -0,0 +1,115 @@
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//tensorflow/lite:build_def.bzl", "tflite_copts")
load("//tensorflow/lite/tools/evaluation/tasks:build_def.bzl", "task_linkopts")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library(
name = "dummy_delegate",
srcs = [
"dummy_delegate.cc",
],
hdrs = [
"dummy_delegate.h",
],
deps = [
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/utils:simple_delegate",
],
)
cc_binary(
name = "dummy_external_delegate.so",
srcs = [
"external_delegate_adaptor.cc",
],
defines = ["TFL_EXTERNAL_DELEGATE_COMPILE_LIBRARY"],
linkshared = 1,
linkstatic = 1,
deps = [
":dummy_delegate",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/external:external_delegate_interface",
"//tensorflow/lite/tools:command_line_flags",
"//tensorflow/lite/tools:logging",
],
)
#### The following are for using the dummy test delegate in TFLite tooling ####
cc_library(
name = "dummy_delegate_provider",
srcs = ["dummy_delegate_provider.cc"],
copts = tflite_copts(),
deps = [
":dummy_delegate",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/tools:command_line_flags",
"//tensorflow/lite/tools:tool_params",
"//tensorflow/lite/tools/delegates:delegate_provider_hdr",
],
alwayslink = 1,
)
cc_binary(
name = "benchmark_model_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/benchmark:benchmark_model_main",
],
)
cc_binary(
name = "inference_diff_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/inference_diff:run_eval_lib",
],
)
cc_binary(
name = "imagenet_classification_eval_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/imagenet_image_classification:run_eval_lib",
],
)
cc_binary(
name = "coco_object_detection_eval_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/coco_object_detection:run_eval_lib",
],
)
sh_test(
name = "external_delegate_test",
srcs = ["external_delegate_test.sh"],
data = [
"//tensorflow/lite/delegates/utils/dummy_delegate:dummy_external_delegate.so",
"//tensorflow/lite/tools/benchmark:benchmark_model",
"@tflite_mobilenet_float//:mobilenet_v1_1.0_224.tflite",
],
visibility = ["//visibility:private"],
)
@@ -0,0 +1,164 @@
When speaking of a TFLite delegate, how to create it and how to reuse existing
TFLite testing and tooling with the new delegate are two major challenging
issues. Here, we show a dummy delegate implementation to illustrate our
recommended approaches to address these issues.
## Delegate Creation
We recommend using
[SimpleDelegateInterface and SimpleDelegateKernelInterface](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/simple_delegate.h).
We believe such APIs will make it easier to create a TFLite delegate. At a high
level, developers only need to focus on
* Whether a TFLite node in the graph is supported by the delegate or not.
* Given the set of supported nodes (i.e. a subgraph of the original model
graph), implement a delegate kernel that executes this set of nodes.
The dummy delegate implementation here is a good starting point to understand
the ideas above. For more sophisticated examples, refer to [Flex delegate](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/delegates/flex),
[Hexagon delegate](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/delegates/hexagon).
## Testing & Tooling
There are currently **two options** to plug in a newly created TFLite delegate
to reuse existing TFLite kernel tests and tooling:
- Utilize the **[delegate registrar](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/delegates)**
mechanism
- Utilize the
**[external delegate](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/delegates/external)**
mechanism.
The former approach requires few changes as detailed below. The latter one
requires even fewer changes and works with pre-built Tensorflow Lite tooling
binaries. However, it is less explicit and it might be more complicated to set
up in automated integration tests. Therefore, for better clarity, the
delegate-registrar approach is slightly preferred here.
We now describe each option above in more details in the following sections.
### Option 1: Utilize Delegate Registrar
In this approach, create a delegate provider like the
[`dummy_delegate_provider.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/dummy_delegate/dummy_delegate_provider.cc)
here, and then add it as an extra dependency when building the binary. Refer
[here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/delegates)
for more delegate provider examples. Now we look at using this provider for
testing and evaluation.
#### Kernel Tests
Tests referred here are defined in [tensorflow/lite/kernels](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/kernels).
They are based on the
[test_util library](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/kernels/test_util.h)
and the [testing main function stub](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/kernels/test_main.cc).
To plug in the newly created delegate and reuse these tests, simply add the
created delegate provider as an extra dependency to
[`test_util_delegate_providers`](https://github.com/tensorflow/tensorflow/blob/f09dc5cf6e7fde978f9891638f529cd52a3c878f/tensorflow/lite/kernels/BUILD#L203)
and remove others that are not relevant, like the following:
```
cc_library(
name = "tflite_driver_delegate_providers",
deps = [
# Existing delegate providers that might be still relevant.
":dummy_delegate_provider",
],
alwayslink = 1,
)
```
Then build a kernel test, and specify the commandline flags defined in the
delegate provider when executing the test. Take this case as an example,
```
bazel build -c opt tensorflow/lite/kernels:add_test
# Setting --use_dummy_delegate=true will apply the dummy delegate to the
# TFLite model graph
bazel-bin/tensorflow/lite/kernels/add_test --use_dummy_delegate=true
```
#### Benchmark and Task Evaluation Tools
In TFLite, we have developed
[model benchmark tool](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark)
and
[evaluation tools](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/evaluation/tasks)
that already have integrated existing various TFLite delegates. To reuse these
tools for the new delegate, similar to the kernel testing above, we simply add
the created delegate provider as an additional dependency when building the
binary. See rules in the
[BUILD](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/BUILD)
file for details.
Take reusing the TFLite model benchmark tool as an example, after the delegate
provider is created, define the BUILD rule like the following:
```
cc_binary(
name = "benchmark_model_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
# Simply add the delegate provider as an extra dep.
":dummy_delegate_provider",
"//tensorflow/lite/tools/benchmark:benchmark_model_main",
],
)
```
Now build the binary, and specify the commandline flags defined in this new
delegate provider and others detailed in the benchmark model tool
[doc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/benchmark/README.md)
when running the benchmark tool like the following:
```
bazel build -c opt tensorflow/lite/delegates/utils/dummy_delegate:benchmark_model_plus_dummy_delegate
# Setting --use_dummy_delegate=true will apply the dummy delegate to the
# TFLite model graph.
bazel-bin/tensorflow/lite/delegates/utils/dummy_delegate/benchmark_model_plus_dummy_delegate --graph=/tmp/mobilenet-v2.tflite --use_dummy_delegate=true
```
### Option 2: Utilize Tensorflow Lite External Delegate
In this **alternative approach to reuse existing Tensorflow Lite kernel testing
and tooling**, we first create an external delegate adaptor like the [`external_delegate_adaptor.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/utils/dummy_delegate/external_delegate_adaptor.cc) here, and create the corresponding BUILD target
to build a dynamic library.
Afterwards, one could build binaries or use pre-built ones to run with the
dummy delegate as long as the binary is linked with the
[`external_delegate_provider`](https://github.com/tensorflow/tensorflow/blob/8c6f2d55762f3fc94f98fdd8b3c5d59ee1276dba/tensorflow/lite/tools/delegates/BUILD#L145-L159)
library which supports command-line flags as described
[here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/delegates#external-delegate-provider).
Note this external delegate provider has already been linked to existing testing
and tooling binaries.
For example, the following illustrates how to benchmark the dummy delegate here
via this external-delegate approach. We could use similar commands for testing
and evaluation tools.
```
bazel build -c opt tensorflow/lite/delegates/utils/dummy_delegate:dummy_external_delegate.so
# Copy the .so file to the directory that the external delegate will be loaded
# from at your choice.
cp bazel-bin/tensorflow/lite/delegates/utils/dummy_delegate/dummy_external_delegate.so /tmp
bazel build -c opt tensorflow/lite/tools/benchmark:benchmark_model
# Setting a non-empty --external_delegate_path value will trigger applying
# the external delegate during runtime.
bazel-bin/tensorflow/lite/tools/benchmark/benchmark_model \
--graph=/tmp/mobilenet-v2.tflite \
--external_delegate_path=/tmp/dummy_external_delegate.so \
--external_delegate_options='error_during_init:true;error_during_prepare:true'
```
It is worth noting the *external delegate* is the corresponding C++
implementation of the *delegate* in Tensorflow Lite Python binding as shown
[here](https://github.com/tensorflow/tensorflow/blob/7145fc0e49be01ef6943f4df386ce38567e37797/tensorflow/lite/python/interpreter.py#L42).
Therefore, the dynamic external delegate adaptor library created here could be
directly used with Tensorflow Lite Python APIs.
More detailed guide on TFLite delegate is coming soon.
@@ -0,0 +1,107 @@
/* Copyright 2020 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/dummy_delegate/dummy_delegate.h"
#include <memory>
#include <utility>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/utils/simple_delegate.h"
namespace tflite {
namespace dummy_test {
// Dummy delegate kernel.
class DummyDelegateKernel : public SimpleDelegateKernelInterface {
public:
explicit DummyDelegateKernel(const DummyDelegateOptions& options)
: options_(options) {}
TfLiteStatus Init(TfLiteContext* context,
const TfLiteDelegateParams* params) override {
return !options_.error_during_init ? kTfLiteOk : kTfLiteError;
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) override {
return !options_.error_during_prepare ? kTfLiteOk : kTfLiteError;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) override {
return !options_.error_during_invoke ? kTfLiteOk : kTfLiteError;
}
private:
const DummyDelegateOptions options_;
};
// DummyDelegate implements the interface of SimpleDelegateInterface.
// This holds the Delegate capabilities.
class DummyDelegate : public SimpleDelegateInterface {
public:
explicit DummyDelegate(const DummyDelegateOptions& options)
: options_(options) {}
bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) const override {
return options_.allowed_builtin_code == registration->builtin_code;
}
TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }
const char* Name() const override {
static constexpr char kName[] = "DummyDelegate";
return kName;
}
std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
override {
return std::make_unique<DummyDelegateKernel>(options_);
}
SimpleDelegateInterface::Options DelegateOptions() const override {
// Use default options.
return SimpleDelegateInterface::Options();
}
private:
const DummyDelegateOptions options_;
};
} // namespace dummy_test
} // namespace tflite
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault() {
DummyDelegateOptions options = {0};
// Just assign an invalid builtin code so that this dummy test delegate will
// not support any node by default.
options.allowed_builtin_code = -1;
return options;
}
// Creates a new delegate instance that need to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options) {
std::unique_ptr<tflite::dummy_test::DummyDelegate> dummy(
new tflite::dummy_test::DummyDelegate(
options ? *options : TfLiteDummyDelegateOptionsDefault()));
return tflite::TfLiteDelegateFactory::CreateSimpleDelegate(std::move(dummy));
}
// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate) {
tflite::TfLiteDelegateFactory::DeleteSimpleDelegate(delegate);
}
@@ -0,0 +1,61 @@
/* Copyright 2020 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_DUMMY_DELEGATE_DUMMY_DELEGATE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_DUMMY_DELEGATE_DUMMY_DELEGATE_H_
#include <memory>
#include "tensorflow/lite/core/c/common.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef struct {
// Allowed ops to delegate.
int allowed_builtin_code;
// Report error during init.
bool error_during_init;
// Report error during prepare.
bool error_during_prepare;
// Report error during invoke.
bool error_during_invoke;
} DummyDelegateOptions;
// Returns a structure with the default delegate options.
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault();
// Creates a new delegate instance that needs to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options);
// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate);
#ifdef __cplusplus
}
#endif // __cplusplus
// A convenient wrapper that returns C++ std::unique_ptr for automatic memory
// management.
inline std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>
TfLiteDummyDelegateCreateUnique(const DummyDelegateOptions* options) {
return std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>(
TfLiteDummyDelegateCreate(options), TfLiteDummyDelegateDelete);
}
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_DUMMY_DELEGATE_DUMMY_DELEGATE_H_
@@ -0,0 +1,76 @@
/* Copyright 2020 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 <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/utils/dummy_delegate/dummy_delegate.h"
#include "tensorflow/lite/tools/command_line_flags.h"
#include "tensorflow/lite/tools/delegates/delegate_provider.h"
#include "tensorflow/lite/tools/tool_params.h"
namespace tflite {
namespace tools {
class DummyDelegateProvider : public DelegateProvider {
public:
DummyDelegateProvider() {
default_params_.AddParam("use_dummy_delegate",
ToolParam::Create<bool>(false));
}
std::vector<Flag> CreateFlags(ToolParams* params) const final;
void LogParams(const ToolParams& params, bool verbose) const final;
TfLiteDelegatePtr CreateTfLiteDelegate(const ToolParams& params) const final;
std::pair<TfLiteDelegatePtr, int> CreateRankedTfLiteDelegate(
const ToolParams& params) const final;
std::string GetName() const final { return "DummyDelegate"; }
};
REGISTER_DELEGATE_PROVIDER(DummyDelegateProvider);
std::vector<Flag> DummyDelegateProvider::CreateFlags(ToolParams* params) const {
std::vector<Flag> flags = {CreateFlag<bool>("use_dummy_delegate", params,
"use the dummy delegate.")};
return flags;
}
void DummyDelegateProvider::LogParams(const ToolParams& params,
bool verbose) const {
LOG_TOOL_PARAM(params, bool, "use_dummy_delegate", "Use dummy test delegate",
verbose);
}
TfLiteDelegatePtr DummyDelegateProvider::CreateTfLiteDelegate(
const ToolParams& params) const {
if (params.Get<bool>("use_dummy_delegate")) {
auto default_options = TfLiteDummyDelegateOptionsDefault();
return TfLiteDummyDelegateCreateUnique(&default_options);
}
return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}
std::pair<TfLiteDelegatePtr, int>
DummyDelegateProvider::CreateRankedTfLiteDelegate(
const ToolParams& params) const {
auto ptr = CreateTfLiteDelegate(params);
return std::make_pair(std::move(ptr),
params.GetPosition<bool>("use_dummy_delegate"));
}
} // namespace tools
} // namespace tflite
@@ -0,0 +1,107 @@
/* Copyright 2020 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 <cstddef>
#include <string>
#include <vector>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/external/external_delegate_interface.h"
#include "tensorflow/lite/delegates/utils/dummy_delegate/dummy_delegate.h"
#include "tensorflow/lite/tools/command_line_flags.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace tools {
TfLiteDelegate* CreateDummyDelegateFromOptions(
const char* const* options_keys, const char* const* options_values,
size_t num_options) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
// Parse key-values options to DummyDelegateOptions by mimicking them as
// command-line flags.
std::vector<const char*> argv;
argv.reserve(num_options + 1);
constexpr char kDummyDelegateParsing[] = "dummy_delegate_parsing";
argv.push_back(kDummyDelegateParsing);
std::vector<std::string> option_args;
option_args.reserve(num_options);
for (int i = 0; i < num_options; ++i) {
option_args.emplace_back("--");
option_args.rbegin()->append(options_keys[i]);
option_args.rbegin()->push_back('=');
option_args.rbegin()->append(options_values[i]);
argv.push_back(option_args.rbegin()->c_str());
}
constexpr char kAllowedBuiltinOp[] = "allowed_builtin_code";
constexpr char kReportErrorDuringInit[] = "error_during_init";
constexpr char kReportErrorDuringPrepare[] = "error_during_prepare";
constexpr char kReportErrorDuringInvoke[] = "error_during_invoke";
std::vector<tflite::Flag> flag_list = {
tflite::Flag::CreateFlag(kAllowedBuiltinOp, &options.allowed_builtin_code,
"Allowed builtin code."),
tflite::Flag::CreateFlag(kReportErrorDuringInit,
&options.error_during_init,
"Report error during init."),
tflite::Flag::CreateFlag(kReportErrorDuringPrepare,
&options.error_during_prepare,
"Report error during prepare."),
tflite::Flag::CreateFlag(kReportErrorDuringInvoke,
&options.error_during_invoke,
"Report error during invoke."),
};
int argc = num_options + 1;
if (!tflite::Flags::Parse(&argc, argv.data(), flag_list)) {
return nullptr;
}
TFLITE_LOG(INFO) << "Dummy delegate: allowed_builtin_code set to "
<< options.allowed_builtin_code << ".";
TFLITE_LOG(INFO) << "Dummy delegate: error_during_init set to "
<< options.error_during_init << ".";
TFLITE_LOG(INFO) << "Dummy delegate: error_during_prepare set to "
<< options.error_during_prepare << ".";
TFLITE_LOG(INFO) << "Dummy delegate: error_during_invoke set to "
<< options.error_during_invoke << ".";
return TfLiteDummyDelegateCreate(&options);
}
} // namespace tools
} // namespace tflite
extern "C" {
// Defines two symbols that need to be exported to use the TFLite external
// delegate. See tensorflow/lite/delegates/external for details.
extern TFL_EXTERNAL_DELEGATE_EXPORT TfLiteDelegate*
tflite_plugin_create_delegate(const char* const* options_keys,
const char* const* options_values,
size_t num_options,
void (*report_error)(const char*)) {
return tflite::tools::CreateDummyDelegateFromOptions(
options_keys, options_values, num_options);
}
TFL_EXTERNAL_DELEGATE_EXPORT void tflite_plugin_destroy_delegate(
TfLiteDelegate* delegate) {
TfLiteDummyDelegateDelete(delegate);
}
} // extern "C"
@@ -0,0 +1,35 @@
#!/bin/bash
# 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.
# ==============================================================================
set -o errexit
set -o nounset
readonly benchmark_tool=tensorflow/lite/tools/benchmark/benchmark_model
readonly external_delegate=tensorflow/lite/delegates/utils/dummy_delegate/dummy_external_delegate.so
readonly model=external/tflite_mobilenet_float/mobilenet_v1_1.0_224.tflite
readonly benchmark_log=/tmp/benchmark.out
die() { echo "$@" >&2; exit 1; }
$benchmark_tool --graph=$model \
--external_delegate_path=$external_delegate \
--external_delegate_options='error_during_init:true;error_during_prepare:true' \
>& $benchmark_log
cat $benchmark_log
grep -q 'EXTERNAL delegate created.' $benchmark_log \
|| die "Didn't find expected log contents"
echo "PASS"
@@ -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:
*;
};
@@ -0,0 +1,81 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_RET_MACROS_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_RET_MACROS_H_
#include <cstddef>
#include <cstdlib>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/minimal_logging.h"
// Evaluate an expression whose type is std::optional<T>. If it returns an
// instance that contains a value, then initialize the declaration with that
// value; otherwise, return the specified error value.
#define TFLITE_ASSIGN_OR_RETURN(declaration, expr, err) \
TFLITE_ASSIGN_OR_RETURN_IMPL( \
TFLITE_ASSIGN_OR_RETURN_MACROS_CONCAT_NAME(_maybe, __COUNTER__), \
declaration, expr, err);
#define TFLITE_ASSIGN_OR_RETURN_IMPL(maybe, declaration, expr, err) \
auto maybe = (expr); \
if (!maybe.has_value()) return (err); \
declaration = *std::move(maybe)
#define TFLITE_ASSIGN_OR_RETURN_MACROS_CONCAT_NAME(x, y) \
TFLITE_ASSIGN_OR_RETURN_MACROS_CONCAT_IMPL(x, y)
#define TFLITE_ASSIGN_OR_RETURN_MACROS_CONCAT_IMPL(x, y) x##y
// If the specified condition is false, log the specified message and return the
// specified error value.
#define TFLITE_RET_CHECK(c, m, r) \
TFLITE_RET_CHECK_IMPL(c, m, r, __FILE__, __LINE__)
#define TFLITE_RET_CHECK_IMPL(c, m, r, file, line) \
do { \
if (!(c)) { \
::tflite::delegates::utils::TfLiteCheckLog("TFLITE_RET_CHECK", file, \
line, #c, m); \
return (r); \
} \
} while (false)
// If the specified condition is false, log the specified message and return
// kTfLiteDelegateError.
#define TFLITE_RET_CHECK_STATUS(c, m) \
TFLITE_RET_CHECK(c, m, kTfLiteDelegateError)
// If the specified condition is false, log the specified message and abort().
#define TFLITE_ABORT_CHECK(c, m) \
TFLITE_ABORT_CHECK_IMPL(c, m, __FILE__, __LINE__)
#define TFLITE_ABORT_CHECK_IMPL(c, m, file, line) \
do { \
if (!(c)) { \
::tflite::delegates::utils::TfLiteCheckLog("TFLITE_ABORT_CHECK", file, \
line, #c, m); \
std::abort(); \
} \
} while (false)
namespace tflite::delegates::utils {
inline void TfLiteCheckLog(const char* kind, const char* file, std::size_t line,
const char* cond, const char* message) {
TFLITE_LOG_PROD(::tflite::TFLITE_LOG_ERROR, "%s failure (%s:%zu) %s \"%s\"",
kind, file, line, cond, message);
}
} // namespace tflite::delegates::utils
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_RET_MACROS_H_
@@ -0,0 +1,162 @@
/* Copyright 2020 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/simple_delegate.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "tensorflow/lite/array.h"
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/utils.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite {
namespace {
TfLiteRegistration GetDelegateKernelRegistration(
SimpleDelegateInterface* delegate) {
TfLiteRegistration kernel_registration{};
kernel_registration.profiling_string = nullptr;
kernel_registration.builtin_code = kTfLiteBuiltinDelegate;
kernel_registration.custom_name = delegate->Name();
kernel_registration.version = 1;
kernel_registration.free = [](TfLiteContext* context, void* buffer) -> void {
delete reinterpret_cast<SimpleDelegateKernelInterface*>(buffer);
};
kernel_registration.init = [](TfLiteContext* context, const char* buffer,
size_t length) -> void* {
const TfLiteDelegateParams* params =
reinterpret_cast<const TfLiteDelegateParams*>(buffer);
if (params == nullptr) {
TF_LITE_KERNEL_LOG(context, "NULL TfLiteDelegateParams passed.");
return TfLiteKernelInitFailed();
}
auto* delegate =
reinterpret_cast<SimpleDelegateInterface*>(params->delegate->data_);
std::unique_ptr<SimpleDelegateKernelInterface> delegate_kernel(
delegate->CreateDelegateKernelInterface());
if (delegate_kernel->Init(context, params) != kTfLiteOk) {
return TfLiteKernelInitFailed();
}
return delegate_kernel.release();
};
kernel_registration.prepare = [](TfLiteContext* context,
TfLiteNode* node) -> TfLiteStatus {
if (node->user_data == nullptr) {
TF_LITE_KERNEL_LOG(context, "Delegate kernel was not initialized");
return kTfLiteError;
}
SimpleDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleDelegateKernelInterface*>(node->user_data);
return delegate_kernel->Prepare(context, node);
};
kernel_registration.invoke = [](TfLiteContext* context,
TfLiteNode* node) -> TfLiteStatus {
SimpleDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleDelegateKernelInterface*>(node->user_data);
TFLITE_DCHECK(delegate_kernel != nullptr);
return delegate_kernel->Eval(context, node);
};
return kernel_registration;
}
TfLiteStatus DelegatePrepare(TfLiteContext* context,
TfLiteDelegate* base_delegate) {
auto* delegate =
reinterpret_cast<SimpleDelegateInterface*>(base_delegate->data_);
auto delegate_options = delegate->DelegateOptions();
if (delegate_options.max_delegated_partitions <= 0)
delegate_options.max_delegated_partitions = std::numeric_limits<int>::max();
TF_LITE_ENSURE_STATUS(delegate->Initialize(context));
delegates::IsNodeSupportedFn node_supported_fn =
[=](TfLiteContext* context, TfLiteNode* node,
TfLiteRegistration* registration,
std::string* unsupported_details) -> bool {
return delegate->IsNodeSupportedByDelegate(registration, node, context);
};
// TODO(b/149484598): Update to have method that gets all supported nodes.
delegates::GraphPartitionHelper helper(context, node_supported_fn);
TF_LITE_ENSURE_STATUS(helper.Partition(nullptr));
std::vector<int> supported_nodes = helper.GetNodesOfFirstNLargestPartitions(
delegate_options.max_delegated_partitions,
delegate_options.min_nodes_per_partition);
TFLITE_LOG_PROD_ONCE(tflite::TFLITE_LOG_INFO,
"%s delegate: %d nodes delegated out of %d nodes with "
"%d partitions.\n",
delegate->Name(), supported_nodes.size(),
helper.num_total_nodes(), helper.num_partitions());
TfLiteRegistration delegate_kernel_registration =
GetDelegateKernelRegistration(delegate);
return context->ReplaceNodeSubsetsWithDelegateKernels(
context, delegate_kernel_registration,
BuildTfLiteArray(supported_nodes).get(), base_delegate);
}
} // namespace
TfLiteDelegate* TfLiteDelegateFactory::CreateSimpleDelegate(
std::unique_ptr<SimpleDelegateInterface> simple_delegate, int64_t flag) {
if (simple_delegate == nullptr) {
return nullptr;
}
auto delegate = new TfLiteDelegate{};
delegate->Prepare = &DelegatePrepare;
delegate->flags = flag;
delegate->data_ = simple_delegate.release();
delegate->CopyFromBufferHandle = [](TfLiteContext* context,
TfLiteDelegate* delegate,
TfLiteBufferHandle buffer_handle,
TfLiteTensor* tensor) -> TfLiteStatus {
auto* simple_delegate =
reinterpret_cast<SimpleDelegateInterface*>(delegate->data_);
return simple_delegate->CopyFromBufferHandle(context, buffer_handle,
tensor);
};
delegate->CopyToBufferHandle = [](TfLiteContext* context,
TfLiteDelegate* delegate,
TfLiteBufferHandle buffer_handle,
TfLiteTensor* tensor) -> TfLiteStatus {
auto* simple_delegate =
reinterpret_cast<SimpleDelegateInterface*>(delegate->data_);
return simple_delegate->CopyToBufferHandle(context, buffer_handle, tensor);
};
delegate->FreeBufferHandle = [](TfLiteContext* context,
TfLiteDelegate* delegate,
TfLiteBufferHandle* buffer_handle) {
auto* simple_delegate =
reinterpret_cast<SimpleDelegateInterface*>(delegate->data_);
simple_delegate->FreeBufferHandle(context, buffer_handle);
};
return delegate;
}
void TfLiteDelegateFactory::DeleteSimpleDelegate(TfLiteDelegate* delegate) {
if (!delegate) return;
SimpleDelegateInterface* simple_delegate =
reinterpret_cast<SimpleDelegateInterface*>(delegate->data_);
delete simple_delegate;
delete delegate;
}
} // namespace tflite
@@ -0,0 +1,175 @@
/* Copyright 2020 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.
==============================================================================*/
// This file has utilities that facilitates creating new delegates.
// - SimpleDelegateKernelInterface: Represents a Kernel which handles a subgraph
// to be delegated. It has Init/Prepare/Invoke which are going to be called
// during inference, similar to TFLite Kernels. Delegate owner should implement
// this interface to build/prepare/invoke the delegated subgraph.
// - SimpleDelegateInterface:
// This class wraps TFLiteDelegate and users need to implement the interface and
// then call TfLiteDelegateFactory::CreateSimpleDelegate(...) to get
// TfLiteDelegate* that can be passed to ModifyGraphWithDelegate and free it via
// TfLiteDelegateFactory::DeleteSimpleDelegate(...).
// or call TfLiteDelegateFactory::Create(...) to get a std::unique_ptr
// TfLiteDelegate that can also be passed to ModifyGraphWithDelegate, in which
// case TfLite interpereter takes the memory ownership of the delegate.
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_DELEGATE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_DELEGATE_H_
#include <stdint.h>
#include <memory>
#include <utility>
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
using TfLiteDelegateUniquePtr =
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>;
// Users should inherit from this class and implement the interface below.
// Each instance represents a single part of the graph (subgraph).
class SimpleDelegateKernelInterface {
public:
virtual ~SimpleDelegateKernelInterface() = default;
// Initializes a delegated subgraph.
// The nodes in the subgraph are inside TfLiteDelegateParams->nodes_to_replace
virtual TfLiteStatus Init(TfLiteContext* context,
const TfLiteDelegateParams* params) = 0;
// Will be called by the framework. Should handle any needed preparation
// for the subgraph e.g. allocating buffers, compiling model.
// Returns status, and signalling any errors.
virtual TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) = 0;
// Actual subgraph inference should happen on this call.
// Returns status, and signalling any errors.
// NOTE: Tensor data pointers (tensor->data) can change every inference, so
// the implementation of this method needs to take that into account.
virtual TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) = 0;
};
// Pure Interface that clients should implement.
// The Interface represents a delegate's capabilities and provides a factory
// for SimpleDelegateKernelInterface.
//
// Clients should implement the following methods:
// - IsNodeSupportedByDelegate
// - Initialize
// - Name
// - CreateDelegateKernelInterface
// - DelegateOptions
class SimpleDelegateInterface {
public:
// Properties of a delegate. These are used by TfLiteDelegateFactory to
// help determine how to partition the graph, i.e. which nodes each delegate
// will get applied to.
struct Options {
// Maximum number of delegated subgraph, values <=0 means unlimited.
int max_delegated_partitions = 0;
// The minimum number of nodes allowed in a delegated graph, values <=0
// means unlimited.
int min_nodes_per_partition = 0;
};
virtual ~SimpleDelegateInterface() = default;
// Returns true if 'node' is supported by the delegate. False otherwise.
virtual bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) const = 0;
// Initialize the delegate before finding and replacing TfLite nodes with
// delegate kernels, for example, retrieving some TFLite settings from
// 'context'.
virtual TfLiteStatus Initialize(TfLiteContext* context) = 0;
// Returns a name that identifies the delegate.
// This name is used for debugging/logging/profiling.
virtual const char* Name() const = 0;
// Returns instance of an object that implements the interface
// SimpleDelegateKernelInterface.
// An instance of SimpleDelegateKernelInterface represents one subgraph to
// be delegated.
// Caller takes ownership of the returned object.
virtual std::unique_ptr<SimpleDelegateKernelInterface>
CreateDelegateKernelInterface() = 0;
// Returns SimpleDelegateInterface::Options which has delegate properties
// relevant for graph partitioning.
virtual SimpleDelegateInterface::Options DelegateOptions() const = 0;
/// Optional method for supporting hardware buffers.
/// Copies the data from delegate buffer handle into raw memory of the given
/// `tensor`. Note that the delegate is allowed to allocate the raw bytes as
/// long as it follows the rules for kTfLiteDynamic tensors.
virtual TfLiteStatus CopyFromBufferHandle(TfLiteContext* context,
TfLiteBufferHandle buffer_handle,
TfLiteTensor* tensor) {
return kTfLiteError;
}
/// Optional method for supporting hardware buffers.
/// Copies the data from raw memory of the given `tensor` to delegate buffer
/// handle.
virtual TfLiteStatus CopyToBufferHandle(TfLiteContext* context,
TfLiteBufferHandle buffer_handle,
const TfLiteTensor* tensor) {
return kTfLiteError;
}
/// Optional method for supporting hardware buffers.
/// Frees the Delegate Buffer Handle. Note: This only frees the handle, but
/// this doesn't release the underlying resource (e.g. textures). The
/// resources are either owned by application layer or the delegate.
virtual void FreeBufferHandle(TfLiteContext* context,
TfLiteBufferHandle* handle) {}
};
// Factory class that provides static methods to deal with SimpleDelegate
// creation and deletion.
class TfLiteDelegateFactory {
public:
// Creates TfLiteDelegate from the provided SimpleDelegateInterface.
// The returned TfLiteDelegate should be deleted using DeleteSimpleDelegate.
// A simple usage of the flags bit mask:
// CreateSimpleDelegate(..., kTfLiteDelegateFlagsAllowDynamicTensors |
// kTfLiteDelegateFlagsRequirePropagatedShapes)
static TfLiteDelegate* CreateSimpleDelegate(
std::unique_ptr<SimpleDelegateInterface> simple_delegate,
int64_t flags = kTfLiteDelegateFlagsNone);
// Deletes 'delegate' the passed pointer must be the one returned
// from CreateSimpleDelegate.
// This function will destruct the SimpleDelegate object too.
static void DeleteSimpleDelegate(TfLiteDelegate* delegate);
// A convenient function wrapping the above two functions and returning a
// std::unique_ptr type for auto memory management.
inline static TfLiteDelegateUniquePtr Create(
std::unique_ptr<SimpleDelegateInterface> simple_delegate) {
return TfLiteDelegateUniquePtr(
CreateSimpleDelegate(std::move(simple_delegate)), DeleteSimpleDelegate);
}
};
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_DELEGATE_H_
@@ -0,0 +1,130 @@
/* Copyright 2020 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 <stdlib.h>
#include <memory>
#include <utility>
#include <gtest/gtest.h>
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/kernels/builtin_op_kernels.h"
#include "tensorflow/lite/delegates/utils/dummy_delegate/dummy_delegate.h"
#include "tensorflow/lite/interpreter.h"
namespace tflite {
namespace {
class TestDelegate : public ::testing::Test {
protected:
void SetUp() override {
interpreter_ = std::make_unique<Interpreter>();
interpreter_->AddTensors(5);
interpreter_->SetInputs({0, 1});
interpreter_->SetOutputs({3, 4});
TfLiteQuantizationParams quant;
interpreter_->SetTensorParametersReadWrite(0, kTfLiteFloat32, "", {3},
quant);
interpreter_->SetTensorParametersReadWrite(1, kTfLiteFloat32, "", {3},
quant);
interpreter_->SetTensorParametersReadWrite(2, kTfLiteFloat32, "", {3},
quant);
interpreter_->SetTensorParametersReadWrite(3, kTfLiteFloat32, "", {3},
quant);
interpreter_->SetTensorParametersReadWrite(4, kTfLiteFloat32, "", {3},
quant);
TfLiteRegistration* reg = ops::builtin::Register_ADD();
void* builtin_data_1 = malloc(sizeof(int));
void* builtin_data_2 = malloc(sizeof(int));
void* builtin_data_3 = malloc(sizeof(int));
interpreter_->AddNodeWithParameters({0, 0}, {2}, nullptr, 0, builtin_data_1,
reg);
interpreter_->AddNodeWithParameters({1, 1}, {3}, nullptr, 0, builtin_data_2,
reg);
interpreter_->AddNodeWithParameters({2, 1}, {4}, nullptr, 0, builtin_data_3,
reg);
}
void TearDown() override { interpreter_.reset(); }
protected:
std::unique_ptr<Interpreter> interpreter_;
};
TEST_F(TestDelegate, BasicDelegate) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
options.allowed_builtin_code = kTfLiteBuiltinAdd;
auto delegate = TfLiteDummyDelegateCreateUnique(&options);
interpreter_->ModifyGraphWithDelegate(std::move(delegate));
ASSERT_EQ(interpreter_->execution_plan().size(), 1);
int node = interpreter_->execution_plan()[0];
const auto* node_and_reg = interpreter_->node_and_registration(node);
EXPECT_STREQ("DummyDelegate", node_and_reg->second.custom_name);
EXPECT_EQ(1, node_and_reg->second.version);
const TfLiteDelegateParams* params = static_cast<const TfLiteDelegateParams*>(
node_and_reg->first.builtin_data);
ASSERT_EQ(params->nodes_to_replace->size, 3);
EXPECT_EQ(params->nodes_to_replace->data[0], 0);
EXPECT_EQ(params->nodes_to_replace->data[1], 1);
EXPECT_EQ(params->nodes_to_replace->data[2], 2);
ASSERT_EQ(params->input_tensors->size, 2);
EXPECT_EQ(params->input_tensors->data[0], 0);
EXPECT_EQ(params->input_tensors->data[1], 1);
ASSERT_EQ(params->output_tensors->size, 2);
EXPECT_EQ(params->output_tensors->data[0], 3);
EXPECT_EQ(params->output_tensors->data[1], 4);
}
TEST_F(TestDelegate, NoNodesToDelegate) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
options.allowed_builtin_code = kTfLiteBuiltinSub;
auto delegate = TfLiteDummyDelegateCreateUnique(&options);
interpreter_->ModifyGraphWithDelegate(std::move(delegate));
ASSERT_EQ(interpreter_->execution_plan().size(), 3);
}
TEST_F(TestDelegate, DelegateFailedPrepare) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
options.allowed_builtin_code = kTfLiteBuiltinAdd;
options.error_during_prepare = true;
auto delegate = TfLiteDummyDelegateCreateUnique(&options);
ASSERT_EQ(kTfLiteDelegateError,
interpreter_->ModifyGraphWithDelegate(std::move(delegate)));
}
TEST_F(TestDelegate, DelegateFailedInvoke) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
options.allowed_builtin_code = kTfLiteBuiltinAdd;
options.error_during_invoke = true;
auto delegate = TfLiteDummyDelegateCreateUnique(&options);
ASSERT_EQ(kTfLiteOk,
interpreter_->ModifyGraphWithDelegate(std::move(delegate)));
ASSERT_EQ(kTfLiteError, interpreter_->Invoke());
}
TEST_F(TestDelegate, DelegateFailedInit) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
options.allowed_builtin_code = kTfLiteBuiltinAdd;
options.error_during_init = true;
auto delegate = TfLiteDummyDelegateCreateUnique(&options);
ASSERT_EQ(kTfLiteDelegateError,
interpreter_->ModifyGraphWithDelegate(std::move(delegate)));
}
} // namespace
} // namespace tflite
@@ -0,0 +1,179 @@
/* 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/simple_opaque_delegate.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "tensorflow/lite/array.h"
#include "tensorflow/lite/builtin_ops.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/internal/compatibility.h"
namespace tflite {
namespace {
TfLiteOperator* CreateDelegateKernelRegistration(
SimpleOpaqueDelegateInterface* delegate) {
TfLiteOperator* kernel_registration =
TfLiteOperatorCreate(kTfLiteBuiltinDelegate, delegate->Name(),
/*version=*/1, /*user_data=*/nullptr);
TfLiteOperatorSetFreeWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context, void* buffer) -> void {
// The type used here must match the type returned from the init method
// that we set below.
delete reinterpret_cast<SimpleOpaqueDelegateKernelInterface*>(buffer);
});
TfLiteOperatorSetInitWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context, const char* buffer,
size_t length) -> void* {
const TfLiteOpaqueDelegateParams* params =
reinterpret_cast<const TfLiteOpaqueDelegateParams*>(buffer);
if (params == nullptr) {
return TfLiteKernelInitFailed();
}
auto* delegate_data = reinterpret_cast<SimpleOpaqueDelegateInterface*>(
params->delegate_data);
std::unique_ptr<SimpleOpaqueDelegateKernelInterface> delegate_kernel(
delegate_data->CreateDelegateKernelInterface());
if (delegate_kernel &&
delegate_kernel->Init(context, params) != kTfLiteOk) {
return TfLiteKernelInitFailed();
}
return delegate_kernel.release();
});
TfLiteOperatorSetPrepareWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context,
TfLiteOpaqueNode* opaque_node) -> TfLiteStatus {
SimpleOpaqueDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleOpaqueDelegateKernelInterface*>(
TfLiteOpaqueNodeGetUserData(opaque_node));
if (delegate_kernel == nullptr) {
return kTfLiteDelegateError;
}
return delegate_kernel->Prepare(context, opaque_node);
});
TfLiteOperatorSetInvokeWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context,
TfLiteOpaqueNode* opaque_node) -> TfLiteStatus {
SimpleOpaqueDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleOpaqueDelegateKernelInterface*>(
TfLiteOpaqueNodeGetUserData(opaque_node));
if (delegate_kernel == nullptr) {
return kTfLiteDelegateError;
}
return delegate_kernel->Eval(context, opaque_node);
});
return kernel_registration;
}
TfLiteStatus DelegatePrepare(TfLiteOpaqueContext* opaque_context,
TfLiteOpaqueDelegate* opaque_delegate,
void* data) {
auto* simple_opaque_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(data);
TF_LITE_ENSURE_STATUS(simple_opaque_delegate->Initialize(opaque_context));
std::vector<int> supported_nodes;
TfLiteIntArray* execution_plan;
TF_LITE_ENSURE_STATUS(
TfLiteOpaqueContextGetExecutionPlan(opaque_context, &execution_plan));
IntArrayUniquePtr plan(TfLiteIntArrayCopy(execution_plan));
for (int i = 0; i < plan->size; ++i) {
const int node_id = plan->data[i];
TfLiteOpaqueNode* opaque_node;
TfLiteOperator* registration_external;
TfLiteOpaqueContextGetNodeAndRegistration(
opaque_context, node_id, &opaque_node, &registration_external);
if (simple_opaque_delegate->IsNodeSupportedByDelegate(
registration_external, opaque_node, opaque_context)) {
supported_nodes.push_back(node_id);
}
}
TfLiteOperator* delegate_kernel_registration =
CreateDelegateKernelRegistration(simple_opaque_delegate);
// Transfers ownership of delegate_kernel_registration to the opaque_context.
return TfLiteOpaqueContextReplaceNodeSubsetsWithDelegateKernels(
opaque_context, delegate_kernel_registration,
BuildTfLiteArray(supported_nodes).get(), opaque_delegate);
}
} // namespace
TfLiteOpaqueDelegate* TfLiteOpaqueDelegateFactory::CreateSimpleDelegate(
std::unique_ptr<SimpleOpaqueDelegateInterface> simple_delegate,
int64_t flags) {
if (simple_delegate == nullptr) {
return {};
}
TfLiteOpaqueDelegateBuilder opaque_delegate_builder{};
opaque_delegate_builder.Prepare = &DelegatePrepare;
opaque_delegate_builder.flags = flags;
opaque_delegate_builder.data = simple_delegate.release();
opaque_delegate_builder.CopyFromBufferHandle =
[](TfLiteOpaqueContext* context, TfLiteOpaqueDelegate* delegate,
void* data, TfLiteBufferHandle buffer_handle,
TfLiteOpaqueTensor* tensor) {
auto* simple_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(data);
return simple_delegate->CopyFromBufferHandle(context, buffer_handle,
tensor);
};
opaque_delegate_builder.CopyToBufferHandle =
[](TfLiteOpaqueContext* context, TfLiteOpaqueDelegate* delegate,
void* data, TfLiteBufferHandle buffer_handle,
TfLiteOpaqueTensor* tensor) {
auto* simple_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(data);
return simple_delegate->CopyToBufferHandle(context, buffer_handle,
tensor);
};
opaque_delegate_builder.FreeBufferHandle =
[](TfLiteOpaqueContext* context, TfLiteOpaqueDelegate* delegate,
void* data, TfLiteBufferHandle* buffer_handle) {
auto* simple_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(data);
simple_delegate->FreeBufferHandle(context, buffer_handle);
};
return TfLiteOpaqueDelegateCreate(&opaque_delegate_builder);
}
void TfLiteOpaqueDelegateFactory::DeleteSimpleDelegate(
TfLiteOpaqueDelegate* opaque_delegate) {
if (!opaque_delegate) return;
auto* simple_delegate = reinterpret_cast<SimpleOpaqueDelegateInterface*>(
TfLiteOpaqueDelegateGetData(opaque_delegate));
delete simple_delegate;
TfLiteOpaqueDelegateDelete(opaque_delegate);
}
} // namespace tflite
@@ -0,0 +1,164 @@
/* 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.
==============================================================================*/
// This file has utilities that facilitates creating new opaque delegates.
// - SimpleOpaqueDelegateKernelInterface: Represents a Kernel which handles a
// subgraph to be delegated. It has Init/Prepare/Invoke which are going to be
// called during inference, similar to TFLite Kernels. Delegate owner should
// implement this interface to build/prepare/invoke the delegated subgraph.
// - SimpleOpaqueDelegateInterface:
// This class wraps TFLiteOpaqueDelegate and users need to implement the
// interface and then call
// TfLiteOpaqueDelegateFactory::CreateSimpleDelegate(...) to get a
// TfLiteOpaqueDelegate* that can be passed to
// TfLiteInterpreterOptionsAddDelegate and free it via
// TfLiteOpaqueDelegateFactory::DeleteSimpleDelegate(...),
// or call TfLiteOpaqueDelegateFactory::Create(...) to get a std::unique_ptr
// to TfLiteOpaqueDelegate that can also be passed to
// TfLiteInterpreterOptionsAddDelegate.
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_OPAQUE_DELEGATE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_OPAQUE_DELEGATE_H_
#include <stdint.h>
#include <memory>
#include <utility>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
namespace tflite {
using TfLiteOpaqueDelegateUniquePtr =
std::unique_ptr<TfLiteOpaqueDelegate, void (*)(TfLiteOpaqueDelegate*)>;
// Users should inherit from this class and implement the interface below.
// Each instance represents a single part of the graph (subgraph).
class SimpleOpaqueDelegateKernelInterface {
public:
virtual ~SimpleOpaqueDelegateKernelInterface() = default;
// Initializes a delegated subgraph.
// The nodes in the subgraph are inside
// TfLiteOpaqueDelegateParams->nodes_to_replace
virtual TfLiteStatus Init(TfLiteOpaqueContext* context,
const TfLiteOpaqueDelegateParams* params) = 0;
// Will be called by the framework. Should handle any needed preparation
// for the subgraph e.g. allocating buffers, compiling model.
// Returns status, and signalling any errors.
virtual TfLiteStatus Prepare(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) = 0;
// Actual subgraph inference should happen on this call.
// Returns status, and signalling any errors.
// NOTE: Tensor data pointers (tensor->data) can change every inference, so
// the implementation of this method needs to take that into account.
virtual TfLiteStatus Eval(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node) = 0;
};
// Pure Interface that clients should implement.
// The Interface represents a delegate's capabilities and provides a factory
// for SimpleDelegateKernelInterface.
//
// Clients should implement the following methods:
// - IsNodeSupportedByDelegate
// - Initialize
// - Name
// - CreateDelegateKernelInterface
class SimpleOpaqueDelegateInterface {
public:
virtual ~SimpleOpaqueDelegateInterface() = default;
// Returns true if 'node' is supported by the delegate. False otherwise.
virtual bool IsNodeSupportedByDelegate(
const TfLiteOperator* registration_external, const TfLiteOpaqueNode* node,
TfLiteOpaqueContext* context) const = 0;
// Initialize the delegate before finding and replacing TfLite nodes with
// delegate kernels, for example, retrieving some TFLite settings from
// 'context'.
virtual TfLiteStatus Initialize(TfLiteOpaqueContext* context) = 0;
// Returns a name that identifies the delegate.
// This name is used for debugging/logging/profiling.
virtual const char* Name() const = 0;
// Returns instance of an object that implements the interface
// SimpleDelegateKernelInterface.
// An instance of SimpleDelegateKernelInterface represents one subgraph to
// be delegated.
// Caller takes ownership of the returned object.
virtual std::unique_ptr<SimpleOpaqueDelegateKernelInterface>
CreateDelegateKernelInterface() = 0;
/// Optional method for supporting hardware buffers.
/// Copies the data from delegate buffer handle into raw memory of the given
/// `tensor`. Note that the delegate is allowed to allocate the raw bytes as
/// long as it follows the rules for kTfLiteDynamic tensors.
virtual TfLiteStatus CopyFromBufferHandle(TfLiteOpaqueContext* context,
TfLiteBufferHandle buffer_handle,
TfLiteOpaqueTensor* tensor) {
return kTfLiteError;
}
/// Optional method for supporting hardware buffers.
/// Copies the data from raw memory of the given `tensor` to delegate buffer
/// handle.
virtual TfLiteStatus CopyToBufferHandle(TfLiteOpaqueContext* context,
TfLiteBufferHandle buffer_handle,
const TfLiteOpaqueTensor* tensor) {
return kTfLiteError;
}
/// Optional method for supporting hardware buffers.
/// Frees the Delegate Buffer Handle. Note: This only frees the handle, but
/// this doesn't release the underlying resource (e.g. textures). The
/// resources are either owned by application layer or the delegate.
virtual void FreeBufferHandle(TfLiteOpaqueContext* context,
TfLiteBufferHandle* handle) {}
};
// Factory class that provides static methods to deal with SimpleDelegate
// creation and deletion.
class TfLiteOpaqueDelegateFactory {
public:
// Creates TfLiteDelegate from the provided SimpleOpaqueDelegateInterface.
// The returned TfLiteDelegate should be deleted using DeleteSimpleDelegate.
// A simple usage of the flags bit mask:
// CreateSimpleDelegate(..., kTfLiteDelegateFlagsAllowDynamicTensors |
// kTfLiteDelegateFlagsRequirePropagatedShapes)
static TfLiteOpaqueDelegate* CreateSimpleDelegate(
std::unique_ptr<SimpleOpaqueDelegateInterface> simple_delegate,
int64_t flags = kTfLiteDelegateFlagsNone);
// Deletes 'delegate' the passed pointer must be the one returned from
// CreateSimpleDelegate. This function will destruct the SimpleDelegate object
// too.
static void DeleteSimpleDelegate(TfLiteOpaqueDelegate* opaque_delegate);
// A convenient function wrapping the above two functions and returning a
// std::unique_ptr type for auto memory management.
inline static TfLiteOpaqueDelegateUniquePtr Create(
std::unique_ptr<SimpleOpaqueDelegateInterface> simple_delegate) {
return TfLiteOpaqueDelegateUniquePtr(
CreateSimpleDelegate(std::move(simple_delegate)), DeleteSimpleDelegate);
}
};
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_SIMPLE_OPAQUE_DELEGATE_H_
@@ -0,0 +1,564 @@
/* 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/simple_opaque_delegate.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <array>
#include <memory>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/builtin_ops.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/delegate_test_util.h"
#include "tensorflow/lite/delegates/utils/experimental/sample_stable_delegate/sample_stable_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model_builder.h"
namespace tflite {
class TestDelegate : public ::testing::Test {};
TEST_F(TestDelegate, TestDataAddBin_SingleInputSingleOutput_FullyDelegated) {
//
// Create the opaque delegate
//
TfLiteOpaqueDelegateUniquePtr my_opaque_delegate =
TfLiteOpaqueDelegateFactory::Create(
std::make_unique<example::SampleStableDelegate>());
//
// 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);
TfLiteInterpreterOptionsSetNumThreads(options, 2);
TfLiteInterpreterOptionsAddDelegate(options, my_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);
ASSERT_EQ(TfLiteInterpreterGetInputTensorCount(interpreter), 1);
ASSERT_EQ(TfLiteInterpreterGetOutputTensorCount(interpreter), 1);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
EXPECT_EQ(TfLiteTensorType(input_tensor), kTfLiteFloat32);
EXPECT_NE(TfLiteTensorData(input_tensor), nullptr);
EXPECT_STREQ(TfLiteTensorName(input_tensor), "input");
TfLiteQuantizationParams input_params =
TfLiteTensorQuantizationParams(input_tensor);
EXPECT_EQ(input_params.scale, 0.f);
EXPECT_EQ(input_params.zero_point, 0);
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
//
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
EXPECT_EQ(TfLiteTensorType(output_tensor), kTfLiteFloat32);
EXPECT_NE(TfLiteTensorData(output_tensor), nullptr);
EXPECT_STREQ(TfLiteTensorName(output_tensor), "output");
TfLiteQuantizationParams output_params =
TfLiteTensorQuantizationParams(output_tensor);
EXPECT_EQ(output_params.scale, 0.f);
EXPECT_EQ(output_params.zero_point, 0);
// 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
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
for (int i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], kTensorCellValue * 3);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TEST(DelegateTest,
TestDataAddBin_SingleInputSingleOutput_FullyDelegated_ResizeInputTensors) {
TfLiteOpaqueDelegateUniquePtr my_opaque_delegate =
TfLiteOpaqueDelegateFactory::Create(
std::make_unique<example::SampleStableDelegate>());
TfLiteModel* model =
TfLiteModelCreateFromFile("tensorflow/lite/testdata/add.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsSetNumThreads(options, 2);
TfLiteInterpreterOptionsAddDelegate(options, my_opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
TfLiteInterpreterOptionsDelete(options);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterGetInputTensorCount(interpreter), 1);
ASSERT_EQ(TfLiteInterpreterGetOutputTensorCount(interpreter), 1);
std::array<int, 1> input_dims = {2};
ASSERT_EQ(TfLiteInterpreterResizeInputTensor(
interpreter, 0, input_dims.data(), input_dims.size()),
kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
TfLiteTensor* input_tensor =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
ASSERT_NE(input_tensor, nullptr);
EXPECT_EQ(TfLiteTensorType(input_tensor), kTfLiteFloat32);
EXPECT_EQ(TfLiteTensorNumDims(input_tensor), 1);
EXPECT_EQ(TfLiteTensorDim(input_tensor, 0), 2);
EXPECT_EQ(TfLiteTensorByteSize(input_tensor), sizeof(float) * 2);
EXPECT_NE(TfLiteTensorData(input_tensor), nullptr);
EXPECT_STREQ(TfLiteTensorName(input_tensor), "input");
TfLiteQuantizationParams input_params =
TfLiteTensorQuantizationParams(input_tensor);
EXPECT_EQ(input_params.scale, 0.f);
EXPECT_EQ(input_params.zero_point, 0);
std::array<float, 2> input = {1.f, 3.f};
ASSERT_EQ(TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
input.size() * sizeof(float)),
kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
EXPECT_EQ(TfLiteTensorType(output_tensor), kTfLiteFloat32);
EXPECT_EQ(TfLiteTensorNumDims(output_tensor), 1);
EXPECT_EQ(TfLiteTensorDim(output_tensor, 0), 2);
EXPECT_EQ(TfLiteTensorByteSize(output_tensor), sizeof(float) * 2);
EXPECT_NE(TfLiteTensorData(output_tensor), nullptr);
EXPECT_STREQ(TfLiteTensorName(output_tensor), "output");
TfLiteQuantizationParams output_params =
TfLiteTensorQuantizationParams(output_tensor);
EXPECT_EQ(output_params.scale, 0.f);
EXPECT_EQ(output_params.zero_point, 0);
std::array<float, 2> output;
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
EXPECT_EQ(output[0], 3.f);
EXPECT_EQ(output[1], 9.f);
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TEST(DelegateTest, TestDataMultiAddBin_MultiInputMultiOutput_FullyDelegated) {
TfLiteOpaqueDelegateUniquePtr my_opaque_delegate =
TfLiteOpaqueDelegateFactory::Create(
std::make_unique<example::SampleStableDelegate>());
TfLiteModel* model = TfLiteModelCreateFromFile(
"tensorflow/lite/testdata/multi_add.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsSetNumThreads(options, 2);
TfLiteInterpreterOptionsAddDelegate(options, my_opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
TfLiteInterpreterOptionsDelete(options);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterGetInputTensorCount(interpreter), 4);
ASSERT_EQ(TfLiteInterpreterGetOutputTensorCount(interpreter), 2);
TfLiteTensor* input_tensor0 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
TfLiteTensor* input_tensor1 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/1);
TfLiteTensor* input_tensor2 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/2);
TfLiteTensor* input_tensor3 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/3);
std::vector<TfLiteTensor*> input_tensors{input_tensor0, input_tensor1,
input_tensor2, input_tensor3};
for (TfLiteTensor* input_tensor : input_tensors) {
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);
}
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor0 =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
const TfLiteTensor* output_tensor1 =
TfLiteInterpreterGetOutputTensor(interpreter, 1);
std::vector<const TfLiteTensor*> output_tensors{output_tensor0,
output_tensor1};
for (const TfLiteTensor* output_tensor : output_tensors) {
int64_t n = tflite::NumElements(output_tensor);
std::vector<float> output_tensor_values(n, 0);
ASSERT_EQ(
TfLiteTensorCopyToBuffer(output_tensor, output_tensor_values.data(),
output_tensor_values.size() * sizeof(float)),
kTfLiteOk);
for (int i = 0; i < n; ++i) {
// We know that the model is wired in a way so that every output tensor
// holds the sum of three input tensors. And because every input tensor
// is filled with 1s we can assert on the output tensors storing 3s.
EXPECT_EQ(output_tensor_values[i], 3.f);
}
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
TfLiteOperator* CreateDelegateKernelRegistrationImpl(
SimpleOpaqueDelegateInterface* delegate) {
TfLiteOperator* kernel_registration = TfLiteOperatorCreate(
kTfLiteBuiltinDelegate, delegate->Name(), 1, /*user_data=*/nullptr);
TfLiteOperatorSetFreeWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context, void* buffer) -> void {
delete reinterpret_cast<SimpleOpaqueDelegateInterface*>(buffer);
});
TfLiteOperatorSetInitWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context, const char* buffer,
size_t length) -> void* {
auto* params =
reinterpret_cast<const TfLiteOpaqueDelegateParams*>(buffer);
if (params == nullptr) {
return nullptr;
}
auto* simple_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(
params->delegate_data);
std::unique_ptr<SimpleOpaqueDelegateKernelInterface> delegate_kernel(
simple_delegate->CreateDelegateKernelInterface());
if (delegate_kernel->Init(context, params) != kTfLiteOk) {
return nullptr;
}
return delegate_kernel.release();
});
TfLiteOperatorSetPrepareWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context,
TfLiteOpaqueNode* opaque_node) -> TfLiteStatus {
SimpleOpaqueDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleOpaqueDelegateKernelInterface*>(
TfLiteOpaqueNodeGetUserData(opaque_node));
return delegate_kernel->Prepare(context, opaque_node);
});
TfLiteOperatorSetInvokeWithData(
kernel_registration,
[](void* user_data, TfLiteOpaqueContext* context,
TfLiteOpaqueNode* opaque_node) -> TfLiteStatus {
SimpleOpaqueDelegateKernelInterface* delegate_kernel =
reinterpret_cast<SimpleOpaqueDelegateKernelInterface*>(
TfLiteOpaqueNodeGetUserData(opaque_node));
TFLITE_DCHECK(delegate_kernel != nullptr);
return delegate_kernel->Eval(context, opaque_node);
});
return kernel_registration;
}
using ::tflite::delegates::test_utils::TestFP16Delegation;
TEST_F(TestFP16Delegation, MultipleDelegateKernels) {
auto my_simple_delegate = std::make_unique<example::SampleStableDelegate>();
TfLiteOpaqueDelegate* opaque_delegate =
TfLiteOpaqueDelegateFactory::CreateSimpleDelegate(
std::move(my_simple_delegate));
// The following cast is safe only because this code is part of the
// TF Lite tests. Apps using TF Lite should not rely on
// TfLiteOpaqueDelegate and TfLiteDelegate being equivalent.
ASSERT_EQ(interpreter_->ModifyGraphWithDelegate(
reinterpret_cast<TfLiteDelegate*>(opaque_delegate)),
kTfLiteOk);
// Should have 7 nodes: delegate, mul, add2 & 4 dequantize ops.
ASSERT_EQ(interpreter_->execution_plan().size(), 7);
VerifyInvoke();
TfLiteOpaqueDelegateFactory::DeleteSimpleDelegate(opaque_delegate);
}
// A test facility used in the 'SetBufferHandle' unit test. See the tests
// comments for further context on the implementation of this class.
class MySimpleOpaqueDelegateWithBufferHandleSupport
: public example::SampleStableDelegate {
public:
static constexpr int kDelegateOutputValue = 42;
TfLiteStatus CopyFromBufferHandle(TfLiteOpaqueContext* context,
TfLiteBufferHandle buffer_handle,
TfLiteOpaqueTensor* tensor) override {
auto* output = reinterpret_cast<float*>(TfLiteOpaqueTensorData(tensor));
std::vector<float> test_output(
example::helpers::CalculateNumElements(tensor), kDelegateOutputValue);
memcpy(output, test_output.data(), test_output.size() * sizeof(float));
return kTfLiteOk;
}
void FreeBufferHandle(TfLiteOpaqueContext* context, // NOLINT
TfLiteBufferHandle* handle) override {
recorded_buffer_handle_ = *handle;
free_buffer_handle_called_ = true;
}
int recorded_buffer_handle_ = -1;
bool free_buffer_handle_called_ = false;
};
TEST_F(TestDelegate, SetBufferHandle) {
// Set up a simple delegate that defines a 'CopyFromBufferHandle' callback as
// as well as a 'FreeBufferHandle' callback. The purpose of these functions
// is to check that the TFLite runtime interacts with the delegate as
// expected. In this case we want to make sure that the 'CopyFromBufferHandle'
// callback is used if the output tensor's data is marked as stale. In
// addition we want to verify that the runtime frees the delegate's buffer
// handles when either a new buffer handle is set, or the buffer handle is no
// longer needed.
MySimpleOpaqueDelegateWithBufferHandleSupport my_simple_delegate;
TfLiteOpaqueDelegateBuilder opaque_delegate_builder{};
// A 'Prepare' callback that blindly replaces the full execution plan.
// We do this because all that we are interested is to verify the buffer
// handle-related code.
opaque_delegate_builder.Prepare = [](TfLiteOpaqueContext* opaque_context,
TfLiteOpaqueDelegate* opaque_delegate,
void* data) {
auto* simple_opaque_delegate =
reinterpret_cast<SimpleOpaqueDelegateInterface*>(data);
TF_LITE_ENSURE_STATUS(simple_opaque_delegate->Initialize(opaque_context));
TfLiteIntArray* execution_plan;
TF_LITE_ENSURE_STATUS(
TfLiteOpaqueContextGetExecutionPlan(opaque_context, &execution_plan));
TfLiteOperator* delegate_kernel_registration =
CreateDelegateKernelRegistrationImpl(simple_opaque_delegate);
return TfLiteOpaqueContextReplaceNodeSubsetsWithDelegateKernels(
opaque_context, delegate_kernel_registration, execution_plan,
opaque_delegate);
};
opaque_delegate_builder.flags = kTfLiteDelegateFlagsNone;
opaque_delegate_builder.data = &my_simple_delegate;
opaque_delegate_builder.CopyFromBufferHandle =
[](TfLiteOpaqueContext* context, TfLiteOpaqueDelegate* delegate,
void* data, TfLiteBufferHandle buffer_handle,
TfLiteOpaqueTensor* tensor) -> TfLiteStatus {
auto* simple_opaque_delegate =
reinterpret_cast<MySimpleOpaqueDelegateWithBufferHandleSupport*>(data);
simple_opaque_delegate->CopyFromBufferHandle(context, buffer_handle,
tensor);
return kTfLiteOk;
};
opaque_delegate_builder.FreeBufferHandle = [](TfLiteOpaqueContext* context,
TfLiteOpaqueDelegate* delegate,
void* data,
TfLiteBufferHandle* handle) {
auto* simple_opaque_delegate =
reinterpret_cast<MySimpleOpaqueDelegateWithBufferHandleSupport*>(data);
simple_opaque_delegate->FreeBufferHandle(context, handle);
};
TfLiteDelegate tflite_delegate{};
tflite_delegate.opaque_delegate_builder = &opaque_delegate_builder;
// Load a model and build an interpreter.
std::unique_ptr<tflite::FlatBufferModel> model =
tflite::FlatBufferModel::BuildFromFile(
"tensorflow/lite/testdata/add.bin");
ASSERT_NE(model, nullptr);
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
builder.AddDelegate(&tflite_delegate);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);
ASSERT_NE(interpreter, nullptr);
// Allocate tensor buffers.
ASSERT_EQ(interpreter->AllocateTensors(), kTfLiteOk);
// Fill input buffers
constexpr int kTensorDimensions = 1 * 8 * 8 * 3;
std::vector<float> floats(kTensorDimensions, 1);
memcpy(interpreter->typed_input_tensor<float>(0), floats.data(),
floats.size() * sizeof(float));
// We set the buffer handle of the output tensor and mark its data as stale.
// This will make the interpreter call 'CopyFromBufferHandle' to refresh the
// output tensor's data. We simply hardcode the values that will be copied to
// the output tensor to
// MySimpleOpaqueDelegateWithBufferHandleSupport::kDelegateOutputValue.
EXPECT_FALSE(my_simple_delegate.free_buffer_handle_called_);
int first_buffer_handle = 1;
const int kOutputTensorIndex = 2;
interpreter->SetBufferHandle(
kOutputTensorIndex, first_buffer_handle,
reinterpret_cast<TfLiteDelegate*>(&tflite_delegate));
TfLiteTensor* output_t = interpreter->output_tensor(0);
output_t->data_is_stale = true;
EXPECT_FALSE(my_simple_delegate.free_buffer_handle_called_);
EXPECT_NE(my_simple_delegate.recorded_buffer_handle_, first_buffer_handle);
// Run inference
ASSERT_EQ(interpreter->Invoke(), kTfLiteOk);
std::vector<float> outputs(kTensorDimensions, 0);
memcpy(outputs.data(), interpreter->typed_output_tensor<float>(0),
outputs.size() * sizeof(float));
for (int i = 0; i < outputs.size(); ++i) {
EXPECT_EQ(
outputs[i],
MySimpleOpaqueDelegateWithBufferHandleSupport::kDelegateOutputValue);
}
// Call 'SetBufferHandle' on a tensor that already has a buffer handle will
// lead to a call of 'FreeBufferHandle' for the currently set
int next_buffer_handle = first_buffer_handle + 1;
interpreter->SetBufferHandle(kOutputTensorIndex, next_buffer_handle,
&tflite_delegate);
EXPECT_TRUE(my_simple_delegate.free_buffer_handle_called_);
EXPECT_EQ(my_simple_delegate.recorded_buffer_handle_, first_buffer_handle);
// Destroying the interpreter will free the currently installed buffer
// handle.
my_simple_delegate.free_buffer_handle_called_ = false;
my_simple_delegate.recorded_buffer_handle_ = first_buffer_handle = -1;
interpreter.reset();
EXPECT_TRUE(my_simple_delegate.free_buffer_handle_called_);
EXPECT_EQ(my_simple_delegate.recorded_buffer_handle_, next_buffer_handle);
}
TEST(DelegateTest,
TestDataConvHugeIm2ColBin_MultiInputSingleOutput_PartiallyDelegated) {
TfLiteOpaqueDelegateUniquePtr my_opaque_delegate =
TfLiteOpaqueDelegateFactory::Create(
std::make_unique<example::SampleStableDelegate>());
TfLiteModel* model = TfLiteModelCreateFromFile(
"tensorflow/lite/testdata/conv_huge_im2col.bin");
ASSERT_NE(model, nullptr);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
ASSERT_NE(options, nullptr);
TfLiteInterpreterOptionsSetNumThreads(options, 2);
TfLiteInterpreterOptionsAddDelegate(options, my_opaque_delegate.get());
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
ASSERT_NE(interpreter, nullptr);
// The options can be deleted immediately after interpreter creation.
TfLiteInterpreterOptionsDelete(options);
ASSERT_EQ(TfLiteInterpreterAllocateTensors(interpreter), kTfLiteOk);
ASSERT_EQ(TfLiteInterpreterGetInputTensorCount(interpreter), 4);
ASSERT_EQ(TfLiteInterpreterGetOutputTensorCount(interpreter), 1);
TfLiteTensor* input_tensor0 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/0);
TfLiteTensor* input_tensor1 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/1);
TfLiteTensor* input_tensor2 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/2);
TfLiteTensor* input_tensor3 =
TfLiteInterpreterGetInputTensor(interpreter, /*input_index=*/3);
std::vector<TfLiteTensor*> input_tensors{input_tensor0, input_tensor1,
input_tensor2, input_tensor3};
for (TfLiteTensor* input_tensor : input_tensors) {
const float kTensorCellValue = 4.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);
}
ASSERT_EQ(TfLiteInterpreterInvoke(interpreter), kTfLiteOk);
const TfLiteTensor* output_tensor =
TfLiteInterpreterGetOutputTensor(interpreter, 0);
ASSERT_NE(output_tensor, nullptr);
EXPECT_EQ(TfLiteTensorType(output_tensor), kTfLiteFloat32);
EXPECT_NE(TfLiteTensorData(output_tensor), nullptr);
TfLiteQuantizationParams output_params =
TfLiteTensorQuantizationParams(output_tensor);
EXPECT_EQ(output_params.scale, 0.f);
EXPECT_EQ(output_params.zero_point, 0);
int64_t n = tflite::NumElements(output_tensor);
std::vector<float> output(n, 0);
ASSERT_EQ(TfLiteTensorCopyToBuffer(output_tensor, output.data(),
output.size() * sizeof(float)),
kTfLiteOk);
for (int i = 0; i < n; ++i) {
// We know that we can expect '4' because that is the model's output when
// no delegate gets applied. The purpose of this expectation is that we
// arrive at the same result when the delegate is applied.
EXPECT_EQ(output[i], 4);
}
TfLiteInterpreterDelete(interpreter);
TfLiteModelDelete(model);
}
} // namespace tflite
@@ -0,0 +1,114 @@
/* 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/sync_fence.h"
#include <poll.h>
#include <cerrno>
#include <cstddef>
#include <optional>
#include <variant>
#include <vector>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/utils/ret_macros.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite::delegates::utils {
namespace {
// Returns how many file descriptors have been signalled, or an error.
// Note that the implementation is loosely based on Android's libsync.
std::optional<size_t> PollFds(absl::Span<const int> fds, bool block) {
constexpr auto kError = std::optional<size_t>();
const int timeout = block ? -1 : 0;
if (fds.empty()) {
return 0;
}
std::vector<struct pollfd> pfds;
pfds.reserve(fds.size());
for (int fd : fds) {
const struct pollfd pfd = {
fd, // .fd
POLLIN, // .events
};
pfds.push_back(pfd);
}
while (true) {
const int ret = poll(pfds.data(), pfds.size(), timeout);
// Handle redo
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) {
continue;
}
// Handle error
TFLITE_RET_CHECK(ret >= 0u, "Poll failed", kError);
// Handle none ready
if (ret == 0) {
TFLITE_RET_CHECK(!block, "", kError);
return 0;
}
// Count how many fds have been signalled, setting them to -1 so they are
// not queried on subsequent passes of the loop.
size_t signalled_count = 0;
for (auto& fd : pfds) {
if (fd.revents & (POLLERR | POLLNVAL)) {
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "invalid fd to poll");
return {};
}
if (fd.fd == -1 || (fd.revents & POLLIN)) {
fd.fd = -1;
signalled_count++;
}
}
// If we are blocking and there are any fds that have not yet been
// signalled, poll again on the next loop.
if (block && signalled_count != fds.size()) {
continue;
}
return signalled_count;
}
TFLITE_ABORT_CHECK(false,
"The code should never reach this point"); // Crash OK
return {};
}
} // namespace
std::optional<std::monostate> WaitForAllFds(absl::Span<const int> fds) {
constexpr auto kError = std::optional<std::monostate>();
TFLITE_ASSIGN_OR_RETURN(const size_t signalled_count,
PollFds(fds, /*block=*/true), kError);
TFLITE_RET_CHECK(signalled_count == fds.size(), "", kError);
return std::monostate{};
}
std::optional<bool> AreAllFdsSignalled(absl::Span<const int> fds) {
TFLITE_ASSIGN_OR_RETURN(const size_t signalled_count,
PollFds(fds, /*block=*/false), std::optional<bool>());
return signalled_count == fds.size();
}
} // namespace tflite::delegates::utils
@@ -0,0 +1,36 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_SYNC_FENCE_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_SYNC_FENCE_H_
#include <optional>
#include <variant>
#include "absl/types/span.h"
namespace tflite::delegates::utils {
// Blocks until all file descriptors have been signalled, or returns an error
// (signified by an instance with no value).
std::optional<std::monostate> WaitForAllFds(absl::Span<const int> fds);
// Returns (without blocking) `true` if all the provided file descriptors are
// signalled, `false` if at least one file descriptor is not yet signalled, or
// an error (indicated by an instance with no value).
std::optional<bool> AreAllFdsSignalled(absl::Span<const int> fds);
} // namespace tflite::delegates::utils
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_SYNC_FENCE_H_
+33
View File
@@ -0,0 +1,33 @@
/* 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/utils.h"
#include "absl/status/status.h"
#include "tensorflow/lite/array.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite::delegates::utils {
TfLiteStatus ConvertToTfLiteStatus(absl::Status status) {
if (!status.ok()) {
TFLITE_LOG_PROD(
TFLITE_LOG_ERROR, "%s",
status.ToString(absl::StatusToStringMode::kWithEverything).c_str());
return kTfLiteError;
}
return kTfLiteOk;
}
} // namespace tflite::delegates::utils
+44
View File
@@ -0,0 +1,44 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_UTILS_UTILS_H_
#define TENSORFLOW_LITE_DELEGATES_UTILS_UTILS_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include "absl/status/status.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/utils/ret_macros.h"
namespace tflite::delegates::utils {
// Returns kTfLiteOk if the status is ok;
// Otherwise, log the error message and returns kTfLiteError.
TfLiteStatus ConvertToTfLiteStatus(absl::Status status);
inline bool IsPowerOfTwo(size_t x) { return x && ((x & (x - 1)) == 0); }
// Round up "size" to the nearest multiple of "multiple".
// "multiple" must be a power of 2.
inline uint32_t RoundUp(uint32_t size, uint32_t multiple) {
TFLITE_ABORT_CHECK(IsPowerOfTwo(multiple), ""); // Crash OK
return (size + (multiple - 1)) & ~(multiple - 1);
}
} // namespace tflite::delegates::utils
#endif // TENSORFLOW_LITE_DELEGATES_UTILS_UTILS_H_