← All courses ← Course home

Lesson 06 of 08

Build on-device chat

Load the baked model and tokenizer once. Then run a small chat flow that makes the SDK line easy to see and test, with no network.

Agent brief (llms.md)

Why the chat screen is the real proof

Style only counts when the phone answers without a network. That is proof the phone has the model. Latency is the time from send to first useful token on the target device. Privacy means no request leaves the process. The clothing-classifier course proves the same last step for labels. This lesson proves it for writing style.

The app loads the merged artifact. It does not attach a LoRA at runtime. A second writing style is a second bake.

The steps, one box at a time

LaunchLoad model and tokenizer
LaunchLoad model and tokenizer
SessionSystem and user text
LaunchLoad model and tokenizer
SessionSystem and user text
GenerationLocal response
LaunchLoad model and tokenizer
SessionSystem and user text
GenerationLocal response
MeasureLatency and memory

Environment setup

Use Xcode 27. Create an iOS or macOS SwiftUI App. Add Core AI. Install the Metal Toolchain. Add the .aimodel and tokenizer files to Copy Bundle Resources.

xcode-select --install
xcodebuild -version
# File > New > Project > App, Interface: SwiftUI
# Xcode Settings > Components > Metal Toolchain
# Add writing-style.aimodel and the tokenizer sidecar to the target.

Keep the Mac uv project so you can rebuild the artifact.

cd writing-style-lora
uv run python -c "import torch; print(torch.__version__); print('MPS:', torch.backends.mps.is_available())"

Worked path

Use the current Core AI language APIs for the SDK in Xcode. A CoreAILanguageModel and LanguageModelSession shape is illustrative. Confirm names in the installed docs.

import SwiftUI
import CoreAI

// Illustrative shape only. Verify names in the installed SDK.
@MainActor
final class Writer: ObservableObject {
    @Published var reply: String = ""
    @Published var status: String = "Preparing"

    private var session: AnyObject?

    func prepare() async {
        do {
            let model = try await loadDocumentedLanguageModel(
                resource: "writing-style.aimodel",
                tokenizer: "tokenizer"
            )
            session = try makeDocumentedSession(model: model)
            status = "Ready"
        } catch {
            status = "Load failed: \(error.localizedDescription)"
        }
    }

    func send(_ userText: String) async {
        do {
            reply = try await documentedRespond(session, to: userText)
            status = "Local"
        } catch {
            status = "Generate failed: \(error.localizedDescription)"
        }
    }
}

Prepare once in a .task or app setup. Keep prompts inside the context budget. Move generation off the main UI path. Make cancellation and memory pressure visible. Do not fetch an adapter.

Evaluate the real app. Short prompt. Long prompt. Two-turn rewrite. A request outside the training set. Compare voice, whether it follows the request, invented facts, latency, and memory against a base-model build. Read Apple Core AI documentation for current deployment details.

Runtime boundary. The app loads the merged artifact and its tokenizer. It does not attach a new LoRA at runtime in this course.

Failure modes

Done when

Keep learning

PyTorch to Core AI in Xcode · From the metal to the model · Core AI models, typed · Model architectures in plain English · Diffusion LoRA to Core AI on device

Key concepts

  • Style counts when the phone answers with no network.
  • Load the baked .aimodel and tokenizer once. Prepare in .task or app setup.
  • Generate off the main UI path. Measure latency and memory.
  • Swift class names in the lesson are illustrative. Check the installed SDK docs.

Takeaways

  • A missing tokenizer sidecar makes output look like the wrong model.
  • Test a short prompt, a long prompt, a two-turn rewrite, and an out-of-training request.
  • A second writing style is a second bake.