Description
🚀 The feature, motivation and pitch
Introduce a native Objective‑C wrapper (ExecuTorchModule
) for the C++ Module class, enabling model loading, execution, and basic input/output management from Objective‑C and Swift. In Swift, the wrapper appears as Module
thanks to NS_SWIFT_NAME
annotations.
Alternatives
See the parent issue #8360 for motivation and alternatives overview.
Additional context
Some capabilities from the underlying C++ Module are intentionally omitted in this initial wrapper to keep the minimal API subset. For example:
-
Method Metadata (e.g.
method_meta
) -
Per‑Method Event Tracer
-
Custom Memory Allocators
These advanced features require additional wrap classes or more granular control that is not yet part of this proposal. We may add them in future iterations if there is demand.
Usage Examples:
Objective‑C:
#import <ExecuTorch/ExecuTorch.h>
NSError *error = nil;
ExecuTorchModule *module = [[ExecuTorchModule alloc] initWithFilePath:@"/path/to/model.pte"];
BOOL didLoad = [module loadMethod:@"forward" error:&error];
if (!didLoad) {
NSLog(@"Error: %@", error);
return;
}
NSArray<ExecuTorchValue *> *inputs = ...; // Define input values.
NSArray<ExecuTorchValue *> *outputs = [module forwardWithInputs:inputs error:&error];
if (outputs) {
NSLog(@"Result: %@", outputs);
} else {
NSLog(@"Error: %@", error);
}
Swift (auto‑bridged):
import ExecuTorch
do {
let module = Module(filePath: "/path/to/model.pte")
try module.load("forward")
let inputs = ... // Define input values.
let outputs = try module.forward(inputs)
print("Outputs:", outputs)
} catch {
print("Error:", error)
}
Conversions between NSArray
of ExecuTorchValue
and std::vector<EValue>
are implemented with minimal overhead. All bridging logic resides in the wrapper, so user code remains concise.
RFC (Optional)
Internal Representation
std::unique_ptr<executorch::extension::Module> _module;
The wrapper keeps a unique pointer to the native Module
. Methods like -load:
and -executeMethod:withInputs:error:
directly call corresponding C++ functions (_module->load()
, _module->execute()
) and convert results or error codes into Objective‑C types.
Key Methods:
- Initialization:
Constructs a Module
from a file path and an optional load mode (file vs. mmap, etc.).
Objective-C
- (instancetype)initWithFilePath:(NSString *)filePath;
- (instancetype)initWithFilePath:(NSString *)filePath
loadMode:(ExecuTorchModuleLoadMode)loadMode NS_DESIGNATED_INITIALIZER;
Swift:
let module = Module(filePath: "path/to/model.pte")
let moduleWithLoadMode = Module(filePath: "path/to/model.pte", loadMode: .mmap)
- Loading:
Invokes corresponding Module
methods, e.g. _module->load();
and returns YES
on success, and on failure populates error.
Objective-C
- (BOOL)load:(NSError **)error;
- (BOOL)isLoaded;
- (BOOL)loadMethod:(NSString *)methodName
error:(NSError **)error NS_SWIFT_NAME(load(_:));
- (BOOL)isMethodLoaded:(NSString *)methodName NS_SWIFT_NAME(isLoaded(_:));
Swift:
try module.load("forward")
- Get Method Names
- (nullable NSSet<NSString *> *)methodNames:(NSError **)error;
- Method Execution:
Converts input ExecuTorchValues
to C++ EValues
, calls _module->execute(methodName, inputs)
, and wraps outputs back into Objective‑C objects.
Objective-C
- (nullable NSArray<ExecuTorchValue *> *)executeMethod:(NSString *)methodName
error:(NSError **)error NS_SWIFT_NAME(execute(_:));
- (nullable NSArray<ExecuTorchValue *> *)executeMethod:(NSString *)methodName
withInput:(ExecuTorchValue *)value
error:(NSError **)error NS_SWIFT_NAME(execute(_:_:));
- (nullable NSArray<ExecuTorchValue *> *)executeMethod:(NSString *)methodName
withInputs:(NSArray<ExecuTorchValue *> *)values
error:(NSError **)error NS_SWIFT_NAME(execute(_:_:));
Swift:
let outputs1 = try module.execute("myMethod")
let inputValue = Value(42)
let outputs2 = try module.execute("myMethod", inputValue)
let inputValues: [Value] = [Value(1), Value(2)]
let outputs3 = try module.execute("myMethod", inputValues)
- Forward:
Convenience variations for calling the “forward” method.
Objective-C
- (nullable NSArray<ExecuTorchValue *> *)forward:(NSError **)error;
- (nullable NSArray<ExecuTorchValue *> *)forwardWithInput:(ExecuTorchValue *)value
error:(NSError **)error NS_SWIFT_NAME(forward(_:));
- (nullable NSArray<ExecuTorchValue *> *)forwardWithInputs:(NSArray<ExecuTorchValue *> *)values
error:(NSError **)error NS_SWIFT_NAME(forward(_:));
- (nullable NSArray<ExecuTorchValue *> *)forwardWithTensors:(NSArray<ExecuTorchTensor *> *)tensors
error:(NSError **)error NS_SWIFT_NAME(forward(_:));
Swift:
let outputs1 = try module.forward()
let input = Value(3.14)
let outputs2 = try module.forward(input)
let inputs: [Value] = [Value(1), Value(2)]
let outputs3 = try module.forward(inputs)
let tensor = Tensor([1, 2, 3])
let outputs4 = try module.forward([tensor])
- Set/Get Inputs/Outputs:
For advanced usage, allows specifying input/output EValues
on a per‑index basis (e.g. _module->set_input(...)
).
Objective-C
- (BOOL)setInput:(ExecuTorchValue *)value
atIndex:(NSUInteger)index
error:(NSError **)error NS_SWIFT_NAME(setInput(_:at:));
- (BOOL)setInputs:(NSArray<ExecuTorchValue *> *)values
error:(NSError **)error NS_SWIFT_NAME(setInputs(_:));
- (BOOL)setInput:(ExecuTorchValue *)value
forMethod:(NSString *)methodName
atIndex:(NSUInteger)index
error:(NSError **)error NS_SWIFT_NAME(setInput(_:for:at:));
- (BOOL)setInputs:(NSArray<ExecuTorchValue *> *)values
forMethod:(NSString *)methodName
error:(NSError **)error NS_SWIFT_NAME(setInputs(_:for:));
- (BOOL)setOutput:(ExecuTorchValue *)value
error:(NSError **)error NS_SWIFT_NAME(setOutput(_:));
- (BOOL)setOutput:(ExecuTorchValue *)value
atIndex:(NSUInteger)index
error:(NSError **)error NS_SWIFT_NAME(setOutput(_:at:));
- (BOOL)setOutput:(ExecuTorchValue *)value
forMethod:(NSString *)methodName
error:(NSError **)error NS_SWIFT_NAME(setOutput(_:for:));
- (BOOL)setOutput:(ExecuTorchValue *)value
forMethod:(NSString *)methodName
atIndex:(NSUInteger)index
error:(NSError **)error NS_SWIFT_NAME(setOutput(_:for:at:));
Swift:
let input = Value(10)
try module.setInput(input, for: "forward", at: 0)
let inputs: [Value] = [Value(1), Value(2)]
try module.setInputs(inputs) // Assuming "forward" method.
let output = Value(Tensor(0))
try module.setOutput(output, for: "forward", at: 0)
-
Error Handling & Reporting:
- C++ return codes of type
::executorch::runtime::Error
are mapped toNSError
instances (using domainExecuTorchErrorDomain
). - Swift sees these as throwing errors, ensuring a natural Swift error‑handling flow.
- C++ return codes of type
Example of a method throwing an error:
- (BOOL)load:(NSError **)error {
const auto errorCode = _module->load();
if (errorCode != ::executorch::runtime::Error::Ok) {
if (error) {
*error = [NSError errorWithDomain:ExecuTorchErrorDomain
code:(NSInteger)errorCode
userInfo:nil];
}
return NO;
}
return YES;
}
- Value Conversion
To achieve the minimal overhead during value conversions, Module can use something like the following helper functions internally:
static inline
::executorch::runtime::EValue ConvertValue(ExecuTorchValue *value) {
if (value.isTensor) {
auto *nativeTensorPtr = value.tensorValue.nativeInstance;
ET_CHECK(nativeTensorPtr);
auto nativeTensor = *reinterpret_cast<::executorch::extension::TensorPtr *>(nativeTensorPtr);
ET_CHECK(nativeTensor);
return *nativeTensor;
}
// Handle other value types.
return ::executorch::runtime::EValue();
}
static inline
ExecuTorchValue *ConstructValue(::executorch::runtime::EValue value) {
if (value.isTensor()) {
auto nativeInstance = ::executorch::extension::make_tensor_ptr(value.toTensor());
return [ExecuTorchValue valueWithTensor:[[ExecuTorchTensor alloc] initWithNativeInstance:&nativeInstance]];
}
// Handle other value types.
return [ExecuTorchValue new];
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status