@preconcurrency import COnnxRuntime import Foundation public enum ORTLoggingLevel: Int32 { case verbose case info case warning case error case fatal internal var native: OrtLoggingLevel { OrtLoggingLevel(UInt32(rawValue)) } } public enum ORTValueType: Int32 { case unknown case tensor internal init(native: ONNXType) throws { switch native { case ONNX_TYPE_UNKNOWN: self = .unknown case ONNX_TYPE_TENSOR: self = .tensor default: throw ORTError.unsupported("Only tensor ONNX values are currently supported") } } } public enum ORTTensorElementDataType: Int32 { case undefined case float case int8 case uInt8 case int32 case uInt32 case int64 case uInt64 case string internal var native: ONNXTensorElementDataType { switch self { case .undefined: return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED case .float: return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT case .int8: return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8 case .uInt8: return ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 case .int32: return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 case .uInt32: return ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32 case .int64: return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 case .uInt64: return ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 case .string: return ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING } } internal init(native: ONNXTensorElementDataType) throws { switch native { case ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED: self = .undefined case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: self = .float case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: self = .int8 case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: self = .uInt8 case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: self = .int32 case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: self = .uInt32 case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: self = .int64 case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: self = .uInt64 case ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING: self = .string default: throw ORTError.unsupported("Unsupported tensor element type \(native.rawValue)") } } } public enum ORTGraphOptimizationLevel: Int32 { case none case basic case extended case layout case all internal var native: GraphOptimizationLevel { switch self { case .none: return ORT_DISABLE_ALL case .basic: return ORT_ENABLE_BASIC case .extended: return ORT_ENABLE_EXTENDED case .layout, .all: return ORT_ENABLE_ALL } } } public typealias ORTCAPIRegisterCustomOpsFnPtr = UnsafeMutableRawPointer private final class ORTValueStorage { let handle: OpaquePointer let retainedBuffer: NSMutableData? init(handle: OpaquePointer, retainedBuffer: NSMutableData? = nil) { self.handle = handle self.retainedBuffer = retainedBuffer } deinit { ORTNative.api.pointee.ReleaseValue?(handle) } } public func ORTVersion() -> String? { ORTNative.apiBase?.pointee.GetVersionString().map { String(cString: $0) } } public func ORTIsCoreMLExecutionProviderAvailable() -> Bool { false } public func ORTSetSeed(_ seed: Int64) { guard let trainingApi = ORTNative.trainingApi else { return } _ = trainingApi.pointee.SetSeed?(seed) } public struct ORTValueTypeInfo { public let type: ORTValueType public let tensorTypeAndShapeInfo: ORTTensorTypeAndShapeInfo? public init(type: ORTValueType, tensorTypeAndShapeInfo: ORTTensorTypeAndShapeInfo?) { self.type = type self.tensorTypeAndShapeInfo = tensorTypeAndShapeInfo } } public struct ORTTensorTypeAndShapeInfo { public let elementType: ORTTensorElementDataType public let shape: [Int] public init(elementType: ORTTensorElementDataType, shape: [Int]) { self.elementType = elementType self.shape = shape } } public struct ORTCoreMLExecutionProviderOptions { public var useCPUOnly: Bool public var useCPUAndGPU: Bool public var enableOnSubgraphs: Bool public var onlyEnableForDevicesWithANE: Bool public var onlyAllowStaticInputShapes: Bool public var createMLProgram: Bool public init( useCPUOnly: Bool = false, useCPUAndGPU: Bool = false, enableOnSubgraphs: Bool = false, onlyEnableForDevicesWithANE: Bool = false, onlyAllowStaticInputShapes: Bool = false, createMLProgram: Bool = false ) { self.useCPUOnly = useCPUOnly self.useCPUAndGPU = useCPUAndGPU self.enableOnSubgraphs = enableOnSubgraphs self.onlyEnableForDevicesWithANE = onlyEnableForDevicesWithANE self.onlyAllowStaticInputShapes = onlyAllowStaticInputShapes self.createMLProgram = createMLProgram } internal var providerOptions: [String: String] { [ "use_cpu_only": ORTNative.boolString(useCPUOnly), "use_cpu_and_gpu": ORTNative.boolString(useCPUAndGPU), "enable_on_subgraphs": ORTNative.boolString(enableOnSubgraphs), "only_enable_for_devices_with_ane": ORTNative.boolString(onlyEnableForDevicesWithANE), "only_allow_static_input_shapes": ORTNative.boolString(onlyAllowStaticInputShapes), "create_mlprogram": ORTNative.boolString(createMLProgram), ] } } public struct ORTXnnpackExecutionProviderOptions { public var intraOpNumThreads: Int32 public init(intraOpNumThreads: Int32 = 0) { self.intraOpNumThreads = intraOpNumThreads } } public final class ORTEnv { public let loggingLevel: ORTLoggingLevel internal let handle: OpaquePointer public init(loggingLevel: ORTLoggingLevel) throws { self.loggingLevel = loggingLevel var env: OpaquePointer? try ORTNative.check( ORTNative.api.pointee.CreateEnv?(loggingLevel.native, "onnxruntime-swift-interface", &env), action: "CreateEnv" ) guard let env else { throw ORTError.runtimeFailure(action: "CreateEnv", message: "Environment was not returned") } handle = env } deinit { ORTNative.api.pointee.ReleaseEnv?(handle) } } public final class ORTSession { private let handle: OpaquePointer public init( env: ORTEnv, modelPath: String, sessionOptions: ORTSessionOptions? = nil ) throws { var session: OpaquePointer? let createdOptions = sessionOptions == nil ? try ORTSessionOptions() : nil let optionsHandle = sessionOptions?.handle ?? createdOptions!.handle try modelPath.withCString { modelPathPointer in try ORTNative.check( ORTNative.api.pointee.CreateSession?(env.handle, modelPathPointer, optionsHandle, &session), action: "CreateSession" ) } guard let session else { throw ORTError.runtimeFailure(action: "CreateSession", message: "Session was not returned") } handle = session } deinit { ORTNative.api.pointee.ReleaseSession?(handle) } public func run( inputs: [String: ORTValue], outputs: [String: ORTValue], runOptions: ORTRunOptions? = nil ) throws { let sortedInputs = inputs.keys.sorted() let sortedOutputs = outputs.keys.sorted() let inputValues = sortedInputs.map { Optional(inputs[$0]!.handle) } var outputValues = sortedOutputs.map { Optional(outputs[$0]!.handle) } let outputCount = outputValues.count try ORTNative.withCStringArray(sortedInputs) { inputNames in try ORTNative.withCStringArray(sortedOutputs) { outputNames in try inputNames.withUnsafeBufferPointer { inputNameBuffer in try outputNames.withUnsafeBufferPointer { outputNameBuffer in try inputValues.withUnsafeBufferPointer { inputValueBuffer in try outputValues.withUnsafeMutableBufferPointer { outputValueBuffer in try ORTNative.check( ORTNative.api.pointee.Run?( handle, runOptions?.handle, inputNameBuffer.baseAddress, inputValueBuffer.baseAddress, inputValues.count, outputNameBuffer.baseAddress, outputCount, outputValueBuffer.baseAddress ), action: "Run" ) } } } } } } } public func run( inputs: [String: ORTValue], outputNames: Set, runOptions: ORTRunOptions? = nil ) throws -> [String: ORTValue] { let sortedInputs = inputs.keys.sorted() let sortedOutputs = outputNames.sorted() let inputValues = sortedInputs.map { Optional(inputs[$0]!.handle) } var outputValues = Array(repeating: nil, count: sortedOutputs.count) let outputCount = sortedOutputs.count try ORTNative.withCStringArray(sortedInputs) { inputNames in try ORTNative.withCStringArray(sortedOutputs) { outputNamesArray in try inputNames.withUnsafeBufferPointer { inputNameBuffer in try outputNamesArray.withUnsafeBufferPointer { outputNameBuffer in try inputValues.withUnsafeBufferPointer { inputValueBuffer in try outputValues.withUnsafeMutableBufferPointer { outputValueBuffer in try ORTNative.check( ORTNative.api.pointee.Run?( handle, runOptions?.handle, inputNameBuffer.baseAddress, inputValueBuffer.baseAddress, inputValues.count, outputNameBuffer.baseAddress, outputCount, outputValueBuffer.baseAddress ), action: "Run" ) } } } } } } var results: [String: ORTValue] = [:] for (index, name) in sortedOutputs.enumerated() { guard let valueHandle = outputValues[index] else { throw ORTError.runtimeFailure(action: "Run", message: "Output \(name) was not returned") } results[name] = ORTValue(adopting: valueHandle) } return results } public func inputNames() throws -> [String] { var count = 0 try ORTNative.check(ORTNative.api.pointee.SessionGetInputCount?(handle, &count), action: "SessionGetInputCount") return try ORTNative.withAllocator { allocator in var results: [String] = [] results.reserveCapacity(count) for index in 0..? try ORTNative.check( ORTNative.api.pointee.SessionGetInputName?(handle, index, allocator, &pointer), action: "SessionGetInputName" ) results.append(try ORTNative.copyAllocatedString(pointer, allocator: allocator)) } return results } } public func overridableInitializerNames() throws -> [String] { var count = 0 try ORTNative.check( ORTNative.api.pointee.SessionGetOverridableInitializerCount?(handle, &count), action: "SessionGetOverridableInitializerCount" ) return try ORTNative.withAllocator { allocator in var results: [String] = [] results.reserveCapacity(count) for index in 0..? try ORTNative.check( ORTNative.api.pointee.SessionGetOverridableInitializerName?(handle, index, allocator, &pointer), action: "SessionGetOverridableInitializerName" ) results.append(try ORTNative.copyAllocatedString(pointer, allocator: allocator)) } return results } } public func outputNames() throws -> [String] { var count = 0 try ORTNative.check(ORTNative.api.pointee.SessionGetOutputCount?(handle, &count), action: "SessionGetOutputCount") return try ORTNative.withAllocator { allocator in var results: [String] = [] results.reserveCapacity(count) for index in 0..? try ORTNative.check( ORTNative.api.pointee.SessionGetOutputName?(handle, index, allocator, &pointer), action: "SessionGetOutputName" ) results.append(try ORTNative.copyAllocatedString(pointer, allocator: allocator)) } return results } } } public final class ORTSessionOptions { public private(set) var executionProviders: [(name: String, options: [String: String])] = [] public private(set) var intraOpNumThreads: Int32 = 0 public private(set) var graphOptimizationLevel: ORTGraphOptimizationLevel = .all public private(set) var optimizedModelFilePath: String? public private(set) var logID: String? public private(set) var logSeverityLevel: ORTLoggingLevel? public private(set) var configEntries: [String: String] = [:] public private(set) var customOpRegistrationFunctionName: String? public private(set) var customOpRegistrationFunctionPointer: ORTCAPIRegisterCustomOpsFnPtr? public private(set) var ortExtensionsCustomOpsEnabled = false public private(set) var coreMLExecutionProviderOptions: ORTCoreMLExecutionProviderOptions? public private(set) var coreMLExecutionProviderV2Options: [String: String]? public private(set) var xnnpackExecutionProviderOptions: ORTXnnpackExecutionProviderOptions? internal let handle: OpaquePointer public init() throws { var options: OpaquePointer? try ORTNative.check(ORTNative.api.pointee.CreateSessionOptions?(&options), action: "CreateSessionOptions") guard let options else { throw ORTError.runtimeFailure(action: "CreateSessionOptions", message: "Session options were not returned") } handle = options } deinit { ORTNative.api.pointee.ReleaseSessionOptions?(handle) } public func appendExecutionProvider( _ providerName: String, providerOptions: [String: String] ) throws { let sorted = providerOptions.keys.sorted().map { ($0, providerOptions[$0]!) } let keyStorage = sorted.map { Array($0.0.utf8CString) } let valueStorage = sorted.map { Array($0.1.utf8CString) } let keys = keyStorage.map { $0.withUnsafeBufferPointer { $0.baseAddress } } let values = valueStorage.map { $0.withUnsafeBufferPointer { $0.baseAddress } } try providerName.withCString { providerNamePointer in try keys.withUnsafeBufferPointer { keyBuffer in try values.withUnsafeBufferPointer { valueBuffer in try ORTNative.check( ORTNative.api.pointee.SessionOptionsAppendExecutionProvider?( handle, providerNamePointer, keyBuffer.baseAddress, valueBuffer.baseAddress, sorted.count ), action: "SessionOptionsAppendExecutionProvider" ) } } } executionProviders.append((providerName, providerOptions)) } public func setIntraOpNumThreads(_ count: Int32) throws { try ORTNative.check( ORTNative.api.pointee.SetIntraOpNumThreads?(handle, Int32(count)), action: "SetIntraOpNumThreads" ) intraOpNumThreads = count } public func setGraphOptimizationLevel(_ level: ORTGraphOptimizationLevel) throws { try ORTNative.check( ORTNative.api.pointee.SetSessionGraphOptimizationLevel?(handle, level.native), action: "SetSessionGraphOptimizationLevel" ) graphOptimizationLevel = level } public func setOptimizedModelFilePath(_ path: String) throws { try path.withCString { pointer in try ORTNative.check( ORTNative.api.pointee.SetOptimizedModelFilePath?(handle, pointer), action: "SetOptimizedModelFilePath" ) } optimizedModelFilePath = path } public func setLogID(_ logID: String) throws { try logID.withCString { pointer in try ORTNative.check( ORTNative.api.pointee.SetSessionLogId?(handle, pointer), action: "SetSessionLogId" ) } self.logID = logID } public func setLogSeverityLevel(_ level: ORTLoggingLevel) throws { try ORTNative.check( ORTNative.api.pointee.SetSessionLogSeverityLevel?(handle, level.rawValue), action: "SetSessionLogSeverityLevel" ) logSeverityLevel = level } public func addConfigEntry(key: String, value: String) throws { try key.withCString { keyPointer in try value.withCString { valuePointer in try ORTNative.check( ORTNative.api.pointee.AddSessionConfigEntry?(handle, keyPointer, valuePointer), action: "AddSessionConfigEntry" ) } } configEntries[key] = value } public func registerCustomOps(usingFunction name: String) throws { try name.withCString { pointer in try ORTNative.check( ORTNative.api.pointee.RegisterCustomOpsUsingFunction?(handle, pointer), action: "RegisterCustomOpsUsingFunction" ) } customOpRegistrationFunctionName = name } public func registerCustomOps(usingFunctionPointer fn: ORTCAPIRegisterCustomOpsFnPtr) throws { let nativeFunction = unsafeBitCast(fn, to: RegisterCustomOpsFn.self) try ORTNative.check( nativeFunction(handle, ORTNative.apiBase), action: "RegisterCustomOpsUsingFunctionPointer" ) customOpRegistrationFunctionPointer = fn } public func enableOrtExtensionsCustomOps() throws { try ORTNative.check( ORTNative.api.pointee.EnableOrtCustomOps?(handle), action: "EnableOrtCustomOps" ) ortExtensionsCustomOpsEnabled = true } public func appendCoreMLExecutionProvider(options: ORTCoreMLExecutionProviderOptions) throws { try appendCoreMLExecutionProviderV2(providerOptions: options.providerOptions) coreMLExecutionProviderOptions = options } public func appendCoreMLExecutionProviderV2(providerOptions: [String: String]) throws { try appendExecutionProvider("CoreML", providerOptions: providerOptions) coreMLExecutionProviderV2Options = providerOptions } public func appendXnnpackExecutionProvider(options: ORTXnnpackExecutionProviderOptions) throws { try appendExecutionProvider( "XNNPACK", providerOptions: ["intra_op_num_threads": String(options.intraOpNumThreads)] ) xnnpackExecutionProviderOptions = options } } public final class ORTRunOptions { public private(set) var logTag: String? public private(set) var logSeverityLevel: ORTLoggingLevel? public private(set) var configEntries: [String: String] = [:] internal let handle: OpaquePointer public init() throws { var options: OpaquePointer? try ORTNative.check(ORTNative.api.pointee.CreateRunOptions?(&options), action: "CreateRunOptions") guard let options else { throw ORTError.runtimeFailure(action: "CreateRunOptions", message: "Run options were not returned") } handle = options } deinit { ORTNative.api.pointee.ReleaseRunOptions?(handle) } public func setLogTag(_ tag: String) throws { try tag.withCString { pointer in try ORTNative.check( ORTNative.api.pointee.RunOptionsSetRunTag?(handle, pointer), action: "RunOptionsSetRunTag" ) } logTag = tag } public func setLogSeverityLevel(_ level: ORTLoggingLevel) throws { try ORTNative.check( ORTNative.api.pointee.RunOptionsSetRunLogSeverityLevel?(handle, level.rawValue), action: "RunOptionsSetRunLogSeverityLevel" ) logSeverityLevel = level } public func addConfigEntry(key: String, value: String) throws { try key.withCString { keyPointer in try value.withCString { valuePointer in try ORTNative.check( ORTNative.api.pointee.AddRunConfigEntry?(handle, keyPointer, valuePointer), action: "AddRunConfigEntry" ) } } configEntries[key] = value } } public final class ORTValue { private let storage: ORTValueStorage internal var handle: OpaquePointer { storage.handle } public init( tensorData: NSMutableData, elementType: ORTTensorElementDataType, shape: [Int] ) throws { let elementCount = try ORTNative.tensorElementCount(shape: shape) let expectedLength = try ORTNative.elementByteCount(for: elementType) * elementCount guard tensorData.length >= expectedLength else { throw ORTError.invalidArgument("Tensor buffer is too small for shape \(shape) and element type \(elementType)") } var memoryInfo: OpaquePointer? try ORTNative.check( ORTNative.api.pointee.CreateCpuMemoryInfo?(OrtArenaAllocator, OrtMemTypeDefault, &memoryInfo), action: "CreateCpuMemoryInfo" ) guard let memoryInfo else { throw ORTError.runtimeFailure(action: "CreateCpuMemoryInfo", message: "Memory info was not returned") } defer { ORTNative.api.pointee.ReleaseMemoryInfo?(memoryInfo) } var value: OpaquePointer? var nativeShape = ORTNative.makeShapeBuffer(shape) let nativeShapeCount = nativeShape.count try nativeShape.withUnsafeMutableBufferPointer { shapeBuffer in try ORTNative.check( ORTNative.api.pointee.CreateTensorWithDataAsOrtValue?( memoryInfo, tensorData.mutableBytes, tensorData.length, shapeBuffer.baseAddress, nativeShapeCount, elementType.native, &value ), action: "CreateTensorWithDataAsOrtValue" ) } guard let value else { throw ORTError.runtimeFailure(action: "CreateTensorWithDataAsOrtValue", message: "Value was not returned") } storage = ORTValueStorage(handle: value, retainedBuffer: tensorData) } public init( tensorStringData: [String], shape: [Int] ) throws { let elementCount = try ORTNative.tensorElementCount(shape: shape) guard elementCount == tensorStringData.count else { throw ORTError.invalidArgument("String tensor element count does not match shape") } var value: OpaquePointer? var nativeShape = ORTNative.makeShapeBuffer(shape) let nativeShapeCount = nativeShape.count try ORTNative.withAllocator { allocator in try nativeShape.withUnsafeMutableBufferPointer { shapeBuffer in try ORTNative.check( ORTNative.api.pointee.CreateTensorAsOrtValue?( allocator, shapeBuffer.baseAddress, nativeShapeCount, ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING, &value ), action: "CreateTensorAsOrtValue" ) } } guard let value else { throw ORTError.runtimeFailure(action: "CreateTensorAsOrtValue", message: "String tensor value was not returned") } let stringStorage = tensorStringData.map { Array($0.utf8CString) } let stringPointers = stringStorage.map { $0.withUnsafeBufferPointer { $0.baseAddress } } try stringPointers.withUnsafeBufferPointer { pointerBuffer in try ORTNative.check( ORTNative.api.pointee.FillStringTensor?(value, pointerBuffer.baseAddress, stringPointers.count), action: "FillStringTensor" ) } storage = ORTValueStorage(handle: value) } internal init(adopting handle: OpaquePointer) { storage = ORTValueStorage(handle: handle) } public func typeInfo() throws -> ORTValueTypeInfo { try ORTNative.fetchValueTypeInfo(from: handle) } public func tensorTypeAndShapeInfo() throws -> ORTTensorTypeAndShapeInfo { try ORTNative.fetchTensorInfo(from: handle) } public func tensorData() throws -> NSMutableData { let tensorInfo = try tensorTypeAndShapeInfo() guard tensorInfo.elementType != .string else { throw ORTError.unsupported("tensorData() is not available for string tensors") } var dataPointer: UnsafeMutableRawPointer? try ORTNative.check( ORTNative.api.pointee.GetTensorMutableData?(handle, &dataPointer), action: "GetTensorMutableData" ) guard let dataPointer else { throw ORTError.runtimeFailure(action: "GetTensorMutableData", message: "Tensor data pointer was not returned") } let elementCount = try ORTNative.tensorElementCount(shape: tensorInfo.shape) let length = try ORTNative.elementByteCount(for: tensorInfo.elementType) * elementCount return NSMutableData(bytesNoCopy: dataPointer, length: length, freeWhenDone: false) } public func tensorStringData() throws -> [String] { let tensorInfo = try tensorTypeAndShapeInfo() guard tensorInfo.elementType == .string else { throw ORTError.unsupported("tensorStringData() is only available for string tensors") } let elementCount = try ORTNative.tensorElementCount(shape: tensorInfo.shape) var totalLength = 0 try ORTNative.check( ORTNative.api.pointee.GetStringTensorDataLength?(handle, &totalLength), action: "GetStringTensorDataLength" ) var content = Array(repeating: UInt8(0), count: totalLength) var offsets = Array(repeating: Int(0), count: elementCount) try ORTNative.check( ORTNative.api.pointee.GetStringTensorContent?( handle, &content, totalLength, &offsets, elementCount ), action: "GetStringTensorContent" ) return (0.. Int64 { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } return try ORTNative.withAllocator { allocator in var propertyType = OrtPropertyType(rawValue: 0) var propertyValue: UnsafeMutableRawPointer? try name.withCString { pointer in try ORTNative.check( trainingApi.pointee.GetProperty?(handle, pointer, allocator, &propertyType, &propertyValue), action: "GetProperty" ) } defer { if let propertyValue { _ = ORTNative.api.pointee.AllocatorFree?(allocator, propertyValue) } } guard propertyType == OrtIntProperty, let propertyValue else { throw ORTError.invalidArgument("Checkpoint property \(name) is not an Int64") } return propertyValue.assumingMemoryBound(to: Int64.self).pointee } } public func floatProperty(name: String) throws -> Float { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } return try ORTNative.withAllocator { allocator in var propertyType = OrtPropertyType(rawValue: 0) var propertyValue: UnsafeMutableRawPointer? try name.withCString { pointer in try ORTNative.check( trainingApi.pointee.GetProperty?(handle, pointer, allocator, &propertyType, &propertyValue), action: "GetProperty" ) } defer { if let propertyValue { _ = ORTNative.api.pointee.AllocatorFree?(allocator, propertyValue) } } guard propertyType == OrtFloatProperty, let propertyValue else { throw ORTError.invalidArgument("Checkpoint property \(name) is not a Float") } return propertyValue.assumingMemoryBound(to: Float.self).pointee } } public func stringProperty(name: String) throws -> String? { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } return try ORTNative.withAllocator { allocator in var propertyType = OrtPropertyType(rawValue: 0) var propertyValue: UnsafeMutableRawPointer? try name.withCString { pointer in try ORTNative.check( trainingApi.pointee.GetProperty?(handle, pointer, allocator, &propertyType, &propertyValue), action: "GetProperty" ) } guard propertyType == OrtStringProperty else { if let propertyValue { _ = ORTNative.api.pointee.AllocatorFree?(allocator, propertyValue) } throw ORTError.invalidArgument("Checkpoint property \(name) is not a String") } let string = propertyValue.map { String(cString: $0.assumingMemoryBound(to: CChar.self)) } if let propertyValue { _ = ORTNative.api.pointee.AllocatorFree?(allocator, propertyValue) } return string } } } public final class ORTTrainingSession { private let handle: OpaquePointer private let checkpoint: ORTCheckpoint public init( env: ORTEnv, sessionOptions: ORTSessionOptions? = nil, checkpoint: ORTCheckpoint, trainModelPath: String, evalModelPath: String? = nil, optimizerModelPath: String? = nil ) throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } self.checkpoint = checkpoint let createdOptions = sessionOptions == nil ? try ORTSessionOptions() : nil let optionsHandle = sessionOptions?.handle ?? createdOptions!.handle var trainingSession: OpaquePointer? try trainModelPath.withCString { trainPointer in try evalModelPath?.withCString { evalPointer in try optimizerModelPath?.withCString { optimizerPointer in try ORTNative.check( trainingApi.pointee.CreateTrainingSession?( env.handle, optionsHandle, checkpoint.handle, trainPointer, evalPointer, optimizerPointer, &trainingSession ), action: "CreateTrainingSession" ) } } ?? { try optimizerModelPath?.withCString { optimizerPointer in try ORTNative.check( trainingApi.pointee.CreateTrainingSession?( env.handle, optionsHandle, checkpoint.handle, trainPointer, nil, optimizerPointer, &trainingSession ), action: "CreateTrainingSession" ) } ?? { try ORTNative.check( trainingApi.pointee.CreateTrainingSession?( env.handle, optionsHandle, checkpoint.handle, trainPointer, nil, nil, &trainingSession ), action: "CreateTrainingSession" ) }() }() } guard let trainingSession else { throw ORTError.runtimeFailure(action: "CreateTrainingSession", message: "Training session was not returned") } handle = trainingSession } deinit { ORTNative.trainingApi?.pointee.ReleaseTrainingSession?(handle) } public func trainStep(inputValues: [ORTValue]) throws -> [ORTValue] { try runStep( countAction: "TrainingSessionGetTrainingModelOutputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetTrainingModelOutputCount?(handle, out) }, stepAction: "TrainStep", stepCall: { outputCount, outputBuffer in let inputs = inputValues.map(\.handle) let inputPointers = inputs.map(Optional.init) return try inputPointers.withUnsafeBufferPointer { inputBuffer in try ORTNative.check( ORTNative.trainingApi!.pointee.TrainStep?( handle, nil, inputs.count, inputBuffer.baseAddress, outputCount, outputBuffer ), action: "TrainStep" ) } } ) } public func evalStep(inputValues: [ORTValue]) throws -> [ORTValue] { try runStep( countAction: "TrainingSessionGetEvalModelOutputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetEvalModelOutputCount?(handle, out) }, stepAction: "EvalStep", stepCall: { outputCount, outputBuffer in let inputs = inputValues.map(\.handle) let inputPointers = inputs.map(Optional.init) return try inputPointers.withUnsafeBufferPointer { inputBuffer in try ORTNative.check( ORTNative.trainingApi!.pointee.EvalStep?( handle, nil, inputs.count, inputBuffer.baseAddress, outputCount, outputBuffer ), action: "EvalStep" ) } } ) } public func lazyResetGrad() throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check(trainingApi.pointee.LazyResetGrad?(handle), action: "LazyResetGrad") } public func optimizerStep() throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check(trainingApi.pointee.OptimizerStep?(handle, nil), action: "OptimizerStep") } public func trainInputNames() throws -> [String] { try trainingNames( countAction: "TrainingSessionGetTrainingModelInputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetTrainingModelInputCount?(handle, out) }, nameAction: "TrainingSessionGetTrainingModelInputName", nameCall: { index, allocator, out in ORTNative.trainingApi!.pointee.TrainingSessionGetTrainingModelInputName?(handle, index, allocator, out) } ) } public func evalInputNames() throws -> [String] { try trainingNames( countAction: "TrainingSessionGetEvalModelInputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetEvalModelInputCount?(handle, out) }, nameAction: "TrainingSessionGetEvalModelInputName", nameCall: { index, allocator, out in ORTNative.trainingApi!.pointee.TrainingSessionGetEvalModelInputName?(handle, index, allocator, out) } ) } public func trainOutputNames() throws -> [String] { try trainingNames( countAction: "TrainingSessionGetTrainingModelOutputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetTrainingModelOutputCount?(handle, out) }, nameAction: "TrainingSessionGetTrainingModelOutputName", nameCall: { index, allocator, out in ORTNative.trainingApi!.pointee.TrainingSessionGetTrainingModelOutputName?(handle, index, allocator, out) } ) } public func evalOutputNames() throws -> [String] { try trainingNames( countAction: "TrainingSessionGetEvalModelOutputCount", countCall: { out in ORTNative.trainingApi!.pointee.TrainingSessionGetEvalModelOutputCount?(handle, out) }, nameAction: "TrainingSessionGetEvalModelOutputName", nameCall: { index, allocator, out in ORTNative.trainingApi!.pointee.TrainingSessionGetEvalModelOutputName?(handle, index, allocator, out) } ) } public func registerLinearLRScheduler( warmupStepCount: Int64, totalStepCount: Int64, initialLr: Float ) throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check( trainingApi.pointee.RegisterLinearLRScheduler?(handle, warmupStepCount, totalStepCount, initialLr), action: "RegisterLinearLRScheduler" ) } public func schedulerStep() throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check(trainingApi.pointee.SchedulerStep?(handle), action: "SchedulerStep") } public func learningRate() throws -> Float { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } var learningRate: Float = 0 try ORTNative.check(trainingApi.pointee.GetLearningRate?(handle, &learningRate), action: "GetLearningRate") return learningRate } public func setLearningRate(_ lr: Float) throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check(trainingApi.pointee.SetLearningRate?(handle, lr), action: "SetLearningRate") } public func fromBuffer(_ buffer: ORTValue) throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try ORTNative.check(trainingApi.pointee.CopyBufferToParameters?(handle, buffer.handle, false), action: "CopyBufferToParameters") } public func toBuffer(onlyTrainable: Bool) throws -> ORTValue { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } var parameterCount = 0 try ORTNative.check( trainingApi.pointee.GetParametersSize?(handle, ¶meterCount, onlyTrainable), action: "GetParametersSize" ) let byteCount = parameterCount * MemoryLayout.stride let buffer = NSMutableData(length: byteCount) ?? NSMutableData() let value = try ORTValue(tensorData: buffer, elementType: .float, shape: [parameterCount]) try ORTNative.check( trainingApi.pointee.CopyParametersToBuffer?(handle, value.handle, onlyTrainable), action: "CopyParametersToBuffer" ) return value } public func exportModelForInference( outputPath: String, graphOutputNames: [String] ) throws { guard let trainingApi = ORTNative.trainingApi else { throw ORTError.unsupported("This ONNX Runtime build does not expose the training API") } try outputPath.withCString { pathPointer in try ORTNative.withCStringArray(graphOutputNames) { namePointers in try namePointers.withUnsafeBufferPointer { nameBuffer in try ORTNative.check( trainingApi.pointee.ExportModelForInferencing?( handle, pathPointer, graphOutputNames.count, nameBuffer.baseAddress ), action: "ExportModelForInferencing" ) } } } } private func trainingNames( countAction: String, countCall: (_ out: UnsafeMutablePointer) -> OpaquePointer?, nameAction: String, nameCall: ( _ index: Int, _ allocator: UnsafeMutablePointer, _ out: UnsafeMutablePointer?> ) -> OpaquePointer? ) throws -> [String] { var count = 0 try ORTNative.check(countCall(&count), action: countAction) return try ORTNative.withAllocator { allocator in var names: [String] = [] names.reserveCapacity(count) for index in 0..? try ORTNative.check(nameCall(index, allocator, &namePointer), action: nameAction) names.append(try ORTNative.copyAllocatedString(namePointer, allocator: allocator)) } return names } } private func runStep( countAction: String, countCall: (_ out: UnsafeMutablePointer) -> OpaquePointer?, stepAction: String, stepCall: (_ outputCount: Int, _ outputBuffer: UnsafeMutablePointer?) throws -> Void ) throws -> [ORTValue] { var outputCount = 0 try ORTNative.check(countCall(&outputCount), action: countAction) var outputs = Array(repeating: nil, count: outputCount) try outputs.withUnsafeMutableBufferPointer { buffer in try stepCall(outputCount, buffer.baseAddress) } return try outputs.enumerated().map { index, handle in guard let handle else { throw ORTError.runtimeFailure(action: stepAction, message: "Output \(index) was not returned") } return ORTValue(adopting: handle) } } }