From c76787fd3ecddbf08a169243edba647dfbad1ffe Mon Sep 17 00:00:00 2001 From: Rachel Guo <35738743+YUNQIUGUO@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:07:43 -0700 Subject: [PATCH] Enable SPM support for Ext and configure pipelines for extensions target testing (#3) * initial ext files * add a workable version of spm for extensions * update * update pipeline * update pipelines * fix dev/release pipelines for extensions pod * fix dev pipelines for extensions pod * fix release pipelines for extensions pod * fix dev pipelines for extensions pod * adding empty include folder for configuring extensions target path * test * test * test * revert pipeline changes * revert gitignore changes * add ext pod binary target for release pipeline * add pipeline for extensions * update * update package.swift * update latest from branch * update * fix * fix * update * try dummy empty file * test * update package.swift to use fatalerror * syntax * try gitignore revert * update gitignore * add -list * onnxruntime-Package * update using onnxruntime-Package * update dev pipeline * fix dev pipelines * syntax * pull extensions/ * update pipelines again * syntax * variables * fix * fix -r * update Package.swift * update Package.swift * minor update * address pr comments * minor updates * fix * refine messages * syntax * syntax again * address pr comments partial * address pr comments * add .h header file and notes * minor updates * syncing objc source files and add code to register custom ops using function pointer * format * move to the header for function doc --------- Co-authored-by: rachguo Co-authored-by: rachguo --- .gitignore | 3 +- .../mac-ios-spm-dev-validation-pipeline.yml | 4 +- ...ios-spm-extensions-validation-pipeline.yml | 102 +++++++ ...ac-ios-spm-release-validation-pipeline.yml | 33 ++- Package.swift | 44 ++- extensions/OrtExt.mm | 14 + extensions/include/OnnxRuntimeExtensions.h | 3 + extensions/include/OrtExt.h | 17 ++ objectivec/cxx_api.h | 31 +-- objectivec/cxx_utils.h | 32 +++ objectivec/cxx_utils.mm | 94 +++++++ objectivec/error_utils.h | 1 + objectivec/error_utils.mm | 8 + objectivec/include/onnxruntime.h | 3 +- objectivec/include/onnxruntime_training.h | 9 + objectivec/include/ort_checkpoint.h | 119 ++++++++ .../include/ort_custom_op_registration.h | 23 ++ objectivec/include/ort_enums.h | 3 +- objectivec/include/ort_env.h | 2 +- objectivec/include/ort_session.h | 50 +++- objectivec/include/ort_training_session.h | 263 ++++++++++++++++++ objectivec/include/ort_value.h | 28 ++ objectivec/ort_checkpoint.mm | 111 ++++++++ objectivec/ort_checkpoint_internal.h | 16 ++ objectivec/ort_enums.mm | 14 +- objectivec/ort_env.mm | 5 +- objectivec/ort_session.mm | 66 +++-- objectivec/ort_training_session.mm | 224 +++++++++++++++ objectivec/ort_training_session_internal.h | 16 ++ objectivec/ort_value.mm | 93 ++++++- objectivec/ort_value_internal.h | 15 +- .../Resources/decode_image.onnx | 14 + .../SwiftOnnxRuntimeExtensionsTests.swift | 26 ++ 33 files changed, 1424 insertions(+), 62 deletions(-) create mode 100644 .pipelines/mac-ios-spm-extensions-validation-pipeline.yml create mode 100644 extensions/OrtExt.mm create mode 100644 extensions/include/OnnxRuntimeExtensions.h create mode 100644 extensions/include/OrtExt.h create mode 100644 objectivec/cxx_utils.h create mode 100644 objectivec/cxx_utils.mm create mode 100644 objectivec/include/onnxruntime_training.h create mode 100644 objectivec/include/ort_checkpoint.h create mode 100644 objectivec/include/ort_custom_op_registration.h create mode 100644 objectivec/include/ort_training_session.h create mode 100644 objectivec/ort_checkpoint.mm create mode 100644 objectivec/ort_checkpoint_internal.h create mode 100644 objectivec/ort_training_session.mm create mode 100644 objectivec/ort_training_session_internal.h create mode 100644 swift/OnnxRuntimeExtensionsTests/Resources/decode_image.onnx create mode 100644 swift/OnnxRuntimeExtensionsTests/SwiftOnnxRuntimeExtensionsTests.swift diff --git a/.gitignore b/.gitignore index e4772f9..375bcf0 100644 --- a/.gitignore +++ b/.gitignore @@ -400,4 +400,5 @@ FodyWeavers.xsd .DS_Store *.DS_Store -.build/ \ No newline at end of file +.build/ +.swiftpm \ 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 index 8a801cd..a400fee 100644 --- a/.pipelines/mac-ios-spm-dev-validation-pipeline.yml +++ b/.pipelines/mac-ios-spm-dev-validation-pipeline.yml @@ -68,5 +68,5 @@ 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 + parameters: + condition: 'succeeded' \ No newline at end of file diff --git a/.pipelines/mac-ios-spm-extensions-validation-pipeline.yml b/.pipelines/mac-ios-spm-extensions-validation-pipeline.yml new file mode 100644 index 0000000..91b22f8 --- /dev/null +++ b/.pipelines/mac-ios-spm-extensions-validation-pipeline.yml @@ -0,0 +1,102 @@ + jobs: + - job: j + displayName: "Test with ORT Extensions native pod" + + pool: + vmImage: "macOS-13" + + variables: + xcodeVersion: "14.3" + artifactsName: "ios_packaging_artifacts_full" + artifactsNameForExt: "ios_packaging_artifacts" + + 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 + git checkout + workingDirectory: "$(Build.SourcesDirectory)" + displayName: "Sparse checkout objectivec/ 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 for ORT C pod from iOS packaging pipeline + - 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 for ORT C Pod" + + # Download artifacts for ORT Ext C pod from extensions iOS packaging pipeline + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'specific' + project: 'Lotus' + definition: 1206 #'definitionid' is obtained from `System.DefinitionId` of extensions CI: extensions.ios_packaging + buildVersionToDownload: 'latest' + branchName: 'main' + targetPath: '$(Build.ArtifactStagingDirectory)' + + - script: | + set -e -x + ls + workingDirectory: '$(Build.ArtifactStagingDirectory)/$(artifactsNameForExt)' + displayName: "List staged artifacts for ORT Ext C Pod" + + # Note: Running xcodebuild test on `onnxruntime-Package` scheme will perform swift unit tests for both OnnxRuntimeBindings + # and OnnxRuntimeExtensions targets. + - script: | + set -e -x + cd "$(Build.ArtifactStagingDirectory)/$(artifactsName)" + POD_ARCHIVE=$(find . -name "pod-archive-onnxruntime-c-*.zip") + + echo "Printing the checksum here to make it easier when updating for actual release pod archive file in Package.swift" + shasum -a 256 "$(Build.ArtifactStagingDirectory)/$(artifactsName)/${POD_ARCHIVE}" + + cd "$(Build.ArtifactStagingDirectory)/$(artifactsNameForExt)" + POD_ARCHIVE_EXT=$(find . -name "pod-archive-onnxruntime-extensions-c-*.zip") + + echo "Printing the checksum here to make it easier when updating for actual release extensions pod archive file in Package.swift" + shasum -a 256 "$(Build.ArtifactStagingDirectory)/$(artifactsNameForExt)/${POD_ARCHIVE_EXT}" + + cd "$(Build.SourcesDirectory)/tmp/onnxruntime" + cp -r "$(Build.SourcesDirectory)/swift" . + cp "$(Build.SourcesDirectory)/Package.swift" . + cp -r "$(Build.SourcesDirectory)/extensions" . + + cp "$(Build.ArtifactStagingDirectory)/$(artifactsName)/${POD_ARCHIVE}" swift/ + export ORT_IOS_POD_LOCAL_PATH="swift/${POD_ARCHIVE}" + cp "$(Build.ArtifactStagingDirectory)/$(artifactsNameForExt)/${POD_ARCHIVE_EXT}" swift/ + export ORT_EXTENSIONS_IOS_POD_LOCAL_PATH="swift/${POD_ARCHIVE_EXT}" + + xcodebuild test -scheme onnxruntime-Package -destination 'platform=iOS Simulator,name=iPhone 14' + + rm swift/pod-archive-onnxruntime-c-*.zip + rm swift/pod-archive-onnxruntime-extensions-c-*.zip + workingDirectory: "$(Build.SourcesDirectory)/tmp" + displayName: "Test Package.swift usage for ORT and ORT Extensions" + + - 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 index f62f79c..5efefa8 100644 --- a/.pipelines/mac-ios-spm-release-validation-pipeline.yml +++ b/.pipelines/mac-ios-spm-release-validation-pipeline.yml @@ -14,13 +14,40 @@ - template: templates/use-xcode-version.yml parameters: xcodeVersion: ${{ variables.xcodeVersion }} + + # Note: Currently it requires a dev version extensions c pod for testing + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'specific' + project: 'Lotus' + definition: 1206 #'definitionid' is obtained from `System.DefinitionId` of extensions CI: extensions.ios_packaging + buildVersionToDownload: 'latest' + branchName: 'main' + targetPath: '$(Build.ArtifactStagingDirectory)' - script: | set -e -x - xcodebuild test -scheme onnxruntime -destination 'platform=iOS Simulator,name=iPhone 14' + ls + workingDirectory: "$(Build.ArtifactStagingDirectory)/ios_packaging_artifacts" + displayName: "List staged artifacts for ORT Ext C Pod" + + # Note: Running xcodebuild test on `onnxruntime-Package` scheme will perform swift tests for both OnnxRuntimeBindings + # and OnnxRuntimeExtensions targets. + - script: | + set -e -x + + cd "$(Build.ArtifactStagingDirectory)/ios_packaging_artifacts" + POD_ARCHIVE=$(find . -name "pod-archive-onnxruntime-extensions-c-*.zip") + + cd "$(Build.SourcesDirectory)" + cp "$(Build.ArtifactStagingDirectory)/ios_packaging_artifacts/${POD_ARCHIVE}" swift/ + export ORT_EXTENSIONS_IOS_POD_LOCAL_PATH="swift/${POD_ARCHIVE}" + + xcodebuild test -scheme onnxruntime-Package -destination 'platform=iOS Simulator,name=iPhone 14' + rm swift/pod-archive-onnxruntime-extensions-c-*.zip 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 + parameters: + condition: 'succeeded' \ No newline at end of file diff --git a/Package.swift b/Package.swift index 29c2247..8c929c2 100644 --- a/Package.swift +++ b/Package.swift @@ -24,13 +24,23 @@ let package = Package( .library(name: "onnxruntime", type: .static, targets: ["OnnxRuntimeBindings"]), + .library(name: "onnxruntime_extensions", + type: .static, + targets: ["OnnxRuntimeExtensions"]), ], dependencies: [], targets: [ .target(name: "OnnxRuntimeBindings", dependencies: ["onnxruntime"], path: "objectivec", - exclude: ["ReadMe.md", "format_objc.sh"], + exclude: ["ReadMe.md", "format_objc.sh", "test", + "ort_checkpoint.mm", + "ort_checkpoint_internal.h", + "ort_training_session_internal.h", + "ort_training_session.mm", + "include/ort_checkpoint.h", + "include/ort_training_session.h", + "include/onnxruntime_training.h"], cxxSettings: [ .define("SPM_BUILD"), .unsafeFlags(["-std=c++17", @@ -45,6 +55,23 @@ let package = Package( resources: [ .copy("Resources/single_add.basic.ort") ]), + .target(name: "OnnxRuntimeExtensions", + dependencies: ["onnxruntime_extensions", "onnxruntime"], + path: "extensions", + cxxSettings: [ + .define("ORT_SWIFT_PACKAGE_MANAGER_BUILD"), + .unsafeFlags(["-std=c++17", + "-fobjc-arc-exceptions" + ]), + ], linkerSettings: [ + .unsafeFlags(["-ObjC"]), + ]), + .testTarget(name: "OnnxRuntimeExtensionsTests", + dependencies: ["OnnxRuntimeExtensions", "OnnxRuntimeBindings"], + path: "swift/OnnxRuntimeExtensionsTests", + resources: [ + .copy("Resources/decode_image.onnx") + ]), ] ) @@ -83,3 +110,18 @@ if let pod_archive_path = ProcessInfo.processInfo.environment["ORT_IOS_POD_LOCAL checksum: "9b41412329a73d7d298b1d94ab40ae9adb65cb84f132054073bc82515b4f5f82") ) } + +if let ext_pod_archive_path = ProcessInfo.processInfo.environment["ORT_EXTENSIONS_IOS_POD_LOCAL_PATH"] { + package.targets.append(Target.binaryTarget(name: "onnxruntime_extensions", path: ext_pod_archive_path)) +} +// Note: ORT Extensions 0.8.0 release version pod (Currently not working - it gives a header path not found error.) + else { + // package.targets.append( + // Target.binaryTarget(name: "onnxruntime_extensions", + // url: "https://onnxruntimepackages.z14.web.core.windows.net/pod-archive-onnxruntime-extensions-c-0.8.0.zip", + // checksum: "1d003770c9a6d0ead92c04ed40d5083e8f4f55ea985750c3efab91489be15512") + // ) + fatalError("It is not valid to use a release version extensions c pod for now.\n" + + "Please set ORT_EXTENSIONS_IOS_POD_LOCAL_PATH environment variable to specify a location for local dev version pod.\n" + + "See Package.swift for more information on using a local pod archive.") + } \ No newline at end of file diff --git a/extensions/OrtExt.mm b/extensions/OrtExt.mm new file mode 100644 index 0000000..2310b04 --- /dev/null +++ b/extensions/OrtExt.mm @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "OrtExt.h" + +#include "onnxruntime_extensions/onnxruntime_extensions.h" + +@implementation OrtExt + ++ (nonnull ORTCAPIRegisterCustomOpsFnPtr)getRegisterCustomOpsFunctionPointer { + return RegisterCustomOps; +} + +@end \ No newline at end of file diff --git a/extensions/include/OnnxRuntimeExtensions.h b/extensions/include/OnnxRuntimeExtensions.h new file mode 100644 index 0000000..bd2115c --- /dev/null +++ b/extensions/include/OnnxRuntimeExtensions.h @@ -0,0 +1,3 @@ +// This is the umbrella header + +#include "OrtExt.h" \ No newline at end of file diff --git a/extensions/include/OrtExt.h b/extensions/include/OrtExt.h new file mode 100644 index 0000000..9d70c16 --- /dev/null +++ b/extensions/include/OrtExt.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@interface OrtExt : NSObject + +typedef struct OrtStatus* (*ORTCAPIRegisterCustomOpsFnPtr)(struct OrtSessionOptions* /*options*/, + const struct OrtApiBase* /*api*/); + +// Note: This returns the address of `RegisterCustomOps` function. At swift +// level, user can call this function to get the RegisterCustomOpsFnPtr +// and use the function pointer to register custom ops. See +// SwiftOnnxRuntimeExtensionsTests.swift for an example usage. ++ (nonnull ORTCAPIRegisterCustomOpsFnPtr)getRegisterCustomOpsFunctionPointer; + +@end \ No newline at end of file diff --git a/objectivec/cxx_api.h b/objectivec/cxx_api.h index 3e4821c..b57c865 100644 --- a/objectivec/cxx_api.h +++ b/objectivec/cxx_api.h @@ -11,29 +11,28 @@ #endif // defined(__clang__) // paths are different when building the Swift Package Manager package as the headers come from the iOS pod archive +// clang-format off +#define STRINGIFY(x) #x #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" +#define ORT_C_CXX_HEADER_FILE_PATH(x) STRINGIFY(onnxruntime/x) #else -#define ORT_OBJC_API_COREML_EP_AVAILABLE 0 +#define ORT_C_CXX_HEADER_FILE_PATH(x) STRINGIFY(x) +#endif +// clang-format on + +#if __has_include(ORT_C_CXX_HEADER_FILE_PATH(onnxruntime_training_c_api.h)) +#include ORT_C_CXX_HEADER_FILE_PATH(onnxruntime_training_c_api.h) +#include ORT_C_CXX_HEADER_FILE_PATH(onnxruntime_training_cxx_api.h) +#else +#include ORT_C_CXX_HEADER_FILE_PATH(onnxruntime_c_api.h) +#include ORT_C_CXX_HEADER_FILE_PATH(onnxruntime_cxx_api.h) #endif -#else -#include "onnxruntime_c_api.h" -#include "onnxruntime_cxx_api.h" - -#if __has_include("coreml_provider_factory.h") +#if __has_include(ORT_C_CXX_HEADER_FILE_PATH(coreml_provider_factory.h)) #define ORT_OBJC_API_COREML_EP_AVAILABLE 1 -#include "coreml_provider_factory.h" +#include ORT_C_CXX_HEADER_FILE_PATH(coreml_provider_factory.h) #else #define ORT_OBJC_API_COREML_EP_AVAILABLE 0 - -#endif - #endif #if defined(__clang__) diff --git a/objectivec/cxx_utils.h b/objectivec/cxx_utils.h new file mode 100644 index 0000000..0b3666d --- /dev/null +++ b/objectivec/cxx_utils.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#include +#include +#include + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN +@class ORTValue; + +namespace utils { + +NSString* toNSString(const std::string& str); +NSString* _Nullable toNullableNSString(const std::optional& str); + +std::string toStdString(NSString* str); +std::optional toStdOptionalString(NSString* _Nullable str); + +std::vector toStdStringVector(NSArray* strs); +NSArray* toNSStringNSArray(const std::vector& strs); + +NSArray* _Nullable wrapUnownedCAPIOrtValues(const std::vector& values, NSError** error); + +std::vector getWrappedCAPIOrtValues(NSArray* values); + +} // namespace utils + +NS_ASSUME_NONNULL_END diff --git a/objectivec/cxx_utils.mm b/objectivec/cxx_utils.mm new file mode 100644 index 0000000..01d0955 --- /dev/null +++ b/objectivec/cxx_utils.mm @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "cxx_utils.h" + +#include +#include +#include + +#import "error_utils.h" + +#import "ort_value_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace utils { + +NSString* toNSString(const std::string& str) { + NSString* nsStr = [NSString stringWithUTF8String:str.c_str()]; + if (!nsStr) { + ORT_CXX_API_THROW("Failed to convert std::string to NSString", ORT_INVALID_ARGUMENT); + } + + return nsStr; +} + +NSString* _Nullable toNullableNSString(const std::optional& str) { + if (str.has_value()) { + return toNSString(*str); + } + return nil; +} + +std::string toStdString(NSString* str) { + return std::string([str UTF8String]); +} + +std::optional toStdOptionalString(NSString* _Nullable str) { + if (str) { + return std::optional([str UTF8String]); + } + return std::nullopt; +} + +std::vector toStdStringVector(NSArray* strs) { + std::vector result; + result.reserve(strs.count); + for (NSString* str in strs) { + result.push_back([str UTF8String]); + } + return result; +} + +NSArray* toNSStringNSArray(const std::vector& strs) { + NSMutableArray* result = [NSMutableArray arrayWithCapacity:strs.size()]; + for (const std::string& str : strs) { + [result addObject:toNSString(str)]; + } + return result; +} + +NSArray* _Nullable wrapUnownedCAPIOrtValues(const std::vector& CAPIValues, NSError** error) { + NSMutableArray* result = [NSMutableArray arrayWithCapacity:CAPIValues.size()]; + for (size_t i = 0; i < CAPIValues.size(); ++i) { + // Wrap the C OrtValue in a C++ Ort::Value to automatically handle its release. + // Then, transfer that C++ Ort::Value to a new ORTValue. + Ort::Value CXXAPIValue{CAPIValues[i]}; + ORTValue* val = [[ORTValue alloc] initWithCXXAPIOrtValue:std::move(CXXAPIValue) + externalTensorData:nil + error:error]; + if (!val) { + // clean up remaining C OrtValues which haven't been wrapped by a C++ Ort::Value yet + for (size_t j = i + 1; j < CAPIValues.size(); ++j) { + Ort::GetApi().ReleaseValue(CAPIValues[j]); + } + return nil; + } + [result addObject:val]; + } + return result; +} + +std::vector getWrappedCAPIOrtValues(NSArray* values) { + std::vector result; + result.reserve(values.count); + for (ORTValue* val in values) { + result.push_back(static_cast([val CXXAPIOrtValue])); + } + return result; +} + +} // namespace utils + +NS_ASSUME_NONNULL_END diff --git a/objectivec/error_utils.h b/objectivec/error_utils.h index 274e74a..4df71ce 100644 --- a/objectivec/error_utils.h +++ b/objectivec/error_utils.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN void ORTSaveCodeAndDescriptionToError(int code, const char* description, NSError** error); +void ORTSaveCodeAndDescriptionToError(int code, NSString* description, NSError** error); void ORTSaveOrtExceptionToError(const Ort::Exception& e, NSError** error); void ORTSaveExceptionToError(const std::exception& e, NSError** error); diff --git a/objectivec/error_utils.mm b/objectivec/error_utils.mm index 9186326..335cf88 100644 --- a/objectivec/error_utils.mm +++ b/objectivec/error_utils.mm @@ -18,6 +18,14 @@ void ORTSaveCodeAndDescriptionToError(int code, const char* descriptionCstr, NSE userInfo:@{NSLocalizedDescriptionKey : description}]; } +void ORTSaveCodeAndDescriptionToError(int code, NSString* description, NSError** error) { + if (!error) return; + + *error = [NSError errorWithDomain:kOrtErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : description}]; +} + void ORTSaveOrtExceptionToError(const Ort::Exception& e, NSError** error) { ORTSaveCodeAndDescriptionToError(e.GetOrtErrorCode(), e.what(), error); } diff --git a/objectivec/include/onnxruntime.h b/objectivec/include/onnxruntime.h index c859f6a..c7bfe6f 100644 --- a/objectivec/include/onnxruntime.h +++ b/objectivec/include/onnxruntime.h @@ -5,8 +5,9 @@ // the headers below can also be imported individually #import "ort_coreml_execution_provider.h" -#import "ort_xnnpack_execution_provider.h" +#import "ort_custom_op_registration.h" #import "ort_enums.h" #import "ort_env.h" #import "ort_session.h" #import "ort_value.h" +#import "ort_xnnpack_execution_provider.h" diff --git a/objectivec/include/onnxruntime_training.h b/objectivec/include/onnxruntime_training.h new file mode 100644 index 0000000..504447e --- /dev/null +++ b/objectivec/include/onnxruntime_training.h @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// this header contains the entire ONNX Runtime training Objective-C API +// the headers below can also be imported individually + +#import "onnxruntime.h" +#import "ort_checkpoint.h" +#import "ort_training_session.h" diff --git a/objectivec/include/ort_checkpoint.h b/objectivec/include/ort_checkpoint.h new file mode 100644 index 0000000..85e5844 --- /dev/null +++ b/objectivec/include/ort_checkpoint.h @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import +#include + +NS_ASSUME_NONNULL_BEGIN + +/** + * An ORT checkpoint is a snapshot of the state of a model at a given point in time. + * + * This class holds the entire training session state that includes model parameters, + * their gradients, optimizer parameters, and user properties. The `ORTTrainingSession` leverages the + * `ORTCheckpoint` by accessing and updating the contained training state. + * + * Available since 1.16. + * + * @note This class is only available when the training APIs are enabled. + */ +@interface ORTCheckpoint : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates a checkpoint from directory on disk. + * + * @param path The path to the checkpoint directory. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + * + * @warning The construction of the checkpoint state requires instantiation of `ORTEnv`. + * The intialization will fail if the `ORTEnv` is not properly initialized. + */ +- (nullable instancetype)initWithPath:(NSString*)path + error:(NSError**)error NS_DESIGNATED_INITIALIZER; + +/** + * Saves a checkpoint to directory on disk. + * + * @param path The path to the checkpoint directory. + * @param includeOptimizerState Flag to indicate whether to save the optimizer state or not. + * @param error Optional error information set if an error occurs. + * @return Whether the checkpoint was saved successfully. + */ +- (BOOL)saveCheckpointToPath:(NSString*)path + withOptimizerState:(BOOL)includeOptimizerState + error:(NSError**)error; + +/** + * Adds an int property to this checkpoint. + * + * @param name The name of the property. + * @param value The value of the property. + * @param error Optional error information set if an error occurs. + * @return Whether the property was added successfully. + */ +- (BOOL)addIntPropertyWithName:(NSString*)name + value:(int64_t)value + error:(NSError**)error; + +/** + * Adds a float property to this checkpoint. + * + * @param name The name of the property. + * @param value The value of the property. + * @param error Optional error information set if an error occurs. + * @return Whether the property was added successfully. + */ +- (BOOL)addFloatPropertyWithName:(NSString*)name + value:(float)value + error:(NSError**)error; + +/** + * Adds a string property to this checkpoint. + * + * @param name The name of the property. + * @param value The value of the property. + * @param error Optional error information set if an error occurs. + * @return Whether the property was added successfully. + */ + +- (BOOL)addStringPropertyWithName:(NSString*)name + value:(NSString*)value + error:(NSError**)error; + +/** + * Gets an int property from this checkpoint. + * + * @param name The name of the property. + * @param error Optional error information set if an error occurs. + * @return The value of the property or 0 if an error occurs. + */ +- (int64_t)getIntPropertyWithName:(NSString*)name + error:(NSError**)error __attribute__((swift_error(nonnull_error))); + +/** + * Gets a float property from this checkpoint. + * + * @param name The name of the property. + * @param error Optional error information set if an error occurs. + * @return The value of the property or 0.0f if an error occurs. + */ +- (float)getFloatPropertyWithName:(NSString*)name + error:(NSError**)error __attribute__((swift_error(nonnull_error))); + +/** + * + * Gets a string property from this checkpoint. + * + * @param name The name of the property. + * @param error Optional error information set if an error occurs. + * @return The value of the property. + */ +- (nullable NSString*)getStringPropertyWithName:(NSString*)name + error:(NSError**)error __attribute__((swift_error(nonnull_error))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_custom_op_registration.h b/objectivec/include/ort_custom_op_registration.h new file mode 100644 index 0000000..8d50b92 --- /dev/null +++ b/objectivec/include/ort_custom_op_registration.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** C API type forward declaration. */ +struct OrtStatus; + +/** C API type forward declaration. */ +struct OrtApiBase; + +/** C API type forward declaration. */ +struct OrtSessionOptions; + +/** + * Pointer to a custom op registration function that uses the ONNX Runtime C API. + * + * The signature is defined in the ONNX Runtime C API: + * https://github.com/microsoft/onnxruntime/blob/67f4cd54fab321d83e4a75a40efeee95a6a17079/include/onnxruntime/core/session/onnxruntime_c_api.h#L697 + * + * This is a low-level type intended for interoperating with libraries which provide such a function for custom op + * registration, such as [ONNX Runtime Extensions](https://github.com/microsoft/onnxruntime-extensions). + */ +typedef struct OrtStatus* (*ORTCAPIRegisterCustomOpsFnPtr)(struct OrtSessionOptions* /*options*/, + const struct OrtApiBase* /*api*/); diff --git a/objectivec/include/ort_enums.h b/objectivec/include/ort_enums.h index d505ce2..78de233 100644 --- a/objectivec/include/ort_enums.h +++ b/objectivec/include/ort_enums.h @@ -38,12 +38,13 @@ typedef NS_ENUM(int32_t, ORTTensorElementDataType) { ORTTensorElementDataTypeUInt32, ORTTensorElementDataTypeInt64, ORTTensorElementDataTypeUInt64, + ORTTensorElementDataTypeString, }; /** * The ORT graph optimization levels. * See here for more details: - * https://onnxruntime.ai/docs/performance/graph-optimizations.html + * https://onnxruntime.ai/docs/performance/model-optimizations/graph-optimizations.html */ typedef NS_ENUM(int32_t, ORTGraphOptimizationLevel) { ORTGraphOptimizationLevelNone, diff --git a/objectivec/include/ort_env.h b/objectivec/include/ort_env.h index 8c4184c..8456b57 100644 --- a/objectivec/include/ort_env.h +++ b/objectivec/include/ort_env.h @@ -16,7 +16,7 @@ extern "C" { * * Available since 1.15. */ -NSString* ORTVersion(void); +NSString* _Nullable ORTVersion(void); #ifdef __cplusplus } diff --git a/objectivec/include/ort_session.h b/objectivec/include/ort_session.h index 92269f4..1b0c47d 100644 --- a/objectivec/include/ort_session.h +++ b/objectivec/include/ort_session.h @@ -3,6 +3,7 @@ #import +#import "ort_custom_op_registration.h" #import "ort_enums.h" NS_ASSUME_NONNULL_BEGIN @@ -196,12 +197,19 @@ NS_ASSUME_NONNULL_BEGIN * Available since 1.14. * * The registration function must have the signature: - * OrtStatus* (*fn)(OrtSessionOptions* options, const OrtApiBase* api); + * `OrtStatus* (*fn)(OrtSessionOptions* options, const OrtApiBase* api);` + * + * The signature is defined in the ONNX Runtime C API: + * https://github.com/microsoft/onnxruntime/blob/67f4cd54fab321d83e4a75a40efeee95a6a17079/include/onnxruntime/core/session/onnxruntime_c_api.h#L697 * * 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. * + * @note The caller must ensure that `registrationFuncName` names a valid function that is visible to the native ONNX + * Runtime code and has the correct signature. + * They must ensure that the function does what they expect it to do because this method will just call it. + * * @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. @@ -209,6 +217,46 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)registerCustomOpsUsingFunction:(NSString*)registrationFuncName error:(NSError**)error; +/** + * Registers custom ops for use with `ORTSession`s using this SessionOptions by calling the specified function + * pointed to by `registerCustomOpsFn`. + * + * Available since 1.16. + * + * The registration function must have the signature: + * `OrtStatus* (*fn)(OrtSessionOptions* options, const OrtApiBase* api);` + * + * The signature is defined in the ONNX Runtime C API: + * https://github.com/microsoft/onnxruntime/blob/67f4cd54fab321d83e4a75a40efeee95a6a17079/include/onnxruntime/core/session/onnxruntime_c_api.h#L697 + * + * 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. + * + * @note The caller must ensure that `registerCustomOpsFn` is a valid function pointer and has the correct signature. + * They must ensure that the function does what they expect it to do because this method will just call it. + * + * @param registerCustomOpsFn A pointer to the registration function to call. + * @param error Optional error information set if an error occurs. + * @return Whether the registration function was successfully called. + */ +- (BOOL)registerCustomOpsUsingFunctionPointer:(ORTCAPIRegisterCustomOpsFnPtr)registerCustomOpsFn + error:(NSError**)error; + +/** + * Registers ONNX Runtime Extensions custom ops that have been built in to ONNX Runtime. + * + * Available since 1.16. + * + * @note ONNX Runtime must have been built with the `--use_extensions` flag for the ONNX Runtime Extensions custom ops + * to be able to be registered with this method. When using a separate ONNX Runtime Extensions library, use + * `registerCustomOpsUsingFunctionPointer:error:` instead. + * + * @param error Optional error information set if an error occurs. + * @return Whether the ONNX Runtime Extensions custom ops were successfully registered. + */ +- (BOOL)enableOrtExtensionsCustomOpsWithError:(NSError**)error; + @end /** diff --git a/objectivec/include/ort_training_session.h b/objectivec/include/ort_training_session.h new file mode 100644 index 0000000..15c0137 --- /dev/null +++ b/objectivec/include/ort_training_session.h @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import +#include + +NS_ASSUME_NONNULL_BEGIN + +@class ORTCheckpoint; +@class ORTEnv; +@class ORTValue; +@class ORTSessionOptions; + +/** + * Trainer class that provides methods to train, evaluate and optimize ONNX models. + * + * The training session requires four training artifacts: + * 1. Training onnx model + * 2. Evaluation onnx model (optional) + * 3. Optimizer onnx model + * 4. Checkpoint directory + * + * [onnxruntime-training python utility](https://github.com/microsoft/onnxruntime/blob/main/orttraining/orttraining/python/training/onnxblock/README.md) + * can be used to generate above training artifacts. + * + * Available since 1.16. + * + * @note This class is only available when the training APIs are enabled. + */ +@interface ORTTrainingSession : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Creates a training session from the training artifacts that can be used to begin or resume training. + * + * The initializer instantiates the training session based on provided env and session options, which can be used to + * begin or resume training from a given checkpoint state. The checkpoint state represents the parameters of training + * session which will be moved to the device specified in the session option if needed. + * + * @param env The `ORTEnv` instance to use for the training session. + * @param sessionOptions The `ORTSessionOptions` to use for the training session. + * @param checkpoint Training states that are used as a starting point for training. + * @param trainModelPath The path to the training onnx model. + * @param evalModelPath The path to the evaluation onnx model. + * @param optimizerModelPath The path to the optimizer onnx model used to perform gradient descent. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + * + * @note Note that the training session created with a checkpoint state uses this state to store the entire training + * state (including model parameters, its gradients, the optimizer states and the properties). The training session + * keeps a strong (owning) pointer to the checkpoint state. + */ +- (nullable instancetype)initWithEnv:(ORTEnv*)env + sessionOptions:(ORTSessionOptions*)sessionOptions + checkpoint:(ORTCheckpoint*)checkpoint + trainModelPath:(NSString*)trainModelPath + evalModelPath:(nullable NSString*)evalModelPath + optimizerModelPath:(nullable NSString*)optimizerModelPath + error:(NSError**)error NS_DESIGNATED_INITIALIZER; + +/** + * Performs a training step, which is equivalent to a forward and backward propagation in a single step. + * + * The training step computes the outputs of the training model and the gradients of the trainable parameters + * for the given input values. The train step is performed based on the training model that was provided to the training session. + * It is equivalent to running forward and backward propagation in a single step. The computed gradients are stored inside + * the training session state so they can be later consumed by `optimizerStep`. The gradients can be lazily reset by + * calling `lazyResetGrad` method. + * + * @param inputs The input values to the training model. + * @param error Optional error information set if an error occurs. + * @return The output values of the training model. + */ +- (nullable NSArray*)trainStepWithInputValues:(NSArray*)inputs + error:(NSError**)error; + +/** + * Performs a evaluation step that computes the outputs of the evaluation model for the given inputs. + * The eval step is performed based on the evaluation model that was provided to the training session. + * + * @param inputs The input values to the eval model. + * @param error Optional error information set if an error occurs. + * @return The output values of the eval model. + * + */ +- (nullable NSArray*)evalStepWithInputValues:(NSArray*)inputs + error:(NSError**)error; + +/** + * Reset the gradients of all trainable parameters to zero lazily. + * + * Calling this method sets the internal state of the training session such that the gradients of the trainable parameters + * in the ORTCheckpoint will be scheduled to be reset just before the new gradients are computed on the next + * invocation of the `trainStep` method. + * + * @param error Optional error information set if an error occurs. + * @return YES if the gradients are set to reset successfully, NO otherwise. + */ +- (BOOL)lazyResetGradWithError:(NSError**)error; + +/** + * Performs the weight updates for the trainable parameters using the optimizer model. The optimizer step is performed + * based on the optimizer model that was provided to the training session. The updated parameters are stored inside the + * training state so that they can be used by the next `trainStep` method call. + * + * @param error Optional error information set if an error occurs. + * @return YES if the optimizer step was performed successfully, NO otherwise. + */ +- (BOOL)optimizerStepWithError:(NSError**)error; + +/** + * Returns the names of the user inputs for the training model that can be associated with + * the `ORTValue` provided to the `trainStep`. + * + * @param error Optional error information set if an error occurs. + * @return The names of the user inputs for the training model. + */ +- (nullable NSArray*)getTrainInputNamesWithError:(NSError**)error; + +/** + * Returns the names of the user inputs for the evaluation model that can be associated with + * the `ORTValue` provided to the `evalStep`. + * + * @param error Optional error information set if an error occurs. + * @return The names of the user inputs for the evaluation model. + */ +- (nullable NSArray*)getEvalInputNamesWithError:(NSError**)error; + +/** + * Returns the names of the user outputs for the training model that can be associated with + * the `ORTValue` returned by the `trainStep`. + * + * @param error Optional error information set if an error occurs. + * @return The names of the user outputs for the training model. + */ +- (nullable NSArray*)getTrainOutputNamesWithError:(NSError**)error; + +/** + * Returns the names of the user outputs for the evaluation model that can be associated with + * the `ORTValue` returned by the `evalStep`. + * + * @param error Optional error information set if an error occurs. + * @return The names of the user outputs for the evaluation model. + */ +- (nullable NSArray*)getEvalOutputNamesWithError:(NSError**)error; + +/** + * Registers a linear learning rate scheduler for the training session. + * + * The scheduler gradually decreases the learning rate from the initial value to zero over the course of the training. + * The decrease is performed by multiplying the current learning rate by a linearly updated factor. + * Before the decrease, the learning rate is gradually increased from zero to the initial value during a warmup phase. + * + * @param warmupStepCount The number of steps to perform the linear warmup. + * @param totalStepCount The total number of steps to perform the linear decay. + * @param initialLr The initial learning rate. + * @param error Optional error information set if an error occurs. + * @return YES if the scheduler was registered successfully, NO otherwise. + */ +- (BOOL)registerLinearLRSchedulerWithWarmupStepCount:(int64_t)warmupStepCount + totalStepCount:(int64_t)totalStepCount + initialLr:(float)initialLr + error:(NSError**)error; + +/** + * Update the learning rate based on the registered learning rate scheduler. + * + * Performs a scheduler step that updates the learning rate that is being used by the training session. + * This function should typically be called before invoking the optimizer step for each round, or as necessary + * to update the learning rate being used by the training session. + * + * @note A valid predefined learning rate scheduler must be first registered to invoke this method. + * + * @param error Optional error information set if an error occurs. + * @return YES if the scheduler step was performed successfully, NO otherwise. + */ +- (BOOL)schedulerStepWithError:(NSError**)error; + +/** + * Returns the current learning rate being used by the training session. + * + * @param error Optional error information set if an error occurs. + * @return The current learning rate or 0.0f if an error occurs. + */ +- (float)getLearningRateWithError:(NSError**)error __attribute__((swift_error(nonnull_error))); + +/** + * Sets the learning rate being used by the training session. + * + * The current learning rate is maintained by the training session and can be overwritten by invoking this method + * with the desired learning rate. This function should not be used when a valid learning rate scheduler is registered. + * It should be used either to set the learning rate derived from a custom learning rate scheduler or to set a constant + * learning rate to be used throughout the training session. + * + * @note It does not set the initial learning rate that may be needed by the predefined learning rate schedulers. + * To set the initial learning rate for learning rate schedulers, use the `registerLinearLRScheduler` method. + * + * @param lr The learning rate to be used by the training session. + * @param error Optional error information set if an error occurs. + * @return YES if the learning rate was set successfully, NO otherwise. + */ +- (BOOL)setLearningRate:(float)lr + error:(NSError**)error; + +/** + * Loads the training session model parameters from a contiguous buffer. + * + * @param buffer Contiguous buffer to load the parameters from. + * @param error Optional error information set if an error occurs. + * @return YES if the parameters were loaded successfully, NO otherwise. + */ +- (BOOL)fromBufferWithValue:(ORTValue*)buffer + error:(NSError**)error; + +/** + * Returns a contiguous buffer that holds a copy of all training state parameters. + * + * @param onlyTrainable If YES, returns a buffer that holds only the trainable parameters, otherwise returns a buffer + * that holds all the parameters. + * @param error Optional error information set if an error occurs. + * @return A contiguous buffer that holds a copy of all training state parameters. + */ +- (nullable ORTValue*)toBufferWithTrainable:(BOOL)onlyTrainable + error:(NSError**)error; + +/** + * Exports the training session model that can be used for inference. + * + * If the training session was provided with an eval model, the training session can generate an inference model if it + * knows the inference graph outputs. The input inference graph outputs are used to prune the eval model so that the + * inference model's outputs align with the provided outputs. The exported model is saved at the path provided and + * can be used for inferencing with `ORTSession`. + * + * @note The method reloads the eval model from the path provided to the initializer and expects this path to be valid. + * + * @param inferenceModelPath The path to the serialized the inference model. + * @param graphOutputNames The names of the outputs that are needed in the inference model. + * @param error Optional error information set if an error occurs. + * @return YES if the inference model was exported successfully, NO otherwise. + */ +- (BOOL)exportModelForInferenceWithOutputPath:(NSString*)inferenceModelPath + graphOutputNames:(NSArray*)graphOutputNames + error:(NSError**)error; +@end + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This function sets the seed for generating random numbers. + * Use this function to generate reproducible results. It should be noted that completely reproducible results are not guaranteed. + * + * @param seed Manually set seed to use for random number generation. + */ +void ORTSetSeed(int64_t seed); + +#ifdef __cplusplus +} +#endif + +NS_ASSUME_NONNULL_END diff --git a/objectivec/include/ort_value.h b/objectivec/include/ort_value.h index 60a9bbe..641c15b 100644 --- a/objectivec/include/ort_value.h +++ b/objectivec/include/ort_value.h @@ -32,6 +32,21 @@ NS_ASSUME_NONNULL_BEGIN shape:(NSArray*)shape error:(NSError**)error; +/** + * Creates a value that is a string tensor. + * The string data will be copied into a buffer owned by this ORTValue instance. + * + * Available since 1.16. + * + * @param tensorStringData The tensor string data. + * @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)initWithTensorStringData:(NSArray*)tensorStringData + shape:(NSArray*)shape + error:(NSError**)error; + /** * Gets the type information. * @@ -63,6 +78,19 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSMutableData*)tensorDataWithError:(NSError**)error; +/** + * Gets the tensor string data. + * This assumes that the value is a string tensor. + * + * This returns a copy of the value's underlying string data. + * + * Available since 1.16. + * + * @param error Optional error information set if an error occurs. + * @return The copy of the tensor string data, or nil if an error occurs. + */ +- (nullable NSArray*)tensorStringDataWithError:(NSError**)error; + @end /** diff --git a/objectivec/ort_checkpoint.mm b/objectivec/ort_checkpoint.mm new file mode 100644 index 0000000..1238645 --- /dev/null +++ b/objectivec/ort_checkpoint.mm @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_checkpoint_internal.h" + +#include +#include +#include +#import "cxx_api.h" + +#import "error_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation ORTCheckpoint { + std::optional _checkpoint; +} + +- (nullable instancetype)initWithPath:(NSString*)path + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + _checkpoint = Ort::CheckpointState::LoadCheckpoint(path.UTF8String); + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)saveCheckpointToPath:(NSString*)path + withOptimizerState:(BOOL)includeOptimizerState + error:(NSError**)error { + try { + Ort::CheckpointState::SaveCheckpoint([self CXXAPIOrtCheckpoint], path.UTF8String, includeOptimizerState); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)addIntPropertyWithName:(NSString*)name + value:(int64_t)value + error:(NSError**)error { + try { + [self CXXAPIOrtCheckpoint].AddProperty(name.UTF8String, value); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)addFloatPropertyWithName:(NSString*)name + value:(float)value + error:(NSError**)error { + try { + [self CXXAPIOrtCheckpoint].AddProperty(name.UTF8String, value); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)addStringPropertyWithName:(NSString*)name + value:(NSString*)value + error:(NSError**)error { + try { + [self CXXAPIOrtCheckpoint].AddProperty(name.UTF8String, value.UTF8String); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (nullable NSString*)getStringPropertyWithName:(NSString*)name error:(NSError**)error { + try { + Ort::Property value = [self CXXAPIOrtCheckpoint].GetProperty(name.UTF8String); + if (std::string* str = std::get_if(&value)) { + return [NSString stringWithUTF8String:str->c_str()]; + } + ORT_CXX_API_THROW("Property is not a string.", ORT_INVALID_ARGUMENT); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (int64_t)getIntPropertyWithName:(NSString*)name error:(NSError**)error { + try { + Ort::Property value = [self CXXAPIOrtCheckpoint].GetProperty(name.UTF8String); + if (int64_t* i = std::get_if(&value)) { + return *i; + } + ORT_CXX_API_THROW("Property is not an integer.", ORT_INVALID_ARGUMENT); + } + ORT_OBJC_API_IMPL_CATCH(error, 0) +} + +- (float)getFloatPropertyWithName:(NSString*)name error:(NSError**)error { + try { + Ort::Property value = [self CXXAPIOrtCheckpoint].GetProperty(name.UTF8String); + if (float* f = std::get_if(&value)) { + return *f; + } + ORT_CXX_API_THROW("Property is not a float.", ORT_INVALID_ARGUMENT); + } + ORT_OBJC_API_IMPL_CATCH(error, 0.0f) +} + +- (Ort::CheckpointState&)CXXAPIOrtCheckpoint { + return *_checkpoint; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_checkpoint_internal.h b/objectivec/ort_checkpoint_internal.h new file mode 100644 index 0000000..3d1550c --- /dev/null +++ b/objectivec/ort_checkpoint_internal.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_checkpoint.h" + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTCheckpoint () + +- (Ort::CheckpointState&)CXXAPIOrtCheckpoint; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_enums.mm b/objectivec/ort_enums.mm index 0144a33..6093981 100644 --- a/objectivec/ort_enums.mm +++ b/objectivec/ort_enums.mm @@ -4,6 +4,7 @@ #import "ort_enums_internal.h" #include +#include #import "cxx_api.h" @@ -39,13 +40,13 @@ constexpr ValueTypeInfo kValueTypeInfos[]{ struct TensorElementTypeInfo { ORTTensorElementDataType type; ONNXTensorElementDataType capi_type; - size_t element_size; + std::optional 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}, + {ORTTensorElementDataTypeUndefined, ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED, std::nullopt}, {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)}, @@ -53,6 +54,7 @@ constexpr TensorElementTypeInfo kElementTypeInfos[]{ {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)}, + {ORTTensorElementDataTypeString, ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING, std::nullopt}, }; struct GraphOptimizationLevelInfo { @@ -119,9 +121,11 @@ ORTTensorElementDataType CAPIToPublicTensorElementType(ONNXTensorElementDataType 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"); + [capi_type](const auto& type_info) { + return type_info.element_size.has_value() && type_info.capi_type == capi_type; + }, + [](const auto& type_info) { return *type_info.element_size; }, + "unsupported tensor element type or tensor element type does not have a known size"); } GraphOptimizationLevel PublicToCAPIGraphOptimizationLevel(ORTGraphOptimizationLevel opt_level) { diff --git a/objectivec/ort_env.mm b/objectivec/ort_env.mm index cd83ac5..7043785 100644 --- a/objectivec/ort_env.mm +++ b/objectivec/ort_env.mm @@ -12,9 +12,8 @@ NS_ASSUME_NONNULL_BEGIN -NSString* ORTVersion(void) { - std::string result = OrtGetApiBase()->GetVersionString(); - return [NSString stringWithUTF8String:result.c_str()]; +NSString* _Nullable ORTVersion(void) { + return [NSString stringWithUTF8String:OrtGetApiBase()->GetVersionString()]; } @implementation ORTEnv { diff --git a/objectivec/ort_session.mm b/objectivec/ort_session.mm index c4f57b0..d27c3e2 100644 --- a/objectivec/ort_session.mm +++ b/objectivec/ort_session.mm @@ -66,22 +66,26 @@ NS_ASSUME_NONNULL_BEGIN } std::vector inputNames, outputNames; - std::vector inputValues; - std::vector outputValues; + std::vector inputCAPIValues; + std::vector outputCAPIValues; + inputNames.reserve(inputs.count); + inputCAPIValues.reserve(inputs.count); for (NSString* inputName in inputs) { inputNames.push_back(inputName.UTF8String); - inputValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); + inputCAPIValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); } + outputNames.reserve(outputs.count); + outputCAPIValues.reserve(outputs.count); for (NSString* outputName in outputs) { outputNames.push_back(outputName.UTF8String); - outputValues.push_back(static_cast([outputs[outputName] CXXAPIOrtValue])); + outputCAPIValues.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())); + inputNames.data(), inputCAPIValues.data(), inputNames.size(), + outputNames.data(), outputNames.size(), outputCAPIValues.data())); return YES; } @@ -103,30 +107,39 @@ NS_ASSUME_NONNULL_BEGIN NSArray* outputNameArray = outputNameSet.allObjects; std::vector inputNames, outputNames; - std::vector inputValues; - std::vector outputValues; + std::vector inputCAPIValues; + std::vector outputCAPIValues; + inputNames.reserve(inputs.count); + inputCAPIValues.reserve(inputs.count); for (NSString* inputName in inputs) { inputNames.push_back(inputName.UTF8String); - inputValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); + inputCAPIValues.push_back(static_cast([inputs[inputName] CXXAPIOrtValue])); } + outputNames.reserve(outputNameArray.count); + outputCAPIValues.reserve(outputNameArray.count); for (NSString* outputName in outputNameArray) { outputNames.push_back(outputName.UTF8String); - outputValues.push_back(nullptr); + outputCAPIValues.push_back(nullptr); } Ort::ThrowOnError(Ort::GetApi().Run(*_session, [runOptions CXXAPIOrtRunOptions], - inputNames.data(), inputValues.data(), inputNames.size(), - outputNames.data(), outputNames.size(), outputValues.data())); + inputNames.data(), inputCAPIValues.data(), inputNames.size(), + outputNames.data(), outputNames.size(), outputCAPIValues.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]; + // Wrap the C OrtValue in a C++ Ort::Value to automatically handle its release. + // Then, transfer that C++ Ort::Value to a new ORTValue. + Ort::Value outputCXXAPIValue{outputCAPIValues[i]}; + ORTValue* outputValue = [[ORTValue alloc] initWithCXXAPIOrtValue:std::move(outputCXXAPIValue) + 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]); + // clean up remaining C OrtValues which haven't been wrapped by a C++ Ort::Value yet + for (NSUInteger j = i + 1; j < outputNameArray.count; ++j) { + Ort::GetApi().ReleaseValue(outputCAPIValues[j]); } return nil; } @@ -296,6 +309,27 @@ NS_ASSUME_NONNULL_BEGIN ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) } +- (BOOL)registerCustomOpsUsingFunctionPointer:(ORTCAPIRegisterCustomOpsFnPtr)registerCustomOpsFn + error:(NSError**)error { + try { + if (!registerCustomOpsFn) { + ORT_CXX_API_THROW("registerCustomOpsFn must not be null", ORT_INVALID_ARGUMENT); + } + Ort::ThrowOnError((*registerCustomOpsFn)(static_cast(*_sessionOptions), + OrtGetApiBase())); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)enableOrtExtensionsCustomOpsWithError:(NSError**)error { + try { + _sessionOptions->EnableOrtCustomOps(); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + #pragma mark - Internal - (Ort::SessionOptions&)CXXAPIOrtSessionOptions { diff --git a/objectivec/ort_training_session.mm b/objectivec/ort_training_session.mm new file mode 100644 index 0000000..285151b --- /dev/null +++ b/objectivec/ort_training_session.mm @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_training_session_internal.h" + +#import +#import +#import + +#import "cxx_api.h" +#import "cxx_utils.h" +#import "error_utils.h" +#import "ort_checkpoint_internal.h" +#import "ort_session_internal.h" +#import "ort_enums_internal.h" +#import "ort_env_internal.h" +#import "ort_value_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation ORTTrainingSession { + std::optional _session; + ORTCheckpoint* _checkpoint; +} + +- (Ort::TrainingSession&)CXXAPIOrtTrainingSession { + return *_session; +} + +- (nullable instancetype)initWithEnv:(ORTEnv*)env + sessionOptions:(ORTSessionOptions*)sessionOptions + checkpoint:(ORTCheckpoint*)checkpoint + trainModelPath:(NSString*)trainModelPath + evalModelPath:(nullable NSString*)evalModelPath + optimizerModelPath:(nullable NSString*)optimizerModelPath + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + std::optional evalPath = utils::toStdOptionalString(evalModelPath); + std::optional optimizerPath = utils::toStdOptionalString(optimizerModelPath); + + _checkpoint = checkpoint; + _session = Ort::TrainingSession{ + [env CXXAPIOrtEnv], + [sessionOptions CXXAPIOrtSessionOptions], + [checkpoint CXXAPIOrtCheckpoint], + trainModelPath.UTF8String, + evalPath, + optimizerPath}; + return self; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSArray*)trainStepWithInputValues:(NSArray*)inputs + error:(NSError**)error { + try { + std::vector inputValues = utils::getWrappedCAPIOrtValues(inputs); + + size_t outputCount; + Ort::ThrowOnError(Ort::GetTrainingApi().TrainingSessionGetTrainingModelOutputCount(*_session, &outputCount)); + std::vector outputValues(outputCount, nullptr); + + Ort::RunOptions runOptions; + Ort::ThrowOnError(Ort::GetTrainingApi().TrainStep( + *_session, + runOptions, + inputValues.size(), + inputValues.data(), + outputValues.size(), + outputValues.data())); + + return utils::wrapUnownedCAPIOrtValues(outputValues, error); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} +- (nullable NSArray*)evalStepWithInputValues:(NSArray*)inputs + error:(NSError**)error { + try { + // create vector of OrtValue from NSArray with same size as inputValues + std::vector inputValues = utils::getWrappedCAPIOrtValues(inputs); + + size_t outputCount; + Ort::ThrowOnError(Ort::GetTrainingApi().TrainingSessionGetEvalModelOutputCount(*_session, &outputCount)); + std::vector outputValues(outputCount, nullptr); + + Ort::RunOptions runOptions; + Ort::ThrowOnError(Ort::GetTrainingApi().EvalStep( + *_session, + runOptions, + inputValues.size(), + inputValues.data(), + outputValues.size(), + outputValues.data())); + + return utils::wrapUnownedCAPIOrtValues(outputValues, error); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)lazyResetGradWithError:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].LazyResetGrad(); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)optimizerStepWithError:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].OptimizerStep(); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (nullable NSArray*)getTrainInputNamesWithError:(NSError**)error { + try { + std::vector inputNames = [self CXXAPIOrtTrainingSession].InputNames(true); + return utils::toNSStringNSArray(inputNames); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSArray*)getTrainOutputNamesWithError:(NSError**)error { + try { + std::vector outputNames = [self CXXAPIOrtTrainingSession].OutputNames(true); + return utils::toNSStringNSArray(outputNames); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSArray*)getEvalInputNamesWithError:(NSError**)error { + try { + std::vector inputNames = [self CXXAPIOrtTrainingSession].InputNames(false); + return utils::toNSStringNSArray(inputNames); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable NSArray*)getEvalOutputNamesWithError:(NSError**)error { + try { + std::vector outputNames = [self CXXAPIOrtTrainingSession].OutputNames(false); + return utils::toNSStringNSArray(outputNames); + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)registerLinearLRSchedulerWithWarmupStepCount:(int64_t)warmupStepCount + totalStepCount:(int64_t)totalStepCount + initialLr:(float)initialLr + error:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].RegisterLinearLRScheduler(warmupStepCount, totalStepCount, initialLr); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)schedulerStepWithError:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].SchedulerStep(); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (float)getLearningRateWithError:(NSError**)error { + try { + return [self CXXAPIOrtTrainingSession].GetLearningRate(); + } + ORT_OBJC_API_IMPL_CATCH(error, 0.0f); +} + +- (BOOL)setLearningRate:(float)lr + error:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].SetLearningRate(lr); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (BOOL)fromBufferWithValue:(ORTValue*)buffer + error:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].FromBuffer([buffer CXXAPIOrtValue]); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +- (nullable ORTValue*)toBufferWithTrainable:(BOOL)onlyTrainable + error:(NSError**)error { + try { + Ort::Value val = [self CXXAPIOrtTrainingSession].ToBuffer(onlyTrainable); + return [[ORTValue alloc] initWithCXXAPIOrtValue:std::move(val) + externalTensorData:nil + error:error]; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (BOOL)exportModelForInferenceWithOutputPath:(NSString*)inferenceModelPath + graphOutputNames:(NSArray*)graphOutputNames + error:(NSError**)error { + try { + [self CXXAPIOrtTrainingSession].ExportModelForInferencing(utils::toStdString(inferenceModelPath), + utils::toStdStringVector(graphOutputNames)); + return YES; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error) +} + +@end + +void ORTSetSeed(int64_t seed) { + Ort::SetSeed(seed); +} + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_training_session_internal.h b/objectivec/ort_training_session_internal.h new file mode 100644 index 0000000..453c941 --- /dev/null +++ b/objectivec/ort_training_session_internal.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "ort_training_session.h" + +#import "cxx_api.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTTrainingSession () + +- (Ort::TrainingSession&)CXXAPIOrtTrainingSession; + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/ort_value.mm b/objectivec/ort_value.mm index ce77ca7..b9dc1a9 100644 --- a/objectivec/ort_value.mm +++ b/objectivec/ort_value.mm @@ -71,6 +71,12 @@ bool SafeMultiply(size_t a, size_t b, size_t& out) { shape:(NSArray*)shape error:(NSError**)error { try { + if (elementType == ORTTensorElementDataTypeString) { + ORT_CXX_API_THROW( + "ORTTensorElementDataTypeString element type provided. " + "Please call initWithTensorStringData:shape:error: instead to create an ORTValue with string data.", + ORT_INVALID_ARGUMENT); + } const auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); const auto ONNXElementType = PublicToCAPITensorElementType(elementType); const auto shapeVector = [shape]() { @@ -85,9 +91,49 @@ bool SafeMultiply(size_t a, size_t b, size_t& out) { memoryInfo, tensorData.mutableBytes, tensorData.length, shapeVector.data(), shapeVector.size(), ONNXElementType); - return [self initWithCAPIOrtValue:ortValue.release() - externalTensorData:tensorData - error:error]; + return [self initWithCXXAPIOrtValue:std::move(ortValue) + externalTensorData:tensorData + error:error]; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + +- (nullable instancetype)initWithTensorStringData:(NSArray*)tensorStringData + shape:(NSArray*)shape + error:(NSError**)error { + try { + Ort::AllocatorWithDefaultOptions allocator; + size_t tensorSize = 1U; + const auto shapeVector = [&tensorSize, shape]() { + std::vector result{}; + result.reserve(shape.count); + for (NSNumber* dim in shape) { + const auto dimValue = dim.longLongValue; + if (dimValue < 0 || !SafeMultiply(static_cast(dimValue), tensorSize, tensorSize)) { + ORT_CXX_API_THROW("Failed to compute the tensor size.", ORT_RUNTIME_EXCEPTION); + } + result.push_back(dimValue); + } + return result; + }(); + + if (tensorSize != [tensorStringData count]) { + ORT_CXX_API_THROW( + "Computed tensor size does not equal the length of the provided tensor string data.", + ORT_INVALID_ARGUMENT); + } + + Ort::Value ortValue = Ort::Value::CreateTensor( + allocator, shapeVector.data(), shapeVector.size(), ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING); + + size_t index = 0; + for (NSString* stringData in tensorStringData) { + ortValue.FillStringTensorElement([stringData UTF8String], index++); + } + + return [self initWithCXXAPIOrtValue:std::move(ortValue) + externalTensorData:nil + error:error]; } ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) } @@ -110,6 +156,12 @@ bool SafeMultiply(size_t a, size_t b, size_t& out) { - (nullable NSMutableData*)tensorDataWithError:(NSError**)error { try { const auto tensorTypeAndShapeInfo = _typeInfo->GetTensorTypeAndShapeInfo(); + if (tensorTypeAndShapeInfo.GetElementType() == ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING) { + ORT_CXX_API_THROW( + "This ORTValue holds string data. Please call tensorStringDataWithError: " + "instead to retrieve the string data from this ORTValue.", + ORT_RUNTIME_EXCEPTION); + } const size_t elementCount = tensorTypeAndShapeInfo.GetElementCount(); const size_t elementSize = SizeOfCAPITensorElementType(tensorTypeAndShapeInfo.GetElementType()); size_t rawDataLength; @@ -127,19 +179,44 @@ bool SafeMultiply(size_t a, size_t b, size_t& out) { ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) } +- (nullable NSArray*)tensorStringDataWithError:(NSError**)error { + try { + const auto tensorTypeAndShapeInfo = _typeInfo->GetTensorTypeAndShapeInfo(); + const size_t elementCount = tensorTypeAndShapeInfo.GetElementCount(); + const size_t tensorStringDataLength = _value->GetStringTensorDataLength(); + std::vector tensorStringData(tensorStringDataLength, '\0'); + std::vector offsets(elementCount); + _value->GetStringTensorContent(tensorStringData.data(), tensorStringDataLength, + offsets.data(), offsets.size()); + + NSMutableArray* result = [NSMutableArray arrayWithCapacity:elementCount]; + for (size_t idx = 0; idx < elementCount; ++idx) { + const size_t strLength = (idx == elementCount - 1) ? tensorStringDataLength - offsets[idx] + : offsets[idx + 1] - offsets[idx]; + [result addObject:[[NSString alloc] initWithBytes:tensorStringData.data() + offsets[idx] + length:strLength + encoding:NSUTF8StringEncoding]]; + } + return result; + } + ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error) +} + #pragma mark - Internal -- (nullable instancetype)initWithCAPIOrtValue:(OrtValue*)CAPIOrtValue - externalTensorData:(nullable NSMutableData*)externalTensorData - error:(NSError**)error { +- (nullable instancetype)initWithCXXAPIOrtValue:(Ort::Value&&)existingCXXAPIOrtValue + externalTensorData:(nullable NSMutableData*)externalTensorData + error:(NSError**)error { if ((self = [super init]) == nil) { return nil; } try { - _value = Ort::Value{CAPIOrtValue}; - _typeInfo = _value->GetTypeInfo(); + _typeInfo = existingCXXAPIOrtValue.GetTypeInfo(); _externalTensorData = externalTensorData; + + // transfer C++ Ort::Value ownership to this instance + _value = std::move(existingCXXAPIOrtValue); return self; } ORT_OBJC_API_IMPL_CATCH_RETURNING_NULLABLE(error); diff --git a/objectivec/ort_value_internal.h b/objectivec/ort_value_internal.h index 345c0fc..6bc2e9b 100644 --- a/objectivec/ort_value_internal.h +++ b/objectivec/ort_value_internal.h @@ -9,9 +9,18 @@ NS_ASSUME_NONNULL_BEGIN @interface ORTValue () -- (nullable instancetype)initWithCAPIOrtValue:(OrtValue*)CAPIOrtValue - externalTensorData:(nullable NSMutableData*)externalTensorData - error:(NSError**)error NS_DESIGNATED_INITIALIZER; +/** + * Creates a value from an existing C++ API Ort::Value and takes ownership from it. + * Note: Ownership is guaranteed to be transferred on success but not otherwise. + * + * @param existingCXXAPIOrtValue The existing C++ API Ort::Value. + * @param externalTensorData Any external tensor data referenced by `existingCXXAPIOrtValue`. + * @param error Optional error information set if an error occurs. + * @return The instance, or nil if an error occurs. + */ +- (nullable instancetype)initWithCXXAPIOrtValue:(Ort::Value&&)existingCXXAPIOrtValue + externalTensorData:(nullable NSMutableData*)externalTensorData + error:(NSError**)error NS_DESIGNATED_INITIALIZER; - (Ort::Value&)CXXAPIOrtValue; diff --git a/swift/OnnxRuntimeExtensionsTests/Resources/decode_image.onnx b/swift/OnnxRuntimeExtensionsTests/Resources/decode_image.onnx new file mode 100644 index 0000000..507fd0a --- /dev/null +++ b/swift/OnnxRuntimeExtensionsTests/Resources/decode_image.onnx @@ -0,0 +1,14 @@ +:© +E +imagebgr_data DecodeImage" DecodeImage:com.microsoft.extensions DecodeImageZ +image + + image_lengthb2 +bgr_data& +$ +  +bgr_data_h +  +bgr_data_w +B +com.microsoft.extensions \ No newline at end of file diff --git a/swift/OnnxRuntimeExtensionsTests/SwiftOnnxRuntimeExtensionsTests.swift b/swift/OnnxRuntimeExtensionsTests/SwiftOnnxRuntimeExtensionsTests.swift new file mode 100644 index 0000000..7fe60a6 --- /dev/null +++ b/swift/OnnxRuntimeExtensionsTests/SwiftOnnxRuntimeExtensionsTests.swift @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import XCTest +import Foundation +@testable import OnnxRuntimeBindings +@testable import OnnxRuntimeExtensions + +final class SwiftOnnxRuntimeExtensionsTests: XCTestCase { + + let modelPath: String = Bundle.module.url(forResource: "decode_image", withExtension: "onnx")!.path + + func testCreateSessionWithCustomOps() throws { + let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose) + let options = try ORTSessionOptions() + try options.setLogSeverityLevel(ORTLoggingLevel.verbose) + try options.setIntraOpNumThreads(1) + + // Register Custom Ops library using function pointer + let ortCustomOpsFnPtr = OrtExt.getRegisterCustomOpsFunctionPointer() + try options.registerCustomOps(functionPointer: ortCustomOpsFnPtr) + + // Create the ORTSession + _ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: options) + } +} \ No newline at end of file