Skip to content

[CoreAI] Integrate the delegate execute path - #23393

Open
metascroy wants to merge 5 commits into
coreai-v2/05a-load-coordinatorfrom
coreai-v2/05b-sdk-delegate
Open

metascroy wants to merge 5 commits into
coreai-v2/05a-load-coordinatorfrom
coreai-v2/05b-sdk-delegate

Conversation

@metascroy

@metascroy metascroy commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Wires the Core AI bridge into an ExecuTorch BackendInterface so CoreAIBackend can load and run models (runtime/coreai_backend.mm).

  • init parses the manifest, checks the OS floor, selects the platform/architecture assets, acquires a prepared model through the load coordinator and binds the named function with ordered inputs/outputs.
  • execute validates dtype (FP16/FP32), CPU storage, default dim order and contiguity, borrows input bytes without copying, then validates and resizes every output before copying any results.
  • init and execute block their calling thread on Swift concurrency work, so the README tells callers not to invoke them from Swift async code.
  • Build: backends/apple/coreai/CMakeLists.txt builds coreaidelegate (static, or shared against executorch_shared) from the backend sources plus coreai_bridge_obj, linking coreai_swift with the C++ linker so ExecuTorch's C/C++ options never reach swiftc.
  • Tests:
    • The fake bridge gains function binding, sessions, execution and the C entry points the backend calls, so the real backend runs in coreai_host_test (delegate suite).
    • coreai_runtime_smoke checks final linkage, registration retention and availability. With EXECUTORCH_BUILD_TESTS=ON it is built and registered with CTest; otherwise it stays out of the default build.
  • README: execution semantics, device-architecture selection, the default assets root, "Runtime options", and delegate linkage in "Building".
  • Changes outside backends/apple/coreai are CMake only and only take effect when EXECUTORCH_BUILD_COREAI=ON:
    • tools/cmake/preset/default.cmake: EXECUTORCH_BUILD_COREAI requires the data loader extension.
    • CMakeLists.txt: add coreaidelegate to the backend list.

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

Test plan: built locally for macOS 27.0; ctest -N lists coreai_host_test, coreai_swift_bridge_test and coreai_runtime_smoke, which the Core AI workflow runs on the macOS 27 runner. The smoke binary needs OS 27, 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/23393

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

❌ 1 Cancelled Job, 1 Unclassified Failure

As of commit d07e206 with merge base 61ec450 (image):

UNCLASSIFIED FAILURE - DrCI could not classify the following job because the workflow did not run on the merge base. The failure may be pre-existing on trunk or introduced by this PR:

  • Core AI / test-coreai / test-coreai (gh) (this job did not run on the merge base, so DrCI cannot tell whether the failure is pre-existing)
    backends/apple/coreai/runtime/test/coreai_storage_test.mm:167:23: error: code should be clang-formatted [-Wclang-format-violations]

CANCELLED JOB - The following job was cancelled. Please retry:

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/05b-sdk-delegate branch from c14c6ea to 836b175 Compare October 4, 2026 19:48
@metascroy
metascroy force-pushed the coreai-v2/05a-load-coordinator branch from 2f63441 to 12fd39f Compare October 4, 2026 19:48
@metascroy
metascroy force-pushed the coreai-v2/05b-sdk-delegate branch from 836b175 to a6f16ff Compare October 5, 2026 03:14
@metascroy
metascroy force-pushed the coreai-v2/05a-load-coordinator branch from 12fd39f to 1beeef8 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 21s —— View job


This PR wires the Core AI bridge into a CoreAIBackend BackendInterface. I found no blocking defects, but three things are worth changing: SDK and caller output mismatches return the wrong error code, a failed multi-output execute can leave earlier outputs resized over stale data, and the multi-output path has no test.

Detailed review

PR Review: #23393

Summary

This PR adds runtime/coreai_backend.mm (manifest parsing, OS and architecture preflight, acquisition and function binding in init, and borrowed-input execution with validated output copy in execute), the coreaidelegate CMake target, a link/registration smoke binary, and a delegate suite running against the fake bridge. The three findings below are not blockers.

API Design

  • Output mismatches return Error::InvalidExternalData (coreai_backend.mm:168-203). runtime/core/error.h:91 defines this code as "Error caused by the contents of external data", meaning named data or .ptd contents. Here it covers two other cases:

    • The SDK returns bad results (wrong output count, shape or byte count). This is a backend or SDK failure, so Error::Internal fits better.
    • The caller passes an output tensor with the wrong dtype or rank (HalfDtypePreservesBitsAndRejectsMixedOutputDtype, ScalarTensorsExecuteAndRejectOutputRankMismatch). This is the caller's fault, so it should be InvalidArgument. The dtype case can also be caught before running inference. validate_arguments already checks each output dtype against FP16/FP32, but never against the model's dtype, so a wrong output dtype costs a full inference before it is rejected.

    Once these codes ship they become part of the backend's documented behaviour, so it is easier to fix them now. Fix this →

Code Quality

  • A failed execute can leave some outputs resized but not written (coreai_backend.mm:205-219). The comment says all outputs are checked before copying to avoid partial writes, but the second loop calls resize_tensor for each output as it goes. Suppose output 0 resizes successfully and output 1 then fails resize_tensor or the storage check. Output 0 keeps its new shape but still holds stale data, so the caller sees a shape that does not match its contents. This only matters with more than one output. Two fixes:
    • Check capacity and dynamism for every output before calling resize_tensor on any of them.
    • Or narrow the comment to say only data writes are all-or-nothing.

Testing

  • The multi-output path is never tested. valid_binding in coreai_fake_loader.mm accepts only @[@"input_0"] / @[@"output_0"], and FakeSession executeInputs: always returns a single tensor. The parts of copy_outputs that exist for more than one output (the separate shape-validation and resize loops) therefore never run. Ordered input/output mapping is also never checked: nothing catches outputs copied into the wrong slot or inputs sent in the wrong order. A two-input, two-output fake case would cover this, ideally one where output 1 is too large for its capacity, to pin down the behaviour in the previous finding.

Recommendation

Approve (once the error codes are settled). None of these findings blocks the PR. The error-code change is the one to make before merge, because the codes become observable behaviour.

@metascroy

Copy link
Copy Markdown
Contributor Author

@claude review this code

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

Context on the previous findings: output failure handling is deliberately not transactional. Earlier output shapes may change before a later resize fails, but no result bytes are copied unless every output validates and resizes successfully. Callers must not use outputs as inference results after an error. We documented and tested this contract rather than adding shape rollback or duplicating runtime resize rules. Rejecting output dtype mismatches before inference would require model-output metadata the current handle does not expose; that bridge expansion was not necessary for the error-classification fix. Malformed SDK outputs now use Internal, incompatible caller outputs use InvalidArgument, and native multi-input/output ordering and later-output-failure tests were added.

@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Claude finished @metascroy's task in 3m 43s —— View job


The follow-up commit addresses all three earlier findings and matches the contract you described, so there are no blockers left. What remains is minor: one output error code that still escapes the new split, the CTest registration and build wiring for the smoke test, two tests that pass on leftover state, and a stale README section.

Detailed review

PR Review: #23393

Summary

This PR adds the CoreAIBackend delegate (init/execute over the Core AI bridge), the coreaidelegate CMake target, a smoke binary for linkage and registration, and a delegate suite that runs against the fake bridge. The changes in ed11289 match the stated contract: malformed SDK outputs return Internal, caller dtype/rank mismatches return InvalidArgument, no bytes are copied unless every output resizes, and multi-input/output ordering is now tested. The findings below are all minor.

API Design

  • resize_tensor errors are passed through unchanged (runtime/coreai_backend.mm:213). The new split says incompatible caller outputs return InvalidArgument, and the dtype/rank check (:207) and the storage check (:222) do. But if the caller gives a static-shape output and the SDK returns a different shape, execute returns whatever resize_tensor returns (NotSupported for static tensors). LaterOutputResizeFailureLeavesResultBytesUntouched (coreai_delegate_test.mm:346) pins that down. Either map this error to InvalidArgument too, or state in the README that resize errors pass through, so callers know they can get both codes for the same mistake.

Infrastructure

  • CTest registers coreai_runtime_smoke on hosts that can't run it (CMakeLists.txt:364-367). add_test only checks EXECUTORCH_BUILD_TESTS. The other OS 27 tests also require CMAKE_SYSTEM_NAME STREQUAL "Darwin", which this one doesn't. Two cases fail:

    • An iOS cross-build with tests on registers an iOS binary in the host's CTest.
    • A macOS 26 host running the README's ctest --test-dir …/backends/apple/coreai launches an SDK27 binary.

    Both contradict the README (:268 "Do not execute SDK27 binaries on an older host", :277 "omitted for Apple cross-builds"). Add the Darwin condition, and consider a host-version gate or SKIP_RETURN_CODE.

  • The preset makes the data loader mandatory, but the delegate never uses it (tools/cmake/preset/default.cmake:443-445, CMakeLists.txt:338,352). Nothing under backends/apple/coreai/runtime/ references DataLoader or extension/data_loader. Even so, EXECUTORCH_BUILD_COREAI=ON without EXECUTORCH_BUILD_EXTENSION_DATA_LOADER is now a configure error, and the static build links extension_data_loader into an exported target. The shared build already sets it to empty. If the data loader is only for consumers (Module/runners), drop the rule and the link. If something later in the stack needs it, add it there.

  • coreaidelegate is missing from optional_lib_list (tools/cmake/executorch-config.cmake:98-140). The PR installs and exports the target and adds it to the backend list, but find_package(executorch) consumers who link ${EXECUTORCH_LIBRARIES} never get it. coremldelegate and mlxdelegate are both in that list. This is a one-line addition.

Testing

  • BorrowedInputStorageStaysLiveUntilSuccessOrError checks leftover values on the error iteration (coreai_delegate_test.mm:450-477). tensors and bridge.state() carry over from the success iteration. observed, last_input_bytes and last_input_first_byte are never reset, so in the failure == true iteration those checks pass even if the fake never read the input. The input-still-live-on-error claim then rests only on the premature/finished timing checks. Reset the state, or write a different first input byte before each iteration.
  • EmptyVectorAndMatrixBorrowNullStorage can pass without running inference (coreai_delegate_test.mm:596-614). Its only check is last_input_bytes == nullptr, which is also the initial value. If execute gained an early return for zero-byte tensors, the test would still pass. Assert that the execution count goes up, or set last_input_bytes to a non-null sentinel first.

Code Quality

  • README "Host Tests" is out of date (README.md:272-277). It still says coreai_host_test covers "manifest parsing, AOT asset selection, and filesystem storage … without Swift or CoreAI SDK calls". The suite now also runs init/execute against the fake bridge. The ctest command in that section also runs coreai_runtime_smoke, which links Swift and the Core AI framework, and the section doesn't mention it.

Recommendation

Approve. Nothing blocks. The cheapest fixes to make before merge are the Darwin gate on add_test and the optional_lib_list entry. The data-loader rule depends on whether something later in the stack needs it.

Summary:
Wire the Core AI bridge into an ExecuTorch `BackendInterface` so
`CoreAIBackend` can load and run models.

- `runtime/coreai_backend.mm`: the `BackendInterface`. `init` parses the
  manifest, checks the OS floor, selects the platform/architecture assets,
  acquires a prepared model through the load coordinator and binds the named
  function with ordered inputs/outputs. `execute` validates dtype (FP16/FP32),
  CPU storage, default dim order and contiguity, borrows input bytes without
  copying, then validates and resizes every output before copying any results.
- Build: `backends/apple/coreai/CMakeLists.txt` builds `coreaidelegate`
  (static, or shared against `executorch_shared`) from the backend sources
  plus the `coreai_bridge_obj` objects, and links `coreai_swift`. The delegate
  and its test consumers link with the C++ linker, so ExecuTorch's C/C++
  compile options never reach swiftc.
- Tests: the fake bridge gains function binding, sessions and execution, plus
  the C entry points the backend calls, so the real backend runs in
  `coreai_host_test`. `coreai_runtime_smoke` checks final linkage, registration
  retention and availability; with `EXECUTORCH_BUILD_TESTS=ON` it is built and
  registered with CTest, otherwise it is excluded from the default build.
- README: execution semantics, device-architecture selection, the default
  assets root, "Runtime options", and delegate linkage in "Building".

`init` and `execute` block their calling thread on Swift concurrency work, so
the README tells callers not to invoke them from Swift `async` code.

Changes outside `backends/apple/coreai` are CMake only and, like the other
backends, only take effect when `EXECUTORCH_BUILD_COREAI=ON`:
- `tools/cmake/preset/default.cmake`: `EXECUTORCH_BUILD_COREAI` now requires
  the data loader extension.
- `CMakeLists.txt`: add `coreaidelegate` to the backend list.

Test Plan:
```
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
ctest --test-dir build/backends/apple/coreai --output-on-failure --no-tests=error
```
CTest runs `coreai_host_test` (adding the delegate suite),
`coreai_swift_bridge_test` and `coreai_runtime_smoke`. The smoke binary needs
OS 27, so locally it is built but not run.
Summary:
Distinguish malformed SDK results from incompatible caller output tensors. Exercise ordered multi-input/output execution and make the failure contract explicit: output shapes may change on error, but result bytes are not copied until all outputs pass validation and resizing. Keep failure handling simple without rollback or additional model metadata.

Test Plan:
All four Core AI CTest suites passed on macOS 27. Native tests verified reversed binding names and distinct output values, unchanged result bytes when a later output cannot resize, malformed output counts, and the updated dtype/rank error classifications.

The full Core AI Python suite passed: 113 tests.

# Conflicts:
#	backends/apple/coreai/runtime/test/test_sdk_source_lifetime.py

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto 0ac18e1
# Last commands done (2 commands done):
#    pick e9a3f17 # [CoreAI] Integrate the delegate execute path
#    pick ed11289 # [CoreAI] Clarify delegate output errors and test multi-output execution
# No commands remaining.
# You are currently rebasing branch 'coreai-v2/05b-sdk-delegate' on '0ac18e163c'.
#
# Changes to be committed:
#	modified:   backends/apple/coreai/README.md
#	modified:   backends/apple/coreai/runtime/coreai_backend.mm
#	modified:   backends/apple/coreai/runtime/test/coreai_delegate_test.mm
#	modified:   backends/apple/coreai/runtime/test/coreai_fake_loader.h
#	modified:   backends/apple/coreai/runtime/test/coreai_fake_loader.mm
#
# Changes not staged for commit:
#	modified:   backends/mlx/third-party/mlx (modified content)
#
Classify incompatible output resizing as InvalidArgument and strengthen borrowed-input and empty-tensor assertions. Gate SDK tests on native host deployment support, remove the unused data-loader dependency, expose the installed delegate in EXECUTORCH_LIBRARIES, and update documentation.

Test Plan: CMake build and all four CoreAI CTest suites passed: 161 native tests, 18 Swift tests, portable/AOT lifetime probes, and runtime registration smoke. Python: 122 passed. Five delegate regressions passed twenty repetitions. Format and whitespace checks passed. Generic Buck owner validation failed without diagnostics. Additional installed-consumer and platform-matrix validation was stopped before completion.
Retain validated SDK arrays behind non-failing scoped copy callbacks instead of allocating and zeroing intermediate Data payloads. Validate and resize every output before copying directly into executor buffers. When destinations alias inputs or each other, stage all source reads into uninitialized storage before ordered copyback. Keep original borrowed input ranges alive for overlap checks and copying.

Test Plan: All 122 CoreAI Python tests passed, including real AOT compilation. All four native CTest targets passed on the owning delegate layer, including real SDK bridge and source-lifetime tests. Added direct-destination/copy-count, partial overlap, cross-output alias, ordered overlapping outputs, SDK storage retention, and deferred copier lifetime coverage. et-extra lintrunner -a and CI clang-format 18.1.3 checks passed. Final code review found no issues.
Summary:
Avoid duplicated dtype dispatch as supported scalar types evolve while preserving borrowed inputs and single-copy outputs. Reject conflicting output shape aliases before writing payload bytes, including different shapes with equal byte counts.

Test Plan:
Reproduced the shared-output shape bug before the fix: execution incorrectly succeeded and invoked output copiers. Regression tests now reject conflicting shapes without copying and preserve ordered copies for matching shapes.

CoreAI native/SDK CTest: 4/4 passed, including scalar registry, bit-preserving output layout, lifetime, and delegate tests. Complete CoreAI Python suite: 122 passed.

Validation: et-extra lintrunner -a and git diff --check passed. Generic Buck validation still fails at the existing Swift owner lookup without diagnostics.

This branch was previously deployed

1 inactive deployment
cadence — d07e2067 Deployed Oct 29, 2026 by metascroy via hifi-op-test / hifi4 #35207
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