From bfd9340bfe95e3bbe24cc5442ad932417a4fb795 Mon Sep 17 00:00:00 2001 From: rachguo Date: Tue, 11 Jul 2023 00:06:58 -0700 Subject: [PATCH] pull swift/ and objc/ folder contents --- objectivec/.DS_Store | Bin 0 -> 6148 bytes objectivec/ReadMe.md | 2 + objectivec/assert_arc_enabled.mm | 4 + objectivec/cxx_api.h | 40 ++ objectivec/cxx_utils.h | 32 ++ objectivec/cxx_utils.mm | 94 +++++ objectivec/error_utils.h | 34 ++ objectivec/error_utils.mm | 37 ++ objectivec/format_objc.sh | 9 + objectivec/include/onnxruntime.h | 13 + objectivec/include/onnxruntime_training.h | 9 + objectivec/include/ort_checkpoint.h | 119 ++++++ .../include/ort_coreml_execution_provider.h | 62 +++ .../include/ort_custom_op_registration.h | 23 ++ objectivec/include/ort_enums.h | 55 +++ objectivec/include/ort_env.h | 44 ++ objectivec/include/ort_session.h | 300 ++++++++++++++ objectivec/include/ort_training_session.h | 263 ++++++++++++ objectivec/include/ort_value.h | 94 +++++ .../include/ort_xnnpack_execution_provider.h | 39 ++ objectivec/ort_checkpoint.mm | 111 +++++ objectivec/ort_checkpoint_internal.h | 16 + objectivec/ort_coreml_execution_provider.mm | 44 ++ objectivec/ort_enums.mm | 133 ++++++ objectivec/ort_enums_internal.h | 17 + objectivec/ort_env.mm | 43 ++ objectivec/ort_env_internal.h | 16 + objectivec/ort_session.mm | 387 ++++++++++++++++++ objectivec/ort_session_internal.h | 22 + objectivec/ort_training_session.mm | 224 ++++++++++ objectivec/ort_training_session_internal.h | 16 + objectivec/ort_value.mm | 162 ++++++++ objectivec/ort_value_internal.h | 29 ++ objectivec/ort_xnnpack_execution_provider.mm | 31 ++ objectivec/test/assert_arc_enabled.mm | 4 + objectivec/test/assertion_utils.h | 46 +++ objectivec/test/ort_checkpoint_test.mm | 117 ++++++ objectivec/test/ort_env_test.mm | 30 ++ objectivec/test/ort_session_test.mm | 264 ++++++++++++ objectivec/test/ort_training_session_test.mm | 359 ++++++++++++++++ objectivec/test/ort_training_utils_test.mm | 26 ++ objectivec/test/ort_value_test.mm | 79 ++++ objectivec/test/test_utils.h | 21 + objectivec/test/test_utils.mm | 44 ++ objectivec/test/testdata/gen_models.sh | 12 + objectivec/test/testdata/single_add.basic.ort | Bin 0 -> 1296 bytes objectivec/test/testdata/single_add.onnx | Bin 0 -> 93 bytes objectivec/test/testdata/single_add_gen.py | 19 + swift/.DS_Store | Bin 0 -> 6148 bytes swift/OnnxRuntimeBindingsTests/.DS_Store | Bin 0 -> 6148 bytes .../Resources/single_add.basic.ort | Bin 0 -> 1296 bytes .../SwiftOnnxRuntimeBindingsTests.swift | 62 +++ 52 files changed, 3607 insertions(+) create mode 100644 objectivec/.DS_Store create mode 100644 objectivec/ReadMe.md create mode 100644 objectivec/assert_arc_enabled.mm create mode 100644 objectivec/cxx_api.h create mode 100644 objectivec/cxx_utils.h create mode 100644 objectivec/cxx_utils.mm create mode 100644 objectivec/error_utils.h create mode 100644 objectivec/error_utils.mm create mode 100755 objectivec/format_objc.sh create mode 100644 objectivec/include/onnxruntime.h create mode 100644 objectivec/include/onnxruntime_training.h create mode 100644 objectivec/include/ort_checkpoint.h create mode 100644 objectivec/include/ort_coreml_execution_provider.h create mode 100644 objectivec/include/ort_custom_op_registration.h create mode 100644 objectivec/include/ort_enums.h create mode 100644 objectivec/include/ort_env.h create mode 100644 objectivec/include/ort_session.h create mode 100644 objectivec/include/ort_training_session.h create mode 100644 objectivec/include/ort_value.h create mode 100644 objectivec/include/ort_xnnpack_execution_provider.h create mode 100644 objectivec/ort_checkpoint.mm create mode 100644 objectivec/ort_checkpoint_internal.h create mode 100644 objectivec/ort_coreml_execution_provider.mm create mode 100644 objectivec/ort_enums.mm create mode 100644 objectivec/ort_enums_internal.h create mode 100644 objectivec/ort_env.mm create mode 100644 objectivec/ort_env_internal.h create mode 100644 objectivec/ort_session.mm create mode 100644 objectivec/ort_session_internal.h create mode 100644 objectivec/ort_training_session.mm create mode 100644 objectivec/ort_training_session_internal.h create mode 100644 objectivec/ort_value.mm create mode 100644 objectivec/ort_value_internal.h create mode 100644 objectivec/ort_xnnpack_execution_provider.mm create mode 100644 objectivec/test/assert_arc_enabled.mm create mode 100644 objectivec/test/assertion_utils.h create mode 100644 objectivec/test/ort_checkpoint_test.mm create mode 100644 objectivec/test/ort_env_test.mm create mode 100644 objectivec/test/ort_session_test.mm create mode 100644 objectivec/test/ort_training_session_test.mm create mode 100644 objectivec/test/ort_training_utils_test.mm create mode 100644 objectivec/test/ort_value_test.mm create mode 100644 objectivec/test/test_utils.h create mode 100644 objectivec/test/test_utils.mm create mode 100755 objectivec/test/testdata/gen_models.sh create mode 100644 objectivec/test/testdata/single_add.basic.ort create mode 100644 objectivec/test/testdata/single_add.onnx create mode 100644 objectivec/test/testdata/single_add_gen.py create mode 100644 swift/.DS_Store create mode 100644 swift/OnnxRuntimeBindingsTests/.DS_Store create mode 100644 swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort create mode 100644 swift/OnnxRuntimeBindingsTests/SwiftOnnxRuntimeBindingsTests.swift diff --git a/objectivec/.DS_Store b/objectivec/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..67f406eedd1fc62e6d3c050cee15d2acd30bd6a1 GIT binary patch literal 6148 zcmeHKyJ`bL3>+nf7}B^*xxbJ!TnYFu6&wD4?@g2RY+q-AkD5$ zbIVR~IsvftX?O-!0G4z|e0rFhKX;$mU1f|&=Q~aq@HTwx$JgVm`g+2-4>;f*dyGH% z$MHHrSt%d|q<|EV0#e{l3V83OEgusVrGONW0^bVw_o2}pd*PHApALo?0f-Bx!?=!F zg4jGk?1fVzGc-#oF{xG~h9#Z(R&~8_N=!Peh7YTgttJ$U)A{}u<*=TpCaDZOd9Q8s7rNJc(cQQX3PZGGVzgs!yd6J9 cQPwqI^La0v5`)fs(24pPa9w0l;J+0(1EVPwfB*mh literal 0 HcmV?d00001 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..b57c865 --- /dev/null +++ b/objectivec/cxx_api.h @@ -0,0 +1,40 @@ +// 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 +// clang-format off +#define STRINGIFY(x) #x +#ifdef SPM_BUILD +#define ORT_C_CXX_HEADER_FILE_PATH(x) STRINGIFY(onnxruntime/x) +#else +#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 + +#if __has_include(ORT_C_CXX_HEADER_FILE_PATH(coreml_provider_factory.h)) +#define ORT_OBJC_API_COREML_EP_AVAILABLE 1 +#include ORT_C_CXX_HEADER_FILE_PATH(coreml_provider_factory.h) +#else +#define ORT_OBJC_API_COREML_EP_AVAILABLE 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif // 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 new file mode 100644 index 0000000..4df71ce --- /dev/null +++ b/objectivec/error_utils.h @@ -0,0 +1,34 @@ +// 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 ORTSaveCodeAndDescriptionToError(int code, NSString* 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..335cf88 --- /dev/null +++ b/objectivec/error_utils.mm @@ -0,0 +1,37 @@ +// 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 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); +} + +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..c7bfe6f --- /dev/null +++ b/objectivec/include/onnxruntime.h @@ -0,0 +1,13 @@ +// 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_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_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_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 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..8456b57 --- /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* _Nullable 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..cfdb3bf --- /dev/null +++ b/objectivec/include/ort_session.h @@ -0,0 +1,300 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_custom_op_registration.h" +#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);` + * + * 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. + */ +- (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; + +@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_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 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_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_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..7043785 --- /dev/null +++ b/objectivec/ort_env.mm @@ -0,0 +1,43 @@ +// 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* _Nullable ORTVersion(void) { + return [NSString stringWithUTF8String:OrtGetApiBase()->GetVersionString()]; +} + +@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..0255e7b --- /dev/null +++ b/objectivec/ort_session.mm @@ -0,0 +1,387 @@ +// 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 inputCAPIValues; + std::vector outputCAPIValues; + + inputNames.reserve(inputs.count); + inputCAPIValues.reserve(inputs.count); + for (NSString* inputName in inputs) { + inputNames.push_back(inputName.UTF8String); + 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); + outputCAPIValues.push_back(static_cast([outputs[outputName] CXXAPIOrtValue])); + } + + Ort::ThrowOnError(Ort::GetApi().Run(*_session, [runOptions CXXAPIOrtRunOptions], + inputNames.data(), inputCAPIValues.data(), inputNames.size(), + outputNames.data(), outputNames.size(), outputCAPIValues.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 inputCAPIValues; + std::vector outputCAPIValues; + + inputNames.reserve(inputs.count); + inputCAPIValues.reserve(inputs.count); + for (NSString* inputName in inputs) { + inputNames.push_back(inputName.UTF8String); + 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); + outputCAPIValues.push_back(nullptr); + } + + Ort::ThrowOnError(Ort::GetApi().Run(*_session, [runOptions CXXAPIOrtRunOptions], + 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) { + // 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 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; + } + + 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) +} + +- (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) +} + +#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_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 new file mode 100644 index 0000000..f6ea674 --- /dev/null +++ b/objectivec/ort_value.mm @@ -0,0 +1,162 @@ +// 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 initWithCXXAPIOrtValue:std::move(ortValue) + 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)initWithCXXAPIOrtValue:(Ort::Value&&)existingCXXAPIOrtValue + externalTensorData:(nullable NSMutableData*)externalTensorData + error:(NSError**)error { + if ((self = [super init]) == nil) { + return nil; + } + + try { + _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); +} + +- (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..6bc2e9b --- /dev/null +++ b/objectivec/ort_value_internal.h @@ -0,0 +1,29 @@ +// 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 () + +/** + * 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; + +@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/objectivec/test/assert_arc_enabled.mm b/objectivec/test/assert_arc_enabled.mm new file mode 100644 index 0000000..9aa0bad --- /dev/null +++ b/objectivec/test/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/test/assertion_utils.h b/objectivec/test/assertion_utils.h new file mode 100644 index 0000000..2b72435 --- /dev/null +++ b/objectivec/test/assertion_utils.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +#define ORTAssertNullableResultSuccessful(result, error) \ + do { \ + XCTAssertNotNil(result, @"Expected non-nil result but got nil. Error: %@", error); \ + XCTAssertNil(error); \ + } while (0) + +#define ORTAssertBoolResultSuccessful(result, error) \ + do { \ + XCTAssertTrue(result, @"Expected true result but got false. Error: %@", error); \ + XCTAssertNil(error); \ + } while (0) + +#define ORTAssertNullableResultUnsuccessful(result, error) \ + do { \ + XCTAssertNil(result); \ + XCTAssertNotNil(error); \ + } while (0) + +#define ORTAssertBoolResultUnsuccessful(result, error) \ + do { \ + XCTAssertFalse(result); \ + XCTAssertNotNil(error); \ + } while (0) + +#define ORTAssertEqualFloatAndNoError(expected, result, error) \ + do { \ + XCTAssertEqualWithAccuracy(expected, result, 1e-3f, @"Expected %f but got %f. Error:%@", expected, result, error); \ + XCTAssertNil(error); \ + } while (0) + +#define ORTAssertEqualFloatArrays(expected, result) \ + do { \ + XCTAssertEqual(expected.count, result.count); \ + for (size_t i = 0; i < expected.count; ++i) { \ + XCTAssertEqualWithAccuracy([expected[i] floatValue], [result[i] floatValue], 1e-3f); \ + } \ + } while (0) + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_checkpoint_test.mm b/objectivec/test/ort_checkpoint_test.mm new file mode 100644 index 0000000..9b2196f --- /dev/null +++ b/objectivec/test/ort_checkpoint_test.mm @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_checkpoint.h" +#import "ort_training_session.h" +#import "ort_env.h" +#import "ort_session.h" + +#import "test/test_utils.h" +#import "test/assertion_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTCheckpointTest : XCTestCase +@property(readonly, nullable) ORTEnv* ortEnv; +@end + +@implementation ORTCheckpointTest + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; + + NSError* err = nil; + _ortEnv = [[ORTEnv alloc] initWithLoggingLevel:ORTLoggingLevelWarning + error:&err]; + ORTAssertNullableResultSuccessful(_ortEnv, err); +} + ++ (NSString*)getCheckpointPath { + NSBundle* bundle = [NSBundle bundleForClass:[ORTCheckpointTest class]]; + NSString* path = [[bundle resourcePath] stringByAppendingPathComponent:@"checkpoint.ckpt"]; + return path; +} + ++ (NSString*)getTrainingModelPath { + NSBundle* bundle = [NSBundle bundleForClass:[ORTCheckpointTest class]]; + NSString* path = [[bundle resourcePath] stringByAppendingPathComponent:@"training_model.onnx"]; + return path; +} + +- (void)testSaveCheckpoint { + NSError* error = nil; + ORTCheckpoint* checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTCheckpointTest getCheckpointPath] error:&error]; + ORTAssertNullableResultSuccessful(checkpoint, error); + + // save checkpoint + NSString* path = [test_utils::createTemporaryDirectory(self) stringByAppendingPathComponent:@"save_checkpoint.ckpt"]; + XCTAssertNotNil(path); + BOOL result = [checkpoint saveCheckpointToPath:path withOptimizerState:NO error:&error]; + + ORTAssertBoolResultSuccessful(result, error); +} + +- (void)testInitCheckpoint { + NSError* error = nil; + ORTCheckpoint* checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTCheckpointTest getCheckpointPath] error:&error]; + ORTAssertNullableResultSuccessful(checkpoint, error); +} + +- (void)testIntProperty { + NSError* error = nil; + // Load checkpoint + ORTCheckpoint* checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTCheckpointTest getCheckpointPath] error:&error]; + ORTAssertNullableResultSuccessful(checkpoint, error); + + // Add property + BOOL result = [checkpoint addIntPropertyWithName:@"test" value:314 error:&error]; + ORTAssertBoolResultSuccessful(result, error); + + // Get property + int64_t value = [checkpoint getIntPropertyWithName:@"test" error:&error]; + XCTAssertEqual(value, 314); +} + +- (void)testFloatProperty { + NSError* error = nil; + // Load checkpoint + ORTCheckpoint* checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTCheckpointTest getCheckpointPath] error:&error]; + ORTAssertNullableResultSuccessful(checkpoint, error); + + // Add property + BOOL result = [checkpoint addFloatPropertyWithName:@"test" value:3.14f error:&error]; + ORTAssertBoolResultSuccessful(result, error); + + // Get property + float value = [checkpoint getFloatPropertyWithName:@"test" error:&error]; + XCTAssertEqual(value, 3.14f); +} + +- (void)testStringProperty { + NSError* error = nil; + // Load checkpoint + ORTCheckpoint* checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTCheckpointTest getCheckpointPath] error:&error]; + ORTAssertNullableResultSuccessful(checkpoint, error); + + // Add property + BOOL result = [checkpoint addStringPropertyWithName:@"test" value:@"hello" error:&error]; + ORTAssertBoolResultSuccessful(result, error); + + // Get property + NSString* value = [checkpoint getStringPropertyWithName:@"test" error:&error]; + XCTAssertEqualObjects(value, @"hello"); +} + +- (void)tearDown { + _ortEnv = nil; + + [super tearDown]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_env_test.mm b/objectivec/test/ort_env_test.mm new file mode 100644 index 0000000..420f55f --- /dev/null +++ b/objectivec/test/ort_env_test.mm @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_env.h" + +#import "test/assertion_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTEnvTest : XCTestCase +@end + +@implementation ORTEnvTest +- (void)testGetOrtVersion { + NSString* ver = ORTVersion(); + XCTAssertNotNil(ver); +} + +- (void)testInitOk { + NSError* err = nil; + ORTEnv* env = [[ORTEnv alloc] initWithLoggingLevel:ORTLoggingLevelWarning + error:&err]; + ORTAssertNullableResultSuccessful(env, err); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_session_test.mm b/objectivec/test/ort_session_test.mm new file mode 100644 index 0000000..57b92fd --- /dev/null +++ b/objectivec/test/ort_session_test.mm @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_coreml_execution_provider.h" +#import "ort_xnnpack_execution_provider.h" +#import "ort_env.h" +#import "ort_session.h" +#import "ort_value.h" + +#import "test/assertion_utils.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTSessionTest : XCTestCase + +@property(readonly, nullable) ORTEnv* ortEnv; + +@end + +@implementation ORTSessionTest + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; + + NSError* err = nil; + _ortEnv = [[ORTEnv alloc] initWithLoggingLevel:ORTLoggingLevelWarning + error:&err]; + ORTAssertNullableResultSuccessful(_ortEnv, err); +} + +- (void)tearDown { + _ortEnv = nil; + + [super tearDown]; +} + +// model with an Add op +// inputs: A, B +// output: C = A + B ++ (NSString*)getAddModelPath { + NSBundle* bundle = [NSBundle bundleForClass:[ORTSessionTest class]]; + NSString* path = [bundle pathForResource:@"single_add.basic" + ofType:@"ort"]; + return path; +} + ++ (NSMutableData*)dataWithScalarFloat:(float)value { + NSMutableData* data = [[NSMutableData alloc] initWithBytes:&value length:sizeof(value)]; + return data; +} + ++ (ORTValue*)ortValueWithScalarFloatData:(NSMutableData*)data { + NSArray* shape = @[ @1 ]; + NSError* err = nil; + ORTValue* ortValue = [[ORTValue alloc] initWithTensorData:data + elementType:ORTTensorElementDataTypeFloat + shape:shape + error:&err]; + ORTAssertNullableResultSuccessful(ortValue, err); + return ortValue; +} + ++ (ORTSessionOptions*)makeSessionOptions { + NSError* err = nil; + ORTSessionOptions* sessionOptions = [[ORTSessionOptions alloc] initWithError:&err]; + ORTAssertNullableResultSuccessful(sessionOptions, err); + return sessionOptions; +} + ++ (ORTRunOptions*)makeRunOptions { + NSError* err = nil; + ORTRunOptions* runOptions = [[ORTRunOptions alloc] initWithError:&err]; + ORTAssertNullableResultSuccessful(runOptions, err); + return runOptions; +} + +- (void)testInitAndRunWithPreallocatedOutputOk { + NSMutableData* aData = [ORTSessionTest dataWithScalarFloat:1.0f]; + NSMutableData* bData = [ORTSessionTest dataWithScalarFloat:2.0f]; + NSMutableData* cData = [ORTSessionTest dataWithScalarFloat:0.0f]; + + ORTValue* a = [ORTSessionTest ortValueWithScalarFloatData:aData]; + ORTValue* b = [ORTSessionTest ortValueWithScalarFloatData:bData]; + ORTValue* c = [ORTSessionTest ortValueWithScalarFloatData:cData]; + + NSError* err = nil; + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:[ORTSessionTest makeSessionOptions] + error:&err]; + ORTAssertNullableResultSuccessful(session, err); + + BOOL runResult = [session runWithInputs:@{@"A" : a, @"B" : b} + outputs:@{@"C" : c} + runOptions:[ORTSessionTest makeRunOptions] + error:&err]; + ORTAssertBoolResultSuccessful(runResult, err); + + const float cExpected = 3.0f; + float cActual; + memcpy(&cActual, cData.bytes, sizeof(float)); + XCTAssertEqual(cActual, cExpected); +} + +- (void)testInitAndRunOk { + NSMutableData* aData = [ORTSessionTest dataWithScalarFloat:1.0f]; + NSMutableData* bData = [ORTSessionTest dataWithScalarFloat:2.0f]; + + ORTValue* a = [ORTSessionTest ortValueWithScalarFloatData:aData]; + ORTValue* b = [ORTSessionTest ortValueWithScalarFloatData:bData]; + + NSError* err = nil; + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:[ORTSessionTest makeSessionOptions] + error:&err]; + ORTAssertNullableResultSuccessful(session, err); + + NSDictionary* outputs = + [session runWithInputs:@{@"A" : a, @"B" : b} + outputNames:[NSSet setWithArray:@[ @"C" ]] + runOptions:[ORTSessionTest makeRunOptions] + error:&err]; + ORTAssertNullableResultSuccessful(outputs, err); + + ORTValue* cOutput = outputs[@"C"]; + XCTAssertNotNil(cOutput); + + NSData* cData = [cOutput tensorDataWithError:&err]; + ORTAssertNullableResultSuccessful(cData, err); + + const float cExpected = 3.0f; + float cActual; + memcpy(&cActual, cData.bytes, sizeof(float)); + XCTAssertEqual(cActual, cExpected); +} + +- (void)testGetNamesOk { + NSError* err = nil; + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:[ORTSessionTest makeSessionOptions] + error:&err]; + ORTAssertNullableResultSuccessful(session, err); + + NSArray* inputNames = [session inputNamesWithError:&err]; + ORTAssertNullableResultSuccessful(inputNames, err); + XCTAssertEqualObjects(inputNames, (@[ @"A", @"B" ])); + + NSArray* overridableInitializerNames = [session overridableInitializerNamesWithError:&err]; + ORTAssertNullableResultSuccessful(overridableInitializerNames, err); + XCTAssertEqualObjects(overridableInitializerNames, (@[])); + + NSArray* outputNames = [session outputNamesWithError:&err]; + ORTAssertNullableResultSuccessful(outputNames, err); + XCTAssertEqualObjects(outputNames, (@[ @"C" ])); +} + +- (void)testInitFailsWithInvalidPath { + NSString* invalidModelPath = @"invalid/path/to/model.ort"; + NSError* err = nil; + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:invalidModelPath + sessionOptions:[ORTSessionTest makeSessionOptions] + error:&err]; + ORTAssertNullableResultUnsuccessful(session, err); +} + +- (void)testRunFailsWithInvalidInput { + NSMutableData* dData = [ORTSessionTest dataWithScalarFloat:1.0f]; + NSMutableData* cData = [ORTSessionTest dataWithScalarFloat:0.0f]; + + ORTValue* d = [ORTSessionTest ortValueWithScalarFloatData:dData]; + ORTValue* c = [ORTSessionTest ortValueWithScalarFloatData:cData]; + + NSError* err = nil; + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:[ORTSessionTest makeSessionOptions] + error:&err]; + ORTAssertNullableResultSuccessful(session, err); + + BOOL runResult = [session runWithInputs:@{@"D" : d} + outputs:@{@"C" : c} + runOptions:[ORTSessionTest makeRunOptions] + error:&err]; + ORTAssertBoolResultUnsuccessful(runResult, err); +} + +- (void)testAppendCoreMLEP { + NSError* err = nil; + ORTSessionOptions* sessionOptions = [ORTSessionTest makeSessionOptions]; + ORTCoreMLExecutionProviderOptions* coreMLOptions = [[ORTCoreMLExecutionProviderOptions alloc] init]; + coreMLOptions.enableOnSubgraphs = YES; // set an arbitrary option + + BOOL appendResult = [sessionOptions appendCoreMLExecutionProviderWithOptions:coreMLOptions + error:&err]; + + if (!ORTIsCoreMLExecutionProviderAvailable()) { + ORTAssertBoolResultUnsuccessful(appendResult, err); + return; + } + + ORTAssertBoolResultSuccessful(appendResult, err); + + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:sessionOptions + error:&err]; + ORTAssertNullableResultSuccessful(session, err); +} + +- (void)testAppendXnnpackEP { + NSError* err = nil; + ORTSessionOptions* sessionOptions = [ORTSessionTest makeSessionOptions]; + ORTXnnpackExecutionProviderOptions* XnnpackOptions = [[ORTXnnpackExecutionProviderOptions alloc] init]; + XnnpackOptions.intra_op_num_threads = 2; + + BOOL appendResult = [sessionOptions appendXnnpackExecutionProviderWithOptions:XnnpackOptions + error:&err]; + // Without xnnpack EP in building also can pass the test + NSString* err_msg = [err localizedDescription]; + if (!appendResult && [err_msg containsString:@"XNNPACK execution provider is not supported in this build. "]) { + return; + } + + ORTAssertBoolResultSuccessful(appendResult, err); + + ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv + modelPath:[ORTSessionTest getAddModelPath] + sessionOptions:sessionOptions + error:&err]; + ORTAssertNullableResultSuccessful(session, err); +} + +static bool gDummyRegisterCustomOpsFnCalled = false; + +static OrtStatus* _Nullable DummyRegisterCustomOpsFn(OrtSessionOptions* /*session_options*/, + const OrtApiBase* /*api*/) { + gDummyRegisterCustomOpsFnCalled = true; + return nullptr; +} + +- (void)testRegisterCustomOpsUsingFunctionPointer { + NSError* err = nil; + ORTSessionOptions* sessionOptions = [ORTSessionTest makeSessionOptions]; + + gDummyRegisterCustomOpsFnCalled = false; + BOOL registerResult = [sessionOptions registerCustomOpsUsingFunctionPointer:&DummyRegisterCustomOpsFn + error:&err]; + ORTAssertBoolResultSuccessful(registerResult, err); + + XCTAssertEqual(gDummyRegisterCustomOpsFnCalled, true); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_training_session_test.mm b/objectivec/test/ort_training_session_test.mm new file mode 100644 index 0000000..683965d --- /dev/null +++ b/objectivec/test/ort_training_session_test.mm @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_checkpoint.h" +#import "ort_training_session.h" +#import "ort_env.h" +#import "ort_session.h" +#import "ort_value.h" + +#import "test/test_utils.h" +#import "test/assertion_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTTrainingSessionTest : XCTestCase +@property(readonly, nullable) ORTEnv* ortEnv; +@property(readonly, nullable) ORTCheckpoint* checkpoint; +@property(readonly, nullable) ORTTrainingSession* session; +@end + +@implementation ORTTrainingSessionTest + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; + + NSError* err = nil; + _ortEnv = [[ORTEnv alloc] initWithLoggingLevel:ORTLoggingLevelWarning + error:&err]; + ORTAssertNullableResultSuccessful(_ortEnv, err); + _checkpoint = [[ORTCheckpoint alloc] initWithPath:[ORTTrainingSessionTest + getFilePathFromName:@"checkpoint.ckpt"] + error:&err]; + ORTAssertNullableResultSuccessful(_checkpoint, err); + _session = [self makeTrainingSessionWithCheckpoint:_checkpoint]; +} + ++ (NSString*)getFilePathFromName:(NSString*)name { + NSBundle* bundle = [NSBundle bundleForClass:[ORTTrainingSessionTest class]]; + NSString* path = [[bundle resourcePath] stringByAppendingPathComponent:name]; + return path; +} + ++ (NSMutableData*)loadTensorDataFromFile:(NSString*)filePath skipHeader:(BOOL)skipHeader { + NSError* error = nil; + NSString* fileContents = [NSString stringWithContentsOfFile:filePath + encoding:NSUTF8StringEncoding + error:&error]; + ORTAssertNullableResultSuccessful(fileContents, error); + + NSArray* lines = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + + if (skipHeader) { + lines = [lines subarrayWithRange:NSMakeRange(1, lines.count - 1)]; + } + + NSArray* dataArray = [lines[0] componentsSeparatedByCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString:@",[] "]]; + NSMutableData* tensorData = [NSMutableData data]; + + for (NSString* str in dataArray) { + if (str.length > 0) { + float value = [str floatValue]; + [tensorData appendBytes:&value length:sizeof(float)]; + } + } + + return tensorData; +} + +- (ORTTrainingSession*)makeTrainingSessionWithCheckpoint:(ORTCheckpoint*)checkpoint { + NSError* error = nil; + ORTSessionOptions* sessionOptions = [[ORTSessionOptions alloc] initWithError:&error]; + ORTAssertNullableResultSuccessful(sessionOptions, error); + + ORTTrainingSession* session = [[ORTTrainingSession alloc] + initWithEnv:self.ortEnv + sessionOptions:sessionOptions + checkpoint:checkpoint + trainModelPath:[ORTTrainingSessionTest getFilePathFromName:@"training_model.onnx"] + evalModelPath:[ORTTrainingSessionTest getFilePathFromName:@"eval_model.onnx"] + optimizerModelPath:[ORTTrainingSessionTest getFilePathFromName:@"adamw.onnx"] + error:&error]; + + ORTAssertNullableResultSuccessful(session, error); + return session; +} + +- (void)testInitTrainingSession { + NSError* error = nil; + + // check that inputNames contains input-0 + NSArray* inputNames = [self.session getTrainInputNamesWithError:&error]; + ORTAssertNullableResultSuccessful(inputNames, error); + + XCTAssertTrue(inputNames.count > 0); + XCTAssertTrue([inputNames containsObject:@"input-0"]); + + // check that outNames contains onnx::loss::21273 + NSArray* outputNames = [self.session getTrainOutputNamesWithError:&error]; + ORTAssertNullableResultSuccessful(outputNames, error); + + XCTAssertTrue(outputNames.count > 0); + XCTAssertTrue([outputNames containsObject:@"onnx::loss::21273"]); +} + +- (void)testInitTrainingSessionWithEval { + NSError* error = nil; + + // check that inputNames contains input-0 + NSArray* inputNames = [self.session getEvalInputNamesWithError:&error]; + ORTAssertNullableResultSuccessful(inputNames, error); + + XCTAssertTrue(inputNames.count > 0); + XCTAssertTrue([inputNames containsObject:@"input-0"]); + + // check that outNames contains onnx::loss::21273 + NSArray* outputNames = [self.session getEvalOutputNamesWithError:&error]; + ORTAssertNullableResultSuccessful(outputNames, error); + + XCTAssertTrue(outputNames.count > 0); + XCTAssertTrue([outputNames containsObject:@"onnx::loss::21273"]); +} + +- (void)runTrainStep { + // load input and expected output + NSError* error = nil; + NSMutableData* expectedOutput = [ORTTrainingSessionTest loadTensorDataFromFile:[ORTTrainingSessionTest + getFilePathFromName:@"loss_1.out"] + skipHeader:YES]; + + NSMutableData* input = [ORTTrainingSessionTest loadTensorDataFromFile:[ORTTrainingSessionTest + getFilePathFromName:@"input-0.in"] + skipHeader:YES]; + + int32_t labels[] = {1, 1}; + + // create ORTValue array for input and labels + NSMutableArray* inputValues = [NSMutableArray array]; + + ORTValue* inputTensor = [[ORTValue alloc] initWithTensorData:input + elementType:ORTTensorElementDataTypeFloat + shape:@[ @2, @784 ] + error:&error]; + ORTAssertNullableResultSuccessful(inputTensor, error); + [inputValues addObject:inputTensor]; + + ORTValue* labelTensor = [[ORTValue alloc] initWithTensorData:[NSMutableData dataWithBytes:labels + length:sizeof(labels)] + elementType:ORTTensorElementDataTypeInt32 + shape:@[ @2 ] + error:&error]; + + ORTAssertNullableResultSuccessful(labelTensor, error); + [inputValues addObject:labelTensor]; + + NSArray* outputs = [self.session trainStepWithInputValues:inputValues error:&error]; + ORTAssertNullableResultSuccessful(outputs, error); + XCTAssertTrue(outputs.count > 0); + + BOOL result = [self.session lazyResetGradWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + + outputs = [self.session trainStepWithInputValues:inputValues error:&error]; + ORTAssertNullableResultSuccessful(outputs, error); + XCTAssertTrue(outputs.count > 0); + + ORTValue* outputValue = outputs[0]; + ORTValueTypeInfo* typeInfo = [outputValue typeInfoWithError:&error]; + ORTAssertNullableResultSuccessful(typeInfo, error); + XCTAssertEqual(typeInfo.type, ORTValueTypeTensor); + XCTAssertNotNil(typeInfo.tensorTypeAndShapeInfo); + + ORTTensorTypeAndShapeInfo* tensorInfo = [outputValue tensorTypeAndShapeInfoWithError:&error]; + ORTAssertNullableResultSuccessful(tensorInfo, error); + XCTAssertEqual(tensorInfo.elementType, ORTTensorElementDataTypeFloat); + + NSMutableData* tensorData = [outputValue tensorDataWithError:&error]; + ORTAssertNullableResultSuccessful(tensorData, error); + ORTAssertEqualFloatArrays(test_utils::getFloatArrayFromData(tensorData), + test_utils::getFloatArrayFromData(expectedOutput)); +} + +- (void)testTrainStepOutput { + [self runTrainStep]; +} + +- (void)testOptimizerStep { + // load input and expected output + NSError* error = nil; + NSMutableData* expectedOutput1 = [ORTTrainingSessionTest loadTensorDataFromFile:[ORTTrainingSessionTest + getFilePathFromName:@"loss_1.out"] + skipHeader:YES]; + + NSMutableData* expectedOutput2 = [ORTTrainingSessionTest loadTensorDataFromFile:[ORTTrainingSessionTest + getFilePathFromName:@"loss_2.out"] + skipHeader:YES]; + + NSMutableData* input = [ORTTrainingSessionTest loadTensorDataFromFile:[ORTTrainingSessionTest + getFilePathFromName:@"input-0.in"] + skipHeader:YES]; + + int32_t labels[] = {1, 1}; + + // create ORTValue array for input and labels + NSMutableArray* inputValues = [NSMutableArray array]; + + ORTValue* inputTensor = [[ORTValue alloc] initWithTensorData:input + elementType:ORTTensorElementDataTypeFloat + shape:@[ @2, @784 ] + error:&error]; + ORTAssertNullableResultSuccessful(inputTensor, error); + [inputValues addObject:inputTensor]; + + ORTValue* labelTensor = [[ORTValue alloc] initWithTensorData:[NSMutableData dataWithBytes:labels + length:sizeof(labels)] + elementType:ORTTensorElementDataTypeInt32 + shape:@[ @2 ] + error:&error]; + ORTAssertNullableResultSuccessful(labelTensor, error); + [inputValues addObject:labelTensor]; + + // run train step, optimizer steps and check loss + NSArray* outputs = [self.session trainStepWithInputValues:inputValues error:&error]; + ORTAssertNullableResultSuccessful(outputs, error); + + NSMutableData* loss = [outputs[0] tensorDataWithError:&error]; + ORTAssertNullableResultSuccessful(loss, error); + ORTAssertEqualFloatArrays(test_utils::getFloatArrayFromData(loss), + test_utils::getFloatArrayFromData(expectedOutput1)); + + BOOL result = [self.session lazyResetGradWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + + outputs = [self.session trainStepWithInputValues:inputValues error:&error]; + ORTAssertNullableResultSuccessful(outputs, error); + + loss = [outputs[0] tensorDataWithError:&error]; + ORTAssertNullableResultSuccessful(loss, error); + ORTAssertEqualFloatArrays(test_utils::getFloatArrayFromData(loss), + test_utils::getFloatArrayFromData(expectedOutput1)); + + result = [self.session optimizerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + + outputs = [self.session trainStepWithInputValues:inputValues error:&error]; + ORTAssertNullableResultSuccessful(outputs, error); + loss = [outputs[0] tensorDataWithError:&error]; + ORTAssertNullableResultSuccessful(loss, error); + ORTAssertEqualFloatArrays(test_utils::getFloatArrayFromData(loss), + test_utils::getFloatArrayFromData(expectedOutput2)); +} + +- (void)testSetLearningRate { + NSError* error = nil; + + float learningRate = 0.1f; + BOOL result = [self.session setLearningRate:learningRate error:&error]; + ORTAssertBoolResultSuccessful(result, error); + + float actualLearningRate = [self.session getLearningRateWithError:&error]; + ORTAssertEqualFloatAndNoError(learningRate, actualLearningRate, error); +} + +- (void)testLinearLRScheduler { + NSError* error = nil; + + float learningRate = 0.1f; + BOOL result = [self.session registerLinearLRSchedulerWithWarmupStepCount:2 + totalStepCount:4 + initialLr:learningRate + error:&error]; + + ORTAssertBoolResultSuccessful(result, error); + + [self runTrainStep]; + + result = [self.session optimizerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + result = [self.session schedulerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + ORTAssertEqualFloatAndNoError(0.05f, [self.session getLearningRateWithError:&error], error); + + result = [self.session optimizerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + result = [self.session schedulerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + ORTAssertEqualFloatAndNoError(0.1f, [self.session getLearningRateWithError:&error], error); + + result = [self.session optimizerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + result = [self.session schedulerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + ORTAssertEqualFloatAndNoError(0.05f, [self.session getLearningRateWithError:&error], error); + + result = [self.session optimizerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + result = [self.session schedulerStepWithError:&error]; + ORTAssertBoolResultSuccessful(result, error); + ORTAssertEqualFloatAndNoError(0.0f, [self.session getLearningRateWithError:&error], error); +} + +- (void)testExportModelForInference { + NSError* error = nil; + + NSString* inferenceModelPath = [test_utils::createTemporaryDirectory(self) + stringByAppendingPathComponent:@"inference_model.onnx"]; + XCTAssertNotNil(inferenceModelPath); + + NSArray* graphOutputNames = [NSArray arrayWithObjects:@"output-0", nil]; + + BOOL result = [self.session exportModelForInferenceWithOutputPath:inferenceModelPath + graphOutputNames:graphOutputNames + error:&error]; + + ORTAssertBoolResultSuccessful(result, error); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:inferenceModelPath]); + + [self addTeardownBlock:^{ + NSError* error = nil; + [[NSFileManager defaultManager] removeItemAtPath:inferenceModelPath error:&error]; + }]; +} + +- (void)testToBuffer { + NSError* error = nil; + ORTValue* buffer = [self.session toBufferWithTrainable:YES error:&error]; + ORTAssertNullableResultSuccessful(buffer, error); + + ORTValueTypeInfo* typeInfo = [buffer typeInfoWithError:&error]; + ORTAssertNullableResultSuccessful(typeInfo, error); + XCTAssertEqual(typeInfo.type, ORTValueTypeTensor); + XCTAssertNotNil(typeInfo.tensorTypeAndShapeInfo); +} + +- (void)testFromBuffer { + NSError* error = nil; + + ORTValue* buffer = [self.session toBufferWithTrainable:YES error:&error]; + ORTAssertNullableResultSuccessful(buffer, error); + + BOOL result = [self.session fromBufferWithValue:buffer error:&error]; + ORTAssertBoolResultSuccessful(result, error); +} + +- (void)tearDown { + _session = nil; + _checkpoint = nil; + _ortEnv = nil; + + [super tearDown]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_training_utils_test.mm b/objectivec/test/ort_training_utils_test.mm new file mode 100644 index 0000000..1695ddc --- /dev/null +++ b/objectivec/test/ort_training_utils_test.mm @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import +#import "ort_training_session.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTTrainingUtilsTest : XCTestCase +@end + +@implementation ORTTrainingUtilsTest + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; +} + +- (void)testSetSeed { + ORTSetSeed(2718); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/ort_value_test.mm b/objectivec/test/ort_value_test.mm new file mode 100644 index 0000000..734ad39 --- /dev/null +++ b/objectivec/test/ort_value_test.mm @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "ort_value.h" + +#include + +#import "test/assertion_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORTValueTest : XCTestCase +@end + +@implementation ORTValueTest + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; +} + +- (void)testInitTensorOk { + int32_t value = 42; + NSMutableData* data = [[NSMutableData alloc] initWithBytes:&value + length:sizeof(int32_t)]; + NSArray* shape = @[ @1 ]; + + const ORTTensorElementDataType elementType = ORTTensorElementDataTypeInt32; + + NSError* err = nil; + ORTValue* ortValue = [[ORTValue alloc] initWithTensorData:data + elementType:elementType + shape:shape + error:&err]; + ORTAssertNullableResultSuccessful(ortValue, err); + + auto checkTensorInfo = [&](ORTTensorTypeAndShapeInfo* tensorInfo) { + XCTAssertEqual(tensorInfo.elementType, elementType); + XCTAssertEqualObjects(tensorInfo.shape, shape); + }; + + ORTValueTypeInfo* typeInfo = [ortValue typeInfoWithError:&err]; + ORTAssertNullableResultSuccessful(typeInfo, err); + XCTAssertEqual(typeInfo.type, ORTValueTypeTensor); + XCTAssertNotNil(typeInfo.tensorTypeAndShapeInfo); + checkTensorInfo(typeInfo.tensorTypeAndShapeInfo); + + ORTTensorTypeAndShapeInfo* tensorInfo = [ortValue tensorTypeAndShapeInfoWithError:&err]; + ORTAssertNullableResultSuccessful(tensorInfo, err); + checkTensorInfo(tensorInfo); + + NSData* actualData = [ortValue tensorDataWithError:&err]; + ORTAssertNullableResultSuccessful(actualData, err); + XCTAssertEqual(actualData.length, sizeof(int32_t)); + int32_t actualValue; + memcpy(&actualValue, actualData.bytes, sizeof(int32_t)); + XCTAssertEqual(actualValue, value); +} + +- (void)testInitTensorFailsWithDataSmallerThanShape { + std::vector values{1, 2, 3, 4}; + NSMutableData* data = [[NSMutableData alloc] initWithBytes:values.data() + length:values.size() * sizeof(int32_t)]; + NSArray* shape = @[ @2, @3 ]; // too large + + NSError* err = nil; + ORTValue* ortValue = [[ORTValue alloc] initWithTensorData:data + elementType:ORTTensorElementDataTypeInt32 + shape:shape + error:&err]; + ORTAssertNullableResultUnsuccessful(ortValue, err); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/test_utils.h b/objectivec/test/test_utils.h new file mode 100644 index 0000000..8a5e6e4 --- /dev/null +++ b/objectivec/test/test_utils.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import +#import + +#import "ort_session.h" +#import "ort_env.h" +#import "ort_value.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace test_utils { + +NSString* _Nullable createTemporaryDirectory(XCTestCase* testCase); + +NSArray* getFloatArrayFromData(NSData* data); + +} // namespace test_utils + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/test_utils.mm b/objectivec/test/test_utils.mm new file mode 100644 index 0000000..ef8e8c5 --- /dev/null +++ b/objectivec/test/test_utils.mm @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "test_utils.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace test_utils { + +NSString* createTemporaryDirectory(XCTestCase* testCase) { + NSString* temporaryDirectory = NSTemporaryDirectory(); + NSString* directoryPath = [temporaryDirectory stringByAppendingPathComponent:@"ort-objective-c-test"]; + + NSError* error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + + XCTAssertNil(error, @"Error creating temporary directory: %@", error.localizedDescription); + + // add teardown block to delete the temporary directory + [testCase addTeardownBlock:^{ + NSError* error = nil; + [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:&error]; + XCTAssertNil(error, @"Error removing temporary directory: %@", error.localizedDescription); + }]; + + return directoryPath; +} + +NSArray* getFloatArrayFromData(NSData* data) { + NSMutableArray* array = [NSMutableArray array]; + float value; + for (size_t i = 0; i < data.length / sizeof(float); ++i) { + [data getBytes:&value range:NSMakeRange(i * sizeof(float), sizeof(float))]; + [array addObject:[NSNumber numberWithFloat:value]]; + } + return array; +} + +} // namespace test_utils + +NS_ASSUME_NONNULL_END diff --git a/objectivec/test/testdata/gen_models.sh b/objectivec/test/testdata/gen_models.sh new file mode 100755 index 0000000..0a58ca8 --- /dev/null +++ b/objectivec/test/testdata/gen_models.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Get directory this script is in +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +cd ${DIR} + +python3 ./single_add_gen.py + +ORT_CONVERT_ONNX_MODELS_TO_ORT_OPTIMIZATION_LEVEL=basic python3 -m onnxruntime.tools.convert_onnx_models_to_ort --optimization_style=Fixed . diff --git a/objectivec/test/testdata/single_add.basic.ort b/objectivec/test/testdata/single_add.basic.ort new file mode 100644 index 0000000000000000000000000000000000000000..f622784b35366ea8f0c33ccbdb513b5d6f7df7c1 GIT binary patch literal 1296 zcmaJ=L2DCH5T59w+gK&qP=ZMC5X6H#a!@S6gSGW228qz?vUxTO&D*eD(+Gkdq9fHT2w9Z|5JC$eX^YH( zlR%{XYq5Zk8bCRK-6(YKQNIB6HiyIBeUTjJWFiO82f%rt3)}>_&xlYiY8;GOSmb7$ z&l6~_CL&tWWB~H``e>1B;n$lD6g595VDqaf9Yo#GIDe}^8Mfg=^Z{}(59JBa!5-}v zK)DTw)V0wS&J3ry_RPrw0VxVu)->>nX{L6ljZwugT zn6K>BBj`Na1Xh4|F#13TU_ZV9N5By10*w3i+xH!++T=^KT{C%=r&&BQQEna2_~=2S zi*eV1I31gD5@+dt`XV=WHnCYUwl0tMa9HtY5MwR_r($65RQ%|}{~$_C>fFpsGCNJ| zfyuKdaf#c-k-R_S&w5M$s^44qU1dPA$jzB){(Y0U+-5G?Gj4Pc)8#w-`Bu5;%UPDZ nW85dyzl#&~J%&)fHONzUGA<@%sQ;t)_p@e%kMWx4xd-D9DJ`oI literal 0 HcmV?d00001 diff --git a/objectivec/test/testdata/single_add.onnx b/objectivec/test/testdata/single_add.onnx new file mode 100644 index 0000000000000000000000000000000000000000..82e8fe669e0d84b6111f678b0742a552d820269b GIT binary patch literal 93 zcmd;Jw+iMG=3;c@VssK>be3XvOi57!5kj27nR)3ssX%5FKTuwXi;IJUQHX_$iGvX& U;DjuY1Qc*a7I0$WVi4c~0E&7GKL7v# literal 0 HcmV?d00001 diff --git a/objectivec/test/testdata/single_add_gen.py b/objectivec/test/testdata/single_add_gen.py new file mode 100644 index 0000000..15b4372 --- /dev/null +++ b/objectivec/test/testdata/single_add_gen.py @@ -0,0 +1,19 @@ +import onnx +from onnx import TensorProto, helper + +graph = helper.make_graph( + [ # nodes + helper.make_node("Add", ["A", "B"], ["C"], "Add"), + ], + "SingleAdd", # name + [ # inputs + helper.make_tensor_value_info("A", TensorProto.FLOAT, [1]), + helper.make_tensor_value_info("B", TensorProto.FLOAT, [1]), + ], + [ # outputs + helper.make_tensor_value_info("C", TensorProto.FLOAT, [1]), + ], +) + +model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 12)]) +onnx.save(model, r"single_add.onnx") diff --git a/swift/.DS_Store b/swift/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f42f1cf4750f83663b0b9f590b707e97ea259a51 GIT binary patch literal 6148 zcmeHKyH3ME5S)b+k!Vt;ykE#4oUBks0zLo{M+hl6g%gR=9ly=&gD92*B?a1*cE`8g zxzkPI^#YLQ)UJR9fH_?epBkp7@6{)E6B!}WxyJ)e=y1dv-Y3=96Utp;Lso~65r2n< z`*dh6zM{tyc8o{#ZNJ^W91i>ITV^&DNCi@XR3H^d1%6RLdoOK%=a?}SNCi@XvjX~k zD0IadI5^s;gTY1s;*4oCKHDroESezJz`>CjnmCo{REZHooX&iSx*9k*IvoxNf~-a5IQ_S!;! srhgl2t(?JHG0|Ev7ut%K7j;FS`Mw4Yjz(wR=*0XHP+ihefxl4T3q58UfB*mh literal 0 HcmV?d00001 diff --git a/swift/OnnxRuntimeBindingsTests/.DS_Store b/swift/OnnxRuntimeBindingsTests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..2669ebf3d77440c8d1f59febf92f2a487412ef29 GIT binary patch literal 6148 zcmeHKF=_)r43rWV4sKkg+%NbCi*Y_+e;~w8XE?BNe^uUzna$xu`(T(FpVKGyP#Fip@s5j~Y;W&&KAb1j_Y>qk*_Ip|zVr9T zw_!NmCn+lhq<|EV0#ZN<{8|C_Uf6t*s3--bfE2hY!0$tY6MNy97@rP=7y*DYq{Fa| zSpwLY0QSN$5gC{#6_`}75yO*?e5<-%I3_0DyoL|2lf5PskJIt~7UkwWQBev=fm;P0 zbGu~!e}Vrn|KF0dlLAuUUn$_T&2F>Cm8!Q+F2`Qm;IDAje8Op12L&P8F)-ROHf+bw cQIvI!Ydr6TV`9*e4?0jk1Jp$(1^!xrFAN41od5s; literal 0 HcmV?d00001 diff --git a/swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort b/swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort new file mode 100644 index 0000000000000000000000000000000000000000..f622784b35366ea8f0c33ccbdb513b5d6f7df7c1 GIT binary patch literal 1296 zcmaJ=L2DCH5T59w+gK&qP=ZMC5X6H#a!@S6gSGW228qz?vUxTO&D*eD(+Gkdq9fHT2w9Z|5JC$eX^YH( zlR%{XYq5Zk8bCRK-6(YKQNIB6HiyIBeUTjJWFiO82f%rt3)}>_&xlYiY8;GOSmb7$ z&l6~_CL&tWWB~H``e>1B;n$lD6g595VDqaf9Yo#GIDe}^8Mfg=^Z{}(59JBa!5-}v zK)DTw)V0wS&J3ry_RPrw0VxVu)->>nX{L6ljZwugT zn6K>BBj`Na1Xh4|F#13TU_ZV9N5By10*w3i+xH!++T=^KT{C%=r&&BQQEna2_~=2S zi*eV1I31gD5@+dt`XV=WHnCYUwl0tMa9HtY5MwR_r($65RQ%|}{~$_C>fFpsGCNJ| zfyuKdaf#c-k-R_S&w5M$s^44qU1dPA$jzB){(Y0U+-5G?Gj4Pc)8#w-`Bu5;%UPDZ nW85dyzl#&~J%&)fHONzUGA<@%sQ;t)_p@e%kMWx4xd-D9DJ`oI literal 0 HcmV?d00001 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) + } + } +}