目录

在Apple平台集成和运行ExecuTorch

作者: Anthony Shoumikhin

iOS和macOS的ExecuTorch运行时以一组预构建的.xcframework二进制目标形式分发。这些目标兼容iOS和macOS设备及模拟器,并且在发布模式和调试模式下均可使用:

  • executorch - 主运行时组件

  • backend_coreml - 核心ML后端

  • backend_mps - MPS后端

  • backend_xnnpack - XNNPACK 后端

  • kernels_custom - LLMs的自定义内核

  • kernels_optimized - 优化内核

  • kernels_portable - 可移植内核(使用作为参考的朴素实现)

  • kernels_quantized - 量化内核

将你的二进制文件与 ExecuTorch 运行时以及导出的机器学习模型所使用的任何后端或内核进行链接。建议将核心运行时直接链接到使用 ExecuTorch 的组件,并将内核和后端链接到主应用程序目标。

注意: 要访问日志,请链接到ExecuTorch运行时的调试构建版本,即executorch_debug框架。为了最佳性能,始终链接到交付物的发布版本(不带_debug后缀的版本),这些版本已移除了所有日志记录开销。

集成

Swift Package Manager

预构建的 ExecuTorch 运行时、后端和内核可用作 Swift PM 包。

Xcode

在 Xcode 中,前往File > Add Package Dependencies. 粘贴 PyTorch 深度学习框架网站的 URLExecuTorch 仓库输入搜索栏并选择它。确保将分支名称更改为所需的 ExecuTorch 版本,格式为 “swiftpm-””,(例如 “swiftpm-0.4.0”),或格式为 “swiftpm-用于特定日期的夜间构建(例如:“swiftpm-0.4.0-20241201”)。

然后选择哪个 ExecuTorch 框架应该链接到哪个目标。

点击下方截图观看如何添加包并在iOS上运行简单ExecuTorch模型的演示视频

Integrating and Running ExecuTorch on Apple Platforms

CLI

在你的包文件中添加对 ExecuTorch 的包和目标依赖,如下所示:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
  name: "YourPackageName",
  platforms: [
    .iOS(.v17),
    .macOS(.v10_15),
  ],
  products: [
    .library(name: "YourPackageName", targets: ["YourTargetName"]),
  ],
  dependencies: [
    // Use "swiftpm-<version>.<year_month_day>" branch name for a nightly build.
    .package(url: "https://github.com/pytorch/executorch.git", branch: "swiftpm-0.4.0")
  ],
  targets: [
    .target(
      name: "YourTargetName",
      dependencies: [
        .product(name: "executorch", package: "executorch"),
        .product(name: "backend_xnnpack", package: "executorch"),
        .product(name: "kernels_portable", package: "executorch"),
        // Add other backends and kernels as needed.
      ]),
  ]
)

然后检查一切是否正常工作:

cd path/to/your/package

swift package resolve

# or just build it
swift build

本地构建

另一种集成 ExecuTorch 运行时的方法是,从源代码本地构建所需的组件,并链接到它们。这种方法更为复杂,但无疑是可以实现的。

  1. 安装 Xcode 15+ 和命令行工具:

xcode-select --install
  1. 克隆 ExecuTorch:

git clone https://github.com/pytorch/executorch.git --depth 1 --recurse-submodules --shallow-submodules && cd executorch
  1. 配置 Python 3.10+ 并激活一个虚拟环境:

python3 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip
  1. 安装所需的依赖项,包括用于后端如 Core MLMPS 的依赖项,如果您计划也构建它们:

./install_requirements.sh --pybind coreml mps xnnpack

# Optional dependencies for Core ML backend.
./backends/apple/coreml/scripts/install_requirements.sh

# And MPS backend.
./backends/apple/mps/install_requirements.sh
  1. 安装 CMake:

CMake 官方网站下载 macOS 二进制分发包,打开 .dmg 文件,将 CMake.app 移动到 /Applications 目录,然后运行以下命令以安装 CMake 命令行工具:

sudo /Applications/CMake.app/Contents/bin/cmake-gui --install
  1. 使用提供的脚本构建 .xcframeworks:

./build/build_apple_frameworks.sh --help

例如,以下调用将构建 ExecuTorch 运行时以及 Apple 平台当前所有可用的内核和后端:

./build/build_apple_frameworks.sh --coreml --mps --xnnpack --custom --optimized --portable --quantized

在上述命令后添加一个 --Debug 标志,如果需要的话,可以构建带有调试符号的二进制文件。

在构建成功完成后,生成的框架可以找到在 cmake-out 目录中。 将它们复制到您的项目中,并将它们链接到您的目标。

链接

ExecuTorch 在应用程序启动时通过在静态字典中注册它们来初始化其后端和内核(操作符)。如果您在运行时遇到“未注册的内核”或“未注册的后端”等错误,可能需要显式强制加载某些组件。在 Xcode 构建配置中使用 -all_load-force_load 链接器标志,以确保组件尽早注册。

这是一个Xcode配置文件的例子 (.xcconfig):

ET_PLATFORM[sdk=iphonesimulator*] = simulator
ET_PLATFORM[sdk=iphoneos*] = ios
ET_PLATFORM[sdk=macos*] = macos

OTHER_LDFLAGS = $(inherited) \
    -force_load $(BUILT_PRODUCTS_DIR)/libexecutorch-$(ET_PLATFORM)-release.a \
    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_coreml-$(ET_PLATFORM)-release.a \
    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_mps-$(ET_PLATFORM)-release.a \
    -force_load $(BUILT_PRODUCTS_DIR)/libbackend_xnnpack-$(ET_PLATFORM)-release.a \
    -force_load $(BUILT_PRODUCTS_DIR)/libkernels_optimized-$(ET_PLATFORM)-release.a \
    -force_load $(BUILT_PRODUCTS_DIR)/libkernels_quantized-$(ET_PLATFORM)-release.a

对于调试构建配置,将库文件名中的 release 替换为 debug。即使其他组件是为发布版本构建的,请记得在调试模式下链接到 ExecuTorch 运行时 (libexecutorch),以便在需要时保留日志。

你可以在 Xcode 中将此类配置文件分配给你的目标:

  1. 添加 .xcconfig 文件到您的项目中。

  2. 导航到项目的 Info 标签页。

  3. 在构建配置中选择配置文件以用于发布(或调试)模式。

运行时API

查看C++运行时API张量教程,以了解更多关于如何加载和运行导出模型的信息。建议在macOS或iOS上使用C++ API,并根据需要使用Objective-C++和Swift代码进行封装,以便将其暴露给其他组件。请参考示例应用作为此类设置的示例。

一旦链接到 executorch 运行时框架,目标就可以导入所有 ExecuTorch 公共头文件。例如,在 Objective-C++ 中:

#import <ExecuTorch/ExecuTorch.h>
#import <executorch/extension/module/module.h>
#import <executorch/extension/tensor/tensor.h>

或者在 Swift 中:

import ExecuTorch

注意: 导入ExecuTorch伞形头文件(或Swift中的ExecuTorch模块)仅提供对日志API的访问。您仍需根据需要显式导入其他运行时头文件,例如 module.h。在Objective-C或Swift中,除了下面描述的日志功能外,不支持其他运行时API。

注意: 在ExecuTorch框架的发布构建中,日志会被剥离。为了保留日志,请在开发期间使用调试构建。

日志记录

我们为Objective-C和Swift提供了额外的API,作为内部ExecuTorch机制的轻量级包装器。要使用它,只需在Objective-C中导入主框架头文件。然后使用ExecuTorchLog接口(或在Swift中使用Log类)来订阅您自己的ExecuTorchLogSink协议(或LogSink在Swift中)实现,以监听日志事件。

#import <ExecuTorch/ExecuTorch.h>
#import <os/log.h>

@interface MyClass : NSObject<ExecuTorchLogSink>
@end

@implementation MyClass

- (instancetype)init {
  self = [super init];
  if (self) {
#if DEBUG
    [ExecuTorchLog.sharedLog addSink:self];
#endif
  }
  return self;
}

- (void)dealloc {
#if DEBUG
  [ExecuTorchLog.sharedLog removeSink:self];
#endif
}

#if DEBUG
- (void)logWithLevel:(ExecuTorchLogLevel)level
           timestamp:(NSTimeInterval)timestamp
            filename:(NSString *)filename
                line:(NSUInteger)line
             message:(NSString *)message {
  NSString *logMessage = [NSString stringWithFormat:@"%@:%lu %@", filename, (unsigned long)line, message];
  switch (level) {
    case ExecuTorchLogLevelDebug:
      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "%{public}@", logMessage);
      break;
    case ExecuTorchLogLevelInfo:
      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_INFO, "%{public}@", logMessage);
      break;
    case ExecuTorchLogLevelError:
      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_ERROR, "%{public}@", logMessage);
      break;
    case ExecuTorchLogLevelFatal:
      os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_FAULT, "%{public}@", logMessage);
      break;
    default:
      os_log(OS_LOG_DEFAULT, "%{public}@", logMessage);
      break;
  }
}
#endif

@end

Swift 版本:

import ExecuTorch
import os.log

public class MyClass {
  public init() {
    #if DEBUG
    Log.shared.add(sink: self)
    #endif
  }
  deinit {
    #if DEBUG
    Log.shared.remove(sink: self)
    #endif
  }
}

#if DEBUG
extension MyClass: LogSink {
  public func log(level: LogLevel, timestamp: TimeInterval, filename: String, line: UInt, message: String) {
    let logMessage = "\(filename):\(line) \(message)"
    switch level {
    case .debug:
      os_log(.debug, "%{public}@", logMessage)
    case .info:
      os_log(.info, "%{public}@", logMessage)
    case .error:
      os_log(.error, "%{public}@", logMessage)
    case .fatal:
      os_log(.fault, "%{public}@", logMessage)
    default:
      os_log("%{public}@", logMessage)
    }
  }
}
#endif

注意: 在示例中,当代码未构建为调试模式时,日志会被有意移除,即 DEBUG 宏未定义或等于零。

调试

如果你正在链接到 ExecuTorch 框架的 Debug 构建版本,请在调试会话中使用以下 LLDB 命令配置调试器,以正确映射源代码:

settings append target.source-map /executorch <path_to_executorch_source_code>

调试

执行缓慢

确保导出的模型使用适当的后端,例如 XNNPACK、Core ML 或 MPS。如果已调用正确的后端但性能问题仍然存在,请确认您正在链接到后端运行时的 Release 构建版本。

为了获得最佳性能,请在发布模式下链接 ExecuTorch 运行时。如果需要调试,可以保留 ExecuTorch 运行时在调试模式下,对性能影响极小,但可以保留日志和调试符号。

Swift PM

如果您在使用Swift PM时遇到校验和不匹配错误,请通过Xcode菜单 (File > Packages > Reset Package Caches) 或以下命令清除包缓存:

rm -rf <YouProjectName>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm \
  ~/Library/org.swift.swiftpm \
  ~/Library/Caches/org.swift.swiftpm \
  ~/Library/Caches/com.apple.dt.Xcode \
  ~/Library/Developer/Xcode/DerivedData

注意: 在运行终端命令之前,请确保已完全退出Xcode,以避免与活动进程发生冲突。

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源