Replace Objective-C bridge with Swift ONNX runtime backend
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
module COnnxRuntime [system] {
|
||||
header "shim.h"
|
||||
link "onnxruntime"
|
||||
export *
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#include <onnxruntime/onnxruntime_c_api.h>
|
||||
#include <onnxruntime/onnxruntime_training_c_api.h>
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public enum ORTError: Error, LocalizedError {
|
||||
case notImplemented(String)
|
||||
case runtimeFailure(action: String, message: String)
|
||||
case invalidArgument(String)
|
||||
case unsupported(String)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .notImplemented(let symbol):
|
||||
return "ONNX Runtime Swift interface symbol is not implemented yet: \(symbol)"
|
||||
case .runtimeFailure(let action, let message):
|
||||
return "\(action) failed: \(message)"
|
||||
case .invalidArgument(let message):
|
||||
return message
|
||||
case .unsupported(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,166 @@
|
||||
@preconcurrency import COnnxRuntime
|
||||
import Foundation
|
||||
|
||||
internal enum ORTNative {
|
||||
nonisolated(unsafe) static let apiBase = OrtGetApiBase()
|
||||
nonisolated(unsafe) static let api = apiBase!.pointee.GetApi(UInt32(ORT_API_VERSION))!
|
||||
nonisolated(unsafe) static let trainingApi = api.pointee.GetTrainingApi?(UInt32(ORT_API_VERSION))
|
||||
|
||||
static func check(_ status: OpaquePointer?, action: String) throws {
|
||||
guard let status else {
|
||||
return
|
||||
}
|
||||
|
||||
let message = api.pointee.GetErrorMessage?(status).map { String(cString: $0) } ?? "Unknown ONNX Runtime error"
|
||||
api.pointee.ReleaseStatus?(status)
|
||||
throw ORTError.runtimeFailure(action: action, message: message)
|
||||
}
|
||||
|
||||
static func withAllocator<Result>(_ body: (UnsafeMutablePointer<OrtAllocator>) throws -> Result) throws -> Result {
|
||||
var allocator: UnsafeMutablePointer<OrtAllocator>?
|
||||
try check(api.pointee.GetAllocatorWithDefaultOptions?(&allocator), action: "GetAllocatorWithDefaultOptions")
|
||||
guard let allocator else {
|
||||
throw ORTError.runtimeFailure(action: "GetAllocatorWithDefaultOptions", message: "Allocator was not returned")
|
||||
}
|
||||
return try body(allocator)
|
||||
}
|
||||
|
||||
static func copyAllocatedString(
|
||||
_ stringPointer: UnsafeMutablePointer<CChar>?,
|
||||
allocator: UnsafeMutablePointer<OrtAllocator>
|
||||
) throws -> String {
|
||||
guard let stringPointer else {
|
||||
throw ORTError.runtimeFailure(action: "copyAllocatedString", message: "Expected allocated string")
|
||||
}
|
||||
|
||||
defer {
|
||||
_ = api.pointee.AllocatorFree?(allocator, stringPointer)
|
||||
}
|
||||
|
||||
return String(cString: stringPointer)
|
||||
}
|
||||
|
||||
static func tensorElementCount(shape: [Int]) throws -> Int {
|
||||
guard shape.allSatisfy({ $0 >= 0 }) else {
|
||||
throw ORTError.invalidArgument("Tensor shape contains a negative dimension: \(shape)")
|
||||
}
|
||||
|
||||
return shape.reduce(1, *)
|
||||
}
|
||||
|
||||
static func elementByteCount(for type: ORTTensorElementDataType) throws -> Int {
|
||||
switch type {
|
||||
case .float:
|
||||
return MemoryLayout<Float>.stride
|
||||
case .int8:
|
||||
return MemoryLayout<Int8>.stride
|
||||
case .uInt8:
|
||||
return MemoryLayout<UInt8>.stride
|
||||
case .int32:
|
||||
return MemoryLayout<Int32>.stride
|
||||
case .uInt32:
|
||||
return MemoryLayout<UInt32>.stride
|
||||
case .int64:
|
||||
return MemoryLayout<Int64>.stride
|
||||
case .uInt64:
|
||||
return MemoryLayout<UInt64>.stride
|
||||
case .string:
|
||||
throw ORTError.invalidArgument("String tensors do not have a fixed byte width")
|
||||
case .undefined:
|
||||
throw ORTError.invalidArgument("Undefined tensor element type is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
static func makeShapeBuffer(_ shape: [Int]) -> [Int64] {
|
||||
shape.map(Int64.init)
|
||||
}
|
||||
|
||||
static func withCStringArray<Result>(
|
||||
_ strings: [String],
|
||||
_ body: ([UnsafePointer<CChar>?]) throws -> Result
|
||||
) rethrows -> Result {
|
||||
let storage = strings.map { Array($0.utf8CString) }
|
||||
let pointers = storage.map { $0.withUnsafeBufferPointer { $0.baseAddress } }
|
||||
return try body(pointers)
|
||||
}
|
||||
|
||||
static func fetchTensorInfo(from value: OpaquePointer) throws -> ORTTensorTypeAndShapeInfo {
|
||||
var info: OpaquePointer?
|
||||
try check(api.pointee.GetTensorTypeAndShape?(value, &info), action: "GetTensorTypeAndShape")
|
||||
guard let info else {
|
||||
throw ORTError.runtimeFailure(action: "GetTensorTypeAndShape", message: "Tensor info was not returned")
|
||||
}
|
||||
|
||||
defer {
|
||||
api.pointee.ReleaseTensorTypeAndShapeInfo?(info)
|
||||
}
|
||||
|
||||
var elementType = ONNXTensorElementDataType(rawValue: 0)
|
||||
try check(api.pointee.GetTensorElementType?(info, &elementType), action: "GetTensorElementType")
|
||||
|
||||
var dimensionsCount: Int = 0
|
||||
try check(api.pointee.GetDimensionsCount?(info, &dimensionsCount), action: "GetDimensionsCount")
|
||||
|
||||
var dimensions = Array(repeating: Int64(0), count: dimensionsCount)
|
||||
if dimensionsCount > 0 {
|
||||
try check(
|
||||
api.pointee.GetDimensions?(info, &dimensions, dimensionsCount),
|
||||
action: "GetDimensions"
|
||||
)
|
||||
}
|
||||
|
||||
return ORTTensorTypeAndShapeInfo(
|
||||
elementType: try ORTTensorElementDataType(native: elementType),
|
||||
shape: dimensions.map(Int.init)
|
||||
)
|
||||
}
|
||||
|
||||
static func fetchValueTypeInfo(from value: OpaquePointer) throws -> ORTValueTypeInfo {
|
||||
var typeInfo: OpaquePointer?
|
||||
try check(api.pointee.GetTypeInfo?(value, &typeInfo), action: "GetTypeInfo")
|
||||
guard let typeInfo else {
|
||||
throw ORTError.runtimeFailure(action: "GetTypeInfo", message: "Type info was not returned")
|
||||
}
|
||||
|
||||
defer {
|
||||
api.pointee.ReleaseTypeInfo?(typeInfo)
|
||||
}
|
||||
|
||||
var onnxType = ONNXType(rawValue: 0)
|
||||
try check(api.pointee.GetOnnxTypeFromTypeInfo?(typeInfo, &onnxType), action: "GetOnnxTypeFromTypeInfo")
|
||||
|
||||
var tensorInfoPointer: OpaquePointer?
|
||||
try check(api.pointee.CastTypeInfoToTensorInfo?(typeInfo, &tensorInfoPointer), action: "CastTypeInfoToTensorInfo")
|
||||
|
||||
let tensorInfo = try tensorInfoPointer.map { pointer in
|
||||
try fetchTensorInfoView(from: pointer)
|
||||
}
|
||||
|
||||
return ORTValueTypeInfo(
|
||||
type: try ORTValueType(native: onnxType),
|
||||
tensorTypeAndShapeInfo: tensorInfo
|
||||
)
|
||||
}
|
||||
|
||||
static func fetchTensorInfoView(from info: OpaquePointer) throws -> ORTTensorTypeAndShapeInfo {
|
||||
var elementType = ONNXTensorElementDataType(rawValue: 0)
|
||||
try check(api.pointee.GetTensorElementType?(info, &elementType), action: "GetTensorElementType")
|
||||
|
||||
var dimensionsCount: Int = 0
|
||||
try check(api.pointee.GetDimensionsCount?(info, &dimensionsCount), action: "GetDimensionsCount")
|
||||
|
||||
var dimensions = Array(repeating: Int64(0), count: dimensionsCount)
|
||||
if dimensionsCount > 0 {
|
||||
try check(api.pointee.GetDimensions?(info, &dimensions, dimensionsCount), action: "GetDimensions")
|
||||
}
|
||||
|
||||
return ORTTensorTypeAndShapeInfo(
|
||||
elementType: try ORTTensorElementDataType(native: elementType),
|
||||
shape: dimensions.map(Int.init)
|
||||
)
|
||||
}
|
||||
|
||||
static func boolString(_ value: Bool) -> String {
|
||||
value ? "1" : "0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@preconcurrency import COnnxRuntime
|
||||
@testable import OnnxRuntimeBindings
|
||||
|
||||
@_cdecl("SwiftTestRegisterCustomOps")
|
||||
func SwiftTestRegisterCustomOps(_ options: OpaquePointer?, _ api: UnsafePointer<OrtApiBase>?) -> OpaquePointer? {
|
||||
_ = options
|
||||
_ = api
|
||||
return nil
|
||||
}
|
||||
|
||||
struct SwiftAPITests {
|
||||
@Test
|
||||
func sessionOptionsRetainConfiguredState() throws {
|
||||
let options = try ORTSessionOptions()
|
||||
|
||||
try options.setIntraOpNumThreads(4)
|
||||
try options.setGraphOptimizationLevel(.extended)
|
||||
try options.setOptimizedModelFilePath("/tmp/model.optimized.onnx")
|
||||
try options.setLogID("session-log")
|
||||
try options.setLogSeverityLevel(.warning)
|
||||
try options.addConfigEntry(key: "session.disable_prepacking", value: "1")
|
||||
|
||||
#expect(options.intraOpNumThreads == 4)
|
||||
#expect(options.graphOptimizationLevel == .extended)
|
||||
#expect(options.optimizedModelFilePath == "/tmp/model.optimized.onnx")
|
||||
#expect(options.logID == "session-log")
|
||||
#expect(options.logSeverityLevel == .warning)
|
||||
#expect(options.configEntries["session.disable_prepacking"] == "1")
|
||||
#expect(options.executionProviders.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func unsupportedExecutionProviderSurfacesRuntimeError() throws {
|
||||
let options = try ORTSessionOptions()
|
||||
|
||||
#expect(throws: ORTError.self) {
|
||||
try options.appendExecutionProvider("XNNPACK", providerOptions: ["threads": "2"])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func customOpsFunctionPointerRegistrationIsInvoked() throws {
|
||||
let options = try ORTSessionOptions()
|
||||
let functionPointer = unsafeBitCast(
|
||||
SwiftTestRegisterCustomOps as RegisterCustomOpsFn,
|
||||
to: UnsafeMutableRawPointer.self
|
||||
)
|
||||
|
||||
try options.registerCustomOps(usingFunctionPointer: functionPointer)
|
||||
|
||||
#expect(options.customOpRegistrationFunctionPointer == functionPointer)
|
||||
}
|
||||
|
||||
@Test
|
||||
func runOptionsRetainConfiguredState() throws {
|
||||
let options = try ORTRunOptions()
|
||||
|
||||
try options.setLogTag("inference")
|
||||
try options.setLogSeverityLevel(.error)
|
||||
try options.addConfigEntry(key: "terminate", value: "0")
|
||||
|
||||
#expect(options.logTag == "inference")
|
||||
#expect(options.logSeverityLevel == .error)
|
||||
#expect(options.configEntries["terminate"] == "0")
|
||||
}
|
||||
|
||||
@Test
|
||||
func numericTensorValueExposesTensorInfo() throws {
|
||||
let bytes = NSMutableData(data: Data([0, 0, 128, 63]))
|
||||
let value = try ORTValue(tensorData: bytes, elementType: .float, shape: [1])
|
||||
|
||||
let typeInfo = try value.typeInfo()
|
||||
let tensorInfo = try value.tensorTypeAndShapeInfo()
|
||||
let data = try value.tensorData()
|
||||
|
||||
#expect(typeInfo.type == .tensor)
|
||||
#expect(typeInfo.tensorTypeAndShapeInfo?.elementType == .float)
|
||||
#expect(tensorInfo.elementType == .float)
|
||||
#expect(tensorInfo.shape == [1])
|
||||
#expect(data.length == bytes.length)
|
||||
#expect(Data(referencing: data) == Data(referencing: bytes))
|
||||
}
|
||||
|
||||
@Test
|
||||
func stringTensorValueExposesTensorInfo() throws {
|
||||
let value = try ORTValue(tensorStringData: ["hello", "world"], shape: [2])
|
||||
|
||||
let typeInfo = try value.typeInfo()
|
||||
let tensorInfo = try value.tensorTypeAndShapeInfo()
|
||||
let strings = try value.tensorStringData()
|
||||
|
||||
#expect(typeInfo.type == .tensor)
|
||||
#expect(typeInfo.tensorTypeAndShapeInfo?.elementType == .string)
|
||||
#expect(tensorInfo.elementType == .string)
|
||||
#expect(tensorInfo.shape == [2])
|
||||
#expect(strings == ["hello", "world"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func wrongTensorAccessorThrows() throws {
|
||||
let numeric = try ORTValue(tensorData: NSMutableData(data: Data([0, 0, 0, 0])), elementType: .float, shape: [])
|
||||
let string = try ORTValue(tensorStringData: [], shape: [0])
|
||||
|
||||
#expect(throws: ORTError.self) {
|
||||
_ = try numeric.tensorStringData()
|
||||
}
|
||||
#expect(throws: ORTError.self) {
|
||||
_ = try string.tensorData()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user