对大多数团队而言,模型和数据集都存放在某个云服务商某个区域的一个存储桶里。而你能获取到的 GPU——无论是用于开发、训练还是推理——却越来越多地落在与数据不同的云上。一旦这两者分离,你就得支付跨云传输费用,仅仅是为了把自己的数据读到自己的 GPU 上。我们与 Hugging Face 合作,将这两部分重新连接起来:你的模型和数据集保留在 Hub 上,而 SkyPilot 则在任何拥有 GPU 的集群上运行计算任务(开发、训练或推理)。只需一个 `hf://` URL 和你已有的 `HF_TOKEN`,就能将 Hugging Face Bucket 或任意 Hub 仓库挂载到 SkyPilot 任务中,然后在任何有算力的地方启动它。Hugging Face 不收取出口流量费,因此在任何云上,将数据读取到那些 GPU 上都不产生任何费用。
以下是新增功能:
- 在任意任务中访问你的 Hub 数据:`store:hf` 通过一个 `hf://` URL 和你现有的 `HF_TOKEN`,以 MOUNT 或 COPY 方式,将 Hugging Face Bucket(可读写)或任意模型/数据集/Space 仓库(只读)挂载到 SkyPilot 任务中。
- 在任何云上、任何 GPU 上运行。SkyPilot 可在 20 多个云服务商、Kubernetes、Slurm 以及本地环境中找到计算资源,因此同一任务可以使用你在任何供应商处预留或按需获取的任意可用 GPU。
- 读取数据无出口费用。Hugging Face Storage 不收取出口流量费或 CDN 费用,因此无论 SkyPilot 将任务调度到何处,它都直接从同一个存储桶读取你的模型和数据集,无需为每个云复制副本,也无需为拉取数据支付出口流量账单。
- 基于 Xet 的去重。存储桶构建在 Xet 之上,因此增量检查点和模型变体仅存储和传输发生变更的数据块。
- 共同构建。Hugging Face 和 SkyPilot 联合发布了此功能,Hugging Face 团队已将 `hf-mount` 的 FUSE 修复代码上游合并,使其能在非特权容器中正常工作。
Hugging Face Storage 现已成为 SkyPilot 的一等后端
SkyPilot 任务已能通过将云对象存储(S3、GCS、Azure、R2 等)挂载到本地路径的方式进行读写。Hugging Face Storage 现已加入该列表,作为 `store:hf`,通过 `hf://` 协议访问。
file_mounts:
# A Hugging Face Bucket, read-write, for checkpoints, logs, processed data.
/checkpoints:
source: hf://buckets/my-org/qwen-sft
store: hf
mode: MOUNT # or COPY
# A model repo, mounted read-only.
/base-model:
source: hf://Qwen/Qwen3.5-4B
store: hf
mode: MOUNT
# A dataset repo, pinned to a revision, read-only.
/data:
source: hf://datasets/my-org/my-dataset@main
store: hf
mode: MOUNT
这个 `hf://` 方案覆盖了整个生命周期:从仓库中读取模型和数据集,在训练时将检查点写入存储桶,将训练完成的模型发布回仓库,以及在提供服务时将其拉取到推理服务器上。大多数团队已经将他们的模型和数据集存放在 Hub 上,因此无需迁移步骤,也无需创建新的存储账户。
MOUNT 使用了 Hugging Face 的 hf-mount FUSE 后端,因此存储桶或仓库会作为一个本地路径出现,与 SkyPilot 的其他 FUSE 挂载(gcsfuse、blobfuse2、rclone、goofys)并列。数据获取发生在文件系统层面:当你的代码发起 `read()` 调用时,驱动程序仅从 Xet 后端拉取那些字节,因此只有你实际访问的数据才会通过网络传输,并且 hf-mount 会维护一个磁盘缓存,使得重复读取在本地完成。这种磁盘缓存行为正是 SkyPilot 在 MOUNT_CACHED 模式下为其其他后端提供的特性,而在普通的 MOUNT 模式下,每次读取都会从存储桶流式传输,本地不保留任何数据。对于 hf 存储,MOUNT 和 MOUNT_CACHED 的行为相同,因此两种模式都会保留缓存。
由于读取是惰性的,一个进程可以在整个大文件下载完成之前就开始处理它,而无需等待完整副本先下载完毕。这使得 GPU 几乎可以立即投入工作,在数据流入时进行训练,而不是在数据集或检查点下载期间闲置(并产生费用)。这在第一个 epoch 时收益最大,因为此时还没有任何缓存。COPY 则走另一条路线,通过 huggingface_hub 预先下载,没有特殊要求。
身份验证使用的是你已有的 token。在你的环境中设置 `HF_TOKEN`,并通过 `--secret HF_TOKEN` 将其传递给运行任务;SkyPilot 会使用该 token 在任务所在的任何云平台上进行挂载。无论任务落在 AWS、GCP、Azure、Nebius、Lambda 还是你自己的 Kubernetes 集群上,同一个 token 都有效,因此无需为不同云平台管理各自的存储桶密钥。
没有出站流量费用:存储不再决定你在哪里运行。
GPU 算力几乎不再来自单一来源。为了获得足够的 H100 和 H200,团队会同时在多个供应商处预留和承诺算力(在超大规模云上预留一块资源,在新型云上部署一个集群,或许还有本地机架),然后在其拥有资源分配的任何地方运行。SkyPilot 正是为此而生:一份任务规格,可调度至 20 多个云平台、Kubernetes 以及本地环境,最终落在任意空闲的预留集群上。
对象存储一直是个难题。对象存储是区域性的,且每个云平台各自独立,因此,为位于不同供应商数据中心的 GPU 或推理服务器提供数据,要么在每个供应商的存储桶中都保留一份数据副本,要么付费将数据拉取过去。大多数云平台会在数据离开其网络时收取出口费用(例如,从 AWS 传出数据约为 0.09 美元/GB),并且通常在同一云平台的不同区域之间也会收费。将基础模型拉取到每个推理节点,或从另一个云平台的集群上对数据集进行多个 epoch 的迭代,都会在已预留的 GPU 费用之上增加一笔巨额开销。团队最终只能将每次运行任务固定在持有数据的供应商那里,而让其余算力闲置。
Hugging Face Storage 消除了在关键环节——即读取端——的成本。由于没有出口或 CDN 费用,且存储价格为每月每 TB 12-18 美元(相比之下,AWS S3 约为每月每 TB 23 美元,另加出口费用),同一个存储桶可以从所有这些集群访问,无论 GPU 在哪里运行,读取都是免费的。写回数据仍需支付计算云平台通常的出口费用,与写入任何其他云外存储一样,但对于大多数 AI 工作负载而言,读取操作占主导地位:例如跨多个 epoch 流式传输的数据集,或拉取到每个新的训练或推理节点上的模型权重。因此,你不再需要将每次运行任务固定在持有数据副本的供应商那里。
快速基准测试
为了收集一些基准测试数据,我们运行了一个小型微调任务:使用 TRL 的 SFTTrainer,在 HuggingFaceH4/Multilingual-Thinking 数据集上微调 Qwen/Qwen3.5-4B 模型,从 Hub 仓库以只读方式挂载模型,并将每个检查点写入 Hugging Face Bucket。同一份 SkyPilot YAML 文件在 AWS、GCP 和 Lambda 上运行,仅更改了 --infra 参数。SkyPilot 将每个任务放置在 GPU 空闲的地方,所有三个平台都读取和写入同一个存储桶。
# qwen-sft.yaml. Launch anywhere: sky launch qwen-sft.yaml --infra aws|gcp|...
resources:
accelerators: H100:1 # or whatever the cloud has
file_mounts:
/base-model:
source: hf://Qwen/Qwen3.5-4B # read-only, lazy-mounted from the Hub
store: hf
mode: MOUNT
/checkpoints:
source: hf://buckets/my-org/qwen-sft # read-write Bucket
store: hf
mode: MOUNT
run: |
python train.py --model /base-model --output_dir /checkpoints
我们测量到的结果是:
- 该模型免费加载到每个云上。惰性读取仅拉取 `from_pretrained` 触及的内容,因此大约 30 秒即可准备就绪进行训练(速度高达 500 MB/s)。由于 Hugging Face 不收取出口流量费,这次拉取完全免费;如果模型存放在 S3 上,每次从其他云上的 GPU 读取数据都会被收取出口流量费(AWS 上为 $0.09/GB)。
- 检查点以高达约 170 MB/s 的速度直接流式传输到存储桶(每个权重文件 8.43 GB),并在 GPU 实例终止后持续保留。
每个云上,检查点写入存储桶的速度为:
| 云平台 | GPU | 检查点写入速度 |
|---|---|---|
| AWS(us-east-2) | L40S | ~168 MB/s |
| GCP(us-central1) | L4 | ~123 MB/s |
| Lambda(us-west-3) | H100 | ~112 MB/s |
基于 Xet 的存储:检查点和模型变体的去重
Hugging Face Buckets 构建于 Xet 之上,它使用内容定义的分块技术将文件分割成约 64 KB 的块,并仅存储每个唯一块一次。由于分块边界跟随内容变化,一次编辑只会更改其触及的块,其余块则被识别为已存储。这在以下几个方面带来了好处:
- 增量检查点和适配器检查点。当你冻结层、训练适配器,或在保存之间保持大部分权重不变时,只有发生变化的块会上传,而不是整个检查点。
- 共享基座的模型变体。同一基座模型的微调和量化版本之间存在大量重叠,因此共享的块在所有版本中仅存储一次。
- 你追加数据的数据集。像对话记录或推理输出这样的日志,通过向大型 Parquet 文件追加行来增长。现有的行组保持字节一致,因此只有新行会被传输:在 Hugging Face 的测试中,向一个 10 万行的表中追加 1 万行数据,仅移动了约 10 MB,而不是完整的约 106 MB。(如果你就地编辑或删除行,请使用 `use_content_defined_chunking=True` 进行写入,以保持更改的局部性。)
- 重新上传会跳过已存储的内容。在我们的测试中,重新上传一个已在存储桶中的 8.43 GB 数据块大约需要 8 秒,而首次上传则需要 24 秒,因为只移动了块的哈希值。同样的机制使得服务端的 `hf buckets cp` 命令能够在仓库和存储桶之间通过引用进行复制,而无需重新上传字节数据。
你能节省多少取决于你的工件重叠程度,但去重是自动完成的:你像往常一样写入检查点,只有新的数据块会离开机器。
pip install "skypilot[huggingface]"
hf auth login # or: export HF_TOKEN=<your-token>
向任何 SkyPilot 任务添加一个 `hf://` 挂载点并启动。MOUNT 需要一个基于 glibc 2.34+ 和 `/dev/fuse` 的基础镜像。
共同构建:Hugging Face 与 SkyPilot
最初的存储:`hf` 支持始于 Nikhil Jha 的贡献。Hugging Face 团队将其推进并向上游提交了 `hf-mount` 的 FUSE 修复,使其能够在非特权容器(许多 Kubernetes 集群的默认设置)中挂载。SkyPilot 团队将其接入存储后端。整个链路都是开源的:SkyPilot、Hugging Face 的 `hf-mount` 以及 `huggingface_hub` 客户端。
资源
- SkyPilot 存储文档
- Hugging Face 存储桶指南
- hf-mount
- Xet:基于内容的数据块切分与去重
- SkyPilot Slack 社区