Skip to content
Draft
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
25 changes: 20 additions & 5 deletions backends/apple/coreai/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ endforeach()
enable_language(OBJCXX)

set(_coreai_runtime_sources
runtime/coreai_backend.mm runtime/coreai_assets.mm
runtime/coreai_storage.mm runtime/coreai_bookmarks.mm
runtime/coreai_load_coordinator.mm runtime/coreai_pte.cpp
runtime/coreai_backend.mm
runtime/coreai_assets.mm
runtime/coreai_storage.mm
runtime/coreai_bookmarks.mm
runtime/coreai_load_coordinator.mm
runtime/coreai_pte.cpp
runtime/coreai_cache.mm
)

# Keep ARC and C++ exception settings away from the private Swift module.
Expand Down Expand Up @@ -61,6 +65,8 @@ if(EXECUTORCH_BUILD_TESTS)
runtime/test/coreai_delegate_test.mm
runtime/test/coreai_program_data_test.mm
runtime/test/coreai_pte_fixture.mm
runtime/test/coreai_cache_test.mm
runtime/test/coreai_eviction_test.mm
runtime/ETCoreAITensor.mm
${_coreai_runtime_sources}
)
Expand All @@ -70,12 +76,15 @@ if(EXECUTORCH_BUILD_TESTS)
# The consolidated runtime target is created after this directory.
if(EXECUTORCH_BUILD_SHARED)
set(_coreai_host_runtime executorch_shared)
set(_coreai_host_data_loader "")
else()
set(_coreai_host_runtime executorch)
set(_coreai_host_data_loader extension_data_loader)
endif()
target_link_libraries(
coreai_host_test PRIVATE ${_coreai_host_runtime} program_schema
GTest::gtest ${COREAI_FOUNDATION_FRAMEWORK}
coreai_host_test
PRIVATE ${_coreai_host_runtime} ${_coreai_host_data_loader} program_schema
GTest::gtest ${COREAI_FOUNDATION_FRAMEWORK}
)
add_test(NAME coreai_host_test COMMAND coreai_host_test)
set_tests_properties(coreai_host_test PROPERTIES TIMEOUT 120)
Expand Down Expand Up @@ -332,6 +341,12 @@ target_link_libraries(
coreai_runtime_smoke PRIVATE coreaidelegate ${_coreai_runtime}
)

install(
FILES runtime/coreai_cache.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/executorch/backends/apple/coreai/runtime
)

install(
TARGETS coreaidelegate coreai_swift
EXPORT ExecuTorchTargets
Expand Down
57 changes: 54 additions & 3 deletions backends/apple/coreai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,56 @@ the original PTE and named data for reconstruction after a cache miss.
Source-free inference, fresh-process SDK restoration and persistent-policy
behavior require separate OS 27 hardware qualification.

### Clearing cached assets

The public C++ maintenance APIs are in `executorch::backends::coreai`:

```cpp
runtime::Error clear_cache(const char* coreai_assets_dir);
runtime::Error clear_cache_for_pte(
runtime::DataLoader& pte, const char* coreai_assets_dir = nullptr);
runtime::Error clear_cache_for_pte(
const char* pte_path, const char* coreai_assets_dir = nullptr);
```
`clear_cache` requires an explicit absolute assets root. The PTE overloads use
user-domain `NSCachesDirectory` plus `executorch_coreai` when the directory is
omitted or null, just like backend initialization. An invalid supplied path is
an error. If loading used a custom root, pass that same root when clearing;
the PTE does not store runtime directory overrides.
Root-wide clearing visits keyed bookmarks and staged directories. PTE clearing
selects and deduplicates keys for all Core AI delegates in every method, using
the current platform, SDK architecture and default specialization configuration.
Both public `clear_cache_for_pte` overloads use the private `inspect_coreai_pte`
reader and retain its `CoreAIPteData`, including the verified program image and
selected processed buffers, through `clear_keys`. Discovery does not load a
Program or initialize a Method, load SDK models, bind functions, extract Core AI
assets or specialize. All selected manifests are validated before any key is
cleared; root-wide clearing remains
available for existing keyed entries. Backend-local structural and semantic
verification is mandatory even with `ET_ENABLE_PROGRAM_VERIFICATION=0`.
The reader's I/O and buffer-lifetime contract is described below.
These synchronous calls require callers to unload affected models and prevent
concurrent loads, inference and maintenance across processes. Per-key locks are
not a whole-root barrier. Clearing first deletes the recorded SDK entry, then
staging, then its bookmark. Busy or unknown SDK errors preserve the files;
SDK-confirmed absence permits cleanup. Filesystem errors may leave partial
cleanup, but the bookmark is retained until staging removal succeeds so cleanup
can be retried. Independent keys are still attempted in sorted order, and the
first key error is returned. Each removal syncs the directory it changed. A
missing root or absent entry is a noncreating no-op.
Everything under a keyed `staging/<key>` directory belongs to the backend and is
removed with that key. Outside keyed entries, clearing does not delete PTEs,
other files or the stable lock files. Unkeyed interrupted-staging directories
also remain untouched. Bookmarks are SDK eviction handles, not weights: lost
bookmarks can leave SDK entries that these APIs cannot target. There is no
history or ownership registry. Copied or reused exported artifacts can share
keys; clearing them also removes the other copy's warm-cache benefit. Subsequent
loads can specialize again.
### Runtime options
Pass options through the public `Module` API before loading. In an error-returning
Expand Down Expand Up @@ -222,7 +272,8 @@ 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.

Enable `EXECUTORCH_BUILD_EXTENSION_DATA_LOADER` for inference consumers.
Enable `EXECUTORCH_BUILD_EXTENSION_DATA_LOADER` for inference consumers and
the public PTE-file maintenance API.
Link the CMake target `coreaidelegate`, not just its archive filename. Its
transitive dependencies supply Swift and Foundation/CoreAI linkage, and its
link interface retains static backend registration. With
Expand All @@ -235,8 +286,8 @@ Select the Apple SDK/toolchain when configuring the consumer project.
SwiftPM and XCFramework distribution are not integrated yet.

The `coreai_runtime_smoke` target checks final linkage of a C++ consumer,
including Swift dependencies and backend registration retention. With
`EXECUTORCH_BUILD_TESTS=ON` it is built and
including Swift dependencies, backend registration retention and all public
cache-clearing overloads. With `EXECUTORCH_BUILD_TESTS=ON` it is built and
registered with CTest; otherwise it is excluded from the default build. On OS 27,
running it checks registration and availability; it does not perform model
inference. Do not execute SDK27 binaries on an older host.
Expand Down
10 changes: 10 additions & 0 deletions backends/apple/coreai/runtime/coreai_bookmarks.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ runtime::Result<NSString*> bookmark_key(
runtime::Result<NSString*> resolve_bookmark_root(
NSString* path,
bool create = true);
// Missing roots produce an empty inventory.
runtime::Result<NSArray<NSString*>*> inventory_bookmark_keys(NSString* root);

class BookmarkLock final {
public:
Expand All @@ -36,6 +38,10 @@ class BookmarkLock final {
lock_bookmark(NSString* root, NSString* key, bool create);
friend runtime::Result<NSData*> read_bookmark(const BookmarkLock& lock);
friend runtime::Error write_bookmark(const BookmarkLock& lock, NSData* data);
friend runtime::Error remove_bookmark(const BookmarkLock& lock);
friend runtime::Error remove_bookmark_staging(
const BookmarkLock& lock,
bool remove);
friend runtime::Result<NSString*> prepare_bookmark_staging(
const BookmarkLock& lock);
BookmarkLock(
Expand All @@ -59,6 +65,10 @@ lock_bookmark(NSString* root, NSString* key, bool create = true);
// closed.
runtime::Result<NSData*> read_bookmark(const BookmarkLock& lock);
runtime::Error write_bookmark(const BookmarkLock& lock, NSData* data);
runtime::Error remove_bookmark(const BookmarkLock& lock);
runtime::Error remove_bookmark_staging(
const BookmarkLock& lock,
bool remove = true);
// Prepared only for cold loads; source preparation appends key/bundle.
runtime::Result<NSString*> prepare_bookmark_staging(const BookmarkLock& lock);

Expand Down
44 changes: 44 additions & 0 deletions backends/apple/coreai/runtime/coreai_bookmarks.mm
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,33 @@ bool digest(id value) {
return resolve_root(path, create);
}

Result<NSArray<NSString*>*> inventory_bookmark_keys(NSString* root) {
auto resolved = resolve_root(root, false);
if (!resolved.ok()) return resolved.error();
auto opened = open_root(resolved.get());
if (!opened.ok()) return opened.error();
FileDescriptor directory(opened.get());
if (directory.get() < 0) return @[];
NSMutableSet<NSString*>* keys = [NSMutableSet set];
for (NSString* name in @[ @"bookmarks", @"staging" ]) {
auto child_result = open_child(directory.get(), name, false);
if (!child_result.ok()) return child_result.error();
FileDescriptor child(child_result.get());
if (child.get() < 0) continue;
auto children = storage_children(child.get(), true);
if (!children.ok()) return children.error();
for (NSString* entry in children.get()) {
NSString* key = entry;
if ([name isEqual:@"bookmarks"]) {
if (![entry hasSuffix:@".bookmark"]) continue;
key = [entry substringToIndex:entry.length - @".bookmark".length];
}
if (digest(key)) [keys addObject:key];
}
}
return [keys.allObjects sortedArrayUsingSelector:@selector(compare:)];
}

BookmarkLock::BookmarkLock(int root, int bookmarks, int file,
NSString* root_path, NSString* key)
: root_(root),
Expand Down Expand Up @@ -236,6 +263,23 @@ Error write_bookmark(const BookmarkLock& lock, NSData* data) {
data);
}

Error remove_bookmark(const BookmarkLock& lock) {
if (lock.bookmarks_.get() < 0) return Error::Ok;
ET_CHECK_OR_RETURN_ERROR(storage_fault(StorageOperation::Remove) == 0,
AccessFailed,
"Core AI bookmark removal interrupted");
ET_CHECK_OR_RETURN_ERROR(
unlinkat(lock.bookmarks_.get(),
bookmark_name(lock.key_).fileSystemRepresentation, 0) == 0 ||
errno == ENOENT,
AccessFailed, "Cannot remove Core AI bookmark");
return sync_storage_directory(lock.bookmarks_.get());
}

Error remove_bookmark_staging(const BookmarkLock& lock, bool remove) {
return remove_storage_staging(lock.root_.get(), lock.key_, remove);
}

Result<NSString*> prepare_bookmark_staging(const BookmarkLock& lock) {
auto staging = open_child(lock.root_.get(), @"staging", true);
if (!staging.ok()) return staging.error();
Expand Down
57 changes: 57 additions & 0 deletions backends/apple/coreai/runtime/coreai_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.
*/
#pragma once

#include <executorch/runtime/core/error.h>

namespace executorch::runtime {
class DataLoader;
}

namespace executorch::backends::coreai {

/**
* Clears tracked SDK entries, keyed staging, and bookmarks in an explicit,
* dedicated Core AI assets directory. Missing roots are successful no-ops.
* PTEs, stable lock files, and unkeyed temporaries are not removed.
* SDK entries without saved bookmarks cannot be targeted.
*
* Callers must unload affected models and prevent concurrent loading,
* inference, and maintenance across processes until this call returns.
* Independent keys are attempted in sorted order, returning the first error;
* clearing is not a transaction. SDK failures preserve an entry's files unless
* the SDK confirms that entry is absent.
*/
[[nodiscard]] runtime::Error clear_cache(const char* coreai_assets_dir);

/**
* Clears current-platform/SDK-architecture cache entries referenced by every
* CoreAI delegate in the PTE, after validating all selected manifests. Does not
* initialize delegates or materialize Core AI assets. Backend-local structural
* and semantic verification is mandatory regardless of runtime build settings.
* Reads the program region, including inline constants and unrelated inline
* blobs, and only selected Core AI external processed-data segments.
*
* A null directory selects the same Caches default as backend initialization.
* Supplied directories must be valid explicit roots; they never fall back to
* the default. The directory is never derived from the PTE's location.
* Copied PTEs can share cache keys. The quiescence and failure contract of
* clear_cache also applies. The loader and its data must remain stable for
* this call; the retained program and selected buffers survive through
* eviction.
*/
[[nodiscard]] runtime::Error clear_cache_for_pte(
runtime::DataLoader& loader,
const char* coreai_assets_dir = nullptr);

/** FileDataLoader convenience overload with the same contract. */
[[nodiscard]] runtime::Error clear_cache_for_pte(
const char* pte_path,
const char* coreai_assets_dir = nullptr);

} // namespace executorch::backends::coreai
Loading
Loading