• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Integrating and Running ExecuTorch on Apple Platforms
2
3**Author:** [Anthony Shoumikhin](https://github.com/shoumikhin)
4
5The ExecuTorch Runtime for iOS and macOS is distributed as a collection of prebuilt [.xcframework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle) binary targets. These targets are compatible with both iOS and macOS devices and simulators and are available in both release and debug modes:
6
7* `executorch` - Main Runtime components
8* `backend_coreml` - Core ML backend
9* `backend_mps` - MPS backend
10* `backend_xnnpack` - XNNPACK backend
11* `kernels_custom` - Custom kernels for LLMs
12* `kernels_optimized` - Optimized kernels
13* `kernels_portable` - Portable kernels (naive implementation used as a reference)
14* `kernels_quantized` - Quantized kernels
15
16Link your binary with the ExecuTorch runtime and any backends or kernels used by the exported ML model. It is recommended to link the core runtime to the components that use ExecuTorch directly, and link kernels and backends against the main app target.
17
18**Note:** To access logs, link against the Debug build of the ExecuTorch runtime, i.e., the `executorch_debug` framework. For optimal performance, always link against the Release version of the deliverables (those without the `_debug` suffix), which have all logging overhead removed.
19
20## Integration
21
22### Setup
23
24#### CMake
25
26Building the Xcode project requires CMake. Installing via homebrew does not
27typically work; instead, install the packaged application and commandline tools
28globally:
29
301. Download the macOS `.dmg` installer from https://cmake.org/download
312. Open the `.dmg`
323. Drag the CMake app to the `/Applications` folder
334. In a terminal, install the command line tools: `sudo /Applications/CMake.app/Contents/bin/cmake-gui --install`
34
35### Swift Package Manager
36
37The prebuilt ExecuTorch runtime, backend, and kernels are available as a [Swift PM](https://www.swift.org/documentation/package-manager/) package.
38
39#### Xcode
40
41In Xcode, go to `File > Add Package Dependencies`. Paste the URL of the [ExecuTorch repo](https://github.com/pytorch/executorch) into the search bar and select it. Make sure to change the branch name to the desired ExecuTorch version in format "swiftpm-<version>", (e.g. "swiftpm-0.4.0"), or a branch name in format "swiftpm-<version>.<year_month_date>" (e.g. "swiftpm-0.4.0-20241114") for a nightly build on a specific date.
42
43![](_static/img/swiftpm_xcode1.png)
44
45Then select which ExecuTorch framework should link against which target.
46
47![](_static/img/swiftpm_xcode2.png)
48
49Click the screenshot below to watch the *demo video* on how to add the package and run a simple ExecuTorch model on iOS.
50
51<a href="https://pytorch.org/executorch/main/_static/img/swiftpm_xcode.mp4">
52  <img src="https://pytorch.org/executorch/main/_static/img/swiftpm_xcode.png" width="800" alt="Integrating and Running ExecuTorch on Apple Platforms">
53</a>
54
55#### CLI
56
57Add a package and target dependencies on ExecuTorch to your package file like this:
58
59```swift
60// swift-tools-version:5.0
61import PackageDescription
62
63let package = Package(
64  name: "YourPackageName",
65  products: [
66    .library(name: "YourPackageName", targets: ["YourTargetName"]),
67  ],
68  dependencies: [
69    // Use "swiftpm-0.4.0.<year_month_day>" branch name for a nightly build.
70    .package(url: "https://github.com/pytorch/executorch.git", .branch("swiftpm-0.4.0"))
71  ],
72  targets: [
73    .target(
74      name: "YourTargetName",
75      dependencies: [
76        .product(name: "executorch", package: "executorch"),
77        .product(name: "xnnpack_backend", package: "executorch")
78      ]),
79  ]
80)
81```
82
83Then check if everything works correctly:
84
85```bash
86cd path/to/your/package
87
88swift package resolve
89
90# or just build it
91swift build
92```
93
94### Local Build
95
96Another way to integrate the ExecuTorch runtime is to build the necessary components from sources locally and link against them. This route is more involved but certainly doable.
97
981. Install [Xcode](https://developer.apple.com/xcode/resources/) 15+ and Command Line Tools:
99
100```bash
101xcode-select --install
102```
103
1042. Clone ExecuTorch:
105
106```bash
107git clone https://github.com/pytorch/executorch.git --depth 1 --recurse-submodules --shallow-submodules && cd executorch
108```
109
1103. Set up [Python](https://www.python.org/downloads/macos/) 3.10+ and activate a virtual environment:
111
112```bash
113python3 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip
114```
115
1164. Install the required dependencies, including those needed for the backends like [Core ML](build-run-coreml.md) or [MPS](build-run-mps.md), if you plan to build them as well:
117
118```bash
119./install_requirements.sh --pybind coreml mps xnnpack
120
121# Optional dependencies for Core ML backend.
122./backends/apple/coreml/scripts/install_requirements.sh
123
124# And MPS backend.
125./backends/apple/mps/install_requirements.sh
126```
127
1285. Install [CMake](https://cmake.org):
129
130Download the macOS binary distribution from the [CMake website](https://cmake.org/download), open the `.dmg` file, move `CMake.app` to the `/Applications` directory, and then run the following command to install the CMake command-line tools:
131
132```bash
133sudo /Applications/CMake.app/Contents/bin/cmake-gui --install
134```
135
1366. Use the provided script to build .xcframeworks:
137
138```bash
139./build/build_apple_frameworks.sh --help
140```
141
142For example, the following invocation will build the ExecuTorch Runtime and all currently available kernels and backends for the Apple platform:
143
144```bash
145./build/build_apple_frameworks.sh --coreml --mps --xnnpack --custom --optimized --portable --quantized
146```
147
148Append a `--Debug` flag to the above command to build the binaries with debug symbols if needed.
149
150After the build finishes successfully, the resulting frameworks can be found in the `cmake-out` directory.
151Copy them to your project and link them against your targets.
152
153## Linkage
154
155ExecuTorch initializes its backends and kernels (operators) during app startup by registering them in a static dictionary. If you encounter errors like "unregistered kernel" or "unregistered backend" at runtime, you may need to explicitly force-load certain components. Use the `-all_load` or `-force_load` linker flags in your Xcode build configuration to ensure components are registered early.
156
157Here's an example of a Xcode configuration file (`.xcconfig`):
158
159```
160ET_PLATFORM[sdk=iphonesimulator*] = simulator
161ET_PLATFORM[sdk=iphoneos*] = ios
162ET_PLATFORM[sdk=macos*] = macos
163
164OTHER_LDFLAGS = $(inherited) \
165    -force_load $(BUILT_PRODUCTS_DIR)/libexecutorch-$(ET_PLATFORM)-release.a \
166    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_coreml-$(ET_PLATFORM)-release.a \
167    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_mps-$(ET_PLATFORM)-release.a \
168    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_xnnpack-$(ET_PLATFORM)-release.a \
169    -force_load $(BUILT_PRODUCTS_DIR)/libkernels_optimized-$(ET_PLATFORM)-release.a \
170    -force_load $(BUILT_PRODUCTS_DIR)/libkernels_quantized-$(ET_PLATFORM)-release.a
171```
172
173For a Debug build configuration, replace `release` with `debug` in the library file names. Remember to link against the ExecuTorch runtime (`libexecutorch`) in Debug mode even if other components are built for Release to preserve logs if needed.
174
175You can assign such a config file to your target in Xcode:
176
1771.	Add the `.xcconfig` file to your project.
1782.	Navigate to the project’s Info tab.
1793.	Select the configuration file in the build configurations for Release (or Debug) mode.
180
181## Runtime API
182
183Check out the [C++ Runtime API](extension-module.md) and [Tensors](extension-tensor.md) tutorials to learn more about how to load and run an exported model. It is recommended to use the C++ API for macOS or iOS, wrapped with Objective-C++ and Swift code if needed to expose it for other components. Please refer to the [Demo App](demo-apps-ios.md) as an example of such a setup.
184
185Once linked against the `executorch` runtime framework, the target can now import all ExecuTorch public headers. For example, in Objective-C++:
186
187```objectivecpp
188#import <ExecuTorch/ExecuTorch.h>
189#import <executorch/extension/module/module.h>
190#import <executorch/extension/tensor/tensor.h>
191```
192
193Or in Swift:
194
195```swift
196import ExecuTorch
197```
198
199**Note:** Importing the ExecuTorch umbrella header (or ExecuTorch module in Swift) provides access to the logging API only. You still need to import the other runtime headers explicitly as needed, e.g., `module.h`. There is no support for other runtime APIs in Objective-C or Swift beyond logging described below.
200
201**Note:** Logs are stripped in the release builds of ExecuTorch frameworks. To preserve logging, use debug builds during development.
202
203### Logging
204
205We provide extra APIs for logging in Objective-C and Swift as a lightweight wrapper of the internal ExecuTorch machinery. To use it, just import the main framework header in Objective-C. Then use the `ExecuTorchLog` interface (or the `Log` class in Swift) to subscribe your own implementation of the `ExecuTorchLogSink` protocol (or `LogSink` in Swift) to listen to log events.
206
207```objectivec
208#import <ExecuTorch/ExecuTorch.h>
209#import <os/log.h>
210
211@interface MyClass : NSObject<ExecuTorchLogSink>
212@end
213
214@implementation MyClass
215
216- (instancetype)init {
217  self = [super init];
218  if (self) {
219#if DEBUG
220    [ExecuTorchLog.sharedLog addSink:self];
221#endif
222  }
223  return self;
224}
225
226- (void)dealloc {
227#if DEBUG
228  [ExecuTorchLog.sharedLog removeSink:self];
229#endif
230}
231
232#if DEBUG
233- (void)logWithLevel:(ExecuTorchLogLevel)level
234           timestamp:(NSTimeInterval)timestamp
235            filename:(NSString *)filename
236                line:(NSUInteger)line
237             message:(NSString *)message {
238  NSString *logMessage = [NSString stringWithFormat:@"%@:%lu %@", filename, (unsigned long)line, message];
239  switch (level) {
240    case ExecuTorchLogLevelDebug:
241      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "%{public}@", logMessage);
242      break;
243    case ExecuTorchLogLevelInfo:
244      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_INFO, "%{public}@", logMessage);
245      break;
246    case ExecuTorchLogLevelError:
247      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_ERROR, "%{public}@", logMessage);
248      break;
249    case ExecuTorchLogLevelFatal:
250      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_FAULT, "%{public}@", logMessage);
251      break;
252    default:
253      os_log(OS_LOG_DEFAULT, "%{public}@", logMessage);
254      break;
255  }
256}
257#endif
258
259@end
260```
261
262Swift version:
263
264```swift
265import ExecuTorch
266import os.log
267
268public class MyClass {
269  public init() {
270    #if DEBUG
271    Log.shared.add(sink: self)
272    #endif
273  }
274  deinit {
275    #if DEBUG
276    Log.shared.remove(sink: self)
277    #endif
278  }
279}
280
281#if DEBUG
282extension MyClass: LogSink {
283  public func log(level: LogLevel, timestamp: TimeInterval, filename: String, line: UInt, message: String) {
284    let logMessage = "\(filename):\(line) \(message)"
285    switch level {
286    case .debug:
287      os_log(.debug, "%{public}@", logMessage)
288    case .info:
289      os_log(.info, "%{public}@", logMessage)
290    case .error:
291      os_log(.error, "%{public}@", logMessage)
292    case .fatal:
293      os_log(.fault, "%{public}@", logMessage)
294    default:
295      os_log("%{public}@", logMessage)
296    }
297  }
298}
299#endif
300```
301
302**Note:** In the example, the logs are intentionally stripped out when the code is not built for Debug mode, i.e., the `DEBUG` macro is not defined or equals zero.
303
304## Debugging
305
306If you are linking against a Debug build of the ExecuTorch frameworks, configure your debugger to map the source code correctly by using the following LLDB command in the debug session:
307
308```
309settings append target.source-map /executorch <path_to_executorch_source_code>
310```
311
312## Troubleshooting
313
314### Slow execution
315
316Ensure the exported model is using an appropriate backend, such as XNNPACK, Core ML, or MPS. If the correct backend is invoked but performance issues persist, confirm that you are linking against the Release build of the backend runtime.
317
318For optimal performance, link the ExecuTorch runtime in Release mode too. If debugging is needed, you can keep the ExecuTorch runtime in Debug mode with minimal impact on performance, but preserve logging and debug symbols.
319
320### Swift PM
321
322If you encounter a checksum mismatch error with Swift PM, clear the package cache using the Xcode menu (`File > Packages > Reset Package Caches`) or the following command:
323
324```bash
325rm -rf <YouProjectName>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm \
326  ~/Library/org.swift.swiftpm \
327  ~/Library/Caches/org.swift.swiftpm \
328  ~/Library/Caches/com.apple.dt.Xcode \
329  ~/Library/Developer/Xcode/DerivedData
330```
331**Note:** Ensure Xcode is fully quit before running the terminal command to avoid conflicts with active processes.
332