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
+169
View File
@@ -0,0 +1,169 @@
# TensorFlow Lite for Objective-C
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load("//tensorflow/lite:special_rules.bzl", "ios_visibility_allowlist", "tflite_ios_lab_runner")
load("//tensorflow/lite/ios:ios.bzl", "TFL_DEFAULT_TAGS", "TFL_DISABLED_SANITIZER_TAGS", "TFL_MINIMUM_OS_VERSION")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:license"],
default_visibility = ["//visibility:private"],
licenses = ["notice"],
)
# TODO: b425991613 - Provide a way to disable CoreML Delegate with `--define=use_coreml_delegate=0`.
SOURCES = glob([
"sources/*.h",
"sources/*.m",
"sources/*.mm",
])
API_HEADERS = glob([
"apis/*.h",
])
# Compiler flags for building regular non-test libraries.
RELEASE_COPTS = [
# Enables language-specific warnings for Objective-C, Objective-C++, C, and C++.
"-Wall",
# Warns if functions, variables, and types marked with the deprecated attribute are being used.
"-Wdeprecated-declarations",
# Warns for errors in documentation.
"-Wdocumentation",
# Turns all warnings into errors.
"-Werror",
# Enables extra warning flags that are not enabled by -Wall.
"-Wextra",
# Warns if a global function is defined without a previous prototype declaration.
"-Wmissing-prototypes",
# From -Wextra. Disables warning when signed value is converted to unsigned value during comparison.
"-Wno-sign-compare",
# From -Wextra. Disables warning for unused parameters, which are common in delegate methods and block callbacks.
"-Wno-unused-parameter",
# Warns if a global or local variable or type declaration shadows another variable, parameter, type, class member, or instance variable.
"-Wshadow",
# Warns if a function is declared or defined without specifying the argument types. For a block with no args, use (void) instead of ().
"-Wstrict-prototypes",
# Warns if an @selector() expression is encountered with a method name that hasn't been defined yet.
"-Wundeclared-selector",
# Turn off warnings for headers not part of TensorFlow Lite Objective-C API.
"--system-header-prefix=tensorflow/lite/c/",
]
# Compiler flags for building test libraries.
TEST_COPTS = RELEASE_COPTS + [
# From -Wall. Disables warning when passing nil to a callee that requires a non-null argument.
"-Wno-nonnull",
# Disables warning when a global or local variable or type declaration shadows another.
"-Wno-shadow",
]
objc_library(
name = "TensorFlowLite",
srcs = SOURCES,
hdrs = API_HEADERS,
copts = RELEASE_COPTS,
tags = TFL_DEFAULT_TAGS,
visibility = ios_visibility_allowlist(),
deps = [
"//tensorflow/lite/core/c:c_api",
"//tensorflow/lite/core/c:c_api_experimental",
"//tensorflow/lite/delegates/coreml:coreml_delegate",
"//tensorflow/lite/delegates/gpu:metal_delegate",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
],
alwayslink = 1,
)
objc_library(
name = "TensorFlowLite_without_op_resolver",
srcs = SOURCES,
hdrs = API_HEADERS,
copts = RELEASE_COPTS,
tags = TFL_DEFAULT_TAGS,
visibility = ios_visibility_allowlist(),
deps = [
"//tensorflow/lite/core/c:c_api_experimental_without_op_resolver",
"//tensorflow/lite/core/c:c_api_without_op_resolver",
"//tensorflow/lite/delegates/coreml:coreml_delegate",
"//tensorflow/lite/delegates/gpu:metal_delegate",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
"//third_party/apple_frameworks:Foundation",
],
alwayslink = 1,
)
# NOTE: This test target name must be lower-cased in order to match it with the
# directory name. (See: b/174508866)
ios_unit_test(
name = "tests",
size = "small",
timeout = "moderate",
minimum_os_version = TFL_MINIMUM_OS_VERSION,
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS,
deps = [
":TestsLibrary",
],
)
objc_library(
name = "TestsLibrary",
testonly = 1,
srcs = glob([
"tests/*.m",
]),
hdrs = glob([
"apis/*.h",
"sources/*.h",
"tests/*.h",
]),
copts = TEST_COPTS,
data = [
"//tensorflow/lite:testdata/add.bin",
"//tensorflow/lite:testdata/add_quantized.bin",
"//tensorflow/lite:testdata/multi_add.bin",
"//tensorflow/lite:testdata/multi_signatures.bin",
],
sdk_frameworks = ["XCTest"],
tags = TFL_DEFAULT_TAGS + ["builder_default_ios_arm64"],
deps = [":TensorFlowLite"],
)
ios_application(
name = "TestApp",
app_icons = glob(["apps/TestApp/TestApp/Assets.xcassets/AppIcon.appiconset/**"]),
bundle_id = "com.tensorflow.lite.objc.TestApp",
families = [
"ipad",
"iphone",
],
infoplists = ["apps/TestApp/TestApp/Info.plist"],
minimum_os_version = TFL_MINIMUM_OS_VERSION,
sdk_frameworks = ["CoreGraphics"],
tags = TFL_DEFAULT_TAGS,
deps = [":TestAppLibrary"],
)
objc_library(
name = "TestAppLibrary",
srcs = glob(["apps/TestApp/TestApp/*.m"]),
hdrs = glob(["apps/TestApp/TestApp/*.h"]),
data = glob(["apps/TestApp/TestApp/Base.lproj/*.storyboard"]) + [
"//tensorflow/lite:testdata/add.bin",
"//tensorflow/lite:testdata/add_quantized.bin",
"//tensorflow/lite:testdata/multi_add.bin",
],
includes = [
"apis",
],
module_name = "TestApp",
tags = TFL_DEFAULT_TAGS + [
"builder_default_ios_arm64",
"manual",
],
deps = [
":TensorFlowLite",
"//third_party/apple_frameworks:UIKit",
],
)
@@ -0,0 +1,14 @@
<?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>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
</dict>
</plist>
+92
View File
@@ -0,0 +1,92 @@
# TensorFlow Lite for Objective-C
[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight
solution for Objective-C developers. It enables low-latency inference of
on-device machine learning models with a small binary size and fast performance
supporting hardware acceleration.
## Build TensorFlow with iOS support
To build the Objective-C TensorFlow Lite library on Apple platforms,
[install from source](https://www.tensorflow.org/install/source#setup_for_linux_and_macos)
or [clone the GitHub repo](https://github.com/tensorflow/tensorflow).
Then, configure TensorFlow by navigating to the root directory and executing the
`configure.py` script:
```shell
python configure.py
```
Follow the prompts and when asked to build TensorFlow with iOS support, enter `y`.
### CocoaPods developers
Add the TensorFlow Lite pod to your `Podfile`:
```ruby
pod 'TensorFlowLiteObjC'
```
Then, run `pod install`.
In your Objective-C files, import the umbrella header:
```objectivec
#import "TFLTensorFlowLite.h"
```
Or, the module if you set `CLANG_ENABLE_MODULES = YES` in your Xcode project:
```objectivec
@import TFLTensorFlowLite;
```
Note: To import the TensorFlow Lite module in your Objective-C files, you must
also include `use_frameworks!` in your `Podfile`.
### Bazel developers
In your `BUILD` file, add the `TensorFlowLite` dependency to your target:
```python
objc_library(
deps=[
"//tensorflow/lite/objc:TensorFlowLite",
],)
```
In your Objective-C files, import the umbrella header:
```objectivec
#import "TFLTensorFlowLite.h"
```
Or, the module if you set `CLANG_ENABLE_MODULES = YES` in your Xcode project:
```objectivec
@import TFLTensorFlowLite;
```
Build the `TensorFlowLite` Objective-C library target:
```shell
bazel build tensorflow/lite/objc:TensorFlowLite
```
Build the `tests` target:
```shell
bazel test tensorflow/lite/objc:tests
```
#### Generate the Xcode project using Tulsi
Open the `//tensorflow/lite/objc/TensorFlowLite.tulsiproj` using
the [TulsiApp](https://github.com/bazelbuild/tulsi)
or by running the
[`generate_xcodeproj.sh`](https://github.com/bazelbuild/tulsi/blob/master/src/tools/generate_xcodeproj.sh)
script from the root `tensorflow` directory:
```shell
generate_xcodeproj.sh --genconfig tensorflow/lite/objc/TensorFlowLite.tulsiproj:TensorFlowLite --outputfolder ~/path/to/generated/TensorFlowLite.xcodeproj
```
@@ -0,0 +1,63 @@
{
"sourceFilters" : [
"tensorflow/lite",
"tensorflow/lite/c",
"tensorflow/lite/objc",
"tensorflow/lite/objc/apis",
"tensorflow/lite/objc/apps/TestApp/TestApp",
"tensorflow/lite/objc/apps/TestApp/TestApp/Base.lproj",
"tensorflow/lite/objc/sources",
"tensorflow/lite/objc/tests",
"tensorflow/lite/kernels",
"tensorflow/lite/kernels/internal",
"tensorflow/lite/nnapi",
"tensorflow/lite/schema"
],
"buildTargets" : [
"//tensorflow/lite/objc:TensorFlowLite",
"//tensorflow/lite/objc:TestApp",
"//tensorflow/lite/objc:tests"
],
"projectName" : "TensorFlowLite",
"optionSet" : {
"LaunchActionPreActionScript" : {
"p" : "$(inherited)"
},
"BazelBuildStartupOptionsRelease" : {
"p" : "$(inherited)"
},
"BazelBuildOptionsRelease" : {
"p" : "$(inherited)"
},
"BazelBuildOptionsDebug" : {
"p" : "$(inherited)"
},
"EnvironmentVariables" : {
"p" : "$(inherited)"
},
"BuildActionPreActionScript" : {
"p" : "$(inherited)"
},
"CommandlineArguments" : {
"p" : "$(inherited)"
},
"TestActionPreActionScript" : {
"p" : "$(inherited)"
},
"BazelBuildStartupOptionsDebug" : {
"p" : "$(inherited)"
},
"BuildActionPostActionScript" : {
"p" : "$(inherited)"
},
"TestActionPostActionScript" : {
"p" : "$(inherited)"
},
"LaunchActionPostActionScript" : {
"p" : "$(inherited)"
}
},
"additionalFilePaths" : [
"tensorflow/lite/objc/BUILD"
]
}
@@ -0,0 +1,17 @@
{
"configDefaults" : {
"optionSet" : {
"BazelBuildOptionsDebug" : {
},
"BazelBuildOptionsRelease" : {
},
}
},
"projectName" : "TensorFlowLite",
"packages" : [
"tensorflow/lite/objc"
],
"workspaceRoot" : "../../../.."
}
@@ -0,0 +1,98 @@
Pod::Spec.new do |s|
s.name = 'TensorFlowLiteObjC'
s.version = '2.14.0'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache' }
s.homepage = 'https://github.com/tensorflow/tensorflow'
s.source = { :git => 'https://github.com/tensorflow/tensorflow.git', :commit => '4dacf3f368eb7965e9b5c3bbdd5193986081c3b2' }
s.summary = 'TensorFlow Lite for Objective-C'
s.description = <<-DESC
TensorFlow Lite is TensorFlow's lightweight solution for Objective-C
developers. It enables low-latency inference of on-device machine learning
models with a small binary size and fast performance supporting hardware
acceleration.
DESC
s.cocoapods_version = '>= 1.9.0'
s.ios.deployment_target = '12.0'
s.module_name = 'TFLTensorFlowLite'
s.static_framework = true
tfl_dir = 'tensorflow/lite/'
objc_dir = tfl_dir + 'objc/'
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' =>
'"${PODS_TARGET_SRCROOT}" ' +
'"${PODS_TARGET_SRCROOT}/' + objc_dir + 'apis"',
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386'
}
s.user_target_xcconfig = {
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386'
}
s.default_subspec = 'Core'
s.subspec 'Core' do |core|
core.public_header_files = objc_dir + 'apis/*.h'
core.source_files = [
objc_dir + '{apis,sources}/*.{h,m,mm}',
tfl_dir + 'builtin_ops.h',
tfl_dir + 'c/c_api.h',
tfl_dir + 'core/c/c_api.h',
tfl_dir + 'c/c_api_experimental.h',
tfl_dir + 'c/c_api_types.h',
tfl_dir + 'c/common.h',
tfl_dir + 'delegates/xnnpack/xnnpack_delegate.h',
tfl_dir + 'core/c/operator.h',
]
core.exclude_files = [
objc_dir + '{apis,sources}/TFL{Metal,CoreML}Delegate.{h,m}',
]
core.dependency 'TensorFlowLiteC', "#{s.version}"
core.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/*.m'
ts.exclude_files = objc_dir + 'tests/TFL{Metal,CoreML}DelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/add.bin',
tfl_dir + 'testdata/add_quantized.bin',
tfl_dir + 'testdata/multi_signatures.bin',
]
end
end
s.subspec 'CoreML' do |coreml|
coreml.source_files = [
objc_dir + '{apis,sources}/TFLCoreMLDelegate.{h,m}',
]
coreml.ios.deployment_target = '12.0'
coreml.dependency 'TensorFlowLiteC/CoreML', "#{s.version}"
coreml.dependency 'TensorFlowLiteObjC/Core', "#{s.version}"
coreml.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/TFLCoreMLDelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/add.bin',
]
end
end
s.subspec 'Metal' do |metal|
metal.source_files = [
objc_dir + '{apis,sources}/TFLMetalDelegate.{h,m}',
]
metal.dependency 'TensorFlowLiteC/Metal', "#{s.version}"
metal.dependency 'TensorFlowLiteObjC/Core', "#{s.version}"
metal.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/TFLMetalDelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/multi_add.bin',
]
end
end
end
@@ -0,0 +1,104 @@
Pod::Spec.new do |s|
s.name = '${POD_NAME}'
s.version = '${TFL_BUILD_VERSION}'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache' }
s.homepage = 'https://github.com/tensorflow/tensorflow'
s.source = { :git => 'https://github.com/tensorflow/tensorflow.git', :commit => '${SOURCE_COMMIT}' }
s.summary = 'TensorFlow Lite for Objective-C'
s.description = <<-DESC
TensorFlow Lite is TensorFlow's lightweight solution for Objective-C
developers. It enables low-latency inference of on-device machine learning
models with a small binary size and fast performance supporting hardware
acceleration.
DESC
s.cocoapods_version = '>= ${TFL_MIN_COCOAPODS_VERSION}'
s.ios.deployment_target = '12.0'
s.module_name = 'TFLTensorFlowLite'
s.static_framework = true
tfl_dir = 'tensorflow/lite/'
objc_dir = tfl_dir + 'objc/'
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' =>
'"${PODS_TARGET_SRCROOT}" ' +
'"${PODS_TARGET_SRCROOT}/' + objc_dir + 'apis"',
# TODO: Remove this after adding support for arm64 simulator.
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
}
# TODO: Remove this after adding support for arm64 simulator.
s.user_target_xcconfig = {
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
}
s.default_subspec = 'Core'
# The privacy manifest shared by other subspecs.
s.subspec 'Privacy' do |ss|
ss.resource_bundles = {
s.module_name => objc_dir + 'PrivacyInfo.xcprivacy'
}
end
s.subspec 'Core' do |core|
core.public_header_files = objc_dir + 'apis/*.h'
core.source_files = [
objc_dir + '{apis,sources}/*.{h,m,mm}',
]
core.exclude_files = [
objc_dir + '{apis,sources}/TFL{Metal,CoreML}Delegate.{h,m}',
]
core.dependency "${C_POD_NAME}", "#{s.version}"
core.dependency "#{s.name}/Privacy", "#{s.version}"
core.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/*.m'
ts.exclude_files = objc_dir + 'tests/TFL{Metal,CoreML}DelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/add.bin',
tfl_dir + 'testdata/add_quantized.bin',
tfl_dir + 'testdata/multi_signatures.bin',
]
end
end
s.subspec 'CoreML' do |coreml|
coreml.source_files = [
objc_dir + '{apis,sources}/TFLCoreMLDelegate.{h,m}',
]
coreml.ios.deployment_target = '12.0'
coreml.dependency "${C_POD_NAME}/CoreML", "#{s.version}"
coreml.dependency "#{s.name}/Core", "#{s.version}"
coreml.dependency "#{s.name}/Privacy", "#{s.version}"
coreml.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/TFLCoreMLDelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/add.bin',
]
end
end
s.subspec 'Metal' do |metal|
metal.source_files = [
objc_dir + '{apis,sources}/TFLMetalDelegate.{h,m}',
]
metal.dependency "${C_POD_NAME}/Metal", "#{s.version}"
metal.dependency "#{s.name}/Core", "#{s.version}"
metal.dependency "#{s.name}/Privacy", "#{s.version}"
metal.test_spec 'Tests' do |ts|
ts.source_files = objc_dir + 'tests/TFLMetalDelegateTests.m'
ts.resources = [
tfl_dir + 'testdata/multi_add.bin',
]
end
end
end
@@ -0,0 +1,87 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
#import "TFLDelegate.h"
NS_ASSUME_NONNULL_BEGIN
/**
* @enum TFLCoreMLDelegateEnabledDevices
* This enum specifies for which devices the Core ML delegate will be enabled.
*/
typedef NS_ENUM(NSUInteger, TFLCoreMLDelegateEnabledDevices) {
/** Enables the delegate for devices with Neural Engine only. */
TFLCoreMLDelegateEnabledDevicesNeuralEngine,
/** Enables the delegate for all devices. */
TFLCoreMLDelegateEnabledDevicesAll,
};
/** Custom configuration options for a Core ML delegate. */
@interface TFLCoreMLDelegateOptions : NSObject
/**
* Indicates which devices the Core ML delegate should be enabled for. The default value is
* `TFLCoreMLDelegateEnabledDevicesNeuralEngine`, indicating that the delegate is enabled for
* Neural Engine devices only.
*/
@property(nonatomic) TFLCoreMLDelegateEnabledDevices enabledDevices;
/**
* Target Core ML version for the model conversion. When it's not set, Core ML version will be set
* to highest available version for the platform.
*/
@property(nonatomic) NSUInteger coreMLVersion;
/**
* The maximum number of Core ML delegate partitions created. Each graph corresponds to one
* delegated node subset in the TFLite model. The default value is `0` indicating that all possible
* partitions are delegated.
*/
@property(nonatomic) NSUInteger maxDelegatedPartitions;
/**
* The minimum number of nodes per partition to be delegated by the Core ML delegate. The default
* value is `2`.
*/
@property(nonatomic) NSUInteger minNodesPerPartition;
@end
/** A delegate that uses the Core ML framework for performing TensorFlow Lite graph operations. */
@interface TFLCoreMLDelegate : TFLDelegate
/**
* Initializes a new Core ML delegate with default options.
*
* @return A Core ML delegate initialized with default options. `nil` when the delegate creation
* fails. For example, trying to initialize a Core ML delegate on an unsupported device.
*/
- (nullable instancetype)init;
/**
* Initializes a new Core ML delegate with the given options.
*
* @param options Core ML delegate options.
*
* @return A Core ML delegate initialized with default options. `nil` when the delegate creation
* fails. For example, trying to initialize Core ML delegate on an unsupported device.
*/
- (nullable instancetype)initWithOptions:(TFLCoreMLDelegateOptions *)options
NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
typedef void* TFLCDelegate;
NS_ASSUME_NONNULL_BEGIN
@interface TFLDelegate : NSObject
/** Pointer to underlying TfLiteDelegate*. */
@property(nonatomic, readonly) TFLCDelegate cDelegate;
@end
NS_ASSUME_NONNULL_END
+217
View File
@@ -0,0 +1,217 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
@class TFLDelegate;
@class TFLInterpreterOptions;
@class TFLSignatureRunner;
@class TFLTensor;
NS_ASSUME_NONNULL_BEGIN
/**
* @enum TFLInterpreterErrorCode
* This enum specifies various error codes related to `TFLInterpreter`.
*/
typedef NS_ENUM(NSUInteger, TFLInterpreterErrorCode) {
/** Provided tensor index is invalid. */
TFLInterpreterErrorCodeInvalidTensorIndex,
/** Input data has invalid byte size. */
TFLInterpreterErrorCodeInvalidInputByteSize,
/** Provided shape is invalid. It must be a non-empty array of positive unsigned integers. */
TFLInterpreterErrorCodeInvalidShape,
/** Provided model cannot be loaded. */
TFLInterpreterErrorCodeFailedToLoadModel,
/** Failed to create `TFLInterpreter`. */
TFLInterpreterErrorCodeFailedToCreateInterpreter,
/** Failed to invoke `TFLInterpreter`. */
TFLInterpreterErrorCodeFailedToInvoke,
/** Failed to retrieve a tensor. */
TFLInterpreterErrorCodeFailedToGetTensor,
/** Invalid tensor. */
TFLInterpreterErrorCodeInvalidTensor,
/** Failed to resize an input tensor. */
TFLInterpreterErrorCodeFailedToResizeInputTensor,
/** Failed to copy data into an input tensor. */
TFLInterpreterErrorCodeFailedToCopyDataToInputTensor,
/** Copying data into an output tensor not allowed. */
TFLInterpreterErrorCodeCopyDataToOutputTensorNotAllowed,
/** Failed to get data from a tensor. */
TFLInterpreterErrorCodeFailedToGetDataFromTensor,
/** Failed to allocate memory for tensors. */
TFLInterpreterErrorCodeFailedToAllocateTensors,
/** Operation not allowed without allocating memory for tensors first. */
TFLInterpreterErrorCodeAllocateTensorsRequired,
/** Operation not allowed without invoking the interpreter first. */
TFLInterpreterErrorCodeInvokeInterpreterRequired,
};
/**
* A TensorFlow Lite model interpreter.
*
* Note: Interpreter instances are *not* thread-safe.
*/
@interface TFLInterpreter : NSObject
/** The total number of input tensors. 0 if the interpreter creation failed. */
@property(nonatomic, readonly) NSUInteger inputTensorCount;
/** The total number of output tensors. 0 if the interpreter creation failed. */
@property(nonatomic, readonly) NSUInteger outputTensorCount;
/** An ordered list of the SignatureDef exported method names available in the model. */
@property(nonatomic, readonly) NSArray<NSString *> *signatureKeys;
/** Unavailable. */
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* Initializes a new TensorFlow Lite interpreter instance with the given model file path and the
* default interpreter options.
*
* @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device.
* @param error An optional error parameter populated when there is an error in initializing the
* interpreter.
*
* @return A new instance of `TFLInterpreter` with the given model and the default interpreter
* options. `nil` if there is an error in initializing the interpreter.
*/
- (nullable instancetype)initWithModelPath:(NSString *)modelPath error:(NSError **)error;
/**
* Initializes a new TensorFlow Lite interpreter instance with the given model file path and
* options.
*
* @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device.
* @param options Options to use for configuring the TensorFlow Lite interpreter.
* @param error An optional error parameter populated when there is an error in initializing the
* interpreter.
*
* @return A new instance of `TFLInterpreter` with the given model and options. `nil` if there is an
* error in initializing the interpreter.
*/
- (nullable instancetype)initWithModelPath:(NSString *)modelPath
options:(TFLInterpreterOptions *)options
error:(NSError **)error;
/**
* Initializes a new TensorFlow Lite interpreter instance with the given model file path, options
* and delegates.
*
* @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device.
* @param options Options to use for configuring the TensorFlow Lite interpreter.
* @param delegates Delegates to use with the TensorFlow Lite interpreter. When the array is empty,
* no delegate will be applied.
* @param error An optional error parameter populated when there is an error in initializing the
* interpreter.
*
* @return A new instance of `TFLInterpreter` with the given model and options. `nil` if there is an
* error in initializing the interpreter.
*/
- (nullable instancetype)initWithModelPath:(NSString *)modelPath
options:(TFLInterpreterOptions *)options
delegates:(NSArray<TFLDelegate *> *)delegates
error:(NSError **)error NS_DESIGNATED_INITIALIZER;
/**
* Invokes the interpreter to run inference.
*
* @param error An optional error parameter populated when there is an error in invoking the
* interpreter.
*
* @return Whether the invocation is successful. Returns NO if an error occurred.
*/
- (BOOL)invokeWithError:(NSError **)error;
/**
* Returns the input tensor at the given index.
*
* @param index The index of an input tensor.
* @param error An optional error parameter populated when there is an error in looking up the input
* tensor.
*
* @return The input tensor at the given index. `nil` if there is an error. See the `TFLTensor`
* class documentation for more details on the life expectancy between the returned tensor and
* this interpreter.
*/
- (nullable TFLTensor *)inputTensorAtIndex:(NSUInteger)index error:(NSError **)error;
/**
* Returns the output tensor at the given index.
*
* @param index The index of an output tensor.
* @param error An optional error parameter populated when there is an error in looking up the
* output tensor.
*
* @return The output tensor at the given index. `nil` if there is an error. See the `TFLTensor`
* class documentation for more details on the life expectancy between the returned tensor and
* this interpreter.
*/
- (nullable TFLTensor *)outputTensorAtIndex:(NSUInteger)index error:(NSError **)error;
/**
* Resizes the input tensor at the given index to the specified shape (an array of positive unsigned
* integers).
*
* @param index The index of an input tensor.
* @param shape Shape that the given input tensor should be resized to. It should be an array of
* positive unsigned integer(s) containing the size of each dimension.
* @param error An optional error parameter populated when there is an error in resizing the input
* tensor.
*
* @return Whether the input tensor was resized successfully. Returns NO if an error occurred.
*/
- (BOOL)resizeInputTensorAtIndex:(NSUInteger)index
toShape:(NSArray<NSNumber *> *)shape
error:(NSError **)error;
/**
* Allocates memory for tensors.
*
* @param error An optional error parameter populated when there is an error in allocating memory.
*
* @return Whether memory allocation is successful. Returns NO if an error occurred.
*/
- (BOOL)allocateTensorsWithError:(NSError **)error;
/**
* Returns a new signature runner instance for the signature with the given key in the model.
*
* @param key The signature key.
* @param error An optional error parameter populated when there is an error creating the signature
* runner.
*
* @return A new signature runner instance for the signature with given key.
*/
- (nullable TFLSignatureRunner *)signatureRunnerWithKey:(NSString *)key error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Custom configuration options for a TensorFlow Lite interpreter. */
@interface TFLInterpreterOptions : NSObject
/**
* Maximum number of threads that the interpreter should run on. Defaults to 0 (unspecified, letting
* TensorFlow Lite to optimize the threading decision).
*/
@property(nonatomic) NSUInteger numberOfThreads;
/**
* Experimental: Enable an optimized set of floating point CPU kernels (provided by XNNPACK).
*
* Enabling this flag will enable use of a new, highly optimized set of CPU kernels provided via the
* XNNPACK delegate. Currently, this is restricted to a subset of floating point operations.
* Eventually, we plan to enable this by default, as it can provide significant performance benefits
* for many classes of floating point models. See
* https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/xnnpack/README.md
* for more details.
*
* Things to keep in mind when enabling this flag:
*
* * Startup time and resize time may increase.
* * Baseline memory consumption may increase.
* * Compatibility with other delegates (e.g., GPU) has not been fully validated.
* * Quantized models will not see any benefit.
*
* WARNING: This is an experimental interface that is subject to change.
*/
@property(nonatomic) BOOL useXNNPACK;
/**
* Initializes a new instance of `TFLInterpreterOptions`.
*
* @return A new instance of `TFLInterpreterOptions`.
*/
- (instancetype)init NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,91 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
#import "TFLDelegate.h"
NS_ASSUME_NONNULL_BEGIN
/**
* @enum TFLMetalDelegateThreadWaitType
* This enum specifies wait type for Metal delegate.
*/
typedef NS_ENUM(NSUInteger, TFLMetalDelegateThreadWaitType) {
/**
* The thread does not wait for the work to complete. Useful when the output of the work is used
* with the GPU pipeline.
*/
TFLMetalDelegateThreadWaitTypeDoNotWait,
/** The thread waits until the work is complete. */
TFLMetalDelegateThreadWaitTypePassive,
/**
* The thread waits for the work to complete with minimal latency, which may require additional
* CPU resources.
*/
TFLMetalDelegateThreadWaitTypeActive,
/** The thread waits for the work while trying to prevent the GPU from going into sleep mode. */
TFLMetalDelegateThreadWaitTypeAggressive,
};
/** Custom configuration options for a Metal delegate. */
@interface TFLMetalDelegateOptions : NSObject
/**
* Indicates whether the GPU delegate allows precision loss, such as allowing `Float16` precision
* for a `Float32` computation. The default is `false`.
*/
@property(nonatomic, getter=isPrecisionLossAllowed) BOOL precisionLossAllowed;
/**
* Indicates how the current thread should wait for work on the GPU to complete. The default
* is `TFLMetalDelegateThreadWaitTypePassive`.
*/
@property(nonatomic) TFLMetalDelegateThreadWaitType waitType;
/**
* Indicates whether the GPU delegate allows execution of an 8-bit quantized model. The default is
* `true`.
*/
@property(nonatomic, getter=isQuantizationEnabled) BOOL quantizationEnabled;
@end
/**
* A delegate that uses the `Metal` framework for performing TensorFlow Lite graph operations with
* GPU acceleration.
*/
@interface TFLMetalDelegate : TFLDelegate
/**
* Initializes a new GPU delegate with default options.
*
* @return A new GPU delegate with default options. `nil` when the GPU delegate creation fails.
*/
- (nullable instancetype)init;
/**
* Initializes a new GPU delegate with the given options.
*
* @param options GPU delegate options.
*
* @return A new GPU delegate with default options. `nil` when the GPU delegate creation fails.
*/
- (nullable instancetype)initWithOptions:(TFLMetalDelegateOptions *)options
NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,36 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Parameters for asymmetric quantization. Quantized values can be converted to float values using:
* `realValue = scale * (quantizedValue - zeroPoint)`.
*/
@interface TFLQuantizationParameters : NSObject
/** Scale of asymmetric quantization. */
@property(nonatomic, readonly) float scale;
/** Zero point of asymmetric quantization. */
@property(nonatomic, readonly) int32_t zeroPoint;
/** Unavailable. */
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,154 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class TFLTensor;
/** Domain for errors in the signature runner API. */
FOUNDATION_EXPORT NSErrorDomain const TFLSignatureRunnerErrorDomain;
/**
* @enum TFLSignatureRunnerErrorCode
* This enum specifies various error codes related to `TFLSignatureRunner`.
*/
typedef NS_ENUM(NSUInteger, TFLSignatureRunnerErrorCode) {
/** Input data has invalid byte size. */
TFLSignatureRunnerErrorCodeInvalidInputByteSize,
/** Provided shape is invalid. It must be a non-empty array of positive unsigned integers. */
TFLSignatureRunnerErrorCodeInvalidShape,
/** Failed to create `TFLSignatureRunner`. */
TFLSignatureRunnerErrorCodeFailedToCreateSignatureRunner,
/** Failed to invoke `TFLSignatureRunner`. */
TFLSignatureRunnerErrorCodeFailedToInvoke,
/** Failed to retrieve a tensor. */
TFLSignatureRunnerErrorCodeFailedToGetTensor,
/** Invalid tensor. */
TFLSignatureRunnerErrorCodeInvalidTensor,
/** Failed to resize an input tensor. */
TFLSignatureRunnerErrorCodeFailedToResizeInputTensor,
/** Failed to copy data into an input tensor. */
TFLSignatureRunnerErrorCodeFailedToCopyDataToInputTensor,
/** Copying data into an output tensor not allowed. */
TFLSignatureRunnerErrorCodeCopyDataToOutputTensorNotAllowed,
/** Failed to get data from a tensor. */
TFLSignatureRunnerErrorCodeFailedToGetDataFromTensor,
/** Failed to allocate memory for tensors. */
TFLSignatureRunnerErrorCodeFailedToAllocateTensors,
};
/**
* A TensorFlow Lite model signature runner. You can get a `TFLSignatureRunner` instance for a
* signature from the `TFLInterpreter` and then use the SignatureRunner APIs.
*
* @note `TFLSignatureRunner` instances are *not* thread-safe.
* @note Each `TFLSignatureRunner` instance is associated with a `TFLInterpreter` instance. As long
* as a `TFLSignatureRunner` instance is still in use, its associated `TFLInterpreter` instance
* will not be deallocated.
*/
@interface TFLSignatureRunner : NSObject
/** The signature key. */
@property(nonatomic, readonly) NSString *signatureKey;
/** An ordered list of the SignatureDefs input names. */
@property(nonatomic, readonly) NSArray<NSString *> *inputs;
/** An ordered list of the SignatureDefs output names. */
@property(nonatomic, readonly) NSArray<NSString *> *outputs;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* Returns the input tensor with the given input name in the signature.
*
* @param name The input name in the signature.
* @param error An optional error parameter populated when there is an error in looking up the input
* tensor.
*
* @return The input tensor with the given input name. `nil` if there is an error. See the
* `TFLTensor` class documentation for more details on the life expectancy between the returned
* tensor and this signature runner.
*/
- (nullable TFLTensor *)inputTensorWithName:(NSString *)name error:(NSError **)error;
/**
* Returns the output tensor with the given output name in the signature.
*
* @param name The output name in the signature.
* @param error An optional error parameter populated when there is an error in looking up the
* output tensor.
*
* @return The output tensor with the given output name. `nil` if there is an error. See the
* `TFLTensor` class documentation for more details on the life expectancy between the returned
* tensor and this signature runner.
*/
- (nullable TFLTensor *)outputTensorWithName:(NSString *)name error:(NSError **)error;
/**
* Resizes the input tensor with the given input name to the specified shape (an array of positive
* unsigned integers).
*
* @param name The input name.
* @param shape Shape that the given input tensor should be resized to. It should be an array of
* positive unsigned integer(s) containing the size of each dimension.
* @param error An optional error parameter populated when there is an error in resizing the input
* tensor.
*
* @return Whether the input tensor was resized successfully. Returns NO if an error occurred.
*/
- (BOOL)resizeInputTensorWithName:(NSString *)name
toShape:(NSArray<NSNumber *> *)shape
error:(NSError **)error;
/**
* Allocates memory for tensors.
*
* @note This call is *purely optional*. Tensor allocation will occur automatically during
* execution.
*
* @param error An optional error parameter populated when there is an error in allocating memory.
*
* @return Whether memory allocation is successful. Returns NO if an error occurred.
*/
- (BOOL)allocateTensorsWithError:(NSError **)error;
/**
* Invoke the signature with given input data.
*
* @param inputs A map from input name to the input data. The input data will be copied into the
* input tensor.
* @param error An optional error parameter populated when there is an error in invoking the
* signature.
*
* @return Whether the invocation is successful. Returns NO if an error occurred.
*/
- (BOOL)invokeWithInputs:(NSDictionary<NSString *, NSData *> *)inputs Error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
+121
View File
@@ -0,0 +1,121 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
@class TFLQuantizationParameters;
NS_ASSUME_NONNULL_BEGIN
/**
* @enum TFLTensorDataType
* This enum specifies supported TensorFlow Lite tensor data types.
*/
typedef NS_ENUM(NSUInteger, TFLTensorDataType) {
/** Tensor data type not available. This indicates an error with the model. */
TFLTensorDataTypeNoType,
/** 32-bit single precision floating point. */
TFLTensorDataTypeFloat32,
/** 16-bit half precision floating point. */
TFLTensorDataTypeFloat16,
/** 32-bit signed integer. */
TFLTensorDataTypeInt32,
/** 8-bit unsigned integer. */
TFLTensorDataTypeUInt8,
/** 64-bit signed integer. */
TFLTensorDataTypeInt64,
/** Boolean. */
TFLTensorDataTypeBool,
/** 16-bit signed integer. */
TFLTensorDataTypeInt16,
/** 8-bit signed integer. */
TFLTensorDataTypeInt8,
/** 64-bit double precision floating point. */
TFLTensorDataTypeFloat64,
/** 16-bit bfloat16 floating point. */
TFLTensorDataTypeBFloat16,
};
/**
* An input or output tensor in a TensorFlow Lite model.
*
* @warning Each `TFLTensor` instance is associated with its provider, either a `TFLInterpreter` or
* a `TFLSignatureRunner` instance. Multiple `TFLTensor` instances of the same TensorFlow Lite model
* are associated with the same provider instance. As long as a `TFLTensor` instance is still in
* use, its associated provider instance will not be deallocated.
*/
@interface TFLTensor : NSObject
/** Name of the tensor. */
@property(nonatomic, readonly, copy) NSString *name;
/** Data type of the tensor. */
@property(nonatomic, readonly) TFLTensorDataType dataType;
/** Parameters for asymmetric quantization. `nil` if the tensor does not use quantization. */
@property(nonatomic, readonly, nullable) TFLQuantizationParameters *quantizationParameters;
/** Unavailable. */
- (instancetype)init NS_UNAVAILABLE;
/**
* Copies the given data into an input tensor. This is allowed only for an input tensor and only
* before the interpreter or the signature runner is invoked; otherwise an error will be returned.
*
* @param data The data to set. The byte size of the data must match what's required by the input
* tensor.
* @param error An optional error parameter populated when there is an error in copying the data.
*
* @return Whether the data was copied into the input tensor successfully. Returns NO if an error
* occurred.
*/
- (BOOL)copyData:(NSData *)data error:(NSError **)error;
/**
* Retrieves a copy of data in the tensor. For an output tensor, the data is only available after
* the interpreter or signature runner invocation has successfully completed; otherwise an error
* will be returned.
*
* @param error An optional error parameter populated when there is an error in retrieving the data.
*
* @return A copy of data in the tensor. `nil` if there is an error in retrieving the data or the
* data is not available.
*/
- (nullable NSData *)dataWithError:(NSError **)error;
/**
* Retrieves the shape of the tensor, an array of positive unsigned integers containing the size
* of each dimension. For example: the shape of [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]] is
* [2, 2, 3] (i.e. an array of 2 arrays of 2 arrays of 3 numbers).
*
* @param error An optional error parameter populated when there is an error in retrieving the
* shape.
*
* @return The shape of the tensor. `nil` if there is an error in retrieving the shape.
*/
- (nullable NSArray<NSNumber *> *)shapeWithError:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,32 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
#import "TFLDelegate.h"
#import "TFLInterpreter.h"
#import "TFLInterpreterOptions.h"
#import "TFLQuantizationParameters.h"
#import "TFLSignatureRunner.h"
#import "TFLTensor.h"
NS_ASSUME_NONNULL_BEGIN
/**
* A string describing the semantic versioning information for the TensorFlow Lite runtime. Is an
* empty string if the version could not be determined.
*/
FOUNDATION_EXPORT NSString *const TFLVersion;
NS_ASSUME_NONNULL_END
@@ -0,0 +1,5 @@
platform :ios, '9.0'
target 'TestApp' do
pod 'TensorFlowLiteObjC'
end
@@ -0,0 +1,25 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property(nonatomic) UIWindow *window;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,22 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 "AppDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@implementation AppDelegate
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Y0Z-8F-bB8"/>
<viewControllerLayoutGuide type="bottom" id="TqJ-Hq-gHs"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="TensorFlow Lite Test" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="zIC-MS-HeK">
<rect key="frame" x="16" y="313" width="343" height="39"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="32"/>
<color key="textColor" red="1" green="0.50329624702372611" blue="0.013296667412401542" alpha="0.84705882352941175" colorSpace="custom" customColorSpace="displayP3"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="zIC-MS-HeK" secondAttribute="trailing" constant="16" id="1Wp-jb-ol6"/>
<constraint firstItem="zIC-MS-HeK" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="R2T-Hp-TBa"/>
<constraint firstItem="zIC-MS-HeK" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" constant="16" id="alE-O6-WL6"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52" y="374.66266866566718"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment version="2304" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="5Qd-iD-SiH"/>
<viewControllerLayoutGuide type="bottom" id="zTS-de-uge"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ahi-i4-2FP" userLabel="Top Model Toolbar">
<rect key="frame" x="0.0" y="34" width="375" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="nwy-nk-0wZ"/>
</constraints>
<items>
<barButtonItem style="plain" id="Ywd-KS-s96">
<segmentedControl key="customView" opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="0" id="8kc-88-CHj" userLabel="Model Control">
<rect key="frame" x="16" y="6" width="343" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="Add"/>
<segment title="AddQuantized"/>
<segment title="MultiAdd"/>
</segments>
<connections>
<action selector="modelChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="z13-8K-EwC"/>
</connections>
</segmentedControl>
</barButtonItem>
</items>
</toolbar>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UWb-3E-O5r" userLabel="Bottom Invoke Toolbar">
<rect key="frame" x="0.0" y="139" width="375" height="44"/>
<items>
<barButtonItem title="Invoke Interpreter" width="374" id="He4-7G-biW">
<connections>
<action selector="invokeInterpreter:" destination="BYZ-38-t0r" id="Ycs-E9-Vul"/>
</connections>
</barButtonItem>
</items>
</toolbar>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" editable="NO" adjustsFontForContentSizeCategory="YES" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7Ws-3t-76I">
<rect key="frame" x="0.0" y="194" width="375" height="488"/>
<color key="backgroundColor" red="0.12820077356385221" green="0.40366933178860925" blue="0.96080166101455688" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<color key="textColor" systemColor="tableCellGroupedBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="ahi-i4-2FP" firstAttribute="top" secondItem="5Qd-iD-SiH" secondAttribute="bottom" constant="14" id="0V2-16-9cM"/>
<constraint firstAttribute="trailing" secondItem="ahi-i4-2FP" secondAttribute="trailing" id="1D2-FC-OQ0"/>
<constraint firstItem="UWb-3E-O5r" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="1To-8n-Knb"/>
<constraint firstItem="7Ws-3t-76I" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="3Et-px-WCV"/>
<constraint firstItem="zTS-de-uge" firstAttribute="top" secondItem="7Ws-3t-76I" secondAttribute="bottom" id="Lkb-XF-ldX"/>
<constraint firstItem="7Ws-3t-76I" firstAttribute="top" secondItem="UWb-3E-O5r" secondAttribute="bottom" id="bXr-pF-Ld2"/>
<constraint firstItem="ahi-i4-2FP" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="c98-HO-3m5"/>
<constraint firstAttribute="trailing" secondItem="UWb-3E-O5r" secondAttribute="trailing" id="oJz-hf-dJa"/>
<constraint firstAttribute="trailing" secondItem="7Ws-3t-76I" secondAttribute="trailing" id="oyy-C7-mUJ"/>
<constraint firstItem="UWb-3E-O5r" firstAttribute="top" secondItem="ahi-i4-2FP" secondAttribute="bottom" constant="62" id="uFg-MF-aJz"/>
</constraints>
</view>
<connections>
<outlet property="invokeButton" destination="He4-7G-biW" id="kpj-CS-Fss"/>
<outlet property="modelControl" destination="8kc-88-CHj" id="GTB-WG-ozW"/>
<outlet property="resultsTextView" destination="7Ws-3t-76I" id="fnd-i5-Pdh"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="136.80000000000001" y="132.68365817091455"/>
</scene>
</scenes>
<resources>
<systemColor name="tableCellGroupedBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,42 @@
<?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>0.0.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>
@@ -0,0 +1,22 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,424 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 "ViewController.h"
#if COCOAPODS
@import TFLTensorFlowLite;
#else
#import "TFLTensorFlowLite.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
* Safely dispatches the given `block` on the main thread. If already on the main thread, the given
* block is executed immediately; otherwise, dispatches the block asynchronously on the main thread.
*
* @param block The block to dispatch on the main thread.
*/
void TLTSafeDispatchOnMain(dispatch_block_t block) {
if (block == nil) return;
if (NSThread.isMainThread) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
/**
* Name of a float model that performs two add operations on one input tensor and returns the result
* in one output tensor.
*/
static NSString *const kModelNameAdd = @"add";
/**
* Name of a quantized model that performs two add operations on one input tensor and returns the
* result in one output tensor.
*/
static NSString *const kModelNameAddQuantized = @"add_quantized";
/**
* Name of a float model that performs three add operations on four input tensors and returns the
* results in 2 output tensors.
*/
static NSString *const kModelNameMultiAdd = @"multi_add";
/** Model resource type. */
static NSString *const kModelType = @"bin";
/** The label for the serial queue for synchronizing interpreter calls. */
static const char *kInterpreterSerialQueueLabel = "com.tensorflow.lite.objc.testapp.interpreter";
static NSString *const kNilInterpreterError =
@"Failed to invoke the interpreter because the interpreter was nil.";
static NSString *const kInvokeInterpreterError = @"Failed to invoke interpreter due to error: %@.";
/** Model paths. */
static NSArray<NSString *> *gModelPaths;
@interface ViewController ()
/** Serial queue for synchronizing interpreter calls. */
@property(nonatomic) dispatch_queue_t interpreterSerialQueue;
/** TensorFlow Lite interpreter for the currently selected model. */
@property(nonatomic) TFLInterpreter *interpreter;
@property(weak, nonatomic) IBOutlet UISegmentedControl *modelControl;
@property(weak, nonatomic) IBOutlet UIBarButtonItem *invokeButton;
@property(weak, nonatomic) IBOutlet UITextView *resultsTextView;
@end
@implementation ViewController
#pragma mark - NSObject
+ (void)initialize {
if (self == [ViewController self]) {
gModelPaths = @[
[NSBundle.mainBundle pathForResource:kModelNameAdd ofType:kModelType],
[NSBundle.mainBundle pathForResource:kModelNameAddQuantized ofType:kModelType],
[NSBundle.mainBundle pathForResource:kModelNameMultiAdd ofType:kModelType],
];
}
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.interpreterSerialQueue =
dispatch_queue_create(kInterpreterSerialQueueLabel, DISPATCH_QUEUE_SERIAL);
self.invokeButton.enabled = NO;
[self updateResultsText:[NSString stringWithFormat:@"Using TensorFlow Lite runtime version %@.",
TFLVersion]];
[self loadModel];
}
#pragma mark - IBActions
- (IBAction)modelChanged:(id)sender {
self.invokeButton.enabled = NO;
NSString *results = [NSString
stringWithFormat:@"Switched to the %@ model.",
[self.modelControl
titleForSegmentAtIndex:self.modelControl.selectedSegmentIndex]];
[self updateResultsText:results];
[self loadModel];
}
- (IBAction)invokeInterpreter:(id)sender {
switch (self.modelControl.selectedSegmentIndex) {
case 0:
[self invokeAdd];
break;
case 1:
[self invokeAddQuantized];
break;
case 2:
[self invokeMultiAdd];
}
}
#pragma mark - Private
/** Path of the currently selected model. */
- (nullable NSString *)currentModelPath {
return self.modelControl.selectedSegmentIndex == UISegmentedControlNoSegment
? nil
: gModelPaths[self.modelControl.selectedSegmentIndex];
}
- (void)loadModel {
NSString *modelPath = [self currentModelPath];
if (modelPath.length == 0) {
[self updateResultsText:@"No model is selected."];
return;
}
__weak typeof(self) weakSelf = self;
dispatch_async(self.interpreterSerialQueue, ^{
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
options.numberOfThreads = 2;
NSError *error;
weakSelf.interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
options:options
delegates:@[]
error:&error];
if (weakSelf.interpreter == nil || error != nil) {
NSString *results =
[NSString stringWithFormat:@"Failed to create the interpreter due to error:%@",
error.localizedDescription];
[weakSelf updateResultsText:results];
} else {
TLTSafeDispatchOnMain(^{
weakSelf.invokeButton.enabled = YES;
});
}
});
}
- (void)invokeAdd {
__weak typeof(self) weakSelf = self;
dispatch_async(self.interpreterSerialQueue, ^{
if (weakSelf.interpreter == nil) {
[weakSelf updateResultsText:kNilInterpreterError];
return;
}
NSArray<NSNumber *> *shape = @[@2];
NSError *error;
if (![weakSelf.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
if (![weakSelf.interpreter allocateTensorsWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
TFLTensor *inputTensor = [weakSelf.interpreter inputTensorAtIndex:0 error:&error];
if (inputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[inputData appendBytes:&one length:sizeof(float)];
[inputData appendBytes:&three length:sizeof(float)];
if (![inputTensor copyData:inputData error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
if (![weakSelf.interpreter invokeWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
TFLTensor *outputTensor = [weakSelf.interpreter outputTensorAtIndex:0 error:&error];
if (outputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSData *outputData = [outputTensor dataWithError:&error];
if (outputData == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
float output[2U];
[outputData getBytes:output length:(sizeof(float) * 2U)];
[weakSelf
updateResultsText:[NSString stringWithFormat:@"Performing 2 add operations:\n\nInput = "
@"[%.1f, %.1f]\n\nOutput = [%.1f, %.1f]",
one, three, output[0], output[1]]];
});
}
- (void)invokeAddQuantized {
__weak typeof(self) weakSelf = self;
dispatch_async(self.interpreterSerialQueue, ^{
if (weakSelf.interpreter == nil) {
[weakSelf updateResultsText:kNilInterpreterError];
return;
}
NSArray<NSNumber *> *shape = @[@2];
NSError *error;
if (![weakSelf.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
if (![weakSelf.interpreter allocateTensorsWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
TFLTensor *inputTensor = [weakSelf.interpreter inputTensorAtIndex:0 error:&error];
if (inputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
uint8_t one = 1U;
uint8_t three = 3U;
[inputData appendBytes:&one length:sizeof(uint8_t)];
[inputData appendBytes:&three length:sizeof(uint8_t)];
if (![inputTensor copyData:inputData error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
if (![weakSelf.interpreter invokeWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
TFLTensor *outputTensor = [weakSelf.interpreter outputTensorAtIndex:0 error:&error];
if (outputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
TFLQuantizationParameters *params = outputTensor.quantizationParameters;
if (params == nil) {
[weakSelf updateResultsText:
[NSString stringWithFormat:kInvokeInterpreterError,
@"Missing qualitization parameters in the output"]];
return;
}
NSData *outputData = [outputTensor dataWithError:&error];
if (outputData == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
uint8_t output[2U];
[outputData getBytes:output length:(sizeof(uint8_t) * 2U)];
float dequantized[2U];
dequantized[0] = params.scale * (output[0] - params.zeroPoint);
dequantized[1] = params.scale * (output[1] - params.zeroPoint);
[weakSelf updateResultsText:
[NSString stringWithFormat:@"Performing 2 add operations on quantized input:\n\n"
@"Input = [%d, %d]\n\nQuantized Output = [%d, %d]\n\n"
@"Dequantized Output = [%f, %f]",
one, three, output[0], output[1], dequantized[0],
dequantized[1]]];
});
}
- (void)invokeMultiAdd {
__weak typeof(self) weakSelf = self;
dispatch_async(self.interpreterSerialQueue, ^{
if (weakSelf.interpreter == nil) {
[weakSelf updateResultsText:kNilInterpreterError];
return;
}
NSArray<NSNumber *> *shape = @[@2];
NSError *error;
for (int i = 0; i < weakSelf.interpreter.inputTensorCount; ++i) {
if (![weakSelf.interpreter resizeInputTensorAtIndex:i toShape:shape error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
}
if (![weakSelf.interpreter allocateTensorsWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSString *inputs = @"";
for (int i = 0; i < weakSelf.interpreter.inputTensorCount; ++i) {
TFLTensor *inputTensor = [weakSelf.interpreter inputTensorAtIndex:i error:&error];
if (inputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float input1 = (float)(i + 1);
float input2 = (float)(i + 2);
inputs = [NSString stringWithFormat:@"%@%@[%.1f, %.1f]", inputs,
(inputs.length == 0 ? @"[" : @", "), input1, input2];
[inputData appendBytes:&input1 length:sizeof(float)];
[inputData appendBytes:&input2 length:sizeof(float)];
if (![inputTensor copyData:inputData error:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
}
inputs = [NSString stringWithFormat:@"%@]", inputs];
if (![weakSelf.interpreter invokeWithError:&error]) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSString *outputs = @"";
for (int i = 0; i < weakSelf.interpreter.outputTensorCount; ++i) {
TFLTensor *outputTensor = [weakSelf.interpreter outputTensorAtIndex:i error:&error];
if (outputTensor == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
NSData *outputData = [outputTensor dataWithError:&error];
if (outputData == nil || error != nil) {
[weakSelf updateResultsText:[NSString stringWithFormat:kInvokeInterpreterError,
error.localizedDescription]];
return;
}
float output[2U];
[outputData getBytes:output length:(sizeof(float) * 2U)];
outputs =
[NSString stringWithFormat:@"%@%@[%.1f, %.1f]", outputs,
(outputs.length == 0 ? @"[" : @", "), output[0], output[1]];
}
outputs = [NSString stringWithFormat:@"%@]", outputs];
[weakSelf
updateResultsText:
[NSString
stringWithFormat:@"Performing 3 add operations:\n\nInputs = %@\n\nOutputs = %@",
inputs, outputs]];
});
}
- (void)updateResultsText:(NSString *)text {
__weak typeof(self) weakSelf = self;
TLTSafeDispatchOnMain(^{
weakSelf.resultsTextView.text = text;
});
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,22 @@
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@@ -0,0 +1,35 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
#import "tensorflow/lite/objc/apis/TFLTensor.h"
NS_ASSUME_NONNULL_BEGIN
typedef struct TfLiteTensor TfLiteTensor;
@class TFLQuantizationParameters;
/** Gets the tensor data type from a c tensor. */
FOUNDATION_EXTERN TFLTensorDataType TFLTensorDataTypeFromCTensor(const TfLiteTensor *cTensor);
/** Gets the tensor name from a c tensor. */
FOUNDATION_EXTERN NSString *__nullable TFLTensorNameFromCTensor(const TfLiteTensor *cTensor);
/** Gets the quantization parameters from a c tensor. */
FOUNDATION_EXTERN TFLQuantizationParameters *__nullable
TFLQuantizationParamsFromCTensor(const TfLiteTensor *cTensor);
NS_ASSUME_NONNULL_END
@@ -0,0 +1,89 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 "TFLCommonUtil.h"
#import "TFLQuantizationParameters+Internal.h"
#import "tensorflow/lite/objc/apis/TFLTensor.h"
#ifdef COCOAPODS
#import <TensorFlowLiteC/TensorFlowLiteC.h>
#else
#include "tensorflow/lite/core/c/c_api.h"
#endif // COCOAPODS
NS_ASSUME_NONNULL_BEGIN
TFLTensorDataType TFLTensorDataTypeFromCTensor(const TfLiteTensor *cTensor) {
TfLiteType cTensorType = TfLiteTensorType(cTensor);
switch (cTensorType) {
case kTfLiteFloat32:
return TFLTensorDataTypeFloat32;
case kTfLiteFloat16:
return TFLTensorDataTypeFloat16;
case kTfLiteBFloat16:
return TFLTensorDataTypeBFloat16;
case kTfLiteFloat64:
return TFLTensorDataTypeFloat64;
case kTfLiteInt32:
return TFLTensorDataTypeInt32;
case kTfLiteUInt8:
return TFLTensorDataTypeUInt8;
case kTfLiteInt8:
return TFLTensorDataTypeInt8;
case kTfLiteInt64:
return TFLTensorDataTypeInt64;
case kTfLiteBool:
return TFLTensorDataTypeBool;
case kTfLiteInt16:
return TFLTensorDataTypeInt16;
case kTfLiteNoType:
case kTfLiteString:
case kTfLiteComplex64:
case kTfLiteComplex128:
case kTfLiteUInt16:
case kTfLiteUInt32:
case kTfLiteUInt64:
case kTfLiteUInt4:
case kTfLiteInt4:
case kTfLiteInt2:
case kTfLiteResource:
case kTfLiteVariant:
case kTfLiteFloat8E4M3FN:
case kTfLiteFloat8E5M2:
// Not all datatypes are supported in the TfLite Objc API.
return TFLTensorDataTypeNoType;
}
}
NSString *__nullable TFLTensorNameFromCTensor(const TfLiteTensor *cTensor) {
const char *cName = TfLiteTensorName(cTensor);
if (cName == nullptr) return nil;
return [NSString stringWithUTF8String:cName];
}
TFLQuantizationParameters *__nullable
TFLQuantizationParamsFromCTensor(const TfLiteTensor *cTensor) {
TfLiteQuantizationParams cParams = TfLiteTensorQuantizationParams(cTensor);
TFLQuantizationParameters *quantizationParams;
// TODO(b/119735362): Update this check once the TfLiteQuantizationParams struct has a mode.
if (cParams.scale != 0.0) {
quantizationParams = [[TFLQuantizationParameters alloc] initWithScale:cParams.scale
zeroPoint:cParams.zero_point];
}
return quantizationParams;
}
NS_ASSUME_NONNULL_END
@@ -0,0 +1,86 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLCoreMLDelegate.h"
#ifdef COCOAPODS
@import TensorFlowLiteCCoreML;
#else
#include "tensorflow/lite/delegates/coreml/coreml_delegate.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@implementation TFLCoreMLDelegateOptions
- (instancetype)init {
self = [super init];
if (self != nil) {
_coreMLVersion = 0;
_maxDelegatedPartitions = 0;
_minNodesPerPartition = 2;
_enabledDevices = TFLCoreMLDelegateEnabledDevicesNeuralEngine;
}
return self;
}
@end
@implementation TFLCoreMLDelegate
@synthesize cDelegate = _cDelegate;
#pragma mark - NSObject
- (void)dealloc {
TfLiteCoreMlDelegateDelete((TfLiteDelegate*)self.cDelegate);
}
#pragma mark - Public
- (nullable instancetype)init {
TFLCoreMLDelegateOptions* options = [[TFLCoreMLDelegateOptions alloc] init];
return [self initWithOptions:options];
}
- (nullable instancetype)initWithOptions:(TFLCoreMLDelegateOptions*)options {
self = [super init];
if (self != nil) {
TfLiteCoreMlDelegateOptions cOptions;
cOptions.coreml_version = options.coreMLVersion;
cOptions.max_delegated_partitions = options.maxDelegatedPartitions;
cOptions.min_nodes_per_partition = options.minNodesPerPartition;
switch (options.enabledDevices) {
case TFLCoreMLDelegateEnabledDevicesNeuralEngine:
cOptions.enabled_devices = TfLiteCoreMlDelegateDevicesWithNeuralEngine;
break;
case TFLCoreMLDelegateEnabledDevicesAll:
cOptions.enabled_devices = TfLiteCoreMlDelegateAllDevices;
break;
}
_cDelegate = TfLiteCoreMlDelegateCreate(&cOptions);
if (_cDelegate == nil) {
return nil;
}
}
return self;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,22 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TFLDelegate
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,53 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
#import "tensorflow/lite/objc/apis/TFLInterpreter.h"
NS_ASSUME_NONNULL_BEGIN
/** Helper utility for error reporting. */
@interface TFLErrorUtil : NSObject
/**
* Creates and saves an interpreter error with the given error code and description.
*
* @param code Error code.
* @param description Error description.
* @param error Pointer to where to save the created error. If `nil`, no error will be saved.
*/
+ (void)saveInterpreterErrorWithCode:(TFLInterpreterErrorCode)code
description:(NSString *)description
error:(NSError **)error;
/**
* Sets the error with the given domain, error code and description.
*
* @param domain The error domain.
* @param code The error code.
* @param description The error description.
* @param error A pointer to populate the error. If `nil`, no error will be populated.
*/
+ (void)setError:(NSError **)error
withDomain:(NSErrorDomain)domain
code:(NSInteger)code
description:(NSString *)description;
/** Unavailable. */
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,45 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 "TFLErrorUtil.h"
NS_ASSUME_NONNULL_BEGIN
/** Error domain of TensorFlow Lite interpreter related errors. */
static NSString *const TFLInterpreterErrorDomain = @"org.tensorflow.lite.interpreter";
@implementation TFLErrorUtil
#pragma mark - Public
+ (void)saveInterpreterErrorWithCode:(TFLInterpreterErrorCode)code
description:(NSString *)description
error:(NSError **)error {
[self setError:error withDomain:TFLInterpreterErrorDomain code:code description:description];
}
+ (void)setError:(NSError **)error
withDomain:(NSErrorDomain)domain
code:(NSInteger)code
description:(NSString *)description {
if (error) {
*error = [NSError errorWithDomain:domain
code:code
userInfo:@{NSLocalizedDescriptionKey : description}];
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLInterpreter.h"
#import "TFLTensorDataAccessor.h"
typedef struct TfLiteInterpreter TfLiteInterpreter;
NS_ASSUME_NONNULL_BEGIN
@interface TFLInterpreter (Internal) <TFLTensorDataAccessor>
/** TfLiteInterpreter backed by C API. */
@property(nonatomic, readonly) TfLiteInterpreter *interpreter;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,459 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLInterpreter.h"
#include <vector>
#import "TFLCommonUtil.h"
#import "TFLErrorUtil.h"
#import "TFLQuantizationParameters+Internal.h"
#import "TFLSignatureRunner+Internal.h"
#import "TFLTensor+Internal.h"
#import "tensorflow/lite/objc/apis/TFLDelegate.h"
#import "tensorflow/lite/objc/apis/TFLInterpreterOptions.h"
#import "tensorflow/lite/objc/apis/TFLTensor.h"
#ifdef COCOAPODS
#import <TensorFlowLiteC/TensorFlowLiteC.h>
#else
#include "tensorflow/lite/core/c/c_api.h"
#include "tensorflow/lite/core/c/c_api_experimental.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#endif // COCOAPODS
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const TFLVersion =
TfLiteVersion() == nullptr ? @"" : [NSString stringWithUTF8String:TfLiteVersion()];
/**
* Error reporter for TFLInterpreter.
*
* @param user_data User data. Not used.
* @param format Error message which may contain argument formatting specifiers.
* @param args Values of the arguments in the error message.
*/
static void TFLInterpreterErrorReporter(void *user_data, const char *format, va_list args) {
NSLog(@"%@", [[NSString alloc] initWithFormat:@(format) arguments:args]);
}
@interface TFLInterpreter ()
/** TfLiteInterpreter backed by C API. */
@property(nonatomic) TfLiteInterpreter *interpreter;
/** TfLiteDelegate backed by C API. */
@property(nonatomic, nullable) TfLiteDelegate *xnnPackDelegate;
@end
@implementation TFLInterpreter
@synthesize signatureKeys = _signatureKeys;
#pragma mark - NSObject
- (void)dealloc {
TfLiteInterpreterDelete(_interpreter);
TfLiteXNNPackDelegateDelete(_xnnPackDelegate);
}
#pragma mark - Public
- (nullable instancetype)initWithModelPath:(NSString *)modelPath error:(NSError **)error {
return [self initWithModelPath:modelPath
options:[[TFLInterpreterOptions alloc] init]
delegates:@[]
error:error];
}
- (nullable instancetype)initWithModelPath:(NSString *)modelPath
options:(TFLInterpreterOptions *)options
error:(NSError **)error {
return [self initWithModelPath:modelPath options:options delegates:@[] error:error];
}
- (nullable instancetype)initWithModelPath:(NSString *)modelPath
options:(TFLInterpreterOptions *)options
delegates:(NSArray<TFLDelegate *> *)delegates
error:(NSError **)error {
self = [super init];
if (self != nil) {
TfLiteModel *model = nullptr;
TfLiteInterpreterOptions *cOptions = nullptr;
@try {
const char *modelPathCString = modelPath.UTF8String;
NSString *pathErrorString =
[NSString stringWithFormat:@"Cannot load model from path (%@).", modelPath];
if (modelPathCString == nullptr) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToLoadModel
description:pathErrorString
error:error];
return nil;
}
model = TfLiteModelCreateFromFile(modelPathCString);
if (model == nullptr) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToLoadModel
description:pathErrorString
error:error];
return nil;
}
cOptions = TfLiteInterpreterOptionsCreate();
if (cOptions == nullptr) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter
description:@"Failed to create the interpreter."
error:error];
return nil;
}
if (options.numberOfThreads > 0) {
TfLiteInterpreterOptionsSetNumThreads(cOptions, (int32_t)options.numberOfThreads);
}
TfLiteInterpreterOptionsSetErrorReporter(cOptions, TFLInterpreterErrorReporter, nullptr);
if (options.useXNNPACK) {
TfLiteXNNPackDelegateOptions xnnPackOptions = TfLiteXNNPackDelegateOptionsDefault();
if (options.numberOfThreads > 0) {
xnnPackOptions.num_threads = (int32_t)options.numberOfThreads;
}
_xnnPackDelegate = TfLiteXNNPackDelegateCreate(&xnnPackOptions);
TfLiteInterpreterOptionsAddDelegate(cOptions, _xnnPackDelegate);
}
for (TFLDelegate *delegate in delegates) {
if (delegate.cDelegate != nullptr) {
TfLiteInterpreterOptionsAddDelegate(
cOptions, reinterpret_cast<TfLiteDelegate *>(delegate.cDelegate));
}
}
TfLiteInterpreter *interpreter = TfLiteInterpreterCreate(model, cOptions);
if (interpreter == nullptr) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter
description:@"Failed to create the interpreter."
error:error];
return nil;
}
_interpreter = interpreter;
_inputTensorCount = (NSUInteger)TfLiteInterpreterGetInputTensorCount(_interpreter);
_outputTensorCount = (NSUInteger)TfLiteInterpreterGetOutputTensorCount(_interpreter);
if (_inputTensorCount <= 0 || _outputTensorCount <= 0) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter
description:@"Failed to create the interpreter."
error:error];
return nil;
}
} @finally {
TfLiteInterpreterOptionsDelete(cOptions);
TfLiteModelDelete(model);
}
}
return self;
}
- (NSArray<NSString *> *)signatureKeys {
if (_signatureKeys) return _signatureKeys;
NSUInteger signatureCount = TfLiteInterpreterGetSignatureCount(self.interpreter);
NSMutableArray<NSString *> *mutableKeyArray =
[[NSMutableArray alloc] initWithCapacity:signatureCount];
for (NSUInteger i = 0; i < signatureCount; i++) {
const char *signatureNameCString =
TfLiteInterpreterGetSignatureKey(self.interpreter, (int32_t)i);
NSString *signatureName = @"";
if (signatureNameCString != nullptr) {
signatureName = [NSString stringWithUTF8String:signatureNameCString] ?: @"";
}
[mutableKeyArray addObject:signatureName];
}
_signatureKeys = [mutableKeyArray copy];
return _signatureKeys;
}
- (BOOL)invokeWithError:(NSError **)error {
if (TfLiteInterpreterInvoke(self.interpreter) != kTfLiteOk) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToInvoke
description:@"Failed to invoke the interpreter."
error:error];
return NO;
}
return YES;
}
- (nullable TFLTensor *)inputTensorAtIndex:(NSUInteger)index error:(NSError **)error {
if (![self isValidTensorIndex:index belowLimit:self.inputTensorCount error:error]) {
return nil;
}
return [self tensorOfType:TFLTensorTypeInput atIndex:index error:error];
}
- (nullable TFLTensor *)outputTensorAtIndex:(NSUInteger)index error:(NSError **)error {
if (![self isValidTensorIndex:index belowLimit:self.outputTensorCount error:error]) {
return nil;
}
return [self tensorOfType:TFLTensorTypeOutput atIndex:index error:error];
}
- (BOOL)resizeInputTensorAtIndex:(NSUInteger)index
toShape:(NSArray<NSNumber *> *)shape
error:(NSError **)error {
if (![self isValidTensorIndex:index belowLimit:self.inputTensorCount error:error]) {
return NO;
}
if (shape.count == 0) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidShape
description:@"Invalid shape. Must not be empty."
error:error];
return NO;
}
std::vector<int> cDimensions(shape.count);
for (int dimIndex = 0; dimIndex < shape.count; ++dimIndex) {
int dimension = shape[dimIndex].intValue;
if (dimension <= 0) {
NSString *errorDescription = @"Invalid shape. Dimensions must be positive integers.";
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidShape
description:errorDescription
error:error];
return NO;
}
cDimensions[dimIndex] = dimension;
}
if (TfLiteInterpreterResizeInputTensor(self.interpreter, (int32_t)index, cDimensions.data(),
(int32_t)shape.count) != kTfLiteOk) {
NSString *errorDescription = [NSString
stringWithFormat:@"Failed to resize input tensor at index (%lu).", (unsigned long)index];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToResizeInputTensor
description:errorDescription
error:error];
return NO;
}
return YES;
}
- (BOOL)allocateTensorsWithError:(NSError **)error {
if (TfLiteInterpreterAllocateTensors(self.interpreter) != kTfLiteOk) {
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToAllocateTensors
description:@"Failed to allocate memory for tensors."
error:error];
return NO;
}
return YES;
}
- (nullable TFLSignatureRunner *)signatureRunnerWithKey:(NSString *)key error:(NSError **)error {
if (![self.signatureKeys containsObject:key]) {
NSString *errorDescription = [NSString
stringWithFormat:@"Failed to create a signature runner. Signature with key (%@) not found.",
key];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToCreateSignatureRunner
description:errorDescription];
return nil;
}
return [[TFLSignatureRunner alloc] initWithInterpreter:self signatureKey:key error:error];
}
#pragma mark - TFLTensorDataAccessor
- (BOOL)copyData:(NSData *)data toInputTensor:(TFLTensor *)inputTensor error:(NSError **)error {
if (inputTensor.type == TFLTensorTypeOutput) {
[TFLErrorUtil
saveInterpreterErrorWithCode:TFLInterpreterErrorCodeCopyDataToOutputTensorNotAllowed
description:@"Cannot copy data into an output tensor."
error:error];
return NO;
}
const TfLiteTensor *cTensor = [self cTensorOfType:TFLTensorTypeInput
atIndex:inputTensor.index
error:error];
if (cTensor == nullptr) {
return NO;
}
NSUInteger byteSize = (NSUInteger)TfLiteTensorByteSize(cTensor);
if (data.length != byteSize) {
NSString *errorDescription = [NSString
stringWithFormat:@"Input tensor at index (%lu) expects data size (%lu), but got (%lu).",
(unsigned long)index, (unsigned long)byteSize, (unsigned long)data.length];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidInputByteSize
description:errorDescription
error:error];
return NO;
}
if (TfLiteTensorCopyFromBuffer((TfLiteTensor *)cTensor, data.bytes, data.length) != kTfLiteOk) {
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to copy data into input tensor at index (%lu).",
(unsigned long)index];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCopyDataToInputTensor
description:errorDescription
error:error];
return NO;
}
return YES;
}
- (nullable NSData *)dataFromTensor:(TFLTensor *)tensor error:(NSError **)error {
const TfLiteTensor *cTensor = [self cTensorOfType:tensor.type atIndex:tensor.index error:error];
if (cTensor == nullptr) {
return nil;
}
void *bytes = TfLiteTensorData(cTensor);
NSUInteger byteSize = (NSUInteger)TfLiteTensorByteSize(cTensor);
if (bytes == nullptr || byteSize == 0) {
NSString *tensorType = [TFLTensor stringForTensorType:tensor.type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get data from %@ tensor at index (%lu).", tensorType,
(unsigned long)tensor.index];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetDataFromTensor
description:errorDescription
error:error];
return nil;
}
return [NSData dataWithBytes:bytes length:byteSize];
}
- (nullable NSArray<NSNumber *> *)shapeOfTensor:(TFLTensor *)tensor error:(NSError **)error {
const TfLiteTensor *cTensor = [self cTensorOfType:tensor.type atIndex:tensor.index error:error];
if (cTensor == nullptr) {
return nil;
}
NSString *tensorType = [TFLTensor stringForTensorType:tensor.type];
int32_t rank = TfLiteTensorNumDims(cTensor);
if (rank <= 0) {
NSString *errorDescription =
[NSString stringWithFormat:@"%@ tensor at index (%lu) has invalid rank (%d).", tensorType,
(unsigned long)tensor.index, rank];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidTensor
description:errorDescription
error:error];
return nil;
}
NSMutableArray<NSNumber *> *shape = [NSMutableArray arrayWithCapacity:rank];
for (int32_t dimIndex = 0; dimIndex < rank; dimIndex++) {
int32_t dimension = TfLiteTensorDim(cTensor, dimIndex);
if (dimension <= 0) {
NSString *errorDescription =
[NSString stringWithFormat:@"%@ tensor at index (%lu) has invalid %d-th dimension (%d).",
tensorType, (unsigned long)tensor.index, dimIndex, dimension];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidTensor
description:errorDescription
error:error];
return nil;
}
shape[dimIndex] = @((NSUInteger)dimension);
}
return shape;
}
#pragma mark - Private
- (const TfLiteTensor *)cTensorOfType:(TFLTensorType)type
atIndex:(NSUInteger)index
error:(NSError **)error {
const TfLiteTensor *tensor = nullptr;
switch (type) {
case TFLTensorTypeInput:
tensor = TfLiteInterpreterGetInputTensor(self.interpreter, (int32_t)index);
break;
case TFLTensorTypeOutput:
tensor = TfLiteInterpreterGetOutputTensor(self.interpreter, (int32_t)index);
break;
}
if (tensor == nullptr) {
NSString *tensorType = [TFLTensor stringForTensorType:type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get %@ tensor at index (%lu).", tensorType,
(unsigned long)index];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetTensor
description:errorDescription
error:error];
}
return tensor;
}
- (nullable TFLTensor *)tensorOfType:(TFLTensorType)type
atIndex:(NSUInteger)index
error:(NSError **)error {
const TfLiteTensor *tensor = [self cTensorOfType:type atIndex:index error:error];
if (tensor == nullptr) {
return nil;
}
NSString *name = TFLTensorNameFromCTensor(tensor);
if (!name) {
NSString *tensorType = [TFLTensor stringForTensorType:type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get name of %@ tensor at index (%lu).", tensorType,
(unsigned long)index];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidTensor
description:errorDescription
error:error];
return nil;
}
TFLTensorDataType dataType = TFLTensorDataTypeFromCTensor(tensor);
TFLQuantizationParameters *quantizationParams = TFLQuantizationParamsFromCTensor(tensor);
return [[TFLTensor alloc] initWithInterpreter:self
type:type
index:index
name:name
dataType:dataType
quantizationParameters:quantizationParams];
}
- (BOOL)isValidTensorIndex:(NSUInteger)index
belowLimit:(NSUInteger)totalTensorCount
error:(NSError **)error {
if (index >= totalTensorCount) {
NSString *errorDescription =
[NSString stringWithFormat:@"Invalid tensor index (%lu) exceeds max (%lu).",
(unsigned long)index, (totalTensorCount - 1)];
[TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidTensorIndex
description:errorDescription
error:error];
return NO;
}
return YES;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLInterpreterOptions.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TFLInterpreterOptions
#pragma mark - Public
- (instancetype)init {
self = [super init];
return self;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,87 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLMetalDelegate.h"
#ifdef COCOAPODS
@import TensorFlowLiteCMetal;
#else
#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@implementation TFLMetalDelegateOptions
#pragma mark - Public
- (instancetype)init {
self = [super init];
if (self != nil) {
_quantizationEnabled = true;
_waitType = TFLMetalDelegateThreadWaitTypePassive;
}
return self;
}
@end
@implementation TFLMetalDelegate
@synthesize cDelegate = _cDelegate;
#pragma mark - NSObject
- (void)dealloc {
TFLGpuDelegateDelete(self.cDelegate);
}
#pragma mark - Public
- (nullable instancetype)init {
TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
return [self initWithOptions:options];
}
- (nullable instancetype)initWithOptions:(TFLMetalDelegateOptions*)options {
self = [super init];
if (self != nil) {
TFLGpuDelegateOptions cOptions;
cOptions.allow_precision_loss = options.precisionLossAllowed;
cOptions.enable_quantization = options.quantizationEnabled;
switch (options.waitType) {
case TFLMetalDelegateThreadWaitTypeDoNotWait:
cOptions.wait_type = TFLGpuDelegateWaitTypeDoNotWait;
break;
case TFLMetalDelegateThreadWaitTypePassive:
cOptions.wait_type = TFLGpuDelegateWaitTypePassive;
break;
case TFLMetalDelegateThreadWaitTypeActive:
cOptions.wait_type = TFLGpuDelegateWaitTypeActive;
break;
case TFLMetalDelegateThreadWaitTypeAggressive:
cOptions.wait_type = TFLGpuDelegateWaitTypeAggressive;
break;
}
_cDelegate = TFLGpuDelegateCreate(&cOptions);
if (_cDelegate == nil) {
return nil;
}
}
return self;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,33 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLQuantizationParameters.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFLQuantizationParameters (Internal)
/**
* Initializes a `TFLQuantizationParameters` instance with the given scale and zero point.
*
* @param scale Scale of asymmetric quantization.
* @param zeroPoint Zero point of asymmetric quantization.
*
* @return A new instance of `TFLQuantizationParameters` with the given scale and zero point.
*/
- (instancetype)initWithScale:(float)scale zeroPoint:(int32_t)zeroPoint;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,36 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLQuantizationParameters.h"
#import "TFLQuantizationParameters+Internal.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TFLQuantizationParameters
#pragma mark - TFLTensor (Internal)
- (instancetype)initWithScale:(float)scale zeroPoint:(int32_t)zeroPoint {
self = [super init];
if (self != nil) {
_scale = scale;
_zeroPoint = zeroPoint;
}
return self;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,43 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLSignatureRunner.h"
#import "TFLTensorDataAccessor.h"
@class TFLInterpreter;
NS_ASSUME_NONNULL_BEGIN
@interface TFLSignatureRunner (Internal) <TFLTensorDataAccessor>
/**
* Initializes a new TensorFlow Lite signature runner instance with the given interpreter and
* signature key.
*
* @param interpreter The TensorFlow Lite model interpreter.
* @param signatureKey The signature key.
* @param error An optional error parameter populated when there is an error in initializing the
* signature runner.
*
* @return A new instance of `TFLSignatureRunner` with the given model and options. `nil` if there
* is an error in initializing the signature runner.
*/
- (nullable instancetype)initWithInterpreter:(TFLInterpreter *)interpreter
signatureKey:(NSString *)signatureKey
error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,392 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 "TFLSignatureRunner+Internal.h"
#include <vector>
#import "TFLCommonUtil.h"
#import "TFLErrorUtil.h"
#import "TFLInterpreter+Internal.h"
#import "TFLQuantizationParameters+Internal.h"
#import "TFLTensor+Internal.h"
#ifdef COCOAPODS
#import <TensorFlowLiteC/TensorFlowLiteC.h>
#else
#include "tensorflow/lite/core/c/c_api_experimental.h"
#endif // COCOAPODS
NS_ASSUME_NONNULL_BEGIN
/** Domain for errors in the signature runner. */
NSErrorDomain const TFLSignatureRunnerErrorDomain = @"org.tensorflow.lite.SignatureRunner";
@interface TFLSignatureRunner ()
/**
* The backing interpreter. It's a strong reference to ensure that the interpreter is never released
* before this signature runner is released.
*
* @warning Never let the interpreter hold a strong reference to the signature runner to avoid
* retain cycles.
*/
@property(nonatomic, readonly) TFLInterpreter *interpreter;
/** TfLiteSignatureRunner backed by C API. */
@property(nonatomic, readonly) TfLiteSignatureRunner *signatureRunner;
@end
@implementation TFLSignatureRunner {
// Whether we need to allocate tensors memory.
BOOL _isTensorsAllocationNeeded;
}
@synthesize inputs = _inputs;
@synthesize outputs = _outputs;
@synthesize signatureKey = _signatureKey;
#pragma mark - Initializer
- (nullable instancetype)initWithInterpreter:(TFLInterpreter *)interpreter
signatureKey:(NSString *)signatureKey
error:(NSError **)error {
self = [super init];
if (self != nil) {
_signatureKey = [signatureKey copy];
const char *signatureKeyCString = _signatureKey.UTF8String;
TfLiteSignatureRunner *signatureRunner =
TfLiteInterpreterGetSignatureRunner(interpreter.interpreter, signatureKeyCString);
if (signatureRunner == nullptr) {
NSString *errorDescription =
[NSString stringWithFormat:
@"Failed to create a signature runner. Signature with key (%@) not found.",
signatureKey];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToCreateSignatureRunner
description:errorDescription];
return nil;
}
_signatureRunner = signatureRunner;
_interpreter = interpreter;
_isTensorsAllocationNeeded = YES;
[self allocateTensorsWithError:error];
}
return self;
}
- (void)dealloc {
TfLiteSignatureRunnerDelete(_signatureRunner);
}
#pragma mark - Public
- (NSArray<NSString *> *)inputs {
if (_inputs) return _inputs;
NSUInteger inputCount = TfLiteSignatureRunnerGetInputCount(self.signatureRunner);
NSMutableArray<NSString *> *mutableInputsArray =
[[NSMutableArray alloc] initWithCapacity:inputCount];
for (NSUInteger i = 0; i < inputCount; i++) {
const char *inputNameCString =
TfLiteSignatureRunnerGetInputName(self.signatureRunner, (int32_t)i);
NSString *inputName = @"";
if (inputNameCString != nullptr) {
inputName = [NSString stringWithUTF8String:inputNameCString] ?: @"";
};
[mutableInputsArray addObject:inputName];
}
_inputs = [mutableInputsArray copy];
return _inputs;
}
- (NSArray<NSString *> *)outputs {
if (_outputs) return _outputs;
NSUInteger outputCount = TfLiteSignatureRunnerGetOutputCount(self.signatureRunner);
NSMutableArray<NSString *> *mutableOutputsArray =
[[NSMutableArray alloc] initWithCapacity:outputCount];
for (NSUInteger i = 0; i < outputCount; i++) {
const char *outputNameCString =
TfLiteSignatureRunnerGetOutputName(self.signatureRunner, (int32_t)i);
NSString *outputName = @"";
if (outputNameCString != nullptr) {
outputName = [NSString stringWithUTF8String:outputNameCString] ?: @"";
}
[mutableOutputsArray addObject:outputName];
}
_outputs = [mutableOutputsArray copy];
return _outputs;
}
- (nullable TFLTensor *)inputTensorWithName:(NSString *)name error:(NSError **)error {
return [self tensorOfType:TFLTensorTypeInput nameInSignature:name error:error];
}
- (nullable TFLTensor *)outputTensorWithName:(NSString *)name error:(NSError **)error {
return [self tensorOfType:TFLTensorTypeOutput nameInSignature:name error:error];
}
- (BOOL)resizeInputTensorWithName:(NSString *)name
toShape:(NSArray<NSNumber *> *)shape
error:(NSError **)error {
if (shape.count == 0) {
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidShape
description:@"Invalid shape. Must not be empty."];
return NO;
}
std::vector<int> cDimensions(shape.count);
for (int dimIndex = 0; dimIndex < shape.count; ++dimIndex) {
int dimension = shape[dimIndex].intValue;
if (dimension <= 0) {
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidShape
description:@"Invalid shape. Dimensions must be positive integers."];
return NO;
}
cDimensions[dimIndex] = dimension;
}
if (TfLiteSignatureRunnerResizeInputTensor(self.signatureRunner, name.UTF8String,
cDimensions.data(),
(int32_t)shape.count) != kTfLiteOk) {
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to resize input tensor with input name (%@).", name];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToResizeInputTensor
description:errorDescription];
return NO;
}
// Need to reallocate tensor memory.
_isTensorsAllocationNeeded = YES;
return YES;
}
- (BOOL)allocateTensorsWithError:(NSError **)error {
if (!_isTensorsAllocationNeeded) return YES;
if (TfLiteSignatureRunnerAllocateTensors(self.signatureRunner) != kTfLiteOk) {
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToAllocateTensors
description:@"Failed to allocate memory for tensors."];
return NO;
}
_isTensorsAllocationNeeded = NO;
return YES;
}
- (BOOL)invokeWithInputs:(NSDictionary<NSString *, NSData *> *)inputs Error:(NSError **)error {
if (![self allocateTensorsWithError:error]) return NO;
// Fill in input data.
for (NSString *inputName in inputs.allKeys) {
TFLTensor *inputTensor = [self inputTensorWithName:inputName error:error];
if (!inputTensor) return NO;
if (![inputTensor copyData:inputs[inputName] error:error]) return NO;
}
if (TfLiteSignatureRunnerInvoke(self.signatureRunner) != kTfLiteOk) {
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToInvoke
description:@"Failed to invoke the signature runner."];
return NO;
}
return YES;
}
#pragma mark - TFLTensorDataAccessor
- (BOOL)copyData:(NSData *)data toInputTensor:(TFLTensor *)inputTensor error:(NSError **)error {
if (inputTensor.type == TFLTensorTypeOutput) {
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeCopyDataToOutputTensorNotAllowed
description:@"Cannot copy data into an output tensor."];
return NO;
}
const TfLiteTensor *cTensor = [self cTensorOfType:TFLTensorTypeInput
nameInSignature:inputTensor.nameInSignature
error:error];
if (cTensor == nullptr) {
return NO;
}
NSUInteger byteSize = (NSUInteger)TfLiteTensorByteSize(cTensor);
if (data.length != byteSize) {
NSString *errorDescription = [NSString
stringWithFormat:
@"Input tensor with input name (%@) expects data size (%lu), but got (%lu).",
inputTensor.nameInSignature, (unsigned long)byteSize, (unsigned long)data.length];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidInputByteSize
description:errorDescription];
return NO;
}
if (TfLiteTensorCopyFromBuffer((TfLiteTensor *)cTensor, data.bytes, data.length) != kTfLiteOk) {
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to copy data into input tensor with input name (%@).",
inputTensor.nameInSignature];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToCopyDataToInputTensor
description:errorDescription];
return NO;
}
return YES;
}
- (nullable NSData *)dataFromTensor:(TFLTensor *)tensor error:(NSError **)error {
const TfLiteTensor *cTensor = [self cTensorOfType:tensor.type
nameInSignature:tensor.nameInSignature
error:error];
if (cTensor == nullptr) {
return nil;
}
void *bytes = TfLiteTensorData(cTensor);
NSUInteger byteSize = (NSUInteger)TfLiteTensorByteSize(cTensor);
if (bytes == nullptr || byteSize == 0) {
NSString *tensorType = [TFLTensor stringForTensorType:tensor.type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get data from %@ tensor with %@ name (%@).",
tensorType, tensorType, tensor.nameInSignature];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToGetDataFromTensor
description:errorDescription];
return nil;
}
return [NSData dataWithBytes:bytes length:byteSize];
}
- (nullable NSArray<NSNumber *> *)shapeOfTensor:(TFLTensor *)tensor error:(NSError **)error {
const TfLiteTensor *cTensor = [self cTensorOfType:tensor.type
nameInSignature:tensor.nameInSignature
error:error];
if (cTensor == nullptr) {
return nil;
}
NSString *tensorType = [TFLTensor stringForTensorType:tensor.type];
int32_t rank = TfLiteTensorNumDims(cTensor);
if (rank <= 0) {
NSString *errorDescription =
[NSString stringWithFormat:@"%@ tensor with %@ name (%@) has invalid rank (%d).",
tensorType, tensorType, tensor.nameInSignature, rank];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidTensor
description:errorDescription];
return nil;
}
NSMutableArray<NSNumber *> *shape = [NSMutableArray arrayWithCapacity:rank];
for (int32_t dimIndex = 0; dimIndex < rank; dimIndex++) {
int32_t dimension = TfLiteTensorDim(cTensor, dimIndex);
if (dimension <= 0) {
NSString *errorDescription = [NSString
stringWithFormat:@"%@ tensor with %@ name (%@) has invalid %d-th dimension (%d).",
tensorType, tensorType, tensor.nameInSignature, dimIndex, dimension];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidTensor
description:errorDescription];
return nil;
}
shape[dimIndex] = @((NSUInteger)dimension);
}
return shape;
}
#pragma mark - Private
- (nullable TFLTensor *)tensorOfType:(TFLTensorType)type
nameInSignature:(NSString *)nameInSignature
error:(NSError **)error {
const TfLiteTensor *tensor = [self cTensorOfType:type
nameInSignature:nameInSignature
error:error];
if (tensor == nullptr) {
return nil;
}
NSString *tensorName = TFLTensorNameFromCTensor(tensor);
if (!tensorName) {
NSString *tensorType = [TFLTensor stringForTensorType:type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get name of %@ tensor with %@ name (%@).",
tensorType, tensorType, nameInSignature];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeInvalidTensor
description:errorDescription];
return nil;
}
TFLTensorDataType dataType = TFLTensorDataTypeFromCTensor(tensor);
TFLQuantizationParameters *quantizationParams = TFLQuantizationParamsFromCTensor(tensor);
return [[TFLTensor alloc] initWithSignatureRunner:self
type:type
nameInSignature:nameInSignature
name:tensorName
dataType:dataType
quantizationParameters:quantizationParams];
}
- (const TfLiteTensor *)cTensorOfType:(TFLTensorType)type
nameInSignature:(NSString *)nameInSignature
error:(NSError **)error {
const TfLiteTensor *tensor = nullptr;
const char *nameCString = nameInSignature.UTF8String;
switch (type) {
case TFLTensorTypeInput:
tensor = TfLiteSignatureRunnerGetInputTensor(self.signatureRunner, nameCString);
break;
case TFLTensorTypeOutput:
tensor = TfLiteSignatureRunnerGetOutputTensor(self.signatureRunner, nameCString);
break;
}
if (tensor == nullptr) {
NSString *tensorType = [TFLTensor stringForTensorType:type];
NSString *errorDescription =
[NSString stringWithFormat:@"Failed to get %@ tensor with %@ name (%@).", tensorType,
tensorType, nameInSignature];
[TFLErrorUtil setError:error
withDomain:TFLSignatureRunnerErrorDomain
code:TFLSignatureRunnerErrorCodeFailedToGetTensor
description:errorDescription];
}
return tensor;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,99 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLTensor.h"
@class TFLInterpreter;
@class TFLSignatureRunner;
NS_ASSUME_NONNULL_BEGIN
/**
* @enum TFLTensorType
* This enum specifies input or output tensor types.
*/
typedef NS_ENUM(NSUInteger, TFLTensorType) {
/** Input tensor type. */
TFLTensorTypeInput,
/** Output tensor type. */
TFLTensorTypeOutput,
};
@interface TFLTensor (Internal)
/** Input or output tensor type. */
@property(nonatomic, readonly) TFLTensorType type;
/** Index of the tensor. */
@property(nonatomic, readonly) NSUInteger index;
/** The input or output name of the tensor in the signatureDef. */
@property(nonatomic, nullable, readonly) NSString *nameInSignature;
/**
* Initializes a `TFLTensor` with the given interpreter, name, data type, and quantization
* parameters.
*
* @param interpreter Interpreter backing the tensor.
* @param type Input or output tensor type.
* @param index Index of the tensor.
* @param name Name of the tensor.
* @param dataType Data type of the tensor.
* @param quantizationParameters Quantization parameters of the tensor. `nil` if the tensor does not
* use quantization.
*
* @return A new instance of `TFLTensor` with the given name, data type, shape, and quantization
* parameters.
*/
- (instancetype)initWithInterpreter:(TFLInterpreter *)interpreter
type:(TFLTensorType)type
index:(NSUInteger)index
name:(NSString *)name
dataType:(TFLTensorDataType)dataType
quantizationParameters:(nullable TFLQuantizationParameters *)quantizationParameters;
/**
* Initializes a new `TFLTensor` instance.
*
* @param signatureRunner The signature runner backing the tensor.
* @param type Input or output tensor type.
* @param nameInSignature The input or output name of the tensor in the signatureDef.
* @param name Name of the tensor.
* @param dataType Data type of the tensor.
* @param quantizationParameters Quantization parameters of the tensor. `nil` if the tensor does not
* use quantization.
*
* @return A new instance of `TFLTensor`.
*/
- (instancetype)initWithSignatureRunner:(TFLSignatureRunner *)signatureRunner
type:(TFLTensorType)type
nameInSignature:(NSString *)nameInSignature
name:(NSString *)name
dataType:(TFLTensorDataType)dataType
quantizationParameters:
(nullable TFLQuantizationParameters *)quantizationParameters;
/**
* Returns the string name of the given input or output tensor type.
*
* @param type Input or output tensor type.
*
* @return The string name of the given input or output tensor type.
*/
+ (NSString *)stringForTensorType:(TFLTensorType)type;
@end
NS_ASSUME_NONNULL_END
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLTensor.h"
#import "TFLErrorUtil.h"
#import "TFLInterpreter+Internal.h"
#import "TFLSignatureRunner+Internal.h"
#import "TFLTensor+Internal.h"
NS_ASSUME_NONNULL_BEGIN
// String names of input or output tensor types.
static NSString *const kTFLInputTensorTypeString = @"input";
static NSString *const kTFLOutputTensorTypeString = @"output";
@interface TFLTensor ()
// Redefines readonly properties.
@property(nonatomic) TFLTensorType type;
@property(nonatomic) NSUInteger index;
@property(nonatomic, nullable) NSString *nameInSignature;
@property(nonatomic, copy) NSString *name;
@property(nonatomic) TFLTensorDataType dataType;
@property(nonatomic, nullable) TFLQuantizationParameters *quantizationParameters;
/**
* The tensor data accessor that could mutate the data on the tensor. It is either an interpreter or
* a signature runner. It's a strong reference to ensure that the interpreter or the signature
* runner is never released before this tensor is released.
*
* @warning Never let the interpreter or the signature runner hold a strong reference to the tensor
* to avoid retain cycles.
*/
@property(nonatomic, strong) id<TFLTensorDataAccessor> tensorDataAccessor;
@end
@implementation TFLTensor
#pragma mark - Public
- (BOOL)copyData:(NSData *)data error:(NSError **)error {
return [self.tensorDataAccessor copyData:data toInputTensor:self error:error];
}
- (nullable NSData *)dataWithError:(NSError **)error {
return [self.tensorDataAccessor dataFromTensor:self error:error];
}
- (nullable NSArray<NSNumber *> *)shapeWithError:(NSError **)error {
return [self.tensorDataAccessor shapeOfTensor:self error:error];
}
#pragma mark - TFLTensor (Internal)
- (instancetype)initWithInterpreter:(TFLInterpreter *)interpreter
type:(TFLTensorType)type
index:(NSUInteger)index
name:(NSString *)name
dataType:(TFLTensorDataType)dataType
quantizationParameters:(nullable TFLQuantizationParameters *)quantizationParameters {
self = [super init];
if (self != nil) {
_tensorDataAccessor = interpreter;
_type = type;
_index = index;
_name = [name copy];
_dataType = dataType;
_quantizationParameters = quantizationParameters;
}
return self;
}
- (instancetype)initWithSignatureRunner:(TFLSignatureRunner *)signatureRunner
type:(TFLTensorType)type
nameInSignature:(NSString *)nameInSignature
name:(NSString *)name
dataType:(TFLTensorDataType)dataType
quantizationParameters:
(nullable TFLQuantizationParameters *)quantizationParameters {
self = [super init];
if (self != nil) {
_tensorDataAccessor = signatureRunner;
_type = type;
_nameInSignature = [nameInSignature copy];
_name = [name copy];
_dataType = dataType;
_quantizationParameters = quantizationParameters;
}
return self;
}
+ (NSString *)stringForTensorType:(TFLTensorType)type {
switch (type) {
case TFLTensorTypeInput:
return kTFLInputTensorTypeString;
case TFLTensorTypeOutput:
return kTFLOutputTensorTypeString;
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,64 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class TFLTensor;
/** Protocol for providing and mutating data on a `TFLTensor`. */
@protocol TFLTensorDataAccessor <NSObject>
/**
* Copies the given data into the input tensor. This is allowed only before the interpreter or
* signature runner is invoked.
*
* @param data The data to set. The byte size of the data must match what's required by the input
* tensor.
* @param inputTensor The input tensor to copy data to.
* @param error An optional error parameter populated when there is an error in setting the data.
*
* @return Whether the data was copied into the input tensor at the given index successfully.
* Returns NO if an error occurred.
*/
- (BOOL)copyData:(NSData *)data toInputTensor:(TFLTensor *)inputTensor error:(NSError **)error;
/**
* Retrieves a copy of the data from the given tensor. For an output tensor, the interpreter or
* signature invocation has to complete before the data can be retrieved.
*
* @param tensor A tensor.
* @param error An optional error parameter populated when there is an error in getting the data.
*
* @return The data of the given tensor. `nil` if there is an error or data is not available.
*/
- (nullable NSData *)dataFromTensor:(TFLTensor *)tensor error:(NSError **)error;
/**
* Retrieves the shape of the given tensor, an array of positive unsigned integer(s) containing the
* size of each dimension. For example: shape of [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]] is
* [2, 2, 3].
*
* @param tensor An input or output tensor.
* @param error An optional error parameter populated when there is an error in retrieving the
* shape.
*
* @return The shape of the tensor. `nil` if there is an error in retrieving the shape.
*/
- (nullable NSArray<NSNumber *> *)shapeOfTensor:(TFLTensor *)tensor error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,112 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifdef COCOAPODS
@import TFLTensorFlowLite;
#else
#import "tensorflow/lite/objc/apis/TFLCoreMLDelegate.h"
#import "tensorflow/lite/objc/apis/TFLTensorFlowLite.h"
#endif
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
/** Float model resource name.
* The model has a graph equivalent to (input + input) + input.
*/
static NSString* const kAddFloatModelResourceName = @"add";
/** Model resource type. */
static NSString* const kAddModelResourceType = @"bin";
/**
* @var kTensorSize
* Size of input and output tensors
* @var kTensorChannels
* Size of channel dimension of input and output tensors
*/
enum EnumType : int {kTensorSize = 8 * 8 * 3, kTensorChannels = 3};
/** Accuracy used in comparing floating numbers. */
static const float kTestAccuracy = 1E-5F;
@interface TFLCoreMLDelegateTests : XCTestCase
@end
@implementation TFLCoreMLDelegateTests
- (void)testCoreMLDelegate {
if (@available(iOS 11.0, *)) {
} else {
return;
}
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* floatModelPath = [bundle pathForResource:kAddFloatModelResourceName
ofType:kAddModelResourceType];
TFLInterpreterOptions* options = [[TFLInterpreterOptions alloc] init];
TFLCoreMLDelegateOptions* coreMLOptions = [[TFLCoreMLDelegateOptions alloc] init];
coreMLOptions.enabledDevices = TFLCoreMLDelegateEnabledDevicesAll;
TFLCoreMLDelegate* coreMLDelegate = [[TFLCoreMLDelegate alloc] initWithOptions:coreMLOptions];
XCTAssertNotNil(coreMLDelegate);
NSError* error;
TFLInterpreter* interpreter = [[TFLInterpreter alloc] initWithModelPath:floatModelPath
options:options
delegates:@[ coreMLDelegate ]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(interpreter);
XCTAssertTrue([interpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Copies the input data.
NSMutableData* inputData = [NSMutableData dataWithLength:sizeof(float) * kTensorSize];
for (int i = 0; i < kTensorSize / kTensorChannels; ++i) {
float* data = (float*)inputData.mutableBytes;
for (int j = 0; j < kTensorChannels; ++j) {
data[i * kTensorChannels + j] = j;
}
}
TFLTensor* inputTensor = [interpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
// Invokes the interpreter.
XCTAssertTrue([interpreter invokeWithError:&error]);
XCTAssertNil(error);
// Gets the output tensor data.
TFLTensor* outputTensor = [interpreter outputTensorAtIndex:0 error:&error];
NSData* outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
float output[kTensorSize];
[outputData getBytes:output length:(sizeof(float) * kTensorSize)];
for (int i = 0; i < kTensorSize / kTensorChannels; ++i) {
for (int j = 0; j < kTensorChannels; ++j) {
XCTAssertEqualWithAccuracy(j * 3, output[i * kTensorChannels + j], kTestAccuracy);
}
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLInterpreterOptions.h"
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Unit tests for TFLInterpreterOptions.
*/
@interface TFLInterpreterOptionsTests : XCTestCase
@end
@implementation TFLInterpreterOptionsTests
#pragma mark - Tests
- (void)testInit {
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
XCTAssertNotNil(options);
XCTAssertEqual(options.numberOfThreads, 0);
XCTAssertFalse(options.useXNNPACK);
}
- (void)testSetNumberOfThread {
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
options.numberOfThreads = 2;
XCTAssertEqual(options.numberOfThreads, 2);
options.numberOfThreads = 0;
XCTAssertEqual(options.numberOfThreads, 0);
options.numberOfThreads = 3;
XCTAssertEqual(options.numberOfThreads, 3);
}
- (void)testUseXNNPACK {
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
options.useXNNPACK = YES;
XCTAssertTrue(options.useXNNPACK);
options.useXNNPACK = NO;
XCTAssertFalse(options.useXNNPACK);
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,376 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLTensorFlowLite.h"
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Regular expression for TensorFlow Lite runtime version string, e.g. "1.14.0", "0.1.2-alpha.1",
* "0.3.4-beta2", "1.14.0-rc.3", "2.20.0-dev0+selfbuilt".
*/
static NSString *const kTFLVersionRegex = @"^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+\\w+)?$";
/** Float model resource name. */
static NSString *const kAddFloatModelResourceName = @"add";
/** Quantized model resource name. */
static NSString *const kAddQuantizedModelResourceName = @"add_quantized";
/** Model resource type. */
static NSString *const kAddModelResourceType = @"bin";
/** Size of the first (and only) dimension of the input and output tensor in the Add model. */
enum EnumType : NSUInteger {kAddModelTensorFirstDimensionSize = 2U};
/** Quantization scale of the quantized model. */
static const float kAddQuantizedModelScale = 0.003922F;
/** Quantization zero point of the quantized model. */
static const int32_t kAddQuantizedModelZeroPoint = 0;
/** Invalid input tensor index. */
static const NSUInteger kInvalidInputTensorIndex = 1U;
/** Invalid output tensor index. */
static const NSUInteger kInvalidOutputTensorIndex = 1U;
/** Accuracy used in comparing floating numbers. */
static const float kTestAccuracy = 1E-5F;
/**
* Unit tests for TFLInterpreter.
*/
@interface TFLInterpreterTests : XCTestCase
/** Absolute path of the Add float model resource. */
@property(nonatomic, nullable) NSString *floatModelPath;
/** Default interpreter using the Add model. */
@property(nonatomic, nullable) TFLInterpreter *interpreter;
@end
@implementation TFLInterpreterTests
#pragma mark - XCTestCase
- (void)setUp {
[super setUp];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
self.floatModelPath = [bundle pathForResource:kAddFloatModelResourceName
ofType:kAddModelResourceType];
NSError *error;
self.interpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error];
XCTAssertNil(error);
XCTAssertNotNil(self.interpreter);
XCTAssertTrue([self.interpreter allocateTensorsWithError:nil]);
}
- (void)tearDown {
self.floatModelPath = nil;
self.interpreter = nil;
[super tearDown];
}
#pragma mark - Tests
- (void)testTFLVersion {
NSLog(@"TFLVersion: %@", TFLVersion);
NSRange range = [TFLVersion rangeOfString:kTFLVersionRegex options:NSRegularExpressionSearch];
XCTAssertNotEqual(range.location, NSNotFound);
}
- (void)testSuccessfulFullRunAddFloatModel {
// Shape for both input and output tensor.
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
XCTAssertNotNil(options);
options.numberOfThreads = 2;
// Creates the interpreter.
NSError *error;
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath
options:options
delegates:@[]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies input and output tensor counts.
XCTAssertEqual(customInterpreter.inputTensorCount, 1);
XCTAssertEqual(customInterpreter.outputTensorCount, 1);
// Resizes the intput tensor.
XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertNil(error);
// Re-allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies the input tensor.
TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertTrue([inputTensor.name isEqualToString:@"input"]);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32);
NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:inputTensorShape]);
// Copies the input data.
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[inputData appendBytes:&one length:sizeof(float)];
[inputData appendBytes:&three length:sizeof(float)];
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
// Invokes the interpreter.
XCTAssertTrue([customInterpreter invokeWithError:&error]);
XCTAssertNil(error);
// Verifies the output tensor.
TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertTrue([outputTensor.name isEqualToString:@"output"]);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32);
NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:outputTensorShape]);
// Tries to query an invalid output tensor index.
TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex
error:&error];
XCTAssertNil(invalidOutputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
// Gets the output tensor data.
error = nil;
NSData *outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
float output[kAddModelTensorFirstDimensionSize];
[outputData getBytes:output length:(sizeof(float) * kAddModelTensorFirstDimensionSize)];
XCTAssertEqualWithAccuracy(output[0], 3.f, kTestAccuracy);
XCTAssertEqualWithAccuracy(output[1], 9.f, kTestAccuracy);
}
- (void)testSuccessfulFullRunQuantizedModel {
// Shape for both input and output tensor.
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
XCTAssertNotNil(options);
options.numberOfThreads = 2;
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *quantizedModelPath = [bundle pathForResource:kAddQuantizedModelResourceName
ofType:kAddModelResourceType];
// Creates the interpreter.
NSError *error;
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:quantizedModelPath
options:options
delegates:@[]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies input and output tensor counts.
XCTAssertEqual(customInterpreter.inputTensorCount, 1);
XCTAssertEqual(customInterpreter.outputTensorCount, 1);
// Resizes the intput tensor.
XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertNil(error);
// Re-allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies the input tensor.
TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertTrue([inputTensor.name isEqualToString:@"input"]);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeUInt8);
XCTAssertEqualWithAccuracy(inputTensor.quantizationParameters.scale, kAddQuantizedModelScale,
kTestAccuracy);
XCTAssertEqual(inputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint);
NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:inputTensorShape]);
// Copies the input data.
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
uint8_t one = 1;
uint8_t three = 3;
[inputData appendBytes:&one length:sizeof(uint8_t)];
[inputData appendBytes:&three length:sizeof(uint8_t)];
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
// Invokes the interpreter.
XCTAssertTrue([customInterpreter invokeWithError:&error]);
XCTAssertNil(error);
// Verifies the output tensor.
TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertTrue([outputTensor.name isEqualToString:@"output"]);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeUInt8);
XCTAssertEqualWithAccuracy(outputTensor.quantizationParameters.scale, kAddQuantizedModelScale,
kTestAccuracy);
XCTAssertEqual(outputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint);
NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:outputTensorShape]);
// Tries to query an invalid output tensor index.
TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex
error:&error];
XCTAssertNil(invalidOutputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
// Gets the output tensor data.
error = nil;
NSData *outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
uint8_t output[kAddModelTensorFirstDimensionSize];
[outputData getBytes:output length:(sizeof(uint8_t) * kAddModelTensorFirstDimensionSize)];
XCTAssertEqual(output[0], 3);
XCTAssertEqual(output[1], 9);
}
- (void)testInitWithModelPath_invalidPath {
// Creates the interpreter.
NSError *error;
TFLInterpreter *brokenInterpreter = [[TFLInterpreter alloc] initWithModelPath:@"InvalidPath"
error:&error];
XCTAssertNil(brokenInterpreter);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel);
}
- (void)testInvoke_beforeAllocation {
NSError *error;
TFLInterpreter *interpreterWithoutAllocation =
[[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error];
XCTAssertNotNil(interpreterWithoutAllocation);
XCTAssertNil(error);
XCTAssertFalse([interpreterWithoutAllocation invokeWithError:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToInvoke);
}
- (void)testInputTensorAtIndex_invalidIndex {
NSError *error;
TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:kInvalidInputTensorIndex
error:&error];
XCTAssertNil(inputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
}
- (void)testResizeInputTensorAtIndex_invalidIndex {
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:kInvalidInputTensorIndex
toShape:shape
error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
}
- (void)testResizeInputTensorAtIndex_emptyShape {
NSMutableArray<NSNumber *> *emptyShape = [NSMutableArray arrayWithCapacity:0];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:emptyShape error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape);
}
- (void)testResizeInputTensorAtIndex_zeroDimensionSize {
NSArray<NSNumber *> *shape = @[ @0 ];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape);
}
- (void)testCopyDataToInputTensorAtIndex_invalidInputDataByteSize {
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[inputData appendBytes:&one length:sizeof(float)];
[inputData appendBytes:&three length:(sizeof(float) - 1)];
NSError *error;
TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertFalse([inputTensor copyData:inputData error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidInputByteSize);
}
- (void)testCopyDataToOutputTensorAtIndex_notAllowed {
NSMutableData *data = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[data appendBytes:&one length:sizeof(float)];
[data appendBytes:&three length:(sizeof(float) - 1)];
NSError *error;
TFLTensor *outputTensor = [self.interpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertFalse([outputTensor copyData:data error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeCopyDataToOutputTensorNotAllowed);
}
- (void)testNilCDelegate {
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
// Creates the interpreter.
NSError *error;
TFLDelegate *delegate = [[TFLDelegate alloc] init]; // Base delegate's cDelegate is nil.
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath
options:options
delegates:@[ delegate ]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,124 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifdef COCOAPODS
@import TFLTensorFlowLite;
#else
#import "tensorflow/lite/objc/apis/TFLMetalDelegate.h"
#import "tensorflow/lite/objc/apis/TFLTensorFlowLite.h"
#endif
#import <Metal/MTLDevice.h>
#import <Metal/Metal.h>
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Float model resource name.
* The model has four inputs (a, b, c, d) and two outputs (x, y)
* x = a + (b + c)
* y = (b + c) + d
*/
static NSString* const kAddFloatModelResourceName = @"multi_add";
/** Model resource type. */
static NSString* const kAddModelResourceType = @"bin";
/**
* @var kTensorSize
* Size of input and output tensors
* @var kTensorChannels
* Size of channel dimension of input and output tensors
*/
enum EnumType : int {kTensorSize = 8 * 8 * 3, kTensorChannels = 3};
/** Number of input tensors */
static const int kNumInputs = 4;
/** Number of output tensors */
static const int kNumOutputs = 2;
/** Accuracy used in comparing floating numbers. */
static const float kTestAccuracy = 1E-5F;
@interface TFLMetalDelegateTests : XCTestCase
@end
@implementation TFLMetalDelegateTests
- (void)testMetalDelegate {
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* floatModelPath = [bundle pathForResource:kAddFloatModelResourceName
ofType:kAddModelResourceType];
TFLInterpreterOptions* options = [[TFLInterpreterOptions alloc] init];
TFLMetalDelegate* metalDelegate = [[TFLMetalDelegate alloc] init];
XCTAssertNotNil(metalDelegate);
id<MTLDevice> mtlDevice = MTLCreateSystemDefaultDevice();
if (mtlDevice == nil) return; // Stop testing if there's no GPU support
NSError* error;
TFLInterpreter* interpreter = [[TFLInterpreter alloc] initWithModelPath:floatModelPath
options:options
delegates:@[ metalDelegate ]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(interpreter);
XCTAssertTrue([interpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Copies the input data. For each input, input[i, j, k] == k
NSMutableData* inputData = [NSMutableData dataWithLength:sizeof(float) * kTensorSize];
for (int i = 0; i < kTensorSize / kTensorChannels; ++i) {
float* data = (float*)inputData.mutableBytes;
for (int j = 0; j < kTensorChannels; ++j) {
data[i * kTensorChannels + j] = j;
}
}
for (int input_idx = 0; input_idx < kNumInputs; ++input_idx) {
TFLTensor* inputTensor = [interpreter inputTensorAtIndex:input_idx error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
}
// Invokes the interpreter.
XCTAssertTrue([interpreter invokeWithError:&error]);
XCTAssertNil(error);
// Gets the output tensor data. For each output, output[i, j, k] == k * 3
for (int output_idx = 0; output_idx < kNumOutputs; ++output_idx) {
TFLTensor* outputTensor = [interpreter outputTensorAtIndex:output_idx error:&error];
NSData* outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
float output[kTensorSize];
[outputData getBytes:output length:(sizeof(float) * kTensorSize)];
for (int i = 0; i < kTensorSize / kTensorChannels; ++i) {
for (int j = 0; j < kTensorChannels; ++j) {
XCTAssertEqualWithAccuracy(j * 3, output[i * kTensorChannels + j], kTestAccuracy);
}
}
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,48 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLQuantizationParameters.h"
#import <XCTest/XCTest.h>
#import "tensorflow/lite/objc/sources/TFLQuantizationParameters+Internal.h"
NS_ASSUME_NONNULL_BEGIN
/** Test scale of quantization parameters. */
static const float kTestScale = 2.0;
/** Test zero point of quantization parameters. */
static const int32_t kTestZeroPoint = 128;
/**
* Unit tests for TFLQuantizationParameters.
*/
@interface TFLQuantizationParametersTests : XCTestCase
@end
@implementation TFLQuantizationParametersTests
#pragma mark - Tests
- (void)testInitWithScaleAndZeroPoint {
TFLQuantizationParameters *params =
[[TFLQuantizationParameters alloc] initWithScale:kTestScale zeroPoint:kTestZeroPoint];
XCTAssertEqual(params.scale, kTestScale);
XCTAssertEqual(params.zeroPoint, kTestZeroPoint);
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,224 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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/objc/apis/TFLInterpreter.h"
#import "tensorflow/lite/objc/apis/TFLQuantizationParameters.h"
#import "tensorflow/lite/objc/apis/TFLSignatureRunner.h"
#import "tensorflow/lite/objc/apis/TFLTensor.h"
#import <XCTest/XCTest.h>
/** Multiple signatures model resource name. */
static NSString *const kMultiSignaturesModelResourceName = @"multi_signatures";
/** Model resource type. */
static NSString *const kModelResourceType = @"bin";
static NSString *const kAddSignatureKey = @"add";
static NSString *const kSubSignatureKey = @"sub";
static NSString *const kDummySignatureKey = @"dummy";
@interface TFLSignatureRunnerTest : XCTestCase
/** Absolute path of the multi-signature model resource. */
@property(nonatomic, nullable) NSString *multiSignaturesModelPath;
@end
@implementation TFLSignatureRunnerTest
- (void)setUp {
[super setUp];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
self.multiSignaturesModelPath = [bundle pathForResource:kMultiSignaturesModelResourceName
ofType:kModelResourceType];
}
- (void)tearDown {
self.multiSignaturesModelPath = nil;
[super tearDown];
}
- (void)testSignatureKeys {
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:self.multiSignaturesModelPath error:&error];
XCTAssertNil(error);
XCTAssertNotNil(interpreter);
NSArray<NSString *> *signatureKeys = interpreter.signatureKeys;
NSArray<NSString *> *expectedKeys = @[ kAddSignatureKey, kSubSignatureKey ];
XCTAssertTrue([signatureKeys isEqualToArray:expectedKeys]);
// Validate signature runner for "add" signature.
XCTAssertNotNil([interpreter signatureRunnerWithKey:kAddSignatureKey error:&error]);
XCTAssertNil(error);
// Test fail to get signature runner for dummy signature.
XCTAssertNil([interpreter signatureRunnerWithKey:kDummySignatureKey error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeFailedToCreateSignatureRunner);
}
- (void)testResizeInputTensor {
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:self.multiSignaturesModelPath error:&error];
TFLSignatureRunner *addRunner = [interpreter signatureRunnerWithKey:kAddSignatureKey
error:&error];
XCTAssertNil(error);
NSArray<NSString *> *expectedInputs = @[ @"x" ];
XCTAssertTrue([addRunner.inputs isEqualToArray:expectedInputs]);
// Validate signature "add" input tensor "x" before resizing.
TFLTensor *inputTensor = [addRunner inputTensorWithName:@"x" error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32);
XCTAssertTrue([[inputTensor shapeWithError:&error] isEqualToArray:@[ @(1) ]]);
XCTAssertNil(error);
XCTAssertEqual([inputTensor dataWithError:&error].length, 4U);
XCTAssertNil(error);
XCTAssertEqual(inputTensor.quantizationParameters.scale, 0.);
XCTAssertEqual(inputTensor.quantizationParameters.zeroPoint, 0U);
// Test fail to copy data before resizing the tensor
float inputs[2] = {2.f, 4.f};
NSData *inputData = [NSData dataWithBytes:&inputs length:(2 * sizeof(float))];
XCTAssertFalse([inputTensor copyData:inputData error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeInvalidInputByteSize);
error = nil;
// Resize signature "add" input tensor "x"
NSArray<NSNumber *> *newShape = @[ @(2) ];
XCTAssertTrue([addRunner resizeInputTensorWithName:@"x" toShape:newShape error:&error]);
XCTAssertNil(error);
XCTAssertTrue([addRunner allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Validate signature "add" input tensor "x" after resizing.
inputTensor = [addRunner inputTensorWithName:@"x" error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32);
XCTAssertTrue([[inputTensor shapeWithError:&error] isEqualToArray:newShape]);
XCTAssertNil(error);
XCTAssertEqual([inputTensor dataWithError:&error].length, 8U);
XCTAssertNil(error);
XCTAssertEqual(inputTensor.quantizationParameters.scale, 0.);
XCTAssertEqual(inputTensor.quantizationParameters.zeroPoint, 0U);
// Validate input tensor "x" after copying data
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
NSData *retrievedInputData = [inputTensor dataWithError:&error];
XCTAssertNil(error);
float retrievedInputs[2];
[retrievedInputData getBytes:&retrievedInputs length:retrievedInputData.length];
XCTAssertEqual(retrievedInputs[0], inputs[0]);
XCTAssertEqual(retrievedInputs[1], inputs[1]);
}
- (void)testResizeInputTensor_invalidTensor {
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:self.multiSignaturesModelPath error:&error];
TFLSignatureRunner *addRunner = [interpreter signatureRunnerWithKey:kAddSignatureKey
error:&error];
XCTAssertNil(error);
// Test fail to get input tensor for a dummy input name.
XCTAssertNil([addRunner inputTensorWithName:@"dummy" error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeFailedToGetTensor);
// Test fail to resize a dummy input tensor
error = nil;
XCTAssertFalse([addRunner resizeInputTensorWithName:@"dummy" toShape:@[ @(2) ] error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeFailedToResizeInputTensor);
}
- (void)testInvokeWithInputs {
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:self.multiSignaturesModelPath error:&error];
TFLSignatureRunner *addRunner = [interpreter signatureRunnerWithKey:kAddSignatureKey
error:&error];
XCTAssertNil(error);
// Validate signature "add" output tensor "output_0" before inference
NSArray<NSString *> *expectedOutputs = @[ @"output_0" ];
XCTAssertTrue([addRunner.outputs isEqualToArray:expectedOutputs]);
TFLTensor *outputTensor = [addRunner outputTensorWithName:@"output_0" error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32);
XCTAssertTrue([[outputTensor shapeWithError:&error] isEqualToArray:@[ @(1) ]]);
XCTAssertNil(error);
XCTAssertEqual([outputTensor dataWithError:&error].length, 4U);
XCTAssertNil(error);
XCTAssertEqual(outputTensor.quantizationParameters.scale, 0.);
XCTAssertEqual(outputTensor.quantizationParameters.zeroPoint, 0U);
// Resize signature "add" input tensor "x"
XCTAssertTrue([addRunner resizeInputTensorWithName:@"x" toShape:@[ @(2) ] error:&error]);
XCTAssertNil(error);
// Invoke signature "add" with inputs.
float inputs[2] = {2.f, 4.f};
NSData *inputData = [NSData dataWithBytes:&inputs length:(2 * sizeof(float))];
XCTAssertTrue([addRunner invokeWithInputs:@{@"x" : inputData} Error:&error]);
XCTAssertNil(error);
// Validate signature "add" output tensor "output_0" after inference
outputTensor = [addRunner outputTensorWithName:@"output_0" error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32);
XCTAssertTrue([[outputTensor shapeWithError:&error] isEqualToArray:@[ @(2) ]]);
XCTAssertNil(error);
XCTAssertEqual(outputTensor.quantizationParameters.scale, 0.);
XCTAssertEqual(outputTensor.quantizationParameters.zeroPoint, 0U);
NSData *outputData = [outputTensor dataWithError:&error];
XCTAssertNil(error);
XCTAssertEqual(outputData.length, 8U);
float outputs[2];
[outputData getBytes:&outputs length:outputData.length];
XCTAssertEqual(outputs[0], inputs[0] + 2.f);
XCTAssertEqual(outputs[1], inputs[1] + 2.f);
}
- (void)testInvokeWithInputs_invalidInputs {
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:self.multiSignaturesModelPath error:&error];
TFLSignatureRunner *addRunner = [interpreter signatureRunnerWithKey:kAddSignatureKey
error:&error];
XCTAssertNil(error);
// Invoke signature "add" with invalid input data.
float inputs[2] = {2.f, 4.f};
NSData *inputData = [NSData dataWithBytes:&inputs length:(2 * sizeof(float))];
XCTAssertFalse([addRunner invokeWithInputs:@{@"x" : inputData} Error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeInvalidInputByteSize);
// Invoke signature "add" with invalid input name.
error = nil;
float input = 2.f;
inputData = [NSData dataWithBytes:&input length:(1 * sizeof(float))];
XCTAssertFalse([addRunner invokeWithInputs:@{@"dummy" : inputData} Error:&error]);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, TFLSignatureRunnerErrorCodeFailedToGetTensor);
}
@end