Deploy machine learning and AI models on-device with Core ML
Machine Learning & AI 进阶 20m

用 Core ML 在设备端部署机器学习和 AI 模型

Deploy machine learning and AI models on-device with Core ML

2024年6月10日

在 Apple 官方观看视频

一句话判断

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 能力,可以定位具体哪个操作是瓶颈。
WWDC 2024