Ashvara
Blog/AI
AI

iOS 27: one Swift API for the on-device model, Apple's cloud model, or yours

In iOS 27 a Foundation Models session can run on device, on Private Cloud Compute or on any model you plug in. How to choose per request, with the code.

S
Sahil Jain
AI · Ashvara
Oct 26, 2026
8 min read
iOS 27 models

In iOS 27, the Foundation Models framework stopped being tied to one model. The same LanguageModelSession code can now run on the 4,096-token on-device model, on Apple's larger server model through Private Cloud Compute with a 32K-token window, or on any model that adopts the new LanguageModel protocol. Apple describes switching as "a single line of code". That makes the choice of model something your app decides for each request. Our default: start on device, measure the request with tokenCount, and move to the cloud only when it won't fit.

Diagram contrasting iOS 26 and iOS 27 model choice in the Foundation Models framework. On the left, an amber panel labelled "iOS 26: one model" shows the request "Summarise this 20-page lease for me" going to the on-device model with a 4,096-token window, and a red dashed arrow to the error exceededContextWindowSize. It is captioned "Your options were to chop the input into pieces, or to run your own server with its own API, keys and bill. Two code paths for one feature." On the right, an indigo panel labelled "iOS 27: pick the model per request" shows LanguageModelSession(model:) leading to a decision diamond, "tokenCount: fits on device?". Yes leads to an on-device box (SystemLanguageModel, 4K, works offline, no usage limit) with a phone icon; no leads to a Private Cloud Compute box (32K, reasoning levels, daily limit per person) with a cloud icon. A red dashed arrow from the cloud box back to the on-device box reads "network failure: retry on device if it fits". A green pill reads "or any LanguageModel: a Core AI open model, or your own server", captioned "One code path. The choice is a function you can test." Stat chips across the top read: 4K tokens on device to 32K; switching model is "a single line of code"; larger model has a daily limit per person and needs no API key. A footer band reproduces Apple's comparison: both preserve privacy; on device works offline and Private Cloud Compute does not; on device has no usage limits while Private Cloud Compute has a limit per day; on device has no reasoning and 4K context while Private Cloud Compute offers light to deep reasoning and 32K. A side note reads "Our default: start on device, measure with tokenCount, and go to the cloud only when the request won't fit."

This continues our Swift series: we built a feature on the on-device model, added semantic search and gave it tools. All three were limited by the same thing, the 4,096-token window. iOS 27 is the first release where a request that doesn't fit has somewhere else to go without a second code path.

What changed, from Apple's own documentation

Apple's June 2026 Foundation Models update notes list the changes. Three matter most for app developers:

  • Any model can sit behind a session. "Adopt the LanguageModel protocol to use any large language model — server or on-device — with the Foundation Models framework."
  • Apple's server model is one line away. PrivateCloudComputeLanguageModel gives "more reasoning capabilities and a larger context size."
  • A new on-device model. It "follows instructions more accurately", and because it changes when people update, Apple says to "test your prompts with the new model."

The Private Cloud Compute guide sets the two Apple models side by side:

On-device modelPrivate Cloud Compute
Preserves privacyYesYes
Works offlineYesNo
Usage limitsUnlimitedLimit per day
ReasoningNot supportedMultiple levels
Context size4K32K

Two practical details from the same guide. There are no API keys: "You don't need to handle either when you use PCC." And the daily limit belongs to the person, not to your app: they "get a daily request limit" and can upgrade their iCloud+ plan for more. Using Private Cloud Compute also needs a managed entitlement that you request from Apple.

The routing function

Here's the decision as one function. It measures the request with tokenCount(for:) (available since iOS 26.4), keeps room for the answer, and only reaches for Private Cloud Compute when the on-device model can't hold the request:

import FoundationModels

struct TooLongForDevice: Error { let tokens: Int }

/// On device when the request fits; Private Cloud Compute when it doesn't.
@available(iOS 27.0, *)
func makeSession(for document: String, instructions: String) async throws -> LanguageModelSession {
    let onDevice = SystemLanguageModel.default
    let needed = try await onDevice.tokenCount(for: document)
        + onDevice.tokenCount(for: Instructions(instructions))
    let roomForAnswer = 800

    if needed + roomForAnswer <= onDevice.contextSize {
        return LanguageModelSession(model: onDevice, instructions: instructions)
    }
    let cloud = PrivateCloudComputeLanguageModel()
    if cloud.isAvailable && !cloud.quotaUsage.isLimitReached {
        return LanguageModelSession(model: cloud, instructions: instructions)
    }
    throw TooLongForDevice(tokens: needed)
}

Everything after this function is the same whichever model it picked: the same respond calls, the same tools, the same @Generable types. That's the real change. In iOS 26, "too long for the device" meant a separate integration with a separate server.

When the cloud fails

Private Cloud Compute needs a network connection, and it can say no. Its error type has three cases: quotaLimitReached, networkFailure and serviceUnavailable. Apple's advice for a network failure is to "retry the request using the on-device model". For a long document, that means splitting it up, which is exactly what you'd have had to do on iOS 26:

@available(iOS 27.0, *)
func summarise(_ document: String) async throws -> String {
    let instructions = "Summarise the document in five plain bullet points."
    let session = try await makeSession(for: document, instructions: instructions)
    do {
        return try await session.respond(to: document).content
    } catch let error as PrivateCloudComputeLanguageModel.Error {
        switch error {
        case .networkFailure, .serviceUnavailable:
            return try await summariseInParts(document)   // on device, in pieces
        case .quotaLimitReached:
            throw error                                    // show the quota UI
        @unknown default:
            throw error
        }
    }
}

The on-device fallback notes each part in its own fresh session, then summarises the notes. It uses only iOS 26 APIs:

/// On-device only: note each part in its own session, then summarise the notes.
func summariseInParts(_ document: String) async throws -> String {
    var notes: [String] = []
    for part in document.chunked(maxCharacters: 6_000) {
        let session = LanguageModelSession(instructions: "Note the key facts in this part of a document.")
        notes.append(try await session.respond(to: part).content)
    }
    let session = LanguageModelSession(instructions: "Summarise these notes in five plain bullet points.")
    return try await session.respond(to: notes.joined(separator: "\n")).content
}

chunked splits on paragraph breaks. Six thousand characters is roughly 1,500 tokens of English. For other languages, especially Chinese, Japanese and Korean where a token can be a single character, size the pieces with tokenCount instead.

For the quota case, Apple gives you the status rather than leaving you to guess. quotaUsage tells you whether the person is approaching or over their daily limit, and its limitIncreaseSuggestion can show the system's own upgrade options. Apple's guidance is to show that state in your UI, not in a dismissable alert. Xcode can also simulate "Approaching Quota Usage Limit" and "Quota Usage Limit Reached" from the scheme options, so the unhappy path can be tested before anyone hits it.

Bringing your own model

The LanguageModel protocol is what makes the third option possible. Apple's own example loads an open-source model exported with the new Core AI framework, and the session code doesn't change:

let model = try await CoreAILanguageModel(resourcesAt: modelURL)
let session = LanguageModelSession(model: model)

Apple names three reasons to use one: to get "specialized capabilities that the model provides", to "support a range of devices that might not support Apple Intelligence", and to "maintain cross-platform support". The second is the big one for us. Until now, every Apple Intelligence feature was simply missing on older iPhones. A small bundled model gives those people something, through the same code. Apple suggests a model of "around 0.6B parameters as a good first choice".

Our opinion

The headline isn't the bigger model. It's that the choice of model now lives in one function in your app. That function can be tested, logged and changed in an update, and that's what we'd build around.

We'd still default to the on-device model, for three reasons. It works offline, it has no usage limit, and the daily cloud limit is the person's to spend, not yours. An app that sends every request to the cloud uses up someone's allowance on work their phone could have done. Measure each request, use the cloud when the request needs it, and make sure the feature still does something useful when the cloud says no.

We'd also take the warning about the new on-device model seriously. Prompts tuned for one model can behave differently on the next, so keep prompts versioned and tested the way you'd test any dependency that changes underneath you.

One honest caveat: Xcode 27 requires an Apple silicon Mac, and our build Mac doesn't have one. The iOS 27 code here follows Apple's documented API exactly but hasn't been compiled by us yet. The on-device fallback (summariseInParts and chunked) does compile under Swift 6 against the iOS 26 SDK.

How Ashvara helps

We build iOS apps with AI features that work on any iPhone that can run them: on-device where possible, Apple's cloud model when the job needs it, and a sensible fallback when neither is available. We set up the routing, the quota UI and the tests for the unhappy paths.

That's the iOS development side of our AI solutions work. If you have an AI feature that's hitting the on-device limits, or one you held back because it wouldn't fit, tell us about it. iOS 27 may be what makes it work.


Sources: Apple Developer documentation, Foundation Models updates, Adding server-side intelligence with Private Cloud Compute, PrivateCloudComputeLanguageModel, LanguageModel and Running a Core AI model in a Foundation Models session.

Share this article
S
Sahil Jain

Founder at Ashvara, a studio that builds software end to end - mobile, web, AI, and the systems behind them. Writes about shipping products that last.

Building something? Let's talk.