← All courses ← Course home

Lesson 01 of 05

Where the first-token wait goes

Before you change anything, split the wait into specialization, load, setup, and inference. Use the Core AI instrument and the debug gauge, plus your own timers.

Agent brief (llms.md)

Environment setup

  • Xcode 27, iOS 27 or macOS 27 on Apple silicon. Ahead-of-time compilation only targets devices that support Apple Intelligence (Apple lists iPhone or iPad with A17 Pro or later, and Mac with M1 or later).
  • Create an iOS App target (SwiftUI). Under Frameworks, Libraries, and Embedded Content, add CoreAI.framework. The Core AI debug gauge only appears when the target links Core AI directly.
  • Install the Metal Toolchain. Builds that include .aimodel files fail without it.
  • Python 3.11+ through uv to export a model with Apple's coreai-models recipes.
xcodebuild -downloadComponent MetalToolchain

curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/apple/coreai-models.git && cd coreai-models
uv venv
# The repo's uv project pins its own deps (coreai-torch, PyTorch).
# PyTorch uses MPS on Apple silicon and falls back to CPU.
uv sync
uv run python -c "import torch; print('mps' if torch.backends.mps.is_available() else 'cpu')"
# Export a small language model for iOS.
uv run coreai.llm.export Qwen/Qwen3-0.6B --platform iOS

If you start a fresh project instead of the repo, use uv init --python 3.11, uv venv, then uv add torch coreai-torch.

The four events

Apple's Instruments guide splits Core AI activity into four event types. In the order they usually appear:

1Specialization
2Load
3Setup
4Inference

Look with Apple's tools

  1. Debug gauge: run from Xcode and open the Core AI gauge in the Debug navigator. It shows median Inference, Load, and Specialization times, plus a table of events. See Monitoring model performance with the debug gauge.
  2. Instruments: Product > Profile, choose the Core AI template, record while you trigger the model. The trace shows which compute units run your model and whether specialization happens when you expect. Profile on a real device, with no other heavy apps running.
One setting to turn off before you time specialization. The scheme option "Include Model Debug Information" (Edit Scheme > Run > Diagnostics > Core AI) adds overhead to every on-device specialization. Apple says load and inference times are not affected. Turn it off when you measure specialization.

Time it in your own code too

Tools are for the lab. In the field you need logs. Wrap the steps you control: model init (which includes specialization when needed), loadFunction(named:), and the first run.

import CoreAI
import os

struct FirstTokenTimings: CustomStringConvertible {
    var cacheHit = false
    var modelInit: Duration = .zero
    var loadFunction: Duration = .zero
    var firstRun: Duration = .zero
    var description: String {
        "cacheHit=\(cacheHit) modelInit=\(modelInit) loadFunction=\(loadFunction) firstRun=\(firstRun)"
    }
}

let log = Logger(subsystem: "dev.edgefde.coreai", category: "first-token")

func timedLoad(modelURL: URL, functionName: String) async throws -> (InferenceFunction, FirstTokenTimings) {
    var t = FirstTokenTimings()
    let clock = ContinuousClock()

    t.cacheHit = (try AIModelCache.default.model(for: modelURL, options: .default)) != nil

    var start = clock.now
    let model = try await AIModel(contentsOf: modelURL, options: .default)
    t.modelInit = clock.now - start

    start = clock.now
    guard let function = try model.loadFunction(named: functionName) else {
        throw CocoaError(.fileReadCorruptFile)
    }
    t.loadFunction = clock.now - start

    log.info("\(t.description, privacy: .public)")
    return (function, t)
}

Then time the first run separately with the same clock. Log the device model and OS version with every line. Specialization output is tied to both, so an OS update can bring the slow first launch back.

For the streaming side (time to first token and time between tokens as the user sees them), see On-Device Streaming Metrics (TTFT).

Sources

Key concepts

  • Core AI work shows up as Specialization, Load, Setup, and Inference events.
  • Specialization is often the slowest and happens at most once per model and options, unless cached.
  • The debug gauge and the Core AI Instruments template show these events and the compute units used.
  • "Include Model Debug Information" slows specialization. Turn it off before timing it.

Takeaways

  • Measure before you optimize. Log cache hit, model init, function load, and first run.
  • Profile on a real device with nothing else heavy running.
  • Log device model and OS version with every timing.