Skip to content

Native Objective‑C/Swift Wrapper for the ExecuTorch Module #8363

Open
@shoumikhin

Description

@shoumikhin

🚀 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 to NSError instances (using domain ExecuTorchErrorDomain).
    • Swift sees these as throwing errors, ensuring a natural Swift error‑handling flow.

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];
}

cc @mergennachin @byjlw

Metadata

Metadata

Assignees

Labels

module: iosIssues related to iOS code, build, and executionmodule: user experienceIssues related to reducing friction for userstriagedThis issue has been looked at a team member, and triaged and prioritized into an appropriate module

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions