用 Core ML 在设备端部署机器学习和 AI 模型
Deploy machine learning and AI models on-device with Core ML
2024年6月10日
一句话判断
Core ML 今年引入的 MLTensor 类型解决了一个长期痛点:模型推理之外的计算胶水代码终于不用手写底层 API 了——这让大语言模型的端侧部署代码量直接砍半。
这场 Session 讲了什么
Core ML 作为 Apple 设备端 ML 推理的统一框架,今年的更新集中在”让模型集成更简单、运行更高效”上。
最重磅的新增是 MLTensor——一个多维数组类型,提供常见的数学运算和变换操作,类似 Python 的 NumPy 或 PyTorch 的 Tensor。它解决的问题是:在端侧运行复杂的 ML Pipeline(特别是大语言模型)时,模型推理本身只占一部分代码,剩下的”胶水代码”(token 解码、概率采样、mask 处理等)之前要么手写、要么调低级 API,非常繁琐。MLTensor 让这些代码变得简洁且高效。
其次是 State API 提升了多轮推理的效率,Multi-Function Models 让一个 ML Package 包含多个相关函数减少部署体积,以及 Core ML 性能工具的更新帮助更好地做 profiling。
一个不需要你做任何事就能获得的福利:iOS 18 上很多模型的推理速度比 iOS 17 快了,这是底层推理栈优化的结果。
值得深挖的点
MLTensor:端侧 ML 的”胶水层”终于有解了
大语言模型在端侧运行时,推理和”解码”是两个独立步骤。推理模型输出整个词表的概率分布,解码器从中选出下一个 token。解码策略有很多种——greedy(选最高概率的)、top-k(从前 k 个中随机采样)、temperature 调整(调整概率分布的锐度)。
这些解码逻辑需要张量运算:比较、mask、乘法、reshape。之前要么自己用 Accelerate 框架写 vDSP 调用,要么用 MLShapedArray 做低效的逐元素操作。Session 用 HuggingFace 的 Swift Transformer 包做对比,展示同一段 top-k 采样逻辑:用 MLTensor 的版本代码量大概是之前的一半,而且更易读。
MLTensor 的所有操作都是异步分发的,利用 Apple Silicon 的 CPU、GPU 和 Neural Engine。在访问底层数据之前需要显式 materialize() 为 MLShapedArray,确保所有上游操作完成。这个设计保证了性能不会因为同步等待而打折扣。
State API 和 Multi-Function Models
State API 解决的是 KV Cache 的管理问题。大语言模型的 autoregressive 推理中,每一步都要用到之前所有步骤的 KV Cache。之前你需要自己管理这个状态,容易出错。State API 把它封装成框架级行为——模型维护自己的内部状态,你只需要在每一步传入新的 token,不需要手动传递和更新 cache。
Multi-Function Models 解决的是部署体积问题。如果你的 App 有多个相关的 ML 模型(比如文本编码器和图像编码器),之前需要分别打包成独立的 ML Package。现在可以把它们合并成一个 Package,共享权重和资源,减少总体积。
代码片段
MLTensor 基础操作
// 创建张量
let a = MLTensor(shape: [2, 3], scalars: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
let b = MLTensor(shape: [2, 3], scalars: [0.5, 1.0, 1.5, 2.0, 2.5, 3.0])
// 元素级运算,自动广播
let result = a * b + 1.0
// 比较 + mask
let mean = result.mean()
let mask = result .> mean // 创建布尔 mask
let filtered = result * mask // 低于均值的置零
// 显式物化才能访问数据
let finalValues: MLShapedArray<Float> = await filtered.materialized()
场景:模型推理后的后处理计算。坑:materialized() 是 async 的,因为上游操作可能还在 GPU 上执行。
Top-k 采样解码器(简化版)
func topKSample(logits: MLTensor, k: Int, temperature: Float) -> Int {
// 调整温度
let scaled = logits / temperature
// 取 top-k(伪代码,实际 API 更简洁)
let probs = softmax(scaled)
let sampled = categoricalSample(probs, k: k)
// 物化后返回 token ID
let token = await sampled.materialized()
return Int(token.scalars.first!)
}
场景:大语言模型的 token 采样,替代 greedy 解码让输出更有创造力。坑:temperature 设置太高会导致输出胡言乱语,建议从 0.8-1.5 开始调试。
Multi-Function Model 加载
// 一个 ML Package 包含多个函数
let model = try MLPackage(contentsOf: modelURL)
// 按名字调用不同函数
let textEmbedding = try model.prediction(from: "text_encoder", input: textInput)
let imageEmbedding = try model.prediction(from: "image_encoder", input: imageInput)
场景:多模态 App 的文本和图像编码器共享一个 ML Package。坑:合并的模型需要在转换阶段就规划好,不能事后把两个独立模型拼在一起。
最佳实践
已有项目迁移:如果你有端侧 LLM 的推理代码,MLTensor 可以立刻简化你的解码器实现。不需要改动模型本身,只需要把推理后的张量操作从 MLShapedArray 迁移到 MLTensor。State API 的迁移需要重新导出模型(使用新版 coremltools),但收益是 KV Cache 管理代码大幅简化。
新项目起步:如果你的 App 涉及任何端侧 ML 推理(不只是 LLM,图像分类、目标检测都算),iOS 18 的免费性能提升直接受益。新项目建议从第一天就用 MLTensor 做推理后的后处理,避免后续迁移成本。如果需要部署多个相关模型,提前规划 Multi-Function Package 的结构。
还有什么值得关注
- iOS 18 上很多模型的推理速度自动变快,不需要重编译模型或改代码,这是底层 MPS Graph 和 BNNS Graph 优化的结果。
- MPS Graph 和 BNNS Graph 也可以直接使用 Core ML 模型,适合需要跟 Metal 深度集成或需要 CPU 实时推理的场景。
- Core ML 的性能工具新增了更细粒度的 profiling 能力,可以定位具体哪个操作是瓶颈。