Hugging Face 在 TRL 中实现 Delta Weight Sync,用 Hub Bucket 同步万亿参数权重
Shipping a Trillion Parameters With a Hub Bucket: Delta Weight Sync in TRL
Hugging Face 在 TRL 中提交 PR,用增量权重同步替代异步强化学习中的全量权重传输:两次 RL 优化步之间约 99% 的 bf16 权重字节完全一致,只把变化的元素编码成稀疏 safetensors 文件上传到 HF Bucket,再让 vLLM 拉取。
Hugging Face 把异步强化学习的权重同步从全量广播改成稀疏增量,并给出可下载运行的实现,读者可以据此判断跨机房训练的门槛变化。
TL;DR, because you have models to train and we respect that:
- 异步 RL 有一个不可告人的秘密:每一步,训练器都必须把整个模型发送到推理引擎。对于一个 bf16 的 7B 模型,那是 14 GB。对于一个前沿的 1T 模型 checkpoint,那大约是 1 TB。每一步都如此。
- 事实证明你不必这样做。在两个连续的 RL 优化器步骤之间,大约 99% 的 bf16 权重是逐位相同的(最坏情况下也从未低于 98%)。实际的增量微乎其微。
- 我们提交了一个 TRL PR,它只把发生变化的元素编码成一个稀疏 safetensors 文件,上传到一个HF Mirror Bucket,然后告诉 vLLM 去获取它。在 Qwen3-0.6B 上,每步的传输负载从 1.2 GB 降至 20 到 35 MB。
- 锦上添花的是:我们运行了一次完整的解耦训练,其中训练器在一台机器上,vLLM 运行在一个 HF Mirror Space 中,Wordle 环境运行在另一个 Space 中,权重通过一个 Hub bucket 流动。没有共享集群,没有 RDMA,没有 VPN。
异步 RL 现在便宜多了。请继续阅读。
1. 一太字节问题
如果你读过我们之前关于异步 RL 训练全景的文章,你已经知道结论了。每一个异步 RL 库,无论它把“actor model”拼写成什么样,或者它的 NCCL 后端被涂成什么颜色,最终都会栽在同一个根源上:权重同步。
推理引擎说的是第 N 步的策略。训练器刚刚完成第 N+1 步。新权重必须从一边传到另一边,否则推理引擎就会开始无可救药地偏离策略。无论你运行的是同步还是异步,这都位于关键路径上:阻塞式传输是 GPU 被浪费的空闲算力,这些 GPU 没有在生成 token。有了稀疏增量路径,你可以把这段空闲时间压缩到几秒钟,而且训练器甚至不必等待推理引擎就绪:它只需在其优化器步骤完成的那一刻发布“权重已就绪”并把权重上传到共享 bucket,推理引擎则按自己的时间安排去获取。
Fireworks 在他们的文章前沿 RL 比你想象的更便宜中给出了一个非常令人难忘的数字:对于一个前沿的 1T 参数 checkpoint 在 fp8 下(他们的设置),完整快照是 1024 GiB,而传统观念认为你每次更新 rollout 集群时都必须传输这个量。正是这种数字让人开始画起包含超大集群、RDMA 架构和专用跨区域链路的架构图。他们测得的相邻 checkpoint 之间的平均增量为 20.3 GiB,即完整模型的 1.98%,并且“bf16 格式中超过 98% 的权重在相邻 checkpoint 之间保持逐位等价”。
Cursor 的Composer 2 报告讲述了一个类似的故事。他们在不同区域运行训练和推理,并用一个共享 S3 bucket(他们的原话)把它们拼接起来,训练器每个训练步骤都会把压缩后的权重差异上传到那里。每个集群独立地从共享增量链中下载并重建,“无需与训练集群直接连接”。双方从不直接就参数进行通信。bucket 就是那根线。
两篇论文在三点上达成一致,我们想慢慢重复一遍,因为本文的其余部分本质上就是一次忠实的开源翻译:
- 在相邻的两次 RL 步骤之间,大部分权重实际上并没有改变。
- 如果你只发送发生变化的部分,你的带宽账单大约会缩减两个数量级。
- 如果你把这些微小的差异通过共享对象存储进行路由,你就不再需要让训练器和推理集群位于同一个数据中心。
唯一缺少的,是一个你可以 pip install 的版本。于是我们写了一个。
2. 为什么 bf16 RL 权重几乎总是稀疏的
在动手接线之前,值得先理解为什么这整件事是有可能成功的。“98% 的权重不变”这个说法听起来很像那种在演示中成立、在现实中却站不住脚的数字。但它不是。它是从 bf16 算术在 RL 所使用的学习率下的工作方式中自然得出的。
一个 bf16 数有 7 个尾数位。在两个相邻的 2 的幂之间,恰好有 27=1282^7 = 128 个可表示的值,因此围绕 ∣w∣|w| 的相邻 bf16 数之间的间距大致为 ∣w∣⋅2−7|w| \cdot 2^{-7}。当更新低于该间距的一半时,即当 ∣Δw∣<∣w∣/256|\Delta w| < |w|/256 时,更新就会被 bf16 类型转换吸收。这就是 PULSE 在其图 3 中绘制的“bf16 可见性阈值”。
现在看看 Adam 做了什么。在 RL 学习率例如为 3×10−63 \times 10^{-6} 时,对单个权重的更新为: Δw=−η⋅m^v^+ϵ\Delta w = -\eta \cdot \frac{\hat{m}}{\sqrt{\hat{v}} + \epsilon}
归一化步长 m^/(v^+ϵ)\hat{m}/(\sqrt{\hat{v}}+\epsilon) 大致为一阶,所以 ∣Δw∣≈η≈3×10−6|\Delta w| \approx \eta \approx 3 \times 10^{-6}。对于大多数权重,∣w∣|w| 大约在 10−210^{-2} 到 10−110^{-1} 之间(PULSE 报告有代表性的 LLM 权重的中位数为 0.019)。在该量级下,阈值 ∣w∣/256|w|/256 大约为 4×10−54 \times 10^{-5} 到 4×10−44 \times 10^{-4},这大于更新量。
换句话说:优化器在低声细语,而 bf16 听不见。更新被舍入吸收,ww 的字节表示没有改变,而从推理引擎的角度看,这个权重根本没有移动。把它乘以几亿个参数,你就免费得到了 >99% 的稀疏度,且零近似。
这正是 PULSE 论文(Mihai & Belilovsky, 2026)中正式论证的观点。他们定义了两个阈值。吸收边界 10η10\eta 是 Adam 更新的保守最坏情况,而有效边界 η\eta 则是你实际所处的区间。bf16 可见性阈值为 ∣w∣/256|w|/256。只要更新量低于可见性阈值,它就会被吸收,bf16 字节不会发生变化。他们的图 3 将这两个边界与一组具有代表性的 LLM 权重云图进行了对比,结论毫无歧义:当 η=3×10−6\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 的 mm 和 vv 统计量来预测变化掩码,但召回率只有可怜的 30%,稍后会详细讨论)。我们只需观察哪些字节发生了翻转。那就是每个参数的一个微小布尔张量,在优化器步骤前后计算得出。
3. HF Buckets 与架构
这就是故事的第二部分登场之处,也是本文从 Fireworks/Cursor 的翻译转变为 HF Mirror 相关内容的地方。
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)])
就这样。两次函数调用,你的权重就已上路。
在底层,bucket 由 Xet 支持,这是 Hub 的内容定义分块存储层。Xet 会查看你上传的每个文件,根据其实际内容(而非固定偏移量)将其切分成块,并与 bucket 中已有的所有内容进行去重。实际效果——在此语境下令人欣喜——是即使我们懒得编写稀疏编码,只是每步上传完整的锚点,Xet 也仍然只会传输发生变化的块。稀疏编码 + Xet 组合:我们只为移动的部分付费,而且只付一次。
这是 Fireworks 和 Cursor 都采用的“共享 S3 bucket”的开源等价物,只不过存储层已经了解内容哈希,你现有的 HF token 已经拥有权限,并且它能与堆栈的其他部分(Spaces、数据集、模型)原生组合。
3.2 三个盒子
完整架构恰好有三个盒子加一个共享基底:
- 训练器。 随你放在哪里。一块 GPU、八块 GPU、一台外接 USB 的 H100 的笔记本电脑,我们不会评判。它拥有模型权重,运行优化器,发出稀疏增量。
- HF Bucket。单个仓库,两个前缀:
anchors/用于偶尔的全量快照,deltas/用于其间的稀疏补丁。这是双方唯一达成一致的地方。 - vLLM rollout 服务器。部署在任何你想要的位置,而且关键在于不一定非得在训练器所在的地方。它从 bucket 拉取数据,应用增量,然后提供 rollout 服务。
- 环境。以通常的方式挂在 rollout 服务器上(HTTP、函数调用,或者你的环境所使用的任何方式)。
需要内化的特性——Cursor 的论文极力推销、在这里也完全成立的那一点:训练器和 rollout 服务器从不互相通信传递权重。它们交换一个包含 {"repo_id": ..., "filename": ...} 的极小 POST 请求,这就是整个控制平面。实际的字节传输发生在每一侧与 bucket 之间,并行进行,不共享网络结构。
为什么这在实践中很重要:
- rollout 服务器可以位于另一个区域、另一个云,或者 HF Mirror Space 内 NAT 之后。它不在乎。
- N 个推理副本可以从同一个 bucket 拉取相同的增量,Xet 会在所有这些副本之间对字节去重。
- 训练器永远不需要知道存在多少个推理副本、它们在哪里,或者其中一个是否刚刚崩溃。
训练器写入。副本读取。Hub 负责管道。
4. 协议
现在我们可以打开引擎盖了。协议包含四个部分:一种线格式、一种 bucket 布局、一个 30 行的 vLLM 扩展,以及一个训练器侧的变更检测器。说实话,代码比听起来要少。
4.1 以 Safetensors 作为线格式
我们选择了 safetensors 作为磁盘上和线上传输的格式。它已经是 Hub 上的规范检查点格式,每个像样的框架都能读取它,而且头信息携带任意字符串元数据。那个元数据字段正是我们隐藏协议的地方。
bucket 中有两类文件。
锚点看起来像正常的检查点:每个参数一个张量,完整的 bf16 权重,每 NN 次同步写入一次(我们默认 N=10N=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 步一个锚点,其间是增量。两者最终都进入同一个 bucket,分别位于 anchors/ 和 deltas/ 前缀下。每个新的推理副本只需要抓取最近的锚点,然后重放此后的增量。
4.2 训练器侧:来自优化器钩子的布尔掩码
训练器需要知道哪些 bf16 元素真正发生了翻转。我们用一个很小的 BF16ChangeDetector 来实现这一点,它在优化器上注册 pre-step 和 post-step 钩子:
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 对象),但核心思路用一张餐巾纸就能画下:快照、步进、差异。
这就是基准事实。我们尝试过更优雅的路径——直接用 bf16 ULP 阈值,从 Adam 的 mm 和 vv 统计量预测掩码。原理上可行。实践中召回率只有约 30%,这意味着我们发布的增量会遗漏三分之二的实际更新。Adam 的归一化足够混乱,使得解析阈值并不紧致。所以我们直接比较字节。代价是在训练端对模型做一次 bf16 CPU 快照,我们愿意承担这个代价。
新的 _sync_weight 流程分为四个阶段:
- 推理持续运行时上传。训练器将掩码元素编码进一个 safetensors 缓冲区,并推送到 bucket。在整个过程中,vLLM 仍在正常服务旧策略。
- 暂停 vLLM。一次短暂的 HTTP 调用,数百毫秒。
- 通知
/update_weights。发送 bucket 坐标。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 时,我们把完整的同步时间都当作暂停时间来付出。而这里,我们把它当作后台时间来付出。
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(),补丁以 (indices, values) 编码,并通过 index_copy_() 原地应用,完全消除了 GPU/CPU 验证往返。该 PR 报告称,对 Qwen3-1.7B 的稀疏补丁传输为 0.16 MB 用时 0.40 ms,而完整密集传输为 942 MB 用时 192 ms。
关于我们推理侧的实现,有一个诚实的提醒:我们保留了一份模型的 CPU bf16 快照,以便从稀疏的 (indices, values) 补丁重建完整张量,因为目前 vLLM 中的 load_weights 期望完整张量。一旦 #40096(或其后续版本)落地并暴露原地稀疏 load_weights 路径,我们就可以直接在 GPU 上应用索引并丢弃快照!
5. 在 Spaces 上真实部署
这是我们引以为傲的部分。到目前为止我们描述的一切都可以在你的笔记本电脑上运行,但通过 Hub bucket 路由权重的意义在于:训练器和 rollout 服务器不必位于彼此附近。因此我们运行了一次完全分离的训练,使用三台机器,它们之间不共享网络:
- 一台带单 GPU 的机器运行 训练器。
- 一个 HF Mirror Space(Docker SDK,L4 GPU)运行带有我们扩展类的 vLLM。
- 第二个 HF Mirror 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 推演中收敛:奖励上升,delta 载荷保持在 20 到 35 MB 区间,每次同步的推理暂停窗口保持在一秒左右。完整的运行日志链接在配套的 PR 中。
6. 那么这究竟解锁了什么?
几件事,而且我们认为它们意义重大。
无需集群的异步 RL 训练。如果你有一块 GPU 和一个 HF Mirror 账号,你现在就可以进行真正的解耦训练。你的训练器在 GPU 上;你的推演集群生活在 Spaces 中;你的环境生活在另一个 Space 中;权重通过一个存储桶流转。过去这要么需要一个同址部署(伴随由此带来的所有吞吐量妥协),要么需要一个具备共享网络的真实集群。现在不再需要了。
多副本推理,免费获得。起两个 vLLM Space,或者十个。它们都从同一个存储桶拉取。Xet 对存储进行内容寻址,因此相邻锚点在静态时共享分块(这能防止你的存储桶膨胀),而 Hub 的边缘缓存使得重复下载同一文件的成本很低。想要一个全球分布的推演集群?现在这只是一个小型 DevOps 练习,而不是一个研究项目。
一种可以用你现有工具调试的传输格式。一个 delta 就是一个 safetensors 文件。你可以在 notebook 中 safe_open 它、列出它的键、检查索引、自己计算稀疏度。我们在 tcpdump 中对着不透明的 NCCL 流花了足够多的时间,所以非常欣赏这一点。
一条通往前沿规模的路径。20 到 35 MB 这个数字是针对 Qwen3-0.6B 的。有趣的问题是,一旦你把旋钮调大,这条曲线会是什么样子。让我们做个餐巾纸上的估算。
以 Llama-3.1-405B 为例。bf16 格式下,磁盘上就是 810 GB。PULSE 在 RL 学习率下测得的平均每步稀疏度约为 99%,所以实际 delta 大约只占参数的 1%。他们部署实测的编码在 7B 模型上达到 108 MB,这正是 PULSE 报告的 约 130 倍 压缩。按线性比例放大到 405B,delta 大约落在 每步 6 GB。
这在挂钟时间上能给你带来什么?集群内部 NCCL 确实很快。假设一个慷慨的 100 GB/s 聚合广播带宽(多节点、RDMA,全套)。一次完整同步就是每一步都要暂停推理 810 GB / 100 GB/s ≈ 8 seconds。而走 delta 路径时,训练器在生成继续运行的同时在后台把 6 GB 流式传输到存储桶,推演服务器实际暂停的窗口仅仅是应用步骤,在这个规模下也就是几秒钟。所以即便在我们离开集群之前,delta 就已经把可见暂停缩短了 4 倍,把线路上的字节数减少了约 130 倍。
现在离开集群。NCCL 跨云根本无法工作。一旦你想在一个 us-east 中有一个 rollout fleet,在另一个 eu-west 中有一个,也许还有一个在 HF Mirror Space 中,基于桶的路径就是唯一的路径。在 1 GB/s 的可用互联网带宽下,一次完整广播需要 13 分钟;增量只需 6 秒。
对于 Fireworks 框架中的 1 TB 级模型,他们自己测量的数字显示 20.3 GiB 增量 vs 1024 GiB 完整快照,约 50 倍的减少。PULSE 更紧凑、稀疏的编码会进一步推动这一点(外推每个增量约 15 GB,接近约 65 倍)。无论哪种方式,你都处于这样一种状态:通过商品对象存储传输权重不再是一种 hack,而是开始成为唯一合理的架构。
7. 我们还有什么待办事项
我们并不假装这已经完成。这里是诚实的清单。
- 两个 CPU bf16 快照,多了一个。 训练器保留一个(用于变化检测器),rollout 服务器保留一个(用于为 vLLM 的
load_weights重建完整张量)。第一个我们只能一直保留,直到有人找到紧致的解析掩码,这比看起来更难。第二个会在 vLLM 获得稀疏load_weightsAPI 时消失。PR 即将到来。 - 固定锚点节奏。 我们目前每 NN 步转储一个完整锚点。自适应策略(“当累积漂移超过 X 时锚定”)会在长时间运行中降低锚点成本。
- 多节点 FSDP2 训练器。
BF16ChangeDetector是围绕每进程优化器钩子构建的。它应该能干净地泛化到 FSDP2,但我们还没有在多节点规模上测量过。PR 中有一个TODO上面有我们的名字。 - 接入优化器。 我们尝试仅从 (m,v)(m, v) 预测掩码,但召回率很低,这意味着解析 bf16 阈值在做一些比教科书公式所暗示的更微妙的事情。我们很想听听任何破解了这个问题的人的意见。
- 与线上压缩叠加。 稀疏 safetensors 和每块 gzip 是正交的。我们还没有尝试将它们结合起来。尽管我们不期望巨大的压缩收益。
8. 试一试
- PR:huggingface/trl#5417。分支是
delta-weight-sync。 - 完整的 Wordle 示例:
examples/scripts/openenv/async_wordle.py。 - Spaces Dockerfiles:
examples/scripts/openenv/vllm_space/和examples/scripts/openenv/wordle_space/。 - 背景阅读:我们的 异步 RL 格局文章、Fireworks 1 TB 文章、Cursor Composer 2 报告。
来源:Hugging Face Blog · hf-mirror.com