别再把大模型供在云端了,WWDC26 CoreAI 大模型下凡实战
引子
以前在苹果生态里搞 AI,有点像去五星级酒店点外卖:
你想吃火锅,前台微笑着说:「先生,我们这里只提供酒店特供套餐。」
Foundation Models 很香,隐私也漂亮,但菜单是苹果定的。第三方大模型?抱歉,请去云端排队,或者自己用 Metal 手搓推理引擎——后者相当于自带锅、自带火、自带厨师,还得保证不被 jetsam 一脚踹出房间。
WWDC26 之后,情况变了。
CoreAI 登场。苹果不再只给你「自家菜」,而是递过来一套厨房:你可以把 PyTorch 模型端上来,切好、炖好、装进.aimodel,然后在 iPhone / Mac 上本地端着碗吃。
今天这篇,就从「这到底是什么」讲到「第三方 Qwen 怎么真在本机流式吐字」——有背景、有逻辑、有能跑通的代码味道。读完你未必马上能上架,但至少不会再把 CoreAI 当成「又一个神秘 Framework」。
无需等待,让我们马上开始 CoreAI 大冒险吧!😉
一、WWDC26 的背景:苹果为什么要推 CoreAI?
先把时间线捋直,不然后面代码会像无根之木。
过去几年,苹果的端侧 AI 路线大致是:
- Core ML:偏传统机器学习 / 视觉音频,擅长「看图说话」,不太擅长「边想边聊」。
- Apple Intelligence + Foundation Models:系统级大模型能力,隐私优先,但模型选择权在苹果手里。
- 开发者的真实痛点:我想跑 Qwen、Llama、自家微调模型——既要 GPU/ANE 性能,又要 Swift 友好 API,还想少写一点 KV Cache 手活。
WWDC26 给出的答案叫CoreAI:
- 面向Apple silicon的新一代本地推理框架;
- 配套工具链:coreai-torch(导出)→ coreai-build(编译)→ CoreAI runtime(运行);
- 官方参考仓apple/coreai-models:既有导出配方,也有 Swift 侧 LLM 引擎、采样、KV Cache 等运行时工具;
- 系统门槛很硬核:macOS / iOS 27.0+,Xcode 27.0+——不是「旧系统装个 SDK 就能用」那种温柔。
一句话概括:
CoreAI 不是「又一个聊天 SDK」,而是「把任意合适结构的模型,变成苹果芯片上的一等公民」的整条产线。
Foundation Models 像「苹果官方外卖」;CoreAI 像「你自带食材,厨房和灶台归苹果管」。
二、先建立心智模型:CoreAI 在干什么?
别一上来就背 API。先记住这条流水线:
AUTHOR(可选) 按平台重写模型结构(静态 shape、ANE/GPU 友好) ↓ COMPRESS(可选) 量化 / 调色盘压缩,换体积与精度 ↓ EXPORT PyTorch → AIProgram(coreai-torch / TorchConverter) ↓ COMPILE(可选) AOT 编译到目标平台(coreai-build) ↓ RUN 设备上加载 .aimodel,Swift / Python 推理对应用开发者来说,日常最常碰到的是最后一步RUN。
而对「第三方大模型适配」来说,你会拿到的是别人已经导好的LanguageBundle——目录里通常长这样:
some_model_bundle/ xxx.aimodel/ ← 真正的模型资产(含 main.mlirb 等) metadata.json tokenizer/.aimodel不是「把权重 zip 一下改个后缀」。它是经过导出、优化、面向 Apple 运行时的模型资产;LLM 场景下,外面还会包一层LanguageBundle,把 tokenizer、上下文长度、function map 这些聊天相关的元数据绑在一起。
浅层理解:
CoreAI = 本地跑模型的「操作系统级厨房」。
深层理解:
你写的不是「HTTP 请求 OpenAI」,而是「加载 bundle → 创建推理引擎 → token 级 generate 流」。
三、最简 Runtime:先看「一张图」怎么跑
在聊 LLM 之前,先看官方风格的最小推理——这能帮你分清两层 API:
- 底层:
AIModel+InferenceFunction+NDArray(通用张量推理) - 上层:
LanguageBundle+EngineFactory+InferenceEngine(给 LLM 准备好的引擎)
底层大概长这样:
importCoreAIletmodel=tryawaitAIModel(contentsOf:modelURL)guardletfn=trymodel.loadFunction(named:"main")else{return}varinput=NDArray(shape:[1,3,224,224],scalarType:.float32)varview=input.mutableView(as:Float32.self)// 填入图像数据...varoutputs=tryawaitfn.run(inputs:["image":input])letresult=outputs.remove("logits")?.ndArray导出侧对应的是 Python:
fromcoreai_torchimportTorchConverter,get_decomp_tableimporttorch ep=torch.export.export(model,args=(torch.randn(1,3,224,224),))ep=ep.run_decompositions(get_decomp_table())program=(TorchConverter().add_exported_program(ep,input_names=["image"],output_names=["logits"]).to_coreai())program.optimize()program.save_asset("model.aimodel")看到没?苹果这次把「训练框架 → 设备资产 → App 调用」串成了闭环。
LLM 只是在这条环上,多叠了一层语言引擎。
四、进入正题:第三方 LLM 怎么接进 CoreAI?
下面以社区已转换好的Qwen3.5-2B CoreAI LanguageBundle为例(Hugging Face 上已有现成资产)。思路适用于同类 pipelined LLM。
1)加载:不是open(file),是「建一座小发电厂」
importCoreAISharedimportCoreAILanguageModelsimportTokenizers// 某些 pipelined decode 图是 static [1,1],需要在创建引擎前设置ifgetenv("COREAI_CHUNK_THRESHOLD")==nil{setenv("COREAI_CHUNK_THRESHOLD","1",1)}letbundle=tryLanguageBundle(at:bundleDirectory)letconfig=ModelConfig(name:bundle.name,tokenizer:bundle.tokenizer,vocabSize:bundle.vocabSize,maxContextLength:bundle.maxContextLength,serializedModel:[bundle.modelAssetPath],function:bundle.language.functionMap?.name(for:"main")??"main")letengine=tryawaitEngineFactory.createEngine(config:tryJSONEncoder().encode(config),modelURL:trybundle.requireModelURL(for:ModelBundle.ComponentKey.main),options:EngineOptions(variant:"coreai-pipelined"))lettokenizer=tryawaitbundle.loadTokenizer()这里有几个「看起来像咒语、其实很关键」的点:
| 点 | 为什么重要 |
|---|---|
LanguageBundle | 一次读齐模型路径、词表、上下文长度、tokenizer |
EngineFactory.createEngine | 真正把.aimodel特化成可推理引擎 |
variant: "coreai-pipelined" | 告诉运行时:这是流水线式 decode 图,不是普通前馈 |
COREAI_CHUNK_THRESHOLD=1 | 适配 static[1,1]decode;设错了可能加载成功、生成翻车 |
第一次加载往往会触发specialization(特化):系统按当前设备把计算图「烤」成更合适的执行形态。冷启动可能几十秒,还可能吃掉数 GB 磁盘缓存——所以产品上千万别让用户盯着转圈圈傻等,得有「首次准备模型」的心智。
2)Prompt:别直接encode(用户原话)
很多模型吃的是chat template,不是裸字符串。正确姿势是先组 messages,再套模板:
letmessages:[[String:String]]=[["role":"system","content":"你是一个直接回答问题的助手。优先使用中文,回答简洁清楚。"],["role":"user","content":prompt]]letinputIds=trytokenizer.applyChatTemplate(messages:messages).map{Int32($0)}guardinputIds.count<maxContextLength-1else{throwMyError.promptTooLong}对 Qwen 这类带/think、/no_think的模型,还可以在应用层做三种模式:
- 高效:系统提示 +
/no_think,尽量直接给答案 - 推理:追加
/think,允许展开思考 - 跟随输入:尊重用户自己写的控制指令
这不是 CoreAI 的硬性要求,却是「第三方模型适配」里最容易被忽略的产品细节——引擎只会吐 token,不会替你懂产品语义。
3)生成:AsyncSequence 式地「一个字一个字挤牙膏」
tryawaitengine.reset()letstream=tryengine.generate(with:inputIds,samplingConfiguration:SamplingConfiguration(temperature:0.7,topK:20,topP:0.8,minP:nil).normalized(),inferenceOptions:InferenceOptions(maxTokens:maxTokens))varanswer=""fortryawaitstepinstream{tryTask.checkCancellation()lettokenId=Int(step.tokenId)// 解码、过滤 stop sequence、<think> 块等letchunk=decodeAndFilter(tokenId)if!chunk.isEmpty{answer+=chunk// 推到 SwiftUI}}这就是本地 LLM 的「灵魂回路」:
prompt → chat template → token ids → engine.generate 流 → 逐 token 解码 → 停词 / 过滤 / UI 追加采样参数(Temperature / Top-K / Top-P / Min-P)和云 API 概念一致;差别在于:一切发生在进程内,隐私不错,内存账单很真实。
五、再深一层:适配第三方模型时,你会撞上的「人情世故」
代码能跑,不等于产品能用。真实适配里,常见坑如下——每条都来自「本机真跑过」的经验,不是 PPT。
1)模型别塞进.app
2B 级量化包常常~3GB。打进 App 包,审核会皱眉,用户下载会骂娘。正确姿势是:
- 首次启动后下载 / 本地导入
- 先落到 staging,校验齐全再原子替换到正式目录
- 至少确认
.aimodel、main.mlirb、metadata.json、tokenizer/齐全,且大文件不是 Git LFS 指针冒充
2)Think 块会「偷吃」你的可见输出
模型可能先吐<think>...。如果你在「高效模式」还直接把原文糊到 UI,用户会以为 App 坏了。
应用层需要:
- 过滤完整
<think>...</think> - 未闭合时先别展示后面内容
- 必要时用更强硬的 system prompt 重试一次
CoreAI 负责「算」,你负责「做人话」。
3)Stop sequence 与「假结束」
命中 EOS 不一定等于「答完了」。高效模式下偶发短句提前结束、没有句末标点——这时可以识别为 incomplete EOS,再补一句「只从截断处继续」。
长回答撞到 token 上限,则做续写:把已有答案塞回 prompt,要求「不要重复,从中断处自然接着写」。
4)内存与 entitlement 是物理定律
- Mac 上建议给系统和别的进程留足余量(官方指导常见说法是留出数 GB headroom)
- iPhone 上大模型可能需要更高内存限额相关 entitlement,否则冷 specialization 直接
bad_alloc - 用进程 footprint 观察「加载前后涨了多少」,比凭感觉喊「好卡」有用得多
5)平台策略不同
苹果自己的指导也很直白:
- iOS:偏能效,静态 shape、更激进压缩,模型尽量别太大
- macOS:可更吃规模,动态 shape / 控制流更宽松
同一套「第三方模型适配」,在 iPhone 和 Mac 上可能要选不同压缩配方——这不是口味问题,是物理与系统策略问题。
六、把整条链路压成一张图
┌──────────────────────────────┐ │ 已转换的 LanguageBundle │ │ (.aimodel + tokenizer + md) │ └───────────────┬──────────────┘ │ LanguageBundle(at:) ▼ ┌──────────────────────────────┐ │ ModelConfig + EngineFactory │ │ variant: coreai-pipelined │ └───────────────┬──────────────┘ │ loadTokenizer() ▼ ┌──────────────────────────────┐ │ chat template → input ids │ └───────────────┬──────────────┘ │ engine.generate(...) ▼ ┌──────────────────────────────┐ │ Async token stream │ │ → 过滤 / 停词 / UI 流式刷新 │ └──────────────────────────────┘如果你只会画 HTTP 时序图,现在可以在旁边再贴一张这个——云端是请求-响应;CoreAI 是加载-特化-流式解码。
七、一句中式总结
CoreAI 的出现,意味着苹果终于承认一件事:
「端侧智能」不能只靠皇家厨师,也得允许民间大厨进厨房——但刀具、灶台、排烟系统,还得按苹果标准来。
对开发者而言:
- 浅:本地加载
.aimodel,Swift 里流式生成 - 中:LanguageBundle + EngineFactory + chat template + 采样
- 深:导出、量化、静态 shape、specialization、内存与平台策略
Foundation Models 解决「大多数 App 的官方智能」;
CoreAI 解决「我就要跑自己的 / 社区的模型,而且要跑在苹果芯上」。
两条腿,才算站稳。
尾声
今天我们顺着「第三方大模型怎么在 CoreAI 里被叫起来」走完了调用侧:bundle、引擎、模板、流式、坑点。
你手里已经有一张能照着做的地图——从「听说 WWDC26 出了 CoreAI」,走到了「真的能在本机看 Qwen 一个 token 一个 token 往外蹦」。
故事到这里就告一段落了,感谢宝子们的观赏。
我是大熊猫侯佩,再会啦!😎
