# TRL 新增 Delta Weight Sync：通过 Hub Bucket 传输权重变化，每步从 1.2 GB 降至 20-35 MB

- 来源：Hugging Face：Blog（RSS）
- 发布时间：2026-05-27 08:00
- AIHOT 分数：61
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmpo5j5lm034mslv4qv9h13v8
- 原文链接：https://huggingface.co/blog/delta-weight-sync

## 精选理由

异步RL训练中权重同步的瓶颈被HuggingFace用稀疏增量方案解决了，带宽直接省了两个数量级，还给了可运行的TRL分支，做RL训练的可以直接上手试。

## AI 摘要

异步强化学习中，训练器每步需将完整模型权重（如1T参数checkpoint约1 TB）传输给推理引擎。TRL新增PR利用相邻RL优化步骤间约99%的bf16权重比特相同的特点，仅将变化的权重编码为稀疏safetensors文件，上传至Hugging Face Bucket并通知vLLM获取。在Qwen3-0.6B上，每步传输从1.2 GB降至20–35 MB。实验还展示了完全分离的训练场景：训练器、vLLM和Wordle环境分别位于不同机器和Hugging Face Space中，权重通过单个Hub bucket流动，无需共享集群、RDMA或VPN。

## 正文

长话短说，因为您还有模型要训练，我们理解这一点：

异步强化学习有一个不为人知的秘密：每一步，训练器都必须将整个模型传输到推理引擎。对于一个 7B 参数、bf16 精度的模型，这相当于 14 GB。对于一个前沿的 1T 参数模型检查点，每次传输的数据量大约在 TB 级别。每一步都是如此。

事实证明，你并不需要这样做。在两个连续的强化学习优化器步骤之间，大约 99% 的 bf16 权重是比特完全相同的（在最坏情况下也从未低于 98%）。实际的差异非常微小。

我们提交了一个 TRL 拉取请求，它仅将发生变化的元素编码为一个稀疏的 safetensors 文件，上传到 Hugging Face 存储桶，并通知 vLLM 去获取它。在 Qwen3-0.6B 模型上，每一步的传输负载从 1.2 GB 降到了 20 到 35 MB。

锦上添花的是：我们运行了一个完全分离的训练流程，其中训练器在一台机器上，vLLM 运行在一个 Hugging Face Space 中，Wordle 环境运行在另一个 Space 中，而权重则通过一个单一的 Hub 存储桶流动。无需共享集群，无需 RDMA，无需 VPN。

异步强化学习变得便宜多了。请继续阅读。

传输相同权重的两种方式。红色部分表示没有生成任何 token 的挂钟时间。

1. 一 TB 的问题

如果你读过我们之前关于异步强化学习训练格局的文章，你已经知道关键点了。每一个异步强化学习库，无论它如何拼写“actor 模型”，或者它的 NCCL 后端是什么颜色，最终都会遇到同一个根源问题：权重同步。

推理引擎使用的是第 N 步的策略。训练器刚刚完成了第 N+1 步。新的权重必须在推理引擎开始危险地偏离策略之前，从一端传输到另一端。无论你运行的是同步还是异步模式，这都处于关键路径上：一次阻塞式传输意味着 GPU 在浪费空闲算力，没有生成任何 token。通过稀疏增量路径，你可以将这段空闲时间压缩到几秒钟，而且训练器甚至不需要等待推理引擎准备好：它只需在优化器步骤完成的那一刻发布“权重已就绪”并上传权重到共享存储桶，而推理引擎则可以在自己的时间线内获取。

Fireworks 在其文章《前沿强化学习比你想象的更便宜》中给出了一个令人印象深刻的数字：对于一个 fp8 格式的前沿 1T 参数检查点，完整快照大小为 1024 GiB，而传统观念认为，每次更新你的推理集群时都必须传输这个完整快照。正是这样的数字，让人们开始绘制包含超级集群、RDMA 网络和专用跨区域链路的架构图。他们测量到的相邻检查点之间的平均差值为 20.3 GiB，仅占完整模型的 1.98%，并且“超过 98% 的 bf16 格式权重在连续检查点之间保持比特级一致”。

Cursor 的 Composer 2 报告讲述了一个类似的故事。他们在不同区域运行训练和推理，并通过一个共享的 S3 存储桶（他们的原话）将它们连接起来，训练器会在每个训练步骤将压缩后的权重差异上传到该存储桶。每个集群独立地从共享的差异链中下载并重建模型，“无需与训练集群建立直接连接”。双方从不直接通信参数信息。这个存储桶就是连接它们的“线”。

两篇论文在三点上达成共识，我们想慢慢重复这几点，因为本文的其余部分本质上是对这些共识的忠实开源翻译：

在相邻的两个强化学习步骤之间，大部分权重实际上并未发生变化。

如果你只发送发生变化的部分，你的带宽成本将大约降低两个数量级。

如果你通过一个共享对象存储来路由这些微小的差异数据，那么训练器和推理集群就不再需要位于同一个数据中心。

唯一缺少的是一个可以通过 `pip install` 安装的版本。于是我们编写了一个。

2. 为什么 bf16 强化学习权重几乎总是稀疏的

在我们连接任何东西之前，有必要理解为什么整个策略是可行的。“98% 的权重不变”这个说法听起来可疑，像是那种在演示中有效、但在实际应用中就会失效的数字。但事实并非如此。这是由 bf16 算术在强化学习所使用的学习率下的工作方式所决定的。

一个 bf16 数有 7 位尾数。在两个相邻的 2 的幂之间，恰好有 $2^{7} = 128$ 个可表示的值，因此在 $\mid w \mid$ 附近相邻 bf16 数之间的间隔大约为 $\mid w \mid \cdot 2^{- 7}$。当一次更新量低于该间隔的一半时，即当 $\mid \Delta w \mid < \mid w \mid / 256$ 时，该更新就会被 bf16 的数值转换所吸收。这就是 PULSE 在其图 3 中绘制的“bf16 可见性阈值”。

现在来看看 Adam 的做法。在强化学习的学习率设为，比如 $3 \times 10^{- 6}$ 时，对单个权重的更新为：$\Delta w = - \eta \cdot \frac{\hat{m}}{\sqrt{\hat{v}} + \epsilon}$

归一化后的步长 $\hat{m} / \left(\right. \sqrt{\hat{v}} + \epsilon \left.\right)$ 大致为 1 的量级，因此 $\mid \Delta w \mid \approx \eta \approx 3 \times 10^{- 6}$。对于大多数权重来说，$\mid w \mid$ 大约在 $10^{- 2}$ 到 $10^{- 1}$ 之间（PULSE 报告称，代表性大语言模型权重的中间值为 0.019）。在该量级下，阈值 $\mid w \mid / 256$ 大约在 $4 \times 10^{- 5}$ 到 $4 \times 10^{- 4}$ 之间，这比更新量要大。

换句话说：优化器在低声细语，而 bf16 听不到。更新被舍入操作所吸收，$w$ 的字节表示没有变化，从推理引擎的角度来看，这个权重根本没有移动。将这种情况乘以数亿个参数，你就得到了大于 99% 的稀疏度数值，无需任何近似，完全免费。

这正是 PULSE 论文（Mihai & Belilovsky, 2026）中正式提出的论点。他们定义了两个阈值。吸收边界 $10 \eta$ 是 Adam 更新的保守最坏情况，而有效边界 $\eta$ 则是实际所处的区间。bf16 可见性阈值是 $\mid w \mid / 256$。每当更新低于可见性阈值时，它就会被吸收，bf16 字节不会发生变化。他们的图 3 将这两个边界与代表性的大语言模型权重云图进行了对比，结论是明确的：在 $\eta = 3 \times 10^{-6}$ 时，吸收边界本身已经低于模型中几乎所有权重的可见性阈值。他们通过 Qwen2.5（0.5B/1.5B/7B）、Llama-3.2-3B 和 Gemma-3-4B 进行了实证测量，一致发现平均每步稀疏度约为 99%，在 400 个训练步上的标准差为 0.2% 到 0.4%。最坏情况下的步数也保持在 98% 以上。因此，<1% 的变化并非幸运的测量结果，而是算术运算所保证的。

我们不需要通过分析来预测这一点（实际上，我们尝试过从 Adam 的 $m$ 和 $v$ 统计量预测变化掩码，但召回率低至可怜的 30%，稍后会详细说明）。我们只需要观察哪些字节发生了翻转。对于每个参数来说，这是一个微小的布尔张量，在优化器步骤附近计算得出。

将学习率拖到强化学习领域，观察回退到 bf16 的标记如何跳回原始刻度。左下角的 256 元素网格显示了在一个小型模型上的总体效果。

3. HF Buckets 与架构

这就是故事的第二部分登场的地方，也是本文从 Fireworks/Cursor 的翻译转变为 Hugging Face 相关内容的地方。

3.1 什么是 Bucket？

Bucket 是 Hub 上的一种仓库类型，专为高频对象存储而设计。没有提交仪式，没有 PR 工作流，也没有 LFS 的古怪问题。你可以添加文件、列出文件、下载文件。其 Python 接口包含两个函数：

from huggingface_hub import batch_bucket_files, download_bucket_files

# Trainer side batch_bucket_files("my-org/wordle-deltas", add=[(buffer, "deltas/step_000042.safetensors")])

# Inference side download_bucket_files("my-org/wordle-deltas", files=[("deltas/step_000042.safetensors", local_path)])

仅此而已。两个函数调用，你的权重就开始传输了。

在底层，存储桶由 Xet 支撑，这是 Hub 基于内容分块的存储层。Xet 会检查你上传的每个文件，根据其实际内容（而非固定偏移量）将其切分成多个块，并与存储桶中已有的所有内容进行去重。实际效果——在此场景下非常令人欣喜——是即使我们懒得编写稀疏编码，每一步都直接上传完整的锚点，Xet 也只会传输发生变化的块。稀疏编码 + Xet 技术栈：我们只为发生变化的内容付费，而且只付一次。

这相当于 Fireworks 和 Cursor 所使用的“共享 S3 存储桶”的开源版本，区别在于该存储层已经了解内容哈希，你现有的 HF token 已具备权限，并且它能与栈中的其他部分（Spaces、数据集、模型）原生组合。

3.2 三个盒子

完整架构恰好包含三个盒子和一个共享基础层：

训练器。放在你想要的任何地方。一块 GPU、八块 GPU、一台通过 USB 连接 H100 的笔记本电脑，我们都不会评判。它拥有模型权重，运行优化器，并输出稀疏增量。

HF 存储桶。一个单一的仓库，包含两个前缀：`anchors/` 用于偶尔的完整快照，`deltas/` 用于期间的稀疏补丁。这是双方唯一达成一致的部分。

vLLM 部署服务器。放在你想要的任何地方，关键是未必与训练器在同一位置。它从存储桶拉取数据，应用增量，并提供部署服务。

环境。以常规方式（HTTP、函数调用，或你的环境支持的任何方式）挂载在部署服务器上。

需要内化的特性——也是 Cursor 那篇论文极力推崇、在此处完全适用的——是训练器和部署服务器之间从不直接通信权重。它们交换一个微小的 POST 请求，包含 `{"repo_id": ..., "filename": ...}`，这就是整个控制平面。实际的字节传输发生在每一方与存储桶之间，并行进行，无需共享网络结构。

为什么这在实践中很重要：

部署服务器可以位于另一个区域、另一个云，或者 Hugging Face Space 内部的 NAT 之后。它完全不在意。

N 个推理副本可以从同一个存储桶拉取同一个增量，而 Xet 会在所有副本之间对字节进行去重。

训练器永远无需知道存在多少个推理副本，它们位于何处，或者其中某个副本是否刚刚崩溃。

训练器负责写入。副本负责读取。Hub 负责管道连接。

4. 协议

现在我们可以打开引擎盖了。该协议包含四个部分：一种线格式、一种存储桶布局、一个 30 行的 vLLM 扩展，以及一个训练器端的变更检测器。老实说，它的代码量比听起来要少。

4.1 Safetensors 作为线格式

我们选择了 safetensors 作为磁盘和网络传输格式。它已经是 Hub 上标准的检查点格式，所有主流的框架都能读取它，并且其头部可以携带任意的字符串元数据。我们正是利用这个元数据字段来隐藏协议。

存储桶中有两种类型的文件。

锚点文件看起来像一个正常的检查点：每个参数对应一个张量，完整的 bf16 权重，每 $N$N 次同步写入一次（我们默认 $N = 10$N=10）。

anchors/step_000010.safetensors ├── model.layers.0.self_attn.q_proj.weight (bf16, full) ├── model.layers.0.self_attn.k_proj.weight (bf16, full) └── ... metadata: sparse=False, model_version=10, sparsity=0.0

增量文件是其中的关键。对于每个实际发生变化的参数，我们存储两个条目：一个平坦的 int32 张量，包含元素索引；以及一个 bf16 张量，包含这些索引位置的值。

deltas/step_000011.safetensors ├── model.layers.0.self_attn.q_proj.weight.indices (int32, [num_changed]) ├── model.layers.0.self_attn.q_proj.weight.values (bf16, [num_changed]) ├── model.layers.0.mlp.gate_proj.weight.indices ├── model.layers.0.mlp.gate_proj.weight.values └── ... metadata: sparse=True, model_version=11, sparsity=0.9938, changed_params=[...]

这种选择带来的一些好处：

增量文件就是一个文件。你可以在 Python 中用 `safe_open(...)` 打开它，并检查其中的每一个张量。没有专有的帧格式，没有长度前缀，没有版本握手。

元数据是自描述的。接收方读取 `sparse=True/False` 并据此分支处理。不需要单独的清单文件。

在推理端，它通过 mmap 实现零拷贝，当你每隔几秒就要执行一次此操作时，这一点至关重要。

节奏很直接：每第 N 步写入一个锚点文件，中间步骤写入增量文件。两者都存放在同一个存储桶中，分别位于 `anchors/` 和 `deltas/` 前缀下。每个新的推理副本只需获取最新的锚点文件，然后重放此后的增量文件即可。

十个训练步骤。在第 1 步和第 6 步写入锚点文件（完整快照），在其他每一步写入稀疏增量文件。你可以实时看到文件落入存储桶中。

4.2 训练器端：来自优化器钩子的布尔掩码

训练器需要知道哪些 bf16 元素实际发生了翻转。我们通过一个微型的 `BF16ChangeDetector` 来实现这一点，它在优化器上注册了一个步骤前和步骤后的钩子：

class BF16ChangeDetector: def __init__(self, model, optimizer): self._pre_step_bf16: dict[str, torch.Tensor] = {} self._validated_masks: dict[str, torch.Tensor] = {} optimizer.register_step_pre_hook(self._pre_step_hook) optimizer.register_step_post_hook(self._post_step_hook)

def _pre_step_hook(self, opt, args, kwargs): for p in self._params: self._pre_step_bf16[name_of(p)] = p.detach().to(torch.bfloat16).cpu().clone()

def _post_step_hook(self, opt, args, kwargs): for p in self._params: self._validated_masks[name_of(p)] = ( p.detach().to(torch.bfloat16).cpu() != self._pre_step_bf16[name_of(p)] )

PR 中的实际代码有更多底层细节（通过 `data_ptr()` 将优化器参数对象与模型参数进行匹配，因为 Accelerate 将它们包装成了不同的 Python 对象），但核心思路简单明了：快照、更新、求差。

这是最可靠的方法。我们尝试过更优雅的路径——利用 Adam 的 $m$ 和 $v$ 统计量来预测掩码，并直接使用 bf16 ULP 阈值。这种方法在原理上可行，但在实际中召回率仅为 30% 左右，这意味着我们最终交付的增量更新会遗漏三分之二的实际更新。Adam 的归一化过程相当复杂，导致分析阈值并不精确。因此我们直接比较字节。代价是在训练端对模型进行一次 bf16 CPU 快照，这个成本我们愿意承担。

新的 `_sync_weight` 流程包含四个阶段：

推理持续运行的同时进行上传。训练器将掩码后的元素编码到 safetensors 缓冲区中，并推送到存储桶。在整个步骤中，vLLM 仍在愉快地使用旧策略提供服务。

暂停 vLLM。一次短暂的 HTTP 调用，耗时数百毫秒。

发送 `/update_weights` 信号。传递存储桶坐标。vLLM 下载、应用更新并返回响应。

恢复运行。vLLM 重新上线。

日志行清晰地说明了情况：

Delta: 1234567/200000000 elements changed (sparsity=99.38%) [delta_engine] uploaded user/wordle-deltas/deltas/step_000042.safetensors (27.4 MB, ...) Weight sync: done. Total 9.4s (inference paused 1.1s)

关键信息在括号中。推理暂停了 1.1 秒。剩余的 9.4 秒用于上传，而这段时间内 rollout 服务器仍在生成 token。使用 NCCL 时，整个同步时间都算作暂停时间。在这里，我们将其转化为后台时间。

一次端到端的同步。在 delta-over-bucket 和 NCCL 广播之间切换，并尝试切换副本数量以观察扇出效果。

4.3 vLLM 侧：一个 30 行的扩展

vLLM 为此提供了一个清晰的抽象，名为 WeightTransferEngine。我们实现了一个 DeltaWeightTransferEngine，其 `receive_weights` 方法的核心逻辑如下：

def receive_weights(self, update_info, load_weights): download_bucket_files(update_info.repo_id, files=[(update_info.filename, local_path)]) with safe_open(local_path, framework="pt", device="cpu") as f: meta = PatchMetadata.from_metadata_dict(f.metadata()) if not meta.sparse: # Anchor: feed every tensor and snapshot for future deltas for name in f.keys(): tensor = f.get_tensor(name) self._bf16_snapshot[name] = tensor.clone() load_weights([(name, tensor)]) else: # Delta: apply (indices, values) to snapshot, hand full tensor to vLLM for name in json.loads(meta.changed_params): indices = f.get_tensor(f"{name}.indices").long() values = f.get_tensor(f"{name}.values") snap = self._bf16_snapshot[name].flatten() snap[indices] = values self._bf16_snapshot[name] = snap.reshape(self._bf16_snapshot[name].shape) load_weights([(name, self._bf16_snapshot[name])])

我们通过 vLLM 的 `--worker-extension-cls` 标志注册它，这意味着无需 fork vLLM。你只需将 TRL 安装到与 vLLM 相同的镜像中，将 CLI 指向我们的类即可完成。

值得一提的是：vLLM 自身正在推进一项将稀疏权重传输原生落地的开发工作，即 vllm-project/vllm#40096。该方案在 WeightTransferEngine 基类上直接添加了 receive_sparse_weights() 和 trainer_send_sparse_weights() 方法，补丁以（索引，数值）形式编码，并通过 index_copy_() 原地应用，完全消除了 GPU/CPU 之间的验证往返。该 PR 报告显示，在 Qwen3-1.7B 上传输一个稀疏补丁仅需 0.40 毫秒传输 0.16 MB，而完整稠密传输则需要 192 毫秒传输 942 MB。

我们在推理侧实现中有一个诚实的注意事项：我们保留了一份模型的 CPU bf16 快照，以便能够从稀疏（索引，数值）补丁中重建完整张量，因为当前 vLLM 中的 load_weights 期望接收完整张量。一旦 #40096（或其后续版本）落地并暴露出一个原地稀疏 load_weights 路径，我们就可以直接在 GPU 上应用索引，并丢弃这份快照！

5. 在 Spaces 上真正部署运行

这是让我们颇为得意的一部分。到目前为止我们描述的所有内容都可以在笔记本电脑上运行，但通过 Hub 存储桶路由权重的意义在于，训练器和部署服务器不必位于彼此附近。因此，我们使用三台机器运行了一次完全分离的训练，这三台机器之间不共享网络：

一台配备单 GPU 的机器运行训练器。

一个 Hugging Face Space（Docker SDK，L4 GPU）运行带有我们扩展类的 vLLM。

第二个 Hugging Face Space（CPU）运行 Wordle 环境服务器，支持 256 个并发会话容量。

中间的一个 Hub 存储桶。

设置这一切实际上只需要几个 hf CLI 命令。vLLM Space 的 Dockerfile 本质上就是上游 vLLM 镜像加上 pip install trl@... 再加上入口点：

FROM vllm/vllm-openai:latest RUN pip install "trl @ git+https://github.com/huggingface/trl.git@delta-weight-sync" ENV VLLM_SERVER_DEV_MODE=1 EXPOSE 7860 ENTRYPOINT ["vllm", "serve", "Qwen/Qwen3-1.7B", \ "--host", "0.0.0.0", "--port", "7860", \ "--worker-extension-cls", "trl.experimental.async_grpo.delta_engine.DeltaWorkerExtension", \ "--weight-transfer-config", "{\"backend\":\"nccl\"}", \ "--max-model-len", "32768", \ "--gpu-memory-utilization", "0.8"]

将其部署为一个 Space：

hf repos create $USER/vllm-wordle-inference \ --type space --space-sdk docker --flavor l4x1 \ --secrets HF_TOKEN=$HF_TOKEN hf upload $USER/vllm-wordle-inference examples/scripts/openenv/vllm_space/ --type space

然后从地球上任何能够进行 HTTPS 通信的地方启动训练：

python examples/scripts/openenv/async_wordle.py \ --vllm-server-url https://$USER-vllm-wordle-inference.hf.space \ --env-url https://openenv-wordle.hf.space \ --delta-sync-repo-id $USER/wordle-deltas \ --model Qwen/Qwen3-1.7B

训练器从不开放端口。Space 从未看到训练器的 IP 地址。Wordle 环境不知道它们两者中的任何一个存在。它们都通过 Hub 进行通信。训练在即时 EOS 合理性检查上收敛，然后在真实的 Wordle 部署上收敛：奖励上升，增量负载保持在 20 到 35 MB 区间，每次同步的推理暂停窗口大约为一秒。完整的运行日志链接在随附的 PR 中。

6. 那么这究竟解锁了哪些能力？

有几项，我们认为意义重大。

无需集群即可进行异步强化学习训练。如果你拥有一块 GPU 和一个 Hugging Face 账号，现在就可以实现真正的解耦训练。你的训练器运行在 GPU 上；你的 rollout 集群部署在 Spaces 中；你的环境运行在另一个 Space 里；模型权重通过一个存储桶传输。过去这需要要么是共置部署（会带来所有吞吐量上的妥协），要么是配备共享网络的真实集群。现在不再需要了。

免费的多副本推理。启动两个 vLLM Space，或者十个。它们都从同一个存储桶拉取数据。Xet 采用内容寻址存储，因此连续的锚点在存储时会共享数据块（这能防止你的存储桶无限膨胀），而 Hub 的边缘缓存使得重复下载同一文件变得成本低廉。想要一个全球分布的 rollout 集群？现在这只是一个小型的 DevOps 任务，而不是一个研究项目。

一种可以用现有工具调试的传输格式。一个 delta 文件就是 safetensors 格式。你可以从 notebook 中通过 safe_open 打开它，列出其键名，检查索引，自行计算稀疏度。我们已经在晦涩的 NCCL 流上用 tcpdump 耗费了足够多的时间，因此深知这一点的价值。

一条通往前沿规模的路径。20 到 35 MB 这个数字是针对 Qwen3-0.6B 的。有趣的问题是，当你把参数调大时，曲线会是什么样子。让我们来做一下粗略估算。

以 Llama-3.1-405B 为例。在 bf16 精度下，它在磁盘上占用 810 GB。PULSE 在强化学习学习率下测得平均每步稀疏度为 99%，因此实际的 delta 大约只占参数的 1%。他们在部署中测得的编码在 7B 模型上达到了 108 MB，这正是 PULSE 报告的 **130 倍** 压缩率。按比例线性放大到 405B，每步的 delta 大约为 6 GB。

这在实际时间上能带来什么好处？NCCL 在集群内部确实很快。假设一个慷慨的 100 GB/s 聚合广播带宽（多节点、RDMA，一应俱全）。一次完整同步需要 810 GB / 100 GB/s ≈ 8 秒的推理暂停，每一步都如此。采用增量路径后，训练器在后台将 6 GB 数据流式传输到存储桶，同时生成过程持续运行，而部署服务器的实际暂停窗口仅在于应用步骤，在此规模下只需几秒钟。因此，即使不离开集群，增量方法也能将可见暂停减少 4 倍，并将网络传输字节减少约 130 倍。

现在离开集群。NCCL 完全无法跨云工作。一旦你希望在美东部署一个推理集群，在欧西部署另一个，甚至可能在一个 Hugging Face Space 中再部署一个，基于存储桶的路径就成了唯一路径。在 1 GB/s 的可用互联网带宽下，一次完整的广播需要 13 分钟；而增量方法只需 6 秒。

对于 Fireworks 框架中 1 TB 级别的模型，他们自己的测量数据显示，增量数据为 20.3 GiB，而完整快照为 1024 GiB，减少了约 50 倍。PULSE 更紧凑的稀疏编码将进一步推动这一优势（推算每个增量约 15 GB，接近 65 倍）。无论哪种方式，你都进入了一个通过通用对象存储传输权重不再是权宜之计，而是唯一合理架构的阶段。

7. 我们仍在处理的问题

我们并不假装这已经完成。以下是诚实的清单。

两份 CPU bf16 快照，多了一份。训练器保留一份（用于变化检测器），部署服务器保留一份（用于为 vLLM 的 load_weights 重建完整张量）。第一份我们暂时无法摆脱，直到有人找到一种紧凑的分析掩码，这比看起来要难。第二份将在 vLLM 获得稀疏 load_weights API 后消失。PR 即将提交。

固定的锚点频率。我们目前每 $N$N 步转储一次完整锚点。自适应策略（“当累积漂移超过 X 时设置锚点”）将在长时间运行中降低锚点成本。

多节点 FSDP2 训练器。BF16ChangeDetector 是围绕每个进程的优化器钩子构建的。它应该能干净地推广到 FSDP2，但我们尚未在多节点规模上进行测量。PR 中有一个 TODO，上面写着我们的名字。

接入优化器。我们仅从 (m, v) 预测掩码的尝试召回率很低，这意味着分析性 bf16 阈值比教科书公式所暗示的要更精妙。我们非常希望听到任何已破解此问题的人的意见。

叠加传输压缩。稀疏 safetensors 和按块 gzip 是正交的。我们尚未尝试将它们结合使用，尽管我们并不期望有巨大的压缩增益。

8. 试试看

该 PR：huggingface/trl#5417。分支名为 delta-weight-sync。

完整的 Wordle 示例：examples/scripts/openenv/async_wordle.py。

Spaces 的 Dockerfile：examples/scripts/openenv/vllm_space/ 和 examples/scripts/openenv/wordle_space/。

背景阅读：我们的异步强化学习全景文章、Fireworks 1 TB 文章、Cursor Composer 2 报告。
