Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,17 @@ endif()
if(EXECUTORCH_BUILD_VULKAN)
string(APPEND _executorch_backend_dependencies "find_dependency(Threads)\n")
endif()
if(EXECUTORCH_BUILD_COREAI)
string(
APPEND
_executorch_backend_dependencies
"include(\"\${CMAKE_CURRENT_LIST_DIR}/ExecuTorchCoreAIDependencies.cmake\")\n"
"executorch_coreai_dependencies()\n"
)
install(FILES tools/cmake/ExecuTorchCoreAIDependencies.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ExecuTorch
)
endif()
configure_file(
tools/cmake/executorch-backend-dependencies.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/executorch-backend-dependencies.cmake @ONLY
Expand Down
267 changes: 267 additions & 0 deletions backends/apple/coreai/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@ if(NOT APPLE)
message(FATAL_ERROR "Core AI requires Apple platforms")
endif()

if(CMAKE_VERSION VERSION_LESS 3.31)
message(FATAL_ERROR "Core AI requires CMake 3.31 or newer for Swift support")
endif()

# Inherited Clang flags and numeric definitions are not valid Swift options.
foreach(property IN ITEMS COMPILE_DEFINITIONS COMPILE_OPTIONS)
get_directory_property(_coreai_inherited ${property})
set_property(DIRECTORY PROPERTY ${property} "")
foreach(value IN LISTS _coreai_inherited)
set_property(
DIRECTORY
APPEND
PROPERTY ${property} "$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:${value}>"
)
endforeach()
endforeach()

enable_language(OBJCXX)

set(_coreai_runtime_sources runtime/coreai_assets.mm runtime/coreai_storage.mm
runtime/coreai_bookmarks.mm
)

# Keep ARC and C++ exception settings away from the private Swift module.
function(coreai_configure_objc_target target)
target_include_directories(
${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/runtime
Expand Down Expand Up @@ -50,3 +68,252 @@ if(EXECUTORCH_BUILD_TESTS AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
add_test(NAME coreai_host_test COMMAND coreai_host_test)
set_tests_properties(coreai_host_test PROPERTIES TIMEOUT 120)
endif()

if(NOT CMAKE_GENERATOR MATCHES "Ninja|Xcode")
message(FATAL_ERROR "Core AI Swift compilation requires Ninja or Xcode")
endif()
if(NOT CMAKE_SYSTEM_NAME MATCHES "^(Darwin|iOS)$")
message(FATAL_ERROR "Core AI currently supports macOS and iOS only")
endif()

include(${PROJECT_SOURCE_DIR}/tools/cmake/ExecuTorchCoreAIDependencies.cmake)
executorch_coreai_dependencies()
set(_coreai_deployment 27.0)
if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER _coreai_deployment)
set(_coreai_deployment "${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
# CMake 3.31 does not infer an iOS target for its Swift compiler probe.
if(CMAKE_SYSTEM_NAME STREQUAL "iOS"
AND NOT CMAKE_GENERATOR STREQUAL "Xcode"
AND NOT CMAKE_Swift_COMPILER_TARGET
)
if(CMAKE_OSX_ARCHITECTURES)
list(GET CMAKE_OSX_ARCHITECTURES 0 _coreai_probe_arch)
else()
set(_coreai_probe_arch "${CMAKE_SYSTEM_PROCESSOR}")
endif()
set(_coreai_probe_environment "")
if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
set(_coreai_probe_environment "-simulator")
endif()
set(CMAKE_Swift_COMPILER_TARGET
"${_coreai_probe_arch}-apple-ios${_coreai_deployment}${_coreai_probe_environment}"
)
endif()
enable_language(Swift)

# CMake has no per-target OSX_DEPLOYMENT_TARGET property for Ninja.
function(coreai_require_os27 target)
set(deployment "${_coreai_deployment}")
set(target_environment "")
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(platform ios)
if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
set(minimum_flag "-mios-simulator-version-min=${deployment}")
set(target_environment -simulator)
else()
set(minimum_flag "-miphoneos-version-min=${deployment}")
endif()
else()
set(platform macosx)
set(minimum_flag "-mmacosx-version-min=${deployment}")
endif()
set_target_properties(
${target}
PROPERTIES XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${deployment}"
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${deployment}"
)
if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
set(arch "${CMAKE_OSX_ARCHITECTURES}")
if(NOT arch)
set(arch "${CMAKE_SYSTEM_PROCESSOR}")
endif()
list(LENGTH arch arch_count)
if(NOT arch_count EQUAL 1)
message(
FATAL_ERROR
"Core AI Ninja builds require one CMAKE_OSX_ARCHITECTURES value"
)
endif()
set(triple "${arch}-apple-${platform}${deployment}${target_environment}")
target_compile_options(
${target}
PRIVATE "$<$<COMPILE_LANGUAGE:C,CXX,OBJC,OBJCXX>:${minimum_flag}>"
"$<$<COMPILE_LANGUAGE:Swift>:-target;${triple}>"
)
target_link_options(
${target} PRIVATE "$<$<LINK_LANGUAGE:C,CXX,OBJC,OBJCXX>:${minimum_flag}>"
"$<$<LINK_LANGUAGE:Swift>:-target;${triple}>"
)
endif()
endfunction()

set(_coreai_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
if(CMAKE_GENERATOR STREQUAL "Xcode")
string(APPEND _coreai_generated_dir
"/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"
)
else()
file(MAKE_DIRECTORY "${_coreai_generated_dir}")
endif()
set(_coreai_swift_header "${_coreai_generated_dir}/CoreAIBridge-Swift.h")
add_library(coreai_swift STATIC runtime/ETCoreAIModel.swift)
set_target_properties(
coreai_swift
PROPERTIES Swift_MODULE_NAME CoreAIBridge
Swift_MODULE_DIRECTORY "${_coreai_generated_dir}"
Swift_LANGUAGE_VERSION 6
POSITION_INDEPENDENT_CODE ON
)
if(CMAKE_GENERATOR STREQUAL "Xcode")
set_target_properties(
coreai_swift
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_MODULE_NAME CoreAIBridge
XCODE_ATTRIBUTE_SWIFT_MODULE_NAME CoreAIBridge
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER
"${CMAKE_CURRENT_SOURCE_DIR}/runtime/ETCoreAIBridge.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME
CoreAIBridge-Swift.h
)
# Xcode merges architecture-specific headers into DERIVED_SOURCES_DIR.
add_custom_command(
TARGET coreai_swift
POST_BUILD
COMMAND
${CMAKE_COMMAND} "-DPUBLISH_ROOT=${CMAKE_CURRENT_BINARY_DIR}/generated" -P
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/PublishSwiftHeader.cmake"
BYPRODUCTS "${_coreai_swift_header}"
COMMENT "Publish the merged Swift Objective-C header"
VERBATIM
)
else()
set(_coreai_swift_options
-import-objc-header
"${CMAKE_CURRENT_SOURCE_DIR}/runtime/ETCoreAIBridge.h" -emit-objc-header
-emit-objc-header-path "${_coreai_swift_header}"
)
target_compile_options(
coreai_swift
PRIVATE "$<$<COMPILE_LANGUAGE:Swift>:${_coreai_swift_options}>"
)
add_custom_command(
TARGET coreai_swift
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E true
BYPRODUCTS "${_coreai_swift_header}"
COMMENT "Track the generated Swift Objective-C header"
)
endif()
# The bridge header is an input to Swift even when only its declarations change.
set_property(
SOURCE runtime/ETCoreAIModel.swift
APPEND
PROPERTY OBJECT_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/runtime/ETCoreAIBridge.h"
)
coreai_require_os27(coreai_swift)
target_link_libraries(coreai_swift PRIVATE executorch::coreai_dependencies)

# Keep SDK entry points separate so delegate tests can substitute a fake.
add_library(
coreai_bridge_obj OBJECT runtime/ETCoreAITensor.mm runtime/coreai_bridge.mm
)
coreai_configure_objc_target(coreai_bridge_obj)
coreai_require_os27(coreai_bridge_obj)
set_target_properties(coreai_bridge_obj PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(coreai_bridge_obj PRIVATE "${_coreai_generated_dir}")
add_dependencies(coreai_bridge_obj coreai_swift)
set_property(
SOURCE runtime/coreai_bridge.mm
APPEND
PROPERTY OBJECT_DEPENDS "${_coreai_swift_header}"
)

# The Swift tests use the bridge's internal declarations, so they compile into
# one module with it and run under xctest. Ninja on macOS only.
if(EXECUTORCH_BUILD_TESTS
AND CMAKE_SYSTEM_NAME STREQUAL "Darwin"
AND NOT CMAKE_GENERATOR STREQUAL "Xcode"
)
execute_process(
COMMAND xcrun --sdk macosx --show-sdk-platform-path
OUTPUT_VARIABLE _coreai_platform
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
)
set(_coreai_xctest_frameworks
"${_coreai_platform}/Developer/Library/Frameworks"
)
set(_coreai_xctest_libs "${_coreai_platform}/Developer/usr/lib")
add_library(
coreai_swift_bridge_test MODULE
runtime/ETCoreAIModel.swift runtime/test/SwiftBridgeTests.swift
runtime/ETCoreAITensor.mm
)
set_target_properties(
coreai_swift_bridge_test
PROPERTIES BUNDLE TRUE
BUNDLE_EXTENSION xctest
Swift_MODULE_NAME CoreAIBridgeTests
Swift_LANGUAGE_VERSION 6
)
coreai_configure_objc_target(coreai_swift_bridge_test)
coreai_require_os27(coreai_swift_bridge_test)
target_compile_options(
coreai_swift_bridge_test
PRIVATE
"$<$<COMPILE_LANGUAGE:Swift>:-import-objc-header;${CMAKE_CURRENT_SOURCE_DIR}/runtime/ETCoreAIBridge.h;-F;${_coreai_xctest_frameworks};-I;${_coreai_xctest_libs}>"
)
target_link_options(
coreai_swift_bridge_test PRIVATE "-F${_coreai_xctest_frameworks}"
"-L${_coreai_xctest_libs}" "LINKER:-rpath,${_coreai_xctest_frameworks}"
"LINKER:-rpath,${_coreai_xctest_libs}"
)
target_link_libraries(
coreai_swift_bridge_test PRIVATE "-framework XCTest" XCTestSwiftSupport
executorch::coreai_dependencies
)
file(GLOB_RECURSE _coreai_fixture_sources CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.py"
)
set(_coreai_fixture_dir "${CMAKE_CURRENT_BINARY_DIR}/bridge_fixture")
add_custom_command(
OUTPUT "${_coreai_fixture_dir}/manifest.json"
"${_coreai_fixture_dir}/dynamic/manifest.json"
BYPRODUCTS "${_coreai_fixture_dir}/model.aimodel/main.hash"
"${_coreai_fixture_dir}/model.aimodel/main.mlirb"
"${_coreai_fixture_dir}/model.aimodel/metadata.json"
"${_coreai_fixture_dir}/dynamic/model.aimodel/main.hash"
"${_coreai_fixture_dir}/dynamic/model.aimodel/main.mlirb"
"${_coreai_fixture_dir}/dynamic/model.aimodel/metadata.json"
COMMAND
"${PYTHON_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/runtime/test/export_bridge_fixture.py"
"${_coreai_fixture_dir}"
DEPENDS ${_coreai_fixture_sources}
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Export the Core AI session test model"
VERBATIM
)
add_custom_target(
coreai_bridge_fixture
DEPENDS "${_coreai_fixture_dir}/manifest.json"
"${_coreai_fixture_dir}/dynamic/manifest.json"
)
add_dependencies(coreai_swift_bridge_test coreai_bridge_fixture)
add_test(NAME coreai_swift_bridge_test
COMMAND xcrun xctest "$<TARGET_BUNDLE_DIR:coreai_swift_bridge_test>"
)
set_tests_properties(
coreai_swift_bridge_test
PROPERTIES TIMEOUT 600 ENVIRONMENT
"COREAI_BRIDGE_FIXTURE_DIR=${_coreai_fixture_dir}"
)
endif()

install(
TARGETS coreai_swift
EXPORT ExecuTorchTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
51 changes: 51 additions & 0 deletions backends/apple/coreai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ export-time SHA-256 digest. Asset paths, including bundle and directory names,
must remain distinct under case-insensitive, canonically normalized Unicode
comparison. Accepted names are preserved, not rewritten.

### Calling from Swift

When using the Core AI backend, run ExecuTorch model loading, inference and
cache operations on a background `DispatchQueue` or a dedicated worker thread.
Do not call these synchronous APIs directly inside Swift `Task` or
`Task.detached`, because doing so can hang your application.

From async Swift code, submit the entire ExecuTorch call with
`DispatchQueue.async` and await its result using a continuation. Do not block
the Swift task with `DispatchQueue.sync` or a semaphore. Keep input tensors alive
and unchanged until the ExecuTorch call returns, even if the waiting task is
cancelled.

### AOT architecture selection

Configure AOT export with `AOTCompileConfig`, including the target `platform` and
Expand Down Expand Up @@ -135,6 +148,44 @@ left untouched; they can be removed separately during a fully quiescent offline
cleanup. The 256-file bound applies to the new stripe pool, not pre-existing
legacy files. Bookmarks and staged bundles need no migration.

## SDK Bridge

The private Swift module `CoreAIBridge` (`runtime/ETCoreAIModel.swift`) wraps
the `CoreAI` framework behind the Objective-C protocols in
`runtime/ETCoreAIBridge.h`. It restores models from bookmarks, specializes source
bundles with the persistent default SDK cache and binds named functions with
ordered inputs and outputs. Asynchronous SDK calls run in detached tasks and
report through completion blocks; function binding completes synchronously.

Inputs are borrowed from caller storage through Core AI raw views; only shape
metadata is copied. Dense row-major outputs are copied with one `memcpy`, and
padded or transposed outputs fall back to strided copies. Only FP16 and FP32
NDArrays are supported.

The Objective-C half of the bridge builds as the private `coreai_bridge_obj`
object library for integration with the production delegate. The separate entry
points allow delegate tests to substitute a fake bridge.

`coreai_swift_bridge_test` runs the bridge's XCTests under `xctest` with
`EXECUTORCH_BUILD_TESTS=ON`. It links CoreAI, so it runs only on macOS 27.
The test build exports static- and dynamic-shape models for real session tests.
CMake's `PYTHON_EXECUTABLE` must use the Python environment with the pinned Core
AI requirements above. Backend Python changes regenerate the dedicated fixture
directory, replacing previous contents. CTest supplies its location to XCTest;
direct XCTest runs without it skip the session tests. Building only
`coreai_host_test` does not export fixtures or build the Swift test bundle.

## Building

Core AI builds require an Apple SDK 27 or newer, Swift from Xcode 27, and CMake
3.31 or newer with the Ninja or Xcode generator. Execution requires macOS 27 or
iOS 27. `EXECUTORCH_BUILD_COREAI` defaults to `OFF`. Enabling it builds the
private Swift implementation, module `CoreAIBridge`. Only Core AI targets get an
OS 27 minimum; unrelated runtime targets keep the configured deployment target.
Ninja builds use one architecture per build directory. Current runtime support
covers arm64 macOS and iOS device builds. x86_64 builds are blocked by Swift
`Float16` availability, and the tested iOS simulator SDKs do not contain Core AI.

## Host Tests

On macOS, `EXECUTORCH_BUILD_COREAI=ON` with `EXECUTORCH_BUILD_TESTS=ON`
Expand Down
17 changes: 17 additions & 0 deletions backends/apple/coreai/cmake/PublishSwiftHeader.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

if("$ENV{DERIVED_SOURCES_DIR}" STREQUAL "" OR "$ENV{CONFIGURATION}" STREQUAL "")
message(FATAL_ERROR "Swift header publication requires Xcode build settings")
endif()
set(_header "$ENV{DERIVED_SOURCES_DIR}/$ENV{SWIFT_OBJC_INTERFACE_HEADER_NAME}")
set(_destination
"${PUBLISH_ROOT}/$ENV{CONFIGURATION}$ENV{EFFECTIVE_PLATFORM_NAME}"
)
file(MAKE_DIRECTORY "${_destination}")
file(COPY_FILE "${_header}" "${_destination}/CoreAIBridge-Swift.h"
ONLY_IF_DIFFERENT
)
Loading
Loading