# 将数据迁移到存储桶

本指南演示如何将大型数据集从当前位置（本地磁盘、其他云或 Hub）迁移到 [Storage Bucket](./storage-buckets)。请在下表中找到您的数据源，并跳转到相应章节。

在可能的情况下，将数据以文件系统形式进行读取，并运行 `hf sync`。它会在客户端进行去重，因此上传的字节数少于直接复制。

## 概览

| 您的数据现在位于 | 推荐 | 替代方案 |
|---|---|---|
| 本地磁盘或集群文件系统 | [`hf sync`](#from-local-disk-or-a-cluster-filesystem) | — |
| AWS S3 或 S3 兼容（R2、MinIO） | [使用 `mount-s3` 挂载，然后运行 `hf sync`](#from-aws-s3-or-s3-compatible-storage) | [通过 S3 网关使用 rclone](#alternative-rclone-through-the-s3-gateway) |
| Google Cloud Storage | [使用 `gcsfuse` 挂载，然后运行 `hf sync`](#from-google-cloud-storage-or-azure-blob-storage) | rclone，或一个 [Job](#run-the-migration-in-the-background-with-jobs) |
| Azure Blob 存储 | [使用 `blobfuse2` 挂载，然后运行 `hf sync`](#from-google-cloud-storage-or-azure-blob-storage) | rclone，或一个 [Job](#run-the-migration-in-the-background-with-jobs) |
| Hub 仓库或其他存储桶 | [`hf buckets cp`（服务端）](#from-a-hub-repository-or-another-bucket) | — |
| 非常大的 S3 前缀 | [hf-s3ream（alpha）](#very-large-s3-prefixes-with-hf-s3ream) | 挂载 + `hf sync`，[按子路径拆分](#split-by-subpath-experimental) |

为什么 `hf sync` 是默认选项：

- **您上传的数据更少。** `hf sync` 通过 [Xet](./xet/index) 客户端进行上传，该客户端会先对数据进行分块和去重，因此只会发送 Hub 尚未见过的数据块。而原始复制（例如通过 S3 网关使用 rclone）则会传输每一个字节。
- **速度至少不慢。** 在我们目前支持的迁移中，通过挂载源使用 `hf sync` 的吞吐量与 rclone 相当或更优。

## 开始之前

本指南中的所有命令均使用 `hf`，即 [Hugging Face CLI](https://huggingface.co/docs/huggingface_hub/guides/cli)。其帮助输出可供智能体读取，因此编码智能体可以为您组装并监控迁移过程（参见 [面向 AI 智能体的 CLI](./agents-cli)）。在大规模运行之前，请审查这些命令。

1. **安装 CLI。** Xet 客户端随附提供。

   ```bash
   curl -LsSf https://hf.co/cli/install.sh | bash
   hf version
   ```

2. 使用具有 **Write** 权限的令牌 **登录**（[创建一个](https://huggingface.co/settings/tokens)）：

   ```bash
   hf auth login
   hf auth whoami
   ```

3. **创建目标存储桶。** `<namespace>` 是您的用户名或组织。如果不指定 `--region`，存储桶将创建在该命名空间的默认 [存储区域](./storage-regions)，对于 Team & Enterprise 计划以外的用户，该区域始终为美国。对于这些计划，请传入 `--region us` 或 `--region eu` 进行选择。

   ```bash
   hf buckets create <namespace>/<bucket> --private
   ```

> [!TIP]
> 先进行一次测试运行。无论采用哪种路径，都请先在一个小子集（一个文件夹，或一个 `--include` 模式）上演练，使用 `--dry-run` 和 `hf buckets ls` 检查结果，然后再启动完整的迁移。本指南中的每条路径都支持断点续传，因此测试运行不会带来任何额外成本。

## 从本地磁盘或集群文件系统

当您的数据位于本地磁盘、挂载的卷或集群临时存储（scratch）上时，请使用此方法：任何机器可以以文件形式读取的位置均可。对于下文中的每种云路径，一旦源端完成挂载，这也是最后一步。

```bash
hf sync ./data hf://buckets/<namespace>/<bucket>/data
```

`hf sync` 会对比源和目的地，仅传输差异部分，因此重新运行相同命令即可恢复中断的迁移：已上传的文件会被跳过。常见变体：

```bash
# Mirror exactly: also delete destination files that are gone from the source
hf sync ./data hf://buckets/<namespace>/<bucket>/data --delete

# Preview what would transfer, without uploading
hf sync ./data hf://buckets/<namespace>/<bucket>/data --dry-run

# Only some files
hf sync ./data hf://buckets/<namespace>/<bucket>/data --include "*.parquet" --exclude "tmp/*"
```

要在运行前审查大型迁移，请将计划写入文件，检查后再应用。这样只需扫描一次源和目的地，而不是在试运行和正式运行时各扫描一次。

```bash
hf sync ./data hf://buckets/<namespace>/<bucket>/data --plan plan.jsonl   # scan once, write the plan
hf sync --apply plan.jsonl                                                # run exactly that plan
```

请在创建计划的目录中运行 `--apply`，因为计划会记录您输入的源路径。`--apply` 不会检查已上传的内容，因此若要恢复中断的运行，请重新运行普通的 `hf sync` 命令。

要验证这一点，请检查试运行是否没有剩余待传输的内容，并抽查其内容：

```bash
hf sync ./data hf://buckets/<namespace>/<bucket>/data --dry-run
hf buckets ls hf://buckets/<namespace>/<bucket>/data
```

### 调优

- **将缓存保留在高速磁盘上。**Xet 客户端会在 `HF_HOME` 下缓存数据块和去重索引（即分片缓存）。如果您的主目录不在 NVMe 上，请将 `HF_HOME` 指向本地 NVMe。
- 在资源充足的机器上执行批量迁移时，请开启**高性能模式**。`HF_XET_HIGH_PERFORMANCE=1` 会提高并发数、缓冲区大小以及同时摄取的文件数量，使客户端能够利用所有 CPU 核心并打满网络带宽。该模式适用于高带宽、快速磁盘的机器；在使用 `hf_xet` 1.7.0 之前的版本时，请确保至少配备 64 GB 内存（可通过 `hf env` 查看当前配置）。请参阅 [环境变量](./xet/using-xet-storage#environment-variables)。
- **限制分片缓存的大小。**其大小限制是软性的：缓存仅在一次运行的开始时被修剪，因此在一次较长的运行过程中，它可能会远远超过该限制。在大规模迁移中，尤其是在高性能模式下，它可能会增长到数百 GB 并占满磁盘，从而导致运行中止。请将 `HF_XET_SHARD_CACHE_SIZE_LIMIT` 设置为明显低于您的可用磁盘空间，但不要低于您的实际需求：大小为 *X* 的缓存大约可以对 1000 × *X* 的数据进行去重。请参阅 [分片缓存](./xet/using-xet-storage#shard-cache)。

在开始同步之前设置这些参数。大小接受人类可读的值，例如 `64gb`：

```bash
export HF_HOME=/mnt/nvme/hf                 # chunk and shard caches on NVMe
export HF_XET_HIGH_PERFORMANCE=1            # use all cores and the full network
export HF_XET_SHARD_CACHE_SIZE_LIMIT=64gb   # keep the shard cache under free disk space

hf sync ./data hf://buckets/<namespace>/<bucket>/data
```

### 按子路径拆分（实验性）

对于非常大的数据集，您可以将源拆分为子路径，并同时运行多个 `hf sync` 进程。早期测试表明吞吐量大致呈线性扩展，但这需要进一步验证。两条规则：

- 按相关数据进行拆分，以便每个工作进程的各个文件之间能够良好地去重。
- 为每个 worker 设置独立的 `HF_HOME`，以避免它们争用缓存。由于每个缓存都会占用磁盘空间，请在各 worker 之间分配 `HF_XET_SHARD_CACHE_SIZE_LIMIT`。

```bash
HF_HOME=/mnt/nvme/hf-a hf sync ./data/groupA hf://buckets/<namespace>/<bucket>/groupA &
HF_HOME=/mnt/nvme/hf-b hf sync ./data/groupB hf://buckets/<namespace>/<bucket>/groupB &
wait
```

单个进程共享一个缓存，去重效果最佳，因此仅当一个进程无法充分利用网络时才进行拆分。

### 故障排除

| 症状 | 修复 |
|---|---|
| `401` 或身份验证错误 | 使用 **Write** 令牌运行 `hf auth login`，并使用 `hf auth whoami` 进行验证。 |
| 比预期更慢 | 将 `HF_HOME` 放置在 NVMe 上，设置 `HF_XET_HIGH_PERFORMANCE=1`，并[在靠近数据的位置运行](#where-to-run-a-large-migration)。 |
| 运行因磁盘已满错误而中止 | 分片缓存已占满磁盘。请将 `HF_XET_SHARD_CACHE_SIZE_LIMIT` 设置为低于您的可用空间，或将 `HF_HOME` 移至更大的磁盘。 |
| 传输已中断 | 重新运行相同的 `hf sync` 命令。已上传的文件将被跳过。 |

## 从 AWS S3 或 S3 兼容存储

`hf sync` 不支持读取 `s3://` 链接，因此请使用 [Mountpoint for Amazon S3](https://github.com/awslabs/mountpoint-s3) 将源存储桶挂载为文件系统，然后从挂载点执行同步。您的数据仍然通过 Xet 客户端传输，因此可以实现客户端去重。

```bash
mount-s3 my-source-bucket /mnt/src
hf sync /mnt/src hf://buckets/<namespace>/<bucket>
umount /mnt/src
```

对于 Cloudflare R2 或 MinIO 等兼容 S3 的存储，请在 `mount-s3` 中添加 `--endpoint-url <url>`。对于允许匿名读取的公共存储桶（例如 [AWS Open Data registry](https://registry.opendata.aws/) 中的存储桶），请添加 `--no-sign-request`。验证、调优和故障排除与 [本地数据](#from-local-disk-or-a-cluster-filesystem) 相同。

### 替代方案：通过 S3 网关使用 rclone

如果您已经在运行 rclone 且不希望使用挂载，请使用此方法。权衡之处在于：rclone 会将每个字节都通过 [S3 网关](./storage-buckets-s3) 传输，且没有客户端去重，因此传输的数据量比 `hf sync` 更多。您需要为网关提供 [S3 凭证](./storage-buckets-s3#generating-s3-credentials)。

> [!WARNING]
> 请使用 rclone 1.70 或更高版本（可通过 `rclone version` 检查，并从 [rclone downloads](https://rclone.org/downloads/) 升级）。旧版本在列出非常大的存储桶时可能会耗尽内存。旧的建议通常推荐使用 [`--fast-list`](https://rclone.org/docs/#fast-list)；使用较新版本的 rclone 时不需要该选项，而且在处理海量列表时，它会将所有内容保留在内存中，可能导致同样的故障。

在 `~/.config/rclone/rclone.conf` 中，将网关添加为远程，与源远程（如下面的 `aws`）并列：

```ini
[hf]
type = s3
provider = Other
endpoint = https://s3.hf.co/<namespace>
access_key_id = HFAK...
secret_access_key = ...
region = us-east-1
force_path_style = true
list_version = 2
upload_cutoff = 256M
chunk_size = 256M
```

网关仅支持 ListObjectsV2，因此必须设置 `list_version = 2`。使用 256M 分片时，大文件所需的分片数量远少于 rclone 默认的 5M 分片。rclone 每次传输在内存中保留 4 个分片，因此下文中的 `--transfers 16` 大约需要 16 GB 内存；在内存较小的机器上请调低该值。然后执行复制并检查：

```bash
rclone copy aws:my-source-bucket hf:<bucket> --transfers 16 --checkers 16 --progress
rclone check aws:my-source-bucket hf:<bucket>
```

`rclone copy` 仅传输缺失或已更改的对象，因此可以安全地重新运行以恢复。在进行大规模导入之前，请阅读[网关限制](./storage-buckets-s3#limitations-and-differences-from-aws-s3)，特别是对象键命名规则。

## 从 Google Cloud Storage 或 Azure Blob Storage

与 S3 相同的方法：使用其云自身的驱动程序挂载源，然后运行 `hf sync`。

**Google Cloud Storage**：使用 [gcsfuse](https://cloud.google.com/storage/docs/gcsfuse) 进行挂载。

```bash
gcsfuse my-gcs-bucket /mnt/src
hf sync /mnt/src hf://buckets/<namespace>/<bucket>
```

**Azure Blob Storage**：使用 [blobfuse2](https://learn.microsoft.com/azure/storage/blobs/blobfuse2-what-is) 进行挂载。配置文件用于指定存储账户和容器。

```bash
blobfuse2 mount /mnt/src --config-file=./blobfuse2.yaml
hf sync /mnt/src hf://buckets/<namespace>/<bucket>
```

如果没有挂载，你可以使用 rclone（它原生支持读取 GCS 和 Azure；复用[上述](#alternative-rclone-through-the-s3-gateway)的 `hf` 远程，具有相同的无去重权衡），或者[将迁移作为 Job 运行](#run-the-migration-in-the-background-with-jobs)。

## 在后台使用 Jobs 运行迁移

[Jobs](./jobs) 在 Hugging Face 的计算资源上运行脚本：你可以选择硬件，通过 CLI 启动，并且 [按分钟计费](./jobs-pricing)。当你希望长时间、多 TB 的迁移任务在无人值守的情况下运行，而不是在笔记本电脑上或需要保持打开的会话中运行时，可以使用它。

**1. 编写迁移脚本**（`migrate.py`）。此示例通过 Xet 客户端将 S3 前缀中的每个对象复制到存储桶中，因此写入操作会进行去重。它适用于一次性复制中等规模的前缀；对于非常大的前缀，请参阅 [hf-s3ream](#very-large-s3-prefixes-with-hf-s3ream)。请调整顶部的配置：

```python
# /// script
# dependencies = ["huggingface_hub", "boto3", "tqdm"]
# ///
import boto3
from botocore import UNSIGNED
from botocore.config import Config
from huggingface_hub import HfFileSystem
from tqdm.contrib.concurrent import thread_map

# --- adapt these ---
SRC_BUCKET = "my-source-bucket"
SRC_PREFIX = "some/prefix/"
DST = "buckets/<namespace>/<bucket>"
WORKERS = 8   # files copied in parallel
# -------------------

s3 = boto3.client("s3")   # AWS credentials come from the Job's secrets
# For a public bucket (for example from the AWS Open Data registry), read anonymously
# instead, and drop the AWS secrets from the launch command:
# s3 = boto3.client("s3", config=Config(signature_version=UNSIGNED))
fs = HfFileSystem()       # HF token comes from the Job's HF_TOKEN secret

# List the destination once: {path: size} of the files already copied
done = {path: info["size"] for path, info in fs.find(DST, detail=True).items()}

# List the source and keep the objects not copied yet
todo = []
pages = s3.get_paginator("list_objects_v2").paginate(Bucket=SRC_BUCKET, Prefix=SRC_PREFIX)
for page in pages:
    for obj in page.get("Contents", []):
        key = obj["Key"]
        if key.endswith("/"):
            continue  # folder marker: bucket keys can't end with "/"
        if done.get(f"{DST}/{key}") != obj["Size"]:
            todo.append(key)

def copy(key):
    body = s3.get_object(Bucket=SRC_BUCKET, Key=key)["Body"]
    with fs.open(f"{DST}/{key}", "wb") as out:
        for chunk in iter(lambda: body.read(64 << 20), b""):
            out.write(chunk)

# Copy WORKERS files at a time, with a progress bar
thread_map(copy, todo, max_workers=WORKERS)
```

- 每个对象都会暂存到 Job 的磁盘上，并在关闭时上传，因此磁盘必须能同时容纳 `WORKERS` 个最大对象。若要加速处理大量小对象，请调高 `WORKERS`；对于非常大的对象，则应调低该值。
- 桶中已存在且大小相同的文件会被跳过，因此重新运行脚本可以从中断处恢复复制操作。如果源文件内容已更改但大小保持不变，脚本不会重新复制该文件：若要使桶与不断变化的源保持同步，请改为[挂载源并运行 `hf sync`](#from-aws-s3-or-s3-compatible-storage)。

对于 GCS 或 Azure，请将 `boto3` 替换为相应云服务的 SDK（`google-cloud-storage`、`azure-storage-blob`），并调整列表和读取调用。目标端部分保持不变。

**2. 将其作为后台作业启动**，并将您的凭据作为机密信息传入：

```bash
hf jobs uv run --detach --flavor cpu-performance --timeout 24h \
  --secrets HF_TOKEN \
  --secrets AWS_ACCESS_KEY_ID \
  --secrets AWS_SECRET_ACCESS_KEY \
  migrate.py
```

每个 `--secrets NAME` 都会从您的环境中传递该变量的值（对于 `HF_TOKEN`，如果未设置，则使用您已登录的 token）。任务默认在 30 分钟后停止，因此请将 `--timeout` 设置得足够长以覆盖整个传输过程；如果任务提前停止，请重新启动它，它将跳过已复制的文件。

`--detach` 会立即返回一个 Job ID。使用 `hf jobs logs -f <job_id>` 跟踪进度，使用 `hf jobs ls` 列出运行记录，使用 `hf jobs hardware` 查看硬件选项。

## 从 Hub 仓库或其他存储桶

如果数据已经位于 Hub 上，例如在模型或数据集仓库或其他存储桶中，请在服务器端进行复制。对于仅由 [Xet](./xet/index) 跟踪的文件，只会移动内容哈希，因此复制几乎是瞬间完成的，且不会重新上传任何内容。

```bash
# From a dataset repository into a bucket
hf buckets cp hf://datasets/<org>/<dataset>/<path> hf://buckets/<namespace>/<bucket>/<path>

# From one bucket to another
hf buckets cp hf://buckets/<namespace>/<bucket-a>/<path> hf://buckets/<namespace>/<bucket-b>/<path>
```

- 源和目标必须位于同一存储区域。
- 您需要对源具有读取权限，对目标具有写入权限。
- 未被 Xet 跟踪的小型文件（如配置文件、README）会自动下载并重新上传。
- 从存储桶复制回仓库的功能目前尚不可用，但已列入开发路线图。

有关 Python 等效操作，请参阅 [在仓库和存储桶之间复制文件](./storage-buckets#copying-files-between-repos-and-buckets)。

## 使用 hf-s3ream 处理非常大的 S3 前缀

> [!WARNING]
> hf-s3ream 是一个早期（alpha）阶段的社区工具，并非 Hugging Face 官方支持的产品。在进行大规模迁移之前，请先在较小的前缀上对其进行测试。

[hf-s3ream](https://huggingface.co/spaces/glutamatt/hf-s3ream)（[GitHub 上的源代码](https://github.com/glutamatt/hf-s3ream)，Apache-2.0）通过 Web 界面执行从 S3 到存储桶的大规模复制：

1. 使用 Hugging Face 登录。
2. 输入 S3 源前缀、目标存储桶以及您的 AWS 凭证。对于允许匿名读取的公共存储桶，请开启 **Public bucket**：无需提供 AWS 凭证，Jobs 将以无签名方式读取 S3。
3. **Analyze** 会执行一次低成本的试运行，用于检查访问权限、检测源区域并推荐配置。如果目标存储桶不存在，它将创建该存储桶（私有）。
4. 点击 **Run** 可启动规划器任务。规划器会构建基于范围的复制计划，启动复制器任务，并在实时图表上显示进度。即使关闭标签页，复制操作仍会继续运行，但页面将无法重新连接到该任务。若要停止复制，请单击 **Stop copy**，或取消规划器任务（`hf jobs cancel <planner-job-id>`）：规划器会随之取消其复制器。

hf-s3ream 在您的账户下运行于 [Jobs](./jobs)，因此相关费用将计入您的账户。仅允许来自私有网络（VPC）访问的 S3 存储桶无法从 Jobs 访问。

## 在哪里运行大型迁移

对于多 TB 规模的迁移，通常会在云虚拟机上而非笔记本电脑上运行传输任务。如果您不想自行管理虚拟机，可以[将其作为 Job 运行](#run-the-migration-in-the-background-with-jobs)。

- **在与源相同的云和区域中运行。**通过本地网络读取源数据是最快且成本最低的方式，而从源云产生的出站流量通常是最主要的成本。随后，上传将通过互联网传输至 Hugging Face（美国区域位于 AWS `us-east-1`）。
- **使用单进程多线程。** 高性能模式下的单个 `hf sync` 共享同一个去重缓存。仅当单个进程无法充分利用网络带宽时，才[按子路径拆分](#split-by-subpath-experimental)。
- **保持长时间运行的任务存活。** 通过 SSH 连接时，请在 [`tmux`](https://github.com/tmux/tmux/wiki)（或 `screen`、`nohup`）中启动迁移，以确保会话断开后任务仍能继续运行：执行 `tmux new -s migrate`，按 Ctrl-b 然后按 d 分离会话，之后可通过 `tmux attach -t migrate` 重新连接。

用于在测试运行中验证的初始规模设置：

| 资源 | 指导 | AWS 示例 | GCP 示例 |
|---|---|---|---|
| 磁盘 | `HF_HOME` 使用本地 NVMe，且可用空间需高于分片缓存上限。请勿将网络附加磁盘用作缓存。 | 实例存储 NVMe（`c7i`/`m7i` `d` 变体，或 `i4i`） | `n2` 或 `c3` 虚拟机上的本地 SSD |
| CPU | 许多 vCPU：客户端在 CPU 上对数据进行分块和哈希处理。 | `c7i.8xlarge` 或更大 | `c3-standard-22` 或更大 |
| 内存 | 高性能模式的缓冲区空间充足；在 1.7.0 之前，使用 `hf_xet` 时至少需要 64 GB。 | — | — |
| 网络 | 到互联网的高带宽（25 Gbps 级别）。 | — | — |

## 后续步骤

- **就地使用数据。**使用 [hf-mount](./storage-buckets-access#mount-as-a-local-filesystem) 将存储桶挂载为本地文件系统，或作为 [Jobs 和 Spaces 中的卷](./storage-buckets-access#volume-mounts-in-jobs-and-spaces)。
- **直接从您的工具中读取。**pandas、Dask、PyArrow、Daft、DuckDB、🤗 Datasets 和 Spark 可以直接读取 `hf://buckets/...` 路径。请参阅 [Bucket 集成](./storage-buckets-integrations)。

> [!WARNING]
> 不要通过挂载点执行批量数据导入。hf-mount 会将写入操作暂存到磁盘缓存中（默认为 `/tmp/hf-mount-cache`），通过它进行大规模迁移可能会占满该磁盘。请使用上述任一方法将数据写入存储桶。如果必须通过挂载点写入大量数据，请将缓存指向一个拥有充足可用空间的磁盘：`hf-mount start --cache-dir /mnt/big bucket <namespace>/<bucket> /mnt/data`（该参数需置于 `bucket` 之前）。

