Skip to content

[CoreAI] Add the Core AI SDK Swift and Objective-C bridge - #23391

Draft
metascroy wants to merge 3 commits into
coreai-v2/03b-source-bundlesfrom
coreai-v2/04-sdk-bridge
Draft

metascroy wants to merge 3 commits into
coreai-v2/03b-source-bundlesfrom
coreai-v2/04-sdk-bridge

Conversation

@metascroy

@metascroy metascroy commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Adds the private bridge between ExecuTorch's Objective-C++ code and the Swift CoreAI framework, and packages it for CMake consumers. No ExecuTorch delegate yet; a later PR wires this into CoreAIBackend.

  • runtime/ETCoreAIBridge.h: Objective-C protocols for model acquisition, prepared models, sessions and tensor transfer, plus availability, device architecture and loader factory entry points.
  • runtime/ETCoreAIModel.swift (module CoreAIBridge):
    • Restore models from bookmarks, specialize source bundles with the persistent default SDK cache, bind named functions with ordered inputs/outputs, and evict cache entries.
    • Asynchronous SDK calls run in detached tasks and report through completion blocks. Only an SDK nil result is a cache miss; thrown errors keep their underlying cause.
    • Inputs are borrowed from caller storage through Core AI raw views; outputs use one memcpy for dense row-major arrays and strided copies otherwise.
  • runtime/ETCoreAITensor.mm, runtime/coreai_bridge.mm: tensor wrappers and C entry points, built as the private coreai_bridge_obj object library so the delegate can absorb them and host tests can substitute a fake bridge.
  • Build (backends/apple/coreai/CMakeLists.txt): requires CMake 3.31 and Ninja/Xcode, keeps ExecuTorch's numeric preprocessor definitions away from swiftc, applies an OS 27 minimum only to Core AI targets, and builds coreai_swift. cmake/PublishSwiftHeader.cmake publishes the generated header for Xcode.
  • Tests: runtime/test/SwiftBridgeTests.swift is built with the bridge sources into the coreai_swift_bridge_test XCTest bundle (Ninja, macOS) and run by CTest under xcrun xctest. The invalid-URL case uses a non-file coreai-test: URL.
  • README: "SDK Bridge" and the build requirements in "Building".
  • Changes outside backends/apple/coreai are CMake and CI only, and only take effect when EXECUTORCH_BUILD_COREAI=ON:
    • tools/cmake/ExecuTorchCoreAIDependencies.cmake (new): resolves Foundation, CoreAI and Swift runtime link paths from the consumer's SDK; fails configuration for SDKs older than 27.
    • CMakeLists.txt: install that file into the package config.
    • tools/cmake/preset/default.cmake: note the SDK 27 requirement.
    • .github/workflows/coreai.yml: also trigger on changes to ExecuTorchCoreAIDependencies.cmake.

Stack: 5 of 9, based on #23390. Review only this PR's commit. Next: #23392.

Test plan: built locally for macOS 27.0; ctest -N lists coreai_host_test and coreai_swift_bridge_test, which the Core AI workflow runs on the macOS 27 runner. The XCTest bundle links CoreAI, so it only runs there (first CI run of it). lintrunner is clean.

@pytorch-bot

pytorch-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/23391

Note: Links to docs will display an error until the docs builds have been completed.

✅ No Failures

As of commit 66fd8b0 with merge base 61ec450 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 3, 2026
@metascroy
metascroy force-pushed the coreai-v2/03b-source-bundles branch from 9d457ae to 84780b8 Compare October 5, 2026 03:14
@metascroy
metascroy force-pushed the coreai-v2/04-sdk-bridge branch from dae7df4 to 756e906 Compare October 5, 2026 03:14
@metascroy

Copy link
Copy Markdown
Contributor Author

@claude review this PR

@claude

claude Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Claude finished @metascroy's task in 2m 27s —— View job


This PR adds the Swift and Objective-C bridge to the Core AI SDK, plus the CMake packaging for it. The main problem is that the runtime availability API can't do its job: the bridge objects are built with an OS 27 minimum and CoreAI is linked strongly, so ETCoreAIIsAvailable() can only ever return YES.

Detailed review

PR Review: #23391

Summary

This PR adds the private Swift and Objective-C bridge to the Core AI SDK and its CMake/package plumbing. The tensor copy, overflow checks and error mapping look correct. The issues below are mostly about how the bridge links and how its contract is documented.

API Design

  • ETCoreAIIsAvailable() and the @available checks are always true at compile time. See runtime/coreai_bridge.mm:13-25, together with coreai_require_os27(coreai_bridge_obj) (CMakeLists.txt) and INTERFACE_LINK_LIBRARIES "${foundation};${coreai}" (tools/cmake/ExecuTorchCoreAIDependencies.cmake).
    • coreai_bridge.mm and ETCoreAIModel.swift are compiled with a minimum of 27.0, so @available(macOS 27.0, iOS 27.0, *) is a constant YES.
    • CoreAI is linked as a full framework path, and Swift's import CoreAI autolinks it too. Both produce a strong LC_LOAD_DYLIB.
    • The PR says it "applies an OS 27 minimum only to Core AI targets", which implies the host app can target an older OS. If it does, dyld will fail at launch on an OS 26 device before ETCoreAIIsAvailable() ever runs.
    • ETCoreAICreateModelLoader() has no availability guard either. It only compiles because the minimum is 27.
    • Fix, either:
      • weak-link CoreAI and the Swift module (-weak_framework CoreAI / LINKER:-weak_framework,CoreAI) and build the bridge at the host's deployment target, so the checks become real; or
      • document that any binary containing the Core AI backend needs OS 27, and remove ETCoreAIIsAvailable since it can't return NO.
  • ETCoreAISession doesn't say which thread its completion runs on. See runtime/ETCoreAIBridge.h:49-55 and ETCoreAIModel.swift:304-316.
    • executeInputs calls completion synchronously on the caller's thread when validation fails (input count, shape or dtype). On success it calls it from a detached task.
    • loadFunctionNamed documents "May complete synchronously". ETCoreAISession should state the same, so the delegate in the next PR doesn't assume completion always happens off the calling thread (for example, by taking a lock it already holds).

Thread Safety

  • Waiting synchronously on a Task.detached can deadlock under Swift concurrency. See ETCoreAIModel.swift:60 ("ExecuTorch waits synchronously") and :318.
    • If an app calls ExecuTorch execute from Swift async code, the delegate blocks a thread from the cooperative pool while it waits for the detached task. That task needs a thread from the same fixed-size pool.
    • With enough concurrent inferences, this starves the pool or deadlocks.
    • This isn't triggered in this PR, but it's part of the bridge's design. Either document that execute must not run on a Swift-concurrency thread, or run the SDK call through something that doesn't depend on the cooperative pool. Low confidence on whether that is practical with the CoreAI SDK; flagging it for discussion.

Infrastructure

  • The installed coreai_swift can't link on its own. See CMakeLists.txt (install(TARGETS coreai_swift EXPORT ExecuTorchTargets ...)).
    • libcoreai_swift.a references ETCoreAIErrorDomain, ETCoreAIInputTensor and ETCoreAITensor. These are defined in ETCoreAITensor.mm, which is only in coreai_bridge_obj, and that target isn't installed.
    • Exporting it now gives package consumers an archive with unresolved symbols.
    • Suggest moving the install into the delegate PR, where these objects are absorbed into coreai_backend.
  • The new CMake checks also block the existing ObjC++ host test. See CMakeLists.txt:11-13 and :69-74.
    • The CMake ≥3.31, Ninja/Xcode and Darwin/iOS checks run unconditionally.
    • So coreai_host_test, which needs no Swift, can no longer be configured with Makefiles or an older CMake.
    • If that's intended, fine. Otherwise, put the Swift-only requirements behind the Swift section.

Testing

  • No test covers CoreAISwiftSession, the most intricate logic in the file. See ETCoreAIModel.swift:244-351. Untested paths:

    • the ordered-binding validation (duplicate names, name-set mismatch, stateful functions)
    • input dtype/shape checks against descriptors
    • output reordering by outputNames
    • the synchronous-failure completion path

    The XCTest bundle already runs on the macOS 27 runner. A small checked-in test model (or one generated in the test) that goes through specializeModel → loadFunctionNamed → executeInputs would cover the real SDK path, which none of the current tests reach. testHitPinsPreparedModelThroughCompletion only calls the fake ProtocolPreparedModel.

Code Quality

  • ETCoreAIModel.swift:25-39: Completion<Value> and AcquisitionCompletion are the same unchecked-Sendable box. One generic box (holding any closure) would do for both, and evictModel's Completion<Void> adapter could go.
  • ETCoreAIModel.swift:259-264: inputNames.count == descriptor.inputCount and descriptor.inputNames.count == inputNames.count check the same thing. With the Set equality and uniqueness checks, one count check per side is enough. The same applies to outputs.

Recommendation

Needs Discussion: the main question is whether the bridge should be weak-linked so that ETCoreAIIsAvailable works, or whether OS 27 is required for the whole binary. The answer decides the linking and the availability API before the delegate PR builds on them. The remaining items are small.

Summary:
Add the private bridge between ExecuTorch's Objective-C++ code and the Swift
`CoreAI` framework, and package it for CMake consumers. No ExecuTorch delegate
is added yet; a later commit wires this bridge into `CoreAIBackend`.

- `runtime/ETCoreAIBridge.h`: Objective-C protocols for model acquisition,
  prepared models, sessions and tensor transfer, plus availability, device
  architecture and loader factory entry points.
- `runtime/ETCoreAIModel.swift` (module `CoreAIBridge`): restore models from
  bookmarks, specialize source bundles with the persistent default SDK cache,
  bind named functions with ordered inputs/outputs and evict cache entries.
  Asynchronous SDK calls run in detached tasks and report through completion
  blocks; function binding completes synchronously. Only an SDK nil result is
  a cache miss; thrown errors keep their underlying cause.
- Inputs are borrowed from caller storage through Core AI raw views; only
  shape metadata is copied. Outputs use one `memcpy` for dense row-major
  arrays and fall back to strided copies otherwise.
- `runtime/ETCoreAITensor.mm`, `runtime/coreai_bridge.mm`: tensor wrappers and
  the C entry points. They build as the private `coreai_bridge_obj` object
  library so the delegate can absorb them and host tests can substitute a
  fake bridge.
- Build: `backends/apple/coreai/CMakeLists.txt` requires CMake 3.31 and the
  Ninja or Xcode generator, keeps ExecuTorch's numeric preprocessor
  definitions away from swiftc, applies an OS 27 minimum to Core AI targets
  only, and builds `coreai_swift`. `cmake/PublishSwiftHeader.cmake` publishes
  the generated Objective-C header for Xcode.
- Tests: `runtime/test/SwiftBridgeTests.swift` builds with the bridge sources
  into the `coreai_swift_bridge_test` XCTest bundle (Ninja, macOS), which
  CTest runs under `xcrun xctest`.
- README: "SDK Bridge" and the build requirements in "Building".

Changes outside `backends/apple/coreai` are CMake and CI only and only take
effect when `EXECUTORCH_BUILD_COREAI=ON`:
- `tools/cmake/ExecuTorchCoreAIDependencies.cmake` (new): resolves Foundation,
  CoreAI and Swift runtime link paths from the consumer's SDK, so installed
  packages do not embed the producer's Xcode paths. It fails configuration
  for SDKs older than 27.
- `CMakeLists.txt`: install `ExecuTorchCoreAIDependencies.cmake` into the
  package config.
- `tools/cmake/preset/default.cmake`: note the SDK 27 requirement in the
  option description.
- `.github/workflows/coreai.yml`: also trigger on changes to
  `ExecuTorchCoreAIDependencies.cmake`.

Test Plan:
```
# Local: configure and build only
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_OSX_DEPLOYMENT_TARGET=27.0 -DEXECUTORCH_BUILD_COREAI=ON \
  -DEXECUTORCH_BUILD_TESTS=ON -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON
cmake --build build --target backends/apple/coreai/all
# CI (macOS 27 runner, .github/workflows/coreai.yml)
ctest --test-dir build/backends/apple/coreai --output-on-failure --no-tests=error
```
CTest runs `coreai_host_test` and `coreai_swift_bridge_test`. The XCTest bundle
links CoreAI, so it only runs on the macOS 27 runner; locally it is built but
not run.
Summary:
Make the synchronous calling contract explicit and protect real model execution against binding and validation regressions. Consolidate callback handling without changing scheduling or retry behavior.

Test Plan:
CoreAI native and Swift CTest targets passed on macOS 27. All 17 Swift tests passed, including five new real-SDK session tests; five repeated suite runs also passed. The Python backend suite passed all 113 tests in the pinned CoreAI environment.

A bounded probe using the production acquisition helper reproduced cooperative-pool starvation with 16 synchronously blocking Swift-task callers under the default runtime. GCD and async-continuation controls completed successfully. This was a scheduling-only probe, not real inference.
@metascroy

Copy link
Copy Markdown
Contributor Author

@claude review this code

Updated head: 66fd8b0a2ddad0c69aac1731419496a559c6cc6c. The stack and parent PR have been refreshed; please review against the current base.

Context on the previous findings: this is intentionally a strongly linked OS-27-only bridge. Availability checks do not promise compatibility with older operating systems; adding weak linking would be a different feature. Requiring CMake 3.31 and Ninja/Xcode when CoreAI is enabled is intentional because that configuration includes the Swift bridge, not a separate Foundation-only build. The downstream delegate supplies the remaining Objective-C linkage, so this layer should not be judged as an independently complete installed delegate. Cooperative-pool starvation was a credible risk, not a reproduced deadlock; the Swift calling restriction is documented without claiming a different executor is required. Completion contracts and actual session/binding/execution/output-order tests were improved.

@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Claude finished @metascroy's task in 5m 30s —— View job


No blockers on the updated head: the earlier linking, toolchain and install points are treated as settled, and what's left is mostly build-graph and test-robustness problems in the Swift XCTest fixture, plus a few small API points.

Detailed review

PR Review: #23391

Summary

This PR adds the private Swift/Objective-C bridge to the Core AI SDK, with CMake packaging and an XCTest bundle that runs real sessions against an exported fixture.

Three independent reviewers went through every changed line. The completion-exactly-once paths, the borrowed-input lifetime, the overflow checks and the strided output copy all held up. As you asked, this review treats strong linking, OS 27 only, CMake 3.31 with Ninja/Xcode, and the delegate supplying the remaining linkage as settled. Nothing was built or run, because the runner is Linux with no Apple SDK.

API Design

  • ETCoreAIBridge.h:32-51: -init and +new aren't marked NS_UNAVAILABLE on ETCoreAIInputTensor and ETCoreAITensor.
    • [ETCoreAIInputTensor new] from Objective-C, or ETCoreAIInputTensor() from Swift, compiles and creates an object whose nonnull shape is nil.
    • It only fails later, inside CoreAITensorPayload.
    • Fix: add - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE;, and mark the existing initializers NS_DESIGNATED_INITIALIZER.
  • The error codes don't separate caller faults from SDK and environment faults (low confidence; it depends on how the delegate in [CoreAI] Coordinate model acquisition #23392 reacts to each code).
    • ETCoreAIModel.swift:47 maps every non-bridge error from restore or specialize to invalidModel, including I/O failures, out-of-space errors and toolchain failures.
    • CoreAITensorLayout.init (:149-176) and copyOutput (:197) also run on SDK-produced output views. A bad layout there comes back as invalidArgument even though the caller's inputs were valid, because mappedError passes bridge-domain errors through and the .runtime fallback at :339 never applies.
    • If the delegate treats invalidModel as "evict and re-specialize", or invalidArgument as a caller bug, it will misclassify these cases.
    • Fix: either use .runtime for unclassified SDK errors and output-layout failures, or document in the header what each code means.

Infrastructure

  • backends/apple/coreai/CMakeLists.txt:277: the fixture's DEPENDS misses most of what goes into the export.
    • Only export_bridge_fixture.py and compiler/preprocess.py are listed. The export also runs the partitioner, get_default_passes, get_default_compile_config, io_compat.py and constants.py.
    • After an edit to any of those, an incremental build keeps the stale manifest.json and model.aimodel, and SwiftSessionTests runs against an outdated model.
    • The script also never clears output_dir, so files left from an earlier export stay next to the new ones.
    • Fix: glob the backend's Python sources into DEPENDS, and remove the output directory before exporting.
    • Fix this →
  • CMakeLists.txt:266,274: the fixture runs under find_package(Python3)'s Python3_EXECUTABLE, not PYTHON_EXECUTABLE.
    • The top-level CMakeLists.txt:170-173 resolves and announces PYTHON_EXECUTABLE.
    • If someone passes -DPYTHON_EXECUTABLE=<venv>, FindPython3 can pick a different interpreter that lacks torch or the Core AI requirements. The fixture then fails, and the build fails with it.
    • Because the fixture is a dependency of a target in all, a developer who only wants coreai_host_test also needs the full export environment.
    • Fix: use ${PYTHON_EXECUTABLE} (and update the README line that names Python3_EXECUTABLE).
  • CMakeLists.txt:15-20: directory COMPILE_OPTIONS still reach swiftc (suspected; not built).
    • Only COMPILE_DEFINITIONS are rewritten to skip Swift.
    • tools/cmake/preset/apple_common.cmake:13-16 adds clang's -ffile-prefix-map=/-fdebug-prefix-map= for every language, and swiftc doesn't accept those spellings.
    • The macos/ios presets use Xcode, where extension/apple already builds Swift without trouble. The exposure is -G Ninja -DEXECUTORCH_BUILD_PRESET_FILE=tools/cmake/preset/macos.cmake -DEXECUTORCH_BUILD_COREAI=ON.
    • Fix: apply the same $<$<NOT:$<COMPILE_LANGUAGE:Swift>>:...> rewrite to COMPILE_OPTIONS.
  • CMakeLists.txt:86-88: on iOS Ninja builds, CMAKE_Swift_COMPILER_TARGET is hard-coded to ios27.0 (low confidence).
    • The variable stays set for the whole directory, not just the compiler probe, and it ignores CMAKE_OSX_DEPLOYMENT_TARGET.
    • With a deployment target of 27.1, Core AI Swift compiles get both -target …ios27.0 and coreai_require_os27's -target …ios27.1, and the driver's argument order decides which one wins.
    • It also overwrites any value a toolchain file set.
    • Fix: compute the deployment value the same way coreai_require_os27 does.

Testing

  • SwiftBridgeTests.swift:136-171 and CMakeLists.txt:289-293: the five session tests may not fit in the bundle's 120 s CTest timeout.
    • Each SwiftSessionTests case specializes the fixture from scratch in setUpWithError (30 s wait), and tearDownWithError evicts it (10 s wait). So the persistent cache never helps the next test.
    • Cold specialization at roughly 20 s on the runner would push the bundle past 120 s. CTest would then kill it with no per-test diagnosis.
    • If the 30 s setUp wait times out, the detached task still writes self.model after the test has moved on.
    • Fix: specialize and evict once per class (override class func setUp()/tearDown()), or raise TIMEOUT.
  • SwiftBridgeTests.swift:35-54: testHitPinsPreparedModelThroughCompletion doesn't test what its name says.
    • released isn't ordered after completed (no enforceOrder: true), so the test still passes if the bridge drops the model before calling the completion.
    • The loadFunctionNamed assertions only exercise the test's own ProtocolPreparedModel fake, so they can never fail.
    • Fix: wait(for: [completed, released], timeout: 5, enforceOrder: true), and drop the fake-only assertions.
  • SwiftBridgeTests.swift:137: running the bundle outside CTest fails all session tests instead of skipping them. XCTUnwrap on COREAI_BRIDGE_FIXTURE_DIR turns a missing fixture into five failures when someone runs xcrun xctest directly. Throwing XCTSkip would separate "fixture not provided" from a regression.
  • The session path never tests a dynamic (-1) descriptor dimension. The fixture is exported with static (2,) shapes. So validateShape against a dynamic descriptor, and output shape tracking from a real run, are only covered by the payload-level simulation in testInputsBorrowStorage.

Code Quality

  • README.md:133-134 and CMakeLists.txt:207-208 describe things that don't exist yet as current behavior.
    • They say host tests replace coreai_bridge.mm with a fake, and that production targets absorb coreai_bridge_obj.
    • On this branch there is no fake bridge under runtime/test/, and nothing consumes coreai_bridge_obj.
    • Fix: word both as what the delegate PR will do.

Recommendation

Approve once the fixture build-graph items (DEPENDS and the Python interpreter) and the session-test timeout risk are addressed. The rest is minor or low confidence and can be fixed here or in #23392.

This branch was successfully deployed

1 active deployment
cadence — 66fd8b0a Deployed Oct 27, 2026 by metascroy via hifi-op-test / hifi4 #34760
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants