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
+186
View File
@@ -0,0 +1,186 @@
# TensorFlow Lite for Swift
load("@build_bazel_rules_apple//apple:apple_xcframework.bzl", "apple_static_xcframework")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework", "ios_unit_test")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
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"],
)
config_setting(
name = "use_coreml_delegate",
define_values = {"use_coreml_delegate": "1"},
)
config_setting(
name = "use_metal_delegate",
define_values = {"use_metal_delegate": "1"},
)
# By default this builds with no delegates.
# To build with the Metal delegate pass --define=use_metal_delegate=1
# To build with the CoreML delegate pass --define=use_coreml_delegate=1
swift_library(
name = "TensorFlowLite",
srcs = glob(
[
"Sources/*.swift",
],
exclude = [
"Sources/CoreMLDelegate.swift",
"Sources/MetalDelegate.swift",
],
) + select({
":use_coreml_delegate": [
"Sources/CoreMLDelegate.swift",
],
"//conditions:default": [],
}) + select({
":use_metal_delegate": [
"Sources/MetalDelegate.swift",
],
"//conditions:default": [],
}),
library_evolution = True,
linkopts = select({
":use_coreml_delegate": [
"-Wl,-weak_framework,CoreML",
],
"//conditions:default": [],
}) + select({
":use_metal_delegate": [
"-Wl,-weak_framework,Metal",
],
"//conditions:default": [],
}),
module_name = "TensorFlowLite",
tags = TFL_DEFAULT_TAGS + ["nobuilder"],
visibility = ios_visibility_allowlist(),
# Do not sort: these targets sort differently internally vs open source
deps = ["//tensorflow/lite/ios:tensorflow_lite_c"] + select({
":use_coreml_delegate": [
"//tensorflow/lite/delegates/coreml:coreml_delegate",
],
"//conditions:default": [],
}) + select({
":use_metal_delegate": [
"//tensorflow/lite/delegates/gpu:metal_delegate",
],
"//conditions:default": [],
}),
)
swift_library(
name = "TensorFlowLiteAllDelegates",
testonly = 1,
srcs = glob(["Sources/*.swift"]),
linkopts = [
"-Wl,-weak_framework,CoreML",
"-Wl,-weak_framework,Metal",
],
module_name = "TensorFlowLite",
tags = TFL_DEFAULT_TAGS + ["builder_default_ios_arm64"],
deps = [
"//tensorflow/lite/delegates/coreml:coreml_delegate",
"//tensorflow/lite/delegates/gpu:metal_delegate",
"//tensorflow/lite/ios:tensorflow_lite_c",
],
)
swift_library(
name = "TensorFlowLiteMetalDelegate",
testonly = 1,
srcs = glob(
[
"Sources/*.swift",
],
exclude = [
"Sources/CoreMLDelegate.swift",
],
),
linkopts = [
"-Wl,-weak_framework,Metal",
],
module_name = "TensorFlowLite",
tags = TFL_DEFAULT_TAGS + ["builder_default_ios_arm64"],
deps = [
"//tensorflow/lite/delegates/gpu:metal_delegate",
"//tensorflow/lite/ios:tensorflow_lite_c",
"//third_party/apple_frameworks:Darwin",
"//third_party/apple_frameworks:Foundation",
],
)
# bazel build -c opt --config=ios_fat //tensorflow/lite/swift:TensorFlowLite_framework
ios_static_framework(
name = "TensorFlowLite_framework",
avoid_deps = [
"//tensorflow/lite/ios:tensorflow_lite_c",
],
bundle_name = "TensorFlowLite",
minimum_os_version = TFL_MINIMUM_OS_VERSION,
deps = [
":TensorFlowLite",
],
)
# bazel build -c opt --config=ios //tensorflow/lite/swift:TensorFlowLite_xcframework
apple_static_xcframework(
name = "TensorFlowLite_xcframework",
avoid_deps = [
"//tensorflow/lite/ios:tensorflow_lite_c",
],
bundle_name = "TensorFlowLite",
ios = {
"simulator": [
"x86_64",
"arm64",
],
"device": ["arm64"],
},
minimum_os_versions = {"ios": TFL_MINIMUM_OS_VERSION},
tags = ["manual"], # TODO: Remove once tf is on bazel 6.x+
deps = [
":TensorFlowLite",
],
)
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",
],
)
swift_library(
name = "TestsLibrary",
testonly = 1,
srcs = glob(["Tests/*.swift"]),
tags = TFL_DEFAULT_TAGS + ["nobuilder"],
deps = [
":Resources",
":TensorFlowLiteAllDelegates",
],
)
objc_library(
name = "Resources",
data = [
"//tensorflow/lite:testdata/add.bin",
"//tensorflow/lite:testdata/add_quantized.bin",
"//tensorflow/lite:testdata/multi_add.bin",
"//tensorflow/lite:testdata/multi_signatures.bin",
],
tags = TFL_DEFAULT_TAGS,
)
@@ -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>
+80
View File
@@ -0,0 +1,80 @@
# TensorFlow Lite for Swift
[TensorFlow Lite](https://www.tensorflow.org/lite/) is TensorFlow's lightweight
solution for Swift 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 Swift 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 'TensorFlowLiteSwift'
```
Then, run `pod install`.
In your Swift files, import the module:
```swift
import TensorFlowLite
```
### Bazel developers
In your `BUILD` file, add the `TensorFlowLite` dependency to your target:
```python
swift_library(
deps = [
"//tensorflow/lite/swift:TensorFlowLite",
],
)
```
In your Swift files, import the module:
```swift
import TensorFlowLite
```
Build the `TensorFlowLite` Swift library target:
```shell
bazel build tensorflow/lite/swift:TensorFlowLite
```
Build the `Tests` target:
```shell
bazel test tensorflow/lite/swift:Tests --swiftcopt=-enable-testing
```
Note: `--swiftcopt=-enable-testing` is required for optimized builds (`-c opt`).
#### Generate the Xcode project using Tulsi
Open the `//tensorflow/lite/swift/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/swift/TensorFlowLite.tulsiproj:TensorFlowLite --outputfolder ~/path/to/generated/TensorFlowLite.xcodeproj
```
@@ -0,0 +1,90 @@
// 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 TensorFlowLiteCCoreML
/// A delegate that uses the `Core ML` framework for performing TensorFlow Lite graph operations.
///
/// - Important: This is an experimental interface that is subject to change.
public final class CoreMLDelegate: Delegate {
/// The configuration options for the `CoreMLDelegate`.
public let options: Options
// Conformance to the `Delegate` protocol.
public private(set) var cDelegate: CDelegate
/// Creates a new instance configured with the given `options`. Returns `nil` if the underlying
/// Core ML delegate could not be created because `Options.enabledDevices` was set to
/// `neuralEngine` but the device does not have the Neural Engine.
///
/// - Parameters:
/// - options: Configurations for the delegate. The default is a new instance of
/// `CoreMLDelegate.Options` with the default configuration values.
public init?(options: Options = Options()) {
self.options = options
var delegateOptions = TfLiteCoreMlDelegateOptions()
delegateOptions.enabled_devices = options.enabledDevices.cEnabledDevices
delegateOptions.coreml_version = Int32(options.coreMLVersion)
delegateOptions.max_delegated_partitions = Int32(options.maxDelegatedPartitions)
delegateOptions.min_nodes_per_partition = Int32(options.minNodesPerPartition)
guard let delegate = TfLiteCoreMlDelegateCreate(&delegateOptions) else { return nil }
cDelegate = delegate
}
deinit {
TfLiteCoreMlDelegateDelete(cDelegate)
}
}
extension CoreMLDelegate {
/// A type indicating which devices the Core ML delegate should be enabled for.
public enum EnabledDevices: Equatable, Hashable {
/// Enables the delegate for devices with Neural Engine only.
case neuralEngine
/// Enables the delegate for all devices.
case all
/// The C `TfLiteCoreMlDelegateEnabledDevices` for the current `EnabledDevices`.
var cEnabledDevices: TfLiteCoreMlDelegateEnabledDevices {
switch self {
case .neuralEngine:
return TfLiteCoreMlDelegateDevicesWithNeuralEngine
case .all:
return TfLiteCoreMlDelegateAllDevices
}
}
}
/// Options for configuring the `CoreMLDelegate`.
// TODO(b/143931022): Add preferred device support.
public struct Options: Equatable, Hashable {
/// A type indicating which devices the Core ML delegate should be enabled for. The default
/// value is `.neuralEngine` indicating that the delegate is enabled for Neural Engine devices
/// only.
public var enabledDevices: EnabledDevices = .neuralEngine
/// 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.
public var coreMLVersion = 0
/// 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.
public var maxDelegatedPartitions = 0
/// The minimum number of nodes per partition to be delegated by the Core ML delegate. The
/// default value is `2`.
public var minNodesPerPartition = 2
/// Creates a new instance with the default values.
public init() {}
}
}
@@ -0,0 +1,24 @@
// 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 TensorFlowLiteC
/// A delegate that the `Interpreter` uses to perform TensorFlow Lite model computations.
public protocol Delegate: AnyObject {
/// The `TfLiteDelegate` C pointer type.
typealias CDelegate = UnsafeMutablePointer<TfLiteDelegate>
/// The delegate that performs model computations.
var cDelegate: CDelegate { get }
}
@@ -0,0 +1,427 @@
// 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
import TensorFlowLiteC
#if os(Linux)
import SwiftGlibc
#else
import Darwin
#endif
/// A TensorFlow Lite interpreter that performs inference from a given model.
///
/// - Note: Interpreter instances are *not* thread-safe.
public final class Interpreter {
/// The configuration options for the `Interpreter`.
public let options: Options?
/// An `Array` of `Delegate`s for the `Interpreter` to use to perform graph operations.
public let delegates: [Delegate]?
/// The total number of input `Tensor`s associated with the model.
public var inputTensorCount: Int {
return Int(TfLiteInterpreterGetInputTensorCount(cInterpreter))
}
/// The total number of output `Tensor`s associated with the model.
public var outputTensorCount: Int {
return Int(TfLiteInterpreterGetOutputTensorCount(cInterpreter))
}
/// An ordered list of SignatureDef exported method names available in the model.
public var signatureKeys: [String] {
guard let signatureKeys = _signatureKeys else {
let signatureCount = Int(TfLiteInterpreterGetSignatureCount(self.cInterpreter))
let keys: [String] = (0..<signatureCount).map {
guard
let signatureNameCString = TfLiteInterpreterGetSignatureKey(
self.cInterpreter, Int32($0))
else {
return ""
}
return String(cString: signatureNameCString)
}
_signatureKeys = keys
return keys
}
return signatureKeys
}
/// The `TfLiteInterpreter` C pointer type represented as an `UnsafePointer<TfLiteInterpreter>`.
internal typealias CInterpreter = OpaquePointer
/// The underlying `TfLiteInterpreter` C pointer.
internal var cInterpreter: CInterpreter?
/// Keep reference to underlying model's data in case of init(modelData:) is used.
internal let _model: Model
/// The underlying `TfLiteDelegate` C pointer for XNNPACK delegate.
private var cXNNPackDelegate: Delegate.CDelegate?
/// An ordered list of SignatureDef exported method names available in the model.
private var _signatureKeys: [String]? = nil
/// Creates a new instance with the given values.
///
/// - Parameters:
/// - modelPath: The local file path to a TensorFlow Lite model.
/// - options: Configurations for the `Interpreter`. The default is `nil` indicating that the
/// `Interpreter` will determine the configuration options.
/// - delegate: `Array` of `Delegate`s for the `Interpreter` to use to peform graph operations.
/// The default is `nil`.
/// - Throws: An error if the model could not be loaded or the interpreter could not be created.
public convenience init(modelPath: String, options: Options? = nil, delegates: [Delegate]? = nil)
throws
{
guard let model = Model(filePath: modelPath) else { throw InterpreterError.failedToLoadModel }
try self.init(model: model, options: options, delegates: delegates)
}
/// Creates a new instance with the given values.
///
/// - Parameters:
/// - modelData: Binary data representing a TensorFlow Lite model.
/// - options: Configurations for the `Interpreter`. The default is `nil` indicating that the
/// `Interpreter` will determine the configuration options.
/// - delegate: `Array` of `Delegate`s for the `Interpreter` to use to peform graph operations.
/// The default is `nil`.
/// - Throws: An error if the model could not be loaded or the interpreter could not be created.
public convenience init(modelData: Data, options: Options? = nil, delegates: [Delegate]? = nil)
throws
{
guard let model = Model(modelData: modelData) else { throw InterpreterError.failedToLoadModel }
try self.init(model: model, options: options, delegates: delegates)
}
/// Create a new instance with the given values.
///
/// - Parameters:
/// - model: An instantiated TensorFlow Lite model.
/// - options: Configurations for the `Interpreter`. The default is `nil` indicating that the
/// `Interpreter` will determine the configuration options.
/// - delegate: `Array` of `Delegate`s for the `Interpreter` to use to peform graph operations.
/// The default is `nil`.
/// - Throws: An error if the model could not be loaded or the interpreter could not be created.
private init(model: Model, options: Options? = nil, delegates: [Delegate]? = nil) throws {
guard let cInterpreterOptions = TfLiteInterpreterOptionsCreate() else {
throw InterpreterError.failedToCreateInterpreter
}
defer { TfLiteInterpreterOptionsDelete(cInterpreterOptions) }
self.options = options
self.delegates = delegates
self._model = model
options.map {
if let threadCount = $0.threadCount, threadCount > 0 {
TfLiteInterpreterOptionsSetNumThreads(cInterpreterOptions, Int32(threadCount))
}
TfLiteInterpreterOptionsSetErrorReporter(
cInterpreterOptions,
{ (_, format, args) -> Void in
// Workaround for optionality differences for x86_64 (non-optional) and arm64 (optional).
let optionalArgs: CVaListPointer? = args
guard let cFormat = format,
let arguments = optionalArgs,
let message = String(cFormat: cFormat, arguments: arguments)
else {
return
}
print(String(describing: InterpreterError.tensorFlowLiteError(message)))
},
nil
)
}
delegates?.forEach { TfLiteInterpreterOptionsAddDelegate(cInterpreterOptions, $0.cDelegate) }
// Configure the XNNPack delegate after the other delegates explicitly added by the user.
options.map {
if $0.isXNNPackEnabled {
configureXNNPack(options: $0, cInterpreterOptions: cInterpreterOptions)
}
}
guard let cInterpreter = TfLiteInterpreterCreate(model.cModel, cInterpreterOptions) else {
throw InterpreterError.failedToCreateInterpreter
}
self.cInterpreter = cInterpreter
}
deinit {
TfLiteInterpreterDelete(cInterpreter)
TfLiteXNNPackDelegateDelete(cXNNPackDelegate)
}
/// Invokes the interpreter to perform inference from the loaded graph.
///
/// - Throws: An error if the model was not ready because the tensors were not allocated.
public func invoke() throws {
guard TfLiteInterpreterInvoke(cInterpreter) == kTfLiteOk else {
throw InterpreterError.allocateTensorsRequired
}
}
/// Returns the input `Tensor` at the given index.
///
/// - Parameters:
/// - index: The index for the input `Tensor`.
/// - Throws: An error if the index is invalid or the tensors have not been allocated.
/// - Returns: The input `Tensor` at the given index.
public func input(at index: Int) throws -> Tensor {
let maxIndex = inputTensorCount - 1
guard case 0...maxIndex = index else {
throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex)
}
guard let cTensor = TfLiteInterpreterGetInputTensor(cInterpreter, Int32(index)),
let bytes = TfLiteTensorData(cTensor),
let nameCString = TfLiteTensorName(cTensor)
else {
throw InterpreterError.allocateTensorsRequired
}
guard let dataType = Tensor.DataType(type: TfLiteTensorType(cTensor)) else {
throw InterpreterError.invalidTensorDataType
}
let name = String(cString: nameCString)
let rank = TfLiteTensorNumDims(cTensor)
let dimensions = (0..<rank).map { Int(TfLiteTensorDim(cTensor, $0)) }
let shape = Tensor.Shape(dimensions)
let byteCount = TfLiteTensorByteSize(cTensor)
let data = Data(bytes: bytes, count: byteCount)
let cQuantizationParams = TfLiteTensorQuantizationParams(cTensor)
let scale = cQuantizationParams.scale
let zeroPoint = Int(cQuantizationParams.zero_point)
var quantizationParameters: QuantizationParameters? = nil
if scale != 0.0 {
quantizationParameters = QuantizationParameters(scale: scale, zeroPoint: zeroPoint)
}
let tensor = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
return tensor
}
/// Returns the output `Tensor` at the given index.
///
/// - Parameters:
/// - index: The index for the output `Tensor`.
/// - Throws: An error if the index is invalid, tensors haven't been allocated, or interpreter
/// has not been invoked for models that dynamically compute output tensors based on the
/// values of its input tensors.
/// - Returns: The output `Tensor` at the given index.
public func output(at index: Int) throws -> Tensor {
let maxIndex = outputTensorCount - 1
guard case 0...maxIndex = index else {
throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex)
}
guard let cTensor = TfLiteInterpreterGetOutputTensor(cInterpreter, Int32(index)),
let bytes = TfLiteTensorData(cTensor),
let nameCString = TfLiteTensorName(cTensor)
else {
throw InterpreterError.invokeInterpreterRequired
}
guard let dataType = Tensor.DataType(type: TfLiteTensorType(cTensor)) else {
throw InterpreterError.invalidTensorDataType
}
let name = String(cString: nameCString)
let rank = TfLiteTensorNumDims(cTensor)
let dimensions = (0..<rank).map { Int(TfLiteTensorDim(cTensor, $0)) }
let shape = Tensor.Shape(dimensions)
let byteCount = TfLiteTensorByteSize(cTensor)
let data = Data(bytes: bytes, count: byteCount)
let cQuantizationParams = TfLiteTensorQuantizationParams(cTensor)
let scale = cQuantizationParams.scale
let zeroPoint = Int(cQuantizationParams.zero_point)
var quantizationParameters: QuantizationParameters? = nil
if scale != 0.0 {
quantizationParameters = QuantizationParameters(scale: scale, zeroPoint: zeroPoint)
}
let tensor = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
return tensor
}
/// Resizes the input `Tensor` at the given index to the specified `Tensor.Shape`.
///
/// - Note: After resizing an input tensor, the client **must** explicitly call
/// `allocateTensors()` before attempting to access the resized tensor data or invoking the
/// interpreter to perform inference.
/// - Parameters:
/// - index: The index for the input `Tensor`.
/// - shape: The shape to resize the input `Tensor` to.
/// - Throws: An error if the input tensor at the given index could not be resized.
public func resizeInput(at index: Int, to shape: Tensor.Shape) throws {
let maxIndex = inputTensorCount - 1
guard case 0...maxIndex = index else {
throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex)
}
guard
TfLiteInterpreterResizeInputTensor(
cInterpreter,
Int32(index),
shape.int32Dimensions,
Int32(shape.rank)
) == kTfLiteOk
else {
throw InterpreterError.failedToResizeInputTensor(index: index)
}
}
/// Copies the given data to the input `Tensor` at the given index.
///
/// - Parameters:
/// - data: The data to be copied to the input `Tensor`'s data buffer.
/// - index: The index for the input `Tensor`.
/// - Throws: An error if the `data.count` does not match the input tensor's `data.count` or if
/// the given index is invalid.
/// - Returns: The input `Tensor` with the copied data.
@discardableResult
public func copy(_ data: Data, toInputAt index: Int) throws -> Tensor {
let maxIndex = inputTensorCount - 1
guard case 0...maxIndex = index else {
throw InterpreterError.invalidTensorIndex(index: index, maxIndex: maxIndex)
}
guard let cTensor = TfLiteInterpreterGetInputTensor(cInterpreter, Int32(index)) else {
throw InterpreterError.allocateTensorsRequired
}
let byteCount = TfLiteTensorByteSize(cTensor)
guard data.count == byteCount else {
throw InterpreterError.invalidTensorDataCount(provided: data.count, required: byteCount)
}
#if swift(>=5.0)
let status = data.withUnsafeBytes {
TfLiteTensorCopyFromBuffer(cTensor, $0.baseAddress, data.count)
}
#else
let status = data.withUnsafeBytes { TfLiteTensorCopyFromBuffer(cTensor, $0, data.count) }
#endif // swift(>=5.0)
guard status == kTfLiteOk else { throw InterpreterError.failedToCopyDataToInputTensor }
return try input(at: index)
}
/// Allocates memory for all input `Tensor`s based on their `Tensor.Shape`s.
///
/// - Note: This is a relatively expensive operation and should only be called after creating the
/// interpreter and resizing any input tensors.
/// - Throws: An error if memory could not be allocated for the input tensors.
public func allocateTensors() throws {
guard TfLiteInterpreterAllocateTensors(cInterpreter) == kTfLiteOk else {
throw InterpreterError.failedToAllocateTensors
}
}
/// Returns a new signature runner instance for the signature with the given key in the model.
///
/// - Parameters:
/// - key: The signature key.
/// - Throws: `SignatureRunnerError` if signature runner creation fails.
/// - Returns: A new signature runner instance for the signature with the given key.
public func signatureRunner(with key: String) throws -> SignatureRunner {
guard signatureKeys.contains(key) else {
throw SignatureRunnerError.failedToCreateSignatureRunner(signatureKey: key)
}
return try SignatureRunner.init(interpreter: self, signatureKey: key)
}
// MARK: - Private
private func configureXNNPack(options: Options, cInterpreterOptions: OpaquePointer) {
var cXNNPackOptions = TfLiteXNNPackDelegateOptionsDefault()
if let threadCount = options.threadCount, threadCount > 0 {
cXNNPackOptions.num_threads = Int32(threadCount)
}
cXNNPackDelegate = TfLiteXNNPackDelegateCreate(&cXNNPackOptions)
TfLiteInterpreterOptionsAddDelegate(cInterpreterOptions, cXNNPackDelegate)
}
}
extension Interpreter {
/// Options for configuring the `Interpreter`.
public struct Options: Equatable, Hashable {
/// The maximum number of CPU threads that the interpreter should run on. The default is `nil`
/// indicating that the `Interpreter` will decide the number of threads to use.
public var threadCount: Int? = nil
/// Indicates whether an optimized set of floating point CPU kernels, provided by XNNPACK, is
/// enabled.
///
/// - Experiment:
/// 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.
///
/// - Important:
/// 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.
public var isXNNPackEnabled: Bool = false
/// Creates a new instance with the default values.
public init() {}
}
}
/// A type alias for `Interpreter.Options` to support backwards compatibility with the deprecated
/// `InterpreterOptions` struct.
@available(*, deprecated, renamed: "Interpreter.Options")
public typealias InterpreterOptions = Interpreter.Options
extension String {
/// Returns a new `String` initialized by using the given format C array as a template into which
/// the remaining argument values are substituted according to the users default locale.
///
/// - Note: Returns `nil` if a new `String` could not be constructed from the given values.
/// - Parameters:
/// - cFormat: The format C array as a template for substituting values.
/// - arguments: A C pointer to a `va_list` of arguments to substitute into `cFormat`.
init?(cFormat: UnsafePointer<CChar>, arguments: CVaListPointer) {
#if os(Linux)
let length = Int(vsnprintf(nil, 0, cFormat, arguments) + 1) // null terminator
guard length > 0 else { return nil }
let buffer = UnsafeMutablePointer<CChar>.allocate(capacity: length)
defer {
buffer.deallocate()
}
guard vsnprintf(buffer, length, cFormat, arguments) == length - 1 else { return nil }
self.init(validatingUTF8: buffer)
#else
var buffer: UnsafeMutablePointer<CChar>?
guard vasprintf(&buffer, cFormat, arguments) != 0, let cString = buffer else { return nil }
self.init(validatingUTF8: cString)
#endif
}
}
@@ -0,0 +1,65 @@
// 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
/// Errors thrown by the TensorFlow Lite `Interpreter`.
public enum InterpreterError: Error, Equatable, Hashable {
case invalidTensorIndex(index: Int, maxIndex: Int)
case invalidTensorDataCount(provided: Int, required: Int)
case invalidTensorDataType
case failedToLoadModel
case failedToCreateInterpreter
case failedToResizeInputTensor(index: Int)
case failedToCopyDataToInputTensor
case failedToAllocateTensors
case allocateTensorsRequired
case invokeInterpreterRequired
case tensorFlowLiteError(String)
}
extension InterpreterError: LocalizedError {
/// A localized description of the interpreter error.
public var errorDescription: String? {
switch self {
case .invalidTensorIndex(let index, let maxIndex):
return "Invalid tensor index \(index), max index is \(maxIndex)."
case .invalidTensorDataCount(let provided, let required):
return "Provided data count \(provided) must match the required count \(required)."
case .invalidTensorDataType:
return "Tensor data type is unsupported or could not be determined due to a model error."
case .failedToLoadModel:
return "Failed to load the given model."
case .failedToCreateInterpreter:
return "Failed to create the interpreter."
case .failedToResizeInputTensor(let index):
return "Failed to resize input tensor at index \(index)."
case .failedToCopyDataToInputTensor:
return "Failed to copy data to input tensor."
case .failedToAllocateTensors:
return "Failed to allocate memory for input tensors."
case .allocateTensorsRequired:
return "Must call allocateTensors()."
case .invokeInterpreterRequired:
return "Must call invoke()."
case .tensorFlowLiteError(let message):
return "TensorFlow Lite Error: \(message)"
}
}
}
extension InterpreterError: CustomStringConvertible {
/// A textual representation of the TensorFlow Lite interpreter error.
public var description: String { return errorDescription ?? "Unknown error." }
}
@@ -0,0 +1,102 @@
// 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 TensorFlowLiteCMetal
/// A delegate that uses the `Metal` framework for performing TensorFlow Lite graph operations with
/// GPU acceleration.
///
/// - Important: This is an experimental interface that is subject to change.
public final class MetalDelegate: Delegate {
/// The configuration options for the `MetalDelegate`.
public let options: Options
// Conformance to the `Delegate` protocol.
public private(set) var cDelegate: CDelegate
/// Creates a new instance configured with the given `options`.
///
/// - Parameters:
/// - options: Configurations for the delegate. The default is a new instance of
/// `MetalDelegate.Options` with the default configuration values.
public init(options: Options = Options()) {
self.options = options
var delegateOptions = TFLGpuDelegateOptions()
delegateOptions.allow_precision_loss = options.isPrecisionLossAllowed
delegateOptions.wait_type = options.waitType.cWaitType
delegateOptions.enable_quantization = options.isQuantizationEnabled
cDelegate = TFLGpuDelegateCreate(&delegateOptions)
}
deinit {
TFLGpuDelegateDelete(cDelegate)
}
}
extension MetalDelegate {
/// Options for configuring the `MetalDelegate`.
public struct Options: Equatable, Hashable {
/// Indicates whether the GPU delegate allows precision loss, such as allowing `Float16`
/// precision for a `Float32` computation. The default is `false`.
public var isPrecisionLossAllowed = false
@available(
*, deprecated, message: "Deprecated since TensorFlow Lite 2.4",
renamed: "isPrecisionLossAllowed"
)
public var allowsPrecisionLoss: Bool {
get { return isPrecisionLossAllowed }
set(value) { isPrecisionLossAllowed = value }
}
/// A type indicating how the current thread should wait for work on the GPU to complete. The
/// default is `passive`.
public var waitType: ThreadWaitType = .passive
/// Indicates whether the GPU delegate allows execution of an 8-bit quantized model. The default
/// is `true`.
public var isQuantizationEnabled = true
/// Creates a new instance with the default values.
public init() {}
}
}
/// A type indicating how the current thread should wait for work scheduled on the GPU to complete.
public enum ThreadWaitType: Equatable, Hashable {
/// The thread does not wait for the work to complete. Useful when the output of the work is used
/// with the GPU pipeline.
case none
/// The thread waits until the work is complete.
case passive
/// The thread waits for the work to complete with minimal latency, which may require additional
/// CPU resources.
case active
/// The thread waits for the work while trying to prevent the GPU from going into sleep mode.
case aggressive
/// The C `TFLGpuDelegateWaitType` for the current `ThreadWaitType`.
var cWaitType: TFLGpuDelegateWaitType {
switch self {
case .none:
return TFLGpuDelegateWaitTypeDoNotWait
case .passive:
return TFLGpuDelegateWaitTypePassive
case .active:
return TFLGpuDelegateWaitTypeActive
case .aggressive:
return TFLGpuDelegateWaitTypeAggressive
}
}
}
+56
View File
@@ -0,0 +1,56 @@
// 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
import TensorFlowLiteC
/// A TensorFlow Lite model used by the `Interpreter` to perform inference.
final class Model {
/// The `TfLiteModel` C pointer type represented as an `UnsafePointer<TfLiteModel>`.
typealias CModel = OpaquePointer
/// The underlying `TfLiteModel` C pointer.
let cModel: CModel?
/// The underlying data if data init is used
/// From c_api.h: The caller retains ownership of the `model_data` and should ensure that
// the lifetime of the `model_data` must be at least as long as the lifetime
// of the `TfLiteModel`.
let data: Data?
/// Creates a new instance with the given `filePath`.
///
/// - Precondition: Initialization can fail if the given `filePath` is invalid.
/// - Parameters:
/// - filePath: The local file path to a TensorFlow Lite model.
init?(filePath: String) {
guard !filePath.isEmpty, let cModel = TfLiteModelCreateFromFile(filePath) else { return nil }
self.cModel = cModel
self.data = nil
}
/// Creates a new instance with the given `modelData`.
///
/// - Precondition: Initialization can fail if the given `modelData` is invalid.
/// - Parameters:
/// - modelData: Binary data representing a TensorFlow Lite model.
init?(modelData: Data) {
self.data = modelData
self.cModel = modelData.withUnsafeBytes { TfLiteModelCreate($0, modelData.count) }
}
deinit {
TfLiteModelDelete(cModel)
}
}
@@ -0,0 +1,35 @@
// 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.
/// Parameters that determine the mapping of quantized values to real values. Quantized values can
/// be mapped to float values using the following conversion:
/// `realValue = scale * (quantizedValue - zeroPoint)`.
public struct QuantizationParameters: Equatable, Hashable {
/// The difference between real values corresponding to consecutive quantized values differing by
/// 1. For example, the range of quantized values for `UInt8` data type is [0, 255].
public let scale: Float
/// The quantized value that corresponds to the real 0 value.
public let zeroPoint: Int
/// Creates a new instance with the given values.
///
/// - Parameters:
/// - scale: The scale value for asymmetric quantization.
/// - zeroPoint: The zero point for asymmetric quantization.
init(scale: Float, zeroPoint: Int) {
self.scale = scale
self.zeroPoint = zeroPoint
}
}
@@ -0,0 +1,284 @@
// 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
import TensorFlowLiteC
#if os(Linux)
import SwiftGlibc
#else
import Darwin
#endif
/// A TensorFlow Lite model signature runner. You can get a `SignatureRunner` instance for a
/// signature from an `Interpreter` and then use the SignatureRunner APIs.
///
/// - Note: `SignatureRunner` instances are *not* thread-safe.
/// - Note: Each `SignatureRunner` instance is associated with an `Interpreter` instance. As long
/// as a `SignatureRunner` instance is still in use, its associated `Interpreter` instance
/// will not be deallocated.
public final class SignatureRunner {
/// The signature key.
public let signatureKey: String
/// The SignatureDefs input names.
public var inputs: [String] {
guard let inputs = _inputs else {
let inputCount = Int(TfLiteSignatureRunnerGetInputCount(self.cSignatureRunner))
let ins: [String] = (0..<inputCount).map {
guard
let inputNameCString = TfLiteSignatureRunnerGetInputName(
self.cSignatureRunner, Int32($0))
else {
return ""
}
return String(cString: inputNameCString)
}
_inputs = ins
return ins
}
return inputs
}
/// The SignatureDefs output names.
public var outputs: [String] {
guard let outputs = _outputs else {
let outputCount = Int(TfLiteSignatureRunnerGetOutputCount(self.cSignatureRunner))
let outs: [String] = (0..<outputCount).map {
guard
let outputNameCString = TfLiteSignatureRunnerGetOutputName(
self.cSignatureRunner, Int32($0))
else {
return ""
}
return String(cString: outputNameCString)
}
_outputs = outs
return outs
}
return outputs
}
/// 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.
private var interpreter: Interpreter
/// The `TfLiteSignatureRunner` C pointer type represented as an
/// `UnsafePointer<TfLiteSignatureRunner>`.
private typealias CSignatureRunner = OpaquePointer
/// The `TfLiteTensor` C pointer type represented as an
/// `UnsafePointer<TfLiteTensor>`.
private typealias CTensor = UnsafePointer<TfLiteTensor>?
/// The underlying `TfLiteSignatureRunner` C pointer.
private var cSignatureRunner: CSignatureRunner
/// Whether we need to allocate tensors memory.
private var isTensorsAllocationNeeded: Bool = true
/// The SignatureDefs input names.
private var _inputs: [String]?
/// The SignatureDefs output names.
private var _outputs: [String]?
// MARK: Initializers
/// Initializes a new TensorFlow Lite signature runner instance with the given interpreter and
/// signature key.
///
/// - Parameters:
/// - interpreter: The TensorFlow Lite model interpreter.
/// - signatureKey: The signature key.
/// - Throws: An error if fail to create the signature runner with given key.
internal init(interpreter: Interpreter, signatureKey: String) throws {
guard let signatureKeyCString = signatureKey.cString(using: String.Encoding.utf8),
let cSignatureRunner = TfLiteInterpreterGetSignatureRunner(
interpreter.cInterpreter, signatureKeyCString)
else {
throw SignatureRunnerError.failedToCreateSignatureRunner(signatureKey: signatureKey)
}
self.cSignatureRunner = cSignatureRunner
self.signatureKey = signatureKey
self.interpreter = interpreter
try allocateTensors()
}
deinit {
TfLiteSignatureRunnerDelete(cSignatureRunner)
}
// MARK: Public
/// Invokes the signature with given input data.
///
/// - Parameters:
/// - inputs: A map from input name to the input data. The input data will be copied into the
/// input tensor.
/// - Throws: `SignatureRunnerError` if input data copying or signature invocation fails.
public func invoke(with inputs: [String: Data]) throws {
try allocateTensors()
for (inputName, inputData) in inputs {
try copy(inputData, toInputNamed: inputName)
}
guard TfLiteSignatureRunnerInvoke(self.cSignatureRunner) == kTfLiteOk else {
throw SignatureRunnerError.failedToInvokeSignature(signatureKey: signatureKey)
}
}
/// Returns the input tensor with the given input name in the signature.
///
/// - Parameters:
/// - name: The input name in the signature.
/// - Throws: An error if fail to get the input `Tensor` or the `Tensor` is invalid.
/// - Returns: The input `Tensor` with the given input name.
public func input(named name: String) throws -> Tensor {
return try tensor(named: name, withType: TensorType.input)
}
/// Returns the output tensor with the given output name in the signature.
///
/// - Parameters:
/// - name: The output name in the signature.
/// - Throws: An error if fail to get the output `Tensor` or the `Tensor` is invalid.
/// - Returns: The output `Tensor` with the given output name.
public func output(named name: String) throws -> Tensor {
return try tensor(named: name, withType: TensorType.output)
}
/// Resizes the input `Tensor` with the given input name to the specified `Tensor.Shape`.
///
/// - Note: After resizing an input tensor, the client **must** explicitly call
/// `allocateTensors()` before attempting to access the resized tensor data.
/// - Parameters:
/// - name: The input name of the `Tensor`.
/// - shape: The shape to resize the input `Tensor` to.
/// - Throws: An error if the input tensor with given input name could not be resized.
public func resizeInput(named name: String, toShape shape: Tensor.Shape) throws {
guard let inputNameCString = name.cString(using: String.Encoding.utf8),
TfLiteSignatureRunnerResizeInputTensor(
self.cSignatureRunner,
inputNameCString,
shape.int32Dimensions,
Int32(shape.rank)
) == kTfLiteOk
else {
throw SignatureRunnerError.failedToResizeInputTensor(inputName: name)
}
isTensorsAllocationNeeded = true
}
/// Copies the given data to the input `Tensor` with the given input name.
///
/// - Parameters:
/// - data: The data to be copied to the input `Tensor`'s data buffer.
/// - name: The input name of the `Tensor`.
/// - Throws: An error if fail to get the input `Tensor` or if the `data.count` does not match the
/// input tensor's `data.count`.
/// - Returns: The input `Tensor` with the copied data.
public func copy(_ data: Data, toInputNamed name: String) throws {
guard let inputNameCString = name.cString(using: String.Encoding.utf8),
let cTensor = TfLiteSignatureRunnerGetInputTensor(self.cSignatureRunner, inputNameCString)
else {
throw SignatureRunnerError.failedToGetTensor(tensorType: "input", nameInSignature: name)
}
let byteCount = TfLiteTensorByteSize(cTensor)
guard data.count == byteCount else {
throw SignatureRunnerError.invalidTensorDataCount(provided: data.count, required: byteCount)
}
#if swift(>=5.0)
let status = data.withUnsafeBytes {
TfLiteTensorCopyFromBuffer(cTensor, $0.baseAddress, data.count)
}
#else
let status = data.withUnsafeBytes { TfLiteTensorCopyFromBuffer(cTensor, $0, data.count) }
#endif // swift(>=5.0)
guard status == kTfLiteOk else { throw SignatureRunnerError.failedToCopyDataToInputTensor }
}
/// Allocates memory for tensors.
/// - Note: This is a relatively expensive operation and this call is *purely optional*.
/// Tensor allocation will occur automatically during execution.
/// - Throws: An error if memory could not be allocated for the tensors.
public func allocateTensors() throws {
if !isTensorsAllocationNeeded { return }
guard TfLiteSignatureRunnerAllocateTensors(self.cSignatureRunner) == kTfLiteOk else {
throw SignatureRunnerError.failedToAllocateTensors
}
isTensorsAllocationNeeded = false
}
// MARK: - Private
/// Returns the I/O tensor with the given name in the signature.
///
/// - Parameters:
/// - nameInSignature: The input or output name in the signature.
/// - type: The tensor type.
/// - Throws: An error if fail to get the `Tensor` or the `Tensor` is invalid.
/// - Returns: The `Tensor` with the given name in the signature.
private func tensor(named nameInSignature: String, withType type: TensorType) throws -> Tensor {
guard let nameInSignatureCString = nameInSignature.cString(using: String.Encoding.utf8)
else {
throw SignatureRunnerError.failedToGetTensor(
tensorType: type.rawValue, nameInSignature: nameInSignature)
}
var cTensorPointer: CTensor
switch type {
case .input:
cTensorPointer = UnsafePointer(
TfLiteSignatureRunnerGetInputTensor(self.cSignatureRunner, nameInSignatureCString))
case .output:
cTensorPointer = TfLiteSignatureRunnerGetOutputTensor(
self.cSignatureRunner, nameInSignatureCString)
}
guard let cTensor = cTensorPointer else {
throw SignatureRunnerError.failedToGetTensor(
tensorType: type.rawValue, nameInSignature: nameInSignature)
}
guard let bytes = TfLiteTensorData(cTensor) else {
throw SignatureRunnerError.allocateTensorsRequired
}
guard let dataType = Tensor.DataType(type: TfLiteTensorType(cTensor)) else {
throw SignatureRunnerError.invalidTensorDataType
}
let nameCString = TfLiteTensorName(cTensor)
let name = nameCString == nil ? "" : String(cString: nameCString!)
let byteCount = TfLiteTensorByteSize(cTensor)
let data = Data(bytes: bytes, count: byteCount)
let rank = TfLiteTensorNumDims(cTensor)
let dimensions = (0..<rank).map { Int(TfLiteTensorDim(cTensor, $0)) }
let shape = Tensor.Shape(dimensions)
let cQuantizationParams = TfLiteTensorQuantizationParams(cTensor)
let scale = cQuantizationParams.scale
let zeroPoint = Int(cQuantizationParams.zero_point)
var quantizationParameters: QuantizationParameters? = nil
if scale != 0.0 {
quantizationParameters = QuantizationParameters(scale: scale, zeroPoint: zeroPoint)
}
let tensor = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
return tensor
}
}
@@ -0,0 +1,59 @@
// 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
/// Errors thrown by the TensorFlow Lite `SignatureRunner`.
public enum SignatureRunnerError: Error, Equatable, Hashable {
case invalidTensorDataCount(provided: Int, required: Int)
case invalidTensorDataType
case failedToCreateSignatureRunner(signatureKey: String)
case failedToGetTensor(tensorType: String, nameInSignature: String)
case failedToResizeInputTensor(inputName: String)
case failedToCopyDataToInputTensor
case failedToAllocateTensors
case failedToInvokeSignature(signatureKey: String)
case allocateTensorsRequired
}
extension SignatureRunnerError: LocalizedError {
/// A localized description of the signature runner error.
public var errorDescription: String? {
switch self {
case .invalidTensorDataCount(let provided, let required):
return "Provided data count \(provided) must match the required count \(required)."
case .invalidTensorDataType:
return "Tensor data type is unsupported or could not be determined due to a model error."
case .failedToCreateSignatureRunner(let signatureKey):
return "Failed to create a signature runner. Signature with key (\(signatureKey)) not found."
case .failedToGetTensor(let tensorType, let nameInSignature):
return "Failed to get \(tensorType) tensor with \(tensorType) name (\(nameInSignature))."
case .failedToResizeInputTensor(let inputName):
return "Failed to resize input tensor with input name (\(inputName))."
case .failedToCopyDataToInputTensor:
return "Failed to copy data to input tensor."
case .failedToAllocateTensors:
return "Failed to allocate memory for input tensors."
case .failedToInvokeSignature(let signatureKey):
return "Failed to invoke the signature runner with key (\(signatureKey))."
case .allocateTensorsRequired:
return "Must call allocateTensors()."
}
}
}
extension SignatureRunnerError: CustomStringConvertible {
/// A textual representation of the TensorFlow Lite signature runner error.
public var description: String { return errorDescription ?? "Unknown error." }
}
+156
View File
@@ -0,0 +1,156 @@
// 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
import TensorFlowLiteC
/// An input or output tensor in a TensorFlow Lite graph.
public struct Tensor: Equatable, Hashable {
/// The name of the `Tensor`.
public let name: String
/// The data type of the `Tensor`.
public let dataType: DataType
/// The shape of the `Tensor`.
public let shape: Shape
/// The data of the `Tensor`. The data is created with copied memory content. See creating data
/// from raw memory at https://developer.apple.com/documentation/foundation/data.
public let data: Data
/// The quantization parameters for the `Tensor` if using a quantized model.
public let quantizationParameters: QuantizationParameters?
/// Creates a new input or output `Tensor` instance.
///
/// - Parameters:
/// - name: The name of the `Tensor`.
/// - dataType: The data type of the `Tensor`.
/// - shape: The shape of the `Tensor`.
/// - data: The data in the input `Tensor`.
/// - quantizationParameters Parameters for the `Tensor` if using a quantized model. The default
/// is `nil`.
init(
name: String,
dataType: DataType,
shape: Shape,
data: Data,
quantizationParameters: QuantizationParameters? = nil
) {
self.name = name
self.dataType = dataType
self.shape = shape
self.data = data
self.quantizationParameters = quantizationParameters
}
}
extension Tensor {
/// The supported `Tensor` data types.
public enum DataType: Equatable, Hashable {
/// A boolean.
case bool
/// An 8-bit unsigned integer.
case uInt8
/// A 16-bit signed integer.
case int16
/// A 32-bit signed integer.
case int32
/// A 64-bit signed integer.
case int64
/// A 16-bit half precision floating point.
case float16
/// A 32-bit single precision floating point.
case float32
/// A 64-bit double precision floating point.
case float64
/// Creates a new instance from the given `TfLiteType` or `nil` if the data type is unsupported
/// or could not be determined because there was an error.
///
/// - Parameter type: A data type for a tensor.
init?(type: TfLiteType) {
switch type {
case kTfLiteBool:
self = .bool
case kTfLiteUInt8:
self = .uInt8
case kTfLiteInt16:
self = .int16
case kTfLiteInt32:
self = .int32
case kTfLiteInt64:
self = .int64
case kTfLiteFloat16:
self = .float16
case kTfLiteFloat32:
self = .float32
case kTfLiteFloat64:
self = .float64
case kTfLiteNoType:
fallthrough
default:
return nil
}
}
}
}
extension Tensor {
/// The shape of a `Tensor`.
public struct Shape: Equatable, Hashable {
/// The number of dimensions of the `Tensor`.
public let rank: Int
/// An array of dimensions for the `Tensor`.
public let dimensions: [Int]
/// An array of `Int32` dimensions for the `Tensor`.
var int32Dimensions: [Int32] { return dimensions.map(Int32.init) }
/// Creates a new instance with the given array of dimensions.
///
/// - Parameters:
/// - dimensions: Dimensions for the `Tensor`.
public init(_ dimensions: [Int]) {
self.rank = dimensions.count
self.dimensions = dimensions
}
/// Creates a new instance with the given elements representing the dimensions.
///
/// - Parameters:
/// - elements: Dimensions for the `Tensor`.
public init(_ elements: Int...) {
self.init(elements)
}
}
}
extension Tensor.Shape: ExpressibleByArrayLiteral {
/// Creates a new instance with the given array literal representing the dimensions.
///
/// - Parameters:
/// - arrayLiteral: Dimensions for the `Tensor`.
public init(arrayLiteral: Int...) {
self.init(arrayLiteral)
}
}
/// A tensor's function level purpose: input or output.
internal enum TensorType: String {
case input
case output
}
@@ -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 TensorFlowLiteC
/// TensorFlow Lite runtime values.
public enum Runtime {
/// A string describing the semantic versioning information for the runtime. Is an empty string if
/// the version could not be determined.
public static var version: String { return TfLiteVersion().map(String.init) ?? "" }
}
@@ -0,0 +1,61 @@
{
"additionalFilePaths" : [
"tensorflow/lite/swift/BUILD"
],
"buildTargets" : [
"//tensorflow/lite/swift:TensorFlowLiteAllDelegates",
"//tensorflow/lite/swift:Tests",
"//tensorflow/lite/swift:TestsLibrary"
],
"optionSet" : {
"BazelBuildOptionsDebug" : {
"p" : "$(inherited)"
},
"BazelBuildOptionsRelease" : {
"p" : "$(inherited)"
},
"BazelBuildStartupOptionsDebug" : {
"p" : "$(inherited)"
},
"BazelBuildStartupOptionsRelease" : {
"p" : "$(inherited)"
},
"BuildActionPostActionScript" : {
"p" : "$(inherited)"
},
"BuildActionPreActionScript" : {
"p" : "$(inherited)"
},
"CLANG_CXX_LANGUAGE_STANDARD" : {
"p" : "c++14"
},
"CommandlineArguments" : {
"p" : "$(inherited)"
},
"EnvironmentVariables" : {
"p" : "$(inherited)"
},
"LaunchActionPostActionScript" : {
"p" : "$(inherited)"
},
"LaunchActionPreActionScript" : {
"p" : "$(inherited)"
},
"ProjectGenerationCompilationMode" : {
"p" : "opt"
},
"TestActionPostActionScript" : {
"p" : "$(inherited)"
},
"TestActionPreActionScript" : {
"p" : "$(inherited)"
}
},
"projectName" : "TensorFlowLite",
"sourceFilters" : [
"tensorflow/lite/c",
"tensorflow/lite/swift",
"tensorflow/lite/swift/Sources",
"tensorflow/lite/swift/Tests"
]
}
@@ -0,0 +1,17 @@
{
"configDefaults" : {
"optionSet" : {
"ProjectPrioritizesSwift" : {
"p" : "YES"
},
"SwiftForcesdSYMs" : {
"p" : "NO"
}
}
},
"projectName" : "TensorFlowLite",
"packages" : [
"tensorflow/lite/swift"
],
"workspaceRoot" : "../../../.."
}
@@ -0,0 +1,72 @@
Pod::Spec.new do |s|
s.name = 'TensorFlowLiteSwift'
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 Swift'
s.description = <<-DESC
TensorFlow Lite is TensorFlow's lightweight solution for Swift 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.swift_version = '5.0'
s.module_name = 'TensorFlowLite'
s.static_framework = true
tfl_dir = 'tensorflow/lite/'
swift_dir = tfl_dir + 'swift/'
s.pod_target_xcconfig = {
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386'
}
s.user_target_xcconfig = {
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386'
}
s.default_subspec = 'Core'
s.subspec 'Core' do |core|
core.dependency 'TensorFlowLiteC', "#{s.version}"
core.source_files = swift_dir + 'Sources/*.swift'
core.exclude_files = swift_dir + 'Sources/{CoreML,Metal}Delegate.swift'
core.test_spec 'Tests' do |ts|
ts.source_files = swift_dir + 'Tests/*.swift'
ts.exclude_files = swift_dir + 'Tests/MetalDelegateTests.swift'
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 = swift_dir + 'Sources/CoreMLDelegate.swift'
coreml.dependency 'TensorFlowLiteC/CoreML', "#{s.version}"
coreml.dependency 'TensorFlowLiteSwift/Core', "#{s.version}"
end
s.subspec 'Metal' do |metal|
metal.source_files = swift_dir + 'Sources/MetalDelegate.swift'
metal.dependency 'TensorFlowLiteC/Metal', "#{s.version}"
metal.dependency 'TensorFlowLiteSwift/Core', "#{s.version}"
metal.test_spec 'Tests' do |ts|
ts.source_files = swift_dir + 'Tests/{Interpreter,MetalDelegate}Tests.swift'
ts.resources = [
tfl_dir + 'testdata/add.bin',
tfl_dir + 'testdata/add_quantized.bin',
tfl_dir + 'testdata/multi_add.bin',
]
end
end
end
@@ -0,0 +1,86 @@
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 Swift'
s.description = <<-DESC
TensorFlow Lite is TensorFlow's lightweight solution for Swift 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.swift_version = '5.0'
s.module_name = 'TensorFlowLite'
s.static_framework = true
tfl_dir = 'tensorflow/lite/'
swift_dir = tfl_dir + 'swift/'
# TODO: Remove this after adding support for arm64 simulator.
s.pod_target_xcconfig = {
'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 => swift_dir + 'PrivacyInfo.xcprivacy'
}
end
s.subspec 'Core' do |core|
core.dependency "${C_POD_NAME}", "#{s.version}"
core.dependency "#{s.name}/Privacy", "#{s.version}"
core.source_files = swift_dir + 'Sources/*.swift'
core.exclude_files = swift_dir + 'Sources/{CoreML,Metal}Delegate.swift'
core.test_spec 'Tests' do |ts|
ts.source_files = swift_dir + 'Tests/*.swift'
ts.exclude_files = swift_dir + 'Tests/MetalDelegateTests.swift'
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 = swift_dir + 'Sources/CoreMLDelegate.swift'
coreml.dependency "${C_POD_NAME}/CoreML", "#{s.version}"
coreml.dependency "#{s.name}/Core", "#{s.version}"
coreml.dependency "#{s.name}/Privacy", "#{s.version}"
end
s.subspec 'Metal' do |metal|
metal.source_files = swift_dir + 'Sources/MetalDelegate.swift'
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 = swift_dir + 'Tests/{Interpreter,MetalDelegate}Tests.swift'
ts.resources = [
tfl_dir + 'testdata/add.bin',
tfl_dir + 'testdata/add_quantized.bin',
tfl_dir + 'testdata/multi_add.bin',
]
end
end
end
+7
View File
@@ -0,0 +1,7 @@
# TensorFlow Lite Swift API Test App
The TensorFlow Lite Swift API usage can be found in various iOS example apps
provided in the following locations.
* [TensorFlow Lite example apps](https://www.tensorflow.org/lite/examples)
* [tensorflow/examples Repository](https://github.com/tensorflow/examples)
@@ -0,0 +1,391 @@
// 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 XCTest
@testable import TensorFlowLite
class InterpreterTests: XCTestCase {
var interpreter: Interpreter!
override func setUp() {
super.setUp()
interpreter = try! Interpreter(modelPath: AddModel.path)
}
override func tearDown() {
interpreter = nil
super.tearDown()
}
func testInit_ValidModelPath() {
XCTAssertNoThrow(try Interpreter(modelPath: AddModel.path))
}
func testInit_InvalidModelPath_ThrowsFailedToLoadModel() {
XCTAssertThrowsError(try Interpreter(modelPath: "/invalid/path")) { error in
self.assertEqualErrors(actual: error, expected: .failedToLoadModel)
}
}
func testInitWithOptions() throws {
var options = Interpreter.Options()
options.threadCount = 2
let interpreter = try Interpreter(modelPath: AddQuantizedModel.path, options: options)
XCTAssertNotNil(interpreter.options)
XCTAssertNil(interpreter.delegates)
}
func testInit_WithData_ValidModelPath() {
XCTAssertNoThrow(
try Interpreter(modelData: try Data(contentsOf: URL(fileURLWithPath: AddModel.path))))
}
func testInitWithDataWithOptions() throws {
var options = Interpreter.Options()
options.threadCount = 2
let interpreter = try Interpreter(
modelData: try Data(contentsOf: URL(fileURLWithPath: AddQuantizedModel.path)),
options: options
)
XCTAssertNotNil(interpreter.options)
XCTAssertNil(interpreter.delegates)
}
func testInputTensorCount() {
XCTAssertEqual(interpreter.inputTensorCount, AddModel.inputTensorCount)
}
func testOutputTensorCount() {
XCTAssertEqual(interpreter.outputTensorCount, AddModel.outputTensorCount)
}
func testInvoke() throws {
try interpreter.allocateTensors()
XCTAssertNoThrow(try interpreter.invoke())
}
func testInvoke_ThrowsAllocateTensorsRequired_ModelNotReady() {
XCTAssertThrowsError(try interpreter.invoke()) { error in
self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired)
}
}
func testInputTensorAtIndex() throws {
try setUpAddModelInputTensor()
let inputTensor = try interpreter.input(at: AddModel.validIndex)
XCTAssertEqual(inputTensor, AddModel.inputTensor)
}
func testInputTensorAtIndex_QuantizedModel() throws {
interpreter = try Interpreter(modelPath: AddQuantizedModel.path)
try setUpAddQuantizedModelInputTensor()
let inputTensor = try interpreter.input(at: AddQuantizedModel.inputOutputIndex)
XCTAssertEqual(inputTensor, AddQuantizedModel.inputTensor)
}
func testInputTensorAtIndex_ThrowsInvalidIndex() throws {
try interpreter.allocateTensors()
XCTAssertThrowsError(try interpreter.input(at: AddModel.invalidIndex)) { error in
let maxIndex = AddModel.inputTensorCount - 1
self.assertEqualErrors(
actual: error,
expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex)
)
}
}
func testInputTensorAtIndex_ThrowsAllocateTensorsRequired() {
XCTAssertThrowsError(try interpreter.input(at: AddModel.validIndex)) { error in
self.assertEqualErrors(actual: error, expected: .allocateTensorsRequired)
}
}
func testOutputTensorAtIndex() throws {
try setUpAddModelInputTensor()
try interpreter.invoke()
let outputTensor = try interpreter.output(at: AddModel.validIndex)
XCTAssertEqual(outputTensor, AddModel.outputTensor)
let expectedResults = [Float32](unsafeData: outputTensor.data)
XCTAssertEqual(expectedResults, AddModel.results)
}
func testOutputTensorAtIndex_QuantizedModel() throws {
interpreter = try Interpreter(modelPath: AddQuantizedModel.path)
try setUpAddQuantizedModelInputTensor()
try interpreter.invoke()
let outputTensor = try interpreter.output(at: AddQuantizedModel.inputOutputIndex)
XCTAssertEqual(outputTensor, AddQuantizedModel.outputTensor)
let expectedResults = [UInt8](outputTensor.data)
XCTAssertEqual(expectedResults, AddQuantizedModel.results)
}
func testOutputTensorAtIndex_ThrowsInvalidIndex() throws {
try interpreter.allocateTensors()
try interpreter.invoke()
XCTAssertThrowsError(try interpreter.output(at: AddModel.invalidIndex)) { error in
let maxIndex = AddModel.outputTensorCount - 1
self.assertEqualErrors(
actual: error,
expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex)
)
}
}
func testOutputTensorAtIndex_ThrowsInvokeInterpreterRequired() {
XCTAssertThrowsError(try interpreter.output(at: AddModel.validIndex)) { error in
self.assertEqualErrors(actual: error, expected: .invokeInterpreterRequired)
}
}
func testResizeInputTensorAtIndexToShape() {
XCTAssertNoThrow(try interpreter.resizeInput(at: AddModel.validIndex, to: [2, 2, 3]))
XCTAssertNoThrow(try interpreter.allocateTensors())
}
func testResizeInputTensorAtIndexToShape_ThrowsInvalidIndex() {
XCTAssertThrowsError(
try interpreter.resizeInput(
at: AddModel.invalidIndex,
to: [2, 2, 3]
)
) { error in
let maxIndex = AddModel.inputTensorCount - 1
self.assertEqualErrors(
actual: error,
expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex)
)
}
}
func testCopyDataToInputTensorAtIndex() throws {
try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape)
try interpreter.allocateTensors()
let inputTensor = try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex)
XCTAssertEqual(inputTensor.data, AddModel.inputData)
}
func testCopyDataToInputTensorAtIndex_ThrowsInvalidIndex() {
XCTAssertThrowsError(
try interpreter.copy(
AddModel.inputData,
toInputAt: AddModel.invalidIndex
)
) { error in
let maxIndex = AddModel.inputTensorCount - 1
self.assertEqualErrors(
actual: error,
expected: .invalidTensorIndex(index: AddModel.invalidIndex, maxIndex: maxIndex)
)
}
}
func testCopyDataToInputTensorAtIndex_ThrowsInvalidDataCount() throws {
try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape)
try interpreter.allocateTensors()
let invalidData = Data(count: AddModel.dataCount - 1)
XCTAssertThrowsError(
try interpreter.copy(
invalidData,
toInputAt: AddModel.validIndex
)
) { error in
self.assertEqualErrors(
actual: error,
expected: .invalidTensorDataCount(provided: invalidData.count, required: AddModel.dataCount)
)
}
}
func testAllocateTensors() {
XCTAssertNoThrow(try interpreter.allocateTensors())
}
// MARK: - Private
private func setUpAddModelInputTensor() throws {
precondition(interpreter != nil)
try interpreter.resizeInput(at: AddModel.validIndex, to: AddModel.shape)
try interpreter.allocateTensors()
try interpreter.copy(AddModel.inputData, toInputAt: AddModel.validIndex)
}
private func setUpAddQuantizedModelInputTensor() throws {
precondition(interpreter != nil)
try interpreter.resizeInput(at: AddQuantizedModel.inputOutputIndex, to: AddQuantizedModel.shape)
try interpreter.allocateTensors()
try interpreter.copy(AddQuantizedModel.inputData, toInputAt: AddQuantizedModel.inputOutputIndex)
}
private func assertEqualErrors(actual: Error, expected: InterpreterError) {
guard let actual = actual as? InterpreterError else {
XCTFail("Actual error should be of type InterpreterError.")
return
}
XCTAssertEqual(actual, expected)
}
}
class InterpreterOptionsTests: XCTestCase {
func testInitWithDefaultValues() {
let options = Interpreter.Options()
XCTAssertNil(options.threadCount)
XCTAssertFalse(options.isXNNPackEnabled)
}
func testInitWithCustomValues() {
var options = Interpreter.Options()
options.threadCount = 2
XCTAssertEqual(options.threadCount, 2)
options.isXNNPackEnabled = false
XCTAssertFalse(options.isXNNPackEnabled)
options.isXNNPackEnabled = true
XCTAssertTrue(options.isXNNPackEnabled)
}
func testEquatable() {
var options1 = Interpreter.Options()
var options2 = Interpreter.Options()
XCTAssertEqual(options1, options2)
options1.threadCount = 2
options2.threadCount = 2
XCTAssertEqual(options1, options2)
options2.threadCount = 3
XCTAssertNotEqual(options1, options2)
options2.threadCount = 2
XCTAssertEqual(options1, options2)
options2.isXNNPackEnabled = true
XCTAssertNotEqual(options1, options2)
options1.isXNNPackEnabled = true
XCTAssertEqual(options1, options2)
}
}
// MARK: - Constants
/// Values for the `add.bin` model.
enum AddModel {
static let info = (name: "add", extension: "bin")
static let inputTensorCount = 1
static let outputTensorCount = 1
static let invalidIndex = 1
static let validIndex = 0
static let shape: Tensor.Shape = [2]
static let dataCount = inputData.count
static let inputData = Data(copyingBufferOf: [Float32(1.0), Float32(3.0)])
static let outputData = Data(copyingBufferOf: [Float32(3.0), Float32(9.0)])
static let results = [Float32(3.0), Float32(9.0)]
static let inputTensor = Tensor(
name: "input",
dataType: .float32,
shape: shape,
data: inputData
)
static let outputTensor = Tensor(
name: "output",
dataType: .float32,
shape: shape,
data: outputData
)
static var path: String = {
let bundle = Bundle(for: InterpreterTests.self)
guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" }
return path
}()
}
/// Values for the `add_quantized.bin` model.
enum AddQuantizedModel {
static let info = (name: "add_quantized", extension: "bin")
static let inputOutputIndex = 0
static let shape: Tensor.Shape = [2]
static let inputData = Data([1, 3])
static let outputData = Data([3, 9])
static let quantizationParameters = QuantizationParameters(scale: 0.003922, zeroPoint: 0)
static let results: [UInt8] = [3, 9]
static let inputTensor = Tensor(
name: "input",
dataType: .uInt8,
shape: shape,
data: inputData,
quantizationParameters: quantizationParameters
)
static let outputTensor = Tensor(
name: "output",
dataType: .uInt8,
shape: shape,
data: outputData,
quantizationParameters: quantizationParameters
)
static var path: String = {
let bundle = Bundle(for: InterpreterTests.self)
guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" }
return path
}()
}
// MARK: - Extensions
extension Array {
/// Creates a new array from the bytes of the given unsafe data.
///
/// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit
/// with no indirection or reference-counting operations; otherwise, copying the raw bytes in
/// the `unsafeData`'s buffer to a new array returns an unsafe copy.
/// - Note: Returns `nil` if `unsafeData.count` is not a multiple of
/// `MemoryLayout<Element>.stride`.
/// - Parameter unsafeData: The data containing the bytes to turn into an array.
init?(unsafeData: Data) {
guard unsafeData.count % MemoryLayout<Element>.stride == 0 else { return nil }
#if swift(>=5.0)
self = unsafeData.withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
#else
self = unsafeData.withUnsafeBytes {
.init(
UnsafeBufferPointer<Element>(
start: $0,
count: unsafeData.count / MemoryLayout<Element>.stride
))
}
#endif // swift(>=5.0)
}
}
extension Data {
/// Creates a new buffer by copying the buffer pointer of the given array.
///
/// - Warning: The given array's element type `T` must be trivial in that it can be copied bit
/// for bit with no indirection or reference-counting operations; otherwise, reinterpreting
/// data from the resulting buffer has undefined behavior.
/// - Parameter array: An array with elements of type `T`.
init<T>(copyingBufferOf array: [T]) {
self = array.withUnsafeBufferPointer(Data.init)
}
}
@@ -0,0 +1,114 @@
// 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 XCTest
@testable import TensorFlowLite
class MetalDelegateTests: XCTestCase {
func testInitDefaultGPUDelegateOptions() {
let delegate = MetalDelegate()
XCTAssertFalse(delegate.options.isPrecisionLossAllowed)
XCTAssertEqual(delegate.options.waitType, .passive)
}
func testInitWithCustomGPUDelegateOptions() {
var options = MetalDelegate.Options()
options.isPrecisionLossAllowed = true
options.waitType = .active
let delegate = MetalDelegate(options: options)
XCTAssertTrue(delegate.options.isPrecisionLossAllowed)
XCTAssertEqual(delegate.options.waitType, .active)
}
func testInitInterpreterWithDelegate() throws {
// If metal device is not available, skip.
if MTLCreateSystemDefaultDevice() == nil {
return
}
let metalDelegate = MetalDelegate()
let interpreter = try Interpreter(modelPath: MultiAddModel.path, delegates: [metalDelegate])
XCTAssertEqual(interpreter.delegates?.count, 1)
XCTAssertNil(interpreter.options)
}
func testInitInterpreterWithOptionsAndDelegate() throws {
// If metal device is not available, skip.
if MTLCreateSystemDefaultDevice() == nil {
return
}
var options = Interpreter.Options()
options.threadCount = 1
let metalDelegate = MetalDelegate()
let interpreter = try Interpreter(
modelPath: MultiAddModel.path,
options: options,
delegates: [metalDelegate]
)
XCTAssertNotNil(interpreter.options)
XCTAssertEqual(interpreter.delegates?.count, 1)
}
}
class MetalDelegateOptionsTests: XCTestCase {
func testInitWithDefaultValues() {
let options = MetalDelegate.Options()
XCTAssertFalse(options.isPrecisionLossAllowed)
XCTAssertEqual(options.waitType, .passive)
}
func testInitWithCustomValues() {
var options = MetalDelegate.Options()
options.isPrecisionLossAllowed = true
options.waitType = .active
XCTAssertTrue(options.isPrecisionLossAllowed)
XCTAssertEqual(options.waitType, .active)
}
func testEquatable() {
var options1 = MetalDelegate.Options()
var options2 = MetalDelegate.Options()
XCTAssertEqual(options1, options2)
options1.isPrecisionLossAllowed = true
options2.isPrecisionLossAllowed = true
XCTAssertEqual(options1, options2)
options1.waitType = .none
options2.waitType = .none
XCTAssertEqual(options1, options2)
options2.isPrecisionLossAllowed = false
XCTAssertNotEqual(options1, options2)
options1.isPrecisionLossAllowed = false
options1.waitType = .aggressive
XCTAssertNotEqual(options1, options2)
}
}
/// Values for the `multi_add.bin` model.
enum MultiAddModel {
static let info = (name: "multi_add", extension: "bin")
static var path: String = {
let bundle = Bundle(for: MetalDelegateTests.self)
guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" }
return path
}()
}
@@ -0,0 +1,72 @@
// 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 XCTest
@testable import TensorFlowLite
class ModelTests: XCTestCase {
var modelPath: String!
override func setUp() {
super.setUp()
let bundle = Bundle(for: type(of: self))
guard
let modelPath = bundle.path(
forResource: Constant.modelInfo.name,
ofType: Constant.modelInfo.extension
)
else {
XCTFail("Failed to get the model file path.")
return
}
self.modelPath = modelPath
}
override func tearDown() {
modelPath = nil
super.tearDown()
}
func testInitWithFilePath() {
let model = Model(filePath: modelPath)
XCTAssertNotNil(model)
XCTAssertNotNil(model?.cModel)
XCTAssertNil(model?.data)
}
func testInitWithEmptyFilePath_FailsInitialization() {
XCTAssertNil(Model(filePath: ""))
}
func testInitWithInvalidFilePath_FailsInitialization() {
XCTAssertNil(Model(filePath: "invalid/path"))
}
func testInitWithData() throws {
let model = Model(modelData: try Data(contentsOf: URL(fileURLWithPath: modelPath)))
XCTAssertNotNil(model)
XCTAssertNotNil(model?.cModel)
XCTAssertNotNil(model?.data)
}
}
// MARK: - Constants
private enum Constant {
static let modelInfo = (name: "add", extension: "bin")
}
@@ -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 XCTest
@testable import TensorFlowLite
class QuantizationParametersTests: XCTestCase {
func testInitWithCustomValues() {
let parameters = QuantizationParameters(scale: 0.5, zeroPoint: 1)
XCTAssertEqual(parameters.scale, 0.5)
XCTAssertEqual(parameters.zeroPoint, 1)
}
func testEquatable() {
let parameters1 = QuantizationParameters(scale: 0.5, zeroPoint: 1)
let parameters2 = QuantizationParameters(scale: 0.5, zeroPoint: 1)
XCTAssertEqual(parameters1, parameters2)
let parameters3 = QuantizationParameters(scale: 0.4, zeroPoint: 1)
XCTAssertNotEqual(parameters1, parameters3)
XCTAssertNotEqual(parameters2, parameters3)
}
}
@@ -0,0 +1,141 @@
// 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 XCTest
@testable import TensorFlowLite
class SignatureRunnerTest: XCTestCase {
func testSignatureKeys() throws {
let interpreter = try Interpreter(modelPath: MultiSignaturesModel.path)
XCTAssertEqual(interpreter.signatureKeys, MultiSignaturesModel.signatureKeys)
XCTAssertNotNil(try interpreter.signatureRunner(with: MultiSignaturesModel.AddSignature.key))
XCTAssertThrowsError(try interpreter.signatureRunner(with: "dummy")) { error in
self.assertEqualErrors(
actual: error, expected: .failedToCreateSignatureRunner(signatureKey: "dummy"))
}
}
func testResizeInputTensor() throws {
let interpreter = try Interpreter(modelPath: MultiSignaturesModel.path)
let addRunner = try interpreter.signatureRunner(with: MultiSignaturesModel.AddSignature.key)
XCTAssertEqual(addRunner.inputs, MultiSignaturesModel.AddSignature.inputs)
let inputTensor = try addRunner.input(named: "x")
// Validate signature "add" input tensor "x" before resizing.
XCTAssertEqual(inputTensor.name, MultiSignaturesModel.AddSignature.inputTensor.name)
XCTAssertEqual(inputTensor.dataType, MultiSignaturesModel.AddSignature.inputTensor.dataType)
XCTAssertEqual(inputTensor.shape, [1])
// Test fail to copy data before resizing the tensor
XCTAssertThrowsError(
try addRunner.copy(MultiSignaturesModel.AddSignature.inputData, toInputNamed: "x")
) { error in
self.assertEqualErrors(
actual: error, expected: .invalidTensorDataCount(provided: 8, required: 4))
}
// Resize signature "add" input tensor "x"
try addRunner.resizeInput(named: "x", toShape: MultiSignaturesModel.AddSignature.shape)
try addRunner.allocateTensors()
// Copy data to input tensor "x"
try addRunner.copy(MultiSignaturesModel.AddSignature.inputData, toInputNamed: "x")
// Validate signature "add" input tensor "x" after resizing and copying data.
XCTAssertEqual(
try addRunner.input(named: "x"), MultiSignaturesModel.AddSignature.inputTensor)
}
func testResizeInputTensor_invalidTensor() throws {
let interpreter = try Interpreter(modelPath: MultiSignaturesModel.path)
let addRunner = try interpreter.signatureRunner(with: MultiSignaturesModel.AddSignature.key)
// Test fail to get input tensor for a dummy input name.
XCTAssertThrowsError(
try addRunner.input(named: "dummy")
) { error in
self.assertEqualErrors(
actual: error, expected: .failedToGetTensor(tensorType: "input", nameInSignature: "dummy"))
}
// Test fail to resize dummy input tensor
XCTAssertThrowsError(
try addRunner.resizeInput(named: "dummy", toShape: [2])
) { error in
self.assertEqualErrors(
actual: error, expected: .failedToResizeInputTensor(inputName: "dummy"))
}
}
func testInvokeWithInputs() throws {
let interpreter = try Interpreter(modelPath: MultiSignaturesModel.path)
let addRunner = try interpreter.signatureRunner(with: MultiSignaturesModel.AddSignature.key)
XCTAssertEqual(addRunner.outputs, MultiSignaturesModel.AddSignature.outputs)
// Validate signature "add" output tensor "output_0" before inference
let outputTensor = try addRunner.output(named: "output_0")
XCTAssertEqual(outputTensor.name, MultiSignaturesModel.AddSignature.outputTensor.name)
XCTAssertEqual(outputTensor.dataType, MultiSignaturesModel.AddSignature.outputTensor.dataType)
XCTAssertEqual(outputTensor.shape, [1])
// Resize signature "add" input tensor "x"
try addRunner.resizeInput(named: "x", toShape: MultiSignaturesModel.AddSignature.shape)
// Invoke signature "add" with inputs.
try addRunner.invoke(with: ["x": MultiSignaturesModel.AddSignature.inputData])
// Validate signature "add" output tensor "output_0" after inference
XCTAssertEqual(
try addRunner.output(named: "output_0"), MultiSignaturesModel.AddSignature.outputTensor)
}
// MARK: - Private
private func assertEqualErrors(actual: Error, expected: SignatureRunnerError) {
guard let actual = actual as? SignatureRunnerError else {
XCTFail("Actual error should be of type SignatureRunnerError.")
return
}
XCTAssertEqual(actual, expected)
}
}
// MARK: - Constants
/// Values for the `multi_signatures.bin` model.
enum MultiSignaturesModel {
static let info = (name: "multi_signatures", extension: "bin")
static let signatureKeys = [AddSignature.key, SubSignature.key]
static var path: String = {
let bundle = Bundle(for: SignatureRunnerTest.self)
guard let path = bundle.path(forResource: info.name, ofType: info.extension) else { return "" }
return path
}()
enum AddSignature {
static let key = "add"
static let inputs = ["x"]
static let outputs = ["output_0"]
static let inputData = Data(copyingBufferOf: [Float32(2.0), Float32(4.0)])
static let outputData = Data(copyingBufferOf: [Float32(4.0), Float32(6.0)])
static let shape: Tensor.Shape = [2]
static let inputTensor = Tensor(
name: "add_x:0",
dataType: .float32,
shape: shape,
data: inputData
)
static let outputTensor = Tensor(
name: "StatefulPartitionedCall:0",
dataType: .float32,
shape: shape,
data: outputData
)
}
enum SubSignature {
static let key = "sub"
}
}
@@ -0,0 +1,29 @@
// 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 XCTest
@testable import TensorFlowLite
class TensorFlowLiteTests: XCTestCase {
func testRuntime_Version() {
#if swift(>=5.0)
let pattern = #"^(\d+)\.(\d+)\.(\d+)([+-][-.0-9A-Za-z]+)?(\+\w+)?$"#
#else
let pattern = "^(\\d+)\\.(\\d+)\\.(\\d+)([+-][-.0-9A-Za-z]+)?(\\+\\w+)?$"
#endif // swift(>=5.0)
XCTAssertNotNil(TensorFlowLite.Runtime.version.range(of: pattern, options: .regularExpression))
}
}
@@ -0,0 +1,109 @@
// 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 XCTest
@testable import TensorFlowLite
class TensorTests: XCTestCase {
func testInit() {
let name = "InputTensor"
let dataType: Tensor.DataType = .uInt8
let shape = Tensor.Shape(Constant.dimensions)
guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return }
let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1)
let inputTensor = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
XCTAssertEqual(inputTensor.name, name)
XCTAssertEqual(inputTensor.dataType, dataType)
XCTAssertEqual(inputTensor.shape, shape)
XCTAssertEqual(inputTensor.data, data)
XCTAssertEqual(inputTensor.quantizationParameters, quantizationParameters)
}
func testEquatable() {
let name = "Tensor"
let dataType: Tensor.DataType = .uInt8
let shape = Tensor.Shape(Constant.dimensions)
guard let data = name.data(using: .utf8) else { XCTFail("Data should not be nil."); return }
let quantizationParameters = QuantizationParameters(scale: 0.5, zeroPoint: 1)
let tensor1 = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
var tensor2 = Tensor(
name: name,
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
XCTAssertEqual(tensor1, tensor2)
tensor2 = Tensor(
name: "Tensor2",
dataType: dataType,
shape: shape,
data: data,
quantizationParameters: quantizationParameters
)
XCTAssertNotEqual(tensor1, tensor2)
}
}
class TensorShapeTests: XCTestCase {
func testInitWithArray() {
let shape = Tensor.Shape(Constant.dimensions)
XCTAssertEqual(shape.rank, Constant.dimensions.count)
XCTAssertEqual(shape.dimensions, Constant.dimensions)
}
func testInitWithElements() {
let shape = Tensor.Shape(2, 2, 3)
XCTAssertEqual(shape.rank, Constant.dimensions.count)
XCTAssertEqual(shape.dimensions, Constant.dimensions)
}
func testInitWithArrayLiteral() {
let shape: Tensor.Shape = [2, 2, 3]
XCTAssertEqual(shape.rank, Constant.dimensions.count)
XCTAssertEqual(shape.dimensions, Constant.dimensions)
}
func testEquatable() {
let shape1 = Tensor.Shape(2, 2, 3)
var shape2: Tensor.Shape = [2, 2, 3]
XCTAssertEqual(shape1, shape2)
shape2 = [2, 2, 4]
XCTAssertNotEqual(shape1, shape2)
}
}
// MARK: - Constants
private enum Constant {
/// Array of 2 arrays of 2 arrays of 3 numbers: [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]].
static let dimensions = [2, 2, 3]
}
+34
View File
@@ -0,0 +1,34 @@
# Generating docs for TensorFlowLiteSwift
Documentation is generated via [Jazzy](https://github.com/realm/jazzy) (Googlers
see cr/363774564 for more details).
To browse the Swift reference documentation visit
https://www.tensorflow.org/lite/api_docs/swift.
This directory contains a dummy Xcode project for generating documentation for
TensorFlowLiteSwift via Jazzy, an open-source tool that hooks into Xcode's
build tooling to parse doc comments. Unfortunately, TensorFlowLiteSwift is not
primarily developed via xcodebuild, so the docs build can potentially become
decoupled from upstream TensorFlowLiteSwift development.
Known issues:
- Every new file added to TensorFlowLiteSwift's BUILD must also manually be
added to this Xcode project.
- This project (and the resulting documentation) does not split types by
module, so there's no way to tell from looking at the generated documentation
which modules must be included in order to access a specific type.
- The TensorFlowLiteC dependency is included in binary form, contributing
significant bloat to the git repository since each binary contains unused
architecture slices.
To generate documentation outside of Google, run jazzy as you would on any other
Swift module:
```
jazzy \
--swift-build-tool xcodebuild \
--module "TensorFlowLiteSwift" \
--author "The TensorFlow Authors" \
--sdk iphoneos \
```
@@ -0,0 +1,22 @@
<?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>$(DEVELOPMENT_LANGUAGE)</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>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
@@ -0,0 +1,25 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#import <Foundation/Foundation.h>
//! Project version number for TensorFlowLiteSwift.
FOUNDATION_EXPORT double TensorFlowLiteSwiftVersionNumber;
//! Project version string for TensorFlowLiteSwift.
FOUNDATION_EXPORT const unsigned char TensorFlowLiteSwiftVersionString[];
// In this header, you should import all the public headers of your framework
// using statements like #import <TensorFlowLiteSwift/PublicHeader.h>
@@ -0,0 +1,35 @@
#!/bin/sh
# Copyright 2020 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Make sure we're running in Xcode environment
if [ -z "${SRCROOT}" ]
then
exit 1
fi
# Download TF Lite models from the internet if it does not exist.
FRAMEWORK_FOLDER="${SRCROOT}/Frameworks"
TFLITE_TAR="${FRAMEWORK_FOLDER}/TensorFlowLiteC"
TFLITE_C="${FRAMEWORK_FOLDER}/TensorFlowLiteC-2.4.0"
if [[ -d "$TFLITE_C" ]]; then
echo "INFO: TFLite frameworks already exist. Skip downloading and use the local frameworks."
else
mkdir -p "${FRAMEWORK_FOLDER}"
curl -o "${TFLITE_TAR}" -L "https://dl.google.com/dl/cpdc/e8a95c1d411b795e/TensorFlowLiteC-2.4.0.tar.gz"
tar -xvf "${TFLITE_TAR}" -C "${FRAMEWORK_FOLDER}"
rm "${TFLITE_TAR}"
echo "INFO: Downloaded TensorFlow frameworks to $TFLITE_C."
fi