All posts

Core AI vs Core ML in October 2026: Run Both, Don't Migrate Yet

Apple's new Core AI framework looks like Core ML's replacement, but the most detailed public migration shipped it as an opt-in and kept Core ML as the default. The comparison, the iPhone 17 Pro numbers, a dual-backend architecture, the beta pitfalls, and the criteria for flipping the default.

Published
October 9, 2026
  • iOS
  • Swift
  • Core AI
  • Core ML
  • On-Device AI
  • SDK Architecture
  • AI

Apple shipped a new framework for on-device models at WWDC26 and called it Core AI. The name invites a simple reading: Core ML is the old thing, Core AI is the new thing, start migrating. Some coverage has said exactly that. Apple has not. Its Core AI developer page does not mention Core ML at all, and I found no deprecation notice or date anywhere.

The most detailed public migration I have seen points the other way. Ultralytics added Core AI to its YOLO iOS SDK in September, benchmarked every task on an iPhone 17 Pro, and shipped it as an opt-in. Core ML stayed the default. The GitHub issue that opened as “Migrate default YOLO26 iOS runtime to Apple Core AI” was retitled two days later to “opt-in now, criteria for making it the default.”

I think that is the right call for most teams in October 2026. The reasons are architectural as much as they are about speed, which is why they are worth spelling out.

What this is based on. I have not migrated a production app to Core AI. The numbers below are Ultralytics’ published measurements, the API details come from Apple’s and Ultralytics’ documentation, and the code is quoted from the Ultralytics SDK source. What I add is how I would structure it, from having owned an SDK that ran on-device inference inside 50+ customer apps.

A new deployment path, not a renamed Core ML

The first thing to know is that you cannot convert in place. A .mlpackage cannot be renamed to .aimodel. In the words of the Ultralytics docs, “they contain different model representations and are loaded by different frameworks.” You re-export from the source PyTorch model with Apple’s coreai-torch. The same docs put it plainly: “Core AI is a new deployment path rather than a new name for Core ML,” and “Apple continues to support Core ML.”

What Apple does promise for Core AI is real: a memory-safe Swift API, automatic specialization for the device’s hardware, ahead-of-time compilation, control over inference memory, zero-copy data paths and stateful execution. There is also a new toolchain around it: coreai-optimization for quantization and palettization, coreai-models for export recipes and Swift runtime helpers, a Core AI Debugger app for macOS, and Xcode and Instruments support.

Core AI Core ML
Model format .aimodel .mlpackage, .mlmodel
Conversion coreai-torch, from PyTorch coremltools
Minimum OS iOS 27, macOS 27, built with Xcode 27 The OS versions your app already supports
iOS Simulator Not available, the Simulator SDK has no Core AI Available
Vision integration None. The app letterboxes the image and builds NDArray tensors itself Direct. Vision handles preprocessing
Input shape Fixed. The graph is traced at one input size Flexible shapes supported
NMS inside the model Not supported, coreai-torch has no NMS lowering, so NMS runs in Swift Can be packaged into the model
Load cost One-time specialization, then tens of milliseconds In the Ultralytics SDK, about 1 to 2 s to compile a bundled .mlpackage on every launch
Precision in the Ultralytics export today FP16 only INT8 shipped
Non-neural models (trees, tabular) Not the target Apple still points these at Core ML

Three rows decide most of this: the minimum OS, the Simulator, and Vision. Together they mean adopting Core AI is not a swap. It is a second backend that only runs on part of your user base, can’t run in the environment most CI uses, and needs preprocessing code that Core ML users never had to write.

What the iPhone 17 Pro numbers say

The headline people repeat is that Core AI runs models about twice as fast. For model time alone, that holds. For the whole pipeline, it mostly does not.

Ultralytics measured YOLO26n on an iPhone 17 Pro running iOS 27.0, with hardware acceleration on, in a release build. Each number is the median of three interleaved rounds of 15 runs after three warmups, averaged over two cooled launches. Totals include preprocessing, inference and postprocessing, in milliseconds.

Task Core AI FP16 Core ML FP16 Core ML INT8 (shipped) Their verdict
Detect 3.73 4.05 4.59 Core AI ahead
Segment 5.33 5.24 5.79 Tie
Pose 4.02 4.06 4.09 Tie
OBB 3.65 3.99 3.79 Tie, Core AI slightly ahead
Classify 1.19 2.34 1.98 Core AI ahead
Semantic 8.55 4.86 4.95 Core AI behind
Depth 7.87 6.62 6.42 Core AI behind

Source: Ultralytics, yolo-ios-app issue #320 and docs/performance.md. Median run-to-run spread was 3%, worst 17%, so the pose and segment gaps are noise.

The raw model is genuinely faster. Detect inference takes 2.05 ms on Core AI against 3.58 to 4.58 ms on Core ML. Then Core AI pays about 1.2 ms of CPU preprocessing per frame, because there is no Vision doing it inside the request, plus NMS in Swift. That gives most of the gain back. Semantic segmentation is slower because the in-graph argmax was unreliable (more on that below), so it runs in Swift at about 3.8 ms per frame.

Three more results matter for a default:

  • CPU-only is 1.5 to 2x slower on Core AI for every 640-pixel task. Only classify is faster. Any code path that falls back to the CPU gets worse, not better.
  • Downloads are about twice the size. The Core AI assets are FP16 because the Ultralytics package cannot export INT8 Core AI yet. Apple’s optimization tools support quantization and palettization, but the export pipeline has not caught up. Ultralytics notes FP16 and INT8 Core ML are within noise of each other on this device, so INT8 only buys you download size.
  • Load time is the clear win. After a one-time specialization (0.6 to 3 s for nano models, 2 to 7 s for the largest), a Core AI model loads in tens of milliseconds. Switching between models is visibly faster.

On that last point, read the baseline carefully. The comparison is against compiling a bundled .mlpackage on every launch. An app that compiles once and keeps the compiled model pays that cost less often, so measure your own baseline before treating load time as the deciding factor.

My read of the whole table: for vision pipelines on current hardware, Core AI is level end to end, ahead on load and model switching, and behind on semantic, depth, CPU-only and download size. Level is not a reason to make a beta runtime the default for everyone.

Running both: the dual-backend shape

If Core AI only runs on iOS 27 devices, you need Core ML for as long as you support anything older. So the question is not which framework to pick. It is where the choice lives, so it doesn’t leak into every feature.

Dual-backend architecture: a backend router picks Core AI for .aimodel assets behind a canImport and #available gate, falls back to Core ML on the Simulator and older OS versions, and both paths feed shared decoders

The asset decides the backend. Core ML is the default and the only path CI exercises. Core AI is opt-in, device-only and gated twice.

The Ultralytics SDK does four things I would copy.

The asset picks the backend. An .aimodel path or an .aimodel.zip URL goes to Core AI. Anything else goes to Core ML. Feature code never asks which framework it is on.

let ext = unwrappedModelURL.pathExtension.lowercased()
if ext == "aimodel" {
  try predictor.loadCoreAIModel(at: unwrappedModelURL, useGpu: useGpu)
  predictor.isModelLoaded = true
  DispatchQueue.main.async { completion(.success(predictor)) }
  return
}

Source: Sources/UltralyticsYOLO/BasePredictor.swift, ultralytics/yolo-ios-app at commit 1f1ee5f (AGPL-3.0).

Capability is gated twice. #if canImport(CoreAI) handles builds where the framework does not exist at all, like the Simulator SDK. #available(iOS 27.0, *) handles older OS versions on a device. One property answers the question for the whole SDK.

public static var isCoreAIAvailable: Bool {
  #if canImport(CoreAI)
    if #available(iOS 27.0, *) { return true }
  #endif
  return false
}

Source: Sources/UltralyticsYOLO/CoreAIRequest.swift, same commit.

It is off by default. The demo app has a Settings toggle, “Core AI Models (iOS 27+)”, and only lists Core AI models when it is on.

Everything after the tensor is shared. The .aimodel export writes its metadata with the same keys and values as the Core ML export, so the label parser is reused. The Core AI output tensors go into the same task decoders the Core ML path uses. The SDK even wraps Core AI in a VNRequest-shaped carrier so every predictor drives both backends through one interface.

For an SDK that other apps embed, I would add four things on top.

  • Make the default remotely switchable. A Settings toggle is fine for a demo app. In production, the default backend should be a config value you can flip back without a release. I learned to value that lever on a server-driven SDK, where most changes did not need a client build.
  • Don’t rely on try/catch alone for fallback. Some failures throw, and you can catch them. Others abort the process inside Apple’s runtime (see the next section). For those, persist a marker before loading a Core AI asset and clear it after. If the marker is still there on the next launch, use the Core ML asset for that model.
  • Compile the Core AI branch in CI, even if you cannot run it there. A Simulator build has no CoreAI module, so the #if canImport(CoreAI) branch is never compiled. Ultralytics notes its CI only exercises the Core ML path. Add a device-SDK build step so that branch at least compiles on every change, and plan real device runs for behaviour.
  • Download the asset the device can use. Bundling both formats ships two copies of every model. The Ultralytics app picks .aimodel or .mlpackage per device for remote models. That keeps the size cost of FP16 limited to devices that run it.

The beta pitfalls, and what each one costs

Core AI and parts of its toolchain are beta software, and the Ultralytics work hit most of the sharp edges a production team would. This is the list I would read before writing any code.

Issue What happens Mitigation in the Ultralytics SDK
FP16 end-to-end pose head (apple/coreai-torch#115) Returns wrong output under hardware acceleration with default specialization, with no error. 0 people detected on a test image where the raw head finds 4 Ship the raw head. CPU-only, GPU-preferred and FP32 are correct
In-graph argmax (apple/coreai-torch#116) Wrong class map under hardware acceleration. A float16-output variant aborts the process Run argmax in Swift, at about 3.8 ms per frame
Stale specialization cache After an asset is replaced under the same path, or the app is reinstalled, the load fails with _GenericObjCError.nilError instead of rebuilding Evict the cache entry and retry once
Output allocation limit (apple/coreai-torch#75) Reported hard crash after 2^14 output NDArray allocations, on all compute units Not reproduced in their 22,000-inference soak on device. Still worth a soak test of your own
Some FP16 assets on load MPSGraph raises a failed assertion and the process ends, with no fallback Fall back to FP32. The same asset loads with a CPU-only specialization
Naive tensor construction NDArray(scalars:) copied element by element, about 55 ms per 640 x 640 frame Allocate the input tensor once and fill it in bulk

The stale-cache retry is short enough to quote in full:

do {
  return try await AIModel(contentsOf: url, options: options).loadFunction(named: "main")
} catch {
  // On iOS 27.0 a stale specialization cache entry (the asset was replaced under the same path, or the app was
  // reinstalled) fails the load with `nilError` instead of being rebuilt, so evict it and specialize again.
  try? AIModelCache.default.deleteEntries(for: url)
  return try await AIModel(contentsOf: url, options: options).loadFunction(named: "main")
}

Source: Sources/UltralyticsYOLO/CoreAIRequest.swift, same commit. Six of their seven official nano assets hit this after two days of reinstalls, and all recovered with the retry.

Two rows in that table produce wrong answers without any error, and two end the process. Those are the ones that should shape your architecture. A wrong answer needs a correctness test, and a process abort needs a fallback that survives a crash. A slow load only needs a spinner.

A smaller trap: the iOS Simulator has no Core AI, and macOS 26 has no system Core AI framework. The pip runtime you can install on a macOS 26 Mac is a CPU reference build, so it cannot tell you anything about device latency. Every Core AI number you care about comes from a real device on iOS 27.

Where AI fits

Apple pitches Core AI for everything “from compact vision models to large-scale generative AI.” The public migration evidence I found is all vision. Stateful execution and fine-grained memory control are the features that matter most for generative models, and I have not seen comparable public numbers for a language model on Core AI against Core ML. So treat the conclusion of this post as being about vision models. For a generative feature the trade could come out differently, and for plain text generation many apps will reach for Foundation Models first anyway.

For developers building with AI, the bigger lesson is about evaluation. The pose bug in #115 returned a fast, confident, wrong answer. A latency benchmark would have scored it as a win. Only a correctness check caught it. If you run two backends, the eval suite has to run per backend, on a device, with golden outputs for each model you ship. That is the same discipline as gating an LLM feature on evals rather than on demos, applied one layer down.

Apple’s coreai-models repository also ships skills for coding agents, installable as a plugin for agent tools. That is useful for export recipes and API usage. But an agent working against a Simulator build cannot run Core AI at all, so anything it writes for this path is unverified until it runs on a device. Keep the device in the loop.

The real-world impact shows up in who gets which backend. An SDK embedded in other teams’ apps, like the one I worked on at about 2.2M monthly active users, inherits every host app’s deployment target. It can’t default to an iOS 27-only runtime until most of those users are on iOS 27, regardless of how good the runtime is.

When to flip the default

Ultralytics wrote down seven criteria for making Core AI the default. I think they work as a template for any team, with each one translated into something you can check in your own app.

Their criterion Where it stood in issue #320 What I would check in my own app
CPU preprocessing (about 1.2 ms per frame) no longer costs the gain, and depth reaches parity Tracked in yolo-ios-app #321 Your own preprocessing cost per frame, on your slowest supported device
Semantic segmentation at parity end to end Tracked in #322 and apple/coreai-torch#116 Any postprocessing you had to move from the graph into Swift
The FP16 wrong-output bug is fixed, or shown irrelevant to every shipped graph Avoided by shipping the raw head A golden-output test per model per backend, on device
The stale specialization cache is fixed, or shown harmless across app updates Worked around with evict and retry An app update that replaces a model under the same name
INT8 or palettized Core AI export, so downloads match Core ML Not yet, assets are FP16 Your download-size budget per model
Every asset loads on device, re-benchmarked on the current iOS release 17 of 35 verified when the criteria were written Every asset you ship, on the current iOS point release
iOS 27 adoption high enough that the default helps most users Open Share of your own sessions on iOS 27

I would add two of my own before flipping it in anything other teams depend on: the crash-safe fallback has to be in place, and the default has to be reversible from config without a release. With both in place, flipping the default becomes a change you can undo the same day.

Until those boxes are checked, the useful work is the boundary, not the migration. Build the second backend behind one switch, keep Core ML as the default, and let the criteria decide when to flip it.

Next

Read more posts, or see the engineering work behind them.