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
+302
View File
@@ -0,0 +1,302 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test")
load("@flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load("//tensorflow:tensorflow.bzl", "workspace_root")
load(
"//tensorflow/core/platform:build_config_root.bzl",
"tf_gpu_tests_tags",
)
load("//tensorflow/lite:build_def.bzl", "CXX17_BAZEL_ONLY_COPTS")
load(
"//tensorflow/lite:special_rules.bzl",
"tflite_ios_lab_runner",
"tflite_ios_per_kernel_test",
"tflite_portable_test_suite",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
DEFAULT_COPTS = [
"-Wno-shorten-64-to-32",
] + CXX17_BAZEL_ONLY_COPTS
objc_library(
name = "buffer",
srcs = ["buffer.mm"],
hdrs = ["buffer.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":gpu_object",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/task:buffer_desc",
"@com_google_absl//absl/types:span",
],
)
objc_library(
name = "buffer_test_lib",
testonly = 1,
srcs = ["buffer_test.mm"],
sdk_frameworks = [
"Metal",
"XCTest",
],
deps = [
":buffer",
"//tensorflow/lite/delegates/gpu/common:types",
],
)
objc_library(
name = "buffer_convert",
srcs = ["buffer_convert.mm"],
hdrs = ["buffer_convert.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":common",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:util",
],
)
objc_library(
name = "common",
srcs = ["common.mm"],
hdrs = ["common.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/strings",
],
)
objc_library(
name = "common_test_lib",
testonly = 1,
srcs = ["common_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [":common"],
)
ios_unit_test(
name = "common_test",
testonly = 1,
minimum_os_version = "12.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":common_test_lib"],
)
objc_library(
name = "compute_task",
srcs = ["compute_task.mm"],
hdrs = ["compute_task.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":common",
":metal_arguments",
":metal_device",
":metal_spatial_tensor",
"//tensorflow/lite/delegates/gpu/common:kernel_info",
"//tensorflow/lite/delegates/gpu/common:precision",
"//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/common/task:gpu_operation",
"//tensorflow/lite/delegates/gpu/common/task:tuning_type",
"@com_google_absl//absl/strings",
],
)
objc_library(
name = "gpu_object",
hdrs = ["gpu_object.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
"//tensorflow/lite/delegates/gpu/common:access_type",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/task:gpu_object_desc",
],
)
objc_library(
name = "inference_context",
srcs = ["inference_context.mm"],
hdrs = ["inference_context.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":compute_task",
":inference_context_cc_fbs",
":metal_device",
":metal_spatial_tensor",
"//tensorflow/lite/delegates/gpu/common:gpu_model",
"//tensorflow/lite/delegates/gpu/common:gpu_model_cc_fbs",
"//tensorflow/lite/delegates/gpu/common:memory_management",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_hints",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:precision",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/common/selectors:operation_selector",
"//tensorflow/lite/delegates/gpu/common/selectors:special_selector",
"//tensorflow/lite/delegates/gpu/common/selectors:subgraph",
"//tensorflow/lite/delegates/gpu/common/task:profiling_info",
"//tensorflow/lite/delegates/gpu/common/task:serialization_base",
"//tensorflow/lite/delegates/gpu/common/task:tuning_type",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
],
)
flatbuffer_cc_library(
name = "inference_context_cc_fbs",
srcs = ["inference_context.fbs"],
flatc_args = [
"--scoped-enums",
"-I " + workspace_root,
],
includes = [
"//tensorflow/lite/delegates/gpu/common:gpu_model_cc_fbs_includes",
"//tensorflow/lite/delegates/gpu/common/task:serialization_base_cc_fbs_includes",
],
)
objc_library(
name = "metal_arguments",
srcs = ["metal_arguments.mm"],
hdrs = ["metal_arguments.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":buffer",
":gpu_object",
":metal_device",
":metal_spatial_tensor",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/common/task:arguments",
"//tensorflow/lite/delegates/gpu/common/task:gpu_object_desc",
"//tensorflow/lite/delegates/gpu/common/task:util",
"@com_google_absl//absl/strings",
],
)
objc_library(
name = "metal_device",
srcs = ["metal_device.mm"],
hdrs = ["metal_device.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = ["//tensorflow/lite/delegates/gpu/common:gpu_info"],
)
objc_library(
name = "metal_spatial_tensor",
srcs = ["metal_spatial_tensor.mm"],
hdrs = ["metal_spatial_tensor.h"],
copts = DEFAULT_COPTS,
sdk_frameworks = ["Metal"],
deps = [
":common",
":gpu_object",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/common/task:buffer_desc",
"//tensorflow/lite/delegates/gpu/common/task:gpu_tensor",
"//tensorflow/lite/delegates/gpu/common/task:tensor_desc",
],
)
objc_library(
name = "metal_spatial_tensor_test_lib",
testonly = 1,
srcs = ["metal_spatial_tensor_test.mm"],
sdk_frameworks = [
"Metal",
"XCTest",
],
deps = [
":metal_spatial_tensor",
"//tensorflow/lite/delegates/gpu/common:types",
],
)
objc_library(
name = "TestBinary",
testonly = 1,
srcs = ["host.m"],
sdk_frameworks = ["UIKit"],
)
# Application for local testing in XCode
ios_application(
name = "TestApplication",
testonly = 1,
bundle_id = "com.tensorflow.lite.delegates.gpu.metal.ComponentsTests",
families = [
"iphone",
],
infoplists = ["Info.plist"],
minimum_os_version = "12.0",
provisioning_profile = "//tensorflow/lite/delegates/gpu/metal:provisioning_profile.mobileprovision",
tags = tf_gpu_tests_tags() + [
"local",
"notap",
],
deps = [":TestBinary"],
)
objc_library(
name = "common_tests_lib",
testonly = 1,
srcs = [
"//tensorflow/lite/delegates/gpu/metal:buffer_test.mm",
"//tensorflow/lite/delegates/gpu/metal:common_test.mm",
"//tensorflow/lite/delegates/gpu/metal:metal_spatial_tensor_test.mm",
],
hdrs = [
],
sdk_frameworks = ["XCTest"],
deps = [
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/metal:buffer",
"//tensorflow/lite/delegates/gpu/metal:common",
"//tensorflow/lite/delegates/gpu/metal:metal_spatial_tensor",
],
)
ios_unit_test(
name = "ComponentsTests",
families = ["iphone"],
minimum_os_version = "12.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + ["notap"],
test_host = ":TestApplication",
deps = [
":common_tests_lib",
"//tensorflow/lite/delegates/gpu/metal/kernels:kernel_tests_lib",
],
)
tflite_portable_test_suite()
tflite_ios_per_kernel_test()
@@ -0,0 +1,14 @@
{
"sourceFilters" : [
"tensorflow/lite/delegates/gpu/...",
],
"buildTargets" : [
"//tensorflow/lite/delegates/gpu/metal:ComponentsTests",
],
"projectName" : "ComponentsTests",
"additionalFilePaths" : [
"tensorflow/lite/delegates/gpu/BUILD",
"tensorflow/lite/delegates/gpu/metal/BUILD",
"tensorflow/lite/delegates/gpu/metal/kernels/BUILD",
]
}
@@ -0,0 +1,14 @@
{
"configDefaults" : {
"optionSet" : {
"ProjectGenerationPlatformConfiguration" : {
"p" : "ios_arm64"
}
}
},
"packages" : [
"tensorflow/lite/delegates/gpu/metal"
],
"projectName" : "ComponentsTests",
"workspaceRoot" : "../../../../../.."
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,56 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
objc_library(
name = "benchmark_lib",
srcs = ["main.mm"],
data = glob([
"models/*.tflite",
]),
sdk_frameworks = [
"Metal",
"QuartzCore",
],
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/core/kernels:builtin_ops",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_builder",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:precision",
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:util",
"//tensorflow/lite/delegates/gpu/common/transformations:model_transformations",
"//tensorflow/lite/delegates/gpu/metal:inference_context",
"//tensorflow/lite/kernels:kernel_util",
],
)
# Application for local benchmarking in XCode
ios_application(
name = "iOSBenchmark",
testonly = 1,
bundle_id = "com.tensorflow.lite.delegates.gpu.metal.benchmarking",
families = [
"iphone",
"ipad",
],
infoplists = ["Info.plist"],
minimum_os_version = "15.0",
provisioning_profile = "//tensorflow/lite/delegates/gpu/metal/benchmarking:provisioning_profile.mobileprovision",
tags = [
"local",
"notap",
],
deps = [":benchmark_lib"],
)
@@ -0,0 +1,16 @@
{
"sourceFilters" : [
"tensorflow/lite/delegates/gpu/...",
"tensorflow/lite/delegates/gpu/metal/benchmarking/models",
],
"buildTargets" : [
"//tensorflow/lite/delegates/gpu/metal/benchmarking:iOSBenchmark",
],
"projectName" : "Benchmark",
"additionalFilePaths" : [
"tensorflow/lite/delegates/gpu/BUILD",
"tensorflow/lite/delegates/gpu/metal/BUILD",
"tensorflow/lite/delegates/gpu/metal/kernels/BUILD",
"tensorflow/lite/delegates/gpu/metal/benchmarking/BUILD",
]
}
@@ -0,0 +1,7 @@
{
"packages" : [
"tensorflow/lite/delegates/gpu/metal"
],
"projectName" : "Benchmark",
"workspaceRoot" : "../../../../../../.."
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,470 @@
/* 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 <Metal/Metal.h>
#include <iostream>
#include <string>
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/core/model_builder.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_builder.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/precision.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/transformations/model_transformations.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/inference_context.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace gpu {
namespace metal {
namespace {
void FillInputTensor(tflite::Interpreter* interpreter) {
for (int k = 0; k < interpreter->inputs().size(); ++k) {
TfLiteTensor* tensor_ptr = interpreter->tensor(interpreter->inputs()[k]);
const auto tensor_elements_count = tflite::NumElements(tensor_ptr);
if (tensor_ptr->type == kTfLiteFloat32) {
float* p = interpreter->typed_input_tensor<float>(k);
for (int i = 0; i < tensor_elements_count; ++i) {
p[i] = std::sin(i);
}
}
if (tensor_ptr->type == kTfLiteInt32) {
int* p = interpreter->typed_input_tensor<int>(k);
for (int i = 0; i < tensor_elements_count; ++i) {
p[i] = i % 32;
}
}
if (tensor_ptr->type == kTfLiteInt8) {
int8_t* p = interpreter->typed_input_tensor<int8_t>(k);
for (int i = 0; i < tensor_elements_count; ++i) {
p[i] = i % 256 - 128;
}
}
if (tensor_ptr->type == kTfLiteUInt8) {
uint8_t* p = interpreter->typed_input_tensor<uint8_t>(k);
for (int i = 0; i < tensor_elements_count; ++i) {
p[i] = i % 256;
}
}
}
}
absl::Status CompareCPUGPUResults(tflite::Interpreter* cpu, const std::vector<Value*>& outputs,
InferenceContext* gpu_context, float per_element_eps) {
for (int i = 0; i < outputs.size(); ++i) {
TfLiteTensor* tensor_ptr = cpu->tensor(outputs[i]->tensor.ref);
const auto tensor_elements_count = tflite::NumElements(tensor_ptr);
std::cout << "Output " << tensor_ptr->name << ":" << std::endl;
tflite::gpu::TensorFloat32 gpu_tensor;
RETURN_IF_ERROR(gpu_context->GetOutputTensor(outputs[i]->id, &gpu_tensor));
const int kMaxPrint = 10;
int printed = 0;
int total_different = 0;
for (int k = 0; k < tensor_elements_count; ++k) {
float cpu_val = 0.0f;
float gpu_val = 0.0f;
if (tensor_ptr->type == kTfLiteFloat32) {
const float* cpu_out = tensor_ptr->data.f;
const float* gpu_out = gpu_tensor.data.data();
cpu_val = cpu_out[k];
gpu_val = gpu_out[k];
}
const float abs_diff = fabs(cpu_val - gpu_val);
if (abs_diff > per_element_eps) {
total_different++;
if (printed < kMaxPrint) {
std::cout << "Element #" << k << ": CPU value - " << cpu_val << ", GPU value - "
<< gpu_val << ", abs diff - " << abs_diff << std::endl;
printed++;
}
if (printed == kMaxPrint) {
std::cout << "Printed " << kMaxPrint << " different elements, threshold - "
<< per_element_eps << ", next different elements skipped" << std::endl;
printed++;
}
}
}
std::cout << "Total " << total_different << " different elements, for output #" << i
<< ", threshold - " << per_element_eps << std::endl;
}
return absl::OkStatus();
}
absl::Status TestCorrectnessVsTfliteCPU(const std::unique_ptr<FlatBufferModel>& flatbuffer,
GraphFloat32* graph, bool use_fp16 = true,
float per_element_eps = 1e-4f) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
std::string device_name = std::string([[device name] UTF8String]);
GpuInfo gpu_info;
GetGpuInfoFromDeviceDescription(device_name, GpuApi::kMetal, &gpu_info);
CalculationsPrecision precision;
if (use_fp16) {
if (gpu_info.IsRoundToNearestSupported()) {
precision = CalculationsPrecision::F16;
} else {
precision = CalculationsPrecision::F32_F16;
}
} else {
precision = CalculationsPrecision::F32;
}
CreateGpuModelInfo create_info;
create_info.precision = precision;
create_info.storage_type = GetFastestStorageType(gpu_info);
create_info.hints.Add(ModelHints::kAllowSpecialKernels);
InferenceContext inference_context;
RETURN_IF_ERROR(inference_context.InitFromGraphWithTransforms(create_info, graph, device));
ops::builtin::BuiltinOpResolver op_resolver;
tflite::InterpreterBuilder builder(*flatbuffer, op_resolver);
// CPU.
std::unique_ptr<tflite::Interpreter> cpu_inference;
builder(&cpu_inference);
if (!cpu_inference) {
return absl::InternalError("Failed to build CPU inference.");
}
auto status = cpu_inference->AllocateTensors();
if (status != kTfLiteOk) {
return absl::InternalError("Failed to AllocateTensors for CPU inference.");
}
FillInputTensor(cpu_inference.get());
status = cpu_inference->Invoke();
if (status != kTfLiteOk) {
return absl::InternalError("Failed to Invoke CPU inference.");
}
for (auto& input : graph->inputs()) {
TensorFloat32 src_tensor;
src_tensor.id = input->id;
src_tensor.shape = input->tensor.shape;
src_tensor.data.resize(src_tensor.shape.DimensionsProduct());
for (int j = 0; j < src_tensor.data.size(); ++j) {
src_tensor.data[j] = std::sin(j);
}
RETURN_IF_ERROR(inference_context.SetInputTensor(input->id, src_tensor));
}
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoder];
inference_context.EncodeWithEncoder(encoder);
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
RETURN_IF_ERROR(CompareCPUGPUResults(cpu_inference.get(), graph->outputs(), &inference_context,
per_element_eps));
return absl::OkStatus();
}
absl::Status GPUBenchmark(GraphFloat32* graph, int num_tests, int iterations,
bool use_fp16 = true) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
std::string device_name = std::string([[device name] UTF8String]);
GpuInfo gpu_info;
GetGpuInfoFromDeviceDescription(device_name, GpuApi::kMetal, &gpu_info);
CalculationsPrecision precision;
if (use_fp16) {
if (gpu_info.IsRoundToNearestSupported()) {
precision = CalculationsPrecision::F16;
} else {
precision = CalculationsPrecision::F32_F16;
}
} else {
precision = CalculationsPrecision::F32;
}
CreateGpuModelInfo create_info;
create_info.precision = precision;
create_info.storage_type = GetFastestStorageType(gpu_info);
create_info.hints.Add(ModelHints::kAllowSpecialKernels);
InferenceContext inference_context;
RETURN_IF_ERROR(inference_context.InitFromGraphWithTransforms(create_info, graph, device));
id<MTLCommandQueue> command_queue = [device newCommandQueue];
bool kPerOpProfiling = false;
if (kPerOpProfiling) {
ProfilingInfo profiling_info;
inference_context.Profile(device, &profiling_info);
std::cout << profiling_info.GetDetailedReport() << std::endl;
}
uint64_t runtime_mem_bytes = inference_context.GetIntermediateTensorsSize();
std::cout << "Memory for intermediate tensors - " << runtime_mem_bytes / 1024.0 / 1024.0 << " MB"
<< std::endl;
const uint64_t const_mem_bytes = inference_context.GetConstantTensorsSize();
std::cout << "Memory for constant tensors - " << const_mem_bytes / 1024.0 / 1024.0 << " MB"
<< std::endl;
std::cout << "Total tensors memory(const + intermediate) - "
<< (const_mem_bytes + runtime_mem_bytes) / 1024.0 / 1024.0 << " MB" << std::endl;
const std::string precision_str = use_fp16 ? "FP16" : "FP32";
std::cout << "Measuring started: (" << num_tests << " tests, " << iterations
<< " iterations every test, " << precision_str << " precision)" << std::endl;
for (int j = 0; j < num_tests; ++j) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
@autoreleasepool {
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoder];
inference_context.EncodeWithEncoder(encoder);
[encoder endEncoding];
[command_buffer commit];
if (i == iterations - 1) {
[command_buffer waitUntilCompleted];
}
}
}
auto end = std::chrono::high_resolution_clock::now();
double t0 = double(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()) /
iterations;
std::cout << " Test: #" << j << " - " << t0 << "ms" << std::endl;
}
return absl::OkStatus();
}
absl::Status GPUBenchmarkSerialized(GraphFloat32* graph, bool use_fp16 = true) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
std::string device_name = std::string([[device name] UTF8String]);
GpuInfo gpu_info;
GetGpuInfoFromDeviceDescription(device_name, GpuApi::kMetal, &gpu_info);
CalculationsPrecision precision;
if (use_fp16) {
if (gpu_info.IsRoundToNearestSupported()) {
precision = CalculationsPrecision::F16;
} else {
precision = CalculationsPrecision::F32_F16;
}
} else {
precision = CalculationsPrecision::F32;
}
CreateGpuModelInfo create_info;
create_info.precision = precision;
create_info.storage_type = GetFastestStorageType(gpu_info);
create_info.hints.Add(ModelHints::kAllowSpecialKernels);
InferenceContext inference_context;
std::vector<uint8_t> serialized_model;
RETURN_IF_ERROR(
inference_context.InitFromGraphWithTransforms(create_info, graph, device, &serialized_model));
{ // calculating time without building serialized model
InferenceContext test_context;
const auto start = std::chrono::high_resolution_clock::now();
RETURN_IF_ERROR(test_context.InitFromGraphWithTransforms(create_info, graph, device));
const auto end = std::chrono::high_resolution_clock::now();
const double total_time_ms = (end - start).count() * 1e-6f;
std::cout << "Inference context initialization total time - " << total_time_ms << "ms"
<< std::endl;
}
std::vector<TensorFloat32> src_tensors(graph->inputs().size());
for (int i = 0; i < graph->inputs().size(); ++i) {
src_tensors[i].id = graph->inputs()[i]->id;
src_tensors[i].shape = graph->inputs()[i]->tensor.shape;
src_tensors[i].data.resize(src_tensors[i].shape.DimensionsProduct());
for (int j = 0; j < src_tensors[i].data.size(); ++j) {
src_tensors[i].data[j] = std::sin(j);
}
}
for (int i = 0; i < graph->inputs().size(); ++i) {
RETURN_IF_ERROR(inference_context.SetInputTensor(graph->inputs()[i]->id, src_tensors[i]));
}
@autoreleasepool {
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoder];
inference_context.EncodeWithEncoder(encoder);
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
std::vector<TensorFloat32> dst_tensors(graph->outputs().size());
for (int i = 0; i < graph->outputs().size(); ++i) {
RETURN_IF_ERROR(inference_context.GetOutputTensor(graph->outputs()[i]->id, &dst_tensors[i]));
}
InferenceContext serialized_context;
{
const auto start = std::chrono::high_resolution_clock::now();
RETURN_IF_ERROR(serialized_context.RestoreDeserialized(serialized_model, device));
const auto end = std::chrono::high_resolution_clock::now();
const double total_time_ms = (end - start).count() * 1e-6f;
std::cout << "Serialized inference context initialization total time - " << total_time_ms
<< "ms" << std::endl;
}
for (int i = 0; i < graph->inputs().size(); ++i) {
RETURN_IF_ERROR(serialized_context.SetInputTensor(graph->inputs()[i]->id, src_tensors[i]));
}
@autoreleasepool {
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoder];
serialized_context.EncodeWithEncoder(encoder);
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
std::vector<TensorFloat32> dst_tensors_v2(graph->outputs().size());
for (int i = 0; i < graph->outputs().size(); ++i) {
RETURN_IF_ERROR(
serialized_context.GetOutputTensor(graph->outputs()[i]->id, &dst_tensors_v2[i]));
}
for (int i = 0; i < graph->outputs().size(); ++i) {
if (dst_tensors[i].data.size() != dst_tensors_v2[i].data.size()) {
std::cout << "Different sizes for " << i << " output tensor" << std::endl;
break;
}
for (int j = 0; j < dst_tensors[i].data.size(); ++j) {
if (dst_tensors[i].data[j] != dst_tensors_v2[i].data[j]) {
std::cout << "Different elements for " << j << " element in " << i
<< " tensor: " << dst_tensors[i].data[j] << " - " << dst_tensors_v2[i].data[j]
<< std::endl;
break;
}
}
}
return absl::OkStatus();
}
class DelegateContext {
public:
bool Init(TfLiteContext* context, const TfLiteDelegateParams* delegate_params) {
auto denormalized_graph = reinterpret_cast<GraphFloat32*>(delegate_params->delegate->data_);
absl::Status status = BuildModel(context, delegate_params, denormalized_graph);
if (!status.ok()) {
TF_LITE_KERNEL_LOG(context, std::string(status.message()).c_str());
}
return status.ok();
}
};
TfLiteStatus DelegatePrepare(TfLiteContext* context, TfLiteDelegate* delegate) {
const TfLiteRegistration kRegistration = {
.init = [](TfLiteContext* context, const char* buffer, size_t) -> void* {
auto* delegate_context = new DelegateContext();
if (!delegate_context->Init(context,
reinterpret_cast<const TfLiteDelegateParams*>(buffer))) {
delete delegate_context;
return nullptr;
}
return delegate_context;
},
.free = [](TfLiteContext* context, void* buffer) -> void {
delete reinterpret_cast<DelegateContext*>(buffer);
},
.prepare = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
return node->user_data ? kTfLiteOk : kTfLiteError;
},
.invoke = nullptr,
};
TfLiteIntArray* ops_to_replace = GetOpsToReplace(context);
const auto status = context->ReplaceNodeSubsetsWithDelegateKernels(context, kRegistration,
ops_to_replace, delegate);
TfLiteIntArrayFree(ops_to_replace);
return status;
}
absl::Status FlatBufferToGPUGraph(const std::unique_ptr<tflite::FlatBufferModel>& flatbuffer,
GraphFloat32* graph) {
ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder interpreter_builder(*flatbuffer, op_resolver);
if (interpreter_builder(&interpreter) != kTfLiteOk || !interpreter) {
return absl::InternalError("Unable to prepare TfLite interpreter.");
}
TfLiteDelegate delegate;
delegate.data_ = graph;
delegate.flags = kTfLiteDelegateFlagsNone;
delegate.Prepare = DelegatePrepare;
delegate.CopyFromBufferHandle = nullptr;
delegate.CopyToBufferHandle = nullptr;
delegate.FreeBufferHandle = nullptr;
if (interpreter->ModifyGraphWithDelegate(&delegate) != kTfLiteOk) {
return absl::InternalError("Conversion from TfLite model failed.");
}
ModelTransformer transformer(graph);
if (!ApplyModelTransformations(&transformer)) {
return absl::InternalError("Graph transformations failed");
}
return absl::OkStatus();
}
} // namespace
} // namespace metal
} // namespace gpu
} // namespace tflite
int main(int argc, char** argv) {
@autoreleasepool {
NSBundle* main = [NSBundle mainBundle];
NSArray<NSString*>* model_paths = [main pathsForResourcesOfType:@"tflite" inDirectory:nil];
for (id model_path in model_paths) {
NSString* model_name = [[model_path lastPathComponent] stringByDeletingPathExtension];
std::string m_name = std::string([model_name UTF8String]);
std::string path = std::string([model_path UTF8String]);
std::cout << m_name << std::endl;
auto flatbuffer = tflite::FlatBufferModel::BuildFromFile(path.c_str());
if (!flatbuffer) {
std::cout << "Failed flatbuffer reading." << std::endl;
}
tflite::gpu::GraphFloat32 graph;
auto s = tflite::gpu::metal::FlatBufferToGPUGraph(flatbuffer, &graph);
if (!s.ok()) {
std::cout << "Failed flatbuffer to graph conversion. " << s.message() << std::endl;
}
s = tflite::gpu::metal::GPUBenchmark(&graph, 5, 200, /*use_fp16*/ true);
if (!s.ok()) {
std::cout << "Error in GPUBenchmark. " << s.message() << std::endl;
}
s = tflite::gpu::metal::TestCorrectnessVsTfliteCPU(flatbuffer, &graph, /*use_fp16*/ true);
if (!s.ok()) {
std::cout << "Error in GPUBenchmark. " << s.message() << std::endl;
}
s = tflite::gpu::metal::GPUBenchmarkSerialized(&graph, true);
if (!s.ok()) {
std::cout << "Error in GPUBenchmark. " << s.message() << std::endl;
}
}
}
return 0;
}
@@ -0,0 +1 @@
.tflite models must be added to this folder before xcodeproj generation
@@ -0,0 +1,121 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_BUFFER_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_BUFFER_H_
#include <string>
#include <vector>
#import <Metal/Metal.h>
#include "absl/types/span.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/buffer_desc.h"
#include "tensorflow/lite/delegates/gpu/metal/gpu_object.h"
namespace tflite {
namespace gpu {
namespace metal {
class Buffer : public GPUObject {
public:
Buffer() = default;
Buffer(id<MTLBuffer> buffer, size_t size_in_bytes);
explicit Buffer(id<MTLBuffer> buffer);
// Move only
Buffer(Buffer&& buffer);
Buffer& operator=(Buffer&& buffer);
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
~Buffer();
// for profiling and memory statistics
uint64_t GetMemorySizeInBytes() const { return size_; }
id<MTLBuffer> GetMemoryPtr() const { return buffer_; }
// Writes data to a buffer. Data should point to a region that
// has exact size in bytes as size_in_bytes(constructor parameter).
template <typename T>
absl::Status WriteData(absl::Span<T> data);
template <typename T>
absl::Status WriteData(id<MTLCommandQueue> command_queue, absl::Span<T> data,
bool wait_for_completion);
// Reads data from Buffer into CPU memory.
template <typename T>
absl::Status ReadData(std::vector<T>* result) const;
absl::Status GetGPUResources(const GPUObjectDescriptor* obj_ptr,
GPUResourcesWithValue* resources) const override;
absl::Status CreateFromBufferDescriptor(const BufferDescriptor& desc, id<MTLDevice> device);
private:
void Release();
id<MTLBuffer> buffer_ = nullptr;
size_t size_;
bool owner_ = true;
};
Buffer CreateBufferShared(id<MTLBuffer> buffer);
absl::Status CreateBuffer(size_t size_in_bytes, const void* data, id<MTLDevice> device,
Buffer* result);
template <typename T>
absl::Status Buffer::WriteData(const absl::Span<T> data) {
if (sizeof(T) * data.size() > size_) {
return absl::InvalidArgumentError(
"absl::Span<T> data size is greater from buffer allocated size.");
}
std::memcpy([buffer_ contents], data.data(), size_);
return absl::OkStatus();
}
template <typename T>
absl::Status Buffer::WriteData(id<MTLCommandQueue> command_queue, absl::Span<T> data,
bool wait_for_completion) {
if (sizeof(T) * data.size() > size_) {
return absl::InvalidArgumentError(
"absl::Span<T> data size is greater from buffer allocated size.");
}
WriteDataToBuffer(buffer_, /*buffer_offset=*/0, command_queue, data.data(), size_,
wait_for_completion);
return absl::OkStatus();
}
template <typename T>
absl::Status Buffer::ReadData(std::vector<T>* result) const {
if (size_ % sizeof(T) != 0) {
return absl::UnknownError("Wrong element size(typename T is not correct?");
}
const int elements_count = size_ / sizeof(T);
result->resize(elements_count);
std::memcpy(result->data(), [buffer_ contents], size_);
return absl::OkStatus();
}
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_BUFFER_H_
@@ -0,0 +1,102 @@
/* 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/metal/buffer.h"
#include <utility>
namespace tflite {
namespace gpu {
namespace metal {
Buffer::Buffer(id<MTLBuffer> buffer, size_t size_in_bytes)
: buffer_(buffer), size_(size_in_bytes) {}
Buffer::Buffer(id<MTLBuffer> buffer)
: buffer_(buffer), size_(0), owner_(false) {}
Buffer::Buffer(Buffer&& buffer)
: buffer_(buffer.buffer_), size_(buffer.size_), owner_(buffer.owner_) {
buffer.buffer_ = nullptr;
buffer.size_ = 0;
}
Buffer& Buffer::operator=(Buffer&& buffer) {
if (this != &buffer) {
Release();
std::swap(size_, buffer.size_);
std::swap(buffer_, buffer.buffer_);
std::swap(owner_, buffer.owner_);
}
return *this;
}
Buffer::~Buffer() { Release(); }
void Buffer::Release() {
if (owner_ && buffer_) {
buffer_ = nullptr;
size_ = 0;
owner_ = false;
}
}
absl::Status Buffer::GetGPUResources(const GPUObjectDescriptor* obj_ptr,
GPUResourcesWithValue* resources) const {
const auto* buffer_desc = dynamic_cast<const BufferDescriptor*>(obj_ptr);
if (!buffer_desc) {
return absl::InvalidArgumentError("Expected BufferDescriptor on input.");
}
resources->buffers.push_back({"buffer", {buffer_, 0}});
return absl::OkStatus();
}
absl::Status Buffer::CreateFromBufferDescriptor(const BufferDescriptor& desc,
id<MTLDevice> device) {
size_ = desc.size;
if (desc.data.empty()) {
buffer_ =
[device newBufferWithLength:size_ options:MTLResourceStorageModeShared];
} else {
buffer_ = [device newBufferWithBytes:desc.data.data()
length:size_
options:MTLResourceStorageModeShared];
}
return absl::OkStatus();
}
Buffer CreateBufferShared(id<MTLBuffer> buffer) { return Buffer(buffer); }
absl::Status CreateBuffer(size_t size_in_bytes, const void* data,
id<MTLDevice> device, Buffer* result) {
id<MTLBuffer> buffer;
if (data) {
buffer = [device newBufferWithBytes:data
length:size_in_bytes
options:MTLResourceStorageModeShared];
} else {
buffer = [device newBufferWithLength:size_in_bytes
options:MTLResourceStorageModeShared];
}
*result = Buffer(buffer, size_in_bytes);
return absl::OkStatus();
}
} // namespace metal
} // 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_METAL_BUFFER_CONVERT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_BUFFER_CONVERT_H_
#import <Metal/Metal.h>
#include "tensorflow/lite/delegates/gpu/common/shape.h"
@interface TFLBufferConvert : NSObject
/// Constructs converter from/to BHWC <-> BPHWC4
/// @param isFloat16 the BPHWC4 buffer is in float16 format.
/// @param convertToPBHWC4 convert BHWC -> BPHWC4 if true or BPHWC4 -> BHWC instead.
- (id)initWithDevice:(id<MTLDevice>)device
isFloat16:(bool)isFloat16
convertToPBHWC4:(bool)convertToPBHWC4;
/// Converts from/to BHWC <-> BPHWC4
/// @param shape shape of BHWC tensor.
- (void)convertWithEncoder:(id<MTLComputeCommandEncoder>)encoder
shape:(const ::tflite::gpu::BHWC&)shape
sourceBuffer:(id<MTLBuffer>)sourceBuffer
convertedBuffer:(id<MTLBuffer>)convertedBuffer;
@end
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_BUFFER_CONVERT_H_
@@ -0,0 +1,123 @@
/* 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.
==============================================================================*/
#import "tensorflow/lite/delegates/gpu/metal/buffer_convert.h"
#import <Metal/Metal.h>
#include <string>
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/common.h"
using ::tflite::gpu::BHWC;
using ::tflite::gpu::DivideRoundUp;
using ::tflite::gpu::metal::CreateComputeProgram;
@implementation TFLBufferConvert {
id<MTLComputePipelineState> _program;
}
- (id)initWithDevice:(id<MTLDevice>)device
isFloat16:(bool)isFloat16
convertToPBHWC4:(bool)convertToPBHWC4 {
if (self = [super init]) {
std::string shaderSource;
if (convertToPBHWC4) {
shaderSource = R"(
#include <metal_stdlib>
using namespace metal;
kernel void ComputeFunction(device float* const input_buffer [[buffer(0)]],
device FLT4* output_buffer [[buffer(1)]],
constant int4& size [[buffer(2)]],
uint3 gid[[thread_position_in_grid]]) {
int linear_id = static_cast<int>(gid.x);
int X = linear_id / size.w;
int B = linear_id % size.w;
int Y = static_cast<int>(gid.y);
int S = static_cast<int>(gid.z);
if (X >= size.x || Y >= size.y) {
return;
}
FLT4 value = FLT4(0.0);
for (int i = 0; i < 4; i++) {
int channel = S * 4 + i;
if (channel >= size.z) break;
const int bhwc_index = ((B * size.y + Y) * size.x + X) * size.z + channel;
value[i] = input_buffer[bhwc_index];
}
const int shwbc4_index = ((S * size.y + Y) * size.x + X) * size.w + B;
output_buffer[shwbc4_index] = value;
}
)";
} else {
shaderSource = R"(
#include <metal_stdlib>
using namespace metal;
kernel void ComputeFunction(device FLT4* const input_buffer [[buffer(0)]],
device float* output_buffer [[buffer(1)]],
constant int4& size [[buffer(2)]],
uint3 gid[[thread_position_in_grid]]) {
int linear_id = static_cast<int>(gid.x);
int X = linear_id / size.w;
int B = linear_id % size.w;
int Y = static_cast<int>(gid.y);
int S = static_cast<int>(gid.z);
if (X >= size.x || Y >= size.y) {
return;
}
const int shwbc4_index = ((S * size.y + Y) * size.x + X) * size.w + B;
FLT4 value = input_buffer[shwbc4_index];
for (int i = 0; i < 4; i++) {
int channel = S * 4 + i;
if (channel >= size.z) break;
const int bhwc_index = ((B * size.y + Y) * size.x + X) * size.z + channel;
output_buffer[bhwc_index] = value[i];
}
}
)";
}
const std::map<std::string, std::string> macros = {{"FLT4", isFloat16 ? "half4" : "float4"}};
id<MTLComputePipelineState> program;
if (CreateComputeProgram(device, shaderSource, "ComputeFunction", macros, &program).ok()) {
_program = program;
return self;
}
}
return nil;
}
- (void)convertWithEncoder:(id<MTLComputeCommandEncoder>)encoder
shape:(const BHWC&)shape
sourceBuffer:(id<MTLBuffer>)sourceBuffer
convertedBuffer:(id<MTLBuffer>)convertedBuffer {
[encoder setComputePipelineState:_program];
[encoder setBuffer:sourceBuffer offset:0 atIndex:0];
[encoder setBuffer:convertedBuffer offset:0 atIndex:1];
std::vector<int> uniforms = {shape.w, shape.h, shape.c, shape.b};
[encoder setBytes:uniforms.data() length:uniforms.size() * sizeof(int) atIndex:2];
MTLSize group_size = MTLSizeMake(16, 8, 1);
int slices = DivideRoundUp(shape.c, 4);
int groups_x = DivideRoundUp(shape.w * shape.b, group_size.width);
int groups_y = DivideRoundUp(shape.h, group_size.height);
int groups_z = DivideRoundUp(slices, group_size.depth);
MTLSize groups_count = MTLSizeMake(groups_x, groups_y, groups_z);
[encoder dispatchThreadgroups:groups_count threadsPerThreadgroup:group_size];
}
@end
@@ -0,0 +1,70 @@
/* 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/metal/buffer.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#import <XCTest/XCTest.h>
#import <Metal/Metal.h>
#include <vector>
#include <iostream>
@interface BufferTest : XCTestCase
@end
@implementation BufferTest
- (void)setUp {
[super setUp];
}
using tflite::gpu::half;
- (void)testBufferF32 {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
const std::vector<float> data = {1.0f, 2.0f, 3.0f, -4.0f, 5.1f};
tflite::gpu::metal::Buffer buffer;
XCTAssertTrue(tflite::gpu::metal::CreateBuffer(sizeof(float) * 5, nullptr, device, &buffer).ok());
XCTAssertTrue(buffer.WriteData(absl::MakeConstSpan(data.data(), data.size())).ok());
std::vector<float> gpu_data;
XCTAssertTrue(buffer.ReadData<float>(&gpu_data).ok());
XCTAssertEqual(gpu_data.size(), data.size());
for (int i = 0; i < gpu_data.size(); ++i) {
XCTAssertEqual(gpu_data[i], data[i]);
}
}
- (void)testBufferF16 {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
const std::vector<half> data = {half(1.0f), half(2.0f), half(3.0f), half(-4.0f), half(5.1f)};
tflite::gpu::metal::Buffer buffer;
XCTAssertTrue(tflite::gpu::metal::CreateBuffer(
sizeof(tflite::gpu::half) * 5, nullptr, device, &buffer).ok());
XCTAssertTrue(buffer.WriteData(absl::MakeConstSpan(data.data(), data.size())).ok());
std::vector<half> gpu_data;
XCTAssertTrue(buffer.ReadData<half>(&gpu_data).ok());
XCTAssertEqual(gpu_data.size(), data.size());
for (int i = 0; i < gpu_data.size(); ++i) {
XCTAssertEqual(gpu_data[i], data[i]);
}
}
@end
@@ -0,0 +1,76 @@
/* 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_METAL_COMMON_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_COMMON_H_
#import <Metal/Metal.h>
#include <map>
#include <utility>
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace metal {
/// Returns system default device on iOS or Intel GPU on macOS.
id<MTLDevice> GetBestSupportedMetalDevice();
absl::Status CreateComputeProgram(
id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLComputePipelineState>* program);
absl::Status CreateComputeProgramWithArgumentBuffer(
id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLComputePipelineState>* program,
id<MTLArgumentEncoder>* arguments_encoder);
// ICB - indirect command buffer
absl::Status CreateComputeProgramWithICBSupport(
id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLComputePipelineState>* program,
id<MTLArgumentEncoder>* arguments_encoder);
absl::Status CreateFunction(id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLFunction>* function);
int PixelFormatToSizeInBytes(MTLPixelFormat pixel_format);
MTLPixelFormat DataTypeToRGBAPixelFormat(DataType type, bool normalized = false);
void WriteDataToTexture2D(id<MTLTexture> texture, id<MTLDevice> device, const void* data);
void ReadDataFromTexture2D(id<MTLTexture> texture, id<MTLDevice> device, void* data);
void WriteDataToTexture3D(id<MTLTexture> texture, id<MTLDevice> device, const void* data);
void ReadDataFromTexture3D(id<MTLTexture> texture, id<MTLDevice> device, void* data);
void WriteDataToTexture2DArray(id<MTLTexture> texture, id<MTLDevice> device, const void* data);
void ReadDataFromTexture2DArray(id<MTLTexture> texture, id<MTLDevice> device, void* data);
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_COMMON_H_
@@ -0,0 +1,401 @@
/* 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/metal/common.h"
#include "absl/strings/match.h"
#import <Metal/Metal.h>
#include <Availability.h>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
// Compile-time message: print define name and value.
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
namespace tflite {
namespace gpu {
namespace metal {
id<MTLDevice> GetBestSupportedMetalDevice() { return MTLCreateSystemDefaultDevice(); }
absl::Status CreateComputeProgram(id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLComputePipelineState>* program) {
id<MTLFunction> function;
RETURN_IF_ERROR(CreateFunction(device, code, function_name, macros, &function));
NSError* error = nil;
*program = [device newComputePipelineStateWithFunction:function error:&error];
if (!*program) {
NSString* errorString =
[NSString stringWithFormat:@"newComputePipelineStateWithFunction error: %@",
[error localizedDescription]];
return absl::InternalError([errorString UTF8String]);
}
return absl::OkStatus();
}
absl::Status CreateComputeProgramWithArgumentBuffer(
id<MTLDevice> device, const std::string& code, const std::string& function_name,
const std::map<std::string, std::string>& macros, id<MTLComputePipelineState>* program,
id<MTLArgumentEncoder>* arguments_encoder) {
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
id<MTLFunction> function;
RETURN_IF_ERROR(CreateFunction(device, code, "ComputeFunction", macros, &function));
*arguments_encoder = [function newArgumentEncoderWithBufferIndex:0];
if (!*arguments_encoder) {
return absl::InternalError("Failed to get MTLArgumentEncoder.");
}
MTLComputePipelineDescriptor* pipeline_desc = [[MTLComputePipelineDescriptor alloc] init];
pipeline_desc.computeFunction = function;
NSError* error = nil;
*program = [device newComputePipelineStateWithDescriptor:pipeline_desc
options:MTLPipelineOptionNone
reflection:nullptr
error:&error];
if (!*program) {
NSString* error_string =
[NSString stringWithFormat:@"newComputePipelineStateWithDescriptor: %@",
[error localizedDescription]];
return absl::InternalError([error_string UTF8String]);
}
return absl::OkStatus();
} else {
return absl::InternalError("Metal argument buffers available since ios 11, tvos 11 or macos "
"10.13.");
}
}
absl::Status CreateComputeProgramWithICBSupport(id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLComputePipelineState>* program,
id<MTLArgumentEncoder>* arguments_encoder) {
if (@available(macOS 11.00, iOS 13.0, tvOS 13.0, *)) {
id<MTLFunction> function;
RETURN_IF_ERROR(CreateFunction(device, code, "ComputeFunction", macros, &function));
*arguments_encoder = [function newArgumentEncoderWithBufferIndex:0];
if (!*arguments_encoder) {
return absl::InternalError("Failed to get MTLArgumentEncoder.");
}
MTLComputePipelineDescriptor* pipeline_desc = [[MTLComputePipelineDescriptor alloc] init];
pipeline_desc.computeFunction = function;
pipeline_desc.supportIndirectCommandBuffers = TRUE;
NSError* error = nil;
*program = [device newComputePipelineStateWithDescriptor:pipeline_desc
options:MTLPipelineOptionNone
reflection:nullptr
error:&error];
if (!*program) {
NSString* error_string =
[NSString stringWithFormat:@"newComputePipelineStateWithDescriptor: %@",
[error localizedDescription]];
return absl::InternalError([error_string UTF8String]);
}
return absl::OkStatus();
} else {
return absl::InternalError("Indirect compute command buffer available since ios 13, tvos 13 "
"or macos 11.00");
}
}
absl::Status CreateFunction(id<MTLDevice> device, const std::string& code,
const std::string& function_name,
const std::map<std::string, std::string>& macros,
id<MTLFunction>* function) {
MTLCompileOptions* options = [[MTLCompileOptions alloc] init];
// Runtime checks for the iOS version independently of minimum target iOS.
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
[options setLanguageVersion:MTLLanguageVersion2_3];
} else if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
[options setLanguageVersion:MTLLanguageVersion2_2];
} else if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) {
[options setLanguageVersion:MTLLanguageVersion2_1];
} else if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
[options setLanguageVersion:MTLLanguageVersion2_0];
} else if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) {
[options setLanguageVersion:MTLLanguageVersion1_2];
} else if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
[options setLanguageVersion:MTLLanguageVersion1_1];
}
#if (defined(__MAC_10_11) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_11) || \
(defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0) || \
(defined(__TVOS_9_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_9_0)
// Minimum target OS version is able to support Metal.
#else
#pragma message(VAR_NAME_VALUE(__MAC_OS_X_VERSION_MIN_REQUIRED))
#pragma message(VAR_NAME_VALUE(__IPHONE_OS_VERSION_MIN_REQUIRED))
#pragma message(VAR_NAME_VALUE(__TV_OS_VERSION_MIN_REQUIRED))
// NOLINTBEGIN
#error \
"The Metal delegate is not supported on current target SDK. Minimum supported os: iOS/tvOS 9.0, macOS 10.11"
// NOLINTEND
#endif
NSMutableDictionary<NSString*, NSString*>* macros_dict = [NSMutableDictionary dictionary];
for (const auto& pair : macros) {
std::string key = pair.first;
std::string value = pair.second;
if (absl::StrContains(key, ' ')) {
key = "\"" + key + "\"";
}
if (absl::StrContains(value, ' ')) {
value = "\"" + value + "\"";
}
[macros_dict setObject:[NSString stringWithCString:value.c_str()
encoding:[NSString defaultCStringEncoding]]
forKey:[NSString stringWithCString:key.c_str()
encoding:[NSString defaultCStringEncoding]]];
}
[options setFastMathEnabled:YES];
[options setPreprocessorMacros:macros_dict];
NSError* error = nil;
NSString* code_ns = [NSString stringWithCString:code.c_str()
encoding:[NSString defaultCStringEncoding]];
id<MTLLibrary> library = [device newLibraryWithSource:code_ns options:options error:&error];
if (!library) {
NSString* errorString =
[NSString stringWithFormat:@"newLibraryWithSource: %@", [error localizedDescription]];
return absl::InternalError([errorString UTF8String]);
}
NSString* function_name_ns = [NSString stringWithCString:function_name.c_str()
encoding:[NSString defaultCStringEncoding]];
*function = [library newFunctionWithName:function_name_ns];
if (!*function) {
NSString* errorString =
[NSString stringWithFormat:@"newFunctionWithName: %@", [error localizedDescription]];
return absl::InternalError([errorString UTF8String]);
}
return absl::OkStatus();
}
int PixelFormatToSizeInBytes(MTLPixelFormat pixel_format) {
if (pixel_format == MTLPixelFormatRGBA32Uint ||
pixel_format == MTLPixelFormatRGBA32Sint ||
pixel_format == MTLPixelFormatRGBA32Float) {
return 16;
} else if (pixel_format == MTLPixelFormatRGBA16Unorm ||
pixel_format == MTLPixelFormatRGBA16Snorm ||
pixel_format == MTLPixelFormatRGBA16Uint ||
pixel_format == MTLPixelFormatRGBA16Sint ||
pixel_format == MTLPixelFormatRGBA16Float) {
return 8;
} else if (pixel_format == MTLPixelFormatRGBA8Unorm ||
pixel_format == MTLPixelFormatRGBA8Snorm ||
pixel_format == MTLPixelFormatRGBA8Uint ||
pixel_format == MTLPixelFormatRGBA8Sint) {
return 4;
}
return -1;
}
MTLPixelFormat DataTypeToRGBAPixelFormat(DataType type, bool normalized) {
switch (type) {
case DataType::FLOAT32:
return MTLPixelFormatRGBA32Float;
case DataType::FLOAT16:
return MTLPixelFormatRGBA16Float;
case DataType::INT8:
return normalized ? MTLPixelFormatRGBA8Snorm : MTLPixelFormatRGBA8Sint;
case DataType::UINT8:
return normalized ? MTLPixelFormatRGBA8Unorm : MTLPixelFormatRGBA8Uint;
case DataType::INT16:
return normalized ? MTLPixelFormatRGBA16Snorm : MTLPixelFormatRGBA16Sint;
case DataType::UINT16:
return normalized ? MTLPixelFormatRGBA16Unorm : MTLPixelFormatRGBA16Uint;
case DataType::INT32:
return MTLPixelFormatRGBA32Sint;
case DataType::UINT32:
return MTLPixelFormatRGBA32Uint;
case DataType::BOOL:
return MTLPixelFormatRGBA8Uint;
default:
return MTLPixelFormatInvalid;
}
}
void WriteDataToTexture2D(id<MTLTexture> texture, id<MTLDevice> device, const void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
id<MTLBuffer> temp_buffer = [device newBufferWithBytes:data
length:pixel_size * texture.width * texture.height
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromBuffer:temp_buffer
sourceOffset:0
sourceBytesPerRow:pixel_size * texture.width
sourceBytesPerImage:pixel_size * texture.width * texture.height
sourceSize:MTLSizeMake(texture.width, texture.height, 1)
toTexture:texture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitCommandEncoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
void ReadDataFromTexture2D(id<MTLTexture> texture, id<MTLDevice> device, void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
const int buffer_size = pixel_size * texture.width * texture.height;
id<MTLBuffer> temp_buffer = [device newBufferWithLength:buffer_size
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromTexture:texture
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(texture.width, texture.height, 1)
toBuffer:temp_buffer
destinationOffset:0
destinationBytesPerRow:pixel_size * texture.width
destinationBytesPerImage:pixel_size * texture.width * texture.height];
[blitCommandEncoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
std::memcpy(data, [temp_buffer contents], buffer_size);
}
void WriteDataToTexture3D(id<MTLTexture> texture, id<MTLDevice> device, const void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
id<MTLBuffer> temp_buffer =
[device newBufferWithBytes:data
length:pixel_size * texture.width * texture.height * texture.depth
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromBuffer:temp_buffer
sourceOffset:0
sourceBytesPerRow:pixel_size * texture.width
sourceBytesPerImage:pixel_size * texture.width * texture.height
sourceSize:MTLSizeMake(texture.width, texture.height, texture.depth)
toTexture:texture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitCommandEncoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
void ReadDataFromTexture3D(id<MTLTexture> texture, id<MTLDevice> device, void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
const int buffer_size = pixel_size * texture.width * texture.height * texture.depth;
id<MTLBuffer> temp_buffer = [device newBufferWithLength:buffer_size
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromTexture:texture
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(texture.width, texture.height, texture.depth)
toBuffer:temp_buffer
destinationOffset:0
destinationBytesPerRow:pixel_size * texture.width
destinationBytesPerImage:pixel_size * texture.width * texture.height];
[blitCommandEncoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
std::memcpy(data, [temp_buffer contents], buffer_size);
}
void WriteDataToTexture2DArray(id<MTLTexture> texture, id<MTLDevice> device, const void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
id<MTLBuffer> temp_buffer =
[device newBufferWithBytes:data
length:pixel_size * texture.width * texture.height * texture.arrayLength
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
for (int i = 0; i < texture.arrayLength; ++i) {
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromBuffer:temp_buffer
sourceOffset:pixel_size * texture.width * texture.height * i
sourceBytesPerRow:pixel_size * texture.width
sourceBytesPerImage:pixel_size * texture.width * texture.height
sourceSize:MTLSizeMake(texture.width, texture.height, 1)
toTexture:texture
destinationSlice:i
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitCommandEncoder endEncoding];
}
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
void ReadDataFromTexture2DArray(id<MTLTexture> texture, id<MTLDevice> device, void* data) {
const int pixel_size = PixelFormatToSizeInBytes(texture.pixelFormat);
const int buffer_size = pixel_size * texture.width * texture.height * texture.arrayLength;
id<MTLBuffer> temp_buffer = [device newBufferWithLength:buffer_size
options:MTLResourceStorageModeShared];
id<MTLCommandQueue> command_queue = [device newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
for (int i = 0; i < texture.arrayLength; ++i) {
id<MTLBlitCommandEncoder> blitCommandEncoder = [command_buffer blitCommandEncoder];
[blitCommandEncoder copyFromTexture:texture
sourceSlice:i
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(texture.width, texture.height, 1)
toBuffer:temp_buffer
destinationOffset:pixel_size * texture.width * texture.height * i
destinationBytesPerRow:pixel_size * texture.width
destinationBytesPerImage:pixel_size * texture.width * texture.height];
[blitCommandEncoder endEncoding];
}
[command_buffer commit];
[command_buffer waitUntilCompleted];
std::memcpy(data, [temp_buffer contents], buffer_size);
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,70 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/metal/common.h"
#import <XCTest/XCTest.h>
#include <string>
#include <tuple>
#include <vector>
using ::tflite::gpu::metal::GetBestSupportedMetalDevice;
using ::tflite::gpu::metal::CreateComputeProgram;
@interface CommonTest : XCTestCase
@end
@implementation CommonTest
- (void)testComputeShaderCompilation {
const std::string code = R"(\
#include <metal_stdlib>
using namespace metal;
kernel void FunctionName(device TYPE* const src_buffer[[buffer(0)]],
device TYPE* const dst_buffer[[buffer(1)]],
constant int2& size[[buffer(2)]],
uint3 gid[[thread_position_in_grid]]) {
if (int(gid.x) >= size.x || int(gid.y) >= size.y) {
return;
}
const int linear_index = (gid.z * size.y + gid.y) * size.x + gid.x;
dst_buffer[linear_index] = src_buffer[linear_index];
}
)";
id<MTLDevice> device = GetBestSupportedMetalDevice();
XCTAssertNotNil(device, @"The Metal device must exists on real device");
id<MTLComputePipelineState> program;
absl::Status status;
status = CreateComputeProgram(device, code, "FunctionName", {{"TYPE", "float4"}}, &program);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
XCTAssertNotNil(program);
status = CreateComputeProgram(device, code, "FunctionName", {{"TYPE", "half4"}}, &program);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
XCTAssertNotNil(program);
// This compilation is intended to be incorrect
program = nil;
status = CreateComputeProgram(device, code, "FunctionName", {{"TYPE", "some_undefined_value"}},
&program);
XCTAssertFalse(status.ok(), @"Shader contains an error that has not been detected");
XCTAssertNil(program);
}
@end
@@ -0,0 +1,110 @@
/* 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_METAL_COMPUTE_TASK_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_COMPUTE_TASK_H_
#import <Metal/Metal.h>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/precision.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
#include "tensorflow/lite/delegates/gpu/common/task/tuning_type.h"
#include "tensorflow/lite/delegates/gpu/metal/common.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_arguments.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_device.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
namespace tflite {
namespace gpu {
namespace metal {
class ComputeTask {
public:
ComputeTask() = default;
~ComputeTask();
// Move only
ComputeTask(ComputeTask&& task);
ComputeTask& operator=(ComputeTask&& task);
ComputeTask(const ComputeTask&) = delete;
ComputeTask& operator=(const ComputeTask&) = delete;
void Init(std::unique_ptr<GPUOperation>&& operation);
const GPUOperation& GetGpuOperation() const { return *operation_; }
absl::Status Compile(MetalDevice* device);
// should be called after changes of inputs/outputs.
absl::Status UpdateParams();
void Encode(id<MTLComputeCommandEncoder> encoder);
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void EncodeToICB(id<MTLIndirectComputeCommand> icb_command);
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void AddResourcesToEncoder(id<MTLComputeCommandEncoder> encoder) const;
void Update();
void SetSrcTensor(MetalSpatialTensor* tensor, int index);
void SetDstTensor(MetalSpatialTensor* tensor, int index);
absl::Status Tune(TuningType tuning_type, MetalDevice* device);
int3 GetWorkGroupSize() const { return operation_->work_group_size_; }
void SetWorkGroupSize(const int3& work_group_size);
const std::string& GetCode() const { return operation_->code_; }
const std::map<std::string, std::string>& GetDefines() const {
return defines_;
}
absl::Status Init(MetalDevice* device, const std::string& code,
const std::map<std::string, std::string>& defines);
absl::Status RestoreDeserialized(MetalDevice* device);
private:
absl::Status CompileProgram(
MetalDevice* device, const std::string& code,
const std::map<std::string, std::string>& defines);
void Release();
std::unique_ptr<GPUOperation> operation_;
id<MTLComputePipelineState> program_ = nullptr;
MetalArguments metal_args_;
bool use_arguments_buffer_ = false; // optional
bool need_icb_support_ = false; // optional
id<MTLArgumentEncoder> arguments_encoder_ = nullptr;
id<MTLBuffer> arg_buffer_ = nullptr;
// for serialization
std::map<std::string, std::string> defines_;
};
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_COMPUTE_TASK_H_
@@ -0,0 +1,327 @@
/* 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/metal/compute_task.h"
#include <Availability.h>
#include <map>
#include <string>
#include <tuple>
#include <utility>
#include "absl/strings/match.h"
#include "absl/strings/substitute.h"
#include "tensorflow/lite/delegates/gpu/common/kernel_info.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/metal/common.h"
namespace tflite {
namespace gpu {
namespace metal {
namespace {
std::map<std::string, std::string> GetMetalDefines(
MetalDevice* device, CalculationsPrecision precision) {
std::string simdgroup_barrier;
// simdgroup_barrier is supported since Metal shading language version 2.0
if (device->IsLanguageVersion2orHigher()) {
simdgroup_barrier = "simdgroup_barrier";
} else {
simdgroup_barrier = "threadgroup_barrier";
}
std::string storage_type;
std::string accumulator_type;
std::string to_accumulator_type4;
if (precision == CalculationsPrecision::F32) {
storage_type = "float";
accumulator_type = "float";
} else {
// FP16
storage_type = "half";
if (precision == CalculationsPrecision::F32_F16) {
accumulator_type = "float";
to_accumulator_type4 = "float4";
} else {
accumulator_type = "half";
}
}
return {
{"FLT", storage_type},
{"FLT2", storage_type + "2"},
{"FLT3", storage_type + "3"},
{"FLT4", storage_type + "4"},
{"ACCUM_FLT", accumulator_type},
{"ACCUM_FLT2", accumulator_type + "2"},
{"ACCUM_FLT3", accumulator_type + "3"},
{"ACCUM_FLT4", accumulator_type + "4"},
{"INIT_ACCUM_FLT4(value)", accumulator_type + "4(value)"},
{"TO_ACCUM_TYPE", to_accumulator_type4},
{"TO_ACCUM_FLT", accumulator_type},
{"TO_ACCUM_FLT2", accumulator_type + "2"},
{"TO_ACCUM_FLT3", accumulator_type + "3"},
{"TO_ACCUM_FLT4", accumulator_type + "4"},
{"TO_FLT4", storage_type + "4"},
{"SIMDGROUP_BARRIER", simdgroup_barrier},
{"SIMD_LOCAL_MEM_BARRIER", simdgroup_barrier},
{"MAIN_FUNCTION", "kernel void ComputeFunction"},
{"GLOBAL_ID_0", "static_cast<int>(reserved_gid.x)"},
{"GLOBAL_ID_1", "static_cast<int>(reserved_gid.y)"},
{"GLOBAL_ID_2", "static_cast<int>(reserved_gid.z)"},
{"LOCAL_ID_0", "static_cast<int>(reserved_lid.x)"},
{"LOCAL_ID_1", "static_cast<int>(reserved_lid.y)"},
{"LOCAL_ID_2", "static_cast<int>(reserved_lid.z)"},
{"GROUP_ID_0", "static_cast<int>(reserved_group_id.x)"},
{"GROUP_ID_1", "static_cast<int>(reserved_group_id.y)"},
{"GROUP_ID_2", "static_cast<int>(reserved_group_id.z)"},
{"GROUP_SIZE_0", "static_cast<int>(reserved_group_size.x)"},
{"GROUP_SIZE_1", "static_cast<int>(reserved_group_size.y)"},
{"GROUP_SIZE_2", "static_cast<int>(reserved_group_size.z)"},
{"SUB_GROUP_LOCAL_ID", "static_cast<int>(reserved_simd_id)"},
{"SUB_GROUP_BROADCAST(V, ID)", "simd_broadcast(V, ID)"},
{"__local", "threadgroup"},
{"__global", "device"},
{"__constant", "constant"},
{"LOCAL_MEM_BARRIER", "threadgroup_barrier(mem_flags::mem_threadgroup)"},
{"INIT_FLT(value)", storage_type + "(value)"},
{"INIT_FLT4(value)", storage_type + "4(value)"},
{"INIT_FLT4v4(v0, v1, v2, v3)", storage_type + "4(v0, v1, v2, v3)"},
{"INIT_FLOAT(value)", "float(value)"},
{"INIT_FLOAT2(value)", "float2(value)"},
{"INIT_FLOAT2v2(v0, v1)", "float2(v0, v1)"},
{"INIT_FLOAT3(value)", "float3(value)"},
{"INIT_FLOAT3v3(v0, v1, v2)", "float3(v0, v1, v2)"},
{"INIT_FLOAT4(value)", "float4(value)"},
{"INIT_FLOAT4v4(v0, v1, v2, v3)", "float4(v0, v1, v2, v3)"},
{"INIT_INT(value)", "int(value)"},
{"INIT_INT2v2(v0, v1)", "int2(v0, v1)"},
{"INIT_INT4v4(v0, v1, v2, v3)", "int4(v0, v1, v2, v3)"},
{"CONVERT_TO_INT4(value)", "int4(value)"},
};
}
} // namespace
ComputeTask::ComputeTask(ComputeTask&& task)
: operation_(std::move(task.operation_)),
program_(task.program_),
metal_args_(std::move(task.metal_args_)),
use_arguments_buffer_(task.use_arguments_buffer_),
need_icb_support_(task.need_icb_support_),
arguments_encoder_(task.arguments_encoder_),
arg_buffer_(task.arg_buffer_) {
task.program_ = nullptr;
task.arguments_encoder_ = nullptr;
task.arg_buffer_ = nullptr;
}
ComputeTask& ComputeTask::operator=(ComputeTask&& task) {
if (this != &task) {
Release();
operation_ = std::move(task.operation_);
std::swap(program_, task.program_);
metal_args_ = std::move(task.metal_args_);
std::swap(use_arguments_buffer_, task.use_arguments_buffer_);
std::swap(need_icb_support_, task.need_icb_support_);
std::swap(arguments_encoder_, task.arguments_encoder_);
std::swap(arg_buffer_, task.arg_buffer_);
}
return *this;
}
ComputeTask::~ComputeTask() { Release(); }
void ComputeTask::Release() {
if (program_) {
program_ = nullptr;
}
if (arguments_encoder_) {
arguments_encoder_ = nullptr;
}
if (arg_buffer_) {
arg_buffer_ = nullptr;
}
}
void ComputeTask::Init(std::unique_ptr<GPUOperation>&& operation) {
operation_ = std::move(operation);
}
absl::Status ComputeTask::Compile(MetalDevice* device) {
RETURN_IF_ERROR(metal_args_.Init(use_arguments_buffer_, device,
&operation_->args_, &operation_->code_));
operation_->args_.ReleaseCPURepresentation();
defines_ = GetMetalDefines(device, operation_->GetPrecision());
return CompileProgram(device, operation_->code_, defines_);
}
absl::Status ComputeTask::CompileProgram(
MetalDevice* device, const std::string& code,
const std::map<std::string, std::string>& defines) {
id<MTLComputePipelineState> program;
if (use_arguments_buffer_) {
id<MTLArgumentEncoder> arguments_encoder;
if (need_icb_support_) {
RETURN_IF_ERROR(CreateComputeProgramWithICBSupport(
device->device(), code, "ComputeFunction", defines, &program,
&arguments_encoder));
} else {
RETURN_IF_ERROR(CreateComputeProgramWithArgumentBuffer(
device->device(), code, "ComputeFunction", defines, &program,
&arguments_encoder));
}
arguments_encoder_ = arguments_encoder;
arg_buffer_ =
[device->device() newBufferWithLength:arguments_encoder_.encodedLength
options:0];
if (!arg_buffer_) {
return absl::InternalError("Failed to create MTLBuffer.");
}
} else {
RETURN_IF_ERROR(CreateComputeProgram(device->device(), code,
"ComputeFunction", defines, &program));
}
program_ = program;
return absl::OkStatus();
}
absl::Status ComputeTask::Init(
MetalDevice* device, const std::string& code,
const std::map<std::string, std::string>& defines) {
return CompileProgram(device, code, defines);
}
absl::Status ComputeTask::RestoreDeserialized(MetalDevice* device) {
RETURN_IF_ERROR(
metal_args_.Init(use_arguments_buffer_, device, &operation_->args_));
operation_->args_.ReleaseCPURepresentation();
return absl::OkStatus();
}
absl::Status ComputeTask::UpdateParams() {
for (int i = 0; i < operation_->GetSrcTensorsNames().size(); ++i) {
const auto* metal_spatial_tensor =
dynamic_cast<const MetalSpatialTensor*>(operation_->GetSrcTensors()[i]);
if (!metal_spatial_tensor) {
return absl::InvalidArgumentError("Expected MetalSpatialTensor.");
}
RETURN_IF_ERROR(metal_args_.SetObjectRef(
operation_->GetSrcTensorsNames()[i], *metal_spatial_tensor));
}
for (int i = 0; i < operation_->GetDstTensorsNames().size(); ++i) {
const auto* metal_spatial_tensor =
dynamic_cast<const MetalSpatialTensor*>(operation_->GetDstTensors()[i]);
if (!metal_spatial_tensor) {
return absl::InvalidArgumentError("Expected MetalSpatialTensor.");
}
RETURN_IF_ERROR(metal_args_.SetObjectRef(
operation_->GetDstTensorsNames()[i], *metal_spatial_tensor));
}
RETURN_IF_ERROR(operation_->BindArguments(&metal_args_));
operation_->RecalculateGridSize();
operation_->RecalculateWorkGroupsCount();
Update();
return absl::OkStatus();
}
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void ComputeTask::EncodeToICB(id<MTLIndirectComputeCommand> icb_command) {
MTLSize groupsCount, groupsSize;
groupsCount.width = operation_->GetWorkGroupsCount().x;
groupsCount.height = operation_->GetWorkGroupsCount().y;
groupsCount.depth = operation_->GetWorkGroupsCount().z;
groupsSize.width = operation_->work_group_size_.x;
groupsSize.height = operation_->work_group_size_.y;
groupsSize.depth = operation_->work_group_size_.z;
[icb_command setComputePipelineState:program_];
[icb_command setKernelBuffer:arg_buffer_ offset:0 atIndex:0];
[icb_command concurrentDispatchThreadgroups:groupsCount
threadsPerThreadgroup:groupsSize];
[icb_command setBarrier];
}
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void ComputeTask::AddResourcesToEncoder(
id<MTLComputeCommandEncoder> encoder) const {
metal_args_.AddResourcesToEncoder(encoder);
}
void ComputeTask::Update() {
if (use_arguments_buffer_) {
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
[arguments_encoder_ setArgumentBuffer:arg_buffer_ offset:0];
metal_args_.EncodeArguments(arguments_encoder_);
}
}
}
void ComputeTask::Encode(id<MTLComputeCommandEncoder> encoder) {
[encoder setComputePipelineState:program_];
if (use_arguments_buffer_) {
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
metal_args_.AddResourcesToEncoder(encoder);
[encoder setBuffer:arg_buffer_ offset:0 atIndex:0];
}
} else {
metal_args_.Encode(encoder, 0);
}
MTLSize groupsCount, groupsSize;
groupsCount.width = operation_->GetWorkGroupsCount().x;
groupsCount.height = operation_->GetWorkGroupsCount().y;
groupsCount.depth = operation_->GetWorkGroupsCount().z;
groupsSize.width = operation_->work_group_size_.x;
groupsSize.height = operation_->work_group_size_.y;
groupsSize.depth = operation_->work_group_size_.z;
[encoder dispatchThreadgroups:groupsCount threadsPerThreadgroup:groupsSize];
}
void ComputeTask::SetSrcTensor(MetalSpatialTensor* tensor, int index) {
operation_->SetSrc(tensor, index);
auto status = metal_args_.SetObjectRef(
operation_->GetSrcTensorsNames()[index], *tensor);
}
void ComputeTask::SetDstTensor(MetalSpatialTensor* tensor, int index) {
operation_->SetDst(tensor, index);
auto status = metal_args_.SetObjectRef(
operation_->GetDstTensorsNames()[index], *tensor);
}
absl::Status ComputeTask::Tune(TuningType tuning_type, MetalDevice* device) {
KernelInfo kernel_info;
kernel_info.max_work_group_size = [program_ maxTotalThreadsPerThreadgroup];
kernel_info.private_memory_size = 0;
std::vector<GPUOperation::DispatchInfo> possible_dispatches;
operation_->GetPossibleDispatches(tuning_type, device->GetInfo(), kernel_info,
&possible_dispatches);
if (possible_dispatches.empty()) {
return absl::NotFoundError("No dispatch parameters to launch kernel");
}
operation_->work_group_size_ = possible_dispatches[0].work_group_size;
operation_->RecalculateWorkGroupsCount();
return absl::OkStatus();
}
void ComputeTask::SetWorkGroupSize(const int3& work_group_size) {
operation_->work_group_size_ = work_group_size;
operation_->RecalculateWorkGroupsCount();
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,76 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_GPU_OBJECT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_GPU_OBJECT_H_
#import <Metal/Metal.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/access_type.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/gpu_object_desc.h"
namespace tflite {
namespace gpu {
namespace metal {
struct GPUResourcesWithValue {
GenericGPUResourcesWithValue generic;
struct BufferParameter {
id<MTLBuffer> handle;
uint64_t offset;
};
std::vector<std::pair<std::string, BufferParameter>> buffers;
std::vector<std::pair<std::string, id<MTLTexture>>> images2d;
std::vector<std::pair<std::string, id<MTLTexture>>> image2d_arrays;
std::vector<std::pair<std::string, id<MTLTexture>>> images3d;
std::vector<std::pair<std::string, id<MTLTexture>>> image_buffers;
void AddFloat(const std::string& name, float value) {
generic.AddFloat(name, value);
}
void AddInt(const std::string& name, int value) {
generic.AddInt(name, value);
}
};
class GPUObject {
public:
GPUObject() = default;
// Move only
GPUObject(GPUObject&& obj_desc) = default;
GPUObject& operator=(GPUObject&& obj_desc) = default;
GPUObject(const GPUObject&) = delete;
GPUObject& operator=(const GPUObject&) = delete;
virtual ~GPUObject() = default;
virtual absl::Status GetGPUResources(
const GPUObjectDescriptor* obj_ptr,
GPUResourcesWithValue* resources) const = 0;
};
using GPUObjectPtr = std::unique_ptr<GPUObject>;
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_GPU_OBJECT_H_
@@ -0,0 +1,53 @@
/* 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.
==============================================================================*/
// This is a shell app used for hosting the unittests. This way any real app
// startup code doesn't get in the way when running unittests.
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
@end
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, @"AppDelegate");
}
}
@@ -0,0 +1,34 @@
// Copyright 2022 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
include "tensorflow/lite/delegates/gpu/common/gpu_model.fbs";
include "tensorflow/lite/delegates/gpu/common/task/tflite_serialization_base.fbs";
namespace tflite.gpu.metal.data;
table MetalProgram {
define_names:[string];
define_expressions:[string];
code:string;
}
table InferenceContext {
gpu_model:tflite.gpu.data.GpuModel;
// Must be serialized after actual Metal objects created
// Separated from nodes in GpuModel
tuned_work_group_sizes_per_node:[tflite.gpu.data.Int3];
metal_programs:[MetalProgram];
}
root_type InferenceContext;
@@ -0,0 +1,197 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_INFERENCE_CONTEXT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_INFERENCE_CONTEXT_H_
#import <Metal/Metal.h>
#include <list>
#include <map>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_model.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_model_generated.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_hints.h"
#include "tensorflow/lite/delegates/gpu/common/precision.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/profiling_info.h"
#include "tensorflow/lite/delegates/gpu/common/task/tuning_type.h"
#include "tensorflow/lite/delegates/gpu/metal/compute_task.h"
#include "tensorflow/lite/delegates/gpu/metal/inference_context_generated.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_device.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
namespace tflite {
namespace gpu {
namespace metal {
struct MetalNode {
ComputeTask task;
std::vector<ValueId> inputs;
std::vector<ValueId> outputs;
// Mostly for debug purposes.
std::string name;
MetalNode() = default;
MetalNode(MetalNode&& node) = default;
MetalNode& operator=(MetalNode&& node) = default;
MetalNode(const MetalNode&) = delete;
MetalNode& operator=(const MetalNode&) = delete;
};
class InferenceContext {
public:
InferenceContext() = default;
// IMPORTANT: If InitFromGraph used, RunGraphTransforms must be applied for
// this graph upfront, otherwise not guaranteed correct behavior
absl::Status InitFromGraph(const CreateGpuModelInfo& create_info,
const GraphFloat32& graph, id<MTLDevice> device_id,
std::vector<uint8_t>* serialized_model = nullptr);
// Applies specific transformations to the graph before the
// initialization. These transformations are either impossible or useless in
// other backends.
absl::Status InitFromGraphWithTransforms(
const CreateGpuModelInfo& create_info, GraphFloat32* graph,
id<MTLDevice> device_id,
std::vector<uint8_t>* serialized_model = nullptr);
absl::Status RestoreDeserialized(
const absl::Span<const uint8_t> serialized_model, id<MTLDevice> device_id,
CreateGpuModelInfo* create_info = nullptr);
/// Inserts all GPU compute tasks into the command encoder.
/// @param inputOutputBuffers Must be created and passed into the method
/// with pairs ID:buffer
/// @discussion No GPU synchronization functions are used inside. All GPU
/// resources must be created
/// with the same device which has been used in
/// compileModelWithDevice() method.
void EncodeWithEncoder(id<MTLComputeCommandEncoder> command_encoder);
/// Inserts all GPU compute tasks into the command buffer. For every task will
/// be used separate
/// encoder.
/// @param inputOutputBuffers Must be created and passed into the method with
/// pairs ID:buffer
/// @discussion No GPU synchronization functions are used inside. All GPU
/// resources must be created
/// with the same device which has been used in
/// compileModelWithDevice() method.
void EncodeWithCommandBuffer(id<MTLCommandBuffer> command_buffer);
/// Adds all GPU compute tasks to the command queue. For every task will be
/// used separate
/// encoder. Few encoders(flushPeriod) batched into compute buffer that sent
/// for execution.
/// @param inputOutputBuffers Must be created and passed into the method with
/// pairs ID:buffer
/// @discussion No GPU synchronization functions are used inside. All GPU
/// resources must be created
/// with the same device which has been used in
/// compileModelWithDevice() method.
void EncodeWithCommandQueue(id<MTLCommandQueue> command_queue,
int flush_period);
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void AddResources(id<MTLComputeCommandEncoder> command_encoder);
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void EncodeWithICB(id<MTLComputeCommandEncoder> command_encoder);
void Profile(id<MTLDevice> device, ProfilingInfo* result);
// Returns size in bytes for all intermediate(runtime) tensors that owned by
// this inference context. Do not include constant tensors.
uint64_t GetIntermediateTensorsSize() const;
uint64_t GetConstantTensorsSize() const;
// Can be used only with ids from external_mutable_tensors in create_info
// Must be called after initialization and before execution
absl::Status SetTensor(const ValueId& tensor_id,
MetalSpatialTensor* tensor_ptr);
MetalSpatialTensor* GetTensor(ValueId tensor_id);
absl::Status SetInputTensor(ValueId id, const TensorFloat32& tensor);
absl::Status GetOutputTensor(ValueId id, TensorFloat32* result);
private:
enum class TensorMemoryType {
kStrongShape,
kBuffer,
kVariable,
kConst,
kExternal
};
flatbuffers::Offset<data::InferenceContext> Encode(
MetalDevice* device,
flatbuffers::Offset<tflite::gpu::data::GpuModel> gpu_model_fb,
flatbuffers::FlatBufferBuilder* builder);
absl::Status Decode(MetalDevice* device,
const data::InferenceContext* fb_inference);
void CopyFromGpuModel(GpuModel* gpu_model);
absl::Status CompileOperations(MetalDevice* device);
void PrepareExternal();
absl::Status AllocateTensors(MetalDevice* device);
absl::Status AllocateMemoryForConstTensors(MetalDevice* device);
absl::Status AllocateMemoryForBuffers(MetalDevice* device);
absl::Status AllocateMemoryForStrongShapes(MetalDevice* device);
void BindTensorsToOperations();
absl::Status UpdateParams(const GpuInfo& gpu_info);
void GetUsages(const std::function<bool(ValueId)>& functor,
std::map<ValueId, int2>* usages);
TensorMemoryType GetTensorMemoryType(const GpuInfo& gpu_info, ValueId id);
absl::Status Tune(TuningType tuning_type, MetalDevice* device);
absl::flat_hash_map<ValueId, TensorDescriptor> tensors_descs_;
std::vector<MetalNode> nodes_;
std::vector<ValueId> input_ids_;
std::vector<ValueId> output_ids_;
absl::flat_hash_map<ValueId, MetalSpatialTensor*> external_immutable_tensors_;
absl::flat_hash_map<ValueId, MetalSpatialTensor*> external_mutable_tensors_;
absl::flat_hash_map<ValueId, std::vector<int>> external_tensor_to_nodes_;
absl::flat_hash_map<ValueId, TensorDescriptor> const_tensors_descs_;
std::map<ValueId, MetalSpatialTensor> const_tensors_;
std::map<ValueId, int> graph_ids_to_shared_buffer_tensors_;
std::vector<id<MTLBuffer>> shared_buffers_;
std::vector<MetalSpatialTensor>
shared_buffer_tensors_; // use references to memory
// from _sharedBuffers
std::map<ValueId, MetalSpatialTensor> strong_shape_tensors_;
std::map<ValueId, ValueId> graph_ids_to_strong_shape_tensors_;
id<MTLIndirectCommandBuffer> icb_ API_AVAILABLE(ios(13.0), macos(11.00),
tvos(13.0)) = nullptr;
id<MTLDevice> device_ = nullptr;
};
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_INFERENCE_CONTEXT_H_
@@ -0,0 +1,827 @@
/* 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/metal/inference_context.h"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <map>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/substitute.h"
#include "absl/time/clock.h"
#include "tensorflow/lite/delegates/gpu/common/memory_management.h"
#include "tensorflow/lite/delegates/gpu/common/memory_management/types.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/precision.h"
#include "tensorflow/lite/delegates/gpu/common/selectors/operation_selector.h"
#include "tensorflow/lite/delegates/gpu/common/selectors/special_selector.h"
#include "tensorflow/lite/delegates/gpu/common/selectors/subgraph.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/serialization_base.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/compute_task.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
namespace tflite {
namespace gpu {
namespace metal {
namespace {
// returns true if actual memory for this storage type is buffer
bool IsBufferBased(const GpuInfo& gpu_info, const TensorStorageType& type) {
const bool family_apple1 =
gpu_info.IsApple() && gpu_info.apple_info.IsFamilyApple1();
if (!family_apple1 && (type == TensorStorageType::TEXTURE_2D ||
type == TensorStorageType::SINGLE_TEXTURE_2D)) {
return true;
}
return type == TensorStorageType::BUFFER ||
type == TensorStorageType::IMAGE_BUFFER;
}
void AddUsage(ValueId id, int task_index,
std::map<ValueId, int2>* usage_records) {
auto it = usage_records->find(id);
if (it == usage_records->end()) {
// initializing start index(.x) and end index(.y)
(*usage_records)[id].x = task_index;
(*usage_records)[id].y = task_index;
} else {
// updating end index(.y)
(*usage_records)[id].y = task_index;
}
}
// Calculates the total size of the assignment.
size_t TotalSize(const ObjectsAssignment<size_t>& assignment,
size_t alignment = 1) {
size_t total_size = 0;
for (auto object_size : assignment.object_sizes) {
total_size += AlignByN(object_size, alignment);
}
return total_size;
}
flatbuffers::Offset<data::MetalProgram> EncodeProgram(
const std::string& code, const std::map<std::string, std::string>& defines,
flatbuffers::FlatBufferBuilder* builder) {
std::vector<flatbuffers::Offset<flatbuffers::String>> names_fb;
std::vector<flatbuffers::Offset<flatbuffers::String>> expressions_fb;
for (auto& define : defines) {
names_fb.push_back(builder->CreateString(define.first));
expressions_fb.push_back(builder->CreateString(define.second));
}
auto names_fb_vec = builder->CreateVector(names_fb);
auto expressions_fb_vec = builder->CreateVector(expressions_fb);
auto code_fb = builder->CreateString(code);
data::MetalProgramBuilder program_builder(*builder);
program_builder.add_define_names(names_fb_vec);
program_builder.add_define_expressions(expressions_fb_vec);
program_builder.add_code(code_fb);
return program_builder.Finish();
}
void DecodeProgram(const data::MetalProgram* metal_program, std::string* code,
std::map<std::string, std::string>* defines) {
*code = std::string(metal_program->code()->c_str(),
metal_program->code()->size());
for (int i = 0; i < metal_program->define_names()->size(); ++i) {
std::string key((*metal_program->define_names())[i]->c_str(),
(*metal_program->define_names())[i]->size());
std::string value((*metal_program->define_expressions())[i]->c_str(),
(*metal_program->define_expressions())[i]->size());
(*defines)[key] = value;
}
}
} // namespace
absl::Status InferenceContext::InitFromGraphWithTransforms(
const CreateGpuModelInfo& create_info, GraphFloat32* graph,
id<MTLDevice> device_id, std::vector<uint8_t>* serialized_model) {
RETURN_IF_ERROR(RunGraphTransformsForGpuModel(graph));
RETURN_IF_ERROR(
InitFromGraph(create_info, *graph, device_id, serialized_model));
return absl::OkStatus();
}
void InferenceContext::CopyFromGpuModel(GpuModel* gpu_model) {
for (const auto& input : gpu_model->input_ids_and_refs) {
input_ids_.push_back(input.first);
}
for (const auto& output : gpu_model->output_ids_and_refs) {
output_ids_.push_back(output.first);
}
nodes_.resize(gpu_model->nodes.size());
for (int i = 0; i < gpu_model->nodes.size(); ++i) {
nodes_[i].task.Init(std::move(gpu_model->nodes[i].gpu_operation));
nodes_[i].inputs = gpu_model->nodes[i].inputs;
nodes_[i].outputs = gpu_model->nodes[i].outputs;
nodes_[i].name = gpu_model->nodes[i].name;
}
const_tensors_descs_ = std::move(gpu_model->const_tensors);
tensors_descs_ = std::move(gpu_model->tensors);
}
absl::Status InferenceContext::InitFromGraph(
const CreateGpuModelInfo& create_info, const GraphFloat32& graph,
id<MTLDevice> device_id, std::vector<uint8_t>* serialized_model) {
device_ = device_id;
MetalDevice metal_device(device_id);
GpuModel gpu_model;
RETURN_IF_ERROR(
GraphToGpuModel(graph, create_info, metal_device.GetInfo(), &gpu_model));
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<tflite::gpu::data::GpuModel> gpu_model_fb;
if (serialized_model) {
gpu_model_fb = tflite::gpu::Encode(gpu_model, &builder);
}
CopyFromGpuModel(&gpu_model);
for (const auto& external_tensor : create_info.external_immutable_tensors) {
auto* metal_spatial_tensor =
dynamic_cast<MetalSpatialTensor*>(external_tensor.second);
if (!metal_spatial_tensor) {
return absl::InvalidArgumentError("Expected MetalSpatialTensor.");
}
external_immutable_tensors_[external_tensor.first] = metal_spatial_tensor;
}
std::map<ValueId, MetalSpatialTensor> temp_external_tensors;
for (const auto& external_tensor : create_info.external_mutable_tensors) {
RETURN_IF_ERROR(
CreateTensor(device_id, tensors_descs_[external_tensor.first],
&temp_external_tensors[external_tensor.first]));
external_mutable_tensors_[external_tensor.first] =
&temp_external_tensors[external_tensor.first];
}
PrepareExternal();
RETURN_IF_ERROR(CompileOperations(&metal_device));
RETURN_IF_ERROR(AllocateTensors(&metal_device));
BindTensorsToOperations();
RETURN_IF_ERROR(UpdateParams(metal_device.GetInfo()));
RETURN_IF_ERROR(Tune(TuningType::kFast, &metal_device));
for (auto& external_tensor : external_mutable_tensors_) {
external_tensor.second = nullptr;
}
if (serialized_model) {
auto encoded_fb = Encode(&metal_device, gpu_model_fb, &builder);
data::FinishInferenceContextBuffer(builder, encoded_fb);
serialized_model->resize(builder.GetSize());
std::memcpy(serialized_model->data(), builder.GetBufferPointer(),
builder.GetSize());
}
bool add_icb_support = false && external_mutable_tensors_.empty();
if (add_icb_support) {
if (@available(macOS 11.00, iOS 13.0, tvOS 13.0, *)) {
MTLIndirectCommandBufferDescriptor* icb_desc =
[[MTLIndirectCommandBufferDescriptor alloc] init];
icb_desc.commandTypes = MTLIndirectCommandTypeConcurrentDispatch;
icb_desc.inheritBuffers = NO;
icb_desc.inheritPipelineState = NO;
icb_desc.maxKernelBufferBindCount = 1;
icb_ = [device_id newIndirectCommandBufferWithDescriptor:icb_desc
maxCommandCount:nodes_.size()
options:0];
for (int i = 0; i < nodes_.size(); ++i) {
id<MTLIndirectComputeCommand> icb_command =
[icb_ indirectComputeCommandAtIndex:i];
auto& node = nodes_[i];
node.task.EncodeToICB(icb_command);
}
}
}
return absl::OkStatus();
}
absl::Status InferenceContext::RestoreDeserialized(
const absl::Span<const uint8_t> serialized_model, id<MTLDevice> device_id,
CreateGpuModelInfo* create_info) {
flatbuffers::Verifier verifier(serialized_model.data(),
serialized_model.size());
if (!data::VerifyInferenceContextBuffer(verifier)) {
return absl::DataLossError("Deserialization failed.");
}
auto decoded_fb = data::GetInferenceContext(serialized_model.data());
device_ = device_id;
MetalDevice metal_device(device_id);
RETURN_IF_ERROR(Decode(&metal_device, decoded_fb));
std::map<ValueId, MetalSpatialTensor> temp_external_tensors;
if (create_info) {
for (const auto& external_tensor :
create_info->external_immutable_tensors) {
auto* cl_spatial_tensor =
dynamic_cast<MetalSpatialTensor*>(external_tensor.second);
if (!cl_spatial_tensor) {
return absl::InvalidArgumentError("Expected MetalSpatialTensor.");
}
external_immutable_tensors_[external_tensor.first] = cl_spatial_tensor;
}
for (const auto& external_tensor : create_info->external_mutable_tensors) {
RETURN_IF_ERROR(
CreateTensor(device_id, tensors_descs_[external_tensor.first],
&temp_external_tensors[external_tensor.first]));
external_mutable_tensors_[external_tensor.first] =
&temp_external_tensors[external_tensor.first];
}
}
PrepareExternal();
RETURN_IF_ERROR(AllocateTensors(&metal_device));
BindTensorsToOperations();
for (auto& node : nodes_) {
RETURN_IF_ERROR(node.task.RestoreDeserialized(&metal_device));
}
RETURN_IF_ERROR(UpdateParams(metal_device.GetInfo()));
for (auto& external_tensor : external_mutable_tensors_) {
external_tensor.second = nullptr;
}
return absl::OkStatus();
}
flatbuffers::Offset<data::InferenceContext> InferenceContext::Encode(
MetalDevice* device,
flatbuffers::Offset<tflite::gpu::data::GpuModel> gpu_model_fb,
flatbuffers::FlatBufferBuilder* builder) {
std::vector<flatbuffers::Offset<tflite::gpu::data::Int3>> work_groups_fb;
for (int i = 0; i < nodes_.size(); ++i) {
auto work_group_fb =
tflite::gpu::Encode(nodes_[i].task.GetWorkGroupSize(), builder);
work_groups_fb.push_back(work_group_fb);
}
auto work_groups_fb_vec = builder->CreateVector(work_groups_fb);
std::vector<flatbuffers::Offset<data::MetalProgram>> programs_fb;
for (int i = 0; i < nodes_.size(); ++i) {
auto program_fb = EncodeProgram(nodes_[i].task.GetCode(),
nodes_[i].task.GetDefines(), builder);
programs_fb.push_back(program_fb);
}
auto programs_fb_vec = builder->CreateVector(programs_fb);
data::InferenceContextBuilder inf_builder(*builder);
inf_builder.add_gpu_model(gpu_model_fb);
inf_builder.add_tuned_work_group_sizes_per_node(work_groups_fb_vec);
inf_builder.add_metal_programs(programs_fb_vec);
return inf_builder.Finish();
}
absl::Status InferenceContext::Decode(
MetalDevice* device, const data::InferenceContext* fb_inference) {
GpuModel gpu_model;
RETURN_IF_ERROR(tflite::gpu::Decode(fb_inference->gpu_model(), &gpu_model));
CopyFromGpuModel(&gpu_model);
for (int i = 0; i < nodes_.size(); ++i) {
std::string code;
std::map<std::string, std::string> defines;
DecodeProgram((*fb_inference->metal_programs())[i], &code, &defines);
RETURN_IF_ERROR(nodes_[i].task.Init(device, code, defines));
int3 wg_size;
wg_size.x = (*fb_inference->tuned_work_group_sizes_per_node())[i]->x();
wg_size.y = (*fb_inference->tuned_work_group_sizes_per_node())[i]->y();
wg_size.z = (*fb_inference->tuned_work_group_sizes_per_node())[i]->z();
nodes_[i].task.SetWorkGroupSize(wg_size);
}
return absl::OkStatus();
}
absl::Status InferenceContext::CompileOperations(MetalDevice* device) {
for (auto& node : nodes_) {
RETURN_IF_ERROR(node.task.Compile(device));
}
return absl::OkStatus();
}
absl::Status InferenceContext::AllocateTensors(MetalDevice* device) {
RETURN_IF_ERROR(AllocateMemoryForConstTensors(device));
RETURN_IF_ERROR(AllocateMemoryForBuffers(device));
RETURN_IF_ERROR(AllocateMemoryForStrongShapes(device));
return absl::OkStatus();
}
MetalSpatialTensor* InferenceContext::GetTensor(ValueId tensor_id) {
if (external_immutable_tensors_.find(tensor_id) !=
external_immutable_tensors_.end()) {
return external_immutable_tensors_[tensor_id];
} else if (external_mutable_tensors_.find(tensor_id) !=
external_mutable_tensors_.end()) {
return external_mutable_tensors_[tensor_id];
} else if (const_tensors_.find(tensor_id) != const_tensors_.end()) {
return &const_tensors_[tensor_id];
} else if (graph_ids_to_shared_buffer_tensors_.find(tensor_id) !=
graph_ids_to_shared_buffer_tensors_.end()) {
return &shared_buffer_tensors_
[graph_ids_to_shared_buffer_tensors_[tensor_id]];
} else if (graph_ids_to_strong_shape_tensors_.find(tensor_id) !=
graph_ids_to_strong_shape_tensors_.end()) {
return &strong_shape_tensors_
[graph_ids_to_strong_shape_tensors_[tensor_id]];
}
return nullptr;
}
absl::Status InferenceContext::SetInputTensor(ValueId id,
const TensorFloat32& tensor) {
MetalSpatialTensor* gpu_tensor = GetTensor(id);
TensorDescriptor descriptor_with_data = gpu_tensor->GetDescriptor();
descriptor_with_data.UploadData(tensor);
return gpu_tensor->UploadDescriptorData(descriptor_with_data, device_);
}
absl::Status InferenceContext::GetOutputTensor(ValueId id,
TensorFloat32* result) {
const MetalSpatialTensor* gpu_tensor = GetTensor(id);
const auto dst_shape = BHWC(gpu_tensor->Batch(), gpu_tensor->Height(),
gpu_tensor->Width(), gpu_tensor->Channels());
result->id = id;
result->shape = dst_shape;
result->data.resize(dst_shape.DimensionsProduct());
TensorDescriptor desc;
RETURN_IF_ERROR(gpu_tensor->ToDescriptor(&desc, device_));
desc.DownloadData(result);
return absl::OkStatus();
}
void InferenceContext::BindTensorsToOperations() {
for (auto& node : nodes_) {
const auto& src_ids = node.inputs;
for (int i = 0; i < src_ids.size(); ++i) {
node.task.SetSrcTensor(GetTensor(src_ids[i]), i);
}
const auto& dst_ids = node.outputs;
for (int i = 0; i < dst_ids.size(); ++i) {
node.task.SetDstTensor(GetTensor(dst_ids[i]), i);
}
}
}
absl::Status InferenceContext::UpdateParams(const GpuInfo& gpu_info) {
for (auto& node : nodes_) {
std::vector<BHWC> src_shapes;
std::vector<BHWC> dst_shapes;
for (const auto& in_id : node.inputs) {
const auto& shape = tensors_descs_[in_id].GetBHWDCShape();
src_shapes.push_back(BHWC(shape.b, shape.h, shape.w, shape.c));
}
for (const auto& out_id : node.outputs) {
const auto& shape = tensors_descs_[out_id].GetBHWDCShape();
dst_shapes.push_back(BHWC(shape.b, shape.h, shape.w, shape.c));
}
RETURN_IF_ERROR(node.task.UpdateParams());
}
return absl::OkStatus();
}
InferenceContext::TensorMemoryType InferenceContext::GetTensorMemoryType(
const GpuInfo& gpu_info, ValueId id) {
if (external_immutable_tensors_.find(id) !=
external_immutable_tensors_.end()) {
return TensorMemoryType::kExternal;
} else if (external_mutable_tensors_.find(id) !=
external_mutable_tensors_.end()) {
return TensorMemoryType::kExternal;
} else if (const_tensors_.find(id) != const_tensors_.end()) {
return TensorMemoryType::kConst;
} else if (IsBufferBased(gpu_info, tensors_descs_[id].GetStorageType())) {
return TensorMemoryType::kBuffer;
} else {
return TensorMemoryType::kStrongShape;
}
}
void InferenceContext::GetUsages(const std::function<bool(ValueId)>& functor,
std::map<ValueId, int2>* usages) {
for (ValueId in_id : input_ids_) {
if (functor(in_id)) {
AddUsage(in_id, 0, usages);
}
}
for (int op_index = 0; op_index < nodes_.size(); ++op_index) {
for (auto& tensor_id : nodes_[op_index].inputs) {
if (functor(tensor_id)) {
AddUsage(tensor_id, op_index, usages);
}
}
for (auto& tensor_id : nodes_[op_index].outputs) {
if (functor(tensor_id)) {
AddUsage(tensor_id, op_index, usages);
}
}
}
for (ValueId out_id : output_ids_) {
if (functor(out_id)) {
AddUsage(out_id, nodes_.size(), usages);
}
}
}
absl::Status InferenceContext::AllocateMemoryForConstTensors(
MetalDevice* device) {
for (auto& description : const_tensors_descs_) {
RETURN_IF_ERROR(const_tensors_[description.first].CreateFromDescriptor(
description.second, device->device()));
}
const_tensors_descs_.clear();
return absl::OkStatus();
}
absl::Status InferenceContext::AllocateMemoryForBuffers(MetalDevice* device) {
std::map<ValueId, int2> buffer_usages;
GetUsages(
[this, device](ValueId id) {
return GetTensorMemoryType(device->GetInfo(), id) ==
TensorMemoryType::kBuffer;
},
&buffer_usages);
if (buffer_usages.empty()) {
return absl::OkStatus();
}
// From Apple documentation:
// For buffers in the device address space, align the offset to the data type
// consumed by the compute function (which is always less than or equal to 16
// bytes).
// For buffers in the constant address space, align the offset to 256
// bytes in macOS. In iOS, align the offset to the maximum of either the data
// type consumed by the compute function, or 4 bytes. A 16-byte alignment is
// safe in iOS if you don't need to consider the data type.
#if defined(TARGET_IOS) || defined(TARGET_TVOS)
const size_t kConstAlignment = 16;
#elif defined(TARGET_MACOS)
const size_t kConstAlignment = 256;
#else
const size_t kConstAlignment = 256;
#endif
size_t min_common_alignment = kConstAlignment;
std::vector<TensorUsageRecord<size_t>> buffer_usage_records;
for (auto& usage : buffer_usages) {
const auto& t = tensors_descs_[usage.first];
const auto& shape = t.GetBHWDCShape();
const auto& descriptor = t;
const size_t element_size = SizeOf(descriptor.GetDataType());
size_t buffer_size;
size_t row_bytes_alignment = [device->device()
minimumLinearTextureAlignmentForPixelFormat:DataTypeToRGBAPixelFormat(
descriptor
.GetDataType(),
false)];
if (descriptor.GetStorageType() == TensorStorageType::TEXTURE_2D) {
min_common_alignment =
std::lcm(min_common_alignment, row_bytes_alignment);
const size_t bytes_per_row = element_size * shape.b * shape.w * 4;
const size_t height = shape.h * DivideRoundUp(shape.c, 4);
buffer_size = AlignByN(bytes_per_row, row_bytes_alignment) * height;
} else if (descriptor.GetStorageType() ==
TensorStorageType::SINGLE_TEXTURE_2D) {
min_common_alignment =
std::lcm(min_common_alignment, row_bytes_alignment);
const size_t bytes_per_row = element_size * shape.b * shape.w * shape.c;
const size_t height = shape.h;
buffer_size = AlignByN(bytes_per_row, row_bytes_alignment) * height;
} else {
buffer_size =
shape.b * shape.w * shape.h * AlignByN(shape.c, 4) * element_size;
}
graph_ids_to_shared_buffer_tensors_[usage.first] =
buffer_usage_records.size();
buffer_usage_records.push_back({buffer_size,
static_cast<TaskId>(usage.second.x),
static_cast<TaskId>(usage.second.y)});
}
ObjectsAssignment<size_t> buffer_assignment;
RETURN_IF_ERROR(AssignObjectsToTensors(
buffer_usage_records, MemoryStrategy::GREEDY_BEST, &buffer_assignment));
OffsetsAssignment offset_assignment;
RETURN_IF_ERROR(AssignOffsetsToTensors(
buffer_usage_records, MemoryStrategy::GREEDY_BY_SIZE, &offset_assignment,
min_common_alignment));
bool use_offset_assignment = false;
if (offset_assignment.total_size <= TotalSize(buffer_assignment) &&
offset_assignment.total_size <= device->GetInfo().GetMaxBufferSize()) {
use_offset_assignment = true;
}
if (use_offset_assignment) {
shared_buffers_.resize(1);
shared_buffers_[0] =
[device->device() newBufferWithLength:offset_assignment.total_size
options:MTLResourceStorageModeShared];
} else {
shared_buffers_.resize(buffer_assignment.object_sizes.size());
for (int i = 0; i < buffer_assignment.object_sizes.size(); ++i) {
// Initialize metal buffer
NSUInteger bufferSize = buffer_assignment.object_sizes[i];
if (bufferSize > device->GetInfo().GetMaxBufferSize()) {
std::string error("Tensor id: ");
error += std::to_string(buffer_assignment.object_ids[i]) +
" with size: " + std::to_string(bufferSize) +
" exceeds MTLDevice maxBufferLength: " +
std::to_string(device->GetInfo().GetMaxBufferSize());
return absl::ResourceExhaustedError(error);
}
shared_buffers_[i] =
[device->device() newBufferWithLength:bufferSize
options:MTLResourceStorageModeShared];
}
}
std::vector<bool> created_tensors(buffer_usage_records.size(), false);
shared_buffer_tensors_.resize(buffer_usage_records.size());
for (auto& node : nodes_) {
std::vector<ValueId> all_ids = node.inputs;
all_ids.insert(all_ids.end(), node.outputs.begin(), node.outputs.end());
for (auto& tensor_id : all_ids) {
if (GetTensorMemoryType(device->GetInfo(), tensor_id) !=
TensorMemoryType::kBuffer) {
continue;
}
const int tensor_index = graph_ids_to_shared_buffer_tensors_[tensor_id];
if (created_tensors[tensor_index]) continue;
const auto& tensor_dummy = tensors_descs_[tensor_id];
const int buffer_index = buffer_assignment.object_ids[tensor_index];
uint64_t base_buffer_offset = 0;
id<MTLBuffer> base_buffer;
if (use_offset_assignment) {
base_buffer = shared_buffers_[0];
base_buffer_offset = offset_assignment.offsets[tensor_index];
} else {
base_buffer = shared_buffers_[buffer_index];
base_buffer_offset = 0;
}
if (tensor_dummy.GetStorageType() == TensorStorageType::TEXTURE_2D ||
tensor_dummy.GetStorageType() ==
TensorStorageType::SINGLE_TEXTURE_2D) {
size_t row_bytes_alignment = [device->device()
minimumLinearTextureAlignmentForPixelFormat:
DataTypeToRGBAPixelFormat(tensor_dummy.GetDataType(), false)];
RETURN_IF_ERROR(CreateTensorSharedImage2DBuffer(
base_buffer, tensor_dummy, row_bytes_alignment,
&shared_buffer_tensors_[tensor_index], base_buffer_offset));
} else {
RETURN_IF_ERROR(CreateTensorSharedBuffer(
base_buffer, tensor_dummy, &shared_buffer_tensors_[tensor_index],
base_buffer_offset));
}
created_tensors[tensor_index] = true;
}
}
return absl::OkStatus();
}
absl::Status InferenceContext::AllocateMemoryForStrongShapes(
MetalDevice* device) {
std::map<ValueId, int2> usages;
GetUsages(
[this, device](ValueId id) {
return GetTensorMemoryType(device->GetInfo(), id) ==
TensorMemoryType::kStrongShape;
},
&usages);
struct TensorDescComparator {
TensorDescriptor tensor_desc;
bool operator==(const TensorDescComparator& t) const {
return tensor_desc == t.tensor_desc &&
tensor_desc.GetBHWDCShape() == t.tensor_desc.GetBHWDCShape();
}
};
std::vector<TensorUsageRecord<TensorDescComparator>> usage_records;
std::map<ValueId, ValueId> remap_from_graph_ids;
for (auto& usage : usages) {
remap_from_graph_ids[usage.first] = usage_records.size();
usage_records.push_back({{tensors_descs_[usage.first]},
static_cast<TaskId>(usage.second.x),
static_cast<TaskId>(usage.second.y)});
}
ObjectsAssignment<TensorDescComparator> assignment;
RETURN_IF_ERROR(AssignObjectsToTensors(
usage_records, MemoryStrategy::EQUALITY, &assignment));
for (auto& node : nodes_) {
std::vector<ValueId> all_ids = node.inputs;
all_ids.insert(all_ids.end(), node.outputs.begin(), node.outputs.end());
for (auto& tensor_id : all_ids) {
const auto& tensor_dummy = tensors_descs_[tensor_id];
if (GetTensorMemoryType(device->GetInfo(), tensor_id) !=
TensorMemoryType::kStrongShape) {
continue;
}
const auto id = assignment.object_ids[remap_from_graph_ids[tensor_id]];
graph_ids_to_strong_shape_tensors_[tensor_id] = id;
const auto& it = strong_shape_tensors_.find(id);
if (it == strong_shape_tensors_.end()) {
RETURN_IF_ERROR(CreateTensor(device->device(), tensor_dummy,
&strong_shape_tensors_[id]));
}
}
}
return absl::OkStatus();
}
absl::Status InferenceContext::Tune(TuningType tuning_type,
MetalDevice* device) {
for (auto& node : nodes_) {
RETURN_IF_ERROR(node.task.Tune(tuning_type, device));
}
return absl::OkStatus();
}
void InferenceContext::EncodeWithEncoder(
id<MTLComputeCommandEncoder> command_encoder) {
for (int i = 0; i < nodes_.size(); ++i) {
auto& task = nodes_[i].task;
task.Encode(command_encoder);
}
}
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void InferenceContext::AddResources(
id<MTLComputeCommandEncoder> command_encoder) {
for (int i = 0; i < nodes_.size(); ++i) {
auto& task = nodes_[i].task;
task.AddResourcesToEncoder(command_encoder);
}
}
API_AVAILABLE(ios(13.0), macos(11.00), tvos(13.0))
void InferenceContext::EncodeWithICB(
id<MTLComputeCommandEncoder> command_encoder) {
[command_encoder executeCommandsInBuffer:icb_
withRange:NSMakeRange(0, nodes_.size())];
}
void InferenceContext::Profile(id<MTLDevice> device, ProfilingInfo* result) {
result->dispatches.resize(nodes_.size());
id<MTLCommandQueue> command_queue = [device newCommandQueue];
for (int k = 0; k < nodes_.size(); ++k) {
@autoreleasepool {
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder =
[command_buffer computeCommandEncoder];
auto& task = nodes_[k].task;
const int kRuns = 500;
for (int i = 0; i < kRuns; ++i) {
task.Encode(encoder);
}
[encoder endEncoding];
auto start = absl::Now();
[command_buffer commit];
[command_buffer waitUntilCompleted];
auto end = absl::Now();
auto& dispatch_info = result->dispatches[k];
dispatch_info.label = nodes_[k].name;
dispatch_info.duration = (end - start) / static_cast<float>(kRuns);
uint64_t read_size = 0;
for (auto& src_id : nodes_[k].inputs) {
read_size += GetTensor(src_id)->GetMemorySizeInBytes();
}
const auto& gpu_op = nodes_[k].task.GetGpuOperation();
read_size += gpu_op.const_args_size_;
uint64_t write_size = 0;
for (auto& dst_id : nodes_[k].outputs) {
write_size += GetTensor(dst_id)->GetMemorySizeInBytes();
}
dispatch_info.flops = gpu_op.flops_;
dispatch_info.read_mem_size = read_size;
dispatch_info.write_mem_size = write_size;
}
}
}
uint64_t InferenceContext::GetIntermediateTensorsSize() const {
uint64_t total_memory = 0;
for (const auto& t : strong_shape_tensors_) {
total_memory += t.second.GetMemorySizeInBytes();
}
for (const auto& b : shared_buffers_) {
total_memory += [b length];
}
return total_memory;
}
uint64_t InferenceContext::GetConstantTensorsSize() const {
uint64_t total_size = 0;
for (const auto& node : nodes_) {
total_size += node.task.GetGpuOperation().const_args_size_;
}
for (const auto& t : const_tensors_) {
total_size += t.second.GetMemorySizeInBytes();
}
return total_size;
}
void InferenceContext::EncodeWithCommandBuffer(
id<MTLCommandBuffer> command_buffer) {
for (int i = 0; i < nodes_.size(); ++i) {
id<MTLComputeCommandEncoder> encoder =
[command_buffer computeCommandEncoder];
auto& task = nodes_[i].task;
task.Encode(encoder);
[encoder endEncoding];
}
}
void InferenceContext::EncodeWithCommandQueue(id<MTLCommandQueue> command_queue,
int flush_period) {
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
for (int i = 0; i < nodes_.size(); ++i) {
id<MTLComputeCommandEncoder> encoder =
[command_buffer computeCommandEncoder];
auto& task = nodes_[i].task;
task.Encode(encoder);
[encoder endEncoding];
if (i % flush_period == (flush_period - 1)) {
[command_buffer commit];
command_buffer = [command_queue commandBuffer];
}
}
[command_buffer commit];
}
absl::Status InferenceContext::SetTensor(const ValueId& tensor_id,
MetalSpatialTensor* tensor_ptr) {
auto it = external_mutable_tensors_.find(tensor_id);
if (it == external_mutable_tensors_.end()) {
return absl::InvalidArgumentError("No external tensor with this id.");
}
external_mutable_tensors_[tensor_id] = tensor_ptr;
for (int node_index : external_tensor_to_nodes_[tensor_id]) {
auto& node = nodes_[node_index];
for (int i = 0; i < node.inputs.size(); ++i) {
if (node.inputs[i] == tensor_id) {
node.task.SetSrcTensor(tensor_ptr, i);
}
}
for (int i = 0; i < node.outputs.size(); ++i) {
if (node.outputs[i] == tensor_id) {
node.task.SetDstTensor(tensor_ptr, i);
}
}
}
return absl::OkStatus();
}
void InferenceContext::PrepareExternal() {
for (auto& external : external_mutable_tensors_) {
for (int i = 0; i < nodes_.size(); ++i) {
bool has_tensor = false;
const auto& src_ids = nodes_[i].inputs;
for (int i = 0; i < src_ids.size(); ++i) {
if (src_ids[i] == external.first) {
has_tensor = true;
}
}
const auto& dst_ids = nodes_[i].outputs;
for (int i = 0; i < dst_ids.size(); ++i) {
if (dst_ids[i] == external.first) {
has_tensor = true;
}
}
if (has_tensor) {
external_tensor_to_nodes_[external.first].push_back(i);
}
}
}
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,913 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load(
"//tensorflow/core/platform:build_config_root.bzl",
"tf_gpu_tests_tags",
)
load(
"//tensorflow/lite:special_rules.bzl",
"tflite_ios_lab_runner",
"tflite_ios_per_kernel_test",
"tflite_portable_test_suite",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
objc_library(
name = "add_test_lib",
testonly = 1,
srcs = ["add_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:add_test_util",
],
)
ios_unit_test(
name = "add_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":add_test_lib"],
)
objc_library(
name = "cast_test_lib",
testonly = 1,
srcs = ["cast_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:cast_test_util",
],
)
ios_unit_test(
name = "cast_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":cast_test_lib"],
)
objc_library(
name = "concat_test_lib",
testonly = 1,
srcs = ["concat_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:concat_test_util",
],
)
ios_unit_test(
name = "concat_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":concat_test_lib"],
)
objc_library(
name = "conv_test_lib",
testonly = 1,
srcs = ["conv_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//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:util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_constants_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_generic",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_generic_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_metal_simd",
"//tensorflow/lite/delegates/gpu/common/tasks:winograd",
"//tensorflow/lite/delegates/gpu/metal:compute_task",
"//tensorflow/lite/delegates/gpu/metal:metal_spatial_tensor",
],
)
ios_unit_test(
name = "conv_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
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 = "15.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 = "cumsum_test_lib",
testonly = 1,
srcs = ["cumsum_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:cumsum_test_util",
],
)
ios_unit_test(
name = "cumsum_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":cumsum_test_lib"],
)
objc_library(
name = "depthwise_conv_test_lib",
testonly = 1,
srcs = ["depthwise_conv_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_3x3_stride_h2_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_3x3_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_test_util",
],
)
ios_unit_test(
name = "depthwise_conv_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":depthwise_conv_test_lib"],
)
objc_library(
name = "elementwise_test_lib",
testonly = 1,
srcs = ["elementwise_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:elementwise_test_util",
],
)
ios_unit_test(
name = "elementwise_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":elementwise_test_lib"],
)
objc_library(
name = "fully_connected_test_lib",
testonly = 1,
srcs = ["fully_connected_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:fully_connected_test_util",
],
)
ios_unit_test(
name = "fully_connected_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":fully_connected_test_lib"],
)
objc_library(
name = "gather_test_lib",
testonly = 1,
srcs = ["gather_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:gather_test_util",
],
)
ios_unit_test(
name = "gather_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":gather_test_lib"],
)
objc_library(
name = "lstm_test_lib",
testonly = 1,
srcs = ["lstm_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:lstm_test_util",
],
)
ios_unit_test(
name = "lstm_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":lstm_test_lib"],
)
objc_library(
name = "max_unpooling_test_lib",
testonly = 1,
srcs = ["max_unpooling_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:max_unpooling_test_util",
],
)
ios_unit_test(
name = "max_unpooling_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":max_unpooling_test_lib"],
)
objc_library(
name = "mean_stddev_normalization_test_lib",
testonly = 1,
srcs = ["mean_stddev_normalization_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:mean_stddev_normalization_test_util",
],
)
ios_unit_test(
name = "mean_stddev_normalization_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":mean_stddev_normalization_test_lib"],
)
objc_library(
name = "one_hot_test_lib",
testonly = 1,
srcs = ["one_hot_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:one_hot_test_util",
],
)
ios_unit_test(
name = "one_hot_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":one_hot_test_lib"],
)
objc_library(
name = "padding_test_lib",
testonly = 1,
srcs = ["padding_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:padding_test_util",
],
)
ios_unit_test(
name = "padding_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":padding_test_lib"],
)
objc_library(
name = "pooling_test_lib",
testonly = 1,
srcs = ["pooling_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:pooling_test_util",
],
)
ios_unit_test(
name = "pooling_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":pooling_test_lib"],
)
objc_library(
name = "prelu_test_lib",
testonly = 1,
srcs = ["prelu_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:prelu_test_util",
],
)
ios_unit_test(
name = "prelu_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":prelu_test_lib"],
)
objc_library(
name = "quantize_and_dequantize_test_lib",
testonly = 1,
srcs = ["quantize_and_dequantize_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//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:util",
"//tensorflow/lite/delegates/gpu/common/tasks:quantize_and_dequantize_test_util",
],
)
ios_unit_test(
name = "quantize_and_dequantize_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":quantize_and_dequantize_test_lib"],
)
objc_library(
name = "reduce_test_lib",
testonly = 1,
srcs = ["reduce_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:reduce_test_util",
],
)
ios_unit_test(
name = "reduce_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":reduce_test_lib"],
)
objc_library(
name = "relu_test_lib",
testonly = 1,
srcs = ["relu_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:relu_test_util",
],
)
ios_unit_test(
name = "relu_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":relu_test_lib"],
)
objc_library(
name = "resampler_test_lib",
testonly = 1,
srcs = ["resampler_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
"//tensorflow/lite/delegates/gpu/common:shape",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:resampler_test_util",
"//tensorflow/lite/delegates/gpu/metal/kernels:test_util",
],
)
ios_unit_test(
name = "resampler_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = [
"no_mac", # TODO(b/183905399)
"notap",
"tflite_mobile_test_jet",
"tflite_not_portable_android",
],
deps = [":resampler_test_lib"],
)
objc_library(
name = "resize_test_lib",
testonly = 1,
srcs = ["resize_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:resize_test_util",
],
)
ios_unit_test(
name = "resize_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":resize_test_lib"],
)
objc_library(
name = "reshape_test_lib",
testonly = 1,
srcs = ["reshape_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:reshape_test_util",
],
)
ios_unit_test(
name = "reshape_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":reshape_test_lib"],
)
objc_library(
name = "select_v2_test_lib",
testonly = 1,
srcs = ["select_v2_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:select_v2_test_util",
],
)
ios_unit_test(
name = "select_v2_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":select_v2_test_lib"],
)
objc_library(
name = "slice_test_lib",
testonly = 1,
srcs = ["slice_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:strided_slice_test_util",
],
)
ios_unit_test(
name = "slice_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":slice_test_lib"],
)
objc_library(
name = "softmax_test_lib",
testonly = 1,
srcs = ["softmax_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:softmax_test_util",
],
)
ios_unit_test(
name = "softmax_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":softmax_test_lib"],
)
objc_library(
name = "space_to_depth_test_lib",
testonly = 1,
srcs = ["space_to_depth_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common/tasks:space_to_depth_test_util",
],
)
ios_unit_test(
name = "space_to_depth_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":space_to_depth_test_lib"],
)
objc_library(
name = "split_test_lib",
testonly = 1,
srcs = ["split_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:split_test_util",
],
)
ios_unit_test(
name = "split_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":split_test_lib"],
)
objc_library(
name = "tile_test_lib",
testonly = 1,
srcs = ["tile_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:tile_test_util",
],
)
ios_unit_test(
name = "tile_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":tile_test_lib"],
)
objc_library(
name = "transpose_conv_test_lib",
testonly = 1,
srcs = ["transpose_conv_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_3x3_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_3x3_thin_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:convolution_transposed_thin_test_util",
],
)
ios_unit_test(
name = "transpose_conv_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":transpose_conv_test_lib"],
)
objc_library(
name = "transpose_test_lib",
testonly = 1,
srcs = ["transpose_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:transpose_test_util",
],
)
ios_unit_test(
name = "transpose_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":transpose_test_lib"],
)
objc_library(
name = "test_util",
testonly = 1,
srcs = [
"test_util.cc",
],
hdrs = ["test_util.h"],
copts = [
"-ObjC++",
],
sdk_frameworks = ["Metal"],
deps = [
"//tensorflow/lite/delegates/gpu/common:convert",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:precision",
"//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/common/task:gpu_operation",
"//tensorflow/lite/delegates/gpu/common/task:testing_util",
"//tensorflow/lite/delegates/gpu/metal:compute_task",
"//tensorflow/lite/delegates/gpu/metal:metal_device",
"//tensorflow/lite/delegates/gpu/metal:metal_spatial_tensor",
"@com_google_absl//absl/strings",
],
)
objc_library(
name = "winograd_test_lib",
testonly = 1,
srcs = ["winograd_test.mm"],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//tensorflow/lite/delegates/gpu/common:winograd_util",
"//tensorflow/lite/delegates/gpu/common/tasks:winograd_test_util",
],
)
ios_unit_test(
name = "winograd_test",
testonly = 1,
minimum_os_version = "15.0",
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = tf_gpu_tests_tags() + [
"notap",
"tflite_not_portable_android",
],
deps = [":winograd_test_lib"],
)
objc_library(
name = "kernel_tests_lib",
testonly = 1,
srcs = [
"add_test.mm",
"cast_test.mm",
"concat_test.mm",
"conv_test.mm",
"conv_weights_converter_test.mm",
"cumsum_test.mm",
"depthwise_conv_test.mm",
"elementwise_test.mm",
"fully_connected_test.mm",
"gather_test.mm",
"lstm_test.mm",
"max_unpooling_test.mm",
"mean_stddev_normalization_test.mm",
"one_hot_test.mm",
"padding_test.mm",
"pooling_test.mm",
"prelu_test.mm",
"quantize_and_dequantize_test.mm",
"reduce_test.mm",
"relu_test.mm",
"resampler_test.mm",
"reshape_test.mm",
"resize_test.mm",
"select_v2_test.mm",
"slice_test.mm",
"softmax_test.mm",
"space_to_depth_test.mm",
"split_test.mm",
"tile_test.mm",
"transpose_conv_test.mm",
"transpose_test.mm",
"winograd_test.mm",
],
hdrs = [
],
sdk_frameworks = ["XCTest"],
deps = [
":test_util",
"//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:util",
"//tensorflow/lite/delegates/gpu/common/tasks:add_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:cast_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:concat_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_constants_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_generic",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_generic_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_metal_simd",
"//tensorflow/lite/delegates/gpu/common/tasks:conv_weights_converter_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_3x3_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:convolution_transposed_3x3_thin_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:convolution_transposed_thin_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:cumsum_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_3x3_stride_h2_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_3x3_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:depthwise_conv_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:elementwise_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:fully_connected_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:gather_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:lstm_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:max_unpooling_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:mean_stddev_normalization_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:one_hot_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:padding_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:pooling_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:prelu_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:quantize_and_dequantize_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:reduce_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:relu_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:resampler_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:reshape_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:resize_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:select_v2_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:softmax_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:space_to_depth_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:split_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:strided_slice_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:tile_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:transpose_test_util",
"//tensorflow/lite/delegates/gpu/common/tasks:winograd",
"//tensorflow/lite/delegates/gpu/common/tasks:winograd_test_util",
"//tensorflow/lite/delegates/gpu/metal:compute_task",
"//tensorflow/lite/delegates/gpu/metal:metal_spatial_tensor",
],
)
tflite_portable_test_suite()
tflite_ios_per_kernel_test()
@@ -0,0 +1,44 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/add_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface AddTest : XCTestCase
@end
@implementation AddTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testAddTwoEqualTensors {
auto status = AddTwoEqualTensorsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testAddFirstTensorHasMoreChannelsThanSecond {
auto status = AddFirstTensorHasMoreChannelsThanSecondTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testAddFirstTensorHasLessChannelsThanSecond {
auto status = AddFirstTensorHasLessChannelsThanSecond(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,44 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/cast_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface CastMetalTest : XCTestCase
@end
@implementation CastMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testCast {
auto status = CastTests(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFromBoolCast {
auto status = CastFromBoolTests(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testToBoolCast {
auto status = CastToBoolTests(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/concat_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ConcatTest : XCTestCase
@end
@implementation ConcatTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testConcatWidth {
auto status = ConcatWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConcatHeight {
auto status = ConcatHeightTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConcatChannels {
auto status = ConcatChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConcatChannelsAlignedx4 {
auto status = ConcatChannelsAlignedx4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,445 @@
/* 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/metal/compute_task.h"
#import <XCTest/XCTest.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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/tasks/conv_constants_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/conv_generic.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/conv_generic_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/conv_metal_simd.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/winograd.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
@interface ConvTest : XCTestCase
@end
@implementation ConvTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
namespace tflite {
namespace gpu {
namespace metal {
absl::Status Winograd4x4To6x6Test(TestExecutionEnvironment* env) {
const int src_channels = 7;
const int dst_channels = 13;
Convolution2DAttributes attr;
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(10, 10);
attr.strides = HW(1, 1);
attr.dilations = HW(1, 1);
attr.weights.shape = OHWI(dst_channels, 3, 3, src_channels);
attr.weights.data.resize(attr.weights.shape.DimensionsProduct());
for (int i = 0; i < attr.weights.data.size(); ++i) {
attr.weights.data[i] = sin(i);
}
attr.bias.shape = Linear(dst_channels);
attr.bias.data.resize(attr.bias.shape.DimensionsProduct());
for (int i = 0; i < attr.bias.data.size(); ++i) {
attr.bias.data[i] = sin(i);
}
auto src_shape = BHWC(1, 17, 13, src_channels);
auto dst_shape = CalculateOutputShape(src_shape, attr);
int new_width = src_shape.w + attr.padding.prepended.w + attr.padding.appended.w - 2;
int new_height = src_shape.h + attr.padding.prepended.h + attr.padding.appended.h - 2;
BHWC conv_shape;
conv_shape.b = dst_shape.b;
conv_shape.h = 36;
conv_shape.w = DivideRoundUp(new_width, 4) * DivideRoundUp(new_height, 4);
conv_shape.c = dst_shape.c;
TensorFloat32 src_tensor;
src_tensor.shape = src_shape;
src_tensor.data.resize(src_tensor.shape.DimensionsProduct());
for (int i = 0; i < src_tensor.data.size(); ++i) {
src_tensor.data[i] = sin(i);
}
for (auto precision : env->GetSupportedPrecisions()) {
auto data_type = DeduceDataTypeFromPrecision(precision);
for (auto storage : env->GetSupportedStorages(data_type)) {
const float eps = precision == CalculationsPrecision::F32 ? 1e-4f : 0.4f;
OperationDef op_def;
op_def.precision = precision;
op_def.src_tensors.push_back({data_type, storage, Layout::HWC});
op_def.dst_tensors.push_back({data_type, storage, Layout::HWC});
TensorFloat32 output0;
auto gpu_op0 = CreateConvGeneric(env->GetGpuInfo(), op_def, attr, &dst_shape);
auto op0_ptr = std::make_unique<ConvGeneric>(std::move(gpu_op0));
RETURN_IF_ERROR(
env->ExecuteGPUOperation(src_tensor, std::move(op0_ptr), dst_shape, &output0));
auto gpu_op1 = CreateWinograd4x4To36(op_def, attr.padding, env->GetGpuInfo());
std::unique_ptr<GPUOperation> op1_ptr = std::make_unique<Winograd4x4To36>(std::move(gpu_op1));
auto gpu_op2 = CreateConvGenericWino4x4To6x6(env->GetGpuInfo(), op_def, attr, &conv_shape);
auto op2_ptr = std::make_unique<ConvGeneric>(std::move(gpu_op2));
auto gpu_op3 = CreateWinograd36To4x4(op_def, attr.bias);
std::unique_ptr<GPUOperation> op3_ptr = std::make_unique<Winograd36To4x4>(std::move(gpu_op3));
TensorFloat32 output1;
BHWC output1_shape = conv_shape;
output1_shape.c = src_shape.c;
RETURN_IF_ERROR(
env->ExecuteGPUOperation(src_tensor, std::move(op1_ptr), output1_shape, &output1));
TensorFloat32 output2;
BHWC output2_shape = conv_shape;
RETURN_IF_ERROR(
env->ExecuteGPUOperation(output1, std::move(op2_ptr), output2_shape, &output2));
TensorFloat32 output3;
BHWC output3_shape = dst_shape;
RETURN_IF_ERROR(
env->ExecuteGPUOperation(output2, std::move(op3_ptr), output3_shape, &output3));
RETURN_IF_ERROR(PointWiseNear(output0.data, output3.data, eps))
<< "Failed using precision " << ToString(precision);
}
}
return absl::OkStatus();
}
absl::Status ConvolutionGroupedTest(TestExecutionEnvironment* env) {
TensorFloat32 src_tensor;
src_tensor.shape = BHWC(1, 1, 1, 8);
src_tensor.data = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f};
Convolution2DAttributes attr;
attr.groups = 2;
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
attr.dilations = HW(1, 1);
attr.weights.shape = OHWI(8, 1, 1, 4);
attr.weights.data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f,
12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, 19.0f, 20.0f, 21.0f, 22.0f,
23.0f, 24.0f, 25.0f, 26.0f, 27.0f, 28.0f, 29.0f, 30.0f, 31.0f, 32.0f};
attr.bias.shape = Linear(8);
attr.bias.data = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
for (auto precision : env->GetSupportedPrecisions()) {
auto data_type = DeduceDataTypeFromPrecision(precision);
for (auto storage : env->GetSupportedStorages(data_type)) {
const float eps = precision == CalculationsPrecision::F32 ? 1e-6f : 1e-3f;
OperationDef op_def;
op_def.precision = precision;
op_def.src_tensors.push_back({data_type, storage, Layout::HWC});
op_def.dst_tensors.push_back({data_type, storage, Layout::HWC});
TensorFloat32 dst_tensor;
auto dst_shape = BHWC(1, 1, 1, 8);
ConvGeneric operation = CreateConvGeneric(env->GetGpuInfo(), op_def, attr, &dst_shape);
RETURN_IF_ERROR(env->ExecuteGPUOperation(
src_tensor, std::make_unique<ConvGeneric>(std::move(operation)), dst_shape, &dst_tensor));
RETURN_IF_ERROR(PointWiseNear({20.0f, 44.0f, 68.0f, 92.0f, 412.0f, 500.0f, 588.0f, 676.0f},
dst_tensor.data, eps))
<< "Failed using precision " << ToString(precision);
}
}
return absl::OkStatus();
}
absl::Status ConvolutionSimdMatrixMultiplyTest(TestExecutionEnvironment* env) {
TensorFloat32 src_tensor;
src_tensor.shape = BHWC(1, 32, 32, 1024);
const BHWC dst_shape(1, 32, 32, 1024);
src_tensor.data.resize(src_tensor.shape.DimensionsProduct());
for (int i = 0; i < src_tensor.data.size(); ++i) {
src_tensor.data[i] = sin(0.01f * i);
}
Convolution2DAttributes attr;
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
attr.dilations = HW(1, 1);
attr.weights.shape = OHWI(dst_shape.c, 1, 1, src_tensor.shape.c);
attr.weights.data.resize(attr.weights.shape.DimensionsProduct());
for (int i = 0; i < attr.weights.data.size(); ++i) {
attr.weights.data[i] = sin(0.1f * i);
}
attr.bias.shape = Linear(dst_shape.c);
attr.bias.data.resize(attr.bias.shape.DimensionsProduct());
for (int i = 0; i < attr.bias.data.size(); ++i) {
attr.bias.data[i] = sin(0.1f * i);
}
TensorFloat32 dst_tensor_ref;
{
OperationDef op_def;
op_def.precision = CalculationsPrecision::F32;
auto data_type = DeduceDataTypeFromPrecision(op_def.precision);
op_def.src_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::HWC});
op_def.dst_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::HWC});
ConvGeneric operation = CreateConvGeneric(env->GetGpuInfo(), op_def, attr, &dst_shape);
RETURN_IF_ERROR(env->ExecuteGPUOperation(src_tensor,
std::make_unique<ConvGeneric>(std::move(operation)),
dst_shape, &dst_tensor_ref));
}
for (auto precision : env->GetSupportedPrecisions()) {
auto data_type = DeduceDataTypeFromPrecision(precision);
for (auto storage : env->GetSupportedStorages(data_type)) {
const float eps = precision == CalculationsPrecision::F32 ? 4e-5f : 0.4f;
OperationDef op_def;
op_def.precision = precision;
op_def.src_tensors.push_back({data_type, storage, Layout::HWC});
op_def.dst_tensors.push_back({data_type, storage, Layout::HWC});
if (!IsConvolutionMetalSimdSupported(env->GetGpuInfo(), op_def, attr)) {
continue;
}
TensorFloat32 dst_tensor_simd;
ConvolutionMetalSimd operation_simd =
CreateConvolutionMetalSimd(op_def, dst_shape, attr, env->GetGpuInfo());
RETURN_IF_ERROR(env->ExecuteGPUOperation(
src_tensor, std::make_unique<ConvolutionMetalSimd>(std::move(operation_simd)), dst_shape,
&dst_tensor_simd));
RETURN_IF_ERROR(PointWiseNear(dst_tensor_ref.data, dst_tensor_simd.data, eps))
<< "Failed using precision " << ToString(precision);
}
}
return absl::OkStatus();
}
absl::Status ConvolutionSimdMatrixMultiplyBatchTest(TestExecutionEnvironment* env) {
TensorFloat32 src_tensor;
src_tensor.shape = BHWC(8, 8, 8, 128);
const BHWC dst_shape(8, 8, 8, 256);
src_tensor.data.resize(src_tensor.shape.DimensionsProduct());
for (int i = 0; i < src_tensor.data.size(); ++i) {
src_tensor.data[i] = sin(0.01f * i);
}
Convolution2DAttributes attr;
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
attr.dilations = HW(1, 1);
attr.weights.shape = OHWI(dst_shape.c, 1, 1, src_tensor.shape.c);
attr.weights.data.resize(attr.weights.shape.DimensionsProduct());
for (int i = 0; i < attr.weights.data.size(); ++i) {
attr.weights.data[i] = sin(0.1f * i);
}
attr.bias.shape = Linear(dst_shape.c);
attr.bias.data.resize(attr.bias.shape.DimensionsProduct());
for (int i = 0; i < attr.bias.data.size(); ++i) {
attr.bias.data[i] = sin(0.1f * i);
}
TensorFloat32 dst_tensor_ref;
{
OperationDef op_def;
op_def.precision = CalculationsPrecision::F32;
auto data_type = DeduceDataTypeFromPrecision(op_def.precision);
op_def.src_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::BHWC});
op_def.dst_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::BHWC});
ConvGeneric operation = CreateConvGeneric(env->GetGpuInfo(), op_def, attr, &dst_shape);
RETURN_IF_ERROR(env->ExecuteGPUOperation(src_tensor,
std::make_unique<ConvGeneric>(std::move(operation)),
dst_shape, &dst_tensor_ref));
}
for (auto precision : env->GetSupportedPrecisions()) {
auto data_type = DeduceDataTypeFromPrecision(precision);
for (auto storage : env->GetSupportedStorages(data_type)) {
const float eps = precision == CalculationsPrecision::F32 ? 8e-6f : 0.2f;
OperationDef op_def;
op_def.precision = precision;
op_def.src_tensors.push_back({data_type, storage, Layout::BHWC});
op_def.dst_tensors.push_back({data_type, storage, Layout::BHWC});
if (!IsConvolutionMetalSimdSupported(env->GetGpuInfo(), op_def, attr)) {
continue;
}
TensorFloat32 dst_tensor_simd;
ConvolutionMetalSimd operation_simd =
CreateConvolutionMetalSimd(op_def, dst_shape, attr, env->GetGpuInfo());
RETURN_IF_ERROR(env->ExecuteGPUOperation(
src_tensor, std::make_unique<ConvolutionMetalSimd>(std::move(operation_simd)), dst_shape,
&dst_tensor_simd));
RETURN_IF_ERROR(PointWiseNear(dst_tensor_ref.data, dst_tensor_simd.data, eps))
<< "Failed using precision " << ToString(precision);
}
}
return absl::OkStatus();
}
absl::Status ConvolutionSimdMatrixMultiplyPerfTest() {
const BHWC src_shape(1, 32, 32, 1024);
const BHWC dst_shape(1, 32, 32, 1024);
Convolution2DAttributes attr;
attr.padding.prepended = HW(0, 0);
attr.padding.appended = HW(0, 0);
attr.strides = HW(1, 1);
attr.dilations = HW(1, 1);
attr.weights.shape = OHWI(dst_shape.c, 1, 1, src_shape.c);
attr.weights.data.resize(attr.weights.shape.DimensionsProduct());
for (int i = 0; i < attr.weights.data.size(); ++i) {
attr.weights.data[i] = sin(0.1f * i);
}
attr.bias.shape = Linear(dst_shape.c);
attr.bias.data.resize(attr.bias.shape.DimensionsProduct());
for (int i = 0; i < attr.bias.data.size(); ++i) {
attr.bias.data[i] = sin(0.1f * i);
}
MetalDevice device;
OperationDef op_def;
op_def.precision = CalculationsPrecision::F32;
auto data_type = DeduceDataTypeFromPrecision(op_def.precision);
op_def.src_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::HWC});
op_def.dst_tensors.push_back({data_type, TensorStorageType::BUFFER, Layout::HWC});
ConvolutionMetalSimd operation_simd =
CreateConvolutionMetalSimd(op_def, dst_shape, attr, device.GetInfo());
auto op_ptr = std::make_unique<ConvolutionMetalSimd>(std::move(operation_simd));
MetalSpatialTensor src_gpu, dst_gpu;
TensorDescriptor descriptor_with_shape = op_def.src_tensors[0];
descriptor_with_shape.SetBHWCShape(src_shape);
RETURN_IF_ERROR(CreateTensor(device.device(), descriptor_with_shape, &src_gpu));
descriptor_with_shape = op_def.dst_tensors[0];
descriptor_with_shape.SetBHWCShape(dst_shape);
RETURN_IF_ERROR(CreateTensor(device.device(), descriptor_with_shape, &dst_gpu));
RETURN_IF_ERROR(op_ptr->AssembleCode(device.GetInfo()));
ComputeTask gpu_task;
gpu_task.Init(std::move(op_ptr));
RETURN_IF_ERROR(gpu_task.Compile(&device));
gpu_task.SetSrcTensor(&src_gpu, 0);
gpu_task.SetDstTensor(&dst_gpu, 0);
RETURN_IF_ERROR(gpu_task.UpdateParams());
const double ops_count = 2.0 * dst_shape.w * dst_shape.h * dst_shape.c * attr.weights.shape.i;
const double gops_count = ops_count * 1e-9;
id<MTLCommandQueue> command_queue = [device.device() newCommandQueue];
const int iterations = 10;
const int iteration_size = 100;
double alu_fp32_gflops_per_cu = 162.0;
if (device.GetInfo().apple_info.gpu_type == AppleGpu::kA15) {
alu_fp32_gflops_per_cu *= 2.0;
}
double alu_fp16_gflops_per_cu = 162.0 * 2.0;
double alu_gflops_per_gpu;
if (op_def.precision == CalculationsPrecision::F32) {
alu_gflops_per_gpu =
alu_fp32_gflops_per_cu * device.GetInfo().apple_info.GetComputeUnitsCount();
} else {
alu_gflops_per_gpu =
alu_fp16_gflops_per_cu * device.GetInfo().apple_info.GetComputeUnitsCount();
}
for (int i = 0; i < iterations; ++i) {
@autoreleasepool {
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
for (int j = 0; j < iteration_size; ++j) {
id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoder];
gpu_task.Encode(encoder);
[encoder endEncoding];
}
const auto start = std::chrono::high_resolution_clock::now();
[command_buffer commit];
[command_buffer waitUntilCompleted];
const auto end = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = end - start;
const double execution_time_ms = diff.count() / static_cast<double>(iteration_size) * 1000.0;
const double fps = 1000.0 / execution_time_ms;
const double pers = gops_count * fps / alu_gflops_per_gpu * 100.0;
std::cout << execution_time_ms << " ms, " << gops_count * fps << " GFLOPS(" << pers << "%)"
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace metal
} // namespace gpu
} // namespace tflite
- (void)testWinograd4x4To6x6 {
auto status = tflite::gpu::metal::Winograd4x4To6x6Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testGroupedConvolution {
auto status = tflite::gpu::metal::ConvolutionGroupedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvGeneric1x1SimpleWeights {
const auto status = ConvGeneric1x1SimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvGeneric1x1 {
const auto status = ConvGeneric1x1Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvGenericSimpleWeights {
const auto status = ConvGenericSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvGeneric {
const auto status = ConvGenericTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvGenericGrouped {
const auto status = ConvGenericGroupedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvConstantsSimpleWeights {
const auto status = ConvConstantsSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvConstants {
const auto status = ConvConstantsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvSimdMatrixMultiply {
const auto status = tflite::gpu::metal::ConvolutionSimdMatrixMultiplyTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvSimdMatrixMultiplyBatch {
const auto status = tflite::gpu::metal::ConvolutionSimdMatrixMultiplyBatchTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvSimdMatrixMultiplyPerf {
const auto status = tflite::gpu::metal::ConvolutionSimdMatrixMultiplyPerfTest();
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,59 @@
/* 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());
}
- (void)testConverterToConvWeights4xTextures {
const auto status = ConverterToConvWeights4xTexturesTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,38 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/cumsum_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface CumsumMetalTest : XCTestCase
@end
@implementation CumsumMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testCumsum {
auto status = CumsumHWCTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testCumsumBatch {
auto status = CumsumBHWCTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,61 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/depthwise_conv_3x3_stride_h2_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/depthwise_conv_3x3_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/depthwise_conv_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface DepthwiseConvMetalTest : XCTestCase
@end
@implementation DepthwiseConvMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testDepthwiseConvSimpleWeights {
auto status = DepthwiseConvSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDepthwiseConvNoMultiplier {
auto status = DepthwiseConvNoMultiplierTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDepthwiseConvMultiplier2 {
auto status = DepthwiseConvMultiplier2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDepthwiseConv3x3SimpleWeights {
auto status = DepthwiseConv3x3SimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDepthwiseConv3x3 {
auto status = DepthwiseConv3x3Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDepthWiseConv3x3StrideH2SimpleWeights {
auto status = DepthWiseConv3x3StrideH2SimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,254 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/elementwise_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ElementwiseTest : XCTestCase
@end
@implementation ElementwiseTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testAbsUnit {
auto status = AbsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testCosUnit {
auto status = CosTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testCopyUnit {
auto status = CopyTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testEluUnit {
auto status = EluTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testExpUnit {
auto status = ExpTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFloorUnit {
auto status = FloorTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFloorDivUnit {
auto status = FloorDivTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFloorModUnit {
auto status = FloorModTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testHardSwishUnit {
auto status = HardSwishTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testLogUnit {
auto status = LogTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testNegUnit {
auto status = NegTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testRsqrtUnit {
auto status = RsqrtTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSigmoidUnit {
auto status = SigmoidTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSinUnit {
auto status = SinTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSqrtUnit {
auto status = SqrtTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSquareUnit {
auto status = SquareTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTanhUnit {
auto status = TanhTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSubUnit {
auto status = SubTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSquaredDiffUnit {
auto status = SquaredDiffTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testDivUnit {
auto status = DivTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPowUnit {
auto status = PowTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testAddUnit {
auto status = AddTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumUnit {
auto status = MaximumTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumWithScalarUnit {
auto status = MaximumWithScalarTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumWithConstantLinearTensorUnit {
auto status = MaximumWithConstantLinearTensorTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumWithConstantHWCTensorUnit {
auto status = MaximumWithConstantHWCTensorTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumWithConstantHWCTensorBroadcastChannelsUnit {
auto status = MaximumWithConstantHWCTensorBroadcastChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMinimumUnit {
auto status = MinimumTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMinimumWithScalarUnit {
auto status = MinimumWithScalarTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMulUnit {
auto status = MulTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMulBroadcastHWUnit {
auto status = MulBroadcastHWTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMulBroadcastChannelsUnit {
auto status = MulBroadcastChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSubWithScalarAtFirstPositionUnit {
auto status = SubWithScalarAtFirstPositionTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testLessUnit {
auto status = LessTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testLessEqualUnit {
auto status = LessEqualTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testGreaterUnit {
auto status = GreaterTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testGreaterEqualUnit {
auto status = GreaterEqualTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testEqualUnit {
auto status = EqualTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testNotEqualUnit {
auto status = NotEqualTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testCosBroadcast {
auto status = CosBroadcastTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaximumScalarBroadcastInput {
auto status = MaximumScalarBroadcastInputTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMulLinearBroadcastInput {
auto status = MulLinearBroadcastInputTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMulBroadcastBothInputs {
auto status = MulBroadcastBothInputsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testLogicalAnd {
auto status = LogicalAndTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testLogicalAndWithConstant {
auto status = LogicalAndWithConstantTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/fully_connected_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface FullyConnectedMetalTest : XCTestCase
@end
@implementation FullyConnectedMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testFullyConnected {
auto status = tflite::gpu::FullyConnectedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFullyConnectedLarge {
auto status = FullyConnectedLargeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFullyConnectedExtraLarge {
auto status = FullyConnectedExtraLargeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testFullyConnectedInt8 {
auto status = FullyConnectedInt8Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,34 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/gather_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface GatherMetalTest : XCTestCase
@end
@implementation GatherMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testGatherWidth {
auto status = GatherWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,38 @@
/* 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/lstm_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface LSTMMetalTest : XCTestCase
@end
@implementation LSTMMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)setUp {
[super setUp];
}
- (void)testLSTM {
auto status = LstmTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/max_unpooling_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface MaxUnpoolingMetalTest : XCTestCase
@end
@implementation MaxUnpoolingMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testMaxUnpooling {
auto status = MaxUnpoolingTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,78 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/mean_stddev_normalization_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface MeanStddevNormalizationTest : XCTestCase
@end
@implementation MeanStddevNormalizationTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
// note: 100.01 is not representable in FP16 (is in FP32), so use 101.0 instead.
- (void)testMeanStddevNormSeparateBatches {
// zero mean, zero variance
auto status = MeanStddevNormSeparateBatchesTest(0.0f, 0.0f, 0.0f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// zero mean, small variance
status = MeanStddevNormSeparateBatchesTest(0.0f, 0.01f, 2.63e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// zero mean, large variance
status = MeanStddevNormSeparateBatchesTest(0.0f, 100.0f, 2.63e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// small mean, zero variance
status = MeanStddevNormSeparateBatchesTest(0.01f, 0.0f, 0.0f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// small mean, small variance
status = MeanStddevNormSeparateBatchesTest(0.01f, 0.01f, 3.57e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// small mean, large variance
status = MeanStddevNormSeparateBatchesTest(1.0f, 100.0f, 2.63e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// large mean, zero variance
status = MeanStddevNormSeparateBatchesTest(100.0f, 0.0f, 0.0f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// large mean, small variance
status = MeanStddevNormSeparateBatchesTest(100.0f, 1.0f, 2.63e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
// large mean, large variance
status = MeanStddevNormSeparateBatchesTest(100.0f, 100.0f, 2.63e-4f, &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMeanStddevNormalizationAllBatches {
auto status = MeanStddevNormalizationAllBatchesTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMeanStddevNormalizationLargeVector {
auto status = MeanStddevNormalizationLargeVectorTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,39 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/one_hot_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface OneHotMetalTest : XCTestCase
@end
@implementation OneHotMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testOneHot {
auto status = OneHotTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testOneHotBatch {
auto status = OneHotBatchTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,79 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/padding_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface PaddingTest : XCTestCase
@end
@implementation PaddingTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testPaddingAppendWidth {
auto status = PaddingAppendWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingPrependWidth {
auto status = PaddingPrependWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingAppendHeight {
auto status = PaddingAppendHeightTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingPrependHeight {
auto status = PaddingPrependHeightTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingAppendChannels {
auto status = PaddingAppendChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingPrependChannels {
auto status = PaddingPrependChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingPrependChannelsX4 {
auto status = PaddingPrependChannelsX4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingComplex {
auto status = PaddingComplexTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingReflectWidth {
auto status = PaddingReflectWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPaddingReflectChannels {
auto status = PaddingReflectChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/pooling_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface PoolingTest : XCTestCase
@end
@implementation PoolingTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testAveragePooling {
auto status = AveragePoolingTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testAveragePoolingNonEmptyPadding {
auto status = AveragePoolingNonEmptyPaddingTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaxPooling {
auto status = MaxPoolingTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testMaxPoolingIndices {
auto status = MaxPoolingIndicesTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,43 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/prelu_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface PReLUTest : XCTestCase
@end
@implementation PReLUTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)setUp {
[super setUp];
}
- (void)testPReLUAlpha {
auto status = PReLUAlphaTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testPReLUHWCAlpha {
auto status = PReLUHWCAlphaTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,49 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/quantize_and_dequantize_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface QuantizeAndDequantizeTest : XCTestCase
@end
@implementation QuantizeAndDequantizeTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testQuantAndDequant_Dim2Bits8 {
auto status = QuantAndDequant_Dim2Bits8Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testQuantAndDequant_Dim3Bits8_NegativeRange {
auto status = QuantAndDequant_Dim3Bits8_NegativeRangeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testQuantAndDequant_Dim3Bits16 {
auto status = QuantAndDequant_Dim3Bits16Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testQuantAndDequant_Dim2Bits16_NegativeRange {
auto status = QuantAndDequant_Dim2Bits16_NegativeRangeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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/reduce_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ReduceTest : XCTestCase
@end
@implementation ReduceTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testMeanHW {
auto status = MeanHWTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReduceSumChannels {
auto status = ReduceSumChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReduceProductChannels {
auto status = ReduceProductChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReduceMaxChannels {
auto status = ReduceMaxChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReduceMinChannels {
auto status = ReduceMinChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/relu_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ReLUTest : XCTestCase
@end
@implementation ReLUTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testReLUNoClipNoAlpha {
auto status = ReLUNoClipNoAlphaTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReLUClip {
auto status = ReLUClipTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReLUAlpha {
auto status = ReLUAlphaTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReLUAlphaClip {
auto status = ReLUAlphaClipTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,43 @@
/* 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 <vector>
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/resampler_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ResamplerTest : XCTestCase
@end
@implementation ResamplerTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testResamplerIdentity {
auto status = ResamplerIdentityTest(tflite::gpu::BHWC(1, 2, 2, 1), &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
status = ResamplerIdentityTest(tflite::gpu::BHWC(1, 3, 5, 3), &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
status = ResamplerIdentityTest(tflite::gpu::BHWC(1, 6, 1, 7), &exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/reshape_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ReshapeMetalTest : XCTestCase
@end
@implementation ReshapeMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testReshape {
auto status = ReshapeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testReshapex4 {
auto status = Reshapex4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,64 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/resize_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface ResizeTest : XCTestCase
@end
@implementation ResizeTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testResizeBilinearAligned {
auto status = ResizeBilinearAlignedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeBilinearNonAligned {
auto status = ResizeBilinearNonAlignedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeBilinearWithoutHalfPixel {
auto status = ResizeBilinearWithoutHalfPixelTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeBilinearWithHalfPixel {
auto status = ResizeBilinearWithHalfPixelTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeNearest {
auto status = ResizeNearestTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeNearestAlignCorners {
auto status = ResizeNearestAlignCornersTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testResizeNearestHalfPixelCenters {
auto status = ResizeNearestHalfPixelCentersTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,69 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/tasks/select_v2_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface SelectV2MetalTest : XCTestCase
@end
@implementation SelectV2MetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testSelectV2Test {
auto status = SelectV2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2BatchTest {
auto status = SelectV2BatchTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2ChannelsTest {
auto status = SelectV2ChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2ChannelsBatchTest {
auto status = SelectV2ChannelsBatchTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2BroadcastTrueTest {
auto status = SelectV2BroadcastTrueTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2BroadcastElseTest {
auto status = SelectV2BroadcastFalseTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2BroadcastBothTest {
auto status = SelectV2BroadcastBothTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSelectV2ChannelsBroadcastElseTest {
auto status = SelectV2ChannelsBroadcastFalseTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/strided_slice_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface SliceTest : XCTestCase
@end
@implementation SliceTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testStridedSlice {
auto status = StridedSliceTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/softmax_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface SoftmaxMetalTest : XCTestCase
@end
@implementation SoftmaxMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testSoftmax {
auto status = SoftmaxTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSoftmaxBigNumber {
auto status = SoftmaxBigNumberTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSoftmax1x1 {
auto status = Softmax1x1Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSoftmax1x1BigNumber {
auto status = Softmax1x1BigNumberTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,54 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/space_to_depth_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface SpaceToDepthTest : XCTestCase
@end
@implementation SpaceToDepthTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testSpaceToDepthTensorShape1x2x2x1BlockSize2 {
auto status = SpaceToDepthTensorShape1x2x2x1BlockSize2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSpaceToDepthTensorShape1x2x2x2BlockSize2 {
auto status = SpaceToDepthTensorShape1x2x2x2BlockSize2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSpaceToDepthTensorShape1x2x2x3BlockSize2 {
auto status = SpaceToDepthTensorShape1x2x2x3BlockSize2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSpaceToDepthTensorShape1x4x4x1BlockSize2 {
auto status = SpaceToDepthTensorShape1x4x4x1BlockSize2Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSpaceToDepthTensorShape1x6x6x1BlockSize3 {
auto status = SpaceToDepthTensorShape1x6x6x1BlockSize3Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,59 @@
/* 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/split_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface SplitTest : XCTestCase
@end
@implementation SplitTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testSplitChannels {
auto status = SplitChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSplitChannelsX4 {
auto status = SplitChannelsX4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSplitWidth {
auto status = SplitWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSplitHeight {
auto status = SplitHeightTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSplitBatch {
auto status = SplitBatchTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testSplitDepth {
auto status = SplitDepthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,139 @@
/* 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/metal/kernels/test_util.h"
#import <Metal/Metal.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/substitute.h"
#include "tensorflow/lite/delegates/gpu/common/convert.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/precision.h"
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/compute_task.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
namespace tflite {
namespace gpu {
namespace metal {
std::vector<CalculationsPrecision>
MetalExecutionEnvironment::GetSupportedPrecisions() const {
return {CalculationsPrecision::F32, CalculationsPrecision::F32_F16,
CalculationsPrecision::F16};
}
std::vector<TensorStorageType> MetalExecutionEnvironment::GetSupportedStorages(
DataType data_type) const {
return {TensorStorageType::BUFFER, TensorStorageType::IMAGE_BUFFER,
TensorStorageType::TEXTURE_2D, TensorStorageType::TEXTURE_3D,
TensorStorageType::TEXTURE_ARRAY};
}
absl::Status MetalExecutionEnvironment::ExecuteGpuOperationInternal(
const std::vector<TensorDescriptor*>& src_cpu,
const std::vector<TensorDescriptor*>& dst_cpu,
std::unique_ptr<GPUOperation>&& operation) {
const OperationDef& op_def = operation->GetDefinition();
std::vector<MetalSpatialTensor> src(src_cpu.size());
for (int i = 0; i < src_cpu.size(); ++i) {
RETURN_IF_ERROR(src[i].CreateFromDescriptor(*src_cpu[i], device_.device()));
operation->SetSrc(&src[i], i);
}
std::vector<MetalSpatialTensor> dst(dst_cpu.size());
for (int i = 0; i < dst_cpu.size(); ++i) {
TensorDescriptor descriptor_with_shape = op_def.dst_tensors[i];
descriptor_with_shape.SetBHWDCShape(dst_cpu[i]->GetBHWDCShape());
RETURN_IF_ERROR(
CreateTensor(device_.device(), descriptor_with_shape, &dst[i]));
operation->SetDst(&dst[i], i);
}
ComputeTask gpu_task;
gpu_task.Init(std::move(operation));
RETURN_IF_ERROR(gpu_task.Compile(&device_));
for (int i = 0; i < src_cpu.size(); ++i) {
gpu_task.SetSrcTensor(&src[i], i);
}
for (int i = 0; i < dst_cpu.size(); ++i) {
gpu_task.SetDstTensor(&dst[i], i);
}
RETURN_IF_ERROR(gpu_task.UpdateParams());
bool use_icb = false;
if (use_icb) {
if (@available(macOS 11.00, iOS 13.0, tvOS 13.0, *)) {
MTLIndirectCommandBufferDescriptor* icb_desc =
[[MTLIndirectCommandBufferDescriptor alloc] init];
icb_desc.commandTypes = MTLIndirectCommandTypeConcurrentDispatch;
icb_desc.inheritBuffers = NO;
icb_desc.inheritPipelineState = NO;
icb_desc.maxKernelBufferBindCount = 1;
id<MTLIndirectCommandBuffer> icb =
[device_.device() newIndirectCommandBufferWithDescriptor:icb_desc
maxCommandCount:1
options:0];
id<MTLIndirectComputeCommand> icb_command =
[icb indirectComputeCommandAtIndex:0];
gpu_task.EncodeToICB(icb_command);
[icb_command setBarrier];
id<MTLCommandQueue> command_queue = [device_.device() newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder =
[command_buffer computeCommandEncoder];
gpu_task.AddResourcesToEncoder(encoder);
[encoder executeCommandsInBuffer:icb withRange:NSMakeRange(0, 1)];
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
} else {
return absl::InternalError(
"Indirect compute command buffer available since ios 13");
}
} else {
id<MTLCommandQueue> command_queue = [device_.device() newCommandQueue];
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLComputeCommandEncoder> encoder =
[command_buffer computeCommandEncoder];
gpu_task.Encode(encoder);
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilCompleted];
}
for (int i = 0; i < dst_cpu.size(); ++i) {
RETURN_IF_ERROR(dst[i].ToDescriptor(dst_cpu[i], device_.device()));
}
return absl::OkStatus();
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,60 @@
/* 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_METAL_KERNELS_TEST_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_KERNELS_TEST_UTIL_H_
#import <Metal/Metal.h>
#include <map>
#include <memory>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/shape.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
#include "tensorflow/lite/delegates/gpu/common/task/testing_util.h"
#include "tensorflow/lite/delegates/gpu/common/tensor.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_device.h"
namespace tflite {
namespace gpu {
namespace metal {
class MetalExecutionEnvironment : public TestExecutionEnvironment {
public:
MetalExecutionEnvironment() = default;
~MetalExecutionEnvironment() = default;
std::vector<CalculationsPrecision> GetSupportedPrecisions() const override;
std::vector<TensorStorageType> GetSupportedStorages(
DataType data_type) const override;
const GpuInfo& GetGpuInfo() const override { return device_.GetInfo(); }
absl::Status ExecuteGpuOperationInternal(
const std::vector<TensorDescriptor*>& src_cpu,
const std::vector<TensorDescriptor*>& dst_cpu,
std::unique_ptr<GPUOperation>&& operation) override;
private:
MetalDevice device_;
};
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_KERNELS_TEST_UTIL_H_
@@ -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/tile_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface TileTest : XCTestCase
@end
@implementation TileTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testTileChannels {
auto status = TileChannelsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTileChannelsX4 {
auto status = TileChannelsX4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTileWidth {
auto status = TileWidthTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTileHeight {
auto status = TileHeightTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTileHWC {
auto status = TileHWCTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,76 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.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/tasks/convolution_transposed_3x3_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/convolution_transposed_3x3_thin_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/convolution_transposed_4x4_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/convolution_transposed_test_util.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/convolution_transposed_thin_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface TransposeConvTest : XCTestCase
@end
@implementation TransposeConvTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testConvolutionTransposedSimpleWeights {
auto status = ConvolutionTransposedSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposed {
auto status = ConvolutionTransposedTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposed4x4SimpleWeights {
auto status = ConvolutionTransposed4x4SimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposedThinSimpleWeights {
auto status = ConvolutionTransposedThinSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposedThin {
auto status = ConvolutionTransposedThinTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposed3x3ThinSimpleWeights {
auto status = ConvolutionTransposed3x3ThinSimpleWeightsTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposed3x3Thin {
auto status = ConvolutionTransposed3x3ThinTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testConvolutionTransposed3x3 {
auto status = ConvolutionTransposed3x3Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,41 @@
/* 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 <vector>
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
#include "tensorflow/lite/delegates/gpu/common/operations.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/tasks/transpose_test_util.h"
@interface TransposeMetalTest : XCTestCase
@end
@implementation TransposeMetalTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)setUp {
[super setUp];
}
- (void)testTranspose {
auto status = TransposeTest(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,52 @@
/* 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.
==============================================================================*/
#import <XCTest/XCTest.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/tasks/winograd_test_util.h"
#include "tensorflow/lite/delegates/gpu/metal/kernels/test_util.h"
@interface WinogradTest : XCTestCase
@end
@implementation WinogradTest {
tflite::gpu::metal::MetalExecutionEnvironment exec_env_;
}
- (void)testWinograd4x4To36TileX6 {
auto status = tflite::gpu::Winograd4x4To36TileX6Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testWinograd36To4x4Tile4x1 {
auto status = tflite::gpu::Winograd36To4x4Tile4x1Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testWinograd4x4To36 {
auto status = tflite::gpu::Winograd4x4To36Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testWinograd36To4x4 {
auto status = tflite::gpu::Winograd36To4x4Test(&exec_env_);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end
@@ -0,0 +1,176 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_ARGUMENTS_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_ARGUMENTS_H_
#import <Metal/Metal.h>
#include <map>
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/arguments.h"
#include "tensorflow/lite/delegates/gpu/common/task/gpu_object_desc.h"
#include "tensorflow/lite/delegates/gpu/metal/gpu_object.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_device.h"
namespace tflite {
namespace gpu {
namespace metal {
class MetalArguments : public ArgumentsBinder {
public:
MetalArguments() = default;
absl::Status Init(bool use_arguments_buffer, MetalDevice* device,
Arguments* args, std::string* code);
absl::Status Init(bool use_arguments_buffer, MetalDevice* device,
Arguments* args);
// Move only
MetalArguments(MetalArguments&& args) = default;
MetalArguments& operator=(MetalArguments&& args) = default;
MetalArguments(const MetalArguments&) = delete;
MetalArguments& operator=(const MetalArguments&) = delete;
absl::Status SetInt(const std::string& name, int value) override;
absl::Status SetFloat(const std::string& name, float value) override;
absl::Status SetHalf(const std::string& name, half value) override;
absl::Status SetObjectRef(const std::string& name, const GPUObject& object);
void Encode(id<MTLComputeCommandEncoder> encoder, int buffer_offset,
int texture_offset = 0) const;
// For usage with Argument Buffers
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void AddResourcesToEncoder(id<MTLComputeCommandEncoder> encoder) const;
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void EncodeArguments(id<MTLArgumentEncoder> arguments_encoder);
private:
// creates structure with layout:
// struct uniforms_buffer {
// int val_0;
// int val_1;
// float val_2;
// int dummy; // for alignment
// };
std::string CopyScalarArgumentsToStructWithScalarFields(
const Arguments& args, const std::string& call_prefix = "",
std::string* code = nullptr);
// creates structure with layout:
// struct uniforms_buffer {
// int4 val_0_val_1_dummy_dummy;
// float4 val_2_dummy_dummy_dummy;
// };
std::string CopyScalarArgumentsToStructWithVec4Fields(
const Arguments& args, const std::string& call_prefix = "",
std::string* code = nullptr);
absl::Status AllocateObjects(const Arguments& args, id<MTLDevice> device);
absl::Status AddObjectArgs(const GpuInfo& gpu_info, const Arguments& args);
void AddGPUResources(const std::string& name, const GPUResources& resources);
std::string GetListOfArgs(int buffer_offset, int textures_offset = 0);
std::string GetArgumentBufferStructDefinition(bool add_constants_struct);
absl::Status SetGPUResources(const std::string& name,
const GPUResourcesWithValue& resources);
void AddBuffer(const std::string& name, const GPUBufferDescriptor& desc);
void AddImage2D(const std::string& name, const GPUImage2DDescriptor& desc);
void AddImage2DArray(const std::string& name,
const GPUImage2DArrayDescriptor& desc);
void AddImage3D(const std::string& name, const GPUImage3DDescriptor& desc);
void AddImageBuffer(const std::string& name,
const GPUImageBufferDescriptor& desc);
absl::Status SetBuffer(const std::string& name, id<MTLBuffer> handle,
uint64_t offset);
absl::Status SetImage2D(const std::string& name, id<MTLTexture> handle);
absl::Status SetImage2DArray(const std::string& name, id<MTLTexture> handle);
absl::Status SetImage3D(const std::string& name, id<MTLTexture> handle);
absl::Status SetImageBuffer(const std::string& name, id<MTLTexture> handle);
absl::Status SetObjectsResources(const Arguments& args);
static constexpr char kArgsPrefix[] = "args.";
struct IntValue {
int value;
// many arguments generated automatically and not used
// to reduce amount of data transferred we adding this optimization
bool active = false;
// offset to shared storage.
uint32_t bytes_offset = -1;
};
std::map<std::string, IntValue> int_values_;
struct FloatValue {
float value;
// many arguments generated automatically and not used
// to reduce amount of data transferred we adding this optimization
bool active = false;
// offset to shared storage.
uint32_t bytes_offset = -1;
};
std::map<std::string, FloatValue> float_values_;
std::vector<uint8_t> const_data_;
struct MetalBufferDescriptor {
GPUBufferDescriptor desc;
id<MTLBuffer> handle;
uint64_t offset;
};
struct MetalImage2DDescriptor {
GPUImage2DDescriptor desc;
id<MTLTexture> handle;
};
struct MetalImage2DArrayDescriptor {
GPUImage2DArrayDescriptor desc;
id<MTLTexture> handle;
};
struct MetalImage3DDescriptor {
GPUImage3DDescriptor desc;
id<MTLTexture> handle;
};
struct MetalImageBufferDescriptor {
GPUImageBufferDescriptor desc;
id<MTLTexture> handle;
};
std::map<std::string, MetalBufferDescriptor> buffers_;
std::map<std::string, MetalImage2DDescriptor> images2d_;
std::map<std::string, MetalImage2DArrayDescriptor> image2d_arrays_;
std::map<std::string, MetalImage3DDescriptor> images3d_;
std::map<std::string, MetalImageBufferDescriptor> image_buffers_;
std::map<std::string, GPUObjectDescriptorPtr> object_refs_;
std::vector<GPUObjectPtr> objects_;
};
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_ARGUMENTS_H_
@@ -0,0 +1,752 @@
/* 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/metal/metal_arguments.h"
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/substitute.h"
#include "tensorflow/lite/delegates/gpu/common/task/util.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/buffer.h"
#include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
namespace tflite {
namespace gpu {
namespace metal {
namespace {
bool IsWordSymbol(char symbol) {
return absl::ascii_isalnum(symbol) || symbol == '_';
}
void ReplaceAllWords(const std::string& old_word, const std::string& new_word,
std::string* str) {
if (!str) {
return;
}
size_t position = str->find(old_word);
while (position != std::string::npos) {
char prev = position == 0 ? '.' : (*str)[position - 1];
char next = position + old_word.size() < str->size()
? (*str)[position + old_word.size()]
: '.';
if (IsWordSymbol(prev) || IsWordSymbol(next)) {
position = str->find(old_word, position + 1);
continue;
}
str->replace(position, old_word.size(), new_word);
position = str->find(old_word, position + new_word.size());
}
}
void AppendArgument(const std::string& arg, std::string* args) {
if (!args->empty()) {
absl::StrAppend(args, ",\n");
}
absl::StrAppend(args, arg);
}
absl::Status CreateMetalObject(id<MTLDevice> device, GPUObjectDescriptor* desc,
GPUObjectPtr* result) {
const auto* buffer_desc = dynamic_cast<const BufferDescriptor*>(desc);
if (buffer_desc) {
Buffer gpu_buffer;
RETURN_IF_ERROR(
gpu_buffer.CreateFromBufferDescriptor(*buffer_desc, device));
*result = std::make_unique<Buffer>(std::move(gpu_buffer));
return absl::OkStatus();
}
const auto* tensor_desc = dynamic_cast<const TensorDescriptor*>(desc);
if (tensor_desc) {
MetalSpatialTensor gpu_tensor;
RETURN_IF_ERROR(gpu_tensor.CreateFromDescriptor(*tensor_desc, device));
*result = std::make_unique<MetalSpatialTensor>(std::move(gpu_tensor));
return absl::OkStatus();
}
return absl::InvalidArgumentError("Unknown GPU descriptor.");
}
std::string AccessToMetalTextureAccess(AccessType access_type) {
if (access_type == AccessType::READ) {
return "access::read";
} else if (access_type == AccessType::READ_WRITE) {
return "access::read_write";
} else if (access_type == AccessType::WRITE) {
return "access::write";
} else {
return "access::unknown";
}
}
} // namespace
// Static
constexpr char MetalArguments::kArgsPrefix[];
absl::Status MetalArguments::Init(
bool use_arguments_buffer, MetalDevice* device, Arguments* args,
std::string* code) {
RETURN_IF_ERROR(AllocateObjects(*args, device->device()));
RETURN_IF_ERROR(AddObjectArgs(device->GetInfo(), *args));
args->MoveObjectRefs(&object_refs_);
std::string call_prefix = use_arguments_buffer ? "args." : "";
std::string struct_desc =
CopyScalarArgumentsToStructWithVec4Fields(*args, call_prefix, code);
RETURN_IF_ERROR(SetObjectsResources(*args));
if (!use_arguments_buffer) {
args->ResolveArgsPass(code);
}
std::string header = R"(
#include <metal_stdlib>
using namespace metal;
)";
header += struct_desc + "\n";
if (use_arguments_buffer) {
const std::string arg_buf_struct =
GetArgumentBufferStructDefinition(!struct_desc.empty());
header += arg_buf_struct + "\n";
}
*code = header + *code;
std::string arguments;
if (use_arguments_buffer) {
arguments = "device ArgBuffer& args[[buffer(0)]]";
} else {
arguments = GetListOfArgs(/*buffer_offset*/ 0);
}
const bool use_global_id = code->find("GLOBAL_ID_") != std::string::npos;
const bool use_local_id = code->find("LOCAL_ID_") != std::string::npos;
const bool use_group_id = code->find("GROUP_ID_") != std::string::npos;
const bool use_group_size = code->find("GROUP_SIZE_") != std::string::npos;
const bool use_simd_id =
code->find("SUB_GROUP_LOCAL_ID") != std::string::npos;
if (use_global_id) {
AppendArgument("uint3 reserved_gid[[thread_position_in_grid]]", &arguments);
}
if (use_local_id) {
AppendArgument("uint3 reserved_lid[[thread_position_in_threadgroup]]",
&arguments);
}
if (use_group_id) {
AppendArgument("uint3 reserved_group_id[[threadgroup_position_in_grid]]",
&arguments);
}
if (use_group_size) {
AppendArgument("uint3 reserved_group_size[[threads_per_threadgroup]]",
&arguments);
}
if (use_simd_id) {
AppendArgument("uint reserved_simd_id[[thread_index_in_simdgroup]]",
&arguments);
}
if (!use_global_id && !use_local_id && !use_group_id && !use_group_size &&
!arguments.empty()) {
arguments += ",\n";
}
*code = absl::Substitute(*code, arguments);
return absl::OkStatus();
}
absl::Status MetalArguments::Init(bool use_arguments_buffer,
MetalDevice* device, Arguments* args) {
RETURN_IF_ERROR(AllocateObjects(*args, device->device()));
RETURN_IF_ERROR(AddObjectArgs(device->GetInfo(), *args));
args->MoveObjectRefs(&object_refs_);
CopyScalarArgumentsToStructWithVec4Fields(*args);
RETURN_IF_ERROR(SetObjectsResources(*args));
return absl::OkStatus();
}
std::string MetalArguments::CopyScalarArgumentsToStructWithScalarFields(
const Arguments& args, const std::string& call_prefix, std::string* code) {
std::string struct_desc = "struct uniforms_buffer {\n";
int pos = 0;
for (auto& fvalue : args.GetFloatValues()) {
auto& new_val = float_values_[fvalue.first];
new_val.value = fvalue.second.value;
new_val.active = fvalue.second.active;
if (fvalue.second.active) {
new_val.bytes_offset = pos * 4;
pos++;
struct_desc += " float " + fvalue.first + ";\n";
ReplaceAllWords(kArgsPrefix + fvalue.first,
call_prefix + "U." + fvalue.first, code);
}
}
for (const auto& hfvalue : args.GetHalfValues()) {
auto& new_val = float_values_[hfvalue.first];
new_val.value = hfvalue.second.value;
new_val.active = hfvalue.second.active;
if (hfvalue.second.active) {
new_val.bytes_offset = pos * 4;
pos++;
struct_desc += " float " + hfvalue.first + ";\n";
ReplaceAllWords(
kArgsPrefix + hfvalue.first,
"static_cast<half>(" + call_prefix + "U." + hfvalue.first + ")",
code);
}
}
for (auto& ivalue : args.GetIntValues()) {
auto& new_val = int_values_[ivalue.first];
new_val.value = ivalue.second.value;
new_val.active = ivalue.second.active;
if (ivalue.second.active) {
new_val.bytes_offset = pos * 4;
pos++;
struct_desc += " int " + ivalue.first + ";\n";
ReplaceAllWords(kArgsPrefix + ivalue.first,
call_prefix + "U." + ivalue.first, code);
}
}
if (pos != 0) {
int aligned_pos = AlignByN(pos, 4);
for (int i = pos; i < aligned_pos; i++) {
struct_desc += " int dummy" + std::to_string(i - pos) + ";\n";
}
struct_desc += "};";
const_data_.resize(aligned_pos * 4);
for (auto& it : float_values_) {
if (it.second.active) {
float* ptr =
reinterpret_cast<float*>(&const_data_[it.second.bytes_offset]);
*ptr = it.second.value;
}
}
for (auto& it : int_values_) {
if (it.second.active) {
int32_t* ptr =
reinterpret_cast<int32_t*>(&const_data_[it.second.bytes_offset]);
*ptr = it.second.value;
}
}
} else {
struct_desc = "";
}
return struct_desc;
}
std::string MetalArguments::CopyScalarArgumentsToStructWithVec4Fields(
const Arguments& args, const std::string& call_prefix, std::string* code) {
std::string struct_desc = "struct uniforms_buffer {\n";
int pos = 0;
std::string channels[4] = {".x", ".y", ".z", ".w"};
for (auto& fvalue : args.GetFloatValues()) {
auto& new_val = float_values_[fvalue.first];
new_val.value = fvalue.second.value;
new_val.active = fvalue.second.active;
if (fvalue.second.active) {
new_val.bytes_offset = pos * 4;
if (pos % 4 == 0) {
struct_desc += " float4 cmp_float4_" + std::to_string(pos / 4) + ";\n";
}
std::string new_name = call_prefix + "U.cmp_float4_" +
std::to_string(pos / 4) + channels[pos % 4];
ReplaceAllWords(kArgsPrefix + fvalue.first, new_name, code);
pos++;
}
}
for (const auto& hfvalue : args.GetHalfValues()) {
auto& new_val = float_values_[hfvalue.first];
new_val.value = hfvalue.second.value;
new_val.active = hfvalue.second.active;
if (hfvalue.second.active) {
new_val.bytes_offset = pos * 4;
if (pos % 4 == 0) {
struct_desc += " float4 cmp_float4_" + std::to_string(pos / 4) + ";\n";
}
std::string new_name = "static_cast<half>(" + call_prefix +
"U.cmp_float4_" + std::to_string(pos / 4) +
channels[pos % 4] + ")";
ReplaceAllWords(kArgsPrefix + hfvalue.first, new_name, code);
pos++;
}
}
pos = AlignByN(pos, 4);
for (auto& ivalue : args.GetIntValues()) {
auto& new_val = int_values_[ivalue.first];
new_val.value = ivalue.second.value;
new_val.active = ivalue.second.active;
if (ivalue.second.active) {
new_val.bytes_offset = pos * 4;
if (pos % 4 == 0) {
struct_desc += " int4 cmp_int4_" + std::to_string(pos / 4) + ";\n";
}
std::string new_name = call_prefix + "U.cmp_int4_" +
std::to_string(pos / 4) + channels[pos % 4];
ReplaceAllWords(kArgsPrefix + ivalue.first, new_name, code);
pos++;
}
}
if (pos != 0) {
int aligned_pos = AlignByN(pos, 4);
struct_desc += "};";
const_data_.resize(aligned_pos * 4);
for (auto& it : float_values_) {
if (it.second.active) {
float* ptr =
reinterpret_cast<float*>(&const_data_[it.second.bytes_offset]);
*ptr = it.second.value;
}
}
for (auto& it : int_values_) {
if (it.second.active) {
int32_t* ptr =
reinterpret_cast<int32_t*>(&const_data_[it.second.bytes_offset]);
*ptr = it.second.value;
}
}
} else {
struct_desc = "";
}
return struct_desc;
}
std::string MetalArguments::GetArgumentBufferStructDefinition(
bool add_constants_struct) {
std::string result;
result = "struct ArgBuffer {\n";
int index = 0;
for (auto& t : buffers_) {
std::string mem_type = MemoryTypeToMetalType(t.second.desc.memory_type);
std::string metal_type;
if (t.second.desc.data_type == DataType::BOOL) {
metal_type = ToMetalDataType(DataType::UINT8, t.second.desc.element_size);
} else {
metal_type =
ToMetalDataType(t.second.desc.data_type, t.second.desc.element_size);
}
result += absl::StrCat(" ", mem_type, " ", metal_type, "* ", t.first,
"[[id(", index, ")]];\n");
index++;
}
for (auto& t : images2d_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
result += absl::StrCat(" texture2d<", data_type, ", ", access, "> ",
t.first, "[[id(", index, ")]];\n");
index++;
}
for (auto& t : image2d_arrays_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
result += absl::StrCat(" texture2d_array<", data_type, ", ", access, "> ",
t.first, "[[id(", index, ")]];\n");
index++;
}
for (auto& t : images3d_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
result += absl::StrCat(" texture3d<", data_type, ", ", access, "> ",
t.first, "[[id(", index, ")]];\n");
index++;
}
for (auto& t : image_buffers_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
result += absl::StrCat(" texture_buffer<", data_type, ", ", access, "> ",
t.first, "[[id(", index, ")]];\n");
index++;
}
if (add_constants_struct) {
result += " uniforms_buffer U;\n";
}
result += "};";
return result;
}
absl::Status MetalArguments::SetInt(const std::string& name, int value) {
auto it = int_values_.find(name);
if (it == int_values_.end()) {
return absl::NotFoundError(
absl::StrCat("No int argument with name - ", name));
}
it->second.value = value;
if (it->second.active) {
int32_t* ptr =
reinterpret_cast<int32_t*>(&const_data_[it->second.bytes_offset]);
*ptr = value;
}
return absl::OkStatus();
}
absl::Status MetalArguments::SetFloat(const std::string& name, float value) {
auto it = float_values_.find(name);
if (it == float_values_.end()) {
return absl::NotFoundError(
absl::StrCat("No float argument with name - ", name));
}
it->second.value = value;
if (it->second.active) {
float* ptr =
reinterpret_cast<float*>(&const_data_[it->second.bytes_offset]);
*ptr = value;
}
return absl::OkStatus();
}
absl::Status MetalArguments::SetHalf(const std::string& name, half value) {
auto it = float_values_.find(name);
if (it == float_values_.end()) {
return absl::NotFoundError(
absl::StrCat("No half argument with name - ", name));
}
it->second.value = value;
if (it->second.active) {
float* ptr =
reinterpret_cast<float*>(&const_data_[it->second.bytes_offset]);
*ptr = value;
}
return absl::OkStatus();
}
absl::Status MetalArguments::SetObjectRef(const std::string& name,
const GPUObject& object) {
auto it = object_refs_.find(name);
if (it == object_refs_.end()) {
return absl::NotFoundError(
absl::StrCat("No object ref with name - ", name));
}
GPUResourcesWithValue resources;
RETURN_IF_ERROR(object.GetGPUResources(it->second.get(), &resources));
return SetGPUResources(name, resources);
}
void MetalArguments::Encode(id<MTLComputeCommandEncoder> encoder,
int buffer_offset, int texture_offset) const {
for (auto& b : buffers_) {
[encoder setBuffer:b.second.handle
offset:b.second.offset
atIndex:buffer_offset];
buffer_offset++;
}
for (auto& image : images2d_) {
[encoder setTexture:image.second.handle atIndex:texture_offset];
texture_offset++;
}
for (auto& image : image2d_arrays_) {
[encoder setTexture:image.second.handle atIndex:texture_offset];
texture_offset++;
}
for (auto& image : images3d_) {
[encoder setTexture:image.second.handle atIndex:texture_offset];
texture_offset++;
}
for (auto& image : image_buffers_) {
[encoder setTexture:image.second.handle atIndex:texture_offset];
texture_offset++;
}
if (!const_data_.empty()) {
[encoder setBytes:const_data_.data()
length:const_data_.size()
atIndex:buffer_offset];
}
}
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void MetalArguments::AddResourcesToEncoder(
id<MTLComputeCommandEncoder> encoder) const {
for (auto& b : buffers_) {
[encoder useResource:b.second.handle
usage:MTLResourceUsageRead | MTLResourceUsageWrite];
}
for (auto& image : images2d_) {
[encoder useResource:image.second.handle
usage:MTLResourceUsageRead | MTLResourceUsageWrite];
}
for (auto& image : image2d_arrays_) {
[encoder useResource:image.second.handle
usage:MTLResourceUsageRead | MTLResourceUsageWrite];
}
for (auto& image : images3d_) {
[encoder useResource:image.second.handle
usage:MTLResourceUsageRead | MTLResourceUsageWrite];
}
for (auto& image : image_buffers_) {
[encoder useResource:image.second.handle
usage:MTLResourceUsageRead | MTLResourceUsageWrite];
}
}
API_AVAILABLE(ios(11.0), macos(10.13), tvos(11.0))
void MetalArguments::EncodeArguments(id<MTLArgumentEncoder> arguments_encoder) {
int index = 0;
for (auto& b : buffers_) {
[arguments_encoder setBuffer:b.second.handle
offset:b.second.offset
atIndex:index];
index++;
}
for (auto& image : images2d_) {
[arguments_encoder setTexture:image.second.handle atIndex:index];
index++;
}
for (auto& image : image2d_arrays_) {
[arguments_encoder setTexture:image.second.handle atIndex:index];
index++;
}
for (auto& image : images3d_) {
[arguments_encoder setTexture:image.second.handle atIndex:index];
index++;
}
for (auto& image : image_buffers_) {
[arguments_encoder setTexture:image.second.handle atIndex:index];
index++;
}
if (!const_data_.empty()) {
std::memcpy([arguments_encoder constantDataAtIndex:index],
const_data_.data(), const_data_.size());
}
}
absl::Status MetalArguments::AllocateObjects(const Arguments& args,
id<MTLDevice> device) {
objects_.resize(args.GetObjects().size());
int i = 0;
for (auto& t : args.GetObjects()) {
RETURN_IF_ERROR(CreateMetalObject(device, t.second.get(), &objects_[i]));
i++;
}
return absl::OkStatus();
}
absl::Status MetalArguments::AddObjectArgs(const GpuInfo& gpu_info,
const Arguments& args) {
for (const auto& t : args.GetObjects()) {
AddGPUResources(t.first, t.second->GetGPUResources(gpu_info));
}
for (const auto& t : args.GetObjectRefs()) {
AddGPUResources(t.first, t.second->GetGPUResources(gpu_info));
}
return absl::OkStatus();
}
std::string MetalArguments::GetListOfArgs(int buffer_offset,
int textures_offset) {
std::string result;
for (auto& t : buffers_) {
std::string metal_type;
if (t.second.desc.data_type == DataType::BOOL) {
metal_type = ToMetalDataType(DataType::UINT8, t.second.desc.element_size);
} else {
metal_type =
ToMetalDataType(t.second.desc.data_type, t.second.desc.element_size);
}
AppendArgument(
absl::StrCat(MemoryTypeToMetalType(t.second.desc.memory_type), " ",
metal_type, "* ", t.first, "[[buffer(", buffer_offset,
")]]"),
&result);
buffer_offset++;
}
for (auto& t : images2d_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
if (t.second.desc.normalized) {
data_type = ToMetalDataType(t.second.desc.normalized_type);
}
AppendArgument(absl::StrCat("texture2d<", data_type, ", ", access, "> ",
t.first, "[[texture(", textures_offset, ")]]"),
&result);
textures_offset++;
}
for (auto& t : image2d_arrays_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
AppendArgument(
absl::StrCat("texture2d_array<", data_type, ", ", access, "> ", t.first,
"[[texture(", textures_offset, ")]]"),
&result);
textures_offset++;
}
for (auto& t : images3d_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
AppendArgument(absl::StrCat("texture3d<", data_type, ", ", access, "> ",
t.first, "[[texture(", textures_offset, ")]]"),
&result);
textures_offset++;
}
for (auto& t : image_buffers_) {
std::string access = AccessToMetalTextureAccess(t.second.desc.access_type);
std::string data_type =
ToMetalDataType(ToMetalTextureType(t.second.desc.data_type));
AppendArgument(
absl::StrCat("texture_buffer<", data_type, ", ", access, "> ", t.first,
"[[texture(", textures_offset, ")]]"),
&result);
textures_offset++;
}
if (!const_data_.empty()) {
AppendArgument(absl::StrCat("constant uniforms_buffer& U[[buffer(",
buffer_offset, ")]]"),
&result);
buffer_offset++;
}
return result;
}
absl::Status MetalArguments::SetGPUResources(
const std::string& name, const GPUResourcesWithValue& resources) {
for (const auto& r : resources.generic.ints) {
RETURN_IF_ERROR(SetInt(absl::StrCat(name, "_", r.first), r.second));
}
for (const auto& r : resources.generic.floats) {
RETURN_IF_ERROR(SetFloat(absl::StrCat(name, "_", r.first), r.second));
}
for (const auto& r : resources.buffers) {
RETURN_IF_ERROR(SetBuffer(absl::StrCat(name, "_", r.first), r.second.handle,
r.second.offset));
}
for (const auto& r : resources.images2d) {
RETURN_IF_ERROR(SetImage2D(absl::StrCat(name, "_", r.first), r.second));
}
for (const auto& r : resources.image2d_arrays) {
RETURN_IF_ERROR(
SetImage2DArray(absl::StrCat(name, "_", r.first), r.second));
}
for (const auto& r : resources.images3d) {
RETURN_IF_ERROR(SetImage3D(absl::StrCat(name, "_", r.first), r.second));
}
for (const auto& r : resources.image_buffers) {
RETURN_IF_ERROR(SetImageBuffer(absl::StrCat(name, "_", r.first), r.second));
}
return absl::OkStatus();
}
void MetalArguments::AddBuffer(const std::string& name,
const GPUBufferDescriptor& desc) {
buffers_[name].desc = desc;
}
void MetalArguments::AddImage2D(const std::string& name,
const GPUImage2DDescriptor& desc) {
images2d_[name].desc = desc;
}
void MetalArguments::AddImage2DArray(const std::string& name,
const GPUImage2DArrayDescriptor& desc) {
image2d_arrays_[name].desc = desc;
}
void MetalArguments::AddImage3D(const std::string& name,
const GPUImage3DDescriptor& desc) {
images3d_[name].desc = desc;
}
void MetalArguments::AddImageBuffer(const std::string& name,
const GPUImageBufferDescriptor& desc) {
image_buffers_[name].desc = desc;
}
void MetalArguments::AddGPUResources(const std::string& name,
const GPUResources& resources) {
for (const auto& r : resources.buffers) {
AddBuffer(absl::StrCat(name, "_", r.first), r.second);
}
for (const auto& r : resources.images2d) {
AddImage2D(absl::StrCat(name, "_", r.first), r.second);
}
for (const auto& r : resources.image2d_arrays) {
AddImage2DArray(absl::StrCat(name, "_", r.first), r.second);
}
for (const auto& r : resources.images3d) {
AddImage3D(absl::StrCat(name, "_", r.first), r.second);
}
for (const auto& r : resources.image_buffers) {
AddImageBuffer(absl::StrCat(name, "_", r.first), r.second);
}
}
absl::Status MetalArguments::SetBuffer(const std::string& name,
id<MTLBuffer> handle, uint64_t offset) {
auto it = buffers_.find(name);
if (it == buffers_.end()) {
return absl::NotFoundError(
absl::StrCat("No buffer argument with name - ", name));
}
it->second.handle = handle;
it->second.offset = offset;
return absl::OkStatus();
}
absl::Status MetalArguments::SetImage2D(const std::string& name,
id<MTLTexture> handle) {
auto it = images2d_.find(name);
if (it == images2d_.end()) {
return absl::NotFoundError(
absl::StrCat("No image2d argument with name - ", name));
}
it->second.handle = handle;
return absl::OkStatus();
}
absl::Status MetalArguments::SetImage2DArray(const std::string& name,
id<MTLTexture> handle) {
auto it = image2d_arrays_.find(name);
if (it == image2d_arrays_.end()) {
return absl::NotFoundError(
absl::StrCat("No image2d array argument with name - ", name));
}
it->second.handle = handle;
return absl::OkStatus();
}
absl::Status MetalArguments::SetImage3D(const std::string& name,
id<MTLTexture> handle) {
auto it = images3d_.find(name);
if (it == images3d_.end()) {
return absl::NotFoundError(
absl::StrCat("No image3d argument with name - ", name));
}
it->second.handle = handle;
return absl::OkStatus();
}
absl::Status MetalArguments::SetImageBuffer(const std::string& name,
id<MTLTexture> handle) {
auto it = image_buffers_.find(name);
if (it == image_buffers_.end()) {
return absl::NotFoundError(
absl::StrCat("No image buffer argument with name - ", name));
}
it->second.handle = handle;
return absl::OkStatus();
}
absl::Status MetalArguments::SetObjectsResources(const Arguments& args) {
int i = 0;
for (const auto& t : args.GetObjects()) {
GPUResourcesWithValue resources;
RETURN_IF_ERROR(objects_[i]->GetGPUResources(t.second.get(), &resources));
RETURN_IF_ERROR(SetGPUResources(t.first, resources));
i++;
}
return absl::OkStatus();
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,58 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_DEVICE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_DEVICE_H_
#import <Metal/Metal.h>
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
namespace tflite {
namespace gpu {
namespace metal {
// A wrapper around metal device
class MetalDevice {
public:
MetalDevice();
MetalDevice(id<MTLDevice> device);
MetalDevice(MetalDevice&& device) = default;
MetalDevice& operator=(MetalDevice&& device) = default;
MetalDevice(const MetalDevice&) = delete;
MetalDevice& operator=(const MetalDevice&) = delete;
~MetalDevice() = default;
id<MTLDevice> device() const { return device_; }
const GpuInfo& GetInfo() const { return info_; }
bool IsLanguageVersion2orHigher() const;
private:
id<MTLDevice> device_ = nullptr;
GpuInfo info_;
};
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_DEVICE_H_
@@ -0,0 +1,109 @@
/* 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/metal/metal_device.h"
#import <sys/utsname.h>
#include <string>
namespace tflite {
namespace gpu {
namespace metal {
namespace {
GpuInfo CreateGpuInfoFromMetalDevice(id<MTLDevice> device) {
std::string device_name = std::string([[device name] UTF8String]);
GpuInfo gpu_info;
GetGpuInfoFromDeviceDescription(device_name, GpuApi::kMetal, &gpu_info);
if (gpu_info.apple_info.gpu_type == AppleGpu::kA15) {
struct utsname system_info;
uname(&system_info);
const std::string gadget_name(system_info.machine);
// iPhone 13 mini(iPhone14,4) and iPhone 13(iPhone14,5) have A15 with 4 core
// GPU.
// In general A15 GPU has 5 cores.
if (gadget_name == "iPhone14,4" || gadget_name == "iPhone14,5") {
gpu_info.apple_info.SetComputeUnits(4);
}
}
const bool family_apple1_or_2 =
gpu_info.IsApple() &&
gpu_info.apple_info.IsFamilyOrLower(AppleInfo::Family::kApple2);
gpu_info.metal_info.image2d_max_width =
family_apple1_or_2 ? 1024 * 8 : 1024 * 16;
gpu_info.metal_info.image2d_max_height =
family_apple1_or_2 ? 1024 * 8 : 1024 * 16;
gpu_info.metal_info.image_array_max_layers = 2048;
gpu_info.metal_info.image3d_max_width = 2048;
gpu_info.metal_info.image3d_max_height = 2048;
gpu_info.metal_info.image3d_max_depth = 2048;
if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
MTLSize threadsPerGroup = [device maxThreadsPerThreadgroup];
gpu_info.metal_info.max_work_group_size_x = threadsPerGroup.width;
gpu_info.metal_info.max_work_group_size_y = threadsPerGroup.height;
gpu_info.metal_info.max_work_group_size_z = threadsPerGroup.depth;
} else {
gpu_info.metal_info.max_work_group_size_x = 256;
gpu_info.metal_info.max_work_group_size_y = 256;
gpu_info.metal_info.max_work_group_size_z = 64;
}
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) {
gpu_info.metal_info.buffer_max_size = [device maxBufferLength];
} else {
// 256 MB
gpu_info.metal_info.buffer_max_size = 256 * 1024 * 1024;
}
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal2_3;
} else if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal2_2;
} else if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal2_1;
} else if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal2_0;
} else if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal1_2;
} else if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal1_1;
} else {
gpu_info.metal_info.language_version = MetalLanguageVersion::kMetal1_0;
}
return gpu_info;
}
} // namespace
MetalDevice::MetalDevice() : device_(MTLCreateSystemDefaultDevice()) {
info_ = CreateGpuInfoFromMetalDevice(device_);
}
MetalDevice::MetalDevice(id<MTLDevice> device) : device_(device) {
info_ = CreateGpuInfoFromMetalDevice(device_);
}
bool MetalDevice::IsLanguageVersion2orHigher() const {
auto version = info_.metal_info.language_version;
return version != MetalLanguageVersion::kMetal1_0 &&
version != MetalLanguageVersion::kMetal1_1 &&
version != MetalLanguageVersion::kMetal1_2;
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,128 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_SPATIAL_TENSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_SPATIAL_TENSOR_H_
#import <Metal/Metal.h>
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/gpu_tensor.h"
#include "tensorflow/lite/delegates/gpu/common/task/tensor_desc.h"
#include "tensorflow/lite/delegates/gpu/common/util.h"
#include "tensorflow/lite/delegates/gpu/metal/common.h"
#include "tensorflow/lite/delegates/gpu/metal/gpu_object.h"
namespace tflite {
namespace gpu {
namespace metal {
class MetalSpatialTensor : public GPUObject, public GpuSpatialTensor {
public:
MetalSpatialTensor()
: memory_(nullptr),
texture_mem_(nullptr),
memory_owner_(true),
texture_mem_owner_(true) {}
MetalSpatialTensor(id<MTLBuffer> buffer, id<MTLTexture> texture,
bool memory_owner, bool texture_mem_owner,
const TensorDescriptor& descriptor);
// Move only
MetalSpatialTensor(MetalSpatialTensor&& tensor);
MetalSpatialTensor& operator=(MetalSpatialTensor&& tensor);
MetalSpatialTensor(const MetalSpatialTensor&) = delete;
MetalSpatialTensor& operator=(const MetalSpatialTensor&) = delete;
~MetalSpatialTensor() override { Release(); }
absl::Status GetGPUResources(const GPUObjectDescriptor* obj_ptr,
GPUResourcesWithValue* resources) const override;
int Width() const override { return descriptor_.GetBHWDCShape().w; }
int Height() const override { return descriptor_.GetBHWDCShape().h; }
int Depth() const override { return descriptor_.GetBHWDCShape().d; }
int Channels() const override { return descriptor_.GetBHWDCShape().c; }
int Slices() const override {
return DivideRoundUp(descriptor_.GetBHWDCShape().c, 4);
}
int Batch() const override { return descriptor_.GetBHWDCShape().b; }
TensorDescriptor GetDescriptor() const override { return descriptor_; }
DataType GetDataType() const { return descriptor_.GetDataType(); }
TensorStorageType GetStorageType() const {
return descriptor_.GetStorageType();
}
uint64_t GetMemorySizeInBytes() const {
return descriptor_.GetMemorySizeInBytes();
}
absl::Status CreateFromDescriptor(const TensorDescriptor& desc,
id<MTLDevice> device);
absl::Status UploadDescriptorData(const TensorDescriptor& desc,
id<MTLDevice> device);
absl::Status ToDescriptor(TensorDescriptor* desc, id<MTLDevice> device) const;
absl::Status SetBufferHandle(id<MTLBuffer> buffer);
id<MTLBuffer> GetBufferHandle() const;
private:
friend absl::Status CreateTensorSharedBuffer(
id<MTLBuffer> buffer, const TensorDescriptor& descriptor,
MetalSpatialTensor* result, uint64_t buffer_offset);
friend absl::Status CreateTensorSharedImage2DBuffer(
id<MTLBuffer> buffer, const TensorDescriptor& descriptor,
int row_bytes_alignment, MetalSpatialTensor* result,
uint64_t buffer_offset);
absl::Status WriteData(id<MTLDevice> device, const void* ptr);
absl::Status ReadData(id<MTLDevice> device, void* ptr) const;
void Release();
id<MTLBuffer> memory_;
id<MTLTexture> texture_mem_;
bool memory_owner_;
bool texture_mem_owner_;
TensorDescriptor descriptor_;
// for use with TEXTURE_2D and when texture created from buffer.
int aligned_texture_width_;
// used when created from shared buffer
uint64_t buffer_offset_ = 0;
};
absl::Status CreateTensor(id<MTLDevice> device,
const TensorDescriptor& descriptor,
MetalSpatialTensor* result);
absl::Status CreateTensorSharedBuffer(id<MTLBuffer> buffer,
const TensorDescriptor& descriptor,
MetalSpatialTensor* result,
uint64_t buffer_offset = 0);
absl::Status CreateTensorSharedImage2DBuffer(id<MTLBuffer> buffer,
const TensorDescriptor& descriptor,
int row_bytes_alignment,
MetalSpatialTensor* result,
uint64_t buffer_offset = 0);
TensorStorageType GetFastestStorageType(const GpuInfo& gpu_info);
} // namespace metal
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_METAL_METAL_SPATIAL_TENSOR_H_
@@ -0,0 +1,422 @@
/* 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/metal/metal_spatial_tensor.h"
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/task/buffer_desc.h"
namespace tflite {
namespace gpu {
namespace metal {
namespace {
absl::Status CreateTextureBuffer(id<MTLBuffer> buffer, uint64_t buffer_offset,
const TensorDescriptor& descriptor,
id<MTLTexture>* texture) {
std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) {
const size_t data_size = storage_dims[0] * descriptor.GetElementSize() *
SizeOf(descriptor.GetDataType());
MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
texture_desc.width = storage_dims[0];
texture_desc.pixelFormat =
DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
texture_desc.textureType = MTLTextureTypeTextureBuffer;
texture_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texture_desc.storageMode = buffer.storageMode;
*texture = [buffer newTextureWithDescriptor:texture_desc
offset:buffer_offset
bytesPerRow:data_size];
if (!*texture) {
return absl::UnknownError("Failed to allocate id<MTLTexture>");
}
} else {
return absl::UnknownError(
"TensorStorageType::IMAGE_BUFFER available only in iOS 12/tvOS "
"12/macOS 10.14 and higher.");
}
return absl::OkStatus();
}
absl::Status AllocateTensorMemory(id<MTLDevice> device,
const TensorDescriptor& descriptor,
id<MTLBuffer>* buffer,
id<MTLTexture>* texture) {
std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
const void* data_ptr =
descriptor.GetData().empty() ? nullptr : descriptor.GetData().data();
switch (descriptor.GetStorageType()) {
case TensorStorageType::BUFFER:
case TensorStorageType::IMAGE_BUFFER: {
const size_t data_size = storage_dims[0] * descriptor.GetElementSize() *
SizeOf(descriptor.GetDataType());
if (data_ptr) {
*buffer = [device newBufferWithBytes:data_ptr
length:data_size
options:MTLResourceStorageModeShared];
} else {
*buffer = [device newBufferWithLength:data_size
options:MTLResourceStorageModeShared];
}
if (!*buffer) {
return absl::UnknownError("Failed to allocate id<MTLBuffer>");
}
if (descriptor.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
RETURN_IF_ERROR(CreateTextureBuffer(*buffer, 0, descriptor, texture));
}
return absl::OkStatus();
}
case TensorStorageType::TEXTURE_2D: {
MTLTextureDescriptor* texture_desc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:DataTypeToRGBAPixelFormat(
descriptor.GetDataType(),
false)
width:storage_dims[0]
height:storage_dims[1]
mipmapped:NO];
texture_desc.textureType = MTLTextureType2D;
texture_desc.usage =
MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texture_desc.storageMode = MTLStorageModePrivate;
*texture = [device newTextureWithDescriptor:texture_desc];
if (!*texture) {
return absl::UnknownError("Failed to allocate id<MTLTexture>");
}
if (data_ptr) {
WriteDataToTexture2D(*texture, device, data_ptr);
}
return absl::OkStatus();
}
case TensorStorageType::TEXTURE_3D: {
MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
texture_desc.width = storage_dims[0];
texture_desc.height = storage_dims[1];
texture_desc.depth = storage_dims[2];
texture_desc.pixelFormat =
DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
texture_desc.textureType = MTLTextureType3D;
texture_desc.usage =
MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texture_desc.storageMode = MTLStorageModePrivate;
*texture = [device newTextureWithDescriptor:texture_desc];
if (!*texture) {
return absl::UnknownError("Failed to allocate id<MTLTexture>");
}
if (data_ptr) {
WriteDataToTexture3D(*texture, device, data_ptr);
}
return absl::OkStatus();
}
case TensorStorageType::TEXTURE_ARRAY: {
MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
texture_desc.width = storage_dims[0];
texture_desc.height = storage_dims[1];
texture_desc.arrayLength = storage_dims[2];
texture_desc.pixelFormat =
DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
texture_desc.textureType = MTLTextureType2DArray;
texture_desc.usage =
MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texture_desc.storageMode = MTLStorageModePrivate;
*texture = [device newTextureWithDescriptor:texture_desc];
if (!*texture) {
return absl::UnknownError("Failed to allocate id<MTLTexture>");
}
if (data_ptr) {
WriteDataToTexture2DArray(*texture, device, data_ptr);
}
return absl::OkStatus();
}
case TensorStorageType::SINGLE_TEXTURE_2D:
default:
return absl::InternalError("Unsupported tensor storage type");
}
}
} // namespace
MetalSpatialTensor::MetalSpatialTensor(id<MTLBuffer> buffer,
id<MTLTexture> texture,
bool memory_owner,
bool texture_mem_owner,
const TensorDescriptor& descriptor)
: memory_(buffer),
texture_mem_(texture),
memory_owner_(memory_owner),
texture_mem_owner_(texture_mem_owner),
descriptor_(descriptor) {}
MetalSpatialTensor::MetalSpatialTensor(MetalSpatialTensor&& tensor)
: memory_(tensor.memory_),
texture_mem_(tensor.texture_mem_),
memory_owner_(tensor.memory_owner_),
texture_mem_owner_(tensor.texture_mem_owner_),
descriptor_(std::move(tensor.descriptor_)),
aligned_texture_width_(tensor.aligned_texture_width_),
buffer_offset_(tensor.buffer_offset_) {
tensor.memory_ = nullptr;
}
MetalSpatialTensor& MetalSpatialTensor::operator=(MetalSpatialTensor&& tensor) {
if (this != &tensor) {
Release();
std::swap(memory_, tensor.memory_);
std::swap(texture_mem_, tensor.texture_mem_);
std::swap(memory_owner_, tensor.memory_owner_);
std::swap(texture_mem_owner_, tensor.texture_mem_owner_);
descriptor_ = std::move(tensor.descriptor_);
std::swap(aligned_texture_width_, tensor.aligned_texture_width_);
std::swap(buffer_offset_, tensor.buffer_offset_);
}
return *this;
}
void MetalSpatialTensor::Release() {
if (memory_owner_ && memory_) {
memory_ = nullptr;
}
if (texture_mem_owner_ && texture_mem_) {
texture_mem_ = nullptr;
}
}
absl::Status MetalSpatialTensor::GetGPUResources(
const GPUObjectDescriptor* obj_ptr,
GPUResourcesWithValue* resources) const {
const auto* buffer_desc = dynamic_cast<const BufferDescriptor*>(obj_ptr);
if (buffer_desc) {
if (descriptor_.GetStorageType() != TensorStorageType::BUFFER) {
return absl::InvalidArgumentError(
"Tensor can be used with BufferDescriptor only wtih "
"TensorStorageType::BUFFER.");
}
resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
return absl::OkStatus();
}
const auto* tensor_desc = dynamic_cast<const TensorDescriptor*>(obj_ptr);
if (!tensor_desc) {
return absl::InvalidArgumentError("Expected TensorDescriptor on input.");
}
tensor_desc->GetGpuResources(descriptor_.GetBHWDCShape(),
&resources->generic);
if (descriptor_.GetStorageType() == TensorStorageType::BUFFER) {
resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
} else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_2D) {
if (obj_ptr->GetAccess() == AccessType::WRITE &&
tensor_desc->GetUseBufferForWriteOnlyTexture2d()) {
resources->AddInt("aligned_texture_width", aligned_texture_width_);
resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
} else {
resources->images2d.push_back({"image2d", texture_mem_});
}
} else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_3D) {
resources->images3d.push_back({"image3d", texture_mem_});
} else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_ARRAY) {
resources->image2d_arrays.push_back({"image2d_array", texture_mem_});
} else if (descriptor_.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
if (obj_ptr->GetAccess() == AccessType::WRITE &&
tensor_desc->GetUseBufferForWriteOnlyImageBuffer()) {
resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
} else {
resources->image_buffers.push_back({"image_buffer", texture_mem_});
}
}
return absl::OkStatus();
}
absl::Status MetalSpatialTensor::CreateFromDescriptor(
const TensorDescriptor& desc, id<MTLDevice> device) {
desc.CopyWithoutData(&descriptor_);
memory_owner_ = true;
id<MTLBuffer> buffer;
id<MTLTexture> texture;
RETURN_IF_ERROR(AllocateTensorMemory(device, desc, &buffer, &texture));
memory_ = buffer;
texture_mem_ = texture;
return absl::OkStatus();
}
absl::Status MetalSpatialTensor::UploadDescriptorData(
const TensorDescriptor& desc, id<MTLDevice> device) {
return WriteData(device, desc.GetData().data());
}
absl::Status MetalSpatialTensor::ToDescriptor(TensorDescriptor* desc,
id<MTLDevice> device) const {
*desc = descriptor_;
std::vector<uint8_t> data(GetMemorySizeInBytes());
RETURN_IF_ERROR(ReadData(device, data.data()));
desc->SetData(std::move(data));
return absl::OkStatus();
}
absl::Status MetalSpatialTensor::WriteData(id<MTLDevice> device,
const void* ptr) {
switch (descriptor_.GetStorageType()) {
case TensorStorageType::BUFFER:
case TensorStorageType::IMAGE_BUFFER:
std::memcpy(
reinterpret_cast<uint8_t*>([memory_ contents]) + buffer_offset_, ptr,
GetMemorySizeInBytes());
break;
case TensorStorageType::TEXTURE_2D:
WriteDataToTexture2D(texture_mem_, device, ptr);
break;
case TensorStorageType::TEXTURE_3D:
WriteDataToTexture3D(texture_mem_, device, ptr);
break;
case TensorStorageType::TEXTURE_ARRAY:
WriteDataToTexture2DArray(texture_mem_, device, ptr);
break;
case TensorStorageType::SINGLE_TEXTURE_2D:
default:
return absl::InternalError("Unsupported tensor storage type");
}
return absl::OkStatus();
}
absl::Status MetalSpatialTensor::ReadData(id<MTLDevice> device,
void* ptr) const {
switch (descriptor_.GetStorageType()) {
case TensorStorageType::BUFFER:
case TensorStorageType::IMAGE_BUFFER:
std::memcpy(
ptr, reinterpret_cast<uint8_t*>([memory_ contents]) + buffer_offset_,
GetMemorySizeInBytes());
break;
case TensorStorageType::TEXTURE_2D:
ReadDataFromTexture2D(texture_mem_, device, ptr);
break;
case TensorStorageType::TEXTURE_3D:
ReadDataFromTexture3D(texture_mem_, device, ptr);
break;
case TensorStorageType::TEXTURE_ARRAY:
ReadDataFromTexture2DArray(texture_mem_, device, ptr);
break;
case TensorStorageType::SINGLE_TEXTURE_2D:
default:
return absl::InternalError("Unsupported tensor storage type");
}
return absl::OkStatus();
}
absl::Status MetalSpatialTensor::SetBufferHandle(id<MTLBuffer> buffer) {
if (memory_owner_) {
return absl::InvalidArgumentError(
"SetBufferHandle can be used only with shared "
"Tensors(CreateSharedBufferTensor).");
}
if (memory_ == buffer) {
return absl::OkStatus();
}
memory_ = buffer;
if (descriptor_.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
id<MTLTexture> texture_buffer = nullptr;
RETURN_IF_ERROR(
CreateTextureBuffer(memory_, 0, descriptor_, &texture_buffer));
texture_mem_ = texture_buffer;
}
return absl::OkStatus();
}
id<MTLBuffer> MetalSpatialTensor::GetBufferHandle() const { return memory_; }
absl::Status CreateTensor(id<MTLDevice> device,
const TensorDescriptor& descriptor,
MetalSpatialTensor* result) {
id<MTLBuffer> buffer;
id<MTLTexture> texture;
RETURN_IF_ERROR(AllocateTensorMemory(device, descriptor, &buffer, &texture));
*result = MetalSpatialTensor(buffer, texture, true, true, descriptor);
return absl::OkStatus();
}
absl::Status CreateTensorSharedBuffer(id<MTLBuffer> buffer,
const TensorDescriptor& descriptor,
MetalSpatialTensor* result,
uint64_t buffer_offset) {
id<MTLTexture> texture_buffer = nullptr;
if (buffer &&
descriptor.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
RETURN_IF_ERROR(CreateTextureBuffer(buffer, buffer_offset, descriptor,
&texture_buffer));
}
*result = MetalSpatialTensor(buffer, texture_buffer, false, true, descriptor);
result->buffer_offset_ = buffer_offset;
return absl::OkStatus();
}
absl::Status CreateTensorSharedImage2DBuffer(id<MTLBuffer> buffer,
const TensorDescriptor& descriptor,
int row_bytes_alignment,
MetalSpatialTensor* result,
uint64_t buffer_offset) {
std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
const int width = storage_dims[0];
const int height = storage_dims[1];
const int channels = descriptor.GetElementSize();
MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
texture_desc.width = width;
texture_desc.height = height;
texture_desc.depth = 1;
texture_desc.textureType = MTLTextureType2D;
texture_desc.arrayLength = 1;
texture_desc.mipmapLevelCount = 1;
texture_desc.sampleCount = 1;
texture_desc.pixelFormat =
DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
texture_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texture_desc.storageMode = buffer.storageMode;
const size_t pixel_size = channels * SizeOf(descriptor.GetDataType());
const size_t bytes_per_row = width * pixel_size;
const size_t bytes_per_row_aligned =
AlignByN(bytes_per_row, row_bytes_alignment);
id<MTLTexture> texture_buffer =
[buffer newTextureWithDescriptor:texture_desc
offset:buffer_offset
bytesPerRow:bytes_per_row_aligned];
if (!texture_buffer) {
return absl::UnknownError("Failed to allocate id<MTLTexture>.");
}
if (bytes_per_row_aligned % pixel_size != 0) {
return absl::UnknownError("Alignment mismatch.");
}
*result = MetalSpatialTensor(buffer, texture_buffer, false, true, descriptor);
result->aligned_texture_width_ = bytes_per_row_aligned / pixel_size;
result->buffer_offset_ = buffer_offset;
return absl::OkStatus();
}
TensorStorageType GetFastestStorageType(const GpuInfo& gpu_info) {
if (gpu_info.IsApple() &&
gpu_info.apple_info.IsFamilyOrLower(AppleInfo::Family::kApple2)) {
return TensorStorageType::TEXTURE_2D;
} else {
return TensorStorageType::BUFFER;
}
}
} // namespace metal
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,487 @@
/* 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/metal/metal_spatial_tensor.h"
#include <cmath>
#include "tensorflow/lite/delegates/gpu/common/types.h"
#import <XCTest/XCTest.h>
#import <Metal/Metal.h>
@interface MetalSpatialTensorTest : XCTestCase
@end
@implementation MetalSpatialTensorTest
- (void)setUp {
[super setUp];
}
using tflite::gpu::half;
using tflite::gpu::TensorDescriptor;
using tflite::gpu::TensorStorageType;
using tflite::gpu::DataType;
using tflite::gpu::BHWC;
using tflite::gpu::BHWDC;
using tflite::gpu::Layout;
namespace {
template <DataType T>
absl::Status TensorBHWCTest(const BHWC& shape, const TensorDescriptor& descriptor,
id<MTLDevice> device) {
tflite::gpu::Tensor<BHWC, T> tensor_cpu;
tensor_cpu.shape = shape;
tensor_cpu.data.resize(shape.DimensionsProduct());
for (int i = 0; i < tensor_cpu.data.size(); ++i) {
// val = [0, 1];
const double val = static_cast<double>(i) / static_cast<double>(tensor_cpu.data.size() - 1);
double transformed_val = sin(val * 2.0 * M_PI) * 256.0;
if (descriptor.GetDataType() == DataType::INT16 ||
descriptor.GetDataType() == DataType::UINT16) {
transformed_val *= 256.0;
}
if (descriptor.GetDataType() == DataType::INT32 ||
descriptor.GetDataType() == DataType::UINT32) {
transformed_val *= 256.0 * 256.0 * 256.0 * 256.0;
}
if (descriptor.GetDataType() == DataType::FLOAT16) {
transformed_val = half(transformed_val);
}
if (descriptor.GetDataType() == DataType::BOOL) {
transformed_val = i % 7;
}
tensor_cpu.data[i] = transformed_val;
}
tflite::gpu::Tensor<BHWC, T> tensor_gpu;
tensor_gpu.shape = shape;
tensor_gpu.data.resize(shape.DimensionsProduct());
for (int i = 0; i < tensor_gpu.data.size(); ++i) {
tensor_gpu.data[i] = 0;
}
tflite::gpu::metal::MetalSpatialTensor tensor;
tflite::gpu::TensorDescriptor descriptor_with_data = descriptor;
descriptor_with_data.UploadData(tensor_cpu);
RETURN_IF_ERROR(tensor.CreateFromDescriptor(descriptor_with_data, device));
tflite::gpu::TensorDescriptor output_descriptor;
RETURN_IF_ERROR(tensor.ToDescriptor(&output_descriptor, device));
output_descriptor.DownloadData(&tensor_gpu);
for (int i = 0; i < tensor_gpu.data.size(); ++i) {
if (tensor_gpu.data[i] != tensor_cpu.data[i]) {
return absl::InternalError("Wrong value at index - " + std::to_string(i) + ". GPU - " +
std::to_string(tensor_gpu.data[i]) + ", CPU - " +
std::to_string(tensor_cpu.data[i]));
}
}
return absl::OkStatus();
}
template absl::Status TensorBHWCTest<DataType::FLOAT32>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::INT32>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::INT16>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::INT8>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::UINT32>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::UINT16>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::UINT8>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWCTest<DataType::BOOL>(const BHWC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template <DataType T>
absl::Status TensorBHWDCTest(const BHWDC& shape, const TensorDescriptor& descriptor,
id<MTLDevice> device) {
tflite::gpu::Tensor<BHWDC, T> tensor_cpu;
tensor_cpu.shape = shape;
tensor_cpu.data.resize(shape.DimensionsProduct());
for (int i = 0; i < tensor_cpu.data.size(); ++i) {
// val = [0, 1];
const double val = static_cast<double>(i) / static_cast<double>(tensor_cpu.data.size() - 1);
double transformed_val = sin(val * 2.0 * M_PI) * 256.0;
if (descriptor.GetDataType() == DataType::INT16 ||
descriptor.GetDataType() == DataType::UINT16) {
transformed_val *= 256.0;
}
if (descriptor.GetDataType() == DataType::INT32 ||
descriptor.GetDataType() == DataType::UINT32) {
transformed_val *= 256.0 * 256.0 * 256.0 * 256.0;
}
if (descriptor.GetDataType() == DataType::FLOAT16) {
transformed_val = half(transformed_val);
}
if (descriptor.GetDataType() == DataType::BOOL) {
transformed_val = i % 7;
}
tensor_cpu.data[i] = transformed_val;
}
tflite::gpu::Tensor<BHWDC, T> tensor_gpu;
tensor_gpu.shape = shape;
tensor_gpu.data.resize(shape.DimensionsProduct());
for (int i = 0; i < tensor_gpu.data.size(); ++i) {
tensor_gpu.data[i] = 0;
}
tflite::gpu::metal::MetalSpatialTensor tensor;
tflite::gpu::TensorDescriptor descriptor_with_data = descriptor;
descriptor_with_data.UploadData(tensor_cpu);
RETURN_IF_ERROR(tensor.CreateFromDescriptor(descriptor_with_data, device));
tflite::gpu::TensorDescriptor output_descriptor;
RETURN_IF_ERROR(tensor.ToDescriptor(&output_descriptor, device));
output_descriptor.DownloadData(&tensor_gpu);
for (int i = 0; i < tensor_gpu.data.size(); ++i) {
if (tensor_gpu.data[i] != tensor_cpu.data[i]) {
return absl::InternalError("Wrong value.");
}
}
return absl::OkStatus();
}
template absl::Status TensorBHWDCTest<DataType::FLOAT32>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::INT32>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::INT16>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::INT8>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::UINT32>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::UINT16>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::UINT8>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template absl::Status TensorBHWDCTest<DataType::BOOL>(const BHWDC& shape,
const TensorDescriptor& descriptor,
id<MTLDevice> device);
template <DataType T>
absl::Status TensorTests(DataType data_type, TensorStorageType storage_type) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(1, 6, 7, 3), {data_type, storage_type, Layout::HWC}, device));
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(1, 1, 4, 12), {data_type, storage_type, Layout::HWC}, device));
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(1, 6, 1, 7), {data_type, storage_type, Layout::HWC}, device));
// Batch tests
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(2, 6, 7, 3), {data_type, storage_type, Layout::BHWC}, device));
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(4, 1, 4, 12), {data_type, storage_type, Layout::BHWC}, device));
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(7, 6, 1, 7), {data_type, storage_type, Layout::BHWC}, device));
RETURN_IF_ERROR(
TensorBHWCTest<T>(BHWC(13, 7, 3, 3), {data_type, storage_type, Layout::BHWC}, device));
// 5D tests with batch = 1
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(1, 6, 7, 4, 3), {data_type, storage_type, Layout::HWDC}, device));
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(1, 1, 4, 3, 12), {data_type, storage_type, Layout::HWDC}, device));
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(1, 6, 1, 7, 7), {data_type, storage_type, Layout::HWDC}, device));
// 5D tests
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(2, 6, 7, 1, 3), {data_type, storage_type, Layout::BHWDC}, device));
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(4, 1, 4, 2, 12), {data_type, storage_type, Layout::BHWDC}, device));
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(7, 6, 1, 3, 7), {data_type, storage_type, Layout::BHWDC}, device));
RETURN_IF_ERROR(
TensorBHWDCTest<T>(BHWDC(13, 7, 3, 4, 3), {data_type, storage_type, Layout::BHWDC}, device));
return absl::OkStatus();
}
template absl::Status TensorTests<DataType::FLOAT32>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::INT32>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::INT16>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::INT8>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::UINT32>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::UINT16>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::UINT8>(DataType data_type,
TensorStorageType storage_type);
template absl::Status TensorTests<DataType::BOOL>(DataType data_type,
TensorStorageType storage_type);
} // namespace
- (void)testBufferF32 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT32, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferF16 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT16, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferInt32 {
auto status = TensorTests<DataType::INT32>(DataType::INT32, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferInt16 {
auto status = TensorTests<DataType::INT16>(DataType::INT16, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferInt8 {
auto status = TensorTests<DataType::INT8>(DataType::INT8, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferUint32 {
auto status = TensorTests<DataType::UINT32>(DataType::UINT32, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferUint16 {
auto status = TensorTests<DataType::UINT16>(DataType::UINT16, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferUint8 {
auto status = TensorTests<DataType::UINT8>(DataType::UINT8, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testBufferBool {
auto status = TensorTests<DataType::BOOL>(DataType::BOOL, TensorStorageType::BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DF32 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT32, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DF16 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT16, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DInt32 {
auto status = TensorTests<DataType::INT32>(DataType::INT32, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DInt16 {
auto status = TensorTests<DataType::INT16>(DataType::INT16, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DInt8 {
auto status = TensorTests<DataType::INT8>(DataType::INT8, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DUint32 {
auto status = TensorTests<DataType::UINT32>(DataType::UINT32, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DUint16 {
auto status = TensorTests<DataType::UINT16>(DataType::UINT16, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DUint8 {
auto status = TensorTests<DataType::UINT8>(DataType::UINT8, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DBool {
auto status = TensorTests<DataType::BOOL>(DataType::BOOL, TensorStorageType::TEXTURE_2D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DF32 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT32, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DF16 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT16, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DInt32 {
auto status = TensorTests<DataType::INT32>(DataType::INT32, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DInt16 {
auto status = TensorTests<DataType::INT16>(DataType::INT16, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DInt8 {
auto status = TensorTests<DataType::INT8>(DataType::INT8, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DUint32 {
auto status = TensorTests<DataType::UINT32>(DataType::UINT32, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DUint16 {
auto status = TensorTests<DataType::UINT16>(DataType::UINT16, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DUint8 {
auto status = TensorTests<DataType::UINT8>(DataType::UINT8, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture3DBool {
auto status = TensorTests<DataType::BOOL>(DataType::BOOL, TensorStorageType::TEXTURE_3D);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayF32 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT32, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayF16 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT16, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayInt32 {
auto status = TensorTests<DataType::INT32>(DataType::INT32, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayInt16 {
auto status = TensorTests<DataType::INT16>(DataType::INT16, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayInt8 {
auto status = TensorTests<DataType::INT8>(DataType::INT8, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayUint32 {
auto status = TensorTests<DataType::UINT32>(DataType::UINT32, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayUint16 {
auto status = TensorTests<DataType::UINT16>(DataType::UINT16, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayUint8 {
auto status = TensorTests<DataType::UINT8>(DataType::UINT8, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTexture2DArrayBool {
auto status = TensorTests<DataType::BOOL>(DataType::BOOL, TensorStorageType::TEXTURE_ARRAY);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferF32 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT32, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferF16 {
auto status = TensorTests<DataType::FLOAT32>(DataType::FLOAT16, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferInt32 {
auto status = TensorTests<DataType::INT32>(DataType::INT32, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferInt16 {
auto status = TensorTests<DataType::INT16>(DataType::INT16, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferInt8 {
auto status = TensorTests<DataType::INT8>(DataType::INT8, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferUint32 {
auto status = TensorTests<DataType::UINT32>(DataType::UINT32, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferUint16 {
auto status = TensorTests<DataType::UINT16>(DataType::UINT16, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferUint8 {
auto status = TensorTests<DataType::UINT8>(DataType::UINT8, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
- (void)testTextureBufferBool {
auto status = TensorTests<DataType::BOOL>(DataType::BOOL, TensorStorageType::IMAGE_BUFFER);
XCTAssertTrue(status.ok(), @"%s", std::string(status.message()).c_str());
}
@end