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
+530
View File
@@ -0,0 +1,530 @@
load("@flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
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_portable_test_suite")
load("//tensorflow/lite/delegates/gpu:build_defs.bzl", "gpu_delegate_linkopts")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "android_sync",
srcs = ["android_sync.cc"],
hdrs = ["android_sync.h"],
linkopts = gpu_delegate_linkopts(),
deps = [
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
cc_test(
name = "android_sync_test",
srcs = ["android_sync_test.cc"],
linkopts = gpu_delegate_linkopts(),
tags = tf_gpu_tests_tags() + [
"local",
"nobuilder",
"notap",
"tflite_not_portable_ios",
],
deps = [
":android_sync",
":egl_environment",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "api",
srcs = ["api.cc"],
hdrs = ["api.h"],
deps = [
":command_queue",
":common_cc_fbs",
":compiler",
":compiler_options",
":gl_call",
":node_shader",
":object",
":object_manager",
":portable",
":request_gpu_info",
":runtime",
":runtime_options",
":stats",
":variable",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/gl/workgroups:calculator",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
] + select({
"//tensorflow/lite/delegates/gpu:tflite_gpu_binary_release": [],
"//conditions:default": [
":serialization",
],
}),
)
cc_library(
name = "api2",
srcs = ["api2.cc"],
hdrs = ["api2.h"],
deps = [
":command_queue",
":compiler",
":egl_environment",
":gl_call",
":object",
":portable",
":request_gpu_info",
":runtime",
":variable",
"//tensorflow/lite/delegates/gpu:api",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:tensor",
"//tensorflow/lite/delegates/gpu/gl/kernels:converter",
"//tensorflow/lite/delegates/gpu/gl/kernels:registry",
"//tensorflow/lite/delegates/gpu/gl/workgroups:default_calculator",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "command_queue",
srcs = ["command_queue.cc"],
hdrs = ["command_queue.h"],
deps = [
":gl_call",
":gl_program",
":gl_sync",
":portable",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/memory",
],
)
flatbuffer_cc_library(
name = "common_cc_fbs",
srcs = ["common.fbs"],
)
# Generic schema for inference on GPU device.
flatbuffer_cc_library(
name = "compiled_model_cc_fbs",
srcs = ["compiled_model.fbs"],
flatc_args = [
"--scoped-enums",
],
includes = [
":common_cc_fbs_includes",
],
)
cc_library(
name = "compiler",
srcs = ["compiler.cc"],
hdrs = ["compiler.h"],
deps = [
":compiler_options",
":float16_conversions",
":node_shader",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl/compiler:compiled_node",
"//tensorflow/lite/delegates/gpu/gl/compiler:fuse_auto_input",
"//tensorflow/lite/delegates/gpu/gl/compiler:fuse_inline",
"//tensorflow/lite/delegates/gpu/gl/compiler:fuse_inplace",
"//tensorflow/lite/delegates/gpu/gl/compiler:shader_code",
"//tensorflow/lite/delegates/gpu/gl/compiler:shader_codegen",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "compiler_options",
hdrs = ["compiler_options.h"],
deps = [
":object",
],
)
cc_library(
name = "egl_context",
srcs = ["egl_context.cc"],
hdrs = ["egl_context.h"],
deps = [
":gl_call",
":gl_errors",
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
cc_library(
name = "egl_environment",
srcs = ["egl_environment.cc"],
hdrs = ["egl_environment.h"],
deps = [
":egl_context",
":egl_surface",
":gl_call",
":portable",
":request_gpu_info",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/memory",
],
)
cc_library(
name = "egl_surface",
srcs = ["egl_surface.cc"],
hdrs = ["egl_surface.h"],
deps = [
":gl_call",
":gl_errors",
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
cc_library(
name = "float16_conversions",
srcs = ["float16_conversions.cc"],
hdrs = ["float16_conversions.h"],
deps = [
":object",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:tensor",
"@FP16",
"@com_google_absl//absl/types:variant",
],
)
cc_library(
name = "gl_buffer",
srcs = ["gl_buffer.cc"],
hdrs = ["gl_buffer.h"],
deps = [
":gl_call",
":gl_errors",
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/types:span",
],
)
cc_test(
name = "gl_buffer_test",
srcs = ["gl_buffer_test.cc"],
linkopts = [
"-lEGL",
"-lGLESv2",
"-lm",
],
tags = tf_gpu_tests_tags() + [
"local",
"nobuilder",
"notap",
"tflite_not_portable_ios",
],
deps = [
":egl_environment",
":gl_buffer",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "gl_call",
hdrs = ["gl_call.h"],
deps = [
":gl_errors",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
cc_library(
name = "gl_errors",
srcs = ["gl_errors.cc"],
hdrs = ["gl_errors.h"],
deps = [
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "gl_program",
srcs = ["gl_program.cc"],
hdrs = ["gl_program.h"],
deps = [
":gl_call",
":gl_errors",
":gl_shader",
":portable",
":variable",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:variant",
],
)
cc_library(
name = "gl_shader",
srcs = ["gl_shader.cc"],
hdrs = ["gl_shader.h"],
deps = [
":gl_call",
":gl_errors",
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
cc_library(
name = "gl_texture",
srcs = ["gl_texture.cc"],
hdrs = ["gl_texture.h"],
deps = [
":gl_call",
":gl_errors",
":gl_texture_helper",
":portable",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:tensor",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "gl_texture_helper",
srcs = ["gl_texture_helper.cc"],
hdrs = ["gl_texture_helper.h"],
deps = [
":portable",
"//tensorflow/lite/delegates/gpu/common:data_type",
],
)
cc_library(
name = "gl_sync",
srcs = ["gl_sync.cc"],
hdrs = ["gl_sync.h"],
deps = [
":gl_buffer",
":gl_call",
":gl_errors",
":gl_program",
":portable",
"//tensorflow/lite/delegates/gpu/common:status",
],
)
flatbuffer_cc_library(
name = "metadata_cc_fbs",
srcs = ["metadata.fbs"],
includes = [
":common_cc_fbs_includes",
":workgroups_cc_fbs_includes",
],
)
cc_library(
name = "node_shader",
hdrs = ["node_shader.h"],
deps = [
":compiler_options",
":object",
":variable",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "object",
hdrs = ["object.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:access_type",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"@com_google_absl//absl/types:variant",
],
)
cc_library(
name = "object_manager",
srcs = ["object_manager.cc"],
hdrs = ["object_manager.h"],
deps = [
":gl_buffer",
":gl_texture",
":stats",
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "portable",
hdrs = [
"portable_egl.h",
"portable_gl31.h",
],
)
cc_library(
name = "request_gpu_info",
srcs = ["request_gpu_info.cc"],
hdrs = ["request_gpu_info.h"],
deps = [
":gl_errors",
":portable",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:status_macros",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "runtime",
srcs = ["runtime.cc"],
hdrs = ["runtime.h"],
deps = [
":command_queue",
":gl_buffer",
":gl_call",
":gl_errors",
":gl_program",
":gl_shader",
":gl_texture",
":object",
":object_manager",
":portable",
":runtime_options",
":stats",
":variable",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:memory_management",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl/runtime:shared_buffer",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "runtime_options",
hdrs = ["runtime_options.h"],
)
cc_library(
name = "serialization",
srcs = ["serialization.cc"],
hdrs = ["serialization.h"],
deps = [
":common_cc_fbs",
":compiled_model_cc_fbs",
":object",
":variable",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:span",
"@com_google_absl//absl/types:variant",
"@flatbuffers",
],
)
cc_test(
name = "serialization_test",
srcs = ["serialization_test.cc"],
linkopts = [
"-lm",
],
tags = [
"local",
"nobuilder",
"notap",
"tflite_not_portable_ios",
],
deps = [
":object",
":serialization",
":variable",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "stats",
hdrs = ["stats.h"],
deps = [
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "variable",
hdrs = ["variable.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_absl//absl/types:variant",
],
)
flatbuffer_cc_library(
name = "workgroups_cc_fbs",
srcs = ["workgroups.fbs"],
includes = [
":common_cc_fbs_includes",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,101 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/android_sync.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GLES2/gl2.h>
#include <unistd.h>
namespace {
PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
bool IsGlSupported() {
static const bool extensions_allowed = [] {
eglDupNativeFenceFDANDROID =
reinterpret_cast<PFNEGLDUPNATIVEFENCEFDANDROIDPROC>(
eglGetProcAddress("eglDupNativeFenceFDANDROID"));
eglCreateSyncKHR = reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(
eglGetProcAddress("eglCreateSyncKHR"));
eglWaitSyncKHR = reinterpret_cast<PFNEGLWAITSYNCKHRPROC>(
eglGetProcAddress("eglWaitSyncKHR"));
eglDestroySyncKHR = reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(
eglGetProcAddress("eglDestroySyncKHR"));
return eglWaitSyncKHR && eglCreateSyncKHR && eglDupNativeFenceFDANDROID &&
eglDestroySyncKHR;
}();
return extensions_allowed;
}
} // namespace
namespace tflite::gpu::gl {
// Insert a gpu wait sync to the queue; return true if successful.
bool WaitFdGpu(int fence_fd) {
if (fence_fd == -1) {
return false;
}
if (!IsGlSupported()) {
return false;
}
// Server-side fence.
EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (egl_display == EGL_NO_DISPLAY) return false;
// EGL will take ownership of the passed fd if eglCreateSyncKHR is
// successful.
int fd_for_egl = dup(fence_fd);
EGLint sync_attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, (EGLint)fd_for_egl,
EGL_NONE};
EGLSync fence_sync = eglCreateSyncKHR(
egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, sync_attribs);
if (fence_sync != EGL_NO_SYNC_KHR) {
eglWaitSyncKHR(egl_display, fence_sync, 0);
return true;
} else {
close(fd_for_egl);
return false;
}
}
// Create a GL Fence object and return the associated fd
int CreateFdGpu() {
if (IsGlSupported()) {
EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (egl_display != EGL_NO_DISPLAY) {
EGLSync fence_sync =
eglCreateSyncKHR(egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (fence_sync != EGL_NO_SYNC_KHR) {
int fence_fd = eglDupNativeFenceFDANDROID(egl_display, fence_sync);
if (fence_fd == -1) {
eglDestroySyncKHR(egl_display, fence_sync);
} else {
return fence_fd;
}
}
}
}
// Can't use Sync object. We use glFinish as CPU wait instead
glFinish();
return -1;
}
} // namespace tflite::gpu::gl
@@ -0,0 +1,28 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_ANDROID_SYNC_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_ANDROID_SYNC_H_
namespace tflite::gpu::gl {
// Insert a gpu wait sync to the GL queue; return true if successful.
bool WaitFdGpu(int fence_fd);
// Create a GL Fence object and return the associated fd
int CreateFdGpu();
} // namespace tflite::gpu::gl
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_ANDROID_SYNC_H_
@@ -0,0 +1,38 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/android_sync.h"
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/gl/egl_environment.h"
namespace tflite::gpu::gl {
// Make sure GPU fences can be waited on by the GPU
TEST(AsyncBufferTest, FenceTest) {
// Check falseness first
EXPECT_EQ(CreateFdGpu(), -1);
EXPECT_FALSE(WaitFdGpu(1)); // False because EGL isn't set up
std::unique_ptr<EglEnvironment> env;
EXPECT_OK(EglEnvironment::NewEglEnvironment(&env));
int gpu_fd = CreateFdGpu();
EXPECT_GE(gpu_fd, 0);
EXPECT_TRUE(WaitFdGpu(gpu_fd));
}
} // namespace tflite::gpu::gl
+423
View File
@@ -0,0 +1,423 @@
/* 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/api.h"
#include <algorithm>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex> // NOLINT
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/model.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/compiler.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#ifndef TFLITE_GPU_BINARY_RELEASE
#include "tensorflow/lite/delegates/gpu/gl/serialization.h"
#endif // TFLITE_GPU_BINARY_RELEASE
namespace tflite {
namespace gpu {
namespace gl {
namespace {
using ObjectsSizes = absl::flat_hash_map<ValueId, size_t>;
enum class InferenceContextState {
NOT_STARTED,
IN_PROGRESS,
};
class InferenceContextImpl : public InferenceContext {
public:
explicit InferenceContextImpl(std::unique_ptr<Runtime> runtime)
: runtime_(std::move(runtime)) {}
absl::Status Execute() final {
std::lock_guard<std::mutex> lock(guard_);
if (state_ != InferenceContextState::NOT_STARTED) {
return absl::FailedPreconditionError("InferenceContext is not reset");
}
state_ = InferenceContextState::IN_PROGRESS;
return runtime_->Execute();
}
absl::Status Reset() final {
std::lock_guard<std::mutex> lock(guard_);
// TODO(akulik): should Reset not return Status?
state_ = InferenceContextState::NOT_STARTED;
return absl::OkStatus();
}
RuntimeStats stats() const final { return runtime_->stats(); }
private:
std::unique_ptr<Runtime> runtime_;
mutable std::mutex guard_;
InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};
class InferenceContextWithBatchImpl : public InferenceContext {
public:
InferenceContextWithBatchImpl(const ObjectsSizes& sizes,
const ObjectManager* objects,
std::unique_ptr<ObjectManager> refs,
std::unique_ptr<Runtime> runtime)
: sizes_(sizes),
objects_(objects),
refs_(std::move(refs)),
runtime_(std::move(runtime)) {}
absl::Status Execute() final {
std::lock_guard<std::mutex> lock(guard_);
if (state_ != InferenceContextState::NOT_STARTED) {
return absl::FailedPreconditionError("InferenceContext is not reset");
}
state_ = InferenceContextState::IN_PROGRESS;
// Calculate expected number of batches and check that all external objects
// match that number.
int num_batches = 0;
for (const auto& s : sizes_) {
const ValueId id = s.first;
const size_t byte_size = s.second;
auto buffer = objects_->FindBuffer(id);
if (!buffer) continue;
if (buffer->bytes_size() % byte_size) {
return absl::InvalidArgumentError(absl::StrCat(
"Object ", id, " does not match expected byte size: ", byte_size));
}
const size_t b = buffer->bytes_size() / byte_size;
if (num_batches == 0) {
num_batches = b;
} else if (num_batches != b) {
return absl::InvalidArgumentError(absl::StrCat(
"Object ", id, " size does not match expected batch size: ", b,
" vs ", num_batches));
}
}
for (size_t b = 0; b < num_batches; ++b) {
// slice external objects by batch.
for (const auto& s : sizes_) {
const ValueId id = s.first;
const size_t byte_size = s.second;
auto buffer = objects_->FindBuffer(id);
if (buffer) {
auto ref = refs_->FindBuffer(id);
if (!ref) {
return absl::InvalidArgumentError(
absl::StrCat("Reference to ", id, " is not found"));
}
RETURN_IF_ERROR(buffer->MakeView(b * byte_size, byte_size, ref));
}
}
RETURN_IF_ERROR(runtime_->Execute());
}
return absl::OkStatus();
}
absl::Status Reset() final {
std::lock_guard<std::mutex> lock(guard_);
state_ = InferenceContextState::NOT_STARTED;
// TODO(akulik): should Reset not return Status?
return absl::OkStatus();
}
RuntimeStats stats() const final { return runtime_->stats(); }
private:
const ObjectsSizes sizes_;
const ObjectManager* objects_;
// view over external objects provided by a user.
std::unique_ptr<ObjectManager> refs_;
std::unique_ptr<Runtime> runtime_;
mutable std::mutex guard_;
InferenceContextState state_ = InferenceContextState::NOT_STARTED;
};
struct ProgramParameters {
// A list of uniform parameters to be set.
std::vector<Variable> parameters;
// A list of objects to bind to opengl program.
std::vector<Object> objects;
uint3 workgroup_size;
uint3 num_workgroups;
size_t shader_idx;
};
std::string GetShaderHeader(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 CompiledModelImpl
#ifndef TFLITE_GPU_BINARY_RELEASE
: public CompiledModel,
public DeserializationHandler {
#else
: public CompiledModel {
#endif // TFLITE_GPU_BINARY_RELEASE
public:
explicit CompiledModelImpl(const GpuInfo& gpu_info) : gpu_info_(gpu_info) {}
// Called while compiling shaders from scratch
absl::Status Add(const WorkgroupsCalculator& workgroup_calculator,
ShaderCode code) {
// Calculate workgroup size.
uint3 workgroup_size = workgroup_calculator.Calculate(code);
uint3 num_workgroups = DivideRoundUp(code.workload, workgroup_size);
for (const auto& object : code.objects) {
if (IsRef(object)) {
object_sizes_[GetRef(object)] = ByteSizeOf(object);
}
}
// Store full shader and compile it if necessary.
size_t shader_idx;
RETURN_IF_ERROR(
AddFullShader(code.source_code, workgroup_size, &shader_idx));
programs_.push_back({
std::move(code.parameters),
std::move(code.objects),
workgroup_size,
num_workgroups,
shader_idx,
});
return absl::OkStatus();
}
// Store full shader and compile it if necessary.
// Returns full_shader_index
absl::Status AddFullShader(const std::string& partial_shader,
const uint3& workgroup_size, size_t* size) {
std::string shader_src = GetShaderHeader(workgroup_size) + partial_shader;
auto it = shader_to_index_.find(shader_src);
if (it == shader_to_index_.end()) {
GlShader shader;
RETURN_IF_ERROR(
GlShader::CompileShader(GL_COMPUTE_SHADER, shader_src, &shader));
shaders_.push_back(std::move(shader));
shader_to_index_.insert({shader_src, shader_to_index_.size()});
*size = shader_to_index_.size() - 1;
} else {
*size = it->second;
}
return absl::OkStatus();
}
absl::Status NewRun(
const RuntimeOptions& options, const ObjectManager* objects,
CommandQueue* command_queue,
std::unique_ptr<InferenceContext>* inference_context) const final {
std::unique_ptr<ObjectManager> refs;
if (dynamic_batch_) {
// Runtime is using objects from refs that will point to provided objects.
// At this point just create 0 batch slice references.
refs = std::make_unique<ObjectManager>();
for (const auto& s : object_sizes_) {
auto buffer = objects->FindBuffer(s.first);
if (!buffer) continue;
GlBuffer ref;
RETURN_IF_ERROR(buffer->MakeView(0, s.second, &ref));
RETURN_IF_ERROR(refs->RegisterBuffer(s.first, std::move(ref)));
}
}
auto runtime = std::make_unique<Runtime>(options, gpu_info_, command_queue,
refs ? refs.get() : objects);
for (auto& program : programs_) {
RETURN_IF_ERROR(runtime->AddProgram(shaders_[program.shader_idx],
program.parameters, program.objects,
program.num_workgroups));
}
RETURN_IF_ERROR(runtime->PrepareForExecution());
if (dynamic_batch_) {
*inference_context = std::make_unique<InferenceContextWithBatchImpl>(
object_sizes_, objects, std::move(refs), std::move(runtime));
} else {
*inference_context =
std::make_unique<InferenceContextImpl>(std::move(runtime));
}
return absl::OkStatus();
}
#ifndef TFLITE_GPU_BINARY_RELEASE
// Called on deserialization
absl::Status OnProgram(const std::vector<Variable>& parameters,
const std::vector<Object>& objects,
const uint3& workgroup_size,
const uint3& num_workgroups,
size_t partial_shader_index) final {
for (auto& object : objects) {
if (IsRef(object)) {
object_sizes_[GetRef(object)] = ByteSizeOf(object);
}
}
size_t shader_idx;
RETURN_IF_ERROR(AddFullShader(partial_shaders_[partial_shader_index],
workgroup_size, &shader_idx));
programs_.push_back({
parameters,
objects,
workgroup_size,
num_workgroups,
shader_idx,
});
return absl::OkStatus();
}
absl::Status Serialize(
std::vector<uint8_t>* serialized_compiled_model) const final {
SerializedCompiledModelBuilder builder;
// sort shaders first. They need to be serialized in order.
std::vector<std::string> full_shaders(shaders_.size());
for (const auto& shader : shader_to_index_) {
full_shaders[shader.second] = shader.first;
}
absl::flat_hash_map<std::string, size_t> partial_shader_to_index;
std::vector<std::string> partial_shaders;
for (const auto& program : programs_) {
// Remove a header from a shader.
std::string shader_without_header = full_shaders[program.shader_idx];
shader_without_header.erase(0, shader_without_header.find("in;") + 3);
// Insert shader into partial shaders array.
auto it = partial_shader_to_index.find(shader_without_header);
size_t shader_idx;
if (it == partial_shader_to_index.end()) {
shader_idx = partial_shaders.size();
partial_shaders.push_back(shader_without_header);
builder.AddShader(shader_without_header);
partial_shader_to_index.insert({shader_without_header, shader_idx});
} else {
shader_idx = it->second;
}
builder.AddProgram(program.parameters, program.objects,
program.workgroup_size, program.num_workgroups,
shader_idx);
}
CompiledModelOptions options;
options.dynamic_batch = dynamic_batch_;
auto data = builder.Finalize(options);
serialized_compiled_model->insert(serialized_compiled_model->end(),
data.begin(), data.end());
return absl::OkStatus();
}
absl::Status OnShader(absl::Span<const char> shader_src) final {
std::string source(shader_src.data(), shader_src.size());
partial_shaders_.push_back(source);
return absl::OkStatus();
}
void OnOptions(const CompiledModelOptions& options) final {
dynamic_batch_ = options.dynamic_batch;
}
#endif // TFLITE_GPU_BINARY_RELEASE
CompilerStats stats() const final { return stats_; }
void set_dynamic_batch(bool dynamic_batch) { dynamic_batch_ = dynamic_batch; }
private:
const GpuInfo gpu_info_;
bool dynamic_batch_ = false;
std::vector<std::string> partial_shaders_;
std::vector<GlShader> shaders_;
// Shaders are serialized in order of their indices.
absl::flat_hash_map<std::string, size_t> shader_to_index_;
std::deque<ProgramParameters> programs_;
absl::flat_hash_map<ValueId, size_t> object_sizes_;
CompilerStats stats_;
};
} // namespace
absl::Status Compile(const CompilationOptions& options,
const GraphFloat32& model,
const std::unordered_set<int>& tflite_graph_io, // NOLINT
const NodeShader& node_shader,
const WorkgroupsCalculator& workgroup_calculator,
std::unique_ptr<CompiledModel>* compiled_model) {
RETURN_IF_ERROR(CheckBatchSizeForAllValues(model));
GpuInfo gpu_info;
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
if (!gpu_info.IsApiOpenGl31OrAbove()) {
return absl::InternalError(
"OpenGL ES 3.1 or above is required to use OpenGL inference.");
}
auto compiled_model_impl = std::make_unique<CompiledModelImpl>(gpu_info);
compiled_model_impl->set_dynamic_batch(options.dynamic_batch);
auto compiler = NewCompiler(&node_shader, &gpu_info, options);
RETURN_IF_ERROR(compiler->Compile(
model, tflite_graph_io, [&](ShaderCode code) -> absl::Status {
return compiled_model_impl->Add(workgroup_calculator, std::move(code));
}));
*compiled_model = std::move(compiled_model_impl);
return absl::OkStatus();
}
#ifndef TFLITE_GPU_BINARY_RELEASE
absl::Status ReadSerializedModel(
const std::vector<uint8_t>& serialized_model,
std::unique_ptr<CompiledModel>* compiled_model) {
GpuInfo gpu_info;
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info));
if (!gpu_info.IsApiOpenGl31OrAbove()) {
return absl::InternalError(
"OpenGL ES 3.1 or above is required to use OpenGL inference.");
}
auto compiled_model_impl = std::make_unique<CompiledModelImpl>(gpu_info);
RETURN_IF_ERROR(DeserializeCompiledModel(
absl::MakeConstSpan(serialized_model), compiled_model_impl.get()));
*compiled_model = std::move(compiled_model_impl);
return absl::OkStatus();
}
#endif // TFLITE_GPU_BINARY_RELEASE
} // namespace gl
} // namespace gpu
} // namespace tflite
+107
View File
@@ -0,0 +1,107 @@
/* 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_API_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_API_H_
#include <cstdint>
#include <functional>
#include <memory>
#include <unordered_set>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/command_queue.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/object_manager.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime_options.h"
#include "tensorflow/lite/delegates/gpu/gl/stats.h"
#include "tensorflow/lite/delegates/gpu/gl/workgroups/calculator.h"
namespace tflite {
namespace gpu {
namespace gl {
class InferenceContext;
// Represents a model that was prepared for execution. It is stored in a format
// most suitable for execution and optionally may include pre-generated or
// pre-compiled GPU shaders or whatever is needed for efficient execution.
class CompiledModel {
public:
virtual ~CompiledModel() = default;
virtual CompilerStats stats() const = 0;
// Creates new inference context. Result can outlive @this.
//
// NewRun call as well as subsequent calls to InferenceContext methods should
// be done from the same EGL context.
virtual absl::Status NewRun(
const RuntimeOptions& options, const ObjectManager* objects,
CommandQueue* command_queue,
std::unique_ptr<InferenceContext>* inference_context) const = 0;
#ifndef TFLITE_GPU_BINARY_RELEASE
// Serializes compiled model to a string.
// @return true if serialization finished successfully.
virtual absl::Status Serialize(
std::vector<uint8_t>* serialized_compiled_model) const = 0;
#endif // TFLITE_GPU_BINARY_RELEASE
};
// Turns the given model into "compiled" form that is suitable for inference.
absl::Status Compile(const CompilationOptions& options,
const GraphFloat32& model,
const std::unordered_set<int>& tflite_graph_io, // NOLINT
const NodeShader& node_shader,
const WorkgroupsCalculator& workgroup_calculator,
std::unique_ptr<CompiledModel>* compiled_model);
#ifndef TFLITE_GPU_BINARY_RELEASE
// Reads serialized representation previously created with
// CompiledModel::Serialize call.
absl::Status ReadSerializedModel(
const std::vector<uint8_t>& serialized_model,
std::unique_ptr<CompiledModel>* compiled_model);
#endif // TFLITE_GPU_BINARY_RELEASE
// Encapsulates everything needed for one or more inference executions done
// sequentially.
//
// Thread-safe.
class InferenceContext {
public:
virtual ~InferenceContext() = default;
virtual RuntimeStats stats() const = 0;
// Executes inference.
virtual absl::Status Execute() = 0;
// Asks context to reset it for another round. Keep in mind that does not
// affect inputs nor outputs which are not cleared, so it is possible to
// re-use them.
// It is an error to call Reset while previous run is still in progress.
virtual absl::Status Reset() = 0;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_API_H_
+781
View File
@@ -0,0 +1,781 @@
/* 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/api2.h"
#include <algorithm>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_environment.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/converter.h"
#include "tensorflow/lite/delegates/gpu/gl/kernels/registry.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
#include "tensorflow/lite/delegates/gpu/gl/runtime.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#include "tensorflow/lite/delegates/gpu/gl/workgroups/default_calculator.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
std::string GetShaderHeader(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");
}
// 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();
}
absl::Status MaybeAllocateGlBuffer(const TensorObjectDef& def, GlBuffer* ssbo) {
if (def.object_def.object_type != gpu::ObjectType::OPENGL_SSBO) {
return absl::InvalidArgumentError("Tensor object is not GL SSBO");
}
const uint32_t num_elements = NumElements(def);
switch (def.object_def.data_type) {
case DataType::FLOAT32:
return CreateReadWriteShaderStorageBuffer<float>(num_elements, ssbo);
case DataType::FLOAT16:
return CreateReadWriteShaderStorageBuffer<uint16_t>(num_elements, ssbo);
default:
return absl::InternalError(
"Unable to create new GL SSBO. Unsupported data type.");
}
return absl::OkStatus();
}
// Does one-step conversion between internal and external objects.
// It may also allocate external objects if requested.
class DefaultTensorTie : public TensorTie {
public:
DefaultTensorTie(const TensorTieDef& def, TensorObject internal_obj,
ObjectManager* objects)
: TensorTie(def), objects_(objects), internal_obj_(internal_obj) {}
static bool IsSupported(
const TensorTieDef& def,
const TensorObjectConverterBuilder& converter_builder) {
return converter_builder.IsSupported(def.internal_def, def.external_def) &&
converter_builder.IsSupported(def.external_def, def.internal_def);
}
static absl::Status New(const TensorTieDef& def,
TensorObjectConverterBuilder* converter_builder,
ObjectManager* objects,
std::unique_ptr<TensorTie>* tie) {
auto tie_impl =
std::make_unique<DefaultTensorTie>(def, TensorObject{}, objects);
RETURN_IF_ERROR(tie_impl->Init(converter_builder));
*tie = std::move(tie_impl);
return absl::OkStatus();
}
static absl::Status New(const TensorTieDef& def,
TensorObjectConverterBuilder* converter_builder,
TensorObject internal_object,
std::unique_ptr<TensorTie>* tie) {
if (!IsValid(def.internal_def, internal_object)) {
return absl::InternalError("Internal object does not match definition.");
}
auto tie_impl =
std::make_unique<DefaultTensorTie>(def, internal_object, nullptr);
RETURN_IF_ERROR(tie_impl->Init(converter_builder));
*tie = std::move(tie_impl);
return absl::OkStatus();
}
absl::Status CopyToExternalObject() final {
if (!converter_to_) {
return absl::OkStatus();
}
return converter_to_->Convert(internal_obj_, GetExternalObject());
}
absl::Status CopyFromExternalObject() final {
if (!converter_from_) {
return absl::OkStatus();
}
return converter_from_->Convert(GetExternalObject(), internal_obj_);
}
absl::Status SetExternalObject(TensorObject obj) final {
if (!def().external_def.object_def.user_provided) {
return absl::InvalidArgumentError("External object is read-only");
}
if (!IsValid(def().external_def, obj)) {
return absl::InvalidArgumentError("Given object is not valid");
}
external_obj_ = obj;
// Internal object is not initialized when external object is going to be
// used as is, with not conversion. In this case we don't need to have a
// separate internal object, we are just registering the appropriate
// external object in the object manager for the future binding in the
// inference runner.
if (!IsObjectInitialized(internal_obj_)) {
if (def().external_def.object_def.object_type ==
gpu::ObjectType::OPENGL_SSBO) {
auto ssbo = std::get_if<OpenGlBuffer>(&obj);
GlBuffer buffer;
RETURN_IF_ERROR(WrapSSBO(*ssbo, &buffer));
RETURN_IF_ERROR(objects_->RegisterBuffer(def().id, std::move(buffer)));
} else {
return absl::InternalError("Unexpected object type.");
}
}
return absl::OkStatus();
}
TensorObject GetExternalObject() final { return external_obj_; }
private:
bool IsSameDef() const {
const auto& external_def = def().external_def.object_def;
const auto& internal_def = def().internal_def.object_def;
return (external_def.object_type == internal_def.object_type &&
external_def.data_type == internal_def.data_type &&
external_def.data_layout == internal_def.data_layout) ||
// Check for equivalent layouts that have the same size.
(external_def.object_type == internal_def.object_type &&
external_def.data_type == internal_def.data_type &&
external_def.data_layout == DataLayout::BHWC &&
internal_def.data_layout == DataLayout::DHWC4 &&
def().external_def.dimensions.c == 4);
}
absl::Status Init(TensorObjectConverterBuilder* converter_builder) {
// First check is an object is user provided.
const auto& external_def = def().external_def.object_def;
const bool is_same_def = IsSameDef();
if (!is_same_def) {
RETURN_IF_ERROR(converter_builder->MakeConverter(
def().internal_def, def().external_def, &converter_to_));
RETURN_IF_ERROR(converter_builder->MakeConverter(
def().external_def, def().internal_def, &converter_from_));
}
if (external_def.user_provided) {
if (is_same_def) {
// Entering this scope indicates that external object is used with no
// conversion to internal one. We still need to register the stub buffer
// in the object manager, even that the real external object is not
// available yet. Later, when the SetExternalObject() is called, the
// proper external object will rewrite this record. The stub value will
// allow us to correctly prepare the runtime for the late binding of
// this object.
GlBuffer invalid_buffer;
RETURN_IF_ERROR(
objects_->RegisterBuffer(def().id, std::move(invalid_buffer)));
return absl::OkStatus();
}
// Object is provided by a user, but runtime expects different object
// type. Therefore, we have to allocate internal object and convert.
return MaybeAllocateInternalObject();
} else {
RETURN_IF_ERROR(MaybeAllocateInternalObject());
if (is_same_def) {
// Object is NOT provided by a user, but it matches definition expected
// by runtime. Conversion is not needed.
external_obj_ = internal_obj_;
return absl::OkStatus();
}
// Object is NOT provided by a user.
return MaybeAllocateExternalObject();
}
return absl::OkStatus();
}
absl::Status MaybeAllocateInternalObject() {
const TensorObjectDef& d = def().internal_def;
if (d.object_def.user_provided) {
return absl::OkStatus();
}
switch (d.object_def.object_type) {
case gpu::ObjectType::OPENGL_SSBO: {
GlBuffer ssbo;
RETURN_IF_ERROR(MaybeAllocateGlBuffer(d, &ssbo));
internal_obj_ = OpenGlBuffer{ssbo.id()};
RETURN_IF_ERROR(objects_->RegisterBuffer(def().id, std::move(ssbo)));
break;
}
// TODO(akulik): support textures as internal object when compiler permits
default:
return absl::InternalError("Unexpected object type");
}
return absl::OkStatus();
}
absl::Status MaybeAllocateExternalObject() {
const TensorObjectDef& d = def().external_def;
switch (d.object_def.object_type) {
case gpu::ObjectType::CPU_MEMORY: {
size_t bytes_size = NumElements(d) * SizeOf(d.object_def.data_type);
cpu_memory_.resize(bytes_size);
external_obj_ = CpuMemory{cpu_memory_.data(), cpu_memory_.size()};
break;
}
case gpu::ObjectType::OPENGL_SSBO: {
RETURN_IF_ERROR(MaybeAllocateGlBuffer(d, &external_ssbo_));
external_obj_ = OpenGlBuffer{external_ssbo_.id()};
GlBuffer bbb;
RETURN_IF_ERROR(WrapSSBO(OpenGlBuffer{external_ssbo_.id()}, &bbb));
break;
}
default:
return absl::InternalError("Unexpected object type");
}
return absl::OkStatus();
}
ObjectManager* objects_;
// hold references to objects.
TensorObject internal_obj_;
TensorObject external_obj_;
// Hold actual objects.
GlBuffer external_ssbo_;
std::vector<uint8_t> cpu_memory_;
std::unique_ptr<TensorObjectConverter> converter_to_;
std::unique_ptr<TensorObjectConverter> converter_from_;
};
// Copies data to intermediate OpenGL buffer and then does two step conversion.
// It drives the following cases were one-step conversion is not supported:
// - CPU BHWC -> GL buffer BHWC -> GL texture DHWC4.
class TwoStepTensorTie : public TensorTie {
public:
explicit TwoStepTensorTie(const TensorTieDef& def) : TensorTie(def) {}
static bool IsSupported(
const TensorTieDef& def,
const TensorObjectConverterBuilder& converter_builder) {
auto defs = MakeOuterInnerDefs(def);
return DefaultTensorTie::IsSupported(defs.first, converter_builder) &&
DefaultTensorTie::IsSupported(defs.second, converter_builder);
}
static absl::Status New(const TensorTieDef& def,
TensorObjectConverterBuilder* converter_builder,
ObjectManager* objects,
std::unique_ptr<TensorTie>* tie) {
auto tie_impl = std::make_unique<TwoStepTensorTie>(def);
RETURN_IF_ERROR(tie_impl->Init(converter_builder, objects));
*tie = std::move(tie_impl);
return absl::OkStatus();
}
absl::Status CopyToExternalObject() final {
RETURN_IF_ERROR(inner_tie_->CopyToExternalObject());
return outer_tie_->CopyToExternalObject();
}
absl::Status CopyFromExternalObject() final {
RETURN_IF_ERROR(outer_tie_->CopyFromExternalObject());
return inner_tie_->CopyFromExternalObject();
}
absl::Status SetExternalObject(TensorObject obj) final {
return outer_tie_->SetExternalObject(obj);
}
TensorObject GetExternalObject() final {
return outer_tie_->GetExternalObject();
}
private:
static std::pair<TensorTieDef, TensorTieDef> MakeOuterInnerDefs(
const TensorTieDef& def) {
TensorTieDef outer_def;
outer_def.external_def = def.external_def;
outer_def.internal_def = def.external_def;
outer_def.internal_def.object_def.object_type =
gpu::ObjectType::OPENGL_SSBO;
// Will not allocate new SSBO
outer_def.internal_def.object_def.user_provided = true;
TensorTieDef inner_def;
inner_def.id = def.id;
inner_def.external_def = outer_def.internal_def;
// Should not allocate external object.
inner_def.external_def.object_def.user_provided = false;
// Reflects what is actually supported by compiler.
inner_def.internal_def.dimensions = inner_def.external_def.dimensions;
inner_def.internal_def.object_def.data_type = DataType::FLOAT32;
inner_def.internal_def.object_def.data_layout = DataLayout::DHWC4;
inner_def.internal_def.object_def.object_type =
gpu::ObjectType::OPENGL_SSBO;
// It may allocate another internal object and should register it to
// ObjectManager.
inner_def.internal_def.object_def.user_provided = false;
return std::make_pair(outer_def, inner_def);
}
absl::Status Init(TensorObjectConverterBuilder* converter_builder,
ObjectManager* objects) {
auto defs = MakeOuterInnerDefs(def());
RETURN_IF_ERROR(DefaultTensorTie::New(defs.second, converter_builder,
objects, &inner_tie_));
return DefaultTensorTie::New(defs.first, converter_builder,
inner_tie_->GetExternalObject(), &outer_tie_);
}
std::unique_ptr<TensorTie> inner_tie_;
std::unique_ptr<TensorTie> outer_tie_;
};
// Responsible for creating new tensor tie objects.
class TensorTieFactory {
public:
explicit TensorTieFactory(const InferenceEnvironmentOptions& env_options)
: converter_builder_(NewConverterBuilder(env_options.queue)) {}
bool IsSupported(const TensorTieDef& def) const {
return IsValid(def.external_def.object_def) &&
(DefaultTensorTie::IsSupported(def, *converter_builder_) ||
TwoStepTensorTie::IsSupported(def, *converter_builder_));
}
absl::Status NewTensorTie(const TensorTieDef& def, ObjectManager* objects,
std::unique_ptr<TensorTie>* tie) {
auto converter = converter_builder_.get();
if (DefaultTensorTie::IsSupported(def, *converter)) {
return DefaultTensorTie::New(def, converter, objects, tie);
}
if (TwoStepTensorTie::IsSupported(def, *converter)) {
return TwoStepTensorTie::New(def, converter, objects, tie);
}
return absl::UnimplementedError("Unsupported tensor tie definition.");
}
private:
std::unique_ptr<TensorObjectConverterBuilder> converter_builder_;
};
class InferenceRunnerImpl : public InferenceRunner {
public:
InferenceRunnerImpl(std::unique_ptr<Runtime> runtime,
std::unique_ptr<ObjectManager> objects
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
,
int gpu_invoke_loop_times
#endif
)
: runtime_(std::move(runtime)),
external_objects_(std::move(objects))
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
,
gpu_invoke_loop_times_(gpu_invoke_loop_times)
#endif
{
}
absl::Status Initialize(const std::vector<TensorTieDef>& input_defs,
const std::vector<TensorTieDef>& output_defs,
TensorTieFactory* tie_factory) {
RETURN_IF_ERROR(LinkTensors(input_defs, tie_factory, &input_tensor_ties_));
RETURN_IF_ERROR(
LinkTensors(output_defs, tie_factory, &output_tensor_ties_));
for (const auto& output_def : output_defs) {
output_to_cpu_ |= output_def.external_def.object_def.object_type ==
gpu::ObjectType::CPU_MEMORY;
}
return absl::OkStatus();
}
std::vector<TensorObjectDef> inputs() const override {
return GetExternalDefinitions(input_tensor_ties_);
}
std::vector<TensorObjectDef> outputs() const override {
return GetExternalDefinitions(output_tensor_ties_);
}
absl::Status GetInputObject(int index, TensorObject* object) override {
if (index < 0 || index >= input_tensor_ties_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
*object = input_tensor_ties_[index]->GetExternalObject();
return absl::OkStatus();
}
absl::Status GetOutputObject(int index, TensorObject* object) override {
if (index < 0 || index >= output_tensor_ties_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
*object = output_tensor_ties_[index]->GetExternalObject();
return absl::OkStatus();
}
absl::Status SetInputObject(int index, TensorObject object) override {
if (index < 0 || index >= input_tensor_ties_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
return input_tensor_ties_[index]->SetExternalObject(object);
}
absl::Status SetOutputObject(int index, TensorObject object) override {
if (index < 0 || index >= output_tensor_ties_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
return output_tensor_ties_[index]->SetExternalObject(object);
}
absl::Status Run() override {
for (auto& obj : input_tensor_ties_) {
RETURN_IF_ERROR(obj->CopyFromExternalObject());
}
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
// TODO(b/328511338): Remove code enabled by TFLITE_GPU_ENABLE_INVOKE_LOOP
// when Async API solution is ready to replace it.
for (int i = 0; i < gpu_invoke_loop_times_; i++) {
RETURN_IF_ERROR(runtime_->Execute());
}
#else
RETURN_IF_ERROR(runtime_->Execute());
#endif // TFLITE_GPU_ENABLE_INVOKE_LOOP
for (auto& obj : output_tensor_ties_) {
RETURN_IF_ERROR(obj->CopyToExternalObject());
}
RETURN_IF_ERROR(runtime_->command_queue()->Flush());
if (output_to_cpu_) {
RETURN_IF_ERROR(runtime_->command_queue()->WaitForCompletion());
}
return absl::OkStatus();
}
private:
absl::Status LinkTensors(const std::vector<TensorTieDef>& defs,
TensorTieFactory* tie_factory,
std::vector<std::unique_ptr<TensorTie>>* objects) {
objects->reserve(defs.size());
for (auto& def : defs) {
std::unique_ptr<TensorTie> object;
RETURN_IF_ERROR(
tie_factory->NewTensorTie(def, external_objects_.get(), &object));
objects->push_back(std::move(object));
}
return absl::OkStatus();
}
static std::vector<TensorObjectDef> GetExternalDefinitions(
const std::vector<std::unique_ptr<TensorTie>>& objects) {
std::vector<TensorObjectDef> defs;
defs.reserve(objects.size());
for (auto& obj : objects) {
defs.push_back(obj->def().external_def);
}
return defs;
}
std::unique_ptr<Runtime> runtime_;
std::unique_ptr<ObjectManager> external_objects_;
std::vector<std::unique_ptr<TensorTie>> input_tensor_ties_;
std::vector<std::unique_ptr<TensorTie>> output_tensor_ties_;
bool output_to_cpu_ = false;
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
int gpu_invoke_loop_times_;
#endif
};
class InferenceBuilderImpl : public InferenceBuilder {
public:
InferenceBuilderImpl(const InferenceEnvironmentOptions& env_options,
const InferenceOptions& options, GraphFloat32 graph,
const GpuInfo* gpu_info)
: env_options_(env_options),
options_(options),
graph_(std::move(graph)),
gpu_info_(gpu_info),
tie_factory_(env_options_)
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
,
gpu_invoke_loop_times_(options.gpu_invoke_loop_times)
#endif
{
}
absl::Status Initialize() {
inputs_ = LinkTensors(graph_.inputs());
outputs_ = LinkTensors(graph_.outputs());
return absl::OkStatus();
}
std::vector<TensorObjectDef> inputs() const final {
return GetExternalDefinitions(inputs_);
}
std::vector<TensorObjectDef> outputs() const final {
return GetExternalDefinitions(outputs_);
}
absl::Status SetInputShape(int index, const Dimensions& dimensions) final {
if (index < 0 || index >= inputs_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
return absl::UnimplementedError("Changing input shapes is not supported");
}
absl::Status SetInputObjectDef(int index, ObjectDef new_def) final {
if (index < 0 || index >= inputs_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
auto def = inputs_[index];
def.external_def.object_def = new_def;
if (!tie_factory_.IsSupported(def)) {
return absl::InvalidArgumentError(
"New object definition is not supported.");
}
inputs_[index] = def;
return absl::OkStatus();
}
absl::Status SetOutputObjectDef(int index, ObjectDef new_def) final {
if (index < 0 || index >= outputs_.size()) {
return absl::OutOfRangeError("Index is out of range");
}
auto def = outputs_[index];
def.external_def.object_def = new_def;
if (!tie_factory_.IsSupported(def)) {
return absl::InvalidArgumentError(
"New object definition is not supported.");
}
outputs_[index] = def;
return absl::OkStatus();
}
absl::Status Build(std::unique_ptr<InferenceRunner>* runner) final {
for (const auto& input : inputs_) {
if (NumElements(input.external_def) >
std::numeric_limits<int32_t>::max()) {
return absl::InvalidArgumentError(
"Input tensor size exceeds 32-bit indexing limits.");
}
}
for (const auto& output : outputs_) {
if (NumElements(output.external_def) >
std::numeric_limits<int32_t>::max()) {
return absl::InvalidArgumentError(
"Output tensor size exceeds 32-bit indexing limits.");
}
}
auto kernels = NewNodeShaderRegistry();
CompilationOptions compiler_options;
compiler_options.allow_precision_loss =
GetPosition(options_, InferencePriority::MAX_PRECISION) > 1;
compiler_options.inline_parameters =
options_.usage == InferenceUsage::SUSTAINED_SPEED &&
GetPosition(options_, InferencePriority::MIN_LATENCY) == 1;
if (GetRelativeImportance(options_, InferencePriority::MIN_MEMORY_USAGE,
InferencePriority::MIN_LATENCY) ==
PriorityImportance::HIGHER) {
// Buffers have far better memory utilization.
compiler_options.preferred_obj_type = ObjectType::BUFFER;
compiler_options.ref_obj_type = ObjectType::BUFFER;
}
auto compiler = NewCompiler(kernels.get(), gpu_info_, compiler_options);
auto workgroup_calculator = NewDefaultWorkgroupsCalculator(*gpu_info_);
auto external_objects = std::make_unique<ObjectManager>();
std::vector<GlShader> shaders;
absl::flat_hash_map<std::string, size_t> shader_to_index;
RuntimeOptions runtime_options;
auto runtime =
std::make_unique<Runtime>(runtime_options, *gpu_info_,
env_options_.queue, external_objects.get());
Runtime* runtime_ptr = runtime.get();
auto runner_impl = std::make_unique<InferenceRunnerImpl>(
std::move(runtime), std::move(external_objects)
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
,
gpu_invoke_loop_times_
#endif
);
RETURN_IF_ERROR(runner_impl->Initialize(inputs_, outputs_, &tie_factory_));
RETURN_IF_ERROR(
compiler->Compile(graph_, {}, [&](ShaderCode code) -> absl::Status {
auto workgroup = workgroup_calculator->Calculate(code);
size_t shader_index;
std::string shader_src =
GetShaderHeader(workgroup) + code.source_code;
// Check if a shader was already compiled.
auto it = shader_to_index.find(shader_src);
if (it == shader_to_index.end()) {
GlShader shader;
RETURN_IF_ERROR(GlShader::CompileShader(GL_COMPUTE_SHADER,
shader_src, &shader));
shaders.push_back(std::move(shader));
shader_to_index.insert({shader_src, shader_to_index.size()});
shader_index = shader_to_index.size() - 1;
} else {
shader_index = it->second;
}
auto num_workgroups = DivideRoundUp(code.workload, workgroup);
return runtime_ptr->AddProgram(shaders[shader_index], code.parameters,
code.objects, num_workgroups);
}));
RETURN_IF_ERROR(runtime_ptr->PrepareForExecution());
*runner = std::move(runner_impl);
return absl::OkStatus();
}
private:
// Links internal tensors with external user-facing objects.
std::vector<TensorTieDef> LinkTensors(const std::vector<Value*>& values) {
std::vector<TensorTieDef> links;
links.reserve(values.size());
for (const auto& value : values) {
TensorObjectDef external_def;
// So far the compiler always forces inputs and outputs to be in the fixed
// format.
const auto& shape = value->tensor.shape;
external_def.dimensions = Dimensions(shape.b, shape.h, shape.w, shape.c);
external_def.object_def.data_type = DataType::FLOAT32;
external_def.object_def.data_layout = DataLayout::DHWC4;
external_def.object_def.object_type = gpu::ObjectType::OPENGL_SSBO;
// Internal object is not expected to be provided by user because: if
// external and internal objects have same defs, the external object is
// propagated and just used as an internal one; otherwise, if they have
// different defs, internal object will be created, because it is not
// provided by user.
TensorObjectDef internal_def = external_def;
external_def.object_def.user_provided = true;
internal_def.object_def.user_provided = false;
AccessType access =
graph_.IsGraphInput(value->id) ? AccessType::READ : AccessType::WRITE;
links.push_back({value->id, access, internal_def, external_def});
}
return links;
}
static std::vector<TensorObjectDef> GetExternalDefinitions(
const std::vector<TensorTieDef>& links) {
std::vector<TensorObjectDef> defs;
defs.reserve(links.size());
for (auto& desc : links) {
defs.push_back(desc.external_def);
}
return defs;
}
const InferenceEnvironmentOptions env_options_;
const InferenceOptions options_;
GraphFloat32 graph_;
const GpuInfo* gpu_info_;
std::vector<TensorTieDef> inputs_;
std::vector<TensorTieDef> outputs_;
TensorTieFactory tie_factory_;
#ifdef TFLITE_GPU_ENABLE_INVOKE_LOOP
int gpu_invoke_loop_times_;
#endif
};
class InferenceEnvironmentImpl : public InferenceEnvironment {
public:
explicit InferenceEnvironmentImpl(const InferenceEnvironmentOptions& options)
: env_options_(options) {}
absl::Status Init() {
RETURN_IF_ERROR(EglEnvironment::NewEglEnvironment(&egl_env_));
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info_));
properties_.is_opengl_available = gpu_info_.IsApiOpenGl31OrAbove();
if (!properties_.is_opengl_available) {
return absl::InternalError(
"OpenGL ES 3.1 or above is required to use OpenGL inference.");
}
if (!env_options_.queue) {
queue_ = NewCommandQueue(gpu_info_);
env_options_.queue = queue_.get();
}
return absl::OkStatus();
}
absl::Status NewInferenceBuilder(
GraphFloat32&& model, const InferenceOptions& options,
std::unique_ptr<InferenceBuilder>* builder) final {
if (!IsValid(options)) {
return absl::InvalidArgumentError("InferenceOptions are invalid.");
}
InferenceOptions resolved_options = options;
ResolveAutoPriority(&resolved_options);
RETURN_IF_ERROR(CheckBatchSizeForAllValues(model));
auto builder_impl = std::make_unique<InferenceBuilderImpl>(
env_options_, resolved_options, std::move(model), &gpu_info_);
RETURN_IF_ERROR(builder_impl->Initialize());
*builder = std::move(builder_impl);
return absl::OkStatus();
}
const InferenceEnvironmentProperties& properties() const {
return properties_;
}
private:
std::unique_ptr<EglEnvironment> egl_env_;
std::unique_ptr<CommandQueue> queue_;
InferenceEnvironmentOptions env_options_;
GpuInfo gpu_info_;
InferenceEnvironmentProperties properties_;
};
} // namespace
absl::Status NewInferenceEnvironment(
const InferenceEnvironmentOptions& options,
std::unique_ptr<InferenceEnvironment>* environment,
InferenceEnvironmentProperties* properties) {
auto env_impl = std::make_unique<InferenceEnvironmentImpl>(options);
absl::Status status = env_impl->Init();
if (properties) {
*properties = env_impl->properties();
}
RETURN_IF_ERROR(status);
*environment = std::move(env_impl);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
+64
View File
@@ -0,0 +1,64 @@
/* 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_API2_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_API2_H_
#include <cstdint>
#include <memory>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/api.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/command_queue.h"
namespace tflite {
namespace gpu {
namespace gl {
struct InferenceOptions : public tflite::gpu::InferenceOptions {};
struct InferenceEnvironmentProperties {
bool is_opengl_available = false;
};
// Manages all resources that need to stay around as long as any inference is
// running using the OpenGL backend.
class InferenceEnvironment {
public:
virtual ~InferenceEnvironment() = default;
virtual absl::Status NewInferenceBuilder(
GraphFloat32&& model, const InferenceOptions& options,
std::unique_ptr<InferenceBuilder>* builder) = 0;
};
struct InferenceEnvironmentOptions {
CommandQueue* queue = nullptr;
};
// Creates a new OpenGL environment that needs to stay around until all
// inference runners are destroyed.
absl::Status NewInferenceEnvironment(
const InferenceEnvironmentOptions& options,
std::unique_ptr<InferenceEnvironment>* environment,
InferenceEnvironmentProperties* properties /* optional */);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_API2_H_
@@ -0,0 +1,105 @@
/* 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/command_queue.h"
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_sync.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class DefaultCommandQueue : public CommandQueue {
public:
absl::Status Dispatch(const GlProgram& program,
const uint3& workgroups) override {
RETURN_IF_ERROR(program.Dispatch(workgroups));
return TFLITE_GPU_CALL_GL(glMemoryBarrier, GL_ALL_BARRIER_BITS);
}
absl::Status WaitForCompletion() override {
// TODO(akulik): Maybe let the user choose which wait method to use.
return GlActiveSyncWait();
}
absl::Status Flush() override { return absl::OkStatus(); }
};
// On Adreno do flush periodically as this affects performance. Command queue
// needs to be manually managed to ensure that accumulated work goes to GPU as
// fast as it can.
//
// Also, on older Adreno devices glFlush is required after every memory barrier
// to avoid hitting GPU driver bug.
class AdrenoCommandQueue : public DefaultCommandQueue {
public:
explicit AdrenoCommandQueue(int flush_every_n)
: flush_every_n_(flush_every_n) {}
absl::Status Dispatch(const GlProgram& program,
const uint3& workgroups) final {
RETURN_IF_ERROR(DefaultCommandQueue::Dispatch(program, workgroups));
if ((++program_counter_ % flush_every_n_) == 0) {
glFlush();
}
return absl::OkStatus();
}
absl::Status WaitForCompletion() override {
program_counter_ = 0;
return DefaultCommandQueue::WaitForCompletion();
}
absl::Status Flush() final {
// Flush exactly once after the last dispatch.
if (program_counter_ != 0) {
program_counter_ = 0;
glFlush();
}
return absl::OkStatus();
}
private:
const int flush_every_n_;
int program_counter_ = 0;
};
} // namespace
std::unique_ptr<CommandQueue> NewCommandQueue(const GpuInfo& gpu_info) {
if (gpu_info.IsAdreno()) {
int flush_every_n = 1;
// On Adreno 630 and Adreno 505 there is up to 2x performance boost when
// glFlush happens not so often.
if (gpu_info.adreno_info.adreno_gpu == AdrenoGpu::kAdreno630 ||
gpu_info.adreno_info.adreno_gpu == AdrenoGpu::kAdreno505) {
flush_every_n = 10;
}
return std::make_unique<AdrenoCommandQueue>(flush_every_n);
}
return std::make_unique<DefaultCommandQueue>();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,55 @@
/* 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_COMMAND_QUEUE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMMAND_QUEUE_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
namespace tflite {
namespace gpu {
namespace gl {
// GL programs can be executed directly via dispatch call or using a queue
// abstraction similar to one in OpenCL and Vulkan.
// CommandQueue executes given programs in order as they come.
class CommandQueue {
public:
virtual ~CommandQueue() = default;
// Dispatches a program. It may or may not call glFlush.
virtual absl::Status Dispatch(const GlProgram& program,
const uint3& workgroups) = 0;
// Called at the end of dispatching of all programs.
virtual absl::Status Flush() = 0;
// Waits until all programs dispatched prior this call are completed.
virtual absl::Status WaitForCompletion() = 0;
};
// By default memory barrier is inserted after every dispatch.
std::unique_ptr<CommandQueue> NewCommandQueue(const GpuInfo& gpu_info);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMMAND_QUEUE_H_
@@ -0,0 +1,30 @@
// 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.
namespace tflite.gpu.gl.data;
table Uint3 {
x:uint32;
y:uint32;
z:uint32;
}
table Uint2 {
x:uint32;
y:uint32;
}
table Uint1 {
x:uint32;
}
@@ -0,0 +1,169 @@
// 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 "common.fbs";
namespace tflite.gpu.gl.data;
file_identifier "AFCM";
file_extension "flow";
// Encapsulates entire OpenGL program with all necessary dependencies and
// parameters.
table Program {
// A collection of objects this program refers to.
objects:[Object];
// Uniform parameters to be set before execution.
parameters:[UniformParameter];
// Defines the number of work groups.
number_workgroups:Uint3;
// Defines the size of a workgroup.
workgroup_size:Uint3;
// Reference to a shader in this compiled model.
shader_index:uint32;
// Contains binary code that was once created after successful shader
// compilation. Normally it is much faster to instantiate a program from
// compiled binary.
binary:ProgramBinary;
}
// Compiled binary representation of a program.
table ProgramBinary {
format:uint32; // GLenum
// Compiled binary shader blob extracted from GL.
binary:[ubyte];
}
enum ParameterType : byte {
INT32 = 0,
UINT32 = 1,
FLOAT32 = 2,
INT32_2 = 3,
}
enum DataType : byte {
UNKNOWN = 0,
FLOAT32 = 1,
FLOAT16 = 2,
INT32 = 3,
INT16 = 4,
}
union DataVariant {
DataInt32,
DataFloat,
DataUint32,
}
table DataFloat {
data:[float];
}
table DataInt32 {
data:[int32];
}
table DataUint32 {
data:[uint32];
}
table UniformParameter {
name:string;
type:ParameterType;
// Data is optional. If it is known in advance, it is encoded here, otherwise
// a parameter will be set in runtime.
data:DataVariant;
}
enum AccessType : byte {
READ = 0,
WRITE = 1,
READ_WRITE = 2,
}
enum ObjectType : byte {
UNKNOWN = 0,
BUFFER = 1,
TEXTURE = 2,
}
union ObjectVariant {
ObjectData,
ObjectRef,
}
union ObjectSize {
Uint1,
Uint2,
Uint3,
}
table Object {
access:AccessType;
binding:uint32;
data_type:DataType;
type:ObjectType;
size:ObjectSize;
object:ObjectVariant;
}
// Represents a reference to another object provided by object manager.
table ObjectRef {
// Unique global identifier to be used by an object manager to lookup this
// buffer.
global_id:uint32;
}
table ObjectData {
data:[uint8];
}
// Represents entire model as a collection of programs, inputs and outputs.
table CompiledModel {
parameters:Parameters;
// A collection of shaders used by programs.
shaders:[string];
// A collection of programs that need to be executed in the same order.
programs:[Program];
}
table Parameters {
// indicated flow engine version that compiled this model. If engine version
// does not match compiled model, then a model need to be recompiled.
// version:uint32; // not implemented
// Could potentially be used to track environment when a model was compiled
// and detect whether it was changed and model recompilation is needed.
// environment_hash:uint32; // not implemented
dynamic_batch:bool;
}
root_type CompiledModel;
@@ -0,0 +1,323 @@
/* 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/compiler.h"
#include <algorithm>
#include <any>
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#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/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_auto_input.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_inline.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_inplace.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/shader_codegen.h"
#include "tensorflow/lite/delegates/gpu/gl/float16_conversions.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif // __ANDROID__
namespace tflite {
namespace gpu {
namespace gl {
namespace {
struct ExceedSizeChecker {
bool operator()(uint32_t v) const { return v > max_size.x; }
bool operator()(const uint2& v) const {
return v.x > max_size.x || v.y > max_size.y;
}
bool operator()(const uint3& v) const {
return v.x > max_size.x || v.y > max_size.y || v.z > max_z_size;
}
int2 max_size;
int max_z_size;
};
// Returns true if any size variable exceeds the given limit
bool ExceedsMaxSize(const Object& object, const GpuInfo& gpu_info) {
ExceedSizeChecker size_checker;
size_checker.max_size =
int2(gpu_info.GetMaxImage2DWidth(), gpu_info.GetMaxImage2DHeight());
size_checker.max_z_size = gpu_info.GetMaxImage2DArrayLayers();
return std::visit(size_checker, object.size);
}
ObjectType ChooseFastestObjectType(const GpuInfo& gpu_info) {
return gpu_info.IsAdreno() ? ObjectType::TEXTURE : ObjectType::BUFFER;
}
ObjectType ChooseFastestRefObjectType(const GpuInfo& gpu_info,
const CompilationOptions& options) {
if (!gpu_info.IsAdreno()) {
return ObjectType::BUFFER;
}
if (gpu_info.adreno_info.adreno_gpu == AdrenoGpu::kAdreno630) {
return ObjectType::TEXTURE;
} else {
return options.allow_precision_loss ? ObjectType::TEXTURE
: ObjectType::BUFFER;
}
}
// Compiler executes the following steps:
// 1. Runs NodeShader for every node in the input graph.
// 2. Creates a compiled graph that mirrors the input graph and keeps
// GeneratedCode in operation's attributes.
// 3. Fuses nodes in the compiled graph.
// 4. Generates the full shader code using the nodes in the compiled graph.
class CompilerImpl : public Compiler {
public:
// We use const GpuInfo* because it doesn't let you assign temporary object
CompilerImpl(const NodeShader* node_shader, const GpuInfo* gpu_info,
const CompilationOptions& options)
: node_shader_(*node_shader), gpu_info_(*gpu_info), options_(options) {
if (options_.preferred_obj_type == ObjectType::UNKNOWN) {
options_.preferred_obj_type = ChooseFastestObjectType(*gpu_info);
}
if (options_.ref_obj_type == ObjectType::UNKNOWN) {
options_.ref_obj_type = ChooseFastestRefObjectType(*gpu_info, options);
}
#ifdef __ANDROID__
// Circumvent FP16 bug with Adreno 660 on Android SDK 30.
if (gpu_info_.IsAdreno() &&
gpu_info_.adreno_info.adreno_gpu == AdrenoGpu::kAdreno660) {
char sdk_version[PROP_VALUE_MAX];
__system_property_get("ro.build.version.sdk", sdk_version);
if (!strcmp(sdk_version, "30")) options_.allow_precision_loss = false;
}
#endif // __ANDROID__
}
absl::Status Compile(
const GraphFloat32& graph,
const std::unordered_set<int>& tflite_graph_io, // NOLINT
const ShaderCodeCallback& callback) final {
// It is important to have ids in a compiled graph identical to the given
// graph.
RETURN_IF_ERROR(graph.MakeExactCopy(&compiled_graph_));
// Clear out batch dimension for dynamic batch support.
if (options_.dynamic_batch) {
for (auto value : compiled_graph_.values()) {
value->tensor.shape.b = 1;
}
}
// Generate a shader for a node and all input/output objects.
for (auto node : compiled_graph_.nodes()) {
CompiledNodeAttributes attr;
attr.node_indices.push_back(node->id);
NodeShader::GenerationContext ctx = {&gpu_info_, options_,
node->operation.type,
node->operation.attributes};
for (const auto& tensor : graph.FindInputs(node->id)) {
const auto& shape = tensor->tensor.shape;
ctx.input_shapes.push_back({shape.b, shape.h, shape.w, shape.c});
}
for (const auto& tensor : graph.FindOutputs(node->id)) {
const auto& shape = tensor->tensor.shape;
ctx.output_shapes.push_back({shape.b, shape.h, shape.w, shape.c});
}
RETURN_IF_ERROR(node_shader_.GenerateCode(ctx, &attr.code));
node->operation.attributes = std::move(attr);
}
ModelTransformer transformer(&compiled_graph_);
if (options_.fuse_operations) {
FuseAutoOutputWithInline fuse_inline;
if (!transformer.Apply("fuse_auto_with_inline", &fuse_inline)) {
return absl::InternalError("fuse_auto_with_inline failed");
}
FuseInplaceUpdate fuse_inplace;
if (!transformer.Apply("fuse_inplace_update", &fuse_inplace)) {
return absl::InternalError("fuse_inplace failed");
}
if (options_.auto_input_fusion) {
FuseAutoInput fuse_auto_input;
if (!transformer.Apply("fuse_auto_input", &fuse_auto_input)) {
return absl::InternalError("fuse_auto_input failed");
}
}
}
RemoveUnusedInplaceUpdates remove_inplace_updates;
if (!transformer.Apply("remove_inplace_updates", &remove_inplace_updates)) {
return absl::InternalError("remove_inplace_updates failed");
}
// Prepare internal objects.
absl::flat_hash_map<ValueId, Object> objects;
for (auto value : compiled_graph_.values()) {
Object object = MakePHWC4Ref(value->id, value->tensor.shape);
object.data_type = value->tensor.type;
// External references may not be upgraded to f16 nor be represented as
// textures.
const bool is_external =
graph.IsGraphInput(value->id) || graph.IsGraphOutput(value->id) ||
tflite_graph_io.find(value->tensor.ref) != tflite_graph_io.end();
if (is_external) {
object.object_type = ObjectType::BUFFER;
} else if (options_.allow_precision_loss) {
MaybeConvertToFloat16(&object);
}
objects[value->id] = std::move(object);
}
// Prepare readonly objects and check whether object types are supported.
for (auto node : compiled_graph_.nodes()) {
auto& attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
// Set workload explicitly.
if (attr.code.workload == uint3()) {
auto outputs = compiled_graph_.FindOutputs(node->id);
auto shape = outputs[0]->tensor.shape;
for (auto output : outputs) {
if (shape != output->tensor.shape) {
return absl::FailedPreconditionError(
"Workload uint3() requires all output sizes to match");
}
}
attr.code.workload = uint3(shape.w, shape.h, DivideRoundUp(shape.c, 4));
}
int num_textures = 0;
// Counts number of used textures and chooses ObjectType for an object.
auto set_object_type = [&](Object* object) {
if (object->object_type == ObjectType::BUFFER) {
// Don't change from buffer once it is set.
return;
}
bool is_ref = IsRef(*object);
if (num_textures < gpu_info_.GetMaxImageArguments() &&
!ExceedsMaxSize(*object, gpu_info_) &&
(object->object_type == ObjectType::TEXTURE ||
(is_ref && options_.ref_obj_type == ObjectType::TEXTURE) ||
(!is_ref && options_.preferred_obj_type == ObjectType::TEXTURE))) {
object->object_type = ObjectType::TEXTURE;
num_textures++;
} else {
object->object_type = ObjectType::BUFFER;
}
};
for (auto& object : attr.code.objects) {
// Downgrade readonly objects to F16 is requested.
if (options_.allow_precision_loss) {
MaybeConvertToFloat16(&object.second);
}
set_object_type(&object.second);
}
for (auto ref : compiled_graph_.FindInputs(node->id)) {
set_object_type(&objects[ref->id]);
}
for (auto ref : compiled_graph_.FindOutputs(node->id)) {
set_object_type(&objects[ref->id]);
}
}
// Generate shaders from the transformed graph.
ShaderCodegen codegen(options_, gpu_info_);
for (auto node : compiled_graph_.nodes()) {
auto& attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
if (attr.code.source_code.empty()) {
// noop. Skip this node.
continue;
}
// Declare inputs and outputs explicitly.
for (auto ref : compiled_graph_.FindInputs(node->id)) {
auto object = objects[ref->id];
object.access = AccessType::READ;
attr.inputs.push_back(object);
}
for (auto ref : compiled_graph_.FindOutputs(node->id)) {
auto object = objects[ref->id];
object.access = AccessType::WRITE;
attr.outputs.push_back(object);
}
// Allocate bindings. Textures must be bound first.
uint32_t binding = 0;
auto set_binding = [&](ObjectType type, Object& object) {
if (object.object_type == type) {
object.binding = binding++;
}
};
for (auto& object : attr.inputs) {
set_binding(ObjectType::TEXTURE, object);
}
for (auto& object : attr.outputs) {
set_binding(ObjectType::TEXTURE, object);
}
for (auto& object : attr.code.objects) {
set_binding(ObjectType::TEXTURE, object.second);
}
for (auto& object : attr.inputs) {
set_binding(ObjectType::BUFFER, object);
}
for (auto& object : attr.outputs) {
set_binding(ObjectType::BUFFER, object);
}
for (auto& object : attr.code.objects) {
set_binding(ObjectType::BUFFER, object.second);
}
// Generate source code.
ShaderCode shader_code;
RETURN_IF_ERROR(codegen.Build(std::move(attr), &shader_code));
RETURN_IF_ERROR(callback(std::move(shader_code)));
}
return absl::OkStatus();
}
private:
const NodeShader& node_shader_;
const GpuInfo& gpu_info_;
CompilationOptions options_;
GraphFloat32 compiled_graph_;
};
} // namespace
std::unique_ptr<Compiler> NewCompiler(const NodeShader* node_shader,
const GpuInfo* gpu_info,
const CompilationOptions& options) {
return std::make_unique<CompilerImpl>(node_shader, gpu_info, options);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,57 @@
/* 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_COMPILER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_H_
#include <functional>
#include <memory>
#include <unordered_set>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/shader_code.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler_options.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
using ShaderCodeCallback = std::function<absl::Status(ShaderCode code)>;
class Compiler {
public:
virtual ~Compiler() = default;
// Goes over a graph and generates OpenGL shaders for the given graph.
// Callback is called for every generated shader. Callback may execute shaders
// as they come or store them elsewhere to execute later.
virtual absl::Status Compile(
const GraphFloat32& graph,
const std::unordered_set<int>& tflite_graph_io, // NOLINT
const ShaderCodeCallback& callback) = 0;
};
std::unique_ptr<Compiler> NewCompiler(
const NodeShader* node_shader, const GpuInfo* gpu_info,
const CompilationOptions& options);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_H_
@@ -0,0 +1,245 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "preprocessor",
srcs = ["preprocessor.cc"],
hdrs = ["preprocessor.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "preprocessor_test",
srcs = ["preprocessor_test.cc"],
tags = [
"local",
"tflite_not_portable_ios",
],
deps = [
":preprocessor",
"@com_google_absl//absl/strings:string_view",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "object_accessor",
srcs = ["object_accessor.cc"],
hdrs = ["object_accessor.h"],
deps = [
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:access_type",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "object_accessor_test",
srcs = ["object_accessor_test.cc"],
tags = [
"local",
],
deps = [
":object_accessor",
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/types:variant",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "shader_code",
hdrs = ["shader_code.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
],
)
cc_library(
name = "shader_codegen",
srcs = ["shader_codegen.cc"],
hdrs = ["shader_codegen.h"],
deps = [
":compiled_node",
":object_accessor",
":preprocessor",
":shader_code",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:compiler_options",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "compiled_node",
srcs = ["compiled_node.cc"],
hdrs = ["compiled_node.h"],
deps = [
":rename",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "compiled_node_test",
srcs = ["compiled_node_test.cc"],
deps = [
":compiled_node",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "fuse_inplace",
srcs = ["fuse_inplace.cc"],
hdrs = ["fuse_inplace.h"],
deps = [
":compiled_node",
":preprocessor",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "fuse_inline",
srcs = ["fuse_inline.cc"],
hdrs = ["fuse_inline.h"],
deps = [
":compiled_node",
":shader_code",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "rename",
srcs = ["rename.cc"],
hdrs = ["rename.h"],
deps = [
":object_accessor",
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "fuse_auto_input",
srcs = ["fuse_auto_input.cc"],
hdrs = ["fuse_auto_input.h"],
deps = [
":compiled_node",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "fuse_auto_input_test",
srcs = ["fuse_auto_input_test.cc"],
tags = [
"local",
"no_mac", # TODO(b/171881489)
"no_oss", # TODO(b/171881489)
],
deps = [
":compiled_node",
":fuse_auto_input",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/types:any",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "variable_accessor",
srcs = ["variable_accessor.cc"],
hdrs = ["variable_accessor.h"],
deps = [
":preprocessor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "variable_accessor_test",
srcs = ["variable_accessor_test.cc"],
tags = [
"local",
"tflite_not_portable_ios",
],
deps = [
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_googletest//:gtest_main",
],
)
tflite_portable_test_suite()
@@ -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/compiler/compiled_node.h"
#include <algorithm>
#include <iterator>
#include <string>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/rename.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status MergeCode(CompiledNodeAttributes* attr,
CompiledNodeAttributes* merged_attr) {
// build a map of known names.
absl::flat_hash_set<std::string> known_names;
for (const auto& parameter : merged_attr->code.parameters) {
known_names.insert(parameter.name);
}
for (const auto& object : merged_attr->code.objects) {
known_names.insert(object.first);
}
// Rewrite parameters with unique names.
int index =
merged_attr->code.parameters.size() + merged_attr->code.objects.size();
RETURN_IF_ERROR(Rename(
[&](absl::string_view name) -> std::string {
std::string n(name.begin(), name.end());
// Add index to the end of a variable name until it's unique
std::string ret = n;
while (known_names.find(ret) != known_names.end()) {
ret = absl::StrCat(n, index++);
}
known_names.insert(ret);
return ret;
},
&attr->code));
std::move(attr->code.objects.begin(), attr->code.objects.end(),
std::back_inserter(merged_attr->code.objects));
std::move(attr->code.parameters.begin(), attr->code.parameters.end(),
std::back_inserter(merged_attr->code.parameters));
std::move(attr->node_indices.begin(), attr->node_indices.end(),
std::back_inserter(merged_attr->node_indices));
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,52 @@
/* 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_COMPILER_COMPILED_NODE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_COMPILED_NODE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// Contains compiler internal attributes for each node after it was processed by
// NodeShader.
struct CompiledNodeAttributes {
std::vector<Object> inputs;
std::vector<Object> outputs;
GeneratedCode code;
// nodes that are covered by the provided shader.
std::vector<NodeId> node_indices;
};
// Moves all code objects, parameters and node indices from attr to merged_attr.
// Parameters and objects in attr.code.source_code are renamed to ensure
// uniqueness.
absl::Status MergeCode(CompiledNodeAttributes* attr,
CompiledNodeAttributes* merged_attr);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_COMPILED_NODE_H_
@@ -0,0 +1,73 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include <algorithm>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
bool VariableDuplicates(std::vector<Variable> variables) {
std::sort(
std::begin(variables), std::end(variables),
[](const auto& lhs, const auto& rhs) { return lhs.name < rhs.name; });
for (int i = 0; i < variables.size() - 1; ++i) {
if (variables[i].name == variables[i + 1].name) return true;
}
return false;
}
TEST(CompiledNodeTest, NoDuplicates) {
Variable scalar;
scalar.name = "scalar";
Variable scalar1;
scalar1.name = "scalar1";
CompiledNodeAttributes attr;
CompiledNodeAttributes merged_attr;
attr.code.parameters = {scalar, scalar1};
merged_attr.code.parameters = {scalar};
ASSERT_OK(MergeCode(&attr, &merged_attr));
// Check for duplicates
EXPECT_FALSE(VariableDuplicates(merged_attr.code.parameters));
}
TEST(CompiledNodeTest, NameConvergenceConflict) {
Variable scalar;
scalar.name = "scalar";
Variable scalar1;
scalar1.name = "scalar1";
CompiledNodeAttributes attr;
CompiledNodeAttributes merged_attr;
attr.code.parameters = {scalar1, scalar};
merged_attr.code.parameters = {scalar};
ASSERT_OK(MergeCode(&attr, &merged_attr));
// Check for duplicates
EXPECT_FALSE(VariableDuplicates(merged_attr.code.parameters));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,255 @@
/* 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/compiler/fuse_auto_input.h"
#include <any>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
std::pair<std::string, std::string> MakeValueReplacement(int n, int k) {
return {absl::StrCat("value_", n), absl::StrCat("value_", k)};
}
std::pair<std::string, std::string> MakeDataReplacement(int n, int k) {
return {absl::StrCat("input_data_", n), absl::StrCat("input_data_", k)};
}
} // namespace
TransformResult FuseAutoInput::ApplyToNode(Node* node, GraphFloat32* graph) {
auto& node_attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
auto& node_code = node_attr.code;
if (node_code.input != IOStructure::AUTO) {
return {TransformStatus::SKIPPED, ""};
}
uint3 workgroup = node_code.workgroup;
auto node_outputs = graph->FindOutputs(node->id);
// Check which inputs could be fused into the current node.
std::vector<std::pair<Node*, int>> nodes_to_fuse;
std::vector<std::pair<ValueId, int>> input_values;
int input_num = -1;
for (auto input_value : graph->FindInputs(node->id)) {
input_num++;
const ValueId input_id = input_value->id;
input_values.push_back({input_id, input_num});
if (graph->FindConsumers(input_id).size() > 1) {
continue; // input is consumed by >1 nodes
}
Node* input_producer = graph->FindProducer(input_id);
if (input_producer == nullptr) {
continue; // graph's input
}
if (graph->FindOutputs(input_producer->id).size() != 1) {
continue; // input node has more than one output
}
auto& input_producer_attr = std::any_cast<const CompiledNodeAttributes&>(
input_producer->operation.attributes);
if (input_producer_attr.code.output != IOStructure::AUTO) {
continue;
}
if (input_producer_attr.code.workload != node_code.workload &&
uint3() != input_producer_attr.code.workload) {
continue;
}
if (input_producer_attr.code.workgroup != uint3()) {
// New fused node should fuse only a single shader that has pre-defined
// workgroup. Such shader is considered "heavy". Do not fuse two heavy
// shaders into one.
// TODO(eignasheva): make sure it still works.
if (workgroup != uint3()) {
continue;
}
workgroup = input_producer_attr.code.workgroup;
}
nodes_to_fuse.push_back({input_producer, input_num});
input_values.pop_back(); // this value will not be used as input.
}
if (nodes_to_fuse.empty()) {
return {TransformStatus::SKIPPED, ""};
}
// Skip fusions which will result in duplicate inputs, e.g. diamond shapes.
{
absl::flat_hash_set<ValueId> all_inputs;
for (const auto& node_to_fuse : nodes_to_fuse) {
for (const auto& input : graph->FindInputs(node_to_fuse.first->id)) {
if (all_inputs.find(input->id) != all_inputs.end()) {
return {TransformStatus::SKIPPED, ""};
}
all_inputs.insert(input->id);
}
}
for (const auto& input : graph->FindInputs(node->id)) {
if (all_inputs.find(input->id) != all_inputs.end()) {
return {TransformStatus::SKIPPED, ""};
}
all_inputs.insert(input->id);
}
}
// Break connections between current node and its inputs.
for (auto value : graph->FindInputs(node->id)) {
if (!graph->RemoveConsumer(node->id, value->id).ok()) {
return {TransformStatus::INVALID, ""};
}
}
std::string operation_type;
std::string source_code;
std::string values;
// Node source code need to be appended later to the end.
std::swap(source_code, node_code.source_code);
// Indicates value_k that is beyond originally declared [0..n] values,
// therefore, it can be used by newly added dependencies.
int extra_input_num = input_num;
input_num = 0;
// Fuse all nodes into one.
for (auto input_and_num : nodes_to_fuse) {
auto& input = input_and_num.first;
auto& attr =
std::any_cast<CompiledNodeAttributes&>(input->operation.attributes);
auto super_inputs = graph->FindInputs(input->id);
// Replace all internal references in the input source code. For example:
// source code "value_0 = max(0, value_0);" will be rewritten into
// "value_2 = max(0, value_2);"
std::vector<std::pair<std::string, std::string>> replacements;
for (int i = 0; i < super_inputs.size(); ++i) {
// Node source code uses value_N to access output value from the fused
// node. Use correct reference.
//
// Here value_N does not correspond to input_N anymore. Instead it tracks
// value_n and input_m independently. Value_index uses an index needed
// for the "final" shader, while input_num preserves the order of inputs.
// For example:
// Shader A: input_0, input_1
// value_0 = value_0 > value_1 ? value_0 : value_1;
//
// Shader B: input_0
// value_0 = max(0, value_0);
//
// AddShader: input_0, input_1
// value_0 = value_0 + value_1;
//
// Fused shader is going to have 3 inputs: input_0 (A), input_1 (A),
// input_2 (B). But Shader B need to store result in value_1, because
// AddShader refers to it as 'value_1'. So, fused shader will look as
// follows:
//
// // Shader A
// vec4 value_0 = input_data_0.data[gid.x, gid.y, gid.z];
// vec4 value_2 = input_data_1.data[gid.x, gid.y, gid.z];
// value_0 = value_0 > value_2 ? value_0 : value_2;
//
// // Shader B
// vec4 value_1 = input_data_2.data[gid.x, gid.y, gid.z];
// value_1 = max(0, value_1);
//
// // AddShader
// value_0 = value_0 + value_1;
//
// output_data_0.data[gid.x, gid.y, gid.z] = value_0;
int value_index = i == 0 ? input_and_num.second : ++extra_input_num;
replacements.push_back(MakeValueReplacement(i, value_index));
replacements.push_back(MakeDataReplacement(i, input_num));
// Declare input values based on the input structure of the merged node.
// This code copies what shader_codegen would do automatically.
if (attr.code.input == IOStructure::AUTO) {
absl::StrAppend(&values, " value_", value_index, " = $input_data_",
input_num, "[gid.x, gid.y, gid.z]$;\n");
}
if (!graph->AddConsumer(node->id, super_inputs[i]->id).ok()) {
return {TransformStatus::INVALID, ""};
}
input_num++;
}
// Also rename all _h and _w parameters to the new names.
for (auto& param : attr.code.parameters) {
param.name = absl::StrReplaceAll(param.name, replacements);
}
attr.code.source_code =
absl::StrReplaceAll(attr.code.source_code, replacements);
// Merge all objects, parameters and source code.
if (!MergeCode(&attr, &node_attr).ok()) {
return {TransformStatus::INVALID, "Unable to merge the code"};
}
absl::StrAppend(&node_attr.code.source_code, "{\n", attr.code.source_code,
"\n}");
if (!operation_type.empty()) {
operation_type += ",";
}
operation_type += input->operation.type;
if (!graph->DeleteNode(input->id).ok()) {
return {TransformStatus::INVALID, ""};
}
}
// Add back all inputs that are used directly by the fused node.
for (int i = 0; i < input_values.size(); i++) {
if (node_code.input == IOStructure::AUTO) {
absl::StrAppend(&values, " value_", input_values[i].second,
" = $input_data_", input_num,
"[gid.x, gid.y, gid.z]$;\n");
}
if (!graph->AddConsumer(node->id, input_values[i].first).ok()) {
return {TransformStatus::INVALID, ""};
}
input_num++;
}
node_code.input = IOStructure::ONLY_DEFINITIONS;
absl::StrAppend(&node->operation.type, "(", operation_type, ")");
node_code.source_code =
absl::StrCat(values, node_code.source_code, "{//FUSED",
node->operation.type, "\n", source_code, "\n}");
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,49 @@
/* 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_COMPILER_FUSE_AUTO_INPUT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_AUTO_INPUT_H_
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuses nodes that have auto output with auto input node using the following
// rules.
//
// Source graph:
// A B C
// \ | /
// D
//
// - A, B and C each have a single output marked as AUTO
// - Each output is used only by D
// - D has all inputs marked as AUTO
//
// Result: in the best case a single node that does (A,B,C)+D operations.
//
class FuseAutoInput : public NodeTransformation {
public:
TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_AUTO_INPUT_H_
@@ -0,0 +1,105 @@
/* 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/compiler/fuse_auto_input.h"
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(FuseAutoInputTest, SkipsDiamond) {
// v0
// / \
// n1 n2
// v1 v2
// \ /
// n3
// v3
GraphFloat32 graph;
auto* v0 = graph.NewValue();
auto* v1 = graph.NewValue();
auto* v2 = graph.NewValue();
auto* v3 = graph.NewValue();
auto* n1 = graph.NewNode();
CompiledNodeAttributes a1;
a1.code.output = IOStructure::AUTO;
n1->operation.attributes = std::move(a1);
ASSERT_OK(graph.AddConsumer(n1->id, v0->id));
ASSERT_OK(graph.SetProducer(n1->id, v1->id));
auto* n2 = graph.NewNode();
CompiledNodeAttributes a2;
a2.code.output = IOStructure::AUTO;
n2->operation.attributes = std::move(a2);
ASSERT_OK(graph.AddConsumer(n2->id, v0->id));
ASSERT_OK(graph.SetProducer(n2->id, v2->id));
auto* n3 = graph.NewNode();
CompiledNodeAttributes a3;
a3.code.input = IOStructure::AUTO;
n3->operation.attributes = std::move(a3);
ASSERT_OK(graph.AddConsumer(n3->id, v1->id));
ASSERT_OK(graph.AddConsumer(n3->id, v2->id));
ASSERT_OK(graph.SetProducer(n3->id, v3->id));
FuseAutoInput fuse_auto_input;
EXPECT_EQ(fuse_auto_input.ApplyToNode(n3, &graph).status,
TransformStatus::SKIPPED);
}
TEST(FuseAutoInputTest, SkipsTriangle) {
// v0
// | \
// | n1
// | v1
// | /
// n2
// v2
GraphFloat32 graph;
auto* v0 = graph.NewValue();
auto* v1 = graph.NewValue();
auto* v2 = graph.NewValue();
auto* n1 = graph.NewNode();
CompiledNodeAttributes a1;
a1.code.output = IOStructure::AUTO;
n1->operation.attributes = std::move(a1);
ASSERT_OK(graph.AddConsumer(n1->id, v0->id));
ASSERT_OK(graph.SetProducer(n1->id, v1->id));
auto* n2 = graph.NewNode();
CompiledNodeAttributes a2;
a2.code.input = IOStructure::AUTO;
n2->operation.attributes = std::move(a2);
ASSERT_OK(graph.AddConsumer(n2->id, v0->id));
ASSERT_OK(graph.AddConsumer(n2->id, v1->id));
ASSERT_OK(graph.SetProducer(n2->id, v2->id));
FuseAutoInput fuse_auto_input;
EXPECT_EQ(fuse_auto_input.ApplyToNode(n2, &graph).status,
TransformStatus::SKIPPED);
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,78 @@
/* 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/compiler/fuse_inline.h"
#include <any>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
TransformResult FuseAutoOutputWithInline::ApplyToNodesSequence(
const std::vector<Node*>& sequence, GraphFloat32* graph) {
Node* node1 = sequence.front();
Node* node2 = sequence.back();
auto& attr1 =
std::any_cast<CompiledNodeAttributes&>(node1->operation.attributes);
auto& attr2 =
std::any_cast<CompiledNodeAttributes&>(node2->operation.attributes);
if (attr1.code.output != IOStructure::AUTO ||
graph->FindInputs(node2->id).size() != 1 ||
graph->FindOutputs(node2->id).size() != 1 ||
attr2.code.output != IOStructure::AUTO ||
attr2.code.input != IOStructure::AUTO ||
(attr1.code.workload != attr2.code.workload &&
uint3() != attr2.code.workload) ||
graph->FindOutputs(node1->id).size() !=
graph->FindInputs(node2->id).size()) {
return {TransformStatus::SKIPPED, ""};
}
// Check if the code was not fused yet, and wrap source code into {}.
if (!absl::StrContains(node1->operation.type, '+')) {
attr1.code.source_code =
absl::StrCat("\n{\n", attr1.code.source_code, "\n}\n");
}
if (!MergeCode(&attr2, &attr1).ok()) {
return {TransformStatus::INVALID, "Unable to merge two nodes"};
}
absl::StrAppend(&attr1.code.source_code, "{\n", attr2.code.source_code,
"\n}");
node1->operation.type += "+" + node2->operation.type;
if (!RemoveFollowingNode(graph, node2, node1).ok()) {
return {TransformStatus::INVALID,
"Unable to remove node " + std::to_string(node2->id)};
}
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,57 @@
/* 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_COMPILER_FUSE_INLINE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INLINE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuses every two nodes where first node does default output and second node
// is INLINE.
//
// Generates code as follows:
// 1. all uniforms are inlined
// 2. source code is wrapped into {}
// For example:
// value = clamp(value, 0.0, clip);
// +
// value = 1.0 / (1.0 + exp(-1.0 * value));
// will turn into:
// {
// value = clamp(value, 0.0, clip);
// }
// {
// value = 1.0 / (1.0 + exp(-1.0 * value));
// }
class FuseAutoOutputWithInline : public SequenceTransformation {
public:
int ExpectedSequenceLength() const final { return 2; }
TransformResult ApplyToNodesSequence(const std::vector<Node*>& sequence,
GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INLINE_H_
@@ -0,0 +1,154 @@
/* 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/compiler/fuse_inplace.h"
#include <any>
#include <cstring>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
static const char* kInplacePrefix = "inplace_update:\0";
class EmptyInplaceRewrite : public InlineRewrite {
public:
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
if (input.compare(0, strlen(kInplacePrefix), kInplacePrefix) == 0) {
num_rewrites_++;
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
int num_rewrites() const { return num_rewrites_; }
private:
int num_rewrites_ = 0;
};
// Takes a code as an input. Replaces 'value_0' in the code with a value that
// comes in a rewrite. For example:
// code: value_0 = max(value_0, 0);
// rewrite: inplace_update:result_12 -> result_12 = max(result_12, 0);
//
class InplaceCodeRewrite : public InlineRewrite {
public:
explicit InplaceCodeRewrite(const std::string& code) : code_(code) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
int len = strlen(kInplacePrefix);
if (input.compare(0, len, kInplacePrefix) == 0) {
auto variable_name = input.substr(len);
absl::StrAppend(output,
absl::StrReplaceAll(code_, {{"value_0", variable_name}}));
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
private:
std::string code_;
};
} // namespace
TransformResult RemoveUnusedInplaceUpdates::ApplyToNode(Node* node,
GraphFloat32* graph) {
auto& attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
// Remove inplace block by rewriting to empty string.
EmptyInplaceRewrite rewrite;
TextPreprocessor preprocessor('$', true);
preprocessor.AddRewrite(&rewrite);
if (!preprocessor.Rewrite(attr.code.source_code, &attr.code.source_code)
.ok()) {
return {TransformStatus::INVALID, ""};
}
return {rewrite.num_rewrites() > 0 ? TransformStatus::APPLIED
: TransformStatus::SKIPPED,
""};
}
TransformResult FuseInplaceUpdate::ApplyToNodesSequence(
const std::vector<Node*>& sequence, GraphFloat32* graph) {
Node* node1 = sequence.front();
Node* node2 = sequence.back();
auto& attr1 =
std::any_cast<CompiledNodeAttributes&>(node1->operation.attributes);
auto& attr2 =
std::any_cast<CompiledNodeAttributes&>(node2->operation.attributes);
if (graph->FindInputs(node2->id).size() != 1 ||
graph->FindOutputs(node2->id).size() != 1 ||
attr2.code.output != IOStructure::AUTO ||
attr2.code.input != IOStructure::AUTO ||
(attr1.code.workload != attr2.code.workload &&
uint3() != attr2.code.workload)) {
return {TransformStatus::SKIPPED, ""};
}
// First count of replaces that would happen to check whether rewrite is
// needed.
{
EmptyInplaceRewrite counting_rewrite;
TextPreprocessor preprocessor('$', true);
preprocessor.AddRewrite(&counting_rewrite);
std::string temp;
if (!preprocessor.Rewrite(attr1.code.source_code, &temp).ok()) {
return {TransformStatus::INVALID, ""};
}
// no rewrites in the source code. skip it.
if (counting_rewrite.num_rewrites() == 0) {
return {TransformStatus::SKIPPED, ""};
}
}
if (!MergeCode(&attr2, &attr1).ok()) {
return {TransformStatus::INVALID, "Unable to merge two nodes"};
}
TextPreprocessor preprocessor('$', true);
InplaceCodeRewrite rewrite(attr2.code.source_code);
preprocessor.AddRewrite(&rewrite);
if (!preprocessor.Rewrite(attr1.code.source_code, &attr1.code.source_code)
.ok()) {
return {TransformStatus::INVALID, ""};
}
node1->operation.type += "+" + node2->operation.type;
if (!RemoveFollowingNode(graph, node2, node1).ok()) {
return {TransformStatus::INVALID,
"Unable to remove node " + std::to_string(node2->id)};
}
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,67 @@
/* 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_COMPILER_FUSE_INPLACE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INPLACE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuse two shaders where second shader is inline shader with the first.
// First shader should have a special symbol that defines a place where such
// fusion should be made and what variable needs to be changed.
// Second shader needs to operation with 'value_0' variable.
// Example:
//
// First shader:
// vec4 result = input_data_0.data[gid.x, gid.y, gid.z];
// $inplace_update:result$
// ...
// output_data_0.data[1,2,3] = result;
//
// Second shader:
// value_0 = max(value_0, 0);
//
// Fused shader:
// vec4 result = input_data_0.data[gid.x, gid.y, gid.z];
// result = max(result, 0);
// ...
// output_data_0.data[1,2,3] = result;
//
class FuseInplaceUpdate : public SequenceTransformation {
public:
int ExpectedSequenceLength() const final { return 2; }
TransformResult ApplyToNodesSequence(const std::vector<Node*>& sequence,
GraphFloat32* graph) final;
};
// Removes all %inplace_update:XXX% strings from the code.
class RemoveUnusedInplaceUpdates : public NodeTransformation {
public:
TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INPLACE_H_
@@ -0,0 +1,624 @@
/* 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/compiler/object_accessor.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/access_type.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace object_accessor_internal {
// Splits name[index1, index2...] into 'name' and {'index1', 'index2'...}.
IndexedElement ParseElement(absl::string_view input) {
auto i = input.find('[');
if (i == std::string::npos || input.back() != ']') {
return {};
}
return {input.substr(0, i),
absl::StrSplit(input.substr(i + 1, input.size() - i - 2), ',',
absl::SkipWhitespace())};
}
} // namespace object_accessor_internal
namespace {
void MaybeConvertToHalf(DataType data_type, absl::string_view value,
std::string* output) {
if (data_type == DataType::FLOAT16) {
absl::StrAppend(output, "Vec4ToHalf(", value, ")");
} else {
absl::StrAppend(output, value);
}
}
void MaybeConvertFromHalf(DataType data_type, absl::string_view value,
std::string* output) {
if (data_type == DataType::FLOAT16) {
absl::StrAppend(output, "Vec4FromHalf(", value, ")");
} else {
absl::StrAppend(output, value);
}
}
struct ReadFromTextureGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
// 1D textures are emulated as 2D textures
if (sampler_textures) {
absl::StrAppend(result, "texelFetch(", element.object_name, ", ivec2(",
element.indices[0], ", 0), 0)");
} else {
absl::StrAppend(result, "imageLoad(", element.object_name, ", ivec2(",
element.indices[0], ", 0))");
}
return RewriteStatus::SUCCESS;
}
template <typename Shape>
RewriteStatus operator()(const Shape&) const {
if (element.indices.size() != Shape::size()) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
if (sampler_textures) {
absl::StrAppend(result, "texelFetch(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"), 0)");
} else {
absl::StrAppend(result, "imageLoad(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"))");
}
return RewriteStatus::SUCCESS;
}
const object_accessor_internal::IndexedElement& element;
const bool sampler_textures;
std::string* result;
};
struct ReadFromBufferGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], "]"),
result);
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint2& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 2) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], " + $",
element.object_name, "_w$ * (", element.indices[1], ")]"),
result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint3& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 3) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], " + $",
element.object_name, "_w$ * (", element.indices[1], " + $",
element.object_name, "_h$ * (", element.indices[2], "))]"),
result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
DataType data_type;
const object_accessor_internal::IndexedElement& element;
std::string* result;
// indicates that generated code accessed _w and/or _h index variables.
bool* requires_sizes;
};
// Generates code for reading an element from an object.
RewriteStatus GenerateReadAccessor(
const Object& object,
const object_accessor_internal::IndexedElement& element,
bool sampler_textures, std::string* result, bool* requires_sizes) {
switch (object.object_type) {
case ObjectType::BUFFER:
return std::visit(ReadFromBufferGenerator{object.data_type, element,
result, requires_sizes},
object.size);
case ObjectType::TEXTURE:
return std::visit(
ReadFromTextureGenerator{element, sampler_textures, result},
object.size);
case ObjectType::UNKNOWN:
return RewriteStatus::ERROR;
}
}
struct WriteToBufferGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
"] = ");
MaybeConvertToHalf(data_type, value, result);
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint2& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 2) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
" + $", element.object_name, "_w$ * (", element.indices[1],
")] = ");
MaybeConvertToHalf(data_type, value, result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint3& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 3) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
" + $", element.object_name, "_w$ * (", element.indices[1],
" + $", element.object_name, "_h$ * (", element.indices[2],
"))] = ");
MaybeConvertToHalf(data_type, value, result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
DataType data_type;
const object_accessor_internal::IndexedElement& element;
absl::string_view value;
std::string* result;
// indicates that generated code accessed _w and/or _h index variables.
bool* requires_sizes;
};
struct WriteToTextureGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
// 1D textures are emulated as 2D textures
absl::StrAppend(result, "imageStore(", element.object_name, ", ivec2(",
element.indices[0], ", 0), ", value, ")");
return RewriteStatus::SUCCESS;
}
template <typename Shape>
RewriteStatus operator()(const Shape&) const {
if (element.indices.size() != Shape::size()) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, "imageStore(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"), ", value, ")");
return RewriteStatus::SUCCESS;
}
const object_accessor_internal::IndexedElement& element;
absl::string_view value;
std::string* result;
};
// Generates code for writing value an element in an object.
RewriteStatus GenerateWriteAccessor(
const Object& object,
const object_accessor_internal::IndexedElement& element,
absl::string_view value, std::string* result, bool* requires_sizes) {
switch (object.object_type) {
case ObjectType::BUFFER:
return std::visit(WriteToBufferGenerator{object.data_type, element, value,
result, requires_sizes},
object.size);
case ObjectType::TEXTURE:
return std::visit(WriteToTextureGenerator{element, value, result},
object.size);
case ObjectType::UNKNOWN:
return RewriteStatus::ERROR;
}
}
std::string ToAccessModifier(AccessType access, bool use_readonly_modifier) {
switch (access) {
case AccessType::READ:
return use_readonly_modifier ? " readonly" : "";
case AccessType::WRITE:
return " writeonly";
case AccessType::READ_WRITE:
return " restrict";
}
return " unknown_access";
}
std::string ToBufferType(DataType data_type) {
switch (data_type) {
case DataType::UINT8:
case DataType::UINT16:
case DataType::UINT32:
return "uvec4";
case DataType::UINT64:
return "u64vec4_not_available_in_glsl";
case DataType::INT8:
case DataType::INT16:
case DataType::INT32:
return "ivec4";
case DataType::INT64:
return "i64vec4_not_available_in_glsl";
case DataType::FLOAT16:
return "uvec2";
case DataType::BOOL:
case DataType::FLOAT32:
return "vec4";
case DataType::FLOAT64:
return "dvec4";
case DataType::UNKNOWN:
return "unknown_buffer_type";
// Do NOT add `default:'; we want build failure for new enum values.
}
}
struct TextureImageTypeGetter {
std::string operator()(size_t) const {
// 1D textures are emulated as 2D textures
return (*this)(uint2());
}
std::string operator()(const uint2&) const {
switch (type) {
case DataType::UINT16:
case DataType::UINT32:
return "uimage2D";
case DataType::INT16:
case DataType::INT32:
return "iimage2D";
case DataType::FLOAT16:
case DataType::FLOAT32:
return "image2D";
default:
return "unknown_image_2d";
}
}
std::string operator()(const uint3&) const {
switch (type) {
case DataType::UINT16:
case DataType::UINT32:
return "uimage2DArray";
case DataType::INT16:
case DataType::INT32:
return "iimage2DArray";
case DataType::FLOAT16:
case DataType::FLOAT32:
return "image2DArray";
default:
return "unknown_image_2d_array";
}
}
DataType type;
};
struct TextureSamplerTypeGetter {
std::string operator()(size_t) const {
// 1D textures are emulated as 2D textures
return (*this)(uint2());
}
std::string operator()(const uint2&) const {
switch (type) {
case DataType::FLOAT16:
case DataType::FLOAT32:
return "sampler2D";
case DataType::INT32:
case DataType::INT16:
return "isampler2D";
case DataType::UINT32:
case DataType::UINT16:
return "usampler2D";
default:
return "unknown_sampler2D";
}
}
std::string operator()(const uint3&) const {
switch (type) {
case DataType::FLOAT16:
case DataType::FLOAT32:
return "sampler2DArray";
case DataType::INT32:
case DataType::INT16:
return "isampler2DArray";
case DataType::UINT32:
case DataType::UINT16:
return "usampler2DArray";
default:
return "unknown_sampler2DArray";
}
}
DataType type;
};
std::string ToImageType(const Object& object, bool sampler_textures) {
if (sampler_textures && (object.access == AccessType::READ)) {
return std::visit(TextureSamplerTypeGetter{object.data_type}, object.size);
} else {
return std::visit(TextureImageTypeGetter{object.data_type}, object.size);
}
}
std::string ToImageLayoutQualifier(DataType type) {
switch (type) {
case DataType::UINT16:
return "rgba16ui";
case DataType::UINT32:
return "rgba32ui";
case DataType::INT16:
return "rgba16i";
case DataType::INT32:
return "rgba32i";
case DataType::FLOAT16:
return "rgba16f";
case DataType::FLOAT32:
return "rgba32f";
default:
return "unknown_image_layout";
}
}
std::string ToImagePrecision(DataType type) {
switch (type) {
case DataType::UINT16:
case DataType::INT16:
case DataType::FLOAT16:
return "mediump";
case DataType::UINT32:
case DataType::INT32:
case DataType::FLOAT32:
return "highp";
default:
return "unknown_image_precision";
}
}
struct SizeParametersAdder {
void operator()(size_t) const {}
void operator()(const uint2& size) const {
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_w"), static_cast<int32_t>(size.x)});
}
// p1 and p2 are padding. For some reason buffer does not map correctly
// without it.
void operator()(const uint3& size) const {
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_w"), static_cast<int32_t>(size.x)});
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_h"), static_cast<int32_t>(size.y)});
}
absl::string_view object_name;
VariableAccessor* variable_accessor;
};
// Adds necessary parameters to parameter accessor that represent object size
// needed for indexed access.
// - 1D : empty
// - 2D : 'int object_name_w'
// - 3D : 'int object_name_w' + 'int object_name_h'
void AddSizeParameters(absl::string_view object_name, const Object& object,
VariableAccessor* parameters) {
std::visit(SizeParametersAdder{object_name, parameters}, object.size);
}
void GenerateObjectDeclaration(absl::string_view name, const Object& object,
std::string* declaration, bool is_mali,
bool sampler_textures) {
switch (object.object_type) {
case ObjectType::BUFFER:
// readonly modifier used to fix shader compilation for Mali on Android 8,
// see b/111601761
absl::StrAppend(declaration, "layout(binding = ", object.binding, ")",
ToAccessModifier(object.access, !is_mali), " buffer B",
object.binding, " { ", ToBufferType(object.data_type),
" data[]; } ", name, ";\n");
break;
case ObjectType::TEXTURE:
if (sampler_textures && (object.access == AccessType::READ)) {
absl::StrAppend(declaration, "layout(binding = ", object.binding,
") uniform ", ToImagePrecision(object.data_type), " ",
ToImageType(object, sampler_textures), " ", name,
";\n");
} else {
absl::StrAppend(
declaration, "layout(", ToImageLayoutQualifier(object.data_type),
", binding = ", object.binding, ")",
ToAccessModifier(object.access, true), " uniform ",
ToImagePrecision(object.data_type), " ",
ToImageType(object, sampler_textures), " ", name, ";\n");
}
break;
case ObjectType::UNKNOWN:
// do nothing.
break;
}
}
} // namespace
RewriteStatus ObjectAccessor::Rewrite(absl::string_view input,
std::string* output) {
// Splits 'a =b' into {'a','b'}.
std::pair<absl::string_view, absl::string_view> n =
absl::StrSplit(input, absl::MaxSplits('=', 1), absl::SkipWhitespace());
if (n.first.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
if (n.second.empty()) {
return RewriteRead(absl::StripAsciiWhitespace(n.first), output);
}
return RewriteWrite(absl::StripAsciiWhitespace(n.first),
absl::StripAsciiWhitespace(n.second), output);
}
RewriteStatus ObjectAccessor::RewriteRead(absl::string_view location,
std::string* output) {
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
bool requires_sizes = false;
auto status = GenerateReadAccessor(it->second, element, sampler_textures_,
output, &requires_sizes);
if (requires_sizes) {
AddSizeParameters(it->first, it->second, variable_accessor_);
}
return status;
}
RewriteStatus ObjectAccessor::RewriteWrite(absl::string_view location,
absl::string_view value,
std::string* output) {
// name[index1, index2...] = value
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
bool requires_sizes = false;
auto status = GenerateWriteAccessor(it->second, element, value, output,
&requires_sizes);
if (requires_sizes) {
AddSizeParameters(it->first, it->second, variable_accessor_);
}
return status;
}
bool ObjectAccessor::AddObject(const std::string& name, Object object) {
if (object.object_type == ObjectType::UNKNOWN) {
return false;
}
return name_to_object_.insert({name, std::move(object)}).second;
}
std::string ObjectAccessor::GetObjectDeclarations() const {
std::string declarations;
for (auto& o : name_to_object_) {
GenerateObjectDeclaration(o.first, o.second, &declarations, is_mali_,
sampler_textures_);
}
return declarations;
}
std::string ObjectAccessor::GetFunctionsDeclarations() const {
// If there is a single object SSBO with F16, then we need to output macros
// as well.
for (const auto& o : name_to_object_) {
if (o.second.data_type == DataType::FLOAT16 &&
o.second.object_type == ObjectType::BUFFER) {
return absl::StrCat(
"#define Vec4FromHalf(v) vec4(unpackHalf2x16(v.x), "
"unpackHalf2x16(v.y))\n",
"#define Vec4ToHalf(v) uvec2(packHalf2x16(v.xy), "
"packHalf2x16(v.zw))");
}
}
return "";
}
std::vector<Object> ObjectAccessor::GetObjects() const {
std::vector<Object> objects;
objects.reserve(name_to_object_.size());
for (auto& o : name_to_object_) {
objects.push_back(o.second);
}
return objects;
}
} // 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
#include <map>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// This rewrite handles access to objects both reads and writes.
//
// The following syntax is supported to access objects:
//
// READ:
// vec4 value = $data[i]$;
// where data is a buffer or 1D texture
// vec4 value = $data[i,j]$;
// where data is 2D texture
// vec4 value = $data[i,j,k]$;
// where data is 3D texture
//
// WRITE:
// $data[i] = value$;
// where data is a buffer or 1D texture
// $data[i,j] = value$;
// where data is 2D texture
// $data[i,j,k] = value$;
// where data is 3D texture
//
// Accessor supports all types (gvecN) as well as float16.
//
// TODO(akulik): support field in data[x,y,z].x
//
class ObjectAccessor : public InlineRewrite {
public:
ObjectAccessor(bool is_mali, VariableAccessor* variable_accessor)
: ObjectAccessor(is_mali, /*sampler_textures=*/false, variable_accessor) {
}
ObjectAccessor(bool is_mali, bool sampler_textures,
VariableAccessor* variable_accessor)
: is_mali_(is_mali),
sampler_textures_(sampler_textures),
variable_accessor_(variable_accessor) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final;
// Return true if object was successfully added.
bool AddObject(const std::string& name, Object object);
// Returns objects declarations that need to be added in a shader's code.
std::string GetObjectDeclarations() const;
// Returns functions declarations that need to be added in a shader's code.
// These functions are used by code accessing objects.
std::string GetFunctionsDeclarations() const;
// Returns a collection of registered objects
std::vector<Object> GetObjects() const;
private:
RewriteStatus RewriteRead(absl::string_view location, std::string* output);
RewriteStatus RewriteWrite(absl::string_view location,
absl::string_view value, std::string* output);
std::map<std::string, Object> name_to_object_;
const bool is_mali_;
const bool sampler_textures_;
VariableAccessor* variable_accessor_;
};
// Implementation details below.
namespace object_accessor_internal {
// Refers to an element in an object.
struct IndexedElement {
absl::string_view object_name;
std::vector<absl::string_view> indices;
};
// Splits name[index1, index2...] into 'name' and {'index1', 'index2'...}.
IndexedElement ParseElement(absl::string_view input);
} // namespace object_accessor_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
@@ -0,0 +1,209 @@
/* 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/compiler/object_accessor.h"
#include <string>
#include <variant>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
struct ParameterComparator {
template <typename T>
bool operator()(const T& t) const {
const T* v = std::get_if<T>(&p.value);
return v && t == *v;
}
const Variable& p;
};
// partially equal
bool operator==(const Variable& l, const Variable& r) {
return l.name == r.name && std::visit(ParameterComparator{l}, r.value);
}
namespace {
TEST(Preprocessor, CornerCases) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
std::string result;
ASSERT_EQ(accessor.Rewrite("", &result), RewriteStatus::NOT_RECOGNIZED);
ASSERT_EQ(accessor.Rewrite("=", &result), RewriteStatus::NOT_RECOGNIZED);
}
TEST(Preprocessor, ReadFromBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i]");
}
TEST(Preprocessor, ReadFromBufferLinear) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i]");
}
TEST(Preprocessor, ReadFromBufferByIndex) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[x,y + 5,z]", &result),
RewriteStatus::SUCCESS);
EXPECT_THAT(variable_accessor.GetUniformParameters(),
testing::UnorderedElementsAre(Variable{"obj_w", 1},
Variable{"obj_h", 2}));
ASSERT_EQ(result, "obj.data[x + $obj_w$ * (y + 5 + $obj_h$ * (z))]");
}
TEST(Preprocessor, ReadFromTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 2, 3), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i,j,k]", &result), RewriteStatus::SUCCESS);
// textures don't need extra variables to be stored for indexed access
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageLoad(obj, ivec3(i, j, k))");
}
TEST(Preprocessor, ReadFromTexture1D) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyTexture({1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageLoad(obj, ivec2(i, 0))");
}
TEST(Preprocessor, WriteToBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i] =value", &result),
RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i] = value");
}
TEST(Preprocessor, WriteToBufferByIndex) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i,j,k] =value", &result),
RewriteStatus::SUCCESS);
EXPECT_THAT(variable_accessor.GetUniformParameters(),
testing::UnorderedElementsAre(Variable{"obj_w", 1},
Variable{"obj_h", 2}));
ASSERT_EQ(result, "obj.data[i + $obj_w$ * (j + $obj_h$ * (k))] = value");
}
TEST(Preprocessor, WriteToTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i,j,k]= value ", &result),
RewriteStatus::SUCCESS);
ASSERT_EQ(result, "imageStore(obj, ivec3(i, j, k), value)");
}
TEST(Preprocessor, WriteToTexture1D) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyTexture({1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]= value ", &result),
RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageStore(obj, ivec2(i, 0), value)");
}
TEST(Preprocessor, FailedWriteToBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i,j] =value", &result),
RewriteStatus::ERROR);
ASSERT_EQ(result, "WRONG_NUMBER_OF_INDICES");
}
TEST(Preprocessor, FailedWriteToTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]= value ", &result), RewriteStatus::ERROR);
ASSERT_EQ(result, "WRONG_NUMBER_OF_INDICES");
}
TEST(Preprocessor, DeclareTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
ASSERT_EQ(accessor.GetObjectDeclarations(),
"layout(rgba32f, binding = 0) readonly uniform highp image2DArray "
"obj;\n");
}
TEST(Preprocessor, DeclareBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(true, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
ASSERT_EQ(accessor.GetObjectDeclarations(),
"layout(binding = 0) buffer B0 { vec4 data[]; } obj;\n");
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,100 @@
/* 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/compiler/preprocessor.h"
#include <cstddef>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Given input string and a delimiter returns back a substring including
// delimiters. If there was only starting delimiter found, returns single char.
absl::string_view FindInlineBlock(absl::string_view s, char delimiter) {
size_t start = s.find(delimiter);
if (start != absl::string_view::npos) {
size_t end = s.find(delimiter, start + 1);
if (end != std::string::npos) {
return s.substr(start, end - start + 1);
}
// Special case to indicate that we didn't find the end.
return s.substr(start, 1);
}
return s.substr(s.size(), 0);
}
// For the given 's' and its substring 'subs' returns new substring of 's' that
// begins past 'subs'.
absl::string_view PastSubstr(absl::string_view s, absl::string_view subs) {
return s.substr(subs.data() + subs.size() - s.data());
}
} // namespace
absl::Status TextPreprocessor::Rewrite(const std::string& input,
std::string* output) {
absl::string_view s = input;
std::string result;
while (true) {
absl::string_view inline_block = FindInlineBlock(s, inline_delimiter_);
result.append(s.data(), inline_block.data() - s.data());
if (inline_block.empty()) {
break;
}
if (inline_block.size() == 1) {
return absl::NotFoundError("Unable to find end of inline block");
}
s = PastSubstr(s, inline_block);
bool processed = false;
for (auto& rewrite : inline_rewrites_) {
if (processed) {
break;
}
switch (rewrite->Rewrite(inline_block.substr(1, inline_block.size() - 2),
&result)) {
case RewriteStatus::NOT_RECOGNIZED:
// try another rewrite.
break;
case RewriteStatus::SUCCESS:
processed = true;
break;
case RewriteStatus::ERROR:
return absl::InternalError(absl::StrCat("Error while rewriting '",
inline_block, "': ", result));
}
}
if (!processed) {
if (!keep_unknown_rewrites_) {
return absl::NotFoundError(absl::StrCat(
"Didn't find inline rewrite for '", inline_block, "'"));
}
absl::StrAppend(&result, inline_block);
}
}
*output = std::move(result);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,74 @@
/* 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_COMPILER_PREPROCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_PREPROCESSOR_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
enum class RewriteStatus {
SUCCESS = 0,
NOT_RECOGNIZED = 1,
ERROR = 2,
};
// Inline rewrite matches a string and rewrites it.
class InlineRewrite {
public:
virtual ~InlineRewrite() = default;
virtual RewriteStatus Rewrite(absl::string_view input,
std::string* output) = 0;
};
// Text preprocessor runs a collection of registered rewrites.
// It uses a single character prefix as inline delimiter that needs to quote
// text to be rewritten.
class TextPreprocessor {
public:
// @param keep_unknown_rewrites if true, will keep unhandled rewrites as is
// instead of reporting an error.
TextPreprocessor(char inline_delimiter, bool keep_unknown_rewrites)
: inline_delimiter_(inline_delimiter),
keep_unknown_rewrites_(keep_unknown_rewrites) {}
void AddRewrite(InlineRewrite* rewrite) {
inline_rewrites_.push_back(rewrite);
}
// input and output may point to the same object.
absl::Status Rewrite(const std::string& input, std::string* output);
private:
const char inline_delimiter_;
const bool keep_unknown_rewrites_;
std::vector<InlineRewrite*> inline_rewrites_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_PREPROCESSOR_H_
@@ -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/compiler/preprocessor.h"
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class AccuInlineRewrite : public InlineRewrite {
public:
explicit AccuInlineRewrite(std::vector<std::string>* blocks)
: blocks_(blocks) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
blocks_->push_back(std::string(input.data(), input.size()));
output->append("r:");
output->append(input.data(), input.size());
return RewriteStatus::SUCCESS;
}
std::vector<std::string>* blocks_;
};
std::vector<std::string> ParseInlines(const std::string& text) {
std::vector<std::string> blocks;
TextPreprocessor preprocessor('$', false);
AccuInlineRewrite rewrite(&blocks);
preprocessor.AddRewrite(&rewrite);
std::string discard;
preprocessor.Rewrite(text, &discard).IgnoreError();
return blocks;
}
TEST(Preprocessor, CornerCases) {
EXPECT_THAT(ParseInlines(""), testing::ElementsAre());
EXPECT_THAT(ParseInlines("text text"), testing::ElementsAre());
EXPECT_THAT(ParseInlines("$$"), testing::ElementsAre(""));
}
TEST(Preprocessor, One) {
EXPECT_THAT(ParseInlines("$text$"), testing::ElementsAre("text"));
EXPECT_THAT(ParseInlines(" $text$ "), testing::ElementsAre("text"));
}
TEST(Preprocessor, More) {
EXPECT_THAT(ParseInlines("Test $inline1$\n$inline2$ test $inline3$ "),
testing::ElementsAre("inline1", "inline2", "inline3"));
}
std::string RewriteInlines(const std::string& text) {
std::vector<std::string> blocks;
TextPreprocessor preprocessor('$', false);
AccuInlineRewrite rewrite(&blocks);
preprocessor.AddRewrite(&rewrite);
std::string out;
preprocessor.Rewrite(text, &out).IgnoreError();
return out;
}
TEST(Preprocessor, RewriteCornerCases) {
EXPECT_EQ(RewriteInlines(""), "");
EXPECT_EQ(RewriteInlines("text text"), "text text");
EXPECT_EQ(RewriteInlines("$$"), "r:");
}
TEST(Preprocessor, RewriteOne) {
EXPECT_EQ(RewriteInlines("$text$"), "r:text");
EXPECT_EQ(RewriteInlines(" $text$ "), " r:text ");
}
TEST(Preprocessor, RewriteMore) {
EXPECT_EQ(RewriteInlines("Test $inline1$\n$inline2$ test $inline3$ "),
"Test r:inline1\nr:inline2 test r:inline3 ");
}
class SingleRewrite : public InlineRewrite {
public:
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
if (input == "foo") {
output->append("bla");
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
std::vector<std::string>* blocks_;
};
TEST(Preprocessor, KeepUnknownRewrites) {
TextPreprocessor preprocessor('$', true);
SingleRewrite rewrite;
preprocessor.AddRewrite(&rewrite);
std::string out;
ASSERT_TRUE(preprocessor.Rewrite("Good morning, $name$! $foo$", &out).ok());
EXPECT_EQ("Good morning, $name$! bla", out);
}
TEST(Preprocessor, KeepUnknownRewrites_Fail) {
TextPreprocessor preprocessor('$', false);
SingleRewrite rewrite;
preprocessor.AddRewrite(&rewrite);
std::string out;
EXPECT_FALSE(preprocessor.Rewrite("Good morning, $name$! $foo$", &out).ok());
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,206 @@
/* 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/compiler/rename.h"
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.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 {
// Rewrites names of all variables according to returned values from the
// given NameFunctor.
class VariableRewriter : public InlineRewrite {
public:
VariableRewriter(const std::string& inline_delimiter,
const NameFunctor& name_func)
: inline_delimiter_(inline_delimiter), name_func_(name_func) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
auto ref = variable_accessor_internal::Parse(input);
if (ref.name.empty()) {
absl::StrAppend(output, "INVALID_SYNTAX");
return RewriteStatus::ERROR;
}
auto it =
name_to_variable_.find(std::string(ref.name.data(), ref.name.size()));
if (it == name_to_variable_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
// reconstruct access using the new name.
absl::StrAppend(output, inline_delimiter_, it->second.name);
if (!ref.index.empty()) {
absl::StrAppend(output, "[", ref.index, "]");
}
absl::StrAppend(output, ref.field, inline_delimiter_);
return RewriteStatus::SUCCESS;
}
// Return true if variable was successfully added.
bool AddVariable(Variable&& variable) {
std::string old_name = variable.name;
variable.name = name_func_(old_name);
return name_to_variable_.insert({old_name, std::move(variable)}).second;
}
// Returns a collection of uniform parameters with updated names.
std::vector<Variable> GetUniformParameters() const {
std::vector<Variable> variables;
variables.reserve(name_to_variable_.size());
for (const auto& variable : name_to_variable_) {
variables.push_back(variable.second);
}
return variables;
}
private:
const std::string inline_delimiter_;
const NameFunctor name_func_;
absl::flat_hash_map<std::string, Variable> name_to_variable_;
};
// Rewrites names of all objects according to returned values from the
// given NameFunctor.
class ObjectRewriter : public InlineRewrite {
public:
ObjectRewriter(const std::string& inline_delimiter,
const NameFunctor& name_func)
: inline_delimiter_(inline_delimiter), name_func_(name_func) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
// Splits 'a = b' into {'a','b'}.
std::pair<absl::string_view, absl::string_view> n =
absl::StrSplit(input, absl::MaxSplits('=', 1), absl::SkipWhitespace());
if (n.first.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
if (n.second.empty()) {
return RewriteRead(absl::StripAsciiWhitespace(n.first), output);
}
return RewriteWrite(absl::StripAsciiWhitespace(n.first),
absl::StripAsciiWhitespace(n.second), output);
}
// Return true if an object was successfully added.
bool AddObject(const std::string& name, Object object) {
std::string new_name = name_func_(name);
return name_to_object_.insert({name, {new_name, std::move(object)}}).second;
}
// Returns a collection of registered objects with updated names.
std::vector<std::pair<std::string, Object>> GetObjects() const {
std::vector<std::pair<std::string, Object>> objects;
objects.reserve(name_to_object_.size());
for (const auto& o : name_to_object_) {
objects.push_back(o.second);
}
return objects;
}
private:
RewriteStatus RewriteRead(absl::string_view location, std::string* output) {
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
absl::StrAppend(output, "UNABLE_TO_PARSE_INDEXED_ELEMENT");
return RewriteStatus::ERROR;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
absl::StrAppend(output, inline_delimiter_, it->second.first, "[",
absl::StrJoin(element.indices, ","), "]",
inline_delimiter_);
return RewriteStatus::SUCCESS;
}
RewriteStatus RewriteWrite(absl::string_view location,
absl::string_view value, std::string* output) {
// name[index1, index2...] = value
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
absl::StrAppend(output, "UNABLE_TO_PARSE_INDEXED_ELEMENT");
return RewriteStatus::ERROR;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
absl::StrAppend(output, inline_delimiter_, it->second.first, "[",
absl::StrJoin(element.indices, ","), "] = ", value,
inline_delimiter_);
return RewriteStatus::SUCCESS;
}
const std::string inline_delimiter_;
const NameFunctor name_func_;
absl::flat_hash_map<std::string, std::pair<std::string, Object>>
name_to_object_;
};
} // namespace
absl::Status Rename(const NameFunctor& name_func, GeneratedCode* code) {
VariableRewriter variable_rewriter("$", name_func);
ObjectRewriter object_rewriter("$", name_func);
for (auto&& uniform_parameter : code->parameters) {
if (!variable_rewriter.AddVariable(std::move(uniform_parameter))) {
return absl::InternalError("Variable name already exists");
}
}
for (auto&& object : code->objects) {
if (!object_rewriter.AddObject(object.first, std::move(object.second))) {
return absl::InternalError("Object name already exists");
}
}
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/true);
preprocessor.AddRewrite(&variable_rewriter);
preprocessor.AddRewrite(&object_rewriter);
std::string source_code;
RETURN_IF_ERROR(preprocessor.Rewrite(code->source_code, &source_code));
code->source_code = source_code;
code->parameters = variable_rewriter.GetUniformParameters();
code->objects = object_rewriter.GetObjects();
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,41 @@
/* 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_COMPILER_RENAME_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_RENAME_H_
#include <functional>
#include <string>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
// Functor takes old name and returns new name.
using NameFunctor = std::function<std::string(absl::string_view name)>;
// Rewrites source code, objects and parameters with the new names supplied
// by the given functor.
absl::Status Rename(const NameFunctor& name_func, GeneratedCode* code);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_RENAME_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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
struct ShaderCode {
ShaderCode() = default;
ShaderCode(const std::vector<Variable>& in_parameters,
const std::vector<Object>& in_objects, const uint3& in_workload,
const uint3& in_recommended_workgroup,
const std::string& in_source_code,
const std::vector<NodeId>& in_node_indices)
: parameters(in_parameters),
objects(in_objects),
workload(in_workload),
recommended_workgroup(in_recommended_workgroup),
source_code(in_source_code),
node_indices(in_node_indices) {}
// A list of uniform parameters to be set.
std::vector<Variable> parameters;
// A list of objects to bind to opengl program.
std::vector<Object> objects;
uint3 workload;
// operation may specify recommended workgroup size
uint3 recommended_workgroup;
// Generated source code does not set local size, therefore it needs to be set
// elsewhere.
std::string source_code;
// nodes of the graph that are covered by the shader.
std::vector<NodeId> node_indices;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
@@ -0,0 +1,204 @@
/* 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/compiler/shader_codegen.h"
#include <algorithm>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif // __ANDROID__
namespace tflite {
namespace gpu {
namespace gl {
ShaderCodegen::ShaderCodegen(const CompilationOptions& options,
const GpuInfo& gpu_info)
: options_(options),
gpu_type_(gpu_info.vendor),
inline_parameters_(options.inline_parameters) {
#ifdef __ANDROID__
if (gpu_info.IsAdreno() &&
gpu_info.adreno_info.adreno_gpu == AdrenoGpu::kAdreno730) {
char sdk_version[PROP_VALUE_MAX];
__system_property_get("ro.build.version.sdk", sdk_version);
if (!strcmp(sdk_version, "31")) inline_parameters_ = false;
} else if (gpu_info.IsPowerVR() &&
!gpu_info.powervr_info.IsBetterThan(PowerVRGpu::kRogueGm9xxx)) {
inline_parameters_ = false;
}
#endif // __ANDROID__
}
absl::Status ShaderCodegen::Build(CompiledNodeAttributes attr,
ShaderCode* shader_code) const {
VariableAccessor variable_accessor(inline_parameters_,
options_.vulkan_support);
ObjectAccessor object_accessor(gpu_type_ == GpuVendor::kMali,
options_.sampler_textures, &variable_accessor);
const auto add_object = [&](const std::string& name, Object&& object) {
if (!object_accessor.AddObject(name, std::forward<Object>(object))) {
return absl::AlreadyExistsError(absl::StrCat("Object \"", name, "\""));
}
return absl::OkStatus();
};
const auto add_uniform_parameter = [&](Variable&& variable) {
const std::string name = variable.name;
const Variable& const_ref = variable;
if (variable_accessor.IsEmptyVariableLength(const_ref)) {
return absl::InvalidArgumentError(
absl::StrCat("Empty uniform vector value \"", name, "\""));
}
if (!variable_accessor.AddUniformParameter(std::move(variable))) {
return absl::AlreadyExistsError(
absl::StrCat("Uniform parameter \"", name, "\""));
}
return absl::OkStatus();
};
for (auto&& object : attr.code.objects) {
RETURN_IF_ERROR(add_object(object.first, std::move(object.second)));
}
for (auto&& variable : attr.code.shared_variables) {
const std::string name = variable.name;
if (!variable_accessor.AddSharedVariable(std::move(variable))) {
return absl::AlreadyExistsError(
absl::StrCat("Shared variable \"", name, "\""));
}
}
for (auto&& variable : attr.code.parameters) {
RETURN_IF_ERROR(add_uniform_parameter(std::move(variable)));
}
int index = 0;
for (auto&& input : attr.inputs) {
RETURN_IF_ERROR(
add_object(absl::StrCat("input_data_", index++), std::move(input)));
}
index = 0;
for (auto&& output : attr.outputs) {
RETURN_IF_ERROR(
add_object(absl::StrCat("output_data_", index++), std::move(output)));
}
// TODO(akulik): workload params need to go away and be replaced with
// output_data_0_w
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_x", static_cast<int32_t>(attr.code.workload.x)}));
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_y", static_cast<int32_t>(attr.code.workload.y)}));
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_z", static_cast<int32_t>(attr.code.workload.z)}));
// NOTE: If the shader has shared variables it will have to use barriers,
// which will conflict with a return at this stage.
// Let the user deal with the geometry constraints.
const bool has_shared_variables = !attr.code.shared_variables.empty();
std::string main_source_code = has_shared_variables ? R"(
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
)"
: R"(
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
if (gid.x >= $workload_x$ || gid.y >= $workload_y$ || gid.z >= $workload_z$) {
return;
}
)";
switch (attr.code.input) {
case IOStructure::ONLY_DEFINITIONS:
for (int i = 0; i < attr.inputs.size(); ++i) {
absl::StrAppend(&main_source_code, " highp vec4 value_", i,
" = vec4(0);\n");
}
break;
case IOStructure::AUTO: {
for (int i = 0; i < attr.inputs.size(); ++i) {
absl::StrAppend(&main_source_code, " highp vec4 value_", i,
" = $input_data_", i, "[gid.x, gid.y, gid.z]$;\n");
}
break;
}
}
main_source_code.append(attr.code.source_code);
if (attr.code.output == IOStructure::AUTO) {
for (int i = 0; i < attr.outputs.size(); ++i) {
absl::StrAppend(&main_source_code, " $output_data_", i,
"[gid.x, gid.y, gid.z] = value_", i, "$;\n");
}
}
// At this point main function is already generated. Now we need to process
// object and variable accessors.
// process objects first. Object accessor may introduce new uniform
// parameters that need to be rewritten in the subsequent pass.
{
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/true);
preprocessor.AddRewrite(&object_accessor);
RETURN_IF_ERROR(preprocessor.Rewrite(main_source_code, &main_source_code));
}
{
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/false);
preprocessor.AddRewrite(&variable_accessor);
RETURN_IF_ERROR(preprocessor.Rewrite(main_source_code, &main_source_code));
}
if (inline_parameters_) {
main_source_code = absl::StrCat(variable_accessor.GetConstDeclarations(),
main_source_code);
}
// partial_source_code is only missing the following which is added later:
// #version 310 es
// layout(local_size_x = ..., local_size_y = ..., local_size_z = ...) in;
const char* precision = options_.allow_precision_loss ? "mediump" : "highp";
const std::string partial_source_code = absl::StrCat(
"layout(std430) buffer;\n", //
"precision ", precision, " float;\n", //
object_accessor.GetFunctionsDeclarations(), "\n", //
object_accessor.GetObjectDeclarations(), "\n", //
variable_accessor.GetUniformParameterDeclarations(), "\n", //
variable_accessor.GetSharedVariableDeclarations(), "\n", //
"void main() {\n", //
main_source_code, //
"}");
*shader_code =
ShaderCode(variable_accessor.GetUniformParameters(),
object_accessor.GetObjects(), attr.code.workload,
attr.code.workgroup, partial_source_code, attr.node_indices);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,55 @@
/* 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_COMPILER_SHADER_CODEGEN_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODEGEN_H_
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/shader_code.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler_options.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// This class is responsible for assembling a shader by putting together
// objects, parameters declarations and main function.
class ShaderCodegen {
public:
ShaderCodegen(const CompilationOptions& options, const GpuInfo& gpu_info);
// Builds final program representation.
absl::Status Build(CompiledNodeAttributes attr,
ShaderCode* shader_code) const;
private:
const CompilationOptions options_;
const GpuVendor gpu_type_;
bool inline_parameters_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODEGEN_H_
@@ -0,0 +1,557 @@
/* 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/compiler/variable_accessor.h"
#include <array>
#include <cstdint>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace variable_accessor_internal {
// Parse the following regex manually
// name(\[index\])?(\.field)?
VariableReference Parse(absl::string_view input) {
VariableReference ref;
auto start_index = input.find('[');
if (start_index != std::string::npos) {
auto end_index = input.rfind(']');
if (end_index == std::string::npos) {
return ref;
}
ref.index = input.substr(start_index + 1, end_index - start_index - 1);
ref.name = input.substr(0, start_index);
ref.field = input.substr(end_index + 1);
} else {
auto dot = input.find('.');
if (dot != std::string::npos) {
ref.name = input.substr(0, dot);
ref.field = input.substr(dot);
} else {
ref.name = input;
}
}
return ref;
}
} // namespace variable_accessor_internal
namespace {
struct VariableTypeGetter {
std::string operator()(int) const { return "int"; }
std::string operator()(const int2&) const { return "ivec2"; }
std::string operator()(const std::vector<int2>&) const { return "ivec2"; }
std::string operator()(const int4&) const { return "ivec4"; }
std::string operator()(unsigned int) const { return "uint"; }
std::string operator()(const uint4&) const { return "uvec4"; }
std::string operator()(float) const { return "float"; }
std::string operator()(const float2&) const { return "vec2"; }
std::string operator()(const float4&) const { return "vec4"; }
std::string operator()(const std::vector<float4>&) const { return "vec4"; }
};
// Returns GLSL uniform type of the given variable.
std::string GetVariableType(const Variable::ValueType& value) {
return std::visit(VariableTypeGetter(), value);
}
struct LengthGetter {
template <typename T>
int operator()(const T& param) const {
return 1;
}
template <typename T>
int operator()(const std::vector<T>& param) const {
return param.size();
}
};
int GetLength(const Variable::ValueType& value) {
return std::visit(LengthGetter(), value);
}
template <typename T>
void FormatValue(std::string* result, T t) {
absl::StrAppend(result, t);
}
template <>
void FormatValue(std::string* result, float t) {
absl::StrAppend(result, absl::StrFormat("%.9ff", t));
}
// Unfortunately absl::StrJoin with custom formatter requires formatter to use
// string, not std::string. Therefore, due to this compatibility issue data
// needs to be converted to string representation first and then joined.
template <typename T, int N>
std::vector<std::string> ToString(const std::array<T, N>& data) {
std::vector<std::string> result(N);
for (int i = 0; i < N; ++i) {
FormatValue(&result[i], data[i]);
}
return result;
}
struct ConstGenerator {
template <typename T>
void operator()(T t) const {
FormatValue(result, t);
}
template <typename T>
void operator()(const Vec2<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 2>(v.data_), ","), ")");
}
template <typename T>
void operator()(const Vec3<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 3>(v.data_), ","), ")");
}
template <typename T>
void operator()(const Vec4<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 4>(v.data_), ","), ")");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
std::string type = VariableTypeGetter()(v);
absl::StrAppend(result, type, "[", v.size(), "](");
bool first = true;
for (const auto& i : v) {
if (first) {
first = false;
} else {
absl::StrAppend(result, ",");
}
(*this)(i);
}
absl::StrAppend(result, ")");
}
std::string* result;
};
// Appends string representation of a variable value.
void GetValue(const Variable::ValueType& value, std::string* result) {
std::visit(ConstGenerator{result}, value);
}
struct SharedVariableDeclarationGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, "shared highp ", GetVariableType(variable.value),
" ", variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, "shared highp ", GetVariableType(variable.value),
" ", variable.name);
if (v.empty()) {
// Normalize the size of the shared array to that of the WorkGroupSize
absl::StrAppend(
result,
"[gl_WorkGroupSize.z * gl_WorkGroupSize.y * gl_WorkGroupSize.x];\n");
} else {
// Use the specified size
absl::StrAppend(result, "[", v.size(), "];\n");
}
}
const Variable& variable;
std::string* result;
};
void GenerateSharedVariableDeclaration(const Variable& variable,
std::string* result) {
std::visit(SharedVariableDeclarationGenerator{variable, result},
variable.value);
}
struct UniformParameterDeclarationGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, "uniform ", GetVariableType(variable.value), " ",
variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, "uniform ", GetVariableType(variable.value), " ",
variable.name, "[", v.size(), "];\n");
}
const Variable& variable;
std::string* result;
};
void GenerateUniformParameterDeclaration(const Variable& variable,
std::string* result) {
std::visit(UniformParameterDeclarationGenerator{variable, result},
variable.value);
}
struct VulkanPushConstantGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, " ", GetVariableType(variable.value), " ",
variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, " ", GetVariableType(variable.value), " ",
variable.name, "[", v.size(), "];\n");
}
const Variable& variable;
std::string* result;
};
void GenerateVulkanPushConstant(const Variable& variable, std::string* result) {
std::visit(VulkanPushConstantGenerator{variable, result}, variable.value);
}
struct VariableLengthGetter {
template <typename T>
bool operator()(const T&) const {
return false;
}
template <typename T>
bool operator()(const std::vector<T>&) const {
return true;
}
};
struct VulkanConstantGenerator {
template <typename T>
void operator()(const T&) const {
const std::string variable_type = GetVariableType(variable.value);
// Vulkan specialization constants are used for scalar types, all other
// types go in push (uniform) constants.
if (variable_type == "int" || variable_type == "uint" ||
variable_type == "float") {
absl::StrAppend(result, "layout(constant_id = ", *constant_id, ") const ",
variable_type, " ", variable.name, " = ");
// Always set the default values to zero to generate generic cacheable
// shaders.
absl::StrAppend(result, (variable_type == "float" ? "0.0" : "0"), ";\n");
(*constant_id)++;
} else {
non_scalar_variables->push_back(variable);
}
}
template <typename T>
void operator()(const std::vector<T>& v) const {
non_scalar_variables->push_back(variable);
}
const Variable& variable;
int* const constant_id;
std::vector<Variable>* non_scalar_variables;
std::string* result;
};
void GenerateVulkanConstant(const Variable& variable, int* constant_id,
std::vector<Variable>* non_scalar_variables,
std::string* result) {
std::visit(VulkanConstantGenerator{variable, constant_id,
non_scalar_variables, result},
variable.value);
}
class VulkanConstantsProcessor {
public:
void ProcessVulkanConstant(const Variable& variable, std::string* result) {
GenerateVulkanConstant(variable, &constant_id_, &non_scalar_variables_,
result);
}
void GeneratePushConstantsDeclarations(std::string* result) {
if (!non_scalar_variables_.empty()) {
*result += "\nlayout(push_constant) uniform pushConstants {\n";
for (const auto& variable : non_scalar_variables_) {
GenerateVulkanPushConstant(variable, result);
}
*result += "};\n";
}
}
protected:
// Reserve the first three specialization constants slots for the
// workgroup size.
int constant_id_ = 3;
std::vector<Variable> non_scalar_variables_;
};
// Returns true if value is a vector
bool IsVariableLength(const Variable::ValueType& value) {
return std::visit(VariableLengthGetter(), value);
}
enum Field : uint8_t { UNKNOWN = 4, X = 0, Y = 1, Z = 2, W = 3 };
Field ToField(absl::string_view field_name) {
if (field_name.size() == 2 && field_name[0] == '.') {
switch (field_name[1]) {
case 'x':
return Field::X;
case 'y':
return Field::Y;
case 'z':
return Field::Z;
case 'w':
return Field::W;
}
}
return Field::UNKNOWN;
}
struct FieldAccessor {
template <typename T>
void operator()(const T&) const {}
template <typename T>
void operator()(const Vec2<T>& v) const {
FormatValue(result, v[field]);
}
template <typename T>
void operator()(const Vec3<T>& v) const {
FormatValue(result, v[field]);
}
template <typename T>
void operator()(const Vec4<T>& v) const {
FormatValue(result, v[field]);
}
Field field;
std::string* result;
};
// Appends formatted value of the given field.
void GetValue(const Variable::ValueType& value, Field field,
std::string* result) {
std::visit(FieldAccessor{field, result}, value);
}
struct FieldChecker {
// For trivial as well as variable-length types indexed access is not allowed.
template <typename T>
bool operator()(const T&) const {
return false;
}
template <typename T>
bool operator()(const Vec2<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const Vec3<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const Vec4<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const std::vector<T>&) const {
// technically accessing [0] element of an empty vector is UB, but we need
// only type information for this check. Therefore, construct default T and
// use it instead.
T t;
return (*this)(t);
}
Field field;
};
// Returns true if field has field access and field is not out of bounds.
bool HasField(const Variable::ValueType& value, Field field) {
return std::visit(FieldChecker{field}, value);
}
void AssembleAccessor(absl::string_view name, absl::string_view index,
absl::string_view field, std::string* result) {
if (index.empty()) {
absl::StrAppend(result, name, field);
} else {
absl::StrAppend(result, name, "[", index, "]", field);
}
}
} // namespace
RewriteStatus VariableAccessor::Rewrite(absl::string_view input,
std::string* output) {
auto ref = variable_accessor_internal::Parse(input);
if (ref.name.empty()) {
absl::StrAppend(output, "INVALID_SYNTAX");
return RewriteStatus::ERROR;
}
auto it =
name_to_variable_.find(std::string(ref.name.data(), ref.name.size()));
if (it == name_to_variable_.end()) {
// Uniform with this name is not registered.
return RewriteStatus::NOT_RECOGNIZED;
}
const auto& value = it->second.value;
if (!ref.index.empty() && !IsVariableLength(value)) {
// Trying to access variable by index, but it is not variable-length.
absl::StrAppend(output, "INVALID_ACCESS_BY_INDEX");
return RewriteStatus::ERROR;
}
Field f = ToField(ref.field);
if (!ref.field.empty() && !HasField(value, f)) {
// Trying to access a variable by field, but it does not have it.
absl::StrAppend(output, "INVALID_ACCESS_BY_FIELD");
return RewriteStatus::ERROR;
}
// Error checks are complete now.
// All variable-length variables are encoded as-is without inlining.
if (!inline_values_ || IsVariableLength(value)) {
AssembleAccessor(it->second.name, ref.index, ref.field, output);
} else {
// Parameter + field is replaced with field value.
if (f != Field::UNKNOWN) {
GetValue(value, f, output);
} else {
// Parameter is accessed directly.
GetValue(value, output);
}
}
return RewriteStatus::SUCCESS;
}
bool VariableAccessor::AddSharedVariable(Variable&& variable) {
const std::string name = variable.name;
if (!name_to_variable_.insert({name, std::move(variable)}).second) {
return false;
}
shared_variables_.insert(name);
return true;
}
bool VariableAccessor::AddUniformParameter(Variable&& variable) {
const std::string name = variable.name;
if (!name_to_variable_.insert({name, std::move(variable)}).second) {
return false;
}
uniform_parameters_.insert(name);
return true;
}
bool VariableAccessor::IsEmptyVariableLength(const Variable& variable) const {
const auto& value = variable.value;
return IsVariableLength(value) && GetLength(value) == 0;
}
std::string VariableAccessor::GetConstDeclarations() const {
// Variable length variables are declared as const and accessed via variable
// with index.
std::string declarations;
for (const auto& variable : name_to_variable_) {
// Skip shared variables.
const std::string& variable_name = variable.second.name;
if (shared_variables_.find(variable_name) != shared_variables_.end()) {
continue;
}
const auto& value = variable.second.value;
if (IsVariableLength(value)) {
absl::StrAppend(&declarations, "const ", GetVariableType(value), " ",
variable_name, "[] = ");
GetValue(value, &declarations);
absl::StrAppend(&declarations, ";\n");
}
}
return declarations;
}
std::string VariableAccessor::GetSharedVariableDeclarations() const {
std::string declarations;
for (const auto& name : shared_variables_) {
const auto& variable = name_to_variable_.at(name);
GenerateSharedVariableDeclaration(variable, &declarations);
}
return declarations;
}
std::string VariableAccessor::GetUniformParameterDeclarations() const {
std::string declarations;
if (!inline_values_) {
if (vulkan_support_) {
VulkanConstantsProcessor processor;
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
processor.ProcessVulkanConstant(variable, &declarations);
}
processor.GeneratePushConstantsDeclarations(&declarations);
} else {
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
GenerateUniformParameterDeclaration(variable, &declarations);
}
}
}
return declarations;
}
std::vector<Variable> VariableAccessor::GetUniformParameters() const {
std::vector<Variable> variables;
if (!inline_values_) {
variables.reserve(name_to_variable_.size());
// Keep the order of the variables consistent with that of the declarations
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
variables.push_back(variable);
}
}
return variables;
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,103 @@
/* 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_COMPILER_VARIABLE_ACCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_VARIABLE_ACCESSOR_H_
#include <set>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
// This rewrite handles access to variables. It may rewrite a variable with
// actual values if 'inline_values' is set to true.
//
// The following syntax is supported to access variables:
// - simple variable: name
// - variable with field: name.(x|y|z|w)
// - variable with index: name[i]
// - variable with index and field: name[i].(x|y|z|w)
//
// If 'inline_values' is set to true, non-variable-length variables will be
// inlined. For example, 'base.x' will be replaced with value of 'x' field from
// 'base'. Variable-length variables are declared as const and accessed via
// index. These declarations are returned by GetConstDeclarations.
//
// If 'inline_values' is set to false, all variables will be declared as
// uniforms. Uniform declarations are returned by GetUniformDeclarations.
class VariableAccessor : public InlineRewrite {
public:
explicit VariableAccessor(bool inline_values, bool vulkan_support = false)
: inline_values_(inline_values), vulkan_support_(vulkan_support) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final;
// Returns true if variable was successfully added.
bool AddSharedVariable(Variable&& variable);
// Returns true if variable was successfully added.
bool AddUniformParameter(Variable&& variable);
// Returns true if variable value is an empty vector.
bool IsEmptyVariableLength(const Variable& variable) const;
// Returns const variables that need to be inlined in the a shader's code.
std::string GetConstDeclarations() const;
// Returns shared variable declarations that need to be inlined.
std::string GetSharedVariableDeclarations() const;
// Returns uniform parameter declarations that need to be inlined.
std::string GetUniformParameterDeclarations() const;
// Returns a collection of uniform parameters.
std::vector<Variable> GetUniformParameters() const;
private:
const bool inline_values_;
const bool vulkan_support_;
absl::flat_hash_map<std::string, Variable> name_to_variable_;
std::set<std::string> shared_variables_;
std::set<std::string> uniform_parameters_;
};
// Implementation details below.
namespace variable_accessor_internal {
struct VariableReference {
absl::string_view name;
absl::string_view index;
absl::string_view field;
};
// Parse the following regex manually
// name(\[index\])?(\.field)?
VariableReference Parse(absl::string_view input);
} // namespace variable_accessor_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_VARIABLE_ACCESSOR_H_
@@ -0,0 +1,101 @@
/* 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/compiler/variable_accessor.h"
#include <cstdint>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(PreprocessorTest, CornerCases) {
VariableAccessor variable_accessor(/*inline_values=*/true);
std::string result;
EXPECT_EQ(variable_accessor.Rewrite("unknown", &result),
RewriteStatus::NOT_RECOGNIZED);
}
TEST(PreprocessorTest, Value) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", int32_t(1)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var", &result), RewriteStatus::SUCCESS);
EXPECT_EQ(result, "1");
}
TEST(PreprocessorTest, ValueVec) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", int2(1, 2)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var", &result), RewriteStatus::SUCCESS);
EXPECT_EQ(result, "ivec2(1,2)");
}
TEST(PreprocessorTest, Field) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(
variable_accessor.AddUniformParameter({"var", float2(1.0, 2.1234567)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var.y", &result),
RewriteStatus::SUCCESS);
EXPECT_EQ(result, "2.123456717f");
}
TEST(PreprocessorTest, FieldFail) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", 1.0f}));
ASSERT_TRUE(variable_accessor.AddUniformParameter({"vec", float2(1.0, 1.0)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var.y", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_FIELD");
result.clear();
ASSERT_EQ(variable_accessor.Rewrite("vec.z", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_FIELD");
}
TEST(PreprocessorTest, Variable) {
VariableAccessor variable_accessor(/*inline_values=*/true);
std::vector<int2> v;
v.push_back(int2(1, 2));
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", v}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var[i].y", &result),
RewriteStatus::SUCCESS);
ASSERT_EQ(result, "var[i].y");
EXPECT_EQ(variable_accessor.GetConstDeclarations(),
"const ivec2 var[] = ivec2[1](ivec2(1,2));\n");
}
TEST(PreprocessorTest, InlineVariableFail) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", 1}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var[i]", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_INDEX");
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,74 @@
/* 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_COMPILER_OPTIONS_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OPTIONS_H_
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// Default constructor for options turns on all optimizations.
struct CompilationOptions {
// Allows to quantify tensors, downcast values, process in float16 etc.
bool allow_precision_loss = false;
// When set few operations are fused into a single shader. Therefore, there
// will be less shaders, but each shader will become larger.
bool fuse_operations = true;
// Parameters will be inlined into a shader. This in turn will generated more
// unique shaders where each will need to be compiled.
bool inline_parameters = false;
// If true, shaders, that have auto-input and auto-output, will use a single
// object for reading and writing.
bool inline_objects = true; // TODO(akulik): unsupported
// Can be only Textures or Buffers
ObjectType preferred_obj_type = ObjectType::UNKNOWN;
// User has an option to choose between textures and buffers. Textures work
// better on Adreno and buffers are better for Mali.
// Chooses object type to represent intermediate tensors. Buffers have more
// efficient memory usage because they represent opaque memory blob, but
// textures work better on Adreno.
// TODO(akulik): may be better name?
ObjectType ref_obj_type = ObjectType::UNKNOWN;
// If true, a user may change BATCH dimension at runtime. Otherwise, static
// batch size will be fixed during compile time.
// Dynamic mode uses less memory, while static mode may yield better
// performance for small models.
bool dynamic_batch = false;
// Fuses consequent nodes which have auto output and auto input.
bool auto_input_fusion = true;
// If true sampler2D and texelFetch will be used to access read only textures.
// This feature is not supported yet by the OpenGL runtime.
bool sampler_textures = false;
// Generate GLSL code compatible with Vulkan.
bool vulkan_support = false;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OPTIONS_H_
@@ -0,0 +1,116 @@
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_portable_test_suite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "util",
hdrs = ["util.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/common:util",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "bhwc_to_phwc4",
srcs = ["bhwc_to_phwc4.cc"],
hdrs = ["bhwc_to_phwc4.h"],
deps = [
":util",
"//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",
"//tensorflow/lite/delegates/gpu/gl:variable",
],
)
cc_test(
name = "bhwc_to_phwc4_test",
size = "small",
srcs = ["bhwc_to_phwc4_test.cc"],
linkopts = [
"-lEGL",
"-lGLESv2",
],
tags = tf_gpu_tests_tags() + [
"local",
"nobuilder",
"notap",
"tflite_not_portable_ios",
],
deps = [
":bhwc_to_phwc4",
"//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",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "phwc4_to_bhwc",
srcs = ["phwc4_to_bhwc.cc"],
hdrs = ["phwc4_to_bhwc.h"],
deps = [
":util",
"//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",
"//tensorflow/lite/delegates/gpu/gl:variable",
],
)
cc_test(
name = "phwc4_to_bhwc_test",
size = "small",
srcs = ["phwc4_to_bhwc_test.cc"],
linkopts = [
"-lEGL",
"-lGLESv2",
],
tags = tf_gpu_tests_tags() + [
"local",
"nobuilder",
"notap",
"tflite_not_portable_ios",
],
deps = [
":phwc4_to_bhwc",
"//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",
"@com_google_googletest//:gtest_main",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,108 @@
/* 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/converters/bhwc_to_phwc4.h"
#include <cstdint>
#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/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/converters/util.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status ConverterBhwcToPhwc4::Create(ConverterBhwcToPhwc4* converter) {
uint3 workgroup_size = uint3(4, 4, 4);
std::string shader_source = GetShaderHeader(workgroup_size) + 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_.z) {
return;
}
vec4 v = vec4(0);
int dst_channel = gid.z * 4;
int index = (gid.y * sizes_.x + gid.x) * sizes_.w + dst_channel;
for (int i = 0; i < 4; ++i, ++index, ++dst_channel) {
if (dst_channel >= sizes_.w) break;
v[i] = input_data.elements[index];
}
output_data.elements[(gid.z * sizes_.y + gid.y) * sizes_.x + gid.x] = v;
})";
GlShader shader;
RETURN_IF_ERROR(
GlShader::CompileShader(GL_COMPUTE_SHADER, shader_source, &shader));
GlProgram program;
RETURN_IF_ERROR(GlProgram::CreateWithShader(shader, &program));
*converter = ConverterBhwcToPhwc4(std::move(program), workgroup_size);
return absl::OkStatus();
}
absl::Status ConverterBhwcToPhwc4::Convert(const BHWC& shape,
const GlBuffer& source,
CommandQueue* command_queue,
GlBuffer* destination) {
if (source.bytes_size() < BytesForBHWC(shape)) {
return absl::InvalidArgumentError(
"BhwcToPhwc4: Input data size does not match expected size.");
}
if (destination->bytes_size() < BytesForPHWC4(shape)) {
return absl::InvalidArgumentError(
"BhwcToPhwc4: output data size does not match expected size.");
}
if (shape.b != 1) {
return absl::UnimplementedError(
"BhwcToPhwc4: Batch size is not equal to 1.");
}
uint3 workload = uint3(shape.w, shape.h, DivideRoundUp(shape.c, 4));
uint3 num_workgroups = DivideRoundUp(workload, workgroup_size_);
RETURN_IF_ERROR(program_.SetParameter(
{"sizes_",
int4(static_cast<int32_t>(workload.x), static_cast<int32_t>(workload.y),
static_cast<int32_t>(workload.z), static_cast<int32_t>(shape.c))}));
RETURN_IF_ERROR(source.BindToIndex(0));
RETURN_IF_ERROR(destination->BindToIndex(1));
if (command_queue) {
return command_queue->Dispatch(program_, num_workgroups);
}
return program_.Dispatch(num_workgroups);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,55 @@
/* 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_CONVERTERS_BHWC_TO_PHWC4_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_BHWC_TO_PHWC4_H_
#include <utility>
#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/gl/command_queue.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
namespace tflite {
namespace gpu {
namespace gl {
class ConverterBhwcToPhwc4 {
public:
// Creates invalid object.
ConverterBhwcToPhwc4() : program_(), workgroup_size_() {}
static absl::Status Create(ConverterBhwcToPhwc4* converter);
absl::Status Convert(const BHWC& shape, const GlBuffer& source,
CommandQueue* command_queue /* optional */,
GlBuffer* destination);
private:
explicit ConverterBhwcToPhwc4(GlProgram program, const uint3& workgroup_size)
: program_(std::move(program)), workgroup_size_(workgroup_size) {}
GlProgram program_;
uint3 workgroup_size_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_BHWC_TO_PHWC4_H_
@@ -0,0 +1,94 @@
/* 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/converters/bhwc_to_phwc4.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;
}
absl::Status RunTest(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.
ConverterBhwcToPhwc4 converter;
RETURN_IF_ERROR(ConverterBhwcToPhwc4::Create(&converter));
RETURN_IF_ERROR(
converter.Convert(shape, input_buffer, nullptr, &output_buffer));
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(HwcToPhwc4, 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);
EXPECT_TRUE(RunTest(shape).ok())
<< shape.h << " " << shape.w << " " << shape.c;
}
}
}
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,104 @@
/* 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/converters/phwc4_to_bhwc.h"
#include <cstdint>
#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/common/util.h"
#include "tensorflow/lite/delegates/gpu/gl/converters/util.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status ConverterPhwc4ToBhwc::Create(ConverterPhwc4ToBhwc* converter) {
uint3 workgroup_size = uint3(4, 4, 4);
std::string shader_source = GetShaderHeader(workgroup_size) + 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];
})";
GlShader shader;
RETURN_IF_ERROR(
GlShader::CompileShader(GL_COMPUTE_SHADER, shader_source, &shader));
GlProgram program;
RETURN_IF_ERROR(GlProgram::CreateWithShader(shader, &program));
*converter = ConverterPhwc4ToBhwc(std::move(program), workgroup_size);
return absl::OkStatus();
}
absl::Status ConverterPhwc4ToBhwc::Convert(const BHWC& shape,
const GlBuffer& source,
CommandQueue* command_queue,
GlBuffer* destination) {
if (source.bytes_size() < BytesForPHWC4(shape)) {
return absl::InvalidArgumentError(
"Phwc4ToBhwc: Input data size does not match expected size.");
}
if (destination->bytes_size() < BytesForBHWC(shape)) {
return absl::InvalidArgumentError(
"Phwc4ToBhwc: output data size does not match expected size.");
}
if (shape.b != 1) {
return absl::UnimplementedError(
"Phwc4ToBhwc: Batch size is not equal to 1.");
}
uint3 workload = uint3(shape.w, shape.h, shape.c);
uint3 num_workgroups = DivideRoundUp(workload, workgroup_size_);
// TODO(akulik): simply pass workload as soon as UniformParameter
// supports uint3
RETURN_IF_ERROR(program_.SetParameter(
{"sizes_",
int4(static_cast<int32_t>(workload.x), static_cast<int32_t>(workload.y),
static_cast<int32_t>(workload.z), 0)}));
RETURN_IF_ERROR(source.BindToIndex(0));
RETURN_IF_ERROR(destination->BindToIndex(1));
if (command_queue) {
return command_queue->Dispatch(program_, num_workgroups);
}
return program_.Dispatch(num_workgroups);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,55 @@
/* 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_CONVERTERS_PHWC4_TO_BHWC_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_PHWC4_TO_BHWC_H_
#include <utility>
#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/gl/command_queue.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
namespace tflite {
namespace gpu {
namespace gl {
class ConverterPhwc4ToBhwc {
public:
// Creates invalid object.
ConverterPhwc4ToBhwc() : program_(), workgroup_size_() {}
static absl::Status Create(ConverterPhwc4ToBhwc* converter);
absl::Status Convert(const BHWC& shape, const GlBuffer& source,
CommandQueue* command_queue /* optional */,
GlBuffer* destination);
private:
explicit ConverterPhwc4ToBhwc(GlProgram program, const uint3& workgroup_size)
: program_(std::move(program)), workgroup_size_(workgroup_size) {}
GlProgram program_;
uint3 workgroup_size_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_PHWC4_TO_BHWC_H_
@@ -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/converters/phwc4_to_bhwc.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;
}
absl::Status RunTest(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.
ConverterPhwc4ToBhwc converter;
RETURN_IF_ERROR(ConverterPhwc4ToBhwc::Create(&converter));
RETURN_IF_ERROR(
converter.Convert(shape, input_buffer, nullptr, &output_buffer));
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(Phwc4ToHwc, 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);
EXPECT_TRUE(RunTest(shape).ok())
<< shape.h << " " << shape.w << " " << shape.c;
}
}
}
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,51 @@
/* 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_CONVERTERS_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_UTIL_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
namespace tflite {
namespace gpu {
namespace gl {
inline 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");
}
inline size_t BytesForPHWC4(const BHWC& shape) {
return static_cast<size_t>(shape.b) * shape.h * shape.w *
AlignByN(shape.c, 4) * sizeof(float);
}
inline size_t BytesForBHWC(const BHWC& shape) {
return static_cast<size_t>(shape.DimensionsProduct()) * sizeof(float);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_CONVERTERS_UTIL_H_
@@ -0,0 +1,147 @@
/* 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/egl_context.h"
#include <cstring>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
absl::Status GetConfig(EGLDisplay display, const EGLint* attributes,
EGLConfig* config) {
EGLint config_count;
bool chosen = eglChooseConfig(display, attributes, config, 1, &config_count);
RETURN_IF_ERROR(GetOpenGlErrors());
if (!chosen || config_count == 0) {
return absl::InternalError("No EGL error, but eglChooseConfig failed.");
}
return absl::OkStatus();
}
absl::Status CreateContext(EGLDisplay display, EGLContext shared_context,
EGLConfig config, EglContext* egl_context) {
static const EGLint attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 3,
#ifdef _DEBUG // Add debugging bit
EGL_CONTEXT_FLAGS_KHR,
EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
#endif
EGL_NONE};
EGLContext context =
eglCreateContext(display, config, shared_context, attributes);
RETURN_IF_ERROR(GetOpenGlErrors());
if (context == EGL_NO_CONTEXT) {
return absl::InternalError("No EGL error, but eglCreateContext failed.");
}
*egl_context = EglContext(context, display, config, true);
return absl::OkStatus();
}
bool HasExtension(EGLDisplay display, const char* name) {
return std::strstr(eglQueryString(display, EGL_EXTENSIONS), name);
}
} // namespace
void EglContext::Invalidate() {
if (context_ != EGL_NO_CONTEXT) {
if (has_ownership_) {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display_, context_);
}
context_ = EGL_NO_CONTEXT;
}
has_ownership_ = false;
}
EglContext::EglContext(EglContext&& other)
: context_(other.context_),
display_(other.display_),
config_(other.config_),
has_ownership_(other.has_ownership_) {
other.context_ = EGL_NO_CONTEXT;
other.has_ownership_ = false;
}
EglContext& EglContext::operator=(EglContext&& other) {
if (this != &other) {
Invalidate();
using std::swap;
swap(context_, other.context_);
display_ = other.display_;
config_ = other.config_;
swap(has_ownership_, other.has_ownership_);
}
return *this;
}
absl::Status EglContext::MakeCurrent(EGLSurface read, EGLSurface write) {
bool is_made_current = eglMakeCurrent(display_, write, read, context_);
RETURN_IF_ERROR(GetOpenGlErrors());
if (!is_made_current) {
return absl::InternalError("No EGL error, but eglMakeCurrent failed.");
}
return absl::OkStatus();
}
bool EglContext::IsCurrent() const {
return context_ == eglGetCurrentContext();
}
absl::Status CreateConfiglessContext(EGLDisplay display,
EGLContext shared_context,
EglContext* egl_context) {
if (!HasExtension(display, "EGL_KHR_no_config_context")) {
return absl::UnavailableError("EGL_KHR_no_config_context not supported");
}
return CreateContext(display, shared_context, EGL_NO_CONFIG_KHR, egl_context);
}
absl::Status CreateSurfacelessContext(EGLDisplay display,
EGLContext shared_context,
EglContext* egl_context) {
if (!HasExtension(display, "EGL_KHR_create_context")) {
return absl::UnavailableError("EGL_KHR_create_context not supported");
}
if (!HasExtension(display, "EGL_KHR_surfaceless_context")) {
return absl::UnavailableError("EGL_KHR_surfaceless_context not supported");
}
const EGLint attributes[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
EGL_NONE};
EGLConfig config;
RETURN_IF_ERROR(GetConfig(display, attributes, &config));
return CreateContext(display, shared_context, config, egl_context);
}
absl::Status CreatePBufferContext(EGLDisplay display, EGLContext shared_context,
EglContext* egl_context) {
const EGLint attributes[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BIND_TO_TEXTURE_RGB,
EGL_TRUE, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
EGL_NONE};
EGLConfig config;
RETURN_IF_ERROR(GetConfig(display, attributes, &config));
return CreateContext(display, shared_context, config, egl_context);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,104 @@
/* 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_EGL_CONTEXT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_CONTEXT_H_
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_egl.h"
namespace tflite {
namespace gpu {
namespace gl {
// EglContext is an RAII wrapper for an EGLContext.
//
// EglContext is moveable but not copyable.
//
// See https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglIntro.xhtml for
// more info.
class EglContext {
public:
// Creates an invalid EglContext.
EglContext()
: context_(EGL_NO_CONTEXT),
display_(EGL_NO_DISPLAY),
config_(EGL_NO_CONFIG_KHR),
has_ownership_(false) {}
EglContext(EGLContext context, EGLDisplay display, EGLConfig config,
bool has_ownership)
: context_(context),
display_(display),
config_(config),
has_ownership_(has_ownership) {}
// Move only
EglContext(EglContext&& other);
EglContext& operator=(EglContext&& other);
EglContext(const EglContext&) = delete;
EglContext& operator=(const EglContext&) = delete;
~EglContext() { Invalidate(); }
EGLContext context() const { return context_; }
EGLDisplay display() const { return display_; }
EGLConfig config() const { return config_; }
// Make this EglContext the current EGL context on this thread, replacing
// the existing current.
absl::Status MakeCurrent(EGLSurface read, EGLSurface write);
absl::Status MakeCurrentSurfaceless() {
return MakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE);
}
// Returns true if this is the currently bound EGL context.
bool IsCurrent() const;
// Returns true if this object actually owns corresponding EGL context
// and manages it's lifetime.
bool has_ownership() const { return has_ownership_; }
private:
void Invalidate();
EGLContext context_;
EGLDisplay display_;
EGLConfig config_;
bool has_ownership_;
};
// It uses the EGL_KHR_no_config_context extension to create a no config context
// since most modern hardware supports the extension.
absl::Status CreateConfiglessContext(EGLDisplay display,
EGLContext shared_context,
EglContext* egl_context);
absl::Status CreateSurfacelessContext(EGLDisplay display,
EGLContext shared_context,
EglContext* egl_context);
absl::Status CreatePBufferContext(EGLDisplay display, EGLContext shared_context,
EglContext* egl_context);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_CONTEXT_H_
@@ -0,0 +1,149 @@
/* 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/egl_environment.h"
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// TODO(akulik): detect power management event when all contexts are destroyed
// and OpenGL ES is reinitialized. See eglMakeCurrent
absl::Status InitDisplay(EGLDisplay* egl_display) {
RETURN_IF_ERROR(
TFLITE_GPU_CALL_EGL(eglGetDisplay, egl_display, EGL_DEFAULT_DISPLAY));
if (*egl_display == EGL_NO_DISPLAY) {
return absl::UnavailableError("eglGetDisplay returned nullptr");
}
bool is_initialized;
RETURN_IF_ERROR(TFLITE_GPU_CALL_EGL(eglInitialize, &is_initialized,
*egl_display, nullptr, nullptr));
if (!is_initialized) {
return absl::InternalError("No EGL error, but eglInitialize failed");
}
return absl::OkStatus();
}
} // namespace
absl::Status EglEnvironment::NewEglEnvironment(
std::unique_ptr<EglEnvironment>* egl_environment) {
*egl_environment = std::make_unique<EglEnvironment>();
RETURN_IF_ERROR((*egl_environment)->Init());
return absl::OkStatus();
}
EglEnvironment::~EglEnvironment() {
if (dummy_framebuffer_ != GL_INVALID_INDEX) {
glDeleteFramebuffers(1, &dummy_framebuffer_);
}
if (dummy_texture_ != GL_INVALID_INDEX) {
glDeleteTextures(1, &dummy_texture_);
}
}
absl::Status EglEnvironment::Init() {
bool is_bound;
RETURN_IF_ERROR(
TFLITE_GPU_CALL_EGL(eglBindAPI, &is_bound, EGL_OPENGL_ES_API));
if (!is_bound) {
return absl::InternalError("No EGL error, but eglBindAPI failed");
}
// Re-use context and display if it was created on this thread.
if (eglGetCurrentContext() != EGL_NO_CONTEXT) {
display_ = eglGetCurrentDisplay();
context_ =
EglContext(eglGetCurrentContext(), display_, EGL_NO_CONFIG_KHR, false);
} else {
RETURN_IF_ERROR(InitDisplay(&display_));
absl::Status status = InitConfiglessContext();
if (!status.ok()) {
status = InitSurfacelessContext();
}
if (!status.ok()) {
status = InitPBufferContext();
}
if (!status.ok()) {
return status;
}
}
if (gpu_info_.vendor == GpuVendor::kUnknown) {
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info_));
}
// TODO(akulik): when do we need ForceSyncTurning?
ForceSyncTurning();
return absl::OkStatus();
}
absl::Status EglEnvironment::InitConfiglessContext() {
RETURN_IF_ERROR(CreateConfiglessContext(display_, EGL_NO_CONTEXT, &context_));
return context_.MakeCurrentSurfaceless();
}
absl::Status EglEnvironment::InitSurfacelessContext() {
RETURN_IF_ERROR(
CreateSurfacelessContext(display_, EGL_NO_CONTEXT, &context_));
RETURN_IF_ERROR(context_.MakeCurrentSurfaceless());
// PowerVR support EGL_KHR_surfaceless_context, but glFenceSync crashes on
// PowerVR when it is surface-less.
RETURN_IF_ERROR(RequestGpuInfo(&gpu_info_));
if (gpu_info_.IsPowerVR()) {
return absl::UnavailableError(
"Surface-less context is not properly supported on powervr.");
}
return absl::OkStatus();
}
absl::Status EglEnvironment::InitPBufferContext() {
RETURN_IF_ERROR(CreatePBufferContext(display_, EGL_NO_CONTEXT, &context_));
RETURN_IF_ERROR(CreatePbufferRGBSurface(context_.config(), display_, 1, 1,
&surface_read_));
RETURN_IF_ERROR(CreatePbufferRGBSurface(context_.config(), display_, 1, 1,
&surface_draw_));
return context_.MakeCurrent(surface_read_.surface(), surface_draw_.surface());
}
void EglEnvironment::ForceSyncTurning() {
glGenFramebuffers(1, &dummy_framebuffer_);
glBindFramebuffer(GL_FRAMEBUFFER, dummy_framebuffer_);
glGenTextures(1, &dummy_texture_);
glBindTexture(GL_TEXTURE_2D, dummy_texture_);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dummy_texture_, 0);
GLenum draw_buffers[1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, draw_buffers);
glViewport(0, 0, 4, 4);
glClear(GL_COLOR_BUFFER_BIT);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,72 @@
/* 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_EGL_ENVIRONMENT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_ENVIRONMENT_H_
#include <memory>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_context.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_surface.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_egl.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/request_gpu_info.h"
namespace tflite {
namespace gpu {
namespace gl {
// Class encapsulates creation of OpenGL objects needed before starting working
// with OpenGL: binds OpenGL ES API, creates new EGL context, binds it to EGL
// display and creates surfaces if needed.
//
// EGL environment needs to be created once per thread.
class EglEnvironment {
public:
static absl::Status NewEglEnvironment(
std::unique_ptr<EglEnvironment>* egl_environment);
EglEnvironment() = default;
~EglEnvironment();
const EglContext& context() const { return context_; }
EGLDisplay display() const { return display_; }
const GpuInfo& gpu_info() const { return gpu_info_; }
private:
absl::Status Init();
absl::Status InitConfiglessContext();
absl::Status InitSurfacelessContext();
absl::Status InitPBufferContext();
EGLDisplay display_ = EGL_NO_DISPLAY;
EglSurface surface_draw_;
EglSurface surface_read_;
EglContext context_;
GpuInfo gpu_info_;
// Strange hack that helps on Mali GPUs
// without it glFinish and glFenceSync don't work
void ForceSyncTurning();
GLuint dummy_framebuffer_ = GL_INVALID_INDEX;
GLuint dummy_texture_ = GL_INVALID_INDEX;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_ENVIRONMENT_H_
@@ -0,0 +1,75 @@
/* 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/egl_surface.h"
#include <cstdint>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
namespace tflite {
namespace gpu {
namespace gl {
EglSurface::EglSurface(EglSurface&& other)
: surface_(other.surface_), display_(other.display_) {
other.surface_ = EGL_NO_SURFACE;
}
EglSurface& EglSurface::operator=(EglSurface&& other) {
if (this != &other) {
display_ = other.display_;
Invalidate();
std::swap(surface_, other.surface_);
}
return *this;
}
void EglSurface::Invalidate() {
if (surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
}
absl::Status CreatePbufferRGBSurface(EGLConfig config, EGLDisplay display,
uint32_t height, uint32_t width,
EglSurface* egl_surface) {
const EGLint pbuffer_attributes[] = {EGL_WIDTH,
static_cast<EGLint>(width),
EGL_HEIGHT,
static_cast<EGLint>(height),
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGB,
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_NONE};
EGLSurface surface =
eglCreatePbufferSurface(display, config, pbuffer_attributes);
RETURN_IF_ERROR(GetOpenGlErrors());
if (surface == EGL_NO_SURFACE) {
return absl::InternalError(
"No EGL error, but eglCreatePbufferSurface failed");
}
*egl_surface = EglSurface(surface, display);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,67 @@
/* 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_EGL_SURFACE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_SURFACE_H_
#include <cstdint>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_egl.h"
namespace tflite {
namespace gpu {
namespace gl {
// An RAII wrapper for EGLSurface.
// See https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglIntro.xhtml for
// an introduction to the concepts.
//
// EglSurface is moveable but not copyable.
class EglSurface {
public:
// Creates an invalid EglSurface.
EglSurface() : surface_(EGL_NO_SURFACE), display_(EGL_NO_DISPLAY) {}
EglSurface(EGLSurface surface, EGLDisplay display)
: surface_(surface), display_(display) {}
// Move-only
EglSurface(EglSurface&& other);
EglSurface& operator=(EglSurface&& other);
EglSurface(const EglSurface&) = delete;
EglSurface& operator=(const EglSurface&) = delete;
~EglSurface() { Invalidate(); }
EGLSurface surface() const { return surface_; }
private:
void Invalidate();
EGLSurface surface_;
EGLDisplay display_;
};
// Creates off-screen pbuffer-based surface of the given height and width.
absl::Status CreatePbufferRGBSurface(EGLConfig config, EGLDisplay display,
uint32_t height, uint32_t width,
EglSurface* egl_surface);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_EGL_SURFACE_H_
@@ -0,0 +1,73 @@
/* 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/float16_conversions.h"
#include <cstdint>
#include <variant>
#include <vector>
#include "fp16.h" // from @FP16
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Performs in-place conversion of float32 into float16
bool ToFloat16(std::vector<uint8_t>* values) {
if (values->size() % sizeof(float) != 0) {
return false;
}
uint16_t* store_f16 = reinterpret_cast<uint16_t*>(values->data());
const float* load_f32 = reinterpret_cast<const float*>(values->data());
const float* end_load_f32 =
reinterpret_cast<const float*>(values->data() + values->size());
while (load_f32 != end_load_f32) {
*store_f16++ = fp16_ieee_from_fp32_value(*load_f32++);
}
values->resize(values->size() / 2);
return true;
}
struct ConverterToFloat16 {
bool operator()(ObjectData& data) const { // NOLINT
return ToFloat16(&data);
}
bool operator()(ObjectRef& buffer) const { // NOLINT
return true;
}
};
} // namespace
bool MaybeConvertToFloat16(Object* object) {
if (object->data_type == DataType::FLOAT32 &&
std::visit(ConverterToFloat16(), object->object)) {
object->data_type = DataType::FLOAT16;
return true;
}
return false;
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,32 @@
/* 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_FLOAT16_CONVERSIONS_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_FLOAT16_CONVERSIONS_H_
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// If an object is float32, converts it to float16 representation.
bool MaybeConvertToFloat16(Object* object);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_FLOAT16_CONVERSIONS_H_
@@ -0,0 +1,167 @@
/* 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/gl_buffer.h"
#include <cstddef>
#include <cstdint>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status CopyBuffer(const GlBuffer& read_buffer,
const GlBuffer& write_buffer) {
if (read_buffer.bytes_size() != write_buffer.bytes_size()) {
return absl::InvalidArgumentError(
"Read buffer does not match write buffer size.");
}
gl_buffer_internal::BufferBinder read_buffer_binder(GL_COPY_READ_BUFFER,
read_buffer.id());
gl_buffer_internal::BufferBinder write_buffer_binder(GL_COPY_WRITE_BUFFER,
write_buffer.id());
return TFLITE_GPU_CALL_GL(glCopyBufferSubData, GL_COPY_READ_BUFFER,
GL_COPY_WRITE_BUFFER, read_buffer.offset(),
write_buffer.offset(), read_buffer.bytes_size());
}
absl::Status GetSSBOSize(GLuint id, int64_t* size_bytes) {
GLuint prev_id;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glGetIntegerv,
GL_SHADER_STORAGE_BUFFER_BINDING,
reinterpret_cast<GLint*>(&prev_id)));
gl_buffer_internal::BufferBinder binder(GL_SHADER_STORAGE_BUFFER, id,
prev_id);
return TFLITE_GPU_CALL_GL(glGetBufferParameteri64v, GL_SHADER_STORAGE_BUFFER,
GL_BUFFER_SIZE, size_bytes);
}
GlBuffer::GlBuffer(GlBuffer&& buffer)
: GlBuffer(buffer.target_, buffer.id_, buffer.bytes_size_, buffer.offset_,
buffer.has_ownership_) {
buffer.has_ownership_ = false;
}
GlBuffer& GlBuffer::operator=(GlBuffer&& buffer) {
if (this != &buffer) {
Invalidate();
target_ = buffer.target_;
bytes_size_ = buffer.bytes_size_;
offset_ = buffer.offset_;
has_ownership_ = buffer.has_ownership_;
id_ = buffer.id_;
buffer.has_ownership_ = false;
}
return *this;
}
GlBuffer::~GlBuffer() { Invalidate(); }
void GlBuffer::Invalidate() {
if (has_ownership_ && id_ != GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glDeleteBuffers, 1, &id_).IgnoreError();
id_ = GL_INVALID_INDEX;
}
}
absl::Status GlBuffer::BindToIndex(uint32_t index) const {
return TFLITE_GPU_CALL_GL(glBindBufferRange, target_, index, id_, offset_,
bytes_size_);
}
absl::Status GlBuffer::MakeView(size_t offset, size_t bytes_size,
GlBuffer* gl_buffer) {
if (offset + bytes_size > bytes_size_) {
return absl::OutOfRangeError("GlBuffer view is out of range.");
}
*gl_buffer = GlBuffer(target_, id_, bytes_size, offset_ + offset,
/*has_ownership=*/false);
return absl::OkStatus();
}
GlBuffer GlBuffer::MakeRef() {
return GlBuffer(target_, id_, bytes_size_, offset_,
/* has_ownership = */ false);
}
GlPersistentBuffer::GlPersistentBuffer(GLenum target, GLuint id,
size_t bytes_size, size_t offset,
bool has_ownership, void* data)
: GlBuffer(target, id, bytes_size, offset, has_ownership), data_(data) {}
GlPersistentBuffer::GlPersistentBuffer()
: GlPersistentBuffer(GL_INVALID_ENUM, GL_INVALID_INDEX, 0, 0, false,
nullptr) {}
GlPersistentBuffer::GlPersistentBuffer(GlPersistentBuffer&& buffer)
: GlBuffer(std::move(buffer)), data_(buffer.data_) {}
GlPersistentBuffer& GlPersistentBuffer::operator=(GlPersistentBuffer&& buffer) {
if (this != &buffer) {
data_ = buffer.data_;
GlBuffer::operator=(std::move(buffer));
}
return *this;
}
GlPersistentBuffer::~GlPersistentBuffer() {
if (!data_) return;
gl_buffer_internal::BufferBinder binder(GL_SHADER_STORAGE_BUFFER, id());
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}
absl::Status CreatePersistentBuffer(size_t size,
GlPersistentBuffer* gl_buffer) {
PFNGLBUFFERSTORAGEEXTPROC glBufferStorageEXT = nullptr;
glBufferStorageEXT = reinterpret_cast<PFNGLBUFFERSTORAGEEXTPROC>(
eglGetProcAddress("glBufferStorageEXT"));
if (!glBufferStorageEXT) {
return absl::UnavailableError("glBufferStorageEXT is not supported");
}
gl_buffer_internal::BufferId id;
gl_buffer_internal::BufferBinder binder(GL_SHADER_STORAGE_BUFFER, id.id());
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(
glBufferStorageEXT, GL_SHADER_STORAGE_BUFFER, size, nullptr,
GL_MAP_COHERENT_BIT_EXT | GL_MAP_READ_BIT | GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT_EXT));
void* data = nullptr;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(
glMapBufferRange, &data, GL_SHADER_STORAGE_BUFFER, 0, size,
GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT));
*gl_buffer = GlPersistentBuffer{
GL_SHADER_STORAGE_BUFFER, id.Release(), size, 0, true, data};
return absl::OkStatus();
}
namespace gl_buffer_internal {
BufferMapper::BufferMapper(GLenum target, size_t offset, size_t bytes,
GLbitfield access)
: target_(target),
data_(glMapBufferRange(target_, offset, bytes, access)) {}
BufferMapper::~BufferMapper() {
TFLITE_GPU_CALL_GL(glUnmapBuffer, target_).IgnoreError();
}
}; // namespace gl_buffer_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,335 @@
/* 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_GL_BUFFER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_BUFFER_H_
#include <cstdint>
#include <cstring>
#include <functional>
#include <utility>
#include <vector>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
// Buffer is an RAII wrapper for OpenGL buffer object.
// See https://www.khronos.org/opengl/wiki/Buffer_Object for more information.
//
// Buffer is moveable but not copyable.
class GlBuffer {
public:
// @param has_ownership indicates that GlBuffer is responsible for
// corresponding GL buffer deletion.
GlBuffer(GLenum target, GLuint id, size_t bytes_size, size_t offset,
bool has_ownership)
: target_(target),
id_(id),
bytes_size_(bytes_size),
offset_(offset),
has_ownership_(has_ownership) {}
// Creates invalid buffer.
GlBuffer() : GlBuffer(GL_INVALID_ENUM, GL_INVALID_INDEX, 0, 0, false) {}
// Move-only
GlBuffer(GlBuffer&& buffer);
GlBuffer& operator=(GlBuffer&& buffer);
GlBuffer(const GlBuffer&) = delete;
GlBuffer& operator=(const GlBuffer&) = delete;
~GlBuffer();
// Reads data from buffer into CPU memory. Data should point to a region that
// has at least bytes_size available.
template <typename T>
absl::Status Read(absl::Span<T> data) const;
// Writes data to a buffer.
template <typename T>
absl::Status Write(absl::Span<const T> data);
// Maps GPU memory to CPU address space and calls reader that may read from
// that memory.
template <typename T>
absl::Status MappedRead(
const std::function<absl::Status(absl::Span<const T>)>& reader) const;
// Maps GPU memory to CPU address space and calls writer that may write into
// that memory.
template <typename T>
absl::Status MappedWrite(
const std::function<absl::Status(absl::Span<T>)>& writer);
absl::Status MakeView(size_t offset, size_t bytes_size, GlBuffer* gl_buffer);
// Makes a copy without ownership of the buffer.
GlBuffer MakeRef();
// Binds a buffer to an index.
absl::Status BindToIndex(uint32_t index) const;
// Releases the ownership of the buffer object.
void Release() { has_ownership_ = false; }
size_t bytes_size() const { return bytes_size_; }
const GLenum target() const { return target_; }
const GLuint id() const { return id_; }
bool is_valid() const { return id_ != GL_INVALID_INDEX; }
size_t offset() const { return offset_; }
// @return true if this object actually owns corresponding GL buffer
// and manages it's lifetime.
bool has_ownership() const { return has_ownership_; }
private:
void Invalidate();
GLenum target_;
GLuint id_;
size_t bytes_size_;
size_t offset_;
bool has_ownership_;
};
absl::Status CopyBuffer(const GlBuffer& read_buffer,
const GlBuffer& write_buffer);
absl::Status GetSSBOSize(GLuint id, int64_t* size_bytes);
// Creates new shader storage buffer that will be modified and used many
// times.
// Buffer will be initialized with 0's.
//
// See https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object for
// details.
template <typename T>
absl::Status CreateReadWriteShaderStorageBuffer(uint32_t num_elements,
GlBuffer* gl_buffer);
// Creates new shader storage buffer that will be filled with data once which
// will be used many times.
template <typename T>
absl::Status CreateReadOnlyShaderStorageBuffer(absl::Span<const T> data,
GlBuffer* gl_buffer);
// Adapts raw Buffer::Read method to read data into a vector.
template <typename T>
absl::Status AppendFromBuffer(const GlBuffer& buffer, std::vector<T>* data) {
if (buffer.bytes_size() % sizeof(T) != 0) {
return absl::InvalidArgumentError("Buffer is not aligned");
}
size_t num_elements = buffer.bytes_size() / sizeof(T);
data->resize(data->size() + num_elements);
return buffer.Read<T>(
absl::MakeSpan(data->data() + data->size() - num_elements, num_elements));
}
// Persistent buffer provides CPU pointer to the buffer that is valid all the
// time. A user should properly synchronize the access to the buffer on CPU and
// GPU sides.
class GlPersistentBuffer : public GlBuffer {
public:
GlPersistentBuffer(GLenum target, GLuint id, size_t bytes_size, size_t offset,
bool has_ownership, void* data);
GlPersistentBuffer();
// Move-only
GlPersistentBuffer(GlPersistentBuffer&& buffer);
GlPersistentBuffer& operator=(GlPersistentBuffer&& buffer);
GlPersistentBuffer(const GlPersistentBuffer&) = delete;
GlPersistentBuffer& operator=(const GlPersistentBuffer&) = delete;
~GlPersistentBuffer();
void* data() { return data_; }
private:
void* data_;
};
// Creates read-write persistent buffer with valid CPU pointer
absl::Status CreatePersistentBuffer(size_t size, GlPersistentBuffer* gl_buffer);
////////////////////////////////////////////////////////////////////////////////
// Implementation details are below.
namespace gl_buffer_internal {
// RAII for creating and/or owning buffer id.
class BufferId {
public:
BufferId() : id_(GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glGenBuffers, 1 /* number of buffers */, &id_)
.IgnoreError();
// only possible error here is when a number of buffers is negative.
}
explicit BufferId(GLuint id) : id_(id) {}
~BufferId() {
if (id_ != GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glDeleteBuffers, 1, &id_).IgnoreError();
}
}
GLuint id() const { return id_; }
GLuint Release() {
GLuint id = GL_INVALID_INDEX;
std::swap(id, id_);
return id;
}
private:
GLuint id_;
};
// RAII for binding and unbinding a buffer.
class BufferBinder {
public:
BufferBinder(GLenum target, GLuint id) : target_(target), prev_id_(0) {
TFLITE_GPU_CALL_GL(glBindBuffer, target_, id).IgnoreError();
}
BufferBinder(GLenum target, GLuint id, GLuint prev_id)
: target_(target), prev_id_(prev_id) {
TFLITE_GPU_CALL_GL(glBindBuffer, target_, id).IgnoreError();
}
~BufferBinder() {
TFLITE_GPU_CALL_GL(glBindBuffer, target_, prev_id_).IgnoreError();
}
private:
const GLenum target_;
GLuint prev_id_;
};
// RAII for mapping and unmapping a buffer.
class BufferMapper {
public:
BufferMapper(GLenum target, size_t offset, size_t bytes, GLbitfield access);
~BufferMapper();
void* data() { return data_; }
private:
const GLenum target_;
void* data_;
};
} // namespace gl_buffer_internal
template <typename T>
absl::Status CreateReadWriteShaderStorageBuffer(uint32_t num_elements,
GlBuffer* gl_buffer) {
gl_buffer_internal::BufferId id;
gl_buffer_internal::BufferBinder binder(GL_SHADER_STORAGE_BUFFER, id.id());
// TODO(akulik): benchmark DYNAMIC vs STREAM buffer
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(
glBufferData, GL_SHADER_STORAGE_BUFFER, num_elements * sizeof(T),
std::vector<T>(num_elements).data(), GL_STREAM_COPY));
*gl_buffer = GlBuffer{GL_SHADER_STORAGE_BUFFER, id.Release(),
num_elements * sizeof(T), 0, true};
return absl::OkStatus();
}
template <typename T>
absl::Status CreateReadOnlyShaderStorageBuffer(absl::Span<const T> data,
GlBuffer* gl_buffer) {
gl_buffer_internal::BufferId id;
gl_buffer_internal::BufferBinder binder(GL_SHADER_STORAGE_BUFFER, id.id());
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glBufferData, GL_SHADER_STORAGE_BUFFER,
data.size() * sizeof(T), data.data(),
GL_STATIC_READ));
*gl_buffer = GlBuffer{GL_SHADER_STORAGE_BUFFER, id.Release(),
data.size() * sizeof(T), 0, true};
return absl::OkStatus();
}
template <typename T>
absl::Status GlBuffer::Read(absl::Span<T> data) const {
if (data.size() * sizeof(T) < bytes_size()) {
return absl::InvalidArgumentError(
"Read from buffer failed. Destination data is shorter than buffer.");
}
// TODO(akulik): glCopyBufferSubData is actually available in ES 3.1, try it.
return MappedRead<T>([this, data](absl::Span<const T> src) {
std::memcpy(data.data(), src.data(), bytes_size());
return absl::OkStatus();
});
}
template <typename T>
absl::Status GlBuffer::Write(absl::Span<const T> data) {
if (data.size() * sizeof(T) > bytes_size_) {
return absl::InvalidArgumentError(
"Write to buffer failed. Source data is larger than buffer.");
}
gl_buffer_internal::BufferBinder binder(target_, id_);
return TFLITE_GPU_CALL_GL(glBufferSubData, target_, offset_, bytes_size_,
data.data());
}
template <typename T>
absl::Status GlBuffer::MappedRead(
const std::function<absl::Status(absl::Span<const T> d)>& reader) const {
if (bytes_size_ % sizeof(T) != 0) {
return absl::InvalidArgumentError("Buffer is not aligned");
}
gl_buffer_internal::BufferBinder binder(target_, id_);
gl_buffer_internal::BufferMapper mapper(target_, offset_, bytes_size_,
GL_MAP_READ_BIT);
if (!mapper.data()) {
return GetOpenGlErrors();
}
return reader(absl::MakeSpan(reinterpret_cast<const T*>(mapper.data()),
bytes_size_ / sizeof(T)));
}
template <typename T>
absl::Status GlBuffer::MappedWrite(
const std::function<absl::Status(absl::Span<T> d)>& writer) {
if (bytes_size_ % sizeof(T) != 0) {
return absl::InvalidArgumentError("Buffer is not aligned");
}
gl_buffer_internal::BufferBinder binder(target_, id_);
gl_buffer_internal::BufferMapper mapper(target_, offset_, bytes_size_,
GL_MAP_WRITE_BIT);
if (!mapper.data()) {
return GetOpenGlErrors();
}
return writer(absl::MakeSpan(reinterpret_cast<T*>(mapper.data()),
bytes_size_ / sizeof(T)));
}
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_BUFFER_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/gl_buffer.h"
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/egl_environment.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(Buffer, CreateReadWrite) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
GlBuffer buffer;
ASSERT_TRUE(CreateReadWriteShaderStorageBuffer<float>(4, &buffer).ok());
std::vector<float> from_buffer;
ASSERT_TRUE(AppendFromBuffer(buffer, &from_buffer).ok());
EXPECT_THAT(from_buffer, testing::ElementsAre(0, 0, 0, 0));
}
TEST(Buffer, Read) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
std::vector<float> test = {0, 1, 2, 3};
GlBuffer buffer;
ASSERT_TRUE(CreateReadOnlyShaderStorageBuffer<float>(test, &buffer).ok());
std::vector<float> from_buffer;
ASSERT_TRUE(AppendFromBuffer(buffer, &from_buffer).ok());
EXPECT_EQ(test, from_buffer);
}
TEST(Buffer, Write) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
GlBuffer buffer;
ASSERT_TRUE(CreateReadWriteShaderStorageBuffer<float>(4, &buffer).ok());
std::vector<float> test = {0, 1, 2, 3};
ASSERT_TRUE(buffer.Write<float>(test).ok());
std::vector<float> from_buffer;
ASSERT_TRUE(AppendFromBuffer(buffer, &from_buffer).ok());
EXPECT_EQ(test, from_buffer);
}
TEST(Buffer, View) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
GlBuffer buffer;
ASSERT_TRUE(CreateReadWriteShaderStorageBuffer<float>(6, &buffer).ok());
EXPECT_TRUE(buffer.has_ownership());
EXPECT_EQ(24, buffer.bytes_size());
EXPECT_EQ(0, buffer.offset());
// Create view and write data there.
GlBuffer view;
ASSERT_TRUE(buffer.MakeView(4, 16, &view).ok());
EXPECT_FALSE(view.has_ownership());
EXPECT_EQ(16, view.bytes_size());
EXPECT_EQ(4, view.offset());
std::vector<float> test = {1, 2, 3, 4};
ASSERT_TRUE(view.Write<float>(test).ok());
// Check that data indeed landed in a buffer with proper offset.
std::vector<float> from_buffer;
ASSERT_TRUE(AppendFromBuffer(buffer, &from_buffer).ok());
EXPECT_THAT(from_buffer, testing::ElementsAre(0, 1, 2, 3, 4, 0));
std::vector<float> from_view;
ASSERT_TRUE(AppendFromBuffer(view, &from_view).ok());
EXPECT_THAT(from_view, testing::ElementsAre(1, 2, 3, 4));
}
TEST(Buffer, SubView) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
GlBuffer buffer;
ASSERT_TRUE(CreateReadWriteShaderStorageBuffer<float>(6, &buffer).ok());
// Create view and another view over that view.
GlBuffer view1;
ASSERT_TRUE(buffer.MakeView(4, 16, &view1).ok());
GlBuffer view2;
EXPECT_FALSE(view1.MakeView(1, 16, &view2).ok());
ASSERT_TRUE(view1.MakeView(2, 2, &view2).ok());
EXPECT_FALSE(view2.has_ownership());
EXPECT_EQ(2, view2.bytes_size());
EXPECT_EQ(6, view2.offset());
}
TEST(Buffer, Copy) {
std::unique_ptr<EglEnvironment> env;
ASSERT_TRUE(EglEnvironment::NewEglEnvironment(&env).ok());
GlBuffer buffer;
ASSERT_TRUE(CreateReadWriteShaderStorageBuffer<float>(4, &buffer).ok());
// Create view and write data there.
GlBuffer view1;
ASSERT_TRUE(buffer.MakeView(4, 4, &view1).ok());
GlBuffer view2;
ASSERT_TRUE(buffer.MakeView(8, 4, &view2).ok());
// Copy data from one view to another
ASSERT_TRUE(view1.Write<float>({1}).ok());
ASSERT_TRUE(CopyBuffer(view1, view2).ok());
// Check that data indeed landed correctly.
std::vector<float> from_buffer;
ASSERT_TRUE(AppendFromBuffer(buffer, &from_buffer).ok());
EXPECT_THAT(from_buffer, testing::ElementsAre(0, 1, 1, 0));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
+118
View File
@@ -0,0 +1,118 @@
/* 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_GL_CALL_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_CALL_H_
#include <string>
#include <type_traits>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
namespace tflite {
namespace gpu {
namespace gl {
// Primary purpose of this file is to provide useful macro for calling GL
// functions and checking errors. It also attaches a context to status in case
// of a GL error.
//
// Use TFLITE_GPU_CALL_GL as follows:
//
// For GL functions with a return value:
// Before:
// GLint result = glFunc(...);
// RETURN_IF_ERROR(GetOpenGlErrors());
// After:
// GLint result;
// RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glFunc, &result, ...));
//
// For GL functions without a return value:
// Before:
// glFunc(...);
// RETURN_IF_ERROR(GetOpenGlErrors());
// After:
// RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glFunc, ...));
namespace gl_call_internal {
// For GL functions with a return value.
template <typename T>
struct Caller {
template <typename F, typename ErrorF, typename... Params>
absl::Status operator()(const std::string& context, F func, ErrorF error_func,
T* result, Params&&... params) {
*result = func(std::forward<Params>(params)...);
const auto status = error_func();
if (status.ok()) return absl::OkStatus();
return absl::Status(status.code(),
std::string(status.message()) + ": " + context);
}
};
// For GL functions without a return value.
template<>
struct Caller<void> {
template <typename F, typename ErrorF, typename... Params>
absl::Status operator()(const std::string& context, F func, ErrorF error_func,
Params&&... params) {
func(std::forward<Params>(params)...);
const auto status = error_func();
if (status.ok()) return absl::OkStatus();
return absl::Status(status.code(),
std::string(status.message()) + ": " + context);
}
};
template <typename F, typename ErrorF, typename ResultT, typename... ParamsT>
absl::Status CallAndCheckError(const std::string& context, F func,
ErrorF error_func, ResultT* result,
ParamsT&&... params) {
return Caller<ResultT>()(context, func, error_func, result,
std::forward<ParamsT>(params)...);
}
template <typename F, typename ErrorF, typename... Params>
absl::Status CallAndCheckError(const std::string& context, F func,
ErrorF error_func, Params&&... params) {
return Caller<void>()(context, func, error_func,
std::forward<Params>(params)...);
}
} // namespace gl_call_internal
// XX_STRINGIFY is a helper macro to effectively apply # operator to an
// arbitrary value.
#define TFLITE_GPU_INTERNAL_STRINGIFY_HELPER(x) #x
#define TFLITE_GPU_INTERNAL_STRINGIFY(x) TFLITE_GPU_INTERNAL_STRINGIFY_HELPER(x)
#define TFLITE_GPU_FILE_LINE \
__FILE__ ":" TFLITE_GPU_INTERNAL_STRINGIFY(__LINE__)
#define TFLITE_GPU_CALL_GL(method, ...) \
::tflite::gpu::gl::gl_call_internal::CallAndCheckError( \
#method " in " TFLITE_GPU_FILE_LINE, method, \
::tflite::gpu::gl::GetOpenGlErrors, __VA_ARGS__)
#define TFLITE_GPU_CALL_EGL(method, ...) \
::tflite::gpu::gl::gl_call_internal::CallAndCheckError( \
#method " in " TFLITE_GPU_FILE_LINE, method, \
::tflite::gpu::gl::GetEglError, __VA_ARGS__)
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_CALL_H_
@@ -0,0 +1,148 @@
/* 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/gl_errors.h"
#include <string>
#include <vector>
#include "absl/strings/str_join.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_egl.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
const char* ErrorToString(GLenum error) {
switch (error) {
case GL_INVALID_ENUM:
return "[GL_INVALID_ENUM]: An unacceptable value is specified for an "
"enumerated argument.";
case GL_INVALID_VALUE:
return "[GL_INVALID_VALUE]: A numeric argument is out of range.";
case GL_INVALID_OPERATION:
return "[GL_INVALID_OPERATION]: The specified operation is not allowed "
"in the current state.";
case GL_INVALID_FRAMEBUFFER_OPERATION:
return "[GL_INVALID_FRAMEBUFFER_OPERATION]: The framebuffer object is "
"not complete.";
case GL_OUT_OF_MEMORY:
return "[GL_OUT_OF_MEMORY]: There is not enough memory left to execute "
"the command.";
}
return "[UNKNOWN_GL_ERROR]";
}
struct ErrorFormatter {
void operator()(std::string* out, GLenum error) const {
absl::StrAppend(out, ErrorToString(error));
}
};
} // namespace
// TODO(akulik): create new error space for GL error.
absl::Status GetOpenGlErrors() {
#ifdef __EMSCRIPTEN__
// This check is not recommended on WebGL, since it will force a wait on the
// GPU process.
return absl::OkStatus();
#else
auto error = glGetError();
if (error == GL_NO_ERROR) {
return absl::OkStatus();
}
auto error2 = glGetError();
if (error2 == GL_NO_ERROR) {
return absl::InternalError(ErrorToString(error));
}
std::vector<GLenum> errors = {error, error2};
for (error = glGetError(); error != GL_NO_ERROR; error = glGetError()) {
errors.push_back(error);
}
return absl::InternalError(absl::StrJoin(errors, ",", ErrorFormatter()));
#endif // __EMSCRIPTEN__
}
absl::Status GetEglError() {
EGLint error = eglGetError();
switch (error) {
case EGL_SUCCESS:
return absl::OkStatus();
case EGL_NOT_INITIALIZED:
return absl::InternalError(
"EGL is not initialized, or could not be initialized, for the "
"specified EGL display connection.");
case EGL_BAD_ACCESS:
return absl::InternalError(
"EGL cannot access a requested resource (for example a context is "
"bound in another thread).");
case EGL_BAD_ALLOC:
return absl::InternalError(
"EGL failed to allocate resources for the requested operation.");
case EGL_BAD_ATTRIBUTE:
return absl::InternalError(
"An unrecognized attribute or attribute value was passed in the "
"attribute list.");
case EGL_BAD_CONTEXT:
return absl::InternalError(
"An EGLContext argument does not name a valid EGL rendering "
"context.");
case EGL_BAD_CONFIG:
return absl::InternalError(
"An EGLConfig argument does not name a valid EGL frame buffer "
"configuration.");
case EGL_BAD_CURRENT_SURFACE:
return absl::InternalError(
"The current surface of the calling thread is a window, pixel buffer "
"or pixmap that is no longer valid.");
case EGL_BAD_DISPLAY:
return absl::InternalError(
"An EGLDisplay argument does not name a valid EGL display "
"connection.");
case EGL_BAD_SURFACE:
return absl::InternalError(
"An EGLSurface argument does not name a valid surface (window, pixel "
"buffer or pixmap) configured for GL rendering.");
case EGL_BAD_MATCH:
return absl::InternalError(
"Arguments are inconsistent (for example, a valid context requires "
"buffers not supplied by a valid surface).");
case EGL_BAD_PARAMETER:
return absl::InternalError("One or more argument values are invalid.");
case EGL_BAD_NATIVE_PIXMAP:
return absl::InternalError(
"A NativePixmapType argument does not refer to a valid native "
"pixmap.");
case EGL_BAD_NATIVE_WINDOW:
return absl::InternalError(
"A NativeWindowType argument does not refer to a valid native "
"window.");
case EGL_CONTEXT_LOST:
return absl::InternalError(
"A power management event has occurred. The application must destroy "
"all contexts and reinitialize OpenGL ES state and objects to "
"continue rendering.");
}
return absl::UnknownError("EGL error: " + std::to_string(error));
}
} // 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_GL_ERRORS_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_ERRORS_H_
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
// @return recent opengl errors and packs them into Status.
absl::Status GetOpenGlErrors();
// @return the error of the last called EGL function in the current thread.
absl::Status GetEglError();
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_ERRORS_H_
@@ -0,0 +1,225 @@
/* 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/gl_program.h"
#include <cstdint>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
absl::Status CreateNewProgramId(GLuint* program_id) {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glCreateProgram, program_id));
if (!*program_id) {
return absl::UnknownError("Can't create opengl program: 0 program_id");
}
return absl::OkStatus();
}
absl::Status CheckProgramLinked(GLuint program_id) {
GLint linked;
glGetProgramiv(program_id, GL_LINK_STATUS, &linked);
if (linked == GL_TRUE) {
return absl::OkStatus();
}
GLint info_size;
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_size);
std::string errors;
errors.resize(info_size + 1 /* plus \0 */);
glGetProgramInfoLog(program_id, info_size + 1, nullptr, &errors[0]);
// TODO(akulik): use glValidateProgram to gather more info.
return absl::UnavailableError("Program is not properly linked: " + errors);
}
struct ParameterSetter {
absl::Status operator()(int value) {
return TFLITE_GPU_CALL_GL(glProgramUniform1i, program_id, uniform_id,
value);
}
absl::Status operator()(const int2& value) {
return TFLITE_GPU_CALL_GL(glProgramUniform2i, program_id, uniform_id,
value.x, value.y);
}
absl::Status operator()(const int4& value) {
return TFLITE_GPU_CALL_GL(glProgramUniform4i, program_id, uniform_id,
value.x, value.y, value.z, value.w);
}
absl::Status operator()(const std::vector<int2>& value) {
std::vector<GLint> ints(value.size() * 2, 0);
for (int i = 0; i < value.size(); ++i) {
ints[i * 2] = value[i].x;
ints[i * 2 + 1] = value[i].y;
}
return TFLITE_GPU_CALL_GL(glProgramUniform2iv, program_id, uniform_id,
ints.size(), ints.data());
}
absl::Status operator()(unsigned int value) {
return TFLITE_GPU_CALL_GL(glProgramUniform1ui, program_id, uniform_id,
value);
}
absl::Status operator()(const uint4& value) {
return TFLITE_GPU_CALL_GL(glProgramUniform4ui, program_id, uniform_id,
value.x, value.y, value.z, value.w);
}
absl::Status operator()(float value) {
return TFLITE_GPU_CALL_GL(glProgramUniform1f, program_id, uniform_id,
value);
}
absl::Status operator()(const float2& value) {
return TFLITE_GPU_CALL_GL(glProgramUniform2f, program_id, uniform_id,
value.x, value.y);
}
absl::Status operator()(const float4& value) {
return TFLITE_GPU_CALL_GL(glProgramUniform4f, program_id, uniform_id,
value.x, value.y, value.z, value.w);
}
absl::Status operator()(const std::vector<float4>& value) {
std::vector<GLfloat> floats(value.size() * 4, 0);
for (int i = 0; i < value.size(); ++i) {
floats[i * 4] = value[i].x;
floats[i * 4 + 1] = value[i].y;
floats[i * 4 + 2] = value[i].z;
floats[i * 4 + 3] = value[i].w;
}
return TFLITE_GPU_CALL_GL(glProgramUniform4fv, program_id, uniform_id,
floats.size(), floats.data());
}
const GLuint program_id;
const GLint uniform_id;
};
} // namespace
absl::Status GlProgram::CreateWithShader(const GlShader& shader,
GlProgram* gl_program) {
GLuint program_id;
RETURN_IF_ERROR(CreateNewProgramId(&program_id));
// program_id needs to be properly deleted if there will be an error, hense
// wrap program_id into Program.
GlProgram program(program_id);
RETURN_IF_ERROR(
TFLITE_GPU_CALL_GL(glAttachShader, program.id(), shader.id()));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glLinkProgram, program.id()));
RETURN_IF_ERROR(CheckProgramLinked(program.id()));
*gl_program = std::move(program);
return absl::OkStatus();
}
absl::Status GlProgram::CreateWithBinaryShader(const BinaryShader& shader,
GlProgram* gl_program) {
GLuint program_id;
RETURN_IF_ERROR(CreateNewProgramId(&program_id));
// program_id needs to be properly deleted if there will be an error, hense
// wrap program_id into Program.
GlProgram program(program_id);
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glProgramBinary, program.id(),
shader.format(), shader.binary().data(),
shader.binary().size()));
RETURN_IF_ERROR(CheckProgramLinked(program.id()));
*gl_program = std::move(program);
return absl::OkStatus();
}
absl::Status GlProgram::GetBinary(BinaryShader* binary_shader) {
GLint size = 0;
RETURN_IF_ERROR(
TFLITE_GPU_CALL_GL(glGetProgramiv, id_, GL_PROGRAM_BINARY_LENGTH, &size));
if (!size) {
return absl::InternalError("Getting binary size failed.");
}
// TODO(akulik): call
// glProgramParameteri(id_, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE)
// before linking a program to increase chances of retrieving a binary.
std::vector<uint8_t> binary(size);
GLsizei returned_size;
GLenum format;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glGetProgramBinary, id_, size,
&returned_size, &format,
reinterpret_cast<void*>(&binary[0])));
if (size != returned_size) {
return absl::InternalError("Getting binary is failed.");
}
*binary_shader = BinaryShader(format, std::move(binary));
return absl::OkStatus();
}
GlProgram::GlProgram(GlProgram&& program) : id_(program.id_) {
program.id_ = 0;
}
void GlProgram::Invalidate() {
if (id_) {
glDeleteProgram(id_);
id_ = 0;
}
}
GlProgram& GlProgram::operator=(GlProgram&& program) {
if (this != &program) {
Invalidate();
std::swap(id_, program.id_);
}
return *this;
}
GlProgram::~GlProgram() { Invalidate(); }
absl::Status GlProgram::SetParameter(const Variable& param) {
GLint uniform_location;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glGetUniformLocation, &uniform_location,
id_, param.name.c_str()));
return std::visit(ParameterSetter{id_, uniform_location}, param.value);
}
absl::Status GlProgram::Dispatch(const uint3& workgroups) const {
if (workgroups.x == 0 || workgroups.y == 0 || workgroups.z == 0) {
return absl::InvalidArgumentError("Invalid workgroups");
}
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glUseProgram, id_));
return TFLITE_GPU_CALL_GL(glDispatchCompute, workgroups.x, workgroups.y,
workgroups.z);
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,86 @@
/* 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_GL_PROGRAM_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_PROGRAM_H_
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
// A wrapper around opengl program id that needs to be recycled when not needed.
// Encapsulates logic needed to bind parameters, link a program and execute it.
class GlProgram {
public:
// Creates invalid program.
GlProgram() : id_(0) {}
// Creates new program, initializes it, attaches the given shader and links
// a program. Thus, if this call returns a program, one may set parameters and
// finally execute a program.
// therefore it needs to be handled elsewhere.
static absl::Status CreateWithShader(const GlShader& shader,
GlProgram* gl_program);
// Same as CreateWithShader but takes compiled shader in a binary form,
// therefore compilation step is avoided.
static absl::Status CreateWithBinaryShader(const BinaryShader& shader,
GlProgram* gl_program);
// move-only
GlProgram(GlProgram&& program);
GlProgram& operator=(GlProgram&& program);
GlProgram(const GlProgram&) = delete;
GlProgram& operator=(const GlProgram&) = delete;
~GlProgram();
GLuint id() const { return id_; }
// Returns a binary representation for a shader currently attached and linked
// into this program.
absl::Status GetBinary(BinaryShader* binary_shader);
absl::Status SetParameter(const Variable& param);
// Executes program
absl::Status Dispatch(const uint3& workgroups) const;
bool is_valid() const { return id_ != 0; }
private:
explicit GlProgram(GLuint program_id) : id_(program_id) {}
void Invalidate();
GLint GetUniformId(const std::string& name);
GLuint id_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_PROGRAM_H_
@@ -0,0 +1,85 @@
/* 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/gl_shader.h"
#include <string>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
namespace tflite {
namespace gpu {
namespace gl {
GlShader::GlShader(GlShader&& shader) : id_(shader.id_) { shader.id_ = 0; }
void GlShader::Invalidate() {
if (id_) {
glDeleteShader(id_);
id_ = 0;
}
}
GlShader& GlShader::operator=(GlShader&& shader) {
if (this != &shader) {
Invalidate();
std::swap(id_, shader.id_);
}
return *this;
}
GlShader::~GlShader() { Invalidate(); }
absl::Status GlShader::CompileShader(GLenum shader_type,
const std::string& shader_source,
GlShader* gl_shader) {
// NOTE: code compilation can fail due to gl errors happened before
GLuint shader_id;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glCreateShader, &shader_id, shader_type));
GlShader shader(shader_id);
const char* src = shader_source.c_str();
RETURN_IF_ERROR(
TFLITE_GPU_CALL_GL(glShaderSource, shader.id(), 1, &src, nullptr));
glCompileShader(shader.id());
#ifndef __EMSCRIPTEN__
// This check is not recommended on WebGL, since it will force a wait on the
// GPU process.
// Didn't check for opengl errors here because we want to get better logs
// if it didn't compile.
GLint compiled = GL_FALSE;
glGetShaderiv(shader.id(), GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint info_log_len = 0;
glGetShaderiv(shader.id(), GL_INFO_LOG_LENGTH, &info_log_len);
std::string errors(info_log_len, 0);
glGetShaderInfoLog(shader.id(), info_log_len, nullptr, &errors[0]);
return absl::InternalError("Shader compilation failed: " + errors +
"\nProblem shader is:\n" + shader_source);
}
#endif // !__EMSCRIPTEN__
*gl_shader = std::move(shader);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,86 @@
/* 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_GL_SHADER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_SHADER_H_
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
// A wrapper around opengl shader id that needs to be recycled when not needed.
class GlShader {
public:
// Creates and compiles a shader.
//
// @param shader_type is one of GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, or
// GL_COMPUTE_SHADER.
static absl::Status CompileShader(GLenum shader_type,
const std::string& shader_source,
GlShader* gl_shader);
GlShader() : id_(0) {}
// move-only
GlShader(GlShader&& shader);
GlShader& operator=(GlShader&& shader);
GlShader(const GlShader&) = delete;
GlShader& operator=(const GlShader&) = delete;
~GlShader();
GLuint id() const { return id_; }
private:
explicit GlShader(GLuint id) : id_(id) {}
void Invalidate();
GLuint id_;
};
// Holds binary blob for compiled shader. It can be used to instantiate
// a program instead of plain Shader that will need to be compiled first.
//
// Some OpenGL implementations allow to extract binary representation once it
// is compiled. Call Program::GetBinary after program is successfully created
// with a shader from sources.
class BinaryShader {
public:
BinaryShader(GLenum format, std::vector<uint8_t> binary)
: format_(format), binary_(std::move(binary)) {}
GLenum format() const { return format_; }
const std::vector<uint8_t>& binary() const { return binary_; }
private:
GLenum format_;
std::vector<uint8_t> binary_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_SHADER_H_
+126
View File
@@ -0,0 +1,126 @@
/* 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/gl_sync.h"
#include <string>
#include <utility>
#ifdef __ARM_ACLE
#include <arm_acle.h>
#endif // __ARM_ACLE
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status GlSyncWait() {
GlSync sync;
RETURN_IF_ERROR(GlSync::NewSync(&sync));
// Flush sync and loop afterwards without it.
GLenum status = glClientWaitSync(sync.sync(), GL_SYNC_FLUSH_COMMANDS_BIT,
/* timeout ns = */ 0);
while (true) {
switch (status) {
case GL_TIMEOUT_EXPIRED:
break;
case GL_CONDITION_SATISFIED:
case GL_ALREADY_SIGNALED:
return absl::OkStatus();
case GL_WAIT_FAILED:
return GetOpenGlErrors();
}
status = glClientWaitSync(sync.sync(), 0, /* timeout ns = */ 10000000);
}
return absl::OkStatus();
}
absl::Status GlActiveSyncWait() {
GlSync sync;
RETURN_IF_ERROR(GlSync::NewSync(&sync));
// Since creating a Sync object is itself a GL command it *must* be flushed.
// Otherwise glGetSynciv may never succeed. Perform a flush with
// glClientWaitSync call.
GLenum status = glClientWaitSync(sync.sync(), GL_SYNC_FLUSH_COMMANDS_BIT,
/* timeout ns = */ 0);
switch (status) {
case GL_TIMEOUT_EXPIRED:
break;
case GL_CONDITION_SATISFIED:
case GL_ALREADY_SIGNALED:
return absl::OkStatus();
case GL_WAIT_FAILED:
return GetOpenGlErrors();
}
// Start active loop.
GLint result = GL_UNSIGNALED;
while (true) {
glGetSynciv(sync.sync(), GL_SYNC_STATUS, sizeof(GLint), nullptr, &result);
if (result == GL_SIGNALED) {
return absl::OkStatus();
}
#ifdef __ARM_ACLE
// Try to save CPU power by yielding CPU to another thread.
__yield();
#endif
}
}
absl::Status GlShaderSync::NewSync(GlShaderSync* gl_sync) {
GlShaderSync sync;
RETURN_IF_ERROR(CreatePersistentBuffer(sizeof(int), &sync.flag_buffer_));
static const std::string* kCode = new std::string(R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1) in;
layout(std430) buffer;
layout(binding = 0) buffer Output {
int elements[];
} output_data;
void main() {
output_data.elements[0] = 1;
})");
GlShader shader;
RETURN_IF_ERROR(GlShader::CompileShader(GL_COMPUTE_SHADER, *kCode, &shader));
RETURN_IF_ERROR(GlProgram::CreateWithShader(shader, &sync.flag_program_));
*gl_sync = std::move(sync);
return absl::OkStatus();
}
// How it works: GPU writes a buffer and CPU checks the buffer value to be
// changed. The buffer is accessible for writing by GPU and reading by CPU
// simultaneously - persistent buffer or buffer across shild context can be used
// for that.
absl::Status GlShaderSync::Wait() {
if (!flag_buffer_.is_valid()) {
return absl::UnavailableError("GlShaderSync is not initialized.");
}
RETURN_IF_ERROR(flag_buffer_.BindToIndex(0));
volatile int* flag_ptr_ = reinterpret_cast<int*>(flag_buffer_.data());
*flag_ptr_ = 0;
RETURN_IF_ERROR(flag_program_.Dispatch({1, 1, 1}));
// glFlush must be called to upload GPU task. Adreno won't start executing
// the task without glFlush.
glFlush();
// Wait for the value is being updated by the shader.
while (*flag_ptr_ != 1) {
}
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
+106
View File
@@ -0,0 +1,106 @@
/* 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_GL_SYNC_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_SYNC_H_
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_buffer.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_program.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
// RAII wrapper for OpenGL GLsync object.
// See https://www.khronos.org/opengl/wiki/Sync_Object for more information.
//
// GlSync is moveable but not copyable.
class GlSync {
public:
static absl::Status NewSync(GlSync* gl_sync) {
GLsync sync;
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glFenceSync, &sync,
GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
*gl_sync = GlSync(sync);
return absl::OkStatus();
}
// Creates invalid object.
GlSync() : GlSync(nullptr) {}
// Move-only
GlSync(GlSync&& sync) : sync_(sync.sync_) { sync.sync_ = nullptr; }
GlSync& operator=(GlSync&& sync) {
if (this != &sync) {
Invalidate();
std::swap(sync_, sync.sync_);
}
return *this;
}
GlSync(const GlSync&) = delete;
GlSync& operator=(const GlSync&) = delete;
~GlSync() { Invalidate(); }
const GLsync sync() const { return sync_; }
private:
explicit GlSync(GLsync sync) : sync_(sync) {}
void Invalidate() {
if (sync_) {
glDeleteSync(sync_);
sync_ = nullptr;
}
}
GLsync sync_;
};
// Waits until GPU is done with processing.
absl::Status GlSyncWait();
// Waits until all commands are flushed and then performs active waiting by
// spinning a thread and checking sync status. It leads to shorter wait time
// (up to tens of ms) but consumes more CPU.
absl::Status GlActiveSyncWait();
// CPU checks the value in the buffer that is going to be written by GPU. The
// persistent buffer is used for the simultaneous access to the buffer by GPU
// and CPU. The instance remains invalid if persistent buffer OpenGL extension
// is not supported by the device.
class GlShaderSync {
public:
static absl::Status NewSync(GlShaderSync* gl_sync);
GlShaderSync() = default;
absl::Status Wait();
private:
GlProgram flag_program_;
GlPersistentBuffer flag_buffer_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_SYNC_H_
@@ -0,0 +1,265 @@
/* 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/gl_texture.h"
#include <cstddef>
#include <cstdint>
#include "absl/types/span.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"
#include "tensorflow/lite/delegates/gpu/gl/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_errors.h"
#include "tensorflow/lite/delegates/gpu/gl/gl_texture_helper.h"
namespace tflite {
namespace gpu {
namespace gl {
GlTexture::GlTexture(GlTexture&& texture)
: GlTexture(texture.target_, texture.id_, texture.format_,
texture.bytes_size_, texture.layer_, texture.owned_) {
texture.owned_ = false;
}
GlTexture& GlTexture::operator=(GlTexture&& texture) {
if (this != &texture) {
Invalidate();
target_ = texture.target_;
format_ = texture.format_;
bytes_size_ = texture.bytes_size_;
layer_ = texture.layer_;
owned_ = texture.owned_;
id_ = texture.id_;
texture.owned_ = false;
}
return *this;
}
GlTexture::~GlTexture() {
Invalidate();
}
void GlTexture::Invalidate() {
if (owned_ && id_ != GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glDeleteTextures, 1, &id_).IgnoreError();
id_ = GL_INVALID_INDEX;
}
}
absl::Status GlTexture::BindImage(uint32_t index, GLenum access) const {
return TFLITE_GPU_CALL_GL(glBindImageTexture, index, id_, /* level = */ 0,
/* layered = */ GL_TRUE, layer_, access, format_);
}
absl::Status GlTexture::BindAsReadonlyImage(uint32_t index) const {
return BindImage(index, GL_READ_ONLY);
}
absl::Status GlTexture::BindAsWriteonlyImage(uint32_t index) const {
return BindImage(index, GL_WRITE_ONLY);
}
absl::Status GlTexture::BindAsReadWriteImage(uint32_t index) const {
return BindImage(index, GL_READ_WRITE);
}
absl::Status GlTexture::BindAsSampler2D(uint32_t index) const {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glActiveTexture, GL_TEXTURE0 + index));
return TFLITE_GPU_CALL_GL(glBindTexture, GL_TEXTURE_2D, id_);
}
namespace {
absl::Status SetTextureWrapAndFilter(GLenum target, GLenum texture_format) {
if (texture_format == GL_RGBA32F) {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_S, GL_REPEAT));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_T, GL_REPEAT));
if (target == GL_TEXTURE_2D_ARRAY || target == GL_TEXTURE_3D) {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_R, GL_REPEAT));
}
// Texture filtering is not available for GL_RGBA32F, hence explicitly
// specifying GL_NEAREST param for texture (Otherwise, we can end up
// sampling some incorrect values from texture.)
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_MAG_FILTER, GL_NEAREST));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_MIN_FILTER, GL_NEAREST));
} else if (texture_format == GL_RGBA16F) {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_S, GL_REPEAT));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_T, GL_REPEAT));
if (target == GL_TEXTURE_2D_ARRAY || target == GL_TEXTURE_3D) {
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_WRAP_R, GL_REPEAT));
}
// Texture filtering is available for GL_RGBA16F, specifying that
// explicitly improves quality for some operations like texture upscaling
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_MAG_FILTER, GL_LINEAR));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexParameteri, target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR));
}
return absl::OkStatus();
}
absl::Status CreateReadOnlyRgba2dImageTexture(DataType data_type,
const uint2& size,
const void* data,
size_t byte_size,
GlTexture* gl_texture) {
if (byte_size != /* RGBA=*/4 * SizeOf(data_type) * size.x * size.y) {
return absl::InvalidArgumentError(
"Creating image texture failed. Source data size is not matching "
"expected dimensions.");
}
const GLenum kTarget = GL_TEXTURE_2D;
const bool normalized = data_type == DataType::UINT8;
GLenum internal_format = ToTextureInternalFormat(data_type, normalized);
GLenum format = ToTextureFormat(data_type, normalized);
GLenum type = ToTextureDataType(data_type);
gl_texture_internal::TextureId id;
gl_texture_internal::TextureBinder binder(kTarget, id.id());
RETURN_IF_ERROR(SetTextureWrapAndFilter(kTarget, internal_format));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexStorage2D, kTarget,
/* num_levels = */ 1, internal_format,
size.x, size.y));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexSubImage2D, kTarget, /* level = */ 0,
0, 0, size.x, size.y, format, type, data));
*gl_texture = GlTexture(kTarget, id.Release(), internal_format, byte_size, 0,
/*owned=*/true);
return absl::OkStatus();
}
absl::Status CreateReadOnlyRgba3dImageTexture(DataType data_type,
const uint3& size,
const void* data,
size_t byte_size,
GlTexture* gl_texture) {
if (byte_size != /* RGBA=*/4 * SizeOf(data_type) * size.x * size.y * size.z) {
return absl::InvalidArgumentError(
"Creating image texture failed. Source data is larger than dimensions "
"product.");
}
const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
const bool normalized = data_type == DataType::UINT8;
GLenum internal_format = ToTextureInternalFormat(data_type, normalized);
GLenum format = ToTextureFormat(data_type, normalized);
GLenum type = ToTextureDataType(data_type);
gl_texture_internal::TextureId id;
gl_texture_internal::TextureBinder binder(kTarget, id.id());
RETURN_IF_ERROR(SetTextureWrapAndFilter(kTarget, internal_format));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexStorage3D, kTarget,
/* num_levels = */ 1, internal_format,
size.x, size.y, size.z));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexSubImage3D, kTarget, /* level = */ 0,
0, 0, 0, size.x, size.y, size.z, format,
type, data));
*gl_texture = GlTexture(kTarget, id.Release(), internal_format, byte_size, 0,
/*owned=*/true);
return absl::OkStatus();
}
} // namespace
absl::Status CreateReadOnlyImageTexture(const uint2& size,
absl::Span<const float> data,
GlTexture* gl_texture) {
return CreateReadOnlyRgba2dImageTexture(DataType::FLOAT32, size, data.data(),
data.size() * sizeof(float),
gl_texture);
}
absl::Status CreateReadOnlyImageTexture(const uint3& size,
absl::Span<const float> data,
GlTexture* gl_texture) {
return CreateReadOnlyRgba3dImageTexture(DataType::FLOAT32, size, data.data(),
data.size() * sizeof(float),
gl_texture);
}
absl::Status CreateReadOnlyImageTextureU8(const uint2& size,
absl::Span<const uint8_t> data,
GlTexture* gl_texture) {
return CreateReadOnlyRgba2dImageTexture(DataType::UINT8, size, data.data(),
data.size() * sizeof(uint8_t),
gl_texture);
}
absl::Status CreateReadOnlyImageTextureF16(const uint2& size,
absl::Span<const uint16_t> data,
GlTexture* gl_texture) {
return CreateReadOnlyRgba2dImageTexture(DataType::FLOAT16, size, data.data(),
data.size() * sizeof(uint16_t),
gl_texture);
}
absl::Status CreateReadOnlyImageTextureF16(const uint3& size,
absl::Span<const uint16_t> data,
GlTexture* gl_texture) {
return CreateReadOnlyRgba3dImageTexture(DataType::FLOAT16, size, data.data(),
data.size() * sizeof(uint16_t),
gl_texture);
}
absl::Status CreateReadWriteRgbaImageTexture(DataType data_type,
const uint2& size,
GlTexture* gl_texture) {
const GLenum kTarget = GL_TEXTURE_2D;
const bool normalized = data_type == DataType::UINT8;
const GLenum internal_format = ToTextureInternalFormat(data_type, normalized);
gl_texture_internal::TextureId id;
gl_texture_internal::TextureBinder binder(kTarget, id.id());
RETURN_IF_ERROR(SetTextureWrapAndFilter(kTarget, internal_format));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexStorage2D, kTarget,
/* num_levels = */ 1, internal_format,
size.x, size.y));
size_t byte_size = /* RGBA = */ 4 * SizeOf(data_type) * size.x * size.y;
*gl_texture = GlTexture(kTarget, id.Release(), internal_format, byte_size,
/* layer = */ 0,
/* owned = */ true);
return absl::OkStatus();
}
absl::Status CreateReadWriteRgbaImageTexture(DataType data_type,
const uint3& size,
GlTexture* gl_texture) {
const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
const bool normalized = data_type == DataType::UINT8;
GLenum internal_format = ToTextureInternalFormat(data_type, normalized);
gl_texture_internal::TextureId id;
gl_texture_internal::TextureBinder binder(kTarget, id.id());
RETURN_IF_ERROR(SetTextureWrapAndFilter(kTarget, internal_format));
RETURN_IF_ERROR(TFLITE_GPU_CALL_GL(glTexStorage3D, kTarget,
/* num_levels = */ 1, internal_format,
size.x, size.y, size.z));
size_t byte_size =
/* RGBA = */ 4 * SizeOf(data_type) * size.x * size.y * size.z;
*gl_texture = GlTexture(kTarget, id.Release(), internal_format, byte_size,
/* layer = */ 0,
/* owned = */ true);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,208 @@
/* 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_GL_TEXTURE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_TEXTURE_H_
#include <cstddef>
#include <cstdint>
#include <utility>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.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/gl_call.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
// Texture is an RAII wrapper for OpenGL texture object.
// See https://www.khronos.org/opengl/wiki/Texture for more information.
//
// Texture is moveable but not copyable.
class GlTexture {
public:
// Creates invalid texture.
GlTexture()
: GlTexture(GL_INVALID_ENUM, GL_INVALID_INDEX, GL_INVALID_ENUM, 0, 0,
false) {}
GlTexture(GLenum target, GLuint id, GLenum format, size_t bytes_size,
GLint layer, bool owned)
: id_(id),
target_(target),
format_(format),
bytes_size_(bytes_size),
layer_(layer),
owned_(owned) {}
// Move-only
GlTexture(GlTexture&& texture);
GlTexture& operator=(GlTexture&& texture);
GlTexture(const GlTexture&) = delete;
GlTexture& operator=(const GlTexture&) = delete;
~GlTexture();
// Binds a texture as an image to the given index.
absl::Status BindAsReadonlyImage(uint32_t index) const;
// Bind texture as an image for write access at given index.
absl::Status BindAsWriteonlyImage(uint32_t index) const;
// Bind texture as an image for read-write access at given index.
absl::Status BindAsReadWriteImage(uint32_t index) const;
// Binds a texture as a sampler to the given index.
absl::Status BindAsSampler2D(uint32_t index) const;
GLenum target() const { return target_; }
GLuint id() const { return id_; }
GLenum format() const { return format_; }
GLint layer() const { return layer_; }
bool is_valid() const { return id_ != GL_INVALID_INDEX; }
size_t bytes_size() const { return bytes_size_; }
// @return true if this object actually owns corresponding GL buffer
// and manages it's lifetime.
bool has_ownership() const { return owned_; }
private:
void Invalidate();
absl::Status BindImage(uint32_t index, GLenum access) const;
GLuint id_;
GLenum target_;
GLenum format_;
size_t bytes_size_;
GLint layer_;
bool owned_;
};
// Creates new 2D image texture that will be filled with float32 data once which
// will be used for reading.
//
// @param size defines 2D image texture size where each pixel is RGBA.
absl::Status CreateReadOnlyImageTexture(const uint2& size,
absl::Span<const float> data,
GlTexture* gl_texture);
// Creates new 2D image texture that will be filled with float16 data once which
// will be used for reading.
//
// @param size defines 2D image texture size where each pixel is RGBA.
absl::Status CreateReadOnlyImageTextureF16(const uint2& size,
absl::Span<const uint16_t> data,
GlTexture* gl_texture);
// Creates new 2D image texture that will be filled with uint8 data once which
// will be used for reading.
//
// @param size defines 2D image texture size where each pixel is RGBA.
absl::Status CreateReadOnlyImageTextureU8(const uint2& size,
absl::Span<const uint8_t> data,
GlTexture* gl_texture);
// Creates new 3D RGBA image texture that will be filled with float32 data once
// which will be used for reading.
//
// @param size defines 3D image texture size where each pixel is RGBA.
absl::Status CreateReadOnlyImageTexture(const uint3& size,
absl::Span<const float> data,
GlTexture* gl_texture);
// Creates new 3D RGBA image texture that will be filled with float16 data once
// which will be used for reading.
//
// @param size defines 3D image texture size where each pixel is RGBA.
absl::Status CreateReadOnlyImageTextureF16(const uint3& size,
absl::Span<const uint16_t> data,
GlTexture* gl_texture);
// Creates new RGBA 2D image texture
//
// @param size defines 2D image texture size where each pixel is RGBA.
absl::Status CreateReadWriteRgbaImageTexture(DataType data_type,
const uint2& size,
GlTexture* gl_texture);
// Creates new RGBA 3D image texture
//
// @param size defines 3D image texture size where each pixel is RGBA.
absl::Status CreateReadWriteRgbaImageTexture(DataType data_type,
const uint3& size,
GlTexture* gl_texture);
namespace gl_texture_internal {
// RAII for creating and/or owning texture id.
class TextureId {
public:
TextureId() : id_(GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glGenTextures, 1 /* number of textures*/, &id_)
.IgnoreError();
}
explicit TextureId(GLuint id) : id_(id) {}
~TextureId() {
if (id_ != GL_INVALID_INDEX) {
TFLITE_GPU_CALL_GL(glDeleteTextures, 1, &id_).IgnoreError();
}
}
GLuint id() const { return id_; }
GLuint Release() {
GLuint id = GL_INVALID_INDEX;
std::swap(id, id_);
return id;
}
private:
GLuint id_;
};
// RAII for binding and unbinding a texture.
class TextureBinder {
public:
TextureBinder(GLenum target, GLuint id) : target_(target) {
TFLITE_GPU_CALL_GL(glBindTexture, target_, id).IgnoreError();
}
~TextureBinder() {
TFLITE_GPU_CALL_GL(glBindTexture, target_, 0).IgnoreError();
}
private:
const GLenum target_;
};
} // namespace gl_texture_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_TEXTURE_H_
@@ -0,0 +1,97 @@
/* 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/gl_texture_helper.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
GLenum ToTextureFormat(DataType type, bool normalized) {
switch (type) {
case DataType::INT8:
case DataType::UINT8:
return normalized ? GL_RGBA : GL_RGBA_INTEGER;
case DataType::BOOL:
return GL_RGBA_INTEGER;
case DataType::UINT16:
case DataType::UINT32:
case DataType::INT16:
case DataType::INT32:
return GL_RGBA_INTEGER;
case DataType::FLOAT16:
case DataType::FLOAT32:
return GL_RGBA;
default:
return 0;
}
}
GLenum ToTextureInternalFormat(DataType type, bool normalized) {
switch (type) {
case DataType::UINT8:
return normalized ? GL_RGBA8 : GL_RGBA8UI;
case DataType::BOOL:
return GL_RGBA8UI;
case DataType::INT8:
return normalized ? GL_RGBA8_SNORM : GL_RGBA8I;
case DataType::UINT16:
return GL_RGBA16UI;
case DataType::UINT32:
return GL_RGBA32UI;
case DataType::INT16:
return GL_RGBA16I;
case DataType::INT32:
return GL_RGBA32I;
case DataType::FLOAT16:
return GL_RGBA16F;
case DataType::FLOAT32:
return GL_RGBA32F;
default:
return 0;
}
}
GLenum ToTextureDataType(DataType type) {
switch (type) {
case DataType::UINT8:
return GL_UNSIGNED_BYTE;
case DataType::BOOL:
return GL_UNSIGNED_BYTE;
case DataType::INT8:
return GL_BYTE;
case DataType::UINT16:
return GL_UNSIGNED_SHORT;
case DataType::UINT32:
return GL_UNSIGNED_INT;
case DataType::INT16:
return GL_SHORT;
case DataType::INT32:
return GL_INT;
case DataType::FLOAT16:
return GL_HALF_FLOAT;
case DataType::FLOAT32:
return GL_FLOAT;
default:
return 0;
}
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,42 @@
/* 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_GL_TEXTURE_HELPER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_TEXTURE_HELPER_H_
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/gl/portable_gl31.h"
namespace tflite {
namespace gpu {
namespace gl {
// From https://www.khronos.org/opengl/wiki/Normalized_Integer
// A Normalized Integer is an integer which is used to store a decimal floating
// point number. When formats use such an integer, OpenGL will automatically
// convert them to/from floating point values as needed. This allows normalized
// integers to be treated equivalently with floating-point values, acting as a
// form of compression.
GLenum ToTextureFormat(DataType type, bool normalized = false);
GLenum ToTextureInternalFormat(DataType type, bool normalized = false);
GLenum ToTextureDataType(DataType type);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_GL_TEXTURE_HELPER_H_
@@ -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

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