diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.h b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.h new file mode 100644 index 00000000000..c0c7dfbc49f --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.h @@ -0,0 +1,27 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#ifdef __cplusplus + #import + #import +#endif +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ExecutorchRuntimeTensorValue : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initWithFloatArray:(NSArray *)floatArray shape:(NSArray *)sizes NS_SWIFT_NAME(init(floatArray:shape:)); + +#ifdef __cplusplus +- (nullable instancetype)initWithTensor:(torch::executor::Tensor)tensor error:(NSError * _Nullable * _Nullable)error; +- (instancetype)initWithData:(std::vector)floatData + shape:(std::vector)shape NS_DESIGNATED_INITIALIZER; +- (torch::executor::Tensor)backedValue; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.mm b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.mm new file mode 100644 index 00000000000..933bbe99e57 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeTensorValue.mm @@ -0,0 +1,100 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import "ExecutorchRuntimeTensorValue.h" + +#import + +#import + +using torch::executor::TensorImpl; +using torch::executor::ScalarType; + +@implementation ExecutorchRuntimeTensorValue +{ + std::unique_ptr _tensor; + // TensorImpl DOES NOT take ownership. + // This float vector is what keeps the data in memory. + std::vector _floatData; + std::vector _shape; +} + +- (instancetype)initWithData:(std::vector)floatData + shape:(std::vector)shape +{ + if (self = [super init]) { + _floatData.assign(floatData.begin(), floatData.end()); + _shape.assign(shape.begin(), shape.end()); + _tensor = std::make_unique(ScalarType::Float, std::size(_shape), _shape.data(), _floatData.data()); + } + return self; +} + +- (instancetype)initWithFloatArray:(NSArray *)floatArray shape:(NSArray *)shape +{ + std::vector floatVector; + std::vector shapeVector; + + floatVector.reserve(floatArray.count); + for (int i = 0; i < floatArray.count; i++) { + floatVector.push_back([floatArray[i] floatValue]); + } + shapeVector.reserve(shape.count); + for (int i = 0; i < shape.count; i++) { + shapeVector.push_back([shape[i] intValue]); + } + + return [self initWithData:floatVector shape:shapeVector]; +} + +- (nullable instancetype)initWithTensor:(torch::executor::Tensor)tensor error:(NSError * _Nullable * _Nullable)error +{ + if (tensor.scalar_type() != ScalarType::Float) { + if (error) { + *error = [ModelRuntimeValueErrorFactory invalidType:[NSString stringWithFormat:@"torch::executor::ScalarType::%hhd", tensor.scalar_type()] expectedType:@"torch::executor::ScalarType::Float"]; + } + return nil; + } + + std::vector floatVector; + std::vector shapeVector; + shapeVector.assign(tensor.sizes().begin(), tensor.sizes().end()); + floatVector.assign(tensor.const_data_ptr(), tensor.const_data_ptr() + tensor.numel()); + return [self initWithData:floatVector shape:shapeVector]; +} + +- (nullable ModelRuntimeTensorValueBridgingTuple *)floatRepresentationAndReturnError:(NSError * _Nullable * _Nullable)error +{ + if (_tensor->scalar_type() == torch::executor::ScalarType::Float) { + const auto *tensorPtr = _tensor->data(); + const auto sizes = _tensor->sizes(); + std::vector tensorVec(tensorPtr, tensorPtr + _tensor->numel()); + std::vector tensorSizes(sizes.begin(), sizes.end()); + + NSMutableArray *floatArray = [[NSMutableArray alloc] initWithCapacity:tensorVec.size()]; + for (float &i : tensorVec) { + [floatArray addObject:@(i)]; + } + + NSMutableArray *sizesArray = [[NSMutableArray alloc] initWithCapacity:tensorSizes.size()]; + for (int &tensorSize : tensorSizes) { + [sizesArray addObject:@(tensorSize)]; + } + + return [[ModelRuntimeTensorValueBridgingTuple alloc] initWithFloatArray:floatArray shape:sizesArray]; + } + + if (error) { + *error = [ModelRuntimeValueErrorFactory + invalidType:[NSString stringWithFormat:@"torch::executor::ScalarType::%hhd", _tensor->scalar_type()] + expectedType:@"torch::executor::ScalarType::Float"]; + } + + return nil; +} + +- (torch::executor::Tensor)backedValue +{ + return torch::executor::Tensor(_tensor.get()); +} + +@end diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.h b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.h new file mode 100644 index 00000000000..591511b2b11 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.h @@ -0,0 +1,28 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#ifdef __cplusplus + #import + #import +#endif + +#import + +#import "ExecutorchRuntimeTensorValue.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ExecutorchRuntimeValue : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initWithTensor:(ExecutorchRuntimeTensorValue *)tensorValue; + +#ifdef __cplusplus +- (instancetype)initWithEValue:(torch::executor::EValue)value NS_DESIGNATED_INITIALIZER; +- (torch::executor::EValue)getBackedValue; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.mm b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.mm new file mode 100644 index 00000000000..f8fb8c4a419 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/Data/ExecutorchRuntimeValue.mm @@ -0,0 +1,73 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import "ExecutorchRuntimeValue.h" + +#import +#import + +#import "ExecutorchRuntimeTensorValue.h" + +using torch::executor::EValue; + +@implementation ExecutorchRuntimeValue +{ + EValue _value; + // IMPORTANT + // Tensor value keeps a reference to the original tensor value. However, the value that is wrapped by LiteInterpreterRuntimeTensorValue DOES NOT TAKE OWNERSHIP OF THE RAW DATA! + // This means once the wrapper is deallocated, the tensor value will be deallocated as well. + // This reference here is to keep the tensor value alive until the runtime is deallocated. + ExecutorchRuntimeTensorValue *_tensorValue; +} + +- (instancetype)initWithEValue:(EValue)value +{ + if (self = [super init]) { + _value = value; + } + return self; +} + +- (instancetype)initWithTensor:(ExecutorchRuntimeTensorValue *)tensorValue +{ + if (self = [self initWithEValue:EValue([tensorValue backedValue])]) { + _tensorValue = tensorValue; + } + return self; +} + +- (nullable NSString *)stringValueAndReturnError:(NSError * _Nullable * _Nullable)error +{ + if (error) { + *error = [ModelRuntimeValueErrorFactory unsupportedType:@"ExecutorchRuntimeValue doesn't support strings"]; + } + return nil; +} + +- (nullable id)tensorValueAndReturnError:(NSError * _Nullable * _Nullable)error +{ + if (_value.isTensor()) { + return [[ExecutorchRuntimeTensorValue alloc] initWithTensor:_value.toTensor() error:error]; + } + + if (error) { + *error = [ModelRuntimeValueErrorFactory + invalidType:[NSString stringWithFormat:@"Tag::%d", _value.tag] + expectedType:@"Tag::Tensor"]; + } + return nil; +} + +- (EValue)getBackedValue +{ + return _value; +} + +- (NSArray> *)arrayValueAndReturnError:(NSError * _Nullable * _Nullable)error +{ + if (error) { + *error = [ModelRuntimeValueErrorFactory unsupportedType:@"EValue doesn't support arrays"]; + } + return nil; +} + +@end diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.h b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.h new file mode 100644 index 00000000000..be965c87a6f --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.h @@ -0,0 +1,23 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import + +#import "ExecutorchRuntimeValue.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ExecutorchRuntimeEngine : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (nullable instancetype)initWithModelPath:(NSString *)modelPath + modelMethodName:(NSString *)modelMethodName + error:(NSError * _Nullable * _Nullable)error NS_DESIGNATED_INITIALIZER; + +- (nullable NSArray *)infer:(NSArray *)input + error:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(infer(input:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.mm b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.mm new file mode 100644 index 00000000000..45a527bd1c0 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/Exported/ExecutorchRuntimeEngine.mm @@ -0,0 +1,107 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import "ExecutorchRuntimeEngine.h" + +#import +#import + +#import + +static int kInitFailed = 0; +static int kInferenceFailed = 1; + +static auto NSStringToString(NSString *string) -> std::string +{ + const char *cStr = [string cStringUsingEncoding:NSUTF8StringEncoding]; + if (cStr) { + return cStr; + } + + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; + return {reinterpret_cast([data bytes]), [data length]}; +} + +static auto StringToNSString(const std::string &string) -> NSString * +{ + CFStringRef cfString = CFStringCreateWithBytes( + kCFAllocatorDefault, + reinterpret_cast(string.c_str()), + string.size(), + kCFStringEncodingUTF8, + false + ); + return (__bridge_transfer NSString *)cfString; +} + +@implementation ExecutorchRuntimeEngine +{ + NSString *_modelPath; + NSString *_modelMethodName; + std::unique_ptr _module; +} + +- (instancetype)initWithModelPath:(NSString *)modelPath + modelMethodName:(NSString *)modelMethodName + error:(NSError * _Nullable * _Nullable)error +{ + if (self = [super init]) { + _modelPath = modelPath; + _modelMethodName = modelMethodName; + try { + _module = std::make_unique(NSStringToString(modelPath)); + const auto e = _module->load_method(NSStringToString(modelMethodName)); + if (e != executorch::runtime::Error::Ok) { + if (error) { + *error = [NSError errorWithDomain:@"ExecutorchRuntimeEngine" + code:kInitFailed + userInfo:@{NSDebugDescriptionErrorKey : StringToNSString(std::to_string(static_cast(e)))}]; + } + return nil; + } + } catch (...) { + if (error) { + *error = [NSError errorWithDomain:@"ExecutorchRuntimeEngine" + code:kInitFailed + userInfo:@{NSDebugDescriptionErrorKey : @"Unknown error"}]; + } + return nil; + } + } + return self; +} + +- (nullable NSArray *)infer:(NSArray *)input + error:(NSError * _Nullable * _Nullable)error +{ + try { + std::vector inputEValues; + inputEValues.reserve(input.count); + for (ExecutorchRuntimeValue *inputValue in input) { + inputEValues.push_back([inputValue getBackedValue]); + } + const auto result = _module->execute(NSStringToString(_modelMethodName), inputEValues); + if (!result.ok()) { + const auto executorchError = static_cast(result.error()); + if (error) { + *error = [NSError errorWithDomain:@"ExecutorchRuntimeEngine" + code:kInferenceFailed + userInfo:@{NSDebugDescriptionErrorKey : StringToNSString(std::to_string(executorchError))}]; + } + return nil; + } + NSMutableArray *const resultValues = [NSMutableArray new]; + for (const auto &evalue : result.get()) { + [resultValues addObject:[[ExecutorchRuntimeValue alloc] initWithEValue:evalue]]; + } + return resultValues; + } catch (...) { + if (error) { + *error = [NSError errorWithDomain:@"LiteInterpreterRuntimeEngine" + code:kInferenceFailed + userInfo:@{NSDebugDescriptionErrorKey : @"Unknown error"}]; + } + return nil; + } +} + +@end diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeEngineTests.mm b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeEngineTests.mm new file mode 100644 index 00000000000..de59902dfca --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeEngineTests.mm @@ -0,0 +1,61 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ExecutorchRuntimeEngineTests : XCTestCase +@end + +@implementation ExecutorchRuntimeEngineTests + +- (void)testInvalidModel +{ + NSString *const modelPath = @"invalid_model_path"; + + NSError *runtimeInitError = nil; + ExecutorchRuntimeEngine *const engine = [[ExecutorchRuntimeEngine alloc] initWithModelPath:modelPath modelMethodName:@"forward" error:&runtimeInitError]; + XCTAssertNil(engine); + XCTAssertNotNil(runtimeInitError); + + XCTAssertEqual(runtimeInitError.code, 0); + XCTAssertEqualObjects(runtimeInitError.userInfo[NSDebugDescriptionErrorKey], @"34"); + // 34 is the code for AccessFailed. +} + +- (void)testValidModel +{ + NSBundle *const bundle = [NSBundle bundleForClass:[self class]]; + // This is a simple model that adds two tensors. + NSString *const modelPath = [bundle pathForResource:@"add" ofType:@"pte"]; + NSError *runtimeInitError = nil; + ExecutorchRuntimeEngine *const engine = [[ExecutorchRuntimeEngine alloc] initWithModelPath:modelPath modelMethodName:@"forward" error:&runtimeInitError]; + XCTAssertNotNil(engine); + XCTAssertNil(runtimeInitError); + + ExecutorchRuntimeTensorValue *inputTensor = [[ExecutorchRuntimeTensorValue alloc] initWithFloatArray:@[@2.0] shape:@[@1]]; + ExecutorchRuntimeValue *inputValue = [[ExecutorchRuntimeValue alloc] initWithTensor:inputTensor]; + + NSError *inferenceError = nil; + const auto output = [engine infer:@[inputValue, inputValue] error:&inferenceError]; + XCTAssertNil(inferenceError); + + XCTAssertEqual(output.count, 1); + NSError *tensorValueError = nil; + NSError *floatRepresentationError = nil; + const auto resultTensorValue = [[output.firstObject tensorValueAndReturnError:&tensorValueError] + floatRepresentationAndReturnError:&floatRepresentationError]; + + XCTAssertNil(tensorValueError); + XCTAssertNil(floatRepresentationError); + XCTAssertEqual(resultTensorValue.floatArray.count, 1); + XCTAssertEqual(resultTensorValue.shape.count, 1); + XCTAssertEqual(resultTensorValue.floatArray.firstObject.floatValue, 4.0); + XCTAssertEqual(resultTensorValue.shape.firstObject.integerValue, 1); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeValueTests.mm b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeValueTests.mm new file mode 100644 index 00000000000..742cfb8d40d --- /dev/null +++ b/extension/apple/ExecutorchRuntimeBridge/ExecutorchRuntimeBridge/__tests__/ExecutorchRuntimeValueTests.mm @@ -0,0 +1,67 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +#import + +#import +#import +#import + +using torch::executor::EValue; +using torch::executor::TensorImpl; +using torch::executor::ScalarType; + +@interface ExecutorchRuntimeValueTests : XCTestCase +@end + +@implementation ExecutorchRuntimeValueTests + +- (void)testStringValueWithError +{ + ExecutorchRuntimeValue *value = [[ExecutorchRuntimeValue alloc] initWithEValue:EValue((int64_t)1)]; + XCTAssertNil([value stringValueAndReturnError:nil]); + NSError *error = nil; + XCTAssertNil([value stringValueAndReturnError:&error]); + XCTAssertNotNil(error); + XCTAssertEqualObjects([error description], @"Unsupported type: ExecutorchRuntimeValue doesn't support strings"); +} + +- (void)testTensorValue +{ + NSMutableArray *data = [NSMutableArray new]; + for (int i = 0; i < 10; i++) { + [data addObject:@(i + 0.5f)]; + } + + NSArray *shape = @[@(10)]; + + ExecutorchRuntimeTensorValue *tensorValue = [[ExecutorchRuntimeTensorValue alloc] initWithFloatArray:data shape:shape]; + + const auto tuple = [tensorValue floatRepresentationAndReturnError:nil]; + XCTAssertEqualObjects(tuple.floatArray, data); + XCTAssertEqualObjects(tuple.shape, shape); +} + +- (void)testTensorValueWithFloatArrayWithError +{ + std::vector data = {1, 2, 3}; + std::vector shape = {3}; + TensorImpl tensorImpl(ScalarType::Int, std::size(shape), shape.data(), data.data()); + + XCTAssertNil([[ExecutorchRuntimeTensorValue alloc] initWithTensor:*new torch::executor::Tensor(&tensorImpl) error:nil]); + NSError *error = nil; + XCTAssertNil([[ExecutorchRuntimeTensorValue alloc] initWithTensor:*new torch::executor::Tensor(&tensorImpl) error:&error]); + XCTAssertNotNil(error); + XCTAssertEqualObjects([error description], @"Invalid type: torch::executor::ScalarType::3, expected torch::executor::ScalarType::Float"); +} + +- (void)testTensorValueWithError +{ + ExecutorchRuntimeValue *value = [[ExecutorchRuntimeValue alloc] initWithEValue:EValue((int64_t)1)]; + XCTAssertNil([value tensorValueAndReturnError:nil]); + NSError *error = nil; + XCTAssertNil([value tensorValueAndReturnError:&error]); + XCTAssertNotNil(error); + XCTAssertEqualObjects([error description], @"Invalid type: Tag::4, expected Tag::Tensor"); +} + +@end diff --git a/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport.swift b/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport.swift new file mode 100644 index 00000000000..3fa2f590d85 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport.swift @@ -0,0 +1,39 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +@_implementationOnly import ExecutorchRuntimeBridge +import Foundation +import ModelRunnerDataKit + +public struct ExecutorchRuntimeValueSupport { + + public init() {} +} + +extension ExecutorchRuntimeValueSupport: ModelRuntimeValueFactory { + + public func createString(value: String) throws -> ModelRuntimeValue { + throw ModelRuntimeValueError.unsupportedType(String(describing: String.self)) + } + + public func createTensor(value: ModelRuntimeTensorValue) throws -> ModelRuntimeValue { + guard let tensorValue = value.innerValue as? ExecutorchRuntimeTensorValue else { + throw ModelRuntimeValueError.invalidType( + String(describing: value.innerValue.self), + String(describing: ExecutorchRuntimeTensorValue.self) + ) + } + return ModelRuntimeValue(innerValue: ExecutorchRuntimeValue(tensor: tensorValue)) + } +} + +extension ExecutorchRuntimeValueSupport: ModelRuntimeTensorValueFactory { + + public func createFloatTensor(value: [Float], shape: [Int]) -> ModelRuntimeTensorValue { + ModelRuntimeTensorValue( + innerValue: ExecutorchRuntimeTensorValue( + floatArray: value.compactMap { NSNumber(value: $0) }, + shape: shape.compactMap { NSNumber(value: $0) } + ) + ) + } +} diff --git a/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/__tests__/ExecutorchRuntimeValueSupportTests.swift b/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/__tests__/ExecutorchRuntimeValueSupportTests.swift new file mode 100644 index 00000000000..474dc798a42 --- /dev/null +++ b/extension/apple/ExecutorchRuntimeValueSupport/ExecutorchRuntimeValueSupport/__tests__/ExecutorchRuntimeValueSupportTests.swift @@ -0,0 +1,42 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +@testable import ExecutorchRuntimeValueSupport +import XCTest + +public extension String { + + /// Returns a random string. + /// This useful for testing when we want to ensure that production code + /// accidentally pass a test by using the same value as the test. + static func random() -> String { + UUID().uuidString + } +} + +public extension Float { + static func randomPositive() -> Float { + .random(in: 1...Float.greatestFiniteMagnitude) + } +} + +class ExecutorchRuntimeValueSupportTests: XCTestCase { + + func testTensorValue() throws { + let factory = ExecutorchRuntimeValueSupport(), + size = 100, + data = (1...size).map { _ in Float.randomPositive() }, + shape = [size] + + let sut = try XCTUnwrap(try? factory.createTensor(value: factory.createFloatTensor(value: data, shape: shape))) + + XCTAssertEqual(try? sut.tensorValue().floatRepresentation().floatArray, data) + XCTAssertEqual(try? sut.tensorValue().floatRepresentation().shape, shape) + } + + func testCreateStringsThrows() { + let factory = ExecutorchRuntimeValueSupport(), + value: String = .random() + + XCTAssertThrowsError(try factory.createString(value: value)) + } +}