diff --git a/.gitignore b/.gitignore index 8a30d25..e4772f9 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,8 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +.DS_Store +*.DS_Store + +.build/ \ No newline at end of file diff --git a/.pipelines/mac-ios-spm-dev-validation-pipeline.yml b/.pipelines/mac-ios-spm-dev-validation-pipeline.yml new file mode 100644 index 0000000..8a801cd --- /dev/null +++ b/.pipelines/mac-ios-spm-dev-validation-pipeline.yml @@ -0,0 +1,72 @@ + jobs: + - job: j + displayName: "Test with latest local ORT native pod" + + pool: + vmImage: "macOS-13" + + variables: + xcodeVersion: "14.3" + artifactsName: "ios_packaging_artifacts_full" + + timeoutInMinutes: 60 + + steps: + - template: templates/use-xcode-version.yml + parameters: + xcodeVersion: ${{ variables.xcodeVersion }} + + - script: | + mkdir tmp + cd tmp + git clone -n --depth=1 --filter=tree:0 https://github.com/microsoft/onnxruntime.git + cd onnxruntime + git sparse-checkout set --no-cone objectivec swift Package.swift + git checkout + workingDirectory: "$(Build.SourcesDirectory)" + displayName: "Sparse checkout objectivec/ swift/ folders from latest ORT main repository" + + - script: | + ls -R "$(Build.SourcesDirectory)/tmp/onnxruntime" + workingDirectory: "$(Build.SourcesDirectory)/tmp" + displayName: "List sparse checkout repo contents" + + # Download artifacts from a specific pipeline + # For now, it consumes rel-1.15.0 version ORT iOS Pod which matches the current source code + # TODO: Update branch to latest main of ORT github repo when syncing changes to ORT SPM repo here. + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'specific' + project: 'Lotus' + definition: 995 #'definitionid' is obtained from `System.DefinitionId` of ORT CI: onnxruntime-ios-packaging-pipeline + buildVersionToDownload: 'latest' + branchName: 'main' + targetPath: '$(Build.ArtifactStagingDirectory)' + + - script: | + set -e -x + ls + workingDirectory: '$(Build.ArtifactStagingDirectory)/$(artifactsName)' + displayName: "List staged artifacts" + + # copy the pod archive to a path relative to Package.swift and set the env var required by Package.swift to use that. + # xcodebuild will implicitly use Package.swift and build/run the .testTarget (tests in swift/onnxTests). + # once that's done cleanup the copy of the pod zip file + - script: | + set -e -x + cd "$(Build.ArtifactStagingDirectory)/$(artifactsName)" + POD_ARCHIVE=$(find . -name "pod-archive-onnxruntime-c*.zip") + + shasum -a 256 "$(Build.ArtifactStagingDirectory)/$(artifactsName)/${POD_ARCHIVE}" + + cd "$(Build.SourcesDirectory)/tmp/onnxruntime" + cp "$(Build.ArtifactStagingDirectory)/$(artifactsName)/${POD_ARCHIVE}" swift/ + export ORT_IOS_POD_LOCAL_PATH="swift/${POD_ARCHIVE}" + xcodebuild test -scheme onnxruntime -destination 'platform=iOS Simulator,name=iPhone 14' + rm swift/pod-archive-onnxruntime-c-*.zip + workingDirectory: "$(Build.SourcesDirectory)/tmp" + displayName: "Print ORT iOS Pod checksum and Test Package.swift usage" + + - template: templates/component-governance-component-detection-steps.yml + parameters : + condition : 'succeeded' \ No newline at end of file diff --git a/.pipelines/mac-ios-spm-release-validation-pipeline.yml b/.pipelines/mac-ios-spm-release-validation-pipeline.yml new file mode 100644 index 0000000..f62f79c --- /dev/null +++ b/.pipelines/mac-ios-spm-release-validation-pipeline.yml @@ -0,0 +1,26 @@ + jobs: + - job: j + displayName: "Test with released ORT native pod" + + pool: + vmImage: "macOS-13" + + variables: + xcodeVersion: "14.3" + + timeoutInMinutes: 60 + + steps: + - template: templates/use-xcode-version.yml + parameters: + xcodeVersion: ${{ variables.xcodeVersion }} + + - script: | + set -e -x + xcodebuild test -scheme onnxruntime -destination 'platform=iOS Simulator,name=iPhone 14' + workingDirectory: "$(Build.SourcesDirectory)" + displayName: "Test Package.swift usage" + + - template: templates/component-governance-component-detection-steps.yml + parameters : + condition : 'succeeded' \ No newline at end of file diff --git a/.pipelines/templates/component-governance-component-detection-steps.yml b/.pipelines/templates/component-governance-component-detection-steps.yml new file mode 100644 index 0000000..fb503bc --- /dev/null +++ b/.pipelines/templates/component-governance-component-detection-steps.yml @@ -0,0 +1,14 @@ +# component detection for component governance checks +parameters: +- name: condition + type: string + default: 'succeeded' # could be 'ci_only', 'always', 'succeeded' + +steps: +- ${{ if eq(variables['System.TeamProject'], 'Lotus') }}: + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + condition: + or(or(and(eq('${{parameters.condition}}', 'ci_only'), and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Scheduled'))), + and(eq('${{parameters.condition}}', 'always'), always())), + and(eq('${{parameters.condition}}', 'succeeded'), succeeded())) diff --git a/.pipelines/templates/use-xcode-version.yml b/.pipelines/templates/use-xcode-version.yml new file mode 100644 index 0000000..c267ecd --- /dev/null +++ b/.pipelines/templates/use-xcode-version.yml @@ -0,0 +1,14 @@ +# Specify use of a specific Xcode version. + +parameters: +- name: xcodeVersion + type: string + default: "14.3" + +steps: +- bash: | + set -e -x + XCODE_DEVELOPER_DIR="/Applications/Xcode_${{ parameters.xcodeVersion }}.app/Contents/Developer" + sudo xcode-select --switch "${XCODE_DEVELOPER_DIR}" + + displayName: Use Xcode ${{ parameters.xcodeVersion }} \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..6d09521 --- /dev/null +++ b/Package.swift @@ -0,0 +1,86 @@ +// swift-tools-version: 5.6 +// The swift-tools-version declares the minimum version of Swift required to build this package and MUST be the first +// line of this file. 5.6 is required to support zip files for the pod archive binaryTarget. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// A user of the Swift Package Manager (SPM) package will consume this file directly from the ORT github repository. +// For context, the end user's config will look something like: +// +// dependencies: [ +// #TODO: update to use release 'version' and the new repo url here when available +// .package(url: "https://github.com/microsoft/onnxruntime", branch: "rel-1.15.0"), +// ... +// ], +// + +import PackageDescription +import class Foundation.ProcessInfo + +let package = Package( + name: "onnxruntime", + platforms: [.iOS(.v11)], + products: [ + .library(name: "onnxruntime", + type: .static, + targets: ["OnnxRuntimeBindings"]), + ], + dependencies: [], + targets: [ + .target(name: "OnnxRuntimeBindings", + dependencies: ["onnxruntime"], + path: "objectivec", + exclude: ["ReadMe.md", "format_objc.sh"], + cxxSettings: [ + .define("SPM_BUILD"), + .unsafeFlags(["-std=c++17", + "-fobjc-arc-exceptions" + ]), + ], linkerSettings: [ + .unsafeFlags(["-ObjC"]), + ]), + .testTarget(name: "OnnxRuntimeBindingsTests", + dependencies: ["OnnxRuntimeBindings"], + path: "swift/OnnxRuntimeBindingsTests", + resources: [ + .copy("Resources/single_add.basic.ort") + ]), + ] +) + +// Add the ORT iOS Pod archive as a binary target. +// +// There are 2 scenarios: +// +// Release version of ORT SPM github repo: +// Target will be set to the latest released ORT iOS pod archive and its checksum. +// +// Current main of ORT SPM github repo: +// Target will be set to the pod archive built in sync with the current main objective-c source code. + +// CI or local testing where you have built/obtained the iOS Pod archive matching the current source code. +// Requires the ORT_IOS_POD_LOCAL_PATH environment variable to be set to specify the location of the pod. +if let pod_archive_path = ProcessInfo.processInfo.environment["ORT_IOS_POD_LOCAL_PATH"] { + // ORT_IOS_POD_LOCAL_PATH MUST be a path that is relative to Package.swift. + // + // To build locally, tools/ci_build/github/apple/build_and_assemble_ios_pods.py can be used + // See https://onnxruntime.ai/docs/build/custom.html#ios + // Example command: + // python3 tools/ci_build/github/apple/build_and_assemble_ios_pods.py \ + // --variant Full \ + // --build-settings-file tools/ci_build/github/apple/default_full_ios_framework_build_settings.json + // + // This should produce the pod archive in build/ios_pod_staging, and ORT_IOS_POD_LOCAL_PATH can be set to + // "build/ios_pod_staging/pod-archive-onnxruntime-c-???.zip" where '???' is replaced by the version info in the + // actual filename. + package.targets.append(Target.binaryTarget(name: "onnxruntime", path: pod_archive_path)) + +} else { + // ORT 1.15.0 release + package.targets.append( + Target.binaryTarget(name: "onnxruntime", + url: "https://onnxruntimepackages.z14.web.core.windows.net/pod-archive-onnxruntime-c-1.15.0.zip", + checksum: "9b41412329a73d7d298b1d94ab40ae9adb65cb84f132054073bc82515b4f5f82") + ) +} diff --git a/README.md b/README.md index 5cd7cec..57c6dc4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -# Project +# Swift Package Manager for ONNX Runtime -> This repo has been populated by an initial template to help get you started. Please -> make sure to update the content to build a great experience for community-building. +A light-weight repository for providing [Swift Package Manager (SPM)](https://www.swift.org/package-manager/) support for [ONNXRuntime](https://github.com/microsoft/onnxruntime). The ONNX Runtime native package is included as a binary dependency of the SPM package. -As the maintainer of this project, please make a few updates: -- Improving this README.MD file to provide a great experience -- Updating SUPPORT.MD with content about this project's support experience -- Understanding the security reporting process in SECURITY.MD -- Remove this section from the README +SPM is the alternative to CocoaPods when desired platform to consume is mobile iOS. + +## Note + +The `objectivec/` and `swift/` directories are copied from ORT repo and it's expected to match. It will be updated periodically/before release to merge the newly checked-in changes for objective-c/swift on ORT main repo. ## Contributing diff --git a/objectivec/ReadMe.md b/objectivec/ReadMe.md new file mode 100644 index 0000000..9b89726 --- /dev/null +++ b/objectivec/ReadMe.md @@ -0,0 +1,2 @@ +NOTE: Flat directory structure to work with both the Objective-C build and the Swift Package Manager build which is done +via ../Package.swift diff --git a/objectivec/assert_arc_enabled.mm b/objectivec/assert_arc_enabled.mm new file mode 100644 index 0000000..9aa0bad --- /dev/null +++ b/objectivec/assert_arc_enabled.mm @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +static_assert(__has_feature(objc_arc), "Objective-C ARC must be enabled."); diff --git a/objectivec/cxx_api.h b/objectivec/cxx_api.h new file mode 100644 index 0000000..3e4821c --- /dev/null +++ b/objectivec/cxx_api.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// wrapper for ORT C/C++ API headers + +#if defined(__clang__) +#pragma clang diagnostic push +// ignore clang documentation-related warnings +// instead, we will rely on Doxygen warnings for the C/C++ API headers +#pragma clang diagnostic ignored "-Wdocumentation" +#endif // defined(__clang__) + +// paths are different when building the Swift Package Manager package as the headers come from the iOS pod archive +#ifdef SPM_BUILD +#include "onnxruntime/onnxruntime_c_api.h" +#include "onnxruntime/onnxruntime_cxx_api.h" + +#if __has_include("onnxruntime/coreml_provider_factory.h") +#define ORT_OBJC_API_COREML_EP_AVAILABLE 1 +#include "onnxruntime/coreml_provider_factory.h" +#else +#define ORT_OBJC_API_COREML_EP_AVAILABLE 0 +#endif + +#else +#include "onnxruntime_c_api.h" +#include "onnxruntime_cxx_api.h" + +#if __has_include("coreml_provider_factory.h") +#define ORT_OBJC_API_COREML_EP_AVAILABLE 1 +#include "coreml_provider_factory.h" +#else +#define ORT_OBJC_API_COREML_EP_AVAILABLE 0 + +#endif + +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif // defined(__clang__) diff --git a/objectivec/error_utils.h b/objectivec/error_utils.h new file mode 100644 index 0000000..274e74a --- /dev/null +++ b/objectivec/error_utils.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#include + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +void ORTSaveCodeAndDescriptionToError(int code, const char* description, NSError** error); +void ORTSaveOrtExceptionToError(const Ort::Exception& e, NSError** error); +void ORTSaveExceptionToError(const std::exception& e, NSError** error); + +// helper macros to catch and handle C++ exceptions +#define ORT_OBJC_API_IMPL_CATCH(error, failure_return_value) \ + catch (const Ort::Exception& e) { \ + ORTSaveOrtExceptionToError(e, (error)); \ + return (failure_return_value); \ + } \ + catch (const std::exception& e) { \ + ORTSaveExceptionToError(e, (error)); \ + return (failure_return_value); \ + } + +#define ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) \ + ORT_OBJC_API_IMPL_CATCH(error, NO) + +#define ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) \ + ORT_OBJC_API_IMPL_CATCH(error, nil) + +NS_ASSUME_NONNULL_END diff --git a/objectivec/error_utils.mm b/objectivec/error_utils.mm new file mode 100644 index 0000000..9186326 --- /dev/null +++ b/objectivec/error_utils.mm @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "error_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString* const kOrtErrorDomain = @"onnxruntime"; + +void ORTSaveCodeAndDescriptionToError(int code, const char* descriptionCstr, NSError** error) { + if (!error) return; + + NSString* description = [NSString stringWithCString:descriptionCstr + encoding:NSASCIIStringEncoding]; + + *error = [NSError errorWithDomain:kOrtErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : description}]; +} + +void ORTSaveOrtExceptionToError(const Ort::Exception& e, NSError** error) { + ORTSaveCodeAndDescriptionToError(e.GetOrtErrorCode(), e.what(), error); +} + +void ORTSaveExceptionToError(const std::exception& e, NSError** error) { + ORTSaveCodeAndDescriptionToError(ORT_RUNTIME_EXCEPTION, e.what(), error); +} + +NS_ASSUME_NONNULL_END diff --git a/objectivec/format_objc.sh b/objectivec/format_objc.sh new file mode 100755 index 0000000..75ab071 --- /dev/null +++ b/objectivec/format_objc.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# formats Objective-C/C++ code + +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +clang-format -i $(find ${SCRIPT_DIR} -name "*.h" -o -name "*.m" -o -name "*.mm") diff --git a/objectivec/include/onnxruntime.h b/objectivec/include/onnxruntime.h new file mode 100644 index 0000000..c859f6a --- /dev/null +++ b/objectivec/include/onnxruntime.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// this header contains the entire ONNX Runtime Objective-C API +// the headers below can also be imported individually + +#import "ort_coreml_execution_provider.h" +#import "ort_xnnpack_execution_provider.h" +#import "ort_enums.h" +#import "ort_env.h" +#import "ort_session.h" +#import "ort_value.h" diff --git a/objectivec/include/ort_coreml_execution_provider.h b/objectivec/include/ort_coreml_execution_provider.h new file mode 100644 index 0000000..a015b6f --- /dev/null +++ b/objectivec/include/ort_coreml_execution_provider.h @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets whether the CoreML execution provider is available. + */ +BOOL ORTIsCoreMLExecutionProviderAvailable(void); + +#ifdef __cplusplus +} +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + * Options for configuring the CoreML execution provider. + */ +@interface ORTCoreMLExecutionProviderOptions : NSObject + +/** + * Whether the CoreML execution provider should run on CPU only. + */ +@property BOOL useCPUOnly; + +/** + * Whether the CoreML execution provider is enabled on subgraphs. + */ +@property BOOL enableOnSubgraphs; + +/** + * Whether the CoreML execution provider is only enabled for devices with Apple + * Neural Engine (ANE). + */ +@property BOOL onlyEnableForDevicesWithANE; + +@end + +@interface ORTSessionOptions (ORTSessionOptionsCoreMLEP) + +/** + * Enables the CoreML execution provider in the session configuration options. + * It is appended to the execution provider list which is ordered by + * decreasing priority. + * + * @param options The CoreML execution provider configuration options. + * @param error Optional error information set if an error occurs. + * @return Whether the provider was enabled successfully. + */ +- (BOOL)appendCoreMLExecutionProviderWithOptions:(ORTCoreMLExecutionProviderOptions*)options + error:(NSError**)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_enums.h b/objectivec/include/ort_enums.h new file mode 100644 index 0000000..d505ce2 --- /dev/null +++ b/objectivec/include/ort_enums.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The ORT logging verbosity levels. + */ +typedef NS_ENUM(int32_t, ORTLoggingLevel) { + ORTLoggingLevelVerbose, + ORTLoggingLevelInfo, + ORTLoggingLevelWarning, + ORTLoggingLevelError, + ORTLoggingLevelFatal, +}; + +/** + * The ORT value types. + * Currently, a subset of all types is supported. + */ +typedef NS_ENUM(int32_t, ORTValueType) { + ORTValueTypeUnknown, + ORTValueTypeTensor, +}; + +/** + * The ORT tensor element data types. + * Currently, a subset of all types is supported. + */ +typedef NS_ENUM(int32_t, ORTTensorElementDataType) { + ORTTensorElementDataTypeUndefined, + ORTTensorElementDataTypeFloat, + ORTTensorElementDataTypeInt8, + ORTTensorElementDataTypeUInt8, + ORTTensorElementDataTypeInt32, + ORTTensorElementDataTypeUInt32, + ORTTensorElementDataTypeInt64, + ORTTensorElementDataTypeUInt64, +}; + +/** + * The ORT graph optimization levels. + * See here for more details: + * https://onnxruntime.ai/docs/performance/graph-optimizations.html + */ +typedef NS_ENUM(int32_t, ORTGraphOptimizationLevel) { + ORTGraphOptimizationLevelNone, + ORTGraphOptimizationLevelBasic, + ORTGraphOptimizationLevelExtended, + ORTGraphOptimizationLevelAll, +}; + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_env.h b/objectivec/include/ort_env.h new file mode 100644 index 0000000..8c4184c --- /dev/null +++ b/objectivec/include/ort_env.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_enums.h" + +NS_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the ORT version string in format major.minor.patch. + * + * Available since 1.15. + */ +NSString* ORTVersion(void); + +#ifdef __cplusplus +} +#endif + +/** + * The ORT environment. + */ +@interface ORTEnv : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates an ORT Environment. + * + * @param loggingLevel The environment logging level. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithLoggingLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_session.h b/objectivec/include/ort_session.h new file mode 100644 index 0000000..92269f4 --- /dev/null +++ b/objectivec/include/ort_session.h @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_enums.h" + +NS_ASSUME_NONNULL_BEGIN + +@class ORTEnv; +@class ORTRunOptions; +@class ORTSessionOptions; +@class ORTValue; + +/** + * An ORT session loads and runs a model. + */ +@interface ORTSession : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates a session. + * + * @param env The ORT Environment instance. + * @param path The path to the ONNX model. + * @param sessionOptions Optional session configuration options. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithEnv:(ORTEnv*)env + modelPath:(NSString*)path + sessionOptions:(nullable ORTSessionOptions*)sessionOptions + error:(NSError**)error NS_DESIGNATED_INITIALIZER; + +/** + * Runs the model. + * The inputs and outputs are pre-allocated. + * + * @param inputs Dictionary of input names to input ORT values. + * @param outputs Dictionary of output names to output ORT values. + * @param runOptions Optional run configuration options. + * @param error Optional error information set if an error occurs. + * @return Whether the model was run successfully. + */ +- (BOOL)runWithInputs:(NSDictionary*)inputs + outputs:(NSDictionary*)outputs + runOptions:(nullable ORTRunOptions*)runOptions + error:(NSError**)error; + +/** + * Runs the model. + * The inputs are pre-allocated and the outputs are allocated by ORT. + * + * @param inputs Dictionary of input names to input ORT values. + * @param outputNames Set of output names. + * @param runOptions Optional run configuration options. + * @param error Optional error information set if an error occurs. + * @return A dictionary of output names to output ORT values with the outputs + * requested in `outputNames`, or nil if an error occurs. + */ +- (nullable NSDictionary*)runWithInputs:(NSDictionary*)inputs + outputNames:(NSSet*)outputNames + runOptions:(nullable ORTRunOptions*)runOptions + error:(NSError**)error; + +/** + * Gets the model's input names. + * + * @param error Optional error information set if an error occurs. + * @return An array of input names, or nil if an error occurs. + */ +- (nullable NSArray*)inputNamesWithError:(NSError**)error; + +/** + * Gets the model's overridable initializer names. + * + * @param error Optional error information set if an error occurs. + * @return An array of overridable initializer names, or nil if an error occurs. + */ +- (nullable NSArray*)overridableInitializerNamesWithError:(NSError**)error; + +/** + * Gets the model's output names. + * + * @param error Optional error information set if an error occurs. + * @return An array of output names, or nil if an error occurs. + */ +- (nullable NSArray*)outputNamesWithError:(NSError**)error; + +@end + +/** + * Options for configuring a session. + */ +@interface ORTSessionOptions : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates session configuration options. + * + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithError:(NSError**)error NS_SWIFT_NAME(init()); + +/** + * Appends an execution provider to the session options to enable the execution provider to be used when running + * the model. + * + * Available since 1.14. + * + * The execution provider list is ordered by decreasing priority. + * i.e. the first provider registered has the highest priority. + * + * @param providerName Provider name. For example, "xnnpack". + * @param providerOptions Provider-specific options. For example, for provider "xnnpack", {"intra_op_num_threads": "2"}. + * @param error Optional error information set if an error occurs. + * @return Whether the execution provider was appended successfully + */ +- (BOOL)appendExecutionProvider:(NSString*)providerName + providerOptions:(NSDictionary*)providerOptions + error:(NSError**)error; +/** + * Sets the number of threads used to parallelize the execution within nodes. + * A value of 0 means ORT will pick a default value. + * + * @param intraOpNumThreads The number of threads. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setIntraOpNumThreads:(int)intraOpNumThreads + error:(NSError**)error; + +/** + * Sets the graph optimization level. + * + * @param graphOptimizationLevel The graph optimization level. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setGraphOptimizationLevel:(ORTGraphOptimizationLevel)graphOptimizationLevel + error:(NSError**)error; + +/** + * Sets the path to which the optimized model file will be saved. + * + * @param optimizedModelFilePath The optimized model file path. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setOptimizedModelFilePath:(NSString*)optimizedModelFilePath + error:(NSError**)error; + +/** + * Sets the session log ID. + * + * @param logID The log ID. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setLogID:(NSString*)logID + error:(NSError**)error; + +/** + * Sets the session log severity level. + * + * @param loggingLevel The log severity level. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setLogSeverityLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error; + +/** + * Sets a session configuration key-value pair. + * Any value for a previously set key will be overwritten. + * The session configuration keys and values are documented here: + * https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/session/onnxruntime_session_options_config_keys.h + * + * @param key The key. + * @param value The value. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)addConfigEntryWithKey:(NSString*)key + value:(NSString*)value + error:(NSError**)error; + +/** + * Registers custom ops for use with `ORTSession`s using this SessionOptions by calling the specified + * native function name. The custom ops library must either be linked against, or have previously been loaded + * by the user. + * + * Available since 1.14. + * + * The registration function must have the signature: + * OrtStatus* (*fn)(OrtSessionOptions* options, const OrtApiBase* api); + * + * See https://onnxruntime.ai/docs/reference/operators/add-custom-op.html for more information on custom ops. + * See https://github.com/microsoft/onnxruntime/blob/342a5bf2b756d1a1fc6fdc582cfeac15182632fe/onnxruntime/test/testdata/custom_op_library/custom_op_library.cc#L115 + * for an example of a custom op library registration function. + * + * @param registrationFuncName The name of the registration function to call. + * @param error Optional error information set if an error occurs. + * @return Whether the registration function was successfully called. + */ +- (BOOL)registerCustomOpsUsingFunction:(NSString*)registrationFuncName + error:(NSError**)error; + +@end + +/** + * Options for configuring a run. + */ +@interface ORTRunOptions : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates run configuration options. + * + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithError:(NSError**)error NS_SWIFT_NAME(init()); + +/** + * Sets the run log tag. + * + * @param logTag The log tag. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setLogTag:(NSString*)logTag + error:(NSError**)error; + +/** + * Sets the run log severity level. + * + * @param loggingLevel The log severity level. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)setLogSeverityLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error; + +/** + * Sets a run configuration key-value pair. + * Any value for a previously set key will be overwritten. + * The run configuration keys and values are documented here: + * https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/session/onnxruntime_run_options_config_keys.h + * + * @param key The key. + * @param value The value. + * @param error Optional error information set if an error occurs. + * @return Whether the option was set successfully. + */ +- (BOOL)addConfigEntryWithKey:(NSString*)key + value:(NSString*)value + error:(NSError**)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_value.h b/objectivec/include/ort_value.h new file mode 100644 index 0000000..60a9bbe --- /dev/null +++ b/objectivec/include/ort_value.h @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_enums.h" + +NS_ASSUME_NONNULL_BEGIN + +@class ORTValueTypeInfo; +@class ORTTensorTypeAndShapeInfo; + +/** + * An ORT value encapsulates data used as an input or output to a model at runtime. + */ +@interface ORTValue : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates a value that is a tensor. + * The tensor data is allocated by the caller. + * + * @param tensorData The tensor data. + * @param elementType The tensor element data type. + * @param shape The tensor shape. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithTensorData:(NSMutableData*)tensorData + elementType:(ORTTensorElementDataType)elementType + shape:(NSArray*)shape + error:(NSError**)error; + +/** + * Gets the type information. + * + * @param error Optional error information set if an error occurs. + * @return The type information, or nil if an error occurs. + */ +- (nullable ORTValueTypeInfo*)typeInfoWithError:(NSError**)error; + +/** + * Gets the tensor type and shape information. + * This assumes that the value is a tensor. + * + * @param error Optional error information set if an error occurs. + * @return The tensor type and shape information, or nil if an error occurs. + */ +- (nullable ORTTensorTypeAndShapeInfo*)tensorTypeAndShapeInfoWithError:(NSError**)error; + +/** + * Gets the tensor data. + * This assumes that the value is a tensor. + * + * This returns the value's underlying data directly, not a copy of it. + * The memory's lifetime may be tied to this value, i.e., if it was allocated + * by ORT. On the other hand, the memory's lifetime is independent of the value + * if the value was created with user-provided data. + * + * @param error Optional error information set if an error occurs. + * @return The tensor data, or nil if an error occurs. + */ +- (nullable NSMutableData*)tensorDataWithError:(NSError**)error; + +@end + +/** + * A value's type information. + */ +@interface ORTValueTypeInfo : NSObject + +/** The value type. */ +@property(nonatomic) ORTValueType type; + +/** The tensor type and shape information, if the value is a tensor. */ +@property(nonatomic, nullable) ORTTensorTypeAndShapeInfo* tensorTypeAndShapeInfo; + +@end + +/** + * A tensor's type and shape information. + */ +@interface ORTTensorTypeAndShapeInfo : NSObject + +/** The tensor element data type. */ +@property(nonatomic) ORTTensorElementDataType elementType; + +/** The tensor shape. */ +@property(nonatomic) NSArray* shape; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_xnnpack_execution_provider.h b/objectivec/include/ort_xnnpack_execution_provider.h new file mode 100644 index 0000000..489fd43 --- /dev/null +++ b/objectivec/include/ort_xnnpack_execution_provider.h @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_session.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Options for configuring the Xnnpack execution provider. + */ +@interface ORTXnnpackExecutionProviderOptions : NSObject + +/** + * How many threads used for the Xnnpack execution provider. + */ +@property int intra_op_num_threads; + +@end + +@interface ORTSessionOptions (ORTSessionOptionsXnnpackEP) + +/** + * Available since 1.14. + * Enables the Xnnpack execution provider in the session configuration options. + * It is appended to the execution provider list which is ordered by + * decreasing priority. + * + * @param options The Xnnpack execution provider configuration options. + * @param error Optional error information set if an error occurs. + * @return Whether the provider was enabled successfully. + */ +- (BOOL)appendXnnpackExecutionProviderWithOptions:(ORTXnnpackExecutionProviderOptions*)options + error:(NSError**)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_coreml_execution_provider.mm b/objectivec/ort_coreml_execution_provider.mm new file mode 100644 index 0000000..6340fde --- /dev/null +++ b/objectivec/ort_coreml_execution_provider.mm @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_coreml_execution_provider.h" + +#import "cxx_api.h" +#import "error_utils.h" +#import "ort_session_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +BOOL ORTIsCoreMLExecutionProviderAvailable() { + return ORT_OBJC_API_COREML_EP_AVAILABLE ? YES : NO; +} + +@implementation ORTCoreMLExecutionProviderOptions + +@end + +@implementation ORTSessionOptions (ORTSessionOptionsCoreMLEP) + +- (BOOL)appendCoreMLExecutionProviderWithOptions:(ORTCoreMLExecutionProviderOptions*)options + error:(NSError**)error { +#if ORT_OBJC_API_COREML_EP_AVAILABLE + try { + const uint32_t flags = + (options.useCPUOnly ? COREML_FLAG_USE_CPU_ONLY : 0) | + (options.enableOnSubgraphs ? COREML_FLAG_ENABLE_ON_SUBGRAPH : 0) | + (options.onlyEnableForDevicesWithANE ? COREML_FLAG_ONLY_ENABLE_DEVICE_WITH_ANE : 0); + Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CoreML( + [self CXXAPIOrtSessionOptions], flags)); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error); +#else // !ORT_OBJC_API_COREML_EP_AVAILABLE + static_cast(options); + ORTSaveCodeAndDescriptionToError(ORT_FAIL, "CoreML execution provider is not enabled.", error); + return NO; +#endif +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_enums.mm b/objectivec/ort_enums.mm new file mode 100644 index 0000000..0144a33 --- /dev/null +++ b/objectivec/ort_enums.mm @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_enums_internal.h" + +#include + +#import "cxx_api.h" + +namespace { + +struct LoggingLevelInfo { + ORTLoggingLevel logging_level; + OrtLoggingLevel capi_logging_level; +}; + +// supported ORT logging levels +// define the mapping from ORTLoggingLevel to C API OrtLoggingLevel here +constexpr LoggingLevelInfo kLoggingLevelInfos[]{ + {ORTLoggingLevelVerbose, ORT_LOGGING_LEVEL_VERBOSE}, + {ORTLoggingLevelInfo, ORT_LOGGING_LEVEL_INFO}, + {ORTLoggingLevelWarning, ORT_LOGGING_LEVEL_WARNING}, + {ORTLoggingLevelError, ORT_LOGGING_LEVEL_ERROR}, + {ORTLoggingLevelFatal, ORT_LOGGING_LEVEL_FATAL}, +}; + +struct ValueTypeInfo { + ORTValueType type; + ONNXType capi_type; +}; + +// supported ORT value types +// define the mapping from ORTValueType to C API ONNXType here +constexpr ValueTypeInfo kValueTypeInfos[]{ + {ORTValueTypeUnknown, ONNX_TYPE_UNKNOWN}, + {ORTValueTypeTensor, ONNX_TYPE_TENSOR}, +}; + +struct TensorElementTypeInfo { + ORTTensorElementDataType type; + ONNXTensorElementDataType capi_type; + size_t element_size; +}; + +// supported ORT tensor element data types +// define the mapping from ORTTensorElementDataType to C API ONNXTensorElementDataType here +constexpr TensorElementTypeInfo kElementTypeInfos[]{ + {ORTTensorElementDataTypeUndefined, ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED, 0}, + {ORTTensorElementDataTypeFloat, ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, sizeof(float)}, + {ORTTensorElementDataTypeInt8, ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8, sizeof(int8_t)}, + {ORTTensorElementDataTypeUInt8, ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8, sizeof(uint8_t)}, + {ORTTensorElementDataTypeInt32, ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32, sizeof(int32_t)}, + {ORTTensorElementDataTypeUInt32, ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32, sizeof(uint32_t)}, + {ORTTensorElementDataTypeInt64, ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64, sizeof(int64_t)}, + {ORTTensorElementDataTypeUInt64, ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64, sizeof(uint64_t)}, +}; + +struct GraphOptimizationLevelInfo { + ORTGraphOptimizationLevel opt_level; + GraphOptimizationLevel capi_opt_level; +}; + +// ORT graph optimization levels +// define the mapping from ORTGraphOptimizationLevel to C API GraphOptimizationLevel here +constexpr GraphOptimizationLevelInfo kGraphOptimizationLevelInfos[]{ + {ORTGraphOptimizationLevelNone, ORT_DISABLE_ALL}, + {ORTGraphOptimizationLevelBasic, ORT_ENABLE_BASIC}, + {ORTGraphOptimizationLevelExtended, ORT_ENABLE_EXTENDED}, + {ORTGraphOptimizationLevelAll, ORT_ENABLE_ALL}, +}; + +template +auto SelectAndTransform( + const Container& container, SelectFn select_fn, TransformFn transform_fn, + const char* not_found_msg) + -> decltype(transform_fn(*std::begin(container))) { + const auto it = std::find_if( + std::begin(container), std::end(container), select_fn); + if (it == std::end(container)) { + ORT_CXX_API_THROW(not_found_msg, ORT_NOT_IMPLEMENTED); + } + return transform_fn(*it); +} + +} // namespace + +OrtLoggingLevel PublicToCAPILoggingLevel(ORTLoggingLevel logging_level) { + return SelectAndTransform( + kLoggingLevelInfos, + [logging_level](const auto& logging_level_info) { return logging_level_info.logging_level == logging_level; }, + [](const auto& logging_level_info) { return logging_level_info.capi_logging_level; }, + "unsupported logging level"); +} + +ORTValueType CAPIToPublicValueType(ONNXType capi_type) { + return SelectAndTransform( + kValueTypeInfos, + [capi_type](const auto& type_info) { return type_info.capi_type == capi_type; }, + [](const auto& type_info) { return type_info.type; }, + "unsupported value type"); +} + +ONNXTensorElementDataType PublicToCAPITensorElementType(ORTTensorElementDataType type) { + return SelectAndTransform( + kElementTypeInfos, + [type](const auto& type_info) { return type_info.type == type; }, + [](const auto& type_info) { return type_info.capi_type; }, + "unsupported tensor element type"); +} + +ORTTensorElementDataType CAPIToPublicTensorElementType(ONNXTensorElementDataType capi_type) { + return SelectAndTransform( + kElementTypeInfos, + [capi_type](const auto& type_info) { return type_info.capi_type == capi_type; }, + [](const auto& type_info) { return type_info.type; }, + "unsupported tensor element type"); +} + +size_t SizeOfCAPITensorElementType(ONNXTensorElementDataType capi_type) { + return SelectAndTransform( + kElementTypeInfos, + [capi_type](const auto& type_info) { return type_info.capi_type == capi_type; }, + [](const auto& type_info) { return type_info.element_size; }, + "unsupported tensor element type"); +} + +GraphOptimizationLevel PublicToCAPIGraphOptimizationLevel(ORTGraphOptimizationLevel opt_level) { + return SelectAndTransform( + kGraphOptimizationLevelInfos, + [opt_level](const auto& opt_level_info) { return opt_level_info.opt_level == opt_level; }, + [](const auto& opt_level_info) { return opt_level_info.capi_opt_level; }, + "unsupported graph optimization level"); +} diff --git a/objectivec/ort_enums_internal.h b/objectivec/ort_enums_internal.h new file mode 100644 index 0000000..f1470d7 --- /dev/null +++ b/objectivec/ort_enums_internal.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_enums.h" + +#import "cxx_api.h" + +OrtLoggingLevel PublicToCAPILoggingLevel(ORTLoggingLevel logging_level); + +ORTValueType CAPIToPublicValueType(ONNXType capi_type); + +ONNXTensorElementDataType PublicToCAPITensorElementType(ORTTensorElementDataType type); +ORTTensorElementDataType CAPIToPublicTensorElementType(ONNXTensorElementDataType capi_type); + +size_t SizeOfCAPITensorElementType(ONNXTensorElementDataType capi_type); + +GraphOptimizationLevel PublicToCAPIGraphOptimizationLevel(ORTGraphOptimizationLevel opt_level); diff --git a/objectivec/ort_env.mm b/objectivec/ort_env.mm new file mode 100644 index 0000000..cd83ac5 --- /dev/null +++ b/objectivec/ort_env.mm @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_env_internal.h" + +#include + +#import "cxx_api.h" + +#import "error_utils.h" +#import "ort_enums_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString* ORTVersion(void) { + std::string result = OrtGetApiBase()->GetVersionString(); + return [NSString stringWithUTF8String:result.c_str()]; +} + +@implementation ORTEnv { + std::optional _env; +} + +- (nullable instancetype)initWithLoggingLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + const auto CAPILoggingLevel = PublicToCAPILoggingLevel(loggingLevel); + _env = Ort::Env{CAPILoggingLevel}; + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (Ort::Env&)CXXAPIOrtEnv { + return *_env; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_env_internal.h b/objectivec/ort_env_internal.h new file mode 100644 index 0000000..32df9bb --- /dev/null +++ b/objectivec/ort_env_internal.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_env.h" + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTEnv () + +- (Ort::Env&)CXXAPIOrtEnv; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_session.mm b/objectivec/ort_session.mm new file mode 100644 index 0000000..c4f57b0 --- /dev/null +++ b/objectivec/ort_session.mm @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_session_internal.h" + +#include +#include + +#import "cxx_api.h" +#import "error_utils.h" +#import "ort_enums_internal.h" +#import "ort_env_internal.h" +#import "ort_value_internal.h" + +namespace { +enum class NamedValueType { + Input, + OverridableInitializer, + Output, +}; +} // namespace + +NS_ASSUME_NONNULL_BEGIN + +@implementation ORTSession { + std::optional _session; +} + +#pragma mark - Public + +- (nullable instancetype)initWithEnv:(ORTEnv*)env + modelPath:(NSString*)path + sessionOptions:(nullable ORTSessionOptions*)sessionOptions + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + if (!sessionOptions) { + sessionOptions = [[ORTSessionOptions alloc] initWithError:error]; + if (!sessionOptions) { + return nil; + } + } + + _session = Ort::Session{[env CXXAPIOrtEnv], + path.UTF8String, + [sessionOptions CXXAPIOrtSessionOptions]}; + + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)runWithInputs:(NSDictionary*)inputs + outputs:(NSDictionary*)outputs + runOptions:(nullable ORTRunOptions*)runOptions + error:(NSError**)error { + try { + if (!runOptions) { + runOptions = [[ORTRunOptions alloc] initWithError:error]; + if (!runOptions) { + return NO; + } + } + + std::vector inputNames, outputNames; + std::vector inputValues; + std::vector outputValues; + + for (NSString* inputName in inputs) { + inputNames.push_back(inputName.UTF8String); + inputValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); + } + + for (NSString* outputName in outputs) { + outputNames.push_back(outputName.UTF8String); + outputValues.push_back(static_cast([outputs[outputName] CXXAPIOrtValue])); + } + + Ort::ThrowOnError(Ort::GetApi().Run(*_session, [runOptions CXXAPIOrtRunOptions], + inputNames.data(), inputValues.data(), inputNames.size(), + outputNames.data(), outputNames.size(), outputValues.data())); + + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (nullable NSDictionary*)runWithInputs:(NSDictionary*)inputs + outputNames:(NSSet*)outputNameSet + runOptions:(nullable ORTRunOptions*)runOptions + error:(NSError**)error { + try { + if (!runOptions) { + runOptions = [[ORTRunOptions alloc] initWithError:error]; + if (!runOptions) { + return nil; + } + } + + NSArray* outputNameArray = outputNameSet.allObjects; + + std::vector inputNames, outputNames; + std::vector inputValues; + std::vector outputValues; + + for (NSString* inputName in inputs) { + inputNames.push_back(inputName.UTF8String); + inputValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); + } + + for (NSString* outputName in outputNameArray) { + outputNames.push_back(outputName.UTF8String); + outputValues.push_back(nullptr); + } + + Ort::ThrowOnError(Ort::GetApi().Run(*_session, [runOptions CXXAPIOrtRunOptions], + inputNames.data(), inputValues.data(), inputNames.size(), + outputNames.data(), outputNames.size(), outputValues.data())); + + NSMutableDictionary* outputs = [[NSMutableDictionary alloc] init]; + for (NSUInteger i = 0; i < outputNameArray.count; ++i) { + ORTValue* outputValue = [[ORTValue alloc] initWithCAPIOrtValue:outputValues[i] externalTensorData:nil error:error]; + if (!outputValue) { + // clean up remaining C API OrtValues which haven't been wrapped by an ORTValue yet + for (NSUInteger j = i; j < outputNameArray.count; ++j) { + Ort::GetApi().ReleaseValue(outputValues[j]); + } + return nil; + } + + outputs[outputNameArray[i]] = outputValue; + } + + return outputs; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSArray*)inputNamesWithError:(NSError**)error { + return [self namesWithType:NamedValueType::Input error:error]; +} + +- (nullable NSArray*)overridableInitializerNamesWithError:(NSError**)error { + return [self namesWithType:NamedValueType::OverridableInitializer error:error]; +} + +- (nullable NSArray*)outputNamesWithError:(NSError**)error { + return [self namesWithType:NamedValueType::Output error:error]; +} + +#pragma mark - Private + +- (nullable NSArray*)namesWithType:(NamedValueType)namedValueType + error:(NSError**)error { + try { + auto getCount = [&session = *_session, namedValueType]() { + if (namedValueType == NamedValueType::Input) { + return session.GetInputCount(); + } else if (namedValueType == NamedValueType::OverridableInitializer) { + return session.GetOverridableInitializerCount(); + } else { + return session.GetOutputCount(); + } + }; + + auto getName = [&session = *_session, namedValueType](size_t i, OrtAllocator* allocator) { + if (namedValueType == NamedValueType::Input) { + return session.GetInputNameAllocated(i, allocator); + } else if (namedValueType == NamedValueType::OverridableInitializer) { + return session.GetOverridableInitializerNameAllocated(i, allocator); + } else { + return session.GetOutputNameAllocated(i, allocator); + } + }; + + const size_t nameCount = getCount(); + + Ort::AllocatorWithDefaultOptions allocator; + NSMutableArray* result = [NSMutableArray arrayWithCapacity:nameCount]; + + for (size_t i = 0; i < nameCount; ++i) { + auto name = getName(i, allocator); + NSString* nameNsstr = [NSString stringWithUTF8String:name.get()]; + NSAssert(nameNsstr != nil, @"nameNsstr must not be nil"); + [result addObject:nameNsstr]; + } + + return result; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +@end + +@implementation ORTSessionOptions { + std::optional _sessionOptions; +} + +#pragma mark - Public + +- (nullable instancetype)initWithError:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + _sessionOptions = Ort::SessionOptions{}; + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)appendExecutionProvider:(NSString*)providerName + providerOptions:(NSDictionary*)providerOptions + error:(NSError**)error { + try { + std::unordered_map options; + NSArray* keys = [providerOptions allKeys]; + + for (NSString* key in keys) { + NSString* value = [providerOptions objectForKey:key]; + options.emplace(key.UTF8String, value.UTF8String); + } + + _sessionOptions->AppendExecutionProvider(providerName.UTF8String, options); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error); +} + +- (BOOL)setIntraOpNumThreads:(int)intraOpNumThreads + error:(NSError**)error { + try { + _sessionOptions->SetIntraOpNumThreads(intraOpNumThreads); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)setGraphOptimizationLevel:(ORTGraphOptimizationLevel)graphOptimizationLevel + error:(NSError**)error { + try { + _sessionOptions->SetGraphOptimizationLevel( + PublicToCAPIGraphOptimizationLevel(graphOptimizationLevel)); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)setOptimizedModelFilePath:(NSString*)optimizedModelFilePath + error:(NSError**)error { + try { + _sessionOptions->SetOptimizedModelFilePath(optimizedModelFilePath.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)setLogID:(NSString*)logID + error:(NSError**)error { + try { + _sessionOptions->SetLogId(logID.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)setLogSeverityLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error { + try { + _sessionOptions->SetLogSeverityLevel(PublicToCAPILoggingLevel(loggingLevel)); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)addConfigEntryWithKey:(NSString*)key + value:(NSString*)value + error:(NSError**)error { + try { + _sessionOptions->AddConfigEntry(key.UTF8String, value.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)registerCustomOpsUsingFunction:(NSString*)registrationFuncName + error:(NSError**)error { + try { + _sessionOptions->RegisterCustomOpsUsingFunction(registrationFuncName.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +#pragma mark - Internal + +- (Ort::SessionOptions&)CXXAPIOrtSessionOptions { + return *_sessionOptions; +} + +@end + +@implementation ORTRunOptions { + std::optional _runOptions; +} + +#pragma mark - Public + +- (nullable instancetype)initWithError:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + _runOptions = Ort::RunOptions{}; + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)setLogTag:(NSString*)logTag + error:(NSError**)error { + try { + _runOptions->SetRunTag(logTag.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)setLogSeverityLevel:(ORTLoggingLevel)loggingLevel + error:(NSError**)error { + try { + _runOptions->SetRunLogSeverityLevel(PublicToCAPILoggingLevel(loggingLevel)); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)addConfigEntryWithKey:(NSString*)key + value:(NSString*)value + error:(NSError**)error { + try { + _runOptions->AddConfigEntry(key.UTF8String, value.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +#pragma mark - Internal + +- (Ort::RunOptions&)CXXAPIOrtRunOptions { + return *_runOptions; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_session_internal.h b/objectivec/ort_session_internal.h new file mode 100644 index 0000000..c250264 --- /dev/null +++ b/objectivec/ort_session_internal.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_session.h" + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTSessionOptions () + +- (Ort::SessionOptions&)CXXAPIOrtSessionOptions; + +@end + +@interface ORTRunOptions () + +- (Ort::RunOptions&)CXXAPIOrtRunOptions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_value.mm b/objectivec/ort_value.mm new file mode 100644 index 0000000..ce77ca7 --- /dev/null +++ b/objectivec/ort_value.mm @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_value_internal.h" + +#include + +#import "cxx_api.h" +#import "error_utils.h" +#import "ort_enums_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace { + +ORTTensorTypeAndShapeInfo* CXXAPIToPublicTensorTypeAndShapeInfo( + const Ort::ConstTensorTypeAndShapeInfo& CXXAPITensorTypeAndShapeInfo) { + auto* result = [[ORTTensorTypeAndShapeInfo alloc] init]; + const auto elementType = CXXAPITensorTypeAndShapeInfo.GetElementType(); + const std::vector shape = CXXAPITensorTypeAndShapeInfo.GetShape(); + + result.elementType = CAPIToPublicTensorElementType(elementType); + auto* shapeArray = [[NSMutableArray alloc] initWithCapacity:shape.size()]; + for (size_t i = 0; i < shape.size(); ++i) { + shapeArray[i] = @(shape[i]); + } + result.shape = shapeArray; + + return result; +} + +ORTValueTypeInfo* CXXAPIToPublicValueTypeInfo( + const Ort::TypeInfo& CXXAPITypeInfo) { + auto* result = [[ORTValueTypeInfo alloc] init]; + const auto valueType = CXXAPITypeInfo.GetONNXType(); + + result.type = CAPIToPublicValueType(valueType); + + if (valueType == ONNX_TYPE_TENSOR) { + const auto tensorTypeAndShapeInfo = CXXAPITypeInfo.GetTensorTypeAndShapeInfo(); + result.tensorTypeAndShapeInfo = CXXAPIToPublicTensorTypeAndShapeInfo(tensorTypeAndShapeInfo); + } + + return result; +} + +// out = a * b +// returns true iff the result does not overflow +bool SafeMultiply(size_t a, size_t b, size_t& out) { + return !__builtin_mul_overflow(a, b, &out); +} + +} // namespace + +@interface ORTValue () + +// pointer to any external tensor data to keep alive for the lifetime of the ORTValue +@property(nonatomic, nullable) NSMutableData* externalTensorData; + +@end + +@implementation ORTValue { + std::optional _value; + std::optional _typeInfo; +} + +#pragma mark - Public + +- (nullable instancetype)initWithTensorData:(NSMutableData*)tensorData + elementType:(ORTTensorElementDataType)elementType + shape:(NSArray*)shape + error:(NSError**)error { + try { + const auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); + const auto ONNXElementType = PublicToCAPITensorElementType(elementType); + const auto shapeVector = [shape]() { + std::vector result{}; + result.reserve(shape.count); + for (NSNumber* dim in shape) { + result.push_back(dim.longLongValue); + } + return result; + }(); + Ort::Value ortValue = Ort::Value::CreateTensor( + memoryInfo, tensorData.mutableBytes, tensorData.length, + shapeVector.data(), shapeVector.size(), ONNXElementType); + + return [self initWithCAPIOrtValue:ortValue.release() + externalTensorData:tensorData + error:error]; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable ORTValueTypeInfo*)typeInfoWithError:(NSError**)error { + try { + return CXXAPIToPublicValueTypeInfo(*_typeInfo); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable ORTTensorTypeAndShapeInfo*)tensorTypeAndShapeInfoWithError:(NSError**)error { + try { + const auto tensorTypeAndShapeInfo = _typeInfo->GetTensorTypeAndShapeInfo(); + return CXXAPIToPublicTensorTypeAndShapeInfo(tensorTypeAndShapeInfo); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSMutableData*)tensorDataWithError:(NSError**)error { + try { + const auto tensorTypeAndShapeInfo = _typeInfo->GetTensorTypeAndShapeInfo(); + const size_t elementCount = tensorTypeAndShapeInfo.GetElementCount(); + const size_t elementSize = SizeOfCAPITensorElementType(tensorTypeAndShapeInfo.GetElementType()); + size_t rawDataLength; + if (!SafeMultiply(elementCount, elementSize, rawDataLength)) { + ORT_CXX_API_THROW("failed to compute tensor data length", ORT_RUNTIME_EXCEPTION); + } + + void* rawData; + Ort::ThrowOnError(Ort::GetApi().GetTensorMutableData(*_value, &rawData)); + + return [NSMutableData dataWithBytesNoCopy:rawData + length:rawDataLength + freeWhenDone:NO]; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +#pragma mark - Internal + +- (nullable instancetype)initWithCAPIOrtValue:(OrtValue*)CAPIOrtValue + externalTensorData:(nullable NSMutableData*)externalTensorData + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + _value = Ort::Value{CAPIOrtValue}; + _typeInfo = _value->GetTypeInfo(); + _externalTensorData = externalTensorData; + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error); +} + +- (Ort::Value&)CXXAPIOrtValue { + return *_value; +} + +@end + +@implementation ORTValueTypeInfo +@end + +@implementation ORTTensorTypeAndShapeInfo +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_value_internal.h b/objectivec/ort_value_internal.h new file mode 100644 index 0000000..345c0fc --- /dev/null +++ b/objectivec/ort_value_internal.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_value.h" + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTValue () + +- (nullable instancetype)initWithCAPIOrtValue:(OrtValue*)CAPIOrtValue + externalTensorData:(nullable NSMutableData*)externalTensorData + error:(NSError**)error NS_DESIGNATED_INITIALIZER; + +- (Ort::Value&)CXXAPIOrtValue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_xnnpack_execution_provider.mm b/objectivec/ort_xnnpack_execution_provider.mm new file mode 100644 index 0000000..324974a --- /dev/null +++ b/objectivec/ort_xnnpack_execution_provider.mm @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_xnnpack_execution_provider.h" + +#import "cxx_api.h" +#import "error_utils.h" +#import "ort_session_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation ORTXnnpackExecutionProviderOptions + +@end + +@implementation ORTSessionOptions (ORTSessionOptionsXnnpackEP) + +- (BOOL)appendXnnpackExecutionProviderWithOptions:(ORTXnnpackExecutionProviderOptions*)options + error:(NSError**)error { + try { + NSDictionary* provider_options = @{ + @"intra_op_num_threads" : [NSString stringWithFormat:@"%d", options.intra_op_num_threads] + }; + return [self appendExecutionProvider:@"XNNPACK" providerOptions:provider_options error:error]; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort b/swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort new file mode 100644 index 0000000..f622784 Binary files /dev/null and b/swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort differ diff --git a/swift/OnnxRuntimeBindingsTests/SwiftOnnxRuntimeBindingsTests.swift b/swift/OnnxRuntimeBindingsTests/SwiftOnnxRuntimeBindingsTests.swift new file mode 100644 index 0000000..48e2764 --- /dev/null +++ b/swift/OnnxRuntimeBindingsTests/SwiftOnnxRuntimeBindingsTests.swift @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import XCTest +import Foundation +@testable import OnnxRuntimeBindings + +final class SwiftOnnxRuntimeBindingsTests: XCTestCase { + let modelPath: String = Bundle.module.url(forResource: "single_add.basic", withExtension: "ort")!.path + + func testGetVersionString() throws { + do { + let version = ORTVersion() + XCTAssertNotNil(version) + } catch let error { + XCTFail(error.localizedDescription) + } + } + + func testCreateSession() throws { + do { + let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose) + let options = try ORTSessionOptions() + try options.setLogSeverityLevel(ORTLoggingLevel.verbose) + try options.setIntraOpNumThreads(1) + // Create the ORTSession + _ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: options) + } catch let error { + XCTFail(error.localizedDescription) + } + } + + func testAppendCoreMLEP() throws { + do { + let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose) + let sessionOptions: ORTSessionOptions = try ORTSessionOptions() + let coreMLOptions: ORTCoreMLExecutionProviderOptions = ORTCoreMLExecutionProviderOptions() + coreMLOptions.enableOnSubgraphs = true + try sessionOptions.appendCoreMLExecutionProvider(with: coreMLOptions) + + XCTAssertTrue(ORTIsCoreMLExecutionProviderAvailable()) + _ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: sessionOptions) + } catch let error { + XCTFail(error.localizedDescription) + } + } + + func testAppendXnnpackEP() throws { + do { + let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose) + let sessionOptions: ORTSessionOptions = try ORTSessionOptions() + let XnnpackOptions: ORTXnnpackExecutionProviderOptions = ORTXnnpackExecutionProviderOptions() + XnnpackOptions.intra_op_num_threads = 2 + try sessionOptions.appendXnnpackExecutionProvider(with: XnnpackOptions) + + XCTAssertTrue(ORTIsCoreMLExecutionProviderAvailable()) + _ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: sessionOptions) + } catch let error { + XCTFail(error.localizedDescription) + } + } +}