Foundation Models 有一个提示词和架构都治不好的别扭之处:这个框架只在 Apple Intelligence 开启的环境里工作。设备旧了一点、所在地区不支持、或者用户在设置里把它关了 —— 你的功能就干脆不存在。
iOS 27 把绕行方案变成了官方路线。LanguageModel 协议开放了,用 Core AI 导出的模型可以放进同一个 LanguageModelSession,提示词、工具、结构化输出都照旧。这篇把整条链走完:从在终端里列出模型,到在 App 里拿到第一个回复。
这是第三篇;Core AI 本身和 Foundation Models 的新东西各有单篇。要求:macOS 27、iOS 27、Xcode 27 或更新版本。
什么时候真的需要它#
苹果自己给出的、带一个不属于它的模型进来的三个理由:
- 你需要那个模型特有的专门能力;
- 你需要支持没有 Apple Intelligence 的设备;
- 你需要跨平台一致 —— 服务端和 App 用同一个模型。
还有第四个,没写出来但很明显:内置模型跟着系统走。它在 26.4 变过一次,到 27 又变了一次,每次苹果都写"请重新验证你的提示词"。装在你自己 bundle 里的模型,什么时候变由你说了算。
拿到模型:注册表与导出#
负责导出的是开源 Swift 包 coreai-models,导出配方和工具都在里面。起点在终端:装上包管理器 uv,克隆仓库,进入 coreai-models 目录。
然后看支持哪些模型:
uv run coreai.model.registry --list-models # 注册表支持的模型及其导出预设输出里要用的是 HF_ID 这一列 —— 导出时使用的标识符。第一个模型选 0.6B 参数左右的:下载快,在设备上跑得也从容。还在确认"这东西到底能不能跑"的阶段就去跟七十亿参数的量化较劲,是多出来的一个变量。
模型会针对运行它的硬件做特化,所以平台在导出时指定:
uv run coreai.llm.export HF_ID # 导出 macOS 版本
uv run coreai.llm.export HF_ID --platform iOS # 同一个模型的 iOS 版本导出结果是一个资源文件夹:.aimodel 加上分词器,以及这个模型需要的其他东西。整个文件夹加进 App。
另外翻一下 coreai-models 里的 models 目录:每个模型都有自己的 README,写着确切的配方和它自己的要求。这里没有一条"什么都能导"的万能命令,而这比一个到第二个模型就会碎掉的承诺更诚实。
接上这个包#
CoreAILanguageModel 就在同一个 coreai-models 里。添加方式和普通依赖一样:File > Add Package Dependencies,搜 coreai-models,添加。在产品列表里 CoreAILM 旁边会显示 None —— 在那里选上你的 App,否则包接上了而模块永远不出现。这是"为什么 import 不进来"这类问题烧掉二十分钟的经典路径。
代码本身#
整座桥就四行:
import FoundationModels
import CoreAILanguageModels
// 你导出并放进 bundle 的资源文件夹。
guard let modelURL = Bundle.main.url(forResource: "The model name",
withExtension: nil) else {
// 处理资源缺失的情况。
return
}
// 加载模型,并创建一个用它来处理请求的 session。
let model = try await CoreAILanguageModel(resourcesAt: modelURL)
let session = LanguageModelSession(model: model)withExtension: nil 不是笔误 —— 指向的是文件夹,不是文件。
CoreAILanguageModel 遵循 LanguageModel 协议,所以 session 用的初始化方法和内置模型完全一样。从这里往后,你功能层的代码看不出任何区别:
let response = try await session.respond(
to: "请总结这份会议记录的要点:\(meetingTranscript)。"
)流式、工具、@Generable、GenerationOptions —— 全都一个字不改地迁过来。这正是整件事的意义:你的功能层并不知道底下是哪个模型。
加载是异步的,而用户感觉得到#
CoreAILanguageModel 初始化方法上的 try await 背后是实打实的工作:第一次请求之前,框架要编译模型、加载分词器。在用户刚按下按钮那一刻去调用它,那就是用户在等。
可行的做法是提前加载,在距离请求还有一两秒的时候:界面打开了、用户开始输入了、某个流程启动了。同样在这个位置用 prewarm(promptPrefix:) 给 session 预热,让指令和工具定义在第一次 respond 之前就进到 KV 缓存里。
模型如果比较大,光靠异步不够 —— 你会需要 coreai-build 的提前编译,以及对特化缓存的显式管理。那是第一篇的地盘,而语言模型在那里还有一处专属讲究:SpecializationOptions 里的 expectFrequentReshapes。LLM 的序列长度每步涨一个 token,按形状逐个优化吃掉的比它省下的更多。
带推理的模型开箱即是对的#
会输出思维链的开源模型,手工集成时挺烦人:得把中间文本和最终答案分开,而这通常以对着标签写正则收场。
Core AI 自己能认出这类输出,并把它作为推理片段送进 transcript。它不会进入 response.content —— 用户看到的只有答案。而当你要排查某个回答为什么奇怪时,推理内容仍然读得到。
模型到底会不会推理,取决于你导出的是什么,可以显式检查:
if model.capabilities.contains(.reasoning) {
// 该模型支持推理。
}该测什么#
Core AI 会自己为设备挑选执行引擎 —— GPU、CPU 还是 Neural Engine,取决于模型是怎么导出的。这件事不需要你从功能代码里去操纵,但结果值得验证。
看的地方是 Instruments 里的 Foundation Models instrument:资产加载耗时、token 计数、每次请求的时长。我会先看的那个数字是轮次之间"缓存命中的输入 token ÷ 输入 token 总数"。如果偏低,说明前缀每次都在重算,而问题不在模型,在 session 是怎么搭的。
你付出的是什么#
对这笔交易不抱幻想。你换来的是摆脱对 Apple Intelligence 的依赖,以及对模型版本的主导权。作为交换,你接手了三件原先由系统承担的事:模型在交付物里的体积(或者它的下载与更新)、带首次启动延迟的端侧特化,以及质量 —— 一个 0.6B 的开源模型没有义务比内置的更好,这件事要拿自己的数据去验,而不是凭感觉。
所以我会守住的顺序是:先用内置模型,并对功能做一次诚实的评估。Core AI 留到撞上 Apple Intelligence 可用性这堵墙,或者需要系统模型不具备的某项能力时再上。好消息是,在两者之间挪动的成本是一行代码,而不是一次重写。



