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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
@@ -0,0 +1,15 @@
# Accelerator allowlisting
Experimental library and tools for determining whether an accelerator engine
works well on a given device, and for a given model.
## Platform-agnostic, Android-first
Android-focused, since the much smaller set of configurations on iOS means there
is much less need for allowlisting on iOS.
## Not just for TfLite
This code lives in the TfLite codebase, since TfLite is the first open-source
customer. It is however meant to support other users (direct use of NNAPI,
mediapipe).
@@ -0,0 +1,248 @@
# Copyright 2019 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.
# ==============================================================================
load("@flatbuffers//:build_defs.bzl", "flatbuffer_android_library", "flatbuffer_cc_library", "flatbuffer_java_library")
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("@xla//third_party/rules_python/python:py_binary.bzl", "py_binary")
load("//tensorflow/core/platform:build_config_root.bzl", "tf_gpu_tests_tags")
load("//tensorflow/lite:special_rules.bzl", "gpu_compatibility_without_gl_deps_internal_visibility_allowlist", "tflite_extra_gles_deps", "tflite_portable_test_suite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
flatbuffer_cc_library(
name = "database_fbs",
srcs = ["database.fbs"],
)
exports_files(srcs = ["database.fbs"])
flatbuffer_java_library(
name = "database_fbs_java",
srcs = ["database.fbs"],
package_prefix = "org.tensorflow",
)
flatbuffer_android_library(
name = "database_fbs_android",
srcs = ["database.fbs"],
package_prefix = "org.tensorflow",
)
cc_library(
name = "canonicalize_value",
srcs = ["canonicalize_value.cc"],
hdrs = ["canonicalize_value.h"],
deps = [
":devicedb",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/strings",
"@com_googlesource_code_re2//:re2",
],
)
cc_test(
name = "canonicalize_value_test",
srcs = ["canonicalize_value_test.cc"],
deps = [
":canonicalize_value",
":devicedb",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "devicedb",
srcs = [
"devicedb.cc",
],
hdrs = [
"devicedb.h",
"variables.h",
],
deps = [
":database_fbs",
],
)
cc_binary(
name = "json_to_fb",
srcs = ["json_to_fb.cc"],
deps = [
"//tensorflow/lite/tools:command_line_flags",
"@flatbuffers",
],
)
genrule(
name = "devicedb-sample_bin",
srcs = [
"database.fbs",
"devicedb-sample.json",
],
outs = ["devicedb-sample.bin"],
cmd = """
$(location :json_to_fb) \
--fbs=$(location :database.fbs) \
--json_input=$(location :devicedb-sample.json) \
--fb_output=$(@)
""",
tools = [":json_to_fb"],
)
py_binary(
name = "convert_binary_to_cc_source",
srcs = ["convert_binary_to_cc_source.py"],
strict_deps = True,
visibility = ["//visibility:public"],
)
genrule(
name = "devicedb-sample_cc",
srcs = ["devicedb-sample.bin"],
outs = [
"devicedb-sample.cc",
"devicedb-sample.h",
],
# convert_file_to_c_source for some reason doesn't define the global with
# 'extern', which is needed for global const variables in C++.
cmd = """
$(location :convert_binary_to_cc_source) \
--input_binary_file $(location :devicedb-sample.bin) \
--output_header_file $(location :devicedb-sample.h) \
--output_source_file $(location :devicedb-sample.cc) \
--array_variable_name g_tflite_acceleration_devicedb_sample_binary
""",
tools = [":convert_binary_to_cc_source"],
)
cc_library(
name = "devicedb_sample",
srcs = ["devicedb-sample.cc"],
hdrs = [
"devicedb-sample.h",
"variables.h",
],
deps = [":database_fbs"],
)
cc_test(
name = "devicedb_test",
srcs = [
"devicedb_test.cc",
],
deps = [
":database_fbs",
":devicedb",
":devicedb_sample",
"//tensorflow/lite/testing:util",
"@com_google_googletest//:gtest_main",
"@flatbuffers",
],
)
exports_files([
"database.fbs",
"gpu_compatibility.bin",
])
genrule(
name = "gpu_compatibility_binary",
srcs = ["gpu_compatibility.bin"],
outs = [
"gpu_compatibility_binary.h",
"gpu_compatibility_binary.cc",
],
# convert_file_to_c_source for some reason doesn't define the global with
# 'extern', which is needed for global const variables in C++.
cmd = """
$(location :convert_binary_to_cc_source) \
--input_binary_file $(location :gpu_compatibility.bin) \
--output_header_file $(location :gpu_compatibility_binary.h) \
--output_source_file $(location :gpu_compatibility_binary.cc) \
--array_variable_name g_tflite_acceleration_gpu_compatibility_binary
""",
tools = [":convert_binary_to_cc_source"],
)
cc_library(
name = "android_info",
srcs = ["android_info.cc"],
hdrs = ["android_info.h"],
deps = [
"@com_google_absl//absl/status",
],
)
cc_library(
name = "gpu_compatibility_binary_embed",
srcs = [":gpu_compatibility_binary.cc"],
hdrs = [":gpu_compatibility_binary.h"],
)
cc_library(
name = "gpu_compatibility",
hdrs = ["gpu_compatibility.h"],
deps = [
":android_info",
":database_fbs",
":devicedb_sample",
":gpu_compatibility_without_gl_deps",
"//tensorflow/lite/delegates/gpu:delegate_options",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"@com_google_absl//absl/strings:string_view",
] + tflite_extra_gles_deps(),
)
cc_library(
name = "gpu_compatibility_without_gl_deps",
srcs = ["gpu_compatibility.cc"],
hdrs = ["gpu_compatibility.h"],
visibility = gpu_compatibility_without_gl_deps_internal_visibility_allowlist(),
deps = [
":android_info",
":canonicalize_value",
":database_fbs",
":devicedb",
":gpu_compatibility_binary_embed",
"//tensorflow/lite/delegates/gpu:delegate_options",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"@flatbuffers",
],
)
cc_test(
name = "gpu_compatibility_test",
srcs = ["gpu_compatibility_test.cc"],
tags = tf_gpu_tests_tags() + [
"no_cuda_asan", # TODO(b/181032551).
],
deps = [
":devicedb_sample",
":gpu_compatibility",
"@com_google_googletest//:gtest_main",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,13 @@
# GPU delegate compatibility database
This package provides data and code for deciding if the GPU delegate is
supported on a specific Android device.
## Customizing the database
- Convert from checked-in flatbuffer to json by running `flatc -t --raw-binary
--strict-json database.fbs -- gpu_compatibility.bin`
- Edit the json
- Convert from json to flatbuffer `flatc -b database.fbs --
gpu_compatibility.json`
- Rebuild ../../../java:tensorflow-lite-gpu
@@ -0,0 +1,92 @@
/* 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/experimental/acceleration/compatibility/android_info.h"
#include <iostream>
#include <string>
#include "absl/status/status.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif // __ANDROID__
namespace {
std::string GetPropertyValue(const std::string& property) {
#ifdef __ANDROID__
char value[PROP_VALUE_MAX];
__system_property_get(property.c_str(), value);
return std::string(value);
#else // !__ANDROID__
return std::string();
#endif // __ANDROID__
}
} // namespace
namespace tflite {
namespace acceleration {
absl::Status RequestAndroidInfo(AndroidInfo* info_out) {
if (!info_out) {
return absl::InvalidArgumentError("info_out may not be null");
}
info_out->android_sdk_version = GetPropertyValue("ro.build.version.sdk");
info_out->device = GetPropertyValue("ro.product.device");
info_out->model = GetPropertyValue("ro.product.model");
info_out->manufacturer = GetPropertyValue("ro.product.manufacturer");
#ifdef __ANDROID__
// Based on
// https://github.com/flutter/plugins/blob/master/packages/device_info/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java
// + QUMA detection (system properties return empty) and qemu detection
// (ro.kernel.qemu).
std::string brand = GetPropertyValue("ro.product.brand");
const std::string& device = info_out->device;
std::string fingerprint = GetPropertyValue("ro.build.fingerprint");
std::string hardware = GetPropertyValue("ro.hardware");
const std::string& model = info_out->model;
const std::string& manufacturer = info_out->manufacturer;
std::string product = GetPropertyValue("ro.build.product");
std::string ro_kernel_qemu = GetPropertyValue("ro.kernel.qemu");
info_out->is_emulator =
((brand.find("generic") == 0 && device.find("generic") == 0) || // NOLINT
fingerprint.find("generic") == 0 || // NOLINT
fingerprint.find("unknown") == 0 || // NOLINT
hardware.find("goldfish") != std::string::npos || // NOLINT
hardware.find("ranchu") != std::string::npos || // NOLINT
model.find("google_sdk") != std::string::npos || // NOLINT
model.find("Emulator") != std::string::npos || // NOLINT
model.find("Android SDK built for x86") != // NOLINT
std::string::npos || // NOLINT
model.find("Android SDK built for arm64") != // NOLINT
std::string::npos || // NOLINT
manufacturer.find("Genymotion") != std::string::npos || // NOLINT
product.find("sdk_google") != std::string::npos || // NOLINT
product.find("google_sdk") != std::string::npos || // NOLINT
product.find("sdk") != std::string::npos || // NOLINT
product.find("sdk_x86") != std::string::npos || // NOLINT
product.find("vbox86p") != std::string::npos || // NOLINT
product.find("emulator") != std::string::npos || // NOLINT
product.find("simulator") != std::string::npos || // NOLINT
ro_kernel_qemu == "1" || // NOLINT
info_out->android_sdk_version.empty()); // NOLINT
#else
info_out->is_emulator = false;
#endif
return absl::OkStatus();
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,45 @@
/* 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_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_ANDROID_INFO_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_ANDROID_INFO_H_
#include <string>
#include "absl/status/status.h"
namespace tflite {
namespace acceleration {
// Information about and Android device, used for determining compatibility
// status.
struct AndroidInfo {
// Property ro.build.version.sdk
std::string android_sdk_version;
// Property ro.product.model
std::string model;
// Property ro.product.device
std::string device;
// Property ro.product.manufacturer
std::string manufacturer;
// Whether code is running on an emulator.
bool is_emulator;
};
absl::Status RequestAndroidInfo(AndroidInfo* info_out);
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_ANDROID_INFO_H_
@@ -0,0 +1,58 @@
/* Copyright 2021 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/experimental/acceleration/compatibility/canonicalize_value.h"
#include <iterator>
#include <string>
#include "absl/algorithm/container.h"
#include "absl/strings/ascii.h"
#include "absl/strings/string_view.h"
#include "re2/re2.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
namespace tflite::acceleration {
namespace {
inline char ascii_normalise(const unsigned char c) {
if (c == ' ' || c == '-') {
return '_';
}
return absl::ascii_tolower(c);
}
} // namespace
std::string CanonicalizeValue(absl::string_view value) {
std::string output;
absl::c_transform(value, std::back_inserter(output),
tflite::acceleration::ascii_normalise);
return output;
}
std::string CanonicalizeValueWithKey(absl::string_view key,
absl::string_view value) {
std::string output = CanonicalizeValue(value);
std::string gpu_output;
static constexpr LazyRE2 kAngleOnVulkan = {
R"(^(angle_\(samsung_xclipse_[0-9]*\)_on_vulkan))"};
return key == kGPUModel &&
RE2::PartialMatch(output, *kAngleOnVulkan, &gpu_output)
? gpu_output
: output;
}
} // namespace tflite::acceleration
@@ -0,0 +1,33 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_CANONICALIZE_VALUE_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_CANONICALIZE_VALUE_H_
#include <string>
#include "absl/strings/string_view.h"
namespace tflite::acceleration {
// Normalises the given ASCII input by converting all alphabets to lower case
// and replacing ' ' and '-' with '_'.
std::string CanonicalizeValue(absl::string_view value);
// Applies the above normalisation plus key specific normalisation.
std::string CanonicalizeValueWithKey(absl::string_view key,
absl::string_view value);
} // namespace tflite::acceleration
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_CANONICALIZE_VALUE_H_
@@ -0,0 +1,51 @@
/* Copyright 2021 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/experimental/acceleration/compatibility/canonicalize_value.h"
#include <string>
#include <gtest/gtest.h>
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
namespace tflite::acceleration {
namespace {
TEST(CanonicalizeValue, CharactersAreLowercased) {
EXPECT_EQ(CanonicalizeValue("hElLo"), "hello");
}
TEST(CanonicalizeValue, HyphensAreReplaced) {
EXPECT_EQ(CanonicalizeValue("-"), "_");
}
TEST(CanonicalizeValue, SpacesAreReplaced) {
EXPECT_EQ(CanonicalizeValue(" "), "_");
}
TEST(CanonicalizeValue, OtherSpecialCharactersAreUnaffected) {
for (unsigned char c = 0; c < 65; ++c) {
if (c == ' ' || c == '-') continue;
std::string s = {1, static_cast<char>(c)};
EXPECT_EQ(CanonicalizeValue(s), s);
}
}
TEST(CanonicalizeValue, SamsungXclipseGpuNormalized) {
EXPECT_EQ(CanonicalizeValueWithKey(
kGPUModel, "ANGLE (Samsung Xclipse 920) on Vulkan 1.1.179"),
"angle_(samsung_xclipse_920)_on_vulkan");
}
} // namespace
} // namespace tflite::acceleration
@@ -0,0 +1,190 @@
# 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.
# ==============================================================================
"""Simple script to convert binary file to C++ source code for embedding."""
# This is a version of //tensorflow/lite/python/convert_file_to_c_source.py
# with minimal dependencies to reduce build times. See b/158254039.
import argparse
import datetime
import sys
# Cribbed from //tensorflow/lite/python/util.py
# Changed:
# - Alignment from 4 to 16 for generality (16 can be required for SIMD)
# - Added 'extern' to source for building on C++ target platforms
# - Changed comments to refer to this script, and C++ rather than C
def _convert_bytes_to_cc_source(data,
array_name,
max_line_width=80,
include_guard=None,
include_path=None,
use_tensorflow_license=False):
"""Returns strings representing a C++ constant array containing `data`.
Args:
data: Byte array that will be converted into a C++ constant.
array_name: String to use as the variable name for the constant array.
max_line_width: The longest line length, for formatting purposes.
include_guard: Name to use for the include guard macro definition.
include_path: Optional path to include in the source file.
use_tensorflow_license: Whether to include the standard TensorFlow Apache2
license in the generated files.
Returns:
Text that can be compiled as a C++ source file to link in the data as a
literal array of values.
Text that can be used as a C++ header file to reference the literal array.
"""
starting_pad = " "
array_lines = []
array_line = starting_pad
for value in bytearray(data):
if (len(array_line) + 4) > max_line_width:
array_lines.append(array_line + "\n")
array_line = starting_pad
array_line += " 0x%02x," % value
if len(array_line) > len(starting_pad):
array_lines.append(array_line + "\n")
array_values = "".join(array_lines)
if include_guard is None:
include_guard = "TENSORFLOW_LITE_UTIL_" + array_name.upper() + "_DATA_H_"
if include_path is not None:
include_line = "#include \"{include_path}\"\n".format(
include_path=include_path)
else:
include_line = ""
if use_tensorflow_license:
license_text = """
/* Copyright {year} 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.
==============================================================================*/
""".format(year=datetime.date.today().year)
else:
license_text = ""
source_template = """{license_text}
// This is a binary file that has been converted into a C++ data array using the
// //tensorflow/lite/experimental/acceleration/compatibility/convert_binary_to_cc_source.py
// script. This form is useful for compiling into a binary to simplify
// deployment on mobile devices
{include_line}
// We need to keep the data array aligned on some architectures.
#ifdef __has_attribute
#define HAVE_ATTRIBUTE(x) __has_attribute(x)
#else
#define HAVE_ATTRIBUTE(x) 0
#endif
#if HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__))
#define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(16)))
#else
#define DATA_ALIGN_ATTRIBUTE
#endif
extern const unsigned char {array_name}[] DATA_ALIGN_ATTRIBUTE = {{
{array_values}}};
extern const int {array_name}_len = {array_length};
"""
source_text = source_template.format(
array_name=array_name,
array_length=len(data),
array_values=array_values,
license_text=license_text,
include_line=include_line)
header_template = """
{license_text}
// This is a binary file that has been converted into a C++ data array using the
// //tensorflow/lite/experimental/acceleration/compatibility/convert_binary_to_cc_source.py
// script. This form is useful for compiling into a binary to simplify
// deployment on mobile devices
#ifndef {include_guard}
#define {include_guard}
extern const unsigned char {array_name}[];
extern const int {array_name}_len;
#endif // {include_guard}
"""
header_text = header_template.format(
array_name=array_name,
include_guard=include_guard,
license_text=license_text)
return source_text, header_text
def main():
parser = argparse.ArgumentParser(
description=("Binary to C++ source converter"))
parser.add_argument(
"--input_binary_file",
type=str,
help="Full filepath of input binary.",
required=True)
parser.add_argument(
"--output_header_file",
type=str,
help="Full filepath of output header.",
required=True)
parser.add_argument(
"--array_variable_name",
type=str,
help="Full filepath of output source.",
required=True)
parser.add_argument(
"--output_source_file",
type=str,
help="Name of global variable that will contain the binary data.",
required=True)
flags, _ = parser.parse_known_args(args=sys.argv[1:])
with open(flags.input_binary_file, "rb") as input_handle:
input_data = input_handle.read()
source, header = _convert_bytes_to_cc_source(
data=input_data,
array_name=flags.array_variable_name,
use_tensorflow_license=True)
with open(flags.output_source_file, "w") as source_handle:
source_handle.write(source)
with open(flags.output_header_file, "w") as header_handle:
header_handle.write(header)
if __name__ == "__main__":
main()
@@ -0,0 +1,59 @@
// 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.
namespace tflite.acceleration;
enum Comparison : byte {
EQUAL = 0,
MINIMUM = 1,
MAXIMUM = 2,
}
// Mapping from available device features to compatibility decisions. Basic usage is to:
// 1) Map easily available device data (like Android version,
// Manufacturer, Device) to things like SoC vendor, SoC model.
// 2) Map complete device data to delegate-specific features and support status
// 3) Map delegate-specific features to delegate configuration.
//
// The structure describes a decision tree, with multiple matching branches.
// The branches are applied depth-first.
table DeviceDatabase {
root:[DeviceDecisionTreeNode];
}
table DeviceDecisionTreeNode {
// The variables are strings, as we have multiple clients that want to
// introduce their own fields. Known variables are listed in variables.h.
variable:string (shared);
comparison:Comparison;
items:[DeviceDecisionTreeEdge];
}
table DeviceDecisionTreeEdge {
// Under which variable value does this item match.
value:string (key, shared);
// Which child branches should also be consulted and used to override this
// node.
children:[DeviceDecisionTreeNode];
// What information can be derived about this device.
derived_properties:[DerivedProperty];
}
// Derived variable value to combine with detected variables.
table DerivedProperty {
variable:string (shared);
value:string (shared);
}
root_type DeviceDatabase;
@@ -0,0 +1,190 @@
{
"root": [
{
"variable": "tflite.device_model",
"items": [
{
"value": "m712c",
"derived_properties": [
{
"variable": "tflite.soc_model",
"value": "exynos_7872"
}
]
},
{
"value": "sc_02l",
"derived_properties": [
{
"variable": "tflite.soc_model",
"value": "exynos_7885"
}
]
}
]
},
{
"variable": "tflite.device_model",
"items": [
{
"value": "shiraz_ag_2011",
"children": [
{
"variable": "tflite.android_sdk_version",
"items": [
{
"value": "28",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "UNSUPPORTED"
}
]
}
],
"comparison": "MAXIMUM"
}
]
}
]
},
{
"variable": "tflite.opengl_es_version",
"items": [
{
"value": "3.1",
"children": [
{
"variable": "tflite.soc_model",
"items": [
{
"value": "exynos_7872",
"children": [
{
"variable": "tflite.android_sdk_version",
"items": [
{
"value": "24",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "SUPPORTED"
}
]
}
],
"comparison": "MINIMUM"
}
]
},
{
"value": "exynos_7883",
"children": [
{
"variable": "tflite.android_sdk_version",
"items": [
{
"value": "28",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "SUPPORTED"
}
]
}
],
"comparison": "MINIMUM"
}
]
}
]
}
]
}
]
},
{
"variable": "tflite.android_sdk_version",
"items": [
{
"value": "21",
"children": [
{
"variable": "tflite.device_model",
"items": [
{
"value": "huawei_gra_l09",
"children": [
{
"variable": "tflite.device_name",
"items": [
{
"value": "hwgra",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "SUPPORTED"
}
]
}
]
}
]
}
]
}
]
},
{
"value": "24",
"children": [
{
"variable": "tflite.device_model",
"items": [
{
"value": "sm_j810f",
"children": [
{
"variable": "tflite.device_name",
"items": [
{
"value": "j8y18lte",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "UNSUPPORTED"
}
]
}
]
}
]
},
{
"value": "sm_j810m",
"children": [
{
"variable": "tflite.device_name",
"items": [
{
"value": "j8y18lte",
"derived_properties": [
{
"variable": "tflite.gpu.status",
"value": "SUPPORTED"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
@@ -0,0 +1,93 @@
/* 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/experimental/acceleration/compatibility/devicedb.h"
#include <map>
#include <string>
#include <vector>
#include "tensorflow/lite/experimental/acceleration/compatibility/database_generated.h"
namespace tflite {
namespace acceleration {
namespace {
std::vector<const DeviceDecisionTreeEdge*> Find(
const DeviceDecisionTreeNode* root, const std::string& value) {
std::vector<const DeviceDecisionTreeEdge*> found;
if (root->comparison() == Comparison_EQUAL) {
// Exact match.
const DeviceDecisionTreeEdge* possible =
root->items()->LookupByKey(value.c_str());
if (possible) {
found.push_back(possible);
}
} else {
// Minimum/Maximum: value should be at least / at most item's value.
for (const DeviceDecisionTreeEdge* item : *(root->items())) {
if ((root->comparison() == Comparison_MINIMUM)
? value >= item->value()->str()
: value <= item->value()->str()) {
found.push_back(item);
}
}
}
return found;
}
void UpdateVariablesFromDeviceDecisionTreeEdges(
std::map<std::string, std::string>* variable_values,
const DeviceDecisionTreeEdge& item) {
if (item.derived_properties()) {
for (const DerivedProperty* p : *(item.derived_properties())) {
(*variable_values)[p->variable()->str()] = p->value()->str();
}
}
}
void Follow(const DeviceDecisionTreeNode* root,
std::map<std::string, std::string>* variable_values) {
if (!root->variable()) {
return;
}
auto possible_value = variable_values->find(root->variable()->str());
if (possible_value == variable_values->end()) {
return;
}
std::vector<const DeviceDecisionTreeEdge*> edges =
Find(root, possible_value->second);
for (const DeviceDecisionTreeEdge* edge : edges) {
UpdateVariablesFromDeviceDecisionTreeEdges(variable_values, *edge);
if (edge->children()) {
for (const DeviceDecisionTreeNode* root : *(edge->children())) {
Follow(root, variable_values);
}
}
}
}
} // namespace
void UpdateVariablesFromDatabase(
std::map<std::string, std::string>* variable_values,
const DeviceDatabase& database) {
if (!database.root()) return;
for (const DeviceDecisionTreeNode* root : *(database.root())) {
Follow(root, variable_values);
}
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,38 @@
/* 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_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_DEVICEDB_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_DEVICEDB_H_
#include <map>
#include <string>
#include "tensorflow/lite/experimental/acceleration/compatibility/database_generated.h"
namespace tflite {
namespace acceleration {
// Use the variables in `variable_values` to evaluate the decision tree in
// `database` and update the `variable_values` based on derived properties in
// the decision tree.
//
// See database.fbs for a description of the decision tree.
void UpdateVariablesFromDatabase(
std::map<std::string, std::string>* variable_values,
const DeviceDatabase& database);
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_DEVICEDB_H_
@@ -0,0 +1,159 @@
/* 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/experimental/acceleration/compatibility/devicedb.h"
#include <map>
#include <string>
#include <gtest/gtest.h>
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/lite/experimental/acceleration/compatibility/devicedb-sample.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
#include "tensorflow/lite/testing/util.h"
namespace tflite {
namespace acceleration {
namespace {
class DeviceDbTest : public ::testing::Test {
protected:
void LoadSample() {
device_db_ = flatbuffers::GetRoot<DeviceDatabase>(
g_tflite_acceleration_devicedb_sample_binary);
}
const DeviceDatabase* device_db_ = nullptr;
};
TEST_F(DeviceDbTest, Load) {
LoadSample();
ASSERT_TRUE(device_db_);
ASSERT_TRUE(device_db_->root());
EXPECT_EQ(device_db_->root()->size(), 4);
}
TEST_F(DeviceDbTest, SocLookup) {
LoadSample();
ASSERT_TRUE(device_db_);
std::map<std::string, std::string> variables;
// Find first device mapping.
variables[kDeviceModel] = "m712c";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[kSoCModel], "exynos_7872");
// Find second device mapping.
variables.clear();
variables[kDeviceModel] = "sc_02l";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[kSoCModel], "exynos_7885");
// Make sure no results are returned without a match.
variables.clear();
variables[kDeviceModel] = "nosuch";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables.find(kSoCModel), variables.end());
}
TEST_F(DeviceDbTest, StatusLookupWithSoC) {
LoadSample();
ASSERT_TRUE(device_db_);
std::map<std::string, std::string> variables;
// Find exact match.
variables[kOpenGLESVersion] = "3.1";
variables[kSoCModel] = "exynos_7872";
variables[kAndroidSdkVersion] = "24";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusSupported);
// Ensure no results without a match.
variables[kOpenGLESVersion] = "3.0";
variables.erase(variables.find(gpu::kStatus));
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables.find(gpu::kStatus), variables.end());
// Find no results with too low an android version.
variables.clear();
variables[kOpenGLESVersion] = "3.1";
variables[kSoCModel] = "exynos_7883";
variables[kAndroidSdkVersion] = "24";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables.find(gpu::kStatus), variables.end());
// Find a match with android version above minimum.
variables[kAndroidSdkVersion] = "29";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusSupported);
}
TEST_F(DeviceDbTest, StatusLookupWithDevice) {
LoadSample();
ASSERT_TRUE(device_db_);
std::map<std::string, std::string> variables;
// Find unsupported device (same model, different device).
variables[kAndroidSdkVersion] = "24";
variables[kDeviceModel] = "sm_j810f";
variables[kDeviceName] = "j8y18lte";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusUnsupported);
// Find supported device (same model, different device).
variables.clear();
variables[kAndroidSdkVersion] = "24";
variables[kDeviceModel] = "sm_j810m";
variables[kDeviceName] = "j8y18lte";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusSupported);
}
TEST_F(DeviceDbTest, StatusLookupBasedOnDerivedProperties) {
LoadSample();
ASSERT_TRUE(device_db_);
std::map<std::string, std::string> variables;
// Find status based on SoC derived from model.
variables[kOpenGLESVersion] = "3.1";
variables[kAndroidSdkVersion] = "24";
variables[kDeviceModel] = "m712c";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusSupported);
}
TEST_F(DeviceDbTest, StatusLookupWithMaximumComparison) {
LoadSample();
ASSERT_TRUE(device_db_);
std::map<std::string, std::string> variables;
variables[kDeviceModel] = "shiraz_ag_2011";
// Value exactly equals the maximum, expecting match
variables[kAndroidSdkVersion] = "28";
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusUnsupported);
// Value below the maximum, expecting match
variables[kAndroidSdkVersion] = "27";
variables.erase(variables.find(gpu::kStatus));
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables[gpu::kStatus], gpu::kStatusUnsupported);
// Value above the maximum, expecting no match
variables[kAndroidSdkVersion] = "29";
variables.erase(variables.find(gpu::kStatus));
UpdateVariablesFromDatabase(&variables, *device_db_);
EXPECT_EQ(variables.find(gpu::kStatus), variables.end());
}
} // namespace
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,179 @@
/* 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/experimental/acceleration/compatibility/gpu_compatibility.h"
#include <cstdint>
#include <cstdio>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/lite/experimental/acceleration/compatibility/canonicalize_value.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/database_generated.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/devicedb.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/gpu_compatibility_binary.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
namespace tflite {
namespace acceleration {
namespace {
void CanonicalizeValues(std::map<std::string, std::string>* variable_values) {
for (auto& i : *variable_values) {
i.second = CanonicalizeValueWithKey(i.first, i.second);
}
}
} // namespace
GPUCompatibilityList::GPUCompatibilityList(
const unsigned char* compatibility_list_flatbuffer) {
if (!compatibility_list_flatbuffer) return;
database_ =
flatbuffers::GetRoot<DeviceDatabase>(compatibility_list_flatbuffer);
}
GPUCompatibilityList::GPUCompatibilityList(
std::string compatibility_list_flatbuffer)
: fbcontent_(std::move(compatibility_list_flatbuffer)) {
database_ = flatbuffers::GetRoot<DeviceDatabase>(fbcontent_.data());
}
std::unique_ptr<GPUCompatibilityList> GPUCompatibilityList::Create() {
return Create(g_tflite_acceleration_gpu_compatibility_binary,
g_tflite_acceleration_gpu_compatibility_binary_len);
}
std::unique_ptr<GPUCompatibilityList> GPUCompatibilityList::Create(
const unsigned char* compatibility_list_flatbuffer, size_t length) {
if (!compatibility_list_flatbuffer ||
!IsValidFlatbuffer(compatibility_list_flatbuffer, length)) {
return nullptr;
}
return std::unique_ptr<GPUCompatibilityList>(
new GPUCompatibilityList(compatibility_list_flatbuffer));
}
std::unique_ptr<GPUCompatibilityList> GPUCompatibilityList::Create(
std::string compatibility_list_flatbuffer) {
if (!IsValidFlatbuffer(reinterpret_cast<const unsigned char*>(
compatibility_list_flatbuffer.data()),
compatibility_list_flatbuffer.size())) {
return nullptr;
}
return std::unique_ptr<GPUCompatibilityList>(
new GPUCompatibilityList(std::move(compatibility_list_flatbuffer)));
}
std::map<std::string, std::string> GPUCompatibilityList::CalculateVariables(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const {
std::map<std::string, std::string> variables =
InfosToMap(android_info, gpu_info);
CanonicalizeValues(&variables);
if (!database_) return variables;
UpdateVariablesFromDatabase(&variables, *database_);
return variables;
}
bool GPUCompatibilityList::Includes(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const {
auto variables = CalculateVariables(android_info, gpu_info);
return variables[gpu::kStatus] == std::string(gpu::kStatusSupported);
}
gpu::CompatibilityStatus GPUCompatibilityList::GetStatus(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const {
std::map<std::string, std::string> variables =
InfosToMap(android_info, gpu_info);
return GetStatus(variables);
}
gpu::CompatibilityStatus GPUCompatibilityList::GetStatus(
std::map<std::string, std::string>& variables) const {
CanonicalizeValues(&variables);
if (!database_) return gpu::CompatibilityStatus::kUnknown;
UpdateVariablesFromDatabase(&variables, *database_);
return StringToCompatibilityStatus(variables[gpu::kStatus]);
}
TfLiteGpuDelegateOptionsV2 GPUCompatibilityList::GetBestOptionsFor(
const AndroidInfo& /* android_info */,
const ::tflite::gpu::GpuInfo& /* gpu_info */) const {
// This method is for forwards-compatibility: the list may later include
// information about which backend to choose (OpenGL/OpenCL/Vulkan) or other
// options.
return TfLiteGpuDelegateOptionsV2Default();
}
// static
bool GPUCompatibilityList::IsValidFlatbuffer(const unsigned char* data,
size_t len) {
// Verify opensource db.
flatbuffers::Verifier verifier(reinterpret_cast<const uint8_t*>(data), len);
return tflite::acceleration::VerifyDeviceDatabaseBuffer(verifier);
}
std::map<std::string, std::string> GPUCompatibilityList::InfosToMap(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const {
std::map<std::string, std::string> variables;
variables[kAndroidSdkVersion] = android_info.android_sdk_version;
variables[kDeviceModel] = android_info.model;
variables[kDeviceName] = android_info.device;
variables[kManufacturer] = android_info.manufacturer;
const auto& gl_info = gpu_info.opengl_info;
variables[kGPUModel] = gl_info.renderer_name;
char buffer[128];
snprintf(buffer, 128 - 1, "%d.%d", gl_info.major_version,
gl_info.minor_version);
variables[kOpenGLESVersion] = std::string(buffer);
return variables;
}
// static
std::string GPUCompatibilityList::CompatibilityStatusToString(
gpu::CompatibilityStatus status) {
switch (status) {
case gpu::CompatibilityStatus::kSupported:
return gpu::kStatusSupported;
case gpu::CompatibilityStatus::kUnsupported:
return gpu::kStatusUnsupported;
case gpu::CompatibilityStatus::kUnknown:
return gpu::kStatusUnknown;
}
}
// static
gpu::CompatibilityStatus GPUCompatibilityList::StringToCompatibilityStatus(
absl::string_view status) {
if (status == gpu::kStatusSupported) {
return gpu::CompatibilityStatus::kSupported;
} else if (status == gpu::kStatusUnsupported) {
return gpu::CompatibilityStatus::kUnsupported;
}
return gpu::CompatibilityStatus::kUnknown;
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,145 @@
/* 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_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_GPU_COMPATIBILITY_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_GPU_COMPATIBILITY_H_
#include <cstddef>
#include <map>
#include <memory>
#include <string>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/delegate_options.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/android_info.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/database_generated.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
namespace tflite {
namespace acceleration {
// This class provides information on GPU delegate support.
//
// The GPU delegate is supported on a subset of Android devices, depending on
// Android version, OpenGL ES version, GPU chipset etc. The support is based on
// measure stability, correctness and peformance. For more detail see README.md.
//
// Example usage:
// tflite::Interpreter* interpreter = ... ;
// tflite::acceleration::AndroidInfo android_info;
// tflite::gpu::GpuInfo gpu_info;
// EXPECT_OK(tflite::acceleration::RequestAndroidInfo(&android_info));
// EXPECT_OK(tflite::gpu::gl::EglEnvironment::NewEglEnvironment(&env));
// EXPECT_OK(tflite::gpu::gl::RequestGpuInfo(&tflite_gpu_info));
// tflite::acceleration::GPUCompatibilityList list;
// TfLiteDelegate* gpu_delegate = nullptr;
// TfLiteGpuDelegateOptions gpu_options;
// if (list.Includes(android_info, gpu_info)) {
// gpu_options = list.BestOptionsFor(android_info, gpu_info);
// gpu_delegate = TfLiteGpuDelegateCreate(&gpu_options);
// EXPECT_EQ(interpreter->ModifyGraphWithDelegate(gpu_delegate), TfLiteOk);
// } else {
// // Fallback path.
// }
class GPUCompatibilityList {
public:
// Construct list from bundled data. Returns a unique_ptr to a nullptr if
// creation fails.
static std::unique_ptr<GPUCompatibilityList> Create();
// Constructs list from the given flatbuffer data. Returns a unique_ptr to a
// nullptr is the given flatbuffer is empty or invalid.
// The flatbuffer pointer must remain valid during the usage of the
// compatibility list, it is the caller's responsibility to make sure of that.
// To have the compatibility list own the flatbuffer, use the alternative
// Create() method below.
static std::unique_ptr<GPUCompatibilityList> Create(
const unsigned char* compatibility_list_flatbuffer, size_t length);
// Constructs list from the given flatbuffer data. Returns a unique_ptr to a
// nullptr is the given flatbuffer is empty or invalid.
// The passed flatbuffer will be owned by the compatibility list object, so
// this method can be used safely with local temporary strings.
static std::unique_ptr<GPUCompatibilityList> Create(
std::string compatibility_list_flatbuffer);
// Returns true if the provided device specs are supported by the database.
bool Includes(const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const;
// Returns the compatibility status as an enum (unknown/supported/unsupported)
gpu::CompatibilityStatus GetStatus(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const;
// Returns the compatibility status as an enum (unknown/supported/unsupported)
// of the provided device specified as a map of variables (properties).
// Map keys should all be from here:
// tensorflow/lite/experimental/acceleration/compatibility/variables.h
gpu::CompatibilityStatus GetStatus(
std::map<std::string, std::string>& variables) const;
// Returns the best TfLiteGpuDelegateOptionsV2 for the provided device specs
// based on the database. The output can be modified as desired before passing
// to delegate creation.
TfLiteGpuDelegateOptionsV2 GetBestOptionsFor(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const;
// Convert android_info and gpu_info into a set of variables used for querying
// the list, and update variables from list data. See variables.h
// and devicedb.h for more information.
std::map<std::string, std::string> CalculateVariables(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const;
GPUCompatibilityList(const GPUCompatibilityList&) = delete;
GPUCompatibilityList& operator=(const GPUCompatibilityList&) = delete;
// Checks if the provided byte array represents a valid compatibility list
static bool IsValidFlatbuffer(const unsigned char* data, size_t len);
std::map<std::string, std::string> InfosToMap(
const AndroidInfo& android_info,
const ::tflite::gpu::GpuInfo& gpu_info) const;
// Converts the compatibility status enum value to the corresponding status
// string.
static std::string CompatibilityStatusToString(
gpu::CompatibilityStatus status);
// Converts the status string to the corresponding compatibility status enum
// value.
static gpu::CompatibilityStatus StringToCompatibilityStatus(
absl::string_view status);
protected:
const DeviceDatabase* database_;
// Optional container of the flatbuffer content, to support ownership of the
// flatbuffer by the compatibility list object itself.
std::string fbcontent_;
private:
explicit GPUCompatibilityList(
const unsigned char* compatibility_list_flatbuffer);
explicit GPUCompatibilityList(std::string compatibility_list_flatbuffer);
};
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_GPU_COMPATIBILITY_H_
@@ -0,0 +1,183 @@
/* 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/experimental/acceleration/compatibility/gpu_compatibility.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <gtest/gtest.h>
#include "tensorflow/lite/experimental/acceleration/compatibility/devicedb-sample.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/variables.h"
namespace {
class GPUCompatibilityTest : public ::testing::Test {
protected:
GPUCompatibilityTest() {
list_ = tflite::acceleration::GPUCompatibilityList::Create(
g_tflite_acceleration_devicedb_sample_binary,
g_tflite_acceleration_devicedb_sample_binary_len);
}
std::unique_ptr<tflite::acceleration::GPUCompatibilityList> list_;
};
TEST_F(GPUCompatibilityTest, ReturnsUnsupportedStatus) {
ASSERT_TRUE(list_ != nullptr);
std::map<std::string, std::string> variables = {
{tflite::acceleration::kAndroidSdkVersion, "28"},
{tflite::acceleration::kDeviceModel, "shiraz-ag-2011"},
};
EXPECT_EQ(list_->GetStatus(variables),
tflite::acceleration::gpu::CompatibilityStatus::kUnsupported);
}
TEST_F(GPUCompatibilityTest, ReturnsSupportedStatus) {
ASSERT_TRUE(list_ != nullptr);
std::map<std::string, std::string> variables = {
{tflite::acceleration::kAndroidSdkVersion, "24"},
{tflite::acceleration::kDeviceModel, "M712C"},
{tflite::acceleration::kOpenGLESVersion, "3.1"},
};
EXPECT_EQ(list_->GetStatus(variables),
tflite::acceleration::gpu::CompatibilityStatus::kSupported);
}
TEST_F(GPUCompatibilityTest, ReturnsUnknownStatus) {
ASSERT_TRUE(list_ != nullptr);
std::map<std::string, std::string> variables = {
{tflite::acceleration::kAndroidSdkVersion, "26"},
{tflite::acceleration::kDeviceModel, "mag2016"},
{tflite::acceleration::kOpenGLESVersion, "3.1"},
};
EXPECT_EQ(list_->GetStatus(variables),
tflite::acceleration::gpu::CompatibilityStatus::kUnknown);
}
TEST_F(GPUCompatibilityTest, ReturnsSupportedForFullMatch) {
ASSERT_TRUE(list_ != nullptr);
tflite::acceleration::AndroidInfo android_info = {.android_sdk_version = "24",
.model = "m712c"};
tflite::gpu::GpuInfo tflite_gpu_info;
tflite_gpu_info.opengl_info.major_version = 3;
tflite_gpu_info.opengl_info.minor_version = 1;
EXPECT_TRUE(list_->Includes(android_info, tflite_gpu_info));
}
TEST_F(GPUCompatibilityTest, ReturnsUnsupportedForFullMatch) {
ASSERT_TRUE(list_ != nullptr);
tflite::acceleration::AndroidInfo android_info = {.android_sdk_version = "28",
.model = "SM-G960F",
.device = "starlte",
.manufacturer = "Samsung"};
tflite::gpu::GpuInfo tflite_gpu_info;
tflite_gpu_info.opengl_info.renderer_name = "Mali-G72";
tflite_gpu_info.opengl_info.major_version = 3;
tflite_gpu_info.opengl_info.minor_version = 2;
EXPECT_FALSE(list_->Includes(android_info, tflite_gpu_info));
}
TEST_F(GPUCompatibilityTest, ReturnsDefaultOptions) {
ASSERT_TRUE(list_ != nullptr);
tflite::acceleration::AndroidInfo android_info;
tflite::gpu::GpuInfo tflite_gpu_info;
auto default_options = TfLiteGpuDelegateOptionsV2Default();
auto best_options = list_->GetBestOptionsFor(android_info, tflite_gpu_info);
EXPECT_EQ(best_options.is_precision_loss_allowed,
default_options.is_precision_loss_allowed);
EXPECT_EQ(best_options.inference_preference,
default_options.inference_preference);
EXPECT_EQ(best_options.inference_priority1,
default_options.inference_priority1);
EXPECT_EQ(best_options.inference_priority2,
default_options.inference_priority2);
EXPECT_EQ(best_options.inference_priority3,
default_options.inference_priority3);
EXPECT_EQ(best_options.experimental_flags,
default_options.experimental_flags);
EXPECT_EQ(best_options.max_delegated_partitions,
default_options.max_delegated_partitions);
}
TEST(GPUCompatibility, RecogniseValidCompatibilityListFlatbuffer) {
EXPECT_TRUE(tflite::acceleration::GPUCompatibilityList::IsValidFlatbuffer(
g_tflite_acceleration_devicedb_sample_binary,
g_tflite_acceleration_devicedb_sample_binary_len));
}
TEST(GPUCompatibility, RecogniseInvalidCompatibilityListFlatbuffer) {
unsigned char invalid_buffer[100];
std::fill(invalid_buffer, invalid_buffer + 100, ' ');
EXPECT_FALSE(tflite::acceleration::GPUCompatibilityList::IsValidFlatbuffer(
invalid_buffer, 100));
}
TEST(GPUCompatibility, CreationWithInvalidCompatibilityListFlatbuffer) {
unsigned char invalid_buffer[10];
std::fill(invalid_buffer, invalid_buffer + 10, ' ');
std::unique_ptr<tflite::acceleration::GPUCompatibilityList> list =
tflite::acceleration::GPUCompatibilityList::Create(invalid_buffer, 10);
EXPECT_EQ(list, nullptr);
}
TEST(GPUCompatibility, CreationWithNullCompatibilityListFlatbuffer) {
std::unique_ptr<tflite::acceleration::GPUCompatibilityList> list =
tflite::acceleration::GPUCompatibilityList::Create(nullptr, 0);
EXPECT_EQ(list, nullptr);
}
TEST(GPUCompatibility, ConvertCompatibilityStatusToStringCorrectly) {
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::CompatibilityStatusToString(
tflite::acceleration::gpu::CompatibilityStatus::kSupported),
tflite::acceleration::gpu::kStatusSupported);
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::CompatibilityStatusToString(
tflite::acceleration::gpu::CompatibilityStatus::kUnsupported),
tflite::acceleration::gpu::kStatusUnsupported);
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::CompatibilityStatusToString(
tflite::acceleration::gpu::CompatibilityStatus::kUnknown),
tflite::acceleration::gpu::kStatusUnknown);
}
TEST(GPUCompatibility, ConvertStringToCompatibilityStatusCorrectly) {
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::StringToCompatibilityStatus(
tflite::acceleration::gpu::kStatusSupported),
tflite::acceleration::gpu::CompatibilityStatus::kSupported);
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::StringToCompatibilityStatus(
tflite::acceleration::gpu::kStatusUnsupported),
tflite::acceleration::gpu::CompatibilityStatus::kUnsupported);
EXPECT_EQ(
tflite::acceleration::GPUCompatibilityList::StringToCompatibilityStatus(
tflite::acceleration::gpu::kStatusUnknown),
tflite::acceleration::gpu::CompatibilityStatus::kUnknown);
}
} // namespace
@@ -0,0 +1,93 @@
/* 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.
==============================================================================*/
// Simple program to convert from JSON to binary flatbuffers for given schema.
//
// Used for creating the binary version of a compatibility list.
//
// The flatc command line is not available in all build environments.
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "flatbuffers/idl.h" // from @flatbuffers
#include "flatbuffers/reflection.h" // from @flatbuffers
#include "flatbuffers/reflection_generated.h" // from @flatbuffers
#include "flatbuffers/util.h" // from @flatbuffers
#include "tensorflow/lite/tools/command_line_flags.h"
int main(int argc, char** argv) {
std::string json_path, fbs_path, fb_path;
std::vector<tflite::Flag> flags = {
tflite::Flag::CreateFlag("json_input", &json_path,
"Path to input json file."),
tflite::Flag::CreateFlag("fbs", &fbs_path,
"Path to flatbuffer schema to use."),
tflite::Flag::CreateFlag("fb_output", &fb_path,
"Path to a output binary flatbuffer."),
};
const bool parse_result =
tflite::Flags::Parse(&argc, const_cast<const char**>(argv), flags);
if (!parse_result || json_path.empty() || fbs_path.empty() ||
fb_path.empty()) {
std::cerr << tflite::Flags::Usage(argv[0], flags);
return 1;
}
std::string json_contents;
if (!flatbuffers::LoadFile(json_path.c_str(), false, &json_contents)) {
std::cerr << "Unable to load file " << json_path << std::endl;
return 2;
}
std::string fbs_contents;
if (!flatbuffers::LoadFile(fbs_path.c_str(), false, &fbs_contents)) {
std::cerr << "Unable to load file " << fbs_path << std::endl;
return 3;
}
const char* include_directories[] = {nullptr};
flatbuffers::Parser schema_parser;
if (!schema_parser.Parse(fbs_contents.c_str(), include_directories)) {
std::cerr << "Unable to parse schema " << schema_parser.error_ << std::endl;
return 4;
}
schema_parser.Serialize();
auto schema =
reflection::GetSchema(schema_parser.builder_.GetBufferPointer());
auto root_table = schema->root_table();
flatbuffers::Parser parser;
parser.Deserialize(schema_parser.builder_.GetBufferPointer(),
schema_parser.builder_.GetSize());
if (!parser.Parse(json_contents.c_str(), include_directories,
json_path.c_str())) {
std::cerr << "Unable to parse json " << parser.error_ << std::endl;
return 5;
}
// Use CopyTable() to deduplicate the strings.
const uint8_t* buffer = parser.builder_.GetBufferPointer();
flatbuffers::FlatBufferBuilder fbb;
auto root_offset = flatbuffers::CopyTable(
fbb, *schema, *root_table, *flatbuffers::GetAnyRoot(buffer), true);
fbb.Finish(root_offset);
std::string binary(reinterpret_cast<const char*>(fbb.GetBufferPointer()),
fbb.GetSize());
std::ofstream output;
output.open(fb_path);
output << binary;
output.close();
return 0;
}
@@ -0,0 +1,106 @@
/* 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_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_VARIABLES_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_VARIABLES_H_
// This file lists generally useful compatibility properties.
// Properties starting with "tflite." are reserved.
// Users of the compatibility library can use arbitrary other property names.
namespace tflite {
namespace acceleration {
// System properties, not specific to any single delegate.
// Android properties.
//
// Android SDK version number. Android system property ro.build.version.sdk.
// E.g., "28".
inline constexpr char kAndroidSdkVersion[] = "tflite.android_sdk_version";
// SoC model. Looked up from database or possibly returned from Android system
// property ro.board.platform, normalized. E.g., "sdm450".
inline constexpr char kSoCModel[] = "tflite.soc_model";
// SoC vendor. Looked up from database. E.g., "qualcomm".
inline constexpr char kSoCVendor[] = "tflite.soc_vendor";
// Device manufacturer. Android API android.os.Build.MANUFACTURER, normalized.
// E.g., "google".
inline constexpr char kManufacturer[] = "tflite.manufacturer";
// Device model. Android API android.os.Build.MODEL, normalized.
// E.g., "pixel_2".
inline constexpr char kDeviceModel[] = "tflite.device_model";
// Device name. Android API android.os.Build.DEVICE, normalized.
// E.g., "walleye".
inline constexpr char kDeviceName[] = "tflite.device_name";
// GPU-related properties.
//
// OpenGL ES version. E.g., 3.2.
inline constexpr char kOpenGLESVersion[] = "tflite.opengl_es_version";
// GPU model, result of querying GL_RENDERER, normalized. E.g.,
// "adreno_(tm)_505".
inline constexpr char kGPUModel[] = "tflite.gpu_model";
// GPU vendor, normalized. E.g., "adreno_(tm)_505".
inline constexpr char kGPUVendor[] = "tflite.gpu_vendor";
// OpenGL driver version, result of querying GL_VERSION. E.g.,
// "opengl_es_3.2_v@328.0_(git@6fb5a5b,_ife855c4895)_(date:08/21/18)"
inline constexpr char kOpenGLDriverVersion[] = "tflite.opengl_driver_version";
// Allowlist use case. This property is used to allow joining multiple lists
// into a single decision tree.
inline constexpr char kUseCase[] = "tflite.use_case";
// NNAPI-related properties.
//
// NNAPI accelerator name, returned by ANeuralNetworksDevice_getName. E.g.,
// "qti-dsp".
inline constexpr char kNNAPIAccelerator[] = "tflite.nnapi_accelerator";
// NNAPI accelerator feature level, returned by
// ANeuralNetworksDevice_getFeatureLevel. E.g., 29. Actual variables are named
// "tflite.nnapi_feature_level.<accelerator name>", e.g.,
// "tflite.nnapi_feature_level.qti-dsp".
inline constexpr char kNNAPIFeatureLevelPrefix[] = "tflite.nnapi_feature_level";
namespace gpu {
// GPU-delegate derived properties.
// Whether the GPU delegate works in general.
// Possible values are ("", "SUPPORTED", "UNSUPPORTED"). An empty value for
// this field means that the device is unsupported.
inline constexpr char kStatus[] = "tflite.gpu.status";
inline constexpr char kStatusSupported[] = "SUPPORTED";
inline constexpr char kStatusUnknown[] = "UNKNOWN";
inline constexpr char kStatusUnsupported[] = "UNSUPPORTED";
enum class CompatibilityStatus {
kUnknown = 0,
kSupported,
kUnsupported,
};
} // namespace gpu
namespace metadata {
// Latency for model to run.
inline constexpr char kLatency[] = "tflite.latency";
// Frame time for one frame through model.
inline constexpr char kFrameTime[] = "tflite.frame_time";
} // namespace metadata
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_COMPATIBILITY_VARIABLES_H_
@@ -0,0 +1,224 @@
# Copyright 2019 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.
# ==============================================================================
# buildifier: disable=out-of-order-load
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")
load("@flatbuffers//:build_defs.bzl", "DEFAULT_FLATC_ARGS", "flatbuffer_android_library", "flatbuffer_cc_library", "flatbuffer_java_library")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load("//tensorflow/lite:build_def.bzl", "tflite_copts", "tflite_copts_warnings")
load("//tensorflow/lite:special_rules.bzl", "nnapi_plugin_impl_visibility_allowlist", "tflite_portable_test_suite")
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "cc_library_with_tflite")
# copybara:uncomment load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
# Generate a C++ header containing the contents of the FlatBuffer schema
# as a char array literal. This is potentially useful for embedding in programs
# (e.g. for JSON parsing using that schema).
genrule(
name = "configuration_fbs_contents_cc",
srcs = ["configuration.fbs"],
outs = ["configuration_fbs_contents-inl.h"],
cmd = """
echo 'constexpr char configuration_fbs_contents[] = R"Delimiter(' > $(@)
cat < $(<) >> $(@)
echo ')Delimiter";' >> $(@)
""",
compatible_with = get_compatible_with_portable(),
)
genrule(
name = "configuration.fbs-backwards-compat-stub",
srcs = ["//tensorflow/lite/acceleration/configuration:configuration.fbs"],
outs = ["configuration.fbs"],
cmd = "cp $< $@",
compatible_with = get_compatible_with_portable(),
)
proto_library(
name = "configuration_proto",
srcs = ["configuration.proto"],
exports = ["//tensorflow/lite/acceleration/configuration:configuration_proto"],
deps = ["//tensorflow/lite/acceleration/configuration:configuration_proto"],
)
alias(
name = "configuration_java_proto_lite",
actual = "//tensorflow/lite/acceleration/configuration:configuration_java_proto_lite",
)
cc_proto_library(
name = "configuration_cc_proto",
deps = [":configuration_proto"],
)
flatbuffer_cc_library(
name = "configuration_fbs",
srcs = [":configuration.fbs"],
compatible_with = get_compatible_with_portable(),
flatc_args = DEFAULT_FLATC_ARGS + ["--gen-compare"],
)
flatbuffer_java_library(
name = "configuration_fbs_java",
srcs = [":configuration.fbs"],
)
flatbuffer_android_library(
name = "configuration_fbs_android",
srcs = [":configuration.fbs"],
)
cc_library(
name = "proto_to_flatbuffer",
hdrs = ["proto_to_flatbuffer.h"],
deps = [
"//tensorflow/lite/acceleration/configuration:proto_to_flatbuffer",
],
)
cc_library(
name = "flatbuffer_to_proto",
hdrs = ["flatbuffer_to_proto.h"],
deps = [
"//tensorflow/lite/acceleration/configuration:flatbuffer_to_proto",
],
)
cc_library_with_tflite(
name = "delegate_registry",
hdrs = ["delegate_registry.h"],
compatible_with = get_compatible_with_portable(),
tflite_deps = [
"//tensorflow/lite/acceleration/configuration:delegate_registry",
],
)
cc_library_with_tflite(
name = "delegate_plugin_converter",
hdrs = ["delegate_plugin_converter.h"],
tflite_deps = [
"//tensorflow/lite/acceleration/configuration:delegate_plugin_converter",
],
)
cc_library(
name = "nnapi_plugin",
compatible_with = get_compatible_with_portable(),
deps = [
":nnapi_plugin_impl",
],
)
cc_library(
name = "nnapi_plugin_impl",
hdrs = ["nnapi_plugin.h"],
compatible_with = get_compatible_with_portable(),
visibility = nnapi_plugin_impl_visibility_allowlist(),
deps = [
"//tensorflow/lite/core/acceleration/configuration:nnapi_plugin",
],
)
cc_library(
name = "hexagon_plugin",
srcs = ["hexagon_plugin.cc"],
deps = [
":configuration_fbs",
"//tensorflow/lite/core/acceleration/configuration:delegate_registry",
"@com_google_absl//absl/memory",
] + select({
"@platforms//cpu:aarch64": [
"//tensorflow/lite/delegates/hexagon:hexagon_delegate",
],
"@platforms//cpu:armv7": [
"//tensorflow/lite/delegates/hexagon:hexagon_delegate",
],
"//conditions:default": [],
}),
alwayslink = 1, # For registration to always run.
)
cc_library(
name = "gpu_plugin",
deps = [
":gpu_plugin_impl",
],
)
common_copts = tflite_copts() + tflite_copts_warnings()
cc_library(
name = "gpu_plugin_impl",
hdrs = ["gpu_plugin.h"],
copts = common_copts + select({
"//tensorflow:ios": [
"-xobjective-c++",
],
"//tensorflow:macos_arm64": [
"-xobjective-c++",
],
"//conditions:default": [],
}),
deps = [
"//tensorflow/lite/acceleration/configuration:gpu_plugin_impl",
],
)
cc_library(
name = "xnnpack_plugin",
deps = [
"//tensorflow/lite/acceleration/configuration:xnnpack_plugin",
],
)
cc_library(
name = "coreml_plugin",
srcs = ["coreml_plugin.cc"],
deps = [
":configuration_fbs",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/core/acceleration/configuration:delegate_registry",
"@com_google_absl//absl/memory",
] + select({
"//tensorflow:macos": [
"//tensorflow/lite/delegates/coreml:coreml_delegate",
],
"//tensorflow:ios": [
"//tensorflow/lite/delegates/coreml:coreml_delegate",
],
"//conditions:default": [],
}),
alwayslink = 1, # For registration to always run.
)
# TODO(b/260582614): Add support for TF Lite in Play Services.
cc_library(
name = "stable_delegate_plugin",
hdrs = ["stable_delegate_plugin.h"],
deps = [
"//tensorflow/lite/acceleration/configuration:stable_delegate_plugin",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,43 @@
# 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.
# ==============================================================================
"""Build macros for checking ABI compatibility."""
load("@flatbuffers//:build_defs.bzl", "flatc_path")
def flatbuffer_schema_compat_test(name, ref_schema, schema):
"""Generates a test for schema binary compatibility.
Generates a test that the specified schema file is binary backwards
compatible with a reference schema (e.g. a previous version of the
schema).
Note: currently this build macro requires that the schema be a single
fully self-contained .fbs file; it does not yet support includes.
"""
native.genrule(
name = name + "_gen",
srcs = [ref_schema, schema],
outs = [name + "_test.sh"],
tools = [flatc_path],
cmd = ("echo $(rootpath {}) --conform $(rootpath {}) $(rootpath {}) > $@"
.format(flatc_path, ref_schema, schema)),
)
native.sh_test(
name = name,
srcs = [name + "_test.sh"],
data = [flatc_path, ref_schema, schema],
)
@@ -0,0 +1,76 @@
# Copyright 2021 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.
# ==============================================================================
# C API for delegate plugins.
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load(
"//tensorflow/lite/core/shims:cc_library_with_tflite.bzl",
"cc_library_with_tflite_with_c_headers_test",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:private"],
licenses = ["notice"],
)
cc_library_with_tflite_with_c_headers_test(
name = "delegate_plugin",
hdrs = ["delegate_plugin.h"],
compatible_with = get_compatible_with_portable(),
generate_opaque_delegate_target = True,
tflite_deps = [
"//tensorflow/lite/acceleration/configuration/c:delegate_plugin",
],
visibility = ["//visibility:public"],
)
cc_library_with_tflite_with_c_headers_test(
name = "nnapi_plugin",
hdrs = ["nnapi_plugin.h"],
tflite_deps = [
"//tensorflow/lite/acceleration/configuration/c:nnapi_plugin",
],
visibility = ["//visibility:public"],
)
cc_library_with_tflite_with_c_headers_test(
name = "gpu_plugin",
hdrs = ["gpu_plugin.h"],
tflite_deps = [
"//tensorflow/lite/acceleration/configuration/c:gpu_plugin",
],
visibility = ["//visibility:public"],
)
cc_library_with_tflite_with_c_headers_test(
name = "xnnpack_plugin",
hdrs = ["xnnpack_plugin.h"],
tflite_deps = [
"//tensorflow/lite/acceleration/configuration/c:xnnpack_plugin",
],
visibility = ["//visibility:public"],
)
cc_library_with_tflite_with_c_headers_test(
name = "stable_delegate",
hdrs = ["stable_delegate.h"],
generate_opaque_delegate_target = True,
tflite_deps = [
"//tensorflow/lite/acceleration/configuration/c:stable_delegate",
],
visibility = ["//visibility:public"],
)
@@ -0,0 +1,25 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_DELEGATE_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_DELEGATE_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_DELEGATE_PLUGIN_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_GPU_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_GPU_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/c/gpu_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/c/gpu_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_GPU_PLUGIN_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_NNAPI_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_NNAPI_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/c/nnapi_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/c/nnapi_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_NNAPI_PLUGIN_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_STABLE_DELEGATE_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_STABLE_DELEGATE_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/c/stable_delegate.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/c/stable_delegate.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_STABLE_DELEGATE_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_XNNPACK_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_XNNPACK_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/c/xnnpack_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/c/xnnpack_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_C_XNNPACK_PLUGIN_H_
@@ -0,0 +1,20 @@
// Copyright 2026 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.
// ==============================================================================
syntax = "proto2";
package tflite.proto;
import public "tensorflow/lite/acceleration/configuration/configuration.proto";
@@ -0,0 +1,73 @@
/* 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 <memory>
#include "absl/memory/memory.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/acceleration/configuration/delegate_registry.h"
#include "tensorflow/lite/minimal_logging.h"
// Guarding anyway although this file not expected to be compiled for non-Apple.
#if defined(__APPLE__)
#include "tensorflow/lite/delegates/coreml/coreml_delegate.h"
namespace tflite {
namespace delegates {
class CoreMLPlugin : public DelegatePluginInterface {
public:
TfLiteDelegatePtr Create() override {
TfLiteDelegate* delegate_ptr = TfLiteCoreMlDelegateCreate(&options_);
TfLiteDelegatePtr delegate(delegate_ptr, [](TfLiteDelegate* delegate) {
TfLiteCoreMlDelegateDelete(delegate);
});
return delegate;
}
int GetDelegateErrno(TfLiteDelegate* /* from_delegate */) override {
return 0;
}
static std::unique_ptr<CoreMLPlugin> New(
const TFLiteSettings& tflite_settings) {
return absl::make_unique<CoreMLPlugin>(tflite_settings);
}
explicit CoreMLPlugin(const TFLiteSettings& tflite_settings) {
const CoreMLSettings* settings = tflite_settings.coreml_settings();
options_ = TfLiteCoreMlDelegateOptions({});
// Using the proto defaults if the settings were not set.
switch (settings->enabled_devices()) {
case tflite::CoreMLSettings_::EnabledDevices_DEVICES_ALL:
options_.enabled_devices = TfLiteCoreMlDelegateAllDevices;
break;
case tflite::CoreMLSettings_::EnabledDevices_DEVICES_WITH_NEURAL_ENGINE:
options_.enabled_devices = TfLiteCoreMlDelegateDevicesWithNeuralEngine;
break;
default:
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Invalid devices enum: %d",
settings->enabled_devices());
}
options_.coreml_version = settings->coreml_version();
options_.max_delegated_partitions = settings->max_delegated_partitions();
options_.min_nodes_per_partition = settings->min_nodes_per_partition();
}
private:
TfLiteCoreMlDelegateOptions options_;
};
TFLITE_REGISTER_DELEGATE_FACTORY_FUNCTION(CoreMLPlugin, CoreMLPlugin::New);
} // namespace delegates
} // namespace tflite
#endif // __APPLE__
@@ -0,0 +1,25 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_PLUGIN_CONVERTER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_PLUGIN_CONVERTER_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/delegate_plugin_converter.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/delegate_plugin_converter.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_PLUGIN_CONVERTER_H_
@@ -0,0 +1,25 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_REGISTRY_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_REGISTRY_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/delegate_registry.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/delegate_registry.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_DELEGATE_REGISTRY_H_
@@ -0,0 +1,25 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_FLATBUFFER_TO_PROTO_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_FLATBUFFER_TO_PROTO_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/flatbuffer_to_proto.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/flatbuffer_to_proto.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_FLATBUFFER_TO_PROTO_H_
@@ -0,0 +1,25 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_GPU_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_GPU_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/gpu_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/gpu_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_GPU_PLUGIN_H_
@@ -0,0 +1,77 @@
/* 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 <memory>
#include "absl/memory/memory.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/acceleration/configuration/delegate_registry.h"
#if defined(__ARM_ARCH)
#include "tensorflow/lite/delegates/hexagon/hexagon_delegate.h"
#endif
namespace tflite {
namespace delegates {
class HexagonPlugin : public DelegatePluginInterface {
public:
TfLiteDelegatePtr Create() override {
#if defined(__ARM_ARCH)
TfLiteHexagonInit();
auto* delegate_ptr = TfLiteHexagonDelegateCreate(&options_);
TfLiteDelegatePtr delegate(delegate_ptr, [](TfLiteDelegate* delegate) {
TfLiteHexagonDelegateDelete(delegate);
TfLiteHexagonTearDown();
});
return delegate;
#else // !defined(__ARM_ARCH)
return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
#endif // defined(__ARM_ARCH)
}
int GetDelegateErrno(TfLiteDelegate* /* from_delegate */) override {
return 0;
}
static std::unique_ptr<HexagonPlugin> New(
const TFLiteSettings& tflite_settings) {
return std::make_unique<HexagonPlugin>(tflite_settings);
}
explicit HexagonPlugin(const TFLiteSettings& tflite_settings) {
const HexagonSettings* settings = tflite_settings.hexagon_settings();
#if defined(__ARM_ARCH)
options_ = TfLiteHexagonDelegateOptions({0});
if (settings) {
options_.debug_level = settings->debug_level();
options_.powersave_level = settings->powersave_level();
options_.print_graph_profile = settings->print_graph_profile();
options_.print_graph_debug = settings->print_graph_debug();
if (tflite_settings.max_delegated_partitions() >= 0) {
options_.max_delegated_partitions =
tflite_settings.max_delegated_partitions();
}
}
#else
(void)settings;
#endif
}
private:
#if defined(__ARM_ARCH)
TfLiteHexagonDelegateOptions options_;
#endif
};
TFLITE_REGISTER_DELEGATE_FACTORY_FUNCTION(HexagonPlugin, HexagonPlugin::New);
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_NNAPI_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_NNAPI_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/core/acceleration/configuration/nnapi_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/core/acceleration/configuration/nnapi_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_NNAPI_PLUGIN_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_PROTO_TO_FLATBUFFER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_PROTO_TO_FLATBUFFER_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/proto_to_flatbuffer.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/proto_to_flatbuffer.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_PROTO_TO_FLATBUFFER_H_
@@ -0,0 +1,25 @@
/* 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_EXPERIMENTAL_ACCELERATION_CONFIGURATION_STABLE_DELEGATE_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_STABLE_DELEGATE_PLUGIN_H_
// This header file is no longer experimental.
// Please use the non-experimental file instead.
#include "tensorflow/lite/acceleration/configuration/stable_delegate_plugin.h" // IWYU pragma: export
// IWYU pragma: private, include "third_party/tensorflow/lite/acceleration/configuration/stable_delegate_plugin.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_CONFIGURATION_STABLE_DELEGATE_PLUGIN_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
/* 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/experimental/acceleration/mini_benchmark/benchmark_result_evaluator.h"
#include <memory>
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
namespace tflite {
namespace acceleration {
EmbeddedResultEvaluator* EmbeddedResultEvaluator::GetInstance() {
static EmbeddedResultEvaluator* const instance =
new EmbeddedResultEvaluator();
return instance;
}
bool EmbeddedResultEvaluator::HasPassedAccuracyCheck(
const BenchmarkResult& result) {
return result.ok();
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,55 @@
/* 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BENCHMARK_RESULT_EVALUATOR_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BENCHMARK_RESULT_EVALUATOR_H_
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
namespace tflite {
namespace acceleration {
// Evaluates the BenchmarkEvent output from validator.
class AbstractBenchmarkResultEvaluator {
public:
virtual ~AbstractBenchmarkResultEvaluator() = default;
// Returns whether this event means the validation test has passed. It checks
// that the test has finished successfully, and the test result passed
// accuracy checks.
bool IsValidationSuccessEvent(const BenchmarkEvent& event) {
return event.event_type() == BenchmarkEventType_END && event.result() &&
HasPassedAccuracyCheck(*event.result());
}
// Returns whether this BenchmarkResult should pass the accuracy check.
virtual bool HasPassedAccuracyCheck(const BenchmarkResult& result) = 0;
};
// Evaluator for embedded validation scenario.
class EmbeddedResultEvaluator : public AbstractBenchmarkResultEvaluator {
public:
static EmbeddedResultEvaluator* GetInstance();
bool HasPassedAccuracyCheck(const BenchmarkResult& result) override;
private:
EmbeddedResultEvaluator() = default;
~EmbeddedResultEvaluator() override = default;
};
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BENCHMARK_RESULT_EVALUATOR_H_
@@ -0,0 +1,123 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/big_little_affinity.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include <set>
#include "include/cpuinfo.h"
namespace tflite {
namespace acceleration {
namespace {
bool IsInOrderArch(cpuinfo_uarch arch) {
switch (arch) {
case cpuinfo_uarch_cortex_a53:
case cpuinfo_uarch_cortex_a55r0:
case cpuinfo_uarch_cortex_a55:
case cpuinfo_uarch_cortex_a57:
return true;
default:
return false;
}
return false;
}
} // namespace
BigLittleAffinity GetAffinity() {
BigLittleAffinity affinity;
if (!cpuinfo_initialize()) {
return affinity;
}
std::map<uint32_t, uint64_t> cluster_to_max_frequency;
uint64_t smallest_max_frequency = UINT64_MAX;
uint64_t largest_max_frequency = 0;
uint64_t processors_count = cpuinfo_get_processors_count();
for (auto i = 0; i < processors_count; i++) {
const struct cpuinfo_processor* processor = cpuinfo_get_processor(i);
if (processor->core->frequency > 0) {
cluster_to_max_frequency[processor->cluster->cluster_id] =
processor->core->frequency;
smallest_max_frequency =
std::min(smallest_max_frequency, processor->core->frequency);
largest_max_frequency =
std::max(largest_max_frequency, processor->core->frequency);
}
}
int count_of_processors_with_largest_max_frequency = 0;
for (auto i = 0; i < cpuinfo_get_processors_count(); i++) {
const struct cpuinfo_processor* processor = cpuinfo_get_processor(i);
uint64_t max_frequency =
cluster_to_max_frequency[processor->cluster->cluster_id];
if (max_frequency == largest_max_frequency) {
++count_of_processors_with_largest_max_frequency;
}
}
std::set<cpuinfo_uarch> archs;
// Three variants for detecting the big/little split:
// - all cores have the same frequency, check the uarch for in-order (on
// big.LITTLE, the big cores are typically out-of-order and the LITTLE
// cores in-order)
// - if there are 2 cores with largest max frequency, those are counted as big
// - otherwise the cores with smallest max frequency are counted as LITTLE
for (auto i = 0; i < cpuinfo_get_processors_count(); i++) {
const struct cpuinfo_processor* processor = cpuinfo_get_processor(i);
uint64_t max_frequency =
cluster_to_max_frequency[processor->cluster->cluster_id];
bool is_little;
archs.insert(processor->core->uarch);
if (count_of_processors_with_largest_max_frequency ==
cpuinfo_get_processors_count()) {
is_little = IsInOrderArch(processor->core->uarch);
} else if (count_of_processors_with_largest_max_frequency == 2) {
is_little = (max_frequency != largest_max_frequency);
} else {
is_little = (max_frequency == smallest_max_frequency);
}
#ifdef __ANDROID__
// On desktop linux there are easily more processors than bits in an int, so
// skip this code. It's still convenient to enable the rest of the code on
// non-Android for quicker testing.
if (is_little) {
affinity.little_core_affinity |= (0x1 << processor->linux_id);
} else {
affinity.big_core_affinity |= (0x1 << processor->linux_id);
}
#endif // __ANDROID__
}
// After the detection we may have determined that all cores are big or
// LITTLE. This is ok if there is only one cluster or if all the cores are the
// same, and in that case we return the same for both masks.
if (cluster_to_max_frequency.size() == 1) {
// Only one cluster.
affinity.big_core_affinity = affinity.little_core_affinity =
std::max(affinity.big_core_affinity, affinity.little_core_affinity);
} else if (count_of_processors_with_largest_max_frequency ==
cpuinfo_get_processors_count() &&
archs.size() == 1) {
// All cores have same uarch and frequency.
affinity.big_core_affinity = affinity.little_core_affinity =
std::max(affinity.big_core_affinity, affinity.little_core_affinity);
}
return affinity;
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,33 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BIG_LITTLE_AFFINITY_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BIG_LITTLE_AFFINITY_H_
#include <cstdint>
namespace tflite {
namespace acceleration {
struct BigLittleAffinity {
uint16_t big_core_affinity = 0;
uint16_t little_core_affinity = 0;
};
BigLittleAffinity GetAffinity();
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BIG_LITTLE_AFFINITY_H_
@@ -0,0 +1,70 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/big_little_affinity.h"
#include <cstdint>
#include <map>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "include/cpuinfo.h"
#include "tensorflow/lite/experimental/acceleration/compatibility/android_info.h"
namespace tflite {
namespace acceleration {
namespace {
TEST(BigLittle, CheckBasics) {
ASSERT_TRUE(cpuinfo_initialize());
auto processors_count = cpuinfo_get_processors_count();
ASSERT_GT(processors_count, 0);
#if defined(__ANDROID__)
AndroidInfo android_info;
auto status = RequestAndroidInfo(&android_info);
if (android_info.is_emulator) {
std::cout << "Running on emulator\n";
return;
} else {
std::cout << "Running on hardware\n";
}
ASSERT_TRUE(status.ok());
std::map<uint32_t, uint64_t> cluster_to_max_frequency;
for (auto i = 0; i < cpuinfo_get_processors_count(); i++) {
const struct cpuinfo_processor* processor = cpuinfo_get_processor(i);
if (processor->core->frequency > 0) {
cluster_to_max_frequency[processor->cluster->cluster_id] =
processor->core->frequency;
}
}
EXPECT_GT(cluster_to_max_frequency.size(), 0);
EXPECT_LE(cluster_to_max_frequency.size(), 3);
for (auto i = 0; i < cpuinfo_get_processors_count(); i++) {
const struct cpuinfo_processor* processor = cpuinfo_get_processor(i);
EXPECT_TRUE(cluster_to_max_frequency.find(processor->cluster->cluster_id) !=
cluster_to_max_frequency.end());
}
BigLittleAffinity affinity = GetAffinity();
EXPECT_GT(affinity.little_core_affinity, 0);
EXPECT_GT(affinity.big_core_affinity, 0);
std::cout << "Little core affinity: " << std::hex
<< affinity.little_core_affinity << std::endl;
std::cout << "Big core affinity: " << std::hex << affinity.big_core_affinity
<< std::endl;
#endif
}
} // namespace
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,171 @@
/* 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/experimental/acceleration/mini_benchmark/blocking_validator_runner.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/time/time.h"
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/validator.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/validator_runner_options.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite {
namespace acceleration {
namespace {
using ::flatbuffers::FlatBufferBuilder;
using ::flatbuffers::GetRoot;
// Wait time between each query to the test result file, defined in
// microseconds.
constexpr absl::Duration kWaitBetweenRefresh = absl::Milliseconds(20);
// Generate a string of 10 chars.
std::string GenerateRandomString() {
static const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const int size = 10;
std::string result;
result.resize(size);
for (int i = 0; i < size; ++i) {
result[i] = charset[rand() % (sizeof(charset) - 1)];
}
return result;
}
} // namespace
BlockingValidatorRunner::BlockingValidatorRunner(
const ValidatorRunnerOptions& options)
: per_test_timeout_ms_(options.per_test_timeout_ms),
storage_path_base_(options.storage_path) {
validator_runner_impl_ = std::make_unique<ValidatorRunnerImpl>(
CreateModelLoaderPath(options), options.storage_path,
options.data_directory_path, options.per_test_timeout_ms,
options.custom_input_data.empty()
? nullptr
: std::make_unique<CustomValidationEmbedder>(
options.custom_input_batch_size, options.custom_input_data,
options.error_reporter),
options.error_reporter, options.nnapi_sl, options.gpu_plugin_handle,
options.validation_entrypoint_name, options.benchmark_result_evaluator);
}
MinibenchmarkStatus BlockingValidatorRunner::Init() {
return validator_runner_impl_->Init();
}
std::vector<FlatBufferBuilder> BlockingValidatorRunner::TriggerValidation(
const std::vector<const TFLiteSettings*>& for_settings) {
if (for_settings.empty()) {
return {};
}
// Create a unique storage_path.
std::string storage_path =
absl::StrCat(storage_path_base_, ".", GenerateRandomString());
TFLITE_LOG_PROD(TFLITE_LOG_INFO, "Validation storage path: %s",
storage_path.c_str());
std::vector<flatbuffers::FlatBufferBuilder> to_be_run;
std::vector<TFLiteSettingsT> for_settings_obj;
for_settings_obj.reserve(for_settings.size());
for (auto settings : for_settings) {
TFLiteSettingsT tflite_settings;
settings->UnPackTo(&tflite_settings);
flatbuffers::FlatBufferBuilder copy;
copy.Finish(CreateTFLiteSettings(copy, &tflite_settings));
to_be_run.emplace_back(std::move(copy));
for_settings_obj.emplace_back(tflite_settings);
}
validator_runner_impl_->TriggerValidationAsync(std::move(to_be_run),
storage_path);
// The underlying process runner should ensure each test finishes on time or
// timed out. deadline_us is added here as an extra safety guard.
int64_t total_timeout_ms = per_test_timeout_ms_ * (1 + for_settings.size());
int64_t deadline_us = Validator::BootTimeMicros() + total_timeout_ms * 1000;
bool within_timeout = true;
// TODO(b/249274787): GetNumCompletedResults() loads the file from disk each
// time when called. We should find a way to optimize the FlatbufferStorage to
// reduce the I/O and remove the sleep().
while ((validator_runner_impl_->GetNumCompletedResults()) <
for_settings.size() &&
(within_timeout = Validator::BootTimeMicros() < deadline_us)) {
usleep(absl::ToInt64Microseconds(kWaitBetweenRefresh));
}
std::vector<FlatBufferBuilder> results =
validator_runner_impl_->GetCompletedResults();
if (!within_timeout) {
TFLITE_LOG_PROD(
TFLITE_LOG_WARNING,
"Validation timed out after %ld ms. Return before all tests finished.",
total_timeout_ms);
} else if (for_settings.size() != results.size()) {
TFLITE_LOG_PROD(TFLITE_LOG_WARNING,
"Validation completed.Started benchmarking for %d "
"TFLiteSettings, received %d results.",
for_settings.size(), results.size());
}
// If there are any for_settings missing from results, add an error event.
std::vector<TFLiteSettingsT> result_settings;
result_settings.reserve(results.size());
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
TFLiteSettingsT event_settings;
event->tflite_settings()->UnPackTo(&event_settings);
result_settings.emplace_back(std::move(event_settings));
}
for (auto& settings_obj : for_settings_obj) {
auto result_it =
std::find(result_settings.begin(), result_settings.end(), settings_obj);
if (result_it == result_settings.end()) {
FlatBufferBuilder fbb;
fbb.Finish(CreateBenchmarkEvent(
fbb, CreateTFLiteSettings(fbb, &settings_obj),
BenchmarkEventType_ERROR, /* result */ 0,
CreateBenchmarkError(fbb, BenchmarkStage_UNKNOWN,
/* exit_code */ 0, /* signal */ 0,
/* error_code */ 0,
/* mini_benchmark_error_code */
kMinibenchmarkCompletionEventMissing),
Validator::BootTimeMicros(), Validator::WallTimeMicros()));
results.emplace_back(std::move(fbb));
}
}
// Delete storage_file before returning. In case of test timeout, the child
// thread or process may create and continue to write to the storage_path. In
// this case we cannot delete the file.
(void)unlink(storage_path.c_str());
return results;
}
} // namespace acceleration
} // namespace tflite
@@ -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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BLOCKING_VALIDATOR_RUNNER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BLOCKING_VALIDATOR_RUNNER_H_
#include <memory>
#include <string>
#include <vector>
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/validator_runner_impl.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/validator_runner_options.h"
namespace tflite {
namespace acceleration {
// Class that runs mini-benchmark validation in a separate process and gives
// access to the results. This class provides a synchronous API for the callers
// to wait until the all the tests have finished.
//
// This class is thread-safe when using different storage_path_. When
// storage_path_ is shared between multiple runners, they will interfere with
// each other.
class BlockingValidatorRunner {
public:
explicit BlockingValidatorRunner(const ValidatorRunnerOptions& options);
MinibenchmarkStatus Init();
// Trigger the validation tests with for_settings, and return the test result.
// Each for_settings will have a corresponding result. The result is of schema
// BenchmarkEvent.
std::vector<flatbuffers::FlatBufferBuilder> TriggerValidation(
const std::vector<const TFLiteSettings*>& for_settings);
private:
int per_test_timeout_ms_ = 0;
const std::string storage_path_base_;
std::unique_ptr<ValidatorRunnerImpl> validator_runner_impl_;
};
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_BLOCKING_VALIDATOR_RUNNER_H_
@@ -0,0 +1,262 @@
/* 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/experimental/acceleration/mini_benchmark/blocking_validator_runner.h"
#include <fcntl.h>
#include <iostream>
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/str_cat.h"
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/benchmark_result_evaluator.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/embedded_mobilenet_model.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/embedded_mobilenet_validation_model.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/mini_benchmark_test_helper.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/validator_runner_options.h"
namespace tflite {
namespace acceleration {
namespace {
using ::flatbuffers::FlatBufferBuilder;
using ::flatbuffers::GetRoot;
class CustomResultEvaluator : public AbstractBenchmarkResultEvaluator {
public:
bool HasPassedAccuracyCheck(const BenchmarkResult& result) override {
return true;
}
};
class BlockingValidatorRunnerTest : public ::testing::Test {
protected:
void SetUp() override {
MiniBenchmarkTestHelper helper;
should_perform_test_ = helper.should_perform_test();
options_.model_path = helper.DumpToTempFile(
"mobilenet_quant_with_validation.tflite",
g_tflite_acceleration_embedded_mobilenet_validation_model,
g_tflite_acceleration_embedded_mobilenet_validation_model_len);
ASSERT_TRUE(!options_.model_path.empty());
options_.data_directory_path = ::testing::TempDir();
options_.storage_path =
absl::StrCat(::testing::TempDir(), "storage_path.fb.1");
options_.per_test_timeout_ms = 5000;
plain_model_path_ = MiniBenchmarkTestHelper::DumpToTempFile(
"mobilenet_quant.tflite",
g_tflite_acceleration_embedded_mobilenet_model,
g_tflite_acceleration_embedded_mobilenet_model_len);
}
std::string plain_model_path_;
ValidatorRunnerOptions options_;
bool should_perform_test_ = true;
};
TEST_F(BlockingValidatorRunnerTest, SucceedWithEmbeddedValidation) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
#ifdef __ANDROID__
fbb.Finish(CreateTFLiteSettings(fbb, Delegate_GPU));
#else
fbb.Finish(CreateTFLiteSettings(fbb));
#endif // __ANDROID__
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
EXPECT_TRUE(event->result()->ok());
}
}
TEST_F(BlockingValidatorRunnerTest, SucceedWithFdCloexecEmbeddedValidation) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
options_.model_fd = open(options_.model_path.c_str(), O_RDONLY | O_CLOEXEC);
ASSERT_GE(options_.model_fd, 0);
struct stat stat_buf = {0};
ASSERT_EQ(fstat(options_.model_fd, &stat_buf), 0);
options_.model_size = stat_buf.st_size;
options_.model_offset = 0;
options_.model_path.clear();
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
#ifdef __ANDROID__
fbb.Finish(CreateTFLiteSettings(fbb, Delegate_GPU));
#else
fbb.Finish(CreateTFLiteSettings(fbb));
#endif // __ANDROID__
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
EXPECT_TRUE(event->result()->ok());
}
}
TEST_F(BlockingValidatorRunnerTest, SucceedWithBufferModel) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
options_.model_buffer =
g_tflite_acceleration_embedded_mobilenet_validation_model;
options_.model_size =
g_tflite_acceleration_embedded_mobilenet_validation_model_len;
options_.model_path.clear();
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
fbb.Finish(CreateTFLiteSettings(fbb));
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
EXPECT_TRUE(event->result()->ok());
}
}
TEST_F(BlockingValidatorRunnerTest, SucceedWithFdModelCustomValidation) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
options_.model_path.clear();
options_.model_fd = open(plain_model_path_.c_str(), O_RDONLY);
ASSERT_GE(options_.model_fd, 0);
struct stat stat_buf = {0};
ASSERT_EQ(fstat(options_.model_fd, &stat_buf), 0);
options_.model_size = stat_buf.st_size;
options_.model_offset = 0;
options_.custom_input_batch_size = 3;
options_.custom_input_data = {std::vector<uint8_t>(3 * 224 * 224 * 3, 1)};
CustomResultEvaluator evaluator;
options_.benchmark_result_evaluator = &evaluator;
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
#ifdef __ANDROID__
fbb.Finish(CreateTFLiteSettings(fbb, Delegate_XNNPACK));
#else
fbb.Finish(CreateTFLiteSettings(fbb));
#endif // __ANDROID__
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
}
}
TEST_F(BlockingValidatorRunnerTest, SucceedWhenRunningMultipleTimes) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
fbb.Finish(CreateTFLiteSettings(fbb));
int num_runs = 3;
for (int i = 0; i < num_runs; i++) {
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer()),
flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
EXPECT_TRUE(event->result()->ok());
}
}
}
TEST_F(BlockingValidatorRunnerTest, ReturnErrorWhenTimedOut) {
if (!should_perform_test_) {
std::cerr << "Skipping test";
return;
}
options_.per_test_timeout_ms = 50;
BlockingValidatorRunner runner(options_);
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
FlatBufferBuilder fbb;
fbb.Finish(CreateTFLiteSettings(fbb));
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
EXPECT_THAT(results, testing::SizeIs(1));
for (auto& result : results) {
const BenchmarkEvent* event =
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
EXPECT_EQ(event->event_type(), BenchmarkEventType_ERROR);
ASSERT_NE(nullptr, event->error());
// The timeout can result in two different behaviors:
// 1. The popen() subprocess got killed by the detached thread because the
// timeout has reached, and the thread wrote error code
// kMinibenchmarkCommandTimedOut, or
// 2. The thread didn't respond the main process in time, and the main
// process returned after the timeout, with error code
// kMinibenchmarkCompletionEventMissing.
EXPECT_THAT(event->error()->mini_benchmark_error_code(),
testing::AnyOf(kMinibenchmarkCommandTimedOut,
kMinibenchmarkCompletionEventMissing));
}
}
} // namespace
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,262 @@
# Copyright 2021 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.
# ==============================================================================
"""Helpers for mini-benchmark build rules."""
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load(
"//tensorflow:tensorflow.bzl",
"clean_dep",
)
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "add_suffix")
load("//tensorflow/lite/experimental/acceleration/mini_benchmark:special_rules.bzl", "libjpeg_handle_deps")
def _concat(lists):
"""Concatenate a list of lists, without requiring the inner lists to be iterable.
This allows the inner lists to be obtained by calls to select().
"""
result = []
for selected_list in lists:
result = result + selected_list
return result
def embedded_binary(name, binary, array_variable_name, testonly = False, exec_properties = None):
"""Create a cc_library that embeds a binary as constant data.
Args:
name: name for the generated cc_library target, and the base name for
generated header file
binary: binary file to be embedded
array_variable_name: name of the constant array for the data.
"""
cc_name = "%s.cc" % name
h_name = "%s.h" % name
native.genrule(
name = name + "_src",
srcs = [binary],
outs = [
cc_name,
h_name,
],
cmd = """
$(location //tensorflow/lite/experimental/acceleration/compatibility:convert_binary_to_cc_source) \
--input_binary_file $(location %s) \
--output_header_file $(location :%s) \
--output_source_file $(location :%s) \
--array_variable_name %s
""" % (binary, h_name, cc_name, array_variable_name),
tools = ["//tensorflow/lite/experimental/acceleration/compatibility:convert_binary_to_cc_source"],
testonly = testonly,
)
cc_library(
name = name,
srcs = [cc_name],
hdrs = [h_name],
testonly = testonly,
exec_properties = exec_properties,
)
def validation_model(
name,
main_model,
metrics_model,
jpegs,
scale = "",
zeropoint = "",
use_ondevice_cpu_for_golden = False,
testonly = 0):
"""Create a tflite model with embedded validation.
Args:
name: name of the target. A file called 'name'.tflite is generated
main_model: main tflite model target
metrics_model: metrics tflite model target
jpegs: target with 1 or more jpeg files
scale: the input (de)quantization scale parameter for float models
zeropoint: the input (de)quantization zeropoint parameter for float models
use_ondevice_cpu_for_golden: use on-device CPU for golden data (rather than embedding)
testonly: whether target is marked testonly
"""
if use_ondevice_cpu_for_golden:
use_ondevice_cpu_for_golden = "true"
else:
use_ondevice_cpu_for_golden = "false"
scale_arg = ""
zeropoint_arg = ""
if scale:
scale_arg = "--scale=" + scale
zeropoint_arg = "--zero_point=" + zeropoint
schema_location = "//tensorflow/compiler/mlir/lite/schema:schema.fbs"
native.genrule(
name = name,
testonly = testonly,
srcs = [
main_model,
jpegs,
schema_location,
metrics_model,
],
outs = [name + ".tflite"],
cmd = """
JPEGS='$(locations %s)'
JPEGS=$${JPEGS// /,}
$(location //tensorflow/lite/experimental/acceleration/mini_benchmark/model_modifier:embedder_cmdline) \
--schema=$(location %s) \
--main_model=$(location %s) \
--metrics_model=$(location %s) \
%s %s \
--jpegs=$$JPEGS \
--use_ondevice_cpu_for_golden=%s \
--output='$(@D)/%s.tflite.tmp'
$(location //tensorflow/lite/experimental/acceleration/mini_benchmark:copy_associated_files) \
'$(@D)/%s.tflite.tmp' \
$(location %s) \
$(location %s.tflite)
rm '$(@D)/%s.tflite.tmp'
""" % (
jpegs,
schema_location,
main_model,
metrics_model,
scale_arg,
zeropoint_arg,
use_ondevice_cpu_for_golden,
name,
name,
main_model,
name,
name,
),
tools = [
"//tensorflow/lite/experimental/acceleration/mini_benchmark/model_modifier:embedder_cmdline",
"//tensorflow/lite/experimental/acceleration/mini_benchmark:copy_associated_files",
],
)
def validation_test(name, validation_model, tags = [], copts = [], deps = []):
"""Create a test binary for the given model with validation.
Args:
name: name of the target.
validation_model: tflite model with validation target.
tags: to be passed to cc_test.
copts: to be passed to cc_test.
deps: to be passed to cc_test.
"""
embed_name = name + "_embed_model"
embedded_binary(
embed_name,
binary = validation_model,
array_variable_name = "g_tflite_acceleration_" + name + "_model",
)
cc_test(
name = name,
srcs = ["//tensorflow/lite/experimental/acceleration/mini_benchmark:model_validation_test.cc"],
tags = tags + ["no_mac", "no_windows", "tflite_not_portable_ios"],
copts = copts + [
"-DTENSORFLOW_ACCELERATION_MODEL_DATA_VARIABLE=\"g_tflite_acceleration_%s_model\"" % name,
"-DTENSORFLOW_ACCELERATION_MODEL_LENGTH_VARIABLE=\"g_tflite_acceleration_%s_model_len\"" % name,
],
deps = deps + [
embed_name,
"@com_google_googletest//:gtest_main",
"@flatbuffers",
"//tensorflow/lite/acceleration/configuration:configuration_fbs",
"//tensorflow/lite/acceleration/configuration:nnapi_plugin",
"//tensorflow/lite/experimental/acceleration/compatibility:android_info",
"//tensorflow/lite/experimental/acceleration/mini_benchmark:big_little_affinity",
"//tensorflow/lite/experimental/acceleration/mini_benchmark:status_codes",
"//tensorflow/lite/experimental/acceleration/mini_benchmark:validator",
"//tensorflow/lite/tools:model_loader",
] + select({
clean_dep("//tensorflow:android"): [
"//tensorflow/lite/acceleration/configuration:gpu_plugin",
],
"//conditions:default": [],
}) + libjpeg_handle_deps(),
linkstatic = 1,
)
def cc_library_with_forced_in_process_benchmark_variant(
name,
deps = [],
forced_in_process_deps = [],
in_process_deps = [],
non_in_process_deps_selects = [],
**kwargs):
"""Defines a cc_library that optionally forces benchmark runs in process.
This generates two cc_library target. The first one runs the benchmark in a
separate process on Android, while it runs the benchmark in process on all
other platforms. It doesn't have TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS
defined.
The second one, which has "_in_process" appended to the name, forces
benchmark runs in process on all platforms. It has
TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS defined.
The default option for MiniBenchmark is to run the benchmark in a separate
process on Android, as this is safer than running the benchmark in the app
process. However, forcing the benchmark to run in-process on Android allows
the benchmark to reuse the same TF Lite runtime that is initialized in the
application process. These two variants may use different dependencies.
For example, the in-process variant uses the statically linked libjpeg
handle, while the other variant uses the dynamically linked libjpeg handle
on Android to minimize binary size.
This build rule ensures that the dependencies listed in
"forced_in_process_deps" are added only when
TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS is defined, that the dependencies
listed in "non_in_process_deps_selects" are added only when
TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS is NOT defined, and that
TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS is defined automatically when
using the "_in_process" target.
Args:
name: determines the name used for the generated cc_library targets.
forced_in_process_deps: dependencies that will be enabled only when the
benchmark is forced to run in-process on all platforms. This should be
used for dependencies arising from code inside
'#ifdef TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS'.
deps: dependencies that will be unconditionally included in the deps of
the generated cc_library targets.
in_process_deps: dependencies on rules that are themselves defined using
'cc_library_with_forced_in_process_benchmark_variant'. Must be
iterable, so cannot be computed by calling 'select'.
non_in_process_deps_selects: A list of dictionaries that will be
converted to dependencies with select on rules. The dependencies will
be enabled only when the benchmark runs in a separate process on
Android. This should be used for dependencies arising from code inside
'#ifndef TFLITE_ACCELERATION_BENCHMARK_IN_PROCESS'.
**kwargs:
Additional cc_library parameters.
"""
cc_library(
name = name,
deps = deps + in_process_deps + _concat([select(map) for map in non_in_process_deps_selects]) + [
"//tensorflow/lite/experimental/acceleration/mini_benchmark:tflite_acceleration_in_process_default",
],
**kwargs
)
in_process_deps_renamed = [add_suffix(in_process_dep, "_in_process") for in_process_dep in in_process_deps]
cc_library(
name = name + "_in_process",
deps = deps + in_process_deps_renamed + forced_in_process_deps + [
"//tensorflow/lite/experimental/acceleration/mini_benchmark:tflite_acceleration_in_process_enable",
],
**kwargs
)
@@ -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.
# ==============================================================================
load("//tensorflow/lite:build_def.bzl", "tflite_cc_library_with_c_headers_test")
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "cc_library_with_tflite_with_c_headers_test")
load("//tensorflow/lite/experimental/acceleration/mini_benchmark:special_rules.bzl", "minibenchmark_visibility_allowlist")
default_visibility_group = [
"//tensorflow/lite/experimental/acceleration/mini_benchmark:__subpackages__",
] + minibenchmark_visibility_allowlist()
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = default_visibility_group,
licenses = ["notice"],
)
# This target runs MiniBenchmark in a separate process on Android, while it runs MiniBenchmark
# in-process on all other platforms.
cc_library_with_tflite_with_c_headers_test(
name = "c_api",
hdrs = ["c_api.h"],
deps = ["//tensorflow/lite/core/experimental/acceleration/mini_benchmark/c:c_api"],
)
# This target forces MiniBenchmark to run in-process on all platforms including Android.
tflite_cc_library_with_c_headers_test(
name = "c_api_in_process",
hdrs = ["c_api.h"],
deps = [
"//tensorflow/lite/core/experimental/acceleration/mini_benchmark/c:c_api_in_process",
],
)
@@ -0,0 +1,23 @@
/* 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_C_C_API_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_C_C_API_H_
/// For documentation, see
/// third_party/tensorflow/lite/core/experimental/acceleration/mini_benchmark/c/c_api.h
#include "tensorflow/lite/core/experimental/acceleration/mini_benchmark/c/c_api.h" // IWYU pragma: export
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_C_C_API_H_
@@ -0,0 +1,288 @@
/* 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 <stddef.h>
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/context_util.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/call_register.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/op_macros.h"
namespace tflite {
namespace acceleration {
namespace ops {
namespace call_kernel {
namespace {
bool MatchDimensionsExceptBatchSize(TfLiteTensor* a, TfLiteTensor* b) {
if (a->dims->size != b->dims->size) {
return false;
}
// First dimension is the batch size.
for (int i = 1; i < a->dims->size; ++i) {
if (a->dims->data[i] != b->dims->data[i]) {
return false;
}
}
return true;
}
// Verifies that number, shape and type of inputs match for subgraph and CALL
// node. If shape of inputs is unset for subgraph, it is inferred.
TfLiteStatus ValidateAndResizeInputsIfNeeded(TfLiteContext* context,
TfLiteNode* node,
Subgraph* subgraph,
int loop_count) {
// Match number of inputs for subgraph and CALL node.
TF_LITE_ENSURE_EQ(context, subgraph->inputs().size(), node->inputs->size);
for (int i = 0; i < node->inputs->size; ++i) {
TfLiteTensor* node_input = context->tensors + node->inputs->data[i];
TfLiteTensor* subgraph_input = subgraph->tensor(subgraph->inputs()[i]);
// Match input types.
TF_LITE_ENSURE_TYPES_EQ(context, node_input->type, subgraph_input->type);
TF_LITE_ENSURE_MSG(
context, node_input->dims->size > 0,
"Dimensions of all of call node's inputs should be non-zero.");
// Ensure batch size of CALL node's input is same as loop size.
TF_LITE_ENSURE_EQ(context, node_input->dims->data[0], loop_count);
if (!subgraph_input->dims->size) {
// Subgraph input dimensions unset and will be inferred.
std::vector<int> new_dims;
new_dims.reserve(node_input->dims->size);
new_dims.push_back(1); // Batch size is fixed as 1 for subgraph.
new_dims.insert(new_dims.end(), node_input->dims->data + 1,
node_input->dims->data + node_input->dims->size);
subgraph->ResizeInputTensor(subgraph->inputs()[i], new_dims);
} else {
// Dimensions already set for subgraph, match input dimensions.
if (!MatchDimensionsExceptBatchSize(node_input, subgraph_input)) {
std::stringstream node_input_dims, subgraph_input_dims;
for (int i = 0; i < node_input->dims->size; i++) {
node_input_dims << node_input->dims->data[i] << " ";
subgraph_input_dims << subgraph_input->dims->data[i] << " ";
}
TF_LITE_KERNEL_LOG(
context,
"%s:%d: All dimensions except the batch size should match for call "
"node and the subgraph to invoke (input tensor %s[ %s], subgraph "
"tensor %s[ %s])",
__FILE__, __LINE__, node_input->name, node_input_dims.str().c_str(),
subgraph_input->name, subgraph_input_dims.str().c_str());
return kTfLiteError;
}
// Batch size of subgraph's input should be 1.
TF_LITE_ENSURE_EQ(context, subgraph_input->dims->data[0], 1);
}
}
return kTfLiteOk;
}
// Verifies that number of outputs match for subgraph and CALL node. Infer the
// shape and type of outputs for CALL node and resize accordingly.
TfLiteStatus ValidateAndResizeOutputs(TfLiteContext* context, TfLiteNode* node,
Subgraph* subgraph, int loop_count) {
// Match number of outputs for subgraph and CALL node.
TF_LITE_ENSURE_EQ(context, subgraph->outputs().size(), node->outputs->size);
// Infer output shape for the CALL node.
for (int i = 0; i < node->outputs->size; ++i) {
const TfLiteTensor* subgraph_output =
subgraph->tensor(subgraph->outputs()[i]);
TfLiteTensor* node_output = context->tensors + node->outputs->data[i];
TF_LITE_ASSERT(subgraph_output->dims->size > 0);
TfLiteIntArray* new_dims_array = TfLiteIntArrayCopy(subgraph_output->dims);
// Batch size is fixed as `loop_count` for CALL node.
new_dims_array->data[0] = loop_count;
TF_LITE_ENSURE_OK(
context, context->ResizeTensor(context, node_output, new_dims_array));
node_output->type = subgraph_output->type;
}
return kTfLiteOk;
}
// Copy input tensor data from CALL node's inputs to subgraph's inputs.
TfLiteStatus CopyInputTensorsData(TfLiteContext* context, TfLiteNode* node,
Subgraph* dst_subgraph, int loop_index,
int loop_count) {
const std::vector<int>& dst_tensor_indices = dst_subgraph->inputs();
TF_LITE_ENSURE_EQ(context, node->inputs->size, dst_tensor_indices.size());
for (int i = 0; i < dst_tensor_indices.size(); ++i) {
TfLiteTensor* src_tensor = context->tensors + node->inputs->data[i];
TfLiteTensor* dst_tensor = dst_subgraph->tensor(dst_tensor_indices[i]);
size_t offset = src_tensor->bytes / loop_count * loop_index;
TF_LITE_ENSURE_EQ(context, src_tensor->bytes / loop_count,
dst_tensor->bytes);
memcpy(dst_tensor->data.raw, src_tensor->data.raw + offset,
src_tensor->bytes / loop_count);
}
return kTfLiteOk;
}
// Copy tensor data from subgraph's outputs to CALL node's outputs.
TfLiteStatus CopyOutputTensorsData(TfLiteContext* context,
Subgraph* src_subgraph, TfLiteNode* node,
int loop_index, int loop_count) {
const std::vector<int>& src_tensor_indices = src_subgraph->outputs();
TF_LITE_ENSURE_EQ(context, src_tensor_indices.size(), node->outputs->size);
for (int i = 0; i < src_tensor_indices.size(); ++i) {
const TfLiteTensor* src_tensor =
src_subgraph->tensor(src_tensor_indices[i]);
TfLiteTensor* dst_tensor = context->tensors + node->outputs->data[i];
size_t offset = dst_tensor->bytes / loop_count * loop_index;
TF_LITE_ENSURE_EQ(context, src_tensor->bytes,
dst_tensor->bytes / loop_count);
memcpy(dst_tensor->data.raw + offset, src_tensor->data.raw,
src_tensor->bytes);
}
return kTfLiteOk;
}
} // namespace
struct OpData {
// Index of the subgraph that needs to be invoked.
// Subgraph should have batch size 1.
int subgraph_index;
// The number of times the CALL op should call the subgraph.
// The inputs to the call op are expected to have this value as their batch
// size.
int loop_count;
};
void* Init(TfLiteContext* context, const char* buffer, size_t length) {
if (!buffer) {
return nullptr;
}
auto* op_data = new OpData;
const uint8_t* buffer_fixed_width = reinterpret_cast<const uint8_t*>(buffer);
const flexbuffers::Map& map =
flexbuffers::GetRoot(buffer_fixed_width, length).AsMap();
// Note: The values below will be set as 0 if the parsing fails or if the
// values have been unset.
op_data->subgraph_index = map["subgraph_index"].AsInt32();
op_data->loop_count = map["loop_count"].AsInt32();
return op_data;
}
void Free(TfLiteContext* context, void* buffer) {
delete reinterpret_cast<OpData*>(buffer);
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
TF_LITE_ENSURE(context, op_data);
// Check subgraph index and get subgraph.
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
TF_LITE_ENSURE_MSG(context,
(op_data->subgraph_index < subgraphs->size()) &&
(op_data->subgraph_index >= 0),
"Index of subgraph to be invoked is invalid.");
Subgraph* subgraph = (*subgraphs)[op_data->subgraph_index].get();
TF_LITE_ENSURE_MSG(
context, subgraph != this_subgraph,
"Subgraph to invoke must be different from the invoking graph.");
int loop_count = op_data->loop_count;
TF_LITE_ENSURE_MSG(context, loop_count >= 0, "Loop count must be positive. ");
// Check if the inputs and outputs of the CALL node and the subgraph have
// proper shapes and types.
TF_LITE_ENSURE_OK(context, ValidateAndResizeInputsIfNeeded(
context, node, subgraph, loop_count));
TF_LITE_ENSURE_OK(context, subgraph->AllocateTensors());
TF_LITE_ENSURE_OK(
context, ValidateAndResizeOutputs(context, node, subgraph, loop_count));
// Since delegates don't support acceleration of models with dynamic outputs,
// this op doesn't support it either.
TF_LITE_ENSURE(context, !subgraph->HasDynamicTensors());
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
const OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
Subgraph* subgraph = (*subgraphs)[op_data->subgraph_index].get();
// The following graph illustrates the current implementation.
//
// This Subgraph Subgraph to invoke
// +-----------+ (1) +------------+
// | CALL |-------->| SUBGRAPH |
// | INPUT | | INPUT |
// +-----------+ +------------+
// |
// (3) | (2)
// v
// +-----------+ +------------+
// | CALL |<-------| SUBGRAPH |
// | OUTPUT | | OUTPUT |
// +-----------+ +------------+
// For every ith loop iteration:
// (1) Copy the ith input of CALL op to the inputs of subgraph.
// (2) Invoke subgraph.
// (3) Copy the outputs of subgraph to the ith output of CALL op.
//
// Requires the subgraph to have a batch size of 1.
// Requires the CALL node's inputs and outputs to have a batch size equal to
// `loop_count`.
//
//
// TODO(b/120234921): Optimize and avoid copying tensors between subgraphs.
for (int loop_index = 0; loop_index < op_data->loop_count; loop_index++) {
// Copy inputs needed for this iteration.
TF_LITE_ENSURE_OK(context,
CopyInputTensorsData(context, node, subgraph, loop_index,
op_data->loop_count));
// Invoke subgraph for this iteration.
TF_LITE_ENSURE_OK(context, subgraph->Invoke());
for (int tensor_index : subgraph->outputs()) {
subgraph->EnsureTensorDataIsReadable(tensor_index);
}
TF_LITE_ENSURE_OK(context,
CopyOutputTensorsData(context, subgraph, node, loop_index,
op_data->loop_count));
}
return kTfLiteOk;
}
} // namespace call_kernel
TfLiteRegistration* Register_CALL() {
static TfLiteRegistration r = {call_kernel::Init, call_kernel::Free,
call_kernel::Prepare, call_kernel::Eval};
return &r;
}
} // namespace ops
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,37 @@
/* 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CALL_REGISTER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CALL_REGISTER_H_
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace acceleration {
namespace ops {
// CALL op can be used to invoke a subgraph a given number of times.
TfLiteRegistration* Register_CALL();
typedef struct {
// Index of the subgraph that needs to be invoked.
// Subgraph should have batch size 1.
int subgraph_index;
// The number of times the CALL op should call the subgraph.
// The inputs to the call op are expected to have this value as their batch
// size.
int loop_count;
} TfLiteCallParams;
} // namespace ops
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CALL_REGISTER_H_
@@ -0,0 +1,420 @@
/* 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 <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/kernels/builtin_op_kernels.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/call_register.h"
#include "tensorflow/lite/interpreter_test_util.h"
#include "tensorflow/lite/kernels/subgraph_test_util.h"
#include "tensorflow/lite/testing/util.h"
namespace tflite {
namespace {
class CallTest : public subgraph_test_util::ControlFlowOpTest {
public:
CallTest() { interpreter_ = std::make_unique<Interpreter>(&error_reporter_); }
~CallTest() override = default;
void SetupTensor(Subgraph* subgraph, int tensor_index, TfLiteType type) {
ASSERT_EQ(subgraph->SetTensorParametersReadWrite(tensor_index, type, "", 0,
nullptr, {}, false),
kTfLiteOk);
}
void BuildCallSubgraph(Subgraph* subgraph, std::vector<uint8_t> params_buffer,
std::vector<int> inputs, std::vector<int> outputs,
int expected_node_index, bool single_node_subgraph) {
if (single_node_subgraph) {
int first_new_tensor_index;
ASSERT_EQ(subgraph->AddTensors(inputs.size() + outputs.size(),
&first_new_tensor_index),
kTfLiteOk);
ASSERT_EQ(first_new_tensor_index, 0);
ASSERT_EQ(subgraph->SetInputs(inputs), kTfLiteOk);
ASSERT_EQ(subgraph->SetOutputs(outputs), kTfLiteOk);
}
for (const int& idx : inputs) {
SetupTensor(subgraph, idx, kTfLiteInt32);
}
for (const int& idx : outputs) {
SetupTensor(subgraph, idx, kTfLiteInt32);
}
int node_index;
subgraph->AddNodeWithParameters(
inputs, outputs, {},
reinterpret_cast<const char*>(params_buffer.data()),
params_buffer.size(), nullptr, acceleration::ops::Register_CALL(),
&node_index);
ASSERT_EQ(node_index, expected_node_index);
}
void BuildCallSubgraph(Subgraph* subgraph, int index, int loop_count,
std::vector<int> inputs, std::vector<int> outputs,
int expected_node_index = 0,
bool single_node_subgraph = true) {
flexbuffers::Builder fbb;
fbb.Map([&] {
fbb.Int("subgraph_index", index);
fbb.Int("loop_count", loop_count);
});
fbb.Finish();
BuildCallSubgraph(subgraph, fbb.GetBuffer(), inputs, outputs,
expected_node_index, single_node_subgraph);
}
void BuildGraphWithMultipleOutputs(Subgraph* subgraph) {
const int kInput1 = 0;
const int kInput2 = 1;
const int kMulOutput = 2;
const int kAddOutput = 3;
const int kTensorCount = 4;
// kInput1(0) --> +---+
// |MUL| --> kOutput(2)
// kInput2(1) --> +---+
//
// kInput1(0) --> +---+
// |ADD| --> kOutput(3)
// kInput2(1) --> +---+
int first_new_tensor_index;
ASSERT_EQ(subgraph->AddTensors(kTensorCount, &first_new_tensor_index),
kTfLiteOk);
ASSERT_EQ(first_new_tensor_index, 0);
ASSERT_EQ(subgraph->SetInputs({kInput1, kInput2}), kTfLiteOk);
ASSERT_EQ(subgraph->SetOutputs({kMulOutput, kAddOutput}), kTfLiteOk);
SetupTensor(subgraph, kInput1, kTfLiteInt32);
SetupTensor(subgraph, kInput2, kTfLiteInt32);
SetupTensor(subgraph, kMulOutput, kTfLiteInt32);
SetupTensor(subgraph, kAddOutput, kTfLiteInt32);
TfLiteMulParams* params_mul =
reinterpret_cast<TfLiteMulParams*>(malloc(sizeof(TfLiteMulParams)));
params_mul->activation = kTfLiteActNone;
int node_index;
subgraph->AddNodeWithParameters(
{kInput1, kInput2}, {kMulOutput}, {}, nullptr, 0, params_mul,
::tflite::ops::builtin::Register_MUL(), &node_index);
TfLiteAddParams* params_add =
reinterpret_cast<TfLiteAddParams*>(malloc(sizeof(TfLiteAddParams)));
params_add->activation = kTfLiteActNone;
subgraph->AddNodeWithParameters(
{kInput1, kInput2}, {kAddOutput}, {}, nullptr, 0, params_add,
::tflite::ops::builtin::Register_ADD(), &node_index);
}
void BuildMultiNodeGraph(Subgraph* this_subgraph) {
// kIn1(0)----------------
// |
// | +----+
// +---+ -------->| | +---+
// kIn2(1)--> |PAD|-->kOut1(4)--->|CALL|-->kOut2(5)-->|MUL|-->kOut3(6)
// kIn3(2)--> | | | | | |
// +---+ +----+ ---->| |
// | +---+
// |
// |
// kIn4(3)----------------------------------------
const int kInput1 = 0, kInput2 = 1, kInput3 = 2, kInput4 = 3;
const int kOutput1 = 4, kOutput2 = 5, kOutput3 = 6;
const int kTensorCount = 7;
int first_new_tensor_index;
ASSERT_EQ(this_subgraph->AddTensors(kTensorCount, &first_new_tensor_index),
kTfLiteOk);
ASSERT_EQ(first_new_tensor_index, 0);
std::vector<int> inputs = {kInput1, kInput2, kInput3, kInput4};
std::vector<int> outputs = {kOutput3};
ASSERT_EQ(this_subgraph->SetInputs(inputs), kTfLiteOk);
ASSERT_EQ(this_subgraph->SetOutputs({kOutput3}), kTfLiteOk);
for (int idx = 0; idx < kTensorCount; ++idx) {
SetupTensor(this_subgraph, idx, kTfLiteInt32);
}
int expected_node_index = 0, node_index;
// Node 1: Pad op.
auto* pad_reg = ops::builtin::Register_PAD();
pad_reg->builtin_code = kTfLiteBuiltinPad;
this_subgraph->AddNodeWithParameters(
{kInput2, kInput3}, {kOutput1}, {}, nullptr, 0,
reinterpret_cast<TfLitePadParams*>(malloc(sizeof(TfLitePadParams))),
pad_reg, &node_index);
ASSERT_EQ(node_index, expected_node_index++);
// Node 2: Call op, calls subgraph that contains Add op.
AddSubgraphs(1);
const int kLoopCount = 1;
const int kSubgraphIndex = 1;
builder_->BuildAddSubgraph(interpreter_->subgraph(1));
CallTest::BuildCallSubgraph(this_subgraph, kSubgraphIndex, kLoopCount,
{kInput1, kOutput1}, {kOutput2},
expected_node_index++, false);
// Node 3: Mul op.
TfLiteMulParams* mul_params =
reinterpret_cast<TfLiteMulParams*>(malloc(sizeof(TfLiteMulParams)));
mul_params->activation = kTfLiteActNone;
auto* mul_reg = ops::builtin::Register_MUL();
mul_reg->builtin_code = kTfLiteBuiltinMul;
this_subgraph->AddNodeWithParameters({kInput4, kOutput2}, {kOutput3}, {},
nullptr, 0, mul_params, mul_reg,
&node_index);
ASSERT_EQ(node_index, expected_node_index++);
}
TestErrorReporter error_reporter_;
};
/** Tests the happy path for `call` op. **/
TEST_F(CallTest, SubgraphMultipleInputsSingleOutput) {
std::vector<std::vector<int>> test_shapes = {
{3, 2}, {2, 3}, {2, 1, 3}, {1, 3, 1, 2}};
// Will loop over and will be fed to the subgraph as {1,2}, {1,3}, {1,1,3},
// {1,3,1,2}.
for (size_t i = 0; i < test_shapes.size(); ++i) {
interpreter_ = std::make_unique<Interpreter>();
AddSubgraphs(1);
int loop_count = test_shapes[i][0];
builder_->BuildMulSubgraph(interpreter_->subgraph(1));
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1,
loop_count, {0, 1}, {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], test_shapes[i]);
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], test_shapes[i]);
ASSERT_EQ(interpreter_->subgraph(1)->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[0]), {-1, 2, -3, 4, -5, 6});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {-1, 2, -3, 4, -5, 6});
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output, test_shapes[i],
{1, 4, 9, 16, 25, 36});
}
}
TEST_F(CallTest, ShouldBeANoOpWhenLoopCountIsZero) {
AddSubgraphs(1);
int loop_count = 0;
builder_->BuildMulSubgraph(interpreter_->subgraph(1));
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, loop_count,
{0, 1}, {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {0, 3});
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {0, 3});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output, {0, 3}, {});
}
TEST_F(CallTest, SubgraphWithFixedInputShapes) {
AddSubgraphs(1);
const int kLoopCount = 2;
const int kBatchSizeSubgraph = 1;
const int kFixedInputLen = 3;
const std::vector<int> kCallOpInputShape = {kLoopCount, kFixedInputLen};
const std::vector<int> kSubgraphInputShape = {kBatchSizeSubgraph,
kFixedInputLen};
Subgraph* subgraph = interpreter_->subgraph(1);
builder_->BuildMulSubgraph(subgraph);
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, kLoopCount,
{0, 1}, {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], kCallOpInputShape);
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], kCallOpInputShape);
subgraph->ResizeInputTensor(subgraph->inputs()[0], kSubgraphInputShape);
subgraph->ResizeInputTensor(subgraph->inputs()[1], kSubgraphInputShape);
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[0]), {-1, 2, -3, 4, -5, 6});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {-1, 2, -3, 4, -5, 6});
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output, kCallOpInputShape,
{1, 4, 9, 16, 25, 36});
}
TEST_F(CallTest, SubgraphWithMultipleInputsAndOutputs) {
std::vector<std::vector<int>> test_shapes = {
{3, 2, 1}, {1, 2, 3}, {2, 1, 3}, {2, 3, 1, 1}, {2, 3}};
for (size_t i = 0; i < test_shapes.size(); ++i) {
interpreter_ = std::make_unique<Interpreter>();
AddSubgraphs(1);
int loop_count = test_shapes[i][0];
CallTest::BuildGraphWithMultipleOutputs(interpreter_->subgraph(1));
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1,
loop_count, {0, 1}, {2, 3});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], test_shapes[i]);
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], test_shapes[i]);
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[0]), {-1, 2, -3, 4, -5, 6});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {-1, 2, -3, 4, -5, 6});
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
TfLiteTensor* output_mul = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output_mul, test_shapes[i],
{1, 4, 9, 16, 25, 36});
TfLiteTensor* output_add = interpreter_->tensor(interpreter_->outputs()[1]);
subgraph_test_util::CheckIntTensor(output_add, test_shapes[i],
{-2, 4, -6, 8, -10, 12});
}
}
TEST_F(CallTest, ShouldHandleInvalidParamsAndSetToDefault) {
flexbuffers::Builder fbb;
fbb.Vector([&]() {
fbb.String("hi");
fbb.String("hello");
});
fbb.Finish();
AddSubgraphs(1);
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(),
fbb.GetBuffer(), {0}, {1}, 0, true);
const int kNodeIndex = 0;
const TfLiteNode* call_node = &interpreter_->primary_subgraph()
.nodes_and_registration()[kNodeIndex]
.first;
tflite::acceleration::ops::TfLiteCallParams* op_data =
reinterpret_cast<tflite::acceleration::ops::TfLiteCallParams*>(
call_node->user_data);
EXPECT_EQ(op_data->subgraph_index, 0);
EXPECT_EQ(op_data->loop_count, 0);
}
TEST_F(CallTest, MultiNodeGraph) {
CallTest::BuildMultiNodeGraph(&interpreter_->primary_subgraph());
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1, 4, 4, 1});
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {1, 2, 2, 1});
interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {4, 2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[3], {1, 4, 4, 1});
ASSERT_EQ(interpreter_->subgraph(1)->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[0]), std::vector<int>(16, 1));
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {1, 2, 3, 4});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[2]),
{0, 0, 1, 1, 1, 1, 0, 0});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[3]), std::vector<int>(16, 2));
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(
output, {1, 4, 4, 1}, {2, 2, 2, 2, 2, 4, 6, 2, 2, 8, 10, 2, 2, 2, 2, 2});
}
// Note: For the tests below the error messages returned by the error reporter
// are of the following format:
// "<filename>:<line number> <error message>. Node <number name> failed to
// prepare.\n"
// It's sufficient to test whether the string returned by error reporter
// contains the expected error message.
TEST_F(CallTest, ShouldFailWith0DInputs) {
AddSubgraphs(1);
int loop_count = 5;
builder_->BuildMulSubgraph(interpreter_->subgraph(1));
interpreter_->subgraph(1)->ResizeInputTensor(0, {});
interpreter_->subgraph(1)->ResizeInputTensor(1, {});
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, loop_count,
{0, 1}, {2});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
EXPECT_THAT(
error_reporter_.error_messages(),
testing::HasSubstr(
"Dimensions of all of call node's inputs should be non-zero."));
}
TEST_F(CallTest, ShouldFailWhenLoopCountDoesNotMatchBatchSize) {
AddSubgraphs(1);
int loop_count = 7;
builder_->BuildMulSubgraph(interpreter_->subgraph(1));
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, loop_count,
{0, 1}, {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {5, 3});
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {5, 3});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
EXPECT_THAT(
error_reporter_.error_messages(),
testing::HasSubstr("node_input->dims->data[0] != loop_count (5 != 7)"));
}
TEST_F(CallTest, ShouldFailForSubgraphWithIncompatibleInputShapes) {
AddSubgraphs(1);
const int kLoopCount = 5;
const int kBatchSizeSubgraph = 1;
std::vector<int> call_op_input = {kLoopCount, 3};
std::vector<int> subgraph_input = {kBatchSizeSubgraph, 7};
Subgraph* subgraph = interpreter_->subgraph(1);
builder_->BuildMulSubgraph(subgraph);
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, kLoopCount,
{0, 1}, {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], call_op_input);
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], call_op_input);
subgraph->ResizeInputTensor(subgraph->inputs()[0], subgraph_input);
subgraph->ResizeInputTensor(subgraph->inputs()[1], subgraph_input);
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
EXPECT_THAT(
error_reporter_.error_messages(),
testing::HasSubstr("All dimensions except the batch size should match "
"for call node and the subgraph to invoke"));
}
TEST_F(CallTest, ShouldFailWhenSubgraphIndexMatchesInvokedSubgraph) {
const int kPrimarySubgraphIndex = 0;
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(),
kPrimarySubgraphIndex, 1, {0}, {1});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
EXPECT_THAT(
error_reporter_.error_messages(),
testing::HasSubstr(
"Subgraph to invoke must be different from the invoking graph."));
}
TEST_F(CallTest, ShouldFailWithNegativeLoopCount) {
AddSubgraphs(1);
CallTest::BuildCallSubgraph(&interpreter_->primary_subgraph(), 1, -1, {0},
{1});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
EXPECT_THAT(error_reporter_.error_messages(),
testing::HasSubstr("Loop count must be positive."));
}
} // namespace
} // namespace tflite
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,32 @@
/* 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CONSTANTS_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CONSTANTS_H_
namespace tflite {
namespace acceleration {
// Model modification.
inline constexpr int kModelSchemaVersion = 3;
inline constexpr char kValidationGraphName[] = "VALIDATION:main";
// Process Execution.
// Number of bytes used to store process id.
inline constexpr int kPidBufferLength = 20;
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_CONSTANTS_H_
@@ -0,0 +1,62 @@
# Copyright 2021 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.
# ==============================================================================
"""TFLite model associated files copier.
A TFLite model with metadata may have 'associated files': extra data stored as a
zip file appended to the model. This script copies such files from one model to
another. This is used as part of constructing validation models.
See https://www.tensorflow.org/lite/convert/metadata for description of models
with metadata.
If there are no associated files in the provided file, the model is output
as-is.
"""
import argparse
import sys
import zipfile
parser = argparse.ArgumentParser(
description='Script to generate a metrics model for mobilenet v1.')
parser.add_argument('model', help='Input model filepath')
parser.add_argument(
'copy_associated_files_from',
help='Model with potential associated files filepath')
parser.add_argument('output', help='Output filepath')
def main(model_path, associated_files_path, output_path):
with open(model_path, 'rb') as input_file:
with open(output_path, 'wb') as output_file:
output_file.write(input_file.read())
if zipfile.is_zipfile(associated_files_path):
zip_src = zipfile.ZipFile(associated_files_path, 'r')
zip_tgt = zipfile.ZipFile(output_path, 'a')
for info in zip_src.infolist():
zip_tgt.writestr(info, zip_src.read(info))
if __name__ == '__main__':
flags, unparsed = parser.parse_known_args()
if unparsed:
parser.print_usage()
sys.stderr.write('\nGot the following unparsed args, %r please fix.\n' %
unparsed)
exit(1)
else:
main(flags.model, flags.copy_associated_files_from, flags.output)
exit(0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -0,0 +1,185 @@
/* Copyright 2021 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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_register.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_status.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/libjpeg_decoder.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/string_util.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
void* Init(TfLiteContext* context, const char* buffer, size_t length) {
if (!buffer) {
return nullptr;
}
#define RET_ENSURE(context, condition) \
do { \
if (!(condition)) { \
TF_LITE_KERNEL_LOG((context), "%s:%d %s was not true.", __FILE__, \
__LINE__, #condition); \
return nullptr; \
} \
} while (0)
const uint8_t* buffer_t = reinterpret_cast<const uint8_t*>(buffer);
const flexbuffers::Map m = flexbuffers::GetRoot(buffer_t, length).AsMap();
RET_ENSURE(context, m["height"].IsInt());
RET_ENSURE(context, m["width"].IsInt());
RET_ENSURE(context, m["num_images"].IsInt());
RET_ENSURE(context, m["channels"].IsInt());
OpData* op_data = new OpData();
op_data->height = m["height"].AsInt32();
op_data->width = m["width"].AsInt32();
op_data->num_images = m["num_images"].AsInt32();
op_data->channels = m["channels"].AsInt32();
return op_data;
#undef RET_ENSURE
}
void Free(TfLiteContext* context, void* buffer) {
delete reinterpret_cast<OpData*>(buffer);
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
TF_LITE_ENSURE(context, op_data);
TF_LITE_ENSURE(context, op_data->height > 0);
TF_LITE_ENSURE(context, op_data->width > 0);
TF_LITE_ENSURE(context, op_data->num_images > 0);
// TODO(b/172544567): Support grayscale images.
TF_LITE_ENSURE(context, op_data->channels == 3 || op_data->channels == 4);
TF_LITE_ENSURE_EQ(context, node->inputs->size, 1);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor* input_buffer;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, /*index=*/0, &input_buffer));
TfLiteTensor* output_tensor;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, /*index=*/0, &output_tensor));
TF_LITE_ENSURE_TYPES_EQ(context, input_buffer->type, kTfLiteString);
TF_LITE_ENSURE_TYPES_EQ(context, output_tensor->type, kTfLiteUInt8);
TF_LITE_ENSURE_EQ(context, NumDimensions(input_buffer), 1);
TF_LITE_ENSURE_EQ(context, input_buffer->dims->data[0], op_data->num_images);
// Resize output.
// Output shape is determined as {num_images, height, width, channels}.
TfLiteIntArray* new_dims = TfLiteIntArrayCreate(4);
new_dims->data[0] = op_data->num_images;
new_dims->data[1] = op_data->height;
new_dims->data[2] = op_data->width;
new_dims->data[3] = op_data->channels;
output_tensor->type = kTfLiteUInt8;
TF_LITE_ENSURE_OK(context,
context->ResizeTensor(context, output_tensor, new_dims));
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
// Decodes a batch of JPEG images.
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
const TfLiteTensor* input_buffer;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, /*index=*/0, &input_buffer));
TF_LITE_ENSURE(context, input_buffer);
TF_LITE_ENSURE(context, input_buffer->data.raw);
const int channels = op_data->channels;
// TODO(b/172544567): Support grayscale images.
const int decode_channels = 3;
TfLiteTensor* output_tensor;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, /*index=*/0, &output_tensor));
// kTfliteUInt8 corresponds to unsigned char as shown in
// "tensorflow/lite/portable_type_to_tflitetype.h".
unsigned char* output_arr = GetTensorData<unsigned char>(output_tensor);
Status decoder_status;
std::unique_ptr<LibjpegDecoder> decoder =
LibjpegDecoder::Create(decoder_status);
if (decoder_status.code != kTfLiteOk) {
TF_LITE_KERNEL_LOG(context, "%s", decoder_status.error_message.c_str());
return kTfLiteError;
}
const int kDecodedImageSize =
op_data->width * op_data->height * decode_channels;
const int kOutputImageSize = op_data->width * op_data->height * channels;
int output_array_offset = 0;
for (int img = 0; img < op_data->num_images; ++img) {
tflite::StringRef inputref =
tflite::GetString(input_buffer, /*string_index=*/img);
unsigned char* decoded = output_arr + output_array_offset;
Status decode_status = decoder->DecodeImage(
inputref, {op_data->height, op_data->width, decode_channels}, decoded,
kDecodedImageSize);
if (channels == 4) {
// Reorganize the decoded buffer from 3 channels to 4 channels.
size_t height = op_data->height;
size_t src_offset = kDecodedImageSize;
size_t dst_offset = kOutputImageSize;
while (height--) {
size_t width = op_data->width;
while (width--) {
src_offset -= decode_channels;
dst_offset -= channels;
std::copy_n(decoded + src_offset, decode_channels,
decoded + dst_offset);
// Add an alpha channel value of 255 (fully opaque) to the
// current pixel if the target output channels is provided as 4. This
// is a workaround to allow jpeg decoder to work with 4 channel input
// models.
decoded[dst_offset + 3] = 255;
}
}
}
output_array_offset += kOutputImageSize;
if (decode_status.code != kTfLiteOk) {
TF_LITE_KERNEL_LOG(context, "%s", decode_status.error_message.c_str());
return kTfLiteError;
}
}
return kTfLiteOk;
}
TfLiteRegistration* Register_DECODE_JPEG() {
static TfLiteRegistration r = {
decode_jpeg_kernel::Init, decode_jpeg_kernel::Free,
decode_jpeg_kernel::Prepare, decode_jpeg_kernel::Eval};
return &r;
}
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,54 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_REGISTER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_REGISTER_H_
#include <cstdint>
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
// DECODE_JPEG can be used to decode a batch of JPEG images on Android.
// TODO(b/172544567): Support iOS.
// TODO(b/172544567): Support greyscale images.
// Expects single 1D input of the shape {num_images} and type string.
// Single output containing the decoded images with shape {num_images, height,
// width, channels}. All input images are required to have 3 channels. The
// decoded images can have 3 or 4 channels depending on the shape of the
// target model input. This op will add an alpha channel value of 255 (fully
// opaque) if the target model accepts input images with 4 channels. This op
// will eventually be included in mainline Tflite as a built-in/custom op once
// it supports both Android and iOS.
TfLiteRegistration* Register_DECODE_JPEG();
struct OpData {
// Number of images to decode.
int32_t num_images;
// All images should have the same height and width.
// Height of images after decoding.
int32_t height;
// Width of images after decoding.
int32_t width;
// Number of channels to be decoded. Accepted values are 3 (RGB) and 4 (RGBA).
int32_t channels;
};
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_REGISTER_H_
@@ -0,0 +1,35 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_STATUS_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_STATUS_H_
#include <string>
#include "tensorflow/lite/core/c/c_api_types.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
struct Status {
TfLiteStatus code = kTfLiteOk;
std::string error_message;
};
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_DECODE_JPEG_STATUS_H_
@@ -0,0 +1,107 @@
/* Copyright 2021 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 <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_register.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/embedded_chessboard_jpeg.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/embedded_test_card_jpeg.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/libjpeg_decoder_test_helper.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
namespace {
using testing::ElementsAre;
const int kHeight = 300, kWidth = 250, kChannels = 3;
const int kDecodedSize = kHeight * kWidth * kChannels;
class DecodeJPEGOpModel : public SingleOpModel {
public:
DecodeJPEGOpModel(const TensorData& input, const TensorData& output,
int num_images, int height, int width, int channels) {
input_id_ = AddInput(input);
output_id_ = AddOutput(output);
flexbuffers::Builder fbb;
fbb.Map([&] {
fbb.Int("num_images", num_images);
fbb.Int("height", height);
fbb.Int("width", width);
fbb.Int("channels", channels);
});
fbb.Finish();
SetCustomOp("DECODE_JPEG", fbb.GetBuffer(),
tflite::acceleration::decode_jpeg_kernel::Register_DECODE_JPEG);
BuildInterpreter({GetShape(input_id_)});
}
int input_buffer_id() { return input_id_; }
int output_id() { return output_id_; }
std::vector<uint8_t> GetOutput() {
return ExtractVector<uint8_t>(output_id_);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_id_); }
protected:
int input_id_;
int shapes_id_;
int output_id_;
};
// TODO(b/172544567): Add more tests to verify that invalid shapes, types and
// params are handled gracefully by the op.
TEST(DecodeJpegTest, TestMultipleJPEGImages) {
// Set up model and populate the input.
std::string chessboard_image(
reinterpret_cast<const char*>(g_tflite_acceleration_chessboard_jpeg),
g_tflite_acceleration_chessboard_jpeg_len);
std::string test_card_image(
reinterpret_cast<const char*>(g_tflite_acceleration_test_card_jpeg),
g_tflite_acceleration_test_card_jpeg_len);
const int kNumImages = 2;
DecodeJPEGOpModel model({TensorType_STRING, {kNumImages}},
{TensorType_UINT8, {}}, kNumImages, kHeight, kWidth,
kChannels);
model.PopulateStringTensor(model.input_buffer_id(),
{chessboard_image, test_card_image});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
// Check output values and shape.
ASSERT_THAT(model.GetOutputShape(),
ElementsAre(kNumImages, kHeight, kWidth, kChannels));
std::vector<uint8_t> output_flattened = model.GetOutput();
std::vector<uint8_t> img1(output_flattened.begin(),
output_flattened.begin() + kDecodedSize);
EXPECT_THAT(img1, HasChessboardPatternWithTolerance(12));
std::vector<uint8_t> img2(output_flattened.begin() + kDecodedSize,
output_flattened.end());
EXPECT_THAT(img2, HasRainbowPatternWithTolerance(5));
}
} // namespace
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,157 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/fb_storage.h"
#include <fcntl.h>
#include <string.h>
#ifndef _WIN32
#include <sys/file.h>
#include <unistd.h>
#endif
#include <fstream>
#include <sstream>
#include <string>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/core/c/c_api_types.h"
// We only really care about Android, but we want the code to be portable for
// ease of testing. See also discussion in cl/224174491.
#ifndef TEMP_FAILURE_RETRY
#ifdef __ANDROID__
#error "TEMP_FAILURE_RETRY not set although on Android"
#else // ! defined(__ANDROID__)
#define TEMP_FAILURE_RETRY(exp) exp
#endif // defined(__ANDROID__)
#endif // defined(TEMP_FAILURE_RETRY)
namespace tflite {
namespace acceleration {
FileStorage::FileStorage(absl::string_view path, ErrorReporter* error_reporter)
: path_(path), error_reporter_(error_reporter) {}
MinibenchmarkStatus FileStorage::ReadFileIntoBuffer() {
#ifndef _WIN32
buffer_.clear();
// O_CLOEXEC is needed for correctness, as another thread may call
// popen() and the callee inherit the lock if it's not O_CLOEXEC.
int fd = TEMP_FAILURE_RETRY(open(path_.c_str(), O_RDONLY | O_CLOEXEC, 0600));
int open_error_no = errno;
if (fd < 0) {
// Try to create if it doesn't exist.
int fd = TEMP_FAILURE_RETRY(
open(path_.c_str(), O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0600));
if (fd >= 0) {
// Successfully created file, all good.
close(fd);
return kMinibenchmarkSuccess;
}
int create_error_no = errno;
TF_LITE_REPORT_ERROR(
error_reporter_,
"Could not open %s for reading: %s, creating failed as well: %s",
path_.c_str(), std::strerror(open_error_no),
std::strerror(create_error_no));
return kMinibenchmarkCantCreateStorageFile;
}
int lock_status = flock(fd, LOCK_EX);
int lock_error_no = errno;
if (lock_status < 0) {
close(fd);
TF_LITE_REPORT_ERROR(error_reporter_, "Could not flock %s: %s",
path_.c_str(), std::strerror(lock_error_no));
return kMinibenchmarkFlockingStorageFileFailed;
}
char buffer[512];
while (true) {
int bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, 512));
int read_error_no = errno;
if (bytes_read == 0) {
// EOF
close(fd);
return kMinibenchmarkSuccess;
} else if (bytes_read < 0) {
close(fd);
TF_LITE_REPORT_ERROR(error_reporter_, "Error reading %s: %s",
path_.c_str(), std::strerror(read_error_no));
return kMinibenchmarkErrorReadingStorageFile;
} else {
buffer_.append(buffer, bytes_read);
}
}
#else // _WIN32
return kMinibenchmarkUnsupportedPlatform;
#endif
}
MinibenchmarkStatus FileStorage::AppendDataToFile(absl::string_view data) {
#ifndef _WIN32
// We use a file descriptor (as opposed to FILE* or C++ streams) for writing
// because we want to be able to use fsync and flock.
// O_CLOEXEC is needed for correctness, as another thread may call
// popen() and the callee inherit the lock if it's not O_CLOEXEC.
int fd = TEMP_FAILURE_RETRY(
open(path_.c_str(), O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0600));
if (fd < 0) {
int error_no = errno;
TF_LITE_REPORT_ERROR(error_reporter_, "Could not open %s for writing: %s",
path_.c_str(), std::strerror(error_no));
return kMinibenchmarkFailedToOpenStorageFileForWriting;
}
int lock_status = flock(fd, LOCK_EX);
int lock_error_no = errno;
if (lock_status < 0) {
close(fd);
TF_LITE_REPORT_ERROR(error_reporter_, "Could not flock %s: %s",
path_.c_str(), std::strerror(lock_error_no));
return kMinibenchmarkFlockingStorageFileFailed;
}
absl::string_view bytes = data;
while (!bytes.empty()) {
ssize_t bytes_written =
TEMP_FAILURE_RETRY(write(fd, bytes.data(), bytes.size()));
if (bytes_written < 0) {
int error_no = errno;
close(fd);
TF_LITE_REPORT_ERROR(error_reporter_, "Could not write to %s: %s",
path_.c_str(), std::strerror(error_no));
return kMinibenchmarkErrorWritingStorageFile;
}
bytes.remove_prefix(bytes_written);
}
if (TEMP_FAILURE_RETRY(fsync(fd)) < 0) {
int error_no = errno;
close(fd);
TF_LITE_REPORT_ERROR(error_reporter_, "Failed to fsync %s: %s",
path_.c_str(), std::strerror(error_no));
return kMinibenchmarkErrorFsyncingStorageFile;
}
if (TEMP_FAILURE_RETRY(close(fd)) < 0) {
int error_no = errno;
TF_LITE_REPORT_ERROR(error_reporter_, "Failed to close %s: %s",
path_.c_str(), std::strerror(error_no));
return kMinibenchmarkErrorClosingStorageFile;
}
return kMinibenchmarkSuccess;
#else // _WIN32
return kMinibenchmarkUnsupportedPlatform;
#endif // !_WIN32
}
ABSL_CONST_INIT const char kFlatbufferStorageIdentifier[] = "STO1";
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,161 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FB_STORAGE_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FB_STORAGE_H_
#include <errno.h>
#include <cstring>
#include <string>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "flatbuffers/base.h" // from @flatbuffers
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
#include "tensorflow/lite/stderr_reporter.h"
namespace tflite {
namespace acceleration {
// FileStorage wraps storage of data in a file with locking and error handling.
// Locking makes appends and reads atomic, using flock(2).
//
// The locking in this class is not meant for general purpose multiple
// reader/writer support, but primarily for the case where a previous instance
// of a program has not finished and we'd like to not corrupt the file
// unnecessarily.
class FileStorage {
public:
FileStorage(absl::string_view path, ErrorReporter* error_reporter);
// Read contents into buffer_. Returns an error if file exists but cannot be
// read.
MinibenchmarkStatus ReadFileIntoBuffer();
// Append data to file. Resets the in-memory items and returns an error if
// writing fails in any way.
//
// This calls fsync() on the file to guarantee persistence and is hence quite
// expensive. The assumption is that this is not done often or in a critical
// path.
MinibenchmarkStatus AppendDataToFile(absl::string_view data);
protected:
std::string path_;
ErrorReporter* error_reporter_;
std::string buffer_;
};
// FlatbufferStorage stores several flatbuffer objects in a file. The primary
// usage is for storing mini benchmark results.
//
// Flatbuffers are not designed for easy mutation. This class is append-only.
// The intended usage is to store a log of events like 'start benchmark with
// configuration X', 'benchmark results for X' / 'crash observed with X' that
// are then parsed to make decisions about how to configure TFLite.
//
// The data is stored as consecutive length-prefixed flatbuffers with identifier
// "STO1".
ABSL_CONST_INIT extern const char kFlatbufferStorageIdentifier[];
template <typename T>
class FlatbufferStorage : protected FileStorage {
public:
explicit FlatbufferStorage(
absl::string_view path,
ErrorReporter* error_reporter = DefaultErrorReporter())
: FileStorage(path, error_reporter) {}
// Reads current contents. Returns an error if file is inaccessible or
// contents are corrupt. The file not existing is not an error.
MinibenchmarkStatus Read();
// Get count of objects stored.
size_t Count() { return contents_.size(); }
// Get object at index i, i < Count();
const T* Get(size_t i) { return contents_[i]; }
// Append a new object to storage and write out to disk. Returns an error if
// disk write or re-read fails.
MinibenchmarkStatus Append(flatbuffers::FlatBufferBuilder* fbb,
flatbuffers::Offset<T> object);
private:
std::vector<const T*> contents_;
};
template <typename T>
MinibenchmarkStatus FlatbufferStorage<T>::Read() {
contents_.clear();
MinibenchmarkStatus status = ReadFileIntoBuffer();
if (status != kMinibenchmarkSuccess) {
return status;
}
size_t remaining_size = buffer_.size();
const uint8_t* current_ptr =
reinterpret_cast<const uint8_t*>(buffer_.c_str());
while (remaining_size != 0) {
if (remaining_size < sizeof(flatbuffers::uoffset_t)) {
TF_LITE_REPORT_ERROR(
error_reporter_,
"Corrupt size-prefixed flatbuffer file %s (remaining size less than "
"size of uoffset_t)",
path_.c_str());
return kMinibenchmarkCorruptSizePrefixedFlatbufferFile;
}
flatbuffers::uoffset_t current_size =
flatbuffers::ReadScalar<flatbuffers::uoffset_t>(current_ptr);
flatbuffers::Verifier verifier(
current_ptr, sizeof(flatbuffers::uoffset_t) + current_size);
if (!verifier.VerifySizePrefixedBuffer<T>(kFlatbufferStorageIdentifier)) {
TF_LITE_REPORT_ERROR(
error_reporter_,
"Corrupt size-prefixed flatbuffer file %s (verifier returned false)",
path_.c_str());
return kMinibenchmarkCorruptSizePrefixedFlatbufferFile;
}
contents_.push_back(flatbuffers::GetSizePrefixedRoot<T>(current_ptr));
size_t consumed = sizeof(flatbuffers::uoffset_t) + current_size;
if (remaining_size < consumed) {
TF_LITE_REPORT_ERROR(
error_reporter_,
"Corrupt size-prefixed flatbuffer file %s (mismatched size "
"calculation)",
path_.c_str());
return kMinibenchmarkCorruptSizePrefixedFlatbufferFile;
}
remaining_size -= consumed;
current_ptr += consumed;
}
return kMinibenchmarkSuccess;
}
template <typename T>
MinibenchmarkStatus FlatbufferStorage<T>::Append(
flatbuffers::FlatBufferBuilder* fbb, flatbuffers::Offset<T> object) {
contents_.clear();
fbb->FinishSizePrefixed(object, kFlatbufferStorageIdentifier);
const char* data = reinterpret_cast<const char*>(fbb->GetBufferPointer());
size_t size = fbb->GetSize();
MinibenchmarkStatus status = AppendDataToFile({data, size});
if (status != kMinibenchmarkSuccess) {
return status;
}
return Read();
}
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FB_STORAGE_H_
@@ -0,0 +1,150 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/fb_storage.h"
#include <algorithm>
#include <string>
#include <thread> // NOLINT - only production use is on Android, where std::thread is allowed
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/c/c_api_types.h"
namespace tflite {
namespace acceleration {
namespace {
std::string GetTemporaryDirectory() {
#ifdef __ANDROID__
return "/data/local/tmp";
#else
if (getenv("TEST_TMPDIR")) {
return getenv("TEST_TMPDIR");
}
if (getenv("TEMP")) {
return getenv("TEMP");
}
return ".";
#endif
}
std::string GetStoragePath() {
std::string path = GetTemporaryDirectory() + "/storage.fb";
unlink(path.c_str());
return path;
}
TEST(FlatbufferStorageTest, AppendAndReadOneItem) {
std::string path = GetStoragePath();
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<BenchmarkEvent> o =
CreateBenchmarkEvent(fbb, 0, BenchmarkEventType_START);
FlatbufferStorage<BenchmarkEvent> storage(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkSuccess);
EXPECT_EQ(storage.Count(), 0);
EXPECT_EQ(storage.Append(&fbb, o), kMinibenchmarkSuccess);
ASSERT_EQ(storage.Count(), 1);
EXPECT_EQ(storage.Get(0)->event_type(), BenchmarkEventType_START);
storage = FlatbufferStorage<BenchmarkEvent>(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkSuccess);
ASSERT_EQ(storage.Count(), 1);
EXPECT_EQ(storage.Get(0)->event_type(), BenchmarkEventType_START);
}
TEST(FlatbufferStorageTest, AppendAndReadThreeItems) {
std::string path = GetStoragePath();
FlatbufferStorage<BenchmarkEvent> storage(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkSuccess);
EXPECT_EQ(storage.Count(), 0);
for (auto event : {BenchmarkEventType_START, BenchmarkEventType_ERROR,
BenchmarkEventType_END}) {
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<BenchmarkEvent> object =
CreateBenchmarkEvent(fbb, 0, event);
EXPECT_EQ(storage.Append(&fbb, object), kMinibenchmarkSuccess);
}
ASSERT_EQ(storage.Count(), 3);
EXPECT_EQ(storage.Get(0)->event_type(), BenchmarkEventType_START);
EXPECT_EQ(storage.Get(1)->event_type(), BenchmarkEventType_ERROR);
EXPECT_EQ(storage.Get(2)->event_type(), BenchmarkEventType_END);
storage = FlatbufferStorage<BenchmarkEvent>(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkSuccess);
ASSERT_EQ(storage.Count(), 3);
EXPECT_EQ(storage.Get(0)->event_type(), BenchmarkEventType_START);
EXPECT_EQ(storage.Get(1)->event_type(), BenchmarkEventType_ERROR);
EXPECT_EQ(storage.Get(2)->event_type(), BenchmarkEventType_END);
}
TEST(FlatbufferStorageTest, PathDoesntExist) {
std::string path = GetTemporaryDirectory() + "/nosuchdirectory/storage.pb";
FlatbufferStorage<BenchmarkEvent> storage(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkCantCreateStorageFile);
}
#ifndef __ANDROID__
// chmod(0444) doesn't block writing on Android.
TEST(FlatbufferStorageTest, WriteFailureResetsStorage) {
std::string path = GetStoragePath();
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<BenchmarkEvent> o =
CreateBenchmarkEvent(fbb, 0, BenchmarkEventType_START);
FlatbufferStorage<BenchmarkEvent> storage(path);
EXPECT_EQ(storage.Append(&fbb, o), kMinibenchmarkSuccess);
ASSERT_EQ(storage.Count(), 1);
chmod(path.c_str(), 0444);
EXPECT_EQ(storage.Append(&fbb, o),
kMinibenchmarkFailedToOpenStorageFileForWriting);
ASSERT_EQ(storage.Count(), 0);
}
#endif // !__ANDROID__
TEST(FlatbufferStorageTest, Locking) {
std::string path = GetStoragePath();
std::vector<std::thread> threads;
const int kNumThreads = 4;
const int kIterations = 10;
threads.reserve(kNumThreads);
for (int i = 0; i < kNumThreads; i++) {
threads.push_back(std::thread([path]() {
for (int j = 0; j < kIterations; j++) {
FlatbufferStorage<BenchmarkEvent> storage(path);
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<BenchmarkEvent> o =
CreateBenchmarkEvent(fbb, 0, BenchmarkEventType_START);
EXPECT_EQ(storage.Append(&fbb, o), kMinibenchmarkSuccess);
}
}));
}
std::for_each(threads.begin(), threads.end(),
[](std::thread& t) { t.join(); });
FlatbufferStorage<BenchmarkEvent> storage(path);
EXPECT_EQ(storage.Read(), kMinibenchmarkSuccess);
EXPECT_EQ(storage.Count(), kNumThreads * kIterations);
}
} // namespace
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,48 @@
/* 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/experimental/acceleration/mini_benchmark/file_lock.h"
#ifndef _WIN32
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif // !_WIN32
#include <string>
namespace tflite {
namespace acceleration {
bool FileLock::TryLock() {
#ifndef _WIN32
if (fd_ < 0) {
// O_CLOEXEC is needed for correctness, as another thread may call
// popen() and the callee would then inherit the lock if it's not O_CLOEXEC.
fd_ = open(path_.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
}
if (fd_ < 0) {
return false;
}
if (flock(fd_, LOCK_EX | LOCK_NB) == 0) {
return true;
}
#endif // !_WIN32
return false;
}
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,57 @@
/* 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FILE_LOCK_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FILE_LOCK_H_
#ifndef _WIN32
#include <unistd.h>
#endif // !_WIN32
#include <string>
namespace tflite {
namespace acceleration {
// A simple mutex lock implemented with file descriptor. Not supported in
// Windows. This lock will release safely when the calling thread / process
// crashes.
class FileLock {
public:
explicit FileLock(const std::string& path) : path_(path) {}
// Move only.
FileLock(FileLock&& other) = default;
FileLock& operator=(FileLock&& other) = default;
~FileLock() {
#ifndef _WIN32
if (fd_ >= 0) {
close(fd_);
}
#endif // !_WIN32
}
// Returns whether the lock is acquired successfully.
bool TryLock();
private:
std::string path_;
int fd_ = -1;
};
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_FILE_LOCK_H_
@@ -0,0 +1,66 @@
/* 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/experimental/acceleration/mini_benchmark/file_lock.h"
#include <csignal>
#include <iostream>
#include <string>
#include <utility>
#include <gtest/gtest.h>
namespace tflite {
namespace acceleration {
namespace {
class FileLockTest : public ::testing::Test {
protected:
void SetUp() override { file_path_ = ::testing::TempDir() + "/file_lock"; }
std::string file_path_;
};
TEST_F(FileLockTest, CanLock) { EXPECT_TRUE(FileLock(file_path_).TryLock()); }
TEST_F(FileLockTest, FailIfLockMoreThanOnce) {
FileLock lock_one(file_path_);
FileLock lock_two(file_path_);
ASSERT_TRUE(lock_one.TryLock());
EXPECT_FALSE(lock_two.TryLock());
}
TEST_F(FileLockTest, LockReleasedWhenThreadCrash) {
pid_t pid = fork();
if (pid == 0) {
// Child process crashed after TryLock().
FileLock lock(file_path_);
if (!lock.TryLock()) {
_exit(1);
}
std::cout << "Lock acquired successfully.";
kill(getpid(), SIGKILL);
}
int wstatus;
int w = waitpid(pid, &wstatus, WUNTRACED);
ASSERT_NE(w, -1);
// Lock again from main process.
FileLock lock_two(file_path_);
EXPECT_TRUE(lock_two.TryLock());
}
} // namespace
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,110 @@
/* 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/experimental/acceleration/mini_benchmark/gpu_module_plugin.h"
#include <dlfcn.h>
#include <memory>
#include <string>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/acceleration/configuration/delegate_registry.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite {
namespace acceleration {
using ::tflite::delegates::TfLiteDelegatePtr;
using SymbolFunc = const TfLiteDelegatePlugin*();
// Function name used to get a pointer to GpuDelegatePlugin.
constexpr char kPluginGetterSymbolName[] = "TfLiteGpuDelegatePluginCApi";
std::unique_ptr<delegates::DelegatePluginInterface> GpuModulePlugin::New(
const TFLiteSettings& acceleration) {
return std::unique_ptr<GpuModulePlugin>(new GpuModulePlugin(acceleration));
}
int GpuModulePlugin::GetDelegateErrno(TfLiteDelegate* from_delegate) {
if (!plugin_handle_) {
return error_code_;
}
return plugin_handle_->get_delegate_errno(from_delegate);
}
TfLiteDelegatePtr GpuModulePlugin::Create() {
if (!plugin_handle_) {
return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}
return TfLiteDelegatePtr(plugin_handle_->create(tflite_settings_),
(plugin_handle_->destroy));
}
// In case GPU acceleration is not supported for this platform, we still need to
// construct an empty object so that Create() can later be called on it.
GpuModulePlugin::GpuModulePlugin(const TFLiteSettings& tflite_settings) {
TFLiteSettingsT settings_obj;
tflite_settings.UnPackTo(&settings_obj);
fbb_.Finish(CreateTFLiteSettings(fbb_, &settings_obj));
tflite_settings_ =
flatbuffers::GetRoot<TFLiteSettings>(fbb_.GetBufferPointer());
module_ = dlopen(tflite_settings_->stable_delegate_loader_settings()
->delegate_path()
->c_str(),
RTLD_NOW | RTLD_LOCAL);
if (!module_) {
TFLITE_LOG_PROD(TFLITE_LOG_WARNING, "Failed to load Gpu Module from %s",
tflite_settings_->stable_delegate_loader_settings()
->delegate_path()
->c_str());
error_code_ = kMinibenchmarkCannotLoadGpuModule;
return;
}
void* sym = dlsym(module_, kPluginGetterSymbolName);
if (!sym) {
TFLITE_LOG_PROD(TFLITE_LOG_WARNING, "Failed to create symbol '%s'",
kPluginGetterSymbolName);
error_code_ = kMinibenchmarkCannotLoadGpuModule;
return;
}
plugin_handle_ = reinterpret_cast<SymbolFunc*>(sym)();
if (!plugin_handle_) {
TFLITE_LOG_PROD(
TFLITE_LOG_WARNING,
"GPU Module loaded successfully from %s, but plugin handle is null.",
tflite_settings_->stable_delegate_loader_settings()
->delegate_path()
->c_str());
error_code_ = kMinibenchmarkDelegatePluginNotFound;
}
}
GpuModulePlugin::~GpuModulePlugin() {
if (module_) {
dlclose(module_);
}
}
static auto* g_delegate_plugin_GpuModulePlugin =
new tflite::delegates::DelegatePluginRegistry::Register(
"GpuModulePlugin", GpuModulePlugin ::New);
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,63 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_GPU_MODULE_PLUGIN_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_GPU_MODULE_PLUGIN_H_
// This file provides the GpuPlugin class, which implements the
// TFLite Delegate Plugin for the GPU Delegate.
#include <memory>
#include <string>
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#include "tensorflow/lite/core/acceleration/configuration/delegate_registry.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/status_codes.h"
namespace tflite {
namespace acceleration {
// A DelegatePlugin that uses external library to create GPU Plugin.
class GpuModulePlugin : public delegates::DelegatePluginInterface {
public:
static std::unique_ptr<DelegatePluginInterface> New(
const TFLiteSettings& acceleration);
// Move only.
GpuModulePlugin(GpuModulePlugin&& other) = default;
GpuModulePlugin& operator=(GpuModulePlugin&& other) = default;
~GpuModulePlugin() override;
delegates::TfLiteDelegatePtr Create() override;
int GetDelegateErrno(TfLiteDelegate* from_delegate) override;
private:
explicit GpuModulePlugin(const TFLiteSettings& tflite_settings);
// The handle to the loaded external library.
void* module_ = nullptr;
const TfLiteDelegatePlugin* plugin_handle_ = nullptr;
// A copy of the input tflite_settings.
flatbuffers::FlatBufferBuilder fbb_;
// A pointer to the data in fbb_.
const TFLiteSettings* tflite_settings_;
MinibenchmarkStatus error_code_ = kMinibenchmarkSuccess;
};
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_GPU_MODULE_PLUGIN_H_
@@ -0,0 +1,35 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_COMMON_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_COMMON_H_
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/libjpeg.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
struct JpegHeader {
int height;
int width;
int channels;
int bits_per_sample = BITS_IN_JSAMPLE;
};
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_COMMON_H_
@@ -0,0 +1,78 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_DECOMPRESS_BUFFERED_STRUCT_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_DECOMPRESS_BUFFERED_STRUCT_H_
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <vector>
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/libjpeg.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
// May provide an extra buffer of characters beyond the `jpeg_decompress_struct`
// for some builds of Libjpeg Dynamic Library on Android that expect a larger
// struct than we were compiled with. Zeroes out any allocated bytes beyond
// sizeof(jpeg_decompress_struct). This class is exclusively used by
// decode_jpeg.cc to resize `jpeg_decompress_struct`. This is to fix a struct
// mismatch problem. See go/libjpeg-android for more details.
class JpegDecompressBufferedStruct {
public:
explicit JpegDecompressBufferedStruct(std::size_t expected_size)
: resized_size_(std::max(sizeof(jpeg_decompress_struct), expected_size)),
buffer_(reinterpret_cast<char*>(malloc(resized_size_))) {
// Note: Malloc guarantees alignment for 8 bytes. Hence, using malloc
// instead of aligned_alloc.
// https://www.gnu.org/software/libc/manual/html_node/Aligned-Memory-Blocks.html
// alignof(jpeg_decompress_struct) is 8 bytes both on 32 and 64 bit.
// It's safe to align the buffered struct as
// alignof(jpeg_decompress_struct). This is because we only access the
// `jpeg_common_fields` fields of `jpeg_decompress_struct`, all of which are
// pointers. The alignment of these pointer fields is 8 and 4 bytes for 64
// bit and 32 bit platforms respectively. Since
// alignof(jpeg_decompress_struct) is 8 bytes on both platforms, accessing
// these fields shouldn't be a problem.
// Zero out any excess bytes. Zero-initialization is safe for the bytes
// beyond sizeof(jpeg_decompress_struct) because both the dynamic library
// and the implementation in decode_jpeg.cc limit their access only to
// `jpeg_common_fields` in `jpeg_decompress_struct`.
while (--expected_size >= sizeof(jpeg_decompress_struct)) {
buffer_[expected_size] = 0;
}
}
~JpegDecompressBufferedStruct() { std::free(buffer_); }
JpegDecompressBufferedStruct(const JpegDecompressBufferedStruct&) = delete;
JpegDecompressBufferedStruct& operator=(const JpegDecompressBufferedStruct&) =
delete;
jpeg_decompress_struct* get() const {
return reinterpret_cast<jpeg_decompress_struct*>(buffer_);
}
int const size() { return resized_size_; }
const char* buffer() { return buffer_; }
private:
int resized_size_;
char* const buffer_;
};
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_DECOMPRESS_BUFFERED_STRUCT_H_
@@ -0,0 +1,56 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/jpeg_decompress_buffered_struct.h"
#include <gtest/gtest.h>
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
namespace {
const int kSizeOfJpegDecompressStruct = sizeof(jpeg_decompress_struct);
TEST(JpegDecompressBufferedStructTest,
ExpectInitializationSizeMatchesStructSize) {
JpegDecompressBufferedStruct buffered_struct(kSizeOfJpegDecompressStruct);
EXPECT_EQ(buffered_struct.size(), kSizeOfJpegDecompressStruct);
}
TEST(JpegDecompressBufferedStructTest,
StructWithSizeGreaterThanCompiledStruct) {
int excess_bytes = 16;
JpegDecompressBufferedStruct buffered_struct(kSizeOfJpegDecompressStruct +
excess_bytes);
EXPECT_EQ(buffered_struct.size(), kSizeOfJpegDecompressStruct + excess_bytes);
const char* buffer = buffered_struct.buffer();
ASSERT_NE(buffer, nullptr);
while (excess_bytes--) {
EXPECT_EQ(
(unsigned char)(buffer[kSizeOfJpegDecompressStruct + excess_bytes]),
'\0');
}
}
TEST(JpegDecompressBufferedStructTest, StructWithSizeLessThanCompiledStruct) {
JpegDecompressBufferedStruct buffered_struct(kSizeOfJpegDecompressStruct -
16);
EXPECT_EQ(buffered_struct.size(), kSizeOfJpegDecompressStruct);
}
} // namespace
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,337 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/jpeg_header_parser.h"
#include <cstdint>
#include <string>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/string_util.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
/*
JPEG file overall file structure
SOI Marker FFD8
Marker XX size=SSSS FFXX SSSS DDDD......
Marker YY size=TTTT FFYY TTTT DDDD......
SOFn marker with the info we want
SOS Marker size=UUUU FFDA UUUU DDDD....
Image stream I I I I....
EOI Marker FFD9
The first marker is either APP0 (JFIF format) or APP1 (EXIF format)
We support only JFIF images
*/
namespace {
using MarkerId = uint16_t;
void AsWord(int value, char* msb, char* lsb) {
*msb = static_cast<char>(value >> 8);
*lsb = static_cast<char>(value);
}
// JFIF spec at
// https://www.ecma-international.org/publications-and-standards/technical-reports/ecma-tr-98/
// Marker definition summary at
// http://lad.dsc.ufcg.edu.br/multimidia/jpegmarker.pdf
// Overall JPEG File structure with discussion of the supported number of
// channels per format
// https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
//
class JfifHeaderParser {
public:
explicit JfifHeaderParser(const tflite::StringRef& jpeg_image_data)
: jpeg_image_data_(jpeg_image_data), offset_(0) {
if (!IsJpegImage(jpeg_image_data_)) {
is_valid_image_buffer_ = false;
validation_error_message_ = "Not a valid JPEG image.";
} else if (!IsJfifImage(jpeg_image_data_)) {
is_valid_image_buffer_ = false;
validation_error_message_ = "Image is not in JFIF format.";
return;
} else {
is_valid_image_buffer_ = true;
}
}
#define ENSURE_READ_STATUS(a) \
do { \
const TfLiteStatus s = (a); \
if (s != kTfLiteOk) { \
return {s, "Error trying to parse JPEG header."}; \
} \
} while (0)
Status ReadJpegHeader(JpegHeader* result) {
if (!is_valid_image_buffer_) {
return {kTfLiteError, validation_error_message_};
}
Status move_to_sof_status = MoveToStartOfFrameMarker();
if (move_to_sof_status.code != kTfLiteOk) {
return move_to_sof_status;
}
ENSURE_READ_STATUS(SkipBytes(2)); // skipping marker length
char precision;
ENSURE_READ_STATUS(ReadByte(&precision));
uint16_t height;
ENSURE_READ_STATUS(ReadWord(&height));
uint16_t width;
ENSURE_READ_STATUS(ReadWord(&width));
char num_of_components;
ENSURE_READ_STATUS(ReadByte(&num_of_components));
if (num_of_components != 1 && num_of_components != 3) {
return {kTfLiteError,
"A JFIF image without App14 marker doesn't support a number of "
"components = " +
std::to_string(static_cast<int>(num_of_components))};
}
result->width = width;
result->height = height;
result->channels = num_of_components;
result->bits_per_sample = precision;
return {kTfLiteOk, ""};
}
Status ApplyHeaderToImage(const JpegHeader& new_header,
std::string& write_to) {
if (!is_valid_image_buffer_) {
return {kTfLiteError, validation_error_message_};
}
Status move_to_sof_status = MoveToStartOfFrameMarker();
if (move_to_sof_status.code != kTfLiteOk) {
return move_to_sof_status;
}
ENSURE_READ_STATUS(SkipBytes(2)); // skipping marker length
if (!HasData(6)) {
return {kTfLiteError,
"Invalid SOF marker, image buffer ends before end of marker"};
}
char header[6];
header[0] = static_cast<char>(new_header.bits_per_sample);
AsWord(new_header.height, header + 1, header + 2);
AsWord(new_header.width, header + 3, header + 4);
header[5] = static_cast<char>(new_header.channels);
write_to.clear();
write_to.append(jpeg_image_data_.str, offset_);
write_to.append(header, 6);
ENSURE_READ_STATUS(SkipBytes(6));
if (HasData()) {
write_to.append(jpeg_image_data_.str + offset_,
jpeg_image_data_.len - offset_);
}
return {kTfLiteOk, ""};
}
private:
const tflite::StringRef jpeg_image_data_;
// Using int for consistency with the size in StringRef
int offset_;
bool is_valid_image_buffer_;
std::string validation_error_message_;
// Moves to the begin of the first SOF marker
Status MoveToStartOfFrameMarker() {
const MarkerId kStartOfStreamMarkerId = 0xFFDA; // Start of image data
offset_ = 0;
ENSURE_READ_STATUS(SkipBytes(4)); // skipping SOI and APP0 marker IDs
ENSURE_READ_STATUS(SkipCurrentMarker()); // skipping APP0
MarkerId curr_marker_id;
// We need at least 2 bytes for the marker ID and 2 for the length
while (HasData(/*min_data_size=*/4)) {
ENSURE_READ_STATUS(ReadWord(&curr_marker_id));
// We are breaking at the first met SOF marker. This won't generate
// results inconsistent with LibJPEG because only
// image with a single SOF marker are successfully parsed by it.
// LibJPEG fails if more than one marker is found in the header (see
// https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jerror.h#L121
// and
// https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jdmarker.c#L264-L265
if (IsStartOfFrameMarkerId(curr_marker_id)) {
break;
}
if (curr_marker_id == kStartOfStreamMarkerId) {
return {kTfLiteError, "Error trying to parse JPEG header."};
}
ENSURE_READ_STATUS(SkipCurrentMarker());
}
return {kTfLiteOk, ""};
}
#undef ENSURE_READ_STATUS
bool HasData(int min_data_size = 1) {
return offset_ <= jpeg_image_data_.len - min_data_size;
}
TfLiteStatus SkipBytes(int bytes) {
if (!HasData(bytes)) {
TFLITE_LOG(TFLITE_LOG_WARNING,
"Trying to move out of image boundaries from offset %d, "
"skipping %d bytes",
offset_, bytes);
return kTfLiteError;
}
offset_ += bytes;
return kTfLiteOk;
}
TfLiteStatus ReadByte(char* result) {
if (!HasData()) {
return kTfLiteError;
}
*result = jpeg_image_data_.str[offset_];
return SkipBytes(1);
}
TfLiteStatus ReadWord(uint16_t* result) {
TF_LITE_ENSURE_STATUS(ReadWordAt(jpeg_image_data_, offset_, result));
return SkipBytes(2);
}
TfLiteStatus SkipCurrentMarker() {
// We just read the marker ID so we are on top of the marker len
uint16_t full_marker_len;
TF_LITE_ENSURE_STATUS(ReadWord(&full_marker_len));
if (full_marker_len <= 2) {
TFLITE_LOG(TFLITE_LOG_WARNING,
"Invalid marker length %d read at offset %X", full_marker_len,
offset_);
return kTfLiteError;
}
// The marker len includes the 2 bytes of marker length
return SkipBytes(full_marker_len - 2);
}
static TfLiteStatus ReadWordAt(const tflite::StringRef& jpeg_image_data,
int read_offset, uint16_t* result) {
if (read_offset < 0 || read_offset + 2 > jpeg_image_data.len) {
return kTfLiteError;
}
// Cast to unsigned since char can be signed.
const unsigned char* buf =
reinterpret_cast<const unsigned char*>(jpeg_image_data.str);
*result = (buf[read_offset] << 8) + buf[read_offset + 1];
return kTfLiteOk;
}
static bool IsJpegImage(const tflite::StringRef& jpeg_image_data) {
const MarkerId kStartOfImageMarkerId = 0xFFD8;
const MarkerId kEndOfImageMarkerId = 0xFFD9;
MarkerId soi_marker_id;
MarkerId eoi_marker_id;
if (ReadWordAt(jpeg_image_data, 0, &soi_marker_id) != kTfLiteOk) {
return false;
}
if (ReadWordAt(jpeg_image_data, jpeg_image_data.len - 2, &eoi_marker_id) !=
kTfLiteOk) {
return false;
}
return (soi_marker_id == kStartOfImageMarkerId) &&
(eoi_marker_id == kEndOfImageMarkerId);
}
static bool IsJfifImage(const tflite::StringRef& jpeg_image_data) {
const MarkerId kApp0MarkerId = 0xFFE0; // First marker in JIFF image
MarkerId app_marker_id;
if ((ReadWordAt(jpeg_image_data, 2, &app_marker_id) != kTfLiteOk) ||
(app_marker_id != kApp0MarkerId)) {
return false;
}
// Checking Jfif identifier string "JFIF\0" in APP0 Marker
const std::string kJfifIdString{"JFIF\0", 5};
// The ID starts after SOI (2 bytes), APP0 marker IDs (2 bytes) and 2 other
// bytes with APP0 marker length
const int KJfifIdStringStartOffset = 6;
if (KJfifIdStringStartOffset + kJfifIdString.size() >=
jpeg_image_data.len) {
TFLITE_LOG(TFLITE_LOG_WARNING,
"Invalid image, reached end of data at offset while "
"parsing APP0 header");
return false;
}
const std::string actualImgId(
jpeg_image_data.str + KJfifIdStringStartOffset, kJfifIdString.size());
if (kJfifIdString != actualImgId) {
TFLITE_LOG(TFLITE_LOG_WARNING, "Invalid image, invalid APP0 header");
return false;
}
return true;
}
static bool IsStartOfFrameMarkerId(MarkerId marker_id) {
return 0xFFC0 <= marker_id && marker_id < 0xFFCF;
}
};
} // namespace
Status ReadJpegHeader(const tflite::StringRef& jpeg_image_data,
JpegHeader* header) {
JfifHeaderParser parser(jpeg_image_data);
return parser.ReadJpegHeader(header);
}
Status BuildImageWithNewHeader(const tflite::StringRef& orig_jpeg_image_data,
const JpegHeader& new_header,
std::string& new_image_data) {
JfifHeaderParser parser(orig_jpeg_image_data);
return parser.ApplyHeaderToImage(new_header, new_image_data);
}
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,47 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_HEADER_PARSER_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_HEADER_PARSER_H_
#include <string>
#include <tuple>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_status.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/jpeg_common.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/string_util.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
// Extract the info in JpegHeader from the given buffer.
// Fails if the buffer doesn't contain a valid JPEG image in JFIF format.
Status ReadJpegHeader(const tflite::StringRef& jpeg_image_data,
JpegHeader* header);
// Writes into the given string the content of the JPEG image altered with
// the content of new_header.
// This is intented to be used in tests to forge existing images.
Status BuildImageWithNewHeader(const tflite::StringRef& orig_jpeg_image_data,
const JpegHeader& new_header,
std::string& new_image_data);
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_JPEG_HEADER_PARSER_H_
@@ -0,0 +1,124 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/jpeg_header_parser.h"
#include <cstddef>
#include <ostream>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/embedded_chessboard_jpeg.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
void PrintTo(const Status& status, std::ostream* os) {
*os << "{ code: " + std::to_string(status.code) + ", error_message: '" +
status.error_message + "'}";
}
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
namespace {
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Matcher;
using tflite::acceleration::decode_jpeg_kernel::JpegHeader;
using tflite::acceleration::decode_jpeg_kernel::ReadJpegHeader;
Matcher<JpegHeader> JpegHeaderEq(const JpegHeader& expected) {
return AllOf(
Field(&JpegHeader::channels, Eq(expected.channels)),
Field(&JpegHeader::height, Eq(expected.height)),
Field(&JpegHeader::width, Eq(expected.width)),
Field(&JpegHeader::bits_per_sample, Eq(expected.bits_per_sample)));
}
using tflite::acceleration::decode_jpeg_kernel::Status;
Matcher<Status> StatusEq(const Status& expected) {
return AllOf(Field(&Status::code, Eq(expected.code)),
Field(&Status::error_message, Eq(expected.error_message)));
}
const int kChessboardImgHeight = 300;
const int kChessboardImgWidth = 250;
const int kChessboardImgChannels = 3;
TEST(ReadJpegHeader, ShouldParseValidJpgImage) {
const tflite::StringRef chessboard_image{
reinterpret_cast<const char*>(g_tflite_acceleration_chessboard_jpeg),
static_cast<size_t>(g_tflite_acceleration_chessboard_jpeg_len)};
ASSERT_GT(chessboard_image.len, 4);
JpegHeader header;
ASSERT_THAT(ReadJpegHeader(chessboard_image, &header),
StatusEq({kTfLiteOk, ""}));
EXPECT_THAT(header, JpegHeaderEq({kChessboardImgHeight, kChessboardImgWidth,
kChessboardImgChannels}));
}
TEST(ReadJpegHeader, ShouldFailForInvalidJpegImage) {
const std::string invalid_image = "invalid image content";
const tflite::StringRef invalid_image_ref{invalid_image.c_str(),
invalid_image.size()};
JpegHeader header;
EXPECT_THAT(ReadJpegHeader(invalid_image_ref, &header),
StatusEq({kTfLiteError, "Not a valid JPEG image."}));
}
TEST(ReadJpegHeader, ShouldFailForEmptyJpegImage) {
const tflite::StringRef invalid_image_ref{"", 0};
JpegHeader header;
EXPECT_THAT(ReadJpegHeader(invalid_image_ref, &header),
StatusEq({kTfLiteError, "Not a valid JPEG image."}));
}
TEST(ApplyHeaderToImage, ReturnsNewImageWithDifferentHeader) {
const tflite::StringRef chessboard_image{
reinterpret_cast<const char*>(g_tflite_acceleration_chessboard_jpeg),
static_cast<size_t>(g_tflite_acceleration_chessboard_jpeg_len)};
JpegHeader new_header{
.height = 20, .width = 30, .channels = 1, .bits_per_sample = 3};
std::string new_image_data;
ASSERT_THAT(
BuildImageWithNewHeader(chessboard_image, new_header, new_image_data),
StatusEq({kTfLiteOk, ""}));
const tflite::StringRef altered_image{new_image_data.c_str(),
new_image_data.size()};
JpegHeader header;
ASSERT_THAT(ReadJpegHeader(altered_image, &header),
StatusEq({kTfLiteOk, ""}));
EXPECT_THAT(header, JpegHeaderEq(new_header));
}
} // namespace
@@ -0,0 +1,62 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/libc_handle.h"
#ifdef __ANDROID__
#include <dlfcn.h>
#endif
#include <stdio.h>
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_status.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
LibCHandle LibCHandle::Create(Status &status) {
#ifndef __ANDROID__
#ifndef _WIN32
// Use the statically linked C lib.
return LibCHandle(nullptr, ::fmemopen);
#else // _WIN32
status = {kTfLiteError, "Windows not supported."};
return LibCHandle(nullptr, nullptr);
#endif // !_WIN32
#else // __ANDROID__
void *libc = nullptr;
FmemopenPtr fmemopen_ptr = nullptr;
if (!(libc = dlopen("libc.so", RTLD_NOW | RTLD_LOCAL))) {
status = {kTfLiteError,
"Failed to load the libc dynamic shared object library."};
return LibCHandle(nullptr, nullptr);
}
if (!(fmemopen_ptr =
reinterpret_cast<FmemopenPtr>(dlsym(libc, "fmemopen")))) {
status = {kTfLiteError, "Failed to dynamically load the method: fmemopen"};
return LibCHandle(nullptr, nullptr);
}
status = {kTfLiteOk, ""};
return LibCHandle(libc, fmemopen_ptr);
#endif // !__ANDROID__
}
FILE *LibCHandle::fmemopen(void *buf, size_t size, const char *mode) const {
return fmemopen_(buf, size, mode);
}
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,81 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBC_HANDLE_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBC_HANDLE_H_
#ifdef __ANDROID__
#include <dlfcn.h>
#endif
#include <cstdio>
#include <memory>
#include <utility>
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_status.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
// This class offers a handle to C Standard Library (LibC) shared object
// library on Android. It offers pointers to functions in LibC.
// Fmemopen is available as native API from Android SDK 23 onwards. In order to
// support Android devices from SDK 21 onwards, we load fmemopen dynamically
// from the libc shared object library.
// TODO(b/172544567): Support Apple.
class LibCHandle {
public:
// Factory to get an initialised instance of LibCHandle.
// Loads the libc dynamic library and gets handle to all the
// required functions. Stores the initialisation status in `status`.
static LibCHandle Create(Status& status);
LibCHandle(LibCHandle const&) = delete;
LibCHandle& operator=(const LibCHandle&) = delete;
LibCHandle(LibCHandle&& other)
: libc_(std::exchange(other.libc_, nullptr)),
fmemopen_(std::exchange(other.fmemopen_, nullptr)) {}
LibCHandle& operator=(LibCHandle&& other) {
if (&other != this) {
CloseHandle();
libc_ = std::exchange(other.libc_, nullptr);
fmemopen_ = std::exchange(other.fmemopen_, nullptr);
}
return *this;
}
~LibCHandle() { CloseHandle(); }
// Definition can be found here
// https://man7.org/linux/man-pages/man3/fmemopen.3.html
FILE* fmemopen(void* buf, size_t size, const char* mode) const;
private:
using FmemopenPtr = FILE* (*)(void*, size_t, const char*);
LibCHandle(void* libc, FmemopenPtr ptr) : libc_(libc), fmemopen_(ptr) {}
// Closes the dynamic library loaded in libc_.
void CloseHandle() {
#ifdef __ANDROID__
if (libc_ != nullptr) {
dlclose(libc_);
}
#endif
}
void* libc_ = nullptr;
FmemopenPtr fmemopen_ = nullptr;
};
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBC_HANDLE_H_
@@ -0,0 +1,34 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/libc_handle.h"
#include <gtest/gtest.h>
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
namespace {
TEST(LibCHandleTest, LoadingSucceedsAndroidPlatforms) {
Status status;
LibCHandle handle = LibCHandle::Create(status);
EXPECT_EQ(status.error_message, "");
EXPECT_EQ(status.code, kTfLiteOk);
}
} // namespace
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite
@@ -0,0 +1,20 @@
/* Copyright 2021 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_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBJPEG_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBJPEG_H_
#include "tensorflow/core/platform/jpeg.h"
#endif // TENSORFLOW_LITE_EXPERIMENTAL_ACCELERATION_MINI_BENCHMARK_LIBJPEG_H_
@@ -0,0 +1,299 @@
/* Copyright 2021 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/experimental/acceleration/mini_benchmark/libjpeg_decoder.h"
#include <setjmp.h>
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/decode_jpeg_status.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/jpeg_decompress_buffered_struct.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/jpeg_header_parser.h"
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/libjpeg_handle.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/string_util.h"
namespace tflite {
namespace acceleration {
namespace decode_jpeg_kernel {
// Limiting max image size to 10,000x10,000x3
// This size would fit on 32 bit systems.
// static
const size_t LibjpegDecoder::kMaxImageHeight = 10000;
// static
const size_t LibjpegDecoder::kMaxImageWidth = 10000;
constexpr char kSizeMismatchError[] =
"JPEG parameter struct mismatch: library thinks size is ";
LibjpegDecoder::Impl::Impl(size_t decompress_struct_size,
const LibjpegHandle* handle)
: decompress_struct_size_(decompress_struct_size),
handle_(handle),
cinfo_(decompress_struct_size) {
cinfo_.get()->err = handle->jpeg_std_error_(&jerr_);
jerr_.error_exit = ErrorExit;
cinfo_.get()->client_data = this;
}
void LibjpegDecoder::Impl::ErrorExit(j_common_ptr cinfo) {
Impl* const impl = reinterpret_cast<Impl*>(cinfo->client_data);
char message[JMSG_LENGTH_MAX];
cinfo->err->format_message(cinfo, message);
impl->status_.code = kTfLiteError;
impl->status_.error_message = message;
// Libjpeg aborts the program in case of any errors by using longjmp and then
// calling exit(). The only way to avoid this, is to transfer the control flow
// to the caller by using setjmp/longjmp.
// Note: Ensure that function containing the corresponding setjmp() is
// guaranteed not to have completed execution.
// https://wiki.sei.cmu.edu/confluence/display/c/MSC22-C.+Use+the+setjmp%28%29%2C+longjmp%28%29+facility+securely
longjmp(impl->env_, 1);
}
Status ExtractSizeFromErrorMessage(const std::string& error_message,
size_t& expected_size) {
Status status;
// Special error handling for struct mismatch issues.
// If there's a mismatch, set `expected_size` with the expected
// size. Error messages are like this: "JPEG parameter struct
// mismatch: library thinks size is 480, caller expects 464".
static const int kExpLengthStart = strlen(kSizeMismatchError);
int end = kExpLengthStart;
while (end < error_message.length() && std::isdigit(error_message[end])) {
end++;
}
if (end > kExpLengthStart) {
expected_size = std::stoi(error_message.substr(kExpLengthStart, end));
} else {
status.code = kTfLiteError;
status.error_message =
"Couldn't parse the size from message: \'" + error_message + "\'";
}
return status;
}
std::unique_ptr<LibjpegDecoder> LibjpegDecoder::Create(Status& status) {
std::unique_ptr<LibjpegDecoder> decoder(
new LibjpegDecoder(LibCHandle::Create(status)));
if (status.code != kTfLiteOk) {
return nullptr;
}
decoder->libjpeg_handle_ = LibjpegHandle::Create(status);
if (decoder->libjpeg_handle_ == nullptr) {
return nullptr;
}
// Tries to probe the libjpeg library to get the expected size of
// `jpeg_decompress_struct`.
Impl impl(sizeof(jpeg_decompress_struct), decoder->libjpeg_handle_.get());
impl.jpeg_CreateDecompress(LibjpegHandle::kLibjpegVersion,
sizeof(jpeg_decompress_struct));
status = impl.status();
if (status.code == kTfLiteOk) {
decoder->expected_size_for_decompress_struct_ =
sizeof(jpeg_decompress_struct);
return decoder;
}
if (!absl::StrContains(status.error_message, kSizeMismatchError)) {
return nullptr;
}
status = ExtractSizeFromErrorMessage(
status.error_message, decoder->expected_size_for_decompress_struct_);
if (status.code != kTfLiteOk) {
return nullptr;
}
return decoder;
}
namespace {
std::string JpegHeaderToString(const JpegHeader& header) {
return "(" + std::to_string(header.height) + ", " +
std::to_string(header.width) + ", " + std::to_string(header.channels) +
", " + std::to_string(header.bits_per_sample) + ")";
}
} // namespace
Status LibjpegDecoder::DecodeImage(const tflite::StringRef& encoded,
const JpegHeader& expected_image_dimensions,
unsigned char* decoded,
const size_t& decoded_size) const {
if (expected_image_dimensions.bits_per_sample != 8) {
return {kTfLiteError, "Supporting only images with 8 bits per sample"};
}
if (expected_image_dimensions.channels != 1 &&
expected_image_dimensions.channels != 3) {
return {kTfLiteError, "Supporting only images with 1 or 3 channels"};
}
if (expected_image_dimensions.width > kMaxImageWidth ||
expected_image_dimensions.height > kMaxImageHeight) {
return {kTfLiteError, "Image is too big, dimensions (" +
std::to_string(expected_image_dimensions.width) +
"," +
std::to_string(expected_image_dimensions.width) +
") larger than the maximum allowed (" +
std::to_string(kMaxImageWidth) + ", " +
std::to_string(kMaxImageHeight) + ")"};
}
// We match the buffer size and the expected size of the decoded image from
// the header to prevent buffer overflows.
JpegHeader header;
Status read_header_status = ReadJpegHeader(encoded, &header);
if (read_header_status.code != kTfLiteOk) {
return read_header_status;
}
if (expected_image_dimensions.channels != header.channels ||
expected_image_dimensions.width != header.width ||
expected_image_dimensions.height != header.height ||
expected_image_dimensions.bits_per_sample != header.bits_per_sample) {
return {kTfLiteError, "Decoded image size " + JpegHeaderToString(header) +
" is different from provided image size " +
JpegHeaderToString(expected_image_dimensions)};
}
size_t header_image_size = static_cast<size_t>(header.width) *
static_cast<size_t>(header.height) *
static_cast<size_t>(header.channels);
if (header_image_size != decoded_size) {
return {kTfLiteError, "Size of buffer(" + std::to_string(decoded_size) +
") for storing decoded image must be equal to "
"the size of decoded image(" +
std::to_string(header_image_size) + ")."};
}
// Dropping constness as fmemopen requires non-const buffers.
char* image_buffer = const_cast<char*>(encoded.str);
size_t image_size = encoded.len;
std::unique_ptr<FILE, std::function<void(FILE*)>> file(
libc_handle_.fmemopen(image_buffer, image_size, "r"),
[](FILE* f) { fclose(f); });
if (file == nullptr) {
return {kTfLiteError, "Fmemopen failed."};
}
Impl impl(expected_size_for_decompress_struct_, libjpeg_handle_.get());
if (impl.jpeg_CreateDecompress(LibjpegHandle::kLibjpegVersion,
expected_size_for_decompress_struct_)) {
return impl.status();
}
if (impl.jpeg_stdio_src(file.get())) {
return impl.status();
}
// jpeg_read_header() must be called before calling jpeg_start_decompress().
// It initialises decompression parameters to default values.
// jpeg_read_header() should not be relied upon for getting image information
// (width, height) etc. Fields populated by jpeg_read_header() such as
// `image_width` and `image_height` come after the `jpeg_common_fields` and
// these may have been shifted in the struct on some builds of libjpeg.
// See go/libjpeg-android.
int read_header_result = 0;
if (impl.jpeg_read_header(read_header_result, true) != kTfLiteOk) {
return impl.status();
}
if (read_header_result != JPEG_HEADER_OK) {
return {kTfLiteError, "Failed call jpeg_read_header"};
}
boolean start_decompress_result = false;
if (impl.jpeg_start_decompress(start_decompress_result) != kTfLiteOk) {
return impl.status();
}
if (!start_decompress_result) {
return {kTfLiteError, "Failed call jpeg_start_decompress_"};
}
size_t height = header.height;
size_t row_stride = header.width * header.channels;
// Decoding the data in a buffer as large as the largest allowed JPEG
// image row to avoid overflows in case we are reading a wrong value for the
// image size in the header or we are receiving an image with an header
// deliberately incorrect to cause a buffer overflow.
// Using 4 channels because the decode color process can handle 3 or 4
// channels:
// https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jdcolor.c#L383
const size_t kMaxImageSize = JPEG_MAX_DIMENSION * 4;
// Initializing the buffer in case we are trying to read more data than
// actually available to avoid having access to uninitialized memory.
// Libjpeg turbo actually fills the unread bytes with zeros
// https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jdhuff.c#L360
// but we don't know what the library on the target system would do.
// Output tensor data stored as RGBRGBRGB..., row-wise for all images.
std::vector<unsigned char> decode_buffer(kMaxImageSize);
// Do not rely on fields such as `output_scanline` from
// `jpeg_decompress_struct` as these would have been shifted. See
// go/libjpeg-android.
unsigned char* buffer_array[1];
buffer_array[0] = decode_buffer.data();
size_t decoded_offset = 0;
while (height--) {
// According to the documentation, jpeg_read_scanlines returns the number
// of lines read
// https://android.googlesource.com/platform/external/jpeg/+/c6859b743e7248b9f401264aac939a5af0d63799/libjpeg.doc#655
// In case of premature ending of the image, the implementation of
// jpeg_read_scanlines in the version of JPEG Turbo we are using to test
// emits a warning ("Corrupt JPEG data: premature end of data segment")
// but doesn't fail and consider the line as successfully read.
// See test
// LibjpegDecoderTest::DoesNotFailDecodingAnImageWithLessDataThanDeclaredInJpegHeader
unsigned int num_of_scanlines_read = 0;
if (impl.jpeg_read_scanlines(num_of_scanlines_read, buffer_array, 1) !=
kTfLiteOk) {
return impl.status();
}
if (num_of_scanlines_read != 1) {
return {kTfLiteError, "Expected " + std::to_string(header.height) +
" lines but found only " +
std::to_string(header.height - height) +
" read scanlines is " +
std::to_string(num_of_scanlines_read)};
}
std::copy_n(buffer_array[0], row_stride, decoded + decoded_offset);
decoded_offset += row_stride;
}
boolean finish_decompress_result = false;
if (impl.jpeg_finish_decompress(finish_decompress_result) != kTfLiteOk) {
return impl.status();
}
if (!finish_decompress_result) {
return {kTfLiteError, "Failed call jpeg_finish_decompress_"};
}
if (impl.jpeg_destroy_decompress() != kTfLiteOk) {
return impl.status();
}
return impl.status();
}
} // namespace decode_jpeg_kernel
} // namespace acceleration
} // namespace tflite

Some files were not shown because too many files have changed in this diff Show More