Part 7 — Core AI: the Swift runtime¶
Version floor: everything here is 27.0 and only 27.0. import CoreAI requires iOS · iPadOS ·
macOS · Mac Catalyst · tvOS · visionOS · watchOS 27.0, and every symbol is Beta. Core AI is a new
framework in the 27 cycle, not a rename of Core ML: nothing back-deploys, no @available(iOS 26, *)
fallback buys you anything, and there is no release-notes page to diff against —
/documentation/updates/coreai returns 404. You need Xcode 27 and the Metal Toolchain, a
separate download (xcodebuild -downloadComponent MetalToolchain); without it any target containing a
.aimodel fails to build with a missing Metal compiler error that never mentions Core AI.
Metal-interop APIs omit watchOS from their doc-page availability lists, while the captured macOS
27.0 beta SDK interface declares them watchOS 27.0 (see 7.1 §16.3); treat the interface as the
stronger signature evidence and verify on device. apple/coreai-models is macOS 27 and iOS 27 only.
Who this is for: Swift developers who have a converted model and must make it load, run and stay fast
on a device. Producing the .aimodel is Part 8; choosing Core AI
over another backend is Part 1.
⚠️ Read this before you trust a signature in this part¶
Core AI ships with zero Apple sample-code projects. Verified, not assumed: Apple's own documentation
index enumerates 312 Core AI symbol and page entries, and filtering it for sampleCode returns
zero. For Parts 1–6 the strongest evidence class was a compiling Apple sample project; here there
is none, so these guides stand on a different ladder, strongest first: shipped Apple source read on
disk (apple/coreai-models, apple/coreai-torch — Apple-authored code calling these APIs for real);
Apple's own agent skills in those repos, written for coding agents and unusually blunt; Apple's
documentation pages including raw DocC JSON, unusually complete for Core AI; then WWDC26 transcripts
(324, 325, 326), useful for intent and weak for spelling.
Two consequences. Signatures are more often 🟡 RECONSTRUCTED here than in Parts 1–6 — individual declarations are usually ✅ VERIFIED from a doc page or a shipped file, but every assembled, runnable example is a composition, because there is no first-party project to copy one from; each guide marks that seam. And four of Apple's own documentation samples do not compile; where a guide reproduces one, it says so and gives the corrected form.
Why this part exists¶
The three-line version — try await AIModel(contentsOf:), loadFunction(named: "main"),
try await fn.run(inputs:) — works, and hides all four of these.
- The
awaithides a compiler, not a disk read. A.aimodelis portable source, closer to LLVM IR than to a.dylib, and must be specialized for this device and this OS version before a validAIModelexists. Apple's own session says to "avoid having model specialization occur within user interactive flows." A 3 GB model was community-measured at 194 seconds on that first load — on an iPhone, on an already AOT-compiled asset. nilandthrowsmean different things, everywhere.loadFunctionreturnsnilfor a wrong name and throws for a failed load;init?(resolvingBookmark:)returnsnilfor a stale entry and throws for malformed data;Outputs.removereturnsnilbenignly on a second take whileNamedMutableViews.taketraps.try?collapses all of it.- Most defects do not throw. A contiguous
NDArraygets copied into the hardware's preferred layout on every run, forever, with no diagnostic. A KV cache gets copy-on-written in full every decode step. Options differing by one flag between two call sites miss the cache permanently. - The fastest engine cannot do structured output. Grammar-constrained decoding needs per-step
logits; the GPU-pipelined engine samples on-GPU and never surfaces them — and it is auto-selected for
every macOS dynamic export, so
@Generableand the default fast path are mutually exclusive.
Read this first: the triage table¶
| If your situation is… | Read | Why |
|---|---|---|
"I have a .aimodel and want it running today" |
7.1 §0–§9 | The whole object model; §14 is a runner you can paste |
"What error type do I catch?" · "contiguousElements is nil" · "shape.reduce won't compile" |
7.1 §13, §7 | ✅ SDK-verified: untyped throws, AssetError only; preferred strides or interleave; Span is not a Sequence |
| "My first launch stalls for minutes" | 7.2 §1–§5 | Specialization. Gate on model(for:options:), pre-specialize behind explanatory UI |
| "The stall came back after I was sure I'd paid it" | 7.2 §4, §6 | The key is (asset, options) — or an OS update, which purges everything regardless of policy |
| "Inference intervals grow along the Instruments timeline" | 7.3 §1–§5 | No states. The one bug here that announces itself visually |
| "Output is right, throughput is a flat multiple too low" · "SIGTRAP at the first execute" | 7.3 §8.1, §13 | Copy-on-write on the state, invisible in the Core AI instrument; or the MPSGraph in-graph KV-write bug |
| "Turn 2 of my chat is as slow as turn 1" · "hybrid/SSM or plain?" | 7.3 §14 · 7.4 §6 | Prefix reuse: ~101× on turn-2 TTFT, one integer assignment — and linear attention forfeits it entirely |
"@Generable throws unsupportedCapability" · "I want topK/topP" |
7.4 §7.8, §9 | You are on the pipelined engine; and sampling knobs live on TextGenerator, not GenerationOptions |
"unsupported metadata_version '0.1'" · "works on my Mac, not on device" |
7.4 §2.3, §2.7 | You pointed at the .aimodel, not the bundle dir; or a missing tokenizer/ is fetching from the Hub |
| "Should my vision pipeline be one function or three?" · "Why is SAM3 on a different compute unit?" | 7.5 §3–§4, §9 | Inspect before specializing; distinguish the package's loader policy from a framework routing rule |
| "What does warmup actually warm?" · "Why is my detector cold at a new shape?" | 7.5 §5, §7–§8 | Load, function load, dummy forward, specialization cache, and semantic feature cache are separate states |
| "How should a diffusion bundle own its components?" · "Should I use lazy loading?" | 7.5 §6, §8–§9 | Multi-asset GPU components have independent residency; unload is not specialization-cache deletion |
The guides in this part¶
7.1 — AIModel, InferenceFunction, NDArray, and the memory model¶
The object-model primer every other guide assumes, built around the structural fact that makes app
architecture fall out: AIModel owns nothing and pins a cache entry; InferenceFunction owns the
weights, so "when does this cost me a gigabyte?" is answered at loadFunction, not at init. Then
the part readers find hardest — Core AI is one of the SDK's heaviest adopters of Swift's non-escapable
machinery (Span, InlineArray, value generics, consuming/borrowing, typed throws), and §7 teaches
what that buys you rather than leaving you to fight the compiler. Also descriptor-driven code so your app
survives a model re-export, the three low-level performance APIs session 324 names and never explains.
⚠️ SILENT FAILURE — the layout-conversion copy (§11.1), and five siblings. Allocate with Apple's own hello-world
NDArray(shape:scalarType:)and specialization may have decided the hardware wants padded or interleaved strides: your call succeeds, the numbers are right, and the framework copies your tensor into a different layout on every inference, forever, with no diagnostic anywhere in the API, the docs or the tooling. Also:outputViews:for a name removes it from the returnedOutputs(§9.4); concurrentruncalls silently allocate more scratch, so an unboundedTaskGroupworks on an M4 Max and gets jetsammed on a phone (§5.2);AsyncValue.ndArrayreturns a copy for anMTLBuffer-backed value (§11.3); a dtype flag cached from the input descriptor and used on an output reinterprets bits into numeric garbage (§7.10); and EXIF orientation is nobody's job — Apple's own repo applies it on one path and not the other, so the same JPEG yields two orientations (§12.4).✅ ANSWERED (was the part's biggest GAP) — the error you catch is
AssetError, or nothing (§13). The macOS 27.0 beta SDK interface, captured 2026-07-29, recaptured 2026-08-20, settles it:AIModel.init,loadFunction,run,encodeand the cachedelete*methods all throw untyped errors, and the only public error type in the entire Core AI surface isCoreAIAsset.AssetError— fiveKindcases, all about the asset file (unsupportedVersion,invalidFeatureType,corruptedMetadata,invalidName,duplicateName). No public inference/specialization/cache error enum exists in this beta; the community-sightedAIModelErroris internal toCoreAIDelegates. §13.2's catch-AssetError-then- catch-broadly, degrade-don't-retry ladder is therefore not a workaround but the correct shape.
7.2 — Specialization, the model cache, and ahead-of-time compilation¶
The single largest source of first-launch stalls, wedged loads and mysterious disk growth. Specialization
is two phases — compilation ("the one which incurs most of the latency") then per-device artifact
generation — and every lever follows from that split: coreai-build compile moves phase 1 to your Mac,
AIModel.specialize moves its timing without reducing it, and AIModelCache.model(for:options:) is a
synchronous probe that never specializes, the gating primitive for a "Preparing…" screen. Also
retention policy, app-group caches, bookmarks that let you delete the source and keep running, and a
five-rung recovery ladder for wedged loads.
⚠️ SILENT FAILURE — three of them. The cache key is
(asset, SpecializationOptions), and the options are astructwith a mutable property, so two code paths can construct almost the same value: each combination gets its own multi-gigabyte entry and its own three-minute stall, with no error and no warning (§4).expectFrequentReshapes— the only settable property, documented with a one-line abstract and no stated default — was device-measured to make the runtime discard your AOT specialization and compile on device when requested on an all-static graph, observed case aSIGSEGVinside the Metal compiler with no message (§11). Andcoreai-build compileexits 0 for architectures the device will reject; only a device load validates the choice (§13).✅ REVERIFIED 2026-09-16 — the reference pages describe the tested runtime (§7). On the beta-5 device, stable macOS 27, and iPhone 15 Pro running iOS 27 build
24A435, deleting an entry retained by a liveAIModelthrew, left the entry findable, and succeeded after release; the article's silent-deferral wording did not match this run. The guide retains release-delete-verify code and notes that the observed error type is non-public. The fullcoreai-buildCLI surface, open when this was written, closed 2026-07-31: the tool ships in the optional Metal Toolchain component (xcodebuild -downloadComponent MetalToolchain), not Xcode-beta.app itself — which is why the 2026-07-29 check found it absent — and its full--helpis captured innotes/sdk-interfaces/coreai-build-help-27.0-beta.txt(§13). Still open: cancellation semantics for a specialization large enough to remain in flight when cancellation arrives; the current Mac/device fixture completed after about 10 seconds and left a cache entry, so it remains inconclusive.
7.3 — States as KV cache, and pipelined execution¶
A decode loop written the naive way gets slower every step, and in Instruments it is unmistakable:
inference intervals that visibly widen along the timeline. The fix is states — arguments the model
both reads and writes in place — taught across all three layers they touch, because a mistake at any one
converts cleanly and then misbehaves: register_buffer plus in-place mutation in PyTorch, state_names
plus automatic state rewriting during conversion, InferenceFunction.MutableViews at runtime. Then the
tier above run: encode(…, to: ComputeStream) is throws, not async throws, so the CPU can encode
step n+1 while the GPU computes step n, with the framework inserting the dependency edges itself.
⚠️ SILENT FAILURE — copy-on-write on the state (§8.1), plus three more.
NDArrayis a value type with COW storage, so holding your states in a dictionary you also read during the step leaves the buffer non-uniquely referenced and the in-place update copies the entire KV cache — tens of megabytes — every decode token. Output is perfect, the Core AI instrument looks healthy (the copy is CPU work outside the inference event), and your tok/s is a flat multiple too low. Also: an in-place mutation of aforward()argument silently promotes it from an input to a state (§8.2);state_namesordering is observed FX behaviour, not a PyTorch contract while every consumer indexesstateNamespositionally, so a swap yields a model that attends keys to values with a clean bill of health from every tool (§4); and nothing resets a state between conversations (§8.4).🔴 GAP + incident — the fixed-shape/ANE decode recipe crashes on the betas (§13). Community isolation (FB23024751,
apple/coreai-models#5) narrowed it to one variable: deriving the KV write index in-graph from runtime data. Conversion succeeds; load and execute die — SIGTRAP on Mac GPU, SIGSEGV on iPhone GPU, SIGABRT on the ANE, where it also corrupts the ANE compile cache. Two workarounds are documented; status unknown, so check the Feedback first. Also 🔴: the widely-repeated 3.5× pipelining figure measures against a naive hand-rolledfn.run()loop, not against the sequential engine, and no controlled comparison of the two exists (§12).
7.4 — Model bundles, the LLM engines, and grammar-constrained decoding¶
The layer above the runtime, where a raw .aimodel becomes something shippable and Apple's own Swift
package turns "I have a converted Qwen3" into LanguageModelSession(model:). The bundle format —
schema 0.2, a role→filename assets map, sidecars — which Apple's documentation never specifies,
reconstructed here from its four Python writers and two Swift readers with the disagreements called out.
The engines — three LLM engines plus a VLM engine, selected for you by the function names inside
your model, which is also what decides whether you land on the GPU or the Neural Engine. And
grammar-constrained decoding: @Generable on a non-Apple model is a JSON schema compiled into a
formal grammar masking the logits to -inf, and Apple, MLX and (by source-level evidence) the CoreAI
framework itself all reach for mlc-ai/xgrammar to do it — documented nowhere.
⚠️ The architectural constraint (§7.8). Constrained decoding needs per-step logits; the GPU-pipelined engine samples on-GPU, reports
supportsLogits == false, and is auto-selected for every macOS dynamic export — so on the default path, on macOS, with a stock Apple export,@GenerablethrowsunsupportedCapabilityat generation time on a session you already built. The nuance most retellings drop: three of the four engines can do it; the GPU path cannot, and on iPhone the ANE static-shape engine is auto-selected and it works out of the box. Worse, the check you would write lies: with the defaultmode: .lazy,capabilitiesreports.guidedGenerationbefore the engine loads. Usemode: .eagerwhenever you branch on it.⚠️ SILENT FAILURE (four more).
metadata.json'scompressionrecords the request, not the result — a quantization that raised is caught, logged as oneWARNINGmid-export, and ships a float16 artifact ~4× too big with exit code 0 (§2.11). A missingtokenizer/tokenizer.jsonfalls back to a HuggingFace Hub fetch, instant from your Mac's cache and broken on a user's device (§2.7).ConstrainedGenerationSessionaccepts astopTokenIdsarray, documents it as what prevents EOS mid-object, logs it, and discards it (§7.4). AndvocabTypedefaults differ between initializers, where the wrong one over-constrains the grammar into producing nothing (§7.5).🔴 GAP —
CoreAISpeechhas no exporter.SpeechBundlerequiresencoder.aimodel+decoder.aimodeland nothing in the repository produces that split; treat it as pre-release (§2.9). Also open: whether a multi-namefunction_mapis honoured anywhere, and whetherSystemLanguageModel's own structured output is this same xgrammar mechanism.
7.5 — Non-LLM engines: bundles, function structure, warmup, specialization, and caching¶
The runtime owner for CoreAISegmentation, CoreAIObjectDetection, and CoreAIDiffusion. It compares
the three shapes Apple's package actually ships: a single main; one asset with
image_encode / text_encode / detect; and a diffusion directory containing independently loaded
component assets. The guide follows each choice through structure probing, specialization options,
function residency, dummy-forward warmup, lazy unloading, and Core AI versus application-level caches.
⚠️ Two performance traps look like successful inference. The public segmentation facade runs
image_encodeagain for every prompt, so it does not realize the advertised same-image reuse unless the app owns and caches the intermediate feature NDArray. Diffusion's lazy mode correctly releases each component after its stage, but repeated requests then reload those components; the specialization cache may survive while resident model/function state does not. Also: detector postprocessing turns malformed output shapes into an empty detection array, indistinguishable from a genuinely empty scene unless the app validates the asset ABI.
Reading order¶
Everyone starts at 7.1 — §1–§9 plus §13, the vocabulary the other four assume; §13's error-handling ladder is a day-one need, not a post-mortem one. Skip §11–§12.
Then branch by what you ship. Segmentation, object detection, or diffusion: read
7.5 after 7.1, then use
7.2 for the cache and AOT mechanics its engines do
not expose. A one-function tensor model with no product facade: go to 7.2 directly; §1–§7 and §17,
plus §13–§14 if the model is large enough to want AOT. A language model you drive yourself:
7.3 next, in order — §1–§5 make it correct,
§6–§9 stop it wasting memory, §10–§12 make it fast — then
7.2 when the stall shows up. A language model behind
LanguageModelSession: go 7.4 directly after
7.1, because apple/coreai-models implements most of 7.3 for you and the thing you must actually
decide — which engine, and therefore whether @Generable works — is §5.8 and §7.8.
Read two out of order — 7.3 §14, because hybrid architectures cannot do prefix reuse and that should reach you before you pick a checkpoint, and 7.2 §14.1, because AOT only produces artifacts for Apple-Intelligence-capable devices. Skippable: 7.1 §11.3 and 7.3 §10–§13 unless you hand-write a pipelined decode loop; 7.4 §2.8 is superseded by 7.5 for anyone who actually ships diffusion.
What this part deliberately does not cover¶
- Making the
.aimodel.coreai-torch,torch.export, op coverage,dynamic_shapes, the ANE-vs-GPU authoring rules: Part 8 and Part 10. This part covers only the slice of authoring that produces a state (7.3 §3–§4), which you cannot debug from the Swift side alone. - Why
NDArray.ScalarTypehas 35 cases 1 — compression, palettization, and whether your 4-bit export is any good: Part 9. The Debugger, the gauge and the Instruments template in depth: Part 10; they appear here only as how you see a specialization event or a growing inference interval. - Authoring a
LanguageModelconformance and choosing among the five conformers: Part 4 —CoreAILanguageModelis dissected in 7.4 §8 only where it differs from the worked examples there. Everything in front of the session —@Generable,Tool, streaming, the failure taxonomy — is Part 2, and context management as a discipline is Part 3. - Background Assets delivery, first-run UX and OS-update re-specialization as operations —
Part 15. Measuring whether what you shipped is any good, for
which Apple's repo has no vision/audio/diffusion benchmark and no published quality number:
Part 6. Migrating image preprocessing and box conventions from Core ML:
Part 17 reference 05; 7.5 owns
the new runtime engine and lifecycle. MLX, which exposes logits
trivially where Core AI's fast path does not: Part 13. Coming from Core ML
or
coreai-torch0.4.x: Part 17.
Sources for this part¶
Strongest first. SDK module interfaces, read on disk (captured 2026-07-29 from Xcode build 27A5228h;
recaptured 2026-08-20 from beta 5 build 27A5237l, macOS 27.0 SDK; stored in notes/sdk-interfaces/): CoreAI (umbrella),
CoreAIDelegates (the loading/caching/options surface and the re-exports), CoreAIRuntime
(1,428 lines), CoreAIAsset, and the empty-in-this-beta CoreAICache/CoreAICommon/
CoreAICompiler — the evidence class that finally closed the error-type gap and confirmed every
runtime signature in 7.1/7.2. Apple source read on disk: apple/coreai-models at commit 5ed9981 (2026-07-23) —
the three LLM engines and the VLM engine, ModelStructure.swift (the structure→compute-unit mapping, the
strongest guidance on SpecializationOptions anywhere), the bundle readers, NDArray+Helpers.swift,
ImagePreprocessor.swift, the two xgrammar wrappers, CoreAILanguageModel.swift, the four Python bundle
writers, Package.swift/Package.resolved and the agent skills in skills/ — plus merged PRs #62,
#74, #89, PR #85 (closed unmerged 2026-08-23), issue #5 (closed completed
2026-09-02), issue #55 (closed completed 2026-08-27; silent GPU fallback remained), #58, #112,
each documenting a real failure; and
apple/coreai-torch v0.4.1 (converter.py, _utils.py, tests/test_stateful.py, the notebooks, and
the release note that gates 0.4.0 assets). Apple documentation, harvested 2026-07-27 via sosumi.ai
plus Apple's raw DocC JSON API (which preserves term lists and tables the Markdown mirrors drop): the
full 312-entry symbol index, ~70 member pages beneath aimodel, aimodelcache,
inferencefunction, ndarray, computestream and asseterror, and the eight articles on integrating,
AOT-compiling, debugging, gauging and profiling. WWDC26 transcripts: 324 "Meet Core AI" and 326
"Core AI app features", with 325 referenced once. Community sources, labelled as such at every
point of use and never presented as Apple figures: john-rocky's Core AI model zoo and fork (the
194-second cold load, the AOT A/B, the expectFrequentReshapes SIGSEGV, the MPSGraph KV-write isolation,
the prefix-reuse speedups — single-author, beta-era, self-declared uncontrolled conditions),
frozen Noema 3.5 snapshot (the copy-on-write trap, shape bucketing, host-cache detection),
1amageek/swift-lm and lucasnewman/mlx2coreai (two independent integrations corroborating the LLM
state contract), and ml-explore/mlx-swift-lm for the xgrammar namespace-collision comment. Apple
published no latency figure for anything in this part beyond one ~800 ms screenshot.
-
Apple’s current
NDArray.ScalarTypereference enumerates the 35 cases summarized by this series: Apple Developer —NDArray.ScalarType. ↩