跳到正文
返回博客
·约 2 分钟读完

在 iOS App 中使用 Hugging Face 模型

把 Hugging Face 模型放进 iOS App 的三条路:现成的 Core ML 包、用 coremltools 转换、或用 MLX Swift 跑语言模型。含 Xcode 步骤、体积、许可证与隐私,Swift 代码均已通过类型检查。

如果 Hugging Face 上已有该模型的 Core ML 版本,就下载 .mlpackage,拖进 Xcode 项目,然后调用 Xcode 为它写好的 Swift 类。如果模型只有 PyTorch 权重,就用 coremltools 转换(ct.convert,再 save("model.mlpackage"))。如果是语言模型,就改看 MLX Swift。无论哪条路,先读模型卡上的许可证,因为“能下载”不等于“可以免费上架”。为写这篇指南,我从 Hugging Face 下载了一个真实的 Core ML 模型,用 Apple 的 coremlcompiler 生成了它的 Swift 类,并在 Xcode 27.1(Swift 6.4)下针对 iOS 18 模拟器 SDK 编译了下面的 Swift 代码。coremltools 那一步来自 Apple 的文档,我没有运行过。

什么模型走哪条路

起点做法你要交付的东西
带 coreml 标签、含 .mlpackage 的仓库(例如 apple/ 组织)下载后加进 XcodeApp 里编译好的 Core ML 模型,或之后再下载
以 PyTorch 或 TensorFlow 权重发布的模型用 coremltools 转换,再加进 Xcode你自己生成的模型包;先确认许可证允许
聊天或文本生成的语言模型MLX Swift(mlx-swift-lm)配 mlx-community 组织的模型一个 Swift 包,加运行时下载的模型

路线一:现成的 Core ML 包

Hugging Face 的模型卡带有 coreml 这类元数据标签,Apple 也在自己的 apple/ 组织下发布了几个视觉模型的 Core ML 版本。我选了 apple/coreml-FastViT-T8,一个在 ImageNet-1k 上训练的图像分类器。它的模型卡写明有 400 万个参数,输入是 256 x 256 的图像,Float16 版本的包是 7.8 MB。模型卡自己的说明是用 Hugging Face 命令行工具下载:

huggingface-cli download \
  --local-dir models --local-dir-use-symlinks False \
  apple/coreml-FastViT-T8

我改用 huggingface_hub 这个 Python 库取回了同样的文件;结果是同一个文件夹 FastViTT8F16.mlpackage,外加 README.md 和 LICENSE。规划之前请先看仓库里到底有什么:有些 apple/ 仓库放的是较老的 .mlmodel 文件而不是模型包,例如 apple/coreml-resnet-50。

加进 Xcode

Apple 的示例描述了步骤:把模型拖进项目导航栏,在 Xcode 里打开它,就能看到模型类型和预期的输入输出。随后 Xcode 会为模型、输入和输出写好 Swift 接口,并把模型编译成适合设备运行的形式,这才是放进 App 包里的内容。

不打开 Xcode 也能看到它写了什么。对下载的包运行下面的命令,得到了一个 Swift 文件:

xcrun coremlcompiler generate FastViTT8F16.mlpackage gen --language Swift

这个文件为该模型定义了三个类:

  • FastViTT8F16Input,有一个 image 属性(256 乘 256 的 CVPixelBuffer),以及接受 CGImage 或文件 URL 的便捷初始化方法
  • FastViTT8F16Output,有 classLabel(String)和 classLabel_probs([String: Double])
  • FastViTT8F16,有 load() 和 prediction(input:),同步、回调和 async 三种形式都有

下面的代码使用的就是这个生成的类。我用下一节的命令把它和生成的文件一起编译,类型检查通过:

import CoreML
import CoreGraphics

func classify(_ image: CGImage) async throws -> (label: String, probability: Double) {
    let model = try await FastViTT8F16.load()
    let input = try FastViTT8F16Input(imageWith: image)
    let output = try await model.prediction(input: input)
    return (output.classLabel, output.classLabel_probs[output.classLabel] ?? 0)
}

我编译了它,但没有拿照片跑过预测,所以没有准确率结果可报。模型卡写明这个 Float16 版本相对原始 PyTorch 模型准确率下降 0.9%,并列出了它测得的推理时间(iPhone 12 Pro Max 上 0.79 毫秒,M3 Max 上 0.62 毫秒,主要在神经网络引擎上运行)。这些是模型卡的数字,不是我测的。

或者用 MLModel 加载

Apple 的文档说,多数情况下用生成的封装类就够了,不必接触 MLModel;但如果 App 要在用户设备上下载并编译模型,就必须直接使用 MLModel 类。这时对下载的文件调用 MLModel.compileModel(at:),再打开编译后的副本。下面这个版本针对 iOS 18 通过了类型检查:

import CoreML
import Foundation

func loadModel(packageURL: URL) async throws -> MLModel {
    let compiledURL = try await MLModel.compileModel(at: packageURL)
    let configuration = MLModelConfiguration()
    configuration.computeUnits = .all
    return try await MLModel.load(contentsOf: compiledURL, configuration: configuration)
}

检查方法,在终端里运行:

xcrun swiftc -typecheck -parse-as-library \
  -target arm64-apple-ios18.0-simulator \
  -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) load.swift

我还围绕同一个函数做了一个小的 macOS 命令行工具,对下载的包运行。它打印出了模型的描述:input image 4 256x256、output classLabel 3 和 output classLabel_probs 6(这些数字是 Core ML 特征类型枚举的原始值,不是尺寸)。computeUnits = .all 让系统选择处理单元,设备有神经网络引擎时也包括它;Apple 的文档说,App 可能在后台运行或要做其他占用 GPU 的工作时,应使用 .cpuOnly。Apple 关于下载模型的指南还说,Core ML 把编译出的模型放在临时位置,所以想复用的话,要把编译后的模型存到永久位置,比如 Application Support;同时也提醒要考虑大模型对 iCloud 备份大小的影响。

路线二:用 coremltools 转换

如果模型没有 Core ML 版本,Apple 的 coremltools 文档描述了两步:先用 torch.jit.trace(文档中稳定的路径)或 torch.export 捕获 PyTorch 模型图,再转换。下面是文档自己的流程,我没有运行过:

import torch
import torchvision
import coremltools as ct

# Load a pre-trained model and put it in evaluation mode.
torch_model = torchvision.models.mobilenet_v2(pretrained=True)
torch_model.eval()

# Trace it with an example input.
example_input = torch.rand(1, 3, 224, 224)
traced_model = torch.jit.trace(torch_model, example_input)

# Convert and save as a model package.
model = ct.convert(
    traced_model,
    inputs=[ct.TensorType(shape=example_input.shape)],
)
model.save("newmodel.mlpackage")

文档里的细节:从 Core ML Tools 7.0 起,convert() 默认生成 ML program,而 ML program 必须保存为 .mlpackage,不能是 .mlmodel。权重和中间值默认是 Float16,Float16 精度不够时可用 compute_precision=ct.precision.FLOAT32 要求 Float32。对于图像模型,文档建议用 ImageType 输入,而不是上面用的 TensorType(见它们的 Image Input and Output 页面)。文档还说,新的 torch.export 路径在 Core ML Tools 8.0 时大约覆盖 70% 的 PyTorch 算子,所以模型可能转换失败;请保留原模型的测试输入,并比较转换前后的输出。

路线三:用 MLX Swift 跑语言模型

视觉、音频和传统模型,Core ML 是合适的默认选择。对于聊天式的语言模型,ml-explore 组织发布了 mlx-swift-lm,这是用于构建大语言模型和视觉语言模型工具与应用的 Swift 包。它的 README 说明了如何加入 Package.swift、要链接哪些产品,并且可以从 Hugging Face Hub 加载模型。MLX 指南介绍了如何先在 Mac 上试同样的模型,mlx-swift-examples 仓库里有一个能在 iOS 和 macOS 上运行并下载语言模型的 App。我没有为本文构建过这样的 App,所以不给出 iPhone 上的内存或速度数字。

设备上的体积与内存

我在 Apple 或 Hugging Face 的文档里找不到 iPhone 的内存对照表,所以不会编一个。来源里确实说了这些:

  • 把模型打包进 App 是最简单的起步方式,但较先进的模型会占用可观的存储。Apple 建议通过 coremltools 的工具使用更低精度的权重(从 32 位降到 16 位,或更低的 1 到 8 位)。
  • 如果下载体积仍要更小,Apple 介绍了安装后再下载模型并在设备上编译的做法,例如用户只会用到 App 所支持模型中的一部分时。
  • coremltools 文档中另有关于调色板化、线性量化和剪枝的章节,用来压缩权重。
  • FastViT 的模型卡列出两个版本,T8 为 7.8 MB,MA36 为 84 MB。选能满足质量要求的最小的那个,并在你支持的最老设备上测试。

上架前检查许可证

Hugging Face 说明许可证写在模型卡的元数据里,并要求使用者尊重它。各个模型卡并不相同,这对要销售的 App 很重要:

  • apple/coreml-resnet-50 标注为 mit。
  • apple/coreml-FastViT-T8 标注为 other,带 license_name: apple-ascl,仓库里有 LICENSE 文件,模型卡要求遵守原始许可证。“other”意味着必须读那个文件;我没有替你解读。
  • 转换后的模型是衍生副本,所以原模型的条款随之而来,模型卡若列出训练数据,数据的条款也一样。

把许可证、模型的仓库地址和你下载时的提交记下来,放进发布记录里。

对 App 审核与隐私意味着什么

Apple 的 App Privacy Details 页面把“collect”定义为:把数据传输到设备之外,并让你或合作方能在超过实时处理请求所需的时间内访问它。页面还说,只在设备上处理的数据不算“collected”,不需要在 App Store Connect 的回答里披露。所以,一个在设备上处理照片、从不把照片发往任何地方的模型,可以不把这张照片算进你的数据收集回答。有三点要小心:

  • 这条规则只覆盖留在设备上的数据。如果你的 App 还会把照片、结果或使用数据发给服务器或第三方 SDK,就要为那部分作答。
  • 运行时下载模型是一次网络请求。请求本身会带出 IP 地址之类的信息,所以要看清楚你把模型放在哪里、托管方会保留什么。
  • 每个 App 仍然需要隐私政策链接,哪怕它什么都不收集。详见我的 iOS App 需要隐私政策吗?,并用平实的话说明模型如何处理用户的输入。

清理

本文用到的所有文件都放在一个临时文件夹里:模型(7.8 MB)、生成的 Swift 文件和一份编译副本。整个文件夹 16 MB,没有加进任何项目。

来源

均于 2026 年 11 月 2 日阅读。

作者

独立开发者,在 Obelisk Club 做本地优先的 Mac 与 iOS 效率应用。 关于作者 →