对大多数团队而言,模型和数据集都存放在某个云服务商某个区域的一个存储桶里。而你能获取到的 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 社区
本文提及的模型
For most teams, models and datasets live in a bucket in one region of one cloud. The GPUs you can get, whether for development, training, or serving, increasingly sit on a different cloud than your data. The moment those two come apart, you pay a cross-cloud transfer tax just to read your own data onto your own GPUs. Together with Hugging Face, we've joined the two halves: your models and datasets stay on the Hub, and SkyPilot runs the compute (dev, training, or serving) on whatever cluster has the GPUs. Mount a Hugging Face Bucket or any Hub repo into a SkyPilot job with one hf:// URL and the HF_TOKEN you already have, then launch it wherever capacity is. Hugging Face charges no egress, so reading your data onto those GPUs costs nothing, on any cloud.
Here's what's new:
- Your Hub data in any job.
store: hfmounts a Hugging Face Bucket (read-write) or any model / dataset / Space repo (read-only) into a SkyPilot task with onehf://URL and your existingHF_TOKEN, viaMOUNTorCOPY. - Run it on any GPU, on any cloud.SkyPilot finds that job compute across 20+ clouds, Kubernetes, Slurm, and on-prem, so the same run uses whichever of your reserved or on-demand GPUs is available, on any vendor.
- No egress to read your data. Hugging Face Storage charges no egress or CDN fees, so wherever SkyPilot lands the job, it reads your models and datasets straight from the same bucket, with no per-cloud copies and no egress bill to pull them in.
- Xet-backed dedup. Buckets are built on Xet, so incremental checkpoints and model variants only store and transfer the chunks that changed.
- Built together.Hugging Face and SkyPilot shipped this jointly, and the Hugging Face team upstreamed the
hf-mountFUSE fixes that make it work in unprivileged containers.
Hugging Face Storage is now a first-class SkyPilot backend
SkyPilot tasks already read and write cloud object stores (S3, GCS, Azure, R2, and many more) by mounting them at a local path. Hugging Face Storage now joins that list as store: hf, reached through the hf:// scheme:
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
That one hf:// scheme covers the whole lifecycle: read the model and dataset from their repos, write checkpoints to a Bucket while you train, publish the finished model back to a repo, and pull it onto inference servers when you serve. Most teams already keep their models and datasets on the Hub, so there is no migration step and no new storage account to create.
MOUNT uses Hugging Face's hf-mount FUSE backend, so a bucket or repo shows up as a local path next to SkyPilot's other FUSE mounts (gcsfuse, blobfuse2, rclone, goofys). The fetching happens at the filesystem layer: when your code issues a read(), the driver pulls just those bytes from the Xet backend, so only the data you actually touch crosses the network, and hf-mount keeps an on-disk cache so repeat reads stay local. That on-disk cache is the behavior SkyPilot gives its other backends under MOUNT_CACHED, where a plain MOUNT instead streams every read from the bucket with nothing kept locally. For the hf store, MOUNT and MOUNT_CACHED behave the same, so either mode keeps the cache.
Because reads are lazy, a process can start working through a large file before the whole file has downloaded, instead of blocking on a full copy first. That keeps the GPU busy almost immediately, training on data as it streams in rather than sitting idle (and billing) while a dataset or checkpoint copies down. It pays off most on the first epoch, when nothing is cached yet. COPY takes the other route and downloads through huggingface_hub up front, with no special requirements.
Authentication is the token you already have. Set HF_TOKEN in your environment and hand it to a run with --secret HF_TOKEN; SkyPilot uses it for the mount on whatever cloud the job lands. One token works whether the job lands on AWS, GCP, Azure, Nebius, Lambda, or your own Kubernetes cluster, so there are no per-cloud bucket keys to juggle.
No egress: storage stops deciding where you run
GPU capacity rarely comes from one place anymore. To get enough H100s and H200s, teams hold reserved and committed capacity across several vendors at once (a block on a hyperscaler, a cluster on a neocloud, maybe an on-prem rack) and run wherever they have allocation. SkyPilot is built for this: one job spec, scheduled across 20+ clouds, Kubernetes, and on-prem, landing on whichever reserved cluster is free.
Object storage has been the catch. Object stores are regional and per-cloud, so feeding a GPU or an inference server that sits in a different vendor's data center means either keeping a copy of your data in every vendor's bucket or paying to pull it across. Most clouds charge egress (around $0.09/GB out of AWS) the moment data leaves their network, and often between regions inside one cloud. Pulling a base model onto every inference node, or iterating a dataset for several epochs from a cluster on another cloud, adds a hefty bill on top of GPUs you have already reserved. Teams end up pinning each run to whichever vendor holds the data and leaving the rest of their capacity idle.
Hugging Face Storage takes that cost off the table where it bites: the read side. With no egress or CDN fees and storage at $12-18/TB/month (versus AWS S3 at roughly $23/TB plus egress), the same bucket is reachable from every one of those clusters, and reading from it is free no matter where the GPUs run. Writing back still costs your compute cloud's usual egress, the same as it would to any off-cloud store, but for most AI work the reads dominate: a dataset streamed over many epochs, or model weights pulled onto every new training or inference node. So you stop pinning each run to whichever vendor holds a copy of the data.
A quick benchmark
To collect some benchmark numbers, we ran a small fine-tune: Qwen/Qwen3.5-4B on the HuggingFaceH4/Multilingual-Thinking dataset with TRL's SFTTrainer, mounting the model read-only from its Hub repo and writing every checkpoint to a Hugging Face Bucket. The same SkyPilot YAML ran on AWS, GCP, and Lambda, changing only --infra. SkyPilot placed each job wherever GPUs were free, and all three read and wrote the same bucket.
# 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
What we measured:
- The model loaded free on every cloud. Lazy reads pull only what
from_pretrainedtouches, so it was ready to train in about 30 seconds (up to500 MB/s). Because Hugging Face charges no egress, that pull cost nothing; had the model lived in S3, every read to a GPU on another cloud would have been billed egress ($0.09/GB on AWS). - Checkpoints streamed straight to the bucket at up to ~170 MB/s (8.43 GB of weights each) and persisted past the GPU instance.
Per cloud, checkpoints wrote to the bucket at:
| Cloud | GPU | Checkpoint write |
|---|---|---|
| AWS (us-east-2) | L40S | ~168 MB/s |
| GCP (us-central1) | L4 | ~123 MB/s |
| Lambda (us-west-3) | H100 | ~112 MB/s |
Xet-backed storage: dedup for checkpoints and model variants
Hugging Face Buckets are built on Xet, which uses content-defined chunking to split files into ~64 KB chunks and store each unique chunk once. Because the boundaries follow the content, an edit changes only the chunks it touches and the rest are recognized as already stored. This pays off in a few places:
- Incremental and adapter checkpoints. When you freeze layers, train adapters, or otherwise leave most weights untouched between saves, only the changed chunks upload instead of the whole checkpoint.
- Model variants that share a base. Fine-tunes and quantizations of one base model overlap heavily, so the shared chunks are stored once across all of them.
- Datasets you append to. Logs like conversation traces or inference outputs grow by appending rows to large Parquet files. The existing row groups stay byte-identical, so only the new rows transfer: in Hugging Face's test, appending 10K rows to a 100K-row table moved about 10 MB instead of the full ~106 MB. (If you edit or delete rows in place, write with
use_content_defined_chunking=Trueto keep changes local.) - Re-uploads skip what's already stored. In our test, re-uploading an 8.43 GB blob already in the bucket took about 8 seconds, versus 24 seconds for the first upload, because only chunk hashes move. The same mechanism lets server-side
hf buckets cpbetween repos and buckets copy by reference instead of re-uploading bytes.
How much you save depends on how much your artifacts overlap, but the deduplication is automatic: you write a checkpoint as usual, and only the new chunks leave the machine.
pip install "skypilot[huggingface]"
hf auth login # or: export HF_TOKEN=<your-token>
Add an hf:// mount to any SkyPilot task and launch. MOUNT needs a base image with glibc 2.34+ and /dev/fuse.
Built together: Hugging Face and SkyPilot
The initial store: hf support started as a contribution from Nikhil Jha. The Hugging Face team carried it forward and upstreamed the hf-mount FUSE fixes that let it mount in unprivileged containers, the default on many Kubernetes clusters. The SkyPilot team wired it into the storage backend. The whole path is open source: SkyPilot, Hugging Face's hf-mount, and the huggingface_hub client.
Resources
- SkyPilot storage docs
- Hugging Face Storage Buckets guide
hf-mount- Xet: content-defined chunking and deduplication
- SkyPilot Slack community