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,834 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load(
"//tensorflow/core/platform:build_config_root.bzl",
"tf_gpu_tests_tags",
)
load(
"//tensorflow/lite:special_rules.bzl",
"tflite_extra_gles_deps",
"tflite_portable_test_suite_combined",
)
load("//tensorflow/lite/delegates/gpu:build_defs.bzl", "tflite_angle_heapcheck_deps")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "converter",
srcs = ["converter.cc"],
hdrs = ["converter.h"],
deps = [
"//tensorflow/lite/delegates/gpu:spi",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:command_queue",
"//tensorflow/lite/delegates/gpu/gl:gl_buffer",
"//tensorflow/lite/delegates/gpu/gl:gl_program",
"//tensorflow/lite/delegates/gpu/gl:gl_shader",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_test(
name = "converter_test",
size = "small",
srcs = ["converter_test.cc"],
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":converter",
":test_util",
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:egl_environment",
"//tensorflow/lite/delegates/gpu/gl:gl_buffer",
"//tensorflow/lite/delegates/gpu/gl:portable",
"@com_google_absl//absl/types:span",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "add",
srcs = ["add.cc"],
hdrs = ["add.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "add_test",
srcs = ["add_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":add",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "concat",
srcs = ["concat.cc"],
hdrs = ["concat.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "concat_test",
srcs = ["concat_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":concat",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "conv",
srcs = ["conv.cc"],
hdrs = ["conv.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"//tensorflow/lite/delegates/gpu/gl/workgroups:ideal_workgroup_picker",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "conv_test",
srcs = ["conv_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":conv",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "custom_registry",
srcs = ["custom_registry.cc"],
hdrs = ["custom_registry.h"],
deps = [
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/container:flat_hash_map",
],
)
cc_library(
name = "depthwise_conv",
srcs = ["depthwise_conv.cc"],
hdrs = ["depthwise_conv.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"//tensorflow/lite/delegates/gpu/gl/workgroups:ideal_workgroup_picker",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "depthwise_conv_test",
srcs = ["depthwise_conv_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":depthwise_conv",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "elementwise",
srcs = ["elementwise.cc"],
hdrs = ["elementwise.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:tensor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "elementwise_test",
srcs = ["elementwise_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":elementwise",
":test_util",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:tensor",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "fully_connected",
srcs = ["fully_connected.cc"],
hdrs = ["fully_connected.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "fully_connected_test",
srcs = ["fully_connected_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":fully_connected",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "lstm",
srcs = ["lstm.cc"],
hdrs = ["lstm.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "lstm_test",
srcs = ["lstm_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":lstm",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "max_unpooling",
srcs = ["max_unpooling.cc"],
hdrs = ["max_unpooling.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "max_unpooling_test",
srcs = ["max_unpooling_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":max_unpooling",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "mean",
srcs = ["mean.cc"],
hdrs = ["mean.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
],
)
cc_test(
name = "mean_test",
srcs = ["mean_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":mean",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "mul",
srcs = ["mul.cc"],
hdrs = ["mul.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:tensor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "mul_test",
srcs = ["mul_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":mul",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "pad",
srcs = ["pad.cc"],
hdrs = ["pad.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "pad_test",
srcs = ["pad_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":pad",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "pooling",
srcs = ["pooling.cc"],
hdrs = ["pooling.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "pooling_test",
srcs = ["pooling_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":pooling",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "prelu",
srcs = ["prelu.cc"],
hdrs = ["prelu.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "prelu_test",
srcs = ["prelu_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":prelu",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "quantize_and_dequantize",
srcs = ["quantize_and_dequantize.cc"],
hdrs = ["quantize_and_dequantize.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "quantize_and_dequantize_test",
srcs = ["quantize_and_dequantize_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":quantize_and_dequantize",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/kernels/internal:quantization_util",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "relu",
srcs = ["relu.cc"],
hdrs = ["relu.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "relu_test",
srcs = ["relu_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":relu",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "resampler",
srcs = ["resampler.cc"],
hdrs = ["resampler.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "resampler_test",
srcs = ["resampler_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":resampler",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
"@com_google_absl//absl/status",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "reshape",
srcs = ["reshape.cc"],
hdrs = ["reshape.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "reshape_test",
srcs = ["reshape_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":reshape",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "resize",
srcs = ["resize.cc"],
hdrs = ["resize.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "resize_test",
srcs = ["resize_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":resize",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "slice",
srcs = ["slice.cc"],
hdrs = ["slice.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "slice_test",
srcs = ["slice_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":slice",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "softmax",
srcs = ["softmax.cc"],
hdrs = ["softmax.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "softmax_test",
srcs = ["softmax_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":softmax",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "space_to_depth",
srcs = ["space_to_depth.cc"],
hdrs = ["space_to_depth.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/types:any",
],
)
cc_test(
name = "space_to_depth_test",
srcs = ["space_to_depth_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":space_to_depth",
":test_util",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "test_util",
testonly = 1,
srcs = ["test_util.cc"],
hdrs = ["test_util.h"],
linkopts = select({
"//tensorflow:android": [
"-lEGL",
"-lGLESv3",
"-ldl",
"-lm",
],
# copybara:uncomment_begin(google-only)
# "//tensorflow/lite/delegates/gpu:tflite_gpu_angle": [],
# "//tensorflow/lite/delegates/gpu:tflite_gpu_extra_gles_deps": [],
# copybara:uncomment_end
"//conditions:default": [
"-lEGL",
"-lGLESv3",
],
}),
deps = [
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:tensor",
"//tensorflow/lite/delegates/gpu/gl:api",
"//tensorflow/lite/delegates/gpu/gl:compiler_options",
"//tensorflow/lite/delegates/gpu/gl:egl_environment",
"//tensorflow/lite/delegates/gpu/gl:gl_buffer",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object_manager",
"//tensorflow/lite/delegates/gpu/gl:request_gpu_info",
"//tensorflow/lite/delegates/gpu/gl:runtime_options",
"//tensorflow/lite/delegates/gpu/gl/workgroups:default_calculator",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_googletest//:gtest",
] + tflite_extra_gles_deps(),
)
cc_library(
name = "tile",
srcs = ["tile.cc"],
hdrs = ["tile.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/types:any",
],
)
cc_test(
name = "tile_test",
srcs = ["tile_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":test_util",
":tile",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
cc_library(
name = "transpose_conv",
srcs = ["transpose_conv.cc"],
hdrs = ["transpose_conv.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "transpose_conv_test",
srcs = ["transpose_conv_test.cc"],
linkstatic = True,
tags = tf_gpu_tests_tags() + [
"tflite_not_portable_ios",
],
deps = [
":test_util",
":transpose_conv",
"//tensorflow/lite/delegates/gpu/common:operations",
] + tflite_angle_heapcheck_deps(),
)
TFLITE_GPU_BINARY_RELEASE_OPERATORS = [
"add",
"concat",
"conv",
"depthwise_conv",
"elementwise",
"fully_connected",
"lstm",
"mul",
"pad",
"pooling",
"prelu",
"quantize_and_dequantize",
"relu",
"mean",
"resampler",
"reshape",
"resize",
"slice",
"softmax",
"space_to_depth",
"tile",
"transpose_conv",
]
NON_TFLITE_GPU_BINARY_RELEASE_OPERATORS = [
"max_unpooling",
]
cc_library(
name = "registry",
srcs = ["registry.cc"],
hdrs = ["registry.h"],
visibility = ["//visibility:public"],
deps = [":" + op_name for op_name in TFLITE_GPU_BINARY_RELEASE_OPERATORS] +
select({
"//tensorflow/lite/delegates/gpu:tflite_gpu_binary_release": [],
"//conditions:default": NON_TFLITE_GPU_BINARY_RELEASE_OPERATORS,
}) + [
":custom_registry",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
tflite_portable_test_suite_combined(combine_conditions = {"deps": [":test_util"]})
@@ -0,0 +1,165 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/add.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Add : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const ElementwiseAttributes&>(ctx.op_attr);
auto adds = std::get_if<Tensor<Linear, DataType::FLOAT32>>(&attr.param);
auto scalar = std::get_if<float>(&attr.param);
const auto* hwc_tensor =
std::get_if<Tensor<HWC, DataType::FLOAT32>>(&attr.param);
if (hwc_tensor) {
std::string code;
const std::string x_coord = hwc_tensor->shape.w == 1 ? "0" : "gid.x";
const std::string y_coord = hwc_tensor->shape.h == 1 ? "0" : "gid.y";
const std::string s_coord = hwc_tensor->shape.c == 1 ? "0" : "gid.z";
code = absl::StrCat("vec4 second_val = $hwc_buffer[", x_coord, ", ",
y_coord, ", ", s_coord, "]$;\n");
if (hwc_tensor->shape.c == 1) {
code += " second_val.y = second_val.x;\n";
code += " second_val.z = second_val.x;\n";
code += " second_val.w = second_val.x;\n";
}
code += " value_0 += second_val;\n";
*generated_code = {
/*parameters=*/{},
/*objects=*/
{{"hwc_buffer",
MakeReadonlyObject(
uint3(hwc_tensor->shape.w, hwc_tensor->shape.h,
DivideRoundUp(hwc_tensor->shape.c, 4)),
ConvertToPHWC4(
std::get<Tensor<HWC, DataType::FLOAT32>>(attr.param)))}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on gid.z.
/*workload=*/
uint3(static_cast<int>(ctx.input_shapes[0][2]),
static_cast<int>(ctx.input_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.input_shapes[0][3]), 4)),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
if (!adds && !scalar) {
// check if it is a broadcast
if (ctx.input_shapes.size() == 2 &&
ctx.input_shapes[0] != ctx.input_shapes[1] &&
ctx.input_shapes[1][1] == 1 && ctx.input_shapes[1][2] == 1 &&
ctx.input_shapes[0][3] == ctx.input_shapes[1][3]) {
// TODO(b/147771327): investigate why input_data_1[gid.z] worked before
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/
"value_0 = $input_data_0[gid.x, gid.y, gid.z]$ + "
" $input_data_1[0, 0, gid.z]$;",
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
std::string code = "value_0 = value_0";
for (int index = 1; index < ctx.input_shapes.size(); ++index) {
if (ctx.input_shapes[index] != ctx.input_shapes[0]) {
return absl::InvalidArgumentError("Shapes are not equal");
}
absl::StrAppend(&code, " + value_", index);
}
absl::StrAppend(&code, ";");
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
if (scalar) {
*generated_code = {
/*parameters=*/{{"scalar", *scalar}},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/"value_0 += $scalar$;",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
*generated_code = {
/*parameters=*/{},
/*objects=*/{{"add_buffer", MakeReadonlyObject(adds->data)}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on gid.z.
/*workload=*/
uint3(ctx.input_shapes[0][2], ctx.input_shapes[0][1],
DivideRoundUp(ctx.input_shapes[0][3], 4)),
/*workgroup=*/uint3(),
/*source_code=*/"value_0 += $add_buffer[gid.z]$;",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewAddNodeShader() {
return std::make_unique<Add>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ADD_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ADD_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewAddNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ADD_H_
@@ -0,0 +1,223 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/add.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(AddTest, TwoInputTensorsOfTheSameShape) {
TensorRef<BHWC> augend, addend, output;
augend.type = DataType::FLOAT32;
augend.ref = 0;
augend.shape = BHWC(1, 2, 2, 1);
addend.type = DataType::FLOAT32;
addend.ref = 1;
addend.shape = BHWC(1, 2, 2, 1);
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 1);
ElementwiseAttributes attr;
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)},
{augend, addend}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {-2.0, 0.2, 0.7, 0.8}));
ASSERT_TRUE(model.PopulateTensor(1, {0.1, 0.2, 0.3, 0.5}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.9, 0.4, 1.0, 1.3}));
}
TEST(AddTest, InputTensorAndScalar) {
ElementwiseAttributes attr;
attr.param = 0.1f;
TensorRef<BHWC> input, output;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 1, 2);
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 1, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {-2.0, 0.2, 0.7, 0.8, 1.1, 2.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.9, 0.3, 0.8, 0.9, 1.2, 2.1}));
}
TEST(AddTest, InputTensorWithConstantBroadcast) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> tensor;
tensor.shape.v = 2;
tensor.id = 1;
tensor.data.push_back(10.0);
tensor.data.push_back(20.0);
attr.param = std::move(tensor);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)}, {input},
{output});
ASSERT_TRUE(
model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{11.0, 22.0, 13.0, 24.0, 15.0, 26.0, 17.0, 28.0}));
}
TEST(AddTest, InputTensorWithRuntimeBroadcast) {
TensorRef<BHWC> input1;
input1.type = DataType::FLOAT32;
input1.ref = 0;
input1.shape = BHWC(1, 2, 2, 2);
TensorRef<BHWC> input2;
input2.type = DataType::FLOAT32;
input2.ref = 1;
input2.shape = BHWC(1, 1, 1, 2);
ElementwiseAttributes attr;
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)},
{input1, input2}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}));
ASSERT_TRUE(model.PopulateTensor(1, {10.0, 20.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{11.0, 22.0, 13.0, 24.0, 15.0, 26.0, 17.0, 28.0}));
}
TEST(AddTest, InputTensorWithConstantHWC) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor;
tensor.shape = HWC(2, 2, 2);
tensor.id = 1;
tensor.data = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0};
attr.param = std::move(tensor);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)}, {input},
{output});
ASSERT_TRUE(
model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0}));
}
TEST(AddTest, InputTensorWithConstantHWCBroadcastChannels) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor;
tensor.shape = HWC(2, 2, 1);
tensor.id = 1;
tensor.data = {1.0, 2.0, 3.0, 4.0};
attr.param = std::move(tensor);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)}, {input},
{output});
ASSERT_TRUE(
model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {2.0, 3.0, 5.0, 6.0, 8.0, 9.0, 11.0, 12.0}));
}
TEST(AddTest, InputTensorWithConstantHWCBroadcastWidth) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor;
tensor.shape = HWC(2, 1, 2);
tensor.id = 1;
tensor.data = {1.0, 2.0, 3.0, 4.0};
attr.param = std::move(tensor);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(OperationType::ADD), std::move(attr)}, {input},
{output});
ASSERT_TRUE(
model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}));
ASSERT_OK(model.Invoke(*NewAddNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {2.0, 4.0, 4.0, 6.0, 8.0, 10.0, 10.0, 12.0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,457 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/concat.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class AlignedConcatByChannels : public NodeShader {
public:
static bool IsSupported(const GenerationContext& ctx) {
const auto& attr = std::any_cast<const ConcatAttributes&>(ctx.op_attr);
// Implementation supports concatenation by channels only.
if (attr.axis != Axis::CHANNELS) return false;
// Implementation supports concatenation of 2 tensors only.
if (ctx.input_shapes.size() != 2) return false;
// H and W must be the same for every concatenated tensor.
for (int i = 1; i < ctx.input_shapes.size(); i++) {
if (ctx.input_shapes[0][1] != ctx.input_shapes[i][1] ||
ctx.input_shapes[0][2] != ctx.input_shapes[i][2]) {
return false;
}
}
// Channels must be aligned by 4 for every concatenated tensor.
for (const auto& shape : ctx.input_shapes) {
if (shape[3] % 4 != 0) return false;
}
return true;
}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (!IsSupported(ctx)) {
return absl::InvalidArgumentError(
"This case is not supported by aligned concat");
}
// Shader below concatenates 2 tensors which channels are aligned by 4
std::string source = R"(
if (gid.z < $border$) {
value_0 = $input_data_0[gid.x, gid.y, gid.z]$;
} else {
int z = gid.z - $border$;
value_0 = $input_data_1[gid.x, gid.y, z]$;
}
)";
*generated_code = {
/*parameters=*/{
{"border", static_cast<int>(ctx.input_shapes[0][3]) / 4}},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class ConcatByAnyChannel : public NodeShader {
public:
static bool IsSupported(const GenerationContext& ctx) {
const auto& attr = std::any_cast<const ConcatAttributes&>(ctx.op_attr);
// Implementation supports concatenation by channels only.
if (attr.axis != Axis::CHANNELS) return false;
// Implementation supports concatenation of more that 1 tensors only.
if (ctx.input_shapes.size() <= 1) return false;
// H and W must be the same for every concatenated tensor.
for (int i = 1; i < ctx.input_shapes.size(); i++) {
if (ctx.input_shapes[0][1] != ctx.input_shapes[i][1] ||
ctx.input_shapes[0][2] != ctx.input_shapes[i][2]) {
return false;
}
}
return true;
}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (!IsSupported(ctx)) {
return absl::UnimplementedError("This case is not supported by concat");
}
std::string code = DeclareVariables();
// "already_written" is used to keep the amount of already joined channels
int already_written = 0;
// "t" is an id of the next temp* variable.
// Generally, temp* variables are used in macros
// READ_BUFFER_VEC4(buff, addr, var).
// This macros instantiate the variable "var" and
// reads the value from buffer "buff" by address "addr"
int t = 0;
for (int current_input_id = 0; current_input_id < ctx.input_shapes.size();
current_input_id++) {
// Start joining next inout tensor
// Grab channels amount
int in_ch = ctx.input_shapes[current_input_id][3];
code += PrintStartMessage(current_input_id, in_ch, already_written);
// Construct the buffer name associated with this tensor
std::string input = "input_data_" + std::to_string(current_input_id);
// "reminder" shows us how many cells in 4-element vector are left after
// the last write. As example, if we join two tensors both with
// 3 channels, after joining the first one we come to this line again
// and, when joining the second tensor, the reminder value
// will be equal to 1
int reminder = already_written % 4;
if (reminder == 0) {
code += AlignedCase(in_ch, input);
} else {
code += UnalignedCase(reminder, in_ch, input, &t);
}
already_written += in_ch;
}
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/
uint3(static_cast<int>(ctx.output_shapes[0][2]),
static_cast<int>(ctx.output_shapes[0][1]), 1),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
return absl::OkStatus();
}
private:
// Utility function
std::string temp(int t) const { return "temp" + std::to_string(t); }
std::string DeclareVariables() const {
// "val" is used to collect useful information before the next
// upcoming write.
return R"(
int z = gid.z;
vec4 val = vec4(0.0f);
)";
}
std::string PrintStartMessage(int current_input_id, int in_ch,
int already_written) const {
return "// Joining " + std::to_string(current_input_id) +
" tensor with " + std::to_string(in_ch) +
" channels\n// * * * *\\n// Already wrote " +
std::to_string(already_written) + " elements\n\n";
}
std::string AlignedCase(int in_ch, const std::string& input) const {
std::string code;
// This branch is for aligned reading and writing, when we can copy
// all 4 components at once. Address of the first element to write
// should be aligned.
// Visual examples:
// 1) when copy input_data_0
//
// | * * * * | * * * @ | @ @ . . .
// ^
// 2) when in the middle of joining process:
//
// | X X X X | * * * @ | @ @ . . .
// ^
// Note that amount of * equals to the in_ch
//
// X - cells were written before
// * - you are going to write into these cells
// @ - you will fill these cells next cycles
// ^ - first elem you start writing from
int blocks_amount = DivideRoundUp<int>(in_ch, 4);
code += "// Aligned case\n";
code += "// I'm going to make " + std::to_string(blocks_amount) +
" write(s)\n\n";
for (int block = 0; block < blocks_amount; block++) {
// Copy full 4-element vector
code += "val = $" + input + "[gid.x, gid.y, " + std::to_string(block) +
"]$;\n" +
"$output_data_0[gid.x, gid.y, z] = val$;\n"
// calculate next address to write
+ "z++; \n\n";
}
return code;
}
std::string UnalignedCase(int reminder, int in_ch, const std::string& input,
int* t) const {
// This branch is for copying cell-by-cell. It will never start from the
// first tensor input_data_0. This function is splitting in two stages:
// 1) Copy the "leftovers" for the previous cells
// 2) Copy all other
// Visual examples:
//
// Stage 1 Stage 2
// ----------- -------------------------
// . . X | X X X *1 | *2 *2 *2 @ | @ @ . . .
// ^
// . . X | X X *1 *1 | *2 *2 *2 *2 | *2 *2 . . .
// ^
// . . X | X *1 *1 *1 | *2 @ @ @ | @ @ . . .
// ^
// Note that amount of * equals to the in_ch
//
// X - cells were written before
// *1 - write there at the Stage 1
// *2 - write there at the Stage 2
// @ - you will fill these cells next cycles
// ^ - first elem you start writing from
std::string code = "// Unaligned case\n";
// Variable "shift" showes how many "empty" cells are left after previous
// write. Remember, that this case should is unaligned.
// shift now can only be 1, 2 or 3
int shift = 4 - reminder;
if (shift > in_ch) {
shift = in_ch;
}
code += "\n// Stage 1\n";
code += "vec4 " + temp(*t) + " = $" + input + "[gid.x, gid.y, 0]$;\n";
for (int i = 0; i < shift; i++) {
// Note that reminder + i has implicitly added 1, cause
// reminder by it's nature is an amount, not an index
code += "val[" + std::to_string(reminder + i) + "] = " + temp(*t) + "[" +
std::to_string(i) + "];\n";
}
// Rewrite previous value with updated last cells
code += "$output_data_0[gid.x, gid.y, z - 1] = val$;\n";
(*t)++;
// "left_blocks" is equal to an amount of WRITE_BUFFER_VEC4 calls
// which will are left for this input to be finally copied
int left_blocks = (in_ch - shift) / 4;
if ((in_ch - shift) % 4 != 0) {
left_blocks++;
}
if (left_blocks) {
code += "\n// Stage 2\n";
for (int block = 0; block < left_blocks; block++) {
for (int elem = 0; elem < 4; elem++) {
if (shift == in_ch) {
break;
}
if (shift % 4 == 0) {
code += "vec4 " + temp(*t) + " = $" + input + "[gid.x, gid.y, " +
std::to_string(block + 1) + "]$;\n";
(*t)++;
}
code += "val[" + std::to_string(elem) + "] = " + temp(*t - 1) + "[" +
std::to_string(shift % 4) + "];\n";
shift++;
}
code += "$output_data_0[gid.x, gid.y, z] = val$;\n";
code += "z++;\n";
}
} else {
code += "// No Stage 2\n";
}
return code;
}
};
class FlatConcatByHeight : public NodeShader {
public:
static bool IsSupported(const GenerationContext& ctx) {
const auto& attr = std::any_cast<const ConcatAttributes&>(ctx.op_attr);
// Implementation supports concatenation by height only.
if (attr.axis != Axis::HEIGHT) return false;
// Implementation supports concatenation of more that 1 tensors only.
if (ctx.input_shapes.size() <= 1) return false;
// C and W must be the same for every concatenated tensor.
for (int i = 1; i < ctx.input_shapes.size(); i++) {
if (ctx.input_shapes[0][3] != ctx.input_shapes[i][3] ||
ctx.input_shapes[0][2] != ctx.input_shapes[i][2]) {
return false;
}
}
return true;
}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string code;
std::vector<Variable> params;
for (int i = 0, shift = 0; i < ctx.input_shapes.size();
shift += ctx.input_shapes[i][1], i++) {
code += "if (";
if (i != 0) {
code += "$input_data_" + std::to_string(i - 1) + "_h$ <= gid.y && ";
}
code +=
"gid.y < " + std::to_string(shift + ctx.input_shapes[i][1]) + ") {\n";
code += "if (gid.y - " + std::to_string(shift) + " >= $input_data_" +
std::to_string(i) + "_h$) return;\n";
code += "value_0 = $input_data_" + std::to_string(i) +
"[gid.x, gid.y - " + std::to_string(shift) + ", gid.z]$;\n}\n";
if (i != ctx.input_shapes.size() - 1) {
code += " else ";
}
params.push_back({"input_data_" + std::to_string(i) + "_h",
static_cast<int>(ctx.input_shapes[i][1])});
}
*generated_code = {
/*parameters=*/std::move(params),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class FlatConcatByWidth : public NodeShader {
public:
static bool IsSupported(const GenerationContext& ctx) {
const auto& attr = std::any_cast<const ConcatAttributes&>(ctx.op_attr);
// Implementation supports concatenation by width only.
if (attr.axis != Axis::WIDTH) return false;
// Implementation supports concatenation of more that 1 tensors only.
if (ctx.input_shapes.size() <= 1) return false;
// C and H must be the same for every concatenated tensor.
for (int i = 1; i < ctx.input_shapes.size(); i++) {
if (ctx.input_shapes[0][3] != ctx.input_shapes[i][3] ||
ctx.input_shapes[0][1] != ctx.input_shapes[i][1]) {
return false;
}
}
return true;
}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string code;
std::vector<Variable> params;
for (int i = 0, shift = 0; i < ctx.input_shapes.size();
shift += ctx.input_shapes[i][2], i++) {
code += "if (";
if (i != 0) {
code += "$input_data_" + std::to_string(i - 1) + "_w$ <= gid.x && ";
}
code +=
"gid.x < " + std::to_string(shift + ctx.input_shapes[i][2]) + ") {\n";
code += "if (gid.x - " + std::to_string(shift) + " >= $input_data_" +
std::to_string(i) + "_w$) return;\n";
code += "value_0 = $input_data_" + std::to_string(i) + "[gid.x - " +
std::to_string(shift) + ", gid.y, gid.z]$;\n}\n";
if (i != ctx.input_shapes.size() - 1) {
code += " else ";
}
params.push_back({"input_data_" + std::to_string(i) + "_w",
static_cast<int>(ctx.input_shapes[i][2])});
}
*generated_code = {
/*parameters=*/std::move(params),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class FlatConcat : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (FlatConcatByHeight::IsSupported(ctx)) {
return flat_concat_by_height_.GenerateCode(ctx, generated_code);
}
if (FlatConcatByWidth::IsSupported(ctx)) {
return flat_concat_by_width_.GenerateCode(ctx, generated_code);
}
return absl::InvalidArgumentError(
"This case is not supported by flat concat");
}
private:
FlatConcatByHeight flat_concat_by_height_;
FlatConcatByWidth flat_concat_by_width_;
};
} // namespace
std::unique_ptr<NodeShader> NewAlignedConcatNodeShader() {
return std::make_unique<AlignedConcatByChannels>();
}
std::unique_ptr<NodeShader> NewConcatNodeShader() {
return std::make_unique<ConcatByAnyChannel>();
}
std::unique_ptr<NodeShader> NewFlatConcatNodeShader() {
return std::make_unique<FlatConcat>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,36 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONCAT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONCAT_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewAlignedConcatNodeShader();
std::unique_ptr<NodeShader> NewConcatNodeShader();
std::unique_ptr<NodeShader> NewFlatConcatNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONCAT_H_
@@ -0,0 +1,140 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/concat.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(ConcatTest, TwoInputTensorsByUnalignedChannel) {
TensorRef<BHWC> input1, input2, output;
input1.type = DataType::FLOAT32;
input1.ref = 0;
input1.shape = BHWC(1, 2, 2, 1);
input2.type = DataType::FLOAT32;
input2.ref = 1;
input2.shape = BHWC(1, 2, 2, 1);
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
ConcatAttributes attr;
attr.axis = Axis::CHANNELS;
SingleOpModel model({ToString(OperationType::CONCAT), attr}, {input1, input2},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 3, 5, 7}));
ASSERT_TRUE(model.PopulateTensor(1, {2, 4, 6, 8}));
ASSERT_OK(model.Invoke(*NewConcatNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6, 7, 8}));
}
TEST(ConcatTest, TwoInputTensorsByAlignedChannel) {
TensorRef<BHWC> input1, input2, output;
input1.type = DataType::FLOAT32;
input1.ref = 0;
input1.shape = BHWC(1, 1, 1, 4);
input2.type = DataType::FLOAT32;
input2.ref = 1;
input2.shape = BHWC(1, 1, 1, 4);
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 1, 1, 8);
ConcatAttributes attr;
attr.axis = Axis::CHANNELS;
SingleOpModel model({ToString(OperationType::CONCAT), attr}, {input1, input2},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {5, 6, 7, 8}));
ASSERT_OK(model.Invoke(*NewAlignedConcatNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6, 7, 8}));
}
TEST(ConcatTest, TwoInputTensorsByHeight) {
TensorRef<BHWC> input1, input2, output;
input1.type = DataType::FLOAT32;
input1.ref = 0;
input1.shape = BHWC(1, 1, 2, 1);
input2.type = DataType::FLOAT32;
input2.ref = 1;
input2.shape = BHWC(1, 2, 2, 1);
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 3, 2, 1);
ConcatAttributes attr;
attr.axis = Axis::HEIGHT;
SingleOpModel model({ToString(OperationType::CONCAT), attr}, {input1, input2},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2}));
ASSERT_TRUE(model.PopulateTensor(1, {3, 4, 5, 6}));
ASSERT_OK(model.Invoke(*NewFlatConcatNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6}));
}
TEST(ConcatTest, TwoInputTensorsByWidth) {
TensorRef<BHWC> input1, input2, output;
input1.type = DataType::FLOAT32;
input1.ref = 0;
input1.shape = BHWC(1, 2, 1, 1);
input2.type = DataType::FLOAT32;
input2.ref = 1;
input2.shape = BHWC(1, 2, 2, 1);
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 3, 1);
ConcatAttributes attr;
attr.axis = Axis::WIDTH;
SingleOpModel model({ToString(OperationType::CONCAT), attr}, {input1, input2},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {2, 3, 5, 6}));
ASSERT_OK(model.Invoke(*NewFlatConcatNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,314 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/conv.h"
#include <any>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#include "tensorflow/lite/delegates/gpu/gl/workgroups/ideal_workgroup_picker.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Convolution : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes.size() != 1) {
return absl::UnimplementedError(
"Convolution does not support more than 1 runtime tensor");
}
const auto& attr =
std::any_cast<const Convolution2DAttributes&>(ctx.op_attr);
if (attr.groups != 1) {
return absl::UnimplementedError(
"Convolution does not support more than 1 group");
}
auto weights = attr.weights.shape;
const int offsets_count = weights.h * weights.w;
const bool offsets_count_too_large = offsets_count > kMaxConstArraySize;
std::vector<Variable> parameters;
if (offsets_count_too_large) {
parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"padding_w", attr.padding.prepended.w},
{"padding_h", attr.padding.prepended.h},
{"dilation_w", attr.dilations.w},
{"dilation_h", attr.dilations.h},
{"kernel_w", weights.w},
{"kernel_h", weights.h},
{"src_depth", DivideRoundUp(weights.i, 4)},
{"stride", int2(attr.strides.w, attr.strides.h)},
};
} else {
std::vector<int2> offsets;
for (int h = 0; h < weights.h; ++h) {
for (int w = 0; w < weights.w; ++w) {
offsets.emplace_back(w * attr.dilations.w - attr.padding.prepended.w,
h * attr.dilations.h - attr.padding.prepended.h);
}
}
parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"offsets_count", offsets_count},
{"offsets", offsets},
{"src_depth", DivideRoundUp(weights.i, 4)},
{"stride", int2(attr.strides.w, attr.strides.h)},
};
}
// at least one padding is not empty
bool non_empty_padding =
attr.padding.appended.h != 0 || attr.padding.appended.w != 0 ||
attr.padding.prepended.h != 0 || attr.padding.prepended.w != 0;
std::vector<std::pair<std::string, Object>> objects = {
{"weights", MakeReadonlyObject(Get3DSizeForPHWO4I4(attr.weights.shape),
ConvertToPHWO4I4(attr.weights))}};
std::string source;
if (offsets_count_too_large) {
source = R"(
int i = 0;
for (int ky = 0; ky < $kernel_h$; ky++) {
for (int kx = 0; kx < $kernel_w$; kx++, i++) {
ivec2 coord = gid.xy * $stride$ + ivec2(kx * $dilation_w$ - $padding_w$, ky * $dilation_h$ - $padding_h$);)";
} else {
source = R"(
for (int i = 0; i < $offsets_count$; ++i) {
ivec2 coord = gid.xy * $stride$ + $offsets[i]$;)";
}
if (non_empty_padding) {
source += R"(
if (coord.x < 0 || coord.y < 0 || coord.x >= $input_data_0_w$ || coord.y >= $input_data_0_h$) {
continue;
})";
}
source += R"(
for (int l = 0; l < $src_depth$; ++l) {
vec4 input_ = $input_data_0[coord.x, coord.y, l]$;
value_0.x += dot(input_, $weights[l * 4 + 0, i, gid.z]$);
value_0.y += dot(input_, $weights[l * 4 + 1, i, gid.z]$);
value_0.z += dot(input_, $weights[l * 4 + 2, i, gid.z]$);
value_0.w += dot(input_, $weights[l * 4 + 3, i, gid.z]$);
}
}
)";
if (offsets_count_too_large) {
source += R"(
}
)";
}
if (!attr.bias.data.empty()) {
source += "value_0 += $bias[gid.z]$;\n";
objects.push_back({"bias", MakeReadonlyObject(attr.bias.data)});
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/
GetIdealWorkgroupIfPossible(
*ctx.gpu_info, OperationType::CONVOLUTION_2D,
HW(weights.h, weights.w), attr.strides, uint3(0, 0, 0),
OHWI(weights.o, ctx.input_shapes[0][1], ctx.input_shapes[0][2],
ctx.input_shapes[0][3])),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
int SelectMultiplier(int32_t input_width,
const NodeShader::GenerationContext& ctx) {
std::vector<int> multipliers = {4, 2};
if (ctx.gpu_info->IsAMD()) {
return 1;
}
if (!ctx.compiler_options.allow_precision_loss && ctx.gpu_info->IsMali()) {
multipliers = {2};
}
for (int i : multipliers) {
if (input_width % i == 0) {
return i;
}
}
return 1;
}
class Convolution1x1 : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes.size() != 1) {
return absl::UnimplementedError(
"Convolution does not support more than 1 runtime tensor");
}
const auto& attr =
std::any_cast<const Convolution2DAttributes&>(ctx.op_attr);
if (attr.weights.shape.h != 1 || attr.weights.shape.w != 1) {
return absl::UnimplementedError("Height and width should be 1.");
}
if (attr.dilations.h != 1 || attr.dilations.w != 1) {
return absl::UnimplementedError("Dilations are not supported.");
}
if (attr.strides.h != 1 || attr.strides.w != 1) {
return absl::UnimplementedError("Strides are not supported.");
}
if (attr.padding.appended.h != 0 || attr.padding.appended.w != 0 ||
attr.padding.prepended.h != 0 || attr.padding.prepended.w != 0) {
return absl::UnimplementedError("Padding is not supported.");
}
int multiplier = SelectMultiplier(ctx.input_shapes[0][2], ctx);
std::vector<Variable> parameters = {
{"src_depth",
DivideRoundUp(static_cast<int>(ctx.input_shapes[0][3]), 4)},
};
std::vector<std::pair<std::string, Object>> objects = {
{"weights",
MakeReadonlyObject(uint3(4, DivideRoundUp(attr.weights.shape.i, 4),
DivideRoundUp(attr.weights.shape.o, 4)),
ConvertToPHWO4I4(attr.weights))}};
std::string source;
for (int i = 0; i < multiplier; i++) {
absl::StrAppend(&source, "highp vec4 result", i, " = vec4(0);\n");
}
absl::StrAppend(&source, "vec4 f;\n");
absl::StrAppend(&source, "for (int l = 0; l < $src_depth$; ++l) {\n");
for (int i = 0; i < multiplier; i++) {
absl::StrAppend(&source, " vec4 input", i, " = $input_data_0[gid.x * ",
multiplier, " + ", i, ",gid.y,l]$;\n");
}
for (int k = 0; k < 4; k++) {
absl::StrAppend(&source, " f = $weights[", k, ", l, gid.z]$;\n");
for (int i = 0; i < multiplier; i++) {
absl::StrAppend(&source, " result", i, "[", k, "] += dot(input", i,
", f);\n");
}
}
absl::StrAppend(&source, "}\n");
if (!attr.bias.data.empty()) {
objects.push_back({"bias", MakeReadonlyObject(attr.bias.data)});
absl::StrAppend(&source, "vec4 b = $bias[gid.z]$;\n");
for (int i = 0; i < multiplier; i++) {
absl::StrAppend(&source, "result", i, " += b;\n");
}
}
if (multiplier != 1) {
for (int i = 0; i < multiplier; i++) {
absl::StrAppend(&source, "$inplace_update:result", i, "$\n");
absl::StrAppend(&source, "$output_data_0[gid.x * ", multiplier, " + ",
i, ",gid.y,gid.z] = result", i, "$;\n");
}
} else {
absl::StrAppend(&source, "value_0 = result0;\n");
}
auto dst_depth = DivideRoundUp(ctx.output_shapes[0][3], 4);
uint3 workgroup = uint3(16, 16, 1);
if (ctx.gpu_info->IsAdreno()) {
if (dst_depth >= 2) {
workgroup = uint3(8, 8, 2);
}
if (dst_depth >= 4) {
workgroup = uint3(4, 8, 4);
}
if (dst_depth >= 8) {
workgroup = uint3(4, 4, 8);
}
if (dst_depth >= 32) {
workgroup = uint3(4, 4, 16);
}
if (dst_depth >= 64) {
workgroup = uint3(2, 8, 16);
}
} else {
if (dst_depth >= 2) {
workgroup = uint3(16, 8, 2);
}
if (dst_depth >= 4) {
workgroup = uint3(16, 4, 4);
}
if (dst_depth >= 8) {
workgroup = uint3(8, 4, 8);
}
if (dst_depth >= 32) {
workgroup = uint3(8, 4, 8);
}
if (dst_depth >= 64) {
workgroup = uint3(8, 4, 8);
}
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/{},
/*workload=*/
uint3(ctx.output_shapes[0][2] / multiplier, ctx.output_shapes[0][1],
DivideRoundUp(ctx.output_shapes[0][3], 4)),
/*workgroup=*/
GetIdealWorkgroupIfPossible(
*ctx.gpu_info, OperationType::CONVOLUTION_2D,
HW(attr.weights.shape.h, attr.weights.shape.w), attr.strides,
workgroup,
OHWI(attr.weights.shape.o, ctx.input_shapes[0][1],
ctx.input_shapes[0][2], ctx.input_shapes[0][3])),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/multiplier == 1 ? IOStructure::AUTO
: IOStructure::ONLY_DEFINITIONS,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewConvolutionNodeShader() {
return std::make_unique<Convolution>();
}
std::unique_ptr<NodeShader> NewConvolution1x1NodeShader() {
return std::make_unique<Convolution1x1>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,37 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONV_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONV_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewConvolutionNodeShader();
// Specialization for 1x1 convolutions.
std::unique_ptr<NodeShader> NewConvolution1x1NodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONV_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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/conv.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(ConvTest, O2H2W1I1Stride1x1Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
Convolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data = {1, 1};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 2, 1, 1);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(1, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_2D), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {4, 8, 4, 8, 2, 4, 2, 4}));
}
TEST(ConvTest, O1H2W2I1Stride1x1Dilation2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
Convolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data.push_back(0.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 2, 2, 1);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.dilations = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 1, 1, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_2D), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1, 1, 1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {10}));
}
TEST(ConvTest, O1H3W3I1Stride1x1Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
Convolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 1;
bias.id = 1;
bias.data.push_back(1.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 3, 3, 1);
weights.id = 2;
weights.data = {1, 2, 3, 1, 2, 3, 1, 2, 3};
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(1, 1);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 1, 1, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_2D), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {11}));
}
TEST(ConvTest, O2H1W1I2Stride1x1Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 1, 2);
Convolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data = {1, 1};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 1, 1, 2);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 2, 1, 2);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_2D), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolution1x1NodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {4, 8, 4, 8}));
}
TEST(ConvTest, O1H1W1I1Stride2x2Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
Convolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data.push_back(0.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 1, 1, 1);
weights.id = 2;
weights.data.push_back(2.0);
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 2, 2, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_2D), std::move(attr)}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 0, 2, 0, 0, 0, 4, 0, 8}));
ASSERT_OK(model.Invoke(*NewConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4, 8, 16}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,403 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/converter.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Wraps given SSBO into GlBuffer object that does not have ownership.
absl::Status WrapSSBO(OpenGlBuffer ssbo, GlBuffer* buffer) {
int64_t size_bytes;
RETURN_IF_ERROR(GetSSBOSize(ssbo.id, &size_bytes));
*buffer = GlBuffer(GL_SHADER_STORAGE_BUFFER, ssbo.id, size_bytes, 0, false);
return absl::OkStatus();
}
std::string GetShaderHeader(const uint3& localsize) {
return absl::StrCat("#version 310 es\nlayout(local_size_x = ", localsize.x,
", local_size_y = ", localsize.y,
", local_size_z = ", localsize.z, ") in;\n");
}
class OpenGlConverterImpl : public TensorObjectConverter {
public:
explicit OpenGlConverterImpl(CommandQueue* command_queue)
: command_queue_(command_queue) {}
virtual absl::Status Init(const TensorObjectDef& input_def,
const TensorObjectDef& output_def) = 0;
protected:
absl::Status InitializeProgram(const uint3& workgroup_size,
const std::string& shader_source) {
workgroup_size_ = workgroup_size;
GlShader shader;
RETURN_IF_ERROR(GlShader::CompileShader(
GL_COMPUTE_SHADER, GetShaderHeader(workgroup_size) + shader_source,
&shader));
return GlProgram::CreateWithShader(shader, &program_);
}
absl::Status Dispatch(const uint3& workload) {
uint3 num_workgroups = DivideRoundUp(workload, workgroup_size_);
if (command_queue_) {
return command_queue_->Dispatch(program_, num_workgroups);
}
return program_.Dispatch(num_workgroups);
}
GlProgram program_;
uint3 workgroup_size_;
CommandQueue* command_queue_;
};
bool IsSupportedDataType(DataType type) { return type == DataType::FLOAT32; }
uint32_t SizeInBytesDHWC4(const BHWC& shape) {
return shape.b * shape.h * shape.w * AlignByN(shape.c, 4) * sizeof(float);
}
uint32_t SizeInBytesBHWC(const BHWC& shape) {
return shape.DimensionsProduct() * sizeof(float);
}
// Implements conversion from OpenGL-specific tensor layout to BHWC.
class FromTensorConverter : public OpenGlConverterImpl {
public:
explicit FromTensorConverter(CommandQueue* command_queue)
: OpenGlConverterImpl(command_queue) {}
static bool IsSupported(const ObjectDef& input, const ObjectDef& output) {
return IsSupportedDataType(input.data_type) &&
IsSupportedDataType(output.data_type) &&
// Output is always SSBO/BHWC
output.object_type == ObjectType::OPENGL_SSBO &&
output.data_layout == DataLayout::BHWC &&
// SSBO/DHWC4 ->
input.object_type == ObjectType::OPENGL_SSBO &&
input.data_layout == DataLayout::DHWC4;
}
absl::Status Init(const TensorObjectDef& input_def,
const TensorObjectDef& output_def) final {
shape_ = BHWC(output_def.dimensions.b, output_def.dimensions.h,
output_def.dimensions.w, output_def.dimensions.c);
if (shape_.b != 1) {
return absl::UnimplementedError(
"FromTensorConverter: Batch size != 1 is not supported.");
}
return InitializeProgram(uint3(8, 4, 2), R"(
layout(std430) buffer;
precision highp float;
layout(binding = 0) readonly buffer B0 {
vec4 elements[];
} input_data;
layout(binding = 1) writeonly buffer B1 {
float elements[];
} output_data;
uniform ivec4 sizes;
void main() {
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
if (gid.x >= sizes.x || gid.y >= sizes.y || gid.z >= sizes.z) {
return;
}
output_data.elements[(gid.y * sizes.x + gid.x) * sizes.z + gid.z] = input_data.elements[(gid.z / 4 * sizes.y + gid.y) * sizes.x + gid.x][gid.z % 4];
})");
}
absl::Status Convert(const TensorObject& input_obj,
const TensorObject& output_obj) override {
auto output = std::get_if<OpenGlBuffer>(&output_obj);
if (!output || !output->id) {
return absl::InvalidArgumentError("Missing output in converter");
}
auto input = std::get_if<OpenGlBuffer>(&input_obj);
if (!input || !input->id) {
return absl::InvalidArgumentError("Missing input in converter");
}
if (input->id == output->id) {
return absl::InvalidArgumentError("Can not execute inplace conversion");
}
GlBuffer input_ssbo;
RETURN_IF_ERROR(WrapSSBO(*input, &input_ssbo));
GlBuffer output_ssbo;
RETURN_IF_ERROR(WrapSSBO(*output, &output_ssbo));
if (input_ssbo.bytes_size() != SizeInBytesDHWC4(shape_)) {
return absl::InvalidArgumentError(
"FromTensorConverter: input data size does not match expected size.");
}
if (output_ssbo.bytes_size() != SizeInBytesBHWC(shape_)) {
return absl::InvalidArgumentError(
"FromTensorConverter: output data size does not match expected "
"size.");
}
RETURN_IF_ERROR(program_.SetParameter(
{"sizes",
int4(static_cast<int32_t>(shape_.w), static_cast<int32_t>(shape_.h),
static_cast<int32_t>(shape_.c), 0)}));
RETURN_IF_ERROR(input_ssbo.BindToIndex(0));
RETURN_IF_ERROR(output_ssbo.BindToIndex(1));
return Dispatch(uint3(shape_.w, shape_.h, shape_.c));
}
BHWC shape_;
};
// Implements conversion from BHWC to OpenGL-specific tensor layout.
class ToTensorConverter : public OpenGlConverterImpl {
public:
explicit ToTensorConverter(CommandQueue* command_queue)
: OpenGlConverterImpl(command_queue) {}
static bool IsSupported(const ObjectDef& input, const ObjectDef& output) {
return IsSupportedDataType(input.data_type) &&
IsSupportedDataType(output.data_type) &&
// Input is always SSBO/BHWC
input.object_type == ObjectType::OPENGL_SSBO &&
input.data_layout == DataLayout::BHWC &&
// -> SSBO/DHWC4
output.object_type == ObjectType::OPENGL_SSBO &&
output.data_layout == DataLayout::DHWC4;
}
absl::Status Init(const TensorObjectDef& input_def,
const TensorObjectDef& output_def) final {
shape_ = BHWC(output_def.dimensions.b, output_def.dimensions.h,
output_def.dimensions.w, output_def.dimensions.c);
if (shape_.b != 1) {
return absl::UnimplementedError(
"ToTensorConverter: Batch size != 1 is not supported.");
}
return InitializeProgram(uint3(8, 4, 2), R"(
layout(std430) buffer;
precision highp float;
layout(binding = 0) readonly buffer B0 {
float elements[];
} input_data;
layout(binding = 1) writeonly buffer B1 {
vec4 elements[];
} output_data;
uniform ivec4 sizes;
void main() {
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
if (gid.x >= sizes.x || gid.y >= sizes.y || gid.z >= sizes.w) {
return;
}
vec4 v = vec4(0);
int dst_channel = gid.z * 4;
int index = (gid.y * sizes.x + gid.x) * sizes.z + dst_channel;
for (int i = 0; i < 4; ++i, ++index, ++dst_channel) {
if (dst_channel >= sizes.z) break;
v[i] = input_data.elements[index];
}
output_data.elements[(gid.z * sizes.y + gid.y) * sizes.x + gid.x] = v;
})");
}
absl::Status Convert(const TensorObject& input_obj,
const TensorObject& output_obj) override {
auto output = std::get_if<OpenGlBuffer>(&output_obj);
if (!output || !output->id) {
return absl::InvalidArgumentError("Missing output in converter");
}
auto input = std::get_if<OpenGlBuffer>(&input_obj);
if (!input || !input->id) {
return absl::InvalidArgumentError("Missing input in converter");
}
if (input->id == output->id) {
return absl::InvalidArgumentError("Can not execute inplace conversion");
}
GlBuffer input_ssbo;
RETURN_IF_ERROR(WrapSSBO(*input, &input_ssbo));
GlBuffer output_ssbo;
RETURN_IF_ERROR(WrapSSBO(*output, &output_ssbo));
if (input_ssbo.bytes_size() != SizeInBytesBHWC(shape_)) {
return absl::InvalidArgumentError(
"ToTensorConverter: input data size does not match expected size.");
}
if (output_ssbo.bytes_size() != SizeInBytesDHWC4(shape_)) {
return absl::InvalidArgumentError(
"ToTensorConverter: output data size does not match expected size.");
}
auto d = DivideRoundUp(shape_.c, 4);
RETURN_IF_ERROR(program_.SetParameter(
{"sizes",
int4(static_cast<int32_t>(shape_.w), static_cast<int32_t>(shape_.h),
static_cast<int32_t>(shape_.c), static_cast<int32_t>(d))}));
RETURN_IF_ERROR(input_ssbo.BindToIndex(0));
RETURN_IF_ERROR(output_ssbo.BindToIndex(1));
return Dispatch(uint3(shape_.w, shape_.h, d));
}
BHWC shape_;
};
// Copies data from one object of the same type and layout to another object.
class TrivialCopier : public TensorObjectConverter {
public:
static bool IsSupported(const ObjectDef& input, const ObjectDef& output) {
return input.object_type == ObjectType::OPENGL_SSBO &&
input.data_type == output.data_type &&
input.object_type == output.object_type &&
input.data_layout == output.data_layout;
}
absl::Status Convert(const TensorObject& input_obj,
const TensorObject& output_obj) override {
auto ssbo_input = std::get_if<OpenGlBuffer>(&input_obj);
auto ssbo_output = std::get_if<OpenGlBuffer>(&output_obj);
if (ssbo_input && ssbo_output) {
return Copy(*ssbo_input, *ssbo_output);
}
return absl::InternalError("Unexpected object");
}
absl::Status Copy(OpenGlBuffer input, OpenGlBuffer output) {
if (input.id == output.id) {
return absl::OkStatus();
}
GlBuffer input_obj;
RETURN_IF_ERROR(WrapSSBO(input, &input_obj));
GlBuffer output_obj;
RETURN_IF_ERROR(WrapSSBO(output, &output_obj));
return CopyBuffer(input_obj, output_obj);
}
};
// Copies data from/to CPU into a tensor.
class CpuCopier : public TensorObjectConverter {
public:
static bool IsSupported(const ObjectDef& input, const ObjectDef& output) {
return input.data_type == output.data_type &&
input.data_layout == output.data_layout &&
((input.object_type == ObjectType::CPU_MEMORY &&
output.object_type == ObjectType::OPENGL_SSBO) ||
(output.object_type == ObjectType::CPU_MEMORY &&
input.object_type == ObjectType::OPENGL_SSBO));
}
absl::Status Convert(const TensorObject& input_obj,
const TensorObject& output_obj) override {
auto cpu_input = std::get_if<CpuMemory>(&input_obj);
auto cpu_output = std::get_if<CpuMemory>(&output_obj);
if (cpu_input) {
auto ssbo_output = std::get_if<OpenGlBuffer>(&output_obj);
if (ssbo_output) {
GlBuffer gl_buffer;
RETURN_IF_ERROR(WrapSSBO(*ssbo_output, &gl_buffer));
return gl_buffer.Write(
absl::MakeConstSpan(static_cast<const uint8_t*>(cpu_input->data),
cpu_input->size_bytes));
}
} else if (cpu_output) {
auto ssbo_input = std::get_if<OpenGlBuffer>(&input_obj);
if (ssbo_input) {
GlBuffer gl_buffer;
RETURN_IF_ERROR(WrapSSBO(*ssbo_input, &gl_buffer));
return gl_buffer.Read(absl::MakeSpan(
static_cast<uint8_t*>(cpu_output->data), cpu_output->size_bytes));
}
}
return absl::InternalError("Unexpected object");
}
};
class TensorConverterBuilderImpl : public TensorObjectConverterBuilder {
public:
explicit TensorConverterBuilderImpl(CommandQueue* command_queue)
: command_queue_(command_queue) {}
bool IsSupported(const TensorObjectDef& input,
const TensorObjectDef& output) const final {
const auto& input_def = input.object_def;
const auto& output_def = output.object_def;
return input.dimensions == output.dimensions &&
(TrivialCopier::IsSupported(input_def, output_def) ||
CpuCopier::IsSupported(input_def, output_def) ||
FromTensorConverter::IsSupported(input_def, output_def) ||
ToTensorConverter::IsSupported(input_def, output_def));
}
absl::Status MakeConverter(
const TensorObjectDef& input, const TensorObjectDef& output,
std::unique_ptr<TensorObjectConverter>* converter) final {
std::unique_ptr<OpenGlConverterImpl> impl;
const auto& input_def = input.object_def;
const auto& output_def = output.object_def;
if (TrivialCopier::IsSupported(input_def, output_def)) {
*converter = std::make_unique<TrivialCopier>();
return absl::OkStatus();
}
if (CpuCopier::IsSupported(input_def, output_def)) {
*converter = std::make_unique<CpuCopier>();
return absl::OkStatus();
}
if (FromTensorConverter::IsSupported(input_def, output_def)) {
impl = std::make_unique<FromTensorConverter>(command_queue_);
} else if (ToTensorConverter::IsSupported(input_def, output_def)) {
impl = std::make_unique<ToTensorConverter>(command_queue_);
} else {
return absl::UnimplementedError("Unsupported conversion");
}
RETURN_IF_ERROR(impl->Init(input, output));
*converter = std::move(impl);
return absl::OkStatus();
}
private:
CommandQueue* command_queue_;
};
} // namespace
std::unique_ptr<TensorObjectConverterBuilder> NewConverterBuilder(
CommandQueue* command_queue) {
return std::make_unique<TensorConverterBuilderImpl>(command_queue);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,37 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONVERTER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONVERTER_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/command_queue.h"
#include "tensorflow/lite/delegates/gpu/spi.h"
namespace tflite {
namespace gpu {
namespace gl {
// Supports conversions from DHWC4 to internal OpenGL tensor representation and
// back. Supports F32 only.
std::unique_ptr<TensorObjectConverterBuilder> NewConverterBuilder(
CommandQueue* command_queue /* optional */);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CONVERTER_H_
@@ -0,0 +1,166 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/converter.h"
#include <cstdint>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_environment.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
inline std::vector<float> GenerateFloats(float multiplier, int size) {
std::vector<float> v(size);
for (int i = 0; i < size; ++i) {
v[i] = multiplier * i * (i % 2 == 0 ? -1 : 1);
}
return v;
}
Dimensions ToDimensions(const BHWC& shape) {
return Dimensions(shape.b, shape.h, shape.w, shape.c);
}
absl::Status RunFromTensorTest(const BHWC& shape) {
// Create random input and calculate expected output for it.
std::vector<float> input =
GenerateFloats(0.01, GetElementsSizeForPHWC4(shape));
std::vector<float> output(shape.DimensionsProduct(), 0);
RETURN_IF_ERROR(
ConvertFromPHWC4(absl::MakeConstSpan(input.data(), input.size()), shape,
absl::MakeSpan(output.data(), output.size())));
std::unique_ptr<EglEnvironment> env;
RETURN_IF_ERROR(EglEnvironment::NewEglEnvironment(&env));
// Create input and output buffers
GlBuffer input_buffer;
RETURN_IF_ERROR(CreateReadOnlyShaderStorageBuffer(
absl::MakeConstSpan(input.data(), input.size()), &input_buffer));
GlBuffer output_buffer;
RETURN_IF_ERROR(CreateReadWriteShaderStorageBuffer<float>(
shape.DimensionsProduct(), &output_buffer));
// Create converter and run it.
auto builder = NewConverterBuilder(nullptr);
TensorObjectDef input_def;
input_def.object_def.data_type = DataType::FLOAT32;
input_def.object_def.data_layout = DataLayout::DHWC4;
input_def.object_def.object_type = ObjectType::OPENGL_SSBO;
input_def.dimensions = ToDimensions(shape);
TensorObjectDef output_def = input_def;
output_def.object_def.data_layout = DataLayout::BHWC;
std::unique_ptr<TensorObjectConverter> converter;
RETURN_IF_ERROR(builder->MakeConverter(input_def, output_def, &converter));
RETURN_IF_ERROR(converter->Convert(OpenGlBuffer{input_buffer.id()},
OpenGlBuffer{output_buffer.id()}));
// Compare outputs.
std::vector<float> converted_output(output.size(), 0);
RETURN_IF_ERROR(output_buffer.Read(
absl::MakeSpan(converted_output.data(), converted_output.size())));
if (output != converted_output) {
return absl::InternalError("Outputs don't match");
}
return absl::OkStatus();
}
TEST(FromTensor, Smoke) {
for (int32_t h : {1, 2, 3, 7, 20}) {
for (int32_t w : {1, 2, 4, 5, 11}) {
for (int32_t c : {1, 2, 4, 5, 8, 9}) {
BHWC shape(1, h, w, c);
auto status = RunFromTensorTest(shape);
EXPECT_TRUE(status.ok()) << status << ", shape = " << shape.h << " "
<< shape.w << " " << shape.c;
}
}
}
}
absl::Status RunToTensorTest(const BHWC& shape) {
// Create random input and calculate expected output for it.
std::vector<float> input = GenerateFloats(0.01, shape.DimensionsProduct());
std::vector<float> output(GetElementsSizeForPHWC4(shape), 0);
RETURN_IF_ERROR(
ConvertToPHWC4(absl::MakeConstSpan(input.data(), input.size()), shape,
absl::MakeSpan(output.data(), output.size())));
std::unique_ptr<EglEnvironment> env;
RETURN_IF_ERROR(EglEnvironment::NewEglEnvironment(&env));
// Create input and output buffers
GlBuffer input_buffer;
RETURN_IF_ERROR(CreateReadOnlyShaderStorageBuffer(
absl::MakeConstSpan(input.data(), input.size()), &input_buffer));
GlBuffer output_buffer;
RETURN_IF_ERROR(CreateReadWriteShaderStorageBuffer<float>(
GetElementsSizeForPHWC4(shape), &output_buffer));
// Create converter and run it.
auto builder = NewConverterBuilder(nullptr);
TensorObjectDef input_def;
input_def.object_def.data_type = DataType::FLOAT32;
input_def.object_def.data_layout = DataLayout::BHWC;
input_def.object_def.object_type = ObjectType::OPENGL_SSBO;
input_def.dimensions = ToDimensions(shape);
TensorObjectDef output_def = input_def;
output_def.object_def.data_layout = DataLayout::DHWC4;
std::unique_ptr<TensorObjectConverter> converter;
RETURN_IF_ERROR(builder->MakeConverter(input_def, output_def, &converter));
RETURN_IF_ERROR(converter->Convert(OpenGlBuffer{input_buffer.id()},
OpenGlBuffer{output_buffer.id()}));
// Compare outputs.
std::vector<float> converted_output(output.size(), 0);
RETURN_IF_ERROR(output_buffer.Read(
absl::MakeSpan(converted_output.data(), converted_output.size())));
if (output != converted_output) {
return absl::InternalError("Outputs don't match");
}
return absl::OkStatus();
}
TEST(ToTensor, Smoke) {
for (int32_t h : {1, 2, 3, 7, 20}) {
for (int32_t w : {1, 2, 4, 5, 11}) {
for (int32_t c : {1, 2, 4, 5, 8, 9}) {
BHWC shape(1, h, w, c);
auto status = RunToTensorTest(shape);
EXPECT_TRUE(status.ok()) << status << ", shape = " << shape.h << " "
<< shape.w << " " << shape.c;
}
}
}
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/custom_registry.h"
#include <memory>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
namespace tflite {
namespace gpu {
namespace gl {
void RegisterCustomOps(
absl::flat_hash_map<std::string, std::vector<std::unique_ptr<NodeShader>>>*
shaders) {}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,39 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CUSTOM_REGISTRY_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CUSTOM_REGISTRY_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
// Registers custom operations.
void RegisterCustomOps(
absl::flat_hash_map<std::string, std::vector<std::unique_ptr<NodeShader>>>*
shaders_);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_CUSTOM_REGISTRY_H_
@@ -0,0 +1,163 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/depthwise_conv.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#include "tensorflow/lite/delegates/gpu/gl/workgroups/ideal_workgroup_picker.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class DepthwiseConvolution : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes.size() != 1) {
return absl::UnimplementedError(
"DepthWise Convolution does not support more than 1 runtime tensor");
}
const auto& attr =
std::any_cast<const DepthwiseConvolution2DAttributes&>(ctx.op_attr);
auto weights = attr.weights.shape;
const int offsets_count = weights.h * weights.w;
const bool offsets_count_too_large = offsets_count > kMaxConstArraySize;
std::vector<Variable> parameters;
if (offsets_count_too_large) {
parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"padding_w", attr.padding.prepended.w},
{"padding_h", attr.padding.prepended.h},
{"dilation_w", attr.dilations.w},
{"dilation_h", attr.dilations.h},
{"kernel_w", weights.w},
{"kernel_h", weights.h},
{"src_depth", DivideRoundUp(weights.i, 4)},
{"channel_multiplier", weights.o},
{"stride", int2(attr.strides.w, attr.strides.h)},
};
} else {
std::vector<int2> offsets;
for (int h = 0; h < weights.h; ++h) {
for (int w = 0; w < weights.w; ++w) {
offsets.emplace_back(w * attr.dilations.w - attr.padding.prepended.w,
h * attr.dilations.h - attr.padding.prepended.h);
}
}
parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"offsets_count", offsets_count},
{"offsets", offsets},
{"src_depth", DivideRoundUp(weights.i, 4)},
{"channel_multiplier", weights.o},
{"stride", int2(attr.strides.w, attr.strides.h)},
};
}
bool non_empty_padding =
attr.padding.appended.h != 0 || attr.padding.appended.w != 0 ||
attr.padding.prepended.h != 0 || attr.padding.prepended.w != 0;
std::vector<std::pair<std::string, Object>> objects = {
{"weights", MakeReadonlyObject(ConvertToPIOHW4(attr.weights))}};
std::string source;
if (offsets_count_too_large) {
source = R"(
int offsets_count = $kernel_w$ * $kernel_h$;
int src_layer_offset = (gid.z % $channel_multiplier$) * 4;
int i = 0;
for (int ky = 0; ky < $kernel_h$; ky++) {
for (int kx = 0; kx < $kernel_w$; kx++, i++) {
ivec2 coord = gid.xy * $stride$ + ivec2(kx * $dilation_w$ - $padding_w$, ky * $dilation_h$ - $padding_h$);)";
} else {
source = R"(
int offsets_count = $offsets_count$;
int src_layer_offset = (gid.z % $channel_multiplier$) * 4;
for (int i = 0; i < offsets_count; ++i) {
ivec2 coord = gid.xy * $stride$ + $offsets[i]$;)";
}
if (non_empty_padding) {
source += R"(
if (coord.x < 0 || coord.y < 0 ||
coord.x >= $input_data_0_w$ || coord.y >= $input_data_0_h$) {
continue;
})";
}
source += R"(
int src_layer = gid.z / $channel_multiplier$;
vec4 input_ = $input_data_0[coord.x, coord.y, src_layer]$;
vec4 input_shifted = vec4(
input_[(src_layer_offset + 0) / $channel_multiplier$],
input_[(src_layer_offset + 1) / $channel_multiplier$],
input_[(src_layer_offset + 2) / $channel_multiplier$],
input_[(src_layer_offset + 3) / $channel_multiplier$]
);
value_0 += input_shifted * $weights[gid.z * offsets_count + i]$;
}
)";
if (offsets_count_too_large) {
source += R"(
}
)";
}
if (!attr.bias.data.empty()) {
source += "value_0 += $bias[gid.z]$;\n";
objects.push_back({"bias", MakeReadonlyObject(attr.bias.data)});
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/
GetIdealWorkgroupIfPossible(
*ctx.gpu_info, OperationType::DEPTHWISE_CONVOLUTION,
HW(attr.weights.shape.h, attr.weights.shape.w), attr.strides,
OHWI(attr.weights.shape.o, ctx.input_shapes[0][1],
ctx.input_shapes[0][2], ctx.input_shapes[0][3])),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewDepthwiseConvolutionNodeShader() {
return std::make_unique<DepthwiseConvolution>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_DEPTHWISE_CONV_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_DEPTHWISE_CONV_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewDepthwiseConvolutionNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_DEPTHWISE_CONV_H_
@@ -0,0 +1,152 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/depthwise_conv.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(DepthwiseConvTest, O4H1W1I2Strides1x1Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 2);
DepthwiseConvolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 4;
bias.id = 1;
bias.data = {1, 2, 3, 4};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 1, 1, 2);
weights.id = 2;
weights.data = {1, 3, 2, 4};
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 1, 1, 4);
SingleOpModel model(
{ToString(OperationType::DEPTHWISE_CONVOLUTION), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 3}));
ASSERT_OK(model.Invoke(*NewDepthwiseConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4, 12, 16}));
}
TEST(DepthwiseConvTest, O2H1W1I1Strides2x2Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
DepthwiseConvolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 4;
bias.id = 1;
bias.data = {0, 0};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 1, 1, 1);
weights.id = 1;
weights.data = {1, 3};
attr.weights = std::move(weights);
attr.dilations = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model(
{ToString(OperationType::DEPTHWISE_CONVOLUTION), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 0, 1, 1, 0, 1, 1, 0, 1}));
ASSERT_OK(model.Invoke(*NewDepthwiseConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 3, 1, 3, 1, 3, 1, 3}));
}
TEST(DepthwiseConvTest, O2H2W2I1Strides1x1Dilation2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
DepthwiseConvolution2DAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 4;
bias.id = 1;
bias.data = {0, 0};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 2, 2, 1);
weights.id = 1;
weights.data = {1, 2, 3, 4, 5, 6, 7, 8};
attr.weights = std::move(weights);
attr.dilations = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 1, 1, 2);
SingleOpModel model(
{ToString(OperationType::DEPTHWISE_CONVOLUTION), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 0, 1, 1, 0, 1, 1, 0, 1}));
ASSERT_OK(model.Invoke(*NewDepthwiseConvolutionNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {10, 26}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,293 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/elementwise.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/substitute.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class ElementwiseOneArgument : public NodeShader {
public:
explicit ElementwiseOneArgument(OperationType operation_type)
: operation_type_(operation_type) {}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string source;
switch (operation_type_) {
case OperationType::ABS:
source = "value_0 = abs(value_0);";
break;
case OperationType::COS:
source = "value_0 = cos(value_0);";
break;
case OperationType::COPY:
source = "value_0 = value_0;";
break;
case OperationType::ELU:
source = R"(
value_0.x = value_0.x < 0.0 ? exp(value_0.x) - 1.0 : value_0.x;
value_0.y = value_0.y < 0.0 ? exp(value_0.y) - 1.0 : value_0.y;
value_0.z = value_0.z < 0.0 ? exp(value_0.z) - 1.0 : value_0.z;
value_0.w = value_0.w < 0.0 ? exp(value_0.w) - 1.0 : value_0.w;
)";
break;
case OperationType::EXP:
source = "value_0 = exp(value_0);";
break;
case tflite::gpu::OperationType::FLOOR:
source = "value_0 = floor(value_0);";
break;
case tflite::gpu::OperationType::GELU:
// Matches the approximate implementation of the cpu reference op.
// There's no gpu implementation of erfc so we can't match the accurate
// version.
// gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
source =
"value_0 = 0.5 * value_0 * (1.0 + tanh(0.7978845608 * (value_0 + "
"0.044715 * value_0 * value_0 * value_0)));";
break;
case OperationType::HARD_SWISH:
source =
"value_0 *= clamp(value_0 / 6.0 + vec4(0.5), vec4(0.0), "
"vec4(1.0));";
break;
case OperationType::LOG:
source = R"(
const float nan = normalize(vec4(0, 0, 0, 0)).x;
value_0.x = value_0.x > 0.0 ? log(value_0.x) : nan;
value_0.y = value_0.y > 0.0 ? log(value_0.y) : nan;
value_0.z = value_0.z > 0.0 ? log(value_0.z) : nan;
value_0.w = value_0.w > 0.0 ? log(value_0.w) : nan;
)";
break;
case OperationType::NEG:
source = "value_0 = -(value_0);";
break;
case OperationType::RSQRT:
source = R"(
const float nan = normalize(vec4(0, 0, 0, 0)).x;
value_0.x = value_0.x > 0.0 ? 1.0 / sqrt(value_0.x) : nan;
value_0.y = value_0.y > 0.0 ? 1.0 / sqrt(value_0.y) : nan;
value_0.z = value_0.z > 0.0 ? 1.0 / sqrt(value_0.z) : nan;
value_0.w = value_0.w > 0.0 ? 1.0 / sqrt(value_0.w) : nan;
)";
break;
case OperationType::SIGMOID:
source = "value_0 = 1.0 / (1.0 + exp(-1.0 * value_0));";
break;
case OperationType::SIN:
source = "value_0 = sin(value_0);";
break;
case OperationType::SQRT:
source = R"(
const float nan = normalize(vec4(0, 0, 0, 0)).x;
value_0.x = value_0.x >= 0.0 ? sqrt(value_0.x) : nan;
value_0.y = value_0.y >= 0.0 ? sqrt(value_0.y) : nan;
value_0.z = value_0.z >= 0.0 ? sqrt(value_0.z) : nan;
value_0.w = value_0.w >= 0.0 ? sqrt(value_0.w) : nan;
)";
break;
case OperationType::SQUARE:
source = "value_0 = value_0 * value_0;";
break;
case OperationType::TANH:
source = "value_0 = tanh(value_0);";
break;
default:
return absl::InvalidArgumentError(
"Incorrect elementwise operation type.");
}
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
source,
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
private:
OperationType operation_type_;
};
class ElementwiseTwoArguments : public NodeShader {
public:
explicit ElementwiseTwoArguments(OperationType operation_type)
: operation_type_(operation_type) {}
inline bool IsElementwiseSupported(const GenerationContext& ctx) const {
return ctx.input_shapes.size() == 2 &&
ctx.input_shapes[0] == ctx.input_shapes[1];
}
inline bool IsBroadcastSupported(const GenerationContext& ctx) const {
return ctx.input_shapes.size() == 2 && ctx.input_shapes[1][1] == 1 &&
ctx.input_shapes[1][2] == 1 &&
ctx.input_shapes[0][3] == ctx.input_shapes[1][3];
}
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::vector<Variable> parameters;
std::vector<std::pair<std::string, Object>> objects;
std::string argument0, argument1;
if (IsElementwiseSupported(ctx)) {
argument0 = "value_0";
argument1 = "value_1";
} else if (IsBroadcastSupported(ctx)) {
argument0 = "$input_data_0[gid.x, gid.y, gid.z]$";
argument1 = "$input_data_1[0, 0, gid.z]$";
} else { // Scalar of const vector case
const auto& attr =
std::any_cast<const ElementwiseAttributes&>(ctx.op_attr);
const auto* tensor =
std::get_if<Tensor<Linear, DataType::FLOAT32>>(&attr.param);
const auto* scalar = std::get_if<float>(&attr.param);
if (!tensor && !scalar) {
return absl::InvalidArgumentError(
"Couldn't read scalar of const vector data from the attributes.");
}
argument0 = "value_0";
if (tensor) {
argument1 = "$const_data[gid.z]$";
objects.push_back({"const_data", MakeReadonlyObject(tensor->data)});
} else {
argument1 = "vec4($const_data$)";
parameters.push_back({"const_data", *scalar});
}
if (attr.runtime_tensor_is_second) {
argument0 = argument1;
argument1 = "value_0";
}
}
std::string source;
switch (operation_type_) {
case OperationType::DIV: {
source = "value_0 = $0/$1;";
break;
}
case tflite::gpu::OperationType::FLOOR_DIV:
source = "value_0 = floor($0 / $1);";
break;
case tflite::gpu::OperationType::FLOOR_MOD:
source = "value_0 = $0 - floor($0 / $1) * $1;";
break;
case OperationType::MAXIMUM: {
source = "value_0 = max($0, $1);";
break;
}
case OperationType::MINIMUM: {
source = "value_0 = min($0, $1);";
break;
}
case OperationType::SQUARED_DIFF: {
source = "value_0 = ($0 - $1) * ($0 - $1);";
break;
}
case OperationType::SUB: {
source = "value_0 = $0 - $1;";
break;
}
case OperationType::POW: {
source = "value_0 = pow($0, $1);";
break;
}
default:
return absl::InvalidArgumentError(
"Incorrect elementwise with scalar operation type.");
}
source = absl::Substitute(source, argument0, argument1);
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/source,
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
private:
OperationType operation_type_;
};
} // namespace
std::unique_ptr<NodeShader> NewElementwiseNodeShader(
OperationType operation_type) {
switch (operation_type) {
case OperationType::ABS:
case OperationType::COS:
case OperationType::COPY:
case OperationType::ELU:
case OperationType::EXP:
case OperationType::FLOOR:
case OperationType::GELU:
case OperationType::HARD_SWISH:
case OperationType::LOG:
case OperationType::NEG:
case OperationType::RSQRT:
case OperationType::SIGMOID:
case OperationType::SIN:
case OperationType::SQRT:
case OperationType::SQUARE:
case OperationType::TANH:
return std::make_unique<ElementwiseOneArgument>(operation_type);
case OperationType::DIV:
case OperationType::FLOOR_DIV:
case OperationType::FLOOR_MOD:
case OperationType::MAXIMUM:
case OperationType::MINIMUM:
case OperationType::POW:
case OperationType::SQUARED_DIFF:
case OperationType::SUB:
return std::make_unique<ElementwiseTwoArguments>(operation_type);
default:
return nullptr;
}
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,35 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ELEMENTWISE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ELEMENTWISE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewElementwiseNodeShader(
OperationType operation_type);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_ELEMENTWISE_H_
@@ -0,0 +1,697 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/elementwise.h"
#include <cmath>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatEq;
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TensorRef<BHWC> GetTensorRef(int ref, const BHWC& shape) {
TensorRef<BHWC> tensor_ref;
tensor_ref.type = DataType::FLOAT32;
tensor_ref.ref = ref;
tensor_ref.shape = shape;
return tensor_ref;
}
TEST(ElementwiseOneArgumentTest, Abs) {
OperationType op_type = OperationType::ABS;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 6.2, 2.0, 4.0}));
}
TEST(ElementwiseOneArgumentTest, Cos) {
OperationType op_type = OperationType::COS;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 3.1415926, -3.1415926, 1}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, -1.0, -1.0, 0.540302}));
}
TEST(ElementwiseOneArgumentTest, Copy) {
OperationType op_type = OperationType::COPY;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatEq(), {0.0, -6.2, 2.0, 4.0}));
}
TEST(ElementwiseOneArgumentTest, Elu) {
OperationType op_type = OperationType::ELU;
const BHWC shape(1, 1, 1, 7);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(
0, {0.0f, 1.0f, -1.0f, 100.0f, -100.0f, 0.01f, -0.01f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0f, 1.0f, std::exp(-1.0f) - 1.0f,
100.0f, std::exp(-100.0f) - 1.0f,
0.01f, std::exp(-0.01f) - 1.0f}));
}
TEST(ElementwiseOneArgumentTest, Exp) {
OperationType op_type = OperationType::EXP;
const BHWC shape(1, 1, 1, 7);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(
0, {0.0f, 1.0f, -1.0f, 100.0f, -100.0f, 0.01f, -0.01f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{std::exp(0.0f), std::exp(1.0f), std::exp(-1.0f),
std::exp(100.0f), std::exp(-100.0f), std::exp(0.01f),
std::exp(-0.01f)}));
}
TEST(ElementwiseOneArgumentTest, Floor) {
OperationType op_type = OperationType::FLOOR;
const BHWC shape(1, 1, 1, 7);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(
model.PopulateTensor(0, {-4.5f, -3.0f, -1.5f, 0.0f, 1.5f, 3.0f, 4.5f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{-5.0f, -3.0f, -2.0f, 0.0f, 1.0f, 3.0f, 4.0f}));
}
TEST(ElementwiseOneArgumentTest, Gelu) {
OperationType op_type = OperationType::GELU;
const BHWC shape(1, 1, 1, 6);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0f, 1.0f, 3.0f, 1.0f, -1.0f, -2.0f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
// Matches FloatActivationsOpTest::GeluApproximate since the gpu kernel only
// uses the approximate version.
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-5), {0.0f, 0.841192f, 2.99636f, 0.841192f,
-0.158808f, -0.0454023f}));
}
TEST(ElementwiseOneArgumentTest, HardSwish) {
OperationType op_type = OperationType::HARD_SWISH;
const BHWC shape(1, 1, 1, 7);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(
model.PopulateTensor(0, {-4.5f, -3.0f, -1.5f, 0.0f, 1.5f, 3.0f, 4.5f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6f),
{0.0f, 0.0f, -0.375f, 0.0f, 1.125f, 3.f, 4.5f}));
}
TEST(ElementwiseOneArgumentTest, Log) {
OperationType op_type = OperationType::LOG;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 3.1415926, 1.0, 1.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.14473, 0.0, 0.0}));
}
TEST(ElementwiseOneArgumentTest, Neg) {
OperationType op_type = OperationType::NEG;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, -3.1415926, 0.0, 1.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.0, 3.1415926, 0.0, -1.0}));
}
TEST(ElementwiseOneArgumentTest, Rsqrt) {
OperationType op_type = OperationType::RSQRT;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 4.0, 9.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 0.707106, 0.5, 0.333333}));
}
TEST(ElementwiseOneArgumentTest, Sigmoid) {
OperationType op_type = OperationType::SIGMOID;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.0, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.5, 0.002473, 0.880797, 0.982014}));
}
TEST(ElementwiseOneArgumentTest, Sin) {
OperationType op_type = OperationType::SIN;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 3.1415926, -3.1415926, 1.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 0.0, 0.0, 0.841471}));
}
TEST(ElementwiseOneArgumentTest, Sqrt) {
OperationType op_type = OperationType::SQRT;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.0, 1.414213, 2.0}));
}
TEST(ElementwiseOneArgumentTest, Square) {
OperationType op_type = OperationType::SQUARE;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 0.5, -3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 4.0, 0.25, 9.0}));
}
TEST(ElementwiseOneArgumentTest, Tanh) {
OperationType op_type = OperationType::TANH;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model({/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(1, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.0, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, -0.999987, 0.964027, 0.999329}));
}
TEST(ElementwiseTwoArgumentsTest, DivElementwise) {
OperationType op_type = OperationType::DIV;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, 4.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 2.0, -0.5, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, -3.1, -4.0, 1.0}));
}
TEST(ElementwiseTwoArgumentsTest, DivBroadcast) {
OperationType op_type = OperationType::DIV;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {0.5, 0.2}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 5.0, 4.0, 15.0}));
}
TEST(ElementwiseTwoArgumentsTest, DivScalar) {
OperationType op_type = OperationType::DIV;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
attr.param = static_cast<float>(0.5);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 2.0, 4.0, 6.0}));
}
TEST(ElementwiseTwoArgumentsTest, DivConstVector) {
OperationType op_type = OperationType::DIV;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {0.4, 0.5};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 2.0, 5.0, 6.0}));
}
TEST(ElementwiseTwoArgumentsTest, FloorDiv) {
OperationType op_type = OperationType::FLOOR_DIV;
const BHWC shape0(1, 1, 1, 7);
float scalar = 2.7f;
ElementwiseAttributes attr;
attr.param = scalar;
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(
model.PopulateTensor(0, {-4.5f, -3.0f, -1.5f, 0.0f, 1.5f, 3.0f, 4.5f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{std::floor(-4.5f / scalar), std::floor(-3.0f / scalar),
std::floor(-1.5f / scalar), std::floor(0.0f / scalar),
std::floor(1.5f / scalar), std::floor(3.0f / scalar),
std::floor(4.5f / scalar)}));
}
TEST(ElementwiseTwoArgumentsTest, FloorMod) {
OperationType op_type = OperationType::FLOOR_MOD;
const BHWC shape0(1, 1, 1, 7);
float scalar = 2.7f;
ElementwiseAttributes attr;
attr.param = scalar;
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(
model.PopulateTensor(0, {-4.5f, -3.0f, -1.5f, 0.0f, 1.5f, 3.0f, 4.5f}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-4.5f - std::floor(-4.5f / scalar) * scalar,
-3.0f - std::floor(-3.0f / scalar) * scalar,
-1.5f - std::floor(-1.5f / scalar) * scalar,
0.0f - std::floor(0.0f / scalar) * scalar,
1.5f - std::floor(1.5f / scalar) * scalar,
3.0f - std::floor(3.0f / scalar) * scalar,
4.5f - std::floor(4.5f / scalar) * scalar}));
}
TEST(ElementwiseTwoArgumentsTest, MaximumElementwise) {
OperationType op_type = OperationType::MAXIMUM;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, -3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 2.0, 3.0, -2.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 2.0, 3.0, -2.0}));
}
TEST(ElementwiseTwoArgumentsTest, MaximumBroadcast) {
OperationType op_type = OperationType::MAXIMUM;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {0.5, 0.2}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.5, 1.0, 2.0, 3.0}));
}
TEST(ElementwiseTwoArgumentsTest, MaximumScalar) {
OperationType op_type = OperationType::MAXIMUM;
const BHWC shape(1, 2, 2, 1);
ElementwiseAttributes attr;
attr.param = -1.0f;
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/std::move(attr)},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, -3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, -1.0, 2.0, -1.0}));
}
TEST(ElementwiseTwoArgumentsTest, MaximumConstVector) {
OperationType op_type = OperationType::MAXIMUM;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {0.4, 0.5};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.4, 1.0, 2.0, 3.0}));
}
TEST(ElementwiseTwoArgumentsTest, MinimumElementwise) {
OperationType op_type = OperationType::MINIMUM;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, -3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 2.0, 3.0, -2.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, -6.2, 2.0, -3.0}));
}
TEST(ElementwiseTwoArgumentsTest, MinimumBroadcast) {
OperationType op_type = OperationType::MINIMUM;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {0.5, 0.2}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 0.2, 0.5, 0.2}));
}
TEST(ElementwiseTwoArgumentsTest, MinimumScalar) {
OperationType op_type = OperationType::MINIMUM;
const BHWC shape(1, 2, 2, 1);
ElementwiseAttributes attr;
attr.param = -1.0f;
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/std::move(attr)},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, -3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.0, -6.2, -1.0, -3.0}));
}
TEST(ElementwiseTwoArgumentsTest, MinimumConstVector) {
OperationType op_type = OperationType::MINIMUM;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {0.5, 0.2};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 0.2, 0.5, 0.2}));
}
TEST(ElementwiseTwoArgumentsTest, PowElementwise) {
OperationType op_type = OperationType::POW;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 4.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 2.0, 3.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.0, 8.0, 256.0}));
}
TEST(ElementwiseTwoArgumentsTest, PowBroadcast) {
OperationType op_type = OperationType::POW;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 4.0}));
ASSERT_TRUE(model.PopulateTensor(1, {2.0, 0.5}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.0, 4.0, 2.0}));
}
TEST(ElementwiseTwoArgumentsTest, PowScalar) {
OperationType op_type = OperationType::POW;
const BHWC shape(1, 2, 2, 1);
ElementwiseAttributes attr;
attr.param = 2.0f;
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/std::move(attr)},
/*inputs=*/{GetTensorRef(0, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.0, 4.0, 16.0}));
}
TEST(ElementwiseTwoArgumentsTest, PowConstVector) {
OperationType op_type = OperationType::POW;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {2.0, 0.5};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 1.0, 4.0, 2.0}));
}
TEST(ElementwiseTwoArgumentsTest, SquaredDiffElementwise) {
OperationType op_type = OperationType::SQUARED_DIFF;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 2.0, 2.0, 4.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 1.0, 5.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 1.0, 9.0, 0.0}));
}
TEST(ElementwiseTwoArgumentsTest, SquaredDiffBroadcast) {
OperationType op_type = OperationType::SQUARED_DIFF;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {-1.0, 5.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 16.0, 9.0, 4.0}));
}
TEST(ElementwiseTwoArgumentsTest, SquaredDiffScalar) {
OperationType op_type = OperationType::SQUARED_DIFF;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
attr.param = static_cast<float>(5.0);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {25.0, 16.0, 9.0, 4.0}));
}
TEST(ElementwiseTwoArgumentsTest, SquaredDiffConstVector) {
OperationType op_type = OperationType::SQUARED_DIFF;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {-1.0, 5.0};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 16.0, 9.0, 4.0}));
}
TEST(ElementwiseTwoArgumentsTest, SubElementwise) {
OperationType op_type = OperationType::SUB;
const BHWC shape(1, 2, 2, 1);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape), GetTensorRef(1, shape)},
/*outputs=*/{GetTensorRef(2, shape)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -6.2, 2.0, 4.0}));
ASSERT_TRUE(model.PopulateTensor(1, {1.0, 2.0, 3.0, 4.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.0, -8.2, -1.0, 0.0}));
}
TEST(ElementwiseTwoArgumentsTest, SubBroadcast) {
OperationType op_type = OperationType::SUB;
const BHWC shape0(1, 2, 1, 2);
const BHWC shape1(1, 1, 1, 2);
SingleOpModel model(
{/*type=*/ToString(op_type), /*attributes=*/{}},
/*inputs=*/{GetTensorRef(0, shape0), GetTensorRef(1, shape1)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_TRUE(model.PopulateTensor(1, {0.3, 0.2}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-0.3, 0.8, 1.7, 2.8}));
}
TEST(ElementwiseTwoArgumentsTest, SubScalar) {
OperationType op_type = OperationType::SUB;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
attr.param = static_cast<float>(0.5);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-0.5, 0.5, 1.5, 2.5}));
}
TEST(ElementwiseTwoArgumentsTest, SubScalarRuntimeTensorSecond) {
OperationType op_type = OperationType::SUB;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
attr.param = static_cast<float>(0.5);
attr.runtime_tensor_is_second = true;
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.5, -0.5, -1.5, -2.5}));
}
TEST(ElementwiseTwoArgumentsTest, SubConstVector) {
OperationType op_type = OperationType::SUB;
const BHWC shape0(1, 2, 1, 2);
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> param;
param.shape = Linear(2);
param.id = 1;
param.data = {0.3, 0.2};
attr.param = std::move(param);
SingleOpModel model({/*type=*/ToString(op_type), attr},
/*inputs=*/{GetTensorRef(0, shape0)},
/*outputs=*/{GetTensorRef(2, shape0)});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, 1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewElementwiseNodeShader(op_type)));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-0.3, 0.8, 1.7, 2.8}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,130 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/fully_connected.h"
#include <algorithm>
#include <any>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class FullyConnectedBuffers : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr =
std::any_cast<const FullyConnectedAttributes&>(ctx.op_attr);
const int src_depth = DivideRoundUp(attr.weights.shape.i, 4);
const int dst_depth = DivideRoundUp(attr.weights.shape.o, 4);
// This shader can work with any workgroup size, the values below work well
// for OpenGL.
constexpr int kWorkgroupHintX = 4;
constexpr int kWorkgroupHintY = 4;
// TODO(akulik): check that input has h,w == 1,1
std::vector<Variable> parameters = {
{"src_depth", src_depth},
{"dst_depth", dst_depth},
};
// TODO(akulik): refactor indexed access to weights.
std::vector<std::pair<std::string, Object>> objects = {
{"weights", MakeReadonlyObject(ConvertToPHWO4I4(attr.weights))}};
std::string source = R"(
const int threads = int(gl_WorkGroupSize.y);
const int workers = int(gl_WorkGroupSize.x);
ivec3 tid = ivec3(gl_LocalInvocationID);
if (gid.x < $dst_depth$) {
int offset = 4 * gid.x * $src_depth$ + 4 * tid.y;
for (int d = tid.y; d < $src_depth$; d += threads, offset += 4 * threads) {
vec4 src = $input_data_0[0, 0, d]$;
value_0.x += dot(src, $weights[offset + 0]$);
value_0.y += dot(src, $weights[offset + 1]$);
value_0.z += dot(src, $weights[offset + 2]$);
value_0.w += dot(src, $weights[offset + 3]$);
}
sh_mem[workers * tid.y + tid.x] = value_0;
}
memoryBarrierShared();
barrier();
if (tid.y > 0 || gid.x >= $dst_depth$) {
return;
}
for (int t = 1; t < threads; t++) {
value_0 += sh_mem[workers * t + tid.x];
}
)";
if (!attr.bias.data.empty()) {
source += " value_0 += $bias[gid.x]$;\n";
objects.push_back({"bias", MakeReadonlyObject(attr.bias.data)});
}
source += " $output_data_0[0, 0, gid.x] = value_0$;";
std::vector<Variable> shared_variables = {
#ifdef __APPLE__
// MoltenVK has problems with shared memory sized using the workgroup
// size. Fortunately with Metal a fixed workgroup size of 32 seems to
// give optimal results.
{"sh_mem", std::vector<float4>(32)},
#else
// The actual size of sh_mem depends on the WorkgroupSize
{"sh_mem", std::vector<float4>(0)},
#endif
};
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/std::move(shared_variables),
/*workload=*/uint3(dst_depth, kWorkgroupHintY, 1),
/*workgroup=*/uint3(kWorkgroupHintX, kWorkgroupHintY, 1),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewFullyConnectedNodeShader() {
return std::make_unique<FullyConnectedBuffers>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_FULLY_CONNECTED_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_FULLY_CONNECTED_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewFullyConnectedNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_FULLY_CONNECTED_H_
@@ -0,0 +1,69 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/fully_connected.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(FullyConnectedTest, MatrixByVectorMultiplication) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 2);
FullyConnectedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 4;
bias.id = 1;
bias.data = {1, 2, 3, 4};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(4, 1, 1, 2);
weights.id = 2;
weights.data = {1, 2, 3, 4, 5, 6, 7, 8};
attr.weights = std::move(weights);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 1, 1, 4);
SingleOpModel model({ToString(OperationType::FULLY_CONNECTED), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2}));
ASSERT_OK(model.Invoke(*NewFullyConnectedNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {6, 13, 20, 27}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,95 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/lstm.h"
#include <memory>
#include <string>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Basic LSTMCell gates.
//
// inputs: 0 1
// activ_temp prev_state
// \ /
// [[LSTM gates]]
// / \
// new_state activation
// outputs: 0 1
//
// The size of activ_temp should be 4x size of new_state.
// The size of prev_state == new_state == activation.
//
class LstmNodeShader : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string code = R"(
vec4 prev_state = $input_data_1[gid.x, gid.y, gid.z]$;
int c0 = 0 * $workload_z$;
int c1 = 1 * $workload_z$;
int c2 = 2 * $workload_z$;
int c3 = 3 * $workload_z$;
// input, new, forget, output
vec4 gate_0 = $input_data_0[gid.x, gid.y, gid.z + c0]$;
vec4 gate_1 = $input_data_0[gid.x, gid.y, gid.z + c1]$;
vec4 gate_2 = $input_data_0[gid.x, gid.y, gid.z + c2]$;
vec4 gate_3 = $input_data_0[gid.x, gid.y, gid.z + c3]$;
vec4 input_gate = 1.0f / (1.0f + exp(-1.0 * gate_0)); // sig(x)
vec4 new_input = tanh(gate_1); // tanh(x)
vec4 forget_gate = 1.0f / (1.0f + exp(-1.0 * gate_2)); // sig(x)
vec4 output_gate = 1.0f / (1.0f + exp(-1.0 * gate_3)); // sig(x)
vec4 new_state = input_gate * new_input + forget_gate * prev_state;
vec4 activation = output_gate * tanh(new_state);
value_0 = new_state;
value_1 = activation;
)";
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewLstmNodeShader() {
return std::make_unique<LstmNodeShader>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_LSTM_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_LSTM_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewLstmNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_LSTM_H_
@@ -0,0 +1,83 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/lstm.h"
#include <cmath>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(LstmTest, BaseTest) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 16);
TensorRef<BHWC> prev_state;
prev_state.type = DataType::FLOAT32;
prev_state.ref = 1;
prev_state.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output_state;
output_state.type = DataType::FLOAT32;
output_state.ref = 2;
output_state.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output_activation;
output_activation.type = DataType::FLOAT32;
output_activation.ref = 3;
output_activation.shape = BHWC(1, 1, 1, 4);
LstmAttributes attr;
attr.kernel_type = LstmKernelType::BASIC;
SingleOpModel model({ToString(OperationType::LSTM), attr},
{input, prev_state}, {output_state, output_activation});
std::vector input_data = {
-std::log(2.0f), -std::log(2.0f), -std::log(2.0f), -std::log(2.0f),
std::log(3.0f), std::log(3.0f), std::log(3.0f), std::log(3.0f),
-std::log(4.0f), -std::log(4.0f), -std::log(4.0f), -std::log(4.0f),
-std::log(5.0f), -std::log(5.0f), -std::log(5.0f), -std::log(5.0f)};
ASSERT_TRUE(model.PopulateTensor(0, std::move(input_data)));
ASSERT_TRUE(model.PopulateTensor(1, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewLstmNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{7.0 / 15.0, 10.0 / 15.0, 13.0 / 15.0, 16.0 / 15.0}));
EXPECT_THAT(
model.GetOutput(1),
Pointwise(FloatNear(1e-6), {(1.f / 6.f) * std::tanh(7.f / 15.f),
(1.f / 6.f) * std::tanh(10.f / 15.f),
(1.f / 6.f) * std::tanh(13.f / 15.f),
(1.f / 6.f) * std::tanh(16.f / 15.f)}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,80 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/max_unpooling.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class MaxUnpooling : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr =
std::any_cast<const MaxUnpooling2DAttributes&>(ctx.op_attr);
std::vector<Variable> parameters = {
{"stride", int2(attr.strides.w, attr.strides.h)},
{"offset", int2(attr.padding.prepended.w, attr.padding.prepended.h)},
{"window_h", attr.kernel.h},
{"window_w", attr.kernel.w},
};
std::string source = R"(
ivec2 coord = (gid.xy + $offset$) / $stride$;
ivec4 indices = $input_data_1[coord.x, coord.y, gid.z]$;
vec4 input_ = $input_data_0[coord.x, coord.y, gid.z]$;
coord = coord * $stride$ - $offset$;
for (int i = 0; i < 4; ++i) {
ivec2 t = coord + ivec2(indices[i] % $window_w$, indices[i] / $window_w$);
if (t.x == gid.x && t.y == gid.y) {
value_0[i] = input_[i];
}
}
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewMaxUnpoolingNodeShader() {
return std::make_unique<MaxUnpooling>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MAX_UNPOOLING_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MAX_UNPOOLING_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewMaxUnpoolingNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MAX_UNPOOLING_H_
@@ -0,0 +1,68 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/max_unpooling.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(MaxUnpoolingTest, Kernel2x2Stride2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> indices;
indices.type = DataType::INT32;
indices.ref = 1;
indices.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 4, 4, 1);
MaxUnpooling2DAttributes attr;
attr.kernel = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
SingleOpModel model({ToString(OperationType::MAX_UNPOOLING_2D), attr},
{input, indices}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {0, 0, 0, 0}));
ASSERT_OK(model.Invoke(*NewMaxUnpoolingNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{1, 0, 2, 0, 0, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,278 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/mean.h"
#include <algorithm>
#include <any>
#include <cmath>
#include <cstdint>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
bool UseSubgroupBasedImpl(const GpuInfo& gpu_info) {
return gpu_info.IsApiVulkan() &&
(gpu_info.vulkan_info.api_version_major > 1 ||
gpu_info.vulkan_info.api_version_minor >= 1) &&
gpu_info.vulkan_info.subgroup_size >= 32 &&
gpu_info.vulkan_info.supports_subgroup_arithmetic;
}
// An implementation of Mean for desktop GPUs and some phones with recent
// Vulkan drivers. It is more parallel than the trivial Mean operation, but
// still limited to using a single work group.
void GenerateSubgroupBasedMean(const NodeShader::GenerationContext& ctx,
GeneratedCode* generated_code) {
int height = ctx.input_shapes[0][1];
int width = ctx.input_shapes[0][2];
int depth = ctx.input_shapes[0][3];
std::vector<Variable> parameters = {
{"input_data_0_h", height},
{"input_data_0_w", width},
{"output_data_0_h", 1},
{"output_data_0_w", 1},
};
std::string source = R"(
// Round columns and rows per invocation up, to ensure that we read the
// entire input.
const uint columns_per_invocation =
($input_data_0_w$ + (gl_WorkGroupSize.x - 1))/gl_WorkGroupSize.x;
const uint rows_per_invocation =
($input_data_0_h$ + (gl_WorkGroupSize.y - 1))/gl_WorkGroupSize.y;
const uint first_row = gl_GlobalInvocationID.y*rows_per_invocation;
const uint first_col = gl_GlobalInvocationID.x*columns_per_invocation;
const uint last_row_exclusive =
min(first_row+rows_per_invocation, $input_data_0_h$);
const uint last_column_exclusive =
min(first_col+columns_per_invocation, $input_data_0_w$);
vec4 value = vec4(0);
for (uint h = first_row; h < last_row_exclusive; ++h) {
for (uint w = first_col; w < last_column_exclusive; ++w) {
value += $input_data_0[w, h, gid.z]$;
}
}
highp vec4 subgroup_sum = subgroupAdd(value);
if(subgroupElect()) {
subgroup_sums[gl_SubgroupID] = subgroup_sum;
}
memoryBarrierShared();
barrier();
// Do the final reduction in the first subgroup.
if(gl_SubgroupID == 0) {
highp vec4 subtotal = vec4(0);
if (gl_SubgroupInvocationID < gl_NumSubgroups) {
subtotal = subgroup_sums[gl_SubgroupInvocationID];
}
highp vec4 grand_total = subgroupAdd(subtotal);
if(subgroupElect()) {
highp vec4 result = grand_total / $input_data_0_w$ / $input_data_0_h$;
$output_data_0[0, 0, gid.z] = result$;
}
}
)";
const uint32_t subgroup_size = ctx.gpu_info->vulkan_info.subgroup_size;
const uint32_t max_wg_size_x = ctx.gpu_info->GetMaxWorkGroupSizeForX();
const uint32_t max_wg_size_y = ctx.gpu_info->GetMaxWorkGroupSizeForY();
// Due to the design of the shader, at most subgroup_size subgroups can be
// launched. This may limit the maximal workgroup size.
const uint32_t max_wg_size =
std::min(static_cast<uint32_t>(ctx.gpu_info->GetMaxWorkGroupTotalSize()),
subgroup_size * subgroup_size);
const uint32_t max_number_of_subgroups = max_wg_size / subgroup_size;
uint32_t wg_size_x = 0;
uint32_t wg_size_y = 0;
if (width * height <= max_wg_size && width <= max_wg_size_x &&
height <= max_wg_size_y) {
wg_size_x = width;
wg_size_y = height;
} else {
// Approximately square workgroup. Also make sure to limit by driver limit
// and input size.
wg_size_x = std::min({static_cast<uint32_t>(std::sqrt(max_wg_size)),
max_wg_size_x, static_cast<uint32_t>(width)});
wg_size_y = std::min({max_wg_size / wg_size_x, max_wg_size_y,
static_cast<uint32_t>(height)});
}
std::vector<Variable> shared_variables = {
{"subgroup_sums", std::vector<float4>(max_number_of_subgroups)},
};
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{std::move(shared_variables)},
// Make sure we get one dispatch of size wg_size_x*wg_size_y*1 per layer.
/*workload=*/
uint3(wg_size_x, wg_size_y, uint32_t(DivideRoundUp(depth, 4))),
/*workgroup=*/uint3(wg_size_x, wg_size_y, 1u),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
}
void GenerateTrivialMean(const NodeShader::GenerationContext& ctx,
GeneratedCode* generated_code) {
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])}};
// Shaders may be compiled with a precision hint mediump, which means that
// GLSL compiler may drop the size of float data type from 32 to 16 bits.
// If "sum" and "size" variables are 16bit floats, their values range
// become not enough for providing a good results accuracy. That is why
// their precision is forced to be 32bit by using highp qualifier.
std::string source = R"(
highp vec4 sum = vec4(0.0);
highp float size = float($input_data_0_w$ * $input_data_0_h$);
for (int w = 0; w < $input_data_0_w$; w++) {
for (int h = 0; h < $input_data_0_h$; h++) {
sum += $input_data_0[w, h, gid.z]$;
}
}
value_0 = sum / size;
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(1, 1, 4),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
}
// Tiled implementation.
constexpr uint3 kTileSize = {8, 8, 1};
inline bool UseTiledImpl(const NodeShader::GenerationContext& ctx) {
const int h = ctx.input_shapes[0][1];
const int w = ctx.input_shapes[0][2];
const int c = ctx.input_shapes[0][3];
return h % kTileSize.y == 0 && w % kTileSize.x == 0 && c % 4 == 0 &&
(h / kTileSize.y) * (w / kTileSize.x) * c * sizeof(float) <=
32768; // required min value for GL_MAX_COMPUTE_SHARED_MEMORY_SIZE
}
void GenerateTiledMean(const NodeShader::GenerationContext& ctx,
GeneratedCode* generated_code) {
const int h = ctx.input_shapes[0][1];
const int w = ctx.input_shapes[0][2];
const int s = DivideRoundUp(ctx.input_shapes[0][3], 4);
std::vector<Variable> parameters = {
{"input_data_0_h", h},
{"input_data_0_w", w},
{"tile_size_h", kTileSize.y},
{"tile_size_w", kTileSize.x},
};
std::vector<Variable> shared_variables = {
{"tile_sum",
std::vector<float4>((w / kTileSize.x) * (h / kTileSize.y) * s)}};
std::string source = R"(
ivec2 tile_size = ivec2($tile_size_w$, $tile_size_h$);
ivec2 num_tiles = ivec2($input_data_0_w$, $input_data_0_h$) / tile_size;
highp vec4 partial_sum = vec4(0.0);
for (int x = gid.x * tile_size.x; x < (gid.x + 1) * tile_size.x; ++x) {
for (int y = gid.y * tile_size.y; y < (gid.y + 1) * tile_size.y; ++y) {
partial_sum += $input_data_0[x, y, gid.z]$;
}
}
$tile_sum$[num_tiles.x * num_tiles.y * gid.z + num_tiles.x * gid.y + gid.x] = partial_sum;
memoryBarrierShared(); barrier();
if (gid.x == 0 && gid.y == 0) {
highp vec4 sum = vec4(0.0);
for (int i = 0; i < num_tiles.x * num_tiles.y; ++i) {
sum += $tile_sum$[num_tiles.x * num_tiles.y * gid.z + i];
}
highp vec4 mean = sum / float($input_data_0_w$ * $input_data_0_h$);
$output_data_0[0, 0, gid.z] = mean$;
}
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/std::move(shared_variables),
/*workload=*/uint3(kTileSize.x, kTileSize.y, static_cast<uint32_t>(s)),
/*workgroup=*/kTileSize,
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
}
class Mean : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const MeanAttributes&>(ctx.op_attr);
if (attr.dims != std::set<Axis>({Axis::HEIGHT, Axis::WIDTH})) {
return absl::InvalidArgumentError(
"Mean calculation is supported only for height and width.");
}
if (!(ctx.input_shapes.size() == 1 && ctx.output_shapes.size() == 1 &&
ctx.output_shapes[0][1] == 1 && ctx.output_shapes[0][2] == 1 &&
ctx.output_shapes[0][3] == ctx.input_shapes[0][3])) {
return absl::InvalidArgumentError(
"Mean calculation is supported for one input and one 1x1 output with "
"the same channel count.");
}
if (UseSubgroupBasedImpl(*ctx.gpu_info)) {
GenerateSubgroupBasedMean(ctx, generated_code);
} else if (UseTiledImpl(ctx)) {
GenerateTiledMean(ctx, generated_code);
} else {
GenerateTrivialMean(ctx, generated_code);
}
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewMeanNodeShader() {
return std::make_unique<Mean>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEAN_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEAN_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewMeanNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEAN_H_
@@ -0,0 +1,81 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/mean.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(MeanTest, TestTrivialImpl) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 1, 1, 1);
MeanAttributes attr;
attr.dims = {Axis::HEIGHT, Axis::WIDTH};
SingleOpModel model({ToString(OperationType::MEAN), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0}));
ASSERT_OK(model.Invoke(*NewMeanNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2.5}));
}
TEST(MeanTest, TestTiledImpl) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 16, 16, 8);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 1, 8);
MeanAttributes attr;
attr.dims = {Axis::HEIGHT, Axis::WIDTH};
SingleOpModel model({ToString(OperationType::MEAN), attr}, {input}, {output});
std::vector<float> input_data;
input_data.reserve(1 * 16 * 16 * 8);
for (int i = 0; i < 1 * 16 * 16 * 8; ++i) input_data.push_back(i % 8);
ASSERT_TRUE(model.PopulateTensor(0, std::move(input_data)));
ASSERT_OK(model.Invoke(*NewMeanNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0, 1, 2, 3, 4, 5, 6, 7}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,212 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/mul.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Returns the coordinate to iterate over the second runtime tensor.
absl::Status GetCoordinate(const NodeShader::GenerationContext& ctx, int dim,
const std::string& default_coord,
std::string* coord) {
std::string result;
if (ctx.input_shapes[1][dim] == 1 && ctx.input_shapes[0][dim] != 1) {
result = "0";
} else if (ctx.input_shapes[0][dim] == ctx.input_shapes[1][dim]) {
result = default_coord;
} else {
return absl::InvalidArgumentError(
absl::StrCat("Second runtime tensor dimension ", dim,
" must either match "
"first tensor's dimensions or be 1."));
}
*coord = result;
return absl::OkStatus();
}
absl::Status GenerateMultiplyRuntimeTensorCode(
const NodeShader::GenerationContext& ctx, GeneratedCode* generated_code) {
std::string x_coord, y_coord, z_coord;
RETURN_IF_ERROR(
GetCoordinate(ctx, /*dim=*/2, /*default_coord=*/"gid.x", &x_coord));
RETURN_IF_ERROR(
GetCoordinate(ctx, /*dim=*/1, /*default_coord=*/"gid.y", &y_coord));
RETURN_IF_ERROR(
GetCoordinate(ctx, /*dim=*/3, /*default_coord=*/"gid.z", &z_coord));
std::string source =
absl::StrCat("vec4 input1_value = $input_data_1[", x_coord, ", ", y_coord,
", ", z_coord, "]$;");
// Single channel mask support. Without this duplication, the rest of channels
// will be zeros, which will make the mul operation produce incorrect result.
if (ctx.input_shapes[1][3] == 1 && ctx.input_shapes[0][3] != 1) {
absl::StrAppend(
&source,
"\ninput1_value = vec4(input1_value.x, input1_value.x, input1_value.x, "
"input1_value.x);\n");
}
absl::StrAppend(
&source, "value_0 = $input_data_0[gid.x, gid.y, gid.z]$ * input1_value;");
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
absl::Status GenerateMultiplyConstantTensorCode(
const NodeShader::GenerationContext& ctx, GeneratedCode* generated_code) {
const auto& attr = std::any_cast<const ElementwiseAttributes&>(ctx.op_attr);
if (std::holds_alternative<float>(attr.param)) {
*generated_code = {
/*parameters=*/{{"scalar", std::get<float>(attr.param)}},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/"value_0 *= $scalar$;",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
if (std::holds_alternative<Tensor<Linear, DataType::FLOAT32>>(attr.param)) {
*generated_code = {
/*parameters=*/{},
/*objects=*/
{{"mul_buffer",
MakeReadonlyObject(
std::get<Tensor<Linear, DataType::FLOAT32>>(attr.param).data)}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on gid.z.
/*workload=*/
uint3(static_cast<int>(ctx.input_shapes[0][2]),
static_cast<int>(ctx.input_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.input_shapes[0][3]), 4)),
/*workgroup=*/uint3(),
/*source_code=*/"value_0 *= $mul_buffer[gid.z]$;",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
if (std::holds_alternative<Tensor<HWC, DataType::FLOAT32>>(attr.param)) {
std::string source;
if (ctx.input_shapes[0][1] == 1 && ctx.input_shapes[0][2] == 1 &&
ctx.input_shapes[0][3] == 1) {
source = R"(
value_0 = $input_data_0[0, 0, 0]$;
value_0 = vec4(value_0.x, value_0.x, value_0.x, value_0.x);
)";
}
auto param_shape =
std::get<Tensor<HWC, DataType::FLOAT32>>(attr.param).shape;
if (param_shape.c == 1) {
if (param_shape.h == 1 && param_shape.w == 1) {
absl::StrAppend(&source, "vec4 const_val = $hwc_buffer[0, 0, 0]$;");
} else {
absl::StrAppend(&source,
"vec4 const_val = $hwc_buffer[gid.x, gid.y, 0]$;");
}
absl::StrAppend(&source,
"const_val = vec4(const_val.x, const_val.x, const_val.x, "
"const_val.x);");
} else {
source += "vec4 const_val = $hwc_buffer[gid.x, gid.y, gid.z]$;";
}
absl::StrAppend(&source, "value_0 *= const_val;");
*generated_code = {
/*parameters=*/{},
/*objects=*/
{{"hwc_buffer",
MakeReadonlyObject(
uint3(param_shape.w, param_shape.h,
DivideRoundUp(param_shape.c, 4)),
ConvertToPHWC4(
std::get<Tensor<HWC, DataType::FLOAT32>>(attr.param)))}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on gid.z.
/*workload=*/
uint3(static_cast<int>(ctx.input_shapes[0][2]),
static_cast<int>(ctx.input_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.input_shapes[0][3]), 4)),
/*workgroup=*/uint3(),
/*source_code=*/
std::move(source),
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
return absl::InvalidArgumentError("Unsupported Multiplication case.");
}
class Multiply : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes.size() == 2) {
return GenerateMultiplyRuntimeTensorCode(ctx, generated_code);
} else {
return GenerateMultiplyConstantTensorCode(ctx, generated_code);
}
}
};
} // namespace
std::unique_ptr<NodeShader> NewMultiplyNodeShader() {
return std::make_unique<Multiply>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,33 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MUL_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MUL_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewMultiplyNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MUL_H_
@@ -0,0 +1,280 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/mul.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(MulTest, ConstantTensorMatchingShape) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = input.shape;
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor_3d;
tensor_3d.shape.h = input.shape.h;
tensor_3d.shape.w = input.shape.w;
tensor_3d.shape.c = input.shape.c;
tensor_3d.id = 2;
tensor_3d.data = {-2, 2, -3, 3};
attr.param = std::move(tensor_3d);
SingleOpModel model({ToString(OperationType::MUL), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-2, 4, -9, 12}));
}
TEST(MulTest, ConstantTensorSingleChannel) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = input.shape;
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor_3d;
tensor_3d.shape.h = input.shape.h;
tensor_3d.shape.w = input.shape.w;
tensor_3d.shape.c = 1;
tensor_3d.id = 2;
tensor_3d.data = {-2, 2};
attr.param = std::move(tensor_3d);
SingleOpModel model({ToString(OperationType::MUL), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-2, -4, 6, 8}));
}
TEST(MulTest, DegenerateConstantTensorSingleValue) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = input.shape;
ElementwiseAttributes attr;
Tensor<HWC, DataType::FLOAT32> tensor_3d;
tensor_3d.shape.h = 1;
tensor_3d.shape.w = 1;
tensor_3d.shape.c = 1;
tensor_3d.id = 2;
tensor_3d.data = {-2};
attr.param = std::move(tensor_3d);
SingleOpModel model({ToString(OperationType::MUL), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-2, -4, -6, -8}));
}
TEST(MulTest, ConstantTensorLinear) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = input.shape;
ElementwiseAttributes attr;
Tensor<Linear, DataType::FLOAT32> tensor;
tensor.shape.v = 2;
tensor.id = 1;
tensor.data = {2, 3};
attr.param = std::move(tensor);
SingleOpModel model({ToString(OperationType::MUL), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 6, 6, 12}));
}
TEST(MulTest, ConstantTensorScalar) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = input.shape;
ElementwiseAttributes attr;
attr.param = 2.f;
SingleOpModel model({ToString(OperationType::MUL), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4, 6, 8}));
}
TEST(MulTest, RuntimeTensorMatchingShapeNonOnes) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
TensorRef<BHWC> mask;
mask.type = DataType::FLOAT32;
mask.ref = 1;
mask.shape = input.shape;
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = input.shape;
SingleOpModel model({ToString(OperationType::MUL), {}}, {input, mask},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4, -1, -2, -3, -4}));
ASSERT_TRUE(model.PopulateTensor(1, {5, 6, 7, 8, 9, 10, 11, 12}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {5, 12, 21, 32, -9, -20, -33, -48}));
}
TEST(MulTest, RuntimeTensorMatchingShapeHeightOne) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> mask;
mask.type = DataType::FLOAT32;
mask.ref = 1;
mask.shape = input.shape;
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = input.shape;
SingleOpModel model({ToString(OperationType::MUL), {}}, {input, mask},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 4, 9, 16}));
}
TEST(MulTest, RuntimeTensorSingleChannel) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> mask;
mask.type = DataType::FLOAT32;
mask.ref = 1;
mask.shape = BHWC(1, input.shape.h, input.shape.w, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = input.shape;
SingleOpModel model({ToString(OperationType::MUL), {}}, {input, mask},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {2, 3}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4, 9, 12}));
}
TEST(MulTest, RuntimeTensorLinear) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> mask;
mask.type = DataType::FLOAT32;
mask.ref = 1;
mask.shape = BHWC(1, 1, 1, input.shape.c);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = input.shape;
SingleOpModel model({ToString(OperationType::MUL), {}}, {input, mask},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {1, 2}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 4, 3, 8}));
}
TEST(MulTest, RuntimeTensorScalar) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> mask;
mask.type = DataType::FLOAT32;
mask.ref = 1;
mask.shape = BHWC(1, 1, 1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = input.shape;
SingleOpModel model({ToString(OperationType::MUL), {}}, {input, mask},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_TRUE(model.PopulateTensor(1, {5}));
ASSERT_OK(model.Invoke(*NewMultiplyNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {5, 10, 15, 20}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,144 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/pad.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Pad : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const PadAttributes&>(ctx.op_attr);
if (attr.type != PaddingContentType::ZEROS &&
attr.type != PaddingContentType::REFLECT) {
return absl::UnimplementedError(
"Only ZERO and REFLECT padding types are supported.");
}
if (attr.appended.h < 0 || attr.appended.w < 0 || attr.appended.c < 0 ||
attr.prepended.h < 0 || attr.prepended.w < 0 || attr.prepended.c < 0) {
return absl::UnimplementedError("Negative padding is not supported.");
}
if (attr.appended.b != 0 || attr.prepended.b != 0) {
return absl::UnimplementedError("Padding for BATCH is not supported.");
}
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"input_data_0_c", static_cast<int>(ctx.input_shapes[0][3])},
{"prepended",
int4(attr.prepended.w, attr.prepended.h, attr.prepended.c, 0)},
};
std::string source;
if (attr.type == PaddingContentType::REFLECT) {
source = R"(
int src_x = gid.x - $prepended.x$;
src_x = abs(src_x);
src_x = $input_data_0_w$ - 1 - abs(src_x - $input_data_0_w$ + 1);
int src_y = gid.y - $prepended.y$;
src_y = abs(src_y);
src_y = $input_data_0_h$ - 1 - abs(src_y - $input_data_0_h$ + 1);
)";
if (attr.prepended.c == 0 && attr.appended.c == 0) {
// optimized case
source += " value_0 = $input_data_0[src_x, src_y, gid.z]$;\n";
} else {
source += R"(
int start_channel = gid.z * 4;
for (int i = 0; i < 4; ++i) {
int channel = start_channel + i;
int src_z = channel - $prepended.z$;
src_z = abs(src_z);
src_z = $input_data_0_c$ - 1 - abs(src_z - $input_data_0_c$ + 1);
// We need additional clamp for z, so that we use alignment for channels
// and can proceed extra channels that can lead to reading out of
// resource.
src_z = clamp(src_z, 0, $input_data_0_c$ - 1);
value_0[i] = $input_data_0[src_x, src_y, src_z / 4]$[src_z % 4];
}
)";
}
} else {
source = R"(
int src_x = gid.x - $prepended.x$;
int src_y = gid.y - $prepended.y$;
if (src_x >= 0 && src_x < $input_data_0_w$ && src_y >= 0 && src_y < $input_data_0_h$) {
)";
if (attr.prepended.c == 0 && attr.appended.c == 0) {
// optimized case
source += " value_0 = $input_data_0[src_x, src_y, gid.z]$;\n";
} else if (attr.prepended.c % 4 == 0) {
parameters.push_back(
{"src_slices",
DivideRoundUp(static_cast<int>(ctx.input_shapes[0][3]), 4)});
source += R"(
int src_z = gid.z - $prepended.z$ / 4;
if (src_z >= 0 && src_z < $src_slices$) {
value_0 = $input_data_0[src_x, src_y, src_z]$;
}
)";
} else {
source += R"(
int start_channel = gid.z * 4;
for (int i = 0; i < 4; ++i) {
int channel = start_channel + i;
int src_z = channel - $prepended.z$;
if (src_z >= 0 && src_z < $input_data_0_c$) {
value_0[i] = $input_data_0[src_x, src_y, src_z / 4]$[src_z % 4];
}
}
)";
}
source += " }\n";
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewPadNodeShader() {
return std::make_unique<Pad>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,33 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PAD_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PAD_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewPadNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PAD_H_
@@ -0,0 +1,172 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/pad.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
namespace {
void TestPadOperation(const HWC& prepend, const HWC& append,
const BHWC& output_shape, std::vector<float>&& expected) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = output_shape;
PadAttributes attr;
attr.prepended = BHWC(0, prepend.h, prepend.w, prepend.c);
attr.appended = BHWC(0, append.h, append.w, append.c);
attr.type = PaddingContentType::ZEROS;
SingleOpModel model({ToString(OperationType::PAD), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0}));
ASSERT_OK(model.Invoke(*NewPadNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), expected));
}
void TestPrepending(const HWC& prepend, const BHWC& output_shape,
std::vector<float>&& expected) {
TestPadOperation(prepend, HWC(0, 0, 0), output_shape, std::move(expected));
}
void TestAppending(const HWC& append, const BHWC& output_shape,
std::vector<float>&& expected) {
TestPadOperation(HWC(0, 0, 0), append, output_shape, std::move(expected));
}
} // namespace
TEST(PadTest, PrependH) {
TestPrepending(/*prepend=*/HWC(1, 0, 0),
/*output_shape=*/BHWC(1, 2, 1, 1), /*expected=*/{0, 1});
}
TEST(PadTest, PrependW) {
TestPrepending(/*prepend=*/HWC(0, 1, 0), /*output_shape=*/BHWC(1, 1, 2, 1),
/*expected=*/{0, 1});
}
TEST(PadTest, PrependC) {
TestPrepending(/*prepend=*/HWC(0, 0, 1), /*output_shape=*/BHWC(1, 1, 1, 2),
/*expected=*/{0, 1});
}
TEST(PadTest, PrependCx4) {
TestPrepending(/*prepend=*/HWC(0, 0, 4), /*output_shape=*/BHWC(1, 1, 1, 5),
/*expected=*/{0, 0, 0, 0, 1});
}
TEST(PadTest, PrependHWC) {
TestPrepending(/*prepend=*/HWC(1, 1, 1), /*output_shape=*/BHWC(1, 2, 2, 2),
/*expected=*/{0, 0, 0, 0, 0, 0, 0, 1});
}
TEST(PadTest, AppendH) {
TestAppending(/*append=*/HWC(1, 0, 0), /*output_shape=*/BHWC(1, 2, 1, 1),
/*expected=*/{1, 0});
}
TEST(PadTest, AppendW) {
TestAppending(/*append=*/HWC(0, 1, 0), /*output_shape=*/BHWC(1, 1, 2, 1),
/*expected=*/{1, 0});
}
TEST(PadTest, AppendC) {
TestAppending(/*append=*/HWC(0, 0, 1), /*output_shape=*/BHWC(1, 1, 1, 2),
/*expected=*/{1, 0});
}
TEST(PadTest, AppendHWC) {
TestAppending(/*append=*/HWC(1, 1, 1), /*output_shape=*/BHWC(1, 2, 2, 2),
/*expected=*/{1, 0, 0, 0, 0, 0, 0, 0});
}
TEST(PadTest, PrependHWCAppendHWC) {
TestPadOperation(/*prepend=*/HWC(1, 1, 1), /*append=*/HWC(1, 1, 1),
/*output_shape=*/BHWC(1, 3, 3, 3),
/*expected=*/{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
}
TEST(MirrorPadWidthTest, Smoke) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 3, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 1, 7, 1);
PadAttributes attr;
attr.prepended = BHWC(0, 0, 2, 0);
attr.appended = BHWC(0, 0, 2, 0);
attr.type = PaddingContentType::REFLECT;
SingleOpModel model({ToString(OperationType::PAD), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewPadNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0}));
}
TEST(MirrorPadChannelsTest, Smoke) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 3);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 1, 1, 7);
PadAttributes attr;
attr.prepended = BHWC(0, 0, 0, 2);
attr.appended = BHWC(0, 0, 0, 2);
attr.type = PaddingContentType::REFLECT;
SingleOpModel model({ToString(OperationType::PAD), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 3.0}));
ASSERT_OK(model.Invoke(*NewPadNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,198 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/pooling.h"
#include <any>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
absl::Status GenerateMaxPoolingCode(const Pooling2DAttributes& attr,
const NodeShader::GenerationContext& ctx,
GeneratedCode* generated_code) {
if (attr.padding.prepended.h > attr.kernel.h ||
attr.padding.prepended.w > attr.kernel.w) {
return absl::InvalidArgumentError("Padding is bigger than kernel.");
}
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"stride", int2(attr.strides.w, attr.strides.h)},
{"offset", int2(attr.padding.prepended.w, attr.padding.prepended.h)},
{"window_h", attr.kernel.h},
{"window_w", attr.kernel.w},
};
// Per GLSL_ES 3.1 spec in Issue 13.4
// "Floating Point Representation and Functionality" highp floats are
// expected to behave as defined in IEEE 754. In particular, signed
// infinities are mandated and defined as a number divided by 0.
std::string source = R"(
const highp float inf = -(1.0f / 0.0f);
value_0 = vec4(inf);)";
if (attr.output_indices) {
source += R"(
ivec4 value_1;
)";
}
source += R"(
ivec2 base_coord = gid.xy * $stride$ - $offset$;
for (int a = 0; a < $window_h$; ++a) {
for (int b = 0; b < $window_w$; ++b) {
ivec2 coord = base_coord + ivec2(b, a);
if (coord.x < 0 || coord.y < 0 || coord.x >= $input_data_0_w$ || coord.y >= $input_data_0_h$) {
continue;
}
vec4 input_ = $input_data_0[coord.x, coord.y, gid.z]$;)";
if (attr.output_indices) {
source += R"(
int window_index = a * $window_w$ + b;
if (input_.x > value_0.x) value_1.x = window_index;
if (input_.y > value_0.y) value_1.y = window_index;
if (input_.z > value_0.z) value_1.z = window_index;
if (input_.w > value_0.w) value_1.w = window_index;)";
}
source += R"(
value_0 = max(value_0, input_);
}
}
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
absl::Status GenerateAveragePoolingCode(
const Pooling2DAttributes& attr, const NodeShader::GenerationContext& ctx,
GeneratedCode* generated_code) {
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"stride", int2(attr.strides.w, attr.strides.h)},
{"offset", int2(attr.padding.prepended.w, attr.padding.prepended.h)},
{"window_h", attr.kernel.h},
{"window_w", attr.kernel.w},
};
// Bounds checking helper functions.
auto x_in_bounds = [input_width = ctx.input_shapes[0][2],
kernel_width = attr.kernel.w](int64_t x) -> bool {
return 0 <= x && x + kernel_width <= input_width;
};
auto y_in_bounds = [input_height = ctx.input_shapes[0][1],
kernel_height = attr.kernel.h](int64_t y) -> bool {
return 0 <= y && y + kernel_height <= input_height;
};
// Only include a bounds check in the shader if it will actually be necessary
// at run time.
const int64_t output_shape_max_y = ctx.output_shapes[0][1] - 1;
const int64_t output_shape_max_x = ctx.output_shapes[0][2] - 1;
const int64_t base_x = -attr.padding.prepended.w;
const int64_t base_y = -attr.padding.prepended.h;
const bool bounds_check_necessary =
!(x_in_bounds(base_x) &&
x_in_bounds(base_x + output_shape_max_x * attr.strides.w) &&
y_in_bounds(base_y) &&
y_in_bounds(base_y + output_shape_max_y * attr.strides.h));
std::string source = bounds_check_necessary ?
R"(
int window_size = 0;
for (int a = 0; a < $window_h$; ++a) {
for (int b = 0; b < $window_w$; ++b) {
ivec2 coord = gid.xy * $stride$ - $offset$ + ivec2(b, a);
if (coord.x >= 0 && coord.y >= 0 && coord.x < $input_data_0_w$ && coord.y < $input_data_0_h$) {
value_0 += $input_data_0[coord.x, coord.y, gid.z]$;
window_size++;
}
}
}
// If window_size==0, window covered nothing. This situation is a sign of
// incorrectly constructed operation. NaNs are expected as output.
value_0 /= float(window_size);
)"
:
R"(
for (int a = 0; a < $window_h$; ++a) {
for (int b = 0; b < $window_w$; ++b) {
ivec2 coord = gid.xy * $stride$ - $offset$ + ivec2(b, a);
value_0 += $input_data_0[coord.x, coord.y, gid.z]$;
}
}
// If the denominator is 0, that is a sign of an incorrectly constructed
// operation. NaNs are expected as output.
value_0 /= float($window_h$ * $window_w$);
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
class Pooling : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const Pooling2DAttributes&>(ctx.op_attr);
switch (attr.type) {
case PoolingType::AVERAGE:
return GenerateAveragePoolingCode(attr, ctx, generated_code);
case PoolingType::MAX:
return GenerateMaxPoolingCode(attr, ctx, generated_code);
default:
return absl::InvalidArgumentError("Incorrect attributes' type.");
}
}
};
} // namespace
std::unique_ptr<NodeShader> NewPoolingNodeShader() {
return std::make_unique<Pooling>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_POOLING_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_POOLING_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewPoolingNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_POOLING_H_
@@ -0,0 +1,127 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/pooling.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
MATCHER(IntEq, "") {
return *reinterpret_cast<const int*>(&std::get<0>(arg)) == std::get<1>(arg);
}
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(PoolingTest, MaxKernel2x2Stride2x2WithIndices) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 4, 4, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> indices;
indices.type = DataType::INT32;
indices.ref = 2;
indices.shape = BHWC(1, 2, 2, 1);
Pooling2DAttributes attr;
attr.kernel = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
attr.type = PoolingType::MAX;
attr.output_indices = true;
SingleOpModel model({ToString(OperationType::POOLING_2D), attr}, {input},
{output, indices});
ASSERT_TRUE(model.PopulateTensor(
0, {1, 2, 1, 2, 3, 4, 3, 4, 7, 8, 7, 8, 5, 6, 5, 6}));
ASSERT_OK(model.Invoke(*NewPoolingNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {4, 4, 8, 8}));
// Indices tensor is a vector<float>, but these float values should be treated
// as integers, that's why special matcher IntNear() is used.
EXPECT_THAT(model.GetOutput(1), Pointwise(IntEq(), {3, 3, 1, 1}));
}
TEST(PoolingTest, MaxKernel2x2Stride2x2WithoutIndices) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 4, 4, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
Pooling2DAttributes attr;
attr.kernel = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
attr.type = PoolingType::MAX;
SingleOpModel model({ToString(OperationType::POOLING_2D), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(
0, {1, 2, 1, 2, 3, 4, 3, 4, 7, 8, 7, 8, 5, 6, 5, 6}));
ASSERT_OK(model.Invoke(*NewPoolingNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {4, 4, 8, 8}));
}
TEST(PoolingTest, AverageKernel2x2Stride2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 4, 4, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
Pooling2DAttributes attr;
attr.kernel = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(2, 2);
attr.type = PoolingType::AVERAGE;
SingleOpModel model({ToString(OperationType::POOLING_2D), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(
0, {1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4}));
ASSERT_OK(model.Invoke(*NewPoolingNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 2, 3, 4}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,135 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/prelu.h"
#include <any>
#include <memory>
#include <variant>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class PReLULinearAlpha : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const PReLUAttributes&>(ctx.op_attr);
auto alpha = std::get_if<Tensor<Linear, DataType::FLOAT32>>(&attr.alpha);
if (!alpha) {
return absl::InvalidArgumentError("Alpha is missing");
}
if (alpha->shape.v != ctx.output_shapes[0][3]) {
return absl::InvalidArgumentError(
"Alpha shape does not match the number of channels.");
}
*generated_code = GeneratedCode{
/*parameters=*/{},
/*objects=*/{{"alpha", MakeReadonlyObject(alpha->data)}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on
// gid.z.
/*workload=*/
uint3(static_cast<int>(ctx.output_shapes[0][2]),
static_cast<int>(ctx.output_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.output_shapes[0][3]), 4)),
/*workgroup=*/uint3(),
/*source_code=*/
"value_0 = max(value_0, 0.0) + $alpha[gid.z]$ * min(value_0, "
"0.0);",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class PReLUFull : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const PReLUAttributes&>(ctx.op_attr);
auto alpha = std::get_if<Tensor<HWC, DataType::FLOAT32>>(&attr.alpha);
if (!alpha) {
return absl::InvalidArgumentError("Alpha is missing");
}
if (alpha->shape.h != ctx.output_shapes[0][1] ||
alpha->shape.w != ctx.output_shapes[0][2] ||
alpha->shape.c != ctx.output_shapes[0][3]) {
return absl::InvalidArgumentError(
"Alpha shape does not match input shape.");
}
ObjectSize obj_size =
uint3(static_cast<int>(ctx.output_shapes[0][2]),
static_cast<int>(ctx.output_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.output_shapes[0][3]), 4));
*generated_code = GeneratedCode{
/*parameters=*/{},
/*objects=*/
{{"alpha", MakeReadonlyObject(obj_size, ConvertToPHWC4(*alpha))}},
/*shared_variables=*/{},
// Declare workload explicitly because shader depends on
// gid.z.
/*workload=*/
uint3(static_cast<int>(ctx.output_shapes[0][2]),
static_cast<int>(ctx.output_shapes[0][1]),
DivideRoundUp(static_cast<int>(ctx.output_shapes[0][3]), 4)),
/*workgroup=*/uint3(),
/*source_code=*/
"value_0 = max(value_0, 0.0) + $alpha[gid.x, gid.y, gid.z]$ "
"* min(value_0, 0.0);",
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class PReLU : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const PReLUAttributes&>(ctx.op_attr);
auto* alpha = std::get_if<Tensor<HWC, DataType::FLOAT32>>(&attr.alpha);
return alpha ? full_.GenerateCode(ctx, generated_code)
: linear_.GenerateCode(ctx, generated_code);
}
private:
PReLULinearAlpha linear_;
PReLUFull full_;
};
} // namespace
std::unique_ptr<NodeShader> NewPReLUNodeShader() {
return std::make_unique<PReLU>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PRELU_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PRELU_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewPReLUNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_PRELU_H_
@@ -0,0 +1,138 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/prelu.h"
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(PReluTest, LinearAlpha) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
PReLUAttributes attr;
Tensor<Linear, DataType::FLOAT32> alpha;
alpha.shape.v = 1;
alpha.id = 1;
alpha.data = {2};
attr.alpha = std::move(alpha);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 1);
SingleOpModel model({ToString(OperationType::PRELU), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {-1.0, -2.0, 1.0, 2.0}));
ASSERT_OK(model.Invoke(*NewPReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-2, -4, 1, 2}));
}
TEST(PReluTest, 2DAlpha) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
OperationType op_type = OperationType::PRELU;
PReLUAttributes attr;
Tensor<HWC, DataType::FLOAT32> alpha;
alpha.shape = HWC(2, 2, 1);
alpha.id = 1;
alpha.data = {1, 2, 2, 2};
attr.alpha = std::move(alpha);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 1);
SingleOpModel model({ToString(op_type), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {0.0, -1.0, 2.0, -3.0}));
ASSERT_OK(model.Invoke(*NewPReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {0, -2, 2, -6}));
}
TEST(PReluTest, 2DAlphaWidthNotEqualHeight) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 1, 1);
OperationType op_type = OperationType::PRELU;
PReLUAttributes attr;
Tensor<HWC, DataType::FLOAT32> alpha;
alpha.shape = HWC(2, 1, 1);
alpha.id = 1;
alpha.data = {1, 1};
attr.alpha = std::move(alpha);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 1, 1);
SingleOpModel model({ToString(op_type), attr}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {-1.0, -1.0}));
ASSERT_OK(model.Invoke(*NewPReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-1, -1}));
}
TEST(PReluTest, 3DAlpha) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 2);
OperationType op_type = OperationType::PRELU;
PReLUAttributes attr;
Tensor<HWC, DataType::FLOAT32> alpha;
alpha.shape = HWC(2, 2, 2);
alpha.id = 1;
alpha.data = {1, 1, 2, 2, 2, 2, 2, 2};
attr.alpha = std::move(alpha);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 2, 2);
SingleOpModel model({ToString(op_type), attr}, {input}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {0.0, 0.0, -1.0, -1.0, 2.0, 2.0, -3.0, -3.0}));
ASSERT_OK(model.Invoke(*NewPReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0, 0, -2, -2, 2, 2, -6, -6}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,69 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/quantize_and_dequantize.h"
#include <any>
#include <memory>
#include <string>
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class QuantizeAndDequantize : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string code = R"(
value_0 = clamp(value_0, vec4($quant_min$), vec4($quant_max$));
value_0 = (value_0 - vec4($quant_min$)) / vec4($quant_scale$);
value_0 = floor(value_0 + vec4(0.5));
value_0 = value_0 * vec4($quant_scale$) + vec4($quant_min$);
)";
const auto& attr =
std::any_cast<const QuantizeAndDequantizeAttributes&>(ctx.op_attr);
*generated_code = {
/*parameters=*/{{"quant_min", attr.min},
{"quant_max", attr.max},
{"quant_scale", attr.scale}},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/code,
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewQuantizeAndDequantizeNodeShader() {
return std::make_unique<QuantizeAndDequantize>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,46 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_QUANTIZE_AND_DEQUANTIZE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_QUANTIZE_AND_DEQUANTIZE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
// Performs the operation: {Quantize, Dequantize} on floating-point data.
// We need this operation to emulate the error introduced by quantization
// on the GPU, which cannot represent int8 tensors.
//
// Implemented as:
// qvalue = round((min(qmax, max(qmin, src_val)) - qmin) * (1/qscale))
// dq_value = qvalue * qscale + qmin
// Here, qmin, qmax & qscale refer to the quantization values as implemented in
// TensorFlow Lite's 'FakeQuant' kernel.
//
// NOTE: We do not need to nudge min/max values in this op, since they would
// already be adjusted while generating the quantized model.
std::unique_ptr<NodeShader> NewQuantizeAndDequantizeNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_QUANTIZE_AND_DEQUANTIZE_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/delegates/gpu/gl/kernels/quantize_and_dequantize.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
#include "tensorflow/lite/kernels/internal/quantization_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(QuantizeAndDequantizeTest, Dim2Bits8) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 2, 1);
// Unlike TFLite's FakeQuant kernel, we assume that the incoming values are
// pre-nudged, since this should be done during model conversion.
const int num_bits = 8;
const int quant_min = 0;
const int quant_max = (1 << num_bits) - 1;
QuantizeAndDequantizeAttributes attr;
NudgeQuantizationRange(/**original_min**/ 0.0, /**original_max**/ 1.0,
quant_min, quant_max, &attr.min, &attr.max,
&attr.scale);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 2, 1);
SingleOpModel model({ToString(OperationType::QUANTIZE_AND_DEQUANTIZE), attr},
{input}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {0.0, 1.0, 0.25, 0.50, 0.4444444, 0.00001}));
ASSERT_OK(model.Invoke(*NewQuantizeAndDequantizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{0.0f, 1.0f, 0.25098f, 0.498039f, 0.443137f, 0.0f}));
}
TEST(QuantizeAndDequantizeTest, Dim3Bits8_NegativeRange) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 1, 2);
// Unlike TFLite's FakeQuant kernel, we assume that the incoming values are
// pre-nudged, since this should be done during model conversion.
const int num_bits = 8;
const int quant_min = 0;
const int quant_max = (1 << num_bits) - 1;
QuantizeAndDequantizeAttributes attr;
NudgeQuantizationRange(/**original_min**/ -0.9, /**original_max**/ 0.9,
quant_min, quant_max, &attr.min, &attr.max,
&attr.scale);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 1, 2);
SingleOpModel model({ToString(OperationType::QUANTIZE_AND_DEQUANTIZE), attr},
{input}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {0.0, -0.9, 0.25, 0.50, 0.4444444, -0.00001}));
ASSERT_OK(model.Invoke(*NewQuantizeAndDequantizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0f, -0.896471f, 0.247059f,
0.501176f, 0.444706f, 0.0f}));
}
TEST(QuantizeAndDequantizeTest, Dim3Bits16) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 1, 2);
// Unlike TFLite's FakeQuant kernel, we assume that the incoming values are
// pre-nudged, since this should be done during model conversion.
const int num_bits = 16;
const int quant_min = 0;
const int quant_max = (1 << num_bits) - 1;
QuantizeAndDequantizeAttributes attr;
NudgeQuantizationRange(/**original_min**/ 0.0, /**original_max**/ 1.0,
quant_min, quant_max, &attr.min, &attr.max,
&attr.scale);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 1, 2);
SingleOpModel model({ToString(OperationType::QUANTIZE_AND_DEQUANTIZE), attr},
{input}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {0.0, 1.0, 0.25, 0.50, 0.4444444, 0.00001}));
ASSERT_OK(model.Invoke(*NewQuantizeAndDequantizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0f, 1.0f, 0.250004f, 0.500008f,
0.44445f, 1.5259e-05f}));
}
TEST(QuantizeAndDequantizeTest, Dim2Bits16_NegativeRange) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 2, 1);
// Unlike TFLite's FakeQuant kernel, we assume that the incoming values are
// pre-nudged, since this should be done during model conversion.
const int num_bits = 16;
const int quant_min = 0;
const int quant_max = (1 << num_bits) - 1;
QuantizeAndDequantizeAttributes attr;
NudgeQuantizationRange(/**original_min**/ -0.9, /**original_max**/ 0.9,
quant_min, quant_max, &attr.min, &attr.max,
&attr.scale);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 2, 1);
SingleOpModel model({ToString(OperationType::QUANTIZE_AND_DEQUANTIZE), attr},
{input}, {output});
ASSERT_TRUE(
model.PopulateTensor(0, {0.0, -0.9, 0.25, 0.50, 0.4444444, -0.00001}));
ASSERT_OK(model.Invoke(*NewQuantizeAndDequantizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0f, -0.900014f, 0.249998f,
0.499995f, 0.444431f, 0.0f}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,168 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/registry.h"
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/add.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/concat.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/conv.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/custom_registry.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/depthwise_conv.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/elementwise.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/fully_connected.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/lstm.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/mean.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/mul.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/pad.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/pooling.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/prelu.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/quantize_and_dequantize.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/relu.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/resampler.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/reshape.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/resize.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/slice.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/softmax.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/space_to_depth.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/tile.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/transpose_conv.h"
#ifndef TFLITE_GPU_BINARY_RELEASE
#include "tensorflow/lite/delegates/gpu/gl/kernels/max_unpooling.h"
#endif // TFLITE_GPU_BINARY_RELEASE
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Registry : public NodeShader {
public:
Registry() {
using Type = OperationType;
using NewShaderFunc = std::function<std::unique_ptr<NodeShader>()>;
const auto insert_op = [&](Type type, NewShaderFunc func) {
shaders_[ToString(type)].push_back(func());
};
const auto insert_elementwise_op = [&](Type operation_type) {
shaders_[ToString(operation_type)].push_back(
NewElementwiseNodeShader(operation_type));
};
insert_op(Type::ADD, NewAddNodeShader);
insert_op(Type::CONCAT, NewAlignedConcatNodeShader);
insert_op(Type::CONCAT, NewFlatConcatNodeShader);
insert_op(Type::CONCAT, NewConcatNodeShader);
insert_op(Type::CONVOLUTION_2D, NewConvolution1x1NodeShader);
insert_op(Type::CONVOLUTION_2D, NewConvolutionNodeShader);
insert_op(Type::CONVOLUTION_TRANSPOSED, NewConvolutionTransposedNodeShader);
insert_op(Type::DEPTHWISE_CONVOLUTION, NewDepthwiseConvolutionNodeShader);
insert_op(Type::DEPTH_TO_SPACE, NewDepthToSpaceNodeShader);
insert_op(Type::FULLY_CONNECTED, NewFullyConnectedNodeShader);
insert_op(Type::LSTM, NewLstmNodeShader);
insert_op(Type::MEAN, NewMeanNodeShader);
// TODO(b/162763635): implement MeanStddevNormalization for OpenGL.
insert_op(Type::MUL, NewMultiplyNodeShader);
insert_op(Type::PAD, NewPadNodeShader);
insert_op(Type::POOLING_2D, NewPoolingNodeShader);
insert_op(Type::PRELU, NewPReLUNodeShader);
insert_op(Type::QUANTIZE_AND_DEQUANTIZE,
NewQuantizeAndDequantizeNodeShader);
insert_op(Type::RELU, NewReLUNodeShader);
insert_op(Type::RESAMPLER, NewResamplerNodeShader);
insert_op(Type::RESIZE, NewResizeNodeShader);
insert_op(Type::RESHAPE, NewReshapeNodeShader);
insert_op(Type::SLICE, NewSliceNodeShader);
insert_op(Type::SOFTMAX, NewSoftmaxNodeShader);
insert_op(Type::SPACE_TO_DEPTH, NewSpaceToDepthNodeShader);
insert_op(Type::TILE, NewTileNodeShader);
insert_elementwise_op(Type::ABS);
insert_elementwise_op(Type::COPY);
insert_elementwise_op(Type::COS);
insert_elementwise_op(Type::DIV);
insert_elementwise_op(Type::ELU);
insert_elementwise_op(Type::EXP);
insert_elementwise_op(Type::FLOOR);
insert_elementwise_op(Type::FLOOR_DIV);
insert_elementwise_op(Type::FLOOR_MOD);
insert_elementwise_op(Type::GELU);
insert_elementwise_op(Type::HARD_SWISH);
insert_elementwise_op(Type::LOG);
insert_elementwise_op(Type::NEG);
insert_elementwise_op(Type::MAXIMUM);
insert_elementwise_op(Type::MINIMUM);
insert_elementwise_op(Type::POW);
insert_elementwise_op(Type::RSQRT);
insert_elementwise_op(Type::SIGMOID);
insert_elementwise_op(Type::SIN);
insert_elementwise_op(Type::SQRT);
insert_elementwise_op(Type::SQUARE);
insert_elementwise_op(Type::SQUARED_DIFF);
insert_elementwise_op(Type::SUB);
insert_elementwise_op(Type::TANH);
#ifndef TFLITE_GPU_BINARY_RELEASE
insert_op(Type::MAX_UNPOOLING_2D, NewMaxUnpoolingNodeShader);
RegisterCustomOps(&shaders_);
#endif // TFLITE_GPU_BINARY_RELEASE
}
~Registry() final = default;
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
auto it = shaders_.find(ctx.op_type);
if (it == shaders_.end()) {
return absl::NotFoundError(
absl::StrCat("No shader implementation for ", ctx.op_type));
}
std::vector<std::string> errors;
for (const auto& shader : it->second) {
const auto status = shader->GenerateCode(ctx, generated_code);
// Return the first suitable shader.
if (status.ok()) return absl::OkStatus();
errors.push_back(std::string(status.message()));
}
return errors.empty() ? absl::OkStatus()
: absl::UnknownError(absl::StrJoin(errors, ", "));
}
private:
absl::flat_hash_map<std::string, std::vector<std::unique_ptr<NodeShader>>>
shaders_;
};
} // namespace
std::unique_ptr<NodeShader> NewNodeShaderRegistry() {
return std::make_unique<Registry>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,33 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_REGISTRY_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_REGISTRY_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewNodeShaderRegistry();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_REGISTRY_H_
@@ -0,0 +1,77 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/relu.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class ReLU : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const ReLUAttributes&>(ctx.op_attr);
// clamp(value, min(0, alpha * value), activation_max)
std::vector<Variable> params;
std::string min;
if (attr.alpha == 0) {
min = "vec4($activation_min$)";
params.push_back({"activation_min", attr.activation_min});
} else {
min = "min($alpha$ * value_0, 0.0)";
params.push_back({"alpha", attr.alpha});
}
std::string code;
if (attr.activation_max == 0) {
code = "value_0 = max(value_0, " + min + ");";
} else {
code = "value_0 = clamp(value_0, " + min + ", vec4($activation_max$));";
params.push_back({"activation_max", attr.activation_max});
}
*generated_code = {
/*parameters=*/std::move(params),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::AUTO,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewReLUNodeShader() {
return std::make_unique<ReLU>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RELU_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RELU_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewReLUNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RELU_H_
@@ -0,0 +1,156 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/relu.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class ReluTest : public ::testing::Test {
public:
ReluTest() = default;
~ReluTest() override = default;
TensorRef<BHWC> GetTensorRef(int ref) {
TensorRef<BHWC> tensor_ref;
tensor_ref.type = DataType::FLOAT32;
tensor_ref.ref = ref;
tensor_ref.shape = BHWC(1, 2, 2, 1);
return tensor_ref;
}
};
TEST_F(ReluTest, Smoke) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_max = 0;
attr.alpha = 0;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 0.0, 2.0, 8.0}));
}
TEST_F(ReluTest, ClipOnly) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_max = 6;
attr.alpha = 0;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {0.0, 0.0, 2.0, 6.0}));
}
TEST_F(ReluTest, AlphaOnly) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_max = 0;
attr.alpha = 0.5;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-3.0, 0.0, 2.0, 8.0}));
}
TEST_F(ReluTest, ClipAndAlpha) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_max = 6;
attr.alpha = 0.5;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-3.0, 0.0, 2.0, 6.0}));
}
TEST_F(ReluTest, ReLUN1Smoke) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_min = -1;
attr.activation_max = 0;
attr.alpha = 0;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-12.0f, -0.5f, 0.8f, 3.2f}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.0f, -0.5f, 0.8f, 3.2f}));
}
TEST_F(ReluTest, ReLUN1ClipOnly) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_min = -1;
attr.activation_max = 1;
attr.alpha = 0;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-12.0f, -0.5f, 0.8f, 3.2f}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-1.0f, -0.5f, 0.8f, 1.0f}));
}
TEST_F(ReluTest, ReLUN1AlphaOnly) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_min = -1; // activation_min ignored if alpha != 0
attr.activation_max = 0;
attr.alpha = 0.5;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-3.0, 0.0, 2.0, 8.0}));
}
TEST_F(ReluTest, ReLUN1ClipAndAlpha) {
OperationType op_type = OperationType::RELU;
ReLUAttributes attr;
attr.activation_min = -1; // activation_min ignored if alpha != 0
attr.activation_max = 6;
attr.alpha = 0.5;
SingleOpModel model({ToString(op_type), attr}, {GetTensorRef(0)},
{GetTensorRef(1)});
ASSERT_TRUE(model.PopulateTensor(0, {-6.0, 0.0, 2.0, 8.0}));
ASSERT_OK(model.Invoke(*NewReLUNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {-3.0, 0.0, 2.0, 6.0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,83 @@
/* 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/delegates/gpu/gl/kernels/resampler.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Resampler : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::vector<Variable> parameters = {
{"src_height", static_cast<int>(ctx.input_shapes[0][1])},
{"src_width", static_cast<int>(ctx.input_shapes[0][2])},
};
std::string source = R"(
highp int X = int(gid.x);
highp int Y = int(gid.y);
highp int S = int(gid.z);
highp vec2 f_coords = ($input_data_1[X, Y, 0]$).xy;
highp vec2 f_coords_floor = floor(f_coords);
highp ivec4 st;
st.xy = ivec2(f_coords_floor.x, f_coords_floor.y);
st.zw = st.xy + ivec2(1, 1);
highp vec2 t = f_coords - f_coords_floor;
bool stx_in = st.x >= 0 && st.x < $src_width$;
bool stz_in = st.z >= 0 && st.z < $src_width$;
bool sty_in = st.y >= 0 && st.y < $src_height$;
bool stw_in = st.w >= 0 && st.w < $src_height$;
vec4 src0 = (stx_in && sty_in) ? $input_data_0[st.x, st.y, S]$ : vec4(0.0);
vec4 src1 = (stz_in && sty_in) ? $input_data_0[st.z, st.y, S]$ : vec4(0.0);
vec4 src2 = (stx_in && stw_in) ? $input_data_0[st.x, st.w, S]$ : vec4(0.0);
vec4 src3 = (stz_in && stw_in) ? $input_data_0[st.z, st.w, S]$ : vec4(0.0);
value_0 = mix(mix(src0, src1, t.x), mix(src2, src3, t.x), t.y);
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewResamplerNodeShader() {
return std::make_unique<Resampler>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESAMPLER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESAMPLER_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewResamplerNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESAMPLER_H_
@@ -0,0 +1,96 @@
/* 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/delegates/gpu/gl/kernels/resampler.h"
#include <cmath>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/status/status.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
absl::Status ResamplerIdentityTest(const BHWC& shape) {
TensorRef<BHWC> src_tensor;
src_tensor.type = DataType::FLOAT32;
src_tensor.ref = 0;
src_tensor.shape = shape;
TensorRef<BHWC> warp_tensor;
warp_tensor.type = DataType::FLOAT32;
warp_tensor.ref = 1;
warp_tensor.shape = BHWC(1, shape.h, shape.w, 2);
TensorRef<BHWC> dst_tensor;
dst_tensor.type = DataType::FLOAT32;
dst_tensor.ref = 2;
dst_tensor.shape = shape;
SingleOpModel model({ToString(OperationType::RESAMPLER)},
{src_tensor, warp_tensor}, {dst_tensor});
std::vector<float> src_data(src_tensor.shape.DimensionsProduct());
std::vector<float> warp_data(warp_tensor.shape.DimensionsProduct());
std::vector<float> dst_data(dst_tensor.shape.DimensionsProduct());
for (int i = 0; i < src_data.size(); ++i) {
src_data[i] = std::sin(i);
dst_data[i] = src_data[i];
}
for (int y = 0; y < shape.h; ++y) {
for (int x = 0; x < shape.w; ++x) {
warp_data[(y * shape.w + x) * 2 + 0] = x;
warp_data[(y * shape.w + x) * 2 + 1] = y;
}
}
if (!model.PopulateTensor(0, std::move(src_data))) {
return absl::InternalError("failed loading data");
}
if (!model.PopulateTensor(1, std::move(warp_data))) {
return absl::InternalError("failed loading data");
}
RETURN_IF_ERROR(model.Invoke(*NewResamplerNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), dst_data));
return absl::OkStatus();
}
TEST(ResamplerTest, Identity_2_2_1) {
auto status = ResamplerIdentityTest(BHWC(1, 2, 2, 1));
ASSERT_TRUE(status.ok()) << status.message();
}
TEST(ResamplerTest, Identity_3_5_3) {
auto status = ResamplerIdentityTest(BHWC(1, 3, 5, 3));
ASSERT_TRUE(status.ok()) << status.message();
}
TEST(ResamplerTest, Identity_6_1_7) {
auto status = ResamplerIdentityTest(BHWC(1, 6, 1, 7));
ASSERT_TRUE(status.ok()) << status.message();
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,95 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/reshape.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Reshape : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes[0][1] * ctx.input_shapes[0][2] *
ctx.input_shapes[0][3] !=
ctx.output_shapes[0][1] * ctx.output_shapes[0][2] *
ctx.output_shapes[0][3]) {
return absl::InvalidArgumentError(
"Number of elements in input & output tensors don't match.");
}
const auto& attr = std::any_cast<const ReshapeAttributes&>(ctx.op_attr);
if (attr.new_shape.h != ctx.output_shapes[0][1] ||
attr.new_shape.w != ctx.output_shapes[0][2] ||
attr.new_shape.c != ctx.output_shapes[0][3]) {
return absl::InvalidArgumentError(
"Dimensions for output does not match new_shape attribute");
}
std::string code = R"(
int input_ch_w = $input_channels$ * $input_data_0_w$;
int output_ch_w = $output_channels$ * $output_data_0_w$;
for (int i = 0; i < 4; ++i) {
int dst_channel = gid.z * 4 + i;
if (dst_channel >= $output_channels$) {
continue;
}
int p = dst_channel + $output_channels$ * gid.x + output_ch_w * gid.y;
int src_y = p / input_ch_w;
int src_x = (p % input_ch_w) / $input_channels$;
int src_z = (p % input_ch_w) % $input_channels$;
int src_layer = src_z / 4;
int src_channel = src_z % 4;
value_0[i] = $input_data_0[src_x, src_y, src_layer]$[src_channel];
}
)";
*generated_code = {
/*parameters=*/{
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"input_channels", static_cast<int>(ctx.input_shapes[0][3])},
{"output_data_0_w", static_cast<int>(ctx.output_shapes[0][2])},
{"output_channels", static_cast<int>(ctx.output_shapes[0][3])},
},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewReshapeNodeShader() {
return std::make_unique<Reshape>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESHAPE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESHAPE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewReshapeNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESHAPE_H_
@@ -0,0 +1,122 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/reshape.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(Reshape, 1x2x3To3x2x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 3);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 2, 1);
ReshapeAttributes attr;
attr.new_shape = output.shape;
SingleOpModel model({ToString(OperationType::RESHAPE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4, 5, 6}));
ASSERT_OK(model.Invoke(*NewReshapeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6}));
}
TEST(Reshape, 3x1x2To2x1x3) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 1, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 1, 3);
ReshapeAttributes attr;
attr.new_shape = output.shape;
SingleOpModel model({ToString(OperationType::RESHAPE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4, 5, 6}));
ASSERT_OK(model.Invoke(*NewReshapeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1, 2, 3, 4, 5, 6}));
}
TEST(Reshape, 1x1x4To2x2x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
ReshapeAttributes attr;
attr.new_shape = output.shape;
SingleOpModel model({ToString(OperationType::RESHAPE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewReshapeNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 2, 3, 4}));
}
TEST(Reshape, BatchIsUnsupported) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(4, 1, 1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
ReshapeAttributes attr;
attr.new_shape = output.shape;
SingleOpModel model({ToString(OperationType::RESHAPE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_THAT(model.Invoke(*NewReshapeNodeShader()).message(),
testing::HasSubstr("Batch size mismatch, expected 4 but got 1"));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,148 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/resize.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Resize : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const Resize2DAttributes&>(ctx.op_attr);
if (ctx.input_shapes[0][2] > ctx.output_shapes[0][2] ||
ctx.input_shapes[0][1] > ctx.output_shapes[0][1]) {
return absl::UnimplementedError(
"Downsampling is currently not supported by the resize op on GPU.");
}
if (ctx.output_shapes[0][2] != attr.new_shape.w ||
ctx.output_shapes[0][1] != attr.new_shape.h) {
return absl::InvalidArgumentError(
"Output size does not match new_size in attributes.");
}
if (ctx.input_shapes[0][3] != ctx.output_shapes[0][3]) {
return absl::InvalidArgumentError("Input/output channels mismatch.");
}
if (ctx.input_shapes[0][1] == 1 && ctx.input_shapes[0][2] == 1) {
// Copy a single element from input.
*generated_code = {
/*parameters=*/{},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/"value_0 = $input_data_0[0, 0, gid.z]$;",
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"scale_factor",
float2(CalculateResizeScale(ctx.input_shapes[0][2],
ctx.output_shapes[0][2], attr),
CalculateResizeScale(ctx.input_shapes[0][1],
ctx.output_shapes[0][1], attr))},
};
std::string source;
if (attr.type == SamplingType::BILINEAR) {
if (attr.half_pixel_centers) {
source = "vec2 coord = (vec2(gid.xy) + 0.5) * $scale_factor$ - 0.5;";
} else {
source = "vec2 coord = vec2(gid.xy) * $scale_factor$;";
}
source += R"(
vec2 coord_floor = floor(coord);
ivec2 icoord_floor = ivec2(coord_floor);
ivec2 borders = ivec2($input_data_0_w$, $input_data_0_h$) - ivec2(1, 1);
ivec4 st;
st.xy = max(icoord_floor, ivec2(0, 0));
st.zw = min(icoord_floor + ivec2(1, 1), borders);
vec2 t = coord - coord_floor; // interpolating factors
vec4 tex11 = $input_data_0[st.x, st.y, gid.z]$;
vec4 tex21 = $input_data_0[st.z, st.y, gid.z]$;
vec4 tex12 = $input_data_0[st.x, st.w, gid.z]$;
vec4 tex22 = $input_data_0[st.z, st.w, gid.z]$;
value_0 = mix(mix(tex11, tex21, t.x), mix(tex12, tex22, t.x), t.y);)";
} else if (attr.type == SamplingType::NEAREST) {
std::string fxc;
std::string fyc;
if (attr.half_pixel_centers) {
fxc = "(float(gid.x) + 0.5) * $scale_factor.x$";
fyc = "(float(gid.y) + 0.5) * $scale_factor.y$";
} else {
fxc = "float(gid.x) * $scale_factor.x$";
fyc = "float(gid.y) * $scale_factor.y$";
}
if (attr.align_corners) {
fxc += " + 0.5";
fyc += " + 0.5";
}
source += " ivec2 coord;\n";
source += " coord.x = int(" + fxc + ");\n";
source += " coord.y = int(" + fyc + ");\n";
source += " coord.x = max(0, coord.x);\n";
source += " coord.y = max(0, coord.y);\n";
source += " coord.x = min(coord.x, $input_data_0_w$ - 1);\n";
source += " coord.y = min(coord.y, $input_data_0_h$ - 1);\n";
source += R"(
value_0 = $input_data_0[coord.x, coord.y, gid.z]$;
)";
} else {
return absl::InvalidArgumentError("Unknown sampling type");
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewResizeNodeShader() {
return std::make_unique<Resize>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESIZE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESIZE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewResizeNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_RESIZE_H_
@@ -0,0 +1,238 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/resize.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(ResizeTest, Bilinear1x1x2To2x2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 2);
Resize2DAttributes attr;
attr.align_corners = true;
attr.new_shape = HW(2, 2);
attr.type = SamplingType::BILINEAR;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0}));
}
TEST(ResizeTest, Bilinear1x2x1To1x4x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 4, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.new_shape = HW(1, 4);
attr.type = SamplingType::BILINEAR;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 4.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 2.5, 4.0, 4.0}));
}
TEST(ResizeTest, Bilinear2x2x1To4x4x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 4, 4, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.new_shape = HW(4, 4);
attr.type = SamplingType::BILINEAR;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 4.0, 6.0, 8.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 2.5, 4.0, 4.0, 3.5, 4.75, 6.0, 6.0, 6.0,
7.0, 8.0, 8.0, 6.0, 7.0, 8.0, 8.0}));
}
TEST(ResizeTest, Bilinear2x2x1To3x3x1WithoutHalfPixel) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 3, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.half_pixel_centers = false;
attr.new_shape = HW(3, 3);
attr.type = SamplingType::BILINEAR;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 1.666666, 2.0, 2.333333, 3.0,
3.333333, 3.0, 3.666666, 4.0}));
}
TEST(ResizeTest, Bilinear2x2x1To3x3x1WithHalfPixel) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 3, 3, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.half_pixel_centers = true;
attr.new_shape = HW(3, 3);
attr.type = SamplingType::BILINEAR;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 3.0, 4.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{1.0, 1.5, 2.0, 2.0, 2.5, 3.0, 3.0, 3.5, 4.0}));
}
TEST(ResizeTest, Nearest1x2x1To2x4x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 2, 4, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.new_shape = HW(2, 4);
attr.type = SamplingType::NEAREST;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0}));
}
TEST(ResizeTest, NearestAlignCorners) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 3, 3, 1);
Resize2DAttributes attr;
attr.align_corners = true;
attr.half_pixel_centers = false;
attr.new_shape = HW(3, 3);
attr.type = SamplingType::NEAREST;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {3.0f, 6.0f, 9.0f, 12.0f}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {3.0f, 6.0f, 6.0f, 9.0f, 12.0f, 12.0f,
9.0f, 12.0f, 12.0f}));
}
TEST(ResizeTest, NearestHalfPixelCenters) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 2;
output.shape = BHWC(1, 3, 3, 1);
Resize2DAttributes attr;
attr.align_corners = false;
attr.half_pixel_centers = true;
attr.new_shape = HW(3, 3);
attr.type = SamplingType::NEAREST;
SingleOpModel model({ToString(OperationType::RESIZE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {3.0f, 6.0f, 9.0f, 12.0f}));
ASSERT_OK(model.Invoke(*NewResizeNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {3.0f, 6.0f, 6.0f, 9.0f, 12.0f, 12.0f,
9.0f, 12.0f, 12.0f}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,124 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/slice.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Slice : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const SliceAttributes&>(ctx.op_attr);
const int4 channels(attr.starts.c, attr.strides.c, attr.ends.c, 0);
const int4 heights(attr.starts.h, attr.strides.h, attr.ends.h, 0);
const int4 widths(attr.starts.w, attr.strides.w, attr.ends.w, 0);
std::vector<Variable> parameters = {
{"channels", channels},
{"heights", heights},
{"widths", widths},
{"dst_size", static_cast<int>(ctx.output_shapes[0][3])},
};
std::string code;
code += " ivec3 offset;\n";
if (attr.strides.w > 0) {
code += " offset.x = $widths.x$;\n";
} else {
if (attr.ends.w > 0) {
code += " offset.x = $widths.z$;\n";
} else {
code += " offset.x = $src_size.x$ + $widths.z$;\n";
}
}
if (attr.strides.h > 0) {
code += " offset.y = $heights.x$;\n";
} else {
if (attr.ends.h > 0) {
code += " offset.y = $heights.z$;\n";
} else {
code += " offset.y = src_height + $heights.z$;\n";
}
}
if (attr.strides.c > 0) {
code += " offset.z = $channels.x$;\n";
} else {
if (attr.ends.c > 0) {
code += " offset.z = $channels.z$;\n";
} else {
code += " offset.z = src_channels + $channels.z$;\n";
}
}
code +=
" ivec3 stride = "
"ivec3($widths.y$, $heights.y$, $channels.y$);\n";
code += " ivec3 coord;\n";
code += " coord.xy = offset.xy + ivec2(gid.xy) * stride.xy;\n";
code += " int step = gid.z * 4;\n";
code += " coord.z = offset.z + step * stride.z;\n";
code +=
" if(step++ < $dst_size$) value_0[0] = "
"$input_data_0[coord.x, coord.y, coord.z / 4]$[coord.z % 4];\n";
code += " coord.z += $channels.y$;\n";
code +=
" if(step++ < $dst_size$) value_0[1] = "
"$input_data_0[coord.x, coord.y, coord.z / 4]$[coord.z % 4];\n";
code += " coord.z += $channels.y$;\n";
code +=
" if(step++ < $dst_size$) value_0[2] = "
"$input_data_0[coord.x, coord.y, coord.z / 4]$[coord.z % 4];\n";
code += " coord.z += $channels.y$;\n";
code +=
" if(step++ < $dst_size$) value_0[3] = "
"$input_data_0[coord.x, coord.y, coord.z / 4]$[coord.z % 4];\n";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewSliceNodeShader() {
return std::make_unique<Slice>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SLICE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SLICE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewSliceNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SLICE_H_
@@ -0,0 +1,174 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/slice.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(SliceTest, Identity) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 2, 2);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 0, 0);
attr.ends = BHWC(input.shape.b, 1, 2, 2);
attr.strides = BHWC(1, 1, 1, 1);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 2, 3, 4}));
}
TEST(SliceTest, NoStrides) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 2, 1);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 0, 0);
attr.ends = BHWC(input.shape.b, 1, 2, 1);
attr.strides = BHWC(1, 1, 1, 1);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 3}));
}
TEST(SliceTest, NoStridesStartOffset) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 1, 2);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 1, 0);
attr.ends = BHWC(input.shape.b, 1, 2, 2);
attr.strides = BHWC(1, 1, 1, 1);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {3, 4}));
}
TEST(SliceTest, StridesByHeight) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 4, 1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 1, 1);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 0, 0);
attr.ends = BHWC(input.shape.b, 4, 1, 1);
attr.strides = BHWC(1, 2, 1, 1);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {1, 3}));
}
TEST(SliceTest, StridesByWidth) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 4, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 2, 1);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 1, 0);
attr.ends = BHWC(input.shape.b, 1, 4, 1);
attr.strides = BHWC(1, 1, 2, 1);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4}));
}
TEST(SliceTest, StridesByChannels) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 1, 2);
SliceAttributes attr;
attr.starts = BHWC(0, 0, 0, 1);
attr.ends = BHWC(input.shape.b, 1, 1, 4);
attr.strides = BHWC(1, 1, 1, 2);
SingleOpModel model({ToString(OperationType::SLICE), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 2, 3, 4}));
ASSERT_OK(model.Invoke(*NewSliceNodeShader()));
EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {2, 4}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,222 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/softmax.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
float4 GetMask(int num_channels) {
float4 mask(0.0f);
const int remainder = num_channels % 4 == 0 ? 4 : num_channels % 4;
for (int i = 0; i < remainder; ++i) mask[i] = 1.0f;
return mask;
}
class Softmax : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr = std::any_cast<const SoftmaxAttributes&>(ctx.op_attr);
if (ctx.input_shapes[0] != ctx.output_shapes[0]) {
return absl::InvalidArgumentError(
"Input and output shapes do not match.");
}
if (attr.axis != Axis::CHANNELS) {
return absl::UnimplementedError(
"Softmax is only supported for channels axis.");
}
return ctx.input_shapes[0][1] == 1 && ctx.input_shapes[0][2] == 1
? GenerateCodeFor1x1(ctx, generated_code)
: GenerateCodeGeneral(ctx, generated_code);
}
private:
absl::Status GenerateCodeFor1x1(const GenerationContext& ctx,
GeneratedCode* generated_code) const {
const int depth = DivideRoundUp(ctx.output_shapes[0][3], 4);
std::vector<Variable> shared_variables = {
{"partial_sum", std::vector<float4>(8)},
};
std::vector<Variable> uniform_parameters = {
{"depth", depth},
{"mask", GetMask(ctx.output_shapes[0][3])},
};
std::string source_code = R"(
highp vec4 kOnes = vec4(1.0);
int tid = int(gl_LocalInvocationID.x);
highp vec4 maxx4 = $input_data_0[0, 0, 0]$;
maxx4.y = maxx4.x;
maxx4.z = maxx4.x;
maxx4.w = maxx4.x;
for (int s = tid; s < $depth$; s += 32) {
highp vec4 mask_a = s == $depth$ - 1 ? $mask$ : kOnes;
highp vec4 mask_b = kOnes - mask_a;
highp vec4 src = $input_data_0[0, 0, s]$;
src = src * mask_a + mask_b * src.x;
maxx4 = max(maxx4, src);
}
highp float maximum = max(maxx4.x, maxx4.y);
maximum = max(maximum, maxx4.z);
maximum = max(maximum, maxx4.w);
partial_sum[tid / 4][tid % 4] = maximum;
memoryBarrierShared();
barrier();
if (tid == 0) {
maxx4 = max(partial_sum[0], partial_sum[1]);
maxx4 = max(maxx4, partial_sum[2]);
maxx4 = max(maxx4, partial_sum[3]);
maxx4 = max(maxx4, partial_sum[4]);
maxx4 = max(maxx4, partial_sum[5]);
maxx4 = max(maxx4, partial_sum[6]);
maxx4 = max(maxx4, partial_sum[7]);
maximum = max(maxx4.x, maxx4.y);
maximum = max(maximum, maxx4.z);
maximum = max(maximum, maxx4.w);
partial_sum[0][0] = maximum;
}
memoryBarrierShared();
barrier();
maximum = partial_sum[0][0];
highp float sum = 0.0;
for (int s = tid; s < $depth$; s += 32) {
highp vec4 mask_temp = s == $depth$ - 1 ? $mask$ : kOnes;
highp vec4 src = $input_data_0[0, 0, s]$ - vec4(maximum);
sum += dot(mask_temp, exp(src));
}
memoryBarrierShared();
barrier();
partial_sum[tid / 4][tid % 4] = sum;
memoryBarrierShared();
barrier();
if (tid == 0) {
sum = dot(kOnes, partial_sum[0]);
sum += dot(kOnes, partial_sum[1]);
sum += dot(kOnes, partial_sum[2]);
sum += dot(kOnes, partial_sum[3]);
sum += dot(kOnes, partial_sum[4]);
sum += dot(kOnes, partial_sum[5]);
sum += dot(kOnes, partial_sum[6]);
sum += dot(kOnes, partial_sum[7]);
partial_sum[0][0] = 1.0 / sum;
}
memoryBarrierShared();
barrier();
sum = partial_sum[0][0];
int dst_s = int(gl_GlobalInvocationID.x);
if (dst_s < $depth$) {
highp vec4 src = $input_data_0[0, 0, dst_s]$ - vec4(maximum);
highp vec4 temp = exp(src) * sum;
$output_data_0[0, 0, dst_s] = temp$;
}
)";
*generated_code = {
/*parameters=*/std::move(uniform_parameters),
/*objects=*/{},
/*shared_variables=*/std::move(shared_variables),
/*workload=*/uint3(depth, 1, 1),
/*workgroup=*/uint3(32, 1, 1),
/*source_code=*/std::move(source_code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
return absl::OkStatus();
}
absl::Status GenerateCodeGeneral(const GenerationContext& ctx,
GeneratedCode* generated_code) const {
std::vector<Variable> parameters = {
{"src_depth",
DivideRoundUp(static_cast<int>(ctx.output_shapes[0][3]), 4)},
{"mask", GetMask(ctx.output_shapes[0][3])},
};
std::string source_code = R"(
highp vec4 kOnes = vec4(1.0);
highp float sum = 0.0;
highp float maximum = $input_data_0[gid.x, gid.y, 0]$.x;
for (int d = 0; d < $src_depth$; ++d) {
highp vec4 mask_a = d == $src_depth$ - 1 ? $mask$ : kOnes;
highp vec4 mask_b = kOnes - mask_a;
highp vec4 src = $input_data_0[gid.x, gid.y, d]$;
src = src * mask_a + mask_b * src.x;
maximum = max(maximum, src.x);
maximum = max(maximum, src.y);
maximum = max(maximum, src.z);
maximum = max(maximum, src.w);
}
for (int d = 0; d < $src_depth$; ++d) {
highp vec4 mask_temp = d == $src_depth$ - 1 ? $mask$ : kOnes;
highp vec4 src = $input_data_0[gid.x, gid.y, d]$ - vec4(maximum);
sum += dot(mask_temp, exp(src));
}
for (int d = 0; d < $src_depth$; ++d) {
highp vec4 src = $input_data_0[gid.x, gid.y, d]$ - vec4(maximum);
highp vec4 temp_sum = exp(src) / sum;
$output_data_0[gid.x, gid.y, d] = temp_sum$;
}
)";
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/
uint3(static_cast<int>(ctx.output_shapes[0][2]),
static_cast<int>(ctx.output_shapes[0][1]), 1),
/*workgroup=*/uint3(),
/*source_code=*/std::move(source_code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::ONLY_DEFINITIONS,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewSoftmaxNodeShader() {
return std::make_unique<Softmax>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SOFTMAX_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SOFTMAX_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewSoftmaxNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SOFTMAX_H_
@@ -0,0 +1,197 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/softmax.h"
#include <cmath>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(SoftmaxTest, Softmax) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
SoftmaxAttributes attr;
attr.axis = Axis::CHANNELS;
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {0.1f, 0.2f, 0.3f, 0.4f}));
ASSERT_OK(model.Invoke(*NewSoftmaxNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6f), {1.0f, 1.0f, 1.0f, 1.0f}));
}
TEST(SoftmaxTest, DoesNotWorkForHeightAxis) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
SoftmaxAttributes attr;
attr.axis = Axis::HEIGHT;
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {0.1f, 0.2f, 0.3f, 0.4f}));
EXPECT_FALSE(model.Invoke(*NewSoftmaxNodeShader()).ok());
}
TEST(SoftmaxTest, DoesNotWorkForWidthAxis) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 2, 1);
SoftmaxAttributes attr;
attr.axis = Axis::WIDTH;
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {0.1f, 0.2f, 0.3f, 0.4f}));
EXPECT_FALSE(model.Invoke(*NewSoftmaxNodeShader()).ok());
}
TEST(SoftmaxTest, Softmax1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 1, 4);
SoftmaxAttributes attr;
attr.axis = Axis::CHANNELS;
const float sum =
std::exp(0.1f) + std::exp(0.2f) + std::exp(0.3f) + std::exp(0.4f);
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {0.1f, 0.2f, 0.3f, 0.4f}));
ASSERT_OK(model.Invoke(*NewSoftmaxNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6f),
{std::exp(0.1f) / sum, std::exp(0.2f) / sum,
std::exp(0.3f) / sum, std::exp(0.4f) / sum}));
}
TEST(SoftmaxTest, SoftmaxBigNumber) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 1, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 2, 1, 2);
SoftmaxAttributes attr;
attr.axis = Axis::CHANNELS;
double doubles[4] = {1.0, 2.0, 3.0, 100.0};
// exp(100) is inf in float (32 bit) but representable in double (64 bit)
ASSERT_TRUE(std::isinf(std::exp(static_cast<float>(doubles[3]))));
ASSERT_FALSE(std::isinf(std::exp(doubles[3])));
double s0 = std::exp(doubles[0]) + std::exp(doubles[1]);
double s1 = std::exp(doubles[2]) + std::exp(doubles[3]);
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(
0, {static_cast<float>(doubles[0]), static_cast<float>(doubles[1]),
static_cast<float>(doubles[2]), static_cast<float>(doubles[3])}));
ASSERT_OK(model.Invoke(*NewSoftmaxNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6f),
{static_cast<float>(std::exp(doubles[0]) / s0),
static_cast<float>(std::exp(doubles[1]) / s0),
static_cast<float>(std::exp(doubles[2]) / s1),
static_cast<float>(std::exp(doubles[3]) / s1)}));
}
TEST(SoftmaxTest, Softmax1x1BigNumber) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 1, 1, 4);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 1;
output.shape = BHWC(1, 1, 1, 4);
SoftmaxAttributes attr;
attr.axis = Axis::CHANNELS;
double doubles[4] = {1.0, 2.0, 3.0, 100.0};
// exp(100) is inf in float (32 bit) but representable in double (64 bit)
ASSERT_TRUE(std::isinf(std::exp(static_cast<float>(doubles[3]))));
ASSERT_FALSE(std::isinf(std::exp(doubles[3])));
double s0 = std::exp(doubles[0]) + std::exp(doubles[1]) +
std::exp(doubles[2]) + std::exp(doubles[3]);
SingleOpModel model({ToString(OperationType::SOFTMAX), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(
0, {static_cast<float>(doubles[0]), static_cast<float>(doubles[1]),
static_cast<float>(doubles[2]), static_cast<float>(doubles[3])}));
ASSERT_OK(model.Invoke(*NewSoftmaxNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6f),
{static_cast<float>(std::exp(doubles[0]) / s0),
static_cast<float>(std::exp(doubles[1]) / s0),
static_cast<float>(std::exp(doubles[2]) / s0),
static_cast<float>(std::exp(doubles[3]) / s0)}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,121 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/space_to_depth.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class SpaceToDepth : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr =
std::any_cast<const SpaceToDepthAttributes&>(ctx.op_attr);
std::string code = R"(
for (int i = 0; i < 4; ++i) {
int dst_c = 4 * gid.z + i;
if (dst_c >= $output_channels$) {
continue;
}
int block_id = dst_c / $input_data_0_c$;
int src_x = gid.x * $block_size$ + block_id % $block_size$;
int src_y = gid.y * $block_size$ + block_id / $block_size$;
int src_c = dst_c % $input_data_0_c$;
value_0[i] = $input_data_0[src_x, src_y, src_c / 4]$[src_c % 4];
}
)";
*generated_code = {
/*parameters=*/{
{"block_size", attr.block_size},
{"input_data_0_c", static_cast<int>(ctx.input_shapes[0][3])},
{"output_channels", static_cast<int>(ctx.output_shapes[0][3])},
},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
class DepthToSpace : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
const auto& attr =
std::any_cast<const SpaceToDepthAttributes&>(ctx.op_attr);
std::string code = R"(
for (int i = 0; i < 4; ++i) {
int dst_c = 4 * gid.z + i;
if (dst_c >= $output_channels$) {
continue;
}
int block_x = gid.x % $block_size$;
int src_x = gid.x / $block_size$;
int block_y = gid.y % $block_size$;
int src_y = gid.y / $block_size$;
int block_id = block_y * $block_size$ + block_x;
int src_c = block_id * $output_channels$ + dst_c;
value_0[i] = $input_data_0[src_x, src_y, src_c / 4]$[src_c % 4];
}
)";
*generated_code = {
/*parameters=*/{
{"block_size", attr.block_size},
{"output_channels", static_cast<int>(ctx.output_shapes[0][3])},
},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewSpaceToDepthNodeShader() {
return std::make_unique<SpaceToDepth>();
}
std::unique_ptr<NodeShader> NewDepthToSpaceNodeShader() {
return std::make_unique<DepthToSpace>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,35 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SPACE_TO_DEPTH_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SPACE_TO_DEPTH_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewSpaceToDepthNodeShader();
std::unique_ptr<NodeShader> NewDepthToSpaceNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_SPACE_TO_DEPTH_H_
@@ -0,0 +1,104 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/space_to_depth.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(SpaceToDepthTest, TensorShape1x2x2x1BlockSize2) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 2, 1), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 1, 1, 4), .ref = 1};
const SpaceToDepthAttributes attr = {.block_size = 2};
SingleOpModel model({ToString(OperationType::SPACE_TO_DEPTH), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, 4.0f}));
ASSERT_OK(model.Invoke(*NewSpaceToDepthNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0f, 2.0f, 3.0f, 4.0f}));
}
TEST(SpaceToDepthTest, TensorShape1x2x2x2BlockSize2) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 2, 2), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 1, 1, 8), .ref = 1};
const SpaceToDepthAttributes attr = {.block_size = 2};
SingleOpModel model({ToString(OperationType::SPACE_TO_DEPTH), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(
0, {1.4f, 2.3f, 3.2f, 4.1f, 5.4f, 6.3f, 7.2f, 8.1f}));
ASSERT_OK(model.Invoke(*NewSpaceToDepthNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{1.4f, 2.3f, 3.2f, 4.1f, 5.4f, 6.3f, 7.2f, 8.1f}));
}
TEST(SpaceToDepthTest, TensorShape1x2x2x3BlockSize2) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 2, 3), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 1, 1, 12), .ref = 1};
const SpaceToDepthAttributes attr = {.block_size = 2};
SingleOpModel model({ToString(OperationType::SPACE_TO_DEPTH), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, //
4.0f, 5.0f, 6.0f, //
7.0f, 8.0f, 9.0f, //
10.0f, 11.0f, 12.0f}));
ASSERT_OK(model.Invoke(*NewSpaceToDepthNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, //
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f}));
}
TEST(SpaceToDepthTest, TensorShape1x4x4x1BlockSize2) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 4, 4, 1), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 2, 4), .ref = 1};
const SpaceToDepthAttributes attr = {.block_size = 2};
SingleOpModel model({ToString(OperationType::SPACE_TO_DEPTH), attr}, {input},
{output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0, 2.0, 5.0, 6.0, //
3.0, 4.0, 7.0, 8.0, //
9.0, 10.0, 13.0, 14.0, //
11.0, 12.0, 15.0, 16.0}));
ASSERT_OK(model.Invoke(*NewSpaceToDepthNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0, 2.0, 3.0, 4.0, //
5.0, 6.0, 7.0, 8.0, //
9.0, 10.0, 11.0, 12.0, //
13.0, 14.0, 15.0, 16.0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,137 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/gl/api.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_environment.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/object_manager.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
#include "tensorflow/lite/delegates/gpu/gl/workgroups/default_calculator.h"
namespace tflite {
namespace gpu {
namespace gl {
SingleOpModel::SingleOpModel(Operation&& operation,
const std::vector<TensorRef<BHWC>>& inputs,
const std::vector<TensorRef<BHWC>>& outputs) {
auto node = graph_.NewNode();
node->operation = std::move(operation);
for (int i = 0; i < inputs.size(); ++i) {
auto input = graph_.NewValue();
input->tensor = inputs[i];
graph_.AddConsumer(node->id, input->id).IgnoreError();
TensorFloat32 tensor;
tensor.id = input->tensor.ref;
tensor.shape = input->tensor.shape;
inputs_.emplace_back(std::move(tensor));
}
for (int i = 0; i < outputs.size(); ++i) {
auto output = graph_.NewValue();
output->tensor = outputs[i];
graph_.SetProducer(node->id, output->id).IgnoreError();
}
}
bool SingleOpModel::PopulateTensor(int index, std::vector<float>&& data) {
if (index >= inputs_.size() ||
inputs_[index].shape.DimensionsProduct() != data.size()) {
return false;
}
inputs_[index].data = std::move(data);
return true;
}
absl::Status SingleOpModel::Invoke(const CompilationOptions& compile_options,
const RuntimeOptions& runtime_options,
const NodeShader& shader) {
std::unique_ptr<EglEnvironment> env;
RETURN_IF_ERROR(EglEnvironment::NewEglEnvironment(&env));
ObjectManager objects;
// Create buffers for input tensors.
{
absl::flat_hash_map<int, uint32_t> tensor_to_id;
for (const auto* input : graph_.inputs()) {
tensor_to_id[input->tensor.ref] = input->id;
}
for (const auto& input : inputs_) {
GlBuffer buffer;
RETURN_IF_ERROR(CreatePHWC4BufferFromTensor(input, &buffer));
RETURN_IF_ERROR(
objects.RegisterBuffer(tensor_to_id[input.id], std::move(buffer)));
}
}
// Create buffers for output tensors.
for (const auto* output : graph_.outputs()) {
GlBuffer buffer;
RETURN_IF_ERROR(CreatePHWC4BufferFromTensorRef(output->tensor, &buffer));
RETURN_IF_ERROR(objects.RegisterBuffer(output->id, std::move(buffer)));
}
// Compile model.
GpuInfo gpu_info;
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
std::unique_ptr<CompiledModel> compiled_model;
RETURN_IF_ERROR(Compile(compile_options, graph_, /*tflite_graph_io=*/{},
shader, *NewDefaultWorkgroupsCalculator(gpu_info),
&compiled_model));
// Get inference context.
auto command_queue = NewCommandQueue(gpu_info);
std::unique_ptr<InferenceContext> inference_context;
RETURN_IF_ERROR(compiled_model->NewRun(
runtime_options, &objects, command_queue.get(), &inference_context));
RETURN_IF_ERROR(inference_context->Reset());
// Run inference.
RETURN_IF_ERROR(inference_context->Execute());
// Copy output tensors to `output_`.
for (const auto* output : graph_.outputs()) {
TensorFloat32 tensor;
tensor.id = output->tensor.ref;
tensor.shape = output->tensor.shape;
tensor.data.reserve(output->tensor.shape.DimensionsProduct());
RETURN_IF_ERROR(
CopyFromPHWC4Buffer(*objects.FindBuffer(output->id), &tensor));
outputs_.push_back(std::move(tensor));
}
return absl::OkStatus();
}
absl::Status SingleOpModel::Invoke(const NodeShader& shader) {
return Invoke(CompilationOptions(), RuntimeOptions(), shader);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,70 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TEST_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TEST_UTIL_H_
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler_options.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime_options.h"
#ifndef ASSERT_OK
#define ASSERT_OK(x) ASSERT_THAT(x.message(), testing::StrEq(""));
#endif
namespace tflite {
namespace gpu {
namespace gl {
class SingleOpModel {
public:
SingleOpModel() = delete;
SingleOpModel(Operation&& operation,
const std::vector<TensorRef<BHWC>>& inputs,
const std::vector<TensorRef<BHWC>>& outputs);
virtual ~SingleOpModel() = default;
bool PopulateTensor(int index, std::vector<float>&& data);
absl::Status Invoke(const NodeShader& shader);
absl::Status Invoke(const CompilationOptions& compile_options,
const RuntimeOptions& runtime_options,
const NodeShader& shader);
const std::vector<float>& GetOutput(int index) const {
return outputs_[index].data;
}
protected:
GraphFloat32 graph_;
std::vector<TensorFloat32> inputs_;
std::vector<TensorFloat32> outputs_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TEST_UTIL_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/delegates/gpu/gl/kernels/tile.h"
#include <memory>
#include <string>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class Tile : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
std::string code = R"(
for (int i = 0; i < 4; ++i) {
int dst_c = 4 * gid.z + i;
int src_x = gid.x % $input_data_w$;
int src_y = gid.y % $input_data_h$;
int src_c = dst_c % $input_data_c$;
value_0[i] = $input_data_0[src_x, src_y, src_c / 4]$[src_c % 4];
}
)";
*generated_code = {
/*parameters=*/{
{"input_data_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_w", static_cast<int>(ctx.input_shapes[0][2])},
{"input_data_c", static_cast<int>(ctx.input_shapes[0][3])},
},
/*objects=*/{},
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/std::move(code),
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewTileNodeShader() {
return std::make_unique<Tile>();
}
} // namespace gl
} // namespace gpu
} // 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_DELEGATES_GPU_GL_KERNELS_TILE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TILE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewTileNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TILE_H_
@@ -0,0 +1,100 @@
/* 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/delegates/gpu/gl/kernels/tile.h"
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(TileTest, ChannelsTiling) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 1, 3), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 1, 6), .ref = 1};
SingleOpModel model({ToString(OperationType::TILE)}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}));
ASSERT_OK(model.Invoke(*NewTileNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0f, 2.0f, 3.0f, 1.0f, 2.0f, 3.0f,
4.0f, 5.0f, 6.0f, 4.0f, 5.0f, 6.0f}));
}
TEST(TileTest, WidthTiling) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 1, 2, 3), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 1, 4, 3), .ref = 1};
SingleOpModel model({ToString(OperationType::TILE)}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}));
ASSERT_OK(model.Invoke(*NewTileNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f,
1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}));
}
TEST(TileTest, HeightTiling) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 1, 3), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 4, 1, 3), .ref = 1};
SingleOpModel model({ToString(OperationType::TILE)}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}));
ASSERT_OK(model.Invoke(*NewTileNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f,
1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}));
}
TEST(TileTest, HWCTiling) {
const TensorRef<BHWC> input = {
.type = DataType::FLOAT32, .shape = BHWC(1, 2, 2, 3), .ref = 0};
const TensorRef<BHWC> output = {
.type = DataType::FLOAT32, .shape = BHWC(1, 4, 4, 6), .ref = 1};
SingleOpModel model({ToString(OperationType::TILE)}, {input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f,
8.0f, 9.0f, 10.0f, 11.0f, 12.0f}));
ASSERT_OK(model.Invoke(*NewTileNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(
FloatNear(1e-6),
{1.0f, 2.0f, 3.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 4.0f,
5.0f, 6.0f, 1.0f, 2.0f, 3.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
6.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 7.0f, 8.0f, 9.0f,
10.0f, 11.0f, 12.0f, 10.0f, 11.0f, 12.0f, 7.0f, 8.0f, 9.0f, 7.0f,
8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 10.0f, 11.0f, 12.0f, 1.0f, 2.0f,
3.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 4.0f, 5.0f, 6.0f,
1.0f, 2.0f, 3.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f,
12.0f, 10.0f, 11.0f, 12.0f, 7.0f, 8.0f, 9.0f, 7.0f, 8.0f, 9.0f,
10.0f, 11.0f, 12.0f, 10.0f, 11.0f, 12.0f}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,114 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/transpose_conv.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class ConvolutionTransposedBuffers : public NodeShader {
public:
absl::Status GenerateCode(const GenerationContext& ctx,
GeneratedCode* generated_code) const final {
if (ctx.input_shapes.size() != 1) {
return absl::UnimplementedError(
"Convolution Transposed does not support more than 1 runtime tensor");
}
const auto& attr =
std::any_cast<const ConvolutionTransposedAttributes&>(ctx.op_attr);
auto weights = attr.weights.shape;
std::vector<Variable> parameters = {
{"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
{"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])},
{"src_depth", DivideRoundUp(weights.i, 4)},
{"kernel_size", int2(weights.w, weights.h)},
{"stride", int2(attr.stride.w, attr.stride.h)},
{"padding", int2(weights.w - 1 - attr.padding.prepended.w,
weights.h - 1 - attr.padding.prepended.h)},
};
std::vector<std::pair<std::string, Object>> objects = {
{"weights",
MakeReadonlyObject(Get3DSizeForPHWO4I4(attr.weights.shape),
ConvertToPHWO4I4Transposed(attr.weights))}};
std::string source = R"(
#define IN_BOUNDS(p, p0, p1) (all(greaterThanEqual(p, p0)) && all(lessThan(p, p1)))
ivec2 p0 = ($padding$ + $stride$ - gid.xy % $stride$) % $stride$;
for (int y = p0.y; y < $kernel_size.y$; y += $stride.y$) {
for (int x = p0.x; x < $kernel_size.x$; x += $stride.x$) {
int i = int(float(y * $kernel_size.x$) + float(x));
ivec2 idx = ivec2(vec2(gid.xy + ivec2(x, y)) - vec2($padding$));
if (IN_BOUNDS(idx, ivec2(0), ivec2($input_data_0_w$, $input_data_0_h$) * $stride$)) {
ivec2 coord = idx / $stride$;
for (int l = 0; l < $src_depth$; ++l) {
vec4 src_color = $input_data_0[coord.x, coord.y, l]$;
value_0.x += dot(src_color, $weights[l * 4 + 0, i, gid.z]$);
value_0.y += dot(src_color, $weights[l * 4 + 1, i, gid.z]$);
value_0.z += dot(src_color, $weights[l * 4 + 2, i, gid.z]$);
value_0.w += dot(src_color, $weights[l * 4 + 3, i, gid.z]$);
}
}
}
}
)";
if (!attr.bias.data.empty()) {
source += "value_0 += $bias[gid.z]$;\n";
objects.push_back({"bias", MakeReadonlyObject(attr.bias.data)});
}
*generated_code = {
/*parameters=*/std::move(parameters),
/*objects=*/std::move(objects),
/*shared_variables=*/{},
/*workload=*/uint3(),
/*workgroup=*/uint3(),
/*source_code=*/source,
/*input=*/IOStructure::ONLY_DEFINITIONS,
/*output=*/IOStructure::AUTO,
};
return absl::OkStatus();
}
};
} // namespace
std::unique_ptr<NodeShader> NewConvolutionTransposedNodeShader() {
return std::make_unique<ConvolutionTransposedBuffers>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TRANSPOSE_CONV_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TRANSPOSE_CONV_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
std::unique_ptr<NodeShader> NewConvolutionTransposedNodeShader();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_TRANSPOSE_CONV_H_
@@ -0,0 +1,236 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/kernels/transpose_conv.h"
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/test_util.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(TransposeConvTest, O2H2W1I1Stride1x1DAdjacent1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
ConvolutionTransposedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data = {1, 1};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 2, 1, 1);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(1, 0);
attr.adjacent = HW(1, 1);
attr.stride = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 3, 3, 2);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_TRANSPOSED), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionTransposedNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {2, 4, 2, 4, 1, 1, 4, 8, 4, 8, 1, 1, 3,
5, 3, 5, 1, 1}));
}
TEST(TransposeConvTest, O1H2W2I1Stride1x1Adjacent2x2) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
ConvolutionTransposedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data.push_back(0.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 2, 2, 1);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.adjacent = HW(2, 2);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.stride = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 6, 6, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_TRANSPOSED), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1, 1, 1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionTransposedNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{1, 3, 3, 2, 0, 0, 4, 10, 10, 6, 0, 0, 4, 10, 10, 6, 0, 0,
3, 7, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
}
TEST(TransposeConvTest, O1H3W3I1Stride1x1Adjacent1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 2, 1);
ConvolutionTransposedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 1;
bias.id = 1;
bias.data.push_back(1.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 3, 3, 1);
weights.id = 2;
weights.data = {1, 2, 3, 1, 2, 3, 1, 2, 3};
attr.weights = std::move(weights);
attr.adjacent = HW(1, 1);
attr.padding.prepended = HW(1, 1);
attr.padding.appended = HW(0, 0);
attr.stride = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 4, 4, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_TRANSPOSED), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionTransposedNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{7, 11, 7, 1, 7, 11, 7, 1, 4, 6, 4, 1, 1, 1, 1, 1}));
}
TEST(TransposeConvTest, O2H1W1I2Stride1x1Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 2, 1, 2);
ConvolutionTransposedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data = {1, 1};
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(2, 1, 1, 2);
weights.id = 2;
weights.data = {1, 2, 3, 4};
attr.weights = std::move(weights);
attr.adjacent = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.stride = HW(1, 1);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 3, 2, 2);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_TRANSPOSED), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 1, 1, 1}));
ASSERT_OK(model.Invoke(*NewConvolutionTransposedNodeShader()));
EXPECT_THAT(model.GetOutput(0),
Pointwise(FloatNear(1e-6), {4, 8, 1, 1, 4, 8, 1, 1, 1, 1, 1, 1}));
}
TEST(TransposeConvTest, O1H1W1I1Stride2x2Dilation1x1) {
TensorRef<BHWC> input;
input.type = DataType::FLOAT32;
input.ref = 0;
input.shape = BHWC(1, 3, 3, 1);
ConvolutionTransposedAttributes attr;
Tensor<Linear, DataType::FLOAT32> bias;
bias.shape.v = 2;
bias.id = 1;
bias.data.push_back(0.0);
attr.bias = std::move(bias);
Tensor<OHWI, DataType::FLOAT32> weights;
weights.shape = OHWI(1, 1, 1, 1);
weights.id = 2;
weights.data.push_back(2.0);
attr.weights = std::move(weights);
attr.adjacent = HW(1, 1);
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.stride = HW(2, 2);
TensorRef<BHWC> output;
output.type = DataType::FLOAT32;
output.ref = 3;
output.shape = BHWC(1, 6, 6, 1);
SingleOpModel model(
{ToString(OperationType::CONVOLUTION_TRANSPOSED), std::move(attr)},
{input}, {output});
ASSERT_TRUE(model.PopulateTensor(0, {1, 0, 2, 0, 0, 0, 4, 0, 8}));
ASSERT_OK(model.Invoke(*NewConvolutionTransposedNodeShader()));
EXPECT_THAT(
model.GetOutput(0),
Pointwise(FloatNear(1e-6),
{2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0}));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite