From e4070161c8a5908f66a8331e3ce42bc4b2896320 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 16 Jan 2025 13:21:53 +0100 Subject: [PATCH 01/25] wip --- .../ExecutorchLib/Exported/ETModel.h | 8 ++ .../ExecutorchLib/Exported/ETModel.mm | 27 +++++++ .../ios/ExecutorchLib/ExecutorchLib/Utils.hpp | 76 ++++++++++++++++--- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h index 8ac3b477e2..cc3a4379f3 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h @@ -11,6 +11,14 @@ - (NSArray *)forward:(NSArray *)input shape:(NSArray *)shape inputType:(NSNumber *)inputType; +- (NSArray *)forward:(NSArray *)inputs + shapes:(NSArray *)shapes + inputTypes: (NSArray *)inputTypes; +- (NSArray *)forwardTemporary:(NSArray *)input + firstShape:(NSArray *)firstShape + secondShape:(NSArray *)secondShape + firstInputType:(NSNumber *)firstInputType + secondInputType:(NSNumber *)secondInputType; - (NSNumber *)getNumberOfInputs; - (NSNumber *)getInputType:(NSNumber *)index; - (NSArray *)getInputShape:(NSNumber *)index; diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index d975b184b8..e265ba6029 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -161,6 +161,32 @@ - (NSNumber *) getTypeAsNumber:(ScalarType)scalarType { } } +- (NSArray *)forward:(NSArray *)inputs + shapes:(NSArray *)shapes + inputTypes: (NSArray *)inputTypes { + std::vector> shapesVec; + std::vector modelInputVec; + + for (NSArray *shape in shapes) { + shapesVec.push_back(NSArrayToIntVector(shape)); + }; + + for (NSUInteger i = 0; i < [inputs count]; i++) { + InputType inputType = (InputType)[[inputTypes objectAtIndex:i] intValue]; + TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], shapesVec[i], inputType); + Error err = _model->set_input("forward", {currentTensor}, i); + if (!(err == Error::Ok)) { + @throw [NSException + exceptionWithName:@"forward_error" + reason:[NSString stringWithFormat:@"%ld", (long)err] + userInfo:nil]; + } + } + + auto result = _model->execute("forward")->at(0).toTensor().const_data_ptr(); + return result; // TODO: cast to NSArray +} + - (NSArray *)forward:(NSArray *)input shape:(NSArray *)shape inputType:(NSNumber *)inputType { @@ -209,4 +235,5 @@ - (NSArray *)forward:(NSArray *)input userInfo:nil]; } + @end diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp index 095c68baab..9ef5edb9a0 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp @@ -1,13 +1,14 @@ #ifndef Utils_hpp #define Utils_hpp +#import "InputType.h" #include #include #include #include +#include #include #include -#include #ifdef __OBJC__ #import @@ -21,13 +22,17 @@ template T getValueFromNSNumber(NSNumber *number) { return static_cast([number charValue]); // `charValue` for 8-bit integers } else if constexpr (std::is_same::value) { return static_cast([number intValue]); // `intValue` for 32-bit integers - } else if constexpr (std::is_same::value) { + } else if constexpr (std::is_same::value || + std::is_same::value) { return static_cast( - [number longLongValue]); // `longLongValue` for 64-bit integers + [number longLongValue]); // Use `longLongValue` for 64-bit integers } else if constexpr (std::is_same::value) { return static_cast([number floatValue]); } else if constexpr (std::is_same::value) { return static_cast([number doubleValue]); + } else { + static_assert(std::is_same::value, + "Unsupported type for getValueFromNSNumber"); } } @@ -48,6 +53,52 @@ std::unique_ptr NSArrayToTypedArray(NSArray *nsArray) { return typedArray; } +std::function getDeleterForInputType(InputType inputType) { + switch (inputType) { + case InputType::InputTypeInt8: + return [](void *ptr) { delete[] static_cast(ptr); }; + case InputType::InputTypeInt32: + return [](void *ptr) { delete[] static_cast(ptr); }; + case InputType::InputTypeInt64: + return [](void *ptr) { delete[] static_cast(ptr); }; + case InputType::InputTypeFloat32: + return [](void *ptr) { delete[] static_cast(ptr); }; + case InputType::InputTypeFloat64: + return [](void *ptr) { delete[] static_cast(ptr); }; + } +} + +ScalarType inputTypeToScalarType(InputType inputType) { + switch (inputType) { + case InputType::InputTypeInt8: + return ScalarType::Char; + case InputType::InputTypeInt32: + return ScalarType::Int; + case InputType::InputTypeInt64: + return ScalarType::Long; + case InputType::InputTypeFloat32: + return ScalarType::Float; + case InputType::InputTypeFloat64: + return ScalarType::Double; + default: + throw std::invalid_argument("Unknown InputType"); + } +} + +TensorPtr NSArrayToTensorPtr(NSArray *nsArray, std::vector shape, + InputType inputType) { + void *voidPointer = (__bridge void *)nsArray; + std::function deleter = getDeleterForInputType(inputType); + ScalarType inputScalarType = inputTypeToScalarType(inputType); + auto tensor = make_tensor_ptr( + shape, + voidPointer, + inputScalarType, + TensorShapeDynamism::DYNAMIC_BOUND, + deleter); + return tensor; +} + template NSArray *arrayToNSArray(const void *array, ssize_t numel) { const T *typedArray = static_cast(array); @@ -62,15 +113,15 @@ NSArray *arrayToNSArray(const void *array, ssize_t numel) { template NSArray *arrayToNSArray(const std::vector> &dataPtrVec) { - NSMutableArray *nsArray = [NSMutableArray array]; - for (const auto &span : dataPtrVec) { - NSMutableArray *innerArray = [NSMutableArray arrayWithCapacity:span.size()]; - for(auto x : span) { - [innerArray addObject:@(x)]; - } - [nsArray addObject:[innerArray copy]]; + NSMutableArray *nsArray = [NSMutableArray array]; + for (const auto &span : dataPtrVec) { + NSMutableArray *innerArray = [NSMutableArray arrayWithCapacity:span.size()]; + for (auto x : span) { + [innerArray addObject:@(x)]; } - return [nsArray copy]; + [nsArray addObject:[innerArray copy]]; + } + return [nsArray copy]; } std::vector NSArrayToIntVector(NSArray *inputArray) { @@ -100,7 +151,8 @@ runForwardFromNSArray(NSArray *inputArray, std::vector shapes, for (const auto ¤tResult : *result) { Tensor currentTensor = currentResult.toTensor(); - std::span currentSpan(currentTensor.const_data_ptr(), currentTensor.numel()); + std::span currentSpan(currentTensor.const_data_ptr(), + currentTensor.numel()); outputVec.push_back(std::move(currentSpan)); } return outputVec; From 1f4b8751f7b89fe739754d75a3cb26eb41b9c3a6 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 16 Jan 2025 13:22:51 +0100 Subject: [PATCH 02/25] chore: remove unused function --- .../ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h index cc3a4379f3..04852227ee 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h @@ -14,11 +14,6 @@ - (NSArray *)forward:(NSArray *)inputs shapes:(NSArray *)shapes inputTypes: (NSArray *)inputTypes; -- (NSArray *)forwardTemporary:(NSArray *)input - firstShape:(NSArray *)firstShape - secondShape:(NSArray *)secondShape - firstInputType:(NSNumber *)firstInputType - secondInputType:(NSNumber *)secondInputType; - (NSNumber *)getNumberOfInputs; - (NSNumber *)getInputType:(NSNumber *)index; - (NSArray *)getInputShape:(NSNumber *)index; From b5970ea93c738aec3f1eb5f5d48b39793b7a3ea2 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 16 Jan 2025 13:26:03 +0100 Subject: [PATCH 03/25] refactor: remove unintended for loop and data structures --- .../ExecutorchLib/ExecutorchLib/Exported/ETModel.mm | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index e265ba6029..352ddc6f96 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -164,18 +164,15 @@ - (NSNumber *) getTypeAsNumber:(ScalarType)scalarType { - (NSArray *)forward:(NSArray *)inputs shapes:(NSArray *)shapes inputTypes: (NSArray *)inputTypes { - std::vector> shapesVec; - std::vector modelInputVec; - - for (NSArray *shape in shapes) { - shapesVec.push_back(NSArrayToIntVector(shape)); - }; for (NSUInteger i = 0; i < [inputs count]; i++) { + std::vector currentInputShape = NSArrayToIntVector([shapes objectAtIndex:i]); InputType inputType = (InputType)[[inputTypes objectAtIndex:i] intValue]; - TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], shapesVec[i], inputType); + + TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], currentInputShape, inputType); + Error err = _model->set_input("forward", {currentTensor}, i); - if (!(err == Error::Ok)) { + if (err != Error::Ok) { @throw [NSException exceptionWithName:@"forward_error" reason:[NSString stringWithFormat:@"%ld", (long)err] From f24d1bb2266959185d085f5147ba8f8f5a5229b3 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 16 Jan 2025 13:30:22 +0100 Subject: [PATCH 04/25] fix: add error handling & lint --- .../ExecutorchLib/Exported/ETModel.mm | 235 ++++++++++-------- 1 file changed, 131 insertions(+), 104 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index 352ddc6f96..88fb4c05e0 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -1,6 +1,6 @@ #import "ETModel.h" -#include "Utils.hpp" #include "InputType.h" +#include "Utils.hpp" #include #include #include @@ -34,58 +34,65 @@ - (NSNumber *)getNumberOfInputs { const auto method_meta = _model->method_meta("forward"); if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_number_of_inputs_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_number_of_inputs_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - + return @(method_meta->num_inputs()); } - (NSNumber *)getInputType:(NSNumber *)index { const auto method_meta = _model->method_meta("forward"); - if(!method_meta.ok()){ + if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_input_type_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_input_type_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - - const auto input_meta = method_meta->input_tensor_meta([index unsignedLongValue]); - if(!input_meta.ok()){ + + const auto input_meta = + method_meta->input_tensor_meta([index unsignedLongValue]); + if (!input_meta.ok()) { @throw [NSException - exceptionWithName:@"get_input_type_error" - reason:[NSString stringWithFormat:@"%ld", (long)input_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_input_type_error" + reason:[NSString + stringWithFormat:@"%ld", (long)input_meta.error()] + userInfo:nil]; } - + return [self getTypeAsNumber:input_meta->scalar_type()]; }; - (NSArray *)getInputShape:(NSNumber *)index { const auto method_meta = _model->method_meta("forward"); - if(!method_meta.ok()){ + if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_input_shape_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_input_shape_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - - const auto input_meta = method_meta->input_tensor_meta([index unsignedLongValue]); - if(!input_meta.ok()){ + + const auto input_meta = + method_meta->input_tensor_meta([index unsignedLongValue]); + if (!input_meta.ok()) { @throw [NSException - exceptionWithName:@"get_input_shape_error" - reason:[NSString stringWithFormat:@"%ld", (long)input_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_input_shape_error" + reason:[NSString + stringWithFormat:@"%ld", (long)input_meta.error()] + userInfo:nil]; } - + const auto shape = input_meta->sizes(); NSMutableArray *nsShape = [[NSMutableArray alloc] init]; - - for(int i = 0; i < shape.size(); i++) { + + for (int i = 0; i < shape.size(); i++) { [nsShape addObject:@(shape[i])]; } - + return [nsShape copy]; }; @@ -93,83 +100,97 @@ - (NSNumber *)getNumberOfOutputs { const auto method_meta = _model->method_meta("forward"); if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_number_of_outputs_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_number_of_outputs_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - + return @(method_meta->num_outputs()); } - (NSNumber *)getOutputType:(NSNumber *)index { const auto method_meta = _model->method_meta("forward"); - if(!method_meta.ok()){ + if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_output_type_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_output_type_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - - const auto output_meta = method_meta->output_tensor_meta([index unsignedLongValue]); - if(!output_meta.ok()){ + + const auto output_meta = + method_meta->output_tensor_meta([index unsignedLongValue]); + if (!output_meta.ok()) { @throw [NSException - exceptionWithName:@"get_output_type_error" - reason:[NSString stringWithFormat:@"%ld", (long)output_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_output_type_error" + reason:[NSString stringWithFormat:@"%ld", + (long)output_meta.error()] + userInfo:nil]; } - + return [self getTypeAsNumber:output_meta->scalar_type()]; }; - (NSArray *)getOutputShape:(NSNumber *)index { const auto method_meta = _model->method_meta("forward"); - if(!method_meta.ok()){ + if (!method_meta.ok()) { @throw [NSException - exceptionWithName:@"get_output_shape_error" - reason:[NSString stringWithFormat:@"%ld", (long)method_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_output_shape_error" + reason:[NSString stringWithFormat:@"%ld", + (long)method_meta.error()] + userInfo:nil]; } - - const auto output_meta = method_meta->output_tensor_meta([index unsignedLongValue]); - if(!output_meta.ok()){ + + const auto output_meta = + method_meta->output_tensor_meta([index unsignedLongValue]); + if (!output_meta.ok()) { @throw [NSException - exceptionWithName:@"get_output_shape_error" - reason:[NSString stringWithFormat:@"%ld", (long)output_meta.error()] - userInfo:nil]; + exceptionWithName:@"get_output_shape_error" + reason:[NSString stringWithFormat:@"%ld", + (long)output_meta.error()] + userInfo:nil]; } - + const auto shape = output_meta->sizes(); NSMutableArray *nsShape = [[NSMutableArray alloc] init]; - - for(int i = 0; i < shape.size(); i++) { + + for (int i = 0; i < shape.size(); i++) { [nsShape addObject:@(shape[i])]; } - + return [nsShape copy]; }; -- (NSNumber *) getTypeAsNumber:(ScalarType)scalarType { - switch(scalarType) { - case ScalarType::Byte: return @(InputTypeInt8); - case ScalarType::Int: return @(InputTypeInt32); - case ScalarType::Long: return @(InputTypeInt64); - case ScalarType::Float: return @(InputTypeFloat32); - case ScalarType::Double: return @(InputTypeFloat64); - - default: - return @-1; +- (NSNumber *)getTypeAsNumber:(ScalarType)scalarType { + switch (scalarType) { + case ScalarType::Byte: + return @(InputTypeInt8); + case ScalarType::Int: + return @(InputTypeInt32); + case ScalarType::Long: + return @(InputTypeInt64); + case ScalarType::Float: + return @(InputTypeFloat32); + case ScalarType::Double: + return @(InputTypeFloat64); + + default: + return @-1; } } - (NSArray *)forward:(NSArray *)inputs shapes:(NSArray *)shapes - inputTypes: (NSArray *)inputTypes { + inputTypes:(NSArray *)inputTypes { for (NSUInteger i = 0; i < [inputs count]; i++) { - std::vector currentInputShape = NSArrayToIntVector([shapes objectAtIndex:i]); + std::vector currentInputShape = + NSArrayToIntVector([shapes objectAtIndex:i]); InputType inputType = (InputType)[[inputTypes objectAtIndex:i] intValue]; - TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], currentInputShape, inputType); + TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], + currentInputShape, inputType); Error err = _model->set_input("forward", {currentTensor}, i); if (err != Error::Ok) { @@ -180,8 +201,15 @@ - (NSArray *)forward:(NSArray *)inputs } } - auto result = _model->execute("forward")->at(0).toTensor().const_data_ptr(); - return result; // TODO: cast to NSArray + Result result = _model->execute("forward"); + if (!result.ok()) { + throw [NSException + exceptionWithName:@"forward_error" + reason:[NSString stringWithFormat:@"%d", result.error()] + userInfo:nil]; + } + // FIXME: return NSArray instead of Result + return result; } - (NSArray *)forward:(NSArray *)input @@ -191,46 +219,45 @@ - (NSArray *)forward:(NSArray *)input std::vector shapes = NSArrayToIntVector(shape); @try { switch (inputTypeIntValue) { - case InputTypeInt8: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeInt32: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeInt64: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeFloat32: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeFloat64: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } + case InputTypeInt8: { + std::vector> output = + runForwardFromNSArray(input, shapes, _model); + return arrayToNSArray(output); + } + case InputTypeInt32: { + std::vector> output = + runForwardFromNSArray(input, shapes, _model); + return arrayToNSArray(output); + } + case InputTypeInt64: { + std::vector> output = + runForwardFromNSArray(input, shapes, _model); + return arrayToNSArray(output); + } + case InputTypeFloat32: { + std::vector> output = + runForwardFromNSArray(input, shapes, _model); + return arrayToNSArray(output); + } + case InputTypeFloat64: { + std::vector> output = + runForwardFromNSArray(input, shapes, _model); + return arrayToNSArray(output); + } } } @catch (NSException *exception) { NSInteger originalCode = [exception.reason integerValue]; @throw [NSException - exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%ld", (long)originalCode] - userInfo:nil]; + exceptionWithName:@"forward_error" + reason:[NSString stringWithFormat:@"%ld", (long)originalCode] + userInfo:nil]; } // throwing an RN-ET exception @ throw [NSException exceptionWithName:@"forward_error" reason:[NSString stringWithFormat:@"%d", - 0x65] // 101 + 0x65] // 101 userInfo:nil]; } - @end From bfeea952663968b6b27c4ec54a97662cda7ce196 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Fri, 17 Jan 2025 15:50:41 +0100 Subject: [PATCH 05/25] fix: (native) update ETModule's forward to accept multiple inputs --- ios/RnExecutorch/ETModule.mm | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ios/RnExecutorch/ETModule.mm b/ios/RnExecutorch/ETModule.mm index abd202796b..7f368d4364 100644 --- a/ios/RnExecutorch/ETModule.mm +++ b/ios/RnExecutorch/ETModule.mm @@ -1,5 +1,6 @@ #import "ETModule.h" #import +#include #import #include @@ -36,20 +37,23 @@ - (void)loadModule:(NSString *)modelSource resolve(result); } -- (void)forward:(NSArray *)input - shape:(NSArray *)shape - inputType:(double)inputType +- (void)forward:(NSArray *)inputs + shapes:(NSArray *)shapes + inputTypes:(NSArray *)inputTypes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { @try { - NSArray *result = [module forward:input - shape:shape - inputType:[NSNumber numberWithInt:inputType]]; + NSArray *result = [module forward:inputs + shapes:shapes + inputTypes:inputTypes]; resolve(result); } @catch (NSException *exception) { - NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason); - reject(@"result_error", [NSString stringWithFormat:@"%@", exception.reason], - nil); + NSLog(@"An exception occurred in forward: %@, %@", exception.name, + exception.reason); + reject( + @"forward_error", + [NSString stringWithFormat:@"An error occurred: %@", exception.reason], + nil); } } From 19f4fb5ea7a3b21e9e9c3ccad394e1ca0f0973ba Mon Sep 17 00:00:00 2001 From: chmjkb Date: Mon, 20 Jan 2025 11:22:20 +0100 Subject: [PATCH 06/25] refactor: get rid of InputType --- .../ios/ExecutorchLib/ExecutorchLib/Utils.hpp | 85 ++++++------------- 1 file changed, 27 insertions(+), 58 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp index 9ef5edb9a0..b91fa1619a 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp @@ -1,7 +1,6 @@ #ifndef Utils_hpp #define Utils_hpp -#import "InputType.h" #include #include #include @@ -53,49 +52,46 @@ std::unique_ptr NSArrayToTypedArray(NSArray *nsArray) { return typedArray; } -std::function getDeleterForInputType(InputType inputType) { - switch (inputType) { - case InputType::InputTypeInt8: +std::function getDeleterForScalarType(ScalarType scalarType) { + switch (scalarType) { + case ScalarType::Char: return [](void *ptr) { delete[] static_cast(ptr); }; - case InputType::InputTypeInt32: + case ScalarType::Int: return [](void *ptr) { delete[] static_cast(ptr); }; - case InputType::InputTypeInt64: + case ScalarType::Long: return [](void *ptr) { delete[] static_cast(ptr); }; - case InputType::InputTypeFloat32: + case ScalarType::Float: return [](void *ptr) { delete[] static_cast(ptr); }; - case InputType::InputTypeFloat64: + case ScalarType::Double: return [](void *ptr) { delete[] static_cast(ptr); }; + default: + throw std::invalid_argument( + "Unsupported ScalarType for getDeleterForScalarType"); } } -ScalarType inputTypeToScalarType(InputType inputType) { - switch (inputType) { - case InputType::InputTypeInt8: - return ScalarType::Char; - case InputType::InputTypeInt32: - return ScalarType::Int; - case InputType::InputTypeInt64: - return ScalarType::Long; - case InputType::InputTypeFloat32: - return ScalarType::Float; - case InputType::InputTypeFloat64: - return ScalarType::Double; - default: - throw std::invalid_argument("Unknown InputType"); +ScalarType intValueToScalarType(int intValue) { + // Check if the intValue is within the valid range of ScalarType + if (intValue < 0 || intValue >= static_cast(ScalarType::NumOptions)) { + throw std::out_of_range("Invalid ScalarType integer value: " + + std::to_string(intValue)); } + return static_cast(intValue); +} + +NSNumber *scalarTypeToNSNumber(ScalarType scalarType) { + return @(static_cast(scalarType)); } TensorPtr NSArrayToTensorPtr(NSArray *nsArray, std::vector shape, - InputType inputType) { + int inputType) { void *voidPointer = (__bridge void *)nsArray; - std::function deleter = getDeleterForInputType(inputType); - ScalarType inputScalarType = inputTypeToScalarType(inputType); - auto tensor = make_tensor_ptr( - shape, - voidPointer, - inputScalarType, - TensorShapeDynamism::DYNAMIC_BOUND, - deleter); + ScalarType inputScalarType = intValueToScalarType(inputType); + std::function deleter = + getDeleterForScalarType(inputScalarType); + auto tensor = make_tensor_ptr(shape, voidPointer, inputScalarType, + TensorShapeDynamism::DYNAMIC_BOUND, deleter); + return tensor; } @@ -137,31 +133,4 @@ std::vector NSArrayToIntVector(NSArray *inputArray) { return output; } -template -std::vector> -runForwardFromNSArray(NSArray *inputArray, std::vector shapes, - std::unique_ptr &model) { - std::unique_ptr inputPtr = NSArrayToTypedArray(inputArray); - - TensorPtr inputTensor = from_blob(inputPtr.get(), shapes); - Result result = model->forward(inputTensor); - - if (result.ok()) { - std::vector> outputVec; - - for (const auto ¤tResult : *result) { - Tensor currentTensor = currentResult.toTensor(); - std::span currentSpan(currentTensor.const_data_ptr(), - currentTensor.numel()); - outputVec.push_back(std::move(currentSpan)); - } - return outputVec; - } - - @throw [NSException - exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%d", (int)result.error()] - userInfo:nil]; -} - #endif // Utils_hpp From 60828c997a0b26bde72d1c72d03181cf900e9805 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 09:22:18 +0100 Subject: [PATCH 07/25] replace single input forward with multiple inputs --- src/hooks/useModule.ts | 37 ++++- src/native/NativeETModule.ts | 9 +- src/types/common.ts | 21 +-- .../ExecutorchLib/Exported/ETModel.h | 3 - .../ExecutorchLib/Exported/ETModel.mm | 138 +++++++----------- 5 files changed, 94 insertions(+), 114 deletions(-) diff --git a/src/hooks/useModule.ts b/src/hooks/useModule.ts index bba9638b70..0de8361d62 100644 --- a/src/hooks/useModule.ts +++ b/src/hooks/useModule.ts @@ -1,7 +1,16 @@ import { useEffect, useState } from 'react'; import { fetchResource } from '../utils/fetchResource'; import { ETError, getError } from '../Error'; -import { ETInput, Module, getTypeIdentifier } from '../types/common'; +import { ETInput, Module } from '../types/common'; + +const getTypeIdentifier = (input: ETInput): number => { + if (input instanceof Int8Array) return 1; + if (input instanceof Int32Array) return 3; + if (input instanceof BigInt64Array) return 4; + if (input instanceof Float32Array) return 6; + if (input instanceof Float64Array) return 7; + return -1; +} interface Props { modelSource: string | number; @@ -13,7 +22,7 @@ interface _Module { isReady: boolean; isGenerating: boolean; downloadProgress: number; - forwardETInput: (input: ETInput, shape: number[]) => Promise; + forwardETInput: (input: ETInput[] | ETInput, shape: number[][]) => Promise; forwardImage: (input: string) => Promise; } @@ -59,7 +68,10 @@ export const useModule = ({ modelSource, module }: Props): _Module => { } }; - const forwardETInput = async (input: ETInput, shape: number[]) => { + const forwardETInput = async ( + input: ETInput[] | ETInput, + shape: number[][] | number[] + ) => { if (!isReady) { throw new Error(getError(ETError.ModuleNotLoaded)); } @@ -67,15 +79,24 @@ export const useModule = ({ modelSource, module }: Props): _Module => { throw new Error(getError(ETError.ModelGenerating)); } - const inputType = getTypeIdentifier(input); - if (inputType === -1) { - throw new Error(getError(ETError.InvalidArgument)); + if (!Array.isArray(input)) { + input = [input]; } + let inputTypeIdentifiers = []; + let modelInputs = []; + for (let idx = 0; idx < input.length; idx++) { + let currentInputTypeIdentifier = getTypeIdentifier(input[idx] as ETInput); + if (currentInputTypeIdentifier == -1) { + throw new Error(getError(ETError.InvalidArgument)); + } + inputTypeIdentifiers.push(currentInputTypeIdentifier); + modelInputs.push([...(input[idx] as ETInput)]); + } + try { - const numberArray = [...input]; setIsGenerating(true); - const output = await module.forward(numberArray, shape, inputType); + const output = await module.forward(modelInputs, shape, inputTypeIdentifiers); setIsGenerating(false); return output; } catch (e) { diff --git a/src/native/NativeETModule.ts b/src/native/NativeETModule.ts index d04da1abf7..7d2b90d43f 100644 --- a/src/native/NativeETModule.ts +++ b/src/native/NativeETModule.ts @@ -5,11 +5,10 @@ export interface Spec extends TurboModule { loadModule(modelSource: string): Promise; forward( - input: number[], - shape: number[], - inputType: number - ): Promise; - + inputs: number[], + shapes: number[], + inputTypes: number[] +): Promise; loadMethod(methodName: string): Promise; } diff --git a/src/types/common.ts b/src/types/common.ts index ec0daa2c02..953a5fcf95 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -26,15 +26,18 @@ export type ETInput = | Float32Array | Float64Array; -export const getTypeIdentifier = (arr: ETInput): number => { - if (arr instanceof Int8Array) return 0; - if (arr instanceof Int32Array) return 1; - if (arr instanceof BigInt64Array) return 2; - if (arr instanceof Float32Array) return 3; - if (arr instanceof Float64Array) return 4; - - return -1; -}; +export interface ExecutorchModule { + error: string | null; + isReady: boolean; + isGenerating: boolean; + forward: ( + inputs: ETInput[] | ETInput, + shapes: number[][], + ) => Promise; + // forward: (input: ETInput[], shape: number[]) => Promise; + loadMethod: (methodName: string) => Promise; + loadForward: () => Promise; +} export type Module = | _ClassificationModule diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h index 04852227ee..9c78377e25 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.h @@ -8,9 +8,6 @@ - (NSNumber *)loadModel:(NSString *)filePath; - (NSNumber *)loadMethod:(NSString *)methodName; - (NSNumber *)loadForward; -- (NSArray *)forward:(NSArray *)input - shape:(NSArray *)shape - inputType:(NSNumber *)inputType; - (NSArray *)forward:(NSArray *)inputs shapes:(NSArray *)shapes inputTypes: (NSArray *)inputTypes; diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index 88fb4c05e0..5e7e79543d 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -1,5 +1,4 @@ #import "ETModel.h" -#include "InputType.h" #include "Utils.hpp" #include #include @@ -63,7 +62,7 @@ - (NSNumber *)getInputType:(NSNumber *)index { userInfo:nil]; } - return [self getTypeAsNumber:input_meta->scalar_type()]; + return scalarTypeToNSNumber(input_meta->scalar_type()); }; - (NSArray *)getInputShape:(NSNumber *)index { @@ -129,7 +128,7 @@ - (NSNumber *)getOutputType:(NSNumber *)index { userInfo:nil]; } - return [self getTypeAsNumber:output_meta->scalar_type()]; + return scalarTypeToNSNumber(output_meta->scalar_type()); }; - (NSArray *)getOutputShape:(NSNumber *)index { @@ -162,102 +161,63 @@ - (NSArray *)getOutputShape:(NSNumber *)index { return [nsShape copy]; }; -- (NSNumber *)getTypeAsNumber:(ScalarType)scalarType { - switch (scalarType) { - case ScalarType::Byte: - return @(InputTypeInt8); - case ScalarType::Int: - return @(InputTypeInt32); - case ScalarType::Long: - return @(InputTypeInt64); - case ScalarType::Float: - return @(InputTypeFloat32); - case ScalarType::Double: - return @(InputTypeFloat64); - - default: - return @-1; - } -} - +/** + * @brief Processes inputs through the forward pass of the model. + * + * This method takes input tensors, their corresponding shapes, and types, + * and performs a forward pass using _model. It supports both + * single and multiple inputs. + * + * @param inputs NSArray* of inputs where each element is an NSArray + * representing the data for a tensor. + * @param shapes An array of shapes corresponding to the input tensors. + * Each element is an NSArray of integers defining the dimensions. + * @param inputTypes An array of NSNumber objects representing the ScalarType of + * the input tensors + * + * @return An NSArray containing the results of the forward pass. Each element + * represents the output of the corresponding input. + * + * @throws NSException Throws an exception with name "forward_error" if + * an error occurs during input processing or model + * execution. + * + * @warning Ensure that the inputs, shapes, and inputTypes arrays have the + * same number of elements. Mismatched sizes can lead to runtime + * errors. + **/ - (NSArray *)forward:(NSArray *)inputs shapes:(NSArray *)shapes inputTypes:(NSArray *)inputTypes { - - for (NSUInteger i = 0; i < [inputs count]; i++) { - std::vector currentInputShape = - NSArrayToIntVector([shapes objectAtIndex:i]); - InputType inputType = (InputType)[[inputTypes objectAtIndex:i] intValue]; - - TensorPtr currentTensor = NSArrayToTensorPtr([inputs objectAtIndex:i], - currentInputShape, inputType); - - Error err = _model->set_input("forward", {currentTensor}, i); - if (err != Error::Ok) { - @throw [NSException - exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%ld", (long)err] - userInfo:nil]; + std::vector inputTensors; + std::vector inputTensorPtrs; + + for (NSUInteger i = 0; i < [inputTypes count]; i++) { + NSArray *inputShapeNSArray = [shapes objectAtIndex:i]; + + std::vector inputShape = NSArrayToIntVector(inputShapeNSArray); + int inputType = [[inputTypes objectAtIndex:i] intValue]; + + NSArray *input = [inputs objectAtIndex:i]; + + TensorPtr currentTensor = NSArrayToTensorPtr(input, inputShape, inputType); + if (!currentTensor) { + NSLog(@"Failed to create tensor for input %lu", (unsigned long)i); + continue; } + + inputTensors.push_back(*currentTensor); + inputTensorPtrs.push_back(currentTensor); } - - Result result = _model->execute("forward"); + Result result = _model->forward(inputTensors); + if (!result.ok()) { throw [NSException - exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%d", result.error()] - userInfo:nil]; - } - // FIXME: return NSArray instead of Result - return result; -} - -- (NSArray *)forward:(NSArray *)input - shape:(NSArray *)shape - inputType:(NSNumber *)inputType { - int inputTypeIntValue = [inputType intValue]; - std::vector shapes = NSArrayToIntVector(shape); - @try { - switch (inputTypeIntValue) { - case InputTypeInt8: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeInt32: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeInt64: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeFloat32: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - case InputTypeFloat64: { - std::vector> output = - runForwardFromNSArray(input, shapes, _model); - return arrayToNSArray(output); - } - } - } @catch (NSException *exception) { - NSInteger originalCode = [exception.reason integerValue]; - @throw [NSException exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%ld", (long)originalCode] + reason:[NSString stringWithFormat:@"%d", result.error()] userInfo:nil]; } - // throwing an RN-ET exception - @ - throw [NSException exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%d", - 0x65] // 101 - userInfo:nil]; + return [NSArray new]; } @end From 84086563be8b8cbc26c7d0a1f0b7012177ed080d Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 09:29:10 +0100 Subject: [PATCH 08/25] lint --- src/hooks/useModule.ts | 18 +++++++++++------- src/native/NativeETModule.ts | 2 +- src/types/common.ts | 6 +----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/hooks/useModule.ts b/src/hooks/useModule.ts index 0de8361d62..8246c9737c 100644 --- a/src/hooks/useModule.ts +++ b/src/hooks/useModule.ts @@ -4,13 +4,13 @@ import { ETError, getError } from '../Error'; import { ETInput, Module } from '../types/common'; const getTypeIdentifier = (input: ETInput): number => { - if (input instanceof Int8Array) return 1; - if (input instanceof Int32Array) return 3; + if (input instanceof Int8Array) return 1; + if (input instanceof Int32Array) return 3; if (input instanceof BigInt64Array) return 4; - if (input instanceof Float32Array) return 6; - if (input instanceof Float64Array) return 7; + if (input instanceof Float32Array) return 6; + if (input instanceof Float64Array) return 7; return -1; -} +}; interface Props { modelSource: string | number; @@ -93,10 +93,14 @@ export const useModule = ({ modelSource, module }: Props): _Module => { inputTypeIdentifiers.push(currentInputTypeIdentifier); modelInputs.push([...(input[idx] as ETInput)]); } - + try { setIsGenerating(true); - const output = await module.forward(modelInputs, shape, inputTypeIdentifiers); + const output = await module.forward( + modelInputs, + shape, + inputTypeIdentifiers + ); setIsGenerating(false); return output; } catch (e) { diff --git a/src/native/NativeETModule.ts b/src/native/NativeETModule.ts index 7d2b90d43f..43a5ddbf38 100644 --- a/src/native/NativeETModule.ts +++ b/src/native/NativeETModule.ts @@ -8,7 +8,7 @@ export interface Spec extends TurboModule { inputs: number[], shapes: number[], inputTypes: number[] -): Promise; + ): Promise; loadMethod(methodName: string): Promise; } diff --git a/src/types/common.ts b/src/types/common.ts index 953a5fcf95..728c729fce 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -30,11 +30,7 @@ export interface ExecutorchModule { error: string | null; isReady: boolean; isGenerating: boolean; - forward: ( - inputs: ETInput[] | ETInput, - shapes: number[][], - ) => Promise; - // forward: (input: ETInput[], shape: number[]) => Promise; + forward: (inputs: ETInput[] | ETInput, shapes: number[][]) => Promise; loadMethod: (methodName: string) => Promise; loadForward: () => Promise; } From 4d904cad353ffd5bcf0a0267c27fb5805e82b16a Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 12:06:05 +0100 Subject: [PATCH 09/25] fix: make use of existing functions, return actual output --- .../ExecutorchLib/Exported/ETModel.mm | 34 ++++++--- .../ios/ExecutorchLib/ExecutorchLib/Utils.hpp | 71 +++++++++++++++++-- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index 5e7e79543d..6739cd30f5 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -191,33 +191,45 @@ - (NSArray *)forward:(NSArray *)inputs inputTypes:(NSArray *)inputTypes { std::vector inputTensors; std::vector inputTensorPtrs; - + for (NSUInteger i = 0; i < [inputTypes count]; i++) { NSArray *inputShapeNSArray = [shapes objectAtIndex:i]; - + std::vector inputShape = NSArrayToIntVector(inputShapeNSArray); int inputType = [[inputTypes objectAtIndex:i] intValue]; - + NSArray *input = [inputs objectAtIndex:i]; - + TensorPtr currentTensor = NSArrayToTensorPtr(input, inputShape, inputType); if (!currentTensor) { - NSLog(@"Failed to create tensor for input %lu", (unsigned long)i); + throw [NSException + exceptionWithName:@"forward_error" + reason:[NSString stringWithFormat:@"%d", Error::InvalidArgument] + userInfo:nil]; continue; } - + inputTensors.push_back(*currentTensor); inputTensorPtrs.push_back(currentTensor); } + Result result = _model->forward(inputTensors); - + if (!result.ok()) { throw [NSException - exceptionWithName:@"forward_error" - reason:[NSString stringWithFormat:@"%d", result.error()] - userInfo:nil]; + exceptionWithName:@"forward_error" + reason:[NSString stringWithFormat:@"%d", result.error()] + userInfo:nil]; + } + + NSMutableArray *output = [NSMutableArray new]; + for (int i = 0; i < result->size(); i++) { + auto currentResultTensor = result->at(i).toTensor(); + NSArray *currentOutput = arrayToNsArray(currentResultTensor.const_data_ptr(), currentResultTensor.numel(), currentResultTensor.scalar_type()); + [output addObject:currentOutput]; } - return [NSArray new]; + return output; + } @end diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp index b91fa1619a..27bdf400a4 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp @@ -17,7 +17,7 @@ using namespace ::executorch::extension; using namespace ::torch::executor; template T getValueFromNSNumber(NSNumber *number) { - if constexpr (std::is_same::value) { + if constexpr (std::is_same::value) { return static_cast([number charValue]); // `charValue` for 8-bit integers } else if constexpr (std::is_same::value) { return static_cast([number intValue]); // `intValue` for 32-bit integers @@ -66,7 +66,7 @@ std::function getDeleterForScalarType(ScalarType scalarType) { return [](void *ptr) { delete[] static_cast(ptr); }; default: throw std::invalid_argument( - "Unsupported ScalarType for getDeleterForScalarType"); + "Unsupported ScalarType passed to getDeleterForScalarType!"); } } @@ -83,14 +83,46 @@ NSNumber *scalarTypeToNSNumber(ScalarType scalarType) { return @(static_cast(scalarType)); } +void *NSArrayToVoidArray(NSArray *nsArray, ScalarType inputScalarType, + size_t &outSize) { + outSize = [nsArray count]; + + switch (inputScalarType) { + case ScalarType::Char: { + auto typedArray = NSArrayToTypedArray(nsArray); + return typedArray.release(); + } + case ScalarType::Long: { + auto typedArray = NSArrayToTypedArray(nsArray); + return typedArray.release(); + } + + case ScalarType::Int: { + auto typedArray = NSArrayToTypedArray(nsArray); + return typedArray.release(); + } + case ScalarType::Float: { + auto typedArray = NSArrayToTypedArray(nsArray); + return typedArray.release(); + } + case ScalarType::Double: { + auto typedArray = NSArrayToTypedArray(nsArray); + return typedArray.release(); + } + default: + throw std::invalid_argument( + "Unsupported ScalarType passed to NSArrayToVoidArray!"); + } +} + TensorPtr NSArrayToTensorPtr(NSArray *nsArray, std::vector shape, int inputType) { - void *voidPointer = (__bridge void *)nsArray; ScalarType inputScalarType = intValueToScalarType(inputType); + size_t arraySize; + void *data = NSArrayToVoidArray(nsArray, inputScalarType, arraySize); std::function deleter = getDeleterForScalarType(inputScalarType); - auto tensor = make_tensor_ptr(shape, voidPointer, inputScalarType, - TensorShapeDynamism::DYNAMIC_BOUND, deleter); + auto tensor = make_tensor_ptr(shape, data, inputScalarType, TensorShapeDynamism::DYNAMIC_UNBOUND, deleter); return tensor; } @@ -120,6 +152,35 @@ NSArray *arrayToNSArray(const std::vector> &dataPtrVec) { return [nsArray copy]; } +NSArray *arrayToNsArray(const void *dataPtr, size_t numel, ScalarType scalarType) { + switch (scalarType) { + case ScalarType::Char: { + NSArray *outputArray = arrayToNSArray(dataPtr, numel); + return outputArray; + } + case ScalarType::Long: { + NSArray *outputArray = arrayToNSArray(dataPtr, numel); + return outputArray; + } + + case ScalarType::Int: { + NSArray *outputArray = arrayToNSArray(dataPtr, numel); + return outputArray; + } + case ScalarType::Float: { + NSArray *outputArray = arrayToNSArray(dataPtr, numel); + return outputArray; + } + case ScalarType::Double: { + NSArray *outputArray = arrayToNSArray(dataPtr, numel); + return outputArray; + } + default: + throw std::invalid_argument( + "Unsupported ScalarType passed to arrayToNSArray!"); + } +} + std::vector NSArrayToIntVector(NSArray *inputArray) { std::vector output; for (NSUInteger i = 0; i < [inputArray count]; ++i) { From 4ab655fbd7357318eee1dfc3cb290d0f0cab8748 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 12:23:39 +0100 Subject: [PATCH 10/25] fix: update rnexecutorchmodules.ts --- src/native/RnExecutorchModules.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/RnExecutorchModules.ts b/src/native/RnExecutorchModules.ts index e898216a6d..7c65fc4a16 100644 --- a/src/native/RnExecutorchModules.ts +++ b/src/native/RnExecutorchModules.ts @@ -112,11 +112,11 @@ class _ClassificationModule { class _ETModule { async forward( - input: number[], - shape: number[], - inputType: number + inputs: number[], + shapes: number[], + inputTypes: number[] ): ReturnType { - return await ETModule.forward(input, shape, inputType); + return await ETModule.forward(inputs, shapes, inputTypes); } async loadModule( modelSource: string From 51c3e7153eb5903a263a66415664f0c66cfd7d4c Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 12:25:38 +0100 Subject: [PATCH 11/25] fix: update BaseModel to match new native implementation, remove InputType from xcproj --- ios/RnExecutorch/models/BaseModel.mm | 14 ++++++++++---- .../ExecutorchLib.xcodeproj/project.pbxproj | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ios/RnExecutorch/models/BaseModel.mm b/ios/RnExecutorch/models/BaseModel.mm index 587eddf68b..1e7217ecb8 100644 --- a/ios/RnExecutorch/models/BaseModel.mm +++ b/ios/RnExecutorch/models/BaseModel.mm @@ -4,16 +4,22 @@ @implementation BaseModel - (NSArray *)forward:(NSArray *)input { - NSArray *result = [module forward:input - shape:[module getInputShape:@0] - inputType:[module getInputType:@0]]; + NSMutableArray *shapes = [NSMutableArray new]; + NSMutableArray *inputTypes = [NSMutableArray new]; + NSNumber *numberOfInputs = [module getNumberOfInputs]; + + for (NSUInteger i = 0; i < [numberOfInputs intValue]; i++) { + [shapes addObject:[module getInputShape:[NSNumber numberWithInt:i]]]; + [inputTypes addObject:[module getInputType:[NSNumber numberWithInt:i]]]; + } + + NSArray *result = [module forward:input shapes:shapes inputTypes:inputTypes]; return result; } - (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber *code))completion { module = [[ETModel alloc] init]; - NSNumber *result = [self->module loadModel:modelURL.path]; if ([result intValue] != 0) { completion(NO, result); diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj b/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj index 73b86b0bec..96a4e2dbe1 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj +++ b/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ 55EA2C562CB90E7D004315B3 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 55EA2C582CB90E80004315B3 /* CoreML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreML.framework; path = System/Library/Frameworks/CoreML.framework; sourceTree = SDKROOT; }; 55EA2C5A2CB90E85004315B3 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; - A84198832D02DF29006D4D5E /* InputType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InputType.h; sourceTree = ""; }; A851C4042CF9F1B600424E93 /* Utils.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Utils.hpp; sourceTree = ""; }; /* End PBXFileReference section */ @@ -146,7 +145,6 @@ 55EA2C322CB90C7A004315B3 /* sampler */, 55EA2C3E2CB90C7A004315B3 /* tokenizer */, A851C4042CF9F1B600424E93 /* Utils.hpp */, - A84198832D02DF29006D4D5E /* InputType.h */, ); path = ExecutorchLib; sourceTree = ""; From ec4d3224c5668b42b9a7c13deb818d211d255044 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Wed, 22 Jan 2025 12:28:06 +0100 Subject: [PATCH 12/25] fix: int8_T -> char --- third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp index 27bdf400a4..77559df1fb 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp @@ -55,11 +55,11 @@ std::unique_ptr NSArrayToTypedArray(NSArray *nsArray) { std::function getDeleterForScalarType(ScalarType scalarType) { switch (scalarType) { case ScalarType::Char: - return [](void *ptr) { delete[] static_cast(ptr); }; + return [](void *ptr) { delete[] static_cast(ptr); }; case ScalarType::Int: return [](void *ptr) { delete[] static_cast(ptr); }; case ScalarType::Long: - return [](void *ptr) { delete[] static_cast(ptr); }; + return [](void *ptr) { delete[] static_cast(ptr); }; case ScalarType::Float: return [](void *ptr) { delete[] static_cast(ptr); }; case ScalarType::Double: From 27af3161ea9aa1dbd0260d822ef0626998e97f07 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 23 Jan 2025 11:13:45 +0100 Subject: [PATCH 13/25] feat: make Android accept multiple inputs --- .../com/swmansion/rnexecutorch/ETModule.kt | 41 +++++++---- .../rnexecutorch/utils/ArrayUtils.kt | 70 ++++++------------- .../rnexecutorch/utils/TensorUtils.kt | 14 ++-- 3 files changed, 56 insertions(+), 69 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt index 7f1e73fc46..929817e713 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt @@ -7,12 +7,13 @@ import com.facebook.react.bridge.ReadableArray import com.swmansion.rnexecutorch.utils.ArrayUtils import com.swmansion.rnexecutorch.utils.ETError import com.swmansion.rnexecutorch.utils.TensorUtils +import org.pytorch.executorch.EValue import org.pytorch.executorch.Module import java.net.URL class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(reactContext) { private lateinit var module: Module - + private var reactApplicationContext = reactContext; override fun getName(): String { return NAME } @@ -33,26 +34,40 @@ class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(react } override fun forward( - input: ReadableArray, - shape: ReadableArray, - inputType: Double, + inputs: ReadableArray, + shapes: ReadableArray, + inputTypes: ReadableArray, promise: Promise ) { + val inputEValues = ArrayList() try { - val executorchInput = - TensorUtils.getExecutorchInput(input, ArrayUtils.createLongArray(shape), inputType.toInt()) + for (i in 0 until inputs.size()) { + val currentInput = inputs.getArray(i) + ?: throw Exception(ETError.InvalidArgument.code.toString()) + val currentShape = shapes.getArray(i) + ?: throw Exception(ETError.InvalidArgument.code.toString()) + val currentInputType = inputTypes.getInt(i) - val result = module.forward(executorchInput) - val resultArray = Arguments.createArray() + val currentEValue = TensorUtils.getExecutorchInput( + currentInput, + ArrayUtils.createLongArray(currentShape), + currentInputType + ) - for (evalue in result) { - resultArray.pushArray(ArrayUtils.createReadableArray(evalue.toTensor())) + inputEValues.add(currentEValue) } - promise.resolve(resultArray) - return + val forwardOutputs = module.forward(*inputEValues.toTypedArray()); + val outputArray = Arguments.createArray() + + for (output in forwardOutputs) { + val arr = ArrayUtils.createReadableArrayFromTensor(output.toTensor()) + outputArray.pushArray(arr) + } + promise.resolve(outputArray) + } catch (e: IllegalArgumentException) { - //The error is thrown when transformation to Tensor fails + // The error is thrown when transformation to Tensor fails promise.reject("Forward Failed Execution", ETError.InvalidArgument.code.toString()) return } catch (e: Exception) { diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt index 10ffe29eb8..b9e0c18911 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt @@ -1,5 +1,6 @@ package com.swmansion.rnexecutorch.utils +import android.util.Log import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReadableArray import org.pytorch.executorch.DType @@ -7,82 +8,57 @@ import org.pytorch.executorch.Tensor class ArrayUtils { companion object { + private inline fun createTypedArrayFromReadableArray(input: ReadableArray, transform: (ReadableArray, Int) -> T): Array { + return Array(input.size()) { index -> transform(input, index) } + } + fun createByteArray(input: ReadableArray): ByteArray { - val byteArray = ByteArray(input.size()) - for (i in 0 until input.size()) { - byteArray[i] = input.getInt(i).toByte() - } - return byteArray + return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index).toByte() }.toByteArray() + } + + fun createCharArray(input: ReadableArray): CharArray { + return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index).toChar() }.toCharArray() } fun createIntArray(input: ReadableArray): IntArray { - val intArray = IntArray(input.size()) - for (i in 0 until input.size()) { - intArray[i] = input.getInt(i) - } - return intArray + return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index) }.toIntArray() } fun createFloatArray(input: ReadableArray): FloatArray { - val floatArray = FloatArray(input.size()) - for (i in 0 until input.size()) { - floatArray[i] = input.getDouble(i).toFloat() - } - return floatArray + return createTypedArrayFromReadableArray(input) { array, index -> array.getDouble(index).toFloat() }.toFloatArray() } fun createLongArray(input: ReadableArray): LongArray { - val longArray = LongArray(input.size()) - for (i in 0 until input.size()) { - longArray[i] = input.getInt(i).toLong() - } - return longArray + return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index).toLong() }.toLongArray() } fun createDoubleArray(input: ReadableArray): DoubleArray { - val doubleArray = DoubleArray(input.size()) - for (i in 0 until input.size()) { - doubleArray[i] = input.getDouble(i) - } - return doubleArray + return createTypedArrayFromReadableArray(input) { array, index -> array.getDouble(index) }.toDoubleArray() } - - fun createReadableArray(result: Tensor): ReadableArray { + fun createReadableArrayFromTensor(result: Tensor): ReadableArray { val resultArray = Arguments.createArray() + when (result.dtype()) { DType.UINT8 -> { - val byteArray = result.dataAsByteArray - for (i in byteArray) { - resultArray.pushInt(i.toInt()) - } + result.dataAsByteArray.forEach { resultArray.pushInt(it.toInt()) } } DType.INT32 -> { - val intArray = result.dataAsIntArray - for (i in intArray) { - resultArray.pushInt(i) - } + result.dataAsIntArray.forEach { resultArray.pushInt(it) } } DType.FLOAT -> { - val longArray = result.dataAsFloatArray - for (i in longArray) { - resultArray.pushDouble(i.toDouble()) - } + result.dataAsFloatArray.forEach { resultArray.pushDouble(it.toDouble()) } } DType.DOUBLE -> { - val floatArray = result.dataAsDoubleArray - for (i in floatArray) { - resultArray.pushDouble(i) - } + result.dataAsDoubleArray.forEach { resultArray.pushDouble(it) } } DType.INT64 -> { - val doubleArray = result.dataAsLongArray - for (i in doubleArray) { - resultArray.pushLong(i) - } + // TODO: Do something to handle or deprecate long dtype + // https://github.com/facebook/react-native/issues/12506 + result.dataAsLongArray.forEach { resultArray.pushInt(it.toInt()) } } else -> { diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/TensorUtils.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/TensorUtils.kt index 2cf217a89d..212b415eb4 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/utils/TensorUtils.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/utils/TensorUtils.kt @@ -12,27 +12,23 @@ class TensorUtils { fun getExecutorchInput(input: ReadableArray, shape: LongArray, type: Int): EValue { try { when (type) { - 0 -> { + 1 -> { val inputTensor = Tensor.fromBlob(ArrayUtils.createByteArray(input), shape) return EValue.from(inputTensor) } - - 1 -> { + 3 -> { val inputTensor = Tensor.fromBlob(ArrayUtils.createIntArray(input), shape) return EValue.from(inputTensor) } - - 2 -> { + 4 -> { val inputTensor = Tensor.fromBlob(ArrayUtils.createLongArray(input), shape) return EValue.from(inputTensor) } - - 3 -> { + 6 -> { val inputTensor = Tensor.fromBlob(ArrayUtils.createFloatArray(input), shape) return EValue.from(inputTensor) } - - 4 -> { + 7 -> { val inputTensor = Tensor.fromBlob(ArrayUtils.createDoubleArray(input), shape) return EValue.from(inputTensor) } From b8d71ae8c592e6a1ea1b622d4f1f6a420a3d6a77 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Thu, 23 Jan 2025 11:15:03 +0100 Subject: [PATCH 14/25] refactor: remove unused function --- .../main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt index b9e0c18911..f01e773dd4 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt @@ -15,11 +15,6 @@ class ArrayUtils { fun createByteArray(input: ReadableArray): ByteArray { return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index).toByte() }.toByteArray() } - - fun createCharArray(input: ReadableArray): CharArray { - return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index).toChar() }.toCharArray() - } - fun createIntArray(input: ReadableArray): IntArray { return createTypedArrayFromReadableArray(input) { array, index -> array.getInt(index) }.toIntArray() } From 4daa790c70c601f10aae49b4531c041a5b6dd2f8 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Fri, 24 Jan 2025 12:52:33 +0100 Subject: [PATCH 15/25] chore: remove useless continue --- third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index 6739cd30f5..2cc9912d32 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -206,7 +206,6 @@ - (NSArray *)forward:(NSArray *)inputs exceptionWithName:@"forward_error" reason:[NSString stringWithFormat:@"%d", Error::InvalidArgument] userInfo:nil]; - continue; } inputTensors.push_back(*currentTensor); From 2719ca4e0c924cf96ece33495037eecf7cf721e7 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Mon, 27 Jan 2025 08:43:21 +0100 Subject: [PATCH 16/25] feat: make use of the new interface in BaseModel --- ios/RnExecutorch/models/BaseModel.h | 5 +++++ ios/RnExecutorch/models/BaseModel.mm | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/ios/RnExecutorch/models/BaseModel.h b/ios/RnExecutorch/models/BaseModel.h index 147215b687..b06e1cfcf6 100644 --- a/ios/RnExecutorch/models/BaseModel.h +++ b/ios/RnExecutorch/models/BaseModel.h @@ -8,6 +8,11 @@ } - (NSArray *)forward:(NSArray *)input; + +- (NSArray *)forward:(NSArray *)inputs + shapes:(NSArray *)shapes + inputTypes:(NSArray *)inputTypes; + - (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber *code))completion; diff --git a/ios/RnExecutorch/models/BaseModel.mm b/ios/RnExecutorch/models/BaseModel.mm index 1e7217ecb8..753fd3b3d7 100644 --- a/ios/RnExecutorch/models/BaseModel.mm +++ b/ios/RnExecutorch/models/BaseModel.mm @@ -17,6 +17,13 @@ - (NSArray *)forward:(NSArray *)input { return result; } +- (NSArray *)forward:(NSArray *)inputs + shapes:(NSArray *)shapes + inputTypes:(NSArray *)inputTypes { + NSArray *result = [module forward:inputs shapes:shapes inputTypes:inputTypes]; + return result; +} + - (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber *code))completion { module = [[ETModel alloc] init]; From c55bbe87d9be8a107c2db853e71e78811f13b462 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Tue, 28 Jan 2025 10:23:53 +0100 Subject: [PATCH 17/25] chore: remove log import --- .../src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt index f01e773dd4..8693a69dcb 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/utils/ArrayUtils.kt @@ -1,6 +1,5 @@ package com.swmansion.rnexecutorch.utils -import android.util.Log import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReadableArray import org.pytorch.executorch.DType From 4acdd1b22bc79f799df30869129648e10ccda275 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Tue, 28 Jan 2025 10:37:51 +0100 Subject: [PATCH 18/25] fix: unsqueeze shapes if a single number is passed --- src/hooks/useModule.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks/useModule.ts b/src/hooks/useModule.ts index 8246c9737c..2d144cc220 100644 --- a/src/hooks/useModule.ts +++ b/src/hooks/useModule.ts @@ -79,9 +79,15 @@ export const useModule = ({ modelSource, module }: Props): _Module => { throw new Error(getError(ETError.ModelGenerating)); } + // Since the native module expects an array of inputs and an array of shapes, + // if the user provides a single ETInput, we want to "unsqueeze" the array so + // the data is properly processed on the native side if (!Array.isArray(input)) { input = [input]; } + if (!Array.isArray(shape)) { + shape = [shape]; + } let inputTypeIdentifiers = []; let modelInputs = []; From 6974d38b477f320c697092c59a2fcc4c4c90cb0a Mon Sep 17 00:00:00 2001 From: chmjkb Date: Tue, 28 Jan 2025 11:02:33 +0100 Subject: [PATCH 19/25] chore: add a comment for a hack in forward() --- .../ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm index 2cc9912d32..1f4c0d1300 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Exported/ETModel.mm @@ -208,6 +208,9 @@ - (NSArray *)forward:(NSArray *)inputs userInfo:nil]; } + // Since pushing back to inputTensors would cast to EValue (forward accepts a vector of EValues) + // We also push back to inputTensorPtrs to keep the underlying tensor alive. + // inputTensorPtrs vector retains shared ownership to prevent premature destruction inputTensors.push_back(*currentTensor); inputTensorPtrs.push_back(currentTensor); } From 5934228b25f425674ddd0bf2f95a39d018315f79 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Tue, 28 Jan 2025 11:07:34 +0100 Subject: [PATCH 20/25] fix: fix types --- src/hooks/useModule.ts | 7 ++++++- src/types/common.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hooks/useModule.ts b/src/hooks/useModule.ts index 2d144cc220..bc6214bcd6 100644 --- a/src/hooks/useModule.ts +++ b/src/hooks/useModule.ts @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react'; import { fetchResource } from '../utils/fetchResource'; import { ETError, getError } from '../Error'; import { ETInput, Module } from '../types/common'; +import { _ETModule } from '../native/RnExecutorchModules'; + const getTypeIdentifier = (input: ETInput): number => { if (input instanceof Int8Array) return 1; @@ -22,7 +24,10 @@ interface _Module { isReady: boolean; isGenerating: boolean; downloadProgress: number; - forwardETInput: (input: ETInput[] | ETInput, shape: number[][]) => Promise; + forwardETInput: ( + input: ETInput[] | ETInput, + shape: number[][] | number[] + ) => ReturnType<_ETModule["forward"]>; forwardImage: (input: string) => Promise; } diff --git a/src/types/common.ts b/src/types/common.ts index 728c729fce..ee65772beb 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -3,6 +3,7 @@ import { _StyleTransferModule, _ObjectDetectionModule, ETModule, + _ETModule, } from '../native/RnExecutorchModules'; export type ResourceSource = string | number; @@ -30,7 +31,7 @@ export interface ExecutorchModule { error: string | null; isReady: boolean; isGenerating: boolean; - forward: (inputs: ETInput[] | ETInput, shapes: number[][]) => Promise; + forward: (inputs: ETInput[] | ETInput, shapes: number[][]) => ReturnType<_ETModule['forward']> loadMethod: (methodName: string) => Promise; loadForward: () => Promise; } From 55f4b1cebfd4f0fc99a4f9120f0059710e46e0f9 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Tue, 28 Jan 2025 11:32:00 +0100 Subject: [PATCH 21/25] chore: add comment for nsarraytovoidptr --- .../ios/ExecutorchLib/ExecutorchLib/Utils.hpp | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp index 77559df1fb..7c3fd7cb17 100644 --- a/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp +++ b/third-party/ios/ExecutorchLib/ExecutorchLib/Utils.hpp @@ -83,30 +83,48 @@ NSNumber *scalarTypeToNSNumber(ScalarType scalarType) { return @(static_cast(scalarType)); } +NSArray* flattenArray(NSArray *array) { + NSMutableArray *flatArray = [NSMutableArray array]; + + for (id element in array) { + if ([element isKindOfClass:[NSArray class]]) { + NSArray *nestedArray = flattenArray(element); + [flatArray addObjectsFromArray:nestedArray]; + } else { + [flatArray addObject:element]; + } + } + + return [flatArray copy]; +} + void *NSArrayToVoidArray(NSArray *nsArray, ScalarType inputScalarType, size_t &outSize) { - outSize = [nsArray count]; + // This function assumes that the passed array may not be flattened, + // that's why we flatten it here + NSArray *flattenedArray = flattenArray(nsArray); + outSize = [flattenedArray count]; switch (inputScalarType) { case ScalarType::Char: { - auto typedArray = NSArrayToTypedArray(nsArray); + auto typedArray = NSArrayToTypedArray(flattenedArray); return typedArray.release(); } case ScalarType::Long: { - auto typedArray = NSArrayToTypedArray(nsArray); + auto typedArray = NSArrayToTypedArray(flattenedArray); return typedArray.release(); } case ScalarType::Int: { - auto typedArray = NSArrayToTypedArray(nsArray); + auto typedArray = NSArrayToTypedArray(flattenedArray); return typedArray.release(); } case ScalarType::Float: { - auto typedArray = NSArrayToTypedArray(nsArray); + auto typedArray = NSArrayToTypedArray(flattenedArray); return typedArray.release(); } case ScalarType::Double: { - auto typedArray = NSArrayToTypedArray(nsArray); + auto typedArray = NSArrayToTypedArray(flattenedArray); return typedArray.release(); } default: From b73c8dca94a1c57c24064c28b72f2b7c548c86b0 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Fri, 21 Feb 2025 14:54:53 +0100 Subject: [PATCH 22/25] fix: unsqueeze input before forwarding it to cpp --- ios/RnExecutorch/models/BaseModel.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RnExecutorch/models/BaseModel.mm b/ios/RnExecutorch/models/BaseModel.mm index 753fd3b3d7..26cebd6e6a 100644 --- a/ios/RnExecutorch/models/BaseModel.mm +++ b/ios/RnExecutorch/models/BaseModel.mm @@ -13,7 +13,7 @@ - (NSArray *)forward:(NSArray *)input { [inputTypes addObject:[module getInputType:[NSNumber numberWithInt:i]]]; } - NSArray *result = [module forward:input shapes:shapes inputTypes:inputTypes]; + NSArray *result = [module forward:@[input] shapes:shapes inputTypes:inputTypes]; return result; } From 5e748b38d07c3fd742d8e6adff0dfa5c96ba37ba Mon Sep 17 00:00:00 2001 From: chmjkb Date: Mon, 24 Feb 2025 08:22:49 +0100 Subject: [PATCH 23/25] feat: add multiple inputs to hookless api --- src/modules/general/ExecutorchModule.ts | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/modules/general/ExecutorchModule.ts b/src/modules/general/ExecutorchModule.ts index 5d5990c407..54f6ac7f36 100644 --- a/src/modules/general/ExecutorchModule.ts +++ b/src/modules/general/ExecutorchModule.ts @@ -1,20 +1,35 @@ import { BaseModule } from '../BaseModule'; import { ETError, getError } from '../../Error'; import { _ETModule } from '../../native/RnExecutorchModules'; -import { ETInput, getTypeIdentifier } from '../../types/common'; +import { ETInput } from '../../types/common'; +import { getTypeIdentifier } from '../../useModule'; export class ExecutorchModule extends BaseModule { static module = new _ETModule(); - static async forward(input: ETInput, shape: number[]) { - const inputType = getTypeIdentifier(input); - if (inputType === -1) { - throw new Error(getError(ETError.InvalidArgument)); + static async forward(input: ETInput[] | ETInput, shape: number[][]) { + if (!Array.isArray(input)) { + input = [input]; + } + + let inputTypeIdentifiers = []; + let modelInputs = []; + + for (let idx = 0; idx < input.length; idx++) { + let currentInputTypeIdentifier = getTypeIdentifier(input[idx] as ETInput); + if (currentInputTypeIdentifier === -1) { + throw new Error(getError(ETError.InvalidArgument)); + } + inputTypeIdentifiers.push(currentInputTypeIdentifier); + modelInputs.push([...(input[idx] as unknown as number[])]); } try { - const numberArray = [...input] as number[]; - return await this.module.forward(numberArray, shape, inputType); + return await this.module.forward( + modelInputs, + shape, + inputTypeIdentifiers + ); } catch (e) { throw new Error(getError(e)); } From 3217786208bd4d2d49e6f1352d7630905fcc0aed Mon Sep 17 00:00:00 2001 From: chmjkb Date: Mon, 24 Feb 2025 11:17:56 +0100 Subject: [PATCH 24/25] fix: minor fix --- src/hooks/general/useExecutorchModule.ts | 5 ++++- src/hooks/useModule.ts | 19 ++++++++++--------- src/modules/general/ExecutorchModule.ts | 2 +- src/native/NativeETModule.ts | 6 +++--- src/native/RnExecutorchModules.ts | 4 ++-- src/types/common.ts | 5 ++++- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/hooks/general/useExecutorchModule.ts b/src/hooks/general/useExecutorchModule.ts index 99e2a2fcf8..6e174fbe35 100644 --- a/src/hooks/general/useExecutorchModule.ts +++ b/src/hooks/general/useExecutorchModule.ts @@ -15,7 +15,10 @@ export const useExecutorchModule = ({ isReady: boolean; isGenerating: boolean; downloadProgress: number; - forward: (input: ETInput, shape: number[]) => Promise; + forward: ( + input: ETInput | ETInput[], + shape: number[] | number[][] + ) => Promise; loadMethod: (methodName: string) => Promise; loadForward: () => Promise; } => { diff --git a/src/hooks/useModule.ts b/src/hooks/useModule.ts index bc6214bcd6..5a85522eb0 100644 --- a/src/hooks/useModule.ts +++ b/src/hooks/useModule.ts @@ -4,8 +4,7 @@ import { ETError, getError } from '../Error'; import { ETInput, Module } from '../types/common'; import { _ETModule } from '../native/RnExecutorchModules'; - -const getTypeIdentifier = (input: ETInput): number => { +export const getTypeIdentifier = (input: ETInput): number => { if (input instanceof Int8Array) return 1; if (input instanceof Int32Array) return 3; if (input instanceof BigInt64Array) return 4; @@ -27,7 +26,7 @@ interface _Module { forwardETInput: ( input: ETInput[] | ETInput, shape: number[][] | number[] - ) => ReturnType<_ETModule["forward"]>; + ) => ReturnType<_ETModule['forward']>; forwardImage: (input: string) => Promise; } @@ -85,20 +84,22 @@ export const useModule = ({ modelSource, module }: Props): _Module => { } // Since the native module expects an array of inputs and an array of shapes, - // if the user provides a single ETInput, we want to "unsqueeze" the array so + // if the user provides a single ETInput, we want to "unsqueeze" the array so // the data is properly processed on the native side if (!Array.isArray(input)) { input = [input]; } - if (!Array.isArray(shape)) { - shape = [shape]; + + if (!Array.isArray(shape[0])) { + shape = [shape] as number[][]; } - let inputTypeIdentifiers = []; - let modelInputs = []; + + let inputTypeIdentifiers: any[] = []; + let modelInputs: any[] = []; for (let idx = 0; idx < input.length; idx++) { let currentInputTypeIdentifier = getTypeIdentifier(input[idx] as ETInput); - if (currentInputTypeIdentifier == -1) { + if (currentInputTypeIdentifier === -1) { throw new Error(getError(ETError.InvalidArgument)); } inputTypeIdentifiers.push(currentInputTypeIdentifier); diff --git a/src/modules/general/ExecutorchModule.ts b/src/modules/general/ExecutorchModule.ts index 54f6ac7f36..9ae1e27a7d 100644 --- a/src/modules/general/ExecutorchModule.ts +++ b/src/modules/general/ExecutorchModule.ts @@ -2,7 +2,7 @@ import { BaseModule } from '../BaseModule'; import { ETError, getError } from '../../Error'; import { _ETModule } from '../../native/RnExecutorchModules'; import { ETInput } from '../../types/common'; -import { getTypeIdentifier } from '../../useModule'; +import { getTypeIdentifier } from '../../hooks/useModule'; export class ExecutorchModule extends BaseModule { static module = new _ETModule(); diff --git a/src/native/NativeETModule.ts b/src/native/NativeETModule.ts index 43a5ddbf38..feb9850938 100644 --- a/src/native/NativeETModule.ts +++ b/src/native/NativeETModule.ts @@ -5,10 +5,10 @@ export interface Spec extends TurboModule { loadModule(modelSource: string): Promise; forward( - inputs: number[], - shapes: number[], + inputs: number[][], + shapes: number[][], inputTypes: number[] - ): Promise; + ): Promise; loadMethod(methodName: string): Promise; } diff --git a/src/native/RnExecutorchModules.ts b/src/native/RnExecutorchModules.ts index 7c65fc4a16..7918281d0d 100644 --- a/src/native/RnExecutorchModules.ts +++ b/src/native/RnExecutorchModules.ts @@ -112,8 +112,8 @@ class _ClassificationModule { class _ETModule { async forward( - inputs: number[], - shapes: number[], + inputs: number[][], + shapes: number[][], inputTypes: number[] ): ReturnType { return await ETModule.forward(inputs, shapes, inputTypes); diff --git a/src/types/common.ts b/src/types/common.ts index ee65772beb..b1f3602824 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -31,7 +31,10 @@ export interface ExecutorchModule { error: string | null; isReady: boolean; isGenerating: boolean; - forward: (inputs: ETInput[] | ETInput, shapes: number[][]) => ReturnType<_ETModule['forward']> + forward: ( + inputs: ETInput[] | ETInput, + shapes: number[][] + ) => ReturnType<_ETModule['forward']>; loadMethod: (methodName: string) => Promise; loadForward: () => Promise; } From 27298382b8d3f7da1b954543bd757db505ce5c10 Mon Sep 17 00:00:00 2001 From: chmjkb Date: Mon, 24 Feb 2025 11:21:47 +0100 Subject: [PATCH 25/25] fix: add --noEmit flag to lefthook --- lefthook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lefthook.yml b/lefthook.yml index 335348649a..30305336c1 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -6,4 +6,4 @@ pre-commit: run: npx eslint {staged_files} types: glob: '*.{js,ts, jsx, tsx}' - run: npx tsc + run: npx tsc --noEmit