diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/BUILD b/tensorflow/lite/delegates/gpu/cl/kernels/BUILD index 88d837998c4..15fb046b8cd 100644 --- a/tensorflow/lite/delegates/gpu/cl/kernels/BUILD +++ b/tensorflow/lite/delegates/gpu/cl/kernels/BUILD @@ -111,6 +111,23 @@ cc_test( ], ) +cc_test( + name = "conv_weights_converter_test", + srcs = ["conv_weights_converter_test.cc"], + linkstatic = True, + tags = tf_gpu_tests_tags() + [ + "linux", + "local", + ], + deps = [ + ":cl_test", + "//tensorflow/lite/delegates/gpu/common:operations", + "//tensorflow/lite/delegates/gpu/common:status", + "//tensorflow/lite/delegates/gpu/common/tasks:conv_weights_converter_test_util", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "converter", srcs = ["converter.cc"], @@ -627,6 +644,7 @@ test_suite( "conv_buffer_1x1_test", "conv_constants_test", "conv_powervr_test", + "conv_weights_converter_test", "convolution_transposed_3x3_test", "convolution_transposed_3x3_thin_test", "convolution_transposed_4x4_test", diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/conv_weights_converter_test.cc b/tensorflow/lite/delegates/gpu/cl/kernels/conv_weights_converter_test.cc new file mode 100644 index 00000000000..34789b9a695 --- /dev/null +++ b/tensorflow/lite/delegates/gpu/cl/kernels/conv_weights_converter_test.cc @@ -0,0 +1,56 @@ +/* Copyright 2021 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include <vector> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "tensorflow/lite/delegates/gpu/cl/kernels/cl_test.h" +#include "tensorflow/lite/delegates/gpu/common/operations.h" +#include "tensorflow/lite/delegates/gpu/common/status.h" +#include "tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.h" + +namespace tflite { +namespace gpu { +namespace cl { + +TEST_F(OpenCLOperationTest, ConverterToConvWeights1x1OutX4) { + const auto status = ConverterToConvWeights1x1OutX4Test(&exec_env_); + ASSERT_TRUE(status.ok()) << status.error_message(); +} + +TEST_F(OpenCLOperationTest, ConverterToConvWeights1x1OutX4Unaligned) { + const auto status = ConverterToConvWeights1x1OutX4UnalignedTest(&exec_env_); + ASSERT_TRUE(status.ok()) << status.error_message(); +} + +TEST_F(OpenCLOperationTest, ConverterToConvWeights1x1OutX2) { + const auto status = ConverterToConvWeights1x1OutX2Test(&exec_env_); + ASSERT_TRUE(status.ok()) << status.error_message(); +} + +TEST_F(OpenCLOperationTest, ConverterToConvWeightsOutX2) { + const auto status = ConverterToConvWeightsOutX2Test(&exec_env_); + ASSERT_TRUE(status.ok()) << status.error_message(); +} + +TEST_F(OpenCLOperationTest, ConverterToConvTransposedWeights4x4) { + const auto status = ConverterToConvTransposedWeights4x4Test(&exec_env_); + ASSERT_TRUE(status.ok()) << status.error_message(); +} + +} // namespace cl +} // namespace gpu +} // namespace tflite diff --git a/tensorflow/lite/delegates/gpu/common/tasks/BUILD b/tensorflow/lite/delegates/gpu/common/tasks/BUILD index 862915f2461..c086937d557 100644 --- a/tensorflow/lite/delegates/gpu/common/tasks/BUILD +++ b/tensorflow/lite/delegates/gpu/common/tasks/BUILD @@ -150,6 +150,20 @@ cc_library( ], ) +cc_library( + name = "conv_weights_converter_test_util", + testonly = 1, + srcs = ["conv_weights_converter_test_util.cc"], + hdrs = ["conv_weights_converter_test_util.h"], + deps = [ + ":conv_weights_converter", + "//tensorflow/lite/delegates/gpu/common:operations", + "//tensorflow/lite/delegates/gpu/common:status", + "//tensorflow/lite/delegates/gpu/common/task:testing_util", + "//tensorflow/lite/delegates/gpu/common/task:weights_conversion", + ], +) + cc_library( name = "convolution_transposed", srcs = ["convolution_transposed.cc"], diff --git a/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.cc b/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.cc new file mode 100644 index 00000000000..790cabd6bc3 --- /dev/null +++ b/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.cc @@ -0,0 +1,242 @@ +/* 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/common/tasks/conv_weights_converter_test_util.h" + +#include <vector> + +#include "tensorflow/lite/delegates/gpu/common/operations.h" +#include "tensorflow/lite/delegates/gpu/common/status.h" +#include "tensorflow/lite/delegates/gpu/common/task/testing_util.h" +#include "tensorflow/lite/delegates/gpu/common/task/weights_conversion.h" +#include "tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter.h" + +namespace tflite { +namespace gpu { +namespace { +absl::Status ConvolutionWeightsConverterTest( + const Tensor<OHWI, DataType::FLOAT32>& weights, + const WeightsDescription& weight_desc, TestExecutionEnvironment* env, + const OperationDef& op_def) { + TensorFloat32 dst_tensor; + const int flt_count = + GetTotalElementsCountForLayout(weight_desc, weights.shape); + dst_tensor.shape = BHWC(1, 1, 1, flt_count); + dst_tensor.data.resize(flt_count); + RearrangeWeights( + weights, weight_desc, DataType::FLOAT32, + absl::MakeSpan(reinterpret_cast<uint8_t*>(dst_tensor.data.data()), + flt_count * 4)); + + // reinterpreting weights in OHWI as tensor in BHWC + TensorFloat32 src_tensor; + auto src_shape = + BHWC(weights.shape.o, weights.shape.h, weights.shape.w, weights.shape.i); + src_tensor.shape = src_shape; + src_tensor.data.resize(src_shape.DimensionsProduct(), 2.0); + for (int o = 0; o < weights.shape.o; ++o) { + for (int y = 0; y < weights.shape.h; ++y) { + for (int x = 0; x < weights.shape.w; ++x) { + for (int i = 0; i < weights.shape.i; ++i) { + const int f_index = weights.shape.LinearIndex({o, y, x, i}); + const int s_index = src_shape.LinearIndex({o, y, x, i}); + src_tensor.data[s_index] = weights.data[f_index]; + } + } + } + } + + TensorFloat32 dst_tensor_gpu; + auto converter = ConverterToConvWeights(op_def, weight_desc); + RETURN_IF_ERROR(env->ExecuteGPUOperation( + src_tensor, + absl::make_unique<ConverterToConvWeights>(std::move(converter)), + dst_tensor.shape, &dst_tensor_gpu)); + RETURN_IF_ERROR(PointWiseNear(dst_tensor.data, dst_tensor_gpu.data, 0.0f)); + return absl::OkStatus(); +} + +} // namespace + +absl::Status ConverterToConvWeights1x1OutX4Test(TestExecutionEnvironment* env) { + const int kSrcChannels = 8; + const int kDstChannels = 32; + auto weights_shape = OHWI(kDstChannels, 1, 1, kSrcChannels); + WeightsDescription conv_weight_desc; + conv_weight_desc.output_group_size = 4; + + Tensor<OHWI, DataType::FLOAT32> weights; + weights.shape = weights_shape; + weights.data.resize(weights_shape.DimensionsProduct()); + for (int i = 0; i < weights.data.size(); ++i) { + weights.data[i] = half(static_cast<float>(i)); + } + + for (auto storage : env->GetSupportedStorages()) { + for (auto precision : env->GetSupportedPrecisions()) { + for (auto weights_layout : + {WeightsLayout::kOHWIOGroupI4O4, WeightsLayout::kOHWIOGroupO4I4}) { + conv_weight_desc.layout = weights_layout; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage, Layout::BHWC}); + op_def.dst_tensors.push_back( + {data_type, TensorStorageType::BUFFER, Layout::UNKNOWN}); + RETURN_IF_ERROR(ConvolutionWeightsConverterTest( + weights, conv_weight_desc, env, op_def)); + } + } + } + return absl::OkStatus(); +} + +absl::Status ConverterToConvWeights1x1OutX4UnalignedTest( + TestExecutionEnvironment* env) { + const int kSrcChannels = 8; + const int kDstChannels = 17; + auto weights_shape = OHWI(kDstChannels, 1, 1, kSrcChannels); + WeightsDescription conv_weight_desc; + conv_weight_desc.output_group_size = 4; + + Tensor<OHWI, DataType::FLOAT32> weights; + weights.shape = weights_shape; + weights.data.resize(weights_shape.DimensionsProduct()); + for (int i = 0; i < weights.data.size(); ++i) { + weights.data[i] = half(static_cast<float>(i)); + } + + for (auto storage : env->GetSupportedStorages()) { + for (auto precision : env->GetSupportedPrecisions()) { + for (auto weights_layout : + {WeightsLayout::kOHWIOGroupI4O4, WeightsLayout::kOHWIOGroupO4I4}) { + conv_weight_desc.layout = weights_layout; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage, Layout::BHWC}); + op_def.dst_tensors.push_back( + {data_type, TensorStorageType::BUFFER, Layout::UNKNOWN}); + RETURN_IF_ERROR(ConvolutionWeightsConverterTest( + weights, conv_weight_desc, env, op_def)); + } + } + } + return absl::OkStatus(); +} + +absl::Status ConverterToConvWeights1x1OutX2Test(TestExecutionEnvironment* env) { + const int kSrcChannels = 7; + const int kDstChannels = 37; + auto weights_shape = OHWI(kDstChannels, 1, 1, kSrcChannels); + WeightsDescription conv_weight_desc; + conv_weight_desc.output_group_size = 2; + + Tensor<OHWI, DataType::FLOAT32> weights; + weights.shape = weights_shape; + weights.data.resize(weights_shape.DimensionsProduct()); + for (int i = 0; i < weights.data.size(); ++i) { + weights.data[i] = half(static_cast<float>(i)); + } + + for (auto storage : env->GetSupportedStorages()) { + for (auto precision : env->GetSupportedPrecisions()) { + for (auto weights_layout : + {WeightsLayout::kOHWIOGroupI4O4, WeightsLayout::kOHWIOGroupO4I4}) { + conv_weight_desc.layout = weights_layout; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage, Layout::BHWC}); + op_def.dst_tensors.push_back( + {data_type, TensorStorageType::BUFFER, Layout::UNKNOWN}); + RETURN_IF_ERROR(ConvolutionWeightsConverterTest( + weights, conv_weight_desc, env, op_def)); + } + } + } + return absl::OkStatus(); +} + +absl::Status ConverterToConvWeightsOutX2Test(TestExecutionEnvironment* env) { + const int kSrcChannels = 8; + const int kDstChannels = 38; + auto weights_shape = OHWI(kDstChannels, 3, 4, kSrcChannels); + WeightsDescription conv_weight_desc; + conv_weight_desc.output_group_size = 2; + + Tensor<OHWI, DataType::FLOAT32> weights; + weights.shape = weights_shape; + weights.data.resize(weights_shape.DimensionsProduct()); + for (int i = 0; i < weights.data.size(); ++i) { + weights.data[i] = half(static_cast<float>(i)); + } + + for (auto storage : env->GetSupportedStorages()) { + for (auto precision : env->GetSupportedPrecisions()) { + for (auto weights_layout : + {WeightsLayout::kOHWIOGroupI4O4, WeightsLayout::kOHWIOGroupO4I4}) { + conv_weight_desc.layout = weights_layout; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage, Layout::BHWC}); + op_def.dst_tensors.push_back( + {data_type, TensorStorageType::BUFFER, Layout::UNKNOWN}); + RETURN_IF_ERROR(ConvolutionWeightsConverterTest( + weights, conv_weight_desc, env, op_def)); + } + } + } + return absl::OkStatus(); +} + +absl::Status ConverterToConvTransposedWeights4x4Test( + TestExecutionEnvironment* env) { + const int kSrcChannels = 7; + const int kDstChannels = 11; + auto weights_shape = OHWI(kDstChannels, 4, 4, kSrcChannels); + WeightsDescription weight_desc; + weight_desc.spatial_remap = {10, 11, 14, 15, 8, 9, 12, 13, + 2, 3, 6, 7, 0, 1, 4, 5}; + + Tensor<OHWI, DataType::FLOAT32> weights; + weights.shape = weights_shape; + weights.data.resize(weights_shape.DimensionsProduct()); + for (int i = 0; i < weights.data.size(); ++i) { + weights.data[i] = half(static_cast<float>(i)); + } + + for (auto storage : env->GetSupportedStorages()) { + for (auto precision : env->GetSupportedPrecisions()) { + for (auto weights_layout : {WeightsLayout::kOICustomSpatialI4O4, + WeightsLayout::kOICustomSpatialO4I4}) { + weight_desc.layout = weights_layout; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage, Layout::BHWC}); + op_def.dst_tensors.push_back( + {data_type, TensorStorageType::BUFFER, Layout::UNKNOWN}); + RETURN_IF_ERROR( + ConvolutionWeightsConverterTest(weights, weight_desc, env, op_def)); + } + } + } + return absl::OkStatus(); +} + +} // namespace gpu +} // namespace tflite diff --git a/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.h b/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.h new file mode 100644 index 00000000000..1afa80e9b7b --- /dev/null +++ b/tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.h @@ -0,0 +1,36 @@ +/* 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_COMMON_TASKS_CONV_WEIGHTS_CONVERTER_TEST_UTIL_H_ +#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_CONV_WEIGHTS_CONVERTER_TEST_UTIL_H_ + +#include "tensorflow/lite/delegates/gpu/common/status.h" +#include "tensorflow/lite/delegates/gpu/common/task/testing_util.h" + +namespace tflite { +namespace gpu { + +absl::Status ConverterToConvWeights1x1OutX4Test(TestExecutionEnvironment* env); +absl::Status ConverterToConvWeights1x1OutX4UnalignedTest( + TestExecutionEnvironment* env); +absl::Status ConverterToConvWeights1x1OutX2Test(TestExecutionEnvironment* env); +absl::Status ConverterToConvWeightsOutX2Test(TestExecutionEnvironment* env); +absl::Status ConverterToConvTransposedWeights4x4Test( + TestExecutionEnvironment* env); + +} // namespace gpu +} // namespace tflite + +#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_CONV_WEIGHTS_CONVERTER_TEST_UTIL_H_ diff --git a/tensorflow/lite/delegates/gpu/metal/kernels/BUILD b/tensorflow/lite/delegates/gpu/metal/kernels/BUILD index 2e9524289bc..7360dedebe4 100644 --- a/tensorflow/lite/delegates/gpu/metal/kernels/BUILD +++ b/tensorflow/lite/delegates/gpu/metal/kernels/BUILD @@ -112,6 +112,29 @@ ios_unit_test( deps = [":conv_test_lib"], ) +objc_library( + name = "conv_weights_converter_test_lib", + testonly = 1, + srcs = ["conv_weights_converter_test.mm"], + sdk_frameworks = ["XCTest"], + deps = [ + ":test_util", + "//tensorflow/lite/delegates/gpu/common/tasks:conv_weights_converter_test_util", + ], +) + +ios_unit_test( + name = "conv_weights_converter_test", + testonly = 1, + minimum_os_version = "11.0", + runner = tflite_ios_lab_runner("IOS_LATEST"), + tags = tf_gpu_tests_tags() + [ + "notap", + "tflite_not_portable_android", + ], + deps = [":conv_weights_converter_test_lib"], +) + objc_library( name = "depthwise_conv_test_lib", testonly = 1, @@ -666,6 +689,7 @@ objc_library( "add_test.mm", "concat_test.mm", "conv_test.mm", + "conv_weights_converter_test.mm", "depthwise_conv_test.mm", "elementwise_test.mm", "fully_connected_test.mm", @@ -702,6 +726,7 @@ objc_library( "//tensorflow/lite/delegates/gpu/common:util", "//tensorflow/lite/delegates/gpu/common/tasks:add_test_util", "//tensorflow/lite/delegates/gpu/common/tasks:concat_test_util", + "//tensorflow/lite/delegates/gpu/common/tasks:conv_weights_converter_test_util", "//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_4x4_test_util", "//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_test_util", "//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_3x3_stride_h2_test_util", diff --git a/tensorflow/lite/delegates/gpu/metal/kernels/conv_weights_converter_test.mm b/tensorflow/lite/delegates/gpu/metal/kernels/conv_weights_converter_test.mm new file mode 100644 index 00000000000..2abcfde99c0 --- /dev/null +++ b/tensorflow/lite/delegates/gpu/metal/kernels/conv_weights_converter_test.mm @@ -0,0 +1,54 @@ +/* Copyright 2021 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#import <XCTest/XCTest.h> + +#include "tensorflow/lite/delegates/gpu/common/status.h" +#include "tensorflow/lite/delegates/gpu/common/tasks/conv_weights_converter_test_util.h" +#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h" + +@interface ConvWeightsConverterMetalTest : XCTestCase +@end + +@implementation ConvWeightsConverterMetalTest { + tflite::gpu::metal::MetalExecutionEnvironment exec_env_; +} + +- (void)testConverterToConvWeights1x1OutX4 { + const auto status = ConverterToConvWeights1x1OutX4Test(&exec_env_); + XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str()); +} + +- (void)testConverterToConvWeights1x1OutX4Unaligned { + const auto status = ConverterToConvWeights1x1OutX4UnalignedTest(&exec_env_); + XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str()); +} + +- (void)testConverterToConvWeights1x1OutX2 { + const auto status = ConverterToConvWeights1x1OutX2Test(&exec_env_); + XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str()); +} + +- (void)testConverterToConvWeightsOutX2 { + const auto status = ConverterToConvWeightsOutX2Test(&exec_env_); + XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str()); +} + +- (void)testConverterToConvTransposedWeights4x4 { + const auto status = ConverterToConvTransposedWeights4x4Test(&exec_env_); + XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str()); +} + +@end