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
@@ -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) ?? "" }
}