今天,我们宣布推出 swift-huggingface,这是一个全新的 Swift 包,为 Hugging Face Hub 提供了完整的客户端。你现在就可以将其作为独立包使用,它很快将集成到 swift-transformers 中,取代其当前的 HubApi 实现。
问题所在
今年早些时候发布 swift-transformers 1.0 时,我们收到了社区的强烈反馈:
- 下载速度慢且不可靠。大型模型文件(通常有数 GB)会在中途下载失败,且无法断点续传。开发者不得不手动下载模型并将其打包到应用中——这完全违背了动态模型加载的初衷。
- 与 Python 生态没有共享缓存。Python 的 transformers 库将模型存储在 ~/.cache/huggingface/hub 中。Swift 应用则下载到另一个不同结构的位置。如果你已经用 Python CLI 下载过某个模型,为 Swift 应用还得再下载一次。
- 身份认证令人困惑。token 应该从哪里获取?环境变量?文件?钥匙串?答案是“视情况而定”,而现有的实现并没有把选项说清楚。
介绍 swift-huggingface
swift-huggingface 是一次从零开始的重写,专注于可靠性和开发者体验。它提供了:
- 完整的 Hub API 覆盖——模型、数据集、空间、收藏集、讨论等
- 稳健的文件操作——进度追踪、断点续传支持以及完善的错误处理
- 与 Python 兼容的缓存——在 Swift 和 Python 客户端之间共享已下载的模型
- 灵活的身份认证——TokenProvider 模式让凭据来源清晰明确
- OAuth 支持——为需要对用户进行身份认证的面向用户应用提供一流支持
- Xet 存储后端支持(即将推出!)——基于块的去重技术,实现显著更快的下载速度
让我们来看一些示例。
使用 TokenProvider 实现灵活的身份认证
最大的改进之一在于身份认证的工作方式。TokenProvider 模式让凭据的来源变得清晰明确:
import HuggingFace
// For development: auto-detect from environment and standard locations
// Checks HF_TOKEN, HUGGING_FACE_HUB_TOKEN, ~/.cache/huggingface/token, etc.
let client = HubClient.default
// For CI/CD: explicit token
let client = HubClient(tokenProvider: .static("hf_xxx"))
// For production apps: read from Keychain
let client = HubClient(tokenProvider: .keychain(service: "com.myapp", account: "hf_token"))
自动检测遵循与 Python huggingface_hub 库相同的约定:
- HF_TOKEN 环境变量
- HUGGING_FACE_HUB_TOKEN 环境变量
- `HF_TOKEN_PATH` 环境变量(指向 token 文件的路径)
- `$HF_HOME/token` 文件
- `~/.cache/huggingface/token`(标准 HF CLI 位置)
- `~/.huggingface/token`(备用位置)
这意味着,如果你已经通过 `hf auth login` 登录过,swift-huggingface 会自动找到并使用那个 token。
面向用户应用的 OAuth
正在构建一个让用户通过 Hugging Face 账户登录的应用?swift-huggingface 包含了完整的 OAuth 2.0 实现:
import HuggingFace
// Create authentication manager
let authManager = try HuggingFaceAuthenticationManager(
clientID: "your_client_id",
redirectURL: URL(string: "yourapp://oauth/callback")!,
scope: [.openid, .profile, .email],
keychainService: "com.yourapp.huggingface",
keychainAccount: "user_token"
)
// Sign in user (presents system browser)
try await authManager.signIn()
// Use with Hub client
let client = HubClient(tokenProvider: .oauth(manager: authManager))
// Tokens are automatically refreshed when needed
let userInfo = try await client.whoami()
print("Signed in as: \(userInfo.name)")
OAuth 管理器负责在 Keychain 中存储 token、自动刷新以及安全登出。无需再手动管理 token。
可靠的下载
现在,下载大型模型变得简单直接,并带有完善的进度追踪和断点续传支持:
// Download with progress tracking
let progress = Progress(totalUnitCount: 0)
Task {
for await _ in progress.publisher(for: \.fractionCompleted).values {
print("Download: \(Int(progress.fractionCompleted * 100))%")
}
}
let fileURL = try await client.downloadFile(
at: "model.safetensors",
from: "microsoft/phi-2",
to: destinationURL,
progress: progress
)
如果下载中断,你可以从中断处继续:
// Resume from where you left off
let fileURL = try await client.resumeDownloadFile(
resumeData: savedResumeData,
to: destinationURL,
progress: progress
)
对于下载整个模型仓库,`downloadSnapshot` 可以处理一切:
let modelDir = try await client.downloadSnapshot(
of: "mlx-community/Llama-3.2-1B-Instruct-4bit",
to: cacheDirectory,
matching: ["*.safetensors", "*.json"], // Only download what you need
progressHandler: { progress in
print("Downloaded \(progress.completedUnitCount) of \(progress.totalUnitCount) files")
}
)
快照函数会追踪每个文件的元数据,因此后续调用只会下载发生变化的文件。
与 Python 共享缓存
还记得我们提到的第二个问题吗?"与 Python 生态没有共享缓存。" 现在这个问题已经解决了。
swift-huggingface 实现了一个与 Python 兼容的缓存结构,允许 Swift 和 Python 客户端之间无缝共享:
~/.cache/huggingface/hub/
├── models--deepseek-ai--DeepSeek-V3.2/
│ ├── blobs/
│ │ └── <etag> # actual file content
│ ├── refs/
│ │ └── main # contains commit hash
│ └── snapshots/
│ └── <commit_hash>/
│ └── config.json # symlink → ../../blobs/<etag>
这意味着:
- 一次下载,随处可用。如果你已经通过 hf CLI 或 Python 库下载过某个模型,swift-huggingface 会自动找到它。
- 内容寻址存储。文件根据其 ETag 存储在 `blobs/` 目录中。如果两个版本共享同一个文件,它只会被存储一次。
- 符号链接提高效率。快照目录包含指向 blobs 的符号链接,在保持清晰文件结构的同时,最大限度地减少磁盘占用。
缓存位置遵循与 Python 相同的环境变量约定:
- `HF_HUB_CACHE` 环境变量
- `HF_HOME` 环境变量 + `/hub`
- `~/.cache/huggingface/hub`(默认)
你也可以直接使用缓存:
let cache = HubCache.default
// Check if a file is already cached
if let cachedPath = cache.cachedFilePath(
repo: "deepseek-ai/DeepSeek-V3.2",
kind: .model,
revision: "main",
filename: "config.json"
) {
let data = try Data(contentsOf: cachedPath)
// Use cached file without any network request
}
为防止多个进程同时访问同一缓存时出现竞态条件,swift-huggingface 使用了文件锁(`flock(2)`)。
前后对比
以下是使用旧的 HubApi 下载模型快照时的代码:
// Before: HubApi in swift-transformers
let hub = HubApi()
let repo = Hub.Repo(id: "mlx-community/Llama-3.2-1B-Instruct-4bit")
// No progress tracking, no resume, errors swallowed
let modelDir = try await hub.snapshot(
from: repo,
matching: ["*.safetensors", "*.json"]
) { progress in
// Progress object exists but wasn't always accurate
print(progress.fractionCompleted)
}
以下是使用 swift-huggingface 执行相同操作的代码:
// After: swift-huggingface
let client = HubClient.default
let modelDir = try await client.downloadSnapshot(
of: "mlx-community/Llama-3.2-1B-Instruct-4bit",
to: cacheDirectory,
matching: ["*.safetensors", "*.json"],
progressHandler: { progress in
// Accurate progress per file
print("\(progress.completedUnitCount)/\(progress.totalUnitCount) files")
}
)
API 类似,但实现方式完全不同——它基于 URLSession 下载任务构建,具备完善的代理处理、断点续传数据支持和元数据追踪功能。
不止于下载
但等等,还有更多功能!swift-huggingface 包含一个完整的 Hub 客户端:
// List trending models
let models = try await client.listModels(
filter: "library:mlx",
sort: "trending",
limit: 10
)
// Get model details
let model = try await client.getModel("mlx-community/Llama-3.2-1B-Instruct-4bit")
print("Downloads: \(model.downloads ?? 0)")
print("Likes: \(model.likes ?? 0)")
// Work with collections
let collections = try await client.listCollections(owner: "huggingface", sort: "trending")
// Manage discussions
let discussions = try await client.listDiscussions(kind: .model, "username/my-model")
这还不是全部!swift-huggingface 拥有与 Hugging Face 推理提供商交互所需的一切功能,让你的应用能够即时访问数百个机器学习模型,并由世界一流的推理提供商提供支持:
import HuggingFace
// Create a client (uses auto-detected credentials from environment)
let client = InferenceClient.default
// Generate images from a text prompt
let response = try await client.textToImage(
model: "black-forest-labs/FLUX.1-schnell",
prompt: "A serene Japanese garden with cherry blossoms",
provider: .hfInference,
width: 1024,
height: 1024,
numImages: 1,
guidanceScale: 7.5,
numInferenceSteps: 50,
seed: 42
)
// Save the generated image
try response.image.write(to: URL(fileURLWithPath: "generated.png"))
请查看 README 以获取所有支持功能的完整列表。
下一步计划
我们正积极在两个方面推进工作:
与 swift-transformers 集成。我们有一个正在进行的拉取请求,旨在用 swift-huggingface 替换 HubApi。这将为所有使用 swift-transformers、mlx-swift-lm 以及更广泛生态系统的用户带来可靠的下载体验。如果你维护基于 Swift 的库或应用,并希望获得采用 swift-huggingface 的帮助,请联系我们——我们很乐意提供支持。
通过 Xet 实现更快的下载。我们正在增加对 Xet 存储后端的支持,该后端能够实现基于块的去重,并显著加快大型模型的下载速度。更多详情即将发布。
立即试用
将 swift-huggingface 添加到你的项目中:
dependencies: [
.package(url: "https://github.com/huggingface/swift-huggingface.git", from: "0.4.0")
]
我们期待你的反馈。如果你曾在 Swift 中为模型下载感到困扰,请尝试一下并告诉我们使用体验。你的体验报告将帮助我们确定下一步的改进优先级。
资源
- GitHub 上的 swift-huggingface
- swift-transformers
- mlx-swift-examples
- AnyLanguageModel
感谢 swift-transformers 社区提供的反馈,这些反馈塑造了这个项目;也感谢所有提交问题并分享经验的人。这是为你们而做的。❤️
Today, we're announcing swift-huggingface, a new Swift package that provides a complete client for the Hugging Face Hub. You can start using it today as a standalone package, and it will soon integrate into swift-transformers as a replacement for its current HubApi implementation.
The Problem
When we released swift-transformers 1.0 earlier this year, we heard loud and clear from the community:
- Downloads were slow and unreliable. Large model files (often several gigabytes) would fail partway through with no way to resume. Developers resorted to manually downloading models and bundling them with their apps — defeating the purpose of dynamic model loading.
- No shared cache with the Python ecosystem. The Python
transformerslibrary stores models in~/.cache/huggingface/hub. Swift apps downloaded to a different location with a different structure. If you'd already downloaded a model using the Python CLI, you'd download it again for your Swift app. - Authentication is confusing. Where should tokens come from? Environment variables? Files? Keychain? The answer is, "It depends", and the existing implementation didn't make the options clear.
Introducing swift-huggingface
swift-huggingface is a ground-up rewrite focused on reliability and developer experience. It provides:
- Complete Hub API coverage — models, datasets, spaces, collections, discussions, and more
- Robust file operations — progress tracking, resume support, and proper error handling
- Python-compatible cache — share downloaded models between Swift and Python clients
- Flexible authentication — a
TokenProviderpattern that makes credential sources explicit - OAuth support — first-class support for user-facing apps that need to authenticate users
- Xet storage backend support(Coming soon!) — chunk-based deduplication for significantly faster downloads
Let's look at some examples.
Flexible Authentication with TokenProvider
One of the biggest improvements is how authentication works. The TokenProvider pattern makes it explicit where credentials come from:
import HuggingFace
// For development: auto-detect from environment and standard locations
// Checks HF_TOKEN, HUGGING_FACE_HUB_TOKEN, ~/.cache/huggingface/token, etc.
let client = HubClient.default
// For CI/CD: explicit token
let client = HubClient(tokenProvider: .static("hf_xxx"))
// For production apps: read from Keychain
let client = HubClient(tokenProvider: .keychain(service: "com.myapp", account: "hf_token"))
The auto-detection follows the same conventions as the Python huggingface_hub library:
HF_TOKENenvironment variableHUGGING_FACE_HUB_TOKENenvironment variableHF_TOKEN_PATHenvironment variable (path to token file)$HF_HOME/tokenfile~/.cache/huggingface/token(standard HF CLI location)~/.huggingface/token(fallback location)
This means if you've already logged in with hf auth login, swift-huggingface will automatically find and use that token.
OAuth for User-Facing Apps
Building an app where users sign in with their Hugging Face account? swift-huggingface includes a complete OAuth 2.0 implementation:
import HuggingFace
// Create authentication manager
let authManager = try HuggingFaceAuthenticationManager(
clientID: "your_client_id",
redirectURL: URL(string: "yourapp://oauth/callback")!,
scope: [.openid, .profile, .email],
keychainService: "com.yourapp.huggingface",
keychainAccount: "user_token"
)
// Sign in user (presents system browser)
try await authManager.signIn()
// Use with Hub client
let client = HubClient(tokenProvider: .oauth(manager: authManager))
// Tokens are automatically refreshed when needed
let userInfo = try await client.whoami()
print("Signed in as: \(userInfo.name)")
The OAuth manager handles token storage in Keychain, automatic refresh, and secure sign-out. No more manual token management.
Reliable Downloads
Downloading large models is now straightforward with proper progress tracking and resume support:
// Download with progress tracking
let progress = Progress(totalUnitCount: 0)
Task {
for await _ in progress.publisher(for: \.fractionCompleted).values {
print("Download: \(Int(progress.fractionCompleted * 100))%")
}
}
let fileURL = try await client.downloadFile(
at: "model.safetensors",
from: "microsoft/phi-2",
to: destinationURL,
progress: progress
)
If a download is interrupted, you can resume it:
// Resume from where you left off
let fileURL = try await client.resumeDownloadFile(
resumeData: savedResumeData,
to: destinationURL,
progress: progress
)
For downloading entire model repositories, downloadSnapshot handles everything:
let modelDir = try await client.downloadSnapshot(
of: "mlx-community/Llama-3.2-1B-Instruct-4bit",
to: cacheDirectory,
matching: ["*.safetensors", "*.json"], // Only download what you need
progressHandler: { progress in
print("Downloaded \(progress.completedUnitCount) of \(progress.totalUnitCount) files")
}
)
The snapshot function tracks metadata for each file, so subsequent calls only download files that have changed.
Shared Cache with Python
Remember the second problem we mentioned? "No shared cache with the Python ecosystem." That's now solved.
swift-huggingface implements a Python-compatible cache structure that allows seamless sharing between Swift and Python clients:
~/.cache/huggingface/hub/
├── models--deepseek-ai--DeepSeek-V3.2/
│ ├── blobs/
│ │ └── <etag> # actual file content
│ ├── refs/
│ │ └── main # contains commit hash
│ └── snapshots/
│ └── <commit_hash>/
│ └── config.json # symlink → ../../blobs/<etag>
This means:
- Download once, use everywhere. If you've already downloaded a model with the
hfCLI or the Python library, swift-huggingface will find it automatically. - Content-addressed storage. Files are stored by their ETag in the
blobs/directory. If two revisions share the same file, it's only stored once. - Symlinks for efficiency. Snapshot directories contain symlinks to blobs, minimizing disk usage while maintaining a clean file structure.
The cache location follows the same environment variable conventions as Python:
HF_HUB_CACHEenvironment variableHF_HOMEenvironment variable +/hub~/.cache/huggingface/hub(default)
You can also use the cache directly:
let cache = HubCache.default
// Check if a file is already cached
if let cachedPath = cache.cachedFilePath(
repo: "deepseek-ai/DeepSeek-V3.2",
kind: .model,
revision: "main",
filename: "config.json"
) {
let data = try Data(contentsOf: cachedPath)
// Use cached file without any network request
}
To prevent race conditions when multiple processes access the same cache, swift-huggingface uses file locking (flock(2)).
Before and After
Here's what downloading a model snapshot looked like with the old HubApi:
// Before: HubApi in swift-transformers
let hub = HubApi()
let repo = Hub.Repo(id: "mlx-community/Llama-3.2-1B-Instruct-4bit")
// No progress tracking, no resume, errors swallowed
let modelDir = try await hub.snapshot(
from: repo,
matching: ["*.safetensors", "*.json"]
) { progress in
// Progress object exists but wasn't always accurate
print(progress.fractionCompleted)
}
And here's the same operation with swift-huggingface:
// After: swift-huggingface
let client = HubClient.default
let modelDir = try await client.downloadSnapshot(
of: "mlx-community/Llama-3.2-1B-Instruct-4bit",
to: cacheDirectory,
matching: ["*.safetensors", "*.json"],
progressHandler: { progress in
// Accurate progress per file
print("\(progress.completedUnitCount)/\(progress.totalUnitCount) files")
}
)
The API is similar, but the implementation is completely different — built on URLSession download tasks with proper delegate handling, resume data support, and metadata tracking.
Beyond Downloads
But wait, there's more! swift-huggingface contains a complete Hub client:
// List trending models
let models = try await client.listModels(
filter: "library:mlx",
sort: "trending",
limit: 10
)
// Get model details
let model = try await client.getModel("mlx-community/Llama-3.2-1B-Instruct-4bit")
print("Downloads: \(model.downloads ?? 0)")
print("Likes: \(model.likes ?? 0)")
// Work with collections
let collections = try await client.listCollections(owner: "huggingface", sort: "trending")
// Manage discussions
let discussions = try await client.listDiscussions(kind: .model, "username/my-model")
And that's not all! swift-huggingface has everything you need to interact with Hugging Face Inference Providers, giving your app instant access to hundreds of machine learning models, powered by world-class inference providers:
import HuggingFace
// Create a client (uses auto-detected credentials from environment)
let client = InferenceClient.default
// Generate images from a text prompt
let response = try await client.textToImage(
model: "black-forest-labs/FLUX.1-schnell",
prompt: "A serene Japanese garden with cherry blossoms",
provider: .hfInference,
width: 1024,
height: 1024,
numImages: 1,
guidanceScale: 7.5,
numInferenceSteps: 50,
seed: 42
)
// Save the generated image
try response.image.write(to: URL(fileURLWithPath: "generated.png"))
Check the README for a full list of everything that's supported.
What's Next
We're actively working on two fronts:
Integration with swift-transformers. We have a pull request in progress to replace HubApi with swift-huggingface. This will bring reliable downloads to everyone using swift-transformers, mlx-swift-lm, and the broader ecosystem. If you maintain a Swift-based library or app and want help adopting swift-huggingface, reach out — we're happy to help.
Faster downloads with Xet. We're adding support for the Xet storage backend, which enables chunk-based deduplication and significantly faster downloads for large models. More on this soon.
Try It Out
Add swift-huggingface to your project:
dependencies: [
.package(url: "https://github.com/huggingface/swift-huggingface.git", from: "0.4.0")
]
We'd love your feedback. If you've been frustrated with model downloads in Swift, give this a try and let us know how it goes. Your experience reports will help us prioritize what to improve next.
Resources
Thanks to the swift-transformers community for the feedback that shaped this project, and to everyone who filed issues and shared their experiences. This is for you. ❤️